InputManager.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. /*
  2. * Copyright (c) 2009-2012 jMonkeyEngine
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
  17. * may be used to endorse or promote products derived from this software
  18. * without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  22. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. package com.jme3.input;
  33. import com.jme3.app.Application;
  34. import com.jme3.cursors.plugins.JmeCursor;
  35. import com.jme3.input.controls.*;
  36. import com.jme3.input.event.*;
  37. import com.jme3.math.FastMath;
  38. import com.jme3.math.Vector2f;
  39. import com.jme3.util.IntMap;
  40. import com.jme3.util.IntMap.Entry;
  41. import java.util.ArrayList;
  42. import java.util.HashMap;
  43. import java.util.logging.Level;
  44. import java.util.logging.Logger;
  45. /**
  46. * The <code>InputManager</code> is responsible for converting input events
  47. * received from the Key, Mouse and Joy Input implementations into an
  48. * abstract, input device independent representation that user code can use.
  49. * <p>
  50. * By default an <code>InputManager</code> is included with every Application instance for use
  51. * in user code to query input, unless the Application is created as headless
  52. * or with input explicitly disabled.
  53. * <p>
  54. * The input manager has two concepts, a {@link Trigger} and a mapping.
  55. * A trigger represents a specific input trigger, such as a key button,
  56. * or a mouse axis. A mapping represents a link onto one or several triggers,
  57. * when the appropriate trigger is activated (e.g. a key is pressed), the
  58. * mapping will be invoked. Any listeners registered to receive an event
  59. * from the mapping will have an event raised.
  60. * <p>
  61. * There are two types of events that {@link InputListener input listeners}
  62. * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action}
  63. * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog}
  64. * events.
  65. * <p>
  66. * <code>onAction</code> events are raised when the specific input
  67. * activates or deactivates. For a digital input such as key press, the <code>onAction()</code>
  68. * event will be raised with the <code>isPressed</code> argument equal to true,
  69. * when the key is released, <code>onAction</code> is called again but this time
  70. * with the <code>isPressed</code> argument set to false.
  71. * For analog inputs, the <code>onAction</code> method will be called any time
  72. * the input is non-zero, however an exception to this is for joystick axis inputs,
  73. * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}.
  74. * <p>
  75. * <code>onAnalog</code> events are raised every frame while the input is activated.
  76. * For digital inputs, every frame that the input is active will cause the
  77. * <code>onAnalog</code> method to be called, the argument <code>value</code>
  78. * argument will equal to the frame's time per frame (TPF) value but only
  79. * for digital inputs. For analog inputs however, the <code>value</code> argument
  80. * will equal the actual analog value.
  81. */
  82. public class InputManager implements RawInputListener {
  83. private static final Logger logger = Logger.getLogger(InputManager.class.getName());
  84. private final KeyInput keys;
  85. private final MouseInput mouse;
  86. private final JoyInput joystick;
  87. private final TouchInput touch;
  88. private float frameTPF;
  89. private long lastLastUpdateTime = 0;
  90. private long lastUpdateTime = 0;
  91. private long frameDelta = 0;
  92. private long firstTime = 0;
  93. private boolean eventsPermitted = false;
  94. private boolean mouseVisible = true;
  95. private boolean safeMode = false;
  96. private float axisDeadZone = 0.05f;
  97. private Vector2f cursorPos = new Vector2f();
  98. private Joystick[] joysticks;
  99. private final IntMap<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>();
  100. private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>();
  101. private final IntMap<Long> pressedButtons = new IntMap<Long>();
  102. private final IntMap<Float> axisValues = new IntMap<Float>();
  103. private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>();
  104. private RawInputListener[] rawListenerArray = null;
  105. private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();
  106. private static class Mapping {
  107. private final String name;
  108. private final ArrayList<Integer> triggers = new ArrayList<Integer>();
  109. private final ArrayList<InputListener> listeners = new ArrayList<InputListener>();
  110. public Mapping(String name) {
  111. this.name = name;
  112. }
  113. }
  114. /**
  115. * Initializes the InputManager.
  116. *
  117. * <p>This should only be called internally in {@link Application}.
  118. *
  119. * @param mouse
  120. * @param keys
  121. * @param joystick
  122. * @param touch
  123. * @throws IllegalArgumentException If either mouseInput or keyInput are null.
  124. */
  125. public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) {
  126. if (keys == null || mouse == null) {
  127. throw new IllegalArgumentException("Mouse or keyboard cannot be null");
  128. }
  129. this.keys = keys;
  130. this.mouse = mouse;
  131. this.joystick = joystick;
  132. this.touch = touch;
  133. keys.setInputListener(this);
  134. mouse.setInputListener(this);
  135. if (joystick != null) {
  136. joystick.setInputListener(this);
  137. joysticks = joystick.loadJoysticks(this);
  138. }
  139. if (touch != null) {
  140. touch.setInputListener(this);
  141. }
  142. firstTime = keys.getInputTimeNanos();
  143. }
  144. private void invokeActions(int hash, boolean pressed) {
  145. ArrayList<Mapping> maps = bindings.get(hash);
  146. if (maps == null) {
  147. return;
  148. }
  149. int size = maps.size();
  150. for (int i = size - 1; i >= 0; i--) {
  151. Mapping mapping = maps.get(i);
  152. ArrayList<InputListener> listeners = mapping.listeners;
  153. int listenerSize = listeners.size();
  154. for (int j = listenerSize - 1; j >= 0; j--) {
  155. InputListener listener = listeners.get(j);
  156. if (listener instanceof ActionListener) {
  157. ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF);
  158. }
  159. }
  160. }
  161. }
  162. private float computeAnalogValue(long timeDelta) {
  163. if (safeMode || frameDelta == 0) {
  164. return 1f;
  165. } else {
  166. return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1);
  167. }
  168. }
  169. private void invokeTimedActions(int hash, long time, boolean pressed) {
  170. if (!bindings.containsKey(hash)) {
  171. return;
  172. }
  173. if (pressed) {
  174. pressedButtons.put(hash, time);
  175. } else {
  176. Long pressTimeObj = pressedButtons.remove(hash);
  177. if (pressTimeObj == null) {
  178. return; // under certain circumstances it can be null, ignore
  179. } // the event then.
  180. long pressTime = pressTimeObj;
  181. long lastUpdate = lastLastUpdateTime;
  182. long releaseTime = time;
  183. long timeDelta = releaseTime - Math.max(pressTime, lastUpdate);
  184. if (timeDelta > 0) {
  185. invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
  186. }
  187. }
  188. }
  189. private void invokeUpdateActions() {
  190. for (Entry<Long> pressedButton : pressedButtons) {
  191. int hash = pressedButton.getKey();
  192. long pressTime = pressedButton.getValue();
  193. long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime);
  194. if (timeDelta > 0) {
  195. invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
  196. }
  197. }
  198. for (Entry<Float> axisValue : axisValues) {
  199. int hash = axisValue.getKey();
  200. float value = axisValue.getValue();
  201. invokeAnalogs(hash, value * frameTPF, true);
  202. }
  203. }
  204. private void invokeAnalogs(int hash, float value, boolean isAxis) {
  205. ArrayList<Mapping> maps = bindings.get(hash);
  206. if (maps == null) {
  207. return;
  208. }
  209. if (!isAxis) {
  210. value *= frameTPF;
  211. }
  212. int size = maps.size();
  213. for (int i = size - 1; i >= 0; i--) {
  214. Mapping mapping = maps.get(i);
  215. ArrayList<InputListener> listeners = mapping.listeners;
  216. int listenerSize = listeners.size();
  217. for (int j = listenerSize - 1; j >= 0; j--) {
  218. InputListener listener = listeners.get(j);
  219. if (listener instanceof AnalogListener) {
  220. // NOTE: multiply by TPF for any button bindings
  221. ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
  222. }
  223. }
  224. }
  225. }
  226. private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) {
  227. if (value < axisDeadZone) {
  228. invokeAnalogs(hash, value, !applyTpf);
  229. return;
  230. }
  231. ArrayList<Mapping> maps = bindings.get(hash);
  232. if (maps == null) {
  233. return;
  234. }
  235. boolean valueChanged = !axisValues.containsKey(hash);
  236. if (applyTpf) {
  237. value *= frameTPF;
  238. }
  239. int size = maps.size();
  240. for (int i = size - 1; i >= 0; i--) {
  241. Mapping mapping = maps.get(i);
  242. ArrayList<InputListener> listeners = mapping.listeners;
  243. int listenerSize = listeners.size();
  244. for (int j = listenerSize - 1; j >= 0; j--) {
  245. InputListener listener = listeners.get(j);
  246. if (listener instanceof ActionListener && valueChanged) {
  247. ((ActionListener) listener).onAction(mapping.name, true, frameTPF);
  248. }
  249. if (listener instanceof AnalogListener) {
  250. ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * Callback from RawInputListener. Do not use.
  257. */
  258. public void beginInput() {
  259. }
  260. /**
  261. * Callback from RawInputListener. Do not use.
  262. */
  263. public void endInput() {
  264. }
  265. private void onJoyAxisEventQueued(JoyAxisEvent evt) {
  266. // for (int i = 0; i < rawListeners.size(); i++){
  267. // rawListeners.get(i).onJoyAxisEvent(evt);
  268. // }
  269. int joyId = evt.getJoyIndex();
  270. int axis = evt.getAxisIndex();
  271. float value = evt.getValue();
  272. if (value < axisDeadZone && value > -axisDeadZone) {
  273. int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
  274. int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
  275. Float val1 = axisValues.get(hash1);
  276. Float val2 = axisValues.get(hash2);
  277. if (val1 != null && val1.floatValue() > axisDeadZone) {
  278. invokeActions(hash1, false);
  279. }
  280. if (val2 != null && val2.floatValue() > axisDeadZone) {
  281. invokeActions(hash2, false);
  282. }
  283. axisValues.remove(hash1);
  284. axisValues.remove(hash2);
  285. } else if (value < 0) {
  286. int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
  287. int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
  288. // Clear the reverse direction's actions in case we
  289. // crossed center too quickly
  290. Float otherVal = axisValues.get(otherHash);
  291. if (otherVal != null && otherVal.floatValue() > axisDeadZone) {
  292. invokeActions(otherHash, false);
  293. }
  294. invokeAnalogsAndActions(hash, -value, true);
  295. axisValues.put(hash, -value);
  296. axisValues.remove(otherHash);
  297. } else {
  298. int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
  299. int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
  300. // Clear the reverse direction's actions in case we
  301. // crossed center too quickly
  302. Float otherVal = axisValues.get(otherHash);
  303. if (otherVal != null && otherVal.floatValue() > axisDeadZone) {
  304. invokeActions(otherHash, false);
  305. }
  306. invokeAnalogsAndActions(hash, value, true);
  307. axisValues.put(hash, value);
  308. axisValues.remove(otherHash);
  309. }
  310. }
  311. /**
  312. * Callback from RawInputListener. Do not use.
  313. */
  314. public void onJoyAxisEvent(JoyAxisEvent evt) {
  315. if (!eventsPermitted) {
  316. throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
  317. }
  318. inputQueue.add(evt);
  319. }
  320. private void onJoyButtonEventQueued(JoyButtonEvent evt) {
  321. // for (int i = 0; i < rawListeners.size(); i++){
  322. // rawListeners.get(i).onJoyButtonEvent(evt);
  323. // }
  324. int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex());
  325. invokeActions(hash, evt.isPressed());
  326. invokeTimedActions(hash, evt.getTime(), evt.isPressed());
  327. }
  328. /**
  329. * Callback from RawInputListener. Do not use.
  330. */
  331. public void onJoyButtonEvent(JoyButtonEvent evt) {
  332. if (!eventsPermitted) {
  333. throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
  334. }
  335. inputQueue.add(evt);
  336. }
  337. private void onMouseMotionEventQueued(MouseMotionEvent evt) {
  338. // for (int i = 0; i < rawListeners.size(); i++){
  339. // rawListeners.get(i).onMouseMotionEvent(evt);
  340. // }
  341. if (evt.getDX() != 0) {
  342. float val = Math.abs(evt.getDX()) / 1024f;
  343. invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false);
  344. }
  345. if (evt.getDY() != 0) {
  346. float val = Math.abs(evt.getDY()) / 1024f;
  347. invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false);
  348. }
  349. if (evt.getDeltaWheel() != 0) {
  350. float val = Math.abs(evt.getDeltaWheel()) / 100f;
  351. invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false);
  352. }
  353. }
  354. /**
  355. * Sets the mouse cursor image or animation.
  356. * Set cursor to null to show default system cursor.
  357. * To hide the cursor completely, use {@link #setCursorVisible(boolean) }.
  358. *
  359. * @param jmeCursor The cursor to set, or null to reset to system cursor.
  360. *
  361. * @see JmeCursor
  362. */
  363. public void setMouseCursor(JmeCursor jmeCursor) {
  364. mouse.setNativeCursor(jmeCursor);
  365. }
  366. /**
  367. * Callback from RawInputListener. Do not use.
  368. */
  369. public void onMouseMotionEvent(MouseMotionEvent evt) {
  370. if (!eventsPermitted) {
  371. throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
  372. }
  373. cursorPos.set(evt.getX(), evt.getY());
  374. inputQueue.add(evt);
  375. }
  376. private void onMouseButtonEventQueued(MouseButtonEvent evt) {
  377. int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex());
  378. invokeActions(hash, evt.isPressed());
  379. invokeTimedActions(hash, evt.getTime(), evt.isPressed());
  380. }
  381. /**
  382. * Callback from RawInputListener. Do not use.
  383. */
  384. public void onMouseButtonEvent(MouseButtonEvent evt) {
  385. if (!eventsPermitted) {
  386. throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
  387. }
  388. //updating cursor pos on click, so that non android touch events can properly update cursor position.
  389. cursorPos.set(evt.getX(), evt.getY());
  390. inputQueue.add(evt);
  391. }
  392. private void onKeyEventQueued(KeyInputEvent evt) {
  393. if (evt.isRepeating()) {
  394. return; // repeat events not used for bindings
  395. }
  396. int hash = KeyTrigger.keyHash(evt.getKeyCode());
  397. invokeActions(hash, evt.isPressed());
  398. invokeTimedActions(hash, evt.getTime(), evt.isPressed());
  399. }
  400. /**
  401. * Callback from RawInputListener. Do not use.
  402. */
  403. public void onKeyEvent(KeyInputEvent evt) {
  404. if (!eventsPermitted) {
  405. throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time.");
  406. }
  407. inputQueue.add(evt);
  408. }
  409. /**
  410. * Set the deadzone for joystick axes.
  411. *
  412. * <p>{@link ActionListener#onAction(java.lang.String, boolean, float) }
  413. * events will only be raised if the joystick axis value is greater than
  414. * the <code>deadZone</code>.
  415. *
  416. * @param deadZone the deadzone for joystick axes.
  417. */
  418. public void setAxisDeadZone(float deadZone) {
  419. this.axisDeadZone = deadZone;
  420. }
  421. /**
  422. * Returns the deadzone for joystick axes.
  423. *
  424. * @return the deadzone for joystick axes.
  425. */
  426. public float getAxisDeadZone() {
  427. return axisDeadZone;
  428. }
  429. /**
  430. * Adds a new listener to receive events on the given mappings.
  431. *
  432. * <p>The given InputListener will be registered to receive events
  433. * on the specified mapping names. When a mapping raises an event, the
  434. * listener will have its appropriate method invoked, either
  435. * {@link ActionListener#onAction(java.lang.String, boolean, float) }
  436. * or {@link AnalogListener#onAnalog(java.lang.String, float, float) }
  437. * depending on which interface the <code>listener</code> implements.
  438. * If the listener implements both interfaces, then it will receive the
  439. * appropriate event for each method.
  440. *
  441. * @param listener The listener to register to receive input events.
  442. * @param mappingNames The mapping names which the listener will receive
  443. * events from.
  444. *
  445. * @see InputManager#removeListener(com.jme3.input.controls.InputListener)
  446. */
  447. public void addListener(InputListener listener, String... mappingNames) {
  448. for (String mappingName : mappingNames) {
  449. Mapping mapping = mappings.get(mappingName);
  450. if (mapping == null) {
  451. mapping = new Mapping(mappingName);
  452. mappings.put(mappingName, mapping);
  453. }
  454. if (!mapping.listeners.contains(listener)) {
  455. mapping.listeners.add(listener);
  456. }
  457. }
  458. }
  459. /**
  460. * Removes a listener from receiving events.
  461. *
  462. * <p>This will unregister the listener from any mappings that it
  463. * was previously registered with via
  464. * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }.
  465. *
  466. * @param listener The listener to unregister.
  467. *
  468. * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[])
  469. */
  470. public void removeListener(InputListener listener) {
  471. for (Mapping mapping : mappings.values()) {
  472. mapping.listeners.remove(listener);
  473. }
  474. }
  475. /**
  476. * Create a new mapping to the given triggers.
  477. *
  478. * <p>
  479. * The given mapping will be assigned to the given triggers, when
  480. * any of the triggers given raise an event, the listeners
  481. * registered to the mappings will receive appropriate events.
  482. *
  483. * @param mappingName The mapping name to assign.
  484. * @param triggers The triggers to which the mapping is to be registered.
  485. *
  486. * @see InputManager#deleteMapping(java.lang.String)
  487. */
  488. public void addMapping(String mappingName, Trigger... triggers) {
  489. Mapping mapping = mappings.get(mappingName);
  490. if (mapping == null) {
  491. mapping = new Mapping(mappingName);
  492. mappings.put(mappingName, mapping);
  493. }
  494. for (Trigger trigger : triggers) {
  495. int hash = trigger.triggerHashCode();
  496. ArrayList<Mapping> names = bindings.get(hash);
  497. if (names == null) {
  498. names = new ArrayList<Mapping>();
  499. bindings.put(hash, names);
  500. }
  501. if (!names.contains(mapping)) {
  502. names.add(mapping);
  503. mapping.triggers.add(hash);
  504. } else {
  505. logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName);
  506. }
  507. }
  508. }
  509. /**
  510. * Returns true if this InputManager has a mapping registered
  511. * for the given mappingName.
  512. *
  513. * @param mappingName The mapping name to check.
  514. *
  515. * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
  516. * @see InputManager#deleteMapping(java.lang.String)
  517. */
  518. public boolean hasMapping(String mappingName) {
  519. return mappings.containsKey(mappingName);
  520. }
  521. /**
  522. * Deletes a mapping from receiving trigger events.
  523. *
  524. * <p>
  525. * The given mapping will no longer be assigned to receive trigger
  526. * events.
  527. *
  528. * @param mappingName The mapping name to unregister.
  529. *
  530. * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
  531. */
  532. public void deleteMapping(String mappingName) {
  533. Mapping mapping = mappings.remove(mappingName);
  534. if (mapping == null) {
  535. //throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
  536. logger.log(Level.WARNING, "Cannot find mapping to be removed, skipping: {0}", mappingName);
  537. return;
  538. }
  539. ArrayList<Integer> triggers = mapping.triggers;
  540. for (int i = triggers.size() - 1; i >= 0; i--) {
  541. int hash = triggers.get(i);
  542. ArrayList<Mapping> maps = bindings.get(hash);
  543. maps.remove(mapping);
  544. }
  545. }
  546. /**
  547. * Deletes a specific trigger registered to a mapping.
  548. *
  549. * <p>
  550. * The given mapping will no longer receive events raised by the
  551. * trigger.
  552. *
  553. * @param mappingName The mapping name to cease receiving events from the
  554. * trigger.
  555. * @param trigger The trigger to no longer invoke events on the mapping.
  556. */
  557. public void deleteTrigger(String mappingName, Trigger trigger) {
  558. Mapping mapping = mappings.get(mappingName);
  559. if (mapping == null) {
  560. throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
  561. }
  562. ArrayList<Mapping> maps = bindings.get(trigger.triggerHashCode());
  563. maps.remove(mapping);
  564. }
  565. /**
  566. * Clears all the input mappings from this InputManager.
  567. * Consequently, also clears all of the
  568. * InputListeners as well.
  569. */
  570. public void clearMappings() {
  571. mappings.clear();
  572. bindings.clear();
  573. reset();
  574. }
  575. /**
  576. * Do not use.
  577. * Called to reset pressed keys or buttons when focus is restored.
  578. */
  579. public void reset() {
  580. pressedButtons.clear();
  581. axisValues.clear();
  582. }
  583. /**
  584. * Returns whether the mouse cursor is visible or not.
  585. *
  586. * <p>By default the cursor is visible.
  587. *
  588. * @return whether the mouse cursor is visible or not.
  589. *
  590. * @see InputManager#setCursorVisible(boolean)
  591. */
  592. public boolean isCursorVisible() {
  593. return mouseVisible;
  594. }
  595. /**
  596. * Set whether the mouse cursor should be visible or not.
  597. *
  598. * @param visible whether the mouse cursor should be visible or not.
  599. */
  600. public void setCursorVisible(boolean visible) {
  601. if (mouseVisible != visible) {
  602. mouseVisible = visible;
  603. mouse.setCursorVisible(mouseVisible);
  604. }
  605. }
  606. /**
  607. * Returns the current cursor position. The position is relative to the
  608. * bottom-left of the screen and is in pixels.
  609. *
  610. * @return the current cursor position
  611. */
  612. public Vector2f getCursorPosition() {
  613. return cursorPos;
  614. }
  615. /**
  616. * Returns an array of all joysticks installed on the system.
  617. *
  618. * @return an array of all joysticks installed on the system.
  619. */
  620. public Joystick[] getJoysticks() {
  621. return joysticks;
  622. }
  623. /**
  624. * Adds a {@link RawInputListener} to receive raw input events.
  625. *
  626. * <p>
  627. * Any raw input listeners registered to this <code>InputManager</code>
  628. * will receive raw input events first, before they get handled
  629. * by the <code>InputManager</code> itself. The listeners are
  630. * each processed in the order they were added, e.g. FIFO.
  631. * <p>
  632. * If a raw input listener has handled the event and does not wish
  633. * other listeners down the list to process the event, it may set the
  634. * {@link InputEvent#setConsumed() consumed flag} to indicate the
  635. * event was consumed and shouldn't be processed any further.
  636. * The listener may do this either at each of the event callbacks
  637. * or at the {@link RawInputListener#endInput() } method.
  638. *
  639. * @param listener A listener to receive raw input events.
  640. *
  641. * @see RawInputListener
  642. */
  643. public void addRawInputListener(RawInputListener listener) {
  644. rawListeners.add(listener);
  645. rawListenerArray = null;
  646. }
  647. /**
  648. * Removes a {@link RawInputListener} so that it no longer
  649. * receives raw input events.
  650. *
  651. * @param listener The listener to cease receiving raw input events.
  652. *
  653. * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
  654. */
  655. public void removeRawInputListener(RawInputListener listener) {
  656. rawListeners.remove(listener);
  657. rawListenerArray = null;
  658. }
  659. /**
  660. * Clears all {@link RawInputListener}s.
  661. *
  662. * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
  663. */
  664. public void clearRawInputListeners() {
  665. rawListeners.clear();
  666. rawListenerArray = null;
  667. }
  668. private RawInputListener[] getRawListenerArray() {
  669. if (rawListenerArray == null)
  670. rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]);
  671. return rawListenerArray;
  672. }
  673. /**
  674. * Enable simulation of mouse events. Used for touchscreen input only.
  675. *
  676. * @param value True to enable simulation of mouse events
  677. */
  678. public void setSimulateMouse(boolean value) {
  679. if (touch != null) {
  680. touch.setSimulateMouse(value);
  681. }
  682. }
  683. /**
  684. * Returns state of simulation of mouse events. Used for touchscreen input only.
  685. *
  686. */
  687. public boolean getSimulateMouse() {
  688. if (touch != null) {
  689. return touch.getSimulateMouse();
  690. } else {
  691. return false;
  692. }
  693. }
  694. /**
  695. * Enable simulation of keyboard events. Used for touchscreen input only.
  696. *
  697. * @param value True to enable simulation of keyboard events
  698. */
  699. public void setSimulateKeyboard(boolean value) {
  700. if (touch != null) {
  701. touch.setSimulateKeyboard(value);
  702. }
  703. }
  704. private void processQueue() {
  705. int queueSize = inputQueue.size();
  706. RawInputListener[] array = getRawListenerArray();
  707. for (RawInputListener listener : array) {
  708. listener.beginInput();
  709. for (int j = 0; j < queueSize; j++) {
  710. InputEvent event = inputQueue.get(j);
  711. if (event.isConsumed()) {
  712. continue;
  713. }
  714. if (event instanceof MouseMotionEvent) {
  715. listener.onMouseMotionEvent((MouseMotionEvent) event);
  716. } else if (event instanceof KeyInputEvent) {
  717. listener.onKeyEvent((KeyInputEvent) event);
  718. } else if (event instanceof MouseButtonEvent) {
  719. listener.onMouseButtonEvent((MouseButtonEvent) event);
  720. } else if (event instanceof JoyAxisEvent) {
  721. listener.onJoyAxisEvent((JoyAxisEvent) event);
  722. } else if (event instanceof JoyButtonEvent) {
  723. listener.onJoyButtonEvent((JoyButtonEvent) event);
  724. } else if (event instanceof TouchEvent) {
  725. listener.onTouchEvent((TouchEvent) event);
  726. } else {
  727. assert false;
  728. }
  729. }
  730. listener.endInput();
  731. }
  732. for (int i = 0; i < queueSize; i++) {
  733. InputEvent event = inputQueue.get(i);
  734. if (event.isConsumed()) {
  735. continue;
  736. }
  737. if (event instanceof MouseMotionEvent) {
  738. onMouseMotionEventQueued((MouseMotionEvent) event);
  739. } else if (event instanceof KeyInputEvent) {
  740. onKeyEventQueued((KeyInputEvent) event);
  741. } else if (event instanceof MouseButtonEvent) {
  742. onMouseButtonEventQueued((MouseButtonEvent) event);
  743. } else if (event instanceof JoyAxisEvent) {
  744. onJoyAxisEventQueued((JoyAxisEvent) event);
  745. } else if (event instanceof JoyButtonEvent) {
  746. onJoyButtonEventQueued((JoyButtonEvent) event);
  747. } else if (event instanceof TouchEvent) {
  748. onTouchEventQueued((TouchEvent) event);
  749. } else {
  750. assert false;
  751. }
  752. // larynx, 2011.06.10 - flag event as reusable because
  753. // the android input uses a non-allocating ringbuffer which
  754. // needs to know when the event is not anymore in inputQueue
  755. // and therefor can be reused.
  756. event.setConsumed();
  757. }
  758. inputQueue.clear();
  759. }
  760. /**
  761. * Updates the <code>InputManager</code>.
  762. * This will query current input devices and send
  763. * appropriate events to registered listeners.
  764. *
  765. * @param tpf Time per frame value.
  766. */
  767. public void update(float tpf) {
  768. frameTPF = tpf;
  769. // Activate safemode if the TPF value is so small
  770. // that rounding errors are inevitable
  771. safeMode = tpf < 0.015f;
  772. long currentTime = keys.getInputTimeNanos();
  773. frameDelta = currentTime - lastUpdateTime;
  774. eventsPermitted = true;
  775. keys.update();
  776. mouse.update();
  777. if (joystick != null) {
  778. joystick.update();
  779. }
  780. if (touch != null) {
  781. touch.update();
  782. }
  783. eventsPermitted = false;
  784. processQueue();
  785. invokeUpdateActions();
  786. lastLastUpdateTime = lastUpdateTime;
  787. lastUpdateTime = currentTime;
  788. }
  789. /**
  790. * Dispatches touch events to touch listeners
  791. * @param evt The touch event to be dispatched to all onTouch listeners
  792. */
  793. public void onTouchEventQueued(TouchEvent evt) {
  794. ArrayList<Mapping> maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode()));
  795. if (maps == null) {
  796. return;
  797. }
  798. int size = maps.size();
  799. for (int i = size - 1; i >= 0; i--) {
  800. Mapping mapping = maps.get(i);
  801. ArrayList<InputListener> listeners = mapping.listeners;
  802. int listenerSize = listeners.size();
  803. for (int j = listenerSize - 1; j >= 0; j--) {
  804. InputListener listener = listeners.get(j);
  805. if (listener instanceof TouchListener) {
  806. ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF);
  807. }
  808. }
  809. }
  810. }
  811. /**
  812. * Callback from RawInputListener. Do not use.
  813. */
  814. @Override
  815. public void onTouchEvent(TouchEvent evt) {
  816. if (!eventsPermitted) {
  817. throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time.");
  818. }
  819. cursorPos.set(evt.getX(), evt.getY());
  820. inputQueue.add(evt);
  821. }
  822. }