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.scrollbar;
8 import w4d.parser.colorset,
9        w4d.style.color,
10        w4d.style.scalar,
11        w4d.task.window,
12        w4d.widget.base,
13        w4d.util.vector,
14        w4d.event;
15 import g4d.element.shape.rect,
16        g4d.shader.base;
17 import gl3n.linalg;
18 import std.algorithm;
19 
20 /// A handler that handles scrolling.
21 alias ScrollHandler = EventHandler!( void, float );
22 
23 /// A widget of scroll bar.
24 class ScrollBarWidget (bool H) : Widget
25 {
26     /// Whether direction of the scroll bar is horizon.
27     alias Horizon = H;
28 
29     /// Magnification of wheel.
30     enum WheelMagnification = 0.1;
31 
32     ///
33     ScrollHandler onScroll;
34 
35     /// Bar length. (>0)
36     protected float _barLength;
37     /// Current value.
38     protected float _value;
39 
40     /// Bar element.
41     protected RectElement _bar;
42     /// Translate.
43     protected vec2 _translate;
44 
45     /// Dragging offset.
46     protected float _dragOffset;
47 
48     protected float getValueAt ( vec2 pos )
49     {
50         auto size = style.box.clientSize;
51         return pos.getLength!H / size.getLength!H;
52     }
53 
54     ///
55     override bool handleMouseMove ( vec2 pos )
56     {
57         if ( super.handleMouseMove(pos) ) return true;
58 
59         if ( isTracked ) {
60             pos -= style.clientLeftTop;
61             setValue( getValueAt(pos) - _dragOffset );
62             return true;
63         }
64         return false;
65     }
66     ///
67     override bool handleMouseButton ( MouseButton btn, bool status, vec2 pos )
68     {
69         if ( super.handleMouseButton(btn,status,pos) ) return true;
70 
71         if ( btn == MouseButton.Left && status ) {
72             pos -= style.clientLeftTop;
73 
74             auto newValue = getValueAt( pos );
75             if ( newValue > _value && newValue < _value+_barLength ) {
76                 _dragOffset = newValue - _value;
77             } else {
78                 _dragOffset = _barLength/2f;
79             }
80             setValue( newValue - _dragOffset );
81             return true;
82         }
83         return false;
84     }
85     ///
86     override bool handleMouseScroll ( vec2 amount, vec2 pos )
87     {
88         if ( super.handleMouseScroll( amount, pos ) ) return true;
89 
90         auto add = -amount.getLength!H *
91             _barLength * WheelMagnification;
92         setValue( _value + add );
93         return true;
94     }
95 
96     ///
97     void handleScroll ( float v )
98     {
99         onScroll.call( v );
100     }
101 
102     ///
103     this ()
104     {
105         super();
106 
107         _barLength = 1f;
108         _value     = 0f;
109 
110         _bar       = new RectElement;
111         _translate = vec2(0,0);
112 
113         parseColorSetsFromFile!"colorset/scrollbar.yaml"( style );
114         static if ( Horizon ) {
115             style.box.size.height = 3.mm;
116         } else {
117             style.box.size.width  = 3.mm;
118         }
119     }
120 
121     /// Checks if drawing the bar is needed.
122     @property needDrawBar ()
123     {
124         return _barLength < 1f;
125     }
126 
127     /// Changes length of the bar.
128     void setBarLength ( float newlen )
129     {
130         newlen = newlen.clamp( 0f, 1f );
131         auto temp = _barLength;
132         _barLength = newlen;
133 
134         correctBar();
135         handleScroll( _value );
136 
137         if ( temp != _barLength ) {
138             resizeElements();
139         }
140     }
141     /// Changes value of the bar.
142     void setValue ( float value )
143     {
144         auto temp = _value;
145         _value = value;
146 
147         if ( temp != _value ) {
148             correctBar();
149             handleScroll( _value );
150             resizeElements();
151         }
152     }
153 
154     protected void correctBar ()
155     {
156         if ( _barLength <= 0f ) {
157             _barLength = 1f;
158         }
159         setValue( max( 0f, _value ) );
160         if ( _value+_barLength > 1f ) {
161             setValue( 1f - _barLength );
162         }
163     }
164 
165     protected void resizeElements ()
166     {
167         if ( !style.isCalced ) {
168             requestLayout();
169 
170         } else if ( needDrawBar ) {
171             auto size    = style.box.clientSize;
172             auto barsize = size;
173             barsize.getLength!H *= _barLength;
174             _bar.resize( barsize );
175 
176             _translate = barsize/2;
177             _translate.getLength!H +=
178                 size.getLength!H * _value;
179         }
180     }
181     ///
182     override vec2 layout ( vec2 pos, vec2 size )
183     {
184         scope(success) resizeElements();
185         return super.layout( pos, size );
186     }
187 
188     ///
189     override void draw ( Window w, ColorSet parent )
190     {
191         super.draw( w, parent );
192         if ( !needDrawBar ) return;
193 
194         auto late = style.clientLeftTop + _translate;
195 
196         auto shader = w.shaders.fill3;
197         auto saver  = ShaderStateSaver( shader );
198         shader.use();
199         shader.matrix.late = vec3( late, 0 );
200         shader.color       = colorset.foreground;
201         _bar.draw( shader );
202     }
203 
204     ///
205     override @property bool trackable () { return true; }
206     ///
207     override @property bool focusable () { return true; }
208 }
209 
210 /// A widget of horizontal scroll bar.
211 alias HorizontalScrollBarWidget = ScrollBarWidget!true;
212 /// A widget of vertical scroll bar.
213 alias VerticalScrollBarWidget   = ScrollBarWidget!false;