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