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 default: 646 BView::MessageReceived(message); 647 break; 648 } 649 } 650 651 652 void 653 BScrollBar::MouseDown(BPoint where) 654 { 655 if (!fPrivateData->fEnabled || fMin == fMax) 656 return; 657 658 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 659 660 int32 buttons; 661 if (Looper() == NULL || Looper()->CurrentMessage() == NULL 662 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) 663 buttons = B_PRIMARY_MOUSE_BUTTON; 664 665 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 666 // special absolute scrolling: move thumb to where we clicked 667 fPrivateData->fButtonDown = THUMB; 668 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 669 if (Orientation() == B_HORIZONTAL) 670 fPrivateData->fClickOffset.x = -fPrivateData->fThumbFrame.Width() / 2; 671 else 672 fPrivateData->fClickOffset.y = -fPrivateData->fThumbFrame.Height() / 2; 673 674 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 675 return; 676 } 677 678 // hit test for the thumb 679 if (fPrivateData->fThumbFrame.Contains(where)) { 680 fPrivateData->fButtonDown = THUMB; 681 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 682 Invalidate(fPrivateData->fThumbFrame); 683 return; 684 } 685 686 // hit test for arrows or empty area 687 float scrollValue = 0.0; 688 fPrivateData->fButtonDown = _ButtonFor(where); 689 switch (fPrivateData->fButtonDown) { 690 case ARROW1: 691 scrollValue = -fSmallStep; 692 break; 693 case ARROW2: 694 scrollValue = fSmallStep; 695 break; 696 case ARROW3: 697 scrollValue = -fSmallStep; 698 break; 699 case ARROW4: 700 scrollValue = fSmallStep; 701 break; 702 case NOARROW: 703 // we hit the empty area, figure out which side of the thumb 704 if (fOrientation == B_VERTICAL) { 705 if (where.y < fPrivateData->fThumbFrame.top) 706 scrollValue = -fLargeStep; 707 else 708 scrollValue = fLargeStep; 709 } else { 710 if (where.x < fPrivateData->fThumbFrame.left) 711 scrollValue = -fLargeStep; 712 else 713 scrollValue = fLargeStep; 714 } 715 _UpdateTargetValue(where); 716 break; 717 } 718 if (scrollValue != 0.0) { 719 SetValue(fValue + scrollValue); 720 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 721 722 // launch the repeat thread 723 if (fPrivateData->fRepeaterThread == -1) { 724 fPrivateData->fExitRepeater = false; 725 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 726 fPrivateData->fThumbInc = scrollValue; 727 fPrivateData->fDoRepeat = true; 728 fPrivateData->fRepeaterThread 729 = spawn_thread(fPrivateData->button_repeater_thread, 730 "scroll repeater", B_NORMAL_PRIORITY, fPrivateData); 731 resume_thread(fPrivateData->fRepeaterThread); 732 } else { 733 fPrivateData->fExitRepeater = false; 734 fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; 735 fPrivateData->fDoRepeat = true; 736 } 737 } 738 } 739 740 741 void 742 BScrollBar::MouseUp(BPoint pt) 743 { 744 if (fPrivateData->fButtonDown == THUMB) 745 Invalidate(fPrivateData->fThumbFrame); 746 else 747 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 748 749 fPrivateData->fButtonDown = NOARROW; 750 fPrivateData->fExitRepeater = true; 751 fPrivateData->fDoRepeat = false; 752 } 753 754 755 void 756 BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message) 757 { 758 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0 759 || fProportion < 0.0) 760 return; 761 762 if (fPrivateData->fButtonDown != NOARROW) { 763 if (fPrivateData->fButtonDown == THUMB) { 764 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 765 } else { 766 // suspend the repeating if the mouse is not over the button 767 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( 768 where); 769 if (fPrivateData->fDoRepeat != repeat) { 770 fPrivateData->fDoRepeat = repeat; 771 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 772 } 773 } 774 } else { 775 // update the value at which we want to stop repeating 776 if (fPrivateData->fDoRepeat) { 777 _UpdateTargetValue(where); 778 // we might have to turn arround 779 if ((fValue < fPrivateData->fStopValue 780 && fPrivateData->fThumbInc < 0) 781 || (fValue > fPrivateData->fStopValue 782 && fPrivateData->fThumbInc > 0)) { 783 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 784 } 785 } 786 } 787 } 788 789 790 void 791 BScrollBar::DetachedFromWindow() 792 { 793 BView::DetachedFromWindow(); 794 } 795 796 797 void 798 BScrollBar::Draw(BRect updateRect) 799 { 800 BRect bounds = Bounds(); 801 802 rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR); 803 804 // stroke a dark frame arround the entire scrollbar 805 // (independent of enabled state) 806 // take care of border highlighting (scroll target is focus view) 807 SetHighColor(tint_color(normal, B_DARKEN_2_TINT)); 808 if (fPrivateData->fBorderHighlighted && fPrivateData->fEnabled) { 809 rgb_color borderColor = HighColor(); 810 rgb_color highlightColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 811 BeginLineArray(4); 812 AddLine(BPoint(bounds.left + 1, bounds.bottom), 813 BPoint(bounds.right, bounds.bottom), borderColor); 814 AddLine(BPoint(bounds.right, bounds.top + 1), 815 BPoint(bounds.right, bounds.bottom - 1), borderColor); 816 if (fOrientation == B_HORIZONTAL) { 817 AddLine(BPoint(bounds.left, bounds.top + 1), 818 BPoint(bounds.left, bounds.bottom), borderColor); 819 } else { 820 AddLine(BPoint(bounds.left, bounds.top), 821 BPoint(bounds.left, bounds.bottom), highlightColor); 822 } 823 if (fOrientation == B_HORIZONTAL) { 824 AddLine(BPoint(bounds.left, bounds.top), 825 BPoint(bounds.right, bounds.top), highlightColor); 826 } else { 827 AddLine(BPoint(bounds.left + 1, bounds.top), 828 BPoint(bounds.right, bounds.top), borderColor); 829 } 830 EndLineArray(); 831 } else 832 StrokeRect(bounds); 833 bounds.InsetBy(1.0, 1.0); 834 835 bool enabled = fPrivateData->fEnabled && fMin < fMax 836 && fProportion < 1.0 && fProportion >= 0.0; 837 838 rgb_color light, light1, dark, dark1, dark2, dark4; 839 if (enabled) { 840 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 841 light1 = tint_color(normal, B_LIGHTEN_1_TINT); 842 dark = tint_color(normal, B_DARKEN_3_TINT); 843 dark1 = tint_color(normal, B_DARKEN_1_TINT); 844 dark2 = tint_color(normal, B_DARKEN_2_TINT); 845 dark4 = tint_color(normal, B_DARKEN_4_TINT); 846 } else { 847 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 848 light1 = normal; 849 dark = tint_color(normal, B_DARKEN_2_TINT); 850 dark1 = tint_color(normal, B_LIGHTEN_2_TINT); 851 dark2 = tint_color(normal, B_LIGHTEN_1_TINT); 852 dark4 = tint_color(normal, B_DARKEN_3_TINT); 853 } 854 855 SetDrawingMode(B_OP_OVER); 856 857 BRect thumbBG = bounds; 858 bool doubleArrows = _DoubleArrows(); 859 860 // Draw arrows 861 if (fOrientation == B_HORIZONTAL) { 862 BRect buttonFrame(bounds.left, bounds.top, 863 bounds.left + bounds.Height(), bounds.bottom); 864 865 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 866 enabled, fPrivateData->fButtonDown == ARROW1); 867 868 if (doubleArrows) { 869 buttonFrame.OffsetBy(bounds.Height() + 1, 0.0); 870 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 871 enabled, fPrivateData->fButtonDown == ARROW2); 872 873 buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), 874 bounds.top); 875 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 876 enabled, fPrivateData->fButtonDown == ARROW3); 877 878 thumbBG.left += bounds.Height() * 2 + 2; 879 thumbBG.right -= bounds.Height() * 2 + 2; 880 } else { 881 thumbBG.left += bounds.Height() + 1; 882 thumbBG.right -= bounds.Height() + 1; 883 } 884 885 buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top); 886 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 887 enabled, fPrivateData->fButtonDown == ARROW4); 888 } else { 889 BRect buttonFrame(bounds.left, bounds.top, bounds.right, 890 bounds.top + bounds.Width()); 891 892 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 893 enabled, fPrivateData->fButtonDown == ARROW1); 894 895 if (doubleArrows) { 896 buttonFrame.OffsetBy(0.0, bounds.Width() + 1); 897 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 898 enabled, fPrivateData->fButtonDown == ARROW2); 899 900 buttonFrame.OffsetTo(bounds.left, bounds.bottom 901 - ((bounds.Width() * 2) + 1)); 902 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 903 enabled, fPrivateData->fButtonDown == ARROW3); 904 905 thumbBG.top += bounds.Width() * 2 + 2; 906 thumbBG.bottom -= bounds.Width() * 2 + 2; 907 } else { 908 thumbBG.top += bounds.Width() + 1; 909 thumbBG.bottom -= bounds.Width() + 1; 910 } 911 912 buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width()); 913 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 914 enabled, fPrivateData->fButtonDown == ARROW4); 915 } 916 917 SetDrawingMode(B_OP_COPY); 918 919 // background for thumb area 920 BRect rect(fPrivateData->fThumbFrame); 921 922 if (be_control_look == NULL) { 923 if (fOrientation == B_HORIZONTAL) { 924 BeginLineArray(8); 925 926 if (rect.left > thumbBG.left) { 927 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 928 BPoint(thumbBG.left, thumbBG.top), 929 rect.left > thumbBG.left + 1 ? dark4 : dark); 930 } 931 if (rect.left > thumbBG.left + 1) { 932 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 933 BPoint(thumbBG.left + 1, thumbBG.bottom), dark2); 934 AddLine(BPoint(thumbBG.left + 1, thumbBG.top), 935 BPoint(rect.left - 1, thumbBG.top), dark2); 936 AddLine(BPoint(rect.left - 1, thumbBG.bottom), 937 BPoint(thumbBG.left + 2, thumbBG.bottom), normal); 938 } 939 940 if (rect.right < thumbBG.right - 1) { 941 AddLine(BPoint(rect.right + 2, thumbBG.top + 1), 942 BPoint(rect.right + 2, thumbBG.bottom), dark2); 943 AddLine(BPoint(rect.right + 1, thumbBG.top), 944 BPoint(thumbBG.right, thumbBG.top), dark2); 945 AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom), 946 BPoint(rect.right + 3, thumbBG.bottom), normal); 947 } 948 if (rect.right < thumbBG.right) { 949 AddLine(BPoint(thumbBG.right, thumbBG.top), 950 BPoint(thumbBG.right, thumbBG.bottom), dark); 951 } 952 953 EndLineArray(); 954 } else { 955 BeginLineArray(8); 956 957 if (rect.top > thumbBG.top) { 958 AddLine(BPoint(thumbBG.left, thumbBG.top), 959 BPoint(thumbBG.right, thumbBG.top), 960 rect.top > thumbBG.top + 1 ? dark4 : dark); 961 } 962 if (rect.top > thumbBG.top + 1) { 963 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 964 BPoint(thumbBG.right, thumbBG.top + 1), dark2); 965 AddLine(BPoint(thumbBG.left, rect.top - 1), 966 BPoint(thumbBG.left, thumbBG.top + 1), dark2); 967 AddLine(BPoint(thumbBG.right, rect.top - 1), 968 BPoint(thumbBG.right, thumbBG.top + 2), normal); 969 } 970 971 if (rect.bottom < thumbBG.bottom - 1) { 972 AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2), 973 BPoint(thumbBG.right, rect.bottom + 2), dark2); 974 AddLine(BPoint(thumbBG.left, rect.bottom + 1), 975 BPoint(thumbBG.left, thumbBG.bottom - 1), dark2); 976 AddLine(BPoint(thumbBG.right, rect.bottom + 3), 977 BPoint(thumbBG.right, thumbBG.bottom - 1), normal); 978 } 979 if (rect.bottom < thumbBG.bottom) { 980 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 981 BPoint(thumbBG.right, thumbBG.bottom), dark); 982 } 983 984 EndLineArray(); 985 } 986 } 987 SetHighColor(dark1); 988 989 if (be_control_look != NULL) { 990 uint32 flags = 0; 991 if (!enabled) 992 flags |= BControlLook::B_DISABLED; 993 994 // fill background besides the thumb 995 if (fOrientation == B_HORIZONTAL) { 996 BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, 997 thumbBG.bottom); 998 BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, 999 thumbBG.bottom); 1000 1001 be_control_look->DrawScrollBarBackground(this, leftOfThumb, 1002 rightOfThumb, updateRect, normal, flags, fOrientation); 1003 } else { 1004 BRect topOfThumb(thumbBG.left, thumbBG.top, 1005 thumbBG.right, rect.top - 1); 1006 1007 BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, 1008 thumbBG.right, thumbBG.bottom); 1009 1010 be_control_look->DrawScrollBarBackground(this, topOfThumb, 1011 bottomOfThumb, updateRect, normal, flags, fOrientation); 1012 } 1013 } 1014 1015 // Draw scroll thumb 1016 if (enabled) { 1017 if (be_control_look == NULL) { 1018 // fill and additional dark lines 1019 thumbBG.InsetBy(1.0, 1.0); 1020 if (fOrientation == B_HORIZONTAL) { 1021 BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1, 1022 thumbBG.bottom); 1023 if (leftOfThumb.IsValid()) 1024 FillRect(leftOfThumb); 1025 1026 BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right, 1027 thumbBG.bottom); 1028 if (rightOfThumb.IsValid()) 1029 FillRect(rightOfThumb); 1030 1031 // dark lines before and after thumb 1032 if (rect.left > thumbBG.left) { 1033 SetHighColor(dark); 1034 StrokeLine(BPoint(rect.left - 1, rect.top), 1035 BPoint(rect.left - 1, rect.bottom)); 1036 } 1037 if (rect.right < thumbBG.right) { 1038 SetHighColor(dark4); 1039 StrokeLine(BPoint(rect.right + 1, rect.top), 1040 BPoint(rect.right + 1, rect.bottom)); 1041 } 1042 } else { 1043 BRect topOfThumb(thumbBG.left, thumbBG.top + 1, 1044 thumbBG.right, rect.top - 1); 1045 if (topOfThumb.IsValid()) 1046 FillRect(topOfThumb); 1047 1048 BRect bottomOfThumb(thumbBG.left, rect.bottom + 3, 1049 thumbBG.right, thumbBG.bottom); 1050 if (bottomOfThumb.IsValid()) 1051 FillRect(bottomOfThumb); 1052 1053 // dark lines before and after thumb 1054 if (rect.top > thumbBG.top) { 1055 SetHighColor(dark); 1056 StrokeLine(BPoint(rect.left, rect.top - 1), 1057 BPoint(rect.right, rect.top - 1)); 1058 } 1059 if (rect.bottom < thumbBG.bottom) { 1060 SetHighColor(dark4); 1061 StrokeLine(BPoint(rect.left, rect.bottom + 1), 1062 BPoint(rect.right, rect.bottom + 1)); 1063 } 1064 } 1065 } 1066 1067 // fill the clickable surface of the thumb 1068 if (be_control_look) { 1069 be_control_look->DrawButtonBackground(this, rect, updateRect, 1070 normal, 0, BControlLook::B_ALL_BORDERS, fOrientation); 1071 } else { 1072 BeginLineArray(4); 1073 AddLine(BPoint(rect.left, rect.bottom), 1074 BPoint(rect.left, rect.top), light); 1075 AddLine(BPoint(rect.left + 1, rect.top), 1076 BPoint(rect.right, rect.top), light); 1077 AddLine(BPoint(rect.right, rect.top + 1), 1078 BPoint(rect.right, rect.bottom), dark1); 1079 AddLine(BPoint(rect.right - 1, rect.bottom), 1080 BPoint(rect.left + 1, rect.bottom), dark1); 1081 EndLineArray(); 1082 1083 // fill 1084 rect.InsetBy(1.0, 1.0); 1085 /*if (fPrivateData->fButtonDown == THUMB) 1086 SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2)); 1087 else*/ 1088 SetHighColor(normal); 1089 1090 FillRect(rect); 1091 } 1092 // TODO: Add the other thumb styles - dots and lines 1093 } else { 1094 if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) { 1095 // we cannot scroll at all 1096 _DrawDisabledBackground(thumbBG, light, dark, dark1); 1097 } else { 1098 // we could scroll, but we're simply disabled 1099 float bgTint = 1.06; 1100 rgb_color bgLight = tint_color(light, bgTint * 3); 1101 rgb_color bgShadow = tint_color(dark, bgTint); 1102 rgb_color bgFill = tint_color(dark1, bgTint); 1103 if (fOrientation == B_HORIZONTAL) { 1104 // left of thumb 1105 BRect besidesThumb(thumbBG); 1106 besidesThumb.right = rect.left - 1; 1107 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1108 // right of thumb 1109 besidesThumb.left = rect.right + 1; 1110 besidesThumb.right = thumbBG.right; 1111 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1112 } else { 1113 // above thumb 1114 BRect besidesThumb(thumbBG); 1115 besidesThumb.bottom = rect.top - 1; 1116 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1117 // below thumb 1118 besidesThumb.top = rect.bottom + 1; 1119 besidesThumb.bottom = thumbBG.bottom; 1120 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 1121 } 1122 // thumb bevel 1123 BeginLineArray(4); 1124 AddLine(BPoint(rect.left, rect.bottom), 1125 BPoint(rect.left, rect.top), light); 1126 AddLine(BPoint(rect.left + 1, rect.top), 1127 BPoint(rect.right, rect.top), light); 1128 AddLine(BPoint(rect.right, rect.top + 1), 1129 BPoint(rect.right, rect.bottom), dark2); 1130 AddLine(BPoint(rect.right - 1, rect.bottom), 1131 BPoint(rect.left + 1, rect.bottom), dark2); 1132 EndLineArray(); 1133 // thumb fill 1134 rect.InsetBy(1.0, 1.0); 1135 SetHighColor(dark1); 1136 FillRect(rect); 1137 } 1138 } 1139 } 1140 1141 1142 void 1143 BScrollBar::FrameMoved(BPoint newPosition) 1144 { 1145 BView::FrameMoved(newPosition); 1146 } 1147 1148 1149 void 1150 BScrollBar::FrameResized(float newWidth, float newHeight) 1151 { 1152 _UpdateThumbFrame(); 1153 } 1154 1155 1156 BHandler* 1157 BScrollBar::ResolveSpecifier(BMessage* message, int32 index, 1158 BMessage* specifier, int32 form, const char *property) 1159 { 1160 return BView::ResolveSpecifier(message, index, specifier, form, property); 1161 } 1162 1163 1164 void 1165 BScrollBar::ResizeToPreferred() 1166 { 1167 BView::ResizeToPreferred(); 1168 } 1169 1170 1171 void 1172 BScrollBar::GetPreferredSize(float* _width, float* _height) 1173 { 1174 if (fOrientation == B_VERTICAL) { 1175 if (_width) 1176 *_width = B_V_SCROLL_BAR_WIDTH; 1177 if (_height) 1178 *_height = Bounds().Height(); 1179 } else if (fOrientation == B_HORIZONTAL) { 1180 if (_width) 1181 *_width = Bounds().Width(); 1182 if (_height) 1183 *_height = B_H_SCROLL_BAR_HEIGHT; 1184 } 1185 } 1186 1187 1188 void 1189 BScrollBar::MakeFocus(bool state) 1190 { 1191 BView::MakeFocus(state); 1192 } 1193 1194 1195 void 1196 BScrollBar::AllAttached() 1197 { 1198 BView::AllAttached(); 1199 } 1200 1201 1202 void 1203 BScrollBar::AllDetached() 1204 { 1205 BView::AllDetached(); 1206 } 1207 1208 1209 status_t 1210 BScrollBar::GetSupportedSuites(BMessage *message) 1211 { 1212 return BView::GetSupportedSuites(message); 1213 } 1214 1215 1216 BSize 1217 BScrollBar::MinSize() 1218 { 1219 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); 1220 } 1221 1222 1223 BSize 1224 BScrollBar::MaxSize() 1225 { 1226 BSize maxSize = _MinSize(); 1227 if (fOrientation == B_HORIZONTAL) 1228 maxSize.width = B_SIZE_UNLIMITED; 1229 else 1230 maxSize.height = B_SIZE_UNLIMITED; 1231 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); 1232 } 1233 1234 1235 BSize 1236 BScrollBar::PreferredSize() 1237 { 1238 BSize preferredSize = _MinSize(); 1239 if (fOrientation == B_HORIZONTAL) 1240 preferredSize.width *= 2; 1241 else 1242 preferredSize.height *= 2; 1243 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); 1244 } 1245 1246 1247 status_t 1248 BScrollBar::Perform(perform_code code, void* _data) 1249 { 1250 switch (code) { 1251 case PERFORM_CODE_MIN_SIZE: 1252 ((perform_data_min_size*)_data)->return_value 1253 = BScrollBar::MinSize(); 1254 return B_OK; 1255 case PERFORM_CODE_MAX_SIZE: 1256 ((perform_data_max_size*)_data)->return_value 1257 = BScrollBar::MaxSize(); 1258 return B_OK; 1259 case PERFORM_CODE_PREFERRED_SIZE: 1260 ((perform_data_preferred_size*)_data)->return_value 1261 = BScrollBar::PreferredSize(); 1262 return B_OK; 1263 case PERFORM_CODE_LAYOUT_ALIGNMENT: 1264 ((perform_data_layout_alignment*)_data)->return_value 1265 = BScrollBar::LayoutAlignment(); 1266 return B_OK; 1267 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 1268 ((perform_data_has_height_for_width*)_data)->return_value 1269 = BScrollBar::HasHeightForWidth(); 1270 return B_OK; 1271 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 1272 { 1273 perform_data_get_height_for_width* data 1274 = (perform_data_get_height_for_width*)_data; 1275 BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, 1276 &data->preferred); 1277 return B_OK; 1278 } 1279 case PERFORM_CODE_SET_LAYOUT: 1280 { 1281 perform_data_set_layout* data = (perform_data_set_layout*)_data; 1282 BScrollBar::SetLayout(data->layout); 1283 return B_OK; 1284 } 1285 case PERFORM_CODE_INVALIDATE_LAYOUT: 1286 { 1287 perform_data_invalidate_layout* data 1288 = (perform_data_invalidate_layout*)_data; 1289 BScrollBar::InvalidateLayout(data->descendants); 1290 return B_OK; 1291 } 1292 case PERFORM_CODE_DO_LAYOUT: 1293 { 1294 BScrollBar::DoLayout(); 1295 return B_OK; 1296 } 1297 } 1298 1299 return BView::Perform(code, _data); 1300 } 1301 1302 1303 #if DISABLES_ON_WINDOW_DEACTIVATION 1304 void 1305 BScrollBar::WindowActivated(bool active) 1306 { 1307 fPrivateData->fEnabled = active; 1308 Invalidate(); 1309 } 1310 #endif // DISABLES_ON_WINDOW_DEACTIVATION 1311 1312 1313 void BScrollBar::_ReservedScrollBar1() {} 1314 void BScrollBar::_ReservedScrollBar2() {} 1315 void BScrollBar::_ReservedScrollBar3() {} 1316 void BScrollBar::_ReservedScrollBar4() {} 1317 1318 1319 1320 BScrollBar& 1321 BScrollBar::operator=(const BScrollBar&) 1322 { 1323 return *this; 1324 } 1325 1326 1327 bool 1328 BScrollBar::_DoubleArrows() const 1329 { 1330 if (!fPrivateData->fScrollBarInfo.double_arrows) 1331 return false; 1332 1333 // if there is not enough room, switch to single arrows even though 1334 // double arrows is specified 1335 if (fOrientation == B_HORIZONTAL) { 1336 return Bounds().Width() > (Bounds().Height() + 1) * 4 1337 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1338 } else { 1339 return Bounds().Height() > (Bounds().Width() + 1) * 4 1340 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1341 } 1342 } 1343 1344 1345 void 1346 BScrollBar::_UpdateThumbFrame() 1347 { 1348 BRect bounds = Bounds(); 1349 bounds.InsetBy(1.0, 1.0); 1350 1351 BRect oldFrame = fPrivateData->fThumbFrame; 1352 fPrivateData->fThumbFrame = bounds; 1353 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1354 float maxSize; 1355 float buttonSize; 1356 1357 // assume square buttons 1358 if (fOrientation == B_VERTICAL) { 1359 maxSize = bounds.Height(); 1360 buttonSize = bounds.Width() + 1.0; 1361 } else { 1362 maxSize = bounds.Width(); 1363 buttonSize = bounds.Height() + 1.0; 1364 } 1365 1366 if (_DoubleArrows()) { 1367 // subtract the size of four buttons 1368 maxSize -= buttonSize * 4; 1369 } else { 1370 // subtract the size of two buttons 1371 maxSize -= buttonSize * 2; 1372 } 1373 // visual adjustments (room for darker line between thumb and buttons) 1374 maxSize--; 1375 1376 float thumbSize; 1377 if (fPrivateData->fScrollBarInfo.proportional) { 1378 float proportion = fProportion; 1379 if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) 1380 proportion = 1.0; 1381 if (proportion == 0.0) { 1382 // Special case a proportion of 0.0, use the large step value 1383 // in that case (NOTE: fMin == fMax already handled above) 1384 // This calculation is based on the assumption that "large step" 1385 // scrolls by one "page size". 1386 proportion = fLargeStep / (2 * (fMax - fMin)); 1387 if (proportion > 1.0) 1388 proportion = 1.0; 1389 } 1390 thumbSize = maxSize * proportion; 1391 if (thumbSize < minSize) 1392 thumbSize = minSize; 1393 } else 1394 thumbSize = minSize; 1395 1396 thumbSize = floorf(thumbSize + 0.5); 1397 thumbSize--; 1398 1399 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" 1400 float offset = 0.0; 1401 if (fMax > fMin) { 1402 offset = floorf(((fValue - fMin) / (fMax - fMin)) 1403 * (maxSize - thumbSize - 1.0)); 1404 } 1405 1406 if (_DoubleArrows()) { 1407 offset += buttonSize * 2; 1408 } else { 1409 offset += buttonSize; 1410 } 1411 // visual adjustments (room for darker line between thumb and buttons) 1412 offset++; 1413 1414 if (fOrientation == B_VERTICAL) { 1415 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top 1416 + thumbSize; 1417 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1418 } else { 1419 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left 1420 + thumbSize; 1421 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1422 } 1423 1424 if (Window()) { 1425 BRect invalid = oldFrame.IsValid() ? 1426 oldFrame | fPrivateData->fThumbFrame 1427 : fPrivateData->fThumbFrame; 1428 // account for those two dark lines 1429 if (fOrientation == B_HORIZONTAL) 1430 invalid.InsetBy(-2.0, 0.0); 1431 else 1432 invalid.InsetBy(0.0, -2.0); 1433 Invalidate(invalid); 1434 } 1435 } 1436 1437 1438 float 1439 BScrollBar::_ValueFor(BPoint where) const 1440 { 1441 BRect bounds = Bounds(); 1442 bounds.InsetBy(1.0, 1.0); 1443 1444 float offset; 1445 float thumbSize; 1446 float maxSize; 1447 float buttonSize; 1448 1449 if (fOrientation == B_VERTICAL) { 1450 offset = where.y; 1451 thumbSize = fPrivateData->fThumbFrame.Height(); 1452 maxSize = bounds.Height(); 1453 buttonSize = bounds.Width() + 1.0; 1454 } else { 1455 offset = where.x; 1456 thumbSize = fPrivateData->fThumbFrame.Width(); 1457 maxSize = bounds.Width(); 1458 buttonSize = bounds.Height() + 1.0; 1459 } 1460 1461 if (_DoubleArrows()) { 1462 // subtract the size of four buttons 1463 maxSize -= buttonSize * 4; 1464 // convert point to inside of area between buttons 1465 offset -= buttonSize * 2; 1466 } else { 1467 // subtract the size of two buttons 1468 maxSize -= buttonSize * 2; 1469 // convert point to inside of area between buttons 1470 offset -= buttonSize; 1471 } 1472 // visual adjustments (room for darker line between thumb and buttons) 1473 maxSize--; 1474 offset++; 1475 1476 float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0)); 1477 if (value >= 0.0) 1478 return floorf(value + 0.5); 1479 else 1480 return ceilf(value - 0.5); 1481 } 1482 1483 1484 int32 1485 BScrollBar::_ButtonFor(BPoint where) const 1486 { 1487 BRect bounds = Bounds(); 1488 bounds.InsetBy(1.0, 1.0); 1489 1490 float buttonSize; 1491 if (fOrientation == B_VERTICAL) { 1492 buttonSize = bounds.Width() + 1.0; 1493 } else { 1494 buttonSize = bounds.Height() + 1.0; 1495 } 1496 1497 BRect rect(bounds.left, bounds.top, 1498 bounds.left + buttonSize, bounds.top + buttonSize); 1499 1500 if (fOrientation == B_VERTICAL) { 1501 if (rect.Contains(where)) 1502 return ARROW1; 1503 if (_DoubleArrows()) { 1504 rect.OffsetBy(0.0, buttonSize); 1505 if (rect.Contains(where)) 1506 return ARROW2; 1507 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1508 if (rect.Contains(where)) 1509 return ARROW3; 1510 } 1511 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1512 if (rect.Contains(where)) 1513 return ARROW4; 1514 } else { 1515 if (rect.Contains(where)) 1516 return ARROW1; 1517 if (_DoubleArrows()) { 1518 rect.OffsetBy(buttonSize, 0.0); 1519 if (rect.Contains(where)) 1520 return ARROW2; 1521 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1522 if (rect.Contains(where)) 1523 return ARROW3; 1524 } 1525 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1526 if (rect.Contains(where)) 1527 return ARROW4; 1528 } 1529 1530 return NOARROW; 1531 } 1532 1533 1534 BRect 1535 BScrollBar::_ButtonRectFor(int32 button) const 1536 { 1537 BRect bounds = Bounds(); 1538 bounds.InsetBy(1.0, 1.0); 1539 1540 float buttonSize; 1541 if (fOrientation == B_VERTICAL) { 1542 buttonSize = bounds.Width() + 1.0; 1543 } else { 1544 buttonSize = bounds.Height() + 1.0; 1545 } 1546 1547 BRect rect(bounds.left, bounds.top, 1548 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1549 1550 if (fOrientation == B_VERTICAL) { 1551 switch (button) { 1552 case ARROW1: 1553 break; 1554 case ARROW2: 1555 rect.OffsetBy(0.0, buttonSize); 1556 break; 1557 case ARROW3: 1558 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1559 break; 1560 case ARROW4: 1561 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1562 break; 1563 } 1564 } else { 1565 switch (button) { 1566 case ARROW1: 1567 break; 1568 case ARROW2: 1569 rect.OffsetBy(buttonSize, 0.0); 1570 break; 1571 case ARROW3: 1572 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1573 break; 1574 case ARROW4: 1575 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1576 break; 1577 } 1578 } 1579 1580 return rect; 1581 } 1582 1583 1584 void 1585 BScrollBar::_UpdateTargetValue(BPoint where) 1586 { 1587 if (fOrientation == B_VERTICAL) { 1588 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y 1589 - fPrivateData->fThumbFrame.Height() / 2.0)); 1590 } else { 1591 fPrivateData->fStopValue = _ValueFor(BPoint(where.x 1592 - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1593 } 1594 } 1595 1596 1597 void 1598 BScrollBar::_UpdateArrowButtons() 1599 { 1600 bool upEnabled = fValue > fMin; 1601 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1602 fPrivateData->fUpArrowsEnabled = upEnabled; 1603 Invalidate(_ButtonRectFor(ARROW1)); 1604 if (_DoubleArrows()) 1605 Invalidate(_ButtonRectFor(ARROW3)); 1606 } 1607 1608 bool downEnabled = fValue < fMax; 1609 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1610 fPrivateData->fDownArrowsEnabled = downEnabled; 1611 Invalidate(_ButtonRectFor(ARROW4)); 1612 if (_DoubleArrows()) 1613 Invalidate(_ButtonRectFor(ARROW2)); 1614 } 1615 } 1616 1617 1618 status_t 1619 control_scrollbar(scroll_bar_info *info, BScrollBar *bar) 1620 { 1621 if (!bar || !info) 1622 return B_BAD_VALUE; 1623 1624 if (bar->fPrivateData->fScrollBarInfo.double_arrows 1625 != info->double_arrows) { 1626 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1627 1628 int8 multiplier = (info->double_arrows) ? 1 : -1; 1629 1630 if (bar->fOrientation == B_VERTICAL) { 1631 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier 1632 * B_H_SCROLL_BAR_HEIGHT); 1633 } else { 1634 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier 1635 * B_V_SCROLL_BAR_WIDTH, 0); 1636 } 1637 } 1638 1639 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1640 1641 // TODO: Figure out how proportional relates to the size of the thumb 1642 1643 // TODO: Add redraw code to reflect the changes 1644 1645 if (info->knob >= 0 && info->knob <= 2) 1646 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1647 else 1648 return B_BAD_VALUE; 1649 1650 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE 1651 && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) 1652 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1653 else 1654 return B_BAD_VALUE; 1655 1656 return B_OK; 1657 } 1658 1659 1660 void 1661 BScrollBar::_DrawDisabledBackground(BRect area, 1662 const rgb_color& light, 1663 const rgb_color& dark, 1664 const rgb_color& fill) 1665 { 1666 if (!area.IsValid()) 1667 return; 1668 1669 if (fOrientation == B_VERTICAL) { 1670 int32 height = area.IntegerHeight(); 1671 if (height == 0) { 1672 SetHighColor(dark); 1673 StrokeLine(area.LeftTop(), area.RightTop()); 1674 } else if (height == 1) { 1675 SetHighColor(dark); 1676 FillRect(area); 1677 } else { 1678 BeginLineArray(4); 1679 AddLine(BPoint(area.left, area.top), 1680 BPoint(area.right, area.top), dark); 1681 AddLine(BPoint(area.left, area.bottom - 1), 1682 BPoint(area.left, area.top + 1), light); 1683 AddLine(BPoint(area.left + 1, area.top + 1), 1684 BPoint(area.right, area.top + 1), light); 1685 AddLine(BPoint(area.right, area.bottom), 1686 BPoint(area.left, area.bottom), dark); 1687 EndLineArray(); 1688 area.left++; 1689 area.top += 2; 1690 area.bottom--; 1691 if (area.IsValid()) { 1692 SetHighColor(fill); 1693 FillRect(area); 1694 } 1695 } 1696 } else { 1697 int32 width = area.IntegerWidth(); 1698 if (width == 0) { 1699 SetHighColor(dark); 1700 StrokeLine(area.LeftBottom(), area.LeftTop()); 1701 } else if (width == 1) { 1702 SetHighColor(dark); 1703 FillRect(area); 1704 } else { 1705 BeginLineArray(4); 1706 AddLine(BPoint(area.left, area.bottom), 1707 BPoint(area.left, area.top), dark); 1708 AddLine(BPoint(area.left + 1, area.bottom), 1709 BPoint(area.left + 1, area.top + 1), light); 1710 AddLine(BPoint(area.left + 1, area.top), 1711 BPoint(area.right - 1, area.top), light); 1712 AddLine(BPoint(area.right, area.top), 1713 BPoint(area.right, area.bottom), dark); 1714 EndLineArray(); 1715 area.left += 2; 1716 area.top ++; 1717 area.right--; 1718 if (area.IsValid()) { 1719 SetHighColor(fill); 1720 FillRect(area); 1721 } 1722 } 1723 } 1724 } 1725 1726 1727 void 1728 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r, 1729 const BRect& updateRect, bool enabled, bool down) 1730 { 1731 if (!updateRect.Intersects(r)) 1732 return; 1733 1734 rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR); 1735 rgb_color light, dark, darker, normal, arrow; 1736 1737 if (down && fPrivateData->fDoRepeat) { 1738 light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0); 1739 dark = darker = c; 1740 normal = tint_color(c, B_DARKEN_1_TINT); 1741 arrow = tint_color(c, B_DARKEN_MAX_TINT); 1742 1743 } else { 1744 // Add a usability perk - disable buttons if they would not do anything 1745 // - like a left arrow if the value == fMin 1746 // NOTE: disabled because of too much visual noise/distraction 1747 /* if ((direction == ARROW_LEFT || direction == ARROW_UP) 1748 && (fValue == fMin)) { 1749 use_enabled_colors = false; 1750 } else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) 1751 && (fValue == fMax)) { 1752 use_enabled_colors = false; 1753 }*/ 1754 1755 if (enabled) { 1756 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1757 dark = tint_color(c, B_DARKEN_1_TINT); 1758 darker = tint_color(c, B_DARKEN_2_TINT); 1759 normal = c; 1760 arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0); 1761 } else { 1762 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1763 dark = tint_color(c, B_LIGHTEN_1_TINT); 1764 darker = tint_color(c, B_DARKEN_2_TINT); 1765 normal = tint_color(c, B_LIGHTEN_2_TINT); 1766 arrow = tint_color(c, B_DARKEN_1_TINT); 1767 } 1768 } 1769 1770 BPoint tri1, tri2, tri3; 1771 float hInset = r.Width() / 3; 1772 float vInset = r.Height() / 3; 1773 r.InsetBy(hInset, vInset); 1774 1775 switch (direction) { 1776 case ARROW_LEFT: 1777 tri1.Set(r.right, r.top); 1778 tri2.Set(r.right - r.Width() / 1.33, (r.top + r.bottom + 1) /2 ); 1779 tri3.Set(r.right, r.bottom + 1); 1780 break; 1781 case ARROW_RIGHT: 1782 tri1.Set(r.left, r.bottom + 1); 1783 tri2.Set(r.left + r.Width() / 1.33, (r.top + r.bottom + 1) / 2); 1784 tri3.Set(r.left, r.top); 1785 break; 1786 case ARROW_UP: 1787 tri1.Set(r.left, r.bottom); 1788 tri2.Set((r.left + r.right + 1) / 2, r.bottom - r.Height() / 1.33); 1789 tri3.Set(r.right + 1, r.bottom); 1790 break; 1791 default: 1792 tri1.Set(r.left, r.top); 1793 tri2.Set((r.left + r.right + 1) / 2, r.top + r.Height() / 1.33); 1794 tri3.Set(r.right + 1, r.top); 1795 break; 1796 } 1797 // offset triangle if down 1798 if (down && fPrivateData->fDoRepeat) { 1799 BPoint offset(1.0, 1.0); 1800 tri1 = tri1 + offset; 1801 tri2 = tri2 + offset; 1802 tri3 = tri3 + offset; 1803 } 1804 1805 r.InsetBy(-(hInset - 1), -(vInset - 1)); 1806 if (be_control_look != NULL) { 1807 BRect temp(r.InsetByCopy(-1, -1)); 1808 uint32 flags = 0; 1809 if (down) 1810 flags |= BControlLook::B_ACTIVATED; 1811 be_control_look->DrawButtonBackground(this, temp, updateRect, 1812 down ? c : normal, flags, BControlLook::B_ALL_BORDERS, 1813 fOrientation); 1814 } else { 1815 SetHighColor(normal); 1816 FillRect(r); 1817 } 1818 1819 BShape arrowShape; 1820 arrowShape.MoveTo(tri1); 1821 arrowShape.LineTo(tri2); 1822 arrowShape.LineTo(tri3); 1823 1824 SetHighColor(arrow); 1825 SetPenSize(ceilf(hInset / 2.0)); 1826 StrokeShape(&arrowShape); 1827 SetPenSize(1.0); 1828 1829 if (be_control_look != NULL) 1830 return; 1831 1832 r.InsetBy(-1, -1); 1833 BeginLineArray(4); 1834 if (direction == ARROW_LEFT || direction == ARROW_RIGHT) { 1835 // horizontal 1836 if (doubleArrows && direction == ARROW_LEFT) { 1837 // draw in such a way that the arrows are 1838 // more visually separated 1839 AddLine(BPoint(r.left + 1, r.top), 1840 BPoint(r.right - 1, r.top), light); 1841 AddLine(BPoint(r.right, r.top), 1842 BPoint(r.right, r.bottom), darker); 1843 } else { 1844 AddLine(BPoint(r.left + 1, r.top), 1845 BPoint(r.right, r.top), light); 1846 AddLine(BPoint(r.right, r.top + 1), 1847 BPoint(r.right, r.bottom), dark); 1848 } 1849 AddLine(BPoint(r.left, r.bottom), 1850 BPoint(r.left, r.top), light); 1851 AddLine(BPoint(r.right - 1, r.bottom), 1852 BPoint(r.left + 1, r.bottom), dark); 1853 } else { 1854 // vertical 1855 if (doubleArrows && direction == ARROW_UP) { 1856 // draw in such a way that the arrows are 1857 // more visually separated 1858 AddLine(BPoint(r.left, r.bottom - 1), 1859 BPoint(r.left, r.top), light); 1860 AddLine(BPoint(r.right, r.bottom), 1861 BPoint(r.left, r.bottom), darker); 1862 } else { 1863 AddLine(BPoint(r.left, r.bottom), 1864 BPoint(r.left, r.top), light); 1865 AddLine(BPoint(r.right, r.bottom), 1866 BPoint(r.left + 1, r.bottom), dark); 1867 } 1868 AddLine(BPoint(r.left + 1, r.top), 1869 BPoint(r.right, r.top), light); 1870 AddLine(BPoint(r.right, r.top + 1), 1871 BPoint(r.right, r.bottom - 1), dark); 1872 } 1873 EndLineArray(); 1874 } 1875 1876 1877 BSize 1878 BScrollBar::_MinSize() const 1879 { 1880 BSize minSize; 1881 if (fOrientation == B_HORIZONTAL) { 1882 minSize.width = 2 * B_V_SCROLL_BAR_WIDTH 1883 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1884 minSize.height = B_H_SCROLL_BAR_HEIGHT; 1885 } else { 1886 minSize.width = B_V_SCROLL_BAR_WIDTH; 1887 minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT 1888 + 2 * fPrivateData->fScrollBarInfo.min_knob_size; 1889 } 1890 return minSize; 1891 } 1892 1893