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 }