1 ///
2 module glui.structs;
3 
4 import raylib;
5 import std.conv;
6 import glui.node;
7 
8 // Disable scissors mode on macOS in Raylib 3, it's broken; see #60
9 version (Glui_Raylib3) version (OSX) version = Glui_DisableScissors;
10 
11 @safe:
12 
13 
14 /// Create a new layout
15 /// Params:
16 ///     expand = Numerator of the fraction of space this node should occupy in the parent.
17 ///     align_ = Align of the node (horizontal and vertical).
18 ///     alignX = Horizontal align of the node.
19 ///     alignY = Vertical align of the node.
20 Layout layout(uint expand, NodeAlign alignX, NodeAlign alignY) {
21 
22     return Layout(expand, [alignX, alignY]);
23 
24 }
25 
26 /// Ditto
27 Layout layout(uint expand, NodeAlign align_) {
28 
29     return Layout(expand, align_);
30 
31 }
32 
33 /// Ditto
34 Layout layout(NodeAlign alignX, NodeAlign alignY) {
35 
36     return Layout(0, [alignX, alignY]);
37 
38 }
39 
40 /// Ditto
41 Layout layout(NodeAlign align_) {
42 
43     return Layout(0, align_);
44 
45 }
46 
47 /// Ditto
48 Layout layout(uint expand) {
49 
50     return Layout(expand);
51 
52 }
53 
54 /// CTFE version of the layout constructor, allows using strings instead of enum members, to avoid boilerplate.
55 Layout layout(uint expand, string alignX, string alignY)() {
56 
57     enum valueX = alignX.to!NodeAlign;
58     enum valueY = alignY.to!NodeAlign;
59 
60     return Layout(expand, [valueX, valueY]);
61 
62 }
63 
64 /// Ditto
65 Layout layout(uint expand, string align_)() {
66 
67     enum valueXY = align_.to!NodeAlign;
68 
69     return Layout(expand, valueXY);
70 
71 }
72 
73 /// Ditto
74 Layout layout(string alignX, string alignY)() {
75 
76     enum valueX = alignX.to!NodeAlign;
77     enum valueY = alignY.to!NodeAlign;
78 
79     return Layout(0, [valueX, valueY]);
80 
81 }
82 
83 /// Ditto
84 Layout layout(string align_)() {
85 
86     enum valueXY = align_.to!NodeAlign;
87 
88     return Layout(0, valueXY);
89 
90 }
91 
92 /// Ditto
93 Layout layout(uint expand)() {
94 
95     return Layout(expand);
96 
97 }
98 
99 unittest {
100 
101     assert(layout!1 == layout(1));
102     assert(layout!("fill") == layout(NodeAlign.fill, NodeAlign.fill));
103     assert(layout!("fill", "fill") == layout(NodeAlign.fill));
104 
105     assert(!__traits(compiles, layout!"expand"));
106     assert(!__traits(compiles, layout!("expand", "noexpand")));
107     assert(!__traits(compiles, layout!(1, "whatever")));
108     assert(!__traits(compiles, layout!(2, "foo", "bar")));
109 
110 }
111 
112 /// Represents a node's layout
113 struct Layout {
114 
115     /// Fraction of available space this node should occupy in the node direction.
116     ///
117     /// If set to `0`, the node doesn't have a strict size limit and has size based on content.
118     uint expand;
119 
120     /// Align the content box to a side of the occupied space.
121     NodeAlign[2] nodeAlign;
122 
123     string toString() const {
124 
125         import std.format;
126 
127         const equalAlign = nodeAlign[0] == nodeAlign[1];
128         const startAlign = equalAlign && nodeAlign[0] == NodeAlign.start;
129 
130         if (expand) {
131 
132             if (startAlign) return format!".layout!%s"(expand);
133             else if (equalAlign) return format!".layout!(%s, %s)"(expand, nodeAlign[0]);
134             else return format!".layout!(%s, %s, %s)"(expand, nodeAlign[0], nodeAlign[1]);
135 
136         }
137 
138         else {
139 
140             if (startAlign) return format!"Layout()";
141             else if (equalAlign) return format!".layout!%s"(nodeAlign[0]);
142             else return format!".layout!(%s, %s)"(nodeAlign[0], nodeAlign[1]);
143 
144         }
145 
146     }
147 
148 }
149 
150 enum NodeAlign {
151 
152     start, center, end, fill
153 
154 }
155 
156 package interface GluiFocusable {
157 
158     void focus();
159     bool isFocused() const;
160     void mouseImpl();
161     bool keyboardImpl();
162     ref inout(bool) isDisabled() inout;
163 
164 }
165 
166 /// Global data for the layout tree.
167 struct LayoutTree {
168 
169     /// Root node of the tree.
170     GluiNode root;
171 
172     /// Top-most hovered node in the tree.
173     GluiNode hover;
174 
175     /// Currently focused node.
176     GluiFocusable focus;
177 
178     /// Check if keyboard input was handled after rendering is has completed.
179     bool keyboardHandled;
180 
181     /// Current depth of "disabled" nodes, incremented for any node descended into, while any of the ancestors is
182     /// disabled.
183     uint disabledDepth;
184 
185     /// Scissors stack.
186     package Rectangle[] scissors;
187 
188     version (Glui_DisableScissors) {
189 
190         Rectangle intersectScissors(Rectangle rect) { return rect; }
191         void pushScissors(Rectangle) { }
192         void popScissors() { }
193 
194     }
195 
196     else {
197 
198         /// Intersect the given rectangle against current scissor area.
199         Rectangle intersectScissors(Rectangle rect) {
200 
201             import std.algorithm : min, max;
202 
203             // No limit applied
204             if (!scissors.length) return rect;
205 
206             const b = scissors[$-1];
207 
208             Rectangle result;
209 
210             // Intersect
211             result.x = max(rect.x, b.x);
212             result.y = max(rect.y, b.y);
213             result.w = min(rect.x + rect.w, b.x + b.w) - result.x;
214             result.h = min(rect.y + rect.h, b.y + b.h) - result.y;
215 
216             return result;
217 
218         }
219 
220         /// Start scissors mode.
221         void pushScissors(Rectangle rect) {
222 
223             auto result = rect;
224 
225             // There's already something on the stack
226             if (scissors.length) {
227 
228                 // Intersect
229                 result = intersectScissors(rect);
230 
231             }
232 
233             // Push to the stack
234             scissors ~= result;
235 
236             // Start the mode
237             applyScissors(result);
238 
239         }
240 
241         void popScissors() @trusted {
242 
243             // Pop the stack
244             scissors = scissors[0 .. $-1];
245 
246             // Pop the mode
247             EndScissorMode();
248 
249             // There's still something left
250             if (scissors.length) {
251 
252                 // Start again
253                 applyScissors(scissors[$-1]);
254 
255             }
256 
257         }
258 
259         private void applyScissors(Rectangle rect) @trusted {
260 
261             import glui.utils;
262 
263             // End the current mode, if any
264             if (scissors.length) EndScissorMode();
265 
266             version (Glui_Raylib3) const scale = hidpiScale;
267             else                   const scale = Vector2(1, 1);
268 
269             // Start this one
270             BeginScissorMode(
271                 to!int(rect.x * scale.x),
272                 to!int(rect.y * scale.y),
273                 to!int(rect.w * scale.x),
274                 to!int(rect.h * scale.y),
275             );
276 
277         }
278 
279     }
280 
281 }