1 /*
2 Copyright (c) 2019 Mateusz Muszyński
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dagon.core.input;
30 
31 import std.stdio;
32 import std.ascii;
33 import std.conv: to;
34 import std.math: abs;
35 import std.algorithm.searching: startsWith;
36 import std..string: isNumeric;
37 import dlib.core.memory;
38 import dlib.core.ownership;
39 import dlib.container.dict;
40 import dlib.container.array;
41 import dlib.text.lexer;
42 import dlib.text.str;
43 import dagon.core.event;
44 import dagon.core.bindings;
45 import dagon.core.keycodes;
46 import dagon.core.config;
47 
48 enum BindingType
49 {
50     None,
51     Keyboard,
52     MouseButton,
53     MouseAxis,
54     GamepadButton,
55     GamepadAxis,
56     VirtualAxis // axis created from two buttons
57 }
58 
59 struct Binding
60 {
61     BindingType type;
62     union
63     {
64         int key;
65         int button;
66         int axis;
67 
68         private struct Vaxis
69         {
70             BindingType typePos;
71             int pos;
72             BindingType typeNeg;
73             int neg;
74         }
75         Vaxis vaxis; // virtual axis
76     }
77 }
78 
79 class InputManager
80 {
81     EventManager eventManager;
82 
83     alias Bindings = Array!Binding;
84 
85     Dict!(Bindings, string) bindings;
86 
87     Configuration config;
88 
89     this(EventManager em)
90     {
91         eventManager = em;
92         bindings = dict!(Bindings, string)();
93 
94         config = New!Configuration(null);
95         if (!config.fromFile("input.conf"))
96         {
97             writeln("Warning: no \"input.conf\" found");
98         }
99 
100         foreach(name, value; config.props.props)
101         {
102             addBindings(name, value.data);
103         }
104     }
105 
106     ~this()
107     {
108         foreach(name, bind; bindings)
109         {
110             bind.free();
111         }
112         Delete(bindings);
113         Delete(config);
114     }
115 
116     private Binding parseBinding(Lexer lexer)
117     {
118         // Binding format consist of device type and name(or number)
119         // coresponding to button or axis of this device
120         // eg. kb_up, kb_w, ma_0, mb_1, gb_a, gb_x, ga_leftx, ga_lefttrigger
121         // kb -> keybaord, by name
122         // ma -> mouse axis, by number
123         // mb -> mouse button, by number
124         // ga -> gamepad axis, by name
125         // gb -> gamepad button, by name
126         // va -> virtual axis, special syntax: va(kb_up, kb_down)
127 
128         BindingType type = BindingType.None;
129         int result = -1;
130 
131         auto lexeme = lexer.getLexeme();
132 
133         switch(lexeme)
134         {
135             case "kb": type = BindingType.Keyboard; break;
136             case "ma": type = BindingType.MouseAxis; break;
137             case "mb": type = BindingType.MouseButton; break;
138             case "ga": type = BindingType.GamepadAxis; break;
139             case "gb": type = BindingType.GamepadButton; break;
140             case "va": type = BindingType.VirtualAxis; break;
141 
142             default: goto fail;
143         }
144 
145         lexeme = lexer.getLexeme();
146 
147         if (type != BindingType.VirtualAxis)
148         {
149             if (lexeme != "_")
150                 goto fail;
151 
152             lexeme = lexer.getLexeme();
153 
154             if(lexeme.isNumeric)
155             {
156                 result = to!int(lexeme);
157             }
158             else
159             {
160                 String svalue = String(lexeme);
161                 const(char)* cvalue = svalue.ptr;
162                 switch(type)
163                 {
164                     case BindingType.Keyboard:      result = cast(int)SDL_GetScancodeFromName(cvalue); break;
165                     case BindingType.GamepadAxis:   result = cast(int)SDL_GameControllerGetAxisFromString(cvalue); break;
166                     case BindingType.GamepadButton: result = cast(int)SDL_GameControllerGetButtonFromString(cvalue); break;
167 
168                     default: break;
169                 }
170 
171                 svalue.free();
172             }
173 
174             if (type != BindingType.None || result > 0)
175                 return Binding(type, result);
176         }
177         else
178         {
179             // Virtual axis
180             if (lexeme != "(")
181                 goto fail;
182 
183             Binding pos = parseBinding(lexer);
184 
185             if (pos.type != BindingType.Keyboard &&
186                pos.type != BindingType.MouseButton &&
187                pos.type != BindingType.GamepadButton)
188                 goto fail;
189 
190             lexeme = lexer.getLexeme();
191             if (lexeme != ",")
192                 goto fail;
193 
194             Binding neg = parseBinding(lexer);
195 
196             if (neg.type != BindingType.Keyboard &&
197                neg.type != BindingType.MouseButton &&
198                neg.type != BindingType.GamepadButton)
199                 goto fail;
200 
201             lexeme = lexer.getLexeme();
202 
203             if (lexeme != ")")
204                 goto fail;
205 
206             Binding bind = Binding(type);
207             bind.vaxis.typePos = pos.type;
208             bind.vaxis.pos = pos.key;
209             bind.vaxis.typeNeg = neg.type;
210             bind.vaxis.neg = neg.key;
211             return bind;
212         }
213 
214     fail:
215         return Binding(BindingType.None, -1);
216     }
217 
218     void addBindings(string name, string value)
219     {
220         auto lexer = New!Lexer(value, ["_", ",", "(", ")"]);
221         lexer.ignoreWhitespaces = true;
222 
223         while(true)
224         {
225             Binding b = parseBinding(lexer);
226             if (b.type == BindingType.None && b.key == -1)
227             {
228                 writefln("Error: wrong binding format \"%s\"", value);
229                 break;
230             }
231 
232             if (auto binding = name in bindings)
233             {
234                 binding.insertBack(b);
235             }
236             else
237             {
238                 auto binds = Bindings();
239                 binds.insertBack(b);
240                 bindings[name] = binds;
241             }
242 
243             auto lexeme = lexer.getLexeme();
244             if (lexeme == ",")
245                 continue;
246 
247             if (lexeme == "")
248                 break;
249         }
250 
251         Delete(lexer);
252     }
253 
254     void clearBindings(string name)
255     {
256         if (auto binding = name in bindings)
257         {
258             binding.removeBack(cast(uint)binding.length);
259         }
260     }
261 
262     bool getButton(Binding binding)
263     {
264         switch(binding.type)
265         {
266             case BindingType.Keyboard:
267                 if (eventManager.keyPressed[binding.key]) return true;
268                 break;
269 
270             case BindingType.MouseButton:
271                 if (eventManager.mouseButtonPressed[binding.button]) return true;
272                 break;
273 
274             case BindingType.GamepadButton:
275                 if (eventManager.controllerButtonPressed[binding.button]) return true;
276                 break;
277 
278             default:
279                 break;
280         }
281 
282         return false;
283     }
284 
285     bool getButton(string name)
286     {
287         auto b = name in bindings;
288         if (!b)
289             return false;
290 
291         for(int i = 0; i < b.length; i++)
292         {
293             if (getButton((*b)[i])) return true;
294         }
295 
296         return false;
297     }
298 
299     bool getButtonUp(string name)
300     {
301         auto b = name in bindings;
302         if (!b)
303             return false;
304 
305         for(int i = 0; i < b.length; i++)
306         {
307             auto binding = (*b)[i];
308             switch(binding.type)
309             {
310                 case BindingType.Keyboard:
311                     if (eventManager.keyUp[binding.key]) return true;
312                     break;
313 
314                 case BindingType.MouseButton:
315                     if (eventManager.mouseButtonUp[binding.button]) return true;
316                     break;
317 
318                 case BindingType.GamepadButton:
319                     if (eventManager.controllerButtonUp[binding.button]) return true;
320                     break;
321 
322                 default:
323                     break;
324             }
325         }
326 
327         return false;
328     }
329 
330     bool getButtonDown(string name)
331     {
332         auto b = name in bindings;
333         if (!b)
334             return false;
335 
336         for(int i = 0; i < b.length; i++)
337         {
338             auto binding = (*b)[i];
339 
340             switch(binding.type)
341             {
342                 case BindingType.Keyboard:
343                     if (eventManager.keyDown[binding.key]) return true;
344                     break;
345 
346                 case BindingType.MouseButton:
347                     if (eventManager.mouseButtonDown[binding.button]) return true;
348                     break;
349 
350                 case BindingType.GamepadButton:
351                     if (eventManager.controllerButtonDown[binding.button]) return true;
352                     break;
353 
354                 default:
355                     break;
356             }
357         }
358 
359         return false;
360     }
361 
362     float getAxis(string name)
363     {
364         auto b = name in bindings;
365         if (!b)
366             return false;
367 
368         float result = 0.0f;
369         float aresult = 0.0f; // absolute result
370 
371         for(int i = 0; i < b.length; i++)
372         {
373             auto binding = (*b)[i];
374             float value = 0.0f;
375 
376             switch(binding.type)
377             {
378                 case BindingType.MouseAxis:
379                     if (binding.axis == 0)
380                         value = eventManager.mouseRelX / (eventManager.windowWidth * 0.5f); // map to -1 to 1 range
381                     else if (binding.axis == 1)
382                         value = eventManager.mouseRelY / (eventManager.windowHeight * 0.5f);
383                     break;
384 
385                 case BindingType.GamepadAxis:
386                     if (eventManager.gameControllerAvailable)
387                         value = eventManager.gameControllerAxis(binding.axis);
388                     break;
389 
390                 case BindingType.VirtualAxis:
391                     value  = getButton(*cast(Binding*)(&binding.vaxis.typePos)) ?  1.0f : 0.0f;
392                     value += getButton(*cast(Binding*)(&binding.vaxis.typeNeg)) ? -1.0f : 0.0f;
393                     break;
394 
395                 default:
396                     break;
397             }
398             float avalue = abs(value);
399             if (avalue > aresult)
400             {
401                 result = value;
402                 aresult = avalue;
403             }
404         }
405 
406         return result;
407     }
408 }