1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains simple controls widgets implementation.
7 
8 TextWidget
9 
10 ImageWidget
11 
12 Button
13 
14 ImageButton
15 
16 ScrollBar
17 
18 
19 Synopsis:
20 
21 ----
22 import dlangui.widgets.controls;
23 
24 ----
25 
26 Copyright: Vadim Lopatin, 2014
27 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
28 Authors:   $(WEB coolreader.org, Vadim Lopatin)
29 */
30 module dlangui.widgets.controls;
31 
32 import dlangui.widgets.widget;
33 import dlangui.widgets.layouts;
34 
35 /// vertical spacer to fill empty space in vertical layouts
36 class VSpacer : Widget {
37     this() {
38         styleId = "VSpacer";
39     }
40 }
41 
42 /// horizontal spacer to fill empty space in horizontal layouts
43 class HSpacer : Widget {
44     this() {
45         styleId = "HSpacer";
46     }
47 }
48 
49 /// static text widget
50 class TextWidget : Widget {
51     this(string ID = null, string textResourceId = null) {
52 		super(ID);
53         styleId = "TEXT";
54         _text = textResourceId;
55     }
56     this(string ID, dstring rawText) {
57 		super(ID);
58         styleId = "TEXT";
59         _text = rawText;
60     }
61     protected UIString _text;
62     /// get widget text
63     override @property dstring text() { return _text; }
64     /// set text to show
65     override @property Widget text(dstring s) { 
66         _text = s; 
67         requestLayout();
68 		return this;
69     }
70     /// set text to show
71     override @property Widget text(ref UIString s) { 
72         _text = s; 
73         requestLayout();
74 		return this;
75     }
76     /// set text resource ID to show
77     @property Widget textResource(string s) { 
78         _text = s; 
79         requestLayout();
80 		return this;
81     }
82 
83     override void measure(int parentWidth, int parentHeight) { 
84         FontRef font = font();
85         //auto measureStart = std.datetime.Clock.currAppTick;
86         Point sz = font.textSize(text);
87         //auto measureEnd = std.datetime.Clock.currAppTick;
88         //auto duration = measureEnd - measureStart;
89         //if (duration.length > 10)
90         //    Log.d("TextWidget measureText took ", duration.length, " ticks");
91         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
92     }
93 
94     bool onClick() {
95         // override it
96         Log.d("Button.onClick ", id);
97         return false;
98     }
99 
100     override void onDraw(DrawBuf buf) {
101         if (visibility != Visibility.Visible)
102             return;
103         super.onDraw(buf);
104         Rect rc = _pos;
105         applyMargins(rc);
106         auto saver = ClipRectSaver(buf, rc);
107         applyPadding(rc);
108         FontRef font = font();
109         Point sz = font.textSize(text);
110         applyAlign(rc, sz);
111         font.drawText(buf, rc.left, rc.top, text, textColor);
112     }
113 }
114 
115 /// static image widget
116 class ImageWidget : Widget {
117 
118     protected string _drawableId;
119     protected DrawableRef _drawable;
120 
121     this(string ID = null, string drawableId = null) {
122 		super(ID);
123         _drawableId = drawableId;
124 	}
125 
126     /// get drawable image id
127     @property string drawableId() { return _drawableId; }
128     /// set drawable image id
129     @property ImageWidget drawableId(string id) { 
130         _drawableId = id; 
131         _drawable.clear();
132         requestLayout();
133         return this; 
134     }
135     /// get drawable
136     @property ref DrawableRef drawable() {
137         if (!_drawable.isNull)
138             return _drawable;
139         if (_drawableId !is null)
140             _drawable = drawableCache.get(_drawableId);
141         return _drawable;
142     }
143     /// set custom drawable (not one from resources)
144     @property ImageWidget drawable(DrawableRef img) {
145         _drawable = img;
146         _drawableId = null;
147         return this;
148     }
149 
150     override void measure(int parentWidth, int parentHeight) { 
151         DrawableRef img = drawable;
152         int w = 0;
153         int h = 0;
154         if (!img.isNull) {
155             w = img.width;
156             h = img.height;
157         }
158         measuredContent(parentWidth, parentHeight, w, h);
159     }
160     override void onDraw(DrawBuf buf) {
161         if (visibility != Visibility.Visible)
162             return;
163         super.onDraw(buf);
164         Rect rc = _pos;
165         applyMargins(rc);
166         auto saver = ClipRectSaver(buf, rc);
167         applyPadding(rc);
168         DrawableRef img = drawable;
169         if (!img.isNull) {
170             Point sz;
171             sz.x = img.width;
172             sz.y = img.height;
173             applyAlign(rc, sz);
174             uint st = state;
175             if (state & State.Checked) {
176                 Log.d("Drawing image for checked state");
177             }
178             img.drawTo(buf, rc, st);
179         }
180     }
181 }
182 
183 /// button with image only
184 class ImageButton : ImageWidget {
185     this(string ID = null, string drawableId = null) {
186         super(ID, drawableId);
187         styleId = "BUTTON";
188         _drawableId = drawableId;
189         clickable = true;
190         focusable = true;
191         trackHover = true;
192     }
193 }
194 
195 /// button with image and text
196 class ImageTextButton : HorizontalLayout {
197     protected ImageWidget _icon;
198     protected TextWidget _label;
199     override @property dstring text() { return _label.text; }
200     override @property Widget text(dstring s) { _label.text = s; requestLayout(); return this; }
201     override @property Widget text(ref UIString s) { _label.text = s; requestLayout(); return this; }
202     this(string ID = null, string drawableId = null, string textResourceId = null) {
203         super(ID);
204         styleId = "BUTTON";
205         _icon = new ImageWidget("icon", drawableId);
206         _label = new TextWidget("label", textResourceId);
207         _label.styleId = "BUTTON_LABEL";
208         _icon.state = State.Parent;
209         _label.state = State.Parent;
210         addChild(_icon);
211         addChild(_label);
212         clickable = true;
213         focusable = true;
214         trackHover = true;
215     }
216     this(string ID, string drawableId, dstring rawText) {
217         super(ID);
218         styleId = "BUTTON";
219         _icon = new ImageWidget("icon", drawableId);
220         _label = new TextWidget("label", rawText);
221         _label.styleId = "BUTTON_LABEL";
222         _icon.styleId = "BUTTON_ICON";
223         _icon.state = State.Parent;
224         _label.state = State.Parent;
225         addChild(_icon);
226         addChild(_label);
227         clickable = true;
228         focusable = true;
229         trackHover = true;
230     }
231 }
232 
233 /// checkbox
234 class CheckBox : ImageTextButton {
235     this(string ID = null, string textResourceId = null) {
236         super(ID, "btn_check", textResourceId);
237         styleId = "TRANSPARENT_BUTTON_BACKGROUND";
238         checkable = true;
239     }
240     this(string ID, dstring labelText) {
241         super(ID, "btn_check", labelText);
242         styleId = "TRANSPARENT_BUTTON_BACKGROUND";
243         checkable = true;
244     }
245     // called to process click and notify listeners
246     override protected bool handleClick() {
247         checked = !checked;
248         return super.handleClick();
249     }
250 }
251 
252 /// radio button
253 class RadioButton : ImageTextButton {
254     this(string ID = null, string textResourceId = null) {
255         super(ID, "btn_radio", textResourceId);
256         styleId = "TRANSPARENT_BUTTON_BACKGROUND";
257         checkable = true;
258     }
259     this(string ID, dstring labelText) {
260         super(ID, "btn_radio", labelText);
261         styleId = "TRANSPARENT_BUTTON_BACKGROUND";
262         checkable = true;
263     }
264 
265 	void uncheckSiblings() {
266 		Widget p = parent;
267 		if (!p)
268 			return;
269 		for (int i = 0; i < p.childCount; i++) {
270 			Widget child = p.child(i);
271 			if (child is this)
272 				continue;
273 			RadioButton rb = cast(RadioButton)child;
274 			if (rb)
275 				rb.checked = false;
276 		}
277 	}
278 
279     // called to process click and notify listeners
280     override protected bool handleClick() {
281 		uncheckSiblings();
282         checked = true;
283 
284         return super.handleClick();
285     }
286 
287 }
288 
289 /// Text only button
290 class Button : Widget {
291     protected UIString _text;
292     override @property dstring text() { return _text; }
293     override @property Widget text(dstring s) { _text = s; requestLayout(); return this; }
294     override @property Widget text(ref UIString s) { _text = s; requestLayout(); return this; }
295     @property Widget textResource(string s) { _text = s; requestLayout(); return this; }
296     this(string ID = null) {
297 		super(ID);
298         styleId = "BUTTON";
299 		clickable = true;
300         focusable = true;
301         trackHover = true;
302     }
303 
304     override void measure(int parentWidth, int parentHeight) { 
305         FontRef font = font();
306         Point sz = font.textSize(text);
307         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
308     }
309 
310 	override void onDraw(DrawBuf buf) {
311         super.onDraw(buf);
312         Rect rc = _pos;
313         applyMargins(rc);
314         buf.fillRect(_pos, backgroundColor);
315         applyPadding(rc);
316         auto saver = ClipRectSaver(buf, rc);
317         FontRef font = font();
318         Point sz = font.textSize(text);
319         applyAlign(rc, sz);
320         font.drawText(buf, rc.left, rc.top, text, textColor);
321     }
322 
323 }
324 
325 /// scroll event handler interface
326 interface OnScrollHandler {
327     /// handle scroll event
328     bool onScrollEvent(AbstractSlider source, ScrollEvent event);
329 }
330 
331 /// base class for widgets like scrollbars and sliders
332 class AbstractSlider : WidgetGroup {
333     protected int _minValue = 0;
334     protected int _maxValue = 100;
335     protected int _pageSize = 30;
336     protected int _position = 20;
337 
338     this(string ID) {
339         super(ID);
340     }
341 
342     /// scroll event listeners
343     Signal!OnScrollHandler onScrollEventListener;
344 
345     /// returns slider position
346     @property int position() const { return _position; }
347     /// sets new slider position
348     @property AbstractSlider position(int newPosition) { 
349         if (_position != newPosition) {
350             _position = newPosition;
351             onPositionChanged();
352         }
353         return this;
354     }
355     protected void onPositionChanged() {
356         requestLayout();
357     }
358     /// returns slider range min value
359     @property int minValue() const { return _minValue; }
360     /// returns slider range max value
361     @property int maxValue() const { return _maxValue; }
362     /// page size (visible area size)
363     @property int pageSize() const { return _pageSize; }
364     /// set page size (visible area size)
365     @property AbstractSlider pageSize(int size) {
366         if (_pageSize != size) {
367             _pageSize = size;
368             requestLayout();
369         }
370         return this;
371     }
372     /// set new range (min and max values for slider)
373     AbstractSlider setRange(int min, int max) {
374         if (_minValue != min || _maxValue != max) {
375             _minValue = min;
376             _maxValue = max;
377             requestLayout();
378         }
379         return this;
380     }
381 
382     protected bool sendScrollEvent(ScrollAction action, int position) {
383         if (!onScrollEventListener.assigned)
384             return false;
385         ScrollEvent event = new ScrollEvent(action, _minValue, _maxValue, _pageSize, position);
386         bool res = onScrollEventListener(this, event);
387         if (event.positionChanged) {
388             _position = event.position;
389             if (_position > _maxValue)
390                 _position = _maxValue;
391             if (_position < _minValue)
392                 _position = _minValue;
393             onPositionChanged();
394         }
395         return true;
396     }
397 }
398 
399 /// scroll bar - either vertical or horizontal
400 class ScrollBar : AbstractSlider, OnClickHandler {
401     protected ImageButton _btnBack;
402     protected ImageButton _btnForward;
403     protected SliderButton _indicator;
404     protected PageScrollButton _pageUp;
405     protected PageScrollButton _pageDown;
406     protected Rect _scrollArea;
407     protected int _btnSize;
408     protected int _minIndicatorSize;
409 
410 
411 
412     class PageScrollButton : Widget {
413         this(string ID) {
414             super(ID);
415             styleId = "PAGE_SCROLL";
416             trackHover = true;
417             clickable = true;
418         }
419     }
420 
421     class SliderButton : ImageButton {
422         Point _dragStart;
423         int _dragStartPosition;
424         bool _dragging;
425         Rect _dragStartRect;
426 
427         this(string resourceId) {
428             super("SLIDER", resourceId);
429             trackHover = true;
430         }
431 
432         /// process mouse event; return true if event is processed by widget.
433         override bool onMouseEvent(MouseEvent event) {
434             // support onClick
435             if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
436                 setState(State.Pressed);
437                 _dragging = true;
438                 _dragStart.x = event.x;
439                 _dragStart.y = event.y;
440                 _dragStartPosition = _position;
441                 _dragStartRect = _pos;
442                 sendScrollEvent(ScrollAction.SliderPressed, _position);
443                 return true;
444             }
445             if (event.action == MouseAction.FocusOut && _dragging) {
446                 return true;
447             }
448             if (event.action == MouseAction.Move && _dragging) {
449                 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x;
450                 Rect rc = _dragStartRect;
451                 int offset;
452                 int space;
453                 if (_orientation == Orientation.Vertical) {
454                     rc.top += delta;
455                     rc.bottom += delta;
456                     if (rc.top < _scrollArea.top) {
457                         rc.top = _scrollArea.top;
458                         rc.bottom = _scrollArea.top + _dragStartRect.height;
459                     } else if (rc.bottom > _scrollArea.bottom) {
460                         rc.top = _scrollArea.bottom - _dragStartRect.height;
461                         rc.bottom = _scrollArea.bottom;
462                     }
463                     offset = rc.top - _scrollArea.top;
464                     space = _scrollArea.height - rc.height;
465                 } else {
466                     rc.left += delta;
467                     rc.right += delta;
468                     if (rc.left < _scrollArea.left) {
469                         rc.left = _scrollArea.left;
470                         rc.right = _scrollArea.left + _dragStartRect.width;
471                     } else if (rc.right > _scrollArea.right) {
472                         rc.left = _scrollArea.right - _dragStartRect.width;
473                         rc.right = _scrollArea.right;
474                     }
475                     offset = rc.left - _scrollArea.left;
476                     space = _scrollArea.width - rc.width;
477                 }
478                 layoutButtons(rc);
479                 //_pos = rc;
480                 int position = space > 0 ? _minValue + offset * (_maxValue - _minValue - _pageSize) / space : 0;
481                 invalidate();
482                 onIndicatorDragging(_dragStartPosition, position);
483                 return true;
484             }
485             if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) {
486                 resetState(State.Pressed);
487                 if (_dragging) {
488                     sendScrollEvent(ScrollAction.SliderReleased, _position);
489                     _dragging = false;
490                 }
491                 return true;
492             }
493             if (event.action == MouseAction.Move && trackHover) {
494                 if (!(state & State.Hovered)) {
495                     Log.d("Hover ", id);
496                     setState(State.Hovered);
497                 }
498 	            return true;
499             }
500             if ((event.action == MouseAction.Leave || event.action == MouseAction.Cancel) && trackHover) {
501                 Log.d("Leave ", id);
502 	            resetState(State.Hovered);
503 	            return true;
504             }
505             if (event.action == MouseAction.Cancel) {
506                 Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel");
507                 resetState(State.Pressed);
508                 _dragging = false;
509                 return true;
510             }
511             return false;
512         }
513 
514     }
515 
516     protected bool onIndicatorDragging(int initialPosition, int currentPosition) {
517         _position = currentPosition;
518         return sendScrollEvent(ScrollAction.SliderMoved, currentPosition);
519     }
520 
521     private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) {
522         int dv = _maxValue - _minValue;
523         if (_pageSize >= dv) {
524             // full size
525             spaceBackSize = spaceForwardSize = 0;
526             indicatorSize = availableSize;
527             return false;
528         }
529         if (dv < 0)
530             dv = 0;
531         indicatorSize = _pageSize * availableSize / dv;
532         if (indicatorSize < _minIndicatorSize)
533             indicatorSize = _minIndicatorSize;
534         if (indicatorSize >= availableSize) {
535             // full size
536             spaceBackSize = spaceForwardSize = 0;
537             indicatorSize = availableSize;
538             return false;
539         }
540         int spaceLeft = availableSize - indicatorSize;
541         int topv = _position - _minValue;
542         int bottomv = _position + _pageSize - _minValue;
543         if (topv < 0)
544             topv = 0;
545         if (bottomv > dv)
546             bottomv = dv;
547         bottomv = dv - bottomv;
548         spaceBackSize = spaceLeft * topv / (topv + bottomv);
549         spaceForwardSize = spaceLeft - spaceBackSize;
550         return true;
551     }
552 
553     protected Orientation _orientation = Orientation.Vertical;
554     /// returns scrollbar orientation (Vertical, Horizontal)
555     @property Orientation orientation() { return _orientation; }
556     /// sets scrollbar orientation
557     @property ScrollBar orientation(Orientation value) { 
558         if (_orientation != value) {
559             _orientation = value; 
560             _btnBack.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT);
561             _btnForward.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT);
562             _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL);
563             requestLayout(); 
564         }
565         return this; 
566     }
567 
568     this(string ID = null, Orientation orient = Orientation.Vertical) {
569 		super(ID);
570         styleId = "SCROLLBAR";
571         _orientation = orient;
572         _btnBack = new ImageButton("BACK", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT));
573         _btnForward = new ImageButton("FORWARD", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT));
574         _pageUp = new PageScrollButton("PAGE_UP");
575         _pageDown = new PageScrollButton("PAGE_DOWN");
576         _btnBack.styleId("SCROLLBAR_BUTTON");
577         _btnForward.styleId("SCROLLBAR_BUTTON");
578         _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL));
579         addChild(_btnBack);
580         addChild(_btnForward);
581         addChild(_indicator);
582         addChild(_pageUp);
583         addChild(_pageDown);
584         _btnBack.onClickListener = &onClick;
585         _btnForward.onClickListener = &onClick;
586         _pageUp.onClickListener = &onClick;
587         _pageDown.onClickListener = &onClick;
588     }
589 
590     override void measure(int parentWidth, int parentHeight) { 
591         Point sz;
592         _btnBack.measure(parentWidth, parentHeight);
593         _btnForward.measure(parentWidth, parentHeight);
594         _indicator.measure(parentWidth, parentHeight);
595         _pageUp.measure(parentWidth, parentHeight);
596         _pageDown.measure(parentWidth, parentHeight);
597         _btnSize = _btnBack.measuredWidth;
598         _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth;
599         if (_btnSize < _btnBack.measuredHeight)
600             _btnSize = _btnBack.measuredHeight;
601         if (_btnSize < 16)
602             _btnSize = 16;
603         if (_orientation == Orientation.Vertical) {
604             // vertical
605             sz.x = _btnSize;
606             sz.y = _btnSize * 5; // min height
607         } else {
608             // horizontal
609             sz.y = _btnSize;
610             sz.x = _btnSize * 5; // min height
611         }
612         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
613     }
614 
615 	override protected void onPositionChanged() {
616 		if (!needLayout)
617         	layoutButtons();
618     }
619 
620     protected void layoutButtons() {
621         Rect irc = _scrollArea;
622         if (_orientation == Orientation.Vertical) {
623             // vertical
624             int spaceBackSize, spaceForwardSize, indicatorSize;
625             bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize);
626             irc.top += spaceBackSize;
627             irc.bottom -= spaceForwardSize;
628             layoutButtons(irc);
629         } else {
630             // horizontal
631             int spaceBackSize, spaceForwardSize, indicatorSize;
632             bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize);
633             irc.left += spaceBackSize;
634             irc.right -= spaceForwardSize;
635             layoutButtons(irc);
636         }
637 	}
638 
639 	protected void layoutButtons(Rect irc) {
640         Rect r;
641         _indicator.visibility = Visibility.Visible;
642         if (_orientation == Orientation.Vertical) {
643             _indicator.layout(irc);
644             if (_scrollArea.top < irc.top) {
645                 r = _scrollArea;
646                 r.bottom = irc.top;
647                 _pageUp.layout(r);
648                 _pageUp.visibility = Visibility.Visible;
649             } else {
650                 _pageUp.visibility = Visibility.Invisible;
651             }
652             if (_scrollArea.bottom > irc.bottom) {
653                 r = _scrollArea;
654                 r.top = irc.bottom;
655                 _pageDown.layout(r);
656                 _pageDown.visibility = Visibility.Visible;
657             } else {
658                 _pageDown.visibility = Visibility.Invisible;
659             }
660         } else {
661             _indicator.layout(irc);
662             if (_scrollArea.left < irc.left) {
663                 r = _scrollArea;
664                 r.right = irc.left;
665                 _pageUp.layout(r);
666                 _pageUp.visibility = Visibility.Visible;
667             } else {
668                 _pageUp.visibility = Visibility.Invisible;
669             }
670             if (_scrollArea.right > irc.right) {
671                 r = _scrollArea;
672                 r.left = irc.right;
673                 _pageDown.layout(r);
674                 _pageDown.visibility = Visibility.Visible;
675             } else {
676                 _pageDown.visibility = Visibility.Invisible;
677             }
678         }
679     }
680 
681     override void layout(Rect rc) {
682         _needLayout = false;
683         applyMargins(rc);
684         applyPadding(rc);
685         Rect r;
686         if (_orientation == Orientation.Vertical) {
687             // vertical
688             // buttons
689             int backbtnpos = rc.top + _btnSize;
690             int fwdbtnpos = rc.bottom - _btnSize;
691             r = rc;
692             r.bottom = backbtnpos;
693             _btnBack.layout(r);
694             r = rc;
695             r.top = fwdbtnpos;
696             _btnForward.layout(r);
697             // indicator
698             r = rc;
699             r.top = backbtnpos;
700             r.bottom = fwdbtnpos;
701             _scrollArea = r;
702         } else {
703             // horizontal
704             int backbtnpos = rc.left + _btnSize;
705             int fwdbtnpos = rc.right - _btnSize;
706             r = rc;
707             r.right = backbtnpos;
708             _btnBack.layout(r);
709             r = rc;
710             r.left = fwdbtnpos;
711             _btnForward.layout(r);
712             // indicator
713             r = rc;
714             r.left = backbtnpos;
715             r.right = fwdbtnpos;
716             _scrollArea = r;
717         }
718 		layoutButtons();
719         _pos = rc;
720     }
721 
722     override bool onClick(Widget source) {
723         Log.d("Scrollbar.onClick ", source.id);
724         if (source.compareId("BACK"))
725             return sendScrollEvent(ScrollAction.LineUp, position);
726         if (source.compareId("FORWARD"))
727             return sendScrollEvent(ScrollAction.LineDown, position);
728         if (source.compareId("PAGE_UP"))
729             return sendScrollEvent(ScrollAction.PageUp, position);
730         if (source.compareId("PAGE_DOWN"))
731             return sendScrollEvent(ScrollAction.PageDown, position);
732         return true;
733     }
734 
735     /// handle mouse wheel events
736     override bool onMouseEvent(MouseEvent event) {
737         if (visibility != Visibility.Visible)
738             return false;
739         if (event.action == MouseAction.Wheel) {
740             int delta = event.wheelDelta;
741             if (delta > 0)
742                 sendScrollEvent(ScrollAction.LineUp, position);
743             else if (delta < 0)
744                 sendScrollEvent(ScrollAction.LineDown, position);
745             return true;
746         }
747         return super.onMouseEvent(event);
748     }
749 
750     /// Draw widget at its position to buffer
751     override void onDraw(DrawBuf buf) {
752         if (visibility != Visibility.Visible)
753             return;
754         super.onDraw(buf);
755         Rect rc = _pos;
756         applyMargins(rc);
757         applyPadding(rc);
758         auto saver = ClipRectSaver(buf, rc);
759         _btnForward.onDraw(buf);
760         _btnBack.onDraw(buf);
761         _pageUp.onDraw(buf);
762         _pageDown.onDraw(buf);
763         _indicator.onDraw(buf);
764     }
765 }
766