JOGL v2.6.0-rc-20250706
JOGL, High-Performance Graphics Binding for Java™ (public API).
PinchToZoomGesture.java
Go to the documentation of this file.
1/**
2 * Copyright 2013 JogAmp Community. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification, are
5 * permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice, this list of
8 * conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 * of conditions and the following disclaimer in the documentation and/or other materials
12 * provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
15 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 *
24 * The views and conclusions contained in the software and documentation are those of the
25 * authors and should not be interpreted as representing official policies, either expressed
26 * or implied, of JogAmp Community.
27 */
28package com.jogamp.newt.event;
29
30import com.jogamp.nativewindow.NativeSurface;
31
32import com.jogamp.newt.event.MouseEvent.PointerClass;
33
34import jogamp.newt.Debug;
35
36/**
37 * 2 pointer zoom, a.k.a. <i>pinch to zoom</i>, gesture handler processing {@link MouseEvent}s
38 * while producing {@link ZoomEvent}s if gesture is completed.
39 * <p>
40 * Zoom value lies within [0..2], with 1 as <i>1:1</i>.
41 * </p>
42 * <pre>
43 * - choosing the smallest surface edge (width/height -> x/y)
44 * - tolerating other fingers to be pressed and hence user to add functionality (scale, ..)
45 * </pre>
46 */
47public class PinchToZoomGesture implements GestureHandler {
48 public static final boolean DEBUG = Debug.debug("Window.MouseEvent");
49
50 /** A {@link GestureHandler.GestureEvent} denominating zoom. */
51 @SuppressWarnings("serial")
52 public static class ZoomEvent extends GestureEvent {
53 private final float zoom;
54 private final float delta;
55 private final float scale;
56 public ZoomEvent(final Object source, final long when, final int modifiers, final GestureHandler handler, final MouseEvent pe,
57 final float zoom, final float delta, final float scale) {
58 super(source, when, modifiers, handler, pe);
59 this.zoom = zoom;
60 this.delta = delta;
61 this.scale = scale;
62 }
63 /** Zoom value lies within [0..2], with 1 as <i>1:1</i>. */
64 public final float getZoom() { return zoom; }
65 /** Delta to last zoom value lies within [-1..1]. */
66 public final float getDelta() { return delta; }
67 /**
68 * Returns the scale used to determine the {@link #getZoom() zoom}
69 * and hence it's {@link #getDelta() delta} value,
70 * which semantics depends on the {@link #getPointerType() pointer type's} {@link PointerClass}.
71 * <p>
72 * For {@link PointerClass#Offscreen}, the scale is usually <code>1.0f</code> and denominates
73 * an abstract value without association to a physical value.
74 * </p>
75 * <p>
76 * For {@link PointerClass#Onscreen}, the scale varies and denominates
77 * the divisor of the distance the finger[s] have moved on the screen.
78 * Hence <code>scale * delta</code> reproduces the screen distance in pixels the finger[s] have moved.
79 * </p>
80 */
81 public final float getScale() { return scale; }
82
83 public final String toString() {
84 return "ZoomEvent[zoom "+zoom+", delta "+delta+", scale "+scale+", trigger "+getTrigger()+", handler "+getHandler()+"]";
85 }
86 }
87
88 private final NativeSurface surface;
89 private final boolean allowMorePointer;
90 private float zoom;
91 private int zoomLastEdgeDist;
92 private boolean zoomFirstTouch;
93 private boolean zoomMode;
94 private ZoomEvent zoomEvent;
95 private final short[] pIds = new short[] { -1, -1 };
96
97 /**
98 * @param surface the {@link NativeSurface}, which size is used to compute the relative zoom factor
99 * @param allowMorePointer if false, allow only 2 pressed pointers (safe and recommended), otherwise accept other pointer to be pressed.
100 */
101 public PinchToZoomGesture(final NativeSurface surface, final boolean allowMorePointer) {
102 clear(true);
103 this.surface = surface;
104 this.allowMorePointer = allowMorePointer;
105 this.zoom = 1f;
106 }
107
108 @Override
109 public String toString() {
110 return "PinchZoom[1stTouch "+zoomFirstTouch+", in "+isWithinGesture()+", has "+(null!=zoomEvent)+", zoom "+zoom+"]";
111 }
112
113 private int gesturePointers(final MouseEvent e, final int excludeIndex) {
114 int j = 0;
115 for(int i=e.getPointerCount()-1; i>=0; i--) {
116 if( excludeIndex != i ) {
117 final int id = e.getPointerId(i);
118 if( pIds[0] == id || pIds[1] == id ) {
119 j++;
120 }
121 }
122 }
123 return j;
124 }
125
126 @Override
127 public void clear(final boolean clearStarted) {
128 zoomEvent = null;
129 if( clearStarted ) {
130 zoomLastEdgeDist = 0;
131 zoomFirstTouch = true;
132 zoomMode = false;
133 pIds[0] = -1;
134 pIds[1] = -1;
135 }
136 }
137
138 @Override
139 public boolean isWithinGesture() {
140 return zoomMode;
141 }
142
143 @Override
144 public boolean hasGesture() {
145 return null != zoomEvent;
146 }
147
148 @Override
150 return zoomEvent;
151 }
152
153 /** Zoom value lies within [0..2], with 1 as <i>1:1</i>. */
154 public final float getZoom() {
155 return zoom;
156 }
157 /** Set zoom value within [0..2], with 1 as <i>1:1</i>. */
158 public final void setZoom(final float zoom) {
159 this.zoom=zoom;
160 }
161
162 @Override
163 public boolean process(final InputEvent in) {
164 if( null != zoomEvent || !(in instanceof MouseEvent) ) {
165 return true;
166 }
167 final MouseEvent pe = (MouseEvent)in;
168 final int pointerDownCount = pe.getPointerCount();
169
170 if( pe.getPointerType(0).getPointerClass() != MouseEvent.PointerClass.Onscreen ||
171 ( !allowMorePointer && pointerDownCount > 2 ) ) {
172 return false;
173 }
174
175 final int eventType = pe.getEventType();
176 final boolean useY = surface.getSurfaceWidth() >= surface.getSurfaceHeight(); // use smallest dimension
177 switch ( eventType ) {
179 if( 1 == pointerDownCount ) {
180 pIds[0] = pe.getPointerId(0);
181 pIds[1] = -1;
182 } else if ( 2 <= pointerDownCount ) { // && 1 == gesturePointers(pe, 0) /* w/o pressed pointer */) {
183 pIds[0] = pe.getPointerId(0);
184 pIds[1] = pe.getPointerId(1);
185 }
186 if(DEBUG) {
187 System.err.println("XXX1: id0 "+pIds[0]+" -> idx0 "+0+", id1 "+pIds[1]+" -> idx1 "+1);
188 System.err.println(this+".pressed: down "+pointerDownCount+", gPtr "+gesturePointers(pe, -1)+", event "+pe);
189 }
190 } break;
191
193 final int gPtr = gesturePointers(pe, 0); // w/o lifted pointer
194 if ( 1 == gPtr ) {
195 zoomFirstTouch = true;
196 zoomMode = false;
197 } else if( 0 == gPtr ) {
198 // all lifted
199 clear(true);
200 }
201 if(DEBUG) {
202 System.err.println(this+".released: down "+pointerDownCount+", gPtr "+gPtr+", event "+pe);
203 }
204 } break;
205
207 if( 2 <= pointerDownCount ) {
208 final int gPtr = gesturePointers(pe, -1);
209 if( 2 == gPtr ) {
210 // same pointers
211 final int p0Idx = pe.getPointerIdx(pIds[0]);
212 final int p1Idx = pe.getPointerIdx(pIds[1]);
213 if( 0 <= p0Idx && 0 <= p1Idx ) {
214 final int edge0 = useY ? pe.getY(p0Idx) : pe.getX(p0Idx);
215 final int edge1 = useY ? pe.getY(p1Idx) : pe.getX(p1Idx);
216 // Diff. 1:1 Zoom: finger-distance to screen-coord
217 if(zoomFirstTouch) {
218 zoomLastEdgeDist = Math.abs(edge0-edge1);
219 zoomFirstTouch=false;
220 zoomMode = true;
221 } else if( zoomMode ) {
222 final int d = Math.abs(edge0-edge1);
223 final int dd = d - zoomLastEdgeDist;
224 final float screenEdge = useY ? surface.getSurfaceHeight() : surface.getSurfaceWidth();
225 final float delta = dd / screenEdge; // [-1..1]
226 if(DEBUG) {
227 System.err.println("XXX2: id0 "+pIds[0]+" -> idx0 "+p0Idx+", id1 "+pIds[1]+" -> idx1 "+p1Idx);
228 System.err.println("XXX3: d "+d+", ld "+zoomLastEdgeDist+", dd "+dd+", screen "+screenEdge+" -> incr "+delta+", zoom "+zoom+" -> "+(zoom+delta));
229 }
230 zoom += delta;
231 // clip value
232 if( 2f < zoom ) {
233 zoom = 2f;
234 } else if( 0 > zoom ) {
235 zoom = 0;
236 }
237 zoomLastEdgeDist = d;
238 zoomEvent = new ZoomEvent(pe.getSource(), pe.getWhen(), pe.getModifiers(), this, pe, zoom, delta, screenEdge);
239 }
240 }
241 }
242 if(DEBUG) {
243 System.err.println(this+".dragged: down "+pointerDownCount+", gPtr "+gPtr+", event "+pe);
244 }
245 }
246 } break;
247
248 default:
249 }
250 return null != zoomEvent;
251 }
252}
final int getModifiers()
Return the modifier bits of this event, e.g.
Pointer event of type PointerType.
Definition: MouseEvent.java:74
final PointerType getPointerType(final int index)
See details for multiple-pointer events.
final int getPointerCount()
See details for multiple-pointer events.
static final short EVENT_MOUSE_PRESSED
final short getPointerId(final int index)
Return the pointer id for the given index or -1 if index not available.
final int getPointerIdx(final short id)
See details for multiple-pointer events.
final int getY()
See details for multiple-pointer events.
static final short EVENT_MOUSE_DRAGGED
static final short EVENT_MOUSE_RELEASED
final int getX()
See details for multiple-pointer events.
final short getEventType()
Returns the event type of this event.
Definition: NEWTEvent.java:72
final long getWhen()
Returns the timestamp, in milliseconds, of this event.
Definition: NEWTEvent.java:77
A GestureHandler.GestureEvent denominating zoom.
final float getDelta()
Delta to last zoom value lies within [-1..1].
ZoomEvent(final Object source, final long when, final int modifiers, final GestureHandler handler, final MouseEvent pe, final float zoom, final float delta, final float scale)
final float getScale()
Returns the scale used to determine the zoom and hence it's delta value, which semantics depends on t...
final float getZoom()
Zoom value lies within [0..2], with 1 as 1:1.
final void setZoom(final float zoom)
Set zoom value within [0..2], with 1 as 1:1.
boolean process(final InputEvent in)
Process the given InputEvent and returns true if it produced the gesture.
boolean hasGesture()
Returns true if a previous process(InputEvent) command produced a gesture, which has not been cleared...
void clear(final boolean clearStarted)
Clears state of handler, i.e.
InputEvent getGestureEvent()
Returns the corresponding InputEvent for the gesture as detected by a previous process(InputEvent),...
final float getZoom()
Zoom value lies within [0..2], with 1 as 1:1.
boolean isWithinGesture()
Returns true if within a gesture as detected by a previous process(InputEvent) command,...
PinchToZoomGesture(final NativeSurface surface, final boolean allowMorePointer)
Provides low-level information required for hardware-accelerated rendering using a surface in a platf...
int getSurfaceWidth()
Returns the width of the client area excluding insets (window decorations) in pixel units.
int getSurfaceHeight()
Returns the height of the client area excluding insets (window decorations) in pixel units.
Generic gesture handler interface designed to allow pass-through filtering of InputEvents.