1 /* 2 * Copyright 2001-2014 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Stephan Aßmus, superstippi@gmx.de 7 * Stefano Ceccherini, burton666@libero.it 8 * DarkWyrm, bpmagic@columbus.rr.com 9 * Marc Flerackers, mflerackers@androme.be 10 * John Scipione, jscipione@gmail.com 11 */ 12 13 14 #include <ScrollBar.h> 15 16 #include <math.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 21 #include <ControlLook.h> 22 #include <LayoutUtils.h> 23 #include <Message.h> 24 #include <OS.h> 25 #include <Shape.h> 26 #include <Window.h> 27 28 #include <binary_compatibility/Interface.h> 29 30 31 //#define TRACE_SCROLLBAR 32 #ifdef TRACE_SCROLLBAR 33 # define TRACE(x...) printf(x) 34 #else 35 # define TRACE(x...) 36 #endif 37 38 39 typedef enum { 40 ARROW_LEFT = 0, 41 ARROW_RIGHT, 42 ARROW_UP, 43 ARROW_DOWN, 44 ARROW_NONE 45 } arrow_direction; 46 47 48 #define SBC_SCROLLBYVALUE 0 49 #define SBC_SETDOUBLE 1 50 #define SBC_SETPROPORTIONAL 2 51 #define SBC_SETSTYLE 3 52 53 // Quick constants for determining which arrow is down and are defined with 54 // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of 55 // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up 56 // and ARROW4 points right/down. 57 #define ARROW1 0 58 #define ARROW2 1 59 #define ARROW3 2 60 #define ARROW4 3 61 #define THUMB 4 62 #define NOARROW -1 63 64 65 static const bigtime_t kRepeatDelay = 300000; 66 67 68 // Because the R5 version kept a lot of data on server-side, we need to kludge 69 // our way into binary compatibility 70 class BScrollBar::Private { 71 public: 72 Private(BScrollBar* scrollBar) 73 : 74 fScrollBar(scrollBar), 75 fEnabled(true), 76 fRepeaterThread(-1), 77 fExitRepeater(false), 78 fRepeaterDelay(0), 79 fThumbFrame(0.0, 0.0, -1.0, -1.0), 80 fDoRepeat(false), 81 fClickOffset(0.0, 0.0), 82 fThumbInc(0.0), 83 fStopValue(0.0), 84 fUpArrowsEnabled(true), 85 fDownArrowsEnabled(true), 86 fBorderHighlighted(false), 87 fButtonDown(NOARROW) 88 { 89 #ifdef TEST_MODE 90 fScrollBarInfo.proportional = true; 91 fScrollBarInfo.double_arrows = true; 92 fScrollBarInfo.knob = 0; 93 fScrollBarInfo.min_knob_size = 15; 94 #else 95 get_scroll_bar_info(&fScrollBarInfo); 96 #endif 97 } 98 99 ~Private() 100 { 101 if (fRepeaterThread >= 0) { 102 status_t dummy; 103 fExitRepeater = true; 104 wait_for_thread(fRepeaterThread, &dummy); 105 } 106 } 107 108 void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction, 109 BRect frame, bool down = false); 110 111 static int32 button_repeater_thread(void* data); 112 113 int32 ButtonRepeaterThread(); 114 115 BScrollBar* fScrollBar; 116 bool fEnabled; 117 118 // TODO: This should be a static, initialized by 119 // _init_interface_kit() at application startup-time, 120 // like BMenu::sMenuInfo 121 scroll_bar_info fScrollBarInfo; 122 123 thread_id fRepeaterThread; 124 volatile bool fExitRepeater; 125 bigtime_t fRepeaterDelay; 126 127 BRect fThumbFrame; 128 volatile bool fDoRepeat; 129 BPoint fClickOffset; 130 131 float fThumbInc; 132 float fStopValue; 133 134 bool fUpArrowsEnabled; 135 bool fDownArrowsEnabled; 136 137 bool fBorderHighlighted; 138 139 int8 fButtonDown; 140 }; 141 142 143 // This thread is spawned when a button is initially pushed and repeatedly scrolls 144 // the scrollbar by a little bit after a short delay 145 int32 146 BScrollBar::Private::button_repeater_thread(void* data) 147 { 148 BScrollBar::Private* privateData = (BScrollBar::Private*)data; 149 return privateData->ButtonRepeaterThread(); 150 } 151 152 153 int32 154 BScrollBar::Private::ButtonRepeaterThread() 155 { 156 // Wait a bit before auto scrolling starts. As long as the user releases 157 // and presses the button again while the repeat delay has not yet 158 // triggered, the value is pushed into the future, so we need to loop such 159 // that repeating starts at exactly the correct delay after the last 160 // button press. 161 while (fRepeaterDelay > system_time() && !fExitRepeater) 162 snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE); 163 164 // repeat loop 165 while (!fExitRepeater) { 166 if (fScrollBar->LockLooper()) { 167 if (fDoRepeat) { 168 float value = fScrollBar->Value() + fThumbInc; 169 if (fButtonDown == NOARROW) { 170 // in this case we want to stop when we're under the mouse 171 if (fThumbInc > 0.0 && value <= fStopValue) 172 fScrollBar->SetValue(value); 173 if (fThumbInc < 0.0 && value >= fStopValue) 174 fScrollBar->SetValue(value); 175 } else 176 fScrollBar->SetValue(value); 177 } 178 179 fScrollBar->UnlockLooper(); 180 } 181 182 snooze(25000); 183 } 184 185 // tell scrollbar we're gone 186 if (fScrollBar->LockLooper()) { 187 fRepeaterThread = -1; 188 fScrollBar->UnlockLooper(); 189 } 190 191 return 0; 192 } 193 194 195 // #pragma mark - BScrollBar 196 197 198 BScrollBar::BScrollBar(BRect frame, const char* name, BView* target, 199 float min, float max, orientation direction) 200 : 201 BView(frame, name, B_FOLLOW_NONE, 202 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 203 fMin(min), 204 fMax(max), 205 fSmallStep(1.0f), 206 fLargeStep(10.0f), 207 fValue(0), 208 fProportion(0.0f), 209 fTarget(NULL), 210 fOrientation(direction) 211 { 212 SetViewColor(B_TRANSPARENT_COLOR); 213 214 fPrivateData = new BScrollBar::Private(this); 215 216 SetTarget(target); 217 SetEventMask(B_NO_POINTER_HISTORY); 218 219 _UpdateThumbFrame(); 220 _UpdateArrowButtons(); 221 222 SetResizingMode(direction == B_VERTICAL 223 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT 224 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 225 } 226 227 228 BScrollBar::BScrollBar(const char* name, BView* target, 229 float min, float max, orientation direction) 230 : 231 BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), 232 fMin(min), 233 fMax(max), 234 fSmallStep(1.0f), 235 fLargeStep(10.0f), 236 fValue(0), 237 fProportion(0.0f), 238 fTarget(NULL), 239 fOrientation(direction) 240 { 241 SetViewColor(B_TRANSPARENT_COLOR); 242 243 fPrivateData = new BScrollBar::Private(this); 244 245 SetTarget(target); 246 SetEventMask(B_NO_POINTER_HISTORY); 247 248 _UpdateThumbFrame(); 249 _UpdateArrowButtons(); 250 } 251 252 253 BScrollBar::BScrollBar(BMessage* data) 254 : 255 BView(data), 256 fTarget(NULL) 257 { 258 fPrivateData = new BScrollBar::Private(this); 259 260 // TODO: Does the BeOS implementation try to find the target 261 // by name again? Does it archive the name at all? 262 if (data->FindFloat("_range", 0, &fMin) < B_OK) 263 fMin = 0.0f; 264 265 if (data->FindFloat("_range", 1, &fMax) < B_OK) 266 fMax = 0.0f; 267 268 if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK) 269 fSmallStep = 1.0f; 270 271 if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK) 272 fLargeStep = 10.0f; 273 274 if (data->FindFloat("_val", &fValue) < B_OK) 275 fValue = 0.0; 276 277 int32 orientation; 278 if (data->FindInt32("_orient", &orientation) < B_OK) { 279 fOrientation = B_VERTICAL; 280 if ((Flags() & B_SUPPORTS_LAYOUT) == 0) { 281 // just to make sure 282 SetResizingMode(fOrientation == B_VERTICAL 283 ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT 284 : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); 285 } 286 } else 287 fOrientation = (enum orientation)orientation; 288 289 if (data->FindFloat("_prop", &fProportion) < B_OK) 290 fProportion = 0.0; 291 292 _UpdateThumbFrame(); 293 _UpdateArrowButtons(); 294 } 295 296 297 BScrollBar::~BScrollBar() 298 { 299 SetTarget((BView*)NULL); 300 delete fPrivateData; 301 } 302 303 304 BArchivable* 305 BScrollBar::Instantiate(BMessage* data) 306 { 307 if (validate_instantiation(data, "BScrollBar")) 308 return new BScrollBar(data); 309 return NULL; 310 } 311 312 313 status_t 314 BScrollBar::Archive(BMessage* data, bool deep) const 315 { 316 status_t err = BView::Archive(data, deep); 317 if (err != B_OK) 318 return err; 319 err = data->AddFloat("_range", fMin); 320 if (err != B_OK) 321 return err; 322 err = data->AddFloat("_range", fMax); 323 if (err != B_OK) 324 return err; 325 err = data->AddFloat("_steps", fSmallStep); 326 if (err != B_OK) 327 return err; 328 err = data->AddFloat("_steps", fLargeStep); 329 if (err != B_OK) 330 return err; 331 err = data->AddFloat("_val", fValue); 332 if (err != B_OK) 333 return err; 334 err = data->AddInt32("_orient", (int32)fOrientation); 335 if (err != B_OK) 336 return err; 337 err = data->AddFloat("_prop", fProportion); 338 339 return err; 340 } 341 342 343 // #pragma mark - 344 345 346 void 347 BScrollBar::AttachedToWindow() 348 { 349 } 350 351 /* 352 From the BeBook (on ValueChanged()): 353 354 Responds to a notification that the value of the scroll bar has changed to 355 newValue. For a horizontal scroll bar, this function interprets newValue 356 as the coordinate value that should be at the left side of the target 357 view's bounds rectangle. For a vertical scroll bar, it interprets 358 newValue as the coordinate value that should be at the top of the rectangle. 359 It calls ScrollTo() to scroll the target's contents into position, unless 360 they have already been scrolled. 361 362 ValueChanged() is called as the result both of user actions 363 (B_VALUE_CHANGED messages received from the Application Server) and of 364 programmatic ones. Programmatically, scrolling can be initiated by the 365 target view (calling ScrollTo()) or by the BScrollBar 366 (calling SetValue() or SetRange()). 367 368 In all these cases, the target view and the scroll bars need to be kept 369 in synch. This is done by a chain of function calls: ValueChanged() calls 370 ScrollTo(), which in turn calls SetValue(), which then calls 371 ValueChanged() again. It's up to ValueChanged() to get off this 372 merry-go-round, which it does by checking the target view's bounds 373 rectangle. If newValue already matches the left or top side of the 374 bounds rectangle, if forgoes calling ScrollTo(). 375 376 ValueChanged() does nothing if a target BView hasn't been set—or 377 if the target has been set by name, but the name doesn't correspond to 378 an actual BView within the scroll bar's window. 379 380 */ 381 382 383 void 384 BScrollBar::SetValue(float value) 385 { 386 if (value > fMax) 387 value = fMax; 388 else if (value < fMin) 389 value = fMin; 390 else if (isnan(value)) 391 return; 392 393 value = roundf(value); 394 if (value == fValue) 395 return; 396 397 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value); 398 399 fValue = value; 400 401 _UpdateThumbFrame(); 402 _UpdateArrowButtons(); 403 404 ValueChanged(fValue); 405 } 406 407 408 float 409 BScrollBar::Value() const 410 { 411 return fValue; 412 } 413 414 415 void 416 BScrollBar::ValueChanged(float newValue) 417 { 418 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue); 419 420 if (fTarget != NULL) { 421 // cache target bounds 422 BRect targetBounds = fTarget->Bounds(); 423 // if vertical, check bounds top and scroll if different from newValue 424 if (fOrientation == B_VERTICAL && targetBounds.top != newValue) 425 fTarget->ScrollBy(0.0, newValue - targetBounds.top); 426 427 // if horizontal, check bounds left and scroll if different from newValue 428 if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) 429 fTarget->ScrollBy(newValue - targetBounds.left, 0.0); 430 } 431 432 TRACE(" -> %.1f\n", newValue); 433 434 SetValue(newValue); 435 } 436 437 438 void 439 BScrollBar::SetProportion(float value) 440 { 441 if (value < 0.0f) 442 value = 0.0f; 443 else if (value > 1.0f) 444 value = 1.0f; 445 446 if (value == fProportion) 447 return; 448 449 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value); 450 451 bool oldEnabled = fPrivateData->fEnabled && fMin < fMax 452 && fProportion < 1.0f && fProportion >= 0.0f; 453 454 fProportion = value; 455 456 bool newEnabled = fPrivateData->fEnabled && fMin < fMax 457 && fProportion < 1.0f && fProportion >= 0.0f; 458 459 _UpdateThumbFrame(); 460 461 if (oldEnabled != newEnabled) 462 Invalidate(); 463 } 464 465 466 float 467 BScrollBar::Proportion() const 468 { 469 return fProportion; 470 } 471 472 473 void 474 BScrollBar::SetRange(float min, float max) 475 { 476 if (min > max || isnanf(min) || isnanf(max) 477 || isinff(min) || isinff(max)) { 478 min = 0.0f; 479 max = 0.0f; 480 } 481 482 min = roundf(min); 483 max = roundf(max); 484 485 if (fMin == min && fMax == max) 486 return; 487 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max); 488 489 fMin = min; 490 fMax = max; 491 492 if (fValue < fMin || fValue > fMax) 493 SetValue(fValue); 494 else { 495 _UpdateThumbFrame(); 496 Invalidate(); 497 } 498 } 499 500 501 void 502 BScrollBar::GetRange(float* min, float* max) const 503 { 504 if (min != NULL) 505 *min = fMin; 506 if (max != NULL) 507 *max = fMax; 508 } 509 510 511 void 512 BScrollBar::SetSteps(float smallStep, float largeStep) 513 { 514 // Under R5, steps can be set only after being attached to a window, 515 // probably because the data is kept server-side. We'll just remove 516 // that limitation... :P 517 518 // The BeBook also says that we need to specify an integer value even 519 // though the step values are floats. For the moment, we'll just make 520 // sure that they are integers 521 smallStep = roundf(smallStep); 522 largeStep = roundf(largeStep); 523 if (fSmallStep == smallStep && fLargeStep == largeStep) 524 return; 525 526 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(), 527 smallStep, largeStep); 528 529 fSmallStep = smallStep; 530 fLargeStep = largeStep; 531 532 if (fProportion == 0.0) { 533 // special case, proportion is based on fLargeStep if it was never 534 // set, so it means we need to invalidate here 535 _UpdateThumbFrame(); 536 Invalidate(); 537 } 538 539 // TODO: test use of fractional values and make them work properly if 540 // they don't 541 } 542 543 544 void 545 BScrollBar::GetSteps(float* smallStep, float* largeStep) const 546 { 547 if (smallStep != NULL) 548 *smallStep = fSmallStep; 549 550 if (largeStep != NULL) 551 *largeStep = fLargeStep; 552 } 553 554 555 void 556 BScrollBar::SetTarget(BView* target) 557 { 558 if (fTarget) { 559 // unset the previous target's scrollbar pointer 560 if (fOrientation == B_VERTICAL) 561 fTarget->fVerScroller = NULL; 562 else 563 fTarget->fHorScroller = NULL; 564 } 565 566 fTarget = target; 567 if (fTarget) { 568 if (fOrientation == B_VERTICAL) 569 fTarget->fVerScroller = this; 570 else 571 fTarget->fHorScroller = this; 572 } 573 } 574 575 576 void 577 BScrollBar::SetTarget(const char* targetName) 578 { 579 // NOTE 1: BeOS implementation crashes for targetName == NULL 580 // NOTE 2: BeOS implementation also does not modify the target 581 // if it can't be found 582 if (!targetName) 583 return; 584 585 if (!Window()) 586 debugger("Method requires window and doesn't have one"); 587 588 BView* target = Window()->FindView(targetName); 589 if (target) 590 SetTarget(target); 591 } 592 593 594 BView* 595 BScrollBar::Target() const 596 { 597 return fTarget; 598 } 599 600 601 void 602 BScrollBar::SetOrientation(orientation orientation) 603 { 604 if (fOrientation == orientation) 605 return; 606 607 fOrientation = orientation; 608 InvalidateLayout(); 609 Invalidate(); 610 } 611 612 613 orientation 614 BScrollBar::Orientation() const 615 { 616 return fOrientation; 617 } 618 619 620 status_t 621 BScrollBar::SetBorderHighlighted(bool state) 622 { 623 if (fPrivateData->fBorderHighlighted == state) 624 return B_OK; 625 626 fPrivateData->fBorderHighlighted = state; 627 628 BRect dirty(Bounds()); 629 if (fOrientation == B_HORIZONTAL) 630 dirty.bottom = dirty.top; 631 else 632 dirty.right = dirty.left; 633 634 Invalidate(dirty); 635 636 return B_OK; 637 } 638 639 640 void 641 BScrollBar::MessageReceived(BMessage* message) 642 { 643 switch(message->what) { 644 case B_VALUE_CHANGED: 645 { 646 int32 value; 647 if (message->FindInt32("value", &value) == B_OK) 648 ValueChanged(value); 649 650 break; 651 } 652 653 case B_MOUSE_WHEEL_CHANGED: 654 { 655 // Must handle this here since BView checks for the existence of 656 // scrollbars, which a scrollbar itself does not have 657 float deltaX = 0.0f; 658 float deltaY = 0.0f; 659 message->FindFloat("be:wheel_delta_x", &deltaX); 660 message->FindFloat("be:wheel_delta_y", &deltaY); 661 662 if (deltaX == 0.0f && deltaY == 0.0f) 663 break; 664 665 if (deltaX != 0.0f && deltaY == 0.0f) 666 deltaY = deltaX; 667 668 ScrollWithMouseWheelDelta(this, deltaY); 669 } 670 671 default: 672 BView::MessageReceived(message); 673 } 674 } 675 676 677 void 678 BScrollBar::MouseDown(BPoint where) 679 { 680 if (!fPrivateData->fEnabled || fMin == fMax) 681 return; 682 683 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 684 685 int32 buttons; 686 if (Looper() == NULL || Looper()->CurrentMessage() == NULL 687 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) { 688 buttons = B_PRIMARY_MOUSE_BUTTON; 689 } 690 691 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 692 // special absolute scrolling: move thumb to where we clicked 693 fPrivateData->fButtonDown = THUMB; 694 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 695 if (Orientation() == B_HORIZONTAL) 696 fPrivateData->fClickOffset.x = -fPrivateData->fThumbFrame.Width() / 2; 697 else 698 fPrivateData->fClickOffset.y = -fPrivateData->fThumbFrame.Height() / 2; 699 700 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 701 return; 702 } 703 704 // hit test for the thumb 705 if (fPrivateData->fThumbFrame.Contains(where)) { 706 fPrivateData->fButtonDown = THUMB; 707 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 708 Invalidate(fPrivateData->fThumbFrame); 709 return; 710 } 711 712 // hit test for arrows or empty area 713 float scrollValue = 0.0; 714 715 // pressing the shift key scrolls faster 716 float buttonStepSize 717 = (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep; 718 719 fPrivateData->fButtonDown = _ButtonFor(where); 720 switch (fPrivateData->fButtonDown) { 721 case ARROW1: 722 scrollValue = -buttonStepSize; 723 break; 724 725 case ARROW2: 726 scrollValue = buttonStepSize; 727 break; 728 729 case ARROW3: 730 scrollValue = -buttonStepSize; 731 break; 732 733 case ARROW4: 734 scrollValue = buttonStepSize; 735 break; 736 737 case NOARROW: 738 // we hit the empty area, figure out which side of the thumb 739 if (fOrientation == B_VERTICAL) { 740 if (where.y < fPrivateData->fThumbFrame.top) 741 scrollValue = -fLargeStep; 742 else 743 scrollValue = fLargeStep; 744 } else { 745 if (where.x < fPrivateData->fThumbFrame.left) 746 scrollValue = -fLargeStep; 747 else 748 scrollValue = fLargeStep; 749 } 750 _UpdateTargetValue(where); 751 break; 752 } 753 if (scrollValue != 0.0) { 754 SetValue(fValue + scrollValue); 755 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 756 757 // launch the repeat thread 758 if (fPrivateData->fRepeaterThread == -1) { 759 fPrivateData->fExitRepeater = false; 760 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 761 fPrivateData->fThumbInc = scrollValue; 762 fPrivateData->fDoRepeat = true; 763 fPrivateData->fRepeaterThread 764 = spawn_thread(fPrivateData->button_repeater_thread, 765 "scroll repeater", B_NORMAL_PRIORITY, fPrivateData); 766 resume_thread(fPrivateData->fRepeaterThread); 767 } else { 768 fPrivateData->fExitRepeater = false; 769 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 770 fPrivateData->fDoRepeat = true; 771 } 772 } 773 } 774 775 776 void 777 BScrollBar::MouseUp(BPoint pt) 778 { 779 if (fPrivateData->fButtonDown == THUMB) 780 Invalidate(fPrivateData->fThumbFrame); 781 else 782 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 783 784 fPrivateData->fButtonDown = NOARROW; 785 fPrivateData->fExitRepeater = true; 786 fPrivateData->fDoRepeat = false; 787 } 788 789 790 void 791 BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message) 792 { 793 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f 794 || fProportion < 0.0f) { 795 return; 796 } 797 798 if (fPrivateData->fButtonDown != NOARROW) { 799 if (fPrivateData->fButtonDown == THUMB) { 800 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 801 } else { 802 // suspend the repeating if the mouse is not over the button 803 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( 804 where); 805 if (fPrivateData->fDoRepeat != repeat) { 806 fPrivateData->fDoRepeat = repeat; 807 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 808 } 809 } 810 } else { 811 // update the value at which we want to stop repeating 812 if (fPrivateData->fDoRepeat) { 813 _UpdateTargetValue(where); 814 // we might have to turn arround 815 if ((fValue < fPrivateData->fStopValue 816 && fPrivateData->fThumbInc < 0) 817 || (fValue > fPrivateData->fStopValue 818 && fPrivateData->fThumbInc > 0)) { 819 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 820 } 821 } 822 } 823 } 824 825 826 void 827 BScrollBar::DetachedFromWindow() 828 { 829 BView::DetachedFromWindow(); 830 } 831 832 833 void 834 BScrollBar::Draw(BRect updateRect) 835 { 836 BRect bounds = Bounds(); 837 838 rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR); 839 840 // stroke a dark frame arround the entire scrollbar 841 // (independent of enabled state) 842 // take care of border highlighting (scroll target is focus view) 843 SetHighColor(tint_color(normal, B_DARKEN_2_TINT)); 844 if (fPrivateData->fBorderHighlighted && fPrivateData->fEnabled) { 845 rgb_color borderColor = HighColor(); 846 rgb_color highlightColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 847 BeginLineArray(4); 848 AddLine(BPoint(bounds.left + 1, bounds.bottom), 849 BPoint(bounds.right, bounds.bottom), borderColor); 850 AddLine(BPoint(bounds.right, bounds.top + 1), 851 BPoint(bounds.right, bounds.bottom - 1), borderColor); 852 if (fOrientation == B_HORIZONTAL) { 853 AddLine(BPoint(bounds.left, bounds.top + 1), 854 BPoint(bounds.left, bounds.bottom), borderColor); 855 } else { 856 AddLine(BPoint(bounds.left, bounds.top), 857 BPoint(bounds.left, bounds.bottom), highlightColor); 858 } 859 if (fOrientation == B_HORIZONTAL) { 860 AddLine(BPoint(bounds.left, bounds.top), 861 BPoint(bounds.right, bounds.top), highlightColor); 862 } else { 863 AddLine(BPoint(bounds.left + 1, bounds.top), 864 BPoint(bounds.right, bounds.top), borderColor); 865 } 866 EndLineArray(); 867 } else 868 StrokeRect(bounds); 869 870 bounds.InsetBy(1.0f, 1.0f); 871 872 bool enabled = fPrivateData->fEnabled && fMin < fMax 873 && fProportion < 1.0f && fProportion >= 0.0f; 874 875 rgb_color light, dark, dark1, dark2; 876 if (enabled) { 877 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 878 dark = tint_color(normal, B_DARKEN_3_TINT); 879 dark1 = tint_color(normal, B_DARKEN_1_TINT); 880 dark2 = tint_color(normal, B_DARKEN_2_TINT); 881 } else { 882 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 883 dark = tint_color(normal, B_DARKEN_2_TINT); 884 dark1 = tint_color(normal, B_LIGHTEN_2_TINT); 885 dark2 = tint_color(normal, B_LIGHTEN_1_TINT); 886 } 887 888 SetDrawingMode(B_OP_OVER); 889 890 BRect thumbBG = bounds; 891 bool doubleArrows = _DoubleArrows(); 892 893 // Draw arrows 894 if (fOrientation == B_HORIZONTAL) { 895 BRect buttonFrame(bounds.left, bounds.top, 896 bounds.left + bounds.Height(), bounds.bottom); 897 898 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 899 enabled, fPrivateData->fButtonDown == ARROW1); 900 901 if (doubleArrows) { 902 buttonFrame.OffsetBy(bounds.Height() + 1, 0.0f); 903 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 904 enabled, fPrivateData->fButtonDown == ARROW2); 905 906 buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), 907 bounds.top); 908 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 909 enabled, fPrivateData->fButtonDown == ARROW3); 910 911 thumbBG.left += bounds.Height() * 2 + 2; 912 thumbBG.right -= bounds.Height() * 2 + 2; 913 } else { 914 thumbBG.left += bounds.Height() + 1; 915 thumbBG.right -= bounds.Height() + 1; 916 } 917 918 buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top); 919 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 920 enabled, fPrivateData->fButtonDown == ARROW4); 921 } else { 922 BRect buttonFrame(bounds.left, bounds.top, bounds.right, 923 bounds.top + bounds.Width()); 924 925 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 926 enabled, fPrivateData->fButtonDown == ARROW1); 927 928 if (doubleArrows) { 929 buttonFrame.OffsetBy(0.0f, bounds.Width() + 1); 930 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 931 enabled, fPrivateData->fButtonDown == ARROW2); 932 933 buttonFrame.OffsetTo(bounds.left, bounds.bottom 934 - ((bounds.Width() * 2) + 1)); 935 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 936 enabled, fPrivateData->fButtonDown == ARROW3); 937 938 thumbBG.top += bounds.Width() * 2 + 2; 939 thumbBG.bottom -= bounds.Width() * 2 + 2; 940 } else { 941 thumbBG.top += bounds.Width() + 1; 942 thumbBG.bottom -= bounds.Width() + 1; 943 } 944 945 buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width()); 946 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 947 enabled, fPrivateData->fButtonDown == ARROW4); 948 } 949 950 SetDrawingMode(B_OP_COPY); 951 952 // background for thumb area 953 BRect rect(fPrivateData->fThumbFrame); 954 955 SetHighColor(dark1); 956 957 uint32 flags = 0; 958 if (!enabled) 959 flags |= BControlLook::B_DISABLED; 960 961 // fill background besides the thumb 962 if (fOrientation == B_HORIZONTAL) { 963 BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, 964 thumbBG.bottom); 965 BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, 966 thumbBG.bottom); 967 968 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 969 rightOfThumb, updateRect, normal, flags, fOrientation); 970 } else { 971 BRect topOfThumb(thumbBG.left, thumbBG.top, 972 thumbBG.right, rect.top - 1); 973 974 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 975 thumbBG.right, thumbBG.bottom); 976 977 be_control_look->DrawScrollBarBackground(this, topOfThumb, 978 bottomOfThumb, updateRect, normal, flags, fOrientation); 979 } 980 981 rgb_color thumbColor = ui_color(B_SCROLL_BAR_THUMB_COLOR); 982 983 // Draw scroll thumb 984 if (enabled) { 985 // fill the clickable surface of the thumb 986 be_control_look->DrawButtonBackground(this, rect, updateRect, 987 thumbColor, 0, BControlLook::B_ALL_BORDERS, fOrientation); 988 // TODO: Add the other thumb styles - dots and lines 989 } else { 990 if (fMin >= fMax || fProportion >= 1.0f || fProportion < 0.0f) { 991 // we cannot scroll at all 992 _DrawDisabledBackground(thumbBG, light, dark, dark1); 993 } else { 994 // we could scroll, but we're simply disabled 995 float bgTint = 1.06; 996 rgb_color bgLight = tint_color(light, bgTint * 3); 997 rgb_color bgShadow = tint_color(dark, bgTint); 998 rgb_color bgFill = tint_color(dark1, bgTint); 999 if (fOrientation == B_HORIZONTAL) { 1000 // left of thumb 1001 BRect besidesThumb(thumbBG); 1002 besidesThumb.right = rect.left - 1; 1003 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1004 // right of thumb 1005 besidesThumb.left = rect.right + 1; 1006 besidesThumb.right = thumbBG.right; 1007 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1008 } else { 1009 // above thumb 1010 BRect besidesThumb(thumbBG); 1011 besidesThumb.bottom = rect.top - 1; 1012 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1013 // below thumb 1014 besidesThumb.top = rect.bottom + 1; 1015 besidesThumb.bottom = thumbBG.bottom; 1016 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1017 } 1018 // thumb bevel 1019 BeginLineArray(4); 1020 AddLine(BPoint(rect.left, rect.bottom), 1021 BPoint(rect.left, rect.top), light); 1022 AddLine(BPoint(rect.left + 1, rect.top), 1023 BPoint(rect.right, rect.top), light); 1024 AddLine(BPoint(rect.right, rect.top + 1), 1025 BPoint(rect.right, rect.bottom), dark2); 1026 AddLine(BPoint(rect.right - 1, rect.bottom), 1027 BPoint(rect.left + 1, rect.bottom), dark2); 1028 EndLineArray(); 1029 // thumb fill 1030 rect.InsetBy(1.0, 1.0); 1031 SetHighColor(dark1); 1032 FillRect(rect); 1033 } 1034 } 1035 } 1036 1037 1038 void 1039 BScrollBar::FrameMoved(BPoint newPosition) 1040 { 1041 BView::FrameMoved(newPosition); 1042 } 1043 1044 1045 void 1046 BScrollBar::FrameResized(float newWidth, float newHeight) 1047 { 1048 _UpdateThumbFrame(); 1049 } 1050 1051 1052 BHandler* 1053 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1054 BMessage* specifier, int32 form, const char* property) 1055 { 1056 return BView::ResolveSpecifier(message, index, specifier, form, property); 1057 } 1058 1059 1060 void 1061 BScrollBar::ResizeToPreferred() 1062 { 1063 BView::ResizeToPreferred(); 1064 } 1065 1066 1067 void 1068 BScrollBar::GetPreferredSize(float* _width, float* _height) 1069 { 1070 if (fOrientation == B_VERTICAL) { 1071 if (_width) 1072 *_width = B_V_SCROLL_BAR_WIDTH; 1073 1074 if (_height) 1075 *_height = Bounds().Height(); 1076 } else if (fOrientation == B_HORIZONTAL) { 1077 if (_width) 1078 *_width = Bounds().Width(); 1079 1080 if (_height) 1081 *_height = B_H_SCROLL_BAR_HEIGHT; 1082 } 1083 } 1084 1085 1086 void 1087 BScrollBar::MakeFocus(bool state) 1088 { 1089 BView::MakeFocus(state); 1090 } 1091 1092 1093 void 1094 BScrollBar::AllAttached() 1095 { 1096 BView::AllAttached(); 1097 } 1098 1099 1100 void 1101 BScrollBar::AllDetached() 1102 { 1103 BView::AllDetached(); 1104 } 1105 1106 1107 status_t 1108 BScrollBar::GetSupportedSuites(BMessage* message) 1109 { 1110 return BView::GetSupportedSuites(message); 1111 } 1112 1113 1114 BSize 1115 BScrollBar::MinSize() 1116 { 1117 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1118 } 1119 1120 1121 BSize 1122 BScrollBar::MaxSize() 1123 { 1124 BSize maxSize = _MinSize(); 1125 if (fOrientation == B_HORIZONTAL) 1126 maxSize.width = B_SIZE_UNLIMITED; 1127 else 1128 maxSize.height = B_SIZE_UNLIMITED; 1129 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1130 } 1131 1132 1133 BSize 1134 BScrollBar::PreferredSize() 1135 { 1136 BSize preferredSize = _MinSize(); 1137 if (fOrientation == B_HORIZONTAL) 1138 preferredSize.width *= 2; 1139 else 1140 preferredSize.height *= 2; 1141 1142 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1143 } 1144 1145 1146 status_t 1147 BScrollBar::Perform(perform_code code, void* _data) 1148 { 1149 switch (code) { 1150 case PERFORM_CODE_MIN_SIZE: 1151 ((perform_data_min_size*)_data)->return_value 1152 = BScrollBar::MinSize(); 1153 1154 return B_OK; 1155 1156 case PERFORM_CODE_MAX_SIZE: 1157 ((perform_data_max_size*)_data)->return_value 1158 = BScrollBar::MaxSize(); 1159 1160 return B_OK; 1161 1162 case PERFORM_CODE_PREFERRED_SIZE: 1163 ((perform_data_preferred_size*)_data)->return_value 1164 = BScrollBar::PreferredSize(); 1165 1166 return B_OK; 1167 1168 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1169 ((perform_data_layout_alignment*)_data)->return_value 1170 = BScrollBar::LayoutAlignment(); 1171 1172 return B_OK; 1173 1174 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1175 ((perform_data_has_height_for_width*)_data)->return_value 1176 = BScrollBar::HasHeightForWidth(); 1177 1178 return B_OK; 1179 1180 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1181 { 1182 perform_data_get_height_for_width* data 1183 = (perform_data_get_height_for_width*)_data; 1184 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1185 &data->preferred); 1186 1187 return B_OK; 1188 } 1189 1190 case PERFORM_CODE_SET_LAYOUT: 1191 { 1192 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1193 BScrollBar::SetLayout(data->layout); 1194 1195 return B_OK; 1196 } 1197 1198 case PERFORM_CODE_LAYOUT_INVALIDATED: 1199 { 1200 perform_data_layout_invalidated* data 1201 = (perform_data_layout_invalidated*)_data; 1202 BScrollBar::LayoutInvalidated(data->descendants); 1203 1204 return B_OK; 1205 } 1206 1207 case PERFORM_CODE_DO_LAYOUT: 1208 { 1209 BScrollBar::DoLayout(); 1210 1211 return B_OK; 1212 } 1213 } 1214 1215 return BView::Perform(code, _data); 1216 } 1217 1218 1219 #if DISABLES_ON_WINDOW_DEACTIVATION 1220 void 1221 BScrollBar::WindowActivated(bool active) 1222 { 1223 fPrivateData->fEnabled = active; 1224 Invalidate(); 1225 } 1226 #endif // DISABLES_ON_WINDOW_DEACTIVATION 1227 1228 1229 void BScrollBar::_ReservedScrollBar1() {} 1230 void BScrollBar::_ReservedScrollBar2() {} 1231 void BScrollBar::_ReservedScrollBar3() {} 1232 void BScrollBar::_ReservedScrollBar4() {} 1233 1234 1235 BScrollBar& 1236 BScrollBar::operator=(const BScrollBar&) 1237 { 1238 return *this; 1239 } 1240 1241 1242 bool 1243 BScrollBar::_DoubleArrows() const 1244 { 1245 if (!fPrivateData->fScrollBarInfo.double_arrows) 1246 return false; 1247 1248 // if there is not enough room, switch to single arrows even though 1249 // double arrows is specified 1250 if (fOrientation == B_HORIZONTAL) { 1251 return Bounds().Width() > (Bounds().Height() + 1) * 4 1252 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1253 } else { 1254 return Bounds().Height() > (Bounds().Width() + 1) * 4 1255 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1256 } 1257 } 1258 1259 1260 void 1261 BScrollBar::_UpdateThumbFrame() 1262 { 1263 BRect bounds = Bounds(); 1264 bounds.InsetBy(1.0, 1.0); 1265 1266 BRect oldFrame = fPrivateData->fThumbFrame; 1267 fPrivateData->fThumbFrame = bounds; 1268 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1269 float maxSize; 1270 float buttonSize; 1271 1272 // assume square buttons 1273 if (fOrientation == B_VERTICAL) { 1274 maxSize = bounds.Height(); 1275 buttonSize = bounds.Width() + 1.0; 1276 } else { 1277 maxSize = bounds.Width(); 1278 buttonSize = bounds.Height() + 1.0; 1279 } 1280 1281 if (_DoubleArrows()) { 1282 // subtract the size of four buttons 1283 maxSize -= buttonSize * 4; 1284 } else { 1285 // subtract the size of two buttons 1286 maxSize -= buttonSize * 2; 1287 } 1288 // visual adjustments (room for darker line between thumb and buttons) 1289 maxSize--; 1290 1291 float thumbSize; 1292 if (fPrivateData->fScrollBarInfo.proportional) { 1293 float proportion = fProportion; 1294 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1295 proportion = 1.0; 1296 1297 if (proportion == 0.0) { 1298 // Special case a proportion of 0.0, use the large step value 1299 // in that case (NOTE: fMin == fMax already handled above) 1300 // This calculation is based on the assumption that "large step" 1301 // scrolls by one "page size". 1302 proportion = fLargeStep / (2 * (fMax - fMin)); 1303 if (proportion > 1.0) 1304 proportion = 1.0; 1305 } 1306 thumbSize = maxSize * proportion; 1307 if (thumbSize < minSize) 1308 thumbSize = minSize; 1309 } else 1310 thumbSize = minSize; 1311 1312 thumbSize = floorf(thumbSize + 0.5); 1313 thumbSize--; 1314 1315 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1316 float offset = 0.0; 1317 if (fMax > fMin) { 1318 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1319 * (maxSize - thumbSize - 1.0)); 1320 } 1321 1322 if (_DoubleArrows()) { 1323 offset += buttonSize * 2; 1324 } else 1325 offset += buttonSize; 1326 1327 // visual adjustments (room for darker line between thumb and buttons) 1328 offset++; 1329 1330 if (fOrientation == B_VERTICAL) { 1331 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1332 + thumbSize; 1333 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1334 } else { 1335 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1336 + thumbSize; 1337 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1338 } 1339 1340 if (Window() != NULL) { 1341 BRect invalid = oldFrame.IsValid() 1342 ? oldFrame | fPrivateData->fThumbFrame 1343 : fPrivateData->fThumbFrame; 1344 // account for those two dark lines 1345 if (fOrientation == B_HORIZONTAL) 1346 invalid.InsetBy(-2.0, 0.0); 1347 else 1348 invalid.InsetBy(0.0, -2.0); 1349 1350 Invalidate(invalid); 1351 } 1352 } 1353 1354 1355 float 1356 BScrollBar::_ValueFor(BPoint where) const 1357 { 1358 BRect bounds = Bounds(); 1359 bounds.InsetBy(1.0f, 1.0f); 1360 1361 float offset; 1362 float thumbSize; 1363 float maxSize; 1364 float buttonSize; 1365 1366 if (fOrientation == B_VERTICAL) { 1367 offset = where.y; 1368 thumbSize = fPrivateData->fThumbFrame.Height(); 1369 maxSize = bounds.Height(); 1370 buttonSize = bounds.Width() + 1.0f; 1371 } else { 1372 offset = where.x; 1373 thumbSize = fPrivateData->fThumbFrame.Width(); 1374 maxSize = bounds.Width(); 1375 buttonSize = bounds.Height() + 1.0f; 1376 } 1377 1378 if (_DoubleArrows()) { 1379 // subtract the size of four buttons 1380 maxSize -= buttonSize * 4; 1381 // convert point to inside of area between buttons 1382 offset -= buttonSize * 2; 1383 } else { 1384 // subtract the size of two buttons 1385 maxSize -= buttonSize * 2; 1386 // convert point to inside of area between buttons 1387 offset -= buttonSize; 1388 } 1389 // visual adjustments (room for darker line between thumb and buttons) 1390 maxSize--; 1391 offset++; 1392 1393 return roundf(fMin + (offset / (maxSize - thumbSize) 1394 * (fMax - fMin + 1.0f))); 1395 } 1396 1397 1398 int32 1399 BScrollBar::_ButtonFor(BPoint where) const 1400 { 1401 BRect bounds = Bounds(); 1402 bounds.InsetBy(1.0f, 1.0f); 1403 1404 float buttonSize = fOrientation == B_VERTICAL 1405 ? bounds.Width() + 1.0f 1406 : bounds.Height() + 1.0f; 1407 1408 BRect rect(bounds.left, bounds.top, 1409 bounds.left + buttonSize, bounds.top + buttonSize); 1410 1411 if (fOrientation == B_VERTICAL) { 1412 if (rect.Contains(where)) 1413 return ARROW1; 1414 1415 if (_DoubleArrows()) { 1416 rect.OffsetBy(0.0, buttonSize); 1417 if (rect.Contains(where)) 1418 return ARROW2; 1419 1420 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1421 if (rect.Contains(where)) 1422 return ARROW3; 1423 } 1424 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1425 if (rect.Contains(where)) 1426 return ARROW4; 1427 } else { 1428 if (rect.Contains(where)) 1429 return ARROW1; 1430 1431 if (_DoubleArrows()) { 1432 rect.OffsetBy(buttonSize, 0.0); 1433 if (rect.Contains(where)) 1434 return ARROW2; 1435 1436 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1437 if (rect.Contains(where)) 1438 return ARROW3; 1439 } 1440 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1441 if (rect.Contains(where)) 1442 return ARROW4; 1443 } 1444 1445 return NOARROW; 1446 } 1447 1448 1449 BRect 1450 BScrollBar::_ButtonRectFor(int32 button) const 1451 { 1452 BRect bounds = Bounds(); 1453 bounds.InsetBy(1.0f, 1.0f); 1454 1455 float buttonSize = fOrientation == B_VERTICAL 1456 ? bounds.Width() + 1.0f 1457 : bounds.Height() + 1.0f; 1458 1459 BRect rect(bounds.left, bounds.top, 1460 bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f); 1461 1462 if (fOrientation == B_VERTICAL) { 1463 switch (button) { 1464 case ARROW1: 1465 break; 1466 1467 case ARROW2: 1468 rect.OffsetBy(0.0, buttonSize); 1469 break; 1470 1471 case ARROW3: 1472 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1473 break; 1474 1475 case ARROW4: 1476 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1477 break; 1478 } 1479 } else { 1480 switch (button) { 1481 case ARROW1: 1482 break; 1483 1484 case ARROW2: 1485 rect.OffsetBy(buttonSize, 0.0); 1486 break; 1487 1488 case ARROW3: 1489 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1490 break; 1491 1492 case ARROW4: 1493 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1494 break; 1495 } 1496 } 1497 1498 return rect; 1499 } 1500 1501 1502 void 1503 BScrollBar::_UpdateTargetValue(BPoint where) 1504 { 1505 if (fOrientation == B_VERTICAL) { 1506 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1507 - fPrivateData->fThumbFrame.Height() / 2.0)); 1508 } else { 1509 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1510 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1511 } 1512 } 1513 1514 1515 void 1516 BScrollBar::_UpdateArrowButtons() 1517 { 1518 bool upEnabled = fValue > fMin; 1519 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1520 fPrivateData->fUpArrowsEnabled = upEnabled; 1521 Invalidate(_ButtonRectFor(ARROW1)); 1522 if (_DoubleArrows()) 1523 Invalidate(_ButtonRectFor(ARROW3)); 1524 } 1525 1526 bool downEnabled = fValue < fMax; 1527 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1528 fPrivateData->fDownArrowsEnabled = downEnabled; 1529 Invalidate(_ButtonRectFor(ARROW4)); 1530 if (_DoubleArrows()) 1531 Invalidate(_ButtonRectFor(ARROW2)); 1532 } 1533 } 1534 1535 1536 status_t 1537 control_scrollbar(scroll_bar_info* info, BScrollBar* bar) 1538 { 1539 if (bar == NULL || info == NULL) 1540 return B_BAD_VALUE; 1541 1542 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1543 != info->double_arrows) { 1544 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1545 1546 int8 multiplier = (info->double_arrows) ? 1 : -1; 1547 1548 if (bar->fOrientation == B_VERTICAL) { 1549 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1550 * B_H_SCROLL_BAR_HEIGHT); 1551 } else { 1552 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1553 * B_V_SCROLL_BAR_WIDTH, 0); 1554 } 1555 } 1556 1557 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1558 1559 // TODO: Figure out how proportional relates to the size of the thumb 1560 1561 // TODO: Add redraw code to reflect the changes 1562 1563 if (info->knob >= 0 && info->knob <= 2) 1564 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1565 else 1566 return B_BAD_VALUE; 1567 1568 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1569 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) { 1570 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1571 } else 1572 return B_BAD_VALUE; 1573 1574 return B_OK; 1575 } 1576 1577 1578 void 1579 BScrollBar::_DrawDisabledBackground(BRect area, const rgb_color& light, 1580 const rgb_color& dark, const rgb_color& fill) 1581 { 1582 if (!area.IsValid()) 1583 return; 1584 1585 if (fOrientation == B_VERTICAL) { 1586 int32 height = area.IntegerHeight(); 1587 if (height == 0) { 1588 SetHighColor(dark); 1589 StrokeLine(area.LeftTop(), area.RightTop()); 1590 } else if (height == 1) { 1591 SetHighColor(dark); 1592 FillRect(area); 1593 } else { 1594 BeginLineArray(4); 1595 AddLine(BPoint(area.left, area.top), 1596 BPoint(area.right, area.top), dark); 1597 AddLine(BPoint(area.left, area.bottom - 1), 1598 BPoint(area.left, area.top + 1), light); 1599 AddLine(BPoint(area.left + 1, area.top + 1), 1600 BPoint(area.right, area.top + 1), light); 1601 AddLine(BPoint(area.right, area.bottom), 1602 BPoint(area.left, area.bottom), dark); 1603 EndLineArray(); 1604 area.left++; 1605 area.top += 2; 1606 area.bottom--; 1607 if (area.IsValid()) { 1608 SetHighColor(fill); 1609 FillRect(area); 1610 } 1611 } 1612 } else { 1613 int32 width = area.IntegerWidth(); 1614 if (width == 0) { 1615 SetHighColor(dark); 1616 StrokeLine(area.LeftBottom(), area.LeftTop()); 1617 } else if (width == 1) { 1618 SetHighColor(dark); 1619 FillRect(area); 1620 } else { 1621 BeginLineArray(4); 1622 AddLine(BPoint(area.left, area.bottom), 1623 BPoint(area.left, area.top), dark); 1624 AddLine(BPoint(area.left + 1, area.bottom), 1625 BPoint(area.left + 1, area.top + 1), light); 1626 AddLine(BPoint(area.left + 1, area.top), 1627 BPoint(area.right - 1, area.top), light); 1628 AddLine(BPoint(area.right, area.top), 1629 BPoint(area.right, area.bottom), dark); 1630 EndLineArray(); 1631 area.left += 2; 1632 area.top ++; 1633 area.right--; 1634 if (area.IsValid()) { 1635 SetHighColor(fill); 1636 FillRect(area); 1637 } 1638 } 1639 } 1640 } 1641 1642 1643 void 1644 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect rect, 1645 const BRect& updateRect, bool enabled, bool down) 1646 { 1647 if (!updateRect.Intersects(rect)) 1648 return; 1649 1650 uint32 flags = 0; 1651 if (!enabled) 1652 flags |= BControlLook::B_DISABLED; 1653 1654 if (down && fPrivateData->fDoRepeat) 1655 flags |= BControlLook::B_ACTIVATED; 1656 1657 // TODO: Why does BControlLook need this as the base color for the 1658 // scrollbar to look right? 1659 rgb_color baseColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1660 B_LIGHTEN_1_TINT); 1661 1662 be_control_look->DrawButtonBackground(this, rect, updateRect, baseColor, 1663 flags, BControlLook::B_ALL_BORDERS, fOrientation); 1664 1665 // TODO: Why does BControlLook need this negative inset for the arrow to 1666 // look right? 1667 rect.InsetBy(-1.0f, -1.0f); 1668 be_control_look->DrawArrowShape(this, rect, updateRect, 1669 baseColor, direction, flags, B_DARKEN_MAX_TINT); 1670 } 1671 1672 1673 BSize 1674 BScrollBar::_MinSize() const 1675 { 1676 BSize minSize; 1677 if (fOrientation == B_HORIZONTAL) { 1678 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1679 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1680 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1681 } else { 1682 minSize.width = B_V_SCROLL_BAR_WIDTH; 1683 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1684 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1685 } 1686 1687 return minSize; 1688 } 1689