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