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