From bc72e232a4b74c2be8c91c540a7b6153bfefb8c0 Mon Sep 17 00:00:00 2001
From: Sven Gothel
+ * Criteria related to parameters:
+ *
*
* @param index Position where the listener will be inserted.
@@ -572,10 +571,61 @@ public interface Window extends NativeWindow, WindowClosingProtocol {
*/
void addMouseListener(int index, MouseListener l);
+ /**
+ * Removes the given {@link MouseListener} from the list.
+ */
void removeMouseListener(MouseListener l);
+ /**
+ * Returns the {@link MouseListener} from the list at the given index.
+ */
MouseListener getMouseListener(int index);
+ /**
+ * Returns all {@link MouseListener}
+ */
MouseListener[] getMouseListeners();
-
+
+ /** Enable or disable default {@link GestureHandler}. Default is enabled. */
+ void setDefaultGesturesEnabled(boolean enable);
+ /** Return true if default {@link GestureHandler} are enabled. */
+ boolean areDefaultGesturesEnabled();
+ /**
+ * Appends the given {@link GestureHandler} to the end of the list.
+ */
+ void addGestureHandler(GestureHandler gh);
+ /**
+ * Inserts the given {@link GestureHandler} at the
+ * specified position in the list.
+ *
+ * @param index Position where the listener will be inserted.
+ * Should be within (0 <= index && index <= size()).
+ * An index value of -1 is interpreted as the end of the list, size().
+ * @param l The listener object to be inserted
+ * @throws IndexOutOfBoundsException If the index is not within (0 <= index && index <= size()), or -1
+ */
+ void addGestureHandler(int index, GestureHandler gh);
+ /**
+ * Removes the given {@link GestureHandler} from the list.
+ */
+ void removeGestureHandler(GestureHandler gh);
+ /**
+ * Appends the given {@link GestureHandler.GestureListener} to the end of the list.
+ */
+ void addGestureListener(GestureHandler.GestureListener gl);
+ /**
+ * Inserts the given {@link GestureHandler.GestureListener} at the
+ * specified position in the list.
+ *
+ * @param index Position where the listener will be inserted.
+ * Should be within (0 <= index && index <= size()).
+ * An index value of -1 is interpreted as the end of the list, size().
+ * @param l The listener object to be inserted
+ * @throws IndexOutOfBoundsException If the index is not within (0 <= index && index <= size()), or -1
+ */
+ void addGestureListener(int index, GestureHandler.GestureListener gl);
+ /**
+ * Removes the given {@link GestureHandler.GestureListener} from the list.
+ */
+ void removeGestureListener(GestureHandler.GestureListener gl);
}
diff --git a/src/newt/classes/com/jogamp/newt/event/DoubleTapScrollGesture.java b/src/newt/classes/com/jogamp/newt/event/DoubleTapScrollGesture.java
new file mode 100644
index 000000000..bc67cbee6
--- /dev/null
+++ b/src/newt/classes/com/jogamp/newt/event/DoubleTapScrollGesture.java
@@ -0,0 +1,346 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.newt.event;
+
+import jogamp.newt.Debug;
+
+/**
+ * 2 pointer scroll/rotate gesture handler processing {@link MouseEvent}s
+ * while producing {@link MouseEvent#EVENT_MOUSE_WHEEL_MOVED} events if gesture is completed.
+ *
+ * - doubleTapSlop (scaled in pixels):
+ * - Max 2 finger distance to start 'scroll' mode
+ * - Max. distance diff of current 2-pointer middle and initiated 2-pointer middle.
+ *
+ * - touchSlop (scaled in pixels):
+ * - Min. movement w/ 2 pointer within ScaledDoubleTapSlop starting 'scroll' mode
+ *
+ * - Avoid computation if not within gesture, especially for MOVE/DRAG
+ *
+ * - Only allow gesture to start with PRESS
+ *
+ * - Leave gesture completely with RELEASE of both/all fingers, or dist-diff exceeds doubleTapSlop
+ *
+ * - Tolerate temporary lift 1 of 2 pointer
+ *
+ * - Always validate pointer-id
+ *
+ *
+ *
from | to | action |
---|---|---|
NONE | 1PRESS | 1-pointer-pressed |
1PRESS | 2PRESS_T | 2-pointer-pressed within doubleTapSlope |
2PRESS_T | SCROLL | 2-pointer dragged, dist-diff within doubleTapSlop and scrollLen >= scrollSlop |
2PRESS_C | SCROLL | 2-pointer dragged, dist-diff within doubleTapSlop |
SCROLL | SCROLL | 2-pointer dragged, dist-diff within doubleTapSlop |
+ * {@link #isWithinGesture()} returns gestureState >= 2PRESS_C + *
+ */ +public class DoubleTapScrollGesture implements GestureHandler { + /** Scroll threshold in pixels (fallback), defaults to 16 pixels. Can be overriden by integer propertynewt.event.scroll_slop_pixel
.*/
+ public static final int SCROLL_SLOP_PIXEL;
+ /** Two pointer 'double tap' slop in pixels (fallback), defaults to 104 pixels. Can be overriden by integer property newt.event.double_tap_slop_pixel
.*/
+ public static final int DOUBLE_TAP_SLOP_PIXEL;
+
+ /** Scroll threshold in millimeter, defaults to 3 mm. Can be overriden by integer property newt.event.scroll_slop_mm
.*/
+ public static final float SCROLL_SLOP_MM;
+ /** Two pointer 'double tap' slop in millimeter, defaults to 20 mm. Can be overriden by integer property newt.event.double_tap_slop_mm
.*/
+ public static final float DOUBLE_TAP_SLOP_MM;
+
+ static {
+ Debug.initSingleton();
+
+ SCROLL_SLOP_PIXEL = Debug.getIntProperty("newt.event.scroll_slop_pixel", true, 16);
+ DOUBLE_TAP_SLOP_PIXEL = Debug.getIntProperty("newt.event.double_tap_slop_pixel", true, 104);
+ SCROLL_SLOP_MM = Debug.getIntProperty("newt.event.scroll_slop_mm", true, 3);
+ DOUBLE_TAP_SLOP_MM = Debug.getIntProperty("newt.event.double_tap_slop_mm", true, 20);
+ }
+
+ private static final int ST_NONE = 0;
+ private static final int ST_1PRESS = 1;
+ private static final int ST_2PRESS_T = 2;
+ private static final int ST_2PRESS_C = 3;
+ private static final int ST_SCROLL = 4;
+
+ private final int scrollSlop, scrollSlopSquare, doubleTapSlop, doubleTapSlopSquare;
+ private final float[] scrollDistance = new float[] { 0f, 0f };
+ private int[] pIds = new int[] { -1, -1 };
+ /** See class docu */
+ private int gestureState;
+ private int sqStartDist;
+ private int lastX, lastY;
+ private int pointerDownCount;
+ private MouseEvent hitGestureEvent;
+
+ private static final int getSquareDistance(float x1, float y1, float x2, float y2) {
+ final int deltaX = (int) x1 - (int) x2;
+ final int deltaY = (int) y1 - (int) y2;
+ return deltaX * deltaX + deltaY * deltaY;
+ }
+
+ private int gesturePointers(final MouseEvent e, final int excludeIndex) {
+ int j = 0;
+ for(int i=e.getPointerCount()-1; i>=0; i--) {
+ if( excludeIndex != i ) {
+ final int id = e.getPointerId(i);
+ if( pIds[0] == id || pIds[1] == id ) {
+ j++;
+ }
+ }
+ }
+ return j;
+ }
+
+ /**
+ * scaledScrollSlop < scaledDoubleTapSlop
+ * @param scaledScrollSlop Distance a pointer can wander before we think the user is scrolling in pixels.
+ * @param scaledDoubleTapSlop Distance in pixels between the first touch and second touch to still be considered a double tap.
+ */
+ public DoubleTapScrollGesture(int scaledScrollSlop, int scaledDoubleTapSlop) {
+ scrollSlop = scaledScrollSlop;
+ scrollSlopSquare = scaledScrollSlop * scaledScrollSlop;
+ doubleTapSlop = scaledDoubleTapSlop;
+ doubleTapSlopSquare = scaledDoubleTapSlop * scaledDoubleTapSlop;
+ pointerDownCount = 0;
+ clear(true);
+ if(DEBUG) {
+ System.err.println("DoubleTapScroll scrollSlop (scaled) "+scrollSlop);
+ System.err.println("DoubleTapScroll doubleTapSlop (scaled) "+doubleTapSlop);
+ }
+ }
+
+ public String toString() {
+ return "DoubleTapScroll[state "+gestureState+", in "+isWithinGesture()+", has "+(null!=hitGestureEvent)+", pc "+pointerDownCount+"]";
+ }
+
+ @Override
+ public void clear(boolean clearStarted) {
+ scrollDistance[0] = 0f;
+ scrollDistance[1] = 0f;
+ hitGestureEvent = null;
+ if( clearStarted ) {
+ gestureState = ST_NONE;
+ sqStartDist = 0;
+ pIds[0] = -1;
+ pIds[1] = -1;
+ lastX = 0;
+ lastY = 0;
+ }
+ }
+
+ @Override
+ public boolean isWithinGesture() {
+ return ST_2PRESS_C <= gestureState;
+ }
+
+ @Override
+ public boolean hasGesture() {
+ return null != hitGestureEvent;
+ }
+
+ @Override
+ public InputEvent getGestureEvent() {
+ if( null != hitGestureEvent ) {
+ final MouseEvent ge = hitGestureEvent;
+ int modifiers = ge.getModifiers();
+ final float[] rotationXYZ = ge.getRotation();
+ rotationXYZ[0] = scrollDistance[0] / scrollSlop;
+ rotationXYZ[1] = scrollDistance[1] / scrollSlop;
+ if( rotationXYZ[0]*rotationXYZ[0] > rotationXYZ[1]*rotationXYZ[1] ) {
+ // Horizontal scroll -> SHIFT
+ modifiers |= com.jogamp.newt.event.InputEvent.SHIFT_MASK;
+ }
+ return new MouseEvent(MouseEvent.EVENT_MOUSE_WHEEL_MOVED, ge.getSource(), ge.getWhen(), modifiers,
+ ge.getAllPointerTypes(), ge.getAllPointerIDs(),
+ ge.getAllX(), ge.getAllY(), ge.getAllPressures(), ge.getMaxPressure(),
+ ge.getButton(), ge.getClickCount(), rotationXYZ, scrollSlop);
+ }
+ return null;
+ }
+
+ public final float[] getScrollDistanceXY() {
+ return scrollDistance;
+ }
+
+ @Override
+ public boolean process(final InputEvent in) {
+ if( null != hitGestureEvent || !(in instanceof MouseEvent) ) {
+ return true;
+ }
+ final MouseEvent pe = (MouseEvent)in;
+ if( pe.getPointerType(0).getPointerClass() != MouseEvent.PointerClass.Onscreen ) {
+ return false;
+ }
+ pointerDownCount = pe.getPointerCount();
+ final int eventType = pe.getEventType();
+ final int x0 = pe.getX(0);
+ final int y0 = pe.getY(0);
+ switch ( eventType ) {
+ case MouseEvent.EVENT_MOUSE_PRESSED: {
+ int gPtr = 0;
+ if( ST_NONE == gestureState && 1 == pointerDownCount ) {
+ pIds[0] = pe.getPointerId(0);
+ pIds[1] = -1;
+ gestureState = ST_1PRESS;
+ } else if( ST_NONE < gestureState && 2 == pointerDownCount && 1 == gesturePointers(pe, 0) /* w/o pressed pointer */ ) {
+ final int x1 = pe.getX(1);
+ final int y1 = pe.getY(1);
+ final int xm = (x0+x1)/2;
+ final int ym = (y0+y1)/2;
+
+ if( ST_1PRESS == gestureState ) {
+ final int sqDist = getSquareDistance(x0, y0, x1, y1);
+ final boolean isDistWithinDoubleTapSlop = sqDist < doubleTapSlopSquare;
+ if( isDistWithinDoubleTapSlop ) {
+ // very first 2-finger touch-down
+ gPtr = 2;
+ pIds[0] = pe.getPointerId(0);
+ pIds[1] = pe.getPointerId(1);
+ lastX = xm;
+ lastY = ym;
+ sqStartDist = sqDist;
+ gestureState = ST_2PRESS_T;
+ }
+ if(DEBUG) {
+ final int dist = (int)Math.round(Math.sqrt(sqDist));
+ System.err.println(this+".pressed.1: dist "+dist+", gPtr "+gPtr+", distWithin2DTSlop "+isDistWithinDoubleTapSlop+", last "+lastX+"/"+lastY+", "+pe);
+ }
+ } else if( ST_2PRESS_C == gestureState ) { // pick up gesture after temp loosing one pointer
+ gPtr = gesturePointers(pe, -1);
+ if( 2 == gPtr ) {
+ // same pointers re-touch-down
+ lastX = xm;
+ lastY = ym;
+ } else {
+ // other 2 pointers .. should rarely happen!
+ clear(true);
+ }
+ }
+ }
+ if(DEBUG) {
+ System.err.println(this+".pressed: gPtr "+gPtr+", this "+lastX+"/"+lastY+", "+pe);
+ }
+ } break;
+
+ case MouseEvent.EVENT_MOUSE_RELEASED: {
+ pointerDownCount--; // lifted
+ final int gPtr = gesturePointers(pe, 0); // w/o lifted pointer
+ if ( 1 == gPtr ) {
+ // tolerate lifting 1 of 2 gesture pointers temporary
+ gestureState = ST_2PRESS_C;
+ } else if( 0 == gPtr ) {
+ // all lifted
+ clear(true);
+ }
+ if(DEBUG) {
+ System.err.println(this+".released: gPtr "+gPtr+", "+pe);
+ }
+ } break;
+
+ case MouseEvent.EVENT_MOUSE_DRAGGED: {
+ if( 2 == pointerDownCount && ST_1PRESS < gestureState ) {
+ final int gPtr = gesturePointers(pe, -1);
+ if( 2 == gPtr ) {
+ // same pointers
+ final int x1 = pe.getX(1);
+ final int y1 = pe.getY(1);
+ final int xm = (x0+x1)/2;
+ final int ym = (y0+y1)/2;
+ final int sqDist = getSquareDistance(x0, y0, x1, y1);
+ final boolean isDistDiffWithinDoubleTapSlop = Math.abs(sqDist - sqStartDist) <= doubleTapSlopSquare;
+ if( isDistDiffWithinDoubleTapSlop ) {
+ switch( gestureState ) {
+ case ST_2PRESS_T: {
+ final int sqScrollLen = getSquareDistance(lastX, lastY, xm, ym);
+ if( sqScrollLen > scrollSlopSquare ) { // min. scrolling threshold reached
+ gestureState = ST_SCROLL;
+ }
+ } break;
+
+ case ST_2PRESS_C:
+ gestureState = ST_SCROLL;
+ break;
+
+ case ST_SCROLL:
+ scrollDistance[0] = lastX - xm;
+ scrollDistance[1] = lastY - ym;
+ hitGestureEvent = pe;
+ break;
+ }
+ if(DEBUG) {
+ final boolean isDistWithinDoubleTapSlop = sqDist < doubleTapSlopSquare;
+ final int dist = (int)Math.round(Math.sqrt(sqDist));
+ final int sqScrollLen = getSquareDistance(lastX, lastY, xm, ym);
+ final int scrollLen = (int)Math.round(Math.sqrt(sqScrollLen));
+ System.err.println(this+".dragged.1: pDist "+dist+", scrollLen "+scrollLen+", gPtr "+gPtr+" ["+pIds[0]+", "+pIds[1]+"]"+
+ ", diffDistWithinTapSlop "+isDistDiffWithinDoubleTapSlop+
+ ", distWithin2DTSlop "+isDistWithinDoubleTapSlop+
+ ", this "+xm+"/"+ym+", last "+lastX+"/"+lastY+", d "+scrollDistance[0]+"/"+scrollDistance[1]);
+ }
+ } else {
+ // distance too big ..
+ if(DEBUG) {
+ final boolean isDistWithinDoubleTapSlop = sqDist < doubleTapSlopSquare;
+ final int dist = (int)Math.round(Math.sqrt(sqDist));
+ final int startDist = (int)Math.round(Math.sqrt(sqStartDist));
+ System.err.println(this+".dragged.X1: pDist "+dist+", distStart "+startDist+", gPtr "+gPtr+" ["+pIds[0]+", "+pIds[1]+"]"+
+ ", diffDistWithinTapSlop "+isDistDiffWithinDoubleTapSlop+
+ ", distWithin2DTSlop "+isDistWithinDoubleTapSlop+
+ ", this "+xm+"/"+ym+", last "+lastX+"/"+lastY+", d "+scrollDistance[0]+"/"+scrollDistance[1]);
+ }
+ clear(true);
+ }
+ if( ST_2PRESS_T < gestureState ) {
+ // state ST_2PRESS_T waits for min scroll threshold !
+ lastX = xm;
+ lastY = ym;
+ }
+ } else {
+ // other 2 pointers .. should rarely happen!
+ if(DEBUG) {
+ System.err.println(this+".dragged.X2: gPtr "+gPtr+" ["+pIds[0]+", "+pIds[1]+"]"+
+ ", last "+lastX+"/"+lastY+", d "+scrollDistance[0]+"/"+scrollDistance[1]);
+ }
+ clear(true);
+ }
+ }
+ } break;
+
+ default:
+ }
+ return null != hitGestureEvent;
+ }
+}
diff --git a/src/newt/classes/com/jogamp/newt/event/GestureHandler.java b/src/newt/classes/com/jogamp/newt/event/GestureHandler.java
new file mode 100644
index 000000000..2c8f29bb7
--- /dev/null
+++ b/src/newt/classes/com/jogamp/newt/event/GestureHandler.java
@@ -0,0 +1,143 @@
+/**
+ * Copyright 2013 JogAmp Community. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and should not be interpreted as representing official policies, either expressed
+ * or implied, of JogAmp Community.
+ */
+package com.jogamp.newt.event;
+
+import jogamp.newt.Debug;
+
+/**
+ * Generic gesture handler interface designed to allow pass-through
+ * filtering of {@link InputEvent}s.
+ * + * To avoid negative impact on event processing, + * implementation shall restrict computation as much as possible + * and only within it's appropriate gesture states. + *
+ *+ * To allow custom user events, other than the normal {@link InputEvent}s, + * a user may return a {@link GestureEvent} in it's implementation. + *
+ */ +public interface GestureHandler { + public static final boolean DEBUG = Debug.debug("Window.MouseEvent"); + + /** A custom gesture event */ + @SuppressWarnings("serial") + public static class GestureEvent extends InputEvent { + /** A gesture has been detected. */ + public static final short EVENT_GESTURE_DETECTED = 400; + + private final GestureHandler handler; + + /** + * Creates a gesture event with default type {@link #EVENT_GESTURE_DETECTED}. + * + * @param source + * @param when + * @param modifiers + * @param handler + */ + public GestureEvent(Object source, long when, int modifiers, GestureHandler handler) { + super(EVENT_GESTURE_DETECTED, source, when, modifiers); + this.handler = handler; + } + + /** + * Creates a gesture event with custom event_type ! + * @param event_type must lie within [400..599] + * @param source + * @param when + * @param modifiers + * @param handler + */ + public GestureEvent(short event_type, Object source, long when, int modifiers, GestureHandler handler) { + super(event_type, source, when, modifiers); + this.handler = handler; + } + + /** Return the {@link GestureHandler}, which produced the event. */ + public final GestureHandler getHandler() { return handler; } + } + + /** + * Listener for {@link GestureEvent}s. + * + * @see GestureEvent + */ + public static interface GestureListener extends NEWTEventListener + { + /** {@link GestureHandler} {@link GestureHandler#hasGesture() has detected} the gesture. */ + public void gestureDetected(GestureEvent gh); + } + + /** + * Clears state of handler, i.e. resets all states incl. previous detected gesture. + * @param clearStarted if true, also clears {@link #isWithinGesture() started} state, + * otherwise stay within gesture - if appropriate. + * Staying within a gesture allows fluent continuous gesture sequence, + * e.g. for scrolling. + */ + public void clear(boolean clearStarted); + + /** + * Returns true if a previous {@link #process(InputEvent)} command produced a gesture, + * which has not been {@link #clear(boolean) cleared}. + * Otherwise returns false. + */ + public boolean hasGesture(); + + /** + * Returns the corresponding {@link InputEvent} for the gesture as detected by + * a previous {@link #process(InputEvent)}, which has not been {@link #clear(boolean) cleared}. + * Otherwise returns null. + *+ * Only implemented for gestures mapping to {@link InputEvent}s. + *
+ */ + public InputEvent getGestureEvent(); + + /** + * Returns true if within a gesture as detected by a previous {@link #process(InputEvent)} command, + * which has not been {@link #clear(boolean) cleared}. + * Otherwise returns false. + */ + public boolean isWithinGesture(); + + /** + * Process the given {@link InputEvent} and returns true if it produced the gesture. + * Otherwise returns false. + *+ * If a gesture was already detected previously and has not been cleared, + * method does not process the event and returns true. + *
+ *+ * Besides validation of the event's details, + * the handler may also validate the {@link InputEvent.InputClass} and/or {@link InputEvent.InputType}. + *
+ */ + public boolean process(InputEvent e); +} diff --git a/src/newt/classes/com/jogamp/newt/event/InputEvent.java b/src/newt/classes/com/jogamp/newt/event/InputEvent.java index b04ebc1af..7712b77e7 100644 --- a/src/newt/classes/com/jogamp/newt/event/InputEvent.java +++ b/src/newt/classes/com/jogamp/newt/event/InputEvent.java @@ -39,6 +39,14 @@ import com.jogamp.newt.Window; @SuppressWarnings("serial") public abstract class InputEvent extends NEWTEvent { + /** Interface marking class of input types */ + public static interface InputClass { + } + + /** Interface marking type of input devices */ + public static interface InputType { + } + public static final int SHIFT_MASK = 1 << 0; public static final int CTRL_MASK = 1 << 1; public static final int META_MASK = 1 << 2; diff --git a/src/newt/classes/com/jogamp/newt/event/MouseEvent.java b/src/newt/classes/com/jogamp/newt/event/MouseEvent.java index 8533a37c6..2c137ab77 100644 --- a/src/newt/classes/com/jogamp/newt/event/MouseEvent.java +++ b/src/newt/classes/com/jogamp/newt/event/MouseEvent.java @@ -40,8 +40,8 @@ package com.jogamp.newt.event; * http://www.w3.org/Submission/pointer-events/#pointerevent-interface * *
- * In case an instance represents multi-touch events, i.e. {@link #getPointerCount()} is > 1,
- * the first data element represents the pointer which triggered the action if individual to one pointer.
+ * In case an instance represents a multiple-pointer event, i.e. {@link #getPointerCount()} is > 1,
+ * the first data element of the multiple-pointer fields represents the pointer which triggered the action.
* For example {@link #getX(int) e.getX(0)} at {@link #EVENT_MOUSE_PRESSED} returns the data of the pressed pointer, etc.
*
+ * First element of multiple-pointer arrays represents the pointer which triggered the event! + *
+ * + * @param eventType + * @param source + * @param when + * @param modifiers + * @param pointerType PointerType for each pointer (multiple pointer) + * @param pointerID Pointer ID for each pointer (multiple pointer) + * @param x X-axis for each pointer (multiple pointer) + * @param y Y-axis for each pointer (multiple pointer) + * @param pressure Pressure for each pointer (multiple pointer) + * @param maxPressure Maximum pointer pressure for all pointer + * @param button Corresponding mouse-button + * @param clickCount Mouse-button click-count + * @param rotationXYZ Rotation of all axis + * @param rotationScale Rotation scale + */ + public MouseEvent(short eventType, Object source, long when, int modifiers, + PointerType pointerType[], short[] pointerID, + int[] x, int[] y, float[] pressure, float maxPressure, + short button, short clickCount, float[] rotationXYZ, float rotationScale) { super(eventType, source, when, modifiers); this.x = x; this.y = y; - if(pointerids.length != pressure.length || - pointerids.length != x.length || - pointerids.length != y.length) { + final int pointerCount = pointerType.length; + if(pointerCount != pointerID.length || + pointerCount != x.length || + pointerCount != y.length || + pointerCount != pressure.length) { throw new IllegalArgumentException("All multiple pointer arrays must be of same size"); } if( 0.0f >= maxPressure ) { @@ -146,55 +169,148 @@ public class MouseEvent extends InputEvent } this.pressure = pressure; this.maxPressure= maxPressure; - this.pointerIDs = pointerids; + this.pointerID = pointerID; this.clickCount=clickCount; this.button=button; this.rotationXYZ = rotationXYZ; this.rotationScale = rotationScale; - this.pointerTypes = pointerTypes; + this.pointerType = pointerType; + } + + public MouseEvent createVariant(short newEventType) { + return new MouseEvent(newEventType, source, getWhen(), getModifiers(), pointerType, pointerID, + x, y, pressure, maxPressure, button, clickCount, rotationXYZ, rotationScale); + } + + /** + * Factory for a multiple-pointer event. + *+ * The index for the element of multiple-pointer arrays represents the pointer which triggered the event + * is passed via actionIdx. + *
+ * + * @param eventType + * @param source + * @param when + * @param modifiers + * @param actionIdx index of multiple-pointer arrays representing the pointer which triggered the event + * @param pointerType PointerType for each pointer (multiple pointer) + * @param pointerID Pointer ID for each pointer (multiple pointer) + * @param x X-axis for each pointer (multiple pointer) + * @param y Y-axis for each pointer (multiple pointer) + * @param pressure Pressure for each pointer (multiple pointer) + * @param maxPressure Maximum pointer pressure for all pointer + * @param button Corresponding mouse-button + * @param clickCount Mouse-button click-count + * @param rotationXYZ Rotation of all axis + * @param rotationScale Rotation scale + */ + public static MouseEvent create(short eventType, Object source, long when, int modifiers, + int actionIdx, PointerType pointerType[], short[] pointerID, + int[] x, int[] y, float[] pressure, float maxPressure, + short button, short clickCount, float[] rotationXYZ, float rotationScale) { + if( 0 <= actionIdx && actionIdx < pointerType.length) { + if( 0 < actionIdx ) { + { + final PointerType aType = pointerType[actionIdx]; + pointerType[actionIdx] = pointerType[0]; + pointerType[0] = aType; + } + { + final short s = pointerID[actionIdx]; + pointerID[actionIdx] = pointerID[0]; + pointerID[0] = s; + } + { + int s = x[actionIdx]; + x[actionIdx] = x[0]; + x[0] = s; + s = y[actionIdx]; + y[actionIdx] = y[0]; + y[0] = s; + } + { + final float aPress = pressure[actionIdx]; + pressure[actionIdx] = pressure[0]; + pressure[0] = aPress; + } + } + return new MouseEvent(eventType, source, when, modifiers, + pointerType, pointerID, x, y, pressure, maxPressure, + button, clickCount, rotationXYZ, rotationScale); + } + throw new IllegalArgumentException("actionIdx out of bounds [0.."+(pointerType.length-1)+"]"); } /** * @return the count of pointers involved in this event */ - public int getPointerCount() { - return x.length; + public final int getPointerCount() { + return pointerType.length; } /** * @return the {@link PointerType} for the data at index. * return null if index not available. */ - public PointerType getPointerType(int index) { - if(index >= pointerIDs.length) { + public final PointerType getPointerType(int index) { + if(0 > index || index >= pointerType.length) { return null; } - return pointerTypes[index]; + return pointerType[index]; + } + + /** + * @return array of all {@link PointerType}s for all pointers + */ + public final PointerType[] getAllPointerTypes() { + return pointerType; } /** - * @return the pointer id for the data at index. + * @return the pointer id for the given index. * return -1 if index not available. */ - public short getPointerId(int index) { - if(index >= pointerIDs.length) { + public final short getPointerId(int index) { + if(0 > index || index >= pointerID.length) { return -1; } - return pointerIDs[index]; + return pointerID[index]; } - public short getButton() { + /** + * @return the pointer index for the given pointer id. + * return -1 if id not available. + */ + public final int getPointerIdx(short id) { + for(int i=pointerID.length-1; i>=0; i--) { + if( pointerID[i] == id ) { + return i; + } + } + return -1; + } + + /** + * @return array of all pointer IDs for all pointers + */ + public final short[] getAllPointerIDs() { + return pointerID; + } + + public final short getButton() { return button; } - public short getClickCount() { + public final short getClickCount() { return clickCount; } - public int getX() { + + public final int getX() { return x[0]; } - public int getY() { + public final int getY() { return y[0]; } @@ -203,7 +319,7 @@ public class MouseEvent extends InputEvent * @return X-Coord associated with the pointer-index. * @see getPointerId(index) */ - public int getX(int index) { + public final int getX(int index) { return x[index]; } @@ -212,20 +328,41 @@ public class MouseEvent extends InputEvent * @return Y-Coord associated with the pointer-index. * @see getPointerId(index) */ - public int getY(int index) { + public final int getY(int index) { return y[index]; } + /** + * @return array of all X-Coords for all pointers + */ + public final int[] getAllX() { + return x; + } + + /** + * @return array of all Y-Coords for all pointers + */ + public final int[] getAllY() { + return y; + } + /** * @param normalized if true, method returns the normalized pressure, i.e.pressure / maxPressure
* @return The pressure associated with the pointer-index 0.
* The value of zero is return if not available.
* @see #getMaxPressure()
*/
- public float getPressure(boolean normalized){
+ public final float getPressure(boolean normalized){
return normalized ? pressure[0] / maxPressure : pressure[0];
}
+ /**
+ * @return array of all raw, un-normalized pressures for all pointers
+ */
+ public final float[] getAllPressures() {
+ return pressure;
+ }
+
/**
* Returns the maximum pressure known for the input device generating this event.
* @@ -239,7 +376,7 @@ public class MouseEvent extends InputEvent * *
*/ - public float getMaxPressure() { + public final float getMaxPressure() { return maxPressure; } @@ -250,7 +387,7 @@ public class MouseEvent extends InputEvent * The value of zero is return if not available. * @see #getMaxPressure() */ - public float getPressure(int index, boolean normalized){ + public final float getPressure(int index, boolean normalized){ return normalized ? pressure[index] / maxPressure : pressure[index]; } @@ -294,7 +431,7 @@ public class MouseEvent extends InputEvent * see {@link #getRotationScale()} for semantics. * */ - public float[] getRotation() { + public final float[] getRotation() { return rotationXYZ; } @@ -311,15 +448,15 @@ public class MouseEvent extends InputEvent * Hencescale * rotation
reproduces the screen distance in pixels the finger[s] have moved.
*
*/
- public float getRotationScale() {
+ public final float getRotationScale() {
return rotationScale;
}
- public String toString() {
+ public final String toString() {
return toString(null).toString();
}
- public StringBuilder toString(StringBuilder sb) {
+ public final StringBuilder toString(StringBuilder sb) {
if(null == sb) {
sb = new StringBuilder();
}
@@ -327,13 +464,13 @@ public class MouseEvent extends InputEvent
.append(", ").append(x).append("/").append(y)
.append(", button ").append(button).append(", count ")
.append(clickCount).append(", rotation [").append(rotationXYZ[0]).append(", ").append(rotationXYZ[1]).append(", ").append(rotationXYZ[2]).append("] * ").append(rotationScale);
- if(pointerIDs.length>0) {
- sb.append(", pointer<").append(pointerIDs.length).append(">[");
- for(int i=0; i100..10x
200..20x
300..30x
400..5xx
600..60x
+ * Zoom value lies within [0..2], with 1 as 1:1. + *
+ *+ * - choosing the smallest surface edge (width/height -> x/y) + * - tolerating other fingers to be pressed and hence user to add functionality (scale, ..) + *+ */ +public class PinchToZoomGesture implements GestureHandler { + public static final boolean DEBUG = Debug.debug("Window.MouseEvent"); + + /** A {@link GestureHandler.GestureEvent} denominating zoom. */ + @SuppressWarnings("serial") + public static class ZoomEvent extends GestureEvent { + private final MouseEvent pe; + private final float zoom; + public ZoomEvent(Object source, long when, int modifiers, GestureHandler handler, MouseEvent pe, float zoom) { + super(source, when, modifiers, handler); + this.pe = pe; + this.zoom = zoom; + } + /** Triggering {@link MouseEvent} */ + public final MouseEvent getTrigger() { return pe; } + /** Zoom value lies within [0..2], with 1 as 1:1. */ + public final float getZoom() { return zoom; } + } + + private final NativeSurface surface; + private float zoom; + private int zoomLastEdgeDist; + private boolean zoomFirstTouch; + private boolean zoomMode; + private ZoomEvent zoomEvent; + private short[] pIds = new short[] { -1, -1 }; + + public PinchToZoomGesture(NativeSurface surface) { + clear(true); + this.surface = surface; + this.zoom = 1f; + } + + public String toString() { + return "PinchZoom[1stTouch "+zoomFirstTouch+", in "+isWithinGesture()+", has "+(null!=zoomEvent)+", zoom "+zoom+"]"; + } + + private int gesturePointers(final MouseEvent e, final int excludeIndex) { + int j = 0; + for(int i=e.getPointerCount()-1; i>=0; i--) { + if( excludeIndex != i ) { + final int id = e.getPointerId(i); + if( pIds[0] == id || pIds[1] == id ) { + j++; + } + } + } + return j; + } + + @Override + public void clear(boolean clearStarted) { + zoomEvent = null; + if( clearStarted ) { + zoomLastEdgeDist = 0; + zoomFirstTouch = true; + zoomMode = false; + pIds[0] = -1; + pIds[1] = -1; + } + } + + @Override + public boolean isWithinGesture() { + return zoomMode; + } + + @Override + public boolean hasGesture() { + return null != zoomEvent; + } + + @Override + public InputEvent getGestureEvent() { + return zoomEvent; + } + + /** Zoom value lies within [0..2], with 1 as 1:1. */ + public final float getZoom() { + return zoom; + } + /** Set zoom value within [0..2], with 1 as 1:1. */ + public final void setZoom(float zoom) { + this.zoom=zoom; + } + + @Override + public boolean process(final InputEvent in) { + if( null != zoomEvent || !(in instanceof MouseEvent) ) { + return true; + } + final MouseEvent pe = (MouseEvent)in; + if( pe.getPointerType(0).getPointerClass() != MouseEvent.PointerClass.Onscreen ) { + return false; + } + + final int pointerDownCount = pe.getPointerCount(); + final int eventType = pe.getEventType(); + final boolean useY = surface.getWidth() >= surface.getHeight(); // use smallest dimension + switch ( eventType ) { + case MouseEvent.EVENT_MOUSE_PRESSED: { + if( 1 == pointerDownCount ) { + pIds[0] = pe.getPointerId(0); + pIds[1] = -1; + } else if ( 2 <= pointerDownCount ) { // && 1 == gesturePointers(pe, 0) /* w/o pressed pointer */) { + pIds[0] = pe.getPointerId(0); + pIds[1] = pe.getPointerId(1); + } + if(DEBUG) { + System.err.println("XXX1: id0 "+pIds[0]+" -> idx0 "+0+", id1 "+pIds[1]+" -> idx1 "+1); + System.err.println(this+".pressed: down "+pointerDownCount+", gPtr "+gesturePointers(pe, -1)+", event "+pe); + } + } break; + + case MouseEvent.EVENT_MOUSE_RELEASED: { + final int gPtr = gesturePointers(pe, 0); // w/o lifted pointer + if ( 1 == gPtr ) { + zoomFirstTouch = true; + zoomMode = false; + } else if( 0 == gPtr ) { + // all lifted + clear(true); + } + if(DEBUG) { + System.err.println(this+".released: down "+pointerDownCount+", gPtr "+gPtr+", event "+pe); + } + } break; + + case MouseEvent.EVENT_MOUSE_DRAGGED: { + if( 2 <= pointerDownCount ) { + final int gPtr = gesturePointers(pe, -1); + if( 2 == gPtr ) { + // same pointers + final int p0Idx = pe.getPointerIdx(pIds[0]); + final int p1Idx = pe.getPointerIdx(pIds[1]); + final int edge0 = useY ? pe.getY(p0Idx) : pe.getX(p0Idx); + final int edge1 = useY ? pe.getY(p1Idx) : pe.getX(p1Idx); + // Diff. 1:1 Zoom: finger-distance to screen-coord + if(zoomFirstTouch) { + zoomLastEdgeDist = Math.abs(edge0-edge1); + zoomFirstTouch=false; + zoomMode = true; + } else if( zoomMode ) { + final int d = Math.abs(edge0-edge1); + final int dd = d - zoomLastEdgeDist; + final float screenEdge = useY ? surface.getHeight() : surface.getWidth(); + final float incr = (float)dd / screenEdge; // [-1..1] + if(DEBUG) { + System.err.println("XXX2: id0 "+pIds[0]+" -> idx0 "+p0Idx+", id1 "+pIds[1]+" -> idx1 "+p1Idx); + System.err.println("XXX3: d "+d+", ld "+zoomLastEdgeDist+", dd "+dd+", screen "+screenEdge+" -> incr "+incr+", zoom "+zoom+" -> "+(zoom+incr)); + } + zoom += incr; + // clip value + if( 2f < zoom ) { + zoom = 2f; + } else if( 0 > zoom ) { + zoom = 0; + } + zoomLastEdgeDist = d; + zoomEvent = new ZoomEvent(pe.getSource(), pe.getWhen(), pe.getModifiers(), this, pe, zoom); + } + } + if(DEBUG) { + System.err.println(this+".dragged: down "+pointerDownCount+", gPtr "+gPtr+", event "+pe); + } + } + } break; + + default: + } + return null != zoomEvent; + } +} diff --git a/src/newt/classes/com/jogamp/newt/opengl/GLWindow.java b/src/newt/classes/com/jogamp/newt/opengl/GLWindow.java index eace0f2af..cae1a06a2 100644 --- a/src/newt/classes/com/jogamp/newt/opengl/GLWindow.java +++ b/src/newt/classes/com/jogamp/newt/opengl/GLWindow.java @@ -77,6 +77,7 @@ import com.jogamp.newt.MonitorDevice; import com.jogamp.newt.NewtFactory; import com.jogamp.newt.Screen; import com.jogamp.newt.Window; +import com.jogamp.newt.event.GestureHandler; import com.jogamp.newt.event.KeyListener; import com.jogamp.newt.event.MouseListener; import com.jogamp.newt.event.NEWTEvent; @@ -774,6 +775,39 @@ public class GLWindow extends GLAutoDrawableBase implements GLAutoDrawable, Wind return window.getMouseListeners(); } + @Override + public void setDefaultGesturesEnabled(boolean enable) { + window.setDefaultGesturesEnabled(enable); + } + @Override + public boolean areDefaultGesturesEnabled() { + return window.areDefaultGesturesEnabled(); + } + @Override + public final void addGestureHandler(GestureHandler gh) { + window.addGestureHandler(gh); + } + @Override + public final void addGestureHandler(int index, GestureHandler gh) { + window.addGestureHandler(index, gh); + } + @Override + public final void removeGestureHandler(GestureHandler gh) { + window.removeGestureHandler(gh); + } + @Override + public final void addGestureListener(GestureHandler.GestureListener gl) { + window.addGestureListener(-1, gl); + } + @Override + public final void addGestureListener(int index, GestureHandler.GestureListener gl) { + window.addGestureListener(index, gl); + } + @Override + public final void removeGestureListener(GestureHandler.GestureListener gl) { + window.removeGestureListener(gl); + } + //---------------------------------------------------------------------- // NativeWindow completion // diff --git a/src/newt/classes/jogamp/newt/WindowImpl.java b/src/newt/classes/jogamp/newt/WindowImpl.java index b7357863f..66ce46ed0 100644 --- a/src/newt/classes/jogamp/newt/WindowImpl.java +++ b/src/newt/classes/jogamp/newt/WindowImpl.java @@ -48,6 +48,8 @@ import com.jogamp.newt.Screen; import com.jogamp.newt.Window; import com.jogamp.common.util.locks.LockFactory; import com.jogamp.common.util.locks.RecursiveLock; +import com.jogamp.newt.event.DoubleTapScrollGesture; +import com.jogamp.newt.event.GestureHandler; import com.jogamp.newt.event.InputEvent; import com.jogamp.newt.event.KeyEvent; import com.jogamp.newt.event.KeyListener; @@ -60,6 +62,7 @@ import com.jogamp.newt.event.MonitorModeListener; import com.jogamp.newt.event.WindowEvent; import com.jogamp.newt.event.WindowListener; import com.jogamp.newt.event.WindowUpdateEvent; +import com.jogamp.newt.event.MouseEvent.PointerType; import javax.media.nativewindow.AbstractGraphicsConfiguration; import javax.media.nativewindow.AbstractGraphicsDevice; @@ -71,6 +74,7 @@ import javax.media.nativewindow.NativeWindowException; import javax.media.nativewindow.NativeWindowFactory; import javax.media.nativewindow.SurfaceUpdatedListener; import javax.media.nativewindow.WindowClosingProtocol; +import javax.media.nativewindow.util.DimensionImmutable; import javax.media.nativewindow.util.Insets; import javax.media.nativewindow.util.InsetsImmutable; import javax.media.nativewindow.util.Point; @@ -176,13 +180,41 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer private ArrayList
+ * The index for the element of multiple-pointer arrays represents the pointer which triggered the event + * is passed via actionIdx. + *
+ * + * @param eventType + * @param source + * @param when + * @param modifiers + * @param actionIdx index of multiple-pointer arrays representing the pointer which triggered the event + * @param pointerType PointerType for each pointer (multiple pointer) + * @param pointerID Pointer ID for each pointer (multiple pointer) + * @param x X-axis for each pointer (multiple pointer) + * @param y Y-axis for each pointer (multiple pointer) + * @param pressure Pressure for each pointer (multiple pointer) + * @param maxPressure Maximum pointer pressure for all pointer + * @param button Corresponding mouse-button + * @param clickCount Mouse-button click-count + * @param rotationXYZ Rotation of all axis + * @param rotationScale Rotation scale + */ + public void sendMouseEvent(short eventType, Object source, long when, int modifiers, + int actionIdx, PointerType pointerType[], short[] pointerID, + int[] x, int[] y, float[] pressure, float maxPressure, + short button, short clickCount, float[] rotationXYZ, float rotationScale) { + final MouseEvent pe = MouseEvent.create(eventType, source, when, modifiers, actionIdx, + pointerType, pointerID, x, y, pressure, maxPressure, button, clickCount, + rotationXYZ, rotationScale); + consumeMouseEvent(pe); + } + @Override - public void addMouseListener(MouseListener l) { + public final void addMouseListener(MouseListener l) { addMouseListener(-1, l); } @Override - public void addMouseListener(int index, MouseListener l) { + public final void addMouseListener(int index, MouseListener l) { if(l == null) { return; } @@ -2439,7 +2517,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } @Override - public void removeMouseListener(MouseListener l) { + public final void removeMouseListener(MouseListener l) { if (l == null) { return; } @@ -2450,7 +2528,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } @Override - public MouseListener getMouseListener(int index) { + public final MouseListener getMouseListener(int index) { @SuppressWarnings("unchecked") ArrayList+ * - validate + * - handle gestures + * - synthesize events if applicable (like gestures) + * - dispatch event to listener + *+ */ + protected void consumeMouseEvent(MouseEvent pe) { + final int x = pe.getX(); + final int y = pe.getY(); + + if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.drop: "+pe); + } + return; // .. invalid .. + } if(DEBUG_MOUSE_EVENT) { - System.err.println("consumeMouseEvent: event: "+e); + System.err.println("consumeMouseEvent.in: "+pe); } + + // + // - Remove redundant events + // - Determine ENTERED/EXITED state + // - synthesize ENTERED event + // - fix MOVED/DRAGGED event + // - Reset states if applicable + // + final long when = pe.getWhen(); + int eventType = pe.getEventType(); + final MouseEvent eEntered; + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_EXITED: + case MouseEvent.EVENT_MOUSE_ENTERED: + pState1.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED; + // clear states + pState1.lastButtonPressTime = 0; + pState1.buttonPressed = 0; + eEntered = null; + break; + + case MouseEvent.EVENT_MOUSE_MOVED: + if ( pState1.buttonPressed > 0 ) { + pe = pe.createVariant(MouseEvent.EVENT_MOUSE_DRAGGED); + eventType = pe.getEventType(); + } + // Fall through intended ! + case MouseEvent.EVENT_MOUSE_DRAGGED: + if( pState1.insideWindow && pState1.lastMovePosition.getX() == x && pState1.lastMovePosition.getY() == y ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent: skip EVENT_MOUSE_MOVED w/ same position: "+pState1.lastMovePosition); + } + return; // skip same position + } + pState1.lastMovePosition.setX(x); + pState1.lastMovePosition.setY(y); + // Fall through intended ! + default: + if(!pState1.insideWindow) { + pState1.insideWindow = true; + eEntered = pe.createVariant(MouseEvent.EVENT_MOUSE_ENTERED); + // clear states + pState1.lastButtonPressTime = 0; + pState1.buttonPressed = 0; + } else { + eEntered = null; + } + } + if( null != eEntered ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.send.0: "+eEntered); + } + dispatchMouseEvent(eEntered); + } + + // + // Handle Default Gestures + // + if( defaultGestureHandlerEnabled && + pe.getPointerType(0).getPointerClass() == MouseEvent.PointerClass.Onscreen ) + { + if( null == gesture2PtrTouchScroll ) { + final int scaledScrollSlop; + final int scaledDoubleTapSlop; + final MonitorDevice monitor = getMainMonitor(); + if ( null != monitor ) { + final DimensionImmutable mm = monitor.getSizeMM(); + final float pixWPerMM = (float)monitor.getCurrentMode().getRotatedWidth() / (float)mm.getWidth(); + final float pixHPerMM = (float)monitor.getCurrentMode().getRotatedHeight() / (float)mm.getHeight(); + final float pixPerMM = Math.min(pixHPerMM, pixWPerMM); + scaledScrollSlop = Math.round(DoubleTapScrollGesture.SCROLL_SLOP_MM * pixPerMM); + scaledDoubleTapSlop = Math.round(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_MM * pixPerMM); + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.gscroll: scrollSlop "+scaledScrollSlop+", doubleTapSlop "+scaledDoubleTapSlop+", pixPerMM "+pixPerMM+", "+monitor); + } + } else { + scaledScrollSlop = DoubleTapScrollGesture.SCROLL_SLOP_PIXEL; + scaledDoubleTapSlop = DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL; + } + gesture2PtrTouchScroll = new DoubleTapScrollGesture(step(DoubleTapScrollGesture.SCROLL_SLOP_PIXEL, DoubleTapScrollGesture.SCROLL_SLOP_PIXEL/2, scaledScrollSlop), + step(DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL, DoubleTapScrollGesture.DOUBLE_TAP_SLOP_PIXEL/2, scaledDoubleTapSlop)); + } + if( gesture2PtrTouchScroll.process(pe) ) { + pe = (MouseEvent) gesture2PtrTouchScroll.getGestureEvent(); + gesture2PtrTouchScroll.clear(false); + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.gscroll: "+pe); + } + dispatchMouseEvent(pe); + return; + } + if( gesture2PtrTouchScroll.isWithinGesture() ) { + return; // within gesture .. need more input .. + } + } + // + // Handle Custom Gestures + // + { + final int pointerGestureHandlerCount = pointerGestureHandler.size(); + if( pointerGestureHandlerCount > 0 ) { + boolean withinGesture = false; + for(int i = 0; !pe.isConsumed() && i < pointerGestureHandlerCount; i++ ) { + final GestureHandler gh = pointerGestureHandler.get(i); + if( gh.process(pe) ) { + final InputEvent ieG = gh.getGestureEvent(); + gh.clear(false); + if( ieG instanceof MouseEvent ) { + dispatchMouseEvent((MouseEvent)ieG); + } else if( ieG instanceof GestureHandler.GestureEvent) { + final GestureHandler.GestureEvent ge = (GestureHandler.GestureEvent) ieG; + for(int j = 0; !ge.isConsumed() && j < gestureListeners.size(); j++ ) { + gestureListeners.get(j).gestureDetected(ge); + } + } + return; + } + withinGesture |= gh.isWithinGesture(); + } + if( withinGesture ) { + return; + } + } + } + + // + // Synthesize mouse click + // + final MouseEvent eClicked; + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_PRESSED: + if( 1 == pe.getPointerCount() ) { + pState1.lastButtonPressTime = when; + } + pState1.buttonPressed = pe.getButton(); + eClicked = null; + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + if( 1 == pe.getPointerCount() && when - pState1.lastButtonPressTime < MouseEvent.getClickTimeout() ) { + eClicked = pe.createVariant(MouseEvent.EVENT_MOUSE_CLICKED); + } else { + eClicked = null; + pState1.lastButtonPressTime = 0; + } + pState1.buttonPressed = 0; + break; + case MouseEvent.EVENT_MOUSE_CLICKED: + // ignore - synthesized here .. + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent: drop recv'ed (synth here) "+pe); + } + pe = null; + eClicked = null; + break; + default: + eClicked = null; + } + + if( null != pe ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.send.1: "+pe); + } + dispatchMouseEvent(pe); // actual mouse event + } + if( null != eClicked ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("consumeMouseEvent.send.2: "+eClicked); + } + dispatchMouseEvent(eClicked); + } + } + + private final void dispatchMouseEvent(MouseEvent e) { for(int i = 0; !e.isConsumed() && i < mouseListeners.size(); i++ ) { MouseListener l = mouseListeners.get(i); switch(e.getEventType()) { @@ -2537,12 +2874,12 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer public void sendKeyEvent(short eventType, int modifiers, short keyCode, short keySym, char keyChar) { // Always add currently pressed mouse buttons to modifier mask - consumeKeyEvent( KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | mouseButtonModMask, keyCode, keySym, keyChar) ); + consumeKeyEvent( KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState0.buttonModMask, keyCode, keySym, keyChar) ); } public void enqueueKeyEvent(boolean wait, short eventType, int modifiers, short keyCode, short keySym, char keyChar) { // Always add currently pressed mouse buttons to modifier mask - enqueueEvent(wait, KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | mouseButtonModMask, keyCode, keySym, keyChar) ); + enqueueEvent(wait, KeyEvent.create(eventType, this, System.currentTimeMillis(), modifiers | pState0.buttonModMask, keyCode, keySym, keyChar) ); } @Override diff --git a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java index 364a348ee..0e76db374 100644 --- a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java +++ b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java @@ -59,19 +59,16 @@ public class AndroidNewtEventFactory { private static final short aMotionEventType2Newt(int aType) { switch( aType ) { case android.view.MotionEvent.ACTION_DOWN: + case android.view.MotionEvent.ACTION_POINTER_DOWN: return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_PRESSED; case android.view.MotionEvent.ACTION_UP: + case android.view.MotionEvent.ACTION_POINTER_UP: + case android.view.MotionEvent.ACTION_CANCEL: return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_RELEASED; case android.view.MotionEvent.ACTION_MOVE: return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_DRAGGED; - case android.view.MotionEvent.ACTION_CANCEL: - return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_RELEASED; case android.view.MotionEvent.ACTION_OUTSIDE: return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_MOVED; - case android.view.MotionEvent.ACTION_POINTER_DOWN: - return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_PRESSED; - case android.view.MotionEvent.ACTION_POINTER_UP: - return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_RELEASED; // case ACTION_HOVER_MOVE case ACTION_SCROLL: // API Level 12 ! return com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_WHEEL_MOVED; @@ -244,18 +241,15 @@ public class AndroidNewtEventFactory { return maxPressure; } - private final int touchSlop, touchSlopSquare, doubleTapSlop, doubleTapSlopSquare; - + private final int touchSlop; public AndroidNewtEventFactory(android.content.Context context, android.os.Handler handler) { final android.view.ViewConfiguration configuration = android.view.ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); - touchSlopSquare = touchSlop * touchSlop; - doubleTapSlop = configuration.getScaledDoubleTapSlop(); - doubleTapSlopSquare = doubleTapSlop * doubleTapSlop; + final int doubleTapSlop = configuration.getScaledDoubleTapSlop(); if(DEBUG_MOUSE_EVENT) { - System.err.println("GestureListener touchSlop (scaled) "+touchSlop); - System.err.println("GestureListener doubleTapSlop (scaled) "+doubleTapSlop); - } + System.err.println("AndroidNewtEventFactory scrollSlop (scaled) "+touchSlop); + System.err.println("AndroidNewtEventFactory doubleTapSlop (scaled) "+doubleTapSlop); + } } private static void collectPointerData(MotionEvent e, int eIdx, int dIdx, final int[] x, final int[] y, final float[] pressure, short[] pointerIds, final com.jogamp.newt.event.MouseEvent.PointerType[] pointerTypes) { @@ -272,8 +266,8 @@ public class AndroidNewtEventFactory { } } - public com.jogamp.newt.event.MouseEvent[] createMouseEvents(boolean isOnTouchEvent, - android.view.MotionEvent event, com.jogamp.newt.Window newtSource) { + public com.jogamp.newt.event.MouseEvent createMouseEvents(boolean isOnTouchEvent, + android.view.MotionEvent event, com.jogamp.newt.Window newtSource) { if(DEBUG_MOUSE_EVENT) { System.err.println("createMouseEvent: isOnTouchEvent "+isOnTouchEvent+", "+event); } @@ -285,67 +279,17 @@ public class AndroidNewtEventFactory { // // Prefilter Android Event (Gesture, ..) and determine final type // - final int aType; - final short nType; + final int aType = event.getActionMasked(); + final short nType = aMotionEventType2Newt(aType); final float rotationScale = touchSlop; final float[] rotationXYZ = new float[] { 0f, 0f, 0f }; - int rotationSource = 0; // 1 - Gesture, 2 - ACTION_SCROLL - { - final int aType0 = event.getActionMasked(); - if( isOnTouchEvent ) { - switch ( aType0 ) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_DOWN: - gesture2FingerScrl.onDown(event); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - gesture2FingerScrl.onUp(event); - break; - case MotionEvent.ACTION_MOVE: - gesture2FingerScrl.onMove(event); - break; - } - } - - if( gesture2FingerScrl.gestureStarted() ) { - if( gesture2FingerScrl.hasGesture(true) ) { - final float[] rot = gesture2FingerScrl.getScrollDistanceXY(); - rotationXYZ[0] = rot[0] / rotationScale; - rotationXYZ[1] = rot[1] / rotationScale; - aType = ACTION_SCROLL; // 8 - rotationSource = 1; - } else { - return new com.jogamp.newt.event.MouseEvent[0]; // skip, but cont. sending events - } - } else { - aType = aType0; - } - nType = aMotionEventType2Newt(aType); - } if( (short)0 != nType ) { final short clickCount = 1; int modifiers = 0; - if( 0 == rotationSource && AndroidVersion.SDK_INT >= 12 && ACTION_SCROLL == aType ) { // API Level 12 - rotationXYZ[0] = event.getAxisValue(android.view.MotionEvent.AXIS_X) / rotationScale; - rotationXYZ[1] = event.getAxisValue(android.view.MotionEvent.AXIS_Y) / rotationScale; - rotationSource = 2; - } - - if( 0 != rotationSource ) { - if( rotationXYZ[0]*rotationXYZ[0] > rotationXYZ[1]*rotationXYZ[1] ) { - // Horizontal - modifiers |= com.jogamp.newt.event.InputEvent.SHIFT_MASK; - } - if(DEBUG_MOUSE_EVENT) { - System.err.println("createMouseEvent: Gesture2FingerScrl Scroll "+rotationXYZ[0]+"/"+rotationXYZ[1]+", "+rotationScale+", mods "+modifiers+", source "+rotationSource); - } - } - // - // Determine newt-button and whether dedicated pointer is pressed + // Determine SDK 12 SCROLL, newt-button and whether dedicated pointer is pressed // final int pIndex; final short button; @@ -361,6 +305,22 @@ public class AndroidNewtEventFactory { } } break; + + case ACTION_SCROLL: + if( AndroidVersion.SDK_INT >= 12 ) { // API Level 12 + rotationXYZ[0] = event.getAxisValue(android.view.MotionEvent.AXIS_X) / rotationScale; + rotationXYZ[1] = event.getAxisValue(android.view.MotionEvent.AXIS_Y) / rotationScale; + + if( rotationXYZ[0]*rotationXYZ[0] > rotationXYZ[1]*rotationXYZ[1] ) { + // Horizontal + modifiers |= com.jogamp.newt.event.InputEvent.SHIFT_MASK; + } + if(DEBUG_MOUSE_EVENT) { + System.err.println("createMouseEvent: SDK-12 Scroll "+rotationXYZ[0]+"/"+rotationXYZ[1]+", "+rotationScale+", mods "+modifiers); + } + } + // Fall through intended! + default: { pIndex = 0; button = com.jogamp.newt.event.MouseEvent.BUTTON1; @@ -378,9 +338,9 @@ public class AndroidNewtEventFactory { final com.jogamp.newt.event.MouseEvent.PointerType[] pointerTypes = new com.jogamp.newt.event.MouseEvent.PointerType[pCount]; if( 0 < pCount ) { if(DEBUG_MOUSE_EVENT) { - System.err.println("createMouseEvent: collect ptr-data [0.."+(pCount-1)+", count "+pCount+", action "+pIndex+"], aType "+aType+", button "+button+", twoFingerScrollGesture "+gesture2FingerScrl); + System.err.println("createMouseEvent: collect ptr-data [0.."+(pCount-1)+", count "+pCount+", action "+pIndex+"], aType "+aType+", button "+button); } - int j = 0; + int j = 0; // Always put action-pointer data at index 0 collectPointerData(event, pIndex, j++, x, y, pressure, pointerIds, pointerTypes); for(int i=0; i < pCount; i++) { @@ -402,202 +362,11 @@ public class AndroidNewtEventFactory { final Object src = (null==newtSource)?null:(Object)newtSource; final long unixTime = System.currentTimeMillis() + ( event.getEventTime() - android.os.SystemClock.uptimeMillis() ); - final com.jogamp.newt.event.MouseEvent me1 = new com.jogamp.newt.event.MouseEvent( - nType, src, unixTime, - modifiers, x, y, pressure, maxPressure, pointerTypes, pointerIds, - clickCount, button, rotationXYZ, rotationScale); - - if( com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_RELEASED == nType ) { - return new com.jogamp.newt.event.MouseEvent[] { me1, - new com.jogamp.newt.event.MouseEvent( - com.jogamp.newt.event.MouseEvent.EVENT_MOUSE_CLICKED, - src, unixTime, modifiers, x, y, pressure, maxPressure, pointerTypes, pointerIds, - clickCount, button, rotationXYZ, rotationScale) }; - } else { - return new com.jogamp.newt.event.MouseEvent[] { me1 }; - } - } + return new com.jogamp.newt.event.MouseEvent(nType, src, unixTime, + modifiers, pointerTypes, pointerIds, x, y, pressure, maxPressure, + button, clickCount, rotationXYZ, rotationScale); + } return null; // no mapping .. } - - static interface GestureHandler { - /** - * Returns true if last on* command produced a gesture, otherwise false. - * @param clear if true, method clears the gesture flag. - */ - public boolean hasGesture(boolean clear); - /** Returns true if the gesture has started */ - public boolean gestureStarted(); - /** Returns distance of the last consecutive double-tab scrolling. */ - public float[] getScrollDistanceXY(); - public void onDown(android.view.MotionEvent e); - public void onUp(android.view.MotionEvent e); - public void onMove(android.view.MotionEvent e); - } - - /** - * Criteria related to Android parameter: - * - ScaledDoubleTapSlop: - * - Max 2 finger distance to start 'scroll' mode - * - * - ScaledTouchSlop: - * - Min. movement w/ 2 pointer withing ScaledDoubleTapSlop starting 'scroll' mode - * - Max. distance growth in respect to initiated 2-finger distance. - * - * - Tolerate temporary lift of 1/2 pointer - * - * - Always validate pointer-id - */ - private final GestureHandler gesture2FingerScrl = new GestureHandler() { - private final float[] scrollDistance = new float[] { 0f, 0f }; - private int[] pIds = new int[] { -1, -1 }; - private int startDist = -1; - private float downY = 0; - private float downX = 0; - private float lastY = 0; - private float lastX = 0; - private int pointerDownCount = 0; - private boolean withinGesture = false; - private boolean hasGesture = false; - - public String toString() { - return "Gesture2FingerScrl[in "+withinGesture+", has "+hasGesture+", pc "+pointerDownCount+"]"; - } - - private void clear() { - downX = 0f; - downY = 0f; - lastX = 0f; - lastY = 0f; - startDist = -1; - withinGesture = false; - hasGesture = false; - pIds[0] = -1; - pIds[1] = -1; - } - - private final int getSquareDistance(float x1, float y1, float x2, float y2) { - final int deltaX = (int) x1 - (int) x2; - final int deltaY = (int) y1 - (int) y2; - return deltaX * deltaX + deltaY * deltaY; - } - - private int gesturePointers(final android.view.MotionEvent e, final int excludeIndex) { - int j = 0; - for(int i=e.getPointerCount()-1; i>=0; i--) { - if( excludeIndex != i ) { - final int id = e.getPointerId(i); - if( pIds[0] == id || pIds[1] == id ) { - j++; - } - } - } - return j; - } - - @Override - public boolean gestureStarted() { - return 0 <= startDist && withinGesture; - } - - @Override - public boolean hasGesture(boolean clear) { - final boolean r = hasGesture; - if( clear ) { - hasGesture = false; - } - return r; - } - - @Override - public final float[] getScrollDistanceXY() { - return scrollDistance; - } - - @Override - public void onDown(android.view.MotionEvent e) { - pointerDownCount = e.getPointerCount(); - final int gPtr = gesturePointers(e, -1); - if( 2 <= gPtr ) { // pick-up dLast coordinate to cont. gesture after temp loosing 1/2 pointers - lastX = e.getX(0); - lastY = e.getY(0); - } - if(DEBUG_MOUSE_EVENT) { - System.err.println(this+".onDown: gPtr "+gPtr+", "+e); - } - } - - @Override - public void onUp(android.view.MotionEvent e) { - pointerDownCount = e.getPointerCount(); - final int gPtr = gesturePointers(e, e.getActionIndex()); // w/o lifted pointer - if( 1 > gPtr ) { // tolerate lifting 1/2 gesture pointers temporary - clear(); - } - pointerDownCount--; // lifted now! - if(DEBUG_MOUSE_EVENT) { - System.err.println(this+".onUp: gPtr "+gPtr+", "+e); - } - } - - @Override - public void onMove(android.view.MotionEvent e) { - pointerDownCount = e.getPointerCount(); - if( 2 <= pointerDownCount ) { - final float x0 = e.getX(0); - final float y0 = e.getY(0); - final int sqDist = getSquareDistance(x0, y0, e.getX(1), e.getY(1)); - final boolean isDistWithinDoubleTapSlop = sqDist < doubleTapSlopSquare; - final int dist = (int)Math.sqrt(sqDist); - if( !withinGesture ) { - int gPtr = 0; - if( isDistWithinDoubleTapSlop ) { - if( 0 > startDist ) { - gPtr = 2; - pIds[0] = e.getPointerId(0); - pIds[1] = e.getPointerId(1); - downX = x0; - downY = y0; - lastX = x0; - lastY = y0; - startDist = dist; - } else { - gPtr = gesturePointers(e, -1); - if( 2 <= gPtr ) { - final int dX = (int) (x0 - downX); - final int dY = (int) (y0 - downY); - final int d = (dX * dX) + (dY * dY); - withinGesture = d > touchSlopSquare; - } - } - } - if(DEBUG_MOUSE_EVENT) { - final double dX = x0 - downX; - final double dY = y0 - downY; - final double d = Math.sqrt( (dX * dX) + (dY * dY) ); - System.err.println(this+".onMove.0: mDist "+d+", pStartDist "+dist+", gPtr "+gPtr+", distWithin2DTSlop "+isDistWithinDoubleTapSlop+", dLast "+lastX+"/"+lastY+", "+e); - } - } - if( withinGesture ) { - final int gPtr = gesturePointers(e, -1); - final boolean isDistGrowthWithinTouchSlop = dist - startDist <= touchSlop; - if( 2 > gPtr || !isDistGrowthWithinTouchSlop ) { - clear(); - } else { - scrollDistance[0] = lastX - x0; - scrollDistance[1] = lastY - y0; - lastX = x0; - lastY = y0; - hasGesture = true; - } - if(DEBUG_MOUSE_EVENT) { - System.err.println(this+".onMove.1: pStartDist "+startDist+", pDist "+dist+", gPtr "+gPtr+" ["+pIds[0]+", "+pIds[1]+"]"+ - ", distWithin2DTSlop "+isDistWithinDoubleTapSlop+", distGrowthWithinTSlop "+isDistGrowthWithinTouchSlop+ - ", dLast "+lastX+"/"+lastY+", d "+scrollDistance[0]+"/"+scrollDistance[1]+", "+e); - } - } - } - } - }; } diff --git a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventTranslator.java b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventTranslator.java index 93735863e..df52208df 100644 --- a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventTranslator.java +++ b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventTranslator.java @@ -13,13 +13,16 @@ public class AndroidNewtEventTranslator implements View.OnKeyListener, View.OnTo } private final boolean processTouchMotionEvents(View v, android.view.MotionEvent event, boolean isOnTouchEvent) { - final com.jogamp.newt.event.MouseEvent[] newtEvents = factory.createMouseEvents(isOnTouchEvent, event, newtWindow); - if(null != newtEvents) { - newtWindow.focusChanged(false, true); - for(int i=0; i