1 // Meta! 2 module nodes.test_space; 3 4 import fluid; 5 6 @safe: 7 8 alias myImage = nodeBuilder!MyImage; 9 class MyImage : Node { 10 11 CanvasIO canvasIO; 12 DrawableImage image; 13 14 this(Image image = Image.init) { 15 this.image = DrawableImage(image); 16 } 17 18 override void resizeImpl(Vector2 space) { 19 use(canvasIO); 20 load(canvasIO, image); 21 } 22 23 override void drawImpl(Rectangle outer, Rectangle inner) { 24 image.draw(inner); 25 } 26 27 } 28 29 alias emitSignal = nodeBuilder!EmitSignal; 30 class EmitSignal : Node { 31 32 DebugSignalIO debugSignalIO; 33 string signal; 34 35 this(string signal) { 36 this.signal = signal; 37 } 38 39 override void resizeImpl(Vector2) { 40 require(debugSignalIO); 41 minSize = Vector2(); 42 } 43 44 override void drawImpl(Rectangle, Rectangle) { 45 debugSignalIO.emitSignal(signal); 46 } 47 48 } 49 50 @("TestSpace can perform basic tests with draws, drawsRectangle and doesNotDraw") 51 unittest { 52 53 class MyNode : Node { 54 55 CanvasIO canvasIO; 56 auto targetRectangle = Rectangle(0, 0, 10, 10); 57 58 override void resizeImpl(Vector2) { 59 require(canvasIO); 60 } 61 62 override void drawImpl(Rectangle, Rectangle) { 63 canvasIO.drawRectangle(targetRectangle, color("#f00")); 64 targetRectangle.x += 1; 65 } 66 67 } 68 69 auto myNode = new MyNode; 70 auto space = testSpace(myNode); 71 space.drawAndAssert( 72 space.doesNotDraw(), 73 myNode.drawsRectangle(0, 0, 10, 10), 74 ); 75 space.drawAndAssert( 76 space.doesNotDraw(), 77 myNode.drawsRectangle(1, 0, 10, 10), 78 ); 79 space.drawAndAssert( 80 space.doesNotDraw(), 81 myNode.drawsRectangle(2, 0, 10, 10).ofColor("#f00"), 82 ); 83 space.drawAndAssert( 84 space.doesNotDraw(), 85 myNode.draws(), 86 ); 87 space.drawAndAssertFailure( 88 space.draws(), 89 ); 90 space.drawAndAssertFailure( 91 myNode.doesNotDraw() 92 ); 93 space.drawAndAssert( 94 myNode.drawsRectangle(), 95 ); 96 space.drawAndAssert( 97 myNode.drawsRectangle().ofColor("#f00"), 98 ); 99 space.drawAndAssertFailure( 100 myNode.drawsRectangle().ofColor("#500"), 101 ); 102 space.drawAndAssertFailure( 103 space.drawsRectangle().ofColor("#500"), 104 ); 105 106 } 107 108 @("TestProbe correctly handles node exits") 109 unittest { 110 111 import fluid.label; 112 113 static class Surround : Space { 114 115 CanvasIO canvasIO; 116 117 this(Node[] nodes...) @safe { 118 super(nodes); 119 } 120 121 override void resizeImpl(Vector2 space) { 122 super.resizeImpl(space); 123 use(canvasIO); 124 } 125 126 override void drawImpl(Rectangle outer, Rectangle inner) { 127 canvasIO.drawRectangle(outer, color("#a00")); 128 super.drawImpl(outer, inner); 129 canvasIO.drawRectangle(outer, color("#0a0")); 130 } 131 132 } 133 134 alias surround = nodeBuilder!Surround; 135 136 { 137 auto myLabel = label("!"); 138 auto root = surround( 139 myLabel, 140 ); 141 auto test = testSpace(root); 142 143 test.drawAndAssert( 144 root.drawsRectangle(), 145 myLabel.isDrawn(), 146 root.drawsRectangle(), 147 ); 148 test.drawAndAssertFailure( 149 root.drawsRectangle(), 150 myLabel.isDrawn(), 151 root.doesNotDraw(), 152 ); 153 test.drawAndAssertFailure( 154 root.doesNotDraw(), 155 myLabel.isDrawn(), 156 root.drawsRectangle(), 157 ); 158 } 159 { 160 auto myLabel = label("!"); 161 auto wrapper = vspace(myLabel); 162 auto root = surround( 163 wrapper, 164 ); 165 auto test = testSpace(root); 166 167 test.drawAndAssert( 168 root.drawsRectangle(), 169 wrapper.isDrawn(), 170 wrapper.doesNotDraw(), 171 myLabel.isDrawn(), 172 wrapper.doesNotDraw(), 173 root.drawsRectangle(), 174 ); 175 test.drawAndAssert( 176 root.drawsRectangle(), 177 wrapper.isDrawn(), 178 wrapper.doesNotDraw(), 179 root.drawsRectangle(), 180 ); 181 test.drawAndAssert( 182 root.drawsRectangle(), 183 root.drawsRectangle(), 184 root.doesNotDraw(), 185 ); 186 test.drawAndAssert( 187 root.drawsRectangle(), 188 root.doesNotDraw(), 189 wrapper.isDrawn(), 190 root.drawsRectangle(), 191 root.doesNotDraw(), 192 ); 193 } 194 195 } 196 197 @("TestSpace can handle images") 198 unittest { 199 200 auto root = myImage(); 201 auto test = testSpace(root); 202 203 // The image will be loaded and drawn 204 test.drawAndAssert( 205 root.drawsImage(root.image), 206 ); 207 assert(test.countLoadedImages == 1); 208 209 // The image will not be loaded, but it will be kept alive 210 test.drawAndAssert( 211 root.drawsImage(root.image), 212 ); 213 assert(test.countLoadedImages == 1); 214 215 // Request a resize — same situation 216 test.updateSize(); 217 test.drawAndAssert( 218 root.drawsImage(root.image), 219 ); 220 assert(test.countLoadedImages == 1); 221 222 // Hide the node: the node won't resize and the image will be freed 223 root.hide(); 224 test.drawAndAssertFailure( 225 root.isDrawn(), 226 ); 227 assert(test.countLoadedImages == 0); 228 229 // Show the node now and try again 230 root.show(); 231 test.drawAndAssert( 232 root.drawsImage(root.image), 233 ); 234 assert(test.countLoadedImages == 1); 235 236 } 237 238 @("TestSpace: Two nodes with the same image will share resources") 239 unittest { 240 241 auto image1 = myImage(); 242 auto image2 = myImage(); 243 auto test = testSpace(image1, image2); 244 245 assert(image1.image == image2.image); 246 247 // Two nodes draw the same image — counts as one 248 test.drawAndAssert( 249 image1.drawsImage(image1.image), 250 image2.drawsImage(image2.image), 251 ); 252 assert(test.countLoadedImages == 1); 253 254 // Hide one image 255 image1.hide(); 256 test.drawAndAssert( 257 image2.drawsImage(image2.image), 258 ); 259 test.drawAndAssertFailure( 260 image1.drawsImage(image1.image), 261 ); 262 assert( test.isImageLoaded(image1.image)); 263 assert( test.isImageLoaded(image2.image)); 264 assert(test.countLoadedImages == 1); 265 266 // Hide both — the images should unload 267 image2.hide(); 268 test.drawAndAssertFailure( 269 image1.drawsImage(image1.image), 270 ); 271 test.drawAndAssertFailure( 272 image2.drawsImage(image2.image), 273 ); 274 assert(!test.isImageLoaded(image1.image)); 275 assert(!test.isImageLoaded(image2.image)); 276 assert(test.countLoadedImages == 0); 277 278 // Show one again 279 image2.show(); 280 test.drawAndAssert( 281 image2.drawsImage(image2.image), 282 ); 283 test.drawAndAssertFailure( 284 image1.drawsImage(image1.image), 285 ); 286 assert( test.isImageLoaded(image2.image)); 287 assert(test.countLoadedImages == 1); 288 289 } 290 291 @("TestSpace correctly manages lifetime of multiple resources") 292 unittest { 293 294 auto image1 = myImage( 295 generateColorImage(4, 4, 296 color("#f00") 297 ) 298 ); 299 auto image2 = myImage( 300 generateColorImage(4, 4, 301 color("#0f0") 302 ) 303 ); 304 auto root = testSpace(image1, image2); 305 306 // Draw both images 307 root.drawAndAssert( 308 image1.drawsImage(image1.image), 309 image2.drawsImage(image2.image), 310 ); 311 assert(root.countLoadedImages == 2); 312 assert(root.isImageLoaded(image1.image)); 313 assert(root.isImageLoaded(image2.image)); 314 315 // Unload the second one 316 image1.hide(); 317 root.drawAndAssert( 318 image2.drawsImage(image2.image), 319 ); 320 root.drawAndAssertFailure( 321 image1.drawsImage(image1.image), 322 ); 323 assert(root.countLoadedImages == 1); 324 assert(!root.isImageLoaded(image1.image)); 325 assert( root.isImageLoaded(image2.image)); 326 327 } 328 329 @("TestSpace / CanvasIO recognizes tint") 330 unittest { 331 332 import fluid.frame; 333 import fluid.style; 334 335 auto theme = nullTheme.derive( 336 rule!Frame( 337 Rule.backgroundColor = color("#aaa"), 338 Rule.tint = color("#aaaa"), 339 ) 340 ); 341 342 Frame[6] frames; 343 344 auto root = testSpace( 345 theme, 346 frames[0] = vframe( 347 frames[1] = vframe( 348 frames[2] = vframe( 349 frames[3] = vframe(), 350 frames[4] = vframe(), 351 ), 352 frames[5] = vframe(), 353 ), 354 ), 355 ); 356 357 root.drawAndAssert( 358 frames[0].drawsRectangle().ofColor("#717171aa"), 359 frames[1].drawsRectangle().ofColor("#4b4b4b71"), 360 frames[2].drawsRectangle().ofColor("#3232324b"), 361 frames[3].drawsRectangle().ofColor("#21212132"), 362 frames[4].drawsRectangle().ofColor("#21212132"), 363 frames[5].drawsRectangle().ofColor("#3232324b"), 364 ); 365 366 } 367 368 @("Tint can be locked to prevent changes") 369 unittest { 370 371 import fluid.frame; 372 import fluid.style; 373 374 static class LockTint : Space { 375 376 this(Ts...)(Ts args) { 377 super(args); 378 } 379 380 override void drawImpl(Rectangle outer, Rectangle inner) { 381 382 treeContext.lockTint(); 383 scope (exit) treeContext.unlockTint(); 384 385 super.drawImpl(outer, inner); 386 387 } 388 389 } 390 391 alias lockTint = nodeBuilder!LockTint; 392 393 auto theme = nullTheme.derive( 394 rule!Frame( 395 Rule.backgroundColor = color("#aaa"), 396 Rule.tint = color("#aaaa"), 397 ) 398 ); 399 400 Frame[7] frames; 401 402 auto root = testSpace( 403 theme, 404 frames[0] = vframe( 405 frames[1] = vframe( 406 lockTint( 407 frames[2] = vframe( 408 frames[3] = vframe(), 409 frames[4] = vframe(), 410 ), 411 frames[5] = vframe(), 412 ), 413 frames[6] = vframe(), 414 ), 415 ), 416 ); 417 418 root.drawAndAssert( 419 frames[0].drawsRectangle().ofColor("#717171aa"), 420 frames[1].drawsRectangle().ofColor("#4b4b4b71"), 421 frames[2].drawsRectangle().ofColor("#4b4b4b71"), 422 frames[3].drawsRectangle().ofColor("#4b4b4b71"), 423 frames[4].drawsRectangle().ofColor("#4b4b4b71"), 424 frames[5].drawsRectangle().ofColor("#4b4b4b71"), 425 frames[6].drawsRectangle().ofColor("#3232324b"), 426 ); 427 428 } 429 430 @("TestSpace can capture and analyze debug signals") 431 unittest { 432 433 EmitSignal[3] emitters; 434 435 auto root = testSpace( 436 emitters[0] = emitSignal("one"), 437 emitters[1] = emitSignal("two"), 438 emitters[2] = emitSignal("three"), 439 ); 440 441 root.drawAndAssert( 442 emitters[0].emits("one"), 443 emitters[1].emits("two"), 444 emitters[2].emits("three"), 445 ); 446 447 root.drawAndAssertFailure( 448 emitters[0].emits("two"), 449 ); 450 451 root.drawAndAssertFailure( 452 emitters[2].emits("one"), 453 ); 454 455 assert(root.emitCount("one") == 3); 456 assert(root.emitCount("two") == 3); 457 assert(root.emitCount("three") == 3); 458 459 } 460 461 @("TestSpace can compare images by SHA256 hash") 462 unittest { 463 464 auto node = myImage(); 465 auto root = testSpace(node); 466 node.image = generateColorImage(2, 2, color("#400")); 467 468 root.drawAndAssert( 469 node.isDrawn().at(0, 0, 0, 0), 470 node.drawsImage().at(0, 0, 0, 0).ofColor("#ffffff") 471 .sha256("e9cc20e218b20c9402676298e71ad7469f7d78f9e9ca253733c91a299995be45"), 472 ); 473 root.drawAndAssertFailure( 474 node.isDrawn().at(0, 0, 0, 0), 475 node.drawsImage().at(0, 0, 0, 0).ofColor("#ffffff") 476 .sha256("fb3a6a641bbce9b762a240d320a72e963ebbfd6d38cf17e86f37f165574bdbe0"), 477 ); 478 479 }