1 // Written in the D programming language.
2
3 /**
4 This module contains simple scrollbar-like controls implementation.
5
6 ScrollBar - scrollbar control
7
8 SliderWidget - slider control
9
10
11
12 Synopsis:
13
14 ----
15 import dlangui.widgets.scrollbar;
16
17 ----
18
19 Copyright: Vadim Lopatin, 2014
20 License: Boost License 1.0
21 Authors: Vadim Lopatin, coolreader.org@gmail.com
22 */
23 module dlangui.widgets.scrollbar;
24
25
26 import dlangui.widgets.widget;
27 import dlangui.widgets.layouts;
28 import dlangui.widgets.controls;
29 import dlangui.core.events;
30 import dlangui.core.stdaction;
31
32 private import std.algorithm;
33 private import std.conv : to;
34 private import std.utf : toUTF32;
35
36 /// scroll event handler interface
37 interface OnScrollHandler {
38 /// handle scroll event
39 bool onScrollEvent(AbstractSlider source, ScrollEvent event);
40 }
41
42 /// base class for widgets like scrollbars and sliders
43 class AbstractSlider : WidgetGroup {
44 protected int _minValue = 0;
45 protected int _maxValue = 100;
46 protected int _pageSize = 30;
47 protected int _position = 20;
48
49 /// create with ID parameter
50 this(string ID) {
51 super(ID);
52 }
53
54 /// scroll event listeners
55 Signal!OnScrollHandler scrollEvent;
56
57 /// returns slider position
58 @property int position() const { return _position; }
59 /// sets new slider position
60 @property AbstractSlider position(int newPosition) {
61 if (_position != newPosition) {
62 _position = newPosition;
63 onPositionChanged();
64 }
65 return this;
66 }
67 protected void onPositionChanged() {
68 requestLayout();
69 }
70 /// returns slider range min value
71 @property int minValue() const { return _minValue; }
72 /// sets slider range min value
73 @property AbstractSlider minValue(int v) { _minValue = v; return this; }
74 /// returns slider range max value
75 @property int maxValue() const { return _maxValue; }
76 /// sets slider range max value
77 @property AbstractSlider maxValue(int v) { _maxValue = v; return this; }
78
79
80
81 /// page size (visible area size)
82 @property int pageSize() const { return _pageSize; }
83 /// set page size (visible area size)
84 @property AbstractSlider pageSize(int size) {
85 if (_pageSize != size) {
86 _pageSize = size;
87 requestLayout();
88 }
89 return this;
90 }
91
92 /// set int property value, for ML loaders
93 //mixin(generatePropertySettersMethodOverride("setIntProperty", "int",
94 // "minValue", "maxValue", "pageSize", "position"));
95 /// set int property value, for ML loaders
96 override bool setIntProperty(string name, int value) {
97 if (name.equal("orientation")) { // use same value for all sides
98 orientation = cast(Orientation)value;
99 return true;
100 }
101 mixin(generatePropertySetters("minValue", "maxValue", "pageSize", "position"));
102 return super.setIntProperty(name, value);
103 }
104
105 /// set new range (min and max values for slider)
106 AbstractSlider setRange(int min, int max) {
107 if (_minValue != min || _maxValue != max) {
108 _minValue = min;
109 _maxValue = max;
110 requestLayout();
111 }
112 return this;
113 }
114
115 bool sendScrollEvent(ScrollAction action) {
116 return sendScrollEvent(action, _position);
117 }
118
119 bool sendScrollEvent(ScrollAction action, int position) {
120 if (!scrollEvent.assigned)
121 return false;
122 ScrollEvent event = new ScrollEvent(action, _minValue, _maxValue, _pageSize, position);
123 bool res = scrollEvent(this, event);
124 if (event.positionChanged) {
125 _position = event.position;
126 if (_position > _maxValue)
127 _position = _maxValue;
128 if (_position < _minValue)
129 _position = _minValue;
130 onPositionChanged();
131 }
132 return true;
133 }
134
135 protected Orientation _orientation = Orientation.Vertical;
136 /// returns scrollbar orientation (Vertical, Horizontal)
137 @property Orientation orientation() { return _orientation; }
138 /// sets scrollbar orientation
139 @property AbstractSlider orientation(Orientation value) {
140 if (_orientation != value) {
141 _orientation = value;
142 requestLayout();
143 }
144 return this;
145 }
146
147 }
148
149 /// scroll bar - either vertical or horizontal
150 class ScrollBar : AbstractSlider, OnClickHandler {
151 protected ImageButton _btnBack;
152 protected ImageButton _btnForward;
153 protected SliderButton _indicator;
154 protected PageScrollButton _pageUp;
155 protected PageScrollButton _pageDown;
156 protected Rect _scrollArea;
157 protected int _btnSize;
158 protected int _minIndicatorSize;
159
160
161
162 class PageScrollButton : Widget {
163 this(string ID) {
164 super(ID);
165 styleId = STYLE_PAGE_SCROLL;
166 trackHover = true;
167 clickable = true;
168 }
169 }
170
171 class SliderButton : ImageButton {
172 Point _dragStart;
173 int _dragStartPosition;
174 bool _dragging;
175 Rect _dragStartRect;
176
177 this(string resourceId) {
178 super("SLIDER", resourceId);
179 styleId = STYLE_SCROLLBAR_BUTTON;
180 trackHover = true;
181 }
182
183 /// process mouse event; return true if event is processed by widget.
184 override bool onMouseEvent(MouseEvent event) {
185 // support onClick
186 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
187 setState(State.Pressed);
188 _dragging = true;
189 _dragStart.x = event.x;
190 _dragStart.y = event.y;
191 _dragStartPosition = _position;
192 _dragStartRect = _pos;
193 sendScrollEvent(ScrollAction.SliderPressed, _position);
194 return true;
195 }
196 if (event.action == MouseAction.FocusOut && _dragging) {
197 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusOut");
198 return true;
199 }
200 if (event.action == MouseAction.FocusIn && _dragging) {
201 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusIn");
202 return true;
203 }
204 if (event.action == MouseAction.Move && _dragging) {
205 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x;
206 debug(scrollbar) Log.d("ScrollBar slider dragging - Move delta=", delta);
207 Rect rc = _dragStartRect;
208 int offset;
209 int space;
210 if (_orientation == Orientation.Vertical) {
211 rc.top += delta;
212 rc.bottom += delta;
213 if (rc.top < _scrollArea.top) {
214 rc.top = _scrollArea.top;
215 rc.bottom = _scrollArea.top + _dragStartRect.height;
216 } else if (rc.bottom > _scrollArea.bottom) {
217 rc.top = _scrollArea.bottom - _dragStartRect.height;
218 rc.bottom = _scrollArea.bottom;
219 }
220 offset = rc.top - _scrollArea.top;
221 space = _scrollArea.height - rc.height;
222 } else {
223 rc.left += delta;
224 rc.right += delta;
225 if (rc.left < _scrollArea.left) {
226 rc.left = _scrollArea.left;
227 rc.right = _scrollArea.left + _dragStartRect.width;
228 } else if (rc.right > _scrollArea.right) {
229 rc.left = _scrollArea.right - _dragStartRect.width;
230 rc.right = _scrollArea.right;
231 }
232 offset = rc.left - _scrollArea.left;
233 space = _scrollArea.width - rc.width;
234 }
235 layoutButtons(rc);
236 //_pos = rc;
237 int position = cast(int)(space > 0 ? _minValue + cast(long)offset * (_maxValue - _minValue - _pageSize) / space : 0);
238 invalidate();
239 onIndicatorDragging(_dragStartPosition, position);
240 return true;
241 }
242 if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) {
243 resetState(State.Pressed);
244 if (_dragging) {
245 sendScrollEvent(ScrollAction.SliderReleased, _position);
246 _dragging = false;
247 }
248 return true;
249 }
250 if (event.action == MouseAction.Move && trackHover) {
251 if (!(state & State.Hovered)) {
252 debug(scrollbar) Log.d("Hover ", id);
253 setState(State.Hovered);
254 }
255 return true;
256 }
257 if (event.action == MouseAction.Leave && trackHover) {
258 debug(scrollbar) Log.d("Leave ", id);
259 resetState(State.Hovered);
260 return true;
261 }
262 if (event.action == MouseAction.Cancel && trackHover) {
263 debug(scrollbar) Log.d("Cancel ? trackHover", id);
264 resetState(State.Hovered);
265 resetState(State.Pressed);
266 _dragging = false;
267 return true;
268 }
269 if (event.action == MouseAction.Cancel) {
270 debug(scrollbar) Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel");
271 resetState(State.Pressed);
272 _dragging = false;
273 return true;
274 }
275 return false;
276 }
277
278 }
279
280 protected bool onIndicatorDragging(int initialPosition, int currentPosition) {
281 _position = currentPosition;
282 return sendScrollEvent(ScrollAction.SliderMoved, currentPosition);
283 }
284
285 /// true if full scroll range is visible, and no need of scrolling at all
286 @property bool fullRangeVisible() {
287 return _pageSize >= _maxValue - _minValue;
288 }
289
290 private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) {
291 int dv = _maxValue - _minValue;
292 if (_pageSize >= dv) {
293 // full size
294 spaceBackSize = spaceForwardSize = 0;
295 indicatorSize = availableSize;
296 return false;
297 }
298 if (dv < 0)
299 dv = 0;
300 indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize;
301 if (indicatorSize < _minIndicatorSize)
302 indicatorSize = _minIndicatorSize;
303 if (indicatorSize >= availableSize) {
304 // full size
305 spaceBackSize = spaceForwardSize = 0;
306 indicatorSize = availableSize;
307 return false;
308 }
309 int spaceLeft = availableSize - indicatorSize;
310 int topv = _position - _minValue;
311 int bottomv = _position + _pageSize - _minValue;
312 if (topv < 0)
313 topv = 0;
314 if (bottomv > dv)
315 bottomv = dv;
316 bottomv = dv - bottomv;
317 spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv));
318 spaceForwardSize = spaceLeft - spaceBackSize;
319 return true;
320 }
321
322 /// returns scrollbar orientation (Vertical, Horizontal)
323 override @property Orientation orientation() { return _orientation; }
324 /// sets scrollbar orientation
325 override @property AbstractSlider orientation(Orientation value) {
326 if (_orientation != value) {
327 _orientation = value;
328 updateDrawableIds();
329 requestLayout();
330 }
331 return this;
332 }
333
334 void updateDrawableIds() {
335 _btnBack.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT);
336 _btnForward.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT);
337 _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL);
338 }
339
340 /// handle theme change: e.g. reload some themed resources
341 override void onThemeChanged() {
342 super.onThemeChanged();
343 updateDrawableIds();
344 }
345
346 /// set string property value, for ML loaders
347 override bool setStringProperty(string name, string value) {
348 if (name.equal("orientation")) {
349 if (value.equal("Vertical") || value.equal("vertical"))
350 orientation = Orientation.Vertical;
351 else
352 orientation = Orientation.Horizontal;
353 return true;
354 }
355 return super.setStringProperty(name, value);
356 }
357
358
359 /// empty parameter list constructor - for usage by factory
360 this() {
361 this(null, Orientation.Vertical);
362 }
363 /// create with ID parameter
364 this(string ID, Orientation orient = Orientation.Vertical) {
365 super(ID);
366 styleId = STYLE_SCROLLBAR;
367 _orientation = orient;
368 _btnBack = new ImageButton("BACK", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT));
369 _btnForward = new ImageButton("FORWARD", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT));
370 _pageUp = new PageScrollButton("PAGE_UP");
371 _pageDown = new PageScrollButton("PAGE_DOWN");
372 _btnBack.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT;
373 _btnForward.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT;
374 _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL));
375 addChild(_btnBack);
376 addChild(_btnForward);
377 addChild(_indicator);
378 addChild(_pageUp);
379 addChild(_pageDown);
380 _btnBack.focusable = false;
381 _btnForward.focusable = false;
382 _indicator.focusable = false;
383 _pageUp.focusable = false;
384 _pageDown.focusable = false;
385 _btnBack.click = &onClick;
386 _btnForward.click = &onClick;
387 _pageUp.click = &onClick;
388 _pageDown.click = &onClick;
389 }
390
391 override void measure(int parentWidth, int parentHeight) {
392 Point sz;
393 _btnBack.measure(parentWidth, parentHeight);
394 _btnForward.measure(parentWidth, parentHeight);
395 _indicator.measure(parentWidth, parentHeight);
396 _pageUp.measure(parentWidth, parentHeight);
397 _pageDown.measure(parentWidth, parentHeight);
398 _btnSize = _btnBack.measuredWidth;
399 _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth;
400 if (_btnSize < _minIndicatorSize)
401 _btnSize = _minIndicatorSize;
402 if (_btnSize < _btnForward.measuredWidth)
403 _btnSize = _btnForward.measuredWidth;
404 if (_btnSize < _btnForward.measuredHeight)
405 _btnSize = _btnForward.measuredHeight;
406 if (_btnSize < _btnBack.measuredHeight)
407 _btnSize = _btnBack.measuredHeight;
408 static if (BACKEND_GUI) {
409 if (_btnSize < 16)
410 _btnSize = 16;
411 }
412 if (_orientation == Orientation.Vertical) {
413 // vertical
414 sz.x = _btnSize;
415 sz.y = _btnSize * 5; // min height
416 } else {
417 // horizontal
418 sz.y = _btnSize;
419 sz.x = _btnSize * 5; // min height
420 }
421 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
422 }
423
424 override protected void onPositionChanged() {
425 if (!needLayout)
426 layoutButtons();
427 }
428
429 /// hide controls when scroll is not possible
430 protected void updateState() {
431 bool canScroll = _maxValue - _minValue > _pageSize;
432 if (canScroll) {
433 _btnBack.setState(State.Enabled);
434 _btnForward.setState(State.Enabled);
435 _indicator.visibility = Visibility.Visible;
436 if (_position > _minValue)
437 _pageUp.visibility = Visibility.Visible;
438 if (_position < _maxValue)
439 _pageDown.visibility = Visibility.Visible;
440 } else {
441 _btnBack.resetState(State.Enabled);
442 _btnForward.resetState(State.Enabled);
443 _indicator.visibility = Visibility.Gone;
444 _pageUp.visibility = Visibility.Gone;
445 _pageDown.visibility = Visibility.Gone;
446 }
447 cancelLayout();
448 }
449
450 override void cancelLayout() {
451 _btnBack.cancelLayout();
452 _btnForward.cancelLayout();
453 _indicator.cancelLayout();
454 _pageUp.cancelLayout();
455 _pageDown.cancelLayout();
456 super.cancelLayout();
457 }
458
459 protected void layoutButtons() {
460 Rect irc = _scrollArea;
461 if (_orientation == Orientation.Vertical) {
462 // vertical
463 int spaceBackSize, spaceForwardSize, indicatorSize;
464 bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize);
465 irc.top += spaceBackSize;
466 irc.bottom -= spaceForwardSize;
467 layoutButtons(irc);
468 } else {
469 // horizontal
470 int spaceBackSize, spaceForwardSize, indicatorSize;
471 bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize);
472 irc.left += spaceBackSize;
473 irc.right -= spaceForwardSize;
474 layoutButtons(irc);
475 }
476 updateState();
477 cancelLayout();
478 }
479
480 protected void layoutButtons(Rect irc) {
481 Rect r;
482 _indicator.visibility = Visibility.Visible;
483 if (_orientation == Orientation.Vertical) {
484 _indicator.layout(irc);
485 if (_scrollArea.top < irc.top) {
486 r = _scrollArea;
487 r.bottom = irc.top;
488 _pageUp.visibility = Visibility.Visible;
489 _pageUp.layout(r);
490 } else {
491 _pageUp.visibility = Visibility.Invisible;
492 }
493 if (_scrollArea.bottom > irc.bottom) {
494 r = _scrollArea;
495 r.top = irc.bottom;
496 _pageDown.visibility = Visibility.Visible;
497 _pageDown.layout(r);
498 } else {
499 _pageDown.visibility = Visibility.Invisible;
500 }
501 } else {
502 _indicator.layout(irc);
503 if (_scrollArea.left < irc.left) {
504 r = _scrollArea;
505 r.right = irc.left;
506 _pageUp.visibility = Visibility.Visible;
507 _pageUp.layout(r);
508 } else {
509 _pageUp.visibility = Visibility.Invisible;
510 }
511 if (_scrollArea.right > irc.right) {
512 r = _scrollArea;
513 r.left = irc.right;
514 _pageDown.visibility = Visibility.Visible;
515 _pageDown.layout(r);
516 } else {
517 _pageDown.visibility = Visibility.Invisible;
518 }
519 }
520 }
521
522 override void layout(Rect rc) {
523 _needLayout = false;
524 applyMargins(rc);
525 applyPadding(rc);
526 Rect r;
527 if (_orientation == Orientation.Vertical) {
528 // vertical
529 // buttons
530 int backbtnpos = rc.top + _btnSize;
531 int fwdbtnpos = rc.bottom - _btnSize;
532 r = rc;
533 r.bottom = backbtnpos;
534 _btnBack.layout(r);
535 r = rc;
536 r.top = fwdbtnpos;
537 _btnForward.layout(r);
538 // indicator
539 r = rc;
540 r.top = backbtnpos;
541 r.bottom = fwdbtnpos;
542 _scrollArea = r;
543 } else {
544 // horizontal
545 int backbtnpos = rc.left + _btnSize;
546 int fwdbtnpos = rc.right - _btnSize;
547 r = rc;
548 r.right = backbtnpos;
549 _btnBack.layout(r);
550 r = rc;
551 r.left = fwdbtnpos;
552 _btnForward.layout(r);
553 // indicator
554 r = rc;
555 r.left = backbtnpos;
556 r.right = fwdbtnpos;
557 _scrollArea = r;
558 }
559 layoutButtons();
560 _pos = rc;
561 }
562
563 override bool onClick(Widget source) {
564 Log.d("Scrollbar.onClick ", source.id);
565 if (source.compareId("BACK"))
566 return sendScrollEvent(ScrollAction.LineUp, position);
567 if (source.compareId("FORWARD"))
568 return sendScrollEvent(ScrollAction.LineDown, position);
569 if (source.compareId("PAGE_UP"))
570 return sendScrollEvent(ScrollAction.PageUp, position);
571 if (source.compareId("PAGE_DOWN"))
572 return sendScrollEvent(ScrollAction.PageDown, position);
573 return true;
574 }
575
576 /// handle mouse wheel events
577 override bool onMouseEvent(MouseEvent event) {
578 if (visibility != Visibility.Visible)
579 return false;
580 if (event.action == MouseAction.Wheel) {
581 int delta = event.wheelDelta;
582 if (delta > 0)
583 sendScrollEvent(ScrollAction.LineUp, position);
584 else if (delta < 0)
585 sendScrollEvent(ScrollAction.LineDown, position);
586 return true;
587 }
588 return true;
589 //return super.onMouseEvent(event);
590 }
591
592 /// Draw widget at its position to buffer
593 override void onDraw(DrawBuf buf) {
594 if (visibility != Visibility.Visible && !buf.isClippedOut(_pos))
595 return;
596 super.onDraw(buf);
597 Rect rc = _pos;
598 applyMargins(rc);
599 applyPadding(rc);
600 auto saver = ClipRectSaver(buf, rc, alpha);
601 _btnForward.onDraw(buf);
602 _btnBack.onDraw(buf);
603 _pageUp.onDraw(buf);
604 _pageDown.onDraw(buf);
605 _indicator.onDraw(buf);
606 }
607 }
608
609 /// scroll bar - either vertical or horizontal
610 class SliderWidget : AbstractSlider, OnClickHandler {
611 protected SliderButton _indicator;
612 protected PageScrollButton _pageUp;
613 protected PageScrollButton _pageDown;
614 protected Rect _scrollArea;
615 protected int _btnSize;
616 protected int _minIndicatorSize;
617
618 class PageScrollButton : Widget {
619 this(string ID) {
620 super(ID);
621 styleId = STYLE_PAGE_SCROLL;
622 trackHover = true;
623 clickable = true;
624 }
625 }
626
627 class SliderButton : ImageButton {
628 Point _dragStart;
629 int _dragStartPosition;
630 bool _dragging;
631 Rect _dragStartRect;
632
633 this(string resourceId) {
634 super("SLIDER", resourceId);
635 styleId = STYLE_SCROLLBAR_BUTTON;
636 trackHover = true;
637 }
638
639 /// process mouse event; return true if event is processed by widget.
640 override bool onMouseEvent(MouseEvent event) {
641 // support onClick
642 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
643 setState(State.Pressed);
644 _dragging = true;
645 _dragStart.x = event.x;
646 _dragStart.y = event.y;
647 _dragStartPosition = _position;
648 _dragStartRect = _pos;
649 sendScrollEvent(ScrollAction.SliderPressed, _position);
650 return true;
651 }
652 if (event.action == MouseAction.FocusOut && _dragging) {
653 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusOut");
654 return true;
655 }
656 if (event.action == MouseAction.FocusIn && _dragging) {
657 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusIn");
658 return true;
659 }
660 if (event.action == MouseAction.Move && _dragging) {
661 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x;
662 debug(scrollbar) Log.d("ScrollBar slider dragging - Move delta=", delta);
663 Rect rc = _dragStartRect;
664 int offset;
665 int space;
666 if (_orientation == Orientation.Vertical) {
667 rc.top += delta;
668 rc.bottom += delta;
669 if (rc.top < _scrollArea.top) {
670 rc.top = _scrollArea.top;
671 rc.bottom = _scrollArea.top + _dragStartRect.height;
672 } else if (rc.bottom > _scrollArea.bottom) {
673 rc.top = _scrollArea.bottom - _dragStartRect.height;
674 rc.bottom = _scrollArea.bottom;
675 }
676 offset = rc.top - _scrollArea.top;
677 space = _scrollArea.height - rc.height;
678 } else {
679 rc.left += delta;
680 rc.right += delta;
681 if (rc.left < _scrollArea.left) {
682 rc.left = _scrollArea.left;
683 rc.right = _scrollArea.left + _dragStartRect.width;
684 } else if (rc.right > _scrollArea.right) {
685 rc.left = _scrollArea.right - _dragStartRect.width;
686 rc.right = _scrollArea.right;
687 }
688 offset = rc.left - _scrollArea.left;
689 space = _scrollArea.width - rc.width;
690 }
691 layoutButtons(rc);
692 //_pos = rc;
693 int position = cast(int)(space > 0 ? _minValue + cast(long)offset * (_maxValue - _minValue - _pageSize) / space : 0);
694 invalidate();
695 onIndicatorDragging(_dragStartPosition, position);
696 return true;
697 }
698 if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) {
699 resetState(State.Pressed);
700 if (_dragging) {
701 sendScrollEvent(ScrollAction.SliderReleased, _position);
702 _dragging = false;
703 }
704 return true;
705 }
706 if (event.action == MouseAction.Move && trackHover) {
707 if (!(state & State.Hovered)) {
708 debug(scrollbar) Log.d("Hover ", id);
709 setState(State.Hovered);
710 }
711 return true;
712 }
713 if (event.action == MouseAction.Leave && trackHover) {
714 debug(scrollbar) Log.d("Leave ", id);
715 resetState(State.Hovered);
716 return true;
717 }
718 if (event.action == MouseAction.Cancel && trackHover) {
719 debug(scrollbar) Log.d("Cancel ? trackHover", id);
720 resetState(State.Hovered);
721 resetState(State.Pressed);
722 _dragging = false;
723 return true;
724 }
725 if (event.action == MouseAction.Cancel) {
726 debug(scrollbar) Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel");
727 resetState(State.Pressed);
728 _dragging = false;
729 return true;
730 }
731 return false;
732 }
733
734 }
735
736 protected bool onIndicatorDragging(int initialPosition, int currentPosition) {
737 _position = currentPosition;
738 return sendScrollEvent(ScrollAction.SliderMoved, currentPosition);
739 }
740
741 private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) {
742 int dv = _maxValue - _minValue;
743 if (_pageSize >= dv) {
744 // full size
745 spaceBackSize = spaceForwardSize = 0;
746 indicatorSize = availableSize;
747 return false;
748 }
749 if (dv < 0)
750 dv = 0;
751 indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize;
752 if (indicatorSize < _minIndicatorSize)
753 indicatorSize = _minIndicatorSize;
754 if (indicatorSize >= availableSize) {
755 // full size
756 spaceBackSize = spaceForwardSize = 0;
757 indicatorSize = availableSize;
758 return false;
759 }
760 int spaceLeft = availableSize - indicatorSize;
761 int topv = _position - _minValue;
762 int bottomv = _position + _pageSize - _minValue;
763 if (topv < 0)
764 topv = 0;
765 if (bottomv > dv)
766 bottomv = dv;
767 bottomv = dv - bottomv;
768 spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv));
769 spaceForwardSize = spaceLeft - spaceBackSize;
770 return true;
771 }
772
773 /// returns scrollbar orientation (Vertical, Horizontal)
774 override @property Orientation orientation() { return _orientation; }
775 /// sets scrollbar orientation
776 override @property AbstractSlider orientation(Orientation value) {
777 if (_orientation != value) {
778 _orientation = value;
779 _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL);
780 requestLayout();
781 }
782 return this;
783 }
784
785 /// set string property value, for ML loaders
786 override bool setStringProperty(string name, string value) {
787 if (name.equal("orientation")) {
788 if (value.equal("Vertical") || value.equal("vertical"))
789 orientation = Orientation.Vertical;
790 else
791 orientation = Orientation.Horizontal;
792 return true;
793 }
794 return super.setStringProperty(name, value);
795 }
796
797
798 /// empty parameter list constructor - for usage by factory
799 this() {
800 this(null, Orientation.Horizontal);
801 }
802 /// create with ID parameter
803 this(string ID, Orientation orient = Orientation.Horizontal) {
804 super(ID);
805 styleId = STYLE_SLIDER;
806 _orientation = orient;
807 _pageSize = 1;
808 _pageUp = new PageScrollButton("PAGE_UP");
809 _pageDown = new PageScrollButton("PAGE_DOWN");
810 _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL));
811 addChild(_indicator);
812 addChild(_pageUp);
813 addChild(_pageDown);
814 _indicator.focusable = false;
815 _pageUp.focusable = false;
816 _pageDown.focusable = false;
817 _pageUp.click = &onClick;
818 _pageDown.click = &onClick;
819 }
820
821 override void measure(int parentWidth, int parentHeight) {
822 Point sz;
823 _indicator.measure(parentWidth, parentHeight);
824 _pageUp.measure(parentWidth, parentHeight);
825 _pageDown.measure(parentWidth, parentHeight);
826 _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth;
827 _btnSize = _minIndicatorSize;
828 if (_btnSize < _minIndicatorSize)
829 _btnSize = _minIndicatorSize;
830 static if (BACKEND_GUI) {
831 if (_btnSize < 16)
832 _btnSize = 16;
833 }
834 if (_orientation == Orientation.Vertical) {
835 // vertical
836 sz.x = _btnSize;
837 sz.y = _btnSize * 5; // min height
838 } else {
839 // horizontal
840 sz.y = _btnSize;
841 sz.x = _btnSize * 5; // min height
842 }
843 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
844 }
845
846 override protected void onPositionChanged() {
847 if (!needLayout)
848 layoutButtons();
849 }
850
851 /// hide controls when scroll is not possible
852 protected void updateState() {
853 bool canScroll = _maxValue - _minValue > _pageSize;
854 if (canScroll) {
855 _indicator.visibility = Visibility.Visible;
856 _pageUp.visibility = Visibility.Visible;
857 _pageDown.visibility = Visibility.Visible;
858 } else {
859 _indicator.visibility = Visibility.Gone;
860 _pageUp.visibility = Visibility.Gone;
861 _pageDown.visibility = Visibility.Gone;
862 }
863 cancelLayout();
864 }
865
866 override void cancelLayout() {
867 _indicator.cancelLayout();
868 _pageUp.cancelLayout();
869 _pageDown.cancelLayout();
870 super.cancelLayout();
871 }
872
873 protected void layoutButtons() {
874 Rect irc = _scrollArea;
875 if (_orientation == Orientation.Vertical) {
876 // vertical
877 int spaceBackSize, spaceForwardSize, indicatorSize;
878 bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize);
879 irc.top += spaceBackSize;
880 irc.bottom -= spaceForwardSize;
881 layoutButtons(irc);
882 } else {
883 // horizontal
884 int spaceBackSize, spaceForwardSize, indicatorSize;
885 bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize);
886 irc.left += spaceBackSize;
887 irc.right -= spaceForwardSize;
888 layoutButtons(irc);
889 }
890 updateState();
891 cancelLayout();
892 }
893
894 protected void layoutButtons(Rect irc) {
895 Rect r;
896 _indicator.visibility = Visibility.Visible;
897 if (_orientation == Orientation.Vertical) {
898 _indicator.layout(irc);
899 if (_scrollArea.top < irc.top) {
900 r = _scrollArea;
901 r.bottom = irc.top;
902 _pageUp.layout(r);
903 _pageUp.visibility = Visibility.Visible;
904 } else {
905 _pageUp.visibility = Visibility.Invisible;
906 }
907 if (_scrollArea.bottom > irc.bottom) {
908 r = _scrollArea;
909 r.top = irc.bottom;
910 _pageDown.layout(r);
911 _pageDown.visibility = Visibility.Visible;
912 } else {
913 _pageDown.visibility = Visibility.Invisible;
914 }
915 } else {
916 _indicator.layout(irc);
917 if (_scrollArea.left < irc.left) {
918 r = _scrollArea;
919 r.right = irc.left;
920 _pageUp.layout(r);
921 _pageUp.visibility = Visibility.Visible;
922 } else {
923 _pageUp.visibility = Visibility.Invisible;
924 }
925 if (_scrollArea.right > irc.right) {
926 r = _scrollArea;
927 r.left = irc.right;
928 _pageDown.layout(r);
929 _pageDown.visibility = Visibility.Visible;
930 } else {
931 _pageDown.visibility = Visibility.Invisible;
932 }
933 }
934 }
935
936 override void layout(Rect rc) {
937 _needLayout = false;
938 applyMargins(rc);
939 applyPadding(rc);
940 Rect r;
941 if (_orientation == Orientation.Vertical) {
942 // vertical
943 // buttons
944 // indicator
945 r = rc;
946 _scrollArea = r;
947 } else {
948 // horizontal
949 // indicator
950 r = rc;
951 _scrollArea = r;
952 }
953 layoutButtons();
954 _pos = rc;
955 }
956
957 override bool onClick(Widget source) {
958 Log.d("Scrollbar.onClick ", source.id);
959 if (source.compareId("PAGE_UP"))
960 return sendScrollEvent(ScrollAction.PageUp, position);
961 if (source.compareId("PAGE_DOWN"))
962 return sendScrollEvent(ScrollAction.PageDown, position);
963 return true;
964 }
965
966 /// handle mouse wheel events
967 override bool onMouseEvent(MouseEvent event) {
968 if (visibility != Visibility.Visible)
969 return false;
970 if (event.action == MouseAction.Wheel) {
971 int delta = event.wheelDelta;
972 if (delta > 0)
973 sendScrollEvent(ScrollAction.LineUp, position);
974 else if (delta < 0)
975 sendScrollEvent(ScrollAction.LineDown, position);
976 return true;
977 }
978 return super.onMouseEvent(event);
979 }
980
981 /// Draw widget at its position to buffer
982 override void onDraw(DrawBuf buf) {
983 if (visibility != Visibility.Visible && !buf.isClippedOut(_pos))
984 return;
985 Rect rc = _pos;
986 applyMargins(rc);
987 auto saver = ClipRectSaver(buf, rc, alpha);
988 DrawableRef bg = backgroundDrawable;
989 if (!bg.isNull) {
990 Rect r = rc;
991 if (_orientation == Orientation.Vertical) {
992 int dw = bg.width;
993 r.left += (rc.width - dw)/2;
994 r.right = r.left + dw;
995 } else {
996 int dw = bg.height;
997 r.top += (rc.height - dw)/2;
998 r.bottom = r.top + dw;
999 }
1000 bg.drawTo(buf, r, state);
1001 }
1002 applyPadding(rc);
1003 if (state & State.Focused) {
1004 rc.expand(FOCUS_RECT_PADDING, FOCUS_RECT_PADDING);
1005 drawFocusRect(buf, rc);
1006 }
1007 _needDraw = false;
1008 _pageUp.onDraw(buf);
1009 _pageDown.onDraw(buf);
1010 _indicator.onDraw(buf);
1011 }
1012 }
1013