1 /**
2  * Retrograde Engine
3  *
4  * Authors:
5  *  Mike Bierlee, m.bierlee@lostmoment.com
6  * Copyright: 2014-2021 Mike Bierlee
7  * License:
8  *  This software is licensed under the terms of the MIT license.
9  *  The full terms of the license can be found in the LICENSE.txt file.
10  */
11 
12 module retrograde.input;
13 
14 import retrograde.messaging;
15 import retrograde.stringid;
16 
17 import poodinis;
18 
19 import std.signals;
20 import std.stdio;
21 import std.format;
22 import std.math;
23 import std.traits;
24 import std.algorithm;
25 import std.typecons;
26 
27 enum InputEvent : StringId {
28     JOYSTICK_AXIS_MOVEMENT = sid("ev_joystick_axis_movement"),
29     JOYSTICK_BALL_MOVEMENT = sid("ev_joystick_ball_movement"),
30     JOYSTICK_HAT = sid("ev_joystick_hat"),
31     JOYSTICK_BUTTON = sid("ev_joystick_button"),
32     JOYSTICK_ADDED = sid("ev_joystick_added"),
33     JOYSTICK_REMOVED = sid("ev_joystick_removed"),
34     KEYBOARD_KEY = sid("ev_keyboard_key"),
35     MOUSE_MOTION = sid("ev_mouse_motion"),
36     MOUSE_BUTTON = sid("ev_mouse_button"),
37     MOUSE_WHEEL = sid("ev_mouse_wheel")
38 }
39 
40 public void registerInputEventDebugSids(SidMap sidMap) {
41     sidMap.add("ev_joystick_axis_movement");
42     sidMap.add("ev_joystick_ball_movement");
43     sidMap.add("ev_joystick_hat");
44     sidMap.add("ev_joystick_button");
45     sidMap.add("ev_joystick_added");
46     sidMap.add("ev_joystick_removed");
47     sidMap.add("ev_keyboard_key");
48     sidMap.add("ev_mouse_motion");
49     sidMap.add("ev_mouse_button");
50     sidMap.add("ev_mouse_wheel");
51 }
52 
53 class InputMessageData : MessageData {
54     public Device device;
55 }
56 
57 class JoystickAxisEventData : InputMessageData {
58     public ubyte axis;
59 }
60 
61 enum JoystickBallAxis {
62     X, Y
63 }
64 
65 class JoystickBallEventData : InputMessageData {
66     public ubyte ball;
67     public JoystickBallAxis axis;
68 }
69 
70 enum JoystickHatPosition {
71     LEFT_UP,
72     LEFT,
73     LEFT_DOWN,
74     UP,
75     CENTERED,
76     DOWN,
77     RIGHT_UP,
78     RIGHT,
79     RIGHT_DOWN
80 }
81 
82 class JoystickHatEventData : InputMessageData {
83     public ubyte hat;
84     public JoystickHatPosition postion;
85 }
86 
87 class JoystickButtonEventData : InputMessageData {
88     public ubyte button;
89 }
90 
91 enum KeyboardKeyCode {
92     A,
93     AC_BACK,
94     AC_BOOKMARKS,
95     AC_FORWARD,
96     AC_HOME,
97     AC_REFRESH,
98     AC_SEARCH,
99     AC_STOP,
100     AGAIN,
101     ALTERASE,
102     APOSTROPHE,
103     APP1,
104     APP2,
105     APPLICATION,
106     AUDIOMUTE,
107     AUDIONEXT,
108     AUDIOPLAY,
109     AUDIOPREV,
110     AUDIOSTOP,
111     B,
112     BACKSLASH,
113     BACKSPACE,
114     BRIGHTNESSDOWN,
115     BRIGHTNESSUP,
116     C,
117     CALCULATOR,
118     CANCEL,
119     CAPSLOCK,
120     CLEAR,
121     CLEARAGAIN,
122     COMMA,
123     COMPUTER,
124     COPY,
125     CRSEL,
126     CURRENCYSUBUNIT,
127     CURRENCYUNIT,
128     CUT,
129     D,
130     DECIMALSEPARATOR,
131     DELETE,
132     DISPLAYSWITCH,
133     DOWN,
134     E,
135     EIGHT,
136     EJECT,
137     END,
138     EQUALS,
139     ESCAPE,
140     EXECUTE,
141     EXSEL,
142     F,
143     F1,
144     F10,
145     F11,
146     F12,
147     F13,
148     F14,
149     F15,
150     F16,
151     F17,
152     F18,
153     F19,
154     F2,
155     F20,
156     F21,
157     F22,
158     F23,
159     F24,
160     F3,
161     F4,
162     F5,
163     F6,
164     F7,
165     F8,
166     F9,
167     FIND,
168     FIVE,
169     FOUR,
170     G,
171     GRAVE,
172     H,
173     HELP,
174     HOME,
175     I,
176     INSERT,
177     INTERNATIONAL1,
178     INTERNATIONAL2,
179     INTERNATIONAL3,
180     INTERNATIONAL4,
181     INTERNATIONAL5,
182     INTERNATIONAL6,
183     INTERNATIONAL7,
184     INTERNATIONAL8,
185     INTERNATIONAL9,
186     J,
187     K,
188     KBDILLUMDOWN,
189     KBDILLUMTOGGLE,
190     KBDILLUMUP,
191     KEYPAD_00,
192     KEYPAD_000,
193     KEYPAD_COMMA,
194     KEYPAD_DIVIDE,
195     KEYPAD_EIGHT,
196     KEYPAD_ENTER,
197     KEYPAD_EQUALS,
198     KEYPAD_EQUALSAS400,
199     KEYPAD_FIVE,
200     KEYPAD_FOUR,
201     KEYPAD_MINUS,
202     KEYPAD_MULTIPLY,
203     KEYPAD_NINE,
204     KEYPAD_ONE,
205     KEYPAD_PERIOD,
206     KEYPAD_PLUS,
207     KEYPAD_SEVEN,
208     KEYPAD_SIX,
209     KEYPAD_THREE,
210     KEYPAD_TWO,
211     KEYPAD_ZERO,
212     KP_A,
213     KP_AMPERSAND,
214     KP_AT,
215     KP_B,
216     KP_BACKSPACE,
217     KP_BINARY,
218     KP_C,
219     KP_CLEAR,
220     KP_CLEARENTRY,
221     KP_COLON,
222     KP_D,
223     KP_DBLAMPERSAND,
224     KP_DBLVERTICALBAR,
225     KP_DECIMAL,
226     KP_E,
227     KP_EXCLAM,
228     KP_F,
229     KP_GREATER,
230     KP_HASH,
231     KP_HEXADECIMAL,
232     KP_LEFTBRACE,
233     KP_LEFTPAREN,
234     KP_LESS,
235     KP_MEMADD,
236     KP_MEMCLEAR,
237     KP_MEMDIVIDE,
238     KP_MEMMULTIPLY,
239     KP_MEMRECALL,
240     KP_MEMSTORE,
241     KP_MEMSUBTRACT,
242     KP_OCTAL,
243     KP_PERCENT,
244     KP_PLUSMINUS,
245     KP_POWER,
246     KP_RIGHTBRACE,
247     KP_RIGHTPAREN,
248     KP_SPACE,
249     KP_TAB,
250     KP_VERTICALBAR,
251     KP_XOR,
252     L,
253     LALT,
254     LANG1,
255     LANG2,
256     LANG3,
257     LANG4,
258     LANG5,
259     LANG6,
260     LANG7,
261     LANG8,
262     LANG9,
263     LCTRL,
264     LEFT,
265     LEFTBRACKET,
266     LGUI,
267     LSHIFT,
268     M,
269     MAIL,
270     MEDIASELECT,
271     MENU,
272     MINUS,
273     MODE,
274     MUTE,
275     N,
276     NINE,
277     NONUSBACKSLASH,
278     NONUSHASH,
279     NUMLOCKCLEAR,
280     O,
281     ONE,
282     OPER,
283     OUT,
284     P,
285     PAGEDOWN,
286     PAGEUP,
287     PASTE,
288     PAUSE,
289     PERIOD,
290     POWER,
291     PRINTSCREEN,
292     PRIOR,
293     Q,
294     R,
295     RALT,
296     RCTRL,
297     RETURN,
298     RETURN2,
299     RGUI,
300     RIGHT,
301     RIGHTBRACKET,
302     RSHIFT,
303     S,
304     SCROLLLOCK,
305     SELECT,
306     SEMICOLON,
307     SEPARATOR,
308     SEVEN,
309     SIX,
310     SLASH,
311     SLEEP,
312     SPACE,
313     STOP,
314     SYSREQ,
315     T,
316     TAB,
317     THOUSANDSSEPARATOR,
318     THREE,
319     TWO,
320     U,
321     UNDO,
322     UP,
323     V,
324     VOLUMEDOWN,
325     VOLUMEUP,
326     W,
327     WWW,
328     X,
329     Y,
330     Z,
331     ZERO
332 }
333 
334 enum MouseButton {
335     LEFT,
336     MIDDLE,
337     RIGHT,
338     X1,
339     X2
340 }
341 
342 enum KeyboardKeyModifier : int {
343     NONE   = 1<<0,
344     LSHIFT = 1<<2,
345     RSHIFT = 1<<3,
346     LCTRL  = 1<<4,
347     RCTRL  = 1<<5,
348     LALT   = 1<<6,
349     RALT   = 1<<7,
350     LGUI   = 1<<8,
351     RGUI   = 1<<9,
352     NUM    = 1<<10,
353     CAPS   = 1<<11,
354     MODE   = 1<<12,
355     CTRL   = 1<<13,
356     SHIFT  = 1<<14,
357     ALT    = 1<<15,
358     GUI    = 1<<16
359 }
360 
361 alias InvertMagnitude = Flag!"InvertMagnitude";
362 
363 class KeyboardKeyEventData : InputMessageData {
364     public KeyboardKeyCode scanCode;
365     public KeyboardKeyModifier modifiers;
366 }
367 
368 enum MouseAxis {
369     X, Y
370 }
371 
372 class MouseMotionEventData : InputMessageData {
373     public MouseAxis axis;
374     public int absolutePosition;
375 }
376 
377 class MouseButtonEventData : InputMessageData {
378     public MouseButton button;
379 }
380 
381 struct EventMappingKey {
382     public StringId eventName;
383     public uint componentOne;
384     public uint componentTwo;
385 }
386 
387 class RawInputEventChannel : EventChannel {}
388 class MappedInputCommandChannel : CommandChannel {}
389 
390 struct Device {
391     DeviceType type;
392     int id;
393 }
394 
395 enum DeviceType {
396     unknown,
397     joystick,
398     keyboard,
399     mouse
400 }
401 
402 class MappedInputCommandData : InputMessageData {}
403 
404 class InputHandler {
405 
406     @Autowire
407     private MappedInputCommandChannel mappedInputCommandChannel;
408 
409     @Autowire
410     private RawInputEventChannel rawInputEventChannel;
411 
412     private const(Event)[] eventQueue;
413     private StringId[][EventMappingKey] eventMappings;
414     private double[ubyte] axisDeadzones;
415     private StringId[] inputEvents;
416     private bool[EventMappingKey] invertMagnitudeMap;
417 
418     public this() {
419         inputEvents = [EnumMembers!InputEvent];
420     }
421 
422     public void initialize() {
423         rawInputEventChannel.connect(&queueEventHandlerEvent);
424     }
425 
426     private void queueEventHandlerEvent(const(Event) event) {
427         if (inputEvents.canFind(event.type)) {
428             eventQueue ~= event;
429         }
430     }
431 
432     public void handleEvents() {
433         foreach (event; eventQueue) {
434             auto eventKey = createMappingKey(event);
435             auto mappedEvents = eventKey in eventMappings;
436             if (mappedEvents) {
437                 double magnitude = event.magnitude;
438                 if (event.type == InputEvent.JOYSTICK_AXIS_MOVEMENT) {
439                     magnitude = calculateAxisDeadzoneMagnitude(event);
440                 }
441 
442                 auto invertMagnitude = eventKey in invertMagnitudeMap;
443                 if (invertMagnitude !is null && *invertMagnitude == true) {
444                     magnitude *= -1;
445                 }
446 
447                 MappedInputCommandData mappedCommandData = null;
448                 auto inputEventData = cast(InputMessageData) event.data;
449                 if (inputEventData) {
450                     mappedCommandData = new MappedInputCommandData();
451                     mappedCommandData.device = inputEventData.device;
452                 }
453 
454                 foreach(mappedEvent; *mappedEvents) {
455                     mappedInputCommandChannel.emit(Command(mappedEvent, magnitude, mappedCommandData));
456                 }
457             }
458         }
459 
460         eventQueue.destroy();
461     }
462 
463     private double calculateAxisDeadzoneMagnitude(const ref Event event) {
464         auto magnitude = event.magnitude;
465         auto data = cast(JoystickAxisEventData) event.data;
466         auto deadzone = data.axis in axisDeadzones;
467         if (deadzone && abs(magnitude) < *deadzone) {
468             return 0;
469         }
470 
471         return magnitude;
472     }
473 
474     private EventMappingKey createMappingKey(const ref Event event) {
475         auto eventKey = EventMappingKey(event.type, 0);
476 
477         switch (event.type) {
478             case InputEvent.JOYSTICK_AXIS_MOVEMENT:
479                 auto data = cast(JoystickAxisEventData) event.data;
480                 eventKey.componentOne = data.axis;
481                 break;
482             case InputEvent.JOYSTICK_BALL_MOVEMENT:
483                 auto data = cast(JoystickBallEventData) event.data;
484                 eventKey.componentOne = data.ball;
485                 eventKey.componentTwo = data.axis;
486                 break;
487             case InputEvent.JOYSTICK_BUTTON:
488                 auto data = cast(JoystickButtonEventData) event.data;
489                 eventKey.componentOne = data.button;
490                 break;
491             case InputEvent.JOYSTICK_HAT:
492                 auto data = cast(JoystickHatEventData) event.data;
493                 eventKey.componentOne = data.hat;
494                 break;
495             case InputEvent.KEYBOARD_KEY:
496                 auto data = cast(KeyboardKeyEventData) event.data;
497                 eventKey.componentOne = data.scanCode;
498                 break;
499             case InputEvent.MOUSE_MOTION:
500                 auto data = cast(MouseMotionEventData) event.data;
501                 eventKey.componentOne = data.axis;
502                 break;
503             case InputEvent.MOUSE_BUTTON:
504                 auto data = cast(MouseButtonEventData) event.data;
505                 eventKey.componentOne = data.button;
506                 break;
507             default:
508                 break;
509         }
510 
511         return eventKey;
512     }
513 
514     public void setEventMapping(EventMappingKey sourceKey, StringId targetCommand, InvertMagnitude invertMagnitude = InvertMagnitude.no) {
515         setEventMapping(sourceKey, [targetCommand], invertMagnitude);
516     }
517 
518     public void setEventMapping(EventMappingKey sourceKey, StringId[] targetCommands, InvertMagnitude invertMagnitude = InvertMagnitude.no) {
519         eventMappings[sourceKey] = targetCommands;
520         invertMagnitudeMap[sourceKey] = invertMagnitude;
521     }
522 
523     public void setJoystickAxisDeadzone(ubyte axis, double deadzone) {
524         axisDeadzones[axis] = deadzone;
525     }
526 }