1 module fluid.input_map_chain;
2 
3 import std.algorithm;
4 
5 import fluid.node;
6 import fluid.utils;
7 import fluid.input;
8 import fluid.types;
9 import fluid.node_chain;
10 
11 import fluid.io.action;
12 
13 import fluid.future.stack;
14 
15 @safe:
16 
17 alias inputMapChain  = nodeBuilder!InputMapChain;
18 
19 class InputMapChain : NodeChain, ActionIO {
20 
21     mixin controlIO;
22 
23     private struct ReceivedInputEvent {
24         InputEvent event;
25         int number;
26         bool delegate(InputActionID action, bool isActive, int number) @safe callback;
27     }
28 
29     public {
30 
31         /// Map of input events to input actions.
32         InputMapping map;
33 
34     }
35 
36     private {
37 
38         /// All collected input events.
39         Stack!ReceivedInputEvent _events;
40 
41     }
42 
43     this(InputMapping map, Node next = null) {
44         super(next);
45         this.map = map;
46     }
47 
48     this(Node next = null) {
49         this(InputMapping.defaultMapping, next);
50     }
51 
52     override void beforeResize(Vector2) {
53         startIO();
54     }
55 
56     override void afterResize(Vector2) {
57         stopIO();
58     }
59 
60     override void afterDraw(Rectangle, Rectangle) {
61 
62         // Process all input events
63         processEvents();
64         _events.clear();
65 
66     }
67 
68     override void emitEvent(InputEvent event, int number,
69         bool delegate(InputActionID action, bool isActive, int number) @safe callback)
70     do {
71 
72         // Save the event to list
73         _events ~= ReceivedInputEvent(event, number, callback);
74 
75     }
76 
77     /// Find the given event type among ones that were emitted this frame.
78     /// Safety:
79     ///     The range has to be exhausted immediately.
80     ///     No input events can be emitted before the range is disposed of, or the range will break.
81     /// Params:
82     ///     code = Input event code to find.
83     /// Returns:
84     ///     A range with all emitted events that match the query.
85     auto findEvents(InputEventCode code) @system {
86 
87         return _events[].filter!(a => a.event.code == code);
88 
89     }
90 
91     /// Detect all input actions that should be emitted as a consequence of the events that occurred this frame.
92     /// Clears the current list of events when done.
93     private void processEvents() @trusted {
94 
95         scope (exit) _events.clear();
96 
97         bool handled;
98 
99         // Test noop event first
100         foreach (event; findEvents(noopEvent.code)) {
101             return;
102         }
103 
104         // Test all mappings
105         foreach (layer; map.layers) {
106 
107             // Check if every modifier in this layer is active
108             if (layer.modifiers.any!(a => findEvents(a).empty)) continue;
109 
110             // Found an active layer, test all bound strokes
111             foreach_reverse (binding; layer.bindings) {
112 
113                 // Check if any of the events matches this binding
114                 foreach (event; findEvents(binding.code)) {
115 
116                     handled = handled || event.callback(binding.inputAction, event.event.isActive, event.number);
117 
118                 }
119 
120                 // Stroke handled, stop here
121                 if (handled) break;
122 
123             }
124 
125             // End on this layer
126             break;
127 
128         }
129 
130         // No event was handled, fire the frame event
131         if (!handled) {
132 
133             foreach (event; findEvents(frameEvent.code)) {
134                 event.callback(inputActionID!(CoreAction.frame), event.event.isActive, event.number);
135             }
136 
137         }
138 
139     }
140 
141 }
142 
143 /// Maps sequences input events to input actions.
144 ///
145 /// Actions are bound to "strokes". A single stroke is a set of modifier events and a trigger event.
146 /// A stroke without modifiers is one that directly binds a button or key to an action, for example
147 /// mapping the backspace key to an "eraseCharacter" action. Modifiers can be added to require that
148 /// multiple other buttons be held for the action to work — the "ctrl+C" stroke, often used to copy
149 /// text, has one modifier key "ctrl" and a trigger key "c".
150 ///
151 /// Mappings are grouped by modifiers into "layers". All mappings that share the same set of modifiers
152 /// will be placed on the same layer. These layers are sorted by number of modifiers, and only one is
153 /// looked up at once; this prevents firing an action with a less complex set of modifiers from
154 /// accidentally firing when performing a more complex one. For example, an action bound to the key "C"
155 /// will not fire when pressing "ctrl+C".
156 ///
157 /// Mappings can combine multiple input events, so it is possible to use both keyboard keys and mouse
158 /// buttons in a mapping. A mouse button could be used as a trigger, combined with a keyboard key as
159 /// a modifier, such as "ctrl + left mouse button".
160 struct InputMapping {
161 
162     /// Final element in a stroke, completing the circuit and creating the event.
163     struct Trigger {
164 
165         /// Input action that should be emitted.
166         InputActionID inputAction;
167 
168         /// Event code that triggers this action.
169         InputEventCode code;
170     }
171 
172     /// A layer groups all mappings that share the same set of input event codes.
173     struct Layer {
174 
175         /// Modifiers that have to be pressed for this layer to be checked.
176         InputEventCode[] modifiers;
177 
178         /// Keys and events on this layer. These binding are tested in reverse-order,
179         /// so bindings that come last are tested first, giving them higher priority.
180         Trigger[] bindings;
181 
182         int opCmp(const Layer other) const {
183 
184             // You're not going to put 2,147,483,646 modifiers in a single stroke, are you?
185             return cast(int) (other.modifiers.length - modifiers.length);
186 
187         }
188 
189     }
190 
191     /// All input layers that have been mapped.
192     ///
193     /// Input layers have to be sorted, so that the layer with most modifiers have to be first, and layer with no
194     /// modifiers have to be last. Every layer should have a unique set of modifiers.
195     Layer[] layers;
196 
197     invariant(layers.isSorted);
198 
199     /// Bind an input stroke to an input action.
200     ///
201     /// Does not replace any existing mappings, even in case of collision. Previously created mappings will
202     /// have higher higher priority than this mapping.
203     ///
204     /// Params:
205     ///     action = Input action the stroke should trigger
206     ///     codes  = Sequence of event codes that triggers the event.
207     void bindNew(InputActionID action, InputEventCode[] codes...)
208     in (codes.length >= 1)
209     do {
210 
211         auto  modifiers = codes[0 .. $-1].dup;
212         const trigger   = codes[$-1];
213         const binding   = Trigger(action, trigger);
214 
215         // Find a matching layer for this mapping
216         foreach (i, ref layer; layers) {
217 
218             if (layer.modifiers.length > modifiers.length) continue;
219 
220             // No layer has this set of modifiers
221             if (layer.modifiers.length < modifiers.length) {
222 
223                 // Create one
224                 auto newLayer = Layer(modifiers, [binding]);
225 
226                 // Insert
227                 layers = layers[0..i] ~ newLayer ~ layers[i..$];
228                 return;
229 
230             }
231 
232             // Found a matching layer
233             if (layer.modifiers == modifiers) {
234 
235                 // Insert the binding
236                 layer.bindings ~= binding;
237                 return;
238 
239             }
240 
241         }
242 
243         // This has less modifiers than any existing layer
244         layers ~= Layer(modifiers, [binding]);
245 
246     }
247 
248     /// ditto
249     void bindNew(alias action)(InputEventCode[] codes...) {
250 
251         const actionID = inputActionID!action;
252         bindNew(actionID, codes);
253 
254     }
255 
256     /// Add a number of bindings to an empty map.
257     unittest {
258 
259         import fluid.io.keyboard;
260 
261         auto map = InputMapping();
262         map.bindNew!(FluidInputAction.press)    (KeyboardIO.codes.enter);
263         map.bindNew!(FluidInputAction.focusNext)(KeyboardIO.codes.tab);
264         map.bindNew!(FluidInputAction.submit)   (KeyboardIO.codes.leftControl, KeyboardIO.codes.enter);
265         map.bindNew!(FluidInputAction.copy)     (KeyboardIO.codes.leftControl, KeyboardIO.codes.c);
266 
267         // The above creates equivalent layers
268         assert(map.layers == [
269             Layer([KeyboardIO.codes.leftControl], [
270                 Trigger(inputActionID!(FluidInputAction.submit), KeyboardIO.codes.enter),
271                 Trigger(inputActionID!(FluidInputAction.copy),   KeyboardIO.codes.c),
272             ]),
273             Layer([], [
274                 Trigger(inputActionID!(FluidInputAction.press),     KeyboardIO.codes.enter),
275                 Trigger(inputActionID!(FluidInputAction.focusNext), KeyboardIO.codes.tab),
276             ]),
277         ]);
278 
279     }
280 
281     /// Create a default input map using commonly used input sequences for each of the default actions.
282     /// Returns:
283     ///     A newly created input mapping.
284     static InputMapping defaultMapping() {
285 
286         import fluid.io.keyboard;
287         import fluid.io.mouse;
288 
289         InputMapping mapping;
290 
291         /// Get the ID of an input action.
292         auto bind(alias a)(InputEventCode code) {
293 
294             return Trigger(InputAction!a.id, code);
295 
296         }
297 
298         with (FluidInputAction) {
299 
300             // System-independent keys
301             auto universalShift = Layer(
302                 [KeyboardIO.codes.leftShift],
303                 [
304                     bind!focusPrevious(KeyboardIO.codes.tab),
305                     bind!contextMenu(KeyboardIO.codes.f10),
306                     bind!breakLine(KeyboardIO.codes.enter),
307                     bind!selectToLineEnd(KeyboardIO.codes.end),
308                     bind!selectToLineStart(KeyboardIO.codes.home),
309                     bind!selectNextLine(KeyboardIO.codes.down),
310                     bind!selectPreviousLine(KeyboardIO.codes.up),
311                     bind!selectNextChar(KeyboardIO.codes.right),
312                     bind!selectPreviousChar(KeyboardIO.codes.left),
313                     bind!outdent(KeyboardIO.codes.tab),
314                     bind!entryPrevious(KeyboardIO.codes.tab),
315                 ]
316             );
317             auto universal = Layer(
318                 [],
319                 [
320                     // Focus control
321                     // bind!focusDown(GamepadButton.dpadDown), TODO
322                     bind!focusDown(KeyboardIO.codes.down),
323                     // bind!focusUp(GamepadButton.dpadUp), TODO
324                     bind!focusUp(KeyboardIO.codes.up),
325                     // bind!focusRight(GamepadButton.dpadRight), TODO
326                     bind!focusRight(KeyboardIO.codes.right),
327                     // bind!focusLeft(GamepadButton.dpadLeft), TODO
328                     bind!focusLeft(KeyboardIO.codes.left),
329                     // bind!focusNext(GamepadButton.rightButton), TODO
330                     bind!focusNext(KeyboardIO.codes.tab),
331                     // bind!focusPrevious(GamepadButton.leftButton), TODO
332 
333                     // Scroll
334                     bind!pageDown(KeyboardIO.codes.pageDown),
335                     bind!pageUp(KeyboardIO.codes.pageUp),
336                     // bind!scrollDown(GamepadButton.dpadDown), TODO
337                     bind!scrollDown(KeyboardIO.codes.down),
338                     // bind!scrollUp(GamepadButton.dpadUp), TODO
339                     bind!scrollUp(KeyboardIO.codes.up),
340                     // bind!scrollRight(GamepadButton.dpadRight), TODO
341                     bind!scrollRight(KeyboardIO.codes.right),
342                     // bind!scrollLeft(GamepadButton.dpadLeft), TODO
343                     bind!scrollLeft(KeyboardIO.codes.left),
344                     // bind!submit(GamepadButton.cross), TODO
345 
346                     // Submit
347                     bind!submit(KeyboardIO.codes.enter),
348 
349                     // Text editing
350                     bind!insertTab(KeyboardIO.codes.tab),
351                     bind!toLineEnd(KeyboardIO.codes.end),
352                     bind!toLineStart(KeyboardIO.codes.home),
353                     // bind!entryNext(GamepadButton.dpadDown), TODO
354                     bind!entryNext(KeyboardIO.codes.tab),
355                     bind!entryNext(KeyboardIO.codes.down),
356                     // bind!entryPrevious(GamepadButton.dpadUp), TODO
357                     bind!entryPrevious(KeyboardIO.codes.up),
358                     bind!nextLine(KeyboardIO.codes.down),
359                     bind!previousLine(KeyboardIO.codes.up),
360                     bind!nextChar(KeyboardIO.codes.right),
361                     bind!previousChar(KeyboardIO.codes.left),
362                     bind!breakLine(KeyboardIO.codes.enter),
363                     bind!deleteChar(KeyboardIO.codes.delete_),
364                     bind!backspace(KeyboardIO.codes.backspace),
365 
366                     // Basic actions
367                     bind!contextMenu(KeyboardIO.codes.contextMenu),
368                     bind!contextMenu(MouseIO.codes.right),
369                     // bind!cancel(GamepadButton.circle), TODO
370                     bind!cancel(KeyboardIO.codes.escape),
371                     // bind!press(GamepadButton.cross), TODO
372                     bind!press(KeyboardIO.codes.enter),
373                     bind!press(MouseIO.codes.left),
374                 ]
375             );
376 
377             // TODO universal left/right key
378             version (Fluid_MacKeyboard)
379                 mapping.layers = [
380 
381                     // Shift + Command
382                     Layer(
383                         [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftSuper],
384                         [
385                             // TODO Command should *expand selection* on macOS instead of current
386                             // toLineStart/toLineEnd behavior
387                             bind!redo(KeyboardIO.codes.z),
388                             bind!selectToEnd(KeyboardIO.codes.down),
389                             bind!selectToStart(KeyboardIO.codes.up),
390                             bind!selectToLineEnd(KeyboardIO.codes.right),
391                             bind!selectToLineStart(KeyboardIO.codes.left),
392                         ]
393                     ),
394 
395                     // Shift + Option
396                     Layer(
397                         [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftAlt],
398                         [
399                             bind!selectNextWord(KeyboardIO.codes.right),
400                             bind!selectPreviousWord(KeyboardIO.codes.left),
401                         ]
402                     ),
403 
404                     // Command
405                     Layer(
406                         [KeyboardIO.codes.leftSuper],
407                         [
408                             bind!submit(KeyboardIO.codes.enter),
409                             bind!redo(KeyboardIO.codes.y),
410                             bind!undo(KeyboardIO.codes.z),
411                             bind!paste(KeyboardIO.codes.v),
412                             bind!cut(KeyboardIO.codes.x),
413                             bind!copy(KeyboardIO.codes.c),
414                             bind!selectAll(KeyboardIO.codes.a),
415                             bind!toEnd(KeyboardIO.codes.down),
416                             bind!toStart(KeyboardIO.codes.up),
417                             bind!toLineEnd(KeyboardIO.codes.right),
418                             bind!toLineStart(KeyboardIO.codes.left),
419                         ]
420                     ),
421 
422                     // Option
423                     Layer(
424                         [KeyboardIO.codes.leftAlt],
425                         [
426                             bind!nextWord(KeyboardIO.codes.right),
427                             bind!previousWord(KeyboardIO.codes.left),
428                             bind!backspaceWord(KeyboardIO.codes.backspace),
429                             bind!deleteWord(KeyboardIO.codes.delete_),
430                         ]
431                     ),
432 
433                     // Control
434                     Layer(
435                         [KeyboardIO.codes.leftControl],
436                         [
437                             bind!entryNext(KeyboardIO.codes.n),  // emacs
438                             bind!entryNext(KeyboardIO.codes.j),  // vim
439                             bind!entryPrevious(KeyboardIO.codes.p),  // emacs
440                             bind!entryPrevious(KeyboardIO.codes.k),  // vim
441                             bind!backspaceWord(KeyboardIO.codes.w),  // emacs & vim
442                         ]
443                     ),
444 
445                     universalShift,
446                     universal,
447                 ];
448             else
449                 mapping.layers = [
450 
451                     Layer(
452                         [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftControl],
453                         [
454                             bind!redo(KeyboardIO.codes.z),
455                             bind!selectToEnd(KeyboardIO.codes.end),
456                             bind!selectToStart(KeyboardIO.codes.home),
457                             bind!selectNextWord(KeyboardIO.codes.right),
458                             bind!selectPreviousWord(KeyboardIO.codes.left),
459                         ]
460                     ),
461 
462                     Layer(
463                         [KeyboardIO.codes.leftControl],
464                         [
465                             bind!submit(KeyboardIO.codes.enter),
466                             bind!toEnd(KeyboardIO.codes.end),
467                             bind!toStart(KeyboardIO.codes.home),
468                             bind!redo(KeyboardIO.codes.y),
469                             bind!undo(KeyboardIO.codes.z),
470                             bind!paste(KeyboardIO.codes.v),
471                             bind!cut(KeyboardIO.codes.x),
472                             bind!copy(KeyboardIO.codes.c),
473                             bind!selectAll(KeyboardIO.codes.a),
474                             bind!nextWord(KeyboardIO.codes.right),
475                             bind!previousWord(KeyboardIO.codes.left),
476                             bind!entryNext(KeyboardIO.codes.n),  // emacs
477                             bind!entryNext(KeyboardIO.codes.j),  // vim
478                             bind!entryPrevious(KeyboardIO.codes.p),  // emacs
479                             bind!entryPrevious(KeyboardIO.codes.k),  // vim
480                             bind!backspaceWord(KeyboardIO.codes.w),  // emacs & vim
481                             bind!backspaceWord(KeyboardIO.codes.backspace),
482                             bind!deleteWord(KeyboardIO.codes.delete_),
483                         ]
484                     ),
485 
486                     Layer(
487                         [KeyboardIO.codes.leftAlt],
488                         [
489                             bind!contextMenu(KeyboardIO.codes.f10),
490                             bind!entryUp(KeyboardIO.codes.up),
491                         ]
492                     ),
493 
494                     universalShift,
495                     universal,
496 
497                 ];
498 
499         }
500 
501         return mapping;
502 
503     }
504 
505 }