1 module fluid.tree;
2 
3 import std.conv;
4 import std.math;
5 import std.container;
6 import std.algorithm;
7 import std.datetime;
8 
9 import fluid.node;
10 import fluid.input;
11 import fluid.style;
12 import fluid.backend;
13 
14 
15 @safe:
16 
17 
18 version (OSX)
19     version = Fluid_MacKeyboard;
20 
21 ///
22 struct FocusDirection {
23 
24     struct WithPriority {
25 
26         /// Pick priority based on tree distance from the focused node.
27         int priority;
28 
29         /// Square of the distance between this node and the focused node.
30         float distance2;
31 
32         /// The node.
33         FluidFocusable node;
34 
35         alias node this;
36 
37     }
38 
39     /// Available space box of the focused item after last frame.
40     Rectangle lastFocusBox;
41 
42     /// Nodes that may get focus with tab navigation.
43     FluidFocusable previous, next;
44 
45     /// First and last focusable nodes in the tree.
46     FluidFocusable first, last;
47 
48     /// Focusable nodes, by direction from the focused node.
49     WithPriority[4] positional;
50 
51     /// Focus priority for the currently drawn node.
52     ///
53     /// Increased until the focused node is found, decremented afterwards. As a result, values will be the highest for
54     /// nodes near the focused one. Changes with tree depth rather than individual nodes.
55     int priority;
56 
57     private {
58 
59         /// Value `prioerity` is summed with on each step. `1` before finding the focused node, `-1` after.
60         int priorityDirection = 1;
61 
62         /// Current tree depth.
63         uint depth;
64 
65     }
66 
67     /// Update focus info with the given node. Automatically called when a node is drawn, shouldn't be called manually.
68     ///
69     /// `previous` will be the last focusable node encountered before the focused node, and `next` will be the first one
70     /// after. `first` and `last will be the last focusable nodes in the entire tree.
71     ///
72     /// Params:
73     ///     current = Node to update the focus info with.
74     ///     box     = Box defining node boundaries (padding box)
75     ///     depth   = Current tree depth. Pass in `tree.depth`.
76     void update(Node current, Rectangle box, uint depth)
77     in (current !is null, "Current node must not be null")
78     do {
79 
80         import std.algorithm : either;
81 
82         auto currentFocusable = cast(FluidFocusable) current;
83 
84         // Count focus priority
85         {
86 
87             // Get depth difference since last time
88             const int depthDiff = depth - this.depth;
89 
90             // Count steps in change of depth
91             priority += priorityDirection * abs(depthDiff);
92 
93             // Update depth
94             this.depth = depth;
95 
96         }
97 
98         // Stop if the current node can't take focus
99         if (!currentFocusable) return;
100 
101         // And it DOES have focus
102         if (current.tree.focus is currentFocusable) {
103 
104             // Mark the node preceding it to the last encountered focusable node
105             previous = last;
106 
107             // Clear the next node, so it can be overwritten by a correct value.
108             next = null;
109 
110             // Reverse priority target
111             priorityDirection = -1;
112 
113         }
114 
115         else {
116 
117             // Update positional focus
118             updatePositional(currentFocusable, box);
119 
120             // There's no node to take focus next, set it now
121             if (next is null) next = currentFocusable;
122 
123         }
124 
125 
126         // Set the current node as the first focusable, if true
127         if (first is null) first = currentFocusable;
128 
129         // Replace the last
130         last = currentFocusable;
131 
132     }
133 
134     /// Check the given node's position and update `positional` to match.
135     private void updatePositional(FluidFocusable node, Rectangle box) {
136 
137         // Note: This might give false-positives if the focused node has changed during this frame
138 
139         // Check each direction
140         foreach (i, ref otherNode; positional) {
141 
142             const side = cast(Style.Side) i;
143             const dist = distance2(box, side);
144 
145             // If a node took this spot before
146             if (otherNode !is null) {
147 
148                 // Ignore if the other node has higher priority
149                 if (otherNode.priority > priority) continue;
150 
151                 // If priorities are equal, check if we're closer than the other node
152                 if (otherNode.priority == priority
153                     && otherNode.distance2 < dist) continue;
154 
155             }
156 
157             // Check if this node matches the direction
158             if (checkDirection(box, side)) {
159 
160                 // Replace the node
161                 otherNode = WithPriority(priority, dist, node);
162 
163             }
164 
165         }
166 
167     }
168 
169     /// Check if the given box is located to the given side of the focus box.
170     bool checkDirection(Rectangle box, Style.Side side) {
171 
172         // Distance between box sides facing each other.
173         //
174         // ↓ lastFocusBox  ↓ box
175         // +======+        +------+
176         // |      |        |      |
177         // |      | ~~~~~~ |      |
178         // |      |        |      |
179         // +======+        +------+
180         //   side ↑        ↑ side.reverse
181         const distanceExternal = lastFocusBox.getSide(side) - box.getSide(side.reverse);
182 
183         // Distance between corresponding box sides.
184         //
185         // ↓ lastFocusBox  ↓ box
186         // +======+        +------+
187         // |      |        :      |
188         // |      | ~~~~~~~~~~~~~ |
189         // |      |        :      |
190         // +======+        +------+
191         //   side ↑          side ↑
192         const distanceInternal = lastFocusBox.getSide(side) - box.getSide(side);
193 
194         // The condition for the return value to be true, is for distanceInternal to be greater than distanceExternal.
195         // This is not the case in the opposite situation.
196         //
197         // For example, if we're checking if the box is on the *right* of lastFocusBox:
198         //
199         // trueish scenario:                                 falseish scenario:
200         // Box is to the right of lastFocusBox               Box is the left of lastFocusBox
201         //
202         // ↓ lastFocusBox  ↓ box                             ↓ box           ↓ lastFocusBox
203         // +======+        +------+                          +------+        +======+
204         // |      | ~~~~~~ :      | external                 | ~~~~~~~~~~~~~~~~~~~~ | external
205         // |      |        :      |    <                     |      :        :      |    >
206         // |      | ~~~~~~~~~~~~~ | internal                 |      : ~~~~~~~~~~~~~ | internal
207         // +======+        +------+                          +------+        +======+
208         //   side ↑        ↑ side.reverse                      side ↑          side ↑
209         const condition = abs(distanceInternal) > abs(distanceExternal);
210 
211         // ↓ box                    There is an edgecase though. If one box entirely overlaps the other on one axis, we
212         // +--------------------+   might end up with unwanted behavior, for example, in a ScrollFrame, focus might
213         // |   ↓ lastFocusBox   |   switch to the scrollbar instead of a child, as we would normally expect.
214         // |   +============+   |
215         // |   |            |   |   For this reason, we require both `distanceInternal` and `distanceExternal` to have
216         // +---|            |---+   the same sign, as it normally would, but not here.
217         //     |            |
218         //     +============+       One can still navigate to the `box` using controls for the other axis.
219         return condition
220             && distanceInternal * distanceExternal >= 0;
221 
222     }
223 
224     /// Get the square of the distance between given box and `lastFocusBox`.
225     float distance2(Rectangle box, Style.Side side) {
226 
227         /// Get the center of given rectangle on the axis opposite to the results of getSide.
228         float center(Rectangle rect) {
229 
230             return side == Style.Side.left || side == Style.Side.right
231                 ? rect.y + rect.height
232                 : rect.x + rect.width;
233 
234         }
235 
236         // Distance between box sides facing each other, see `checkDirection`
237         const distanceExternal = lastFocusBox.getSide(side) - box.getSide(side.reverse);
238 
239         /// Distance between centers of the boxes on the other axis
240         const distanceOpposite = center(box) - center(lastFocusBox);
241 
242         return distanceExternal^^2 + distanceOpposite^^2;
243 
244     }
245 
246 }
247 
248 /// A class for iterating over the node tree.
249 abstract class TreeAction {
250 
251     public {
252 
253         /// Node to descend into; `beforeDraw` and `afterDraw` will only be emitted for this node and its children.
254         ///
255         /// May be null to enable iteration over the entire tree.
256         Node startNode;
257 
258         /// If true, this action is complete and no callbacks should be ran.
259         ///
260         /// Overloads of the same callbacks will still be called for the event that prompted stopping.
261         bool toStop;
262 
263     }
264 
265     private {
266 
267         /// Set to true once the action has descended into `startNode`.
268         bool startNodeFound;
269 
270     }
271 
272     /// Stop the action
273     final void stop() {
274 
275         toStop = true;
276 
277     }
278 
279     /// Called before the tree is drawn. Keep in mind this might not be called if the action is started when tree
280     /// iteration has already begun.
281     /// Params:
282     ///     root     = Root of the tree.
283     ///     viewport = Screen space for the node.
284     void beforeTree(Node root, Rectangle viewport) { }
285 
286     /// Called before a node is resized.
287     void beforeResize(Node node, Vector2 viewportSpace) { }
288 
289     /// Called before each `drawImpl` call of any node in the tree, so supplying parent nodes before their children.
290     ///
291     /// This might not be called if the node is offscreen. If you need to find all nodes, try `beforeResize`.
292     ///
293     /// Params:
294     ///     node       = Node that's about to be drawn.
295     ///     space      = Space given for the node.
296     ///     paddingBox = Padding box of the node.
297     ///     contentBox = Content box of teh node.
298     void beforeDraw(Node node, Rectangle space, Rectangle paddingBox, Rectangle contentBox) { }
299 
300     /// ditto
301     void beforeDraw(Node node, Rectangle space) { }
302 
303     /// internal
304     final package void beforeDrawImpl(Node node, Rectangle space, Rectangle paddingBox, Rectangle contentBox) {
305 
306         // There is a start node set
307         if (startNode !is null) {
308 
309             // Check if we're descending into its branch
310             if (node is startNode) startNodeFound = true;
311 
312             // Continue only if it was found
313             else if (!startNodeFound) return;
314 
315         }
316 
317         // Call the hooks
318         beforeDraw(node, space, paddingBox, contentBox);
319         beforeDraw(node, space);
320 
321     }
322 
323     /// Called after each `drawImpl` call of any node in the tree, so supplying children nodes before their parents.
324     ///
325     /// This might not be called if the node is offscreen. If you need to find all nodes, try `beforeResize`.
326     ///
327     /// Params:
328     ///     node       = Node that's about to be drawn.
329     ///     space      = Space given for the node.
330     ///     paddingBox = Padding box of the node.
331     ///     contentBox = Content box of teh node.
332     void afterDraw(Node node, Rectangle space, Rectangle paddingBox, Rectangle contentBox) { }
333 
334     /// ditto
335     void afterDraw(Node node, Rectangle space) { }
336 
337     /// internal
338     final package void afterDrawImpl(Node node, Rectangle space, Rectangle paddingBox, Rectangle contentBox) {
339 
340         // There is a start node set
341         if (startNode !is null) {
342 
343             // Check if we're leaving the node
344             if (node is startNode) startNodeFound = false;
345 
346             // Continue only if it was found
347             else if (!startNodeFound) return;
348             // Note: We still emit afterDraw for that node, hence `else if`
349 
350         }
351 
352         afterDraw(node, space, paddingBox, contentBox);
353         afterDraw(node, space);
354     }
355 
356     /// Called after the tree is drawn. Called before input events, so they can assume actions have completed.
357     ///
358     /// By default, calls `stop()` preventing the action from evaluating during next draw.
359     void afterTree() {
360 
361         stop();
362 
363     }
364 
365     /// Hook that triggers after processing input. Useful if post-processing is necessary to, perhaps, implement
366     /// fallback input.
367     ///
368     /// Warning: This will **not trigger** unless `afterTree` is overrided not to stop the action. If you make use of
369     /// this, make sure to make the action stop in this method.
370     ///
371     /// Params:
372     ///     keyboardHandled = If true, keyboard input was handled. Passed by reference, so if you react to input, change
373     ///         this to true.
374     void afterInput(ref bool keyboardHandled) { }
375 
376 }
377 
378 /// Global data for the layout tree.
379 struct LayoutTree {
380 
381     // Nodes
382     public {
383 
384         /// Root node of the tree.
385         Node root;
386 
387         /// Top-most hovered node in the tree.
388         Node hover;
389 
390         /// Currently focused node.
391         ///
392         /// Changing this value directly is discouraged. Some nodes might not want the focus! Be gentle, call
393         /// `FluidFocusable.focus()` instead and let the node set the value on its own.
394         FluidFocusable focus;
395 
396         /// Deepest hovered scrollable node.
397         FluidScrollable scroll;
398 
399     }
400 
401     // Input
402     public {
403 
404         /// Focus direction data.
405         FocusDirection focusDirection;
406 
407         /// Padding box of the currently focused node. Only available after the node has been drawn.
408         ///
409         /// See_also: `focusDirection.lastFocusBox`.
410         Rectangle focusBox;
411 
412         /// Tree actions queued to execute during next draw.
413         DList!TreeAction actions;
414 
415         /// Input strokes bound to emit given action signals.
416         ///
417         /// Input layers have to be sorted.
418         InputLayer[] boundInputs;
419 
420         invariant(boundInputs.isSorted);
421 
422         /// Actions that are currently held down.
423         DList!InputBinding downActions;
424 
425         /// Actions that have just triggered.
426         DList!InputBinding activeActions;
427 
428         /// Access to core input and output facilities.
429         FluidBackend backend;
430         alias io = backend;
431 
432         /// Check if keyboard input was handled; updated after rendering has completed.
433         bool keyboardHandled;
434 
435     }
436 
437     /// Miscelleanous, technical properties.
438     public {
439 
440         /// Current node drawing depth.
441         uint depth;
442 
443         /// Current rectangle drawing is limited to.
444         Rectangle scissors;
445 
446         /// True if the current tree branch is marked as disabled (doesn't take input).
447         bool isBranchDisabled;
448 
449     }
450 
451     /// Incremented for every `filterActions` access to prevent nested accesses from breaking previously made ranges.
452     private int _actionAccessCounter;
453 
454     /// Create a new tree with the given node as its root, and using the given backend for I/O.
455     this(Node root, FluidBackend backend) {
456 
457         this.root = root;
458         this.backend = backend;
459         this.restoreDefaultInputBinds();
460 
461     }
462 
463     /// Create a new tree with the given node as its root. Use the default backend, if any is present.
464     this(Node root) {
465 
466         this(root, defaultFluidBackend);
467 
468         assert(backend, "Cannot create LayoutTree; no backend was chosen, and no default is set.");
469 
470     }
471 
472     /// Returns true if this branch requested a resize or is pending a resize.
473     bool resizePending() const {
474 
475         return root.resizePending;
476 
477     }
478 
479     /// Queue an action to perform while iterating the tree.
480     ///
481     /// Avoid using this; most of the time `Node.queueAction` is what you want. `LayoutTree.queueAction` might fire
482     /// too early
483     void queueAction(TreeAction action)
484     in (action, "Invalid action queued")
485     do {
486 
487         actions ~= action;
488 
489     }
490 
491     /// Restore defaults for given actions.
492     void restoreDefaultInputBinds() {
493 
494         /// Get the ID of an input action.
495         auto bind(alias a, T)(T arg) {
496 
497             return InputBinding(InputAction!a.id, InputStroke.Item(arg));
498 
499         }
500 
501         with (FluidInputAction) {
502 
503             // System-independent keys
504             auto universalShift = InputLayer(
505                 InputStroke(KeyboardKey.leftShift),
506                 [
507                     bind!focusPrevious(KeyboardKey.tab),
508                     bind!entryPrevious(KeyboardKey.tab),
509                     bind!outdent(KeyboardKey.tab),
510                     bind!selectPreviousChar(KeyboardKey.left),
511                     bind!selectNextChar(KeyboardKey.right),
512                     bind!selectPreviousLine(KeyboardKey.up),
513                     bind!selectNextLine(KeyboardKey.down),
514                     bind!selectToLineStart(KeyboardKey.home),
515                     bind!selectToLineEnd(KeyboardKey.end),
516                     bind!breakLine(KeyboardKey.enter),
517                     bind!contextMenu(KeyboardKey.f10),
518                 ]
519             );
520             auto universal = InputLayer(
521                 InputStroke(),
522                 [
523                     // Press
524                     bind!press(MouseButton.left),
525                     bind!press(KeyboardKey.enter),
526                     bind!press(GamepadButton.cross),
527 
528                     // Submit
529                     bind!submit(KeyboardKey.enter),
530                     bind!submit(GamepadButton.cross),
531 
532                     // Cancel
533                     bind!cancel(KeyboardKey.escape),
534                     bind!cancel(GamepadButton.circle),
535 
536                     // Menu
537                     bind!contextMenu(MouseButton.right),
538                     bind!contextMenu(KeyboardKey.contextMenu),
539 
540                     // Tabbing; index-focus
541                     bind!focusPrevious(GamepadButton.leftButton),
542                     bind!focusNext(KeyboardKey.tab),
543                     bind!focusNext(GamepadButton.rightButton),
544 
545                     // Directional focus
546                     bind!focusLeft(KeyboardKey.left),
547                     bind!focusLeft(GamepadButton.dpadLeft),
548                     bind!focusRight(KeyboardKey.right),
549                     bind!focusRight(GamepadButton.dpadRight),
550                     bind!focusUp(KeyboardKey.up),
551                     bind!focusUp(GamepadButton.dpadUp),
552                     bind!focusDown(KeyboardKey.down),
553                     bind!focusDown(GamepadButton.dpadDown),
554 
555                     // Text input
556                     bind!backspace(KeyboardKey.backspace),
557                     bind!deleteChar(KeyboardKey.delete_),
558                     bind!breakLine(KeyboardKey.enter),
559                     bind!previousChar(KeyboardKey.left),
560                     bind!nextChar(KeyboardKey.right),
561                     bind!previousLine(KeyboardKey.up),
562                     bind!nextLine(KeyboardKey.down),
563                     bind!entryPrevious(KeyboardKey.up),
564                     bind!entryPrevious(GamepadButton.dpadUp),
565                     bind!entryNext(KeyboardKey.down),
566                     bind!entryNext(KeyboardKey.tab),
567                     bind!entryNext(GamepadButton.dpadDown),
568                     bind!toLineStart(KeyboardKey.home),
569                     bind!toLineEnd(KeyboardKey.end),
570                     bind!insertTab(KeyboardKey.tab),
571 
572                     // Scrolling
573                     bind!scrollLeft(KeyboardKey.left),
574                     bind!scrollLeft(GamepadButton.dpadLeft),
575                     bind!scrollRight(KeyboardKey.right),
576                     bind!scrollRight(GamepadButton.dpadRight),
577                     bind!scrollUp(KeyboardKey.up),
578                     bind!scrollUp(GamepadButton.dpadUp),
579                     bind!scrollDown(KeyboardKey.down),
580                     bind!scrollDown(GamepadButton.dpadDown),
581                     bind!pageUp(KeyboardKey.pageUp),
582                     bind!pageDown(KeyboardKey.pageDown),
583                 ]
584             );
585 
586             // TODO universal left/right key
587             version (Fluid_MacKeyboard)
588                 boundInputs = [
589 
590                     // Shift + Command
591                     InputLayer(
592                         InputStroke(KeyboardKey.leftShift, KeyboardKey.leftSuper),
593                         [
594                             // TODO Command should *expand selection* on macOS instead of current
595                             // toLineStart/toLineEnd behavior
596                             bind!selectToLineStart(KeyboardKey.left),
597                             bind!selectToLineEnd(KeyboardKey.right),
598                             bind!selectToStart(KeyboardKey.up),
599                             bind!selectToEnd(KeyboardKey.down),
600                             bind!redo(KeyboardKey.z),
601                         ]
602                     ),
603 
604                     // Shift + Option
605                     InputLayer(
606                         InputStroke(KeyboardKey.leftShift, KeyboardKey.leftAlt),
607                         [
608                             bind!selectPreviousWord(KeyboardKey.left),
609                             bind!selectNextWord(KeyboardKey.right),
610                         ]
611                     ),
612 
613                     // Command
614                     InputLayer(
615                         InputStroke(KeyboardKey.leftSuper),
616                         [
617                             bind!toLineStart(KeyboardKey.left),
618                             bind!toLineEnd(KeyboardKey.right),
619                             bind!toStart(KeyboardKey.up),
620                             bind!toEnd(KeyboardKey.down),
621                             bind!selectAll(KeyboardKey.a),
622                             bind!copy(KeyboardKey.c),
623                             bind!cut(KeyboardKey.x),
624                             bind!paste(KeyboardKey.v),
625                             bind!undo(KeyboardKey.z),
626                             bind!redo(KeyboardKey.y),
627                             bind!submit(KeyboardKey.enter),
628                         ]
629                     ),
630 
631                     // Option
632                     InputLayer(
633                         InputStroke(KeyboardKey.leftAlt),
634                         [
635                             bind!deleteWord(KeyboardKey.delete_),
636                             bind!backspaceWord(KeyboardKey.backspace),
637                             bind!previousWord(KeyboardKey.left),
638                             bind!nextWord(KeyboardKey.right),
639                         ]
640                     ),
641 
642                     // Control
643                     InputLayer(
644                         InputStroke(KeyboardKey.leftControl),
645                         [
646                             bind!backspaceWord(KeyboardKey.w),  // emacs & vim
647                             bind!entryPrevious(KeyboardKey.k),  // vim
648                             bind!entryPrevious(KeyboardKey.p),  // emacs
649                             bind!entryNext(KeyboardKey.j),  // vim
650                             bind!entryNext(KeyboardKey.n),  // emacs
651                         ]
652                     ),
653 
654                     universalShift,
655                     universal,
656                 ];
657             else
658                 boundInputs = [
659 
660                     InputLayer(
661                         InputStroke(KeyboardKey.leftShift, KeyboardKey.leftControl),
662                         [
663                             bind!selectPreviousWord(KeyboardKey.left),
664                             bind!selectNextWord(KeyboardKey.right),
665                             bind!selectToStart(KeyboardKey.home),
666                             bind!selectToEnd(KeyboardKey.end),
667                             bind!redo(KeyboardKey.z),
668                         ]
669                     ),
670 
671                     InputLayer(
672                         InputStroke(KeyboardKey.leftControl),
673                         [
674                             bind!deleteWord(KeyboardKey.delete_),
675                             bind!backspaceWord(KeyboardKey.backspace),
676                             bind!backspaceWord(KeyboardKey.w),  // emacs & vim
677                             bind!entryPrevious(KeyboardKey.k),  // vim
678                             bind!entryPrevious(KeyboardKey.p),  // emacs
679                             bind!entryNext(KeyboardKey.j),  // vim
680                             bind!entryNext(KeyboardKey.n),  // emacs
681                             bind!previousWord(KeyboardKey.left),
682                             bind!nextWord(KeyboardKey.right),
683                             bind!selectAll(KeyboardKey.a),
684                             bind!copy(KeyboardKey.c),
685                             bind!cut(KeyboardKey.x),
686                             bind!paste(KeyboardKey.v),
687                             bind!undo(KeyboardKey.z),
688                             bind!redo(KeyboardKey.y),
689                             bind!toStart(KeyboardKey.home),
690                             bind!toEnd(KeyboardKey.end),
691 
692                             // Submit with ctrl+enter
693                             bind!submit(KeyboardKey.enter),
694                         ]
695                     ),
696 
697                     InputLayer(
698                         InputStroke(KeyboardKey.leftAlt),
699                         [
700                             bind!entryUp(KeyboardKey.up),
701                         ]
702                     ),
703 
704                     universalShift,
705                     universal,
706 
707                 ];
708 
709         }
710 
711     }
712 
713     /// Remove any inputs bound to given input action.
714     /// Returns: `true` if the action was cleared.
715     bool clearBoundInput(InputActionID action) {
716 
717         import std.array;
718 
719         // TODO test
720 
721         bool found;
722 
723         foreach (ref layer; boundInputs) {
724 
725             const oldLength = layer.bindings.length;
726 
727             layer.bindings = layer.bindings.filter!(a => a.action == action).array;
728 
729             if (layer.bindings.length != oldLength) {
730                 found = true;
731             }
732 
733         }
734 
735         return found;
736 
737     }
738 
739     /// Find a layer for the given input stroke.
740     /// Returns: Layer found for the given input stroke. `null` if none found.
741     inout(InputLayer)* layerForStroke(InputStroke stroke) inout scope return {
742 
743         auto modifiers = stroke.modifiers;
744 
745         foreach (i, layer; boundInputs) {
746 
747             // Found a matching layer
748             if (modifiers == layer.modifiers) {
749 
750                 return &boundInputs[i];
751 
752             }
753 
754             // Stop if other layers are less complex
755             if (modifiers.length > layer.modifiers.length) break;
756 
757         }
758 
759         return null;
760 
761     }
762 
763     /// Bind a key stroke or button to given input action. Multiple key strokes are allowed to match given action.
764     void bindInput(InputActionID action, InputStroke stroke)
765     in (stroke.length != 0)
766     do {
767 
768         // TODO tests
769 
770         auto binding = InputBinding(action, stroke.input[$-1]);
771 
772         // Layer exists, add the binding
773         if (auto layer = layerForStroke(stroke)) {
774 
775             layer.bindings ~= binding;
776 
777         }
778 
779         // Layer doesn't exist, create it
780         else {
781 
782             auto modifiers = stroke.modifiers;
783             auto newLayer = InputLayer(modifiers, [binding]);
784             bool found;
785 
786             // Insert the layer before any layer that is less complex
787             foreach (i, layer; boundInputs) {
788 
789                 if (modifiers.length > layer.modifiers.length) {
790 
791                     boundInputs = boundInputs[0..i] ~ newLayer ~ boundInputs[i..$];
792                     found = true;
793                     break;
794 
795                 }
796 
797             }
798 
799             if (!found) boundInputs ~= newLayer;
800 
801             assert(isSorted(boundInputs));
802 
803         }
804 
805     }
806 
807     /// Bind a key stroke or button to given input action, replacing any previously bound inputs.
808     void bindInputReplace(InputActionID action, InputStroke stroke)
809     in (stroke.length != 0)
810     do {
811 
812         import std.array;
813 
814         // Find a matching layer
815         if (auto layer = layerForStroke(stroke)) {
816 
817             // Remove any stroke that matches
818             layer.bindings = layer.bindings.filter!(a => a.trigger == stroke.input[$-1]).array;
819 
820             // Insert the binding
821             layer.bindings ~= InputBinding(action, stroke.input[$-1]);
822 
823         }
824 
825         // Layer doesn't exist, bind it the straightforward way
826         else bindInput(action, stroke);
827 
828     }
829 
830     /// List actions in the tree, remove finished actions while iterating.
831     auto filterActions() {
832 
833         struct ActionIterator {
834 
835             LayoutTree* tree;
836 
837             int opApply(int delegate(TreeAction) @safe fun) {
838 
839                 tree._actionAccessCounter++;
840                 scope (exit) tree._actionAccessCounter--;
841 
842                 // Regular access
843                 if (tree._actionAccessCounter == 1) {
844 
845                     for (auto range = tree.actions[]; !range.empty; ) {
846 
847                         // Yield the item
848                         auto result = fun(range.front);
849 
850                         // If finished, remove from the queue
851                         if (range.front.toStop) tree.actions.popFirstOf(range);
852 
853                         // Continue to the next item
854                         else range.popFront();
855 
856                         // Stop iteration if requested
857                         if (result) return result;
858 
859                     }
860 
861                 }
862 
863                 // Nested access
864                 else {
865 
866                     for (auto range = tree.actions[]; !range.empty; ) {
867 
868                         auto front = range.front;
869                         range.popFront();
870 
871                         // Ignore stopped items
872                         if (front.toStop) continue;
873 
874                         // Yield the item
875                         if (auto result = fun(front)) {
876 
877                             return result;
878 
879                         }
880 
881                     }
882 
883                 }
884 
885                 return 0;
886 
887             }
888 
889         }
890 
891         return ActionIterator(&this);
892 
893     }
894 
895     /// Intersect the given rectangle against current scissor area.
896     Rectangle intersectScissors(Rectangle rect) {
897 
898         import std.algorithm : min, max;
899 
900         // No limit applied
901         if (scissors is scissors.init) return rect;
902 
903         Rectangle result;
904 
905         // Intersect
906         result.x = max(rect.x, scissors.x);
907         result.y = max(rect.y, scissors.y);
908         result.w = max(0, min(rect.x + rect.w, scissors.x + scissors.w) - result.x);
909         result.h = max(0, min(rect.y + rect.h, scissors.y + scissors.h) - result.y);
910 
911         return result;
912 
913     }
914 
915     /// Start scissors mode.
916     /// Returns: Previous scissors mode value. Pass that value to `popScissors`.
917     Rectangle pushScissors(Rectangle rect) {
918 
919         const lastScissors = scissors;
920 
921         // Intersect with the current scissors rectangle.
922         io.area = scissors = intersectScissors(rect);
923 
924         return lastScissors;
925 
926     }
927 
928     void popScissors(Rectangle lastScissorsMode) @trusted {
929 
930         // Pop the stack
931         scissors = lastScissorsMode;
932 
933         // No scissors left
934         if (scissors is scissors.init) {
935 
936             // Restore full draw area
937             backend.restoreArea();
938 
939         }
940 
941         else {
942 
943             // Start again
944             backend.area = scissors;
945 
946         }
947 
948     }
949 
950     /// Fetch tree events (e.g. actions)
951     package void poll() {
952 
953         // Run texture reaper
954         io.reaper.check();
955 
956         // Reset all actions
957         downActions.clear();
958         activeActions.clear();
959 
960         // Test all bindings
961         foreach (layer; boundInputs) {
962 
963             // Check if the layer is active
964             if (!layer.modifiers.isDown(backend)) continue;
965 
966             // Found an active layer, test all bound strokes
967             foreach (binding; layer.bindings) {
968 
969                 // Register held-down actions
970                 if (InputStroke.isItemDown(backend, binding.trigger)) {
971 
972                     downActions ~= binding;
973 
974                 }
975 
976                 // Register triggered actions
977                 if (InputStroke.isItemActive(backend, binding.trigger)) {
978 
979                     activeActions ~= binding;
980 
981                 }
982 
983             }
984 
985             // End on this layer
986             break;
987 
988         }
989 
990     }
991 
992 }