1 /*
2 Copyright (c) 2014-2019 Timur Gafarov, 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.event;
30 
31 import std.stdio;
32 import std.ascii;
33 import std.conv;
34 import dlib.core.memory;
35 import dlib.core.ownership;
36 import dlib.container.array;
37 import dagon.core.bindings;
38 import dagon.core.input;
39 
40 enum EventType
41 {
42     KeyDown,
43     KeyUp,
44     TextInput,
45     MouseMotion,
46     MouseButtonDown,
47     MouseButtonUp,
48     MouseWheel,
49     JoystickButtonDown,
50     JoystickButtonUp,
51     JoystickAxisMotion,
52     Resize,
53     FocusLoss,
54     FocusGain,
55     Quit,
56     FileChange,
57     DropFile,
58     UserEvent
59 }
60 
61 struct Event
62 {
63     EventType type;
64     int key;
65     dchar unicode;
66     int button;
67     int joystickButton;
68     int joystickAxis;
69     float joystickAxisValue;
70     int width;
71     int height;
72     int userCode;
73     int mouseWheelX;
74     int mouseWheelY;
75     string filename;
76 }
77 
78 class EventManager
79 {
80     SDL_Window* window;
81 
82     enum maxNumEvents = 50;
83     Event[maxNumEvents] eventStack;
84     Event[maxNumEvents] userEventStack;
85     uint numEvents;
86     uint numUserEvents;
87 
88     bool running = true;
89 
90     bool[512] keyPressed = false;
91     bool[512] keyUp = false;
92     bool[512] keyDown = false;
93 
94     bool[255] mouseButtonPressed = false;
95     bool[255] mouseButtonUp = false;
96     bool[255] mouseButtonDown = false;
97 
98     bool[255] controllerButtonPressed = false;
99     bool[255] controllerButtonUp = false;
100     bool[255] controllerButtonDown = false;
101 
102     Array!(bool*) toReset; // Used for resetting UP and DOWN events after end of frame
103 
104     int mouseX = 0;
105     int mouseY = 0;
106     int mouseRelX = 0;
107     int mouseRelY = 0;
108     bool enableKeyRepeat = false;
109 
110     double deltaTime = 0.0;
111     uint deltaTimeMs = 0;
112 
113     uint windowWidth;
114     uint windowHeight;
115     bool windowFocused = true;
116 
117     SDL_GameController* controller = null;
118     SDL_Joystick* joystick = null;
119 
120     InputManager inputManager;
121 
122     this(SDL_Window* win, uint winWidth, uint winHeight)
123     {
124         window = win;
125 
126         windowWidth = winWidth;
127         windowHeight = winHeight;
128 
129         if(SDL_NumJoysticks() > 0)
130         {
131             SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
132 
133             if (SDL_IsGameController(0))
134             {
135                 controller = SDL_GameControllerOpen(0);
136 
137                 if (SDL_GameControllerMapping(controller) is null)
138                     writeln("Warning: no mapping found for controller!");
139 
140                 SDL_GameControllerEventState(SDL_ENABLE);
141             }
142             else
143             {
144                 joystick = SDL_JoystickOpen(0);
145             }
146         }
147 
148         toReset = Array!(bool*)();
149 
150         inputManager = New!InputManager(this);
151     }
152 
153     ~this()
154     {
155         toReset.free();
156         Delete(inputManager);
157     }
158 
159     void exit()
160     {
161         running = false;
162     }
163 
164     void addEvent(Event e)
165     {
166         if (numEvents < maxNumEvents)
167         {
168             eventStack[numEvents] = e;
169             numEvents++;
170         }
171         else
172             writeln("Warning: event stack overflow");
173     }
174 
175     void generateFileChangeEvent(string filename)
176     {
177         Event e = Event(EventType.FileChange);
178         e.filename = filename;
179         addUserEvent(e);
180     }
181 
182     void addUserEvent(Event e)
183     {
184         if (numUserEvents < maxNumEvents)
185         {
186             userEventStack[numUserEvents] = e;
187             numUserEvents++;
188         }
189         else
190             writeln("Warning: user event stack overflow");
191     }
192 
193     void generateUserEvent(int code)
194     {
195         Event e = Event(EventType.UserEvent);
196         e.userCode = code;
197         addUserEvent(e);
198     }
199 
200     bool gameControllerAvailable()
201     {
202         return (controller !is null);
203     }
204 
205     bool joystickAvailable()
206     {
207         return (joystick !is null || controller !is null);
208     }
209 
210     float gameControllerAxis(int axis)
211     {
212         return cast(float)(SDL_GameControllerGetAxis(controller, cast(SDL_GameControllerAxis)axis)) / 32768.0f;
213     }
214 
215     float joystickAxis(int axis)
216     {
217         if (joystick)
218         {
219             double a = cast(double)(SDL_JoystickGetAxis(joystick, axis));
220             return a / 32768.0f;
221         }
222         else if (controller)
223         {
224             return cast(float)(SDL_GameControllerGetAxis(controller, cast(SDL_GameControllerAxis)axis)) / 32768.0f;
225         }
226         else return 0.0;
227     }
228 
229     void update()
230     {
231         numEvents = 0;
232 
233         mouseRelX = 0;
234         mouseRelY = 0;
235 
236         for (uint i = 0; i < numUserEvents; i++)
237         {
238             Event e = userEventStack[i];
239             addEvent(e);
240         }
241 
242         numUserEvents = 0;
243 
244         SDL_Event event;
245 
246         while(SDL_PollEvent(&event))
247         {
248             Event e;
249             switch (event.type)
250             {
251                 case SDL_KEYDOWN:
252                     if (event.key.repeat && !enableKeyRepeat)
253                         break;
254 
255                     keyPressed[event.key.keysym.scancode] = true;
256                     keyDown[event.key.keysym.scancode] = true;
257                     keyUp[event.key.keysym.scancode] = false;
258                     toReset.insertBack(&keyDown[event.key.keysym.scancode]);
259 
260                     e = Event(EventType.KeyDown);
261                     e.key = event.key.keysym.scancode;
262                     addEvent(e);
263                     break;
264 
265                 case SDL_KEYUP:
266                     keyPressed[event.key.keysym.scancode] = false;
267                     keyDown[event.key.keysym.scancode] = false;
268                     keyUp[event.key.keysym.scancode] = true;
269                     toReset.insertBack(&keyUp[event.key.keysym.scancode]);
270 
271                     e = Event(EventType.KeyUp);
272                     e.key = event.key.keysym.scancode;
273                     addEvent(e);
274                     break;
275 
276                 case SDL_TEXTINPUT:
277                     e = Event(EventType.TextInput);
278                     char[] input = event.text.text;
279                     if ((input[0] & 0x80) == 0)
280                     {
281                         e.unicode = input[0];
282                     }
283                     else if ((input[0] & 0xE0) == 0xC0)
284                     {
285                         e.unicode = ((input[0] & 0x1F) << 6) | (input[1] & 0x3F);
286                     }
287                     else if ((input[0] & 0xF0) == 0xE0)
288                     {
289                         e.unicode = ((input[0] & 0x0F) << 12) | ((input[1] & 0x3F) << 6) | (input[2] & 0x3F);
290                     }
291                     else if ((input[0] & 0xF8) == 0xF0)
292                     {
293                         e.unicode = (((input[0] & 0x0F) << 18) | ((input[1] & 0x3F) << 12) | ((input[2] & 0x3F) << 6) | (input[3] & 0x3F));
294                     }
295                     addEvent(e);
296                     break;
297 
298                 case SDL_MOUSEMOTION:
299                     mouseX = event.motion.x;
300                     mouseY = event.motion.y;
301                     mouseRelX = event.motion.xrel;
302                     mouseRelY = event.motion.yrel;
303                     break;
304 
305                 case SDL_MOUSEBUTTONDOWN:
306                     mouseButtonPressed[event.button.button] = true;
307                     mouseButtonDown[event.button.button] = true;
308                     toReset.insertBack(&mouseButtonDown[event.button.button]);
309 
310                     e = Event(EventType.MouseButtonDown);
311                     e.button = event.button.button;
312                     addEvent(e);
313                     break;
314 
315                 case SDL_MOUSEBUTTONUP:
316                     mouseButtonPressed[event.button.button] = false;
317                     mouseButtonUp[event.button.button] = true;
318                     toReset.insertBack(&mouseButtonUp[event.button.button]);
319 
320                     e = Event(EventType.MouseButtonUp);
321                     e.button = event.button.button;
322                     addEvent(e);
323                     break;
324 
325                 case SDL_MOUSEWHEEL:
326                     e = Event(EventType.MouseWheel);
327                     e.mouseWheelX = event.wheel.x;
328                     e.mouseWheelY = event.wheel.y;
329                     addEvent(e);
330                     break;
331 
332                 case SDL_JOYBUTTONDOWN:
333                     if(joystick is null) break;
334                     if (event.jbutton.state == SDL_PRESSED)
335                         e = Event(EventType.JoystickButtonDown);
336                     else if (event.jbutton.state == SDL_RELEASED)
337                         e = Event(EventType.JoystickButtonUp);
338                     e.joystickButton = event.jbutton.button;
339                     addEvent(e);
340                     break;
341 
342                 case SDL_JOYBUTTONUP:
343                     // TODO: add state modification
344                     if(joystick is null) break;
345                     if (event.jbutton.state == SDL_PRESSED)
346                         e = Event(EventType.JoystickButtonDown);
347                     else if (event.jbutton.state == SDL_RELEASED)
348                         e = Event(EventType.JoystickButtonUp);
349                     e.joystickButton = event.jbutton.button;
350                     addEvent(e);
351                     break;
352 
353                 case SDL_CONTROLLERBUTTONDOWN:
354                     // TODO: add state modification
355                     controllerButtonPressed[event.cbutton.button] = true;
356                     controllerButtonDown[event.cbutton.button] = true;
357                     toReset.insertBack(&controllerButtonDown[event.cbutton.button]);
358 
359                     e = Event(EventType.JoystickButtonDown);
360                     e.joystickButton = event.cbutton.button;
361                     addEvent(e);
362                     break;
363 
364                 case SDL_CONTROLLERBUTTONUP:
365                     // TODO: add state modification
366                     controllerButtonPressed[event.cbutton.button] = false;
367                     controllerButtonUp[event.cbutton.button] = true;
368                     toReset.insertBack(&controllerButtonUp[event.cbutton.button]);
369 
370                     e = Event(EventType.JoystickButtonUp);
371                     e.joystickButton = event.cbutton.button;
372                     addEvent(e);
373                     break;
374 
375                 case SDL_CONTROLLERAXISMOTION:
376                     // TODO: add state modification
377                     e = Event(EventType.JoystickAxisMotion);
378                     e.joystickAxis = event.caxis.axis;
379                     e.joystickAxisValue = cast(float)event.caxis.value / 32768.0f;
380 
381                     if (controller)
382                     {
383                         if (e.joystickAxis == 0)
384                             e.joystickAxisValue = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
385                         if (e.joystickAxis == 1)
386                             e.joystickAxisValue = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
387 
388                         e.joystickAxisValue = e.joystickAxisValue / 32768.0f;
389                     }
390 
391                     addEvent(e);
392                     break;
393 
394                 case SDL_WINDOWEVENT:
395                     if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
396                     {
397                         windowWidth = event.window.data1;
398                         windowHeight = event.window.data2;
399                         e = Event(EventType.Resize);
400                         e.width = windowWidth;
401                         e.height = windowHeight;
402                         addEvent(e);
403                     }
404                     break;
405 
406                 case SDL_DROPFILE:
407                     e = Event(EventType.DropFile);
408                     e.filename = to!string(event.drop.file);
409                     addEvent(e);
410                     break;
411 
412                 case SDL_QUIT:
413                     exit();
414                     e = Event(EventType.Quit);
415                     addEvent(e);
416                     break;
417 
418                 default:
419                     break;
420             }
421         }
422     }
423 
424     protected int lastTime = 0;
425     void updateTimer()
426     {
427         int currentTime = SDL_GetTicks();
428         auto elapsedTime = currentTime - lastTime;
429         lastTime = currentTime;
430         deltaTimeMs = elapsedTime;
431         deltaTime = cast(double)(elapsedTime) * 0.001;
432     }
433 
434     void setMouse(int x, int y)
435     {
436         SDL_WarpMouseInWindow(window, x, y);
437         mouseX = x;
438         mouseY = y;
439     }
440 
441     void setMouseToCenter()
442     {
443         float x = (cast(float)windowWidth) / 2;
444         float y = (cast(float)windowHeight) / 2;
445         setMouse(cast(int)x, cast(int)y);
446     }
447 
448     void showCursor(bool mode)
449     {
450         SDL_ShowCursor(mode);
451     }
452 
453     float aspectRatio()
454     {
455         return cast(float)windowWidth / cast(float)windowHeight;
456     }
457 
458     void resetUpDown()
459     {
460         // reset all UP and DOWN events
461         foreach(key; toReset)
462         {
463             *key = false;
464         }
465         toReset.removeBack(cast(uint)toReset.length);
466     }
467 }
468 
469 abstract class EventListener: Owner
470 {
471     EventManager eventManager;
472     InputManager inputManager;
473     bool enabled = true;
474 
475     this(EventManager emngr, Owner owner)
476     {
477         super(owner);
478         eventManager = emngr;
479         if(emngr !is null)
480             inputManager = emngr.inputManager;
481     }
482 
483     protected void generateUserEvent(int code)
484     {
485         eventManager.generateUserEvent(code);
486     }
487 
488     void processEvents()
489     {
490         if (!enabled)
491             return;
492 
493         for (uint i = 0; i < eventManager.numEvents; i++)
494         {
495             Event* e = &eventManager.eventStack[i];
496             processEvent(e);
497         }
498     }
499 
500     void processEvent(Event* e)
501     {
502         switch(e.type)
503         {
504             case EventType.KeyDown:
505                 onKeyDown(e.key);
506                 break;
507             case EventType.KeyUp:
508                 onKeyUp(e.key);
509                 break;
510             case EventType.TextInput:
511                 onTextInput(e.unicode);
512                 break;
513             case EventType.MouseButtonDown:
514                 onMouseButtonDown(e.button);
515                 break;
516             case EventType.MouseButtonUp:
517                 onMouseButtonUp(e.button);
518                 break;
519             case EventType.MouseWheel:
520                 onMouseWheel(e.mouseWheelX, e.mouseWheelY);
521                 break;
522             case EventType.JoystickButtonDown:
523                 onJoystickButtonDown(e.joystickButton);
524                 break;
525             case EventType.JoystickButtonUp:
526                 onJoystickButtonUp(e.joystickButton);
527                 break;
528             case EventType.JoystickAxisMotion:
529                 onJoystickAxisMotion(e.joystickAxis, e.joystickAxisValue);
530                 break;
531             case EventType.Resize:
532                 onResize(e.width, e.height);
533                 break;
534             case EventType.FocusLoss:
535                 onFocusLoss();
536                 break;
537             case EventType.FocusGain:
538                 onFocusGain();
539                 break;
540             case EventType.Quit:
541                 onQuit();
542                 break;
543             case EventType.FileChange:
544                 onFileChange(e.filename);
545                 break;
546             case EventType.DropFile:
547                 onDropFile(e.filename);
548                 break;
549             case EventType.UserEvent:
550                 onUserEvent(e.userCode);
551                 break;
552             default:
553                 break;
554         }
555     }
556 
557     void onKeyDown(int key) {}
558     void onKeyUp(int key) {}
559     void onTextInput(dchar code) {}
560     void onMouseButtonDown(int button) {}
561     void onMouseButtonUp(int button) {}
562     void onMouseWheel(int x, int y) {}
563     void onJoystickButtonDown(int button) {}
564     void onJoystickButtonUp(int button) {}
565     void onJoystickAxisMotion(int axis, float value) {}
566     void onResize(int width, int height) {}
567     void onFocusLoss() {}
568     void onFocusGain() {}
569     void onQuit() {}
570     void onFileChange(string filename) {}
571     void onDropFile(string filename) {}
572     void onUserEvent(int code) {}
573 }