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;