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