Added base input handler - touch and simulated mouse

experimental
Kostyantyn Hushchyn 11 years ago
parent 10af945f36
commit 625264b6d8
  1. 203
      jme3-ios/src/main/java/com/jme3/input/ios/IosInputHandler.java
  2. 191
      jme3-ios/src/main/java/com/jme3/input/ios/IosTouchHandler.java
  3. 121
      jme3-ios/src/main/java/com/jme3/input/ios/TouchEventPool.java
  4. 13
      jme3-ios/src/main/java/com/jme3/system/ios/IGLESContext.java
  5. 76
      jme3-ios/src/main/java/com/jme3/util/RingBuffer.java

@ -0,0 +1,203 @@
package com.jme3.input.ios;
import com.jme3.input.RawInputListener;
import com.jme3.input.TouchInput;
import com.jme3.input.event.InputEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.system.AppSettings;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
public class IosInputHandler implements TouchInput {
private static final Logger logger = Logger.getLogger(IosInputHandler.class.getName());
private final static int MAX_TOUCH_EVENTS = 1024;
// Custom settings
private boolean mouseEventsEnabled = true;
private boolean mouseEventsInvertX = false;
private boolean mouseEventsInvertY = false;
private boolean keyboardEventsEnabled = false;
private boolean dontSendHistory = false;
// Internal
private boolean initialized = false;
private RawInputListener listener = null;
private ConcurrentLinkedQueue<InputEvent> inputEventQueue = new ConcurrentLinkedQueue<InputEvent>();
private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS);
private IosTouchHandler touchHandler;
private float scaleX = 1f;
private float scaleY = 1f;
private int width = 0;
private int height = 0;
public IosInputHandler() {
touchHandler = new IosTouchHandler(this);
}
@Override
public void initialize() {
touchEventPool.initialize();
if (touchHandler != null) {
touchHandler.initialize();
}
initialized = true;
}
@Override
public void update() {
logger.log(Level.FINE, "InputEvent update : {0}",
new Object[]{listener});
if (listener != null) {
InputEvent inputEvent;
while ((inputEvent = inputEventQueue.poll()) != null) {
if (inputEvent instanceof TouchEvent) {
listener.onTouchEvent((TouchEvent)inputEvent);
} else if (inputEvent instanceof MouseButtonEvent) {
listener.onMouseButtonEvent((MouseButtonEvent)inputEvent);
} else if (inputEvent instanceof MouseMotionEvent) {
listener.onMouseMotionEvent((MouseMotionEvent)inputEvent);
} else if (inputEvent instanceof KeyInputEvent) {
listener.onKeyEvent((KeyInputEvent)inputEvent);
}
}
}
}
@Override
public void destroy() {
initialized = false;
touchEventPool.destroy();
if (touchHandler != null) {
touchHandler.destroy();
}
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void setInputListener(RawInputListener listener) {
this.listener = listener;
}
@Override
public long getInputTimeNanos() {
return System.nanoTime();
}
@Override
public void setSimulateMouse(boolean simulate) {
this.mouseEventsEnabled = simulate;
}
@Override
public boolean getSimulateMouse() {
return mouseEventsEnabled;
}
@Override
public boolean isSimulateMouse() {
return mouseEventsEnabled;
}
@Override
public void setSimulateKeyboard(boolean simulate) {
this.keyboardEventsEnabled = simulate;
}
@Override
public void setOmitHistoricEvents(boolean dontSendHistory) {
this.dontSendHistory = dontSendHistory;
}
@Override
public void showVirtualKeyboard(boolean visible) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
// ----------------
public void loadSettings(AppSettings settings) {
// TODO: add simulate keyboard to settings
// keyboardEventsEnabled = true;
mouseEventsEnabled = true;//settings.isEmulateMouse();
mouseEventsInvertX = settings.isEmulateMouseFlipX();
mouseEventsInvertY = settings.isEmulateMouseFlipY();
// view width and height are 0 until the view is displayed on the screen
//if (view.getWidth() != 0 && view.getHeight() != 0) {
// scaleX = (float)settings.getWidth() / (float)view.getWidth();
// scaleY = (float)settings.getHeight() / (float)view.getHeight();
//}
scaleX = 1.0f;
scaleY = 1.0f;
width = settings.getWidth();
height = settings.getHeight();
logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}",
new Object[]{scaleX, scaleY, width, height});
}
public boolean isMouseEventsInvertX() {
return mouseEventsInvertX;
}
public boolean isMouseEventsInvertY() {
return mouseEventsInvertY;
}
public float invertX(float origX) {
return getJmeX(width) - origX;
}
public float invertY(float origY) {
return getJmeY(height) - origY;
}
public float getJmeX(float origX) {
return origX * scaleX;
}
public float getJmeY(float origY) {
return origY * scaleY;
}
public TouchEvent getFreeTouchEvent() {
return touchEventPool.getNextFreeEvent();
}
public void addEvent(InputEvent event) {
inputEventQueue.add(event);
if (event instanceof TouchEvent) {
touchEventPool.storeEvent((TouchEvent)event);
}
}
// ----------------
public void injectTouchDown(int pointerId, long time, float x, float y) {
logger.log(Level.FINE, "Using input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}",
new Object[]{scaleX, scaleY, width, height});
if (touchHandler != null) {
touchHandler.actionDown(pointerId, time, x, y);
}
}
public void injectTouchUp(int pointerId, long time, float x, float y) {
if (touchHandler != null) {
touchHandler.actionUp(pointerId, time, x, y);
}
}
public void injectTouchMove(int pointerId, long time, float x, float y) {
if (touchHandler != null) {
touchHandler.actionMove(pointerId, time, x, y);
}
}
}

@ -0,0 +1,191 @@
/*
* 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.ios;
import com.jme3.input.event.InputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import static com.jme3.input.event.TouchEvent.Type.DOWN;
import static com.jme3.input.event.TouchEvent.Type.MOVE;
import static com.jme3.input.event.TouchEvent.Type.UP;
import com.jme3.math.Vector2f;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* AndroidTouchHandler is the base class that receives touch inputs from the
* Android system and creates the TouchEvents for jME. This class is designed
* to handle the base touch events for Android rev 9 (Android 2.3). This is
* extended by other classes to add features that were introducted after
* Android rev 9.
*
* @author iwgeric
*/
public class IosTouchHandler {
private static final Logger logger = Logger.getLogger(IosTouchHandler.class.getName());
final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
protected int numPointers = 1;
protected IosInputHandler iosInput;
public IosTouchHandler(IosInputHandler iosInput) {
this.iosInput = iosInput;
}
public void initialize() {
}
public void destroy() {
}
public void actionDown(int pointerId, long time, float x, float y) {
logger.log(Level.FINE, "Inject input pointer: {0}, time: {1}, x: {2}, y: {3}",
new Object[]{pointerId, time, x, y});
float jmeX = iosInput.getJmeX(x);
float jmeY = iosInput.invertY(iosInput.getJmeY(y));
TouchEvent touch = iosInput.getFreeTouchEvent();
touch.set(TouchEvent.Type.DOWN, jmeX, jmeY, 0, 0);
touch.setPointerId(pointerId);//TODO: pointer ID
touch.setTime(time);
touch.setPressure(1.0f);
//touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure
lastPositions.put(pointerId, new Vector2f(jmeX, jmeY));
processEvent(touch);
}
public void actionUp(int pointerId, long time, float x, float y) {
float jmeX = iosInput.getJmeX(x);
float jmeY = iosInput.invertY(iosInput.getJmeY(y));
TouchEvent touch = iosInput.getFreeTouchEvent();
touch.set(TouchEvent.Type.UP, jmeX, jmeY, 0, 0);
touch.setPointerId(pointerId);//TODO: pointer ID
touch.setTime(time);
touch.setPressure(1.0f);
//touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure
lastPositions.remove(pointerId);
processEvent(touch);
}
public void actionMove(int pointerId, long time, float x, float y) {
float jmeX = iosInput.getJmeX(x);
float jmeY = iosInput.invertY(iosInput.getJmeY(y));
Vector2f lastPos = lastPositions.get(pointerId);
if (lastPos == null) {
lastPos = new Vector2f(jmeX, jmeY);
lastPositions.put(pointerId, lastPos);
}
float dX = jmeX - lastPos.x;
float dY = jmeY - lastPos.y;
if (dX != 0 || dY != 0) {
TouchEvent touch = iosInput.getFreeTouchEvent();
touch.set(TouchEvent.Type.MOVE, jmeX, jmeY, dX, dY);
touch.setPointerId(pointerId);
touch.setTime(time);
touch.setPressure(1.0f);
//touch.setPressure(event.getPressure(p));
lastPos.set(jmeX, jmeY);
processEvent(touch);
}
}
protected void processEvent(TouchEvent event) {
// Add the touch event
iosInput.addEvent(event);
// MouseEvents do not support multi-touch, so only evaluate 1 finger pointer events
if (iosInput.isSimulateMouse() && numPointers == 1) {
InputEvent mouseEvent = generateMouseEvent(event);
if (mouseEvent != null) {
// Add the mouse event
iosInput.addEvent(mouseEvent);
}
}
}
// TODO: Ring Buffer for mouse events?
protected InputEvent generateMouseEvent(TouchEvent event) {
InputEvent inputEvent = null;
int newX;
int newY;
int newDX;
int newDY;
if (iosInput.isMouseEventsInvertX()) {
newX = (int) (iosInput.invertX(event.getX()));
newDX = (int)event.getDeltaX() * -1;
} else {
newX = (int) event.getX();
newDX = (int)event.getDeltaX();
}
if (iosInput.isMouseEventsInvertY()) {
newY = (int) (iosInput.invertY(event.getY()));
newDY = (int)event.getDeltaY() * -1;
} else {
newY = (int) event.getY();
newDY = (int)event.getDeltaY();
}
switch (event.getType()) {
case DOWN:
// Handle mouse down event
inputEvent = new MouseButtonEvent(0, true, newX, newY);
inputEvent.setTime(event.getTime());
break;
case UP:
// Handle mouse up event
inputEvent = new MouseButtonEvent(0, false, newX, newY);
inputEvent.setTime(event.getTime());
break;
case HOVER_MOVE:
case MOVE:
inputEvent = new MouseMotionEvent(newX, newY, newDX, newDY, (int)event.getScaleSpan(), (int)event.getDeltaScaleSpan());
inputEvent.setTime(event.getTime());
break;
}
return inputEvent;
}
}

@ -0,0 +1,121 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.input.ios;
import com.jme3.input.event.TouchEvent;
import com.jme3.util.RingBuffer;
import java.util.logging.Logger;
/**
* TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage
* collection on Android. Each TouchEvent is stored in the RingBuffer and is
* reused if the TouchEvent has been consumed.
*
* If a TouchEvent has not been consumed, it is placed back into the pool at the
* end for later use. If a TouchEvent has been consumed, it is reused to avoid
* creating lots of little objects.
*
* If the pool is full of unconsumed events, then a new event is created and provided.
*
*
* @author iwgeric
*/
public class TouchEventPool {
private static final Logger logger = Logger.getLogger(TouchEventPool.class.getName());
private final RingBuffer<TouchEvent> eventPool;
private final int maxEvents;
public TouchEventPool (int maxEvents) {
eventPool = new RingBuffer<TouchEvent>(maxEvents);
this.maxEvents = maxEvents;
}
public void initialize() {
TouchEvent newEvent;
while (!eventPool.isEmpty()) {
eventPool.pop();
}
for (int i = 0; i < maxEvents; i++) {
newEvent = new TouchEvent();
newEvent.setConsumed();
eventPool.push(newEvent);
}
}
public void destroy() {
// Clean up queues
while (!eventPool.isEmpty()) {
eventPool.pop();
}
}
/**
* Fetches a touch event from the reuse pool
*
* @return a usable TouchEvent
*/
public TouchEvent getNextFreeEvent() {
TouchEvent evt = null;
int curSize = eventPool.size();
while (curSize > 0) {
evt = (TouchEvent)eventPool.pop();
if (evt.isConsumed()) {
break;
} else {
eventPool.push(evt);
evt = null;
}
curSize--;
}
if (evt == null) {
logger.warning("eventPool full of unconsumed events");
evt = new TouchEvent();
}
return evt;
}
/**
* Stores the TouchEvent back in the pool for later reuse. It is only reused
* if the TouchEvent has been consumed.
*
* @param event TouchEvent to store for later use if consumed.
*/
public void storeEvent(TouchEvent event) {
if (eventPool.size() < maxEvents) {
eventPool.push(event);
} else {
logger.warning("eventPool full");
}
}
}

@ -37,6 +37,7 @@ import com.jme3.input.dummy.DummyKeyInput;
import com.jme3.input.dummy.DummyMouseInput; import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.renderer.ios.IGLESShaderRenderer; import com.jme3.renderer.ios.IGLESShaderRenderer;
import com.jme3.system.*; import com.jme3.system.*;
import com.jme3.input.ios.IosInputHandler;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -56,6 +57,7 @@ public class IGLESContext implements JmeContext {
protected IGLESShaderRenderer renderer; protected IGLESShaderRenderer renderer;
protected Timer timer; protected Timer timer;
protected SystemListener listener; protected SystemListener listener;
protected IosInputHandler input;
protected int minFrameDuration = 0; // No FPS cap protected int minFrameDuration = 0; // No FPS cap
public IGLESContext() { public IGLESContext() {
@ -71,12 +73,9 @@ public class IGLESContext implements JmeContext {
public void setSettings(AppSettings settings) { public void setSettings(AppSettings settings) {
logger.log(Level.FINE, "IGLESContext setSettings"); logger.log(Level.FINE, "IGLESContext setSettings");
this.settings.copyFrom(settings); this.settings.copyFrom(settings);
/* if (input != null) {
if (androidInput != null) { input.loadSettings(settings);
androidInput.loadSettings(settings);
} }
*/
} }
@Override @Override
@ -119,8 +118,7 @@ public class IGLESContext implements JmeContext {
@Override @Override
public TouchInput getTouchInput() { public TouchInput getTouchInput() {
//return androidInput; return input;
return null;// new DummyTouchInput();
} }
@Override @Override
@ -153,6 +151,7 @@ public class IGLESContext implements JmeContext {
public void create(boolean waitFor) { public void create(boolean waitFor) {
logger.log(Level.FINE, "IGLESContext create"); logger.log(Level.FINE, "IGLESContext create");
renderer = new IGLESShaderRenderer(); renderer = new IGLESShaderRenderer();
input = new IosInputHandler();
timer = new NanoTimer(); timer = new NanoTimer();
//synchronized (createdLock){ //synchronized (createdLock){

@ -0,0 +1,76 @@
package com.jme3.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Ring buffer (fixed size queue) implementation using a circular array (array
* with wrap-around).
*/
// suppress unchecked warnings in Java 1.5.0_6 and later
@SuppressWarnings("unchecked")
public class RingBuffer<T> implements Iterable<T> {
private T[] buffer; // queue elements
private int count = 0; // number of elements on queue
private int indexOut = 0; // index of first element of queue
private int indexIn = 0; // index of next available slot
// cast needed since no generic array creation in Java
public RingBuffer(int capacity) {
buffer = (T[]) new Object[capacity];
}
public boolean isEmpty() {
return count == 0;
}
public int size() {
return count;
}
public void push(T item) {
if (count == buffer.length) {
throw new RuntimeException("Ring buffer overflow");
}
buffer[indexIn] = item;
indexIn = (indexIn + 1) % buffer.length; // wrap-around
count++;
}
public T pop() {
if (isEmpty()) {
throw new RuntimeException("Ring buffer underflow");
}
T item = buffer[indexOut];
buffer[indexOut] = null; // to help with garbage collection
count--;
indexOut = (indexOut + 1) % buffer.length; // wrap-around
return item;
}
public Iterator<T> iterator() {
return new RingBufferIterator();
}
// an iterator, doesn't implement remove() since it's optional
private class RingBufferIterator implements Iterator<T> {
private int i = 0;
public boolean hasNext() {
return i < count;
}
public void remove() {
throw new UnsupportedOperationException();
}
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return buffer[i++];
}
}
}
Loading…
Cancel
Save