1 ///
2 module fluid.structs;
3 
4 import std.conv;
5 import std.traits;
6 
7 import fluid.node;
8 import fluid.types;
9 
10 
11 @safe:
12 
13 
14 /// Check if the given type implements node parameter interface.
15 ///
16 /// Node parameters passed at the beginning of a simpleConstructor will not be passed to the node constructor. Instead,
17 /// their `apply` function will be called on the node after the node has been created. This can be used to initialize
18 /// properties at the time of creation. A basic implementation of the interface looks as follows:
19 ///
20 /// ---
21 /// struct MyParameter {
22 ///     void apply(Node node) { }
23 /// }
24 /// ---
25 ///
26 /// Params:
27 ///     T = Type to check
28 //      NodeType = Node to implement.
29 enum isNodeParam(T, NodeType = Node)
30     = __traits(compiles, T.init.apply(NodeType.init));
31 
32 
33 enum NodeAlign {
34 
35     start, center, end, fill,
36 
37     centre = center
38 
39 }
40 
41 /// Create a new layout
42 /// Params:
43 ///     expand = Numerator of the fraction of space this node should occupy in the parent.
44 ///     align_ = Align of the node (horizontal and vertical).
45 ///     alignX = Horizontal align of the node.
46 ///     alignY = Vertical align of the node.
47 Layout layout(uint expand, NodeAlign alignX, NodeAlign alignY) pure {
48 
49     return Layout(expand, [alignX, alignY]);
50 
51 }
52 
53 /// Ditto
54 Layout layout(uint expand, NodeAlign align_) pure {
55 
56     return Layout(expand, align_);
57 
58 }
59 
60 /// Ditto
61 Layout layout(NodeAlign alignX, NodeAlign alignY) pure {
62 
63     return Layout(0, [alignX, alignY]);
64 
65 }
66 
67 /// Ditto
68 Layout layout(NodeAlign align_) pure {
69 
70     return Layout(0, align_);
71 
72 }
73 
74 /// Ditto
75 Layout layout(uint expand) pure {
76 
77     return Layout(expand);
78 
79 }
80 
81 /// CTFE version of the layout constructor, allows using strings instead of enum members, to avoid boilerplate.
82 Layout layout(uint expand, string alignX, string alignY)() pure {
83 
84     enum valueX = alignX.to!NodeAlign;
85     enum valueY = alignY.to!NodeAlign;
86 
87     return Layout(expand, [valueX, valueY]);
88 
89 }
90 
91 /// Ditto
92 Layout layout(uint expand, string align_)() pure {
93 
94     enum valueXY = align_.to!NodeAlign;
95 
96     return Layout(expand, valueXY);
97 
98 }
99 
100 /// Ditto
101 Layout layout(string alignX, string alignY)() pure {
102 
103     enum valueX = alignX.to!NodeAlign;
104     enum valueY = alignY.to!NodeAlign;
105 
106     return Layout(0, [valueX, valueY]);
107 
108 }
109 
110 /// Ditto
111 Layout layout(string align_)() pure {
112 
113     enum valueXY = align_.to!NodeAlign;
114 
115     return Layout(0, valueXY);
116 
117 }
118 
119 /// Ditto
120 Layout layout(uint expand)() pure {
121 
122     return Layout(expand);
123 
124 }
125 
126 unittest {
127 
128     assert(layout!1 == layout(1));
129     assert(layout!("fill") == layout(NodeAlign.fill, NodeAlign.fill));
130     assert(layout!("fill", "fill") == layout(NodeAlign.fill));
131 
132     assert(!__traits(compiles, layout!"expand"));
133     assert(!__traits(compiles, layout!("expand", "noexpand")));
134     assert(!__traits(compiles, layout!(1, "whatever")));
135     assert(!__traits(compiles, layout!(2, "foo", "bar")));
136 
137 }
138 
139 /// Node parameter for setting the node layout.
140 struct Layout {
141 
142     /// Fraction of available space this node should occupy in the node direction.
143     ///
144     /// If set to `0`, the node doesn't have a strict size limit and has size based on content.
145     uint expand;
146 
147     /// Align the content box to a side of the occupied space.
148     NodeAlign[2] nodeAlign;
149 
150     /// Apply this layout to the given node. Implements the node parameter.
151     void apply(Node node) {
152 
153         node.layout = this;
154 
155     }
156 
157     string toString() const {
158 
159         import std.format;
160 
161         const equalAlign = nodeAlign[0] == nodeAlign[1];
162         const startAlign = equalAlign && nodeAlign[0] == NodeAlign.start;
163 
164         if (expand) {
165 
166             if (startAlign) return format!".layout!%s"(expand);
167             else if (equalAlign) return format!".layout!(%s, %s)"(expand, nodeAlign[0]);
168             else return format!".layout!(%s, %s, %s)"(expand, nodeAlign[0], nodeAlign[1]);
169 
170         }
171 
172         else {
173 
174             if (startAlign) return format!"Layout()";
175             else if (equalAlign) return format!".layout!%s"(nodeAlign[0]);
176             else return format!".layout!(%s, %s)"(nodeAlign[0], nodeAlign[1]);
177 
178         }
179 
180     }
181 
182 }
183 
184 /// Place a rectangle inside another based on its specified alignment. This function considers
185 /// `NodeAlign.fill` to be equivalent to `NodeAlign.start`.
186 ///
187 /// This function may commonly be used with `Layout.nodeAlign`.
188 ///
189 /// Params:
190 ///     alignment = Alignment to use for the child rectangle.
191 ///     space     = Rectangle denoting available space (parent).
192 ///     size      = Size of the rectangle to place (child).
193 /// Returns:
194 ///     position = Position assigned to the child rectangle.
195 Vector2 alignRectangle(NodeAlign[2] alignment, Rectangle space, Vector2 size) {
196 
197     float impl(NodeAlign align_, lazy float spaceLeft) {
198         with (NodeAlign)
199         final switch (align_) {
200             case start, fill: return 0;
201             case center:      return spaceLeft / 2;
202             case end:         return spaceLeft;
203         }
204     }
205 
206     return Vector2(
207         space.x + impl(alignment[0], space.width  - size.x),
208         space.y + impl(alignment[1], space.height - size.y),
209     );
210 
211 }
212 
213 /// Tags are optional "marks" left on nodes that are used to apply matching styles. Tags closely resemble
214 /// [HTML classes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class).
215 ///
216 /// Tags have to be explicitly defined before usage by creating an enum and marking it with the `@NodeTag` attribute.
217 /// Such tags can then be applied by passing them to the constructor.
218 enum NodeTag;
219 
220 ///
221 unittest {
222 
223     import fluid.label;
224 
225     @NodeTag
226     enum Tags {
227         myTag,
228     }
229 
230     static assert(isNodeTag!(Tags.myTag));
231 
232     auto myLabel = label(
233         .tags!(Tags.myTag),
234         "Hello, World!"
235     );
236 
237     assert(myLabel.tags == .tags!(Tags.myTag));
238 
239 }
240 
241 /// Check if the given item is a node tag.
242 template isNodeTag(alias tag) {
243 
244     // @NodeTag enum Tag;
245     // enum Tag { @NodeTag tag }
246     enum isDirectTag
247         = isSomeEnum!tag
248         && hasUDA!(tag, NodeTag);
249 
250     // @NodeTag enum Enum { tag }
251     static if (isType!tag)
252         enum isTagMember = false;
253     else
254         enum isTagMember
255             = is(typeof(tag)== enum)
256             && hasUDA!(typeof(tag), NodeTag);
257 
258     enum isNodeTag = isDirectTag || isTagMember;
259 
260 }
261 
262 /// Test if the given symbol is an enum, or an enum member.
263 enum isSomeEnum(alias tag)
264     = is(tag == enum)
265     || is(__traits(parent, tag) == enum);
266 
267 /// Specify tags for the next node to add.
268 TagList tags(input...)() {
269 
270     return TagList.init.add!input;
271 
272 }
273 
274 /// Node parameter assigning a new set of tags to a node.
275 struct TagList {
276 
277     import std.range;
278     import std.algorithm;
279 
280     /// A *sorted* array of tags.
281     private SortedRange!(TagID[]) range;
282 
283     /// Check if the range is empty.
284     bool empty() {
285 
286         return range.empty;
287 
288     }
289 
290     /// Count all tags.
291     size_t length() {
292 
293         return range.length;
294 
295     }
296 
297     /// Get a list of all tags in the list.
298     const(TagID)[] get() {
299 
300         return range.release;
301 
302     }
303 
304     /// Create a new set of tags expanded by the given set of tags.
305     TagList add(input...)() {
306 
307         const originalLength = this.range.length;
308 
309         TagID[input.length] newTags;
310 
311         // Load the tags
312         static foreach (i, tag; input) {
313 
314             newTags[i] = tagID!tag;
315 
316         }
317 
318         // Allocate output range
319         auto result = new TagID[originalLength + input.length];
320         auto lhs = result[0..originalLength] = this.range.release;
321 
322         // Sort the result
323         completeSort(assumeSorted(lhs), newTags[]);
324 
325         // Add the remaining tags
326         result[originalLength..$] = newTags;
327 
328         return TagList(assumeSorted(result));
329 
330     }
331 
332     /// Remove given tags from the list.
333     TagList remove(input...)() {
334 
335         TagID[input.length] targetTags;
336 
337         // Load the tags
338         static foreach (i, tag; input) {
339 
340             targetTags[i] = tagID!tag;
341 
342         }
343 
344         // Sort them
345         sort(targetTags[]);
346 
347         return TagList(
348             setDifference(this.range, targetTags[])
349                 .array
350                 .assumeSorted
351         );
352 
353     }
354 
355     unittest {
356 
357         @NodeTag
358         enum Foo { a, b, c, d }
359 
360         auto myTags = tags!(Foo.a, Foo.b, Foo.c);
361 
362         assert(myTags.remove!(Foo.b, Foo.a) == tags!(Foo.c));
363         assert(myTags.remove!(Foo.d) == myTags);
364         assert(myTags.remove!() == myTags);
365         assert(myTags.remove!(Foo.a, Foo.b, Foo.c) == tags!());
366         assert(myTags.remove!(Foo.a, Foo.b, Foo.c, Foo.d) == tags!());
367 
368     }
369 
370     /// Get the intesection of the two tag lists.
371     /// Returns: A range with tags that are present in both of the lists.
372     auto intersect(TagList tags) {
373 
374         return setIntersection(this.range, tags.range);
375 
376     }
377 
378     /// Assign this list of tags to the given node.
379     void apply(Node node) {
380 
381         node.tags = this;
382 
383     }
384 
385     string toString() {
386 
387         // Prevent writeln from clearing the range
388         return text(range.release);
389 
390     }
391 
392 }
393 
394 unittest {
395 
396     @NodeTag
397     enum singleEnum;
398 
399     assert(isNodeTag!singleEnum);
400 
401     @NodeTag
402     enum Tags { a, b, c }
403 
404     assert(isNodeTag!(Tags.a));
405     assert(isNodeTag!(Tags.b));
406     assert(isNodeTag!(Tags.c));
407 
408     enum NonTags { a, b, c }
409 
410     assert(!isNodeTag!(NonTags.a));
411     assert(!isNodeTag!(NonTags.b));
412     assert(!isNodeTag!(NonTags.c));
413 
414     enum SomeTags { a, b, @NodeTag tag }
415 
416     assert(!isNodeTag!(SomeTags.a));
417     assert(!isNodeTag!(SomeTags.b));
418     assert(isNodeTag!(SomeTags.tag));
419 
420 }
421 
422 unittest {
423 
424     import std.range;
425     import std.algorithm;
426 
427     @NodeTag
428     enum MyTags {
429         tag1, tag2
430     }
431 
432     auto tags1 = tags!(MyTags.tag1, MyTags.tag2);
433     auto tags2 = tags!(MyTags.tag2, MyTags.tag1);
434 
435     assert(tags1.intersect(tags2).walkLength == 2);
436     assert(tags2.intersect(tags1).walkLength == 2);
437     assert(tags1 == tags2);
438 
439     auto tags3 = tags!(MyTags.tag1);
440     auto tags4 = tags!(MyTags.tag2);
441 
442     assert(tags1.intersect(tags3).equal(tagID!(MyTags.tag1).only));
443     assert(tags1.intersect(tags4).equal(tagID!(MyTags.tag2).only));
444     assert(tags3.intersect(tags4).empty);
445 
446 }
447 
448 TagID tagID(alias tag)()
449 out (r; r.id, "Invalid ID returned for tag " ~ tag.stringof)
450 do {
451 
452     enum Tag = TagIDImpl!tag();
453 
454     debug
455         return TagID(cast(long) &Tag._id, fullyQualifiedName!tag);
456     else
457         return TagID(cast(long) &Tag._id);
458 
459 }
460 
461 /// Unique ID of a node tag.
462 struct TagID {
463 
464     /// Unique ID of the tag.
465     long id;
466 
467     invariant(id, "Tag ID must not be 0.");
468 
469     /// Tag name. Only emitted when debugging.
470     debug string name;
471 
472     bool opEqual(TagID other) {
473 
474         return id == other.id;
475 
476     }
477 
478     long opCmp(TagID other) const {
479 
480         return id - other.id;
481 
482     }
483 
484 }
485 
486 private struct TagIDImpl(alias nodeTag)
487 if (isNodeTag!nodeTag) {
488 
489     alias tag = nodeTag;
490 
491     /// Implementation is the same as input action IDs, see fluid.input.InputAction.
492     /// For what's important, the _id field is not the ID; its pointer however, is.
493     private static immutable bool _id;
494 
495 }
496 
497 @("Members of anonymous enums cannot be NodeTags.")
498 unittest {
499 
500     class A {
501         @NodeTag enum { foo }
502     }
503     class B : A {
504         @NodeTag enum { bar }
505     }
506 
507     assert(!__traits(compiles, tagID!(B.foo)));
508     assert(!__traits(compiles, tagID!(B.bar)));
509 
510 }
511 
512 /// This node property will disable mouse input on the given node.
513 ///
514 /// Params:
515 ///     value = If set to false, the effect is reversed and mouse input is instead enabled.
516 auto ignoreMouse(bool value = true) {
517 
518     static struct IgnoreMouse {
519 
520         bool value;
521 
522         void apply(Node node) {
523 
524             node.ignoreMouse = value;
525 
526         }
527 
528     }
529 
530     return IgnoreMouse(value);
531 
532 }
533 
534 ///
535 unittest {
536 
537     import fluid.label;
538     import fluid.button;
539 
540     // Prevents the label from blocking the button
541     vframeButton(
542         label(.ignoreMouse, "Click me!"),
543         delegate { }
544     );
545 
546 }
547 
548 @("ignoreMouse property sets Node.ignoreMouse to true")
549 unittest {
550 
551     import fluid.space;
552 
553     assert(vspace().ignoreMouse == false);
554     assert(vspace(.ignoreMouse).ignoreMouse == true);
555     assert(vspace(.ignoreMouse(false)).ignoreMouse == false);
556     assert(vspace(.ignoreMouse(true)).ignoreMouse == true);
557 
558 }
559 
560 /// This node property will make the subject hidden, setting the `isHidden` field to true.
561 ///
562 /// Params:
563 ///     value = If set to false, the effect is reversed and the node is set to be visible instead.
564 /// See_Also: `Node.isHidden`
565 auto hidden(bool value = true) {
566 
567     static struct Hidden {
568 
569         bool value;
570 
571         void apply(Node node) {
572 
573             node.isHidden = value;
574 
575         }
576 
577     }
578 
579     return Hidden(value);
580 
581 }
582 
583 ///
584 unittest {
585 
586     import fluid.label;
587 
588     auto myLabel = label(.hidden, "The user will never see this label");
589     myLabel.draw();  // doesn't draw anything!
590 
591 }
592 
593 /// This node property will disable the subject, setting the `isHidden` field to true.
594 ///
595 /// Params:
596 ///     value = If set to false, the effect is reversed and the node is set to be enabled instead.
597 /// See_Also: `Node.isDisabled`
598 auto disabled(bool value = true) {
599 
600     static struct Disabled {
601 
602         bool value;
603 
604         void apply(Node node) {
605 
606             node.isDisabled = value;
607 
608         }
609 
610     }
611 
612     return Disabled(value);
613 
614 }
615 
616 unittest {
617 
618     import fluid.button;
619 
620     button(
621         .disabled,
622         "You cannot press this button!",
623         delegate {
624             assert(false);
625         }
626     );
627 
628 
629 }
630 
631 /// `InBounds` is used as a return value of `Node.inBounds`. For most use-cases,
632 /// `InBounds.yes` and `InBounds.no` are the most appropriate, specifying that the point in
633 /// question is, or is not, in the node's bounds. This defines the way nodes interact with
634 /// mouse, touchscreen or other hover events (`fluid.io.hover`).
635 ///
636 /// The node is not normally responsible for the bounds of its children. The node's should
637 /// only specify its own bounds, so neither a `yes` or `no` answer prevent children nodes
638 /// from overriding the answer.
639 ///
640 /// Despite the above, it is sometimes desirable to keep children from occupying space, for
641 /// example to hijack and control mouse input. To specify that children nodes *cannot* be in
642 /// bounds, use `InBounds.notInBranch` (to indicate none of the nodes include the point) or
643 /// `InBounds.onlySelf` (the node captures all events, including of its children).
644 ///
645 /// See_Also:
646 ///     `Node.inBounds`.
647 enum IsOpaque : IsOpaqueMask {
648 
649     /// The point is in bounds of this node.
650     yes         = IsOpaqueMask(0),
651 
652     /// The point is *not* in bounds of this node.
653     no          = IsOpaqueMask(1),
654 
655     /// The point is in bounds, but not in the bounds of any of the children nodes.
656     onlySelf    = IsOpaqueMask(2),
657 
658     /// Indicates that the point is *not* in bounds of any of the nodes in the branch; neither
659     /// of self, nor any of the children nodes.
660     notInBranch = IsOpaqueMask(3),
661 }
662 
663 /// This bitmask defines whether a node contains a point in its boundaries.
664 ///
665 /// To allow this to default to `InBounds.yes` while being
666 /// [zero-initialized](https://dlang.org/spec/traits.html#isZeroInit), each bit is inverted;
667 /// i.e. `0` means *yes, in bounds* and `1` means, *no, not in bounds*.
668 ///
669 /// See_Also:
670 ///     `InBounds` for all possible values of this bitmask.
671 ///     `Node.inBounds` for a function returning this value.
672 struct IsOpaqueMask {
673     int bitmask;
674 
675     /// Returns:
676     ///     True if the queried point can be found in the node itself.
677     bool inSelf() const {
678         return (bitmask & 1) == 0;
679     }
680 
681     /// Returns:
682     ///     True if the queried point may or may not be found in the children of the node.
683     ///     A false value indicates that the point will not be in any of the children nodes,
684     ///     and that children nodes should not be tested.
685     bool inChildren() const {
686         return (bitmask & 2) == 0;
687     }
688 
689     /// Create a value that combines the restrictions of both masks. It can be said that either
690     /// of the masks acts as a "filter", hence the name.
691     ///
692     /// For example, combining `IsOpaque.yes` with `IsOpaque.no` returns `IsOpaque.no`.
693     /// Combining `IsOpaque.no` with `IsOpaque.onlySelf` returns `IsOpaque.notInBranch`.
694     ///
695     /// Params:
696     ///     other = Mask to combine with.
697     /// Returns:
698     ///     A mask with `inSelf == false` if false for either of the masks,
699     ///     and similarly `inChildren == false` if false for either of the values.
700     IsOpaque filter(IsOpaqueMask other) const {
701         return cast(IsOpaque) IsOpaqueMask((bitmask | other.bitmask) & 3);
702     }
703 
704     /// Set the node's opacity filter. This can be used as a node property — an opacity mask
705     /// can be passed to a node builder.
706     /// Params:
707     ///     node = Node to change.
708     void apply(Node node) {
709         node.isOpaque = cast(IsOpaque) IsOpaqueMask(bitmask & 3);
710     }
711 }
712 
713 static assert(IsOpaque.init == IsOpaque.yes);
714 static assert(!IsOpaque.no.inSelf);
715 static assert( IsOpaque.no.inChildren);
716 static assert( IsOpaque.yes.inSelf);
717 static assert( IsOpaque.yes.inChildren);
718 static assert(!IsOpaque.notInBranch.inSelf);
719 static assert(!IsOpaque.notInBranch.inChildren);
720 static assert( IsOpaque.onlySelf.inSelf);
721 static assert(!IsOpaque.onlySelf.inChildren);
722 static assert(IsOpaque.yes.filter(IsOpaque.no) == IsOpaque.no);
723 static assert(IsOpaque.no.filter(IsOpaque.onlySelf) == IsOpaque.notInBranch);