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     auto myLabel = label(
201         .tags!(Tags.myTag),
202         "Hello, World!"
203     );
204 
205     assert(myLabel.tags == .tags!(Tags.myTag));
206 
207 }
208 
209 /// Check if the given item is a node tag.
210 enum isNodeTag(alias tag)
211     // @NodeTag enum Tag;
212     // enum Tag { @NodeTag tag }
213     = (isSomeEnum!tag
214       && hasUDA!(tag, NodeTag)) 
215 
216     // @NodeTag enum Enum { tag }
217     || (!isType!tag 
218         && is(__traits(parent, tag) == enum)
219         && hasUDA!(typeof(tag), NodeTag));
220 
221 /// Test if the given symbol is an enum, or an enum member. 
222 enum isSomeEnum(alias tag) 
223     = is(tag == enum)
224     || is(__traits(parent, tag) == enum);
225 
226 /// Specify tags for the next node to add.
227 TagList tags(input...)() {
228 
229     return TagList.init.add!input;
230 
231 }
232 
233 /// Node parameter assigning a new set of tags to a node.
234 struct TagList {
235 
236     import std.range;
237     import std.algorithm;
238 
239     /// A *sorted* array of tags.
240     private SortedRange!(TagID[]) range;
241 
242     /// Check if the range is empty.
243     bool empty() {
244 
245         return range.empty;
246 
247     }
248 
249     /// Count all tags.
250     size_t length() {
251 
252         return range.length;
253 
254     }
255 
256     /// Get a list of all tags in the list.
257     const(TagID)[] get() {
258 
259         return range.release;
260 
261     }
262 
263     /// Create a new set of tags expanded by the given set of tags.
264     TagList add(input...)() {
265 
266         const originalLength = this.range.length;
267 
268         TagID[input.length] newTags;
269 
270         // Load the tags
271         static foreach (i, tag; input) {
272 
273             newTags[i] = tagID!tag;
274 
275         }
276 
277         // Allocate output range
278         auto result = new TagID[originalLength + input.length];
279         auto lhs = result[0..originalLength] = this.range.release;
280 
281         // Sort the result
282         completeSort(assumeSorted(lhs), newTags[]);
283 
284         // Add the remaining tags
285         result[originalLength..$] = newTags;
286 
287         return TagList(assumeSorted(result));
288 
289     }
290 
291     /// Remove given tags from the list.
292     TagList remove(input...)() {
293 
294         TagID[input.length] targetTags;
295 
296         // Load the tags
297         static foreach (i, tag; input) {
298 
299             targetTags[i] = tagID!tag;
300 
301         }
302 
303         // Sort them
304         sort(targetTags[]);
305 
306         return TagList(
307             setDifference(this.range, targetTags[])
308                 .array
309                 .assumeSorted
310         );
311 
312     }
313 
314     unittest {
315 
316         @NodeTag
317         enum Foo { a, b, c, d }
318 
319         auto myTags = tags!(Foo.a, Foo.b, Foo.c);
320 
321         assert(myTags.remove!(Foo.b, Foo.a) == tags!(Foo.c));
322         assert(myTags.remove!(Foo.d) == myTags);
323         assert(myTags.remove!() == myTags);
324         assert(myTags.remove!(Foo.a, Foo.b, Foo.c) == tags!());
325         assert(myTags.remove!(Foo.a, Foo.b, Foo.c, Foo.d) == tags!());
326 
327     }
328 
329     /// Get the intesection of the two tag lists.
330     /// Returns: A range with tags that are present in both of the lists.
331     auto intersect(TagList tags) {
332 
333         return setIntersection(this.range, tags.range);
334 
335     }
336 
337     /// Assign this list of tags to the given node.
338     void apply(Node node) {
339 
340         node.tags = this;
341 
342     }
343 
344     string toString() {
345 
346         // Prevent writeln from clearing the range
347         return text(range.release);
348 
349     }
350 
351 }
352 
353 unittest {
354 
355     @NodeTag
356     enum singleEnum;
357 
358     assert(isNodeTag!singleEnum);
359 
360     @NodeTag
361     enum Tags { a, b, c }
362 
363     assert(isNodeTag!(Tags.a));
364     assert(isNodeTag!(Tags.b));
365     assert(isNodeTag!(Tags.c));
366 
367     enum NonTags { a, b, c }
368 
369     assert(!isNodeTag!(NonTags.a));
370     assert(!isNodeTag!(NonTags.b));
371     assert(!isNodeTag!(NonTags.c));
372 
373     enum SomeTags { a, b, @NodeTag tag }
374 
375     assert(!isNodeTag!(SomeTags.a));
376     assert(!isNodeTag!(SomeTags.b));
377     assert(isNodeTag!(SomeTags.tag));
378 
379 }
380 
381 unittest {
382 
383     import std.range;
384     import std.algorithm;
385 
386     @NodeTag
387     enum MyTags {
388         tag1, tag2
389     }
390 
391     auto tags1 = tags!(MyTags.tag1, MyTags.tag2);
392     auto tags2 = tags!(MyTags.tag2, MyTags.tag1);
393 
394     assert(tags1.intersect(tags2).walkLength == 2);
395     assert(tags2.intersect(tags1).walkLength == 2);
396     assert(tags1 == tags2);
397 
398     auto tags3 = tags!(MyTags.tag1);
399     auto tags4 = tags!(MyTags.tag2);
400 
401     assert(tags1.intersect(tags3).equal(tagID!(MyTags.tag1).only));
402     assert(tags1.intersect(tags4).equal(tagID!(MyTags.tag2).only));
403     assert(tags3.intersect(tags4).empty);
404 
405 }
406 
407 TagID tagID(alias tag)()
408 out (r; r.id, "Invalid ID returned for tag " ~ tag.stringof)
409 do {
410 
411     enum Tag = TagIDImpl!tag();
412 
413     debug
414         return TagID(cast(long) &Tag._id, fullyQualifiedName!tag);
415     else
416         return TagID(cast(long) &Tag._id);
417 
418 }
419 
420 /// Unique ID of a node tag.
421 struct TagID {
422 
423     /// Unique ID of the tag.
424     long id;
425 
426     invariant(id, "Tag ID must not be 0.");
427 
428     /// Tag name. Only emitted when debugging.
429     debug string name;
430 
431     bool opEqual(TagID other) {
432 
433         return id == other.id;
434 
435     }
436 
437     long opCmp(TagID other) const {
438 
439         return id - other.id;
440 
441     }
442 
443 }
444 
445 private struct TagIDImpl(alias nodeTag)
446 if (isNodeTag!nodeTag) {
447 
448     alias tag = nodeTag;
449 
450     /// Implementation is the same as input action IDs, see fluid.input.InputAction.
451     /// For what's important, the _id field is not the ID; its pointer however, is.
452     private static immutable bool _id;
453 
454 }
455 
456 @("Members of anonymous enums cannot be NodeTags.")
457 unittest {
458 
459     class A {
460         @NodeTag enum { foo }
461     }
462     class B : A {
463         @NodeTag enum { bar }
464     }
465 
466     assert(!__traits(compiles, tagID!(B.foo)));
467     assert(!__traits(compiles, tagID!(B.bar)));
468 
469 }
470 
471 /// This node property will disable mouse input on the given node.
472 /// 
473 /// Params:
474 ///     value = If set to false, the effect is reversed and mouse input is instead enabled.
475 auto ignoreMouse(bool value = true) {
476 
477     static struct IgnoreMouse {
478 
479         bool value;
480 
481         void apply(Node node) {
482 
483             node.ignoreMouse = value;
484 
485         }
486 
487     }
488 
489     return IgnoreMouse(value);
490 
491 }
492 
493 ///
494 unittest {
495 
496     import fluid.label;
497     import fluid.button;
498 
499     // Prevents the label from blocking the button
500     vframeButton(
501         label(.ignoreMouse, "Click me!"),
502         delegate { }
503     );
504 
505 }
506 
507 @("ignoreMouse property sets Node.ignoreMouse to true")
508 unittest {
509 
510     import fluid.space;
511 
512     assert(vspace().ignoreMouse == false);
513     assert(vspace(.ignoreMouse).ignoreMouse == true);
514     assert(vspace(.ignoreMouse(false)).ignoreMouse == false);
515     assert(vspace(.ignoreMouse(true)).ignoreMouse == true);
516 
517 }
518 
519 /// This node property will make the subject hidden, setting the `isHidden` field to true.
520 /// 
521 /// Params:
522 ///     value = If set to false, the effect is reversed and the node is set to be visible instead.
523 /// See_Also: `Node.isHidden`
524 auto hidden(bool value = true) {
525 
526     static struct Hidden {
527 
528         bool value;
529 
530         void apply(Node node) {
531 
532             node.isHidden = value;
533 
534         }
535 
536     }
537 
538     return Hidden(value);
539 
540 }
541 
542 ///
543 unittest {
544 
545     import fluid.label;
546 
547     auto myLabel = label(.hidden, "The user will never see this label");
548     myLabel.draw();  // doesn't draw anything!
549 
550 }
551 
552 /// This node property will disable the subject, setting the `isHidden` field to true.
553 /// 
554 /// Params:
555 ///     value = If set to false, the effect is reversed and the node is set to be enabled instead.
556 /// See_Also: `Node.isDisabled`
557 auto disabled(bool value = true) {
558 
559     static struct Disabled {
560 
561         bool value;
562 
563         void apply(Node node) {
564 
565             node.isDisabled = value;
566 
567         }
568 
569     }
570 
571     return Disabled(value);
572 
573 }
574 
575 unittest {
576 
577     import fluid.button;
578 
579     button(
580         .disabled,
581         "You cannot press this button!",
582         delegate {
583             assert(false);
584         }
585     );
586 
587 
588 }