1 /**
2  * Defines InputSystem and State, which are used for defining an input device (keyboard, mouse, gamepad, etc.)
3  */
4 module dash.utility.input.inputsystem;
5 import dash.utility.input.input;
6 
7 import std.typecons, std.traits;
8 
9 package:
10 /// The type each button is stored as.
11 alias ButtonStorageType = bool;
12 /// The type each axis is stored as.
13 alias AxisStorageType   = float;
14 
15 /**
16  * Defines a system of inputs, buttons, axes or both.
17  *
18  * Params:
19  *  ButtonEnum =        The enum of buttons for this system.
20  *  AxisEnum =          The enum of axes for this system.
21  */
22 final abstract class InputSystem( ButtonEnum, AxisEnum )
23 {
24 static:
25 public:
26     /// Whether or not the system has buttons.
27     enum HasButtons = !is( ButtonEnum == void );
28     /// Whether or not the system has axes.
29     enum HasAxes    = !is( AxisEnum == void );
30 
31 package:
32     /**
33      * Initialize all states and events.
34      */
35     void initialize()
36     {
37         static if( HasButtons )
38         {
39             buttonCurrent.reset();
40             buttonPrevious.reset();
41             buttonStaging.reset();
42 
43             foreach( key; buttonEvents.keys )
44                 buttonEvents.remove( key );
45         }
46         static if( HasAxes )
47         {
48             axisCurrent.reset();
49             axisStaging.reset();
50 
51             foreach( key; axisEvents.keys )
52                 axisEvents.remove( key );
53         }
54     }
55 
56     /**
57      * Update all events and states.
58      */
59     void update()
60     {
61         static if( HasButtons )
62         {
63             auto diffButtons = buttonStaging - buttonCurrent;
64             buttonPrevious = buttonCurrent;
65             buttonCurrent = buttonStaging;
66 
67             foreach( state; diffButtons )
68                 if( auto buttonEvent = state[ 0 ] in buttonEvents )
69                     foreach( event; *buttonEvent )
70                         event( state[ 0 ], state[ 1 ] );
71 
72             Input.processDiffs!(typeof(this))( diffButtons );
73         }
74         static if( HasAxes )
75         {
76             auto diffAxis = axisStaging - axisCurrent;
77             axisCurrent = axisStaging;
78 
79             foreach( state; diffAxis )
80                 if( auto axisEvent = state[ 0 ] in axisEvents )
81                     foreach( event; *axisEvent )
82                         event( state[ 0 ], state[ 1 ] );
83 
84             Input.processDiffs!(typeof(this))( diffAxis );
85         }
86     }
87 
88 // If we have buttons
89 static if( HasButtons )
90 {
91 public:
92     /// The enum of buttons that the input system has.
93     alias Buttons           = ButtonState.Inputs;
94     /// A delegate that takes the changed button and the new state.
95     alias ButtonEvent       = void delegate( Buttons, ButtonStorageType );
96     /// A delegate that takes the changed button.
97     alias ButtonStateEvent  = void delegate( Buttons );
98 
99     /**
100      * Check if a given button is down.
101      *
102      * Params:
103      *  buttonCode =        The button to check.
104      *  checkPrevious =     Whether or not to make sure the button was down last frame.
105      *
106      * Returns: The state of the button.
107      */
108     ButtonStorageType getButtonState( Buttons buttonCode )
109     {
110         return buttonCurrent[ buttonCode ];
111     }
112 
113     /**
114      * Check if a given button is down.
115      *
116      * Params:
117      *  buttonCode =        The code of the button to check.
118      *  checkPrevious =     Whether or not to make sure the button was down last frame.
119      *
120      * Returns: The state of the button.
121      */
122     ButtonStorageType isButtonDown( Buttons buttonCode, bool checkPrevious = false )
123     {
124         return buttonCurrent[ buttonCode ] && ( !checkPrevious || !buttonPrevious[ buttonCode ] );
125     }
126 
127     /**
128      * Check if a given button is up.
129      *
130      * Params:
131      *      buttonCode =        The code of the button to check.
132      *      checkPrevious =     Whether or not to make sure the button was up last frame.
133      *
134      * Returns: The state of the button.
135      */
136     ButtonStorageType isButtonUp( Buttons buttonCode, bool checkPrevious = false )
137     {
138         return !buttonCurrent[ buttonCode ] && ( !checkPrevious || buttonPrevious[ buttonCode ] );
139     }
140 
141     /**
142      * Add an event to be fired when the given button changes.
143      *
144      * Params:
145      *      buttonCode =    The code of the button to add the event to.
146      *      func =          The function to call when the button state changes.
147      */
148     deprecated( "Use Input.addButtonEvent with a binding instead." )
149     void addButtonEvent( Buttons buttonCode, ButtonEvent func )
150     {
151         buttonEvents[ buttonCode ] ~= func;
152     }
153 
154     /**
155      * Add a button event only when the button is down.
156      */
157     deprecated( "Use Input.addButtonDownEvent with a binding instead." )
158     void addButtonDownEvent( Buttons buttonCode, ButtonStateEvent func )
159     {
160         addButtonEvent( buttonCode, ( Buttons buttonCode, ButtonStorageType newState ) { if( newState ) func( buttonCode ); } );
161     }
162 
163     /**
164      * Add a button event only when the button is up.
165      */
166     deprecated( "Use Input.addButtonUpEvent with a binding instead." )
167     void addButtonUpEvent( Buttons buttonCode, ButtonStateEvent func )
168     {
169         addButtonEvent( buttonCode, ( Buttons buttonCode, ButtonStorageType newState ) { if( !newState ) func( buttonCode ); } );
170     }
171 
172     /**
173      * Sets the state of the button to be assigned at the beginning of next frame.
174      * Should only be called from a window controller.
175      */
176     void setButtonState( Buttons buttonCode, ButtonStorageType newState )
177     {
178         // HACK: Don't mind me.
179         import dash.utility.input.mouse, dash.core.dgame, dash.graphics.graphics;
180         import dash.utility.bindings.awesomium;
181         version( Windows ) {
182         static if( is( Buttons == MouseButtons ) )
183         {
184             if( buttonCode == MouseButtons.Left )
185             {
186                 auto ui = DGame.instance.activeScene.ui;
187                 auto mousePos = Input.mousePos;
188                 auto offset = ( Graphics.width * (mousePos.y - 1) + mousePos.x ) * 4;
189                 auto transparency = ui.view.glBuffer[ offset + 3 ];
190 
191                 import dash.utility.output;
192                 tracef( "Transparency at point %d, %d: %d", cast(int)mousePos.x, cast(int)( Graphics.height - mousePos.y ), transparency );
193 
194                 if( ui && newState && transparency > 0 )
195                 {
196                     awe_webview_inject_mouse_down( ui.view.webView, awe_mousebutton.AWE_MB_LEFT );
197                 }
198                 else
199                 {
200                     buttonStaging[ buttonCode ] = newState;
201 
202                     if( !newState )
203                     {
204                         awe_webview_inject_mouse_up( ui.view.webView, awe_mousebutton.AWE_MB_LEFT );
205                     }
206                 }
207             }
208             else
209             {
210                 buttonStaging[ buttonCode ] = newState;
211             }
212         }
213         else
214         {
215             buttonStaging[ buttonCode ] = newState;
216         }
217         }
218         else
219         {
220             buttonStaging[ buttonCode ] = newState;
221         }
222     }
223 
224 private:
225     /// The struct storing the state of the buttons.
226     alias ButtonState = State!( ButtonStorageType, ButtonEnum );
227 
228     /// The state of the buttons as of the beginning of the current frame.
229     ButtonState buttonCurrent;
230     /// The state of the buttons for the last frame.
231     ButtonState buttonPrevious;
232     /// The state of the buttons that has not been applied yet.
233     ButtonState buttonStaging;
234 
235     /// The events tied to the buttons of this system.
236     ButtonEvent[][ Buttons ] buttonEvents;
237 }
238 
239 // If we have axes
240 static if( HasAxes )
241 {
242 public:
243     /// The enum of axes that the input system has.
244     alias Axes              = AxisState.Inputs;
245     /// A delegate that takes the changed axis and the new state.
246     alias AxisEvent         = void delegate( Axes, AxisStorageType );
247 
248     /**
249      * Get the state of a given axis.
250      *
251      * Params:
252      *  axis =          The axis to get the state of.
253      *
254      * Returns: The state of the axis.
255      */
256     AxisStorageType getAxisState( Axes axis )
257     {
258         return axisCurrent[ axis ];
259     }
260 
261     /**
262      * Add an event to be fired when the given axis changes.
263      *
264      * Params:
265      *      axis =      The name of the input to add the event to.
266      *      event =     The event to trigger when the axis state changes.
267      */
268     deprecated( "Use Input.addAxisEvent with a binding instead." )
269     void addAxisEvent( Axes axis, AxisEvent event )
270     {
271         axisEvents[ axis ] ~= event;
272     }
273 
274     /**
275      * Sets the state of the axis to be assigned at the beginning of next frame.
276      * Should only be called from a window controller.
277      */
278     void setAxisState( Axes axisCode, AxisStorageType newState )
279     {
280         axisStaging[ axisCode ] = newState;
281     }
282 
283 private:
284     /// The struct storing the state of the axes.
285     alias AxisState = State!( AxisStorageType, AxisEnum );
286     /// The state of the axes as of the beginning of the current frame.
287     AxisState axisCurrent;
288     /// The state of the axes that has not been applied yet.
289     AxisState axisStaging;
290 
291     /// The events tied to the axes of this system.
292     AxisEvent[][ Axes ] axisEvents;
293 }
294 }
295 
296 /**
297  * Represents the state of an input method (ie. keyboard, gamepad, etc.).
298  *
299  * Params:
300  *  T =                 The type being stored (ie. bool for keys, floats for axes, etc.).
301  *  totalSize =         The number of inputs to store.
302  */
303 struct State( T, InputEnum ) if( is( InputEnum == enum ) )
304 {
305 private:
306     // enum totalSize = Inputs.END;
307     enum totalSize = EnumMembers!Inputs.length;
308     alias StorageType = T;
309     alias Inputs = InputEnum;
310 public:
311     T[ size_t ] keys;
312 
313     ref typeof(this) opAssign( const ref typeof(this) other )
314     {
315         foreach(size_t index, T value; other.keys)
316         {
317             keys[index] = value;
318         }
319 
320         return this;
321     }
322 
323     T opIndex( size_t keyCode ) 
324     {
325         // If the key being pressed doesn't have 
326         // an entry in keys, add it, and set it
327         // to the default value
328         if( !( keyCode in keys ) )
329             keys[ keyCode ] = cast(T)0;
330 
331         return keys[ keyCode ];
332     }
333 
334     T opIndexAssign( T newValue, size_t keyCode )
335     {
336         keys[ keyCode ] = newValue;
337 
338         return newValue;
339     }
340 
341     Tuple!( InputEnum, T )[] opBinary( string Op : "-" )( ref typeof(this) other )
342     {
343         Tuple!( InputEnum, T )[] differences;
344 
345         foreach( size_t index, T value; keys)
346             if( value != other[ index ] )
347                 differences ~= tuple( cast(InputEnum)index, value );
348 
349         return differences;
350     }
351 
352     void reset()
353     {
354         foreach( ii; keys.keys )
355             keys[ ii ] = cast(T)0;
356     }
357 }