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