diff options
author | Sven Gothel <[email protected]> | 2013-10-15 15:36:03 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2013-10-15 15:36:03 +0200 |
commit | bc72e232a4b74c2be8c91c540a7b6153bfefb8c0 (patch) | |
tree | a96ef243eb96fad1ca9d304eae6b3479c8d1facd /src/newt | |
parent | 5ac6d508c7208ac4fe5d057a9ea1bdcba5f7b998 (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')
11 files changed, 1487 insertions, 433 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 > 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 > 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 // 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<NativeWindow> childWindows = new ArrayList<NativeWindow>(); private ArrayList<MouseListener> mouseListeners = new ArrayList<MouseListener>(); - private short mouseButtonPressed = (short)0; // current pressed mouse button number - private int mouseButtonModMask = 0; // current pressed mouse button modifier mask - private long lastMousePressed = 0; // last time when a mouse button was pressed - private short lastMouseClickCount = (short)0; // last mouse button click count - private boolean mouseInWindow = false;// mouse entered window - is inside the window (may be synthetic) - private Point lastMousePosition = new Point(); - + + private static class PointerState0 { + /** current pressed mouse button number */ + private short buttonPressed = (short)0; + /** current pressed mouse button modifier mask */ + private int buttonModMask = 0; + /** last time when a mouse button was pressed */ + private long lastButtonPressTime = 0; + /** last mouse button click count */ + private short lastButtonClickCount = (short)0; + /** mouse entered window - is inside the window (may be synthetic) */ + private boolean insideWindow = false; + /** last mouse-move position */ + private Point lastMovePosition = new Point(); + } + private static class PointerState1 { + /** current pressed mouse button number */ + private short buttonPressed = (short)0; + /** last time when a mouse button was pressed */ + private long lastButtonPressTime = 0; + /** mouse entered window - is inside the window (may be synthetic) */ + private boolean insideWindow = false; + /** last mouse-move position */ + private Point lastMovePosition = new Point(); + } + /** doMouseEvent */ + private PointerState0 pState0 = new PointerState0(); + /** consumeMouseEvent */ + private PointerState1 pState1 = new PointerState1(); + private boolean defaultGestureHandlerEnabled = true; + private DoubleTapScrollGesture gesture2PtrTouchScroll = null; + private ArrayList<GestureHandler> pointerGestureHandler = new ArrayList<GestureHandler>(); + + private ArrayList<GestureHandler.GestureListener> gestureListeners = new ArrayList<GestureHandler.GestureListener>(); + private ArrayList<KeyListener> keyListeners = new ArrayList<KeyListener>(); private ArrayList<WindowListener> windowListeners = new ArrayList<WindowListener>(); @@ -1754,6 +1786,10 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer for (int i = 0; i < mouseListeners.size(); i++ ) { sb.append(mouseListeners.get(i)+", "); } + sb.append("], PointerGestures default "+defaultGestureHandlerEnabled+", custom "+pointerGestureHandler.size()+" ["); + for (int i = 0; i < pointerGestureHandler.size(); i++ ) { + sb.append(pointerGestureHandler.get(i)+", "); + } sb.append("], KeyListeners num "+keyListeners.size()+" ["); for (int i = 0; i < keyListeners.size(); i++ ) { sb.append(keyListeners.get(i)+", "); @@ -2166,7 +2202,6 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer private final MonitorModeListenerImpl monitorModeListenerImpl = new MonitorModeListenerImpl(); - //---------------------------------------------------------------------- // Child Window Management // @@ -2286,17 +2321,22 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer // // MouseListener/Event Support // + + // + // Native MouseEvents pre-processed to be enqueued or consumed directly + // + public final void sendMouseEvent(short eventType, int modifiers, int x, int y, short button, float rotation) { - doMouseEvent(false, false, eventType, modifiers, x, y, button, rotation); + doMouseEvent(false, false, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } public final void enqueueMouseEvent(boolean wait, short eventType, int modifiers, int x, int y, short button, float rotation) { - doMouseEvent(true, wait, eventType, modifiers, x, y, button, rotation); + doMouseEvent(true, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } protected final void doMouseEvent(boolean enqueue, boolean wait, short eventType, int modifiers, int x, int y, short button, float rotation) { - this.doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); + doMouseEvent(enqueue, wait, eventType, modifiers, x, y, button, MouseEvent.getRotationXYZ(rotation, modifiers), 1f); } /** public final void sendMouseEvent(short eventType, int modifiers, @@ -2309,57 +2349,73 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } */ protected void doMouseEvent(boolean enqueue, boolean wait, short eventType, int modifiers, int x, int y, short button, float[] rotationXYZ, float rotationScale) { - if( eventType == MouseEvent.EVENT_MOUSE_ENTERED || eventType == MouseEvent.EVENT_MOUSE_EXITED ) { - if( eventType == MouseEvent.EVENT_MOUSE_EXITED && x==-1 && y==-1 ) { - x = lastMousePosition.getX(); - y = lastMousePosition.getY(); - } - // clip coordinates to window dimension - x = Math.min(Math.max(x, 0), getWidth()-1); - y = Math.min(Math.max(y, 0), getHeight()-1); - mouseInWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED; - // clear states - lastMousePressed = 0; - lastMouseClickCount = (short)0; - mouseButtonPressed = 0; - mouseButtonModMask = 0; - } - if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) { - return; // .. invalid .. - } - if(DEBUG_MOUSE_EVENT) { - System.err.println("doMouseEvent: enqueue "+enqueue+", wait "+wait+", "+MouseEvent.getEventTypeString(eventType)+ - ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+lastMousePosition); + if( 0 > button || button > MouseEvent.BUTTON_NUMBER ) { + throw new NativeWindowException("Invalid mouse button number" + button); } + + // + // Remove redundant events, determine ENTERED state and reset states if applicable + // final long when = System.currentTimeMillis(); - MouseEvent eEntered = null; - if(eventType == MouseEvent.EVENT_MOUSE_MOVED) { - if(!mouseInWindow) { - mouseInWindow = true; - eEntered = new MouseEvent(MouseEvent.EVENT_MOUSE_ENTERED, this, when, - modifiers, x, y, (short)0, (short)0, rotationXYZ, rotationScale); + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_EXITED: + if( x==-1 && y==-1 ) { + x = pState0.lastMovePosition.getX(); + y = pState0.lastMovePosition.getY(); + } + // Fall through intended! + + case MouseEvent.EVENT_MOUSE_ENTERED: + // clip coordinates to window dimension + x = Math.min(Math.max(x, 0), getWidth()-1); + y = Math.min(Math.max(y, 0), getHeight()-1); + pState0.insideWindow = eventType == MouseEvent.EVENT_MOUSE_ENTERED; // clear states - lastMousePressed = 0; - lastMouseClickCount = (short)0; - mouseButtonPressed = 0; - mouseButtonModMask = 0; - } else if( lastMousePosition.getX() == x && lastMousePosition.getY()==y ) { - if(DEBUG_MOUSE_EVENT) { - System.err.println("doMouseEvent: skip EVENT_MOUSE_MOVED w/ same position: "+lastMousePosition); + pState0.lastButtonPressTime = 0; + pState0.lastButtonClickCount = (short)0; + pState0.buttonPressed = 0; + pState0.buttonModMask = 0; + break; + + case MouseEvent.EVENT_MOUSE_MOVED: + case MouseEvent.EVENT_MOUSE_DRAGGED: + if( pState0.insideWindow && pState0.lastMovePosition.getX() == x && pState0.lastMovePosition.getY() == y ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("doMouseEvent: skip EVENT_MOUSE_MOVED w/ same position: "+pState0.lastMovePosition); + } + return; // skip same position } - return; // skip same position + pState0.lastMovePosition.setX(x); + pState0.lastMovePosition.setY(y); + + // Fall through intended ! + + default: + if(!pState0.insideWindow) { + pState0.insideWindow = true; + + // clear states + pState0.lastButtonPressTime = 0; + pState0.lastButtonClickCount = (short)0; + pState0.buttonPressed = 0; + pState0.buttonModMask = 0; + } + } + + if( x < 0 || y < 0 || x >= getWidth() || y >= getHeight() ) { + if(DEBUG_MOUSE_EVENT) { + System.err.println("doMouseEvent: drop: "+MouseEvent.getEventTypeString(eventType)+ + ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+pState0.lastMovePosition); } - lastMousePosition.setX(x); - lastMousePosition.setY(y); + return; // .. invalid .. } - if( 0 > button || button > MouseEvent.BUTTON_NUMBER ) { - throw new NativeWindowException("Invalid mouse button number" + button); + if(DEBUG_MOUSE_EVENT) { + System.err.println("doMouseEvent: enqueue "+enqueue+", wait "+wait+", "+MouseEvent.getEventTypeString(eventType)+ + ", mod "+modifiers+", pos "+x+"/"+y+", button "+button+", lastMousePosition: "+pState0.lastMovePosition); } + modifiers |= InputEvent.getButtonMask(button); // Always add current button to modifier mask (Bug 571) - modifiers |= mouseButtonModMask; // Always add currently pressed mouse buttons to modifier mask - - MouseEvent eClicked = null; - MouseEvent e = null; + modifiers |= pState0.buttonModMask; // Always add currently pressed mouse buttons to modifier mask if( isPointerConfined() ) { modifiers |= InputEvent.CONFINED_MASK; @@ -2368,64 +2424,86 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer modifiers |= InputEvent.INVISIBLE_MASK; } - if( MouseEvent.EVENT_MOUSE_PRESSED == eventType ) { - if( when - lastMousePressed < MouseEvent.getClickTimeout() ) { - lastMouseClickCount++; - } else { - lastMouseClickCount=(short)1; - } - lastMousePressed = when; - mouseButtonPressed = button; - mouseButtonModMask |= MouseEvent.getButtonMask(button); - e = new MouseEvent(eventType, this, when, - modifiers, x, y, lastMouseClickCount, button, rotationXYZ, rotationScale); - } else if( MouseEvent.EVENT_MOUSE_RELEASED == eventType ) { - e = new MouseEvent(eventType, this, when, - modifiers, x, y, lastMouseClickCount, button, rotationXYZ, rotationScale); - if( when - lastMousePressed < MouseEvent.getClickTimeout() ) { - eClicked = new MouseEvent(MouseEvent.EVENT_MOUSE_CLICKED, this, when, - modifiers, x, y, lastMouseClickCount, button, rotationXYZ, rotationScale); - } else { - lastMouseClickCount = (short)0; - lastMousePressed = 0; - } - mouseButtonPressed = 0; - mouseButtonModMask &= ~MouseEvent.getButtonMask(button); - } else if( MouseEvent.EVENT_MOUSE_MOVED == eventType ) { - if ( mouseButtonPressed > 0 ) { - e = new MouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, this, when, - modifiers, x, y, (short)1, mouseButtonPressed, rotationXYZ, rotationScale); - } else { + final MouseEvent e; + + switch( eventType ) { + case MouseEvent.EVENT_MOUSE_PRESSED: + if( when - pState0.lastButtonPressTime < MouseEvent.getClickTimeout() ) { + pState0.lastButtonClickCount++; + } else { + pState0.lastButtonClickCount=(short)1; + } + pState0.lastButtonPressTime = when; + pState0.buttonPressed = button; + pState0.buttonModMask |= MouseEvent.getButtonMask(button); e = new MouseEvent(eventType, this, when, - modifiers, x, y, (short)0, button, rotationXYZ, rotationScale); - } - } else if( MouseEvent.EVENT_MOUSE_WHEEL_MOVED == eventType ) { - e = new MouseEvent(eventType, this, when, modifiers, x, y, (short)0, button, rotationXYZ, rotationScale); - } else { - e = new MouseEvent(eventType, this, when, modifiers, x, y, (short)0, button, rotationXYZ, rotationScale); - } - if( null != eEntered ) { - if(DEBUG_MOUSE_EVENT) { - System.err.println("doMouseEvent: synthesized MOUSE_ENTERED event: "+eEntered); - } - doEvent(enqueue, wait, eEntered); + modifiers, x, y, pState0.lastButtonClickCount, button, rotationXYZ, rotationScale); + break; + case MouseEvent.EVENT_MOUSE_RELEASED: + e = new MouseEvent(eventType, this, when, + modifiers, x, y, pState0.lastButtonClickCount, button, rotationXYZ, rotationScale); + if( when - pState0.lastButtonPressTime >= MouseEvent.getClickTimeout() ) { + pState0.lastButtonClickCount = (short)0; + pState0.lastButtonPressTime = 0; + } + pState0.buttonPressed = 0; + pState0.buttonModMask &= ~MouseEvent.getButtonMask(button); + break; + case MouseEvent.EVENT_MOUSE_MOVED: + if ( pState0.buttonPressed > 0 ) { + e = new MouseEvent(MouseEvent.EVENT_MOUSE_DRAGGED, this, when, + modifiers, x, y, (short)1, pState0.buttonPressed, rotationXYZ, rotationScale); + } else { + e = new MouseEvent(eventType, this, when, + modifiers, x, y, (short)0, button, rotationXYZ, rotationScale); + } + break; + default: + e = new MouseEvent(eventType, this, when, modifiers, x, y, (short)0, button, rotationXYZ, rotationScale); } doEvent(enqueue, wait, e); // actual mouse event - if( null != eClicked ) { - if(DEBUG_MOUSE_EVENT) { - System.err.println("doMouseEvent: synthesized MOUSE_CLICKED event: "+eClicked); - } - doEvent(enqueue, wait, eClicked); - } } + /** + * Send multiple-pointer event directly to be consumed + * <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 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<MouseListener> clonedListeners = (ArrayList<MouseListener>) mouseListeners.clone(); if(0>index) { @@ -2460,14 +2538,273 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } @Override - public MouseListener[] getMouseListeners() { + public final MouseListener[] getMouseListeners() { return mouseListeners.toArray(new MouseListener[mouseListeners.size()]); } - protected void consumeMouseEvent(MouseEvent e) { + @Override + public final void setDefaultGesturesEnabled(boolean enable) { + defaultGestureHandlerEnabled = enable; + } + @Override + public final boolean areDefaultGesturesEnabled() { + return defaultGestureHandlerEnabled; + } + + @Override + public final void addGestureHandler(GestureHandler gh) { + addGestureHandler(-1, gh); + } + @Override + public final void addGestureHandler(int index, GestureHandler gh) { + if(gh == null) { + return; + } + @SuppressWarnings("unchecked") + ArrayList<GestureHandler> cloned = (ArrayList<GestureHandler>) pointerGestureHandler.clone(); + if(0>index) { + index = cloned.size(); + } + cloned.add(index, gh); + pointerGestureHandler = cloned; + } + @Override + public final void removeGestureHandler(GestureHandler gh) { + if (gh == null) { + return; + } + @SuppressWarnings("unchecked") + ArrayList<GestureHandler> cloned = (ArrayList<GestureHandler>) pointerGestureHandler.clone(); + cloned.remove(gh); + pointerGestureHandler = cloned; + } + @Override + public final void addGestureListener(GestureHandler.GestureListener gl) { + addGestureListener(-1, gl); + } + @Override + public final void addGestureListener(int index, GestureHandler.GestureListener gl) { + if(gl == null) { + return; + } + @SuppressWarnings("unchecked") + ArrayList<GestureHandler.GestureListener> cloned = (ArrayList<GestureHandler.GestureListener>) gestureListeners.clone(); + if(0>index) { + index = cloned.size(); + } + cloned.add(index, gl); + gestureListeners = cloned; + } + @Override + public final void removeGestureListener(GestureHandler.GestureListener gl) { + if (gl == null) { + return; + } + @SuppressWarnings("unchecked") + ArrayList<GestureHandler.GestureListener> cloned = (ArrayList<GestureHandler.GestureListener>) gestureListeners.clone(); + cloned.remove(gl); + gestureListeners= cloned; + } + + private static int step(int lower, int edge, int value) { + return value < edge ? lower : value; + } + + /** + * Consume the {@link MouseEvent}, i.e. + * <pre> + * - validate + * - handle gestures + * - synthesize events if applicable (like gestures) + * - dispatch event to listener + * </pre> + */ + 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<newtEvents.length; i++) { - newtWindow.enqueueEvent(false, newtEvents[i]); + final com.jogamp.newt.event.MouseEvent newtEvent = factory.createMouseEvents(isOnTouchEvent, event, newtWindow); + if(null != newtEvent) { + switch( event.getActionMasked() ) { + case android.view.MotionEvent.ACTION_DOWN: + case android.view.MotionEvent.ACTION_POINTER_DOWN: + newtWindow.focusChanged(false, true); + break; } - try { Thread.sleep((long) (1000.0F/30.0F)); } + newtWindow.enqueueEvent(false, newtEvent); + try { Thread.sleep((long) (100.0F/3.0F)); } // 33 ms catch(InterruptedException e) { } return true; // consumed/handled, further interest in events } |