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