1 ///
2 module fluid.checkbox;
3 
4 import fluid.node;
5 import fluid.input;
6 import fluid.utils;
7 import fluid.style;
8 import fluid.backend;
9 
10 @safe:
11 
12 /// A checkbox can be selected by the user to indicate a true or false state.
13 alias checkbox = simpleConstructor!Checkbox;
14 
15 /// ditto
16 class Checkbox : InputNode!Node {
17 
18     mixin enableInputActions;
19 
20     /// Additional features available for checkbox styling.
21     static class Extra : typeof(super).Extra {
22 
23         /// Image to use for the checkmark.
24         Image checkmark;
25 
26         this(Image checkmark) {
27             this.checkmark = checkmark;
28         }
29 
30     }
31 
32     // Button status
33     public {
34 
35         /// Size of the checkbox.
36         Vector2 size;
37 
38     }
39 
40     private {
41 
42         bool _isChecked;
43 
44     }
45 
46     /// Create a new checkbox.
47     /// Params:
48     ///     isChecked = Whether the checkbox should be checked or not.
49     ///     changed   = Callback to run whenever the user changes the value.
50     this(bool isChecked, void delegate() @safe changed = null) {
51 
52         this(changed);
53         this._isChecked = isChecked;
54 
55     }
56 
57     /// ditto
58     this(void delegate() @safe changed = null) {
59 
60         this.size = Vector2(10, 10);
61         this.changed = changed;
62 
63     }
64 
65     /// If true, the box is checked.
66     bool isChecked() const {
67 
68         return _isChecked;
69 
70     }
71 
72     /// ditto
73     bool isChecked(bool value) {
74 
75         return _isChecked = value;
76 
77     }
78 
79     protected override void resizeImpl(Vector2 space) {
80 
81         minSize = size;
82 
83     }
84 
85     protected override void drawImpl(Rectangle outer, Rectangle inner) {
86 
87         import std.algorithm : min;
88 
89         auto style = pickStyle();
90 
91         style.drawBackground(io, outer);
92 
93         if (auto texture = getTexture(style)) {
94 
95             // Get the scale
96             const scale = min(
97                 inner.width / texture.width,
98                 inner.height / texture.height
99             );
100             const size     = Vector2(texture.width * scale, texture.height * scale);
101             const position = center(inner) - size/2;
102 
103             texture.draw(Rectangle(position.tupleof, size.tupleof));
104 
105         }
106 
107     }
108 
109     /// Get checkmark texture used by this checkbox.
110     protected TextureGC* getTexture(Style style) @trusted {
111 
112         auto extra = cast(Extra) style.extra;
113 
114         // No valid extra data, ignore
115         if (!extra) return null;
116 
117         // Load the texture
118         return extra.getTexture(io, extra.checkmark);
119 
120     }
121 
122 
123     /// Toggle the checkbox.
124     void toggle() {
125 
126         isChecked = !isChecked;
127         if (changed) changed();
128 
129     }
130 
131     /// ditto
132     @(FluidInputAction.press)
133     protected void press() {
134 
135         toggle();
136 
137     }
138 
139     static if (is(typeof(text) : string))
140     override string toString() const {
141 
142         if (isChecked)
143             return "checkbox(true)";
144         else
145             return "checkbox()";
146 
147     }
148 
149 }
150 
151 ///
152 unittest {
153 
154     // Checkbox creates a toggleable button
155     auto myCheckbox = checkbox();
156 
157     assert(!myCheckbox.isChecked);
158 
159 }
160 
161 unittest {
162 
163     int changed;
164 
165     auto io = new HeadlessBackend;
166     auto root = checkbox(delegate {
167 
168         changed++;
169 
170     });
171 
172     root.io = io;
173     root.runInputAction!(FluidInputAction.press);
174 
175     assert(changed == 1);
176     assert(root.isChecked);
177 
178     root.runInputAction!(FluidInputAction.press);
179 
180     assert(changed == 2);
181     assert(!root.isChecked);
182 
183 }