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 private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) { 286 int dv = _maxValue - _minValue; 287 if (_pageSize >= dv) { 288 // full size 289 spaceBackSize = spaceForwardSize = 0; 290 indicatorSize = availableSize; 291 return false; 292 } 293 if (dv < 0) 294 dv = 0; 295 indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize; 296 if (indicatorSize < _minIndicatorSize) 297 indicatorSize = _minIndicatorSize; 298 if (indicatorSize >= availableSize) { 299 // full size 300 spaceBackSize = spaceForwardSize = 0; 301 indicatorSize = availableSize; 302 return false; 303 } 304 int spaceLeft = availableSize - indicatorSize; 305 int topv = _position - _minValue; 306 int bottomv = _position + _pageSize - _minValue; 307 if (topv < 0) 308 topv = 0; 309 if (bottomv > dv) 310 bottomv = dv; 311 bottomv = dv - bottomv; 312 spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv)); 313 spaceForwardSize = spaceLeft - spaceBackSize; 314 return true; 315 } 316 317 /// returns scrollbar orientation (Vertical, Horizontal) 318 override @property Orientation orientation() { return _orientation; } 319 /// sets scrollbar orientation 320 override @property AbstractSlider orientation(Orientation value) { 321 if (_orientation != value) { 322 _orientation = value; 323 _btnBack.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT); 324 _btnForward.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT); 325 _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL); 326 requestLayout(); 327 } 328 return this; 329 } 330 331 /// set string property value, for ML loaders 332 override bool setStringProperty(string name, string value) { 333 if (name.equal("orientation")) { 334 if (value.equal("Vertical") || value.equal("vertical")) 335 orientation = Orientation.Vertical; 336 else 337 orientation = Orientation.Horizontal; 338 return true; 339 } 340 return super.setStringProperty(name, value); 341 } 342 343 344 /// empty parameter list constructor - for usage by factory 345 this() { 346 this(null, Orientation.Vertical); 347 } 348 /// create with ID parameter 349 this(string ID, Orientation orient = Orientation.Vertical) { 350 super(ID); 351 styleId = STYLE_SCROLLBAR; 352 _orientation = orient; 353 _btnBack = new ImageButton("BACK", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_UP : ATTR_SCROLLBAR_BUTTON_LEFT)); 354 _btnForward = new ImageButton("FORWARD", style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_BUTTON_DOWN : ATTR_SCROLLBAR_BUTTON_RIGHT)); 355 _pageUp = new PageScrollButton("PAGE_UP"); 356 _pageDown = new PageScrollButton("PAGE_DOWN"); 357 _btnBack.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT; 358 _btnForward.styleId = STYLE_SCROLLBAR_BUTTON_TRANSPARENT; 359 _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL)); 360 addChild(_btnBack); 361 addChild(_btnForward); 362 addChild(_indicator); 363 addChild(_pageUp); 364 addChild(_pageDown); 365 _btnBack.focusable = false; 366 _btnForward.focusable = false; 367 _indicator.focusable = false; 368 _pageUp.focusable = false; 369 _pageDown.focusable = false; 370 _btnBack.click = &onClick; 371 _btnForward.click = &onClick; 372 _pageUp.click = &onClick; 373 _pageDown.click = &onClick; 374 } 375 376 override void measure(int parentWidth, int parentHeight) { 377 Point sz; 378 _btnBack.measure(parentWidth, parentHeight); 379 _btnForward.measure(parentWidth, parentHeight); 380 _indicator.measure(parentWidth, parentHeight); 381 _pageUp.measure(parentWidth, parentHeight); 382 _pageDown.measure(parentWidth, parentHeight); 383 _btnSize = _btnBack.measuredWidth; 384 _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth; 385 if (_btnSize < _minIndicatorSize) 386 _btnSize = _minIndicatorSize; 387 if (_btnSize < _btnForward.measuredWidth) 388 _btnSize = _btnForward.measuredWidth; 389 if (_btnSize < _btnForward.measuredHeight) 390 _btnSize = _btnForward.measuredHeight; 391 if (_btnSize < _btnBack.measuredHeight) 392 _btnSize = _btnBack.measuredHeight; 393 static if (BACKEND_GUI) { 394 if (_btnSize < 16) 395 _btnSize = 16; 396 } 397 if (_orientation == Orientation.Vertical) { 398 // vertical 399 sz.x = _btnSize; 400 sz.y = _btnSize * 5; // min height 401 } else { 402 // horizontal 403 sz.y = _btnSize; 404 sz.x = _btnSize * 5; // min height 405 } 406 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 407 } 408 409 override protected void onPositionChanged() { 410 if (!needLayout) 411 layoutButtons(); 412 } 413 414 /// hide controls when scroll is not possible 415 protected void updateState() { 416 bool canScroll = _maxValue - _minValue > _pageSize; 417 if (canScroll) { 418 _btnBack.setState(State.Enabled); 419 _btnForward.setState(State.Enabled); 420 _indicator.visibility = Visibility.Visible; 421 _pageUp.visibility = Visibility.Visible; 422 _pageDown.visibility = Visibility.Visible; 423 } else { 424 _btnBack.resetState(State.Enabled); 425 _btnForward.resetState(State.Enabled); 426 _indicator.visibility = Visibility.Gone; 427 _pageUp.visibility = Visibility.Gone; 428 _pageDown.visibility = Visibility.Gone; 429 } 430 cancelLayout(); 431 } 432 433 override void cancelLayout() { 434 _btnBack.cancelLayout(); 435 _btnForward.cancelLayout(); 436 _indicator.cancelLayout(); 437 _pageUp.cancelLayout(); 438 _pageDown.cancelLayout(); 439 super.cancelLayout(); 440 } 441 442 protected void layoutButtons() { 443 Rect irc = _scrollArea; 444 if (_orientation == Orientation.Vertical) { 445 // vertical 446 int spaceBackSize, spaceForwardSize, indicatorSize; 447 bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize); 448 irc.top += spaceBackSize; 449 irc.bottom -= spaceForwardSize; 450 layoutButtons(irc); 451 } else { 452 // horizontal 453 int spaceBackSize, spaceForwardSize, indicatorSize; 454 bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize); 455 irc.left += spaceBackSize; 456 irc.right -= spaceForwardSize; 457 layoutButtons(irc); 458 } 459 updateState(); 460 cancelLayout(); 461 } 462 463 protected void layoutButtons(Rect irc) { 464 Rect r; 465 _indicator.visibility = Visibility.Visible; 466 if (_orientation == Orientation.Vertical) { 467 _indicator.layout(irc); 468 if (_scrollArea.top < irc.top) { 469 r = _scrollArea; 470 r.bottom = irc.top; 471 _pageUp.layout(r); 472 _pageUp.visibility = Visibility.Visible; 473 } else { 474 _pageUp.visibility = Visibility.Invisible; 475 } 476 if (_scrollArea.bottom > irc.bottom) { 477 r = _scrollArea; 478 r.top = irc.bottom; 479 _pageDown.layout(r); 480 _pageDown.visibility = Visibility.Visible; 481 } else { 482 _pageDown.visibility = Visibility.Invisible; 483 } 484 } else { 485 _indicator.layout(irc); 486 if (_scrollArea.left < irc.left) { 487 r = _scrollArea; 488 r.right = irc.left; 489 _pageUp.layout(r); 490 _pageUp.visibility = Visibility.Visible; 491 } else { 492 _pageUp.visibility = Visibility.Invisible; 493 } 494 if (_scrollArea.right > irc.right) { 495 r = _scrollArea; 496 r.left = irc.right; 497 _pageDown.layout(r); 498 _pageDown.visibility = Visibility.Visible; 499 } else { 500 _pageDown.visibility = Visibility.Invisible; 501 } 502 } 503 } 504 505 override void layout(Rect rc) { 506 _needLayout = false; 507 applyMargins(rc); 508 applyPadding(rc); 509 Rect r; 510 if (_orientation == Orientation.Vertical) { 511 // vertical 512 // buttons 513 int backbtnpos = rc.top + _btnSize; 514 int fwdbtnpos = rc.bottom - _btnSize; 515 r = rc; 516 r.bottom = backbtnpos; 517 _btnBack.layout(r); 518 r = rc; 519 r.top = fwdbtnpos; 520 _btnForward.layout(r); 521 // indicator 522 r = rc; 523 r.top = backbtnpos; 524 r.bottom = fwdbtnpos; 525 _scrollArea = r; 526 } else { 527 // horizontal 528 int backbtnpos = rc.left + _btnSize; 529 int fwdbtnpos = rc.right - _btnSize; 530 r = rc; 531 r.right = backbtnpos; 532 _btnBack.layout(r); 533 r = rc; 534 r.left = fwdbtnpos; 535 _btnForward.layout(r); 536 // indicator 537 r = rc; 538 r.left = backbtnpos; 539 r.right = fwdbtnpos; 540 _scrollArea = r; 541 } 542 layoutButtons(); 543 _pos = rc; 544 } 545 546 override bool onClick(Widget source) { 547 Log.d("Scrollbar.onClick ", source.id); 548 if (source.compareId("BACK")) 549 return sendScrollEvent(ScrollAction.LineUp, position); 550 if (source.compareId("FORWARD")) 551 return sendScrollEvent(ScrollAction.LineDown, position); 552 if (source.compareId("PAGE_UP")) 553 return sendScrollEvent(ScrollAction.PageUp, position); 554 if (source.compareId("PAGE_DOWN")) 555 return sendScrollEvent(ScrollAction.PageDown, position); 556 return true; 557 } 558 559 /// handle mouse wheel events 560 override bool onMouseEvent(MouseEvent event) { 561 if (visibility != Visibility.Visible) 562 return false; 563 if (event.action == MouseAction.Wheel) { 564 int delta = event.wheelDelta; 565 if (delta > 0) 566 sendScrollEvent(ScrollAction.LineUp, position); 567 else if (delta < 0) 568 sendScrollEvent(ScrollAction.LineDown, position); 569 return true; 570 } 571 return true; 572 //return super.onMouseEvent(event); 573 } 574 575 /// Draw widget at its position to buffer 576 override void onDraw(DrawBuf buf) { 577 if (visibility != Visibility.Visible && !buf.isClippedOut(_pos)) 578 return; 579 super.onDraw(buf); 580 Rect rc = _pos; 581 applyMargins(rc); 582 applyPadding(rc); 583 auto saver = ClipRectSaver(buf, rc, alpha); 584 _btnForward.onDraw(buf); 585 _btnBack.onDraw(buf); 586 _pageUp.onDraw(buf); 587 _pageDown.onDraw(buf); 588 _indicator.onDraw(buf); 589 } 590 } 591 592 /// scroll bar - either vertical or horizontal 593 class SliderWidget : AbstractSlider, OnClickHandler { 594 protected SliderButton _indicator; 595 protected PageScrollButton _pageUp; 596 protected PageScrollButton _pageDown; 597 protected Rect _scrollArea; 598 protected int _btnSize; 599 protected int _minIndicatorSize; 600 601 class PageScrollButton : Widget { 602 this(string ID) { 603 super(ID); 604 styleId = STYLE_PAGE_SCROLL; 605 trackHover = true; 606 clickable = true; 607 } 608 } 609 610 class SliderButton : ImageButton { 611 Point _dragStart; 612 int _dragStartPosition; 613 bool _dragging; 614 Rect _dragStartRect; 615 616 this(string resourceId) { 617 super("SLIDER", resourceId); 618 styleId = STYLE_SCROLLBAR_BUTTON; 619 trackHover = true; 620 } 621 622 /// process mouse event; return true if event is processed by widget. 623 override bool onMouseEvent(MouseEvent event) { 624 // support onClick 625 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 626 setState(State.Pressed); 627 _dragging = true; 628 _dragStart.x = event.x; 629 _dragStart.y = event.y; 630 _dragStartPosition = _position; 631 _dragStartRect = _pos; 632 sendScrollEvent(ScrollAction.SliderPressed, _position); 633 return true; 634 } 635 if (event.action == MouseAction.FocusOut && _dragging) { 636 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusOut"); 637 return true; 638 } 639 if (event.action == MouseAction.FocusIn && _dragging) { 640 debug(scrollbar) Log.d("ScrollBar slider dragging - FocusIn"); 641 return true; 642 } 643 if (event.action == MouseAction.Move && _dragging) { 644 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; 645 debug(scrollbar) Log.d("ScrollBar slider dragging - Move delta=", delta); 646 Rect rc = _dragStartRect; 647 int offset; 648 int space; 649 if (_orientation == Orientation.Vertical) { 650 rc.top += delta; 651 rc.bottom += delta; 652 if (rc.top < _scrollArea.top) { 653 rc.top = _scrollArea.top; 654 rc.bottom = _scrollArea.top + _dragStartRect.height; 655 } else if (rc.bottom > _scrollArea.bottom) { 656 rc.top = _scrollArea.bottom - _dragStartRect.height; 657 rc.bottom = _scrollArea.bottom; 658 } 659 offset = rc.top - _scrollArea.top; 660 space = _scrollArea.height - rc.height; 661 } else { 662 rc.left += delta; 663 rc.right += delta; 664 if (rc.left < _scrollArea.left) { 665 rc.left = _scrollArea.left; 666 rc.right = _scrollArea.left + _dragStartRect.width; 667 } else if (rc.right > _scrollArea.right) { 668 rc.left = _scrollArea.right - _dragStartRect.width; 669 rc.right = _scrollArea.right; 670 } 671 offset = rc.left - _scrollArea.left; 672 space = _scrollArea.width - rc.width; 673 } 674 layoutButtons(rc); 675 //_pos = rc; 676 int position = cast(int)(space > 0 ? _minValue + cast(long)offset * (_maxValue - _minValue - _pageSize) / space : 0); 677 invalidate(); 678 onIndicatorDragging(_dragStartPosition, position); 679 return true; 680 } 681 if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { 682 resetState(State.Pressed); 683 if (_dragging) { 684 sendScrollEvent(ScrollAction.SliderReleased, _position); 685 _dragging = false; 686 } 687 return true; 688 } 689 if (event.action == MouseAction.Move && trackHover) { 690 if (!(state & State.Hovered)) { 691 debug(scrollbar) Log.d("Hover ", id); 692 setState(State.Hovered); 693 } 694 return true; 695 } 696 if (event.action == MouseAction.Leave && trackHover) { 697 debug(scrollbar) Log.d("Leave ", id); 698 resetState(State.Hovered); 699 return true; 700 } 701 if (event.action == MouseAction.Cancel && trackHover) { 702 debug(scrollbar) Log.d("Cancel ? trackHover", id); 703 resetState(State.Hovered); 704 resetState(State.Pressed); 705 _dragging = false; 706 return true; 707 } 708 if (event.action == MouseAction.Cancel) { 709 debug(scrollbar) Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); 710 resetState(State.Pressed); 711 _dragging = false; 712 return true; 713 } 714 return false; 715 } 716 717 } 718 719 protected bool onIndicatorDragging(int initialPosition, int currentPosition) { 720 _position = currentPosition; 721 return sendScrollEvent(ScrollAction.SliderMoved, currentPosition); 722 } 723 724 private bool calcButtonSizes(int availableSize, ref int spaceBackSize, ref int spaceForwardSize, ref int indicatorSize) { 725 int dv = _maxValue - _minValue; 726 if (_pageSize >= dv) { 727 // full size 728 spaceBackSize = spaceForwardSize = 0; 729 indicatorSize = availableSize; 730 return false; 731 } 732 if (dv < 0) 733 dv = 0; 734 indicatorSize = dv ? _pageSize * availableSize / dv : _minIndicatorSize; 735 if (indicatorSize < _minIndicatorSize) 736 indicatorSize = _minIndicatorSize; 737 if (indicatorSize >= availableSize) { 738 // full size 739 spaceBackSize = spaceForwardSize = 0; 740 indicatorSize = availableSize; 741 return false; 742 } 743 int spaceLeft = availableSize - indicatorSize; 744 int topv = _position - _minValue; 745 int bottomv = _position + _pageSize - _minValue; 746 if (topv < 0) 747 topv = 0; 748 if (bottomv > dv) 749 bottomv = dv; 750 bottomv = dv - bottomv; 751 spaceBackSize = cast(int)(cast(long)spaceLeft * topv / (topv + bottomv)); 752 spaceForwardSize = spaceLeft - spaceBackSize; 753 return true; 754 } 755 756 /// returns scrollbar orientation (Vertical, Horizontal) 757 override @property Orientation orientation() { return _orientation; } 758 /// sets scrollbar orientation 759 override @property AbstractSlider orientation(Orientation value) { 760 if (_orientation != value) { 761 _orientation = value; 762 _indicator.drawableId = style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL); 763 requestLayout(); 764 } 765 return this; 766 } 767 768 /// set string property value, for ML loaders 769 override bool setStringProperty(string name, string value) { 770 if (name.equal("orientation")) { 771 if (value.equal("Vertical") || value.equal("vertical")) 772 orientation = Orientation.Vertical; 773 else 774 orientation = Orientation.Horizontal; 775 return true; 776 } 777 return super.setStringProperty(name, value); 778 } 779 780 781 /// empty parameter list constructor - for usage by factory 782 this() { 783 this(null, Orientation.Horizontal); 784 } 785 /// create with ID parameter 786 this(string ID, Orientation orient = Orientation.Horizontal) { 787 super(ID); 788 styleId = STYLE_SLIDER; 789 _orientation = orient; 790 _pageSize = 1; 791 _pageUp = new PageScrollButton("PAGE_UP"); 792 _pageDown = new PageScrollButton("PAGE_DOWN"); 793 _indicator = new SliderButton(style.customDrawableId(_orientation == Orientation.Vertical ? ATTR_SCROLLBAR_INDICATOR_VERTICAL : ATTR_SCROLLBAR_INDICATOR_HORIZONTAL)); 794 addChild(_indicator); 795 addChild(_pageUp); 796 addChild(_pageDown); 797 _indicator.focusable = false; 798 _pageUp.focusable = false; 799 _pageDown.focusable = false; 800 _pageUp.click = &onClick; 801 _pageDown.click = &onClick; 802 } 803 804 override void measure(int parentWidth, int parentHeight) { 805 Point sz; 806 _indicator.measure(parentWidth, parentHeight); 807 _pageUp.measure(parentWidth, parentHeight); 808 _pageDown.measure(parentWidth, parentHeight); 809 _minIndicatorSize = _orientation == Orientation.Vertical ? _indicator.measuredHeight : _indicator.measuredWidth; 810 _btnSize = _minIndicatorSize; 811 if (_btnSize < _minIndicatorSize) 812 _btnSize = _minIndicatorSize; 813 static if (BACKEND_GUI) { 814 if (_btnSize < 16) 815 _btnSize = 16; 816 } 817 if (_orientation == Orientation.Vertical) { 818 // vertical 819 sz.x = _btnSize; 820 sz.y = _btnSize * 5; // min height 821 } else { 822 // horizontal 823 sz.y = _btnSize; 824 sz.x = _btnSize * 5; // min height 825 } 826 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 827 } 828 829 override protected void onPositionChanged() { 830 if (!needLayout) 831 layoutButtons(); 832 } 833 834 /// hide controls when scroll is not possible 835 protected void updateState() { 836 bool canScroll = _maxValue - _minValue > _pageSize; 837 if (canScroll) { 838 _indicator.visibility = Visibility.Visible; 839 _pageUp.visibility = Visibility.Visible; 840 _pageDown.visibility = Visibility.Visible; 841 } else { 842 _indicator.visibility = Visibility.Gone; 843 _pageUp.visibility = Visibility.Gone; 844 _pageDown.visibility = Visibility.Gone; 845 } 846 cancelLayout(); 847 } 848 849 override void cancelLayout() { 850 _indicator.cancelLayout(); 851 _pageUp.cancelLayout(); 852 _pageDown.cancelLayout(); 853 super.cancelLayout(); 854 } 855 856 protected void layoutButtons() { 857 Rect irc = _scrollArea; 858 if (_orientation == Orientation.Vertical) { 859 // vertical 860 int spaceBackSize, spaceForwardSize, indicatorSize; 861 bool indicatorVisible = calcButtonSizes(_scrollArea.height, spaceBackSize, spaceForwardSize, indicatorSize); 862 irc.top += spaceBackSize; 863 irc.bottom -= spaceForwardSize; 864 layoutButtons(irc); 865 } else { 866 // horizontal 867 int spaceBackSize, spaceForwardSize, indicatorSize; 868 bool indicatorVisible = calcButtonSizes(_scrollArea.width, spaceBackSize, spaceForwardSize, indicatorSize); 869 irc.left += spaceBackSize; 870 irc.right -= spaceForwardSize; 871 layoutButtons(irc); 872 } 873 updateState(); 874 cancelLayout(); 875 } 876 877 protected void layoutButtons(Rect irc) { 878 Rect r; 879 _indicator.visibility = Visibility.Visible; 880 if (_orientation == Orientation.Vertical) { 881 _indicator.layout(irc); 882 if (_scrollArea.top < irc.top) { 883 r = _scrollArea; 884 r.bottom = irc.top; 885 _pageUp.layout(r); 886 _pageUp.visibility = Visibility.Visible; 887 } else { 888 _pageUp.visibility = Visibility.Invisible; 889 } 890 if (_scrollArea.bottom > irc.bottom) { 891 r = _scrollArea; 892 r.top = irc.bottom; 893 _pageDown.layout(r); 894 _pageDown.visibility = Visibility.Visible; 895 } else { 896 _pageDown.visibility = Visibility.Invisible; 897 } 898 } else { 899 _indicator.layout(irc); 900 if (_scrollArea.left < irc.left) { 901 r = _scrollArea; 902 r.right = irc.left; 903 _pageUp.layout(r); 904 _pageUp.visibility = Visibility.Visible; 905 } else { 906 _pageUp.visibility = Visibility.Invisible; 907 } 908 if (_scrollArea.right > irc.right) { 909 r = _scrollArea; 910 r.left = irc.right; 911 _pageDown.layout(r); 912 _pageDown.visibility = Visibility.Visible; 913 } else { 914 _pageDown.visibility = Visibility.Invisible; 915 } 916 } 917 } 918 919 override void layout(Rect rc) { 920 _needLayout = false; 921 applyMargins(rc); 922 applyPadding(rc); 923 Rect r; 924 if (_orientation == Orientation.Vertical) { 925 // vertical 926 // buttons 927 // indicator 928 r = rc; 929 _scrollArea = r; 930 } else { 931 // horizontal 932 // indicator 933 r = rc; 934 _scrollArea = r; 935 } 936 layoutButtons(); 937 _pos = rc; 938 } 939 940 override bool onClick(Widget source) { 941 Log.d("Scrollbar.onClick ", source.id); 942 if (source.compareId("PAGE_UP")) 943 return sendScrollEvent(ScrollAction.PageUp, position); 944 if (source.compareId("PAGE_DOWN")) 945 return sendScrollEvent(ScrollAction.PageDown, position); 946 return true; 947 } 948 949 /// handle mouse wheel events 950 override bool onMouseEvent(MouseEvent event) { 951 if (visibility != Visibility.Visible) 952 return false; 953 if (event.action == MouseAction.Wheel) { 954 int delta = event.wheelDelta; 955 if (delta > 0) 956 sendScrollEvent(ScrollAction.LineUp, position); 957 else if (delta < 0) 958 sendScrollEvent(ScrollAction.LineDown, position); 959 return true; 960 } 961 return super.onMouseEvent(event); 962 } 963 964 /// Draw widget at its position to buffer 965 override void onDraw(DrawBuf buf) { 966 if (visibility != Visibility.Visible && !buf.isClippedOut(_pos)) 967 return; 968 Rect rc = _pos; 969 applyMargins(rc); 970 auto saver = ClipRectSaver(buf, rc, alpha); 971 DrawableRef bg = backgroundDrawable; 972 if (!bg.isNull) { 973 Rect r = rc; 974 if (_orientation == Orientation.Vertical) { 975 int dw = bg.width; 976 r.left += (rc.width - dw)/2; 977 r.right = r.left + dw; 978 } else { 979 int dw = bg.height; 980 r.top += (rc.height - dw)/2; 981 r.bottom = r.top + dw; 982 } 983 bg.drawTo(buf, r, state); 984 } 985 applyPadding(rc); 986 if (state & State.Focused) { 987 rc.expand(FOCUS_RECT_PADDING, FOCUS_RECT_PADDING); 988 drawFocusRect(buf, rc); 989 } 990 _needDraw = false; 991 _pageUp.onDraw(buf); 992 _pageDown.onDraw(buf); 993 _indicator.onDraw(buf); 994 } 995 } 996