1 ///
2 module fluid.structs;
3 
4 import std.conv;
5 import std.traits;
6 
7 import fluid.node;
8 
9 
10 @safe:
11 
12 
13 /// Check if the given type implements node parameter interface.
14 ///
15 /// Node parameters passed at the beginning of a simpleConstructor will not be passed to the node constructor. Instead,
16 /// their `apply` function will be called on the node after the node has been created. This can be used to initialize
17 /// properties at the time of creation. A basic implementation of the interface looks as follows:
18 ///
19 /// ---
20 /// struct MyParameter {
21 ///     void apply(Node node) { }
22 /// }
23 /// ---
24 ///
25 /// Params:
26 ///     T = Type to check
27 //      NodeType = Node to implement.
28 enum isNodeParam(T, NodeType = Node)
29     = __traits(compiles, T.init.apply(NodeType.init));
30 
31 
32 enum NodeAlign {
33 
34     start, center, end, fill,
35     
36     centre = center
37 
38 }
39 
40 /// Create a new layout
41 /// Params:
42 ///     expand = Numerator of the fraction of space this node should occupy in the parent.
43 ///     align_ = Align of the node (horizontal and vertical).
44 ///     alignX = Horizontal align of the node.
45 ///     alignY = Vertical align of the node.
46 Layout layout(uint expand, NodeAlign alignX, NodeAlign alignY) pure {
47 
48     return Layout(expand, [alignX, alignY]);
49 
50 }
51 
52 /// Ditto
53 Layout layout(uint expand, NodeAlign align_) pure {
54 
55     return Layout(expand, align_);
56 
57 }
58 
59 /// Ditto
60 Layout layout(NodeAlign alignX, NodeAlign alignY) pure {
61 
62     return Layout(0, [alignX, alignY]);
63 
64 }
65 
66 /// Ditto
67 Layout layout(NodeAlign align_) pure {
68 
69     return Layout(0, align_);
70 
71 }
72 
73 /// Ditto
74 Layout layout(uint expand) pure {
75 
76     return Layout(expand);
77 
78 }
79 
80 /// CTFE version of the layout constructor, allows using strings instead of enum members, to avoid boilerplate.
81 Layout layout(uint expand, string alignX, string alignY)() pure {
82 
83     enum valueX = alignX.to!NodeAlign;
84     enum valueY = alignY.to!NodeAlign;
85 
86     return Layout(expand, [valueX, valueY]);
87 
88 }
89 
90 /// Ditto
91 Layout layout(uint expand, string align_)() pure {
92 
93     enum valueXY = align_.to!NodeAlign;
94 
95     return Layout(expand, valueXY);
96 
97 }
98 
99 /// Ditto
100 Layout layout(string alignX, string alignY)() pure {
101 
102     enum valueX = alignX.to!NodeAlign;
103     enum valueY = alignY.to!NodeAlign;
104 
105     return Layout(0, [valueX, valueY]);
106 
107 }
108 
109 /// Ditto
110 Layout layout(string align_)() pure {
111 
112     enum valueXY = align_.to!NodeAlign;
113 
114     return Layout(0, valueXY);
115 
116 }
117 
118 /// Ditto
119 Layout layout(uint expand)() pure {
120 
121     return Layout(expand);
122 
123 }
124 
125 unittest {
126 
127     assert(layout!1 == layout(1));
128     assert(layout!("fill") == layout(NodeAlign.fill, NodeAlign.fill));
129     assert(layout!("fill", "fill") == layout(NodeAlign.fill));
130 
131     assert(!__traits(compiles, layout!"expand"));
132     assert(!__traits(compiles, layout!("expand", "noexpand")));
133     assert(!__traits(compiles, layout!(1, "whatever")));
134     assert(!__traits(compiles, layout!(2, "foo", "bar")));
135 
136 }
137 
138 /// Node parameter for setting the node layout.
139 struct Layout {
140 
141     /// Fraction of available space this node should occupy in the node direction.
142     ///
143     /// If set to `0`, the node doesn't have a strict size limit and has size based on content.
144     uint expand;
145 
146     /// Align the content box to a side of the occupied space.
147     NodeAlign[2] nodeAlign;
148 
149     /// Apply this layout to the given node. Implements the node parameter.
150     void apply(Node node) {
151 
152         node.layout = this;
153 
154     }
155 
156     string toString() const {
157 
158         import std.format;
159 
160         const equalAlign = nodeAlign[0] == nodeAlign[1];
161         const startAlign = equalAlign && nodeAlign[0] == NodeAlign.start;
162 
163         if (expand) {
164 
165             if (startAlign) return format!".layout!%s"(expand);
166             else if (equalAlign) return format!".layout!(%s, %s)"(expand, nodeAlign[0]);
167             else return format!".layout!(%s, %s, %s)"(expand, nodeAlign[0], nodeAlign[1]);
168 
169         }
170 
171         else {
172 
173             if (startAlign) return format!"Layout()";
174             else if (equalAlign) return format!".layout!%s"(nodeAlign[0]);
175             else return format!".layout!(%s, %s)"(nodeAlign[0], nodeAlign[1]);
176 
177         }
178 
179     }
180 
181 }
182 
183 /// Tags are optional "marks" left on nodes that are used to apply matching styles. Tags closely resemble
184 /// [HTML classes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class).
185 ///
186 /// Tags have to be explicitly defined before usage by creating an enum and marking it with the `@NodeTag` attribute.
187 /// Such tags can then be applied by passing them to the constructor.
188 enum NodeTag;
189 
190 ///
191 unittest {
192 
193     import fluid.label;
194 
195     @NodeTag
196     enum Tags {
197         myTag,
198     }
199 
200     static assert(isNodeTag!(Tags.myTag));
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 template isNodeTag(alias tag) {
213 
214     // @NodeTag enum Tag;
215     // enum Tag { @NodeTag tag }
216     enum isDirectTag
217         = isSomeEnum!tag
218         && hasUDA!(tag, NodeTag);
219 
220     // @NodeTag enum Enum { tag }
221     static if (isType!tag) 
222         enum isTagMember = false;
223     else 
224         enum isTagMember
225             = is(typeof(tag)== enum)
226             && hasUDA!(typeof(tag), NodeTag);
227 
228     enum isNodeTag = isDirectTag || isTagMember;
229 
230 }
231 
232 /// Test if the given symbol is an enum, or an enum member. 
233 enum isSomeEnum(alias tag) 
234     = is(tag == enum)
235     || is(__traits(parent, tag) == enum);
236 
237 /// Specify tags for the next node to add.
238 TagList tags(input...)() {
239 
240     return TagList.init.add!input;
241 
242 }
243 
244 /// Node parameter assigning a new set of tags to a node.
245 struct TagList {
246 
247     import std.range;
248     import std.algorithm;
249 
250     /// A *sorted* array of tags.
251     private SortedRange!(TagID[]) range;
252 
253     /// Check if the range is empty.
254     bool empty() {
255 
256         return range.empty;
257 
258     }
259 
260     /// Count all tags.
261     size_t length() {
262 
263         return range.length;
264 
265     }
266 
267     /// Get a list of all tags in the list.
268     const(TagID)[] get() {
269 
270         return range.release;
271 
272     }
273 
274     /// Create a new set of tags expanded by the given set of tags.
275     TagList add(input...)() {
276 
277         const originalLength = this.range.length;
278 
279         TagID[input.length] newTags;
280 
281         // Load the tags
282         static foreach (i, tag; input) {
283 
284             newTags[i] = tagID!tag;
285 
286         }
287 
288         // Allocate output range
289         auto result = new TagID[originalLength + input.length];
290         auto lhs = result[0..originalLength] = this.range.release;
291 
292         // Sort the result
293         completeSort(assumeSorted(lhs), newTags[]);
294 
295         // Add the remaining tags
296         result[originalLength..$] = newTags;
297 
298         return TagList(assumeSorted(result));
299 
300     }
301 
302     /// Remove given tags from the list.
303     TagList remove(input...)() {
304 
305         TagID[input.length] targetTags;
306 
307         // Load the tags
308         static foreach (i, tag; input) {
309 
310             targetTags[i] = tagID!tag;
311 
312         }
313 
314         // Sort them
315         sort(targetTags[]);
316 
317         return TagList(
318             setDifference(this.range, targetTags[])
319                 .array
320                 .assumeSorted
321         );
322 
323     }
324 
325     unittest {
326 
327         @NodeTag
328         enum Foo { a, b, c, d }
329 
330         auto myTags = tags!(Foo.a, Foo.b, Foo.c);
331 
332         assert(myTags.remove!(Foo.b, Foo.a) == tags!(Foo.c));
333         assert(myTags.remove!(Foo.d) == myTags);
334         assert(myTags.remove!() == myTags);
335         assert(myTags.remove!(Foo.a, Foo.b, Foo.c) == tags!());
336         assert(myTags.remove!(Foo.a, Foo.b, Foo.c, Foo.d) == tags!());
337 
338     }
339 
340     /// Get the intesection of the two tag lists.
341     /// Returns: A range with tags that are present in both of the lists.
342     auto intersect(TagList tags) {
343 
344         return setIntersection(this.range, tags.range);
345 
346     }
347 
348     /// Assign this list of tags to the given node.
349     void apply(Node node) {
350 
351         node.tags = this;
352 
353     }
354 
355     string toString() {
356 
357         // Prevent writeln from clearing the range
358         return text(range.release);
359 
360     }
361 
362 }
363 
364 unittest {
365 
366     @NodeTag
367     enum singleEnum;
368 
369     assert(isNodeTag!singleEnum);
370 
371     @NodeTag
372     enum Tags { a, b, c }
373 
374     assert(isNodeTag!(Tags.a));
375     assert(isNodeTag!(Tags.b));
376     assert(isNodeTag!(Tags.c));
377 
378     enum NonTags { a, b, c }
379 
380     assert(!isNodeTag!(NonTags.a));
381     assert(!isNodeTag!(NonTags.b));
382     assert(!isNodeTag!(NonTags.c));
383 
384     enum SomeTags { a, b, @NodeTag tag }
385 
386     assert(!isNodeTag!(SomeTags.a));
387     assert(!isNodeTag!(SomeTags.b));
388     assert(isNodeTag!(SomeTags.tag));
389 
390 }
391 
392 unittest {
393 
394     import std.range;
395     import std.algorithm;
396 
397     @NodeTag
398     enum MyTags {
399         tag1, tag2
400     }
401 
402     auto tags1 = tags!(MyTags.tag1, MyTags.tag2);
403     auto tags2 = tags!(MyTags.tag2, MyTags.tag1);
404 
405     assert(tags1.intersect(tags2).walkLength == 2);
406     assert(tags2.intersect(tags1).walkLength == 2);
407     assert(tags1 == tags2);
408 
409     auto tags3 = tags!(MyTags.tag1);
410     auto tags4 = tags!(MyTags.tag2);
411 
412     assert(tags1.intersect(tags3).equal(tagID!(MyTags.tag1).only));
413     assert(tags1.intersect(tags4).equal(tagID!(MyTags.tag2).only));
414     assert(tags3.intersect(tags4).empty);
415 
416 }
417 
418 TagID tagID(alias tag)()
419 out (r; r.id, "Invalid ID returned for tag " ~ tag.stringof)
420 do {
421 
422     enum Tag = TagIDImpl!tag();
423 
424     debug
425         return TagID(cast(long) &Tag._id, fullyQualifiedName!tag);
426     else
427         return TagID(cast(long) &Tag._id);
428 
429 }
430 
431 /// Unique ID of a node tag.
432 struct TagID {
433 
434     /// Unique ID of the tag.
435     long id;
436 
437     invariant(id, "Tag ID must not be 0.");
438 
439     /// Tag name. Only emitted when debugging.
440     debug string name;
441 
442     bool opEqual(TagID other) {
443 
444         return id == other.id;
445 
446     }
447 
448     long opCmp(TagID other) const {
449 
450         return id - other.id;
451 
452     }
453 
454 }
455 
456 private struct TagIDImpl(alias nodeTag)
457 if (isNodeTag!nodeTag) {
458 
459     alias tag = nodeTag;
460 
461     /// Implementation is the same as input action IDs, see fluid.input.InputAction.
462     /// For what's important, the _id field is not the ID; its pointer however, is.
463     private static immutable bool _id;
464 
465 }
466 
467 @("Members of anonymous enums cannot be NodeTags.")
468 unittest {
469 
470     class A {
471         @NodeTag enum { foo }
472     }
473     class B : A {
474         @NodeTag enum { bar }
475     }
476 
477     assert(!__traits(compiles, tagID!(B.foo)));
478     assert(!__traits(compiles, tagID!(B.bar)));
479 
480 }
481 
482 /// This node property will disable mouse input on the given node.
483 /// 
484 /// Params:
485 ///     value = If set to false, the effect is reversed and mouse input is instead enabled.
486 auto ignoreMouse(bool value = true) {
487 
488     static struct IgnoreMouse {
489 
490         bool value;
491 
492         void apply(Node node) {
493 
494             node.ignoreMouse = value;
495 
496         }
497 
498     }
499 
500     return IgnoreMouse(value);
501 
502 }
503 
504 ///
505 unittest {
506 
507     import fluid.label;
508     import fluid.button;
509 
510     // Prevents the label from blocking the button
511     vframeButton(
512         label(.ignoreMouse, "Click me!"),
513         delegate { }
514     );
515 
516 }
517 
518 @("ignoreMouse property sets Node.ignoreMouse to true")
519 unittest {
520 
521     import fluid.space;
522 
523     assert(vspace().ignoreMouse == false);
524     assert(vspace(.ignoreMouse).ignoreMouse == true);
525     assert(vspace(.ignoreMouse(false)).ignoreMouse == false);
526     assert(vspace(.ignoreMouse(true)).ignoreMouse == true);
527 
528 }
529 
530 /// This node property will make the subject hidden, setting the `isHidden` field to true.
531 /// 
532 /// Params:
533 ///     value = If set to false, the effect is reversed and the node is set to be visible instead.
534 /// See_Also: `Node.isHidden`
535 auto hidden(bool value = true) {
536 
537     static struct Hidden {
538 
539         bool value;
540 
541         void apply(Node node) {
542 
543             node.isHidden = value;
544 
545         }
546 
547     }
548 
549     return Hidden(value);
550 
551 }
552 
553 ///
554 unittest {
555 
556     import fluid.label;
557 
558     auto myLabel = label(.hidden, "The user will never see this label");
559     myLabel.draw();  // doesn't draw anything!
560 
561 }
562 
563 /// This node property will disable the subject, setting the `isHidden` field to true.
564 /// 
565 /// Params:
566 ///     value = If set to false, the effect is reversed and the node is set to be enabled instead.
567 /// See_Also: `Node.isDisabled`
568 auto disabled(bool value = true) {
569 
570     static struct Disabled {
571 
572         bool value;
573 
574         void apply(Node node) {
575 
576             node.isDisabled = value;
577 
578         }
579 
580     }
581 
582     return Disabled(value);
583 
584 }
585 
586 unittest {
587 
588     import fluid.button;
589 
590     button(
591         .disabled,
592         "You cannot press this button!",
593         delegate {
594             assert(false);
595         }
596     );
597 
598 
599 }