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