package org.openautonomousconnection.luascript.fx;
import org.openautonomousconnection.luascript.events.UiEventRegistry;
import org.openautonomousconnection.luascript.hosts.EventHost;
import org.openautonomousconnection.luascript.runtime.LuaEventRouter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* EventHost implementation for JavaFX WebView using W3C DOM EventTarget listeners.
*
*
No JavaScript required. Events are forwarded to Lua via {@link LuaEventRouter}.
*/
public final class FxEventHost implements EventHost {
private static final String GLOBAL_TARGET_ID = "__global__";
private final FxDomHost dom;
private volatile LuaEventRouter router;
private final ConcurrentHashMap elementListeners = new ConcurrentHashMap<>();
private final ConcurrentHashMap globalListeners = new ConcurrentHashMap<>();
private final ExecutorService dispatchExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "oac-fx-event-dispatch");
t.setDaemon(true);
return t;
});
/**
* Creates a new FxEventHost.
*
* @param dom fx dom host
*/
public FxEventHost(FxDomHost dom) {
this.dom = Objects.requireNonNull(dom, "dom");
}
/**
* Sets the active Lua router for this host.
*
* @param router lua router
*/
public void setRouter(LuaEventRouter router) {
this.router = Objects.requireNonNull(router, "router");
}
@Override
public void addListener(String elementId, String eventName) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(eventName, "eventName");
String evt = UiEventRegistry.normalize(eventName);
String key = elementId + "\n" + evt;
elementListeners.computeIfAbsent(key, k -> {
EventListener listener = ev -> {
Map payload = FxEventPayloadExtractor.extract(ev);
dispatchExecutor.execute(() -> emitSafely(elementId, evt, payload));
};
FxThreadBridge.runAndWait(() -> {
Element el = dom.byId(elementId);
((EventTarget) el).addEventListener(evt, listener, false);
});
return listener;
});
}
@Override
public void removeListener(String elementId, String eventName) {
Objects.requireNonNull(elementId, "elementId");
Objects.requireNonNull(eventName, "eventName");
String evt = UiEventRegistry.normalize(eventName);
String key = elementId + "\n" + evt;
EventListener listener = elementListeners.remove(key);
if (listener == null) return;
FxThreadBridge.runAndWait(() -> {
Element el = dom.byId(elementId);
((EventTarget) el).removeEventListener(evt, listener, false);
});
}
@Override
public void addGlobalListener(String eventName) {
Objects.requireNonNull(eventName, "eventName");
String evt = UiEventRegistry.normalize(eventName);
globalListeners.computeIfAbsent(evt, k -> {
EventListener listener = ev -> {
Map payload = FxEventPayloadExtractor.extract(ev);
dispatchExecutor.execute(() -> emitSafely(GLOBAL_TARGET_ID, evt, payload));
};
FxThreadBridge.runAndWait(() -> {
Document doc = dom.requireDocument();
((EventTarget) doc).addEventListener(evt, listener, false);
});
return listener;
});
}
@Override
public void removeGlobalListener(String eventName) {
Objects.requireNonNull(eventName, "eventName");
String evt = UiEventRegistry.normalize(eventName);
EventListener listener = globalListeners.remove(evt);
if (listener == null) return;
FxThreadBridge.runAndWait(() -> {
Document doc = dom.requireDocument();
((EventTarget) doc).removeEventListener(evt, listener, false);
});
}
private void emitSafely(String elementId, String eventName, Map payload) {
try {
LuaEventRouter activeRouter = router;
if (activeRouter == null) {
System.err.println("[oac.event] Dropping '" + eventName + "' for '" + elementId + "': router not initialized");
return;
}
activeRouter.emit(elementId, eventName, payload);
} catch (RuntimeException ex) {
System.err.println("[oac.event] Failed to dispatch '" + eventName + "' for '" + elementId + "': " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
}