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