1 module nodes.overlay_chain;
2 
3 import fluid;
4 
5 @safe:
6 
7 alias sampleOverlay = nodeBuilder!SampleOverlay;
8 alias relativeOverlay = nodeBuilder!(SampleOverlay, (a) {
9     a.isRelative = true;
10 });
11 
12 class SampleOverlay : Node, Overlayable {
13 
14     CanvasIO canvasIO;
15     Rectangle _anchor;
16     Vector2 size;
17     bool isRelative;
18 
19     this(Vector2 size, Rectangle anchor) {
20         this.layout = .layout!"fill";
21         this.size = size;
22         this._anchor = anchor;
23     }
24 
25     override Rectangle getAnchor(Rectangle viewport) const nothrow {
26         if (isRelative) {
27             return Rectangle(
28                 (viewport.start + _anchor.start).tupleof,
29                 _anchor.size.tupleof,
30             );
31         }
32         else {
33             return _anchor;
34         }
35     }
36 
37     override void resizeImpl(Vector2) {
38         require(canvasIO);
39         minSize = size;
40     }
41 
42     override void drawImpl(Rectangle, Rectangle inner) {
43         canvasIO.drawRectangle(inner, color("#000"));
44     }
45 
46     alias opEquals = typeof(super).opEquals;
47 
48     override bool opEquals(const Object object) const {
49         return super.opEquals(object);
50     }
51 
52 }
53 
54 @("OverlayChain can draw a number of different overlays")
55 unittest {
56 
57     enum overlaySize = Vector2(7, 7);
58 
59     auto label = label("Under overlay");
60     auto chain = overlayChain(label);
61     auto root = sizeLock!testSpace(
62         .sizeLimit(100, 100),
63         .nullTheme,
64         chain
65     );
66 
67     root.drawAndAssert(
68         chain.drawsChild(label),
69         chain.doesNotDrawChildren(),
70     );
71 
72     auto firstOverlay = sampleOverlay(
73         .layout!"start",
74         overlaySize,
75         Rectangle(50, 50, 10, 10)
76     );
77 
78     chain.addOverlay(firstOverlay);
79     root.drawAndAssert(
80         label.isDrawn(),
81         firstOverlay.isDrawn(),
82         firstOverlay.drawsRectangle(60, 60, overlaySize.tupleof),
83     );
84 
85     auto secondOverlay = sampleOverlay(
86         .layout!"start",
87         overlaySize,
88         Rectangle(100, 100, 0, 0)
89     );
90     chain.addOverlay(secondOverlay);
91     root.drawAndAssert(
92         label.isDrawn(),
93         firstOverlay.isDrawn(),
94         firstOverlay.drawsRectangle(60, 60, overlaySize.tupleof),
95         secondOverlay.drawsRectangle(100, 100, overlaySize.tupleof),
96     );
97 
98 }
99 
100 @("Overlays in OverlayChain can be aligned with NodeAlign")
101 unittest {
102 
103     enum overlaySize = Vector2(7, 7);
104 
105     auto chain = overlayChain();
106     auto root = testSpace(chain);
107 
108     const centerTarget = Vector2(55, 55) - overlaySize/2;
109     auto centerOverlay = sampleOverlay(
110         .layout!"center",
111         overlaySize,
112         Rectangle(50, 50, 10, 10),
113     );
114     chain.addOverlay(centerOverlay);
115 
116     const endTarget = Vector2(50, 50) - overlaySize;
117     auto endOverlay = sampleOverlay(
118         .layout!"end",
119         overlaySize,
120         Rectangle(50, 50, 10, 10),
121     );
122     chain.addOverlay(endOverlay);
123 
124     root.drawAndAssert(
125         centerOverlay.drawsRectangle(centerTarget.tupleof, overlaySize.tupleof),
126         endOverlay.drawsRectangle(endTarget.tupleof, overlaySize.tupleof),
127     );
128 
129 }
130 
131 @("In OverlayChain, NodeAlign.fill chooses alignment based on available space")
132 unittest {
133 
134     auto chain = overlayChain(
135         .layout!(1, "fill")
136     );
137     auto root = sizeLock!testSpace(
138         .sizeLimit(100, 100),
139         chain
140     );
141 
142     auto smallOverlay = sampleOverlay(
143         Vector2(25, 25),
144         Rectangle(40, 40, 20, 20),
145     );
146     chain.addOverlay(smallOverlay);
147 
148     auto cornerOverlay = sampleOverlay(
149         Vector2(32, 32),
150         Rectangle(40, 40, 40, 40),
151     );
152     chain.addOverlay(cornerOverlay);
153 
154     auto edgeOverlay = sampleOverlay(
155         Vector2(32, 32),
156         Rectangle(60, 50, 20, 0),
157     );
158     chain.addOverlay(edgeOverlay);
159 
160     auto bigOverlay = sampleOverlay(
161         Vector2(80, 80),
162         Rectangle(20, 20, 20, 20),
163     );
164     chain.addOverlay(bigOverlay);
165 
166     root.drawAndAssert(
167         smallOverlay.drawsRectangle(60, 60, 25, 25),
168         cornerOverlay.drawsRectangle(8, 8, 32, 32),
169         edgeOverlay.drawsRectangle(28, 50, 32, 32),
170         bigOverlay.drawsRectangle(-10, -10, 80, 80),
171     );
172 
173 }
174 
175 @("OverlayChain allows using different alignment per axis")
176 unittest {
177 
178     auto chain = overlayChain(
179         .layout!(1, "fill")
180     );
181     auto root = testSpace(chain);
182 
183     SampleOverlay[] overlays;
184 
185     foreach (x; [NodeAlign.start, NodeAlign.center, NodeAlign.end]) {
186         foreach (y; [NodeAlign.start, NodeAlign.center, NodeAlign.end]) {
187 
188             overlays ~= sampleOverlay(
189                 .layout(x, y),
190                 Vector2(32, 16),
191                 Rectangle(0, 0, 10, 20),
192             );
193             chain.addOverlay(overlays[$-1]);
194 
195         }
196     }
197 
198     root.drawAndAssert(
199         // (start, _)
200         overlays[0].drawsRectangle( 10,  20, 32, 16),  // start
201         overlays[1].drawsRectangle( 10,   2, 32, 16),  // center
202         overlays[2].drawsRectangle( 10, -16, 32, 16),  // end
203 
204         // (center, _)
205         overlays[3].drawsRectangle(-11,  20, 32, 16),  // start
206         overlays[4].drawsRectangle(-11,   2, 32, 16),  // center
207         overlays[5].drawsRectangle(-11, -16, 32, 16),  // end
208 
209         // (end, _)
210         overlays[6].drawsRectangle(-32,  20, 32, 16),  // start
211         overlays[7].drawsRectangle(-32,   2, 32, 16),  // center
212         overlays[8].drawsRectangle(-32, -16, 32, 16),  // end
213      );
214 
215 }
216 
217 @("Overlays in OverlayChain can move without a resize")
218 unittest {
219 
220     auto chain = overlayChain(
221         .layout!(1, "fill")
222     );
223     auto root = testSpace(chain);
224     auto overlay = sampleOverlay(
225         .layout!"start",
226         Vector2(32, 32),
227         Rectangle(0, 0, 0, 0),
228     );
229     chain.addOverlay(overlay);
230 
231     root.drawAndAssert(
232         overlay.drawsRectangle(0, 0, 32, 32),
233     );
234 
235     overlay._anchor = Rectangle(-16, -16, 0, 0),
236     root.drawAndAssert(
237         overlay.drawsRectangle(-16, -16, 32, 32),
238     );
239 
240     overlay._anchor = Rectangle(400, 400, 0, 0),
241     root.drawAndAssert(
242         overlay.drawsRectangle(400, 400, 32, 32),
243     );
244 
245 }
246 
247 @("OverlayChain overlays can be positioned relative to screen, or to the chain")
248 unittest {
249 
250     auto chain = overlayChain(
251         .layout!(1, "fill")
252     );
253     auto root = sizeLock!vtestSpace(
254         .sizeLimit(300, 300),
255         vspace(.layout!1),
256         chain,
257         vspace(.layout!1),
258     );
259     auto absolute = sampleOverlay(
260         .layout!"start",
261         Vector2(32, 32),
262         Rectangle(0, 0, 0, 0),
263     );
264     auto relative = relativeOverlay(
265         .layout!"start",
266         Vector2(32, 32),
267         Rectangle(0, 0, 0, 0),
268     );
269 
270     chain.addOverlay(absolute);
271     chain.addOverlay(relative);
272 
273     root.drawAndAssert(
274         absolute.drawsRectangle(0,   0, 32, 32),
275         relative.drawsRectangle(0, 100, 32, 32),
276     );
277 
278 }
279 
280 @("OverlayChain supports removing nodes with .remove()")
281 unittest {
282 
283     const size = Vector2(32, 32);
284     const anchor = Rectangle(0, 0, 0, 0);
285 
286     auto chain = overlayChain();
287     auto root = testSpace(chain);
288 
289     auto one   = sampleOverlay(.layout!1, size, anchor);
290     auto two   = sampleOverlay(.layout!2, size, anchor);
291     auto three = sampleOverlay(.layout!3, size, anchor);
292 
293     chain.addOverlay(one);
294     chain.addOverlay(two);
295     chain.addOverlay(three);
296 
297     root.drawAndAssert(
298         one.isDrawn,
299         two.isDrawn,
300         three.isDrawn,
301         chain.doesNotDrawChildren,
302     );
303 
304     two.remove();
305     root.drawAndAssertFailure(
306         one.isDrawn,
307         two.isDrawn,
308         three.isDrawn,
309         chain.doesNotDrawChildren,
310     );
311     root.drawAndAssert(
312         one.isDrawn,
313         three.isDrawn,
314         chain.doesNotDrawChildren,
315     );
316 
317     chain.addOverlay(two);
318     assert(two.toRemove == false);
319     root.drawAndAssert(
320         one.isDrawn,
321         three.isDrawn,
322         two.isDrawn,
323         chain.doesNotDrawChildren,
324     );
325 
326 }