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 /// Convert points to pixels.
179 /// Params:
180 ///     points = Input value in points.
181 /// Returns: Given value in pixels.
182 float pt(float points) {
183 
184     // 1 pt = 1/72 in
185     // 1 px = 1/96 in
186     // 96 px = 72 pt
187 
188     return points * 96 / 72;
189 
190 }
191 
192 /// Convert pixels to points.
193 /// Params:
194 ///     points = Input value in pixels.
195 /// Returns: Given value in points.
196 float pxToPt(float px) {
197 
198     return px * 72 / 96;
199 
200 }
201 
202 unittest {
203 
204     import std.conv;
205 
206     assert(to!int(4.pt * 100) == 533);
207     assert(to!int(5.33.pxToPt * 100) == 399);
208 
209 
210 }
211 
212 /// Check if the rectangle contains a point.
213 bool contains(Rectangle rectangle, Vector2 point) {
214 
215     return rectangle.x <= point.x
216         && point.x < rectangle.x + rectangle.width
217         && rectangle.y <= point.y
218         && point.y < rectangle.y + rectangle.height;
219 
220 }
221 
222 /// Check if the two rectangles overlap.
223 bool overlap(Rectangle a, Rectangle b) {
224 
225     const x = (start(b).x <= a.x && a.x <= end(b).x)
226         ||    (start(a).x <= b.x && b.x <= end(a).x);
227     const y = (start(b).y <= a.y && a.y <= end(b).y)
228         ||    (start(a).y <= b.y && b.y <= end(a).y);
229 
230     return x && y;
231 
232 }
233 
234 // Extremely useful Rectangle utilities
235 
236 /// Get the top-left corner of a rectangle.
237 Vector2 start(Rectangle r) {
238     return Vector2(r.x, r.y);
239 }
240 
241 /// Get the bottom-right corner of a rectangle.
242 Vector2 end(Rectangle r) {
243     return Vector2(r.x + r.w, r.y + r.h);
244 }
245 
246 /// Get the center of a rectangle.
247 Vector2 center(Rectangle r) {
248     return Vector2(r.x + r.w/2, r.y + r.h/2);
249 }
250 
251 /// Get the size of a rectangle.
252 Vector2 size(Rectangle r) {
253     return Vector2(r.w, r.h);
254 }
255 
256 /// Get names of static fields in the given object.
257 ///
258 /// Ignores deprecated fields.
259 template StaticFieldNames(T) {
260 
261     import std.traits : hasStaticMember;
262     import std.meta : Alias, Filter;
263 
264     // Prepare data
265     alias Members = __traits(allMembers, T);
266 
267     template isStaticMember(string member) {
268 
269         enum isStaticMember =
270 
271             // Make sure this isn't an alias
272             __traits(compiles,
273                 Alias!(__traits(getMember, T, member))
274             )
275 
276             && !__traits(isDeprecated, __traits(getMember, T, member))
277 
278             // Find the member
279             && hasStaticMember!(T, member);
280 
281     }
282 
283     // Result
284     alias StaticFieldNames = Filter!(isStaticMember, Members);
285 
286 }
287 
288 /// Open given URL in a web browser.
289 ///
290 /// Supports all major desktop operating systems. Does nothing if not supported on the given platform.
291 ///
292 /// At the moment this simply wraps `std.process.browse`.
293 void openURL(scope const(char)[] url) nothrow {
294 
295     version (Posix) {
296         import std.process;
297         browse(url);
298     }
299     else version (Windows) {
300         import std.process;
301         browse(url);
302     }
303 
304     // Do nothing on remaining platforms
305 
306 }