1 ///
2 module fluid.utils;
3 
4 import std.meta;
5 import std.traits;
6 import std.functional;
7 
8 import fluid.backend;
9 
10 
11 @safe:
12 
13 
14 alias simpleConstructor = nodeBuilder;
15 alias SimpleConstructor = NodeBuilder;
16 alias isSimpleConstructor = isNodeBuilder;
17 
18 deprecated("Use NodeBuilder instead") {
19     alias componentBuilder = nodeBuilder;
20     alias ComponentBuilder = NodeBuilder;
21     alias isComponentBuilder = isNodeBuilder;
22 }
23 
24 // For saner testing and debugging.
25 version (unittest)
26 private extern(C) __gshared string[] rt_options = ["oncycle=ignore"];
27 
28 /// Create a component builder for declarative usage.
29 ///
30 /// Initial properties can be provided in the function provided in the second argument.
31 enum nodeBuilder(T, alias fun = "a") = NodeBuilder!(T, fun).init;
32 
33 /// Create a simple template node constructor for declarative usage.
34 ///
35 /// If the parent is a simple constructor, its initializer will be ran *after* this one. This is because the user
36 /// usually specifies the parent in templates, so it has more importance.
37 ///
38 /// T must be a template accepting a single parameter — Parent type will be passed to it.
39 template nodeBuilder(alias T, alias Parent, alias fun = "a") {
40 
41     alias nodeBuilder = nodeBuilder!(T!(Parent.Type), (a) {
42 
43         alias initializer = unaryFun!fun;
44 
45         initializer(a);
46         Parent.initializer(a);
47 
48     });
49 
50 }
51 
52 /// ditto
53 alias nodeBuilder(alias T, Parent, alias fun = "a") = nodeBuilder!(T!Parent, fun);
54 
55 enum isNodeBuilder(T) = is(T : NodeBuilder!(A, a), A, alias a);
56 
57 struct NodeBuilder(T, alias fun = "a") {
58 
59     import fluid.style;
60     import fluid.structs;
61 
62     alias Type = T;
63     alias initializer = unaryFun!fun;
64 
65     Type opCall(Args...)(Args args) {
66 
67         // Collect parameters
68         enum paramCount = leadingParams!Args;
69 
70         // Construct the node
71         auto result = new Type(args[paramCount..$]);
72 
73         // Run the initializer
74         initializer(result);
75 
76         // Pass the parameters
77         foreach (param; args[0..paramCount]) {
78 
79             param.apply(result);
80 
81         }
82 
83         return result;
84 
85     }
86 
87     /// Count node parameters present at the beginning of the given type list. This function is only available at
88     /// compile-time.
89     ///
90     /// If a node parameter is passed *after* a non-parameter, it will not be included in the count, and will not be
91     /// treated as one by ComponentBuilder.
92     static int leadingParams(Args...)() {
93 
94         assert(__ctfe, "leadingParams is not available at runtime");
95 
96         if (__ctfe)
97         foreach (i, Arg; Args) {
98 
99             // Found a non-parameter, return the index
100             if (!isNodeParam!(Arg, T))
101                 return i;
102 
103         }
104 
105         // All arguments are parameters
106         return Args.length;
107 
108     }
109 
110 }
111 
112 unittest {
113 
114     static class Foo {
115 
116         string value;
117 
118         this() { }
119 
120     }
121 
122     alias xfoo = nodeBuilder!Foo;
123     assert(xfoo().value == "");
124 
125     alias yfoo = nodeBuilder!(Foo, (a) {
126         a.value = "foo";
127     });
128     assert(yfoo().value == "foo");
129 
130     auto myFoo = new Foo;
131     yfoo.initializer(myFoo);
132     assert(myFoo.value == "foo");
133 
134     static class Bar(T) : T {
135 
136         int foo;
137 
138         this(int foo) {
139 
140             this.foo = foo;
141 
142         }
143 
144     }
145 
146     alias xbar(alias T) = nodeBuilder!(Bar, T);
147 
148     const barA = xbar!Foo(1);
149     assert(barA.value == "");
150     assert(barA.foo == 1);
151 
152     const barB = xbar!xfoo(2);
153     assert(barB.value == "");
154     assert(barB.foo == 2);
155 
156     const barC = xbar!yfoo(3);
157     assert(barC.value == "foo");
158     assert(barC.foo == 3);
159 
160 }
161 
162 /// Get distance between two vectors.
163 float distance(Vector2 a, Vector2 b) {
164 
165     import std.math : sqrt;
166 
167     return sqrt(distance2(a, b));
168 
169 }
170 
171 /// Get distance between two vectors, squared.
172 float distance2(Vector2 a, Vector2 b) {
173 
174     return (a.x - b.x)^^2 + (a.y - b.y)^^2;
175 
176 }
177 
178 /// Check if the rectangle contains a point.
179 bool contains(Rectangle rectangle, Vector2 point) {
180 
181     return rectangle.x <= point.x
182         && point.x < rectangle.x + rectangle.width
183         && rectangle.y <= point.y
184         && point.y < rectangle.y + rectangle.height;
185 
186 }
187 
188 /// Check if the two rectangles overlap.
189 bool overlap(Rectangle a, Rectangle b) {
190 
191     const x = start(b).x <= a.x && a.x <= end(b).x
192         ||    start(a).x <= b.x && b.x <= end(a).x;
193     const y = start(b).y <= a.y && a.y <= end(b).y
194         ||    start(a).y <= b.y && b.y <= end(a).y;
195 
196     return x && y;
197 
198 }
199 
200 // Extremely useful Rectangle utilities
201 
202 /// Get the top-left corner of a rectangle.
203 Vector2 start(Rectangle r) => Vector2(r.x, r.y);
204 
205 /// Get the bottom-right corner of a rectangle.
206 Vector2 end(Rectangle r) => Vector2(r.x + r.w, r.y + r.h);
207 
208 /// Get the center of a rectangle.
209 Vector2 center(Rectangle r) => Vector2(r.x + r.w/2, r.y + r.h/2);
210 
211 /// Get the size of a rectangle.
212 Vector2 size(Rectangle r) => Vector2(r.w, r.h);
213 
214 /// Get names of static fields in the given object.
215 ///
216 /// Ignores deprecated fields.
217 template StaticFieldNames(T) {
218 
219     import std.traits : hasStaticMember;
220     import std.meta : Alias, Filter;
221 
222     // Prepare data
223     alias Members = __traits(allMembers, T);
224 
225     template isStaticMember(string member) {
226 
227         enum isStaticMember =
228 
229             // Make sure this isn't an alias
230             __traits(compiles,
231                 Alias!(__traits(getMember, T, member))
232             )
233 
234             && !__traits(isDeprecated, __traits(getMember, T, member))
235 
236             // Find the member
237             && hasStaticMember!(T, member);
238 
239     }
240 
241     // Result
242     alias StaticFieldNames = Filter!(isStaticMember, Members);
243 
244 }
245 
246 /// Open given URL in a web browser.
247 ///
248 /// Supports all major desktop operating systems. Does nothing if not supported on the given platform.
249 ///
250 /// At the moment this simply wraps `std.process.browse`.
251 void openURL(scope const(char)[] url) nothrow {
252 
253     version (Posix) {
254         import std.process;
255         browse(url);
256     }
257     else version (Windows) {
258         import std.process;
259         browse(url);
260     }
261 
262     // Do nothing on remaining platforms
263 
264 }