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 }