1 /++
2 A module for listening to incoming events from the window manager for their 
3 subsequent processing.
4 
5 Such a layer does not admit events directly to the data, but whether it can show
6 what is happening at the moment, which can serve as a cross-plotter tracking 
7 of events.
8 
9 Using the IEventHandler.nextEvent function, you can scroll through the queue of 
10 events that can be processed and at each queue, the programmer needs to track 
11 the events he needs by the functions of the same interface:
12 ---
13 while (event.nextEvent()) {
14     if (event.keyDown == Key.Space) foo();
15 }
16 ---
17 As you can see, we loop through each event and read what happened.
18 
19 Macros:
20     LREF = <a href="#$1">$1</a>
21     HREF = <a href="$1">$2</a>
22 
23 Authors: $(HREF https://github.com/TodNaz,TodNaz)
24 Copyright: Copyright (c) 2020 - 2021, TodNaz.
25 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT)
26 +/
27 module tida.event;
28 
29 enum DeprecatedMethodSize = 
30 "This function is useless. Use the parameters `IWindow.width/IWindow.height`.";
31 
32 /// Mouse keys.
33 enum MouseButton
34 {
35     unknown = 0, /// Unknown mouse button
36     left = 1, /// Left mouse button
37     right = 3, /// Right mouse button
38     middle = 2 /// Middle mouse button
39 }
40 
41 /++
42 Interface for cross-platform listening for events from the window manager.
43 +/
44 interface IEventHandler
45 {
46 @safe:
47     /++
48     Moves to the next event. If there are no more events, it returns false, 
49     otherwise, it throws true and the programmer can safely check which event 
50     s in the current queue.
51     +/
52     bool nextEvent();
53 
54     /++
55     Checking if any key is pressed in the current event.
56     +/
57     bool isKeyDown();
58 
59     /++
60     Checking if any key is released in the current event.
61     +/
62     bool isKeyUp();
63 
64     /++
65     Will return the key that was pressed. Returns zero if no key is pressed 
66     in the current event.
67     +/
68     @property int key();
69 
70     /++
71     Returns the key at the moment the key was pressed, 
72     otherwise it returns zero.
73     +/
74     @property final int keyDown()
75     {
76         return isKeyDown ? key : 0;
77     }
78 
79     /++
80     Returns the key at the moment the key was released, 
81     otherwise it returns zero.
82     +/
83     @property final int keyUp()
84     {
85         return isKeyUp ? key : 0;
86     }
87 
88     /++
89     Check if the mouse button is pressed in the current event.
90     +/
91     bool isMouseDown();
92 
93     /++
94     Check if the mouse button is released in the current event.
95     +/
96     bool isMouseUp();
97 
98     /++
99     Returns the currently pressed or released mouse button.
100     +/
101     @property MouseButton mouseButton();
102 
103     /++
104     Returns the mouse button at the moment the key was pressed; 
105     otherwise, it returns zero.
106     +/
107     @property final MouseButton mouseDownButton()
108     {
109         return isMouseDown ? mouseButton : MouseButton.unknown;
110     }
111 
112     /++
113     Returns the mouse button at the moment the key was released; 
114     otherwise, it returns zero.
115     +/
116     @property final MouseButton mouseUpButton()
117     {
118         return isMouseUp ? mouseButton : MouseButton.unknown;
119     }
120 
121     /++
122     Returns the position of the mouse in the window.
123     +/
124     @property int[2] mousePosition();
125 
126     /++
127     Returns in which direction the user is turning the mouse wheel. 
128     1 - down, -1 - up, 0 - does not twist. 
129 
130     This iteration is convenient for multiplying with some real movement coefficient.
131     +/
132     @property int mouseWheel();
133 
134     /++
135     Indicates whether the window has been resized in this event.
136     +/
137     bool isResize();
138 
139     /++
140     Returns the new size of the window 
141     
142     deprecated: 
143     although this will already be available directly in the 
144     window structure itself.
145     +/
146     deprecated(DeprecatedMethodSize) 
147     uint[2] newSizeWindow();
148 
149     /++
150     Indicates whether the user is attempting to exit the program.
151     +/
152     bool isQuit();
153 
154     /++
155     Indicates whether the user has entered text information.
156     +/
157     bool isInputText();
158 
159     /++
160     User entered data.
161     +/
162     @property string inputChar();
163 
164 @trusted:
165     final int opApply(scope int delegate(ref int) dg)
166     {
167         int count = 0;
168 
169         while (this.nextEvent()) 
170         {
171             dg(++count);
172         }
173 
174         return 0;
175     }
176 }
177 
178 version(Posix)
179 class EventHandler : IEventHandler
180 {
181     import x11.X, x11.Xlib, x11.Xutil;
182     import tida.window, tida.runtime;
183 
184 private:
185     tida.window.Window window;
186     Atom destroyWindowEvent;
187     _XIC* ic;
188 
189 public:
190     XEvent event;
191 
192 @trusted:
193     this(tida.window.Window window)
194     {   
195         this.window = window;
196 
197         this.destroyWindowEvent = XInternAtom(runtime.display, "WM_DELETE_WINDOW", 0);
198 
199         ic = XCreateIC( XOpenIM(runtime.display, null, null, null), 
200                         XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 
201                         XNClientWindow, this.window.handle, null);
202         XSetICFocus(ic);
203         XSetLocaleModifiers("@im=none");
204     }
205 
206 override:
207     bool nextEvent()
208     {
209         auto pen = XPending(runtime.display);
210         
211         if (pen != 0) 
212         {
213             XNextEvent(runtime.display, &this.event);
214         }
215 
216         return pen != 0;
217     }
218 
219     bool isKeyDown()
220     {
221         return this.event.type == KeyPress;
222     }
223 
224     bool isKeyUp()
225     {
226         return this.event.type == KeyRelease;
227     }
228 
229     @property int key()
230     {
231         return this.event.xkey.keycode;
232     }
233 
234     bool isMouseDown()
235     {
236         return this.event.type == ButtonPress;
237     }
238 
239     bool isMouseUp()
240     {
241         return this.event.type == ButtonRelease;
242     }
243 
244     @property MouseButton mouseButton()
245     {
246         return cast(MouseButton) this.event.xbutton.button;
247     }
248 
249     @property int[2] mousePosition() @trusted
250     {
251         return [this.event.xmotion.x, this.event.xmotion.y];
252     }
253 
254     @property int mouseWheel()
255     {
256         return this.isMouseDown ? 
257             (this.mouseButton == 4 ? -1 : (this.mouseButton == 5 ? 1 : 0)) : 0;
258     }
259 
260     bool isResize()
261     {
262         return this.event.type == ConfigureNotify;
263     }
264 
265     uint[2] newSizeWindow()
266     {
267         XWindowAttributes attr;
268         XGetWindowAttributes(   runtime.display, 
269                                 (cast(tida.window.Window) this.window).handle, &attr);
270 
271         return [attr.width, attr.height];
272     }
273 
274     bool isQuit()
275     {
276         return this.event.xclient.data.l[0] == this.destroyWindowEvent;
277     }
278 
279     bool isInputText() 
280     {
281         return this.isKeyDown;
282     } 
283 
284     @property string inputChar()
285     {
286         int count;
287         string buf = new string(20);
288         KeySym ks;
289         Status status = 0;
290 
291         count = Xutf8LookupString(  this.ic, cast(XKeyPressedEvent*) &this.event.xkey, 
292                                     cast(char*) buf.ptr, 20, &ks, &status);
293 
294         return buf[0 .. count];
295     }
296 }
297 
298 version(Windows)
299 class EventHandler : IEventHandler
300 {
301     import tida.window, tida.runtime;
302     import core.sys.windows.windows;
303 
304 private:
305     tida.window.Window window;
306 
307 public:
308     MSG msg;
309 
310 @safe:
311     this(tida.window.Window window)
312     {
313         this.window = window;
314     }
315 
316 @trusted:
317     bool nextEvent()
318     {
319         TranslateMessage(&this.msg); 
320         DispatchMessage(&this.msg);
321 
322         return PeekMessage(&this.msg, this.window.handle, 0, 0, PM_REMOVE) != 0;
323     }
324 
325     bool isKeyDown()
326     {
327         return this.msg.message == WM_KEYDOWN;
328     }
329 
330     bool isKeyUp()
331     {
332         return this.msg.message == WM_KEYUP;
333     }
334 
335     @property int key()
336     {
337         return cast(int) this.msg.wParam;
338     }
339 
340     bool isMouseDown()
341     {
342         return this.msg.message == WM_LBUTTONDOWN ||
343                this.msg.message == WM_RBUTTONDOWN ||
344                this.msg.message == WM_MBUTTONDOWN;
345     }
346 
347     bool isMouseUp()
348     {
349         return this.msg.message == WM_LBUTTONUP ||
350                this.msg.message == WM_RBUTTONUP ||
351                this.msg.message == WM_MBUTTONUP;
352     }
353 
354     @property MouseButton mouseButton()
355     {
356         if (this.msg.message == WM_LBUTTONUP || this.msg.message == WM_LBUTTONDOWN)
357             return MouseButton.left;
358 
359         if (this.msg.message == WM_RBUTTONUP || this.msg.message == WM_RBUTTONDOWN)
360             return MouseButton.right;
361 
362         if (this.msg.message == WM_MBUTTONUP || this.msg.message == WM_MBUTTONDOWN)
363             return MouseButton.middle;
364 
365         return MouseButton.unknown;
366     }
367 
368     @property int[2] mousePosition()
369     {
370         POINT p;
371         GetCursorPos(&p);
372         ScreenToClient((cast(Window) this.window).handle, &p);
373 
374         return [p.x, p.y];
375     }
376 
377     @property int mouseWheel()
378     {
379         if (this.msg.message != WM_MOUSEWHEEL) return 0;
380 
381         return (cast(int) this.msg.wParam) > 0 ? -1 : 1;
382     }
383 
384     bool isResize()
385     {
386         return this.msg.message == WM_SIZE;
387     }
388 
389     uint[2] newSizeWindow()
390     {
391         RECT rect;
392         GetWindowRect((cast(Window) this.window).handle, &rect);
393 
394         return [rect.right, rect.bottom];
395     }
396 
397     bool isQuit()
398     {
399         return window.isClose;
400     }
401 
402     bool isInputText()
403     {
404         return this.msg.message == WM_CHAR;
405     }
406 
407     string inputChar()
408     {
409         import std.utf : toUTF8;
410 
411         wstring text = [];
412         text = [cast(wchar) msg.wParam];
413 
414         string utftext = text.toUTF8;
415 
416         return [utftext[0]];
417     }
418 }
419 
420 version(Posix)///
421 static enum Key
422 {
423     Escape = 9,
424     F1 = 67,
425     F2 = 68,
426     F3 = 69,
427     F4 = 70,
428     F5 = 71,
429     F6 = 72,
430     F7 = 73,
431     F8 = 74,
432     F9 = 75,
433     F10 = 76,
434     F11 = 95,
435     F12 = 96,
436     PrintScrn = 111,
437     ScrollLock = 78,
438     Pause = 110,
439     Backtick = 49,
440     K1 = 10,
441     K2 = 11,
442     K3 = 12,
443     K4 = 13,
444     K5 = 14,
445     K6 = 15,
446     K7 = 16,
447     K8 = 17,
448     K9 = 18,
449     K0 = 19,
450     Minus = 20,
451     Equal = 21,
452     Backspace = 22,
453     Insert = 106,
454     Home = 97,
455     PageUp = 99,
456     NumLock = 77,
457     KPSlash = 112,
458     KPStar = 63,
459     KPMinus = 82,
460     Tab = 23,
461     Q = 24,
462     W = 25,
463     E = 26,
464     R = 27,
465     T = 28,
466     Y = 29,
467     U = 30,
468     I = 31,
469     O = 32,
470     P = 33,
471 
472     SqBrackLeft = 34,
473     SqBrackRight = 35,
474     SquareBracketLeft = 34,
475     SquareBracketRight = 35,
476 
477     Return = 36,
478     Delete = 107,
479     End = 103,
480     PageDown = 105,
481 
482     KP7 = 79,
483     KP8 = 80,
484     KP9 = 81,
485 
486     CapsLock = 66,
487     A = 38,
488     S = 39,
489     D = 40,
490     F = 41,
491     G = 42,
492     H = 43,
493     J = 44,
494     K = 45,
495     L = 46,
496     Semicolons = 47,
497     Apostrophe = 48,
498 
499     KP4 = 83,
500     KP5 = 84,
501     KP6 = 85,
502 
503     ShiftLeft = 50,
504     International = 94,
505 
506     Z = 52,
507     X = 53,
508     C = 54,
509     V = 55,
510     B = 56,
511     N = 57,
512     M = 58,
513     Comma = 59,
514     Point = 60,
515     Slash = 61,
516 
517     ShiftRight = 62,
518 
519     BackSlash = 51,
520     Up = 111,
521 
522     KP1 = 87,
523     KP2 = 88,
524     KP3 = 89,
525 
526     KPEnter = 108,
527     CtrlLeft = 37,
528     SuperLeft = 115,
529     AltLeft = 64,
530     Space = 65,
531     AltRight = 113,
532     LogoRight = 116,
533     Menu = 117,
534     CtrlRight = 109,
535     Left = 113,
536     Down = 116,
537     Right = 114,
538     KP0 = 90,
539     KPPoint = 91
540 }
541 
542 version(Windows)///
543 static enum Key
544 {
545     Escape = 0x1B,
546     F1 = 0x70,
547     F2 = 0x71,
548     F3 = 0x72,
549     F4 = 0x73,
550     F5 = 0x74,
551     F6 = 0x75,
552     F7 = 0x76,
553     F8 = 0x77,
554     F9 = 0x78,
555     F10 = 0x79,
556     F11 = 0x7A,
557     F12 = 0x7B,
558     PrintScrn = 0x2A,
559     ScrollLock = 0x91,
560     Pause = 0x13,
561     Backtick = 0xC0,
562     K1 = 0x31,
563     K2 = 0x32,
564     K3 = 0x33,
565     K4 = 0x34,
566     K5 = 0x35,
567     K6 = 0x36,
568     K7 = 0x37,
569     K8 = 0x38,
570     K9 = 0x39,
571     K0 = 0x30,
572     Minus = 0xBD,
573     Equal = 0xBB,
574     Backspace = 0x08,
575     Insert = 0x2D,
576     Home = 0x24,
577     PageUp = 0x21,
578     NumLock = 0x90,
579     KPSlash = 0x6F,
580     KPStar = 0xBB,
581     KPMinus = 0xBD,
582     Tab = 0x09,
583     Q = 0x51,
584     W = 0x57,
585     E = 0x45,
586     R = 0x52,
587     T = 0x54,
588     Y = 0x59,
589     U = 0x55,
590     I = 0x49,
591     O = 0x4F,
592     P = 0x50,
593 
594     SqBrackLeft = 0xDB,
595     SqBrackRight = 0xDD,
596     SquareBracketLeft = 0x30,
597     SquareBracketRight = 0xBD,
598 
599     Return = 0x0D,
600     Delete = 0x2E,
601     End = 0x23,
602     PageDown = 0x22,
603 
604     KP7 = 0x67,
605     KP8 = 0x68,
606     KP9 = 0x69,
607 
608     CapsLock = 0x14,
609     A = 0x41,
610     S = 0x53,
611     D = 0x44,
612     F = 0x46,
613     G = 0x47,
614     H = 0x48,
615     J = 0x4A,
616     K = 0x4B,
617     L = 0x4C,
618     Semicolons = 0xBA,
619     Apostrophe = 0xBF,
620 
621     KP4 = 0x64,
622     KP5 = 0x65,
623     KP6 = 0x66,
624 
625     ShiftLeft = 0xA0,
626     International = 0xA4,
627 
628     Z = 0x5A,
629     X = 0x58,
630     C = 0x43,
631     V = 0x56,
632     B = 0x42,
633     N = 0x4E,
634     M = 0x4D,
635     Comma = 0xBC,
636     Point = 0xBE,
637     Slash = 0xBF,
638 
639     ShiftRight = 0xA1,
640 
641     BackSlash = 0xE2,
642     Up = 0x26,
643 
644     KP1 = 0x61,
645     KP2 = 0x62,
646     KP3 = 0x63,
647 
648     KPEnter = 0x6A,
649     CtrlLeft = 0xA2,
650     SuperLeft = 0xA4,
651     AltLeft = 0xA4,
652     Space = 0x20,
653     AltRight = 0xA5,
654     SuperRight = 0xA5,
655     Menu = 0,
656     CtrlRight = 0xA3,
657     Left = 0x25,
658     Down = 0x28,
659     Right = 0x27,
660     KP0 = 0x60,
661     KPPoint = 0x6F
662 }