diff --git a/engine/src/android/com/jme3/input/android/AndroidInput.java b/engine/src/android/com/jme3/input/android/AndroidInput.java index d0e6c183a..9bbbe4e50 100644 --- a/engine/src/android/com/jme3/input/android/AndroidInput.java +++ b/engine/src/android/com/jme3/input/android/AndroidInput.java @@ -1,616 +1,620 @@ -package com.jme3.input.android; - -import android.content.Context; -import android.opengl.GLSurfaceView; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import com.jme3.input.KeyInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.TouchInput; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jme3.input.event.TouchEvent; -import com.jme3.input.event.TouchEvent.Type; -import com.jme3.math.Vector2f; -import com.jme3.util.RingBuffer; -import java.util.HashMap; -import java.util.logging.Logger; - -/** - * AndroidInput is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs - * @author larynx - * - */ -public class AndroidInput extends GLSurfaceView implements - TouchInput, - GestureDetector.OnGestureListener, - GestureDetector.OnDoubleTapListener, - ScaleGestureDetector.OnScaleGestureListener { - - final private static int MAX_EVENTS = 1024; - // Custom settings - public boolean mouseEventsEnabled = true; - public boolean mouseEventsInvertX = false; - public boolean mouseEventsInvertY = false; - public boolean keyboardEventsEnabled = false; - public boolean dontSendHistory = false; - // Used to transfer events from android thread to GLThread - final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS); - final private RingBuffer eventPoolUnConsumed = new RingBuffer(MAX_EVENTS); - final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS); - final private HashMap lastPositions = new HashMap(); - // Internal - private ScaleGestureDetector scaledetector; - private GestureDetector detector; - private int lastX; - private int lastY; - private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); - private boolean isInitialized = false; - private RawInputListener listener = null; - 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_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 AndroidInput(Context ctx, AttributeSet attribs) { - super(ctx, attribs); - detector = new GestureDetector(null, this, null, false); - scaledetector = new ScaleGestureDetector(ctx, this); - - } - - public AndroidInput(Context ctx) { - super(ctx); - detector = new GestureDetector(null, this, null, false); - scaledetector = new ScaleGestureDetector(ctx, this); - } - - private TouchEvent getNextFreeTouchEvent() { - return getNextFreeTouchEvent(false); - } - - /** - * Fetches a touch event from the reuse pool - * @param wait if true waits for a reusable event to get available/released - * by an other thread, if false returns a new one if needed. - * - * @return a usable TouchEvent - */ - private TouchEvent getNextFreeTouchEvent(boolean wait) { - TouchEvent evt = null; - synchronized (eventPoolUnConsumed) { - int size = eventPoolUnConsumed.size(); - while (size > 0) { - evt = eventPoolUnConsumed.pop(); - if (!evt.isConsumed()) { - eventPoolUnConsumed.push(evt); - evt = null; - } else { - break; - } - size--; - } - } - - if (evt == null) { - if (eventPool.isEmpty() && wait) { - logger.warning("eventPool buffer underrun"); - boolean isEmpty; - do { - synchronized (eventPool) { - isEmpty = eventPool.isEmpty(); - } - try { - Thread.sleep(50); - } catch (InterruptedException e) { - } - } while (isEmpty); - synchronized (eventPool) { - evt = eventPool.pop(); - } - } else if (eventPool.isEmpty()) { - evt = new TouchEvent(); - logger.warning("eventPool buffer underrun"); - } else { - synchronized (eventPool) { - evt = eventPool.pop(); - } - } - } - return evt; - } - - /** - * onTouchEvent gets called from android thread on touchpad events - */ - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean bWasHandled = false; - TouchEvent touch; - // System.out.println("native : " + event.getAction()); - int action = event.getAction() & MotionEvent.ACTION_MASK; - int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - int pointerId = event.getPointerId(pointerIndex); - - // final int historySize = event.getHistorySize(); - //final int pointerCount = event.getPointerCount(); - - switch (action) { - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_DOWN: - touch = getNextFreeTouchEvent(); - touch.set(Type.DOWN, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - processEvent(touch); - - bWasHandled = true; - break; - - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - - touch = getNextFreeTouchEvent(); - touch.set(Type.UP, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - processEvent(touch); - - - bWasHandled = true; - break; - case MotionEvent.ACTION_MOVE: - - - // Convert all pointers into events - for (int p = 0; p < event.getPointerCount(); p++) { - Vector2f lastPos = lastPositions.get(pointerIndex); - if (lastPos == null) { - lastPos = new Vector2f(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); - lastPositions.put(pointerId, lastPos); - } - touch = getNextFreeTouchEvent(); - touch.set(Type.MOVE, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), event.getX(pointerIndex) - lastPos.x, this.getHeight() - event.getY(pointerIndex) - lastPos.y); - touch.setPointerId(pointerId); - touch.setTime(event.getEventTime()); - touch.setPressure(event.getPressure(pointerIndex)); - processEvent(touch); - lastPos.set(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); - } - bWasHandled = true; - break; - case MotionEvent.ACTION_OUTSIDE: - break; - - } - - // Try to detect gestures - this.detector.onTouchEvent(event); - this.scaledetector.onTouchEvent(event); - - return bWasHandled; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - TouchEvent evt; - evt = getNextFreeTouchEvent(); - evt.set(TouchEvent.Type.KEY_DOWN); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - processEvent(evt); - - // Handle all keys ourself except Volume Up/Down - if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { - return false; - } else { - return true; - } - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - TouchEvent evt; - evt = getNextFreeTouchEvent(); - evt.set(TouchEvent.Type.KEY_UP); - evt.setKeyCode(keyCode); - evt.setCharacters(event.getCharacters()); - evt.setTime(event.getEventTime()); - - // Send the event - processEvent(evt); - - // Handle all keys ourself except Volume Up/Down - if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { - return false; - } else { - return true; - } - } - - // ----------------------------------------- - // JME3 Input interface - @Override - public void initialize() { - TouchEvent item; - for (int i = 0; i < MAX_EVENTS; i++) { - item = new TouchEvent(); - eventPool.push(item); - } - isInitialized = true; - } - - @Override - public void destroy() { - isInitialized = false; - - // Clean up queues - while (!eventPool.isEmpty()) { - eventPool.pop(); - } - while (!eventQueue.isEmpty()) { - eventQueue.pop(); - } - } - - @Override - public boolean isInitialized() { - return isInitialized; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return System.nanoTime(); - } - // ----------------------------------------- - - private void processEvent(TouchEvent event) { - synchronized (eventQueue) { - eventQueue.push(event); - } - } - - // --------------- INSIDE GLThread --------------- - @Override - public void update() { - generateEvents(); - } - - private void generateEvents() { - if (listener != null) { - TouchEvent event; - MouseButtonEvent btn; - int newX; - int newY; - - while (!eventQueue.isEmpty()) { - synchronized (eventQueue) { - event = eventQueue.pop(); - } - if (event != null) { - listener.onTouchEvent(event); - - if (mouseEventsEnabled) { - if (mouseEventsInvertX) { - newX = this.getWidth() - (int) event.getX(); - } else { - newX = (int) event.getX(); - } - - if (mouseEventsInvertY) { - newY = this.getHeight() - (int) event.getY(); - } else { - newY = (int) event.getY(); - } - - switch (event.getType()) { - case DOWN: - // Handle mouse down event - btn = new MouseButtonEvent(0, true, newX, newY); - btn.setTime(event.getTime()); - listener.onMouseButtonEvent(btn); - // Store current pos - lastX = -1; - lastY = -1; - break; - - case UP: - // Handle mouse up event - btn = new MouseButtonEvent(0, false, newX, newY); - btn.setTime(event.getTime()); - listener.onMouseButtonEvent(btn); - // Store current pos - lastX = -1; - lastY = -1; - break; - - case MOVE: - int dx; - int dy; - if (lastX != -1) { - dx = newX - lastX; - dy = newY - lastY; - } else { - dx = 0; - dy = 0; - } - MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); - mot.setTime(event.getTime()); - listener.onMouseMotionEvent(mot); - lastX = newX; - lastY = newY; - break; - } - - - } - } - - if (event.isConsumed() == false) { - synchronized (eventPoolUnConsumed) { - eventPoolUnConsumed.push(event); - } - - } else { - synchronized (eventPool) { - eventPool.push(event); - } - } - } - - } - } - // --------------- ENDOF INSIDE GLThread --------------- - - // --------------- Gesture detected callback events --------------- - public boolean onDown(MotionEvent event) { - return false; - } - - public void onLongPress(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.LONGPRESSED, event.getX(), this.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - } - - public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.FLING, event.getX(), this.getHeight() - event.getY(), vx, vy); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - - return true; - } - - public boolean onSingleTapConfirmed(MotionEvent event) { - //Nothing to do here the tap has already been detected. - return false; - } - - public boolean onDoubleTap(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.DOUBLETAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - return true; - } - - public boolean onDoubleTapEvent(MotionEvent event) { - return false; - } - - public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - processEvent(touch); - // System.out.println("scaleBegin"); - - return true; - } - - public boolean onScale(ScaleGestureDetector scaleGestureDetector) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - processEvent(touch); - // System.out.println("scale"); - - return false; - } - - public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(scaleGestureDetector.getEventTime()); - touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); - touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); - processEvent(touch); - } - - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SCROLL, e1.getX(), this.getHeight() - e1.getY(), distanceX, distanceY * (-1)); - touch.setPointerId(0); - touch.setTime(e1.getEventTime()); - processEvent(touch); - //System.out.println("scroll " + e1.getPointerCount()); - return false; - } - - public void onShowPress(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.SHOWPRESS, event.getX(), this.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - } - - public boolean onSingleTapUp(MotionEvent event) { - TouchEvent touch = getNextFreeTouchEvent(); - touch.set(Type.TAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); - touch.setPointerId(0); - touch.setTime(event.getEventTime()); - processEvent(touch); - return true; - } - - @Override - public void setSimulateMouse(boolean simulate) { - mouseEventsEnabled = simulate; - } - - @Override - public void setSimulateKeyboard(boolean simulate) { - keyboardEventsEnabled = simulate; - } - - @Override - public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; - } - - // TODO: move to TouchInput - public boolean isMouseEventsEnabled() { - return mouseEventsEnabled; - } - - public void setMouseEventsEnabled(boolean mouseEventsEnabled) { - this.mouseEventsEnabled = mouseEventsEnabled; - } - - public boolean isMouseEventsInvertY() { - return mouseEventsInvertY; - } - - public void setMouseEventsInvertY(boolean mouseEventsInvertY) { - this.mouseEventsInvertY = mouseEventsInvertY; - } - - public boolean isMouseEventsInvertX() { - return mouseEventsInvertX; - } - - public void setMouseEventsInvertX(boolean mouseEventsInvertX) { - this.mouseEventsInvertX = mouseEventsInvertX; - } -} +package com.jme3.input.android; + +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.input.event.TouchEvent.Type; +import com.jme3.math.Vector2f; +import com.jme3.util.RingBuffer; +import java.util.HashMap; +import java.util.logging.Logger; + +/** + * AndroidInput is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs + * @author larynx + * + */ +public class AndroidInput extends GLSurfaceView implements + TouchInput, + GestureDetector.OnGestureListener, + GestureDetector.OnDoubleTapListener, + ScaleGestureDetector.OnScaleGestureListener { + + final private static int MAX_EVENTS = 1024; + // Custom settings + public boolean mouseEventsEnabled = true; + public boolean mouseEventsInvertX = false; + public boolean mouseEventsInvertY = false; + public boolean keyboardEventsEnabled = false; + public boolean dontSendHistory = false; + // Used to transfer events from android thread to GLThread + final private RingBuffer eventQueue = new RingBuffer(MAX_EVENTS); + final private RingBuffer eventPoolUnConsumed = new RingBuffer(MAX_EVENTS); + final private RingBuffer eventPool = new RingBuffer(MAX_EVENTS); + final private HashMap lastPositions = new HashMap(); + // Internal + private ScaleGestureDetector scaledetector; + private GestureDetector detector; + private int lastX; + private int lastY; + private final static Logger logger = Logger.getLogger(AndroidInput.class.getName()); + private boolean isInitialized = false; + private RawInputListener listener = null; + 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_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 AndroidInput(Context ctx, AttributeSet attribs) { + super(ctx, attribs); + detector = new GestureDetector(null, this, null, false); + scaledetector = new ScaleGestureDetector(ctx, this); + + } + + public AndroidInput(Context ctx) { + super(ctx); + detector = new GestureDetector(null, this, null, false); + scaledetector = new ScaleGestureDetector(ctx, this); + } + + private TouchEvent getNextFreeTouchEvent() { + return getNextFreeTouchEvent(false); + } + + /** + * Fetches a touch event from the reuse pool + * @param wait if true waits for a reusable event to get available/released + * by an other thread, if false returns a new one if needed. + * + * @return a usable TouchEvent + */ + private TouchEvent getNextFreeTouchEvent(boolean wait) { + TouchEvent evt = null; + synchronized (eventPoolUnConsumed) { + int size = eventPoolUnConsumed.size(); + while (size > 0) { + evt = eventPoolUnConsumed.pop(); + if (!evt.isConsumed()) { + eventPoolUnConsumed.push(evt); + evt = null; + } else { + break; + } + size--; + } + } + + if (evt == null) { + if (eventPool.isEmpty() && wait) { + logger.warning("eventPool buffer underrun"); + boolean isEmpty; + do { + synchronized (eventPool) { + isEmpty = eventPool.isEmpty(); + } + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + } while (isEmpty); + synchronized (eventPool) { + evt = eventPool.pop(); + } + } else if (eventPool.isEmpty()) { + evt = new TouchEvent(); + logger.warning("eventPool buffer underrun"); + } else { + synchronized (eventPool) { + evt = eventPool.pop(); + } + } + } + return evt; + } + + /** + * onTouchEvent gets called from android thread on touchpad events + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean bWasHandled = false; + TouchEvent touch; + // System.out.println("native : " + event.getAction()); + int action = event.getAction() & MotionEvent.ACTION_MASK; + int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + int pointerId = event.getPointerId(pointerIndex); + + // final int historySize = event.getHistorySize(); + //final int pointerCount = event.getPointerCount(); + + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + touch = getNextFreeTouchEvent(); + touch.set(Type.DOWN, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + + bWasHandled = true; + break; + + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + + touch = getNextFreeTouchEvent(); + touch.set(Type.UP, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + + + bWasHandled = true; + break; + case MotionEvent.ACTION_MOVE: + + + // Convert all pointers into events + for (int p = 0; p < event.getPointerCount(); p++) { + Vector2f lastPos = lastPositions.get(pointerIndex); + if (lastPos == null) { + lastPos = new Vector2f(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); + lastPositions.put(pointerId, lastPos); + } + touch = getNextFreeTouchEvent(); + touch.set(Type.MOVE, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), event.getX(pointerIndex) - lastPos.x, this.getHeight() - event.getY(pointerIndex) - lastPos.y); + touch.setPointerId(pointerId); + touch.setTime(event.getEventTime()); + touch.setPressure(event.getPressure(pointerIndex)); + processEvent(touch); + lastPos.set(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex)); + } + bWasHandled = true; + break; + case MotionEvent.ACTION_OUTSIDE: + break; + + } + + // Try to detect gestures + this.detector.onTouchEvent(event); + this.scaledetector.onTouchEvent(event); + + return bWasHandled; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_DOWN); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + TouchEvent evt; + evt = getNextFreeTouchEvent(); + evt.set(TouchEvent.Type.KEY_UP); + evt.setKeyCode(keyCode); + evt.setCharacters(event.getCharacters()); + evt.setTime(event.getEventTime()); + + // Send the event + processEvent(evt); + + // Handle all keys ourself except Volume Up/Down + if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) { + return false; + } else { + return true; + } + } + + // ----------------------------------------- + // JME3 Input interface + @Override + public void initialize() { + TouchEvent item; + for (int i = 0; i < MAX_EVENTS; i++) { + item = new TouchEvent(); + eventPool.push(item); + } + isInitialized = true; + } + + @Override + public void destroy() { + isInitialized = false; + + // Clean up queues + while (!eventPool.isEmpty()) { + eventPool.pop(); + } + while (!eventQueue.isEmpty()) { + eventQueue.pop(); + } + } + + @Override + public boolean isInitialized() { + return isInitialized; + } + + @Override + public void setInputListener(RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return System.nanoTime(); + } + // ----------------------------------------- + + private void processEvent(TouchEvent event) { + synchronized (eventQueue) { + eventQueue.push(event); + } + } + + // --------------- INSIDE GLThread --------------- + @Override + public void update() { + generateEvents(); + } + + private void generateEvents() { + if (listener != null) { + TouchEvent event; + MouseButtonEvent btn; + int newX; + int newY; + + while (!eventQueue.isEmpty()) { + synchronized (eventQueue) { + event = eventQueue.pop(); + } + if (event != null) { + listener.onTouchEvent(event); + + if (mouseEventsEnabled) { + if (mouseEventsInvertX) { + newX = this.getWidth() - (int) event.getX(); + } else { + newX = (int) event.getX(); + } + + if (mouseEventsInvertY) { + newY = this.getHeight() - (int) event.getY(); + } else { + newY = (int) event.getY(); + } + + switch (event.getType()) { + case DOWN: + // Handle mouse down event + btn = new MouseButtonEvent(0, true, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case UP: + // Handle mouse up event + btn = new MouseButtonEvent(0, false, newX, newY); + btn.setTime(event.getTime()); + listener.onMouseButtonEvent(btn); + // Store current pos + lastX = -1; + lastY = -1; + break; + + case MOVE: + int dx; + int dy; + if (lastX != -1) { + dx = newX - lastX; + dy = newY - lastY; + } else { + dx = 0; + dy = 0; + } + MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0); + mot.setTime(event.getTime()); + listener.onMouseMotionEvent(mot); + lastX = newX; + lastY = newY; + break; + } + + + } + } + + if (event.isConsumed() == false) { + synchronized (eventPoolUnConsumed) { + eventPoolUnConsumed.push(event); + } + + } else { + synchronized (eventPool) { + eventPool.push(event); + } + } + } + + } + } + // --------------- ENDOF INSIDE GLThread --------------- + + // --------------- Gesture detected callback events --------------- + public boolean onDown(MotionEvent event) { + return false; + } + + public void onLongPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.LONGPRESSED, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.FLING, event.getX(), this.getHeight() - event.getY(), vx, vy); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + + return true; + } + + public boolean onSingleTapConfirmed(MotionEvent event) { + //Nothing to do here the tap has already been detected. + return false; + } + + public boolean onDoubleTap(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.DOUBLETAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + public boolean onDoubleTapEvent(MotionEvent event) { + return false; + } + + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + // System.out.println("scaleBegin"); + + return true; + } + + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + // System.out.println("scale"); + + return false; + } + + public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(scaleGestureDetector.getEventTime()); + touch.setScaleSpan(scaleGestureDetector.getCurrentSpan()); + touch.setScaleFactor(scaleGestureDetector.getScaleFactor()); + processEvent(touch); + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SCROLL, e1.getX(), this.getHeight() - e1.getY(), distanceX, distanceY * (-1)); + touch.setPointerId(0); + touch.setTime(e1.getEventTime()); + processEvent(touch); + //System.out.println("scroll " + e1.getPointerCount()); + return false; + } + + public void onShowPress(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.SHOWPRESS, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + } + + public boolean onSingleTapUp(MotionEvent event) { + TouchEvent touch = getNextFreeTouchEvent(); + touch.set(Type.TAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f); + touch.setPointerId(0); + touch.setTime(event.getEventTime()); + processEvent(touch); + return true; + } + + @Override + public void setSimulateMouse(boolean simulate) { + mouseEventsEnabled = simulate; + } + @Override + public boolean getSimulateMouse() { + return mouseEventsEnabled; + } + + @Override + public void setSimulateKeyboard(boolean simulate) { + keyboardEventsEnabled = simulate; + } + + @Override + public void setOmitHistoricEvents(boolean dontSendHistory) { + this.dontSendHistory = dontSendHistory; + } + + // TODO: move to TouchInput + public boolean isMouseEventsEnabled() { + return mouseEventsEnabled; + } + + public void setMouseEventsEnabled(boolean mouseEventsEnabled) { + this.mouseEventsEnabled = mouseEventsEnabled; + } + + public boolean isMouseEventsInvertY() { + return mouseEventsInvertY; + } + + public void setMouseEventsInvertY(boolean mouseEventsInvertY) { + this.mouseEventsInvertY = mouseEventsInvertY; + } + + public boolean isMouseEventsInvertX() { + return mouseEventsInvertX; + } + + public void setMouseEventsInvertX(boolean mouseEventsInvertX) { + this.mouseEventsInvertX = mouseEventsInvertX; + } +} diff --git a/engine/src/core/com/jme3/input/InputManager.java b/engine/src/core/com/jme3/input/InputManager.java index 23f29887f..eb6b09849 100644 --- a/engine/src/core/com/jme3/input/InputManager.java +++ b/engine/src/core/com/jme3/input/InputManager.java @@ -1,881 +1,892 @@ -/* - * 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; - -import com.jme3.app.Application; -import com.jme3.input.controls.*; -import com.jme3.input.event.*; -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.util.IntMap; -import com.jme3.util.IntMap.Entry; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The InputManager is responsible for converting input events - * received from the Key, Mouse and Joy Input implementations into an - * abstract, input device independent representation that user code can use. - *

- * By default an InputManager is included with every Application instance for use - * in user code to query input, unless the Application is created as headless - * or with input explicitly disabled. - *

- * The input manager has two concepts, a {@link Trigger} and a mapping. - * A trigger represents a specific input trigger, such as a key button, - * or a mouse axis. A mapping represents a link onto one or several triggers, - * when the appropriate trigger is activated (e.g. a key is pressed), the - * mapping will be invoked. Any listeners registered to receive an event - * from the mapping will have an event raised. - *

- * There are two types of events that {@link InputListener input listeners} - * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action} - * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog} - * events. - *

- * onAction events are raised when the specific input - * activates or deactivates. For a digital input such as key press, the onAction() - * event will be raised with the isPressed argument equal to true, - * when the key is released, onAction is called again but this time - * with the isPressed argument set to false. - * For analog inputs, the onAction method will be called any time - * the input is non-zero, however an exception to this is for joystick axis inputs, - * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}. - *

- * onAnalog events are raised every frame while the input is activated. - * For digital inputs, every frame that the input is active will cause the - * onAnalog method to be called, the argument value - * argument will equal to the frame's time per frame (TPF) value but only - * for digital inputs. For analog inputs however, the value argument - * will equal the actual analog value. - */ -public class InputManager implements RawInputListener { - - private static final Logger logger = Logger.getLogger(InputManager.class.getName()); - private final KeyInput keys; - private final MouseInput mouse; - private final JoyInput joystick; - private final TouchInput touch; - private float frameTPF; - private long lastLastUpdateTime = 0; - private long lastUpdateTime = 0; - private long frameDelta = 0; - private long firstTime = 0; - private boolean eventsPermitted = false; - private boolean mouseVisible = true; - private boolean safeMode = false; - private float axisDeadZone = 0.05f; - private Vector2f cursorPos = new Vector2f(); - private Joystick[] joysticks; - private final IntMap> bindings = new IntMap>(); - private final HashMap mappings = new HashMap(); - private final IntMap pressedButtons = new IntMap(); - private final IntMap axisValues = new IntMap(); - private ArrayList rawListeners = new ArrayList(); - private RawInputListener[] rawListenerArray = null; - private ArrayList inputQueue = new ArrayList(); - - private static class Mapping { - - private final String name; - private final ArrayList triggers = new ArrayList(); - private final ArrayList listeners = new ArrayList(); - - public Mapping(String name) { - this.name = name; - } - } - - /** - * Initializes the InputManager. - * - *

This should only be called internally in {@link Application}. - * - * @param mouse - * @param keys - * @param joystick - * @param touch - * @throws IllegalArgumentException If either mouseInput or keyInput are null. - */ - public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) { - if (keys == null || mouse == null) { - throw new NullPointerException("Mouse or keyboard cannot be null"); - } - - this.keys = keys; - this.mouse = mouse; - this.joystick = joystick; - this.touch = touch; - - keys.setInputListener(this); - mouse.setInputListener(this); - if (joystick != null) { - joystick.setInputListener(this); - joysticks = joystick.loadJoysticks(this); - } - if (touch != null) { - touch.setInputListener(this); - } - - firstTime = keys.getInputTimeNanos(); - } - - private void invokeActions(int hash, boolean pressed) { - ArrayList maps = bindings.get(hash); - if (maps == null) { - return; - } - - int size = maps.size(); - for (int i = size - 1; i >= 0; i--) { - Mapping mapping = maps.get(i); - ArrayList listeners = mapping.listeners; - int listenerSize = listeners.size(); - for (int j = listenerSize - 1; j >= 0; j--) { - InputListener listener = listeners.get(j); - if (listener instanceof ActionListener) { - ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF); - } - } - } - } - - private float computeAnalogValue(long timeDelta) { - if (safeMode || frameDelta == 0) { - return 1f; - } else { - return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1); - } - } - - private void invokeTimedActions(int hash, long time, boolean pressed) { - if (!bindings.containsKey(hash)) { - return; - } - - if (pressed) { - pressedButtons.put(hash, time); - } else { - Long pressTimeObj = pressedButtons.remove(hash); - if (pressTimeObj == null) { - return; // under certain circumstances it can be null, ignore - } // the event then. - - long pressTime = pressTimeObj; - long lastUpdate = lastLastUpdateTime; - long releaseTime = time; - long timeDelta = releaseTime - Math.max(pressTime, lastUpdate); - - if (timeDelta > 0) { - invokeAnalogs(hash, computeAnalogValue(timeDelta), false); - } - } - } - - private void invokeUpdateActions() { - for (Entry pressedButton : pressedButtons) { - int hash = pressedButton.getKey(); - - long pressTime = pressedButton.getValue(); - long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime); - - if (timeDelta > 0) { - invokeAnalogs(hash, computeAnalogValue(timeDelta), false); - } - } - - for (Entry axisValue : axisValues) { - int hash = axisValue.getKey(); - float value = axisValue.getValue(); - invokeAnalogs(hash, value * frameTPF, true); - } - } - - private void invokeAnalogs(int hash, float value, boolean isAxis) { - ArrayList maps = bindings.get(hash); - if (maps == null) { - return; - } - - if (!isAxis) { - value *= frameTPF; - } - - int size = maps.size(); - for (int i = size - 1; i >= 0; i--) { - Mapping mapping = maps.get(i); - ArrayList listeners = mapping.listeners; - int listenerSize = listeners.size(); - for (int j = listenerSize - 1; j >= 0; j--) { - InputListener listener = listeners.get(j); - if (listener instanceof AnalogListener) { - // NOTE: multiply by TPF for any button bindings - ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF); - } - } - } - } - - private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) { - if (value < axisDeadZone) { - invokeAnalogs(hash, value, !applyTpf); - return; - } - - ArrayList maps = bindings.get(hash); - if (maps == null) { - return; - } - - boolean valueChanged = !axisValues.containsKey(hash); - if (applyTpf) { - value *= frameTPF; - } - - int size = maps.size(); - for (int i = size - 1; i >= 0; i--) { - Mapping mapping = maps.get(i); - ArrayList listeners = mapping.listeners; - int listenerSize = listeners.size(); - for (int j = listenerSize - 1; j >= 0; j--) { - InputListener listener = listeners.get(j); - - if (listener instanceof ActionListener && valueChanged) { - ((ActionListener) listener).onAction(mapping.name, true, frameTPF); - } - - if (listener instanceof AnalogListener) { - ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF); - } - - } - } - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void beginInput() { - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void endInput() { - } - - private void onJoyAxisEventQueued(JoyAxisEvent evt) { -// for (int i = 0; i < rawListeners.size(); i++){ -// rawListeners.get(i).onJoyAxisEvent(evt); -// } - - int joyId = evt.getJoyIndex(); - int axis = evt.getAxisIndex(); - float value = evt.getValue(); - if (value < axisDeadZone && value > -axisDeadZone) { - int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true); - int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false); - - Float val1 = axisValues.get(hash1); - Float val2 = axisValues.get(hash2); - - if (val1 != null && val1.floatValue() > axisDeadZone) { - invokeActions(hash1, false); - } - if (val2 != null && val2.floatValue() > axisDeadZone) { - invokeActions(hash2, false); - } - - axisValues.remove(hash1); - axisValues.remove(hash2); - - } else if (value < 0) { - int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); - int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); - invokeAnalogsAndActions(hash, -value, true); - axisValues.put(hash, -value); - axisValues.remove(otherHash); - } else { - int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); - int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); - invokeAnalogsAndActions(hash, value, true); - axisValues.put(hash, value); - axisValues.remove(otherHash); - } - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void onJoyAxisEvent(JoyAxisEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); - } - - inputQueue.add(evt); - } - - private void onJoyButtonEventQueued(JoyButtonEvent evt) { -// for (int i = 0; i < rawListeners.size(); i++){ -// rawListeners.get(i).onJoyButtonEvent(evt); -// } - - int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex()); - invokeActions(hash, evt.isPressed()); - invokeTimedActions(hash, evt.getTime(), evt.isPressed()); - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void onJoyButtonEvent(JoyButtonEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); - } - - inputQueue.add(evt); - } - - private void onMouseMotionEventQueued(MouseMotionEvent evt) { -// for (int i = 0; i < rawListeners.size(); i++){ -// rawListeners.get(i).onMouseMotionEvent(evt); -// } - - if (evt.getDX() != 0) { - float val = Math.abs(evt.getDX()) / 1024f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false); - } - if (evt.getDY() != 0) { - float val = Math.abs(evt.getDY()) / 1024f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false); - } - if (evt.getDeltaWheel() != 0) { - float val = Math.abs(evt.getDeltaWheel()) / 100f; - invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false); - } - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void onMouseMotionEvent(MouseMotionEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); - } - - cursorPos.set(evt.getX(), evt.getY()); - inputQueue.add(evt); - } - - private void onMouseButtonEventQueued(MouseButtonEvent evt) { - int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex()); - invokeActions(hash, evt.isPressed()); - invokeTimedActions(hash, evt.getTime(), evt.isPressed()); - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void onMouseButtonEvent(MouseButtonEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); - } - //updating cursor pos on click, so that non android touch events can properly update cursor position. - cursorPos.set(evt.getX(), evt.getY()); - inputQueue.add(evt); - } - - private void onKeyEventQueued(KeyInputEvent evt) { - if (evt.isRepeating()) { - return; // repeat events not used for bindings - } - - int hash = KeyTrigger.keyHash(evt.getKeyCode()); - invokeActions(hash, evt.isPressed()); - invokeTimedActions(hash, evt.getTime(), evt.isPressed()); - } - - /** - * Callback from RawInputListener. Do not use. - */ - public void onKeyEvent(KeyInputEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time."); - } - - inputQueue.add(evt); - } - - /** - * Set the deadzone for joystick axes. - * - *

{@link ActionListener#onAction(java.lang.String, boolean, float) } - * events will only be raised if the joystick axis value is greater than - * the deadZone. - * - * @param deadZone the deadzone for joystick axes. - */ - public void setAxisDeadZone(float deadZone) { - this.axisDeadZone = deadZone; - } - - /** - * Returns the deadzone for joystick axes. - * - * @return the deadzone for joystick axes. - */ - public float getAxisDeadZone() { - return axisDeadZone; - } - - /** - * Adds a new listener to receive events on the given mappings. - * - *

The given InputListener will be registered to receive events - * on the specified mapping names. When a mapping raises an event, the - * listener will have its appropriate method invoked, either - * {@link ActionListener#onAction(java.lang.String, boolean, float) } - * or {@link AnalogListener#onAnalog(java.lang.String, float, float) } - * depending on which interface the listener implements. - * If the listener implements both interfaces, then it will receive the - * appropriate event for each method. - * - * @param listener The listener to register to receive input events. - * @param mappingNames The mapping names which the listener will receive - * events from. - * - * @see InputManager#removeListener(com.jme3.input.controls.InputListener) - */ - public void addListener(InputListener listener, String... mappingNames) { - for (String mappingName : mappingNames) { - Mapping mapping = mappings.get(mappingName); - if (mapping == null) { - mapping = new Mapping(mappingName); - mappings.put(mappingName, mapping); - } - if (!mapping.listeners.contains(listener)) { - mapping.listeners.add(listener); - } - } - } - - /** - * Removes a listener from receiving events. - * - *

This will unregister the listener from any mappings that it - * was previously registered with via - * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }. - * - * @param listener The listener to unregister. - * - * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) - */ - public void removeListener(InputListener listener) { - for (Mapping mapping : mappings.values()) { - mapping.listeners.remove(listener); - } - } - - /** - * Create a new mapping to the given triggers. - * - *

- * The given mapping will be assigned to the given triggers, when - * any of the triggers given raise an event, the listeners - * registered to the mappings will receive appropriate events. - * - * @param mappingName The mapping name to assign. - * @param triggers The triggers to which the mapping is to be registered. - * - * @see InputManager#deleteMapping(java.lang.String) - */ - public void addMapping(String mappingName, Trigger... triggers) { - Mapping mapping = mappings.get(mappingName); - if (mapping == null) { - mapping = new Mapping(mappingName); - mappings.put(mappingName, mapping); - } - - for (Trigger trigger : triggers) { - int hash = trigger.triggerHashCode(); - ArrayList names = bindings.get(hash); - if (names == null) { - names = new ArrayList(); - bindings.put(hash, names); - } - if (!names.contains(mapping)) { - names.add(mapping); - mapping.triggers.add(hash); - } else { - logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName); - } - } - } - - /** - * Returns true if this InputManager has a mapping registered - * for the given mappingName. - * - * @param mappingName The mapping name to check. - * - * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) - * @see InputManager#deleteMapping(java.lang.String) - */ - public boolean hasMapping(String mappingName) { - return mappings.containsKey(mappingName); - } - - /** - * Deletes a mapping from receiving trigger events. - * - *

- * The given mapping will no longer be assigned to receive trigger - * events. - * - * @param mappingName The mapping name to unregister. - * - * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) - */ - public void deleteMapping(String mappingName) { - Mapping mapping = mappings.remove(mappingName); - if (mapping == null) { - throw new IllegalArgumentException("Cannot find mapping: " + mappingName); - } - - ArrayList triggers = mapping.triggers; - for (int i = triggers.size() - 1; i >= 0; i--) { - int hash = triggers.get(i); - ArrayList maps = bindings.get(hash); - maps.remove(mapping); - } - } - - /** - * Deletes a specific trigger registered to a mapping. - * - *

- * The given mapping will no longer receive events raised by the - * trigger. - * - * @param mappingName The mapping name to cease receiving events from the - * trigger. - * @param trigger The trigger to no longer invoke events on the mapping. - */ - public void deleteTrigger(String mappingName, Trigger trigger) { - Mapping mapping = mappings.get(mappingName); - if (mapping == null) { - throw new IllegalArgumentException("Cannot find mapping: " + mappingName); - } - - ArrayList maps = bindings.get(trigger.triggerHashCode()); - maps.remove(mapping); - - } - - /** - * Clears all the input mappings from this InputManager. - * Consequently, also clears all of the - * InputListeners as well. - */ - public void clearMappings() { - mappings.clear(); - bindings.clear(); - reset(); - } - - /** - * Do not use. - * Called to reset pressed keys or buttons when focus is restored. - */ - public void reset() { - pressedButtons.clear(); - axisValues.clear(); - } - - /** - * Returns whether the mouse cursor is visible or not. - * - *

By default the cursor is visible. - * - * @return whether the mouse cursor is visible or not. - * - * @see InputManager#setCursorVisible(boolean) - */ - public boolean isCursorVisible() { - return mouseVisible; - } - - /** - * Set whether the mouse cursor should be visible or not. - * - * @param visible whether the mouse cursor should be visible or not. - */ - public void setCursorVisible(boolean visible) { - if (mouseVisible != visible) { - mouseVisible = visible; - mouse.setCursorVisible(mouseVisible); - } - } - - /** - * Returns the current cursor position. The position is relative to the - * bottom-left of the screen and is in pixels. - * - * @return the current cursor position - */ - public Vector2f getCursorPosition() { - return cursorPos; - } - - /** - * Returns an array of all joysticks installed on the system. - * - * @return an array of all joysticks installed on the system. - */ - public Joystick[] getJoysticks() { - return joysticks; - } - - /** - * Adds a {@link RawInputListener} to receive raw input events. - * - *

- * Any raw input listeners registered to this InputManager - * will receive raw input events first, before they get handled - * by the InputManager itself. The listeners are - * each processed in the order they were added, e.g. FIFO. - *

- * If a raw input listener has handled the event and does not wish - * other listeners down the list to process the event, it may set the - * {@link InputEvent#setConsumed() consumed flag} to indicate the - * event was consumed and shouldn't be processed any further. - * The listener may do this either at each of the event callbacks - * or at the {@link RawInputListener#endInput() } method. - * - * @param listener A listener to receive raw input events. - * - * @see RawInputListener - */ - public void addRawInputListener(RawInputListener listener) { - rawListeners.add(listener); - rawListenerArray = null; - } - - /** - * Removes a {@link RawInputListener} so that it no longer - * receives raw input events. - * - * @param listener The listener to cease receiving raw input events. - * - * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) - */ - public void removeRawInputListener(RawInputListener listener) { - rawListeners.remove(listener); - rawListenerArray = null; - } - - /** - * Clears all {@link RawInputListener}s. - * - * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) - */ - public void clearRawInputListeners() { - rawListeners.clear(); - rawListenerArray = null; - } - - private RawInputListener[] getRawListenerArray() { - if (rawListenerArray == null) - rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]); - return rawListenerArray; - } - - /** - * Enable simulation of mouse events. Used for touchscreen input only. - * - * @param value True to enable simulation of mouse events - */ - public void setSimulateMouse(boolean value) { - if (touch != null) { - touch.setSimulateMouse(value); - } - } - - /** - * Enable simulation of keyboard events. Used for touchscreen input only. - * - * @param value True to enable simulation of keyboard events - */ - public void setSimulateKeyboard(boolean value) { - if (touch != null) { - touch.setSimulateKeyboard(value); - } - } - - private void processQueue() { - int queueSize = inputQueue.size(); - RawInputListener[] array = getRawListenerArray(); - - for (RawInputListener listener : array) { - listener.beginInput(); - - for (int j = 0; j < queueSize; j++) { - InputEvent event = inputQueue.get(j); - if (event.isConsumed()) { - continue; - } - - if (event instanceof MouseMotionEvent) { - listener.onMouseMotionEvent((MouseMotionEvent) event); - } else if (event instanceof KeyInputEvent) { - listener.onKeyEvent((KeyInputEvent) event); - } else if (event instanceof MouseButtonEvent) { - listener.onMouseButtonEvent((MouseButtonEvent) event); - } else if (event instanceof JoyAxisEvent) { - listener.onJoyAxisEvent((JoyAxisEvent) event); - } else if (event instanceof JoyButtonEvent) { - listener.onJoyButtonEvent((JoyButtonEvent) event); - } else if (event instanceof TouchEvent) { - listener.onTouchEvent((TouchEvent) event); - } else { - assert false; - } - } - - listener.endInput(); - } - - for (int i = 0; i < queueSize; i++) { - InputEvent event = inputQueue.get(i); - if (event.isConsumed()) { - continue; - } - - if (event instanceof MouseMotionEvent) { - onMouseMotionEventQueued((MouseMotionEvent) event); - } else if (event instanceof KeyInputEvent) { - onKeyEventQueued((KeyInputEvent) event); - } else if (event instanceof MouseButtonEvent) { - onMouseButtonEventQueued((MouseButtonEvent) event); - } else if (event instanceof JoyAxisEvent) { - onJoyAxisEventQueued((JoyAxisEvent) event); - } else if (event instanceof JoyButtonEvent) { - onJoyButtonEventQueued((JoyButtonEvent) event); - } else if (event instanceof TouchEvent) { - onTouchEventQueued((TouchEvent) event); - } else { - assert false; - } - // larynx, 2011.06.10 - flag event as reusable because - // the android input uses a non-allocating ringbuffer which - // needs to know when the event is not anymore in inputQueue - // and therefor can be reused. - event.setConsumed(); - } - - inputQueue.clear(); - } - - /** - * Updates the InputManager. - * This will query current input devices and send - * appropriate events to registered listeners. - * - * @param tpf Time per frame value. - */ - public void update(float tpf) { - frameTPF = tpf; - - // Activate safemode if the TPF value is so small - // that rounding errors are inevitable - safeMode = tpf < 0.015f; - - long currentTime = keys.getInputTimeNanos(); - frameDelta = currentTime - lastUpdateTime; - - eventsPermitted = true; - - keys.update(); - mouse.update(); - if (joystick != null) { - joystick.update(); - } - if (touch != null) { - touch.update(); - } - - eventsPermitted = false; - - processQueue(); - invokeUpdateActions(); - - lastLastUpdateTime = lastUpdateTime; - lastUpdateTime = currentTime; - } - - /** - * Dispatches touch events to touch listeners - * @param evt The touch event to be dispatched to all onTouch listeners - */ - public void onTouchEventQueued(TouchEvent evt) { - ArrayList maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode())); - if (maps == null) { - return; - } - - int size = maps.size(); - for (int i = size - 1; i >= 0; i--) { - Mapping mapping = maps.get(i); - ArrayList listeners = mapping.listeners; - int listenerSize = listeners.size(); - for (int j = listenerSize - 1; j >= 0; j--) { - InputListener listener = listeners.get(j); - if (listener instanceof TouchListener) { - ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF); - } - } - } - } - - /** - * Callback from RawInputListener. Do not use. - */ - @Override - public void onTouchEvent(TouchEvent evt) { - if (!eventsPermitted) { - throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time."); - } - inputQueue.add(evt); - } -} +/* + * 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; + +import com.jme3.app.Application; +import com.jme3.input.controls.*; +import com.jme3.input.event.*; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.util.IntMap; +import com.jme3.util.IntMap.Entry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The InputManager is responsible for converting input events + * received from the Key, Mouse and Joy Input implementations into an + * abstract, input device independent representation that user code can use. + *

+ * By default an InputManager is included with every Application instance for use + * in user code to query input, unless the Application is created as headless + * or with input explicitly disabled. + *

+ * The input manager has two concepts, a {@link Trigger} and a mapping. + * A trigger represents a specific input trigger, such as a key button, + * or a mouse axis. A mapping represents a link onto one or several triggers, + * when the appropriate trigger is activated (e.g. a key is pressed), the + * mapping will be invoked. Any listeners registered to receive an event + * from the mapping will have an event raised. + *

+ * There are two types of events that {@link InputListener input listeners} + * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action} + * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog} + * events. + *

+ * onAction events are raised when the specific input + * activates or deactivates. For a digital input such as key press, the onAction() + * event will be raised with the isPressed argument equal to true, + * when the key is released, onAction is called again but this time + * with the isPressed argument set to false. + * For analog inputs, the onAction method will be called any time + * the input is non-zero, however an exception to this is for joystick axis inputs, + * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}. + *

+ * onAnalog events are raised every frame while the input is activated. + * For digital inputs, every frame that the input is active will cause the + * onAnalog method to be called, the argument value + * argument will equal to the frame's time per frame (TPF) value but only + * for digital inputs. For analog inputs however, the value argument + * will equal the actual analog value. + */ +public class InputManager implements RawInputListener { + + private static final Logger logger = Logger.getLogger(InputManager.class.getName()); + private final KeyInput keys; + private final MouseInput mouse; + private final JoyInput joystick; + private final TouchInput touch; + private float frameTPF; + private long lastLastUpdateTime = 0; + private long lastUpdateTime = 0; + private long frameDelta = 0; + private long firstTime = 0; + private boolean eventsPermitted = false; + private boolean mouseVisible = true; + private boolean safeMode = false; + private float axisDeadZone = 0.05f; + private Vector2f cursorPos = new Vector2f(); + private Joystick[] joysticks; + private final IntMap> bindings = new IntMap>(); + private final HashMap mappings = new HashMap(); + private final IntMap pressedButtons = new IntMap(); + private final IntMap axisValues = new IntMap(); + private ArrayList rawListeners = new ArrayList(); + private RawInputListener[] rawListenerArray = null; + private ArrayList inputQueue = new ArrayList(); + + private static class Mapping { + + private final String name; + private final ArrayList triggers = new ArrayList(); + private final ArrayList listeners = new ArrayList(); + + public Mapping(String name) { + this.name = name; + } + } + + /** + * Initializes the InputManager. + * + *

This should only be called internally in {@link Application}. + * + * @param mouse + * @param keys + * @param joystick + * @param touch + * @throws IllegalArgumentException If either mouseInput or keyInput are null. + */ + public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) { + if (keys == null || mouse == null) { + throw new NullPointerException("Mouse or keyboard cannot be null"); + } + + this.keys = keys; + this.mouse = mouse; + this.joystick = joystick; + this.touch = touch; + + keys.setInputListener(this); + mouse.setInputListener(this); + if (joystick != null) { + joystick.setInputListener(this); + joysticks = joystick.loadJoysticks(this); + } + if (touch != null) { + touch.setInputListener(this); + } + + firstTime = keys.getInputTimeNanos(); + } + + private void invokeActions(int hash, boolean pressed) { + ArrayList maps = bindings.get(hash); + if (maps == null) { + return; + } + + int size = maps.size(); + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + if (listener instanceof ActionListener) { + ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF); + } + } + } + } + + private float computeAnalogValue(long timeDelta) { + if (safeMode || frameDelta == 0) { + return 1f; + } else { + return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1); + } + } + + private void invokeTimedActions(int hash, long time, boolean pressed) { + if (!bindings.containsKey(hash)) { + return; + } + + if (pressed) { + pressedButtons.put(hash, time); + } else { + Long pressTimeObj = pressedButtons.remove(hash); + if (pressTimeObj == null) { + return; // under certain circumstances it can be null, ignore + } // the event then. + + long pressTime = pressTimeObj; + long lastUpdate = lastLastUpdateTime; + long releaseTime = time; + long timeDelta = releaseTime - Math.max(pressTime, lastUpdate); + + if (timeDelta > 0) { + invokeAnalogs(hash, computeAnalogValue(timeDelta), false); + } + } + } + + private void invokeUpdateActions() { + for (Entry pressedButton : pressedButtons) { + int hash = pressedButton.getKey(); + + long pressTime = pressedButton.getValue(); + long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime); + + if (timeDelta > 0) { + invokeAnalogs(hash, computeAnalogValue(timeDelta), false); + } + } + + for (Entry axisValue : axisValues) { + int hash = axisValue.getKey(); + float value = axisValue.getValue(); + invokeAnalogs(hash, value * frameTPF, true); + } + } + + private void invokeAnalogs(int hash, float value, boolean isAxis) { + ArrayList maps = bindings.get(hash); + if (maps == null) { + return; + } + + if (!isAxis) { + value *= frameTPF; + } + + int size = maps.size(); + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + if (listener instanceof AnalogListener) { + // NOTE: multiply by TPF for any button bindings + ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF); + } + } + } + } + + private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) { + if (value < axisDeadZone) { + invokeAnalogs(hash, value, !applyTpf); + return; + } + + ArrayList maps = bindings.get(hash); + if (maps == null) { + return; + } + + boolean valueChanged = !axisValues.containsKey(hash); + if (applyTpf) { + value *= frameTPF; + } + + int size = maps.size(); + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + + if (listener instanceof ActionListener && valueChanged) { + ((ActionListener) listener).onAction(mapping.name, true, frameTPF); + } + + if (listener instanceof AnalogListener) { + ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF); + } + + } + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void beginInput() { + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void endInput() { + } + + private void onJoyAxisEventQueued(JoyAxisEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onJoyAxisEvent(evt); +// } + + int joyId = evt.getJoyIndex(); + int axis = evt.getAxisIndex(); + float value = evt.getValue(); + if (value < axisDeadZone && value > -axisDeadZone) { + int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + + Float val1 = axisValues.get(hash1); + Float val2 = axisValues.get(hash2); + + if (val1 != null && val1.floatValue() > axisDeadZone) { + invokeActions(hash1, false); + } + if (val2 != null && val2.floatValue() > axisDeadZone) { + invokeActions(hash2, false); + } + + axisValues.remove(hash1); + axisValues.remove(hash2); + + } else if (value < 0) { + int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + invokeAnalogsAndActions(hash, -value, true); + axisValues.put(hash, -value); + axisValues.remove(otherHash); + } else { + int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false); + int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true); + invokeAnalogsAndActions(hash, value, true); + axisValues.put(hash, value); + axisValues.remove(otherHash); + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onJoyAxisEvent(JoyAxisEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + private void onJoyButtonEventQueued(JoyButtonEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onJoyButtonEvent(evt); +// } + + int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onJoyButtonEvent(JoyButtonEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + private void onMouseMotionEventQueued(MouseMotionEvent evt) { +// for (int i = 0; i < rawListeners.size(); i++){ +// rawListeners.get(i).onMouseMotionEvent(evt); +// } + + if (evt.getDX() != 0) { + float val = Math.abs(evt.getDX()) / 1024f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false); + } + if (evt.getDY() != 0) { + float val = Math.abs(evt.getDY()) / 1024f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false); + } + if (evt.getDeltaWheel() != 0) { + float val = Math.abs(evt.getDeltaWheel()) / 100f; + invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false); + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onMouseMotionEvent(MouseMotionEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); + } + + cursorPos.set(evt.getX(), evt.getY()); + inputQueue.add(evt); + } + + private void onMouseButtonEventQueued(MouseButtonEvent evt) { + int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); + } + //updating cursor pos on click, so that non android touch events can properly update cursor position. + cursorPos.set(evt.getX(), evt.getY()); + inputQueue.add(evt); + } + + private void onKeyEventQueued(KeyInputEvent evt) { + if (evt.isRepeating()) { + return; // repeat events not used for bindings + } + + int hash = KeyTrigger.keyHash(evt.getKeyCode()); + invokeActions(hash, evt.isPressed()); + invokeTimedActions(hash, evt.getTime(), evt.isPressed()); + } + + /** + * Callback from RawInputListener. Do not use. + */ + public void onKeyEvent(KeyInputEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time."); + } + + inputQueue.add(evt); + } + + /** + * Set the deadzone for joystick axes. + * + *

{@link ActionListener#onAction(java.lang.String, boolean, float) } + * events will only be raised if the joystick axis value is greater than + * the deadZone. + * + * @param deadZone the deadzone for joystick axes. + */ + public void setAxisDeadZone(float deadZone) { + this.axisDeadZone = deadZone; + } + + /** + * Returns the deadzone for joystick axes. + * + * @return the deadzone for joystick axes. + */ + public float getAxisDeadZone() { + return axisDeadZone; + } + + /** + * Adds a new listener to receive events on the given mappings. + * + *

The given InputListener will be registered to receive events + * on the specified mapping names. When a mapping raises an event, the + * listener will have its appropriate method invoked, either + * {@link ActionListener#onAction(java.lang.String, boolean, float) } + * or {@link AnalogListener#onAnalog(java.lang.String, float, float) } + * depending on which interface the listener implements. + * If the listener implements both interfaces, then it will receive the + * appropriate event for each method. + * + * @param listener The listener to register to receive input events. + * @param mappingNames The mapping names which the listener will receive + * events from. + * + * @see InputManager#removeListener(com.jme3.input.controls.InputListener) + */ + public void addListener(InputListener listener, String... mappingNames) { + for (String mappingName : mappingNames) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + mapping = new Mapping(mappingName); + mappings.put(mappingName, mapping); + } + if (!mapping.listeners.contains(listener)) { + mapping.listeners.add(listener); + } + } + } + + /** + * Removes a listener from receiving events. + * + *

This will unregister the listener from any mappings that it + * was previously registered with via + * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }. + * + * @param listener The listener to unregister. + * + * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) + */ + public void removeListener(InputListener listener) { + for (Mapping mapping : mappings.values()) { + mapping.listeners.remove(listener); + } + } + + /** + * Create a new mapping to the given triggers. + * + *

+ * The given mapping will be assigned to the given triggers, when + * any of the triggers given raise an event, the listeners + * registered to the mappings will receive appropriate events. + * + * @param mappingName The mapping name to assign. + * @param triggers The triggers to which the mapping is to be registered. + * + * @see InputManager#deleteMapping(java.lang.String) + */ + public void addMapping(String mappingName, Trigger... triggers) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + mapping = new Mapping(mappingName); + mappings.put(mappingName, mapping); + } + + for (Trigger trigger : triggers) { + int hash = trigger.triggerHashCode(); + ArrayList names = bindings.get(hash); + if (names == null) { + names = new ArrayList(); + bindings.put(hash, names); + } + if (!names.contains(mapping)) { + names.add(mapping); + mapping.triggers.add(hash); + } else { + logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName); + } + } + } + + /** + * Returns true if this InputManager has a mapping registered + * for the given mappingName. + * + * @param mappingName The mapping name to check. + * + * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) + * @see InputManager#deleteMapping(java.lang.String) + */ + public boolean hasMapping(String mappingName) { + return mappings.containsKey(mappingName); + } + + /** + * Deletes a mapping from receiving trigger events. + * + *

+ * The given mapping will no longer be assigned to receive trigger + * events. + * + * @param mappingName The mapping name to unregister. + * + * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) + */ + public void deleteMapping(String mappingName) { + Mapping mapping = mappings.remove(mappingName); + if (mapping == null) { + throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + } + + ArrayList triggers = mapping.triggers; + for (int i = triggers.size() - 1; i >= 0; i--) { + int hash = triggers.get(i); + ArrayList maps = bindings.get(hash); + maps.remove(mapping); + } + } + + /** + * Deletes a specific trigger registered to a mapping. + * + *

+ * The given mapping will no longer receive events raised by the + * trigger. + * + * @param mappingName The mapping name to cease receiving events from the + * trigger. + * @param trigger The trigger to no longer invoke events on the mapping. + */ + public void deleteTrigger(String mappingName, Trigger trigger) { + Mapping mapping = mappings.get(mappingName); + if (mapping == null) { + throw new IllegalArgumentException("Cannot find mapping: " + mappingName); + } + + ArrayList maps = bindings.get(trigger.triggerHashCode()); + maps.remove(mapping); + + } + + /** + * Clears all the input mappings from this InputManager. + * Consequently, also clears all of the + * InputListeners as well. + */ + public void clearMappings() { + mappings.clear(); + bindings.clear(); + reset(); + } + + /** + * Do not use. + * Called to reset pressed keys or buttons when focus is restored. + */ + public void reset() { + pressedButtons.clear(); + axisValues.clear(); + } + + /** + * Returns whether the mouse cursor is visible or not. + * + *

By default the cursor is visible. + * + * @return whether the mouse cursor is visible or not. + * + * @see InputManager#setCursorVisible(boolean) + */ + public boolean isCursorVisible() { + return mouseVisible; + } + + /** + * Set whether the mouse cursor should be visible or not. + * + * @param visible whether the mouse cursor should be visible or not. + */ + public void setCursorVisible(boolean visible) { + if (mouseVisible != visible) { + mouseVisible = visible; + mouse.setCursorVisible(mouseVisible); + } + } + + /** + * Returns the current cursor position. The position is relative to the + * bottom-left of the screen and is in pixels. + * + * @return the current cursor position + */ + public Vector2f getCursorPosition() { + return cursorPos; + } + + /** + * Returns an array of all joysticks installed on the system. + * + * @return an array of all joysticks installed on the system. + */ + public Joystick[] getJoysticks() { + return joysticks; + } + + /** + * Adds a {@link RawInputListener} to receive raw input events. + * + *

+ * Any raw input listeners registered to this InputManager + * will receive raw input events first, before they get handled + * by the InputManager itself. The listeners are + * each processed in the order they were added, e.g. FIFO. + *

+ * If a raw input listener has handled the event and does not wish + * other listeners down the list to process the event, it may set the + * {@link InputEvent#setConsumed() consumed flag} to indicate the + * event was consumed and shouldn't be processed any further. + * The listener may do this either at each of the event callbacks + * or at the {@link RawInputListener#endInput() } method. + * + * @param listener A listener to receive raw input events. + * + * @see RawInputListener + */ + public void addRawInputListener(RawInputListener listener) { + rawListeners.add(listener); + rawListenerArray = null; + } + + /** + * Removes a {@link RawInputListener} so that it no longer + * receives raw input events. + * + * @param listener The listener to cease receiving raw input events. + * + * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) + */ + public void removeRawInputListener(RawInputListener listener) { + rawListeners.remove(listener); + rawListenerArray = null; + } + + /** + * Clears all {@link RawInputListener}s. + * + * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener) + */ + public void clearRawInputListeners() { + rawListeners.clear(); + rawListenerArray = null; + } + + private RawInputListener[] getRawListenerArray() { + if (rawListenerArray == null) + rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]); + return rawListenerArray; + } + + /** + * Enable simulation of mouse events. Used for touchscreen input only. + * + * @param value True to enable simulation of mouse events + */ + public void setSimulateMouse(boolean value) { + if (touch != null) { + touch.setSimulateMouse(value); + } + } + /** + * Returns state of simulation of mouse events. Used for touchscreen input only. + * + */ + public boolean getSimulateMouse() { + if (touch != null) { + return touch.getSimulateMouse(); + } else { + return false; + } + } + + /** + * Enable simulation of keyboard events. Used for touchscreen input only. + * + * @param value True to enable simulation of keyboard events + */ + public void setSimulateKeyboard(boolean value) { + if (touch != null) { + touch.setSimulateKeyboard(value); + } + } + + private void processQueue() { + int queueSize = inputQueue.size(); + RawInputListener[] array = getRawListenerArray(); + + for (RawInputListener listener : array) { + listener.beginInput(); + + for (int j = 0; j < queueSize; j++) { + InputEvent event = inputQueue.get(j); + if (event.isConsumed()) { + continue; + } + + if (event instanceof MouseMotionEvent) { + listener.onMouseMotionEvent((MouseMotionEvent) event); + } else if (event instanceof KeyInputEvent) { + listener.onKeyEvent((KeyInputEvent) event); + } else if (event instanceof MouseButtonEvent) { + listener.onMouseButtonEvent((MouseButtonEvent) event); + } else if (event instanceof JoyAxisEvent) { + listener.onJoyAxisEvent((JoyAxisEvent) event); + } else if (event instanceof JoyButtonEvent) { + listener.onJoyButtonEvent((JoyButtonEvent) event); + } else if (event instanceof TouchEvent) { + listener.onTouchEvent((TouchEvent) event); + } else { + assert false; + } + } + + listener.endInput(); + } + + for (int i = 0; i < queueSize; i++) { + InputEvent event = inputQueue.get(i); + if (event.isConsumed()) { + continue; + } + + if (event instanceof MouseMotionEvent) { + onMouseMotionEventQueued((MouseMotionEvent) event); + } else if (event instanceof KeyInputEvent) { + onKeyEventQueued((KeyInputEvent) event); + } else if (event instanceof MouseButtonEvent) { + onMouseButtonEventQueued((MouseButtonEvent) event); + } else if (event instanceof JoyAxisEvent) { + onJoyAxisEventQueued((JoyAxisEvent) event); + } else if (event instanceof JoyButtonEvent) { + onJoyButtonEventQueued((JoyButtonEvent) event); + } else if (event instanceof TouchEvent) { + onTouchEventQueued((TouchEvent) event); + } else { + assert false; + } + // larynx, 2011.06.10 - flag event as reusable because + // the android input uses a non-allocating ringbuffer which + // needs to know when the event is not anymore in inputQueue + // and therefor can be reused. + event.setConsumed(); + } + + inputQueue.clear(); + } + + /** + * Updates the InputManager. + * This will query current input devices and send + * appropriate events to registered listeners. + * + * @param tpf Time per frame value. + */ + public void update(float tpf) { + frameTPF = tpf; + + // Activate safemode if the TPF value is so small + // that rounding errors are inevitable + safeMode = tpf < 0.015f; + + long currentTime = keys.getInputTimeNanos(); + frameDelta = currentTime - lastUpdateTime; + + eventsPermitted = true; + + keys.update(); + mouse.update(); + if (joystick != null) { + joystick.update(); + } + if (touch != null) { + touch.update(); + } + + eventsPermitted = false; + + processQueue(); + invokeUpdateActions(); + + lastLastUpdateTime = lastUpdateTime; + lastUpdateTime = currentTime; + } + + /** + * Dispatches touch events to touch listeners + * @param evt The touch event to be dispatched to all onTouch listeners + */ + public void onTouchEventQueued(TouchEvent evt) { + ArrayList maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode())); + if (maps == null) { + return; + } + + int size = maps.size(); + for (int i = size - 1; i >= 0; i--) { + Mapping mapping = maps.get(i); + ArrayList listeners = mapping.listeners; + int listenerSize = listeners.size(); + for (int j = listenerSize - 1; j >= 0; j--) { + InputListener listener = listeners.get(j); + if (listener instanceof TouchListener) { + ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF); + } + } + } + } + + /** + * Callback from RawInputListener. Do not use. + */ + @Override + public void onTouchEvent(TouchEvent evt) { + if (!eventsPermitted) { + throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time."); + } + inputQueue.add(evt); + } +} diff --git a/engine/src/core/com/jme3/input/TouchInput.java b/engine/src/core/com/jme3/input/TouchInput.java index 2f45b444e..e69c4b320 100644 --- a/engine/src/core/com/jme3/input/TouchInput.java +++ b/engine/src/core/com/jme3/input/TouchInput.java @@ -73,6 +73,12 @@ public interface TouchInput extends Input { * @param simulate if mouse events should be generated */ public void setSimulateMouse(boolean simulate); + + /** + * Get if mouse events are generated + * + */ + public boolean getSimulateMouse(); /** * Set if keyboard events should be generated diff --git a/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java index 0b8013164..3948c89f3 100644 --- a/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java +++ b/engine/src/niftygui/com/jme3/niftygui/InputSystemJme.java @@ -1,233 +1,235 @@ -/* - * Copyright (c) 2009-2010 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.niftygui; - -import com.jme3.input.InputManager; -import com.jme3.input.KeyInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.*; -import de.lessvoid.nifty.Nifty; -import de.lessvoid.nifty.NiftyInputConsumer; -import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; -import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent; -import de.lessvoid.nifty.spi.input.InputSystem; -import java.util.ArrayList; - -public class InputSystemJme implements InputSystem, RawInputListener { - - private final ArrayList inputQueue = new ArrayList(); - - private InputManager inputManager; - - private boolean isDragging = false, niftyOwnsDragging = false; - private boolean pressed = false; - private int buttonIndex; - private int x, y; - private int height; - - private boolean shiftDown = false; - private boolean ctrlDown = false; - - private Nifty nifty; - - public InputSystemJme(InputManager inputManager){ - this.inputManager = inputManager; - } - - public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { - } - - public void setNifty(Nifty nifty) { - this.nifty = nifty; - } - - /** - * @param height The height of the viewport. Used to convert - * buttom-left origin to upper-left origin. - */ - public void setHeight(int height){ - this.height = height; - } - - public void setMousePosition(int x, int y){ - } - - public void beginInput(){ - } - - public void endInput(){ - boolean result = nifty.update(); - } - - private void onTouchEventQueued(TouchEvent evt, NiftyInputConsumer nic) { - boolean consumed = false; - - x = (int) evt.getX(); - y = (int) (height - evt.getY()); - - switch (evt.getType()) { - case DOWN: - consumed = nic.processMouseEvent(x, y, 0, 0, false); - isDragging = true; - niftyOwnsDragging = consumed; - if (consumed){ - evt.setConsumed(); - } - - break; - - case UP: - if (niftyOwnsDragging){ - consumed = nic.processMouseEvent(x, y, 0, buttonIndex, pressed); - if (consumed){ - evt.setConsumed(); - } - } - - isDragging = false; - niftyOwnsDragging = false; - break; - } - } - - private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer nic) { - x = evt.getX(); - y = height - evt.getY(); - nic.processMouseEvent(x, y, evt.getDeltaWheel(), buttonIndex, pressed); -// if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){ - // Do not consume motion events - //evt.setConsumed(); -// } - } - - private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) { - boolean wasPressed = pressed; - boolean forwardToNifty = true; - - buttonIndex = evt.getButtonIndex(); - pressed = evt.isPressed(); - - // Mouse button raised. End dragging - if (wasPressed && !pressed){ - if (!niftyOwnsDragging){ - forwardToNifty = false; - } - isDragging = false; - niftyOwnsDragging = false; - } - - boolean consumed = false; - if (forwardToNifty){ - consumed = nic.processMouseEvent(x, y, 0, buttonIndex, pressed); - if (consumed){ - evt.setConsumed(); - } - } - - // Mouse button pressed. Begin dragging - if (!wasPressed && pressed){ - isDragging = true; - niftyOwnsDragging = consumed; - } - } - - private void onKeyEventQueued(KeyInputEvent evt, NiftyInputConsumer nic) { - int code = evt.getKeyCode(); - - if (code == KeyInput.KEY_LSHIFT || code == KeyInput.KEY_RSHIFT) { - shiftDown = evt.isPressed(); - } else if (code == KeyInput.KEY_LCONTROL || code == KeyInput.KEY_RCONTROL) { - ctrlDown = evt.isPressed(); - } - - KeyboardInputEvent keyEvt = new KeyboardInputEvent(code, - evt.getKeyChar(), - evt.isPressed(), - shiftDown, - ctrlDown); - - if (nic.processKeyboardEvent(keyEvt)){ - evt.setConsumed(); - } - } - - public void onMouseMotionEvent(MouseMotionEvent evt) { - // Only forward the event if there's actual motion involved. - if (inputManager.isCursorVisible() && (evt.getDX() != 0 || - evt.getDY() != 0 || - evt.getDeltaWheel() != 0)){ - inputQueue.add(evt); - } - } - - public void onMouseButtonEvent(MouseButtonEvent evt) { - if (inputManager.isCursorVisible() && evt.getButtonIndex() >= 0 && evt.getButtonIndex() <= 2){ - inputQueue.add(evt); - } - } - - public void onJoyAxisEvent(JoyAxisEvent evt) { - } - - public void onJoyButtonEvent(JoyButtonEvent evt) { - } - - public void onKeyEvent(KeyInputEvent evt) { - inputQueue.add(evt); - } - - public void onTouchEvent(TouchEvent evt) { - inputQueue.add(evt); - } - - public void forwardEvents(NiftyInputConsumer nic) { - int queueSize = inputQueue.size(); - - for (int i = 0; i < queueSize; i++){ - InputEvent evt = inputQueue.get(i); - if (evt instanceof MouseMotionEvent){ - onMouseMotionEventQueued( (MouseMotionEvent)evt, nic); - }else if (evt instanceof MouseButtonEvent){ - onMouseButtonEventQueued( (MouseButtonEvent)evt, nic); - }else if (evt instanceof KeyInputEvent){ - onKeyEventQueued( (KeyInputEvent)evt, nic); - }else if (evt instanceof TouchEvent){ - onTouchEventQueued( (TouchEvent)evt, nic); - } - } - - inputQueue.clear(); - } - - -} +/* + * Copyright (c) 2009-2010 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.niftygui; + +import com.jme3.input.InputManager; +import com.jme3.input.KeyInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.event.*; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.NiftyInputConsumer; +import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; +import de.lessvoid.nifty.input.keyboard.KeyboardInputEvent; +import de.lessvoid.nifty.spi.input.InputSystem; +import java.util.ArrayList; + +public class InputSystemJme implements InputSystem, RawInputListener { + + private final ArrayList inputQueue = new ArrayList(); + + private InputManager inputManager; + + private boolean isDragging = false, niftyOwnsDragging = false; + private boolean pressed = false; + private int buttonIndex; + private int x, y; + private int height; + + private boolean shiftDown = false; + private boolean ctrlDown = false; + + private Nifty nifty; + + public InputSystemJme(InputManager inputManager){ + this.inputManager = inputManager; + } + + public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { + } + + public void setNifty(Nifty nifty) { + this.nifty = nifty; + } + + /** + * @param height The height of the viewport. Used to convert + * buttom-left origin to upper-left origin. + */ + public void setHeight(int height){ + this.height = height; + } + + public void setMousePosition(int x, int y){ + } + + public void beginInput(){ + } + + public void endInput(){ + boolean result = nifty.update(); + } + + private void onTouchEventQueued(TouchEvent evt, NiftyInputConsumer nic) { + boolean consumed = false; + + x = (int) evt.getX(); + y = (int) (height - evt.getY()); + + if (!inputManager.getSimulateMouse()) { + switch (evt.getType()) { + case DOWN: + consumed = nic.processMouseEvent(x, y, 0, 0, true); + isDragging = true; + niftyOwnsDragging = consumed; + if (consumed){ + evt.setConsumed(); + } + + break; + + case UP: + if (niftyOwnsDragging){ + consumed = nic.processMouseEvent(x, y, 0, 0, false); + if (consumed){ + evt.setConsumed(); + } + } + + isDragging = false; + niftyOwnsDragging = false; + break; + } + } + } + + private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer nic) { + x = evt.getX(); + y = height - evt.getY(); + nic.processMouseEvent(x, y, evt.getDeltaWheel(), buttonIndex, pressed); +// if (nic.processMouseEvent(niftyEvt) /*|| nifty.getCurrentScreen().isMouseOverElement()*/){ + // Do not consume motion events + //evt.setConsumed(); +// } + } + + private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) { + boolean wasPressed = pressed; + boolean forwardToNifty = true; + + buttonIndex = evt.getButtonIndex(); + pressed = evt.isPressed(); + + // Mouse button raised. End dragging + if (wasPressed && !pressed){ + if (!niftyOwnsDragging){ + forwardToNifty = false; + } + isDragging = false; + niftyOwnsDragging = false; + } + + boolean consumed = false; + if (forwardToNifty){ + consumed = nic.processMouseEvent(x, y, 0, buttonIndex, pressed); + if (consumed){ + evt.setConsumed(); + } + } + + // Mouse button pressed. Begin dragging + if (!wasPressed && pressed){ + isDragging = true; + niftyOwnsDragging = consumed; + } + } + + private void onKeyEventQueued(KeyInputEvent evt, NiftyInputConsumer nic) { + int code = evt.getKeyCode(); + + if (code == KeyInput.KEY_LSHIFT || code == KeyInput.KEY_RSHIFT) { + shiftDown = evt.isPressed(); + } else if (code == KeyInput.KEY_LCONTROL || code == KeyInput.KEY_RCONTROL) { + ctrlDown = evt.isPressed(); + } + + KeyboardInputEvent keyEvt = new KeyboardInputEvent(code, + evt.getKeyChar(), + evt.isPressed(), + shiftDown, + ctrlDown); + + if (nic.processKeyboardEvent(keyEvt)){ + evt.setConsumed(); + } + } + + public void onMouseMotionEvent(MouseMotionEvent evt) { + // Only forward the event if there's actual motion involved. + if (inputManager.isCursorVisible() && (evt.getDX() != 0 || + evt.getDY() != 0 || + evt.getDeltaWheel() != 0)){ + inputQueue.add(evt); + } + } + + public void onMouseButtonEvent(MouseButtonEvent evt) { + if (inputManager.isCursorVisible() && evt.getButtonIndex() >= 0 && evt.getButtonIndex() <= 2){ + inputQueue.add(evt); + } + } + + public void onJoyAxisEvent(JoyAxisEvent evt) { + } + + public void onJoyButtonEvent(JoyButtonEvent evt) { + } + + public void onKeyEvent(KeyInputEvent evt) { + inputQueue.add(evt); + } + + public void onTouchEvent(TouchEvent evt) { + inputQueue.add(evt); + } + + public void forwardEvents(NiftyInputConsumer nic) { + int queueSize = inputQueue.size(); + + for (int i = 0; i < queueSize; i++){ + InputEvent evt = inputQueue.get(i); + if (evt instanceof MouseMotionEvent){ + onMouseMotionEventQueued( (MouseMotionEvent)evt, nic); + }else if (evt instanceof MouseButtonEvent){ + onMouseButtonEventQueued( (MouseButtonEvent)evt, nic); + }else if (evt instanceof KeyInputEvent){ + onKeyEventQueued( (KeyInputEvent)evt, nic); + }else if (evt instanceof TouchEvent){ + onTouchEventQueued( (TouchEvent)evt, nic); + } + } + + inputQueue.clear(); + } + + +}