summaryrefslogtreecommitdiffstats
path: root/src/newt/classes/com/jogamp
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2013-10-15 15:36:03 +0200
committerSven Gothel <[email protected]>2013-10-15 15:36:03 +0200
commitbc72e232a4b74c2be8c91c540a7b6153bfefb8c0 (patch)
treea96ef243eb96fad1ca9d304eae6b3479c8d1facd /src/newt/classes/com/jogamp
parent5ac6d508c7208ac4fe5d057a9ea1bdcba5f7b998 (diff)
Bug 861 - NEWT: Unify MouseEvent Processing incl. gesture processing
We processed MouseEvents within NEWT as follows: sendMouseEvent/enqueueMouseEvent -> doMouseEvent, - called by native code to be delivered via consumeMouseEvent (now or later) - events are validated (move/drag, boundaries) - missing events are synthesized (click, enter, ..) as well as in several factories, i.e.: - AWTNewtEventFactory (1:1) - AndroidNewtEventFactory - synthesized events .. (click, ..) - android typed gesture detection (drag -> 1 finger scroll..) The latter enqueues events do Window/Display directly to be consumed by WindowImpl. Then users may have their own gesture detection etc. +++ This change unifies mouse/pointer event processing within NEWT within consumeEvent(..) which represents a common entry point. Gesture processing is now realized w/ a public API - GestureHandler - GestureHandler.GestureListener - GestureHandler.GesureEvent which supplies: - default impl. of optional gesture handlers (scroll, .. - default: enabled) - public API to add/remove gesture-handler and -listener +++ This allows our impl. to scale better in support of more multiple pointer devices (-> Win7/Win8, X11, ..).
Diffstat (limited to 'src/newt/classes/com/jogamp')
-rw-r--r--src/newt/classes/com/jogamp/newt/Window.java62
-rw-r--r--src/newt/classes/com/jogamp/newt/event/DoubleTapScrollGesture.java346
-rw-r--r--src/newt/classes/com/jogamp/newt/event/GestureHandler.java143
-rw-r--r--src/newt/classes/com/jogamp/newt/event/InputEvent.java8
-rw-r--r--src/newt/classes/com/jogamp/newt/event/MouseEvent.java238
-rw-r--r--src/newt/classes/com/jogamp/newt/event/NEWTEvent.java1
-rw-r--r--src/newt/classes/com/jogamp/newt/event/PinchToZoomGesture.java217
-rw-r--r--src/newt/classes/com/jogamp/newt/opengl/GLWindow.java34
8 files changed, 997 insertions, 52 deletions
diff --git a/src/newt/classes/com/jogamp/newt/Window.java b/src/newt/classes/com/jogamp/newt/Window.java
index 8a43ef153..02727353f 100644
--- a/src/newt/classes/com/jogamp/newt/Window.java
+++ b/src/newt/classes/com/jogamp/newt/Window.java
@@ -30,12 +30,14 @@ package com.jogamp.newt;
import java.util.List;
+import com.jogamp.newt.event.GestureHandler;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.KeyListener;
import com.jogamp.newt.event.KeyEvent;
import com.jogamp.newt.event.InputEvent;
import com.jogamp.newt.event.MouseListener;
+
import jogamp.newt.Debug;
import jogamp.newt.WindowImpl;
@@ -553,15 +555,12 @@ public interface Window extends NativeWindow, WindowClosingProtocol {
//
/**
- *
- * Appends the given {@link com.jogamp.newt.event.MouseListener} to the end of
- * the list.
+ * Appends the given {@link MouseListener} to the end of the list.
*/
void addMouseListener(MouseListener l);
/**
- *
- * Inserts the given {@link com.jogamp.newt.event.MouseListener} at the
+ * Inserts the given {@link MouseListener} at the
* specified position in the list.<br>
*
* @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.<br>
+ *
+ * @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.<br>
+ *
+ * @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.
+ * <p>
+ * Criteria related to parameters:
+ * <pre>
+ * - 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
+ * </pre>
+ * </p>
+ * Implementation uses a n-state to get detect gesture:
+ * <p>
+ * <table border="1">
+ * <tr><th>from</th> <th>to</th> <th>action</th></tr>
+ * <tr><td>NONE</td> <td>1PRESS</td> <td>1-pointer-pressed</td></tr>
+ * <tr><td>1PRESS</td> <td>2PRESS_T</td> <td>2-pointer-pressed within doubleTapSlope</td></tr>
+ * <tr><td>2PRESS_T</td> <td>SCROLL</td> <td>2-pointer dragged, dist-diff within doubleTapSlop and scrollLen >= scrollSlop</td></tr>
+ * <tr><td>2PRESS_C</td> <td>SCROLL</td> <td>2-pointer dragged, dist-diff within doubleTapSlop</td></tr>
+ * <tr><td>SCROLL</td> <td>SCROLL</td> <td>2-pointer dragged, dist-diff within doubleTapSlop</td></tr>
+ * </table>
+ * State ST_2PRESS_C merely exist to pick up gesture after one pointer has been lost temporarily.
+ * </p>
+ * <p>
+ * {@link #isWithinGesture()} returns gestureState >= 2PRESS_C
+ * </p>
+ */
+public class DoubleTapScrollGesture implements GestureHandler {
+ /** Scroll threshold in pixels (fallback), defaults to 16 pixels. Can be overriden by integer property <code>newt.event.scroll_slop_pixel</code>.*/
+ 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 <code>newt.event.double_tap_slop_pixel</code>.*/
+ public static final int DOUBLE_TAP_SLOP_PIXEL;
+
+ /** Scroll threshold in millimeter, defaults to 3 mm. Can be overriden by integer property <code>newt.event.scroll_slop_mm</code>.*/
+ public static final float SCROLL_SLOP_MM;
+ /** Two pointer 'double tap' slop in millimeter, defaults to 20 mm. Can be overriden by integer property <code>newt.event.double_tap_slop_mm</code>.*/
+ 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 <i>pixels</i>.
+ * @param scaledDoubleTapSlop Distance in <i>pixels</i> 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.
+ * <p>
+ * To avoid negative impact on event processing,
+ * implementation shall restrict computation as much as possible
+ * and only within it's appropriate gesture states.
+ * </p>
+ * <p>
+ * To allow custom user events, other than the <i>normal</i> {@link InputEvent}s,
+ * a user may return a {@link GestureEvent} in it's implementation.
+ * </p>
+ */
+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 <i>event_type</i> !
+ * @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.
+ * <p>
+ * Only implemented for gestures mapping to {@link InputEvent}s.
+ * </p>
+ */
+ 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.
+ * <p>
+ * If a gesture was already detected previously and has not been cleared,
+ * method does not process the event and returns true.
+ * </p>
+ * <p>
+ * Besides validation of the event's details,
+ * the handler may also validate the {@link InputEvent.InputClass} and/or {@link InputEvent.InputType}.
+ * </p>
+ */
+ 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
* </p>
* <p>
- * In case an instance represents multi-touch events, i.e. {@link #getPointerCount()} is &gt; 1,
- * the first data element represents the pointer which triggered the action if individual to one pointer.<br/>
+ * In case an instance represents a multiple-pointer event, i.e. {@link #getPointerCount()} is &gt; 1,
+ * the first data element of the multiple-pointer fields represents the pointer which triggered the action.<br/>
* For example {@link #getX(int) e.getX(0)} at {@link #EVENT_MOUSE_PRESSED} returns the data of the pressed pointer, etc.
* </p>
*/
@@ -49,12 +49,12 @@ package com.jogamp.newt.event;
public class MouseEvent extends InputEvent
{
/** Class of pointer types */
- public static enum PointerClass{
+ public static enum PointerClass implements InputEvent.InputClass {
Offscreen, Onscreen, Undefined;
}
/** Type of pointer devices */
- public static enum PointerType{
+ public static enum PointerType implements InputEvent.InputType {
/** {@link PointerClass#Offscreen} mouse. */
Mouse(PointerClass.Offscreen),
/** {@link PointerClass#Offscreen} touch pad, usually using fingers. */
@@ -110,7 +110,7 @@ public class MouseEvent extends InputEvent
return 300;
}
- /** Constructor for tradition 1-pointer mouse events. */
+ /** Constructor for traditional one-pointer event. */
public MouseEvent(short eventType, Object source, long when,
int modifiers, int x, int y, short clickCount, short button,
float[] rotationXYZ, float rotationScale)
@@ -120,25 +120,48 @@ public class MouseEvent extends InputEvent
this.y = new int[]{y};
this.pressure = constMousePressure;
this.maxPressure= 1.0f;
- this.pointerIDs = constMousePointerIDs;
+ this.pointerID = constMousePointerIDs;
this.clickCount=clickCount;
this.button=button;
this.rotationXYZ = rotationXYZ;
this.rotationScale = rotationScale;
- this.pointerTypes = constMousePointerTypes;
+ this.pointerType = constMousePointerTypes;
}
- /** Constructor for multi-touch pointer events. */
- public MouseEvent(short eventType, Object source, long when,
- int modifiers, int[] x, int[] y, float[] pressure, float maxPressure, PointerType pointerTypes[], short[] pointerids, short clickCount,
- short button, float[] rotationXYZ, float rotationScale)
+ /**
+ * Constructor for a multiple-pointer event.
+ * <p>
+ * First element of multiple-pointer arrays represents the pointer which triggered the event!
+ * </p>
+ *
+ * @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.
+ * <p>
+ * The index for the element of multiple-pointer arrays represents the pointer which triggered the event
+ * is passed via <i>actionIdx</i>.
+ * </p>
+ *
+ * @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. <code>pressure / maxPressure</code>
* @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.
* <p>
@@ -239,7 +376,7 @@ public class MouseEvent extends InputEvent
* </ul>
* </p>
*/
- 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.
* </p>
*/
- public float[] getRotation() {
+ public final float[] getRotation() {
return rotationXYZ;
}
@@ -311,15 +448,15 @@ public class MouseEvent extends InputEvent
* Hence <code>scale * rotation</code> reproduces the screen distance in pixels the finger[s] have moved.
* </p>
*/
- 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; i<pointerIDs.length; i++) {
+ if(pointerID.length>0) {
+ sb.append(", pointer<").append(pointerID.length).append(">[");
+ for(int i=0; i<pointerID.length; i++) {
if(i>0) {
sb.append(", ");
}
- sb.append(pointerIDs[i]).append("/").append(pointerTypes[i]).append(": ")
+ sb.append(pointerID[i]).append("/").append(pointerType[i]).append(": ")
.append(x[i]).append("/").append(y[i]).append(", ")
.append("p[").append(pressure[i]).append("/").append(maxPressure).append("=").append(pressure[i]/maxPressure).append("]");
}
@@ -356,15 +493,24 @@ public class MouseEvent extends InputEvent
default: return "unknown (" + type + ")";
}
}
- private final int x[], y[];
+
+ /** PointerType for each pointer (multiple pointer) */
+ private final PointerType pointerType[];
+ /** Pointer-ID for each pointer (multiple pointer) */
+ private final short pointerID[];
+ /** X-axis for each pointer (multiple pointer) */
+ private final int x[];
+ /** Y-axis for each pointer (multiple pointer) */
+ private final int y[];
+ /** Pressure for each pointer (multiple pointer) */
+ private final float pressure[];
// private final short tiltX[], tiltY[]; // TODO: A generic way for pointer axis information, see Android MotionEvent!
private final short clickCount, button;
+ /** Rotation around the X, Y and X axis */
private final float[] rotationXYZ;
+ /** Rotation scale */
private final float rotationScale;
- private final float pressure[];
private final float maxPressure;
- private final short pointerIDs[];
- private final PointerType pointerTypes[];
private static final float[] constMousePressure = new float[]{0f};
private static final short[] constMousePointerIDs = new short[]{0};
diff --git a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java
index c1bc791d8..02bb4f929 100644
--- a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java
+++ b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java
@@ -44,6 +44,7 @@ package com.jogamp.newt.event;
* <li> WindowEvent <code>100..10x</code></li>
* <li> MouseEvent <code>200..20x</code></li>
* <li> KeyEvent <code>300..30x</code></li>
+ * <li> GestureEvent <code>400..5xx</code></li>
* <li> MonitorEvent <code>600..60x</code></li>
* </ul><br>
*/
diff --git a/src/newt/classes/com/jogamp/newt/event/PinchToZoomGesture.java b/src/newt/classes/com/jogamp/newt/event/PinchToZoomGesture.java
new file mode 100644
index 000000000..3a34c6253
--- /dev/null
+++ b/src/newt/classes/com/jogamp/newt/event/PinchToZoomGesture.java
@@ -0,0 +1,217 @@
+/**
+ * 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 javax.media.nativewindow.NativeSurface;
+
+import jogamp.newt.Debug;
+
+/**
+ * 2 pointer zoom, a.k.a. <i>pinch to zoom</i>, gesture handler processing {@link MouseEvent}s
+ * while producing {@link ZoomEvent}s if gesture is completed.
+ * <p>
+ * Zoom value lies within [0..2], with 1 as <i>1:1</i>.
+ * </p>
+ * <pre>
+ * - choosing the smallest surface edge (width/height -> x/y)
+ * - tolerating other fingers to be pressed and hence user to add functionality (scale, ..)
+ * </pre>
+ */
+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 <i>1:1</i>. */
+ 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 <i>1:1</i>. */
+ public final float getZoom() {
+ return zoom;
+ }
+ /** Set zoom value within [0..2], with 1 as <i>1:1</i>. */
+ 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
//