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 */
32package com.jme3.input;
33
34import com.jme3.app.Application;
35import com.jme3.input.controls.*;
36import com.jme3.input.event.*;
37import com.jme3.math.FastMath;
38import com.jme3.math.Vector2f;
39import com.jme3.util.IntMap;
40import com.jme3.util.IntMap.Entry;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.logging.Level;
44import 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 */
83public 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
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     */
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
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
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
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
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
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
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     */
289    public void beginInput() {
290    }
291
292    /**
293     * Callback from RawInputListener. Do not use.
294     */
295    public void endInput() {
296    }
297
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     */
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
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     */
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
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     */
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
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
713    public void clearRawInputListeners() {
714        rawListeners.clear();
715        rawListenerArray = null;
716    }
717
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     */
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     */
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     */
751    public void setSimulateKeyboard(boolean value) {
752        if (touch != null) {
753            touch.setSimulateKeyboard(value);
754        }
755    }
756
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     */
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
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