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