1 /// 2 module fluid.structs; 3 4 import std.conv; 5 import std.traits; 6 7 import fluid.node; 8 9 import fluid.node; 10 11 12 @safe: 13 14 15 /// Check if the given type implements node parameter interface. 16 /// 17 /// Node parameters passed at the beginning of a simpleConstructor will not be passed to the node constructor. Instead, 18 /// their `apply` function will be called on the node after the node has been created. This can be used to initialize 19 /// properties at the time of creation. A basic implementation of the interface looks as follows: 20 /// 21 /// --- 22 /// struct MyParameter { 23 /// void apply(Node node) { } 24 /// } 25 /// --- 26 /// 27 /// Params: 28 /// T = Type to check 29 // NodeType = Node to implement. 30 enum isNodeParam(T, NodeType = Node) 31 = __traits(compiles, T.init.apply(NodeType.init)); 32 33 34 enum NodeAlign { 35 36 start, center, end, fill, 37 38 centre = center 39 40 } 41 42 /// Create a new layout 43 /// Params: 44 /// expand = Numerator of the fraction of space this node should occupy in the parent. 45 /// align_ = Align of the node (horizontal and vertical). 46 /// alignX = Horizontal align of the node. 47 /// alignY = Vertical align of the node. 48 Layout layout(uint expand, NodeAlign alignX, NodeAlign alignY) pure { 49 50 return Layout(expand, [alignX, alignY]); 51 52 } 53 54 /// Ditto 55 Layout layout(uint expand, NodeAlign align_) pure { 56 57 return Layout(expand, align_); 58 59 } 60 61 /// Ditto 62 Layout layout(NodeAlign alignX, NodeAlign alignY) pure { 63 64 return Layout(0, [alignX, alignY]); 65 66 } 67 68 /// Ditto 69 Layout layout(NodeAlign align_) pure { 70 71 return Layout(0, align_); 72 73 } 74 75 /// Ditto 76 Layout layout(uint expand) pure { 77 78 return Layout(expand); 79 80 } 81 82 /// CTFE version of the layout constructor, allows using strings instead of enum members, to avoid boilerplate. 83 Layout layout(uint expand, string alignX, string alignY)() pure { 84 85 enum valueX = alignX.to!NodeAlign; 86 enum valueY = alignY.to!NodeAlign; 87 88 return Layout(expand, [valueX, valueY]); 89 90 } 91 92 /// Ditto 93 Layout layout(uint expand, string align_)() pure { 94 95 enum valueXY = align_.to!NodeAlign; 96 97 return Layout(expand, valueXY); 98 99 } 100 101 /// Ditto 102 Layout layout(string alignX, string alignY)() pure { 103 104 enum valueX = alignX.to!NodeAlign; 105 enum valueY = alignY.to!NodeAlign; 106 107 return Layout(0, [valueX, valueY]); 108 109 } 110 111 /// Ditto 112 Layout layout(string align_)() pure { 113 114 enum valueXY = align_.to!NodeAlign; 115 116 return Layout(0, valueXY); 117 118 } 119 120 /// Ditto 121 Layout layout(uint expand)() pure { 122 123 return Layout(expand); 124 125 } 126 127 unittest { 128 129 assert(layout!1 == layout(1)); 130 assert(layout!("fill") == layout(NodeAlign.fill, NodeAlign.fill)); 131 assert(layout!("fill", "fill") == layout(NodeAlign.fill)); 132 133 assert(!__traits(compiles, layout!"expand")); 134 assert(!__traits(compiles, layout!("expand", "noexpand"))); 135 assert(!__traits(compiles, layout!(1, "whatever"))); 136 assert(!__traits(compiles, layout!(2, "foo", "bar"))); 137 138 } 139 140 /// Node parameter for setting the node layout. 141 struct Layout { 142 143 /// Fraction of available space this node should occupy in the node direction. 144 /// 145 /// If set to `0`, the node doesn't have a strict size limit and has size based on content. 146 uint expand; 147 148 /// Align the content box to a side of the occupied space. 149 NodeAlign[2] nodeAlign; 150 151 /// Apply this layout to the given node. Implements the node parameter. 152 void apply(Node node) { 153 154 node.layout = this; 155 156 } 157 158 string toString() const { 159 160 import std.format; 161 162 const equalAlign = nodeAlign[0] == nodeAlign[1]; 163 const startAlign = equalAlign && nodeAlign[0] == NodeAlign.start; 164 165 if (expand) { 166 167 if (startAlign) return format!".layout!%s"(expand); 168 else if (equalAlign) return format!".layout!(%s, %s)"(expand, nodeAlign[0]); 169 else return format!".layout!(%s, %s, %s)"(expand, nodeAlign[0], nodeAlign[1]); 170 171 } 172 173 else { 174 175 if (startAlign) return format!"Layout()"; 176 else if (equalAlign) return format!".layout!%s"(nodeAlign[0]); 177 else return format!".layout!(%s, %s)"(nodeAlign[0], nodeAlign[1]); 178 179 } 180 181 } 182 183 } 184 185 /// Tags are optional "marks" left on nodes that are used to apply matching styles. Tags closely resemble 186 /// [HTML classes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class). 187 /// 188 /// Tags have to be explicitly defined before usage, by creating an enum and marking it with the `@NodeTag` attribute. 189 /// Such tags can then be applied by passing them to the constructor. 190 enum NodeTag; 191 192 /// 193 unittest { 194 195 import fluid.label; 196 197 @NodeTag 198 enum Tags { 199 myTag, 200 } 201 202 auto myLabel = label( 203 .tags!(Tags.myTag), 204 "Hello, World!" 205 ); 206 207 assert(myLabel.tags == .tags!(Tags.myTag)); 208 209 } 210 211 /// Check if the given item is a node tag. 212 enum isNodeTag(alias tag) 213 = hasUDA!(tag, NodeTag) 214 || (!isType!tag && hasUDA!(typeof(tag), NodeTag)); 215 216 /// Specify tags for the next node to add. 217 TagList tags(input...)() { 218 219 return TagList.init.add!input; 220 221 } 222 223 /// Node parameter assigning a new set of tags to a node. 224 struct TagList { 225 226 import std.range; 227 import std.algorithm; 228 229 /// A *sorted* array of tags. 230 private SortedRange!(TagID[]) range; 231 232 /// Check if the range is empty. 233 bool empty() { 234 235 return range.empty; 236 237 } 238 239 /// Count all tags. 240 size_t length() { 241 242 return range.length; 243 244 } 245 246 /// Get a list of all tags in the list. 247 const(TagID)[] get() { 248 249 return range.release; 250 251 } 252 253 /// Create a new set of tags expanded by the given set of tags. 254 TagList add(input...)() { 255 256 const originalLength = this.range.length; 257 258 TagID[input.length] newTags; 259 260 // Load the tags 261 static foreach (i, tag; input) { 262 263 newTags[i] = tagID!tag; 264 265 } 266 267 // Allocate output range 268 auto result = new TagID[originalLength + input.length]; 269 auto lhs = result[0..originalLength] = this.range.release; 270 271 // Sort the result 272 completeSort(assumeSorted(lhs), newTags[]); 273 274 // Add the remaining tags 275 result[originalLength..$] = newTags; 276 277 return TagList(assumeSorted(result)); 278 279 } 280 281 /// Remove given tags from the list. 282 TagList remove(input...)() { 283 284 TagID[input.length] targetTags; 285 286 // Load the tags 287 static foreach (i, tag; input) { 288 289 targetTags[i] = tagID!tag; 290 291 } 292 293 // Sort them 294 sort(targetTags[]); 295 296 return TagList( 297 setDifference(this.range, targetTags[]) 298 .array 299 .assumeSorted 300 ); 301 302 } 303 304 unittest { 305 306 @NodeTag 307 enum Foo { a, b, c, d } 308 309 auto myTags = tags!(Foo.a, Foo.b, Foo.c); 310 311 assert(myTags.remove!(Foo.b, Foo.a) == tags!(Foo.c)); 312 assert(myTags.remove!(Foo.d) == myTags); 313 assert(myTags.remove!() == myTags); 314 assert(myTags.remove!(Foo.a, Foo.b, Foo.c) == tags!()); 315 assert(myTags.remove!(Foo.a, Foo.b, Foo.c, Foo.d) == tags!()); 316 317 } 318 319 /// Get the intesection of the two tag lists. 320 /// Returns: A range with tags that are present in both of the lists. 321 auto intersect(TagList tags) { 322 323 return setIntersection(this.range, tags.range); 324 325 } 326 327 /// Assign this list of tags to the given node. 328 void apply(Node node) { 329 330 node.tags = this; 331 332 } 333 334 string toString() { 335 336 // Prevent writeln from clearing the range 337 return text(range.release); 338 339 } 340 341 } 342 343 unittest { 344 345 @NodeTag 346 enum singleEnum; 347 348 assert(isNodeTag!singleEnum); 349 350 @NodeTag 351 enum Tags { a, b, c } 352 353 assert(isNodeTag!(Tags.a)); 354 assert(isNodeTag!(Tags.b)); 355 assert(isNodeTag!(Tags.c)); 356 357 enum NonTags { a, b, c } 358 359 assert(!isNodeTag!(NonTags.a)); 360 assert(!isNodeTag!(NonTags.b)); 361 assert(!isNodeTag!(NonTags.c)); 362 363 enum SomeTags { a, b, @NodeTag tag } 364 365 assert(!isNodeTag!(SomeTags.a)); 366 assert(!isNodeTag!(SomeTags.b)); 367 assert(isNodeTag!(SomeTags.tag)); 368 369 } 370 371 unittest { 372 373 import std.range; 374 import std.algorithm; 375 376 @NodeTag 377 enum MyTags { 378 tag1, tag2 379 } 380 381 auto tags1 = tags!(MyTags.tag1, MyTags.tag2); 382 auto tags2 = tags!(MyTags.tag2, MyTags.tag1); 383 384 assert(tags1.intersect(tags2).walkLength == 2); 385 assert(tags2.intersect(tags1).walkLength == 2); 386 assert(tags1 == tags2); 387 388 auto tags3 = tags!(MyTags.tag1); 389 auto tags4 = tags!(MyTags.tag2); 390 391 assert(tags1.intersect(tags3).equal(tagID!(MyTags.tag1).only)); 392 assert(tags1.intersect(tags4).equal(tagID!(MyTags.tag2).only)); 393 assert(tags3.intersect(tags4).empty); 394 395 } 396 397 TagID tagID(alias tag)() 398 out (r; r.id, "Invalid ID returned for tag " ~ tag.stringof) 399 do { 400 401 enum Tag = TagIDImpl!tag(); 402 403 debug 404 return TagID(cast(long) &Tag._id, fullyQualifiedName!tag); 405 else 406 return TagID(cast(long) &Tag._id); 407 408 } 409 410 /// Unique ID of a node tag. 411 struct TagID { 412 413 /// Unique ID of the tag. 414 long id; 415 416 invariant(id, "Tag ID must not be 0."); 417 418 /// Tag name. Only emitted when debugging. 419 debug string name; 420 421 bool opEqual(TagID other) { 422 423 return id == other.id; 424 425 } 426 427 long opCmp(TagID other) const { 428 429 return id - other.id; 430 431 } 432 433 } 434 435 private struct TagIDImpl(alias nodeTag) 436 if (isNodeTag!nodeTag) { 437 438 alias tag = nodeTag; 439 440 /// Implementation is the same as input action IDs, see fluid.input.InputAction. 441 /// For what's important, the _id field is not the ID; its pointer however, is. 442 align(1) 443 private static immutable bool _id; 444 445 }