1 module nodes.number_input;
2 
3 import fluid;
4 
5 import std.algorithm;
6 
7 @safe:
8 
9 @("NumberInput supports scientific notation")
10 unittest {
11 
12     import std.math;
13 
14     auto input = floatInput();
15     auto focus = focusChain();
16     auto root = chain(focus, input);
17 
18     focus.typeText("10e8");
19     focus.currentFocus = input;
20     root.draw();
21 
22     focus.runInputAction!(FluidInputAction.submit);
23     root.draw();
24 
25     assert(input.value.isClose(10e8));
26     assert(input.value.isClose(1e9));
27     assert(input.TextInput.value.among("1e+9", "1e+09"));
28 
29 }
30 
31 @("NumberInput supports math operations")
32 unittest {
33 
34     int calls;
35 
36     auto input = intInput(delegate {
37         calls++;
38     });
39     auto focus = focusChain();
40     auto root = chain(focus, input);
41 
42     // First frame: initial state
43     focus.currentFocus = input;
44     root.draw();
45     assert(input.value == 0);
46     assert(input.TextInput.value == "0");
47 
48     // Second frame, type in "10"; value should remain unchanged
49     focus.typeText("10");
50     root.draw();
51     assert(calls == 0);
52     assert(input.value == 0);
53     assert(input.TextInput.value.among("010", "10"));
54 
55     // Submit to update
56     focus.runInputAction!(FluidInputAction.submit);
57     root.draw();
58     assert(calls == 1);
59     assert(input.value == 10);
60     assert(input.TextInput.value == "10");
61 
62     // Test math equations
63     focus.typeText("+20*5");
64     root.draw();
65     assert(calls == 1);
66     assert(input.value == 10);
67     assert(input.TextInput.value == "10+20*5");
68 
69     // Submit the expression
70     input.submit();
71     root.draw();
72     assert(calls == 2);
73     assert(input.value != (10+20)*5);
74     assert(input.value == 110);
75     assert(input.TextInput.value == "110");
76 
77 }
78 
79 @("NumberInput supports incrementing and decrementing through buttons")
80 unittest {
81 
82     int calls;
83 
84     auto input = intInput(delegate { calls++; });
85     auto hover = hoverChain();
86     auto root = chain(hover, input);
87 
88     root.theme = Theme(
89         rule!IntInput(
90             Rule.margin  = 0,
91             Rule.border  = 0,
92             Rule.padding = 0,
93         ),
94         rule!NumberInputSpinner(
95             Rule.margin  = 0,
96             Rule.border  = 0,
97             Rule.padding = 0,
98         ),
99     );
100 
101     input.TextInput.value = "10+1";
102     root.draw();
103 
104     const size = input.getMinSize;
105     const bottom = size - Vector2(1, 1);  // safety margin
106     const top = Vector2(bottom.x, 0);
107 
108     // Try incrementing
109     hover.point(top)
110         .then((a) {
111             assert(a.isHovered(input.spinner));
112             a.press;
113             return a.stayIdle;
114         })
115         .then((a) {
116             assert(calls == 1);  // Does this make sense?
117             assert(input.value == 12);
118             assert(input.TextInput.value == "12");
119 
120             // Try decrementing
121             return a.move(bottom - Vector2(1, 1));
122         })
123         .then((a) {
124             a.press;
125             assert(a.isHovered(input.spinner));
126             return a.stayIdle;
127         })
128         .runWhileDrawing(root);
129 
130     assert(calls == 2);
131     assert(input.value == 11);
132     assert(input.TextInput.value == "11");
133 
134 }
135 
136 @("NumberInput correctly scales buttons in HiDPI")
137 unittest {
138 
139     auto input = intInput();
140     auto root = testSpace(input);
141     auto node = input.spinner;
142 
143     // 100% scale
144     root.drawAndAssert(
145         node.drawsRectangle(6, 2, 200, 27).ofColor("#00000000"),
146         node.drawsImage().at(189.125, 2, 16.875, 27).ofColor("#ffffff")
147             .sha256("341d0882a4db03e29bfa6b016d133c6082df3c2d6817978adc82d3678310565e"),
148     );
149 
150     // 125% scale, buttons are drawn all the same
151     root.setScale(1.25);
152     root.drawAndAssert(
153         node.drawsRectangle(6, 2, 200, 27).ofColor("#00000000"),
154         node.drawsImage().at(189.125, 2, 16.875, 27).ofColor("#ffffff")
155             .sha256("341d0882a4db03e29bfa6b016d133c6082df3c2d6817978adc82d3678310565e"),
156     );
157 
158 }
159 
160 @("NumberInputSpinner correctly maps visual space in HiDPI")
161 unittest {
162 
163     int ups, downs;
164 
165     auto node = new NumberInputSpinner(
166         { ups++; },
167         { downs++; },
168     );
169     auto hover = hoverChain(node);
170     auto root = sizeLock!testSpace(
171         .sizeLimit(100, 100),
172         hover,
173     );
174 
175     node.layout = .layout!"fill";
176     hover.layout = .layout!(1, "fill");
177 
178     // Scale does not affect hitboxes
179     foreach (scale; [1.00, 1.25]) {
180         ups = 0;
181         downs = 0;
182 
183         root.setScale(scale);
184         hover.point(99, 49)
185             .then((a) {
186                 a.click();
187                 assert(ups   == 1);
188                 assert(downs == 0);
189                 return a.move(99, 51);
190             })
191             .then((a) {
192                 a.click();
193                 assert(ups   == 1);
194                 assert(downs == 1);
195             })
196             .runWhileDrawing(root);
197     }
198 
199     assert(ups   == 1);
200     assert(downs == 1);
201 
202 }