1 // Written in the D programming language. 2 /++ 3 + Authors: KanzakiKino 4 + Copyright: KanzakiKino 2018 5 + License: LGPL-3.0 6 ++/ 7 module w4d.widget.input.slider; 8 import w4d.parser.colorset, 9 w4d.style.color, 10 w4d.style.rect, 11 w4d.style.scalar, 12 w4d.task.window, 13 w4d.widget.base, 14 w4d.util.vector, 15 w4d.event, 16 w4d.exception; 17 import g4d.element.shape.rect, 18 g4d.element.shape.regular, 19 g4d.glfw.cursor, 20 g4d.shader.base; 21 import gl3n.linalg; 22 import std.algorithm, 23 std.math; 24 25 /// A handler that handles changing value of slider. 26 alias ValueChangeHandler = EventHandler!( void, float ); 27 28 /// A widget of slider. 29 class SliderWidget(bool H) : Widget 30 { 31 /// Whether this slider is horizon. 32 alias Horizon = H; 33 34 protected float _min, _max; 35 protected float _value; 36 protected float _unit; 37 38 protected float _barLength; 39 protected float _barWeight; 40 protected RectElement _bar; 41 42 protected float _pointerSize; 43 protected RegularNgonElement!3 _pointer; 44 45 /// 46 ValueChangeHandler onValueChange; 47 48 protected @property magnification () 49 { 50 enforce( _context ); 51 return _context.shift? 10f: 2f; 52 } 53 54 /// 55 override bool handleMouseMove ( vec2 pos ) 56 { 57 if ( super.handleMouseMove( pos ) ) return true; 58 59 if ( isTracked && !status.locked ) { 60 setValue( retrieveValueFromAbsPos( pos ) ); 61 return true; 62 } 63 return false; 64 } 65 /// 66 override bool handleMouseButton ( MouseButton btn, bool status, vec2 pos ) 67 { 68 if ( super.handleMouseButton( btn, status, pos ) ) return true; 69 70 if ( btn == MouseButton.Left && status && !_status.locked ) { 71 setValue( retrieveValueFromAbsPos( pos ) ); 72 focus(); 73 return true; 74 } 75 return false; 76 } 77 /// 78 override bool handleMouseScroll ( vec2 amount, vec2 pos ) 79 { 80 if ( super.handleMouseScroll( amount, pos ) ) return true; 81 if ( status.locked ) return false; 82 83 const a = (amount.x != 0)? amount.x: amount.y; 84 setValue( _value - a*_unit*magnification ); 85 return true; 86 } 87 /// 88 override bool handleKey ( Key key, KeyState status ) 89 { 90 if ( super.handleKey( key, status ) ) return true; 91 if ( !isFocused || _status.locked ) return false; 92 93 const pressing = status != KeyState.Release; 94 if ( key == Key.Left && pressing ) { 95 setValue( _value - _unit*magnification ); 96 } else if ( key == Key.Right && pressing ) { 97 setValue( _value + _unit*magnification ); 98 } else if ( key == Key.Home && pressing ) { 99 setValue( _min ); 100 } else if ( key == Key.End && pressing ) { 101 setValue( _max ); 102 103 } else { 104 return false; 105 } 106 return true; 107 } 108 109 /// 110 override @property const(Cursor) cursor () 111 { 112 static if ( Horizon ) { 113 return Cursor.HResize; 114 } else { 115 return Cursor.VResize; 116 } 117 } 118 119 /// 120 this () 121 { 122 super(); 123 124 setRange( 0f, 1f, 11e-4f ); 125 _value = 0.5; 126 127 _barLength = 0f; 128 _barWeight = 0f; 129 _bar = new RectElement; 130 131 _pointerSize = 0f; 132 _pointer = new RegularNgonElement!3; 133 134 style.box.size.height = 6.mm; 135 style.box.paddings = Rect(2.mm); 136 style.box.margins = Rect(1.mm); 137 style.box.borderWidth = Rect(1.pixel); 138 } 139 140 protected float retrieveValueFromAbsPos ( vec2 pos ) 141 { 142 pos -= style.clientLeftTop; 143 144 const length = pos.getLength!H; 145 const rate = length / _barLength; 146 147 return rate*rangeSize + _min; 148 } 149 150 /// 151 @property rangeSize () 152 { 153 return _max - _min; 154 } 155 /// 156 @property valueRate () 157 { 158 return (_value-_min) / rangeSize; 159 } 160 161 /// Sets range of slider value. 162 void setRange ( float min, float max, float unit ) 163 { 164 enforce( min < max, "Min must be less than max." ); 165 enforce( unit > 0 , "Unit must be more than 0." ); 166 enforce( max-min >= unit, "Unit must be size of range or less." ); 167 _min = min; 168 _max = max; 169 _unit = unit; 170 setValue( _value ); // to re-clamp value 171 requestRedraw(); 172 } 173 /// Changes value. 174 void setValue ( float v ) 175 { 176 v -= v%_unit; 177 const temp = _value; 178 _value = v.clamp( _min, _max ); 179 if ( _value != temp ) { 180 onValueChange.call( _value ); 181 requestRedraw(); 182 } 183 } 184 185 protected void resizeElements () 186 { 187 const size = style.box.clientSize; 188 189 auto barsz = vec2( size ); 190 barsz.getWeight!H /= 3f; 191 _bar.resize( barsz ); 192 _barLength = barsz.getLength!H; 193 _barWeight = barsz.getWeight!H; 194 195 _pointerSize = 196 (size.getWeight!H - _barWeight) / 2; 197 _pointer.resize( _pointerSize ); 198 } 199 /// 200 override vec2 layout ( vec2 pos, vec2 size ) 201 { 202 scope(success) resizeElements(); 203 return super.layout( pos, size ); 204 } 205 206 protected @property barLate () 207 { 208 auto result = vec3(style.clientLeftTop,0); 209 result.getLength!H += _barLength / 2; 210 result.getWeight!H += _barWeight / 2; 211 return result; 212 } 213 protected @property pointerLate () 214 { 215 auto result = vec3(style.clientLeftTop,0); 216 result.getLength!H += _barLength * valueRate; 217 result.getWeight!H += _barWeight + _pointerSize; 218 return result; 219 } 220 /// 221 override void draw ( Window w, in ColorSet parent ) 222 { 223 super.draw( w, parent ); 224 225 auto shader = w.shaders.fill3; 226 const saver = ShaderStateSaver( shader ); 227 228 shader.use(); 229 shader.matrix.late = barLate; 230 shader.color = colorset.foreground; 231 _bar.draw( shader ); 232 233 shader.matrix.late = pointerLate; 234 shader.matrix.rota = vec3( 0, 0, PI ); 235 _pointer.draw( shader ); 236 } 237 238 /// 239 override @property bool trackable () { return true; } 240 /// 241 override @property bool focusable () { return true; } 242 } 243 244 /// A widget of horizontal slider. 245 alias HorizontalSliderWidget = SliderWidget!true; 246 /// A widget of vertical slider. 247 alias VerticalSliderWidget = SliderWidget!false;