From 97f551c43b25c2d3657f0ce09323d516b2bca37d Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 31 Oct 2023 09:18:20 +0000 Subject: [PATCH] Added WebFX mechanism for events consumed by JavaFX that yet need to be propagated to the peer --- .../src/main/java/javafx/event/Event.java | 36 +++++++++++++++++-- .../behavior/TextInputControlBehavior.java | 18 ++++++++++ .../gwt/shared/HtmlSvgNodePeer.java | 19 +++++++--- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/webfx-kit/webfx-kit-javafxbase-emul/src/main/java/javafx/event/Event.java b/webfx-kit/webfx-kit-javafxbase-emul/src/main/java/javafx/event/Event.java index 724e240241..72369865c3 100644 --- a/webfx-kit/webfx-kit-javafxbase-emul/src/main/java/javafx/event/Event.java +++ b/webfx-kit/webfx-kit-javafxbase-emul/src/main/java/javafx/event/Event.java @@ -25,11 +25,11 @@ package javafx.event; -import java.util.EventObject; - import com.sun.javafx.event.EventUtil; import javafx.beans.NamedArg; +import java.util.EventObject; + // PENDING_DOC_REVIEW /** * Base class for FX events. Each FX event has associated an event source, @@ -184,4 +184,36 @@ public static void fireEvent(EventTarget eventTarget, Event event) { EventUtil.fireEvent(eventTarget, event); } + + // WebFX addition: + + // Sometimes, events are managed by the Node peer on its own, but should also be consumed by JavaFX. This is this + // special case that we are handling here. + + // For example, a TextInputControl can be mapped to a HTML element, and this element can consume and handle + // the key events on its own (text typing, arrow navigation, etc...), but in general in WebFX, all events are first + // passed to JavaFX, and it's only if no JavaFX handler consumed the event, that they are passed back to the browser + // event handling (and eventually in this way to the peer). So we could think that the solution to guarantee the + // event propagation to the peer would be to just not consume the key events in TextInputControl. However, this may + // not always work, because if the TextInputControl is inside a TabPane for example, then the TabPane will consume + // some key events like the arrow keys to handle the keyboard tabs navigation, and therefore these key events + // consumed by TabPane won't be passed to the peer (so the user won't be able to navigate using the arrow keys in + // the element). To fix this issue, the TextInputControl actually must consume the key events (like it would + // do in OpenJFX), to stop their propagation to the TabPane. This is actually done in TextInputControlBehavior, but + // then we have the issue that the default WebFX behaviour is to not pass the key events back to the browser (and + // therefore to the peer). So we need to bypass that default behaviour in that case, and ask WebFX to pass the event + // back to the browser even if it has been consumed by JavaFX. This is the purpose of the propagateToPeerEvent field. + private static Event propagateToPeerEvent; + + // This setter can be called by the control (or behaviour) that consumed the event in JavaFX to request WebFX to + // not stop its propagation, but pass it to the peer. + public static void setPropagateToPeerEvent(Event propagateToPeerEvent) { + Event.propagateToPeerEvent = propagateToPeerEvent; + } + + // The getter will be used by WebFX to check this request. + public static Event getPropagateToPeerEvent() { + return propagateToPeerEvent; + } + } diff --git a/webfx-kit/webfx-kit-javafxcontrols-emul/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java b/webfx-kit/webfx-kit-javafxcontrols-emul/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java index 46424a8763..c75c5107af 100644 --- a/webfx-kit/webfx-kit-javafxcontrols-emul/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java +++ b/webfx-kit/webfx-kit-javafxcontrols-emul/src/main/java/com/sun/javafx/scene/control/behavior/TextInputControlBehavior.java @@ -1,6 +1,8 @@ package com.sun.javafx.scene.control.behavior; +import javafx.event.Event; import javafx.scene.control.TextInputControl; +import javafx.scene.input.KeyEvent; import java.util.List; @@ -21,6 +23,22 @@ public abstract class TextInputControlBehavior exten */ public TextInputControlBehavior(T textInputControl, List bindings) { super(textInputControl, bindings); + // Although the key events are entirely managed by the peer, we consume them in JavaFX to not propagate these + // events to further JavaFX controls. + textInputControl.addEventHandler(KeyEvent.ANY, e -> { + if (textInputControl.isFocused()) { + // Exception is made for accelerators such as Enter or ESC, as they should be passed beyond this control + switch (e.getCode()) { + case ENTER: + case ESCAPE: + return; + } + // Otherwise, we stop the propagation in JavaFX + e.consume(); + // But we still ask WebFX to propagate them to the peer. + Event.setPropagateToPeerEvent(e); // See WebFX comments on Event class for more explanation. + } + }); } } \ No newline at end of file diff --git a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/shared/HtmlSvgNodePeer.java b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/shared/HtmlSvgNodePeer.java index a40d020762..e76ad60965 100644 --- a/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/shared/HtmlSvgNodePeer.java +++ b/webfx-kit/webfx-kit-javafxgraphics-peers-gwt/src/main/java/dev/webfx/kit/mapper/peers/javafxgraphics/gwt/shared/HtmlSvgNodePeer.java @@ -220,7 +220,18 @@ private void callFxDragEventHandler(Event evt, EventType dragEventTyp /************************************* End of "Drag & drop support" section ***************************************/ public static boolean passOnToFx(javafx.event.EventTarget eventTarget, javafx.event.Event fxEvent) { - return isFxEventConsumed(EventUtil.fireEvent(eventTarget, fxEvent)); + // Ensuring that propagateToPeerEvent is reset to null before passing the event to JavaFX + javafx.event.Event.setPropagateToPeerEvent(null); // see Event comments for more explanation + // Passing the event to JavaFX and checking if it has been consumed by a JavaFX event handler + boolean stopPropagation = isFxEventConsumed(EventUtil.fireEvent(eventTarget, fxEvent)); + // The value returned by this method indicates if we should stop the propagation of the event, or not (if not, + // it will be passed to the default browser event handling). By default, we stop the propagation of any event + // consumed by JavaFX. However, in some cases (see Event comments), a control can ask to bypass this default + // behaviour and to not stop the propagation of an event that it consumed, but pass it back to the browser and + // therefore eventually to the peer. + if (javafx.event.Event.getPropagateToPeerEvent() != null) + stopPropagation = false; + return stopPropagation; } private static boolean isFxEventConsumed(javafx.event.Event fxEvent) { @@ -350,8 +361,8 @@ private void installScrollListeners() { ScrollEvent fxEvent = new ScrollEvent(node, node, ScrollEvent.SCROLL, we.pageX, we.pageY, we.screenX, we.screenY, we.shiftKey, we.ctrlKey, we.altKey, we.metaKey, true,false, we.deltaX, we.deltaY, we.deltaX, we.deltaY, ScrollEvent.HorizontalTextScrollUnits.NONE, 0, ScrollEvent.VerticalTextScrollUnits.NONE, 0, 0, new PickResult(node, we.pageX, we.pageY)); - boolean fxConsumed = passOnToFx(node, fxEvent); - if (fxConsumed) { + boolean stopPropagation = passOnToFx(node, fxEvent); + if (stopPropagation) { e.stopPropagation(); e.preventDefault(); } @@ -598,7 +609,7 @@ protected final void applyClipPathToClipNodes() { // Should be called when this N thisClip = getNode(); for (Iterator it = clipNodes.iterator(); it.hasNext(); ) { Node clipNode = it.next(); - if (clipNode.getClip() == thisClip) // double checking we are still the clip + if (clipNode.getClip() == thisClip) // double-checking we are still the clip applyClipPathToClipNode(clipNode); else // Otherwise we remove that node from the clip nodes it.remove();