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 }