You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
17 KiB
348 lines
17 KiB
/*
|
|
* Copyright (c) 2009-2012 jMonkeyEngine
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * 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.
|
|
*
|
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "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 THE COPYRIGHT OWNER 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.
|
|
*/
|
|
|
|
package com.jme3.input.android;
|
|
|
|
import android.view.GestureDetector;
|
|
import android.view.MotionEvent;
|
|
import android.view.ScaleGestureDetector;
|
|
import android.view.View;
|
|
import com.jme3.input.event.InputEvent;
|
|
import com.jme3.input.event.MouseMotionEvent;
|
|
import com.jme3.input.event.TouchEvent;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
/**
|
|
* AndroidGestureHandler uses Gesture type listeners to create jME TouchEvents
|
|
* for gestures. This class is designed to handle the gestures supported
|
|
* on Android rev 9 (Android 2.3). Extend this class to add functionality
|
|
* added by Android after rev 9.
|
|
*
|
|
* @author iwgeric
|
|
*/
|
|
public class AndroidGestureHandler implements
|
|
GestureDetector.OnGestureListener,
|
|
GestureDetector.OnDoubleTapListener,
|
|
ScaleGestureDetector.OnScaleGestureListener {
|
|
private static final Logger logger = Logger.getLogger(AndroidGestureHandler.class.getName());
|
|
private AndroidInputHandler androidInput;
|
|
private GestureDetector gestureDetector;
|
|
private ScaleGestureDetector scaleDetector;
|
|
float gestureDownX = -1f;
|
|
float gestureDownY = -1f;
|
|
float scaleStartX = -1f;
|
|
float scaleStartY = -1f;
|
|
|
|
public AndroidGestureHandler(AndroidInputHandler androidInput) {
|
|
this.androidInput = androidInput;
|
|
}
|
|
|
|
public void initialize() {
|
|
}
|
|
|
|
public void destroy() {
|
|
setView(null);
|
|
}
|
|
|
|
public void setView(View view) {
|
|
if (view != null) {
|
|
gestureDetector = new GestureDetector(view.getContext(), this);
|
|
scaleDetector = new ScaleGestureDetector(view.getContext(), this);
|
|
} else {
|
|
gestureDetector = null;
|
|
scaleDetector = null;
|
|
}
|
|
}
|
|
|
|
public void detectGesture(MotionEvent event) {
|
|
if (gestureDetector != null && scaleDetector != null) {
|
|
gestureDetector.onTouchEvent(event);
|
|
scaleDetector.onTouchEvent(event);
|
|
}
|
|
}
|
|
|
|
private int getPointerIndex(MotionEvent event) {
|
|
return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
|
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
|
}
|
|
|
|
private int getPointerId(MotionEvent event) {
|
|
return event.getPointerId(getPointerIndex(event));
|
|
}
|
|
|
|
private void processEvent(TouchEvent event) {
|
|
// Add the touch event
|
|
androidInput.addEvent(event);
|
|
if (androidInput.isSimulateMouse()) {
|
|
InputEvent mouseEvent = generateMouseEvent(event);
|
|
if (mouseEvent != null) {
|
|
// Add the mouse event
|
|
androidInput.addEvent(mouseEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Ring Buffer for mouse events?
|
|
private InputEvent generateMouseEvent(TouchEvent event) {
|
|
InputEvent inputEvent = null;
|
|
int newX;
|
|
int newY;
|
|
int newDX;
|
|
int newDY;
|
|
|
|
if (androidInput.isMouseEventsInvertX()) {
|
|
newX = (int) (androidInput.invertX(event.getX()));
|
|
newDX = (int)event.getDeltaX() * -1;
|
|
} else {
|
|
newX = (int) event.getX();
|
|
newDX = (int)event.getDeltaX();
|
|
}
|
|
int wheel = (int) (event.getScaleSpan()); // might need to scale to match mouse wheel
|
|
int dWheel = (int) (event.getDeltaScaleSpan()); // might need to scale to match mouse wheel
|
|
|
|
if (androidInput.isMouseEventsInvertY()) {
|
|
newY = (int) (androidInput.invertY(event.getY()));
|
|
newDY = (int)event.getDeltaY() * -1;
|
|
} else {
|
|
newY = (int) event.getY();
|
|
newDY = (int)event.getDeltaY();
|
|
}
|
|
|
|
switch (event.getType()) {
|
|
case SCALE_MOVE:
|
|
inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, wheel, dWheel);
|
|
inputEvent.setTime(event.getTime());
|
|
break;
|
|
}
|
|
|
|
return inputEvent;
|
|
}
|
|
|
|
/* Events from onGestureListener */
|
|
|
|
public boolean onDown(MotionEvent event) {
|
|
// start of all GestureListeners. Not really a gesture by itself
|
|
// so we don't create an event.
|
|
// However, reset the scaleInProgress here since this is the beginning
|
|
// of a series of gesture events.
|
|
// logger.log(Level.INFO, "onDown pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
gestureDownX = androidInput.getJmeX(event.getX());
|
|
gestureDownY = androidInput.invertY(androidInput.getJmeY(event.getY()));
|
|
return true;
|
|
}
|
|
|
|
public boolean onSingleTapUp(MotionEvent event) {
|
|
// Up of single tap. May be followed by a double tap later.
|
|
// use onSingleTapConfirmed instead.
|
|
// logger.log(Level.INFO, "onSingleTapUp pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
return true;
|
|
}
|
|
|
|
public void onShowPress(MotionEvent event) {
|
|
// logger.log(Level.INFO, "onShowPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
float jmeX = androidInput.getJmeX(event.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.SHOWPRESS, jmeX, jmeY, 0, 0);
|
|
touchEvent.setPointerId(getPointerId(event));
|
|
touchEvent.setTime(event.getEventTime());
|
|
touchEvent.setPressure(event.getPressure());
|
|
processEvent(touchEvent);
|
|
}
|
|
|
|
public void onLongPress(MotionEvent event) {
|
|
// logger.log(Level.INFO, "onLongPress pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
float jmeX = androidInput.getJmeX(event.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.LONGPRESSED, jmeX, jmeY, 0, 0);
|
|
touchEvent.setPointerId(getPointerId(event));
|
|
touchEvent.setTime(event.getEventTime());
|
|
touchEvent.setPressure(event.getPressure());
|
|
processEvent(touchEvent);
|
|
}
|
|
|
|
public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) {
|
|
// if not scaleInProgess, send scroll events. This is to avoid sending
|
|
// scroll events when one of the fingers is lifted just before the other one.
|
|
// Avoids sending the scroll for that brief period of time.
|
|
// Return true so that the next event doesn't accumulate the distX and distY values.
|
|
// Apparantly, both distX and distY are negative.
|
|
// Negate distX to get the real value, but leave distY negative to compensate
|
|
// for the fact that jME has y=0 at bottom where Android has y=0 at top.
|
|
// if (!scaleInProgress) {
|
|
if (!scaleDetector.isInProgress()) {
|
|
// logger.log(Level.INFO, "onScroll pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, dx: {7}, dy: {8}",
|
|
// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), distX, distY});
|
|
|
|
float jmeX = androidInput.getJmeX(endEvent.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(endEvent.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.SCROLL, jmeX, jmeY, androidInput.getJmeX(-distX), androidInput.getJmeY(distY));
|
|
touchEvent.setPointerId(getPointerId(endEvent));
|
|
touchEvent.setTime(endEvent.getEventTime());
|
|
touchEvent.setPressure(endEvent.getPressure());
|
|
processEvent(touchEvent);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float velocityX, float velocityY) {
|
|
// Fling happens only once at the end of the gesture (all fingers up).
|
|
// Fling returns the velocity of the finger movement in pixels/sec.
|
|
// Therefore, the dX and dY values are actually velocity instead of distance values
|
|
// Since this does not track the movement, use the start position and velocity values.
|
|
|
|
// logger.log(Level.INFO, "onFling pointerId: {0}, startAction: {1}, startX: {2}, startY: {3}, endAction: {4}, endX: {5}, endY: {6}, velocityX: {7}, velocityY: {8}",
|
|
// new Object[]{getPointerId(startEvent), getAction(startEvent), startEvent.getX(), startEvent.getY(), getAction(endEvent), endEvent.getX(), endEvent.getY(), velocityX, velocityY});
|
|
|
|
float jmeX = androidInput.getJmeX(startEvent.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(startEvent.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.FLING, jmeX, jmeY, velocityX, velocityY);
|
|
touchEvent.setPointerId(getPointerId(endEvent));
|
|
touchEvent.setTime(endEvent.getEventTime());
|
|
touchEvent.setPressure(endEvent.getPressure());
|
|
processEvent(touchEvent);
|
|
return true;
|
|
}
|
|
|
|
/* Events from onDoubleTapListener */
|
|
|
|
public boolean onSingleTapConfirmed(MotionEvent event) {
|
|
// Up of single tap when no double tap followed.
|
|
// logger.log(Level.INFO, "onSingleTapConfirmed pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
float jmeX = androidInput.getJmeX(event.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.TAP, jmeX, jmeY, 0, 0);
|
|
touchEvent.setPointerId(getPointerId(event));
|
|
touchEvent.setTime(event.getEventTime());
|
|
touchEvent.setPressure(event.getPressure());
|
|
processEvent(touchEvent);
|
|
return true;
|
|
}
|
|
|
|
public boolean onDoubleTap(MotionEvent event) {
|
|
//The down motion event of the first tap of the double-tap
|
|
// We could use this event to fire off a double tap event, or use
|
|
// DoubleTapEvent with a check for the UP action
|
|
// logger.log(Level.INFO, "onDoubleTap pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
float jmeX = androidInput.getJmeX(event.getX());
|
|
float jmeY = androidInput.invertY(androidInput.getJmeY(event.getY()));
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.DOUBLETAP, jmeX, jmeY, 0, 0);
|
|
touchEvent.setPointerId(getPointerId(event));
|
|
touchEvent.setTime(event.getEventTime());
|
|
touchEvent.setPressure(event.getPressure());
|
|
processEvent(touchEvent);
|
|
return true;
|
|
}
|
|
|
|
public boolean onDoubleTapEvent(MotionEvent event) {
|
|
//Notified when an event within a double-tap gesture occurs, including the down, move(s), and up events.
|
|
// this means it will get called multiple times for a single double tap
|
|
// logger.log(Level.INFO, "onDoubleTapEvent pointerId: {0}, action: {1}, x: {2}, y: {3}",
|
|
// new Object[]{getPointerId(event), getAction(event), event.getX(), event.getY()});
|
|
// if (getAction(event) == MotionEvent.ACTION_UP) {
|
|
// TouchEvent touchEvent = touchEventPool.getNextFreeEvent();
|
|
// touchEvent.set(TouchEvent.Type.DOUBLETAP, event.getX(), androidInput.invertY(event.getY()), 0, 0);
|
|
// touchEvent.setPointerId(getPointerId(event));
|
|
// touchEvent.setTime(event.getEventTime());
|
|
// touchEvent.setPressure(event.getPressure());
|
|
// processEvent(touchEvent);
|
|
// }
|
|
return true;
|
|
}
|
|
|
|
/* Events from ScaleGestureDetector */
|
|
|
|
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
|
|
// Scale uses a focusX and focusY instead of x and y. Focus is the middle
|
|
// of the fingers. Therefore, use the x and y values from the Down event
|
|
// so that the x and y values don't jump to the middle position.
|
|
// return true or all gestures for this beginning event will be discarded
|
|
logger.log(Level.INFO, "onScaleBegin");
|
|
scaleStartX = gestureDownX;
|
|
scaleStartY = gestureDownY;
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.SCALE_START, scaleStartX, scaleStartY, 0f, 0f);
|
|
touchEvent.setPointerId(0);
|
|
touchEvent.setTime(scaleGestureDetector.getEventTime());
|
|
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
|
|
touchEvent.setDeltaScaleSpan(0f);
|
|
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
|
|
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
|
|
processEvent(touchEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
|
|
// return true or all gestures for this event will be accumulated
|
|
logger.log(Level.INFO, "onScale");
|
|
scaleStartX = gestureDownX;
|
|
scaleStartY = gestureDownY;
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.SCALE_MOVE, scaleStartX, scaleStartY, 0f, 0f);
|
|
touchEvent.setPointerId(0);
|
|
touchEvent.setTime(scaleGestureDetector.getEventTime());
|
|
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
|
|
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
|
|
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
|
|
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
|
|
processEvent(touchEvent);
|
|
return true;
|
|
}
|
|
|
|
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
|
|
logger.log(Level.INFO, "onScaleEnd");
|
|
scaleStartX = gestureDownX;
|
|
scaleStartY = gestureDownY;
|
|
TouchEvent touchEvent = androidInput.getFreeTouchEvent();
|
|
touchEvent.set(TouchEvent.Type.SCALE_END, scaleStartX, scaleStartY, 0f, 0f);
|
|
touchEvent.setPointerId(0);
|
|
touchEvent.setTime(scaleGestureDetector.getEventTime());
|
|
touchEvent.setScaleSpan(scaleGestureDetector.getCurrentSpan());
|
|
touchEvent.setDeltaScaleSpan(scaleGestureDetector.getCurrentSpan() - scaleGestureDetector.getPreviousSpan());
|
|
touchEvent.setScaleFactor(scaleGestureDetector.getScaleFactor());
|
|
touchEvent.setScaleSpanInProgress(scaleDetector.isInProgress());
|
|
processEvent(touchEvent);
|
|
}
|
|
}
|
|
|