1 /// This module defines the `Children` struct which will catch mutations made to it while drawing, and defines utils
2 /// for operating on children.
3 module fluid.children;
4 
5 import std.range;
6 
7 import fluid.node;
8 
9 @safe pure:
10 
11 debug struct Children {
12 
13     private enum mutateError = "Cannot mutate children list while its being rendered. This should be done in an event "
14         ~ "handler, such as the mouseImpl/keyboardImpl methods.";
15 
16     private {
17 
18         Node[] _children;
19         bool _isLocked;
20         bool _hasChanged;
21 
22     }
23 
24     @safe:
25 
26     this(inout(Children) old) inout {
27 
28         this._children = old._children;
29         this._isLocked = false;
30 
31     }
32 
33     @property {
34 
35         size_t length() const {
36 
37             return _children.length;
38 
39         }
40 
41         size_t length(size_t value) {
42 
43             assert(!_isLocked, mutateError);
44 
45             _hasChanged = true;
46 
47             return _children.length = value;
48 
49         }
50 
51     }
52 
53     @property
54     bool empty() const {
55 
56         return _children.length == 0;
57 
58     }
59 
60     /// Remove the first item.
61     void popFront() {
62 
63         assert(!_isLocked, mutateError);
64         assert(!empty, "Can't pop an empty children list");
65 
66         _hasChanged = true;
67         _children.popFront();
68 
69     }
70 
71     /// Get the first child.
72     ref inout(Node) front() inout {
73 
74         assert(!empty, "Can't get the first item of an empty children list");
75 
76         return _children[0];
77 
78     }
79 
80     void opAssign(Node[] newList) {
81 
82         assert(!_isLocked, mutateError);
83         _hasChanged = true;
84         _children = newList;
85 
86     }
87 
88     // Indexing and slicing is allowed
89     inout(Node[]) opIndex() inout {
90 
91         return _children[];
92 
93     }
94     ref auto opIndex(Args...)(Args args) inout {
95 
96         return _children[args];
97 
98     }
99 
100     @property
101     size_t opDollar() const {
102 
103         return _children.length;
104 
105     }
106 
107     ref Node[] getChildren() return {
108 
109         debug assert(!_isLocked, "Can't get a mutable reference to children while rendering. Consider doing this in "
110             ~ "input handling methods like mouseImpl/keyboardImpl which happen after rendering is complete. But if "
111             ~ "this is necessary, you may use `fluid.children.asConst` instead. Note, iterating over the (mutable) "
112             ~ "children is still legal. You can also use `node.remove` if you want to simply remove a node.");
113         _hasChanged = true;
114         return _children;
115 
116     }
117 
118     int opApply(scope int delegate(Node node) @safe dg) {
119 
120         foreach (child; _children) {
121 
122             if (auto result = dg(child)) return result;
123 
124         }
125 
126         return 0;
127 
128     }
129 
130     int opApply(scope int delegate(size_t index, Node node) @safe dg) {
131 
132         foreach (i, child; _children) {
133 
134             if (auto result = dg(i, child)) return result;
135 
136         }
137 
138         return 0;
139 
140     }
141 
142     int opApply(scope int delegate(Node node) @system dg) @system {
143 
144         foreach (child; _children) {
145 
146             if (auto result = dg(child)) return result;
147 
148         }
149 
150         return 0;
151 
152     }
153 
154     int opApply(scope int delegate(size_t index, Node node) @system dg) @system {
155 
156         foreach (i, child; _children) {
157 
158             if (auto result = dg(i, child)) return result;
159 
160         }
161 
162         return 0;
163 
164     }
165 
166     alias getChildren this;
167 
168 }
169 
170 else alias Children = Node[];
171 
172 static assert(isInputRange!Children);
173 
174 /// Make sure the given children list hasn't changed since the dirty bit was last cleared.
175 void assertClean(ref Children children, lazy string message) {
176 
177     debug assert(!children._hasChanged, message);
178 
179 }
180 
181 /// Make sure the given children list hasn't changed since the dirty bit was last cleared.
182 void assertClean(ref Children children) {
183 
184     debug assert(!children._hasChanged);
185 
186 }
187 
188 /// Clear the dirty bit on the given children list
189 void clearDirty(ref Children children) {
190 
191     debug children._hasChanged = false;
192 
193 }
194 
195 pragma(inline)
196 void lock(ref Children children) {
197 
198     debug children._isLocked = true;
199 
200     assertLocked(children);
201 
202 }
203 
204 pragma(inline)
205 void unlock(ref Children children) {
206 
207     debug {
208 
209         assert(children._isLocked, "Already unlocked.");
210 
211         children._isLocked = false;
212 
213     }
214 
215 }
216 
217 pragma(inline)
218 void assertLocked(ref Children children) {
219 
220     debug assert(children._isLocked);
221 
222 }
223 
224 /// Get the children list as const.
225 pragma(inline)
226 const(Node[]) asConst(Children children) {
227 
228     debug return children._children;
229     else  return children;
230 
231 }
232 
233 /// Get a reference to the children list forcefully, ignoring the lock. Doesn't set the dirty flag.
234 pragma(inline)
235 ref Node[] forceMutable(return ref Children children) @system {
236 
237     debug return children._children;
238     else  return children;
239 
240 }