1 module sily.terminal.input;
2 
3 import std.conv: to;
4 import std.algorithm.searching: canFind;
5 
6 import sily.queue;
7 import sily.terminal;
8 import sily.vector: uvec2;
9 
10 private Queue!Input inputQueue = Queue!Input();
11 
12 /// Add normal discard?
13 /// Returns last element and removes it from queue if `remove` is true
14 Input peekEvent(bool remove = true)() {
15     if (inputQueue.empty) return InputEvent();
16     if (remove) {
17         return inputQueue.pop();
18     } else {
19         return inputQueue.front;
20     }
21 }
22 
23 /// Returns true if there's still input in buffer
24 bool queueEmpty() {
25     return inputQueue.empty;
26 }
27 
28 /// Clears input queue
29 void discardAll() {
30     inputQueue.clear();
31 }
32 
33 bool queueHas(Input key) {
34     Input[] events = inputQueue.toArray();
35     foreach (e; events) {
36         if (e.isKey(key)) {
37             return true;
38         }
39     }
40     return false;
41 }
42 
43 /// Buffers input from getch
44 /// Returns: true if operation was sucessfull
45 bool pollEvent() {
46     if (!kbhit()) return false;
47     
48     // 0: ctrl + 2
49     // 1 - 26: ctrl + [a-z]
50     // 8: ^H or ctrl + backspace
51     // 9: ^I or tab
52     // 13: ^M or enter
53     // 27: esc, ctrl + [ 
54     // 27+27: alt + esc
55     // 28: ctrl + \
56     // 29: ctrl + ]
57     // 30: ctrl + shift + 6 (^^), ctrl + shift + ` (^~)
58     // 31: ctrl + shift + - (^_)
59     // 32 - 126: look ascii table
60     // 127: del (backspace), ctrl + shift + /
61     // 27 [65-90]: alt + shift + [a-z]
62     // 27 [97-122]: alt + [a-z]
63 
64     // 27 91 [65-68]: U,D,R,L
65     // 27 79 [80-83]: F[1-4]
66     // 27 91 49 [53, 55-57] 126: F[5-8]
67     // 27 91 50 [48,49,51,52] 126: F[9-12]
68     // 27 91 49 50 [65-68]: Shift + U,D,R,L
69     // 27 91 49 53 [65-68]: ^ + U,D,R,L
70     // 27 91 49 54 [65-68]: ^ + shift + U,D,R,L
71     // 27 91 51 126: del
72     // 27 91 [70, 72]: end, home
73     // 27 91 49 59 51 80: meta (win)
74     // TODO: figure out ctrl+shift
75     // TODO: figure out meta
76     // TODO: figure out scroll and middle click?
77     // TODO: figure out shift combos
78     // TODO: mouse?
79     // TODO: ctrl + numbers
80     
81     // \e[A: A-D UDRL
82 
83     // \e?: ?=97-122 alt + a-z
84     // \e?: ?=1-26 ctrl + alt + a-z
85     // \e?: ?=65-90 shift + alt + a-z
86     // \e[X;6u: X=97-122 ctrl + shift + a-z
87     // \e[X;8u: X=97-122 ctrl + shift + alt + a-z
88     // \e[X;9u: same, win + a-z
89     // \e[X;10u: same, win + shift + a-z
90     // \e[X;11u: same, win + alt + a-z
91     // \e[X;12u: same, win + alt + shift + a-z
92     // \e[X;13u: same, win + ctrl + a-z
93     // \e[X;14u: same, win + ctrl + shift + a-z
94     // \e[X;15u: same, win + ctrl + alt + a-z
95     // \e[X;16u: same, win + ctrl + shift + alt + a-z
96 
97     // \eOP: f1
98     // \eOQ: f2
99     // \eOR: f3
100     // \eOS: f4
101     // \e[1;XY: X=look combos, Y=PQRS
102 
103     // \e[X~: x=[15,17-21,23,24]: f5-12
104 
105     // \e[1;XA: A-D, X = 2!s, 3!a, 4!as, 5!c, 6!cs, 7!ca, 8!csa, for meta look normal keys
106     string seq = "";
107     while (kbhit()) {
108         seq ~= cast(char) getch();
109     }
110     
111     if (seq.length == 1) {
112         int key = cast(int) seq[0];
113 
114         // Control letters / Control sequences
115         if (key >= 1 && key <= 26) inputQueue.push(ikey(key + 96, Mod.c));
116         // Shift letters
117         if (key >= 65 && key <= 90) inputQueue.push(ikey(key + 32, Mod.s));
118         // Normal letters
119         if (key >= 97 && key <= 122) inputQueue.push(ikey(key));
120         // Duplicates (same as some control seq)
121         if (key == 8) inputQueue.push(ikey(Key.backspace, Mod.c));
122         if (key == 9) inputQueue.push(ikey(Key.tab));
123         if (key == 13) inputQueue.push(ikey(Key.enter));
124         if (key == 27) {
125             inputQueue.push(ikey(Key.escape));
126             inputQueue.push(ikey(Key.leftBracket, Mod.c));
127         }
128         // Space
129         if (key == 32) inputQueue.push(ikey(Key.space));
130         // Numbers
131         if (key >= 48 && key <= 57) inputQueue.push(ikey(key));
132         // TODO: Symbols
133         return true;
134     }
135 
136     advaceMouseAll();
137     
138     // sequence
139     if (seq.length >= 2 && seq[0] == 27) {
140         if (seq[1] == '[') {
141             import std.stdio;
142             // normal sequence
143 
144             // \e[96;x;yM - mouse wheel up
145             // \e[97;x;yM - mouse wheel down
146             // \e[32;x;yM - lmb
147             // \e[33;x;yM - mmb
148             // \e[34;x;yM - rmb
149             // \e[160;x;yM - mbb
150             // \e[161;x;yM - mfb
151             // 012345678
152             // 123456789
153             // e[96;...M
154             // e[161;...M
155 
156             if (seq.length > 5 && seq[$-1] == 'M') {
157                 string sq = seq[2..5];
158                 if (sq == "96;") {
159                     advanceMouse(Button.wheelUp, true);
160                     inputQueue.push(ikey(Key.mouseWheelUp, Mod.n, mouseButtonState(Button.wheelUp)));
161                 }
162                 if (sq == "97;") {
163                     advanceMouse(Button.wheelDown, true);
164                     inputQueue.push(ikey(Key.mouseWheelDown, Mod.n, mouseButtonState(Button.wheelDown)));
165                 }
166                 if (sq == "32;") {
167                     advanceMouse(Button.left, true);
168                     inputQueue.push(ikey(Key.mouseLeft, Mod.n, mouseButtonState(Button.left)));
169                 }
170                 if (sq == "33;") {
171                     advanceMouse(Button.middle, true);
172                     inputQueue.push(ikey(Key.mouseMiddle, Mod.n, mouseButtonState(Button.middle)));
173                 }
174                 if (sq == "34;") {
175                     advanceMouse(Button.right, true);
176                     inputQueue.push(ikey(Key.mouseRight, Mod.n, mouseButtonState(Button.right)));
177                 }
178                 if (["96;", "97;", "32;", "33;", "34;"].canFind(sq)) {
179                     int x = 0;
180                     int y = 0;
181                     bool isX = true;
182                     string tmp = "";
183                     foreach (ch; seq[5..$]) {
184                         if (ch == ';') {
185                             isX = false;
186                             x = tmp.to!int - 1;
187                             tmp = "";
188                             continue;
189                         }
190                         if (ch == 'M') break;
191                         tmp ~= ch;
192                     }
193                     y = tmp.to!int - 1;
194                     lastMousePos = uvec2(x, y);
195                 }
196             }
197             
198         } else
199         if (seq.length == 3 && seq[1] == 'O') {
200             // f1-4
201             switch (seq[2]) {
202                 case 'P': inputQueue.push(ikey(Key.f1)); break;
203                 case 'Q': inputQueue.push(ikey(Key.f2)); break;
204                 case 'R': inputQueue.push(ikey(Key.f3)); break;
205                 case 'S': inputQueue.push(ikey(Key.f4)); break;
206                 default: break;
207             }
208         } else {
209             // letters
210             int key = cast(int) seq[1];
211             // Control letters / Control sequences
212             if (key >= 1 && key <= 26) inputQueue.push(ikey(key + 96, Mod.ca));
213             // Shift letters
214             if (key >= 65 && key <= 90) inputQueue.push(ikey(key + 32, Mod.sa));
215             // Normal letters
216             if (key >= 97 && key <= 122) inputQueue.push(ikey(key, Mod.a));
217         }
218         return true;
219     }
220 
221 
222     return false;
223 }
224 
225 /// Returns new InputKey with set mod keys
226 Input ikey(uint key, uint mod = Mod.n, ButtonState state = ButtonState.press) {
227     Key enumKey = to!Key(key);
228     return ikey(enumKey, mod, state);
229 }
230 /// Ditto
231 Input ikey(Key key, uint mod = Mod.n, ButtonState state = ButtonState.press) {
232     // return Input(key, mod.hasFlag(Mod.c), mod.hasFlag(Mod.s), mod.hasFlag(Mod.a), mod.hasFlag(Mod.m));
233     return Input(key, mod, state);
234 } 
235 
236 private bool hasFlag(uint flags, uint flag) {
237     return (flags & flag) == flag;
238 }
239 
240 /// Input mod keys (ctrl, shift, alt)
241 alias Mod = InputMod;
242 /// Ditto
243 enum InputMod: uint {
244     /// No key
245     none  = 0b0000,
246     /// Ditto
247     n     = 0b0000,
248     /// Control
249     ctrl  = 0b0001,
250     /// Ditto
251     c     = 0b0001,
252     /// Shift
253     shift = 0b0010,
254     /// Ditto
255     s     = 0b0010,
256     /// Alt (command)
257     alt   = 0b0100,
258     /// Ditto
259     a     = 0b0100,
260     /// Meta (win/option)
261     meta  = 0b1000,
262     /// Ditto
263     m     = 0b1000,
264     /// Control + Shift
265     cs    = 0b0011,
266     /// Control + Alt (command)
267     ca    = 0b0101,
268     /// Shift + Alt (command)
269     sa    = 0b0110,
270     /// Control + Shift + Alt (command)
271     csa   = 0b0111,
272     /// Meta + Control
273     mc    = 0b1001,
274     /// Meta + Alt
275     ma    = 0b1100,
276     /// Meta + Shift
277     ms    = 0b1010,
278     /// Meta + Control + Shift
279     mcs   = 0b1011,
280     /// Meta + Control + Alt
281     mca   = 0b1101,
282     /// Meta + Shift + Alt
283     msa   = 0b1110,
284     /// Meta + Control + Shift + Alt
285     mcsa  = 0b1111,
286     /// All modifiers
287     all   = 0b1111
288 }
289 
290 /// Input event (keypress)
291 alias Input = InputEvent;
292 /// Ditto
293 struct InputEvent {
294     /// Pressed key
295     public Key key = Key.none;
296     // /// Control pressed
297     // public bool ctrl = false;
298     // /// Shift pressed
299     // public bool shift = false;
300     // /// Alt (command) pressed
301     // public bool alt = false;
302     // /// Meta (win/option) pressed
303     // public bool meta = false;
304     public uint mod = Mod.n;
305 
306     public ButtonState state = ButtonState.press;
307 
308     bool opEquals()(in Input b) const {
309         // return key == b.key && ctrl == b.ctrl && shift == b.shift && alt == b.alt && meta == b.meta;
310         return key == b.key && mod == b.mod;
311     }
312 
313     /// Returns hash 
314     size_t toHash() const @safe nothrow {
315         return typeid(this).getHash(&this);
316     }
317     
318     /// Checks if two inputs are same
319     bool isKey(Input b) {
320         return this == b;
321     }
322     
323     /// Checks if key has modifier m
324     bool hasMod(Mod m) {
325         return mod.hasFlag(m);
326     }
327 }
328 
329 // 0 - released, 1 - pressing, 2 - pressed, 3 - releasing
330 // wheels always on 0:1
331 private ubyte[Button] mouseState;
332 
333 static this() {
334     mouseState = [
335         Button.left: 0,
336         Button.middle: 0,
337         Button.right: 0,
338         Button.wheelUp: 0,
339         Button.wheelDown: 0,
340         Button.forward: 0,
341         Button.backward: 0
342     ];
343 }
344 
345 private void advanceMouse(Button b, bool press = false) {
346     if (mouseState[b] == 0 && press) {
347         mouseState[b] = 1;
348     } else
349     if (mouseState[b] == 1) {
350         mouseState[b] = 2;
351     } else
352     if (mouseState[b] == 2 && press) {
353         mouseState[b] = 3;
354     } else
355     if (mouseState[b] == 3) {
356         mouseState[b] = 0;
357     }
358 
359     if (b == Button.wheelUp || b == Button.wheelDown) {
360         if (mouseState[b] > 1) mouseState[b] = 0;
361     }
362 }
363 
364 ButtonState mouseButtonState(Button b) {
365     return cast(ButtonState) mouseState[b];
366 }
367 
368 private void advaceMouseAll() {
369     foreach (k; mouseState.keys) {
370         advanceMouse(k);
371     }
372 }
373 
374 private uvec2 lastMousePos = uvec2(1);
375 
376 uvec2 mousePosition() {
377     return lastMousePos;
378 }
379 
380 /// Mouse buttons
381 alias Button = MouseButton;
382 /// Ditto
383 enum MouseButton {
384     left, middle, right, wheelUp, wheelDown, forward, backward
385 }
386 
387 /// Mouse button state
388 alias ButtonState = MouseButtonState;
389 /// Ditto
390 enum MouseButtonState: ubyte {
391     none = 0, press, hold, release
392 }
393 
394 /// Normal input keys
395 alias Key = InputKey;
396 /// Ditto
397 enum InputKey {
398     none = -1,
399     quote, equals, comma, minus, slash, colon, period, leftBracket, rightBracket,
400     backslash, grave, up, down, left, right, meta,
401     f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12,
402     enter, tab, backspace, escape,
403 
404     space = 32,
405     num0 = 48, num1, num2, num3, num4, num5, num6, num7, num8, num9,
406     a = 97, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z,
407     mouseWheelUp = 256, mouseWheelDown, mouseLeft, mouseMiddle, mouseRight
408 
409 }
410 
411 import std.stdio: write;
412 
413 /// Enables/disables mouse input capture
414 void mouseEnable() {
415     write("\033[?1000;1006;1015h");
416 }
417 /// Ditto
418 void mouseDisable() {
419     write("\033[?1000;1006;1015l");
420 }
421 
422