Skip to content

Commit

Permalink
Added WebFX mechanism for events consumed by JavaFX that yet need to …
Browse files Browse the repository at this point in the history
…be propagated to the peer
  • Loading branch information
salmonb committed Oct 31, 2023
1 parent feaedb0 commit 97f551c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 <input> 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 <input> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -21,6 +23,22 @@ public abstract class TextInputControlBehavior<T extends TextInputControl> exten
*/
public TextInputControlBehavior(T textInputControl, List<KeyBinding> 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.
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,18 @@ private void callFxDragEventHandler(Event evt, EventType<DragEvent> 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) {
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -598,7 +609,7 @@ protected final void applyClipPathToClipNodes() { // Should be called when this
N thisClip = getNode();
for (Iterator<Node> 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();
Expand Down

0 comments on commit 97f551c

Please sign in to comment.