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;