/* | |
* 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 <code>InputManager</code> 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. | |
* <p> | |
* By default an <code>InputManager</code> 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. | |
* <p> | |
* 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. | |
* <p> | |
* 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. | |
* <p> | |
* <code>onAction</code> events are raised when the specific input | |
* activates or deactivates. For a digital input such as key press, the <code>onAction()</code> | |
* event will be raised with the <code>isPressed</code> argument equal to true, | |
* when the key is released, <code>onAction</code> is called again but this time | |
* with the <code>isPressed</code> argument set to false. | |
* For analog inputs, the <code>onAction</code> 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}. | |
* <p> | |
* <code>onAnalog</code> events are raised every frame while the input is activated. | |
* For digital inputs, every frame that the input is active will cause the | |
* <code>onAnalog</code> method to be called, the argument <code>value</code> | |
* argument will equal to the frame's time per frame (TPF) value but only | |
* for digital inputs. For analog inputs however, the <code>value</code> 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<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>(); | |
private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>(); | |
private final IntMap<Long> pressedButtons = new IntMap<Long>(); | |
private final IntMap<Float> axisValues = new IntMap<Float>(); | |
private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>(); | |
private RawInputListener[] rawListenerArray = null; | |
private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>(); | |
private static class Mapping { | |
private final String name; | |
private final ArrayList<Integer> triggers = new ArrayList<Integer>(); | |
private final ArrayList<InputListener> listeners = new ArrayList<InputListener>(); | |
public Mapping(String name) { | |
this.name = name; | |
} | |
} | |
/** | |
* Initializes the InputManager. | |
* | |
* <p>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<Mapping> 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<InputListener> 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() { | |
if (pressedButtons.size() > 0) for (Entry<Long> 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); | |
} | |
} | |
if (axisValues.size() > 0) for (Entry<Float> 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<Mapping> 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<InputListener> 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<Mapping> 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<InputListener> 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. | |
* | |
* <p>{@link ActionListener#onAction(java.lang.String, boolean, float) } | |
* events will only be raised if the joystick axis value is greater than | |
* the <code>deadZone</code>. | |
* | |
* @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. | |
* | |
* <p>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 <code>listener</code> 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. | |
* | |
* <p>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. | |
* | |
* <p> | |
* 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<Mapping> names = bindings.get(hash); | |
if (names == null) { | |
names = new ArrayList<Mapping>(); | |
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. | |
* | |
* <p> | |
* 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<Integer> triggers = mapping.triggers; | |
for (int i = triggers.size() - 1; i >= 0; i--) { | |
int hash = triggers.get(i); | |
ArrayList<Mapping> maps = bindings.get(hash); | |
maps.remove(mapping); | |
} | |
} | |
/** | |
* Deletes a specific trigger registered to a mapping. | |
* | |
* <p> | |
* 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<Mapping> 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. | |
* | |
* <p>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. | |
* | |
* <p> | |
* Any raw input listeners registered to this <code>InputManager</code> | |
* will receive raw input events first, before they get handled | |
* by the <code>InputManager</code> itself. The listeners are | |
* each processed in the order they were added, e.g. FIFO. | |
* <p> | |
* 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 <code>InputManager</code>. | |
* 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<Mapping> 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<InputListener> 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); | |
} | |
} |