diff --git a/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java b/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java new file mode 100644 index 000000000..8b6896baf --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidGestureHandler.java @@ -0,0 +1,336 @@ +/* + * 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 = event.getX(); + gestureDownY = androidInput.invertY(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()}); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SHOWPRESS, event.getX(), androidInput.invertY(event.getY()), 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()}); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.LONGPRESSED, event.getX(), androidInput.invertY(event.getY()), 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}); + + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.SCROLL, endEvent.getX(), androidInput.invertY(endEvent.getY()), -distX, 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}); + + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.FLING, startEvent.getX(), androidInput.invertY(startEvent.getY()), 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()}); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.TAP, event.getX(), androidInput.invertY(event.getY()), 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()}); + TouchEvent touchEvent = androidInput.getFreeTouchEvent(); + 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; + } + + 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); + } +} diff --git a/engine/src/android/com/jme3/input/android/AndroidInputHandler.java b/engine/src/android/com/jme3/input/android/AndroidInputHandler.java new file mode 100644 index 000000000..8fd594030 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidInputHandler.java @@ -0,0 +1,252 @@ +/* + * 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.os.Build; +import android.view.View; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.system.AppSettings; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidInput is the main class that connects the Android system + * inputs to jME. It serves as the manager that gathers inputs from the various + * Android input methods and provides them to jME's InputManager. + * + * @author iwgeric + */ +public class AndroidInputHandler implements TouchInput { + private static final Logger logger = Logger.getLogger(AndroidInputHandler.class.getName()); + + // Custom settings + private boolean mouseEventsEnabled = true; + private boolean mouseEventsInvertX = false; + private boolean mouseEventsInvertY = false; + private boolean keyboardEventsEnabled = false; + private boolean joystickEventsEnabled = false; + private boolean dontSendHistory = false; + + + // Internal + private View view; + private AndroidTouchHandler touchHandler; + private AndroidKeyHandler keyHandler; + private AndroidGestureHandler gestureHandler; + private boolean initialized = false; + private RawInputListener listener = null; + private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + final private static int MAX_TOUCH_EVENTS = 1024; + private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); + + public AndroidInputHandler() { + int buildVersion = Build.VERSION.SDK_INT; + logger.log(Level.INFO, "Android Build Version: {0}", buildVersion); + if (buildVersion >= 14) { + // add support for onHover and GenericMotionEvent (ie. gamepads) + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler14(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } else if (buildVersion >= 8){ + gestureHandler = new AndroidGestureHandler(this); + touchHandler = new AndroidTouchHandler(this, gestureHandler); + keyHandler = new AndroidKeyHandler(this); + } + } + + public AndroidInputHandler(AndroidTouchHandler touchInput, + AndroidKeyHandler keyInput, AndroidGestureHandler gestureHandler) { + this.touchHandler = touchInput; + this.keyHandler = keyInput; + this.gestureHandler = gestureHandler; + } + + public void setView(View view) { + if (touchHandler != null) { + touchHandler.setView(view); + } + if (keyHandler != null) { + keyHandler.setView(view); + } + if (gestureHandler != null) { + gestureHandler.setView(view); + } + this.view = view; + } + + public View getView() { + return view; + } + + public float invertX(float origX) { + return view.getWidth()-origX; + } + + public float invertY(float origY) { + return view.getHeight()-origY; + } + + public void loadSettings(AppSettings settings) { + // TODO: add simulate keyboard to settings +// keyboardEventsEnabled = true; + mouseEventsEnabled = settings.isEmulateMouse(); + mouseEventsInvertX = settings.isEmulateMouseFlipX(); + mouseEventsInvertY = settings.isEmulateMouseFlipY(); + joystickEventsEnabled = settings.useJoysticks(); + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + touchEventPool.initialize(); + if (touchHandler != null) { + touchHandler.initialize(); + } + if (keyHandler != null) { + keyHandler.initialize(); + } + if (gestureHandler != null) { + gestureHandler.initialize(); + } + + initialized = true; + } + + @Override + public void destroy() { + initialized = false; + + touchEventPool.destroy(); + if (touchHandler != null) { + touchHandler.destroy(); + } + if (keyHandler != null) { + keyHandler.destroy(); + } + if (gestureHandler != null) { + gestureHandler.destroy(); + } + + setView(null); + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + + public void update() { + if (listener != null) { + InputEvent inputEvent; + + while ((inputEvent = inputEventQueue.poll()) != null) { + if (inputEvent instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent)inputEvent); + } else if (inputEvent instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent)inputEvent); + } else if (inputEvent instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent)inputEvent); + } else if (inputEvent instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent)inputEvent); + } + } + } + } + + // ----------------------------------------- + + public TouchEvent getFreeTouchEvent() { + return touchEventPool.getNextFreeEvent(); + } + + public void addEvent(InputEvent event) { + inputEventQueue.add(event); + if (event instanceof TouchEvent) { + touchEventPool.storeEvent((TouchEvent)event); + } + } + + public void setSimulateMouse(boolean simulate) { + this.mouseEventsEnabled = simulate; + } + + public boolean isSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean getSimulateMouse() { + return mouseEventsEnabled; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setSimulateKeyboard(boolean simulate) { + this.keyboardEventsEnabled = simulate; + } + + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + + public void showVirtualKeyboard(boolean visible) { + if (keyHandler != null) { + keyHandler.showVirtualKeyboard(visible); + } + } + + +} diff --git a/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java b/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java new file mode 100644 index 000000000..ffd299d60 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidKeyHandler.java @@ -0,0 +1,156 @@ +/* + * 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.content.Context; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.TouchEvent; +import java.util.logging.Logger; + +/** + * AndroidKeyHandler recieves onKey events from the Android system and creates + * the jME KeyEvents. onKey is used by Android to receive keys from the keyboard + * or device buttons. All key events are consumed by jME except for the Volume + * buttons and menu button. + * + * This class also provides the functionality to display or hide the soft keyboard + * for inputing single key events. Use OGLESContext to display an dialog to type + * in complete strings. + * + * @author iwgeric + */ +public class AndroidKeyHandler implements View.OnKeyListener { + private static final Logger logger = Logger.getLogger(AndroidKeyHandler.class.getName()); + + private AndroidInputHandler androidInput; + private boolean sendKeyEvents = true; + + public AndroidKeyHandler(AndroidInputHandler androidInput) { + this.androidInput = androidInput; + } + + public void initialize() { + } + + public void destroy() { + } + + public void setView(View view) { + if (view != null) { + view.setOnKeyListener(this); + } else { + androidInput.getView().setOnKeyListener(null); + } + } + + /** + * onKey gets called from android thread on key events + */ + public boolean onKey(View view, int keyCode, KeyEvent event) { + if (androidInput.isInitialized() && view != androidInput.getView()) { + return false; + } + + TouchEvent evt; + // TODO: get touch event from pool + if (event.getAction() == KeyEvent.ACTION_DOWN) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + androidInput.addEvent(evt); + + } else if (event.getAction() == KeyEvent.ACTION_UP) { + evt = new TouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + androidInput.addEvent(evt); + + } + + + KeyInputEvent kie; + char unicodeChar = (char)event.getUnicodeChar(); + int jmeKeyCode = AndroidKeyMapping.getJmeKey(keyCode); + + boolean pressed = event.getAction() == KeyEvent.ACTION_DOWN; + boolean repeating = pressed && event.getRepeatCount() > 0; + + kie = new KeyInputEvent(jmeKeyCode, unicodeChar, pressed, repeating); + kie.setTime(event.getEventTime()); + androidInput.addEvent(kie); +// logger.log(Level.FINE, "onKey keyCode: {0}, jmeKeyCode: {1}, pressed: {2}, repeating: {3}", +// new Object[]{keyCode, jmeKeyCode, pressed, repeating}); +// logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie); + + // consume all keys ourself except Volume Up/Down and Menu + // Don't do Menu so that typical Android Menus can be created and used + // by the user in MainActivity + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || + (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || + (keyCode == KeyEvent.KEYCODE_MENU)) { + return false; + } else { + return true; + } + } + + public void showVirtualKeyboard (final boolean visible) { + androidInput.getView().getHandler().post(new Runnable() { + + public void run() { + InputMethodManager manager = + (InputMethodManager)androidInput.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + + if (visible) { + manager.showSoftInput(androidInput.getView(), 0); + sendKeyEvents = true; + } else { + manager.hideSoftInputFromWindow(androidInput.getView().getWindowToken(), 0); + sendKeyEvents = false; + } + } + }); + } + +} diff --git a/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java b/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java new file mode 100644 index 000000000..e99d8e9fc --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidKeyMapping.java @@ -0,0 +1,149 @@ +/* + * 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 com.jme3.input.KeyInput; +import java.util.logging.Logger; + +/** + * AndroidKeyMapping is just a utility to convert the Android keyCodes into + * jME KeyCodes received in jME's KeyEvent will match between Desktop and Android. + * + * @author iwgeric + */ +public class AndroidKeyMapping { + private static final Logger logger = Logger.getLogger(AndroidKeyMapping.class.getName()); + + private static final int[] ANDROID_TO_JME = { + 0x0, // unknown + 0x0, // key code soft left + 0x0, // key code soft right + KeyInput.KEY_HOME, + KeyInput.KEY_ESCAPE, // key back + 0x0, // key call + 0x0, // key endcall + KeyInput.KEY_0, + KeyInput.KEY_1, + KeyInput.KEY_2, + KeyInput.KEY_3, + KeyInput.KEY_4, + KeyInput.KEY_5, + KeyInput.KEY_6, + KeyInput.KEY_7, + KeyInput.KEY_8, + KeyInput.KEY_9, + KeyInput.KEY_MULTIPLY, + 0x0, // key pound + KeyInput.KEY_UP, + KeyInput.KEY_DOWN, + KeyInput.KEY_LEFT, + KeyInput.KEY_RIGHT, + KeyInput.KEY_RETURN, // dpad center + 0x0, // volume up + 0x0, // volume down + KeyInput.KEY_POWER, // power (?) + 0x0, // camera + 0x0, // clear + KeyInput.KEY_A, + KeyInput.KEY_B, + KeyInput.KEY_C, + KeyInput.KEY_D, + KeyInput.KEY_E, + KeyInput.KEY_F, + KeyInput.KEY_G, + KeyInput.KEY_H, + KeyInput.KEY_I, + KeyInput.KEY_J, + KeyInput.KEY_K, + KeyInput.KEY_L, + KeyInput.KEY_M, + KeyInput.KEY_N, + KeyInput.KEY_O, + KeyInput.KEY_P, + KeyInput.KEY_Q, + KeyInput.KEY_R, + KeyInput.KEY_S, + KeyInput.KEY_T, + KeyInput.KEY_U, + KeyInput.KEY_V, + KeyInput.KEY_W, + KeyInput.KEY_X, + KeyInput.KEY_Y, + KeyInput.KEY_Z, + KeyInput.KEY_COMMA, + KeyInput.KEY_PERIOD, + KeyInput.KEY_LMENU, + KeyInput.KEY_RMENU, + KeyInput.KEY_LSHIFT, + KeyInput.KEY_RSHIFT, + // 0x0, // fn + // 0x0, // cap (?) + + KeyInput.KEY_TAB, + KeyInput.KEY_SPACE, + 0x0, // sym (?) symbol + 0x0, // explorer + 0x0, // envelope + KeyInput.KEY_RETURN, // newline/enter + KeyInput.KEY_BACK, //used to be KeyInput.KEY_DELETE, + KeyInput.KEY_GRAVE, + KeyInput.KEY_MINUS, + KeyInput.KEY_EQUALS, + KeyInput.KEY_LBRACKET, + KeyInput.KEY_RBRACKET, + KeyInput.KEY_BACKSLASH, + KeyInput.KEY_SEMICOLON, + KeyInput.KEY_APOSTROPHE, + KeyInput.KEY_SLASH, + KeyInput.KEY_AT, // at (@) + KeyInput.KEY_NUMLOCK, //0x0, // num + 0x0, //headset hook + 0x0, //focus + KeyInput.KEY_ADD, + KeyInput.KEY_LMETA, //menu + 0x0,//notification + 0x0,//search + 0x0,//media play/pause + 0x0,//media stop + 0x0,//media next + 0x0,//media previous + 0x0,//media rewind + 0x0,//media fastforward + 0x0,//mute + }; + + public static int getJmeKey(int androidKey) { + return ANDROID_TO_JME[androidKey]; + } + +} diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java b/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java new file mode 100644 index 000000000..a5d2d85ed --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidTouchHandler.java @@ -0,0 +1,249 @@ +/* + * 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.MotionEvent; +import android.view.View; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import static com.jme3.input.event.TouchEvent.Type.DOWN; +import static com.jme3.input.event.TouchEvent.Type.MOVE; +import static com.jme3.input.event.TouchEvent.Type.UP; +import com.jme3.math.Vector2f; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * AndroidTouchHandler is the base class that receives touch inputs from the + * Android system and creates the TouchEvents for jME. This class is designed + * to handle the base touch events for Android rev 9 (Android 2.3). This is + * extended by other classes to add features that were introducted after + * Android rev 9. + * + * @author iwgeric + */ +public class AndroidTouchHandler implements View.OnTouchListener { + private static final Logger logger = Logger.getLogger(AndroidTouchHandler.class.getName()); + + final private HashMap lastPositions = new HashMap(); + + protected int numPointers = 0; + + protected AndroidInputHandler androidInput; + protected AndroidGestureHandler gestureHandler; + + public AndroidTouchHandler(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { + this.androidInput = androidInput; + this.gestureHandler = gestureHandler; + } + + public void initialize() { + } + + public void destroy() { + setView(null); + } + + public void setView(View view) { + if (view != null) { + view.setOnTouchListener(this); + } else { + androidInput.getView().setOnTouchListener(null); + } + } + + protected int getPointerIndex(MotionEvent event) { + return (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + } + + protected int getPointerId(MotionEvent event) { + return event.getPointerId(getPointerIndex(event)); + } + + protected int getAction(MotionEvent event) { + return event.getAction() & MotionEvent.ACTION_MASK; + } + + /** + * onTouch gets called from android thread on touch events + */ + public boolean onTouch(View view, MotionEvent event) { + if (!androidInput.isInitialized() || view != androidInput.getView()) { + return false; + } + + boolean bWasHandled = false; + TouchEvent touch = null; + // System.out.println("native : " + event.getAction()); + int action = getAction(event); + int pointerIndex = getPointerIndex(event); + int pointerId = getPointerId(event); + Vector2f lastPos = lastPositions.get(pointerId); + + numPointers = event.getPointerCount(); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + switch (getAction(event)) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.DOWN, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + + lastPos = new Vector2f(event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex))); + lastPositions.put(pointerId, lastPos); + + processEvent(touch); + + bWasHandled = true; + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.UP, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + lastPositions.remove(pointerId); + + processEvent(touch); + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + lastPos = lastPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(event.getX(p), androidInput.invertY(event.getY(p))); + lastPositions.put(event.getPointerId(p), lastPos); + } + + float dX = event.getX(p) - lastPos.x; + float dY = androidInput.invertY(event.getY(p)) - lastPos.y; + if (dX != 0 || dY != 0) { + touch = androidInput.getFreeTouchEvent(); + touch.set(TouchEvent.Type.MOVE, event.getX(p), androidInput.invertY(event.getY(p)), dX, dY); + touch.setPointerId(event.getPointerId(p)); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(p)); + lastPos.set(event.getX(p), androidInput.invertY(event.getY(p))); + + processEvent(touch); + + bWasHandled = true; + } + } + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + if (gestureHandler != null) { + gestureHandler.detectGesture(event); + } + + return bWasHandled; + } + + protected void processEvent(TouchEvent event) { + // Add the touch event + androidInput.addEvent(event); + // MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events + if (androidInput.isSimulateMouse() && numPointers == 1) { + InputEvent mouseEvent = generateMouseEvent(event); + if (mouseEvent != null) { + // Add the mouse event + androidInput.addEvent(mouseEvent); + } + } + + } + + // TODO: Ring Buffer for mouse events? + protected 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(); + } + + 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 DOWN: + // Handle mouse down event + inputEvent = new MouseButtonEvent(0, true, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case UP: + // Handle mouse up event + inputEvent = new MouseButtonEvent(0, false, newX, newY); + inputEvent.setTime(event.getTime()); + break; + + case HOVER_MOVE: + case MOVE: + inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan()); + inputEvent.setTime(event.getTime()); + break; + } + + return inputEvent; + } + +} diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java b/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java new file mode 100644 index 000000000..1736ea66b --- /dev/null +++ b/engine/src/android/com/jme3/input/android/AndroidTouchHandler14.java @@ -0,0 +1,144 @@ +/* + * 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.MotionEvent; +import android.view.View; +import com.jme3.input.event.TouchEvent; +import com.jme3.math.Vector2f; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * AndroidTouchHandler14 is an extension of AndroidTouchHander that adds the + * Android touch event functionality between Android rev 9 (Android 2.3) and + * Android rev 14 (Android 4.0). + * + * @author iwgeric + */ +public class AndroidTouchHandler14 extends AndroidTouchHandler implements + View.OnHoverListener { + private static final Logger logger = Logger.getLogger(AndroidTouchHandler14.class.getName()); + final private HashMap lastHoverPositions = new HashMap(); + + public AndroidTouchHandler14(AndroidInputHandler androidInput, AndroidGestureHandler gestureHandler) { + super(androidInput, gestureHandler); + } + + @Override + public void setView(View view) { + if (view != null) { + view.setOnHoverListener(this); + } else { + androidInput.getView().setOnHoverListener(null); + } + super.setView(view); + } + + public boolean onHover(View view, MotionEvent event) { + if (view == null || view != androidInput.getView()) { + return false; + } + + boolean consumed = false; + int action = getAction(event); + int pointerId = getPointerId(event); + int pointerIndex = getPointerIndex(event); + Vector2f lastPos = lastHoverPositions.get(pointerId); + + numPointers = event.getPointerCount(); + + logger.log(Level.INFO, "onHover pointerId: {0}, action: {1}, x: {2}, y: {3}, numPointers: {4}", + new Object[]{pointerId, action, event.getX(), event.getY(), event.getPointerCount()}); + + TouchEvent touchEvent; + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_START, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0); + touchEvent.setPointerId(pointerId); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(pointerIndex)); + + lastPos = new Vector2f(event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex))); + lastHoverPositions.put(pointerId, lastPos); + + processEvent(touchEvent); + consumed = true; + break; + case MotionEvent.ACTION_HOVER_MOVE: + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + lastPos = lastHoverPositions.get(event.getPointerId(p)); + if (lastPos == null) { + lastPos = new Vector2f(event.getX(p), androidInput.invertY(event.getY(p))); + lastHoverPositions.put(event.getPointerId(p), lastPos); + } + + float dX = event.getX(p) - lastPos.x; + float dY = androidInput.invertY(event.getY(p)) - lastPos.y; + if (dX != 0 || dY != 0) { + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_MOVE, event.getX(p), androidInput.invertY(event.getY(p)), dX, dY); + touchEvent.setPointerId(event.getPointerId(p)); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(p)); + lastPos.set(event.getX(p), androidInput.invertY(event.getY(p))); + + processEvent(touchEvent); + + } + } + consumed = true; + break; + case MotionEvent.ACTION_HOVER_EXIT: + touchEvent = androidInput.getFreeTouchEvent(); + touchEvent.set(TouchEvent.Type.HOVER_END, event.getX(pointerIndex), androidInput.invertY(event.getY(pointerIndex)), 0, 0); + touchEvent.setPointerId(pointerId); + touchEvent.setTime(event.getEventTime()); + touchEvent.setPressure(event.getPressure(pointerIndex)); + lastHoverPositions.remove(pointerId); + + processEvent(touchEvent); + consumed = true; + break; + default: + consumed = false; + break; + } + + return consumed; + } + +} diff --git a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java b/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java deleted file mode 100644 index c8b222832..000000000 --- a/engine/src/android/com/jme3/input/android/AndroidTouchInputListener.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jme3.input.android; - -import android.view.KeyEvent; -import android.view.MotionEvent; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.TouchEvent; - -/** - * AndroidTouchInputListener is an inputlistener interface which defines - * callbacks/events for android touch screens For use with class AndroidInput - * - * @author larynx - * - */ -public interface AndroidTouchInputListener extends RawInputListener { - - public void onTouchEvent(TouchEvent evt); - - public void onMotionEvent(MotionEvent evt); - - public void onAndroidKeyEvent(KeyEvent evt); -} diff --git a/engine/src/android/com/jme3/input/android/TouchEventPool.java b/engine/src/android/com/jme3/input/android/TouchEventPool.java new file mode 100644 index 000000000..400a3bd38 --- /dev/null +++ b/engine/src/android/com/jme3/input/android/TouchEventPool.java @@ -0,0 +1,121 @@ +/* + * 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 com.jme3.input.event.TouchEvent; +import com.jme3.util.RingBuffer; +import java.util.logging.Logger; + +/** + * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage + * collection on Android. Each TouchEvent is stored in the RingBuffer and is + * reused if the TouchEvent has been consumed. + * + * If a TouchEvent has not been consumed, it is placed back into the pool at the + * end for later use. If a TouchEvent has been consumed, it is reused to avoid + * creating lots of little objects. + * + * If the pool is full of unconsumed events, then a new event is created and provided. + * + * + * @author iwgeric + */ +public class TouchEventPool { + private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName()); + private final RingBuffer eventPool; + private final int maxEvents; + + public TouchEventPool (int maxEvents) { + eventPool = new RingBuffer(maxEvents); + this.maxEvents = maxEvents; + } + + public void initialize() { + TouchEvent newEvent; + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + for (int i = 0; i < maxEvents; i++) { + newEvent = new TouchEvent(); + newEvent.setConsumed(); + eventPool.push(newEvent); + } + } + + public void destroy() { + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + } + + /** + * Fetches a touch event from the reuse pool + * + * @return a usable TouchEvent + */ + public TouchEvent getNextFreeEvent() { + TouchEvent evt = null; + int curSize = eventPool.size(); + while (curSize > 0) { + evt = (TouchEvent)eventPool.pop(); + if (evt.isConsumed()) { + break; + } else { + eventPool.push(evt); + evt = null; + } + curSize--; + } + + if (evt == null) { + logger.warning("eventPool full of unconsumed events"); + evt = new TouchEvent(); + } + return evt; + } + + /** + * Stores the TouchEvent back in the pool for later reuse. It is only reused + * if the TouchEvent has been consumed. + * + * @param event TouchEvent to store for later use if consumed. + */ + public void storeEvent(TouchEvent event) { + if (eventPool.size() < maxEvents) { + eventPool.push(event); + } else { + logger.warning("eventPool full"); + } + } + +}