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