1 module fluid.slot;
2 
3 import std.traits;
4 
5 import fluid.node;
6 import fluid.utils;
7 import fluid.style;
8 import fluid.backend;
9 import fluid.structs;
10 
11 import fluid.io.canvas;
12 
13 @safe:
14 
15 
16 /// A "node slot" node, which displays the node given to it. Allows safely swapping nodes in the layout by reference,
17 /// even during drawing. Useful for creating tabs and menus.
18 ///
19 /// Because NodeSlot does not inherit from T, it uses the single-parameter overload of simpleConstructor.
20 alias nodeSlot(alias T) = simpleConstructor!(NodeSlot!T);
21 
22 /// ditto
23 class NodeSlot(T : Node) : Node {
24 
25     CanvasIO canvasIO;
26 
27     public {
28 
29         /// Node placed in the slot.
30         T value;
31 
32     }
33 
34     /// Create a new node slot and optionally place a node within.
35     this(T node = null) {
36 
37         this.value = node;
38 
39     }
40 
41     /// Change the node in the slot.
42     ///
43     /// This function is a little bit more convenient than setting the value directly, as it'll mark itself as
44     /// needing an update. Additionally, it returns the slot, not the given node, so you can assign a value to a
45     /// constructed slot while adding it to the scene tree.
46     typeof(this) opAssign(T value) {
47 
48         updateSize();
49 
50         this.value = value;
51 
52         return this;
53 
54     }
55 
56     /// Remove the assigned node from the slot.
57     void clear() {
58 
59         value = null;
60         updateSize();
61 
62     }
63 
64     protected override void resizeImpl(Vector2 space) {
65 
66         use(canvasIO);
67 
68         minSize = Vector2();
69 
70         // Don't resize if there's no child node
71         if (!value) return;
72 
73         // Remove the value if requested
74         if (value.toRemove) {
75             value = null;
76             return;
77         }
78 
79         resizeChild(value, space);
80         minSize = value.minSize;
81 
82     }
83 
84     protected override void drawImpl(Rectangle outer, Rectangle inner) {
85 
86         pickStyle().drawBackground(io, canvasIO, outer);
87 
88         if (!value) return;
89 
90         drawChild(value, inner);
91 
92     }
93 
94     protected override bool hoveredImpl(Rectangle rect, Vector2 position) {
95 
96         if (!value) return false;
97 
98         // If the child has ignoreMouse set, we should ignore it as well
99         if (value.ignoreMouse) return false;
100 
101         // hoveredImpl may be private... uhhh
102         return (cast(Node) value).hoveredImpl(rect, position);
103 
104     }
105 
106     /// Swap contents of the two slots.
107     void swapSlots(Slot : Node)(Slot other) {
108 
109         static if (is(Slot : NodeSlot!U, U)) {
110 
111             import std.format;
112             import std.algorithm;
113 
114             updateSize();
115 
116             static if (is(T == U)) {
117 
118                 swap(value, other.value);
119 
120             }
121 
122             else static if (is(T : U) || is(U : T)) {
123 
124                 auto theirs = cast(T) other.value;
125                 auto ours   = cast(U) value;
126 
127                 const canAcceptTheirs = theirs || other.value is null;
128                 const canAcceptOurs   = ours   || value is null;
129 
130                 assert(canAcceptTheirs, format!"Can't swap: This slot doesn't accept %s"(typeid(theirs)));
131                 assert(canAcceptOurs,   format!"Can't swap: Other slot doesn't accept %s"(typeid(ours)));
132 
133                 // Perform the swap
134                 value = theirs;
135                 other.value = ours;
136 
137             }
138 
139             else static assert(false, "Slots given to swapSlots are not compatible");
140 
141         }
142 
143         else static assert(false, "The other item is not a node");
144 
145     }
146 
147 }
148 
149 ///
150 unittest {
151 
152     import fluid;
153 
154     NodeSlot!Label slot1, slot2;
155 
156     // Slots can be empty, with no node inside
157     auto root = vspace(
158         label("Hello, "),
159         slot1 = nodeSlot!Label(.layout!"fill"),
160         label(" and "),
161         slot2 = nodeSlot!Label(.layout!"fill"),
162     );
163 
164     // Slots can be assigned other nodes
165     slot1 = label("John");
166 
167     slot2 = button("Jane", delegate {
168 
169         // Slot contents can be swapped with a single call
170         slot1.swapSlots(slot2);
171 
172     });
173 
174     // Slots can be reassigned at any time
175     slot1 = label("Joe");
176 
177     // Their values can also be removed
178     slot1.clear();
179 
180 }