View Javadoc
1   package org.newdawn.slick.command;
2   
3   import org.newdawn.slick.Input;
4   import org.newdawn.slick.util.AbstractInputAdapter;
5   
6   import java.util.*;
7   
8   /**
9    * The central provider that maps real device input into abstract commands
10   * defined by the developer. Registering a control against an command with this
11   * class will cause the provider to produce an event for the command when the
12   * input is pressed and released.
13   * 
14   * @author joverton
15   */
16  public final class InputProvider {
17      /** The commands that have been defined. */
18      private final Map<Control, Command> commands;
19  
20      /** The list of listeners that may be listening. */
21      private final List<InputProviderListener> listeners = new ArrayList<>();
22  
23      /** The input context we're responding to. */
24      private final Input input;
25  
26      /** The command input states. */
27      private final Map<Command, CommandState> commandState = new HashMap<>();
28  
29      /** True if this provider is actively sending events. */
30      private boolean active = true;
31  
32      /**
33       * Create a new input provider which will provide abstract input descriptions
34       * based on the input from the supplied context.
35       *
36       * @param input
37       *            The input from which this provider will receive events
38       */
39      public InputProvider(Input input) {
40          this.input = input;
41          this.input.addListener(new InputListenerImpl());
42          commands = new HashMap<>();
43      }
44  
45      /**
46       * Get the list of commands that have been registered with the provider,
47       * i.e. the commands that can be issued to the listeners.
48       *
49       * @return The list of commands (@see Command) that can be issued from this
50       *         provider
51       */
52      public Set<Command> getUniqueCommands() {
53          final Set<Command> uniqueCommands = new HashSet<>();
54          commands.values().stream().filter(command -> !uniqueCommands.contains(command)).forEach(uniqueCommands::add);
55          return uniqueCommands;
56      }
57  
58      /**
59       * Get a list of the registered controls (@see Control) that can cause a
60       * particular command to be invoked.
61       *
62       * @param command
63       *            The command to be invoked
64       * @return The list of controls that can cause the command (@see Control)
65       */
66      List<Control> getControlsFor(Command command) {
67          List<Control> controlsForCommand = new ArrayList<>();
68  
69          for (Map.Entry<Control, Command> controlCommandEntry : commands.entrySet()) {
70              Control key = controlCommandEntry.getKey();
71              Command value = controlCommandEntry.getValue();
72  
73              if (value == command) {
74                  controlsForCommand.add(key);
75              }
76          }
77          return controlsForCommand;
78      }
79  
80      /**
81       * Indicate whether this provider should be sending events.
82       *
83       * @param active
84       *            True if this provider should be sending events
85       */
86      public void setActive(boolean active) {
87          this.active = active;
88      }
89  
90      /**
91       * Check if this provider should be sending events.
92       *
93       * @return True if this provider should be sending events
94       */
95      boolean isActive() {
96          return active;
97      }
98  
99      /**
100      * Add a listener to the provider. This listener will be notified of
101      * commands detected from the input.
102      *
103      * @param listener
104      *            The listener to be added
105      */
106     public void addListener(InputProviderListener listener) {
107         listeners.add(listener);
108     }
109 
110     /**
111      * Remove a listener from this provider. The listener will no longer be
112      * provided with notification of commands performed.
113      *
114      * @param listener
115      *            The listener to be removed
116      */
117     public void removeListener(InputProviderListener listener) {
118         listeners.remove(listener);
119     }
120 
121     /**
122      * Bind an command to a control.
123      *
124      * @param command
125      *            The command to bind to
126      * @param control
127      *            The control that is pressed/released to represent the command
128      */
129     public void bindCommand(Control control, Command command) {
130         commands.put(control, command);
131 
132         if (commandState.get(command) == null) {
133             commandState.put(command, new CommandState());
134         }
135     }
136 
137     /**
138      * Clear all the controls that have been configured for a given command.
139      *
140      * @param command The command whose controls should be unbound
141      */
142     public void clearCommand(Command command) {
143         List<Control> controls = getControlsFor(command);
144 
145         controls.forEach(this::unbindCommand);
146     }
147 
148     /**
149      * Unbinds the command associated with this control.
150      *
151      * @param control
152      *            The control to remove
153      */
154     void unbindCommand(Control control) {
155         Command command = commands.remove(control);
156         if (command != null) {
157             if (!commandState.keySet().contains(command)) {
158                 commandState.remove(command);
159             }
160         }
161     }
162 
163     /**
164      * Get the recorded state for a given command.
165      *
166      * @param command
167      *            The command to get the state for
168      * @return The given command state
169      */
170     private CommandState getState(Command command) {
171         return commandState.get(command);
172     }
173 
174     /**
175      * Check if the last control event we received related to the given command
176      * indicated that a control was down.
177      *
178      * @param command
179      *            The command to check
180      * @return True if the last event indicated a button down
181      */
182     public boolean isCommandControlDown(Command command) {
183         return getState(command).isDown();
184     }
185 
186     /**
187      * Check if one of the controls related to the command specified has been
188      * pressed since we last called this method.
189      *
190      * @param command
191      *            The command to check
192      * @return True if one of the controls has been pressed
193      */
194     public boolean isCommandControlPressed(Command command) {
195         return getState(command).isPressed();
196     }
197 
198     /**
199      * Fire notification to any interested listeners that a control has been
200      * pressed indication an particular command.
201      *
202      * @param command
203      *            The command that has been pressed
204      */
205     void firePressed(Command command) {
206         getState(command).down = true;
207         getState(command).pressed = true;
208 
209         if (!isActive()) {
210             return;
211         }
212 
213         for (InputProviderListener listener : listeners) {
214             listener.controlPressed(command);
215         }
216     }
217 
218     /**
219      * Fire notification to any interested listeners that a control has been
220      * released indication an particular command should be stopped.
221      *
222      * @param command
223      *            The command that has been pressed
224      */
225     void fireReleased(Command command) {
226         getState(command).down = false;
227 
228         if (!isActive()) {
229             return;
230         }
231 
232         for (InputProviderListener listener : listeners) {
233             listener.controlReleased(command);
234         }
235     }
236 
237     /**
238      * A token representing the state of all the controls causing an command to
239      * be invoked.
240      *
241      * @author kevin
242      */
243     private class CommandState {
244         /** True if one of the controls for this command is down. */
245         private boolean down;
246 
247         /** True if one of the controls for this command is pressed. */
248         private boolean pressed;
249 
250         /**
251          * Check if a control for the command has been pressed since last call.
252          *
253          * @return True if the command has been pressed
254          */
255         public boolean isPressed() {
256             if (pressed) {
257                 pressed = false;
258                 return true;
259             }
260 
261             return false;
262         }
263 
264         /**
265          * Check if the last event we had indicated the control was pressed.
266          *
267          * @return True if the control was pressed
268          */
269         public boolean isDown() {
270             return down;
271         }
272     }
273 
274     /**
275      * A simple listener to respond to input and look up any required commands.
276      *
277      * @author kevin
278      */
279     private final class InputListenerImpl extends AbstractInputAdapter {
280         /**
281          * @see org.newdawn.slick.util.AbstractInputAdapter#isAcceptingInput()
282          */
283         @Override
284         public boolean isAcceptingInput() {
285             return true;
286         }
287 
288         /**
289          * @see org.newdawn.slick.util.AbstractInputAdapter#keyPressed(int, char)
290          */
291         @Override
292         public void keyPressed(int key, char c) {
293             Command command = commands.get(new KeyControl(key));
294             if (command != null) {
295                 firePressed(command);
296             }
297         }
298 
299         /**
300          * @see org.newdawn.slick.util.AbstractInputAdapter#keyReleased(int, char)
301          */
302         @Override
303         public void keyReleased(int key, char c) {
304             Command command = commands.get(new KeyControl(key));
305             if (command != null) {
306                 fireReleased(command);
307             }
308         }
309 
310         /**
311          * @see org.newdawn.slick.util.AbstractInputAdapter#mousePressed(int, int, int)
312          */
313         @Override
314         public void mousePressed(int button, int x, int y) {
315             Command command = commands.get(new MouseButtonControl(
316                     button));
317             if (command != null) {
318                 firePressed(command);
319             }
320         }
321 
322         /**
323          * @see org.newdawn.slick.util.AbstractInputAdapter#mouseReleased(int, int, int)
324          */
325         @Override
326         public void mouseReleased(int button, int x, int y) {
327             Command command = commands.get(new MouseButtonControl(
328                     button));
329             if (command != null) {
330                 fireReleased(command);
331             }
332         }
333 
334         /**
335          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerLeftPressed(int)
336          */
337         @Override
338         public void controllerLeftPressed(int controller) {
339             Command command = commands
340                     .get(new ControllerDirectionControl(controller,
341                             ControllerDirectionControl.LEFT));
342             if (command != null) {
343                 firePressed(command);
344             }
345         }
346 
347         /**
348          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerLeftReleased(int)
349          */
350         @Override
351         public void controllerLeftReleased(int controller) {
352             Command command = commands
353                     .get(new ControllerDirectionControl(controller,
354                             ControllerDirectionControl.LEFT));
355             if (command != null) {
356                 fireReleased(command);
357             }
358         }
359 
360         /**
361          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerRightPressed(int)
362          */
363         @Override
364         public void controllerRightPressed(int controller) {
365             Command command = commands
366                     .get(new ControllerDirectionControl(controller,
367                             ControllerDirectionControl.RIGHT));
368             if (command != null) {
369                 firePressed(command);
370             }
371         }
372 
373         /**
374          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerRightReleased(int)
375          */
376         @Override
377         public void controllerRightReleased(int controller) {
378             Command command = commands
379                     .get(new ControllerDirectionControl(controller,
380                             ControllerDirectionControl.RIGHT));
381             if (command != null) {
382                 fireReleased(command);
383             }
384         }
385 
386         /**
387          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerUpPressed(int)
388          */
389         @Override
390         public void controllerUpPressed(int controller) {
391             Command command = commands
392                     .get(new ControllerDirectionControl(controller,
393                             ControllerDirectionControl.UP));
394             if (command != null)
395                 firePressed(command);
396         }
397 
398         /**
399          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerUpReleased(int)
400          */
401         @Override
402         public void controllerUpReleased(int controller) {
403             Command command = commands
404                     .get(new ControllerDirectionControl(controller,
405                             ControllerDirectionControl.UP));
406             if (command != null) {
407                 fireReleased(command);
408             }
409         }
410 
411         /**
412          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerDownPressed(int)
413          */
414         @Override
415         public void controllerDownPressed(int controller) {
416             Command command = commands
417                     .get(new ControllerDirectionControl(controller,
418                             ControllerDirectionControl.DOWN));
419             if (command != null) {
420                 firePressed(command);
421             }
422         }
423 
424         /**
425          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerDownReleased(int)
426          */
427         @Override
428         public void controllerDownReleased(int controller) {
429             Command command = commands
430                     .get(new ControllerDirectionControl(controller,
431                             ControllerDirectionControl.DOWN));
432             if (command != null) {
433                 fireReleased(command);
434             }
435         }
436 
437         /**
438          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerButtonPressed(int,
439          *      int)
440          */
441         @Override
442         public void controllerButtonPressed(int controller, int button) {
443             Command command = commands
444                     .get(new ControllerButtonControl(controller, button));
445             if (command != null) {
446                 firePressed(command);
447             }
448         }
449 
450         /**
451          * @see org.newdawn.slick.util.AbstractInputAdapter#controllerButtonReleased(int,
452          *      int)
453          */
454         @Override
455         public void controllerButtonReleased(int controller, int button) {
456             Command command = commands
457                     .get(new ControllerButtonControl(controller, button));
458             if (command != null) {
459                 fireReleased(command);
460             }
461         }
462     }
463 }