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