1 ///
2 module fluid.field_slot;
3 
4 import fluid.node;
5 import fluid.utils;
6 import fluid.input;
7 import fluid.actions;
8 import fluid.backend;
9 
10 import fluid.io.hover;
11 import fluid.io.focus;
12 
13 @safe:
14 
15 
16 /// A field slot is a node meant to hold an input node along with associated nodes, like labels. It's functionally
17 /// equivalent to the [`<label>` element in HTML](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label).
18 ///
19 /// Fields expand the interactable (clickable) area of input nodes by other nodes that are placed inside the slot. For
20 /// example, in the code snippet below, if the user clicks on the "username" label, the text input underneath will gain
21 /// focus.
22 ///
23 /// ---
24 /// fieldSlot!vframe(
25 ///     label("Username"),
26 ///     textInput(),
27 /// )
28 /// ---
29 alias fieldSlot(alias node) = simpleConstructor!(FieldSlot, node);
30 
31 /// ditto
32 class FieldSlot(T : Node) : T, FluidHoverable, Hoverable, Focusable {
33 
34     mixin makeHoverable;
35     mixin FluidHoverable.enableInputActions;
36     mixin Hoverable.enableInputActions;
37 
38     private {
39         Focusable _focusableChild;
40     }
41 
42     this(Args...)(Args args) {
43         super(args);
44     }
45 
46     /// Pass focus to the field contained by this slot and press it.
47     @(FluidInputAction.press)
48     void press() {
49 
50         focusAnd.then((Node node) {
51             if (auto focusable = cast(FluidFocusable) node) {
52                 focusable.runInputAction!(FluidInputAction.press);
53             }
54         });
55 
56     }
57 
58     /// Pass focus to the field contained by this slot.
59     void focus() {
60         cast(void) focusAnd();
61     }
62 
63     /// ditto
64     FocusRecurseAction focusAnd() {
65 
66         auto action = this.focusRecurseChildren();
67         action.then(&setFocusableChild);
68         return action;
69 
70     }
71 
72     private void setFocusableChild(Focusable focus) {
73         _focusableChild = focus;
74     }
75 
76     override void drawImpl(Rectangle outer, Rectangle inner) {
77 
78         const previousHover = tree.hover;
79 
80         // Draw children
81         super.drawImpl(outer, inner);
82 
83         // Test if hover has switched to any of them
84         const isChildHovered = tree.hover !is previousHover;
85 
86         // If the node doesn't handle hover itself, take over
87         // (pun not intended)
88         if (isChildHovered && !cast(FluidHoverable) tree.hover) {
89 
90             tree.hover = this;
91 
92         }
93 
94     }
95 
96     // implements FluidHoverable
97     override bool isHovered() const {
98         return tree.hover is this
99             || super.isHovered();
100     }
101 
102     // implements FluidHoverable
103     override void mouseImpl() {
104 
105     }
106 
107     override bool blocksInput() const {
108         return isDisabled || isDisabledInherited;
109     }
110 
111     override bool hoverImpl(HoverPointer) {
112         return false;
113     }
114 
115     override bool focusImpl() {
116         return false;
117     }
118 
119     override bool isFocused() const {
120         return _focusableChild && _focusableChild.isFocused();
121     }
122 
123 }
124