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