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 }