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         bool isResize = window.isResize;
387         window.isResize = false;
388 
389         return isResize;
390     }
391 
392     uint[2] newSizeWindow()
393     {
394         RECT rect;
395         GetWindowRect((cast(Window) this.window).handle, &rect);
396 
397         return [rect.right, rect.bottom];
398     }
399 
400     bool isQuit()
401     {
402         return window.isClose;
403     }
404 
405     bool isInputText()
406     {
407         return this.msg.message == WM_CHAR;
408     }
409 
410     string inputChar()
411     {
412         import std.utf : toUTF8;
413 
414         wstring text = [];
415         text = [cast(wchar) msg.wParam];
416 
417         string utftext = text.toUTF8;
418 
419         return [utftext[0]];
420     }
421 }
422 
423 version(Posix)///
424 static enum Key
425 {
426     Escape = 9,
427     F1 = 67,
428     F2 = 68,
429     F3 = 69,
430     F4 = 70,
431     F5 = 71,
432     F6 = 72,
433     F7 = 73,
434     F8 = 74,
435     F9 = 75,
436     F10 = 76,
437     F11 = 95,
438     F12 = 96,
439     PrintScrn = 111,
440     ScrollLock = 78,
441     Pause = 110,
442     Backtick = 49,
443     K1 = 10,
444     K2 = 11,
445     K3 = 12,
446     K4 = 13,
447     K5 = 14,
448     K6 = 15,
449     K7 = 16,
450     K8 = 17,
451     K9 = 18,
452     K0 = 19,
453     Minus = 20,
454     Equal = 21,
455     Backspace = 22,
456     Insert = 106,
457     Home = 97,
458     PageUp = 99,
459     NumLock = 77,
460     KPSlash = 112,
461     KPStar = 63,
462     KPMinus = 82,
463     Tab = 23,
464     Q = 24,
465     W = 25,
466     E = 26,
467     R = 27,
468     T = 28,
469     Y = 29,
470     U = 30,
471     I = 31,
472     O = 32,
473     P = 33,
474 
475     SqBrackLeft = 34,
476     SqBrackRight = 35,
477     SquareBracketLeft = 34,
478     SquareBracketRight = 35,
479 
480     Return = 36,
481     Delete = 107,
482     End = 103,
483     PageDown = 105,
484 
485     KP7 = 79,
486     KP8 = 80,
487     KP9 = 81,
488 
489     CapsLock = 66,
490     A = 38,
491     S = 39,
492     D = 40,
493     F = 41,
494     G = 42,
495     H = 43,
496     J = 44,
497     K = 45,
498     L = 46,
499     Semicolons = 47,
500     Apostrophe = 48,
501 
502     KP4 = 83,
503     KP5 = 84,
504     KP6 = 85,
505 
506     ShiftLeft = 50,
507     International = 94,
508 
509     Z = 52,
510     X = 53,
511     C = 54,
512     V = 55,
513     B = 56,
514     N = 57,
515     M = 58,
516     Comma = 59,
517     Point = 60,
518     Slash = 61,
519 
520     ShiftRight = 62,
521 
522     BackSlash = 51,
523     Up = 111,
524 
525     KP1 = 87,
526     KP2 = 88,
527     KP3 = 89,
528 
529     KPEnter = 108,
530     CtrlLeft = 37,
531     SuperLeft = 115,
532     AltLeft = 64,
533     Space = 65,
534     AltRight = 113,
535     LogoRight = 116,
536     Menu = 117,
537     CtrlRight = 109,
538     Left = 113,
539     Down = 116,
540     Right = 114,
541     KP0 = 90,
542     KPPoint = 91
543 }
544 
545 version(Windows)///
546 static enum Key
547 {
548     Escape = 0x1B,
549     F1 = 0x70,
550     F2 = 0x71,
551     F3 = 0x72,
552     F4 = 0x73,
553     F5 = 0x74,
554     F6 = 0x75,
555     F7 = 0x76,
556     F8 = 0x77,
557     F9 = 0x78,
558     F10 = 0x79,
559     F11 = 0x7A,
560     F12 = 0x7B,
561     PrintScrn = 0x2A,
562     ScrollLock = 0x91,
563     Pause = 0x13,
564     Backtick = 0xC0,
565     K1 = 0x31,
566     K2 = 0x32,
567     K3 = 0x33,
568     K4 = 0x34,
569     K5 = 0x35,
570     K6 = 0x36,
571     K7 = 0x37,
572     K8 = 0x38,
573     K9 = 0x39,
574     K0 = 0x30,
575     Minus = 0xBD,
576     Equal = 0xBB,
577     Backspace = 0x08,
578     Insert = 0x2D,
579     Home = 0x24,
580     PageUp = 0x21,
581     NumLock = 0x90,
582     KPSlash = 0x6F,
583     KPStar = 0xBB,
584     KPMinus = 0xBD,
585     Tab = 0x09,
586     Q = 0x51,
587     W = 0x57,
588     E = 0x45,
589     R = 0x52,
590     T = 0x54,
591     Y = 0x59,
592     U = 0x55,
593     I = 0x49,
594     O = 0x4F,
595     P = 0x50,
596 
597     SqBrackLeft = 0xDB,
598     SqBrackRight = 0xDD,
599     SquareBracketLeft = 0x30,
600     SquareBracketRight = 0xBD,
601 
602     Return = 0x0D,
603     Delete = 0x2E,
604     End = 0x23,
605     PageDown = 0x22,
606 
607     KP7 = 0x67,
608     KP8 = 0x68,
609     KP9 = 0x69,
610 
611     CapsLock = 0x14,
612     A = 0x41,
613     S = 0x53,
614     D = 0x44,
615     F = 0x46,
616     G = 0x47,
617     H = 0x48,
618     J = 0x4A,
619     K = 0x4B,
620     L = 0x4C,
621     Semicolons = 0xBA,
622     Apostrophe = 0xBF,
623 
624     KP4 = 0x64,
625     KP5 = 0x65,
626     KP6 = 0x66,
627 
628     ShiftLeft = 0xA0,
629     International = 0xA4,
630 
631     Z = 0x5A,
632     X = 0x58,
633     C = 0x43,
634     V = 0x56,
635     B = 0x42,
636     N = 0x4E,
637     M = 0x4D,
638     Comma = 0xBC,
639     Point = 0xBE,
640     Slash = 0xBF,
641 
642     ShiftRight = 0xA1,
643 
644     BackSlash = 0xE2,
645     Up = 0x26,
646 
647     KP1 = 0x61,
648     KP2 = 0x62,
649     KP3 = 0x63,
650 
651     KPEnter = 0x6A,
652     CtrlLeft = 0xA2,
653     SuperLeft = 0xA4,
654     AltLeft = 0xA4,
655     Space = 0x20,
656     AltRight = 0xA5,
657     SuperRight = 0xA5,
658     Menu = 0,
659     CtrlRight = 0xA3,
660     Left = 0x25,
661     Down = 0x28,
662     Right = 0x27,
663     KP0 = 0x60,
664     KPPoint = 0x6F
665 }