1 module nodes.scroll;
2 
3 import fluid;
4 import fluid.future.pipe;
5 
6 @safe:
7 
8 @NodeTag
9 enum TestTag {
10     green,
11 }
12 
13 PlainBox tallBox() {
14     return plainBox(40, 5250);
15 }
16 
17 PlainBox wideBox() {
18     return plainBox(5250, 40);
19 }
20 
21 alias plainBox = nodeBuilder!PlainBox;
22 
23 class PlainBox : Node {
24 
25     CanvasIO canvasIO;
26     Vector2 size;
27 
28     this(float width, float height) {
29         this.size = Vector2(width, height);
30     }
31 
32     override void resizeImpl(Vector2) {
33         require(canvasIO);
34         minSize = size;
35     }
36 
37     override void drawImpl(Rectangle outer, Rectangle) {
38         style.drawBackground(io, canvasIO, outer);
39     }
40 
41 }
42 
43 Theme testTheme;
44 
45 static this() {
46     testTheme = nullTheme.derive(
47         rule!ScrollFrame(
48             Rule.backgroundColor = color("#555"),
49         ),
50         rule!ScrollInput(
51             Rule.backgroundColor = color("#f00"),
52         ),
53         rule!ScrollInputHandle(
54             Rule.backgroundColor = color("#00f"),
55         ),
56         rule!(Node, TestTag.green)(
57             Rule.backgroundColor = color("#0f0"),
58         ),
59     );
60 }
61 
62 @("ScrollFrame crops and scrolls its content")
63 unittest {
64 
65     auto box = tallBox();
66     auto frame = sizeLock!vscrollFrame(
67         .sizeLimit(400, 250),
68         box,
69     );
70     auto root = testSpace(
71         .testTheme,
72         frame
73     );
74 
75     root.drawAndAssert(
76          frame.cropsTo(0, 0, 390, 250),
77          frame.drawsRectangle(0, 0, 390, 250).ofColor("#555555"),
78          box.drawsRectangle(0, 0, 40, 5250),
79          frame.resetsCrop(),
80     );
81 
82     frame.scroll = 100;
83     root.drawAndAssert(
84          frame.cropsTo(0, 0, 390, 250),
85          frame.drawsRectangle(0, 0, 390, 250).ofColor("#555555"),
86          box.drawsRectangle(0, -100, 40, 5250),
87          frame.resetsCrop(),
88     );
89 
90     frame.scrollEnd();
91     root.drawAndAssert(
92          frame.cropsTo(0, 0, 390, 250),
93          frame.drawsRectangle(0, 0, 390, 250).ofColor("#555555"),
94          box.drawsRectangle(0, -5000, 40, 5250),
95          frame.resetsCrop(),
96     );
97 
98 }
99 
100 @("ScrollFrames can be nested")
101 unittest {
102 
103     auto box = tallBox();
104     auto innerFrame = sizeLock!vscrollFrame(
105         .sizeLimit(400, 500),
106         box,
107     );
108     auto frame = sizeLock!vscrollFrame(
109         .sizeLimit(400, 250),
110         innerFrame,
111         tallBox(),
112     );
113     auto root = testSpace(
114         .testTheme,
115         frame
116     );
117 
118     root.drawAndAssert(
119         frame.cropsTo       (0, 0, 390, 250),
120         frame.drawsRectangle(0, 0, 390, 250),
121         innerFrame.cropsTo       (0, 0, 380, 250),  // 250, not 500, because the boxes
122         innerFrame.drawsRectangle(0, 0, 380, 500),  // intersect
123         box.drawsRectangle(0, 0, 40, 5250),
124         innerFrame.cropsTo       (0, 0, 390, 250),
125         frame.resetsCrop(),
126     );
127 
128     frame.scroll = 400;
129     root.drawAndAssert(
130         frame.cropsTo       (0, 0, 390, 250),
131         frame.drawsRectangle(0, 0, 390, 250),
132         innerFrame.cropsTo       (0,    0, 380, 100),
133         innerFrame.drawsRectangle(0, -400, 380, 500),
134         box.drawsRectangle(0, -400, 40, 5250),
135         innerFrame.cropsTo       (0, 0, 390, 250),
136         frame.resetsCrop(),
137     );
138 
139 }
140 
141 @("ScrollFrames can be horizontal or vertical")
142 unittest {
143 
144     auto vbox = tallBox();
145     auto hbox = wideBox();
146     auto vf = sizeLock!vscrollFrame(
147         .sizeLimit(300, 300),
148         vbox,
149     );
150     auto hf = sizeLock!hscrollFrame(
151         .sizeLimit(300, 300),
152         hbox,
153     );
154     auto hover = hoverChain(
155         vspace(vf, hf),
156     );
157     auto root = vtestSpace(
158         .testTheme,
159         hover,
160     );
161 
162     root.drawAndAssert(
163         vf.cropsTo         (0, 0, 290, 300),
164         vf.drawsRectangle  (0, 0, 290, 300),
165         vbox.drawsRectangle(0, 0, 40, 5250),
166         vf.resetsCrop      (),
167 
168         hf.cropsTo         (0, 300, 300, 290),
169         hf.drawsRectangle  (0, 300, 300, 290),
170         hbox.drawsRectangle(0, 300, 5250, 40),
171         hf.resetsCrop      (),
172     );
173 
174     join(
175         hover.point(100, 100).scroll(30, 90),
176         hover.point(100, 400).scroll(20, 80),
177     )
178         .runWhileDrawing(root, 1);
179 
180     assert(vf.scroll == 90);
181     assert(hf.scroll == 20);
182 
183     root.drawAndAssert(
184         vf.cropsTo         (0,   0, 290,  300),
185         vf.drawsRectangle  (0,   0, 290,  300),
186         vbox.drawsRectangle(0, -90,  40, 5250),
187         vf.resetsCrop      (),
188 
189         hf.cropsTo         (  0, 300,  300, 290),
190         hf.drawsRectangle  (  0, 300,  300, 290),
191         hbox.drawsRectangle(-20, 300, 5250,  40),
192         hf.resetsCrop      (),
193     );
194 
195 }
196 
197 @("ScrollFrame sets canScroll to false if maxed out")
198 unittest {
199 
200     const vec = Vector2(0, 100);
201 
202     auto frame = vscrollFrame(
203         tallBox(),
204     );
205     testSpace(frame).draw();
206 
207     assert(frame.canScroll(vec));
208 
209     frame.scroll = frame.maxScroll - 200;
210     assert(frame.canScroll(vec));
211 
212     frame.scroll = frame.maxScroll - 10;
213     assert(frame.canScroll(vec));
214 
215     // Only an exact (or excess) value should output false
216     frame.scroll = frame.maxScroll;
217     assert(!frame.canScroll( vec));
218     assert( frame.canScroll(-vec));
219 
220     frame.scroll = frame.maxScroll + 10;
221     assert(!frame.canScroll( vec));
222     assert( frame.canScroll(-vec));
223 
224     // Same test but for the other direction
225     frame.scroll = 200;
226     assert(frame.canScroll(-vec));
227 
228     frame.scroll = 10;
229     assert( frame.canScroll( vec));
230     assert( frame.canScroll(-vec));
231 
232     frame.scroll = 0;
233     assert( frame.canScroll( vec));
234     assert(!frame.canScroll(-vec));
235 
236     frame.scroll = -10;
237     assert( frame.canScroll( vec));
238     assert(!frame.canScroll(-vec));
239 
240 }
241 
242 @("ScrollFrame blocks canScroll on the other axis")
243 unittest {
244 
245     auto vf = vscrollFrame(
246         tallBox(),
247     );
248     auto hf = hscrollFrame(
249         wideBox(),
250     );
251     testSpace(vf, hf).draw();
252 
253     vf.scroll = 500;
254     hf.scroll = 500;
255 
256     assert( vf.canScroll(Vector2(  0,  50)));
257     assert( vf.canScroll(Vector2(  0, -50)));
258     assert( vf.canScroll(Vector2( 50,  50)));
259     assert( vf.canScroll(Vector2(-50, -50)));
260     assert( vf.canScroll(Vector2(-50,  50)));
261     assert( vf.canScroll(Vector2( 50, -50)));
262     assert(!vf.canScroll(Vector2( 50,   0)));
263     assert(!vf.canScroll(Vector2(-50,   0)));
264 
265     assert( hf.canScroll(Vector2( 50,   0)));
266     assert( hf.canScroll(Vector2(-50,   0)));
267     assert( hf.canScroll(Vector2( 50,  50)));
268     assert( hf.canScroll(Vector2(-50, -50)));
269     assert( hf.canScroll(Vector2( 50, -50)));
270     assert( hf.canScroll(Vector2(-50,  50)));
271     assert(!hf.canScroll(Vector2(  0,  50)));
272     assert(!hf.canScroll(Vector2(  0, -50)));
273 
274 }
275 
276 @("ScrollFrame supports scrollIntoView")
277 unittest {
278 
279     PlainBox[6] boxes;
280 
281     // TODO tests scrolling to odd-numbered boxes too please
282     //      https://git.samerion.com/Samerion/Fluid/issues/307
283     auto frame = sizeLock!vscrollFrame(
284         .sizeLimit(500, 500),
285         .testTheme,
286         boxes[0] = plainBox(.tags!(TestTag.green), 500, 100),
287         boxes[1] = plainBox(500, 1000),
288         boxes[2] = plainBox(.tags!(TestTag.green), 500, 100),
289         boxes[3] = plainBox(500, 1000),
290         boxes[4] = plainBox(.tags!(TestTag.green), 500, 100),
291         boxes[5] = plainBox(500, 1000),
292     );
293     auto root = testSpace(frame);
294 
295     boxes[0]
296         .scrollIntoView()
297         .runWhileDrawing(root);
298     assert(frame.scroll == 0);
299 
300     boxes[2]
301         .scrollIntoView()
302         .runWhileDrawing(root);
303     assert(frame.scroll == 100 + 1000 - 500 + 100);
304 
305     boxes[2]
306         .scrollToTop()
307         .runWhileDrawing(root);
308     assert(frame.scroll == 100 + 1000);
309 
310     // Scroll into view should yield a different result when scrolling
311     // from the bottom than from the top.
312     frame.scrollEnd();
313     boxes[2]
314         .scrollIntoView()
315         .runWhileDrawing(root);
316     assert(frame.scroll == 100 + 1000);
317 
318 }
319 
320 @("ScrollFrame.scroll is clamped to its boundaries")
321 unittest {
322 
323     auto frame = sizeLock!vscrollFrame(
324         .sizeLimit(250, 250),
325         plainBox(250, 5250),
326     );
327     auto root = testSpace(frame);
328 
329     root.draw();
330 
331     assert(frame.scroll == 0);
332 
333     frame.scroll = -50;
334     assert(frame.scroll == 0);
335 
336     frame.scroll = 5250;
337     assert(frame.scroll == 5000);
338 
339     frame.scroll = -50;
340     assert(frame.scroll == 0);
341 
342     frame.scroll = 5;
343     assert(frame.scroll == 5);
344 
345     frame.scroll = 5000;
346     assert(frame.scroll == 5000);
347 
348 
349 }