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.container.dict;
39 import dlib.container.array;
40 import dlib.text.lexer;
41 import dlib.text.unmanagedstring;
42 import dagon.core.event;
43 import dagon.core.libs;
44 import dagon.core.ownership;
45 import dagon.core.keycodes;
46 import dagon.resource.asset;
47 import dagon.resource.config;
48 
49 enum BindingType
50 {
51     None,
52     Keyboard,
53     MouseButton,
54     MouseAxis,
55     GamepadButton,
56     GamepadAxis,
57     VirtualAxis // axis created from two buttons
58 }
59 
60 struct Binding
61 {
62     BindingType type;
63     union
64     {
65         int key;
66         int button;
67         int axis;
68 
69         private struct Vaxis
70         {
71             BindingType typePos;
72             int pos;
73             BindingType typeNeg;
74             int neg;
75         }
76         Vaxis vaxis; // virtual axis
77     }
78 }
79 
80 class InputManager
81 {
82     EventManager eventManager;
83 
84     alias Bindings = DynamicArray!Binding;
85 
86     Dict!(Bindings, string) bindings;
87 
88     Configuration config;
89 
90     this(EventManager em)
91     {
92         eventManager = em;
93         bindings = dict!(Bindings, string)();
94 
95         config = New!Configuration(null);
96         if (!config.fromFile("input.conf"))
97         {
98             writeln("Warning: no \"input.conf\" found");
99         }
100 
101         foreach(name, value; config.props.props)
102         {
103             addBindings(name, value.data);
104         }
105     }
106 
107     ~this()
108     {
109         foreach(name, bind; bindings)
110         {
111             bind.free();
112         }
113         Delete(bindings);
114         Delete(config);
115     }
116 
117     private Binding parseBinding(Lexer lexer)
118     {
119         // Binding format consist of device type and name(or number)
120         // coresponding to button or axis of this device
121         // eg. kb_up, kb_w, ma_0, mb_1, gb_a, gb_x, ga_leftx, ga_lefttrigger
122         // kb -> keybaord, by name
123         // ma -> mouse axis, by number
124         // mb -> mouse button, by number
125         // ga -> gamepad axis, by name
126         // gb -> gamepad button, by name
127         // va -> virtual axis, special syntax: va(kb_up, kb_down)
128 
129         BindingType type = BindingType.None;
130         int result = -1;
131 
132         auto lexeme = lexer.getLexeme();
133 
134         switch(lexeme)
135         {
136             case "kb": type = BindingType.Keyboard; break;
137             case "ma": type = BindingType.MouseAxis; break;
138             case "mb": type = BindingType.MouseButton; break;
139             case "ga": type = BindingType.GamepadAxis; break;
140             case "gb": type = BindingType.GamepadButton; break;
141             case "va": type = BindingType.VirtualAxis; break;
142 
143             default: goto fail;
144         }
145 
146         lexeme = lexer.getLexeme();
147 
148         if (type != BindingType.VirtualAxis)
149         {
150             if (lexeme != "_")
151                 goto fail;
152 
153             lexeme = lexer.getLexeme();
154 
155             if(lexeme.isNumeric)
156             {
157                 result = to!int(lexeme);
158             }
159             else
160             {
161                 String svalue = String(lexeme);
162                 char* cvalue = svalue.cString;
163                 switch(type)
164                 {
165                     case BindingType.Keyboard:      result = cast(int)SDL_GetScancodeFromName(cvalue); break;
166                     case BindingType.GamepadAxis:   result = cast(int)SDL_GameControllerGetAxisFromString(cvalue); break;
167                     case BindingType.GamepadButton: result = cast(int)SDL_GameControllerGetButtonFromString(cvalue); break;
168 
169                     default: break;
170                 }
171 
172                 svalue.free();
173             }
174 
175             if (type != BindingType.None || result > 0)
176                 return Binding(type, result);
177         }
178         else
179         {
180             // Virtual axis
181             if (lexeme != "(")
182                 goto fail;
183 
184             Binding pos = parseBinding(lexer);
185 
186             if (pos.type != BindingType.Keyboard &&
187                pos.type != BindingType.MouseButton &&
188                pos.type != BindingType.GamepadButton)
189                 goto fail;
190 
191             lexeme = lexer.getLexeme();
192             if (lexeme != ",")
193                 goto fail;
194 
195             Binding neg = parseBinding(lexer);
196 
197             if (neg.type != BindingType.Keyboard &&
198                neg.type != BindingType.MouseButton &&
199                neg.type != BindingType.GamepadButton)
200                 goto fail;
201 
202             lexeme = lexer.getLexeme();
203 
204             if (lexeme != ")")
205                 goto fail;
206 
207             Binding bind = Binding(type);
208             bind.vaxis.typePos = pos.type;
209             bind.vaxis.pos = pos.key;
210             bind.vaxis.typeNeg = neg.type;
211             bind.vaxis.neg = neg.key;
212             return bind;
213         }
214 
215     fail:
216         return Binding(BindingType.None, -1);
217     }
218 
219     void addBindings(string name, string value)
220     {
221         auto lexer = New!Lexer(value, ["_", ",", "(", ")"]);
222         lexer.ignoreWhitespaces = true;
223 
224         while(true)
225         {
226             Binding b = parseBinding(lexer);
227             if (b.type == BindingType.None && b.key == -1)
228             {
229                 writefln("Error: wrong binding format \"%s\"", value);
230                 break;
231             }
232 
233             if (auto binding = name in bindings)
234             {
235                 binding.insertBack(b);
236             }
237             else
238             {
239                 auto binds = Bindings();
240                 binds.insertBack(b);
241                 bindings[name] = binds;
242             }
243 
244             auto lexeme = lexer.getLexeme();
245             if (lexeme == ",")
246                 continue;
247 
248             if (lexeme == "")
249                 break;
250         }
251 
252         Delete(lexer);
253     }
254 
255     void clearBindings(string name)
256     {
257         if (auto binding = name in bindings)
258         {
259             binding.removeBack(cast(uint)binding.length);
260         }
261     }
262 
263     bool getButton(Binding binding)
264     {
265         switch(binding.type)
266         {
267             case BindingType.Keyboard:
268                 if (eventManager.keyPressed[binding.key]) return true;
269                 break;
270 
271             case BindingType.MouseButton:
272                 if (eventManager.mouseButtonPressed[binding.button]) return true;
273                 break;
274 
275             case BindingType.GamepadButton:
276                 if (eventManager.controllerButtonPressed[binding.button]) return true;
277                 break;
278 
279             default:
280                 break;
281         }
282 
283         return false;
284     }
285 
286     bool getButton(string name)
287     {
288         auto b = name in bindings;
289         if (!b)
290             return false;
291 
292         for(int i = 0; i < b.length; i++)
293         {
294             if (getButton((*b)[i])) return true;
295         }
296 
297         return false;
298     }
299 
300     bool getButtonUp(string name)
301     {
302         auto b = name in bindings;
303         if (!b)
304             return false;
305 
306         for(int i = 0; i < b.length; i++)
307         {
308             auto binding = (*b)[i];
309             switch(binding.type)
310             {
311                 case BindingType.Keyboard:
312                     if (eventManager.keyUp[binding.key]) return true;
313                     break;
314 
315                 case BindingType.MouseButton:
316                     if (eventManager.mouseButtonUp[binding.button]) return true;
317                     break;
318 
319                 case BindingType.GamepadButton:
320                     if (eventManager.controllerButtonUp[binding.button]) return true;
321                     break;
322 
323                 default:
324                     break;
325             }
326         }
327 
328         return false;
329     }
330 
331     bool getButtonDown(string name)
332     {
333         auto b = name in bindings;
334         if (!b)
335             return false;
336 
337         for(int i = 0; i < b.length; i++)
338         {
339             auto binding = (*b)[i];
340 
341             switch(binding.type)
342             {
343                 case BindingType.Keyboard:
344                     if (eventManager.keyDown[binding.key]) return true;
345                     break;
346 
347                 case BindingType.MouseButton:
348                     if (eventManager.mouseButtonDown[binding.button]) return true;
349                     break;
350 
351                 case BindingType.GamepadButton:
352                     if (eventManager.controllerButtonDown[binding.button]) return true;
353                     break;
354 
355                 default:
356                     break;
357             }
358         }
359 
360         return false;
361     }
362 
363     float getAxis(string name)
364     {
365         auto b = name in bindings;
366         if (!b)
367             return false;
368 
369         float result = 0.0f;
370         float aresult = 0.0f; // absolute result
371 
372         for(int i = 0; i < b.length; i++)
373         {
374             auto binding = (*b)[i];
375             float value = 0.0f;
376 
377             switch(binding.type)
378             {
379                 case BindingType.MouseAxis:
380                     if (binding.axis == 0)
381                         value = eventManager.mouseRelX / (eventManager.windowWidth * 0.5f); // map to -1 to 1 range
382                     else if (binding.axis == 1)
383                         value = eventManager.mouseRelY / (eventManager.windowHeight * 0.5f);
384                     break;
385 
386                 case BindingType.GamepadAxis:
387                     if (eventManager.gameControllerAvailable)
388                         value = eventManager.gameControllerAxis(binding.axis);
389                     break;
390 
391                 case BindingType.VirtualAxis:
392                     value  = getButton(*cast(Binding*)(&binding.vaxis.typePos)) ?  1.0f : 0.0f;
393                     value += getButton(*cast(Binding*)(&binding.vaxis.typeNeg)) ? -1.0f : 0.0f;
394                     break;
395 
396                 default:
397                     break;
398             }
399             float avalue = abs(value);
400             if (avalue > aresult)
401             {
402                 result = value;
403                 aresult = avalue;
404             }
405         }
406 
407         return result;
408     }
409 }