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 }