< prev index next >

modules/javafx.swt/src/main/java/javafx/embed/swt/FXCanvas.java

Print this page
rev 10032 : 8143596: Ensure FXCanvas properly forwards SWT gesture events to its embedded scene.
Summary: Ensured SWT magnify, rotate, pan, and swipe events are properly forwarded to the embedded scene.
Reviewed-by: azvegint

*** 45,54 **** --- 45,55 ---- import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Set; + import java.util.Stack; import java.util.concurrent.CountDownLatch; import javafx.application.Platform; import javafx.beans.NamedArg; import javafx.scene.Scene;
*** 75,84 **** --- 76,86 ---- import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; + import org.eclipse.swt.events.GestureEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener;
*** 462,476 **** } else { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); } }); addListener(SWT.MouseVerticalWheel, e -> { ! FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL); }); addListener(SWT.MouseHorizontalWheel, e -> { ! FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL); }); addMouseTrackListener(new MouseTrackListener() { @Override public void mouseEnter(MouseEvent me) { --- 464,486 ---- } else { FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_MOVED); } }); + // SWT emulates mouse events from PAN gesture events. + // We need to suppress them while a gesture is active or inertia events are still processed. addListener(SWT.MouseVerticalWheel, e -> { ! if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) { ! FXCanvas.this.sendScrollEventToFX(AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL, ! 0, SWTEvents.getWheelRotation(e), e.x, e.y, e.stateMask, false); ! } }); addListener(SWT.MouseHorizontalWheel, e -> { ! if (!gestureActive && (!panGestureInertiaActive || lastGestureEvent == null || e.time != lastGestureEvent.time)) { ! FXCanvas.this.sendScrollEventToFX(AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL, ! SWTEvents.getWheelRotation(e), 0, e.x, e.y, e.stateMask, false); ! } }); addMouseTrackListener(new MouseTrackListener() { @Override public void mouseEnter(MouseEvent me) {
*** 518,527 **** --- 528,541 ---- public void keyReleased(KeyEvent e) { FXCanvas.this.sendKeyEventToFX(e, SWT.KeyUp); } }); + addGestureListener(ge -> { + FXCanvas.this.sendGestureEventToFX(ge); + }); + addMenuDetectListener(e -> { Runnable r = () -> { if (isDisposed()) return; FXCanvas.this.sendMenuEventToFX(e); };
*** 678,701 **** los.x, los.y, shift, control, alt, meta, false); // RT-32990: popup trigger not implemented } ! private void sendScrollEventToFX(Event event, int type){ if (scenePeer == null) { return; } ! Point los = toDisplay(event.x, event.y); scenePeer.scrollEvent(type, ! (type == AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0, ! (type == AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0, ! event.x, event.y, los.x, los.y, ! (event.stateMask & SWT.SHIFT) != 0, ! (event.stateMask & SWT.CONTROL) != 0, ! (event.stateMask & SWT.ALT) != 0, ! (event.stateMask & SWT.COMMAND) != 0); } private void sendKeyEventToFX(final KeyEvent e, int type) { if (scenePeer == null /*|| !isFxEnabled()*/) { return; --- 692,751 ---- los.x, los.y, shift, control, alt, meta, false); // RT-32990: popup trigger not implemented } ! double totalScrollX = 0; ! double totalScrollY = 0; ! private void sendScrollEventToFX(int type, double scrollX, double scrollY, int x, int y, int stateMask, boolean inertia) { if (scenePeer == null) { return; } ! ! double multiplier = 5.0; ! if (type == AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL || type == AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL) { ! // granularity for mouse wheel scroll events is more coarse-grained than for pan gesture events ! multiplier = 40.0; ! ! // mouse wheel scroll events do not belong to a gesture, ! // so total scroll is not accumulated ! totalScrollX = scrollX; ! totalScrollY = scrollY; ! } else { ! // up to and including SWT 4.5, direction was inverted for pan gestures on the Mac ! // (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=481331) ! if ("cocoa".equals(SWT.getPlatform()) && SWT.getVersion() < 4600) { ! multiplier *= -1.0; ! } ! ! if (type == AbstractEvents.SCROLLEVENT_STARTED) { ! totalScrollX = 0; ! totalScrollY = 0; ! } else if (inertia) { ! // inertia events do not belong to the gesture, ! // thus total scroll is not accumulated ! totalScrollX = scrollX; ! totalScrollY = scrollY; ! } else { ! // accumulate total scroll as long as the gesture occurs ! totalScrollX += scrollX; ! totalScrollY += scrollY; ! } ! } ! ! Point los = toDisplay(x, y); scenePeer.scrollEvent(type, ! scrollX, scrollY, ! totalScrollX, totalScrollY, ! multiplier, multiplier, ! x, y, los.x, los.y, ! (stateMask & SWT.SHIFT) != 0, ! (stateMask & SWT.CONTROL) != 0, ! (stateMask & SWT.ALT) != 0, ! (stateMask & SWT.COMMAND) != 0, ! inertia); } private void sendKeyEventToFX(final KeyEvent e, int type) { if (scenePeer == null /*|| !isFxEnabled()*/) { return;
*** 724,733 **** --- 774,968 ---- e.keyCode, chars, SWTEvents.keyModifiersToEmbedKeyModifiers(stateMask)); } } + // true in between begin and end events of a (compound) gesture (not including inertia events) + private boolean gestureActive = false; + // true while inertia events of a pan gesture might be processed + private boolean panGestureInertiaActive = false; + // the last gesture event that was received (may also be an inertia event) + private GestureEvent lastGestureEvent; + // used to keep track of which (atomic) gestures are enclosed + private Stack<Integer> nestedGestures = new Stack<>(); + // data used to compute inertia values for pan gesture events (as SWT does not provide these) + private long inertiaTime = 0; + private double inertiaXScroll = 0.0; + private double inertiaYScroll = 0.0; + private void sendGestureEventToFX(GestureEvent gestureEvent) { + if (scenePeer == null) { + return; + } + + // An SWT gesture may be compound, comprising several MAGNIFY, PAN, and ROTATE events, which are enclosed by a + // generic BEGIN and END event (while SWIPE events occur without being enclosed). + // In JavaFX, such a compound gesture is represented through (possibly nested) atomic gestures, which all + // (again excluding swipe) have their specific START and FINISH events. + // While a complex SWT gesture is active, we therefore have to generate START events for atomic gestures as + // needed, finishing them all when the compound SWT gesture ends (in the reverse order they were started), + // after which we still process inertia events (that only seem to occur for PAN). SWIPE events may simply be + // forwarded. + switch (gestureEvent.detail) { + case SWT.GESTURE_BEGIN: + // a (complex) gesture has started + gestureActive = true; + // we are within an active gesture, so no inertia processing now + panGestureInertiaActive = false; + break; + case SWT.GESTURE_MAGNIFY: + // emulate the start of an atomic gesture + if (gestureActive && !nestedGestures.contains(SWT.GESTURE_MAGNIFY)) { + sendZoomEventToFX(AbstractEvents.ZOOMEVENT_STARTED, gestureEvent); + nestedGestures.push(SWT.GESTURE_MAGNIFY); + } + sendZoomEventToFX(AbstractEvents.ZOOMEVENT_ZOOM, gestureEvent); + break; + case SWT.GESTURE_PAN: + // emulate the start of an atomic gesture + if (gestureActive && !nestedGestures.contains(SWT.GESTURE_PAN)) { + sendScrollEventToFX(AbstractEvents.SCROLLEVENT_STARTED, gestureEvent.xDirection, gestureEvent.yDirection, + gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false); + nestedGestures.push(SWT.GESTURE_PAN); + } + + // SWT does not flag inertia events and does not allow to distinguish emulated PAN gesture events + // (resulting from mouse wheel interaction) from native ones (resulting from touch device interaction); + // as it will always send both, mouse wheel as well as PAN gesture events when using the touch device or + // the mouse wheel, we can identify native PAN gesture inertia events only based on their temporal relationship + // to the preceding gesture event. + if(panGestureInertiaActive && gestureEvent.time > lastGestureEvent.time + 250) { + panGestureInertiaActive = false; + } + + if(gestureActive || panGestureInertiaActive) { + double xDirection = gestureEvent.xDirection; + double yDirection = gestureEvent.yDirection; + + if (panGestureInertiaActive) { + // calculate inertia values for scrollX and scrollY, as SWT (at least on MacOSX) provides zero values + if (xDirection == 0 && yDirection == 0) { + double delta = Math.max(0.0, Math.min(1.0, (gestureEvent.time - inertiaTime) / 1500.0)); + xDirection = (1.0 - delta) * inertiaXScroll; + yDirection = (1.0 - delta) * inertiaYScroll; + } + } + + sendScrollEventToFX(AbstractEvents.SCROLLEVENT_SCROLL, xDirection, yDirection, + gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, panGestureInertiaActive); + } + break; + case SWT.GESTURE_ROTATE: + // emulate the start of an atomic gesture + if(gestureActive && !nestedGestures.contains(SWT.GESTURE_ROTATE)) { + sendRotateEventToFX(AbstractEvents.ROTATEEVENT_STARTED, gestureEvent); + nestedGestures.push(SWT.GESTURE_ROTATE); + } + sendRotateEventToFX(AbstractEvents.ROTATEEVENT_ROTATE, gestureEvent); + break; + case SWT.GESTURE_SWIPE: + sendSwipeEventToFX(gestureEvent); + break; + case SWT.GESTURE_END: + // finish atomic gesture(s) in reverse order of their start; SWIPE may be ignored, + // as JavaFX (like SWT) does not recognize it as a gesture + while (!nestedGestures.isEmpty()) { + switch (nestedGestures.pop()) { + case SWT.GESTURE_MAGNIFY: + sendZoomEventToFX(AbstractEvents.ZOOMEVENT_FINISHED, gestureEvent); + break; + case SWT.GESTURE_PAN: + sendScrollEventToFX(AbstractEvents.SCROLLEVENT_FINISHED, gestureEvent.xDirection, gestureEvent.yDirection, + gestureEvent.x, gestureEvent.y, gestureEvent.stateMask, false); + // use the scroll values of the preceding scroll event to compute values for inertia events + inertiaXScroll = lastGestureEvent.xDirection; + inertiaYScroll = lastGestureEvent.yDirection; + inertiaTime = gestureEvent.time; + // from now on, inertia events may occur + panGestureInertiaActive = true; + break; + case SWT.GESTURE_ROTATE: + sendRotateEventToFX(AbstractEvents.ROTATEEVENT_FINISHED, gestureEvent); + break; + } + } + // compound SWT gesture has ended + gestureActive = false; + break; + default: + // ignore + } + // keep track of currently received gesture event; this is needed to identify inertia events + lastGestureEvent = gestureEvent; + } + + // used to compute zoom deltas, which are not provided by SWT + private double lastTotalZoom = 0.0; + private void sendZoomEventToFX(int type, GestureEvent gestureEvent) { + Point los = toDisplay(gestureEvent.x, gestureEvent.y); + + double totalZoom = gestureEvent.magnification; + if (type == AbstractEvents.ZOOMEVENT_STARTED) { + // ensure first event does not provide any zoom yet + totalZoom = lastTotalZoom = 1.0; + } else if (type == AbstractEvents.ZOOMEVENT_FINISHED) { + // SWT uses 0.0 for final event, while JavaFX still provides a (total) zoom value + totalZoom = lastTotalZoom; + } + double zoom = type == AbstractEvents.ZOOMEVENT_FINISHED ? 1.0 : totalZoom / lastTotalZoom; + lastTotalZoom = totalZoom; + + scenePeer.zoomEvent(type, zoom, totalZoom, + gestureEvent.x, gestureEvent.y, los.x, los.y, + (gestureEvent.stateMask & SWT.SHIFT) != 0, + (gestureEvent.stateMask & SWT.CONTROL) != 0, + (gestureEvent.stateMask & SWT.ALT) != 0, + (gestureEvent.stateMask & SWT.COMMAND) != 0, + !gestureActive); + } + + private double lastTotalAngle = 0.0; + private void sendRotateEventToFX(int type, GestureEvent gestureEvent) { + Point los = toDisplay(gestureEvent.x, gestureEvent.y); + + double totalAngle = gestureEvent.rotation; + if (type == AbstractEvents.ROTATEEVENT_STARTED) { + totalAngle = lastTotalAngle = 0.0; + } else if (type == AbstractEvents.ROTATEEVENT_FINISHED) { + // SWT uses 0.0 for final event, while JavaFX still provides a (total) rotation value + totalAngle = lastTotalAngle; + } + double angle = type == AbstractEvents.ROTATEEVENT_FINISHED ? 0.0 : totalAngle - lastTotalAngle; + lastTotalAngle = totalAngle; + + scenePeer.rotateEvent(type, angle, totalAngle, + gestureEvent.x, gestureEvent.y, los.x, los.y, + (gestureEvent.stateMask & SWT.SHIFT) != 0, + (gestureEvent.stateMask & SWT.CONTROL) != 0, + (gestureEvent.stateMask & SWT.ALT) != 0, + (gestureEvent.stateMask & SWT.COMMAND) != 0, + !gestureActive); + } + + private void sendSwipeEventToFX(GestureEvent gestureEvent) { + Point los = toDisplay(gestureEvent.x, gestureEvent.y); + int type = -1; + if(gestureEvent.yDirection > 0) { + type = AbstractEvents.SWIPEEVENT_DOWN; + } else if(gestureEvent.yDirection < 0) { + type = AbstractEvents.SWIPEEVENT_UP; + } else if(gestureEvent.xDirection > 0) { + type = AbstractEvents.SWIPEEVENT_RIGHT; + } else if(gestureEvent.xDirection < 0) { + type = AbstractEvents.SWIPEEVENT_LEFT; + } + scenePeer.swipeEvent(type, gestureEvent.x, gestureEvent.y, los.x, los.y, + (gestureEvent.stateMask & SWT.SHIFT) != 0, + (gestureEvent.stateMask & SWT.CONTROL) != 0, + (gestureEvent.stateMask & SWT.ALT) != 0, + (gestureEvent.stateMask & SWT.COMMAND) != 0); + } + private void sendMenuEventToFX(MenuDetectEvent me) { if (scenePeer == null /*|| !isFxEnabled()*/) { return; } Point pt = toControl(me.x, me.y);
< prev index next >