1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains base implementation of scrolling capabilities for widgets
6 
7 
8 ScrollWidgetBase - abstract scrollable widget (used as a base for other widgets with scrolling)
9 
10 ScrollWidget - widget which can scroll its content (directly usable class)
11 
12 
13 Synopsis:
14 
15 ----
16 import dlangui.widgets.scroll;
17 
18 // Scroll view example
19 ScrollWidget scroll = new ScrollWidget("SCROLL1");
20 scroll.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
21 WidgetGroup scrollContent = new VerticalLayout("CONTENT");
22 scrollContent.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
23 
24 TableLayout table2 = new TableLayout("TABLE2");
25 table2.colCount = 2;
26 // headers
27 table2.addChild((new TextWidget(null, "Parameter Name"d)).alignment(Align.Right | Align.VCenter));
28 table2.addChild((new TextWidget(null, "Edit Box to edit parameter"d)).alignment(Align.Left | Align.VCenter));
29 // row 1
30 table2.addChild((new TextWidget(null, "Parameter 1 name"d)).alignment(Align.Right | Align.VCenter));
31 table2.addChild((new EditLine("edit1", "Text 1"d)).layoutWidth(FILL_PARENT));
32 // row 2
33 table2.addChild((new TextWidget(null, "Parameter 2 name bla bla"d)).alignment(Align.Right | Align.VCenter));
34 table2.addChild((new EditLine("edit2", "Some text for parameter 2 blah blah blah"d)).layoutWidth(FILL_PARENT));
35 // row 3
36 table2.addChild((new TextWidget(null, "Param 3"d)).alignment(Align.Right | Align.VCenter));
37 table2.addChild((new EditLine("edit3", "Parameter 3 value"d)).layoutWidth(FILL_PARENT));
38 // row 4
39 table2.addChild((new TextWidget(null, "Param 4"d)).alignment(Align.Right | Align.VCenter));
40 table2.addChild((new EditLine("edit3", "Parameter 4 value shdjksdfh hsjdfas hdjkf hdjsfk ah"d)).layoutWidth(FILL_PARENT));
41 // row 5
42 table2.addChild((new TextWidget(null, "Param 5 - edit text here - blah blah blah"d)).alignment(Align.Right | Align.VCenter));
43 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT));
44 // row 6
45 table2.addChild((new TextWidget(null, "Param 6 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter));
46 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT));
47 // row 7
48 table2.addChild((new TextWidget(null, "Param 7 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter));
49 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT));
50 // row 8
51 table2.addChild((new TextWidget(null, "Param 8 - just to fill content widget"d)).alignment(Align.Right | Align.VCenter));
52 table2.addChild((new EditLine("edit3", "Parameter 5 value"d)).layoutWidth(FILL_PARENT));
53 table2.margins(Rect(10,10,10,10)).layoutWidth(FILL_PARENT);
54 scrollContent.addChild(table2);
55 
56 scrollContent.addChild(new TextWidget(null, "Now - some buttons"d));
57 scrollContent.addChild(new ImageTextButton("btn1", "fileclose", "Close"d));
58 scrollContent.addChild(new ImageTextButton("btn2", "fileopen", "Open"d));
59 scrollContent.addChild(new TextWidget(null, "And checkboxes"d));
60 scrollContent.addChild(new CheckBox("btn1", "CheckBox 1"d));
61 scrollContent.addChild(new CheckBox("btn2", "CheckBox 2"d));
62 
63 scroll.contentWidget = scrollContent;
64 
65 ----
66 
67 Copyright: Vadim Lopatin, 2014
68 License:   Boost License 1.0
69 Authors:   Vadim Lopatin, coolreader.org@gmail.com
70 */
71 module dlangui.widgets.scroll;
72 
73 import dlangui.widgets.widget;
74 import dlangui.widgets.controls;
75 import dlangui.widgets.scrollbar;
76 import std.conv;
77 
78 /** Scroll bar visibility mode. */
79 enum ScrollBarMode {
80     /** always invisible */
81     Invisible,
82     /** always visible */
83     Visible,
84     /** automatically show/hide scrollbar depending on content size */
85     Auto,
86     /** Scrollbar is provided by external control outside this widget */
87     External,
88 }
89 
90 /**
91     Abstract scrollable widget
92 
93     Provides scroll bars and basic scrolling functionality.
94 
95  */
96 class ScrollWidgetBase :  WidgetGroup, OnScrollHandler {
97     protected ScrollBarMode _vscrollbarMode;
98     protected ScrollBarMode _hscrollbarMode;
99     /// vertical scrollbar control
100     protected ScrollBar _vscrollbar;
101     /// horizontal scrollbar control
102     protected ScrollBar _hscrollbar;
103     /// inner area, excluding additional controls like scrollbars
104     protected Rect _clientRect;
105 
106     protected Rect _fullScrollableArea;
107     protected Rect _visibleScrollableArea;
108 
109     this(string ID = null, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) {
110         super(ID);
111         _hscrollbarMode = hscrollbarMode;
112         _vscrollbarMode = vscrollbarMode;
113         if (_vscrollbarMode != ScrollBarMode.Invisible) {
114             _vscrollbar = new ScrollBar("vscrollbar", Orientation.Vertical);
115             _vscrollbar.scrollEvent = this;
116             addChild(_vscrollbar);
117         }
118         if (_hscrollbarMode != ScrollBarMode.Invisible) {
119             _hscrollbar = new ScrollBar("hscrollbar", Orientation.Horizontal);
120             _hscrollbar.scrollEvent = this;
121             addChild(_hscrollbar);
122         }
123     }
124 
125     /// vertical scrollbar mode
126     @property ScrollBarMode vscrollbarMode() { return _vscrollbarMode; }
127     @property void vscrollbarMode(ScrollBarMode m) { _vscrollbarMode = m; }
128     /// horizontal scrollbar mode
129     @property ScrollBarMode hscrollbarMode() { return _hscrollbarMode; }
130     @property void hscrollbarMode(ScrollBarMode m) { _hscrollbarMode = m; }
131 
132     /// returns client area rectangle
133     @property Rect clientRect() { return _clientRect; }
134 
135     /// process horizontal scrollbar event
136     bool onHScroll(ScrollEvent event) {
137         return true;
138     }
139 
140     /// process vertical scrollbar event
141     bool onVScroll(ScrollEvent event) {
142         return true;
143     }
144 
145     /// process mouse event; return true if event is processed by widget.
146     override bool onMouseEvent(MouseEvent event) {
147         if (event.action == MouseAction.Wheel) {
148             if (event.flags == MouseFlag.Shift) {
149                 if (_hscrollbar) {
150                     _hscrollbar.sendScrollEvent(event.wheelDelta > 0 ? ScrollAction.LineUp : ScrollAction.LineDown);
151                     return true;
152                 }
153             } else if (event.flags == 0) {
154                 if (_vscrollbar) {
155                     _vscrollbar.sendScrollEvent(event.wheelDelta > 0 ? ScrollAction.LineUp : ScrollAction.LineDown);
156                     return true;
157                 }
158             }
159         }
160         return super.onMouseEvent(event);
161     }
162 
163     /// handle scroll event
164     override bool onScrollEvent(AbstractSlider source, ScrollEvent event) {
165         if (source.orientation == Orientation.Horizontal) {
166             return onHScroll(event);
167         } else {
168             return onVScroll(event);
169         }
170     }
171 
172     protected bool _insideChangeScrollbarVisibility;
173     protected void checkIfNeededToChangeScrollbarVisibility() {
174         if (_insideChangeScrollbarVisibility)
175             return;
176         bool needHScroll = false;
177         bool needVScroll = false;
178         checkIfScrollbarsNeeded(needHScroll, needVScroll);
179         bool hscrollVisible = _hscrollbar && _hscrollbar.visibility == Visibility.Visible;
180         bool vscrollVisible = _vscrollbar && _vscrollbar.visibility == Visibility.Visible;
181         bool needChange = false;
182         if (_hscrollbar && hscrollVisible != needHScroll)
183             needChange = true;
184         if (_vscrollbar && vscrollVisible != needVScroll)
185             needChange = true;
186         if (needChange) {
187             _insideChangeScrollbarVisibility = true;
188             layout(_pos);
189             _insideChangeScrollbarVisibility = false;
190         }
191     }
192 
193     /// update scrollbar positions
194     protected void updateScrollBars() {
195         if (_hscrollbar) {
196             updateHScrollBar();
197         }
198         if (_vscrollbar) {
199             updateVScrollBar();
200         }
201         checkIfNeededToChangeScrollbarVisibility();
202     }
203 
204     public @property ScrollBar hscrollbar() { return _hscrollbar; }
205     public @property ScrollBar vscrollbar() { return _vscrollbar; }
206 
207     public @property void hscrollbar(ScrollBar hscroll) {
208         if (_hscrollbar) {
209             removeChild(_hscrollbar);
210             destroy(_hscrollbar);
211             _hscrollbar = null;
212             _hscrollbarMode = ScrollBarMode.Invisible;
213         }
214         if (hscroll) {
215             _hscrollbar = hscroll;
216             _hscrollbarMode = ScrollBarMode.External;
217         }
218     }
219 
220     public @property void vscrollbar(ScrollBar vscroll) {
221         if (_vscrollbar) {
222             removeChild(_vscrollbar);
223             destroy(_vscrollbar);
224             _vscrollbar = null;
225             _vscrollbarMode = ScrollBarMode.Invisible;
226         }
227         if (vscroll) {
228             _vscrollbar = vscroll;
229             _vscrollbarMode = ScrollBarMode.External;
230         }
231     }
232 
233     /// update horizontal scrollbar widget position
234     protected void updateHScrollBar() {
235         // default implementation: use _fullScrollableArea, _visibleScrollableArea: override it if necessary
236         _hscrollbar.setRange(0, _fullScrollableArea.width);
237         _hscrollbar.pageSize(_visibleScrollableArea.width);
238         _hscrollbar.position(_visibleScrollableArea.left - _fullScrollableArea.left);
239     }
240 
241     /// update verticat scrollbar widget position
242     protected void updateVScrollBar() {
243         // default implementation: use _fullScrollableArea, _visibleScrollableArea: override it if necessary
244         _vscrollbar.setRange(0, _fullScrollableArea.height);
245         _vscrollbar.pageSize(_visibleScrollableArea.height);
246         _vscrollbar.position(_visibleScrollableArea.top - _fullScrollableArea.top);
247     }
248 
249     protected void drawClient(DrawBuf buf) {
250         // override it
251     }
252 
253     protected void drawExtendedArea(DrawBuf buf) {
254     }
255 
256     /// Draw widget at its position to buffer
257     override void onDraw(DrawBuf buf) {
258         if (visibility != Visibility.Visible)
259             return;
260         super.onDraw(buf);
261         Rect rc = _pos;
262         applyMargins(rc);
263         {
264             auto saver = ClipRectSaver(buf, rc, alpha);
265             DrawableRef bg = backgroundDrawable;
266             if (!bg.isNull) {
267                 bg.drawTo(buf, rc, state);
268             }
269             applyPadding(rc);
270             if (_hscrollbar)
271                 _hscrollbar.onDraw(buf);
272             if (_vscrollbar)
273                 _vscrollbar.onDraw(buf);
274             // apply clipping
275             {
276                 auto saver2 = ClipRectSaver(buf, _clientRect, alpha);
277                 drawClient(buf);
278             }
279             {
280                 // no clipping for drawing of extended area
281                 Rect clipr = rc;
282                 clipr.bottom = _clientRect.bottom;
283                 auto saver3 = ClipRectSaver(buf, clipr, alpha);
284                 drawExtendedArea(buf);
285             }
286         }
287 
288         _needDraw = false;
289     }
290 
291     /// calculate full content size in pixels
292     Point fullContentSize() {
293         // override it
294         Point sz;
295         return sz;
296     }
297 
298     /// calculate full content size in pixels including widget borders / margins
299     Point fullContentSizeWithBorders() {
300         Point sz = fullContentSize;
301         Rect paddingrc = padding;
302         Rect marginsrc = margins;
303         sz.x += paddingrc.left + paddingrc.right + marginsrc.left + marginsrc.right;
304         sz.y += paddingrc.top + paddingrc.bottom + marginsrc.top + marginsrc.bottom;
305         return sz;
306     }
307 
308     // override to set minimum scrollwidget size - default 100x100
309     Point minimumVisibleContentSize() {
310         return Point(100,100);
311     }
312 
313     /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
314     override void measure(int parentWidth, int parentHeight) {
315         if (visibility == Visibility.Gone) {
316             return;
317         }
318         Rect m = margins;
319         Rect p = padding;
320 
321         // calc size constraints for children
322         int pwidth = parentWidth;
323         int pheight = parentHeight;
324         if (parentWidth != SIZE_UNSPECIFIED)
325             pwidth -= m.left + m.right + p.left + p.right;
326         if (parentHeight != SIZE_UNSPECIFIED)
327             pheight -= m.top + m.bottom + p.top + p.bottom;
328         int vsbw = 0;
329         int hsbh = 0;
330         if (_hscrollbar && (_hscrollbarMode == ScrollBarMode.Visible || _hscrollbarMode == ScrollBarMode.Auto)) {
331             Visibility oldVisibility = _hscrollbar.visibility;
332             _hscrollbar.visibility = Visibility.Visible;
333             _hscrollbar.measure(pwidth, pheight);
334             hsbh = _hscrollbar.measuredHeight;
335             _hscrollbar.visibility = oldVisibility;
336         }
337         if (_vscrollbar && (_vscrollbarMode == ScrollBarMode.Visible || _vscrollbarMode == ScrollBarMode.Auto)) {
338             Visibility oldVisibility = _vscrollbar.visibility;
339             _vscrollbar.visibility = Visibility.Visible;
340             _vscrollbar.measure(pwidth, pheight);
341             vsbw = _vscrollbar.measuredWidth;
342             _vscrollbar.visibility = oldVisibility;
343         }
344         Point sz = minimumVisibleContentSize();
345         
346         //if (_hscrollbar && _hscrollbarMode == ScrollBarMode.Visible) {
347             sz.y += hsbh;
348         //}
349         //if (_vscrollbar && _vscrollbarMode == ScrollBarMode.Visible) {
350             sz.x += vsbw;
351         //}
352 
353         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
354     }
355 
356     /// override to support modification of client rect after change, e.g. apply offset
357     protected void handleClientRectLayout(ref Rect rc) {
358     }
359 
360     /// override to determine if scrollbars are needed or not
361     protected void checkIfScrollbarsNeeded(ref bool needHScroll, ref bool needVScroll) {
362         needHScroll = _hscrollbar && (_hscrollbarMode == ScrollBarMode.Visible || _hscrollbarMode == ScrollBarMode.Auto);
363         needVScroll = _vscrollbar && (_vscrollbarMode == ScrollBarMode.Visible || _vscrollbarMode == ScrollBarMode.Auto);
364         if (!needHScroll && !needVScroll)
365             return; // not needed
366         if (_hscrollbarMode != ScrollBarMode.Auto && _vscrollbarMode != ScrollBarMode.Auto)
367             return; // no auto scrollbars
368         // either h or v scrollbar is in auto mode
369         Point contentSize = fullContentSize();
370         int contentWidth = contentSize.x;
371         int contentHeight = contentSize.y;
372         int clientWidth = _clientRect.width;
373         int clientHeight = _clientRect.height;
374 
375         int hsbHeight = _hscrollbar.measuredHeight;
376         int vsbWidth = _hscrollbar.measuredWidth;
377 
378         int clientWidthWithScrollbar = clientWidth - vsbWidth;
379         int clientHeightWithScrollbar = clientHeight - hsbHeight;
380 
381         if (_hscrollbarMode == ScrollBarMode.Auto && _vscrollbarMode == ScrollBarMode.Auto) {
382             // both scrollbars in auto mode
383             bool xFits = contentWidth <= clientWidth;
384             bool yFits = contentHeight <= clientHeight;
385             if (!xFits && !yFits) {
386                 // none fits, need both scrollbars
387             } else if (xFits && yFits) {
388                 // everything fits!
389                 needHScroll = false;
390                 needVScroll = false;
391             } else if (xFits) {
392                 // only X fits
393                 if (contentWidth <= clientWidthWithScrollbar)
394                     needHScroll = false; // disable hscroll
395             } else { // yFits
396                 // only Y fits
397                 if (contentHeight <= clientHeightWithScrollbar)
398                     needVScroll = false; // disable vscroll
399             }
400         } else if (_hscrollbarMode == ScrollBarMode.Auto) {
401             // only hscroll is in auto mode
402             if (needVScroll)
403                 clientWidth = clientWidthWithScrollbar;
404             needHScroll = contentWidth > clientWidth;
405         } else {
406             // only vscroll is in auto mode
407             if (needHScroll)
408                 clientHeight = clientHeightWithScrollbar;
409             needVScroll = contentHeight > clientHeight;
410         }
411     }
412 
413     /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
414     override void layout(Rect rc) {
415         if (visibility == Visibility.Gone) {
416             return;
417         }
418         _pos = rc;
419         _needLayout = false;
420         applyMargins(rc);
421         applyPadding(rc);
422 
423         // client area - initial setup w/o scrollbars
424         _clientRect = rc;
425         handleClientRectLayout(_clientRect);
426 
427         bool needHscroll;
428         bool needVscroll;
429 
430         checkIfScrollbarsNeeded(needHscroll, needVscroll);
431 
432         // scrollbars
433         Rect vsbrc = rc;
434         vsbrc.left = vsbrc.right - (needVscroll ? _vscrollbar.measuredWidth : 0);
435         vsbrc.bottom = vsbrc.bottom - (needHscroll ? _hscrollbar.measuredHeight : 0);
436         Rect hsbrc = rc;
437         hsbrc.right = hsbrc.right - (needVscroll ? _vscrollbar.measuredWidth : 0);
438         hsbrc.top = hsbrc.bottom - (needHscroll ? _hscrollbar.measuredHeight : 0);
439         if (_vscrollbar && _vscrollbarMode != ScrollBarMode.External) {
440             _vscrollbar.visibility = needVscroll ? Visibility.Visible : Visibility.Gone;
441             _vscrollbar.layout(vsbrc);
442         }
443         if (_hscrollbar && _hscrollbarMode != ScrollBarMode.External) {
444             _hscrollbar.visibility = needHscroll ? Visibility.Visible : Visibility.Gone;
445             _hscrollbar.layout(hsbrc);
446         }
447 
448         _clientRect = rc;
449         if (needVscroll)
450             _clientRect.right = vsbrc.left;
451         if (needHscroll)
452             _clientRect.bottom = hsbrc.top;
453         handleClientRectLayout(_clientRect);
454         updateScrollBars();
455     }
456 
457     void makeRectVisible(Rect rc, bool alignHorizontally = true, bool alignVertically = true) {
458         if (rc.isInsideOf(_visibleScrollableArea))
459             return;
460         Rect oldRect = _visibleScrollableArea;
461         if (alignHorizontally && rc.right > _visibleScrollableArea.right)
462             _visibleScrollableArea.offset(rc.right - _visibleScrollableArea.right, 0);
463         if (alignVertically && rc.bottom > _visibleScrollableArea.bottom)
464             _visibleScrollableArea.offset(0, rc.bottom - _visibleScrollableArea.bottom);
465         if (alignHorizontally && rc.left < _visibleScrollableArea.left)
466             _visibleScrollableArea.offset(rc.left - _visibleScrollableArea.left, 0);
467         if (alignVertically && rc.top < _visibleScrollableArea.top)
468             _visibleScrollableArea.offset(0, rc.top - _visibleScrollableArea.top);
469         if (_visibleScrollableArea != oldRect)
470             requestLayout();
471     }
472 }
473 
474 /**
475     Widget which can show content of widget group with optional scrolling
476 
477     If size of content widget exceeds available space, allows to scroll it.
478  */
479 class ScrollWidget :  ScrollWidgetBase {
480     protected Widget _contentWidget;
481     @property Widget contentWidget() { return _contentWidget; }
482     @property ScrollWidget contentWidget(Widget newContent) {
483         if (_contentWidget) {
484             removeChild(childIndex(_contentWidget));
485             destroy(_contentWidget);
486         }
487         _contentWidget = newContent;
488         addChild(_contentWidget);
489         requestLayout();
490         return this;
491     }
492     /// empty parameter list constructor - for usage by factory
493     this() {
494         this(null);
495     }
496     /// create with ID parameter
497     this(string ID, ScrollBarMode hscrollbarMode = ScrollBarMode.Visible, ScrollBarMode vscrollbarMode = ScrollBarMode.Visible) {
498         super(ID, hscrollbarMode, vscrollbarMode);
499     }
500 
501     /// calculate full content size in pixels
502     override Point fullContentSize() {
503         // override it
504         Point sz;
505         if (_contentWidget) {
506             _contentWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
507             sz.x = _contentWidget.measuredWidth;
508             sz.y = _contentWidget.measuredHeight;
509         }
510         _fullScrollableArea.right = sz.x;
511         _fullScrollableArea.bottom = sz.y;
512         return sz;
513     }
514 
515     /// update scrollbar positions
516     override protected void updateScrollBars() {
517         Point sz = fullContentSize();
518         _visibleScrollableArea.right = _visibleScrollableArea.left + _clientRect.width;
519         _visibleScrollableArea.bottom = _visibleScrollableArea.top + _clientRect.height;
520         // move back if scroll is too big after window resize
521         int extrax = _visibleScrollableArea.right - _fullScrollableArea.right;
522         int extray = _visibleScrollableArea.bottom - _fullScrollableArea.bottom;
523         if (extrax > _visibleScrollableArea.left)
524             extrax = _visibleScrollableArea.left;
525         if (extray > _visibleScrollableArea.top)
526             extray = _visibleScrollableArea.top;
527         if (extrax < 0)
528             extrax = 0;
529         if (extray < 0)
530             extray = 0;
531         _visibleScrollableArea.offset(-extrax, -extray);
532         super.updateScrollBars();
533     }
534 
535     override protected void drawClient(DrawBuf buf) {
536         if (_contentWidget) {
537             Point sz = fullContentSize();
538             Point p = scrollPos;
539             _contentWidget.layout(Rect(_clientRect.left - p.x, _clientRect.top - p.y, _clientRect.left + sz.x - p.x, _clientRect.top + sz.y - p.y));
540             _contentWidget.onDraw(buf);
541         }
542     }
543 
544 
545     @property Point scrollPos() {
546         return Point(_visibleScrollableArea.left - _fullScrollableArea.left, _visibleScrollableArea.top - _fullScrollableArea.top);
547     }
548 
549     protected void scrollTo(int x, int y) {
550         if (x > _fullScrollableArea.right - _visibleScrollableArea.width)
551             x = _fullScrollableArea.right - _visibleScrollableArea.width;
552         if (y > _fullScrollableArea.bottom - _visibleScrollableArea.height)
553             y = _fullScrollableArea.bottom - _visibleScrollableArea.height;
554         if (x < 0)
555             x = 0;
556         if (y < 0)
557             y = 0;
558         _visibleScrollableArea.left = x;
559         _visibleScrollableArea.top = y;
560         updateScrollBars();
561         invalidate();
562     }
563 
564     /// process horizontal scrollbar event
565     override bool onHScroll(ScrollEvent event) {
566         if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) {
567             scrollTo(event.position, scrollPos.y);
568         } else if (event.action == ScrollAction.PageUp) {
569             scrollTo(scrollPos.x - _clientRect.width * 3 / 4, scrollPos.y);
570         } else if (event.action == ScrollAction.PageDown) {
571             scrollTo(scrollPos.x + _clientRect.width * 3 / 4, scrollPos.y);
572         } else if (event.action == ScrollAction.LineUp) {
573             scrollTo(scrollPos.x - _clientRect.width / 10, scrollPos.y);
574         } else if (event.action == ScrollAction.LineDown) {
575             scrollTo(scrollPos.x + _clientRect.width / 10, scrollPos.y);
576         }
577         return true;
578     }
579 
580     /// process vertical scrollbar event
581     override bool onVScroll(ScrollEvent event) {
582         if (event.action == ScrollAction.SliderMoved || event.action == ScrollAction.SliderReleased) {
583             scrollTo(scrollPos.x, event.position);
584         } else if (event.action == ScrollAction.PageUp) {
585             scrollTo(scrollPos.x, scrollPos.y - _clientRect.height * 3 / 4);
586         } else if (event.action == ScrollAction.PageDown) {
587             scrollTo(scrollPos.x, scrollPos.y + _clientRect.height * 3 / 4);
588         } else if (event.action == ScrollAction.LineUp) {
589             scrollTo(scrollPos.x, scrollPos.y - _clientRect.height / 10);
590         } else if (event.action == ScrollAction.LineDown) {
591             scrollTo(scrollPos.x, scrollPos.y + _clientRect.height / 10);        }
592         return true;
593     }
594 
595     void makeWidgetVisible(Widget widget, bool alignHorizontally = true, bool alignVertically = true) {
596         if (!widget || !widget.visibility == Visibility.Gone)
597             return;
598         if (!_contentWidget || !_contentWidget.isChild(widget))
599             return;
600         Rect wpos = widget.pos;
601         Rect cpos = _contentWidget.pos;
602         wpos.offset(-cpos.left, -cpos.top);
603         makeRectVisible(wpos, alignHorizontally, alignVertically);
604     }
605 }