1 /// 2 module fluid.utils; 3 4 import std.meta; 5 import std.traits; 6 import std.functional; 7 8 import fluid.types; 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.initialize(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 64 deprecated("`NodeBuilder.initializer` is affected by a codegen bug in DMD, " 65 ~ "and has been replaced with `initialize`. " 66 ~ "Please update your code before Fluid 0.8.0") 67 alias initializer = unaryFun!fun; 68 69 void initialize(T node) { 70 unaryFun!fun(node); 71 } 72 73 Type opCall(Args...)(Args args) { 74 75 // Collect parameters 76 enum paramCount = leadingParams!Args; 77 78 // Construct the node 79 auto result = new Type(args[paramCount..$]); 80 81 // Run the initializer 82 initialize(result); 83 84 // Apply the parameters 85 result.applyAll(args[0..paramCount]); 86 87 return result; 88 89 } 90 91 /// Count node parameters present at the beginning of the given type list. This function is only available at 92 /// compile-time. 93 /// 94 /// If a node parameter is passed *after* a non-parameter, it will not be included in the count, and will not be 95 /// treated as one by ComponentBuilder. 96 static int leadingParams(Args...)() { 97 98 assert(__ctfe, "leadingParams is not available at runtime"); 99 100 if (__ctfe) 101 foreach (i, Arg; Args) { 102 103 // Found a non-parameter, return the index 104 if (!isNodeParam!(Arg, T)) 105 return i; 106 107 } 108 109 // All arguments are parameters 110 return Args.length; 111 112 } 113 114 } 115 116 unittest { 117 118 static class Foo { 119 120 string value; 121 122 this() { } 123 124 } 125 126 alias xfoo = nodeBuilder!Foo; 127 assert(xfoo().value == ""); 128 129 alias yfoo = nodeBuilder!(Foo, (a) { 130 a.value = "foo"; 131 }); 132 assert(yfoo().value == "foo"); 133 134 auto myFoo = new Foo; 135 yfoo.initialize(myFoo); 136 assert(myFoo.value == "foo"); 137 138 static class Bar(T) : T { 139 140 int foo; 141 142 this(int foo) { 143 144 this.foo = foo; 145 146 } 147 148 } 149 150 alias xbar(alias T) = nodeBuilder!(Bar, T); 151 152 const barA = xbar!Foo(1); 153 assert(barA.value == ""); 154 assert(barA.foo == 1); 155 156 const barB = xbar!xfoo(2); 157 assert(barB.value == ""); 158 assert(barB.foo == 2); 159 160 const barC = xbar!yfoo(3); 161 assert(barC.value == "foo"); 162 assert(barC.foo == 3); 163 164 } 165 166 /// Modify the subject by passing it to the `apply` method of each of the parameters. 167 /// 168 /// This is made for `nodeBuilder` to apply node parameters on a node. The subject doesn't have to be a node. 169 /// 170 /// Params: 171 /// subject = Subject to modify. 172 /// parameters = Parameters to apply onto the subject; 173 /// Returns: 174 /// The subject after applying the modifications. 175 /// If subject is a class, this is the same object as passed. 176 Subject applyAll(Subject, Parameters...)(Subject subject, Parameters parameters) { 177 178 foreach (param; parameters) { 179 180 param.apply(subject); 181 182 } 183 184 return subject; 185 186 } 187 188 /// Get distance between two vectors. 189 float distance(Vector2 a, Vector2 b) { 190 191 import std.math : sqrt; 192 193 return sqrt(distance2(a, b)); 194 195 } 196 197 /// Get distance between two vectors, squared. 198 float distance2(Vector2 a, Vector2 b) { 199 200 return (a.x - b.x)^^2 + (a.y - b.y)^^2; 201 202 } 203 204 /// Convert points to pixels. 205 /// Params: 206 /// points = Input value in points. 207 /// Returns: Given value in pixels. 208 float pt(float points) { 209 210 // 1 pt = 1/72 in 211 // 1 px = 1/96 in 212 // 96 px = 72 pt 213 214 return points * 96 / 72; 215 216 } 217 218 /// Convert pixels to points. 219 /// Params: 220 /// points = Input value in pixels. 221 /// Returns: Given value in points. 222 float pxToPt(float px) { 223 224 return px * 72 / 96; 225 226 } 227 228 unittest { 229 230 import std.conv; 231 232 assert(to!int(4.pt * 100) == 533); 233 assert(to!int(5.33.pxToPt * 100) == 399); 234 235 236 } 237 238 /// Check if the rectangle contains a point. 239 bool contains(Rectangle rectangle, Vector2 point) { 240 241 return rectangle.x <= point.x 242 && point.x < rectangle.x + rectangle.width 243 && rectangle.y <= point.y 244 && point.y < rectangle.y + rectangle.height; 245 246 } 247 248 /// Check if the two rectangles overlap. 249 bool overlap(Rectangle a, Rectangle b) { 250 251 const x = (start(b).x <= a.x && a.x <= end(b).x) 252 || (start(a).x <= b.x && b.x <= end(a).x); 253 const y = (start(b).y <= a.y && a.y <= end(b).y) 254 || (start(a).y <= b.y && b.y <= end(a).y); 255 256 return x && y; 257 258 } 259 260 /// Load a two dimensional vector from a string. 261 /// 262 /// The string should either be a single float value, like `1.5`, or two, separated by an `x` 263 /// character: `1.5 x 1.2`. If there is only one value, it will be used for both axes. 264 /// 265 /// Params: 266 /// source = String to parse. 267 /// Params: 268 /// String to load from. 269 Vector2 toSizeVector2(string source) { 270 271 import std.conv : to; 272 import std.string : strip; 273 import std.algorithm : findSplit; 274 275 // Load the render scale from environment 276 if (auto pair = source.findSplit("x")) { 277 return Vector2( 278 pair[0].strip.to!float, 279 pair[2].strip.to!float 280 ); 281 } 282 else { 283 const value = source.strip.to!float; 284 return Vector2(value, value); 285 } 286 287 } 288 289 unittest { 290 291 assert("1.5".toSizeVector2 == Vector2(1.5, 1.5)); 292 assert("1.5x1.2".toSizeVector2 == Vector2(1.5, 1.2)); 293 assert("2.0 x 1.0".toSizeVector2 == Vector2(2.0, 1.0)); 294 assert(" 2.0x1.0 ".toSizeVector2 == Vector2(2.0, 1.0)); 295 296 } 297 298 // Extremely useful Rectangle utilities 299 300 /// Get the top-left corner of a rectangle. 301 Vector2 start(Rectangle r) nothrow { 302 return Vector2(r.x, r.y); 303 } 304 305 /// Get the bottom-right corner of a rectangle. 306 Vector2 end(Rectangle r) nothrow { 307 return Vector2(r.x + r.w, r.y + r.h); 308 } 309 310 /// Get the center of a rectangle. 311 Vector2 center(Rectangle r) nothrow { 312 return Vector2(r.x + r.w/2, r.y + r.h/2); 313 } 314 315 /// Get the size of a rectangle. 316 Vector2 size(Rectangle r) nothrow { 317 return Vector2(r.w, r.h); 318 } 319 320 /// Get the area of a rectangle. 321 float area(Rectangle r) nothrow { 322 return r.w * r.h; 323 } 324 325 /// Intersect two rectangles 326 Rectangle intersect(Rectangle one, Rectangle two) nothrow { 327 328 import std.algorithm : min, max; 329 330 Rectangle result; 331 result.x = max(one.x, two.x); 332 result.y = max(one.y, two.y); 333 result.w = max(0, min(one.x + one.w, two.x + two.w) - result.x); 334 result.h = max(0, min(one.y + one.h, two.y + two.h) - result.y); 335 return result; 336 337 } 338 339 /// Create a point that is in the same position relative to the destination rectangle, 340 /// as is the input point relative to the source rectangle. 341 /// 342 /// Relation is expressed is in term of a fraction or percentage. If the point is in the center 343 /// of the source rectangle, the returned point will be in the center of the destination 344 /// rectangle. 345 /// 346 /// Params: 347 /// point = Point to transform. 348 /// source = Original viewport; source point is relative to this viewport. 349 /// destination = Viewport used as destination. Resulting point will be relative 350 /// to this viewport. 351 /// Returns: 352 /// A point located in the same place, relative to the other viewport. 353 Vector2 viewportTransform(Vector2 point, Rectangle source, Rectangle destination) { 354 point = point - source.start; 355 point = Vector2( 356 point.x * destination.width / source.width, 357 point.y * destination.height / source.height, 358 ); 359 return point + destination.start; 360 } 361 362 /// 363 @("Viewport transform example works") 364 unittest { 365 366 const source = Rectangle(100, 100, 50, 50); 367 const destination = Rectangle(100, 0, 100, 100); 368 369 // Corners and center 370 assert(source.start .viewportTransform(source, destination) == destination.start); 371 assert(source.center.viewportTransform(source, destination) == destination.center); 372 assert(source.end .viewportTransform(source, destination) == destination.end); 373 374 // Arbitrary positions 375 assert(Vector2(125, 100).viewportTransform(source, destination) == Vector2( 150, 0)); 376 assert(Vector2( 0, 0).viewportTransform(source, destination) == Vector2(-100, -200)); 377 378 } 379 380 /// Get names of static fields in the given object. 381 /// 382 /// Ignores deprecated fields. 383 template StaticFieldNames(T) { 384 385 import std.traits : hasStaticMember; 386 import std.meta : Alias, Filter; 387 388 // Prepare data 389 alias Members = __traits(allMembers, T); 390 391 template isStaticMember(string member) { 392 393 enum isStaticMember = 394 395 // Make sure this isn't an alias 396 __traits(compiles, 397 Alias!(__traits(getMember, T, member)) 398 ) 399 400 && !__traits(isDeprecated, __traits(getMember, T, member)) 401 402 // Find the member 403 && hasStaticMember!(T, member); 404 405 } 406 407 // Result 408 alias StaticFieldNames = Filter!(isStaticMember, Members); 409 410 } 411 412 /// Open given URL in a web browser. 413 /// 414 /// Supports all major desktop operating systems. Does nothing if not supported on the given platform. 415 /// 416 /// At the moment this simply wraps `std.process.browse`. 417 void openURL(scope const(char)[] url) nothrow { 418 419 version (Posix) { 420 import std.process; 421 browse(url); 422 } 423 else version (Windows) { 424 import std.process; 425 browse(url); 426 } 427 428 // Do nothing on remaining platforms 429 430 }