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 }