• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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