1 module des.app.joystick;
2 
3 import std.conv : to;
4 
5 import derelict.sdl2.sdl;
6 
7 import des.util.arch;
8 import des.util.logsys;
9 import des.util.stdext..string;
10 import des.app.evproc;
11 
12 import des.app.base : DesAppException;
13 
14 ///
15 class Joystick : DesObject
16 {
17     mixin DES;
18 private:
19     bool is_open;
20 
21 protected:
22     ///
23     int index;
24     ///
25     SDL_Joystick *dev;
26 
27     string joy_name;
28 
29     ///
30     short[] axis_vals;
31     ///
32     ivec2[] ball_vals;
33     ///
34     bool[] button_vals;
35     ///
36     HatState[] hat_vals;
37 
38 public:
39 
40     ///
41     Signal!( uint, short ) axisChange;
42     ///
43     Signal!( uint, ivec2 ) ballChange;
44     ///
45     Signal!( uint, bool ) buttonChange;
46     ///
47     Signal!( uint, HatState ) hatChange;
48 
49     ///
50     enum HatState
51     {
52         CENTERED  = SDL_HAT_CENTERED,  /// `SDL_HAT_CENTERED`
53         UP        = SDL_HAT_UP,        /// `SDL_HAT_UP`
54         RIGHT     = SDL_HAT_RIGHT,     /// `SDL_HAT_RIGHT`
55         DOWN      = SDL_HAT_DOWN,      /// `SDL_HAT_DOWN`
56         LEFT      = SDL_HAT_LEFT,      /// `SDL_HAT_LEFT`
57         RIGHTUP   = SDL_HAT_RIGHTUP,   /// `SDL_HAT_RIGHTUP`
58         RIGHTDOWN = SDL_HAT_RIGHTDOWN, /// `SDL_HAT_RIGHTDOWN`
59         LEFTUP    = SDL_HAT_LEFTUP,    /// `SDL_HAT_LEFTUP`
60         LEFTDOWN  = SDL_HAT_LEFTDOWN,  /// `SDL_HAT_LEFTDOWN`
61     };
62 
63     ///
64     this( int index )
65     {
66         this.index = index;
67         open();
68     }
69 
70     ///
71     void open() { if( !isOpen ) selfConstruct(); }
72 
73     pure
74     {
75         const
76         {
77             @property
78             {
79                 ///
80                 string name() { return joy_name; }
81                 ///
82                 bool isOpen() { return is_open; }
83             }
84 
85             ///
86             short axis( uint i ) { return axis_vals[i]; }
87             ///
88             ivec2 ball( uint i ) { return ball_vals[i]; }
89             ///
90             bool button( uint i ) { return button_vals[i]; }
91             ///
92             HatState hat( uint i ) { return hat_vals[i]; }
93         }
94     }
95 
96 package:
97     ///
98     void updateAxis( uint i, short v )
99     {
100         axis_vals[i] = v;
101         axisChange( i, v );
102     }
103     ///
104     void updateBall( uint i, in ivec2 v )
105     {
106         ball_vals[i] = v;
107         ballChange( i, v );
108     }
109     ///
110     void updateButton( uint i, bool v )
111     {
112         button_vals[i] = v;
113         buttonChange( i, v );
114     }
115     ///
116     void updateHat( uint i, uint v )
117     {
118         auto vv = cast(HatState)v;
119         hat_vals[i] = vv;
120         hatChange( i, vv );
121     }
122 
123 protected:
124 
125     ///
126     override void selfConstruct()
127     {
128         dev = SDL_JoystickOpen( index );
129         if( !dev )
130             throw new DesAppException( "Error open joystick: " ~ toDString( SDL_GetError() ) );
131 
132         logger.Debug( "joy opened: ", dev );
133 
134         joy_name = SDL_JoystickName( dev ).toDString;
135 
136         createVals();
137         setVals();
138 
139         is_open = true;
140         logger.Debug( "pass" );
141     }
142 
143     ///
144     void createVals()
145     {
146         axis_vals = new short[]( SDL_JoystickNumAxes( dev ) );
147         logger.trace( "axis vals created: ", axis_vals.length );
148         ball_vals = new ivec2[]( SDL_JoystickNumBalls( dev ) );
149         logger.trace( "ball vals created: ", ball_vals.length );
150         button_vals = new bool[]( SDL_JoystickNumButtons( dev ) );
151         logger.trace( "ball vals setted: ", button_vals.length );
152         hat_vals = to!(HatState[])( new byte[]( SDL_JoystickNumHats( dev ) ) );
153         logger.trace( "hat vals setted: ", hat_vals.length );
154     }
155 
156     /// without calling change signals
157     void setVals()
158     {
159         setAxes();
160         setBalls();
161         setButtons();
162         setHats();
163     }
164 
165     ///
166     void setAxes()
167     {
168         foreach( int i, ref axis; axis_vals )
169             axis = SDL_JoystickGetAxis( dev, i );
170     }
171 
172     ///
173     void setBalls()
174     {
175         foreach( int i, ref ball; ball_vals )
176             SDL_JoystickGetBall( dev, i, ball.data.ptr, ball.data.ptr+1 );
177     }
178 
179     ///
180     void setButtons()
181     {
182         foreach( int i, ref button; button_vals )
183             button = cast(bool)SDL_JoystickGetButton( dev, i );
184     }
185 
186     ///
187     void setHats()
188     {
189         foreach( int i, ref hat; hat_vals )
190             hat = cast(HatState)SDL_JoystickGetHat( dev, i );
191     }
192 
193     ///
194     override void selfDestroy()
195     {
196         SDL_JoystickClose( dev );
197         is_open = false;
198     }
199 }
200 
201 ///
202 class JoyEventProcessor : BaseSDLEventProcessor
203 {
204     mixin DES;
205 
206     /// number of joystick in `devlist`
207     Signal!( uint ) added;
208     /// number of joystick in `devlist`
209     Signal!( uint ) removed;
210 
211     /// number of joystick in `devlist`, number of axis, value
212     Signal!( uint, uint, short ) axisChange;
213     /// number of joystick in `devlist`, number of ball, relative move
214     Signal!( uint, uint, ivec2 ) ballChange;
215     /// number of joystick in `devlist`, number of button, pressed
216     Signal!( uint, uint, bool ) buttonChange;
217     /// number of joystick in `devlist`, number of hat, state
218     Signal!( uint, uint, Joystick.HatState ) hatChange;
219 
220 
221     /// list of registred devices
222     Joystick[uint] devlist;
223 
224     this()
225     {
226         if( SDL_InitSubSystem( SDL_INIT_JOYSTICK ) < 0 )
227             throw new DesAppException( "Error initializing SDL joystick subsystem: " ~ toDString( SDL_GetError() ) );
228 
229         SDL_JoystickEventState( SDL_ENABLE );
230 
231         int num_joys = SDL_NumJoysticks();
232         foreach( index; 0 .. num_joys )
233             addJoystick( index );
234 
235         logger.Debug( "pass" );
236     }
237 
238     bool procSDLEvent( in SDL_Event ev )
239     {
240         switch( ev.type )
241         {
242             case SDL_JOYAXISMOTION:
243                 with( ev.jaxis )
244                     updateAxis( which, axis, value );
245                 return true;
246 
247             case SDL_JOYBALLMOTION: 
248                 with( ev.jball )
249                     updateBall( which, ball, ivec2( xrel, yrel ) );
250                 return true;
251 
252             case SDL_JOYHATMOTION:
253                 with( ev.jhat )
254                     updateHat( which, hat, value );
255                 return true;
256 
257             case SDL_JOYBUTTONDOWN:
258             case SDL_JOYBUTTONUP: 
259                 with( ev.jbutton )
260                     updateButton( which, button, cast(bool)state );
261                 return true;
262 
263             case SDL_JOYDEVICEADDED:
264                 addJoystick( ev.jdevice.which );
265                 return true;
266 
267             case SDL_JOYDEVICEREMOVED:
268                 removeJoystick( ev.jdevice.which );
269                 return true;
270 
271             default: return false;
272         }
273     }
274 
275 protected:
276 
277     void updateAxis( uint ind, uint axis, short value )
278     {
279         if( auto j = devlist.get( ind, null ) )
280             j.updateAxis( axis, value );
281         else
282             logger.warn( "no joystick device in list: ", ind );
283         axisChange( ind, axis, value );
284     }
285 
286     void updateBall( uint ind, uint ball, ivec2 rel )
287     {
288         if( auto j = devlist.get( ind, null ) )
289             j.updateBall( ball, rel );
290         else
291             logger.warn( "no joystick device in list: ", ind );
292         ballChange( ind, ball, rel );
293     }
294 
295     void updateHat( uint ind, uint hat, uint value )
296     {
297         if( auto j = devlist.get( ind, null ) )
298             j.updateHat( hat, value );
299         else
300             logger.warn( "no joystick device in list: ", ind );
301         hatChange( ind, hat, cast(Joystick.HatState)value );
302     }
303 
304     void updateButton( uint ind, uint btn, bool value )
305     {
306         if( auto j = devlist.get( ind, null ) )
307             j.updateButton( btn, value );
308         else
309             logger.warn( "no joystick device in list: ", ind );
310         buttonChange( ind, btn, value );
311     }
312 
313     void addJoystick( uint ind )
314     {
315         auto j = newEMM!Joystick(ind);
316         auto i = SDL_JoystickInstanceID( j.dev );
317         devlist[i] = j;
318         added( i );
319         logger.Debug( "[%d]", i );
320     }
321 
322     void removeJoystick( uint ind )
323     {
324         if( ind in devlist )
325         {
326             auto j = devlist[ind];
327             devlist.remove(ind);
328             j.destroy();
329             removed( ind );
330             logger.Debug( "[%d]", ind );
331         }
332     }
333 }