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 }