1 /* 2 * Copyright (c) 2001-2006, 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",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 // MouseDown 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 // hit test for the thumb 506 if (fPrivateData->fThumbFrame.Contains(where)) { 507 fPrivateData->fButtonDown = THUMB; 508 fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; 509 Invalidate(fPrivateData->fThumbFrame); 510 return; 511 } 512 513 // hit test for arrows or empty area 514 float scrollValue = 0.0; 515 fPrivateData->fButtonDown = _ButtonFor(where); 516 switch (fPrivateData->fButtonDown) { 517 case ARROW1: 518 scrollValue = -fSmallStep; 519 break; 520 case ARROW2: 521 scrollValue = fSmallStep; 522 break; 523 case ARROW3: 524 scrollValue = -fSmallStep; 525 break; 526 case ARROW4: 527 scrollValue = fSmallStep; 528 break; 529 case NOARROW: 530 // we hit the empty area, figure out which side of the thumb 531 if (fOrientation == B_VERTICAL) { 532 if (where.y < fPrivateData->fThumbFrame.top) 533 scrollValue = -fLargeStep; 534 else 535 scrollValue = fLargeStep; 536 } else { 537 if (where.x < fPrivateData->fThumbFrame.left) 538 scrollValue = -fLargeStep; 539 else 540 scrollValue = fLargeStep; 541 } 542 _UpdateTargetValue(where); 543 break; 544 } 545 if (scrollValue != 0.0) { 546 SetValue(fValue + scrollValue); 547 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 548 549 // launch the repeat thread 550 if (fPrivateData->fRepeaterThread == -1) { 551 fPrivateData->fExitRepeater = false; 552 fPrivateData->fThumbInc = scrollValue; 553 fPrivateData->fDoRepeat = true; 554 fPrivateData->fRepeaterThread 555 = spawn_thread(fPrivateData->button_repeater_thread, 556 "scroll repeater", B_NORMAL_PRIORITY, fPrivateData); 557 resume_thread(fPrivateData->fRepeaterThread); 558 } 559 } 560 } 561 562 // MouseUp 563 void 564 BScrollBar::MouseUp(BPoint pt) 565 { 566 if (fPrivateData->fButtonDown == THUMB) 567 Invalidate(fPrivateData->fThumbFrame); 568 else 569 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 570 571 fPrivateData->fButtonDown = NOARROW; 572 fPrivateData->fExitRepeater = true; 573 fPrivateData->fDoRepeat = false; 574 } 575 576 // MouseMoved 577 void 578 BScrollBar::MouseMoved(BPoint where, uint32 transit, const BMessage* message) 579 { 580 if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) 581 return; 582 583 if (fPrivateData->fButtonDown != NOARROW) { 584 if (fPrivateData->fButtonDown == THUMB) { 585 SetValue(_ValueFor(where + fPrivateData->fClickOffset)); 586 } else { 587 // suspend the repeating if the mouse is not over the button 588 bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains(where); 589 if (fPrivateData->fDoRepeat != repeat) { 590 fPrivateData->fDoRepeat = repeat; 591 Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); 592 } 593 } 594 } else { 595 // update the value at which we want to stop repeating 596 if (fPrivateData->fDoRepeat) { 597 _UpdateTargetValue(where); 598 // we might have to turn arround 599 if ((fValue < fPrivateData->fStopValue && fPrivateData->fThumbInc < 0) || 600 (fValue > fPrivateData->fStopValue && fPrivateData->fThumbInc > 0)) 601 fPrivateData->fThumbInc = -fPrivateData->fThumbInc; 602 } 603 } 604 } 605 606 // DetachedFromWindow 607 void 608 BScrollBar::DetachedFromWindow() 609 { 610 BView::DetachedFromWindow(); 611 } 612 613 // Draw 614 void 615 BScrollBar::Draw(BRect updateRect) 616 { 617 BRect bounds = Bounds(); 618 619 rgb_color normal = ui_color(B_PANEL_BACKGROUND_COLOR); 620 621 // stroke a dark frame arround the entire scrollbar (independent of enabled state) 622 SetHighColor(tint_color(normal, B_DARKEN_2_TINT)); 623 StrokeRect(bounds); 624 bounds.InsetBy(1.0, 1.0); 625 626 bool enabled = fPrivateData->fEnabled && fMin < fMax && fProportion < 1.0 && fProportion >= 0.0; 627 628 rgb_color light, light1, dark, dark1, dark2, dark4; 629 if (enabled) { 630 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 631 light1 = tint_color(normal, B_LIGHTEN_1_TINT); 632 dark = tint_color(normal, B_DARKEN_3_TINT); 633 dark1 = tint_color(normal, B_DARKEN_1_TINT); 634 dark2 = tint_color(normal, B_DARKEN_2_TINT); 635 dark4 = tint_color(normal, B_DARKEN_4_TINT); 636 } else { 637 light = tint_color(normal, B_LIGHTEN_MAX_TINT); 638 light1 = normal; 639 dark = tint_color(normal, B_DARKEN_2_TINT); 640 dark1 = tint_color(normal, B_LIGHTEN_2_TINT); 641 dark2 = tint_color(normal, B_LIGHTEN_1_TINT); 642 dark4 = tint_color(normal, B_DARKEN_3_TINT); 643 } 644 645 SetDrawingMode(B_OP_OVER); 646 647 BRect thumbBG = bounds; 648 bool doubleArrows = _DoubleArrows(); 649 650 // Draw arrows 651 if (fOrientation == B_HORIZONTAL) { 652 BRect buttonFrame(bounds.left, bounds.top, bounds.left + bounds.Height(), bounds.bottom); 653 654 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 655 enabled, fPrivateData->fButtonDown == ARROW1); 656 657 if (doubleArrows) { 658 buttonFrame.OffsetBy(bounds.Height() + 1, 0.0); 659 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 660 enabled, fPrivateData->fButtonDown == ARROW2); 661 662 buttonFrame.OffsetTo(bounds.right - ((bounds.Height() * 2) + 1), bounds.top); 663 _DrawArrowButton(ARROW_LEFT, doubleArrows, buttonFrame, updateRect, 664 enabled, fPrivateData->fButtonDown == ARROW3); 665 666 thumbBG.left += bounds.Height() * 2 + 2; 667 thumbBG.right -= bounds.Height() * 2 + 2; 668 } else { 669 thumbBG.left += bounds.Height() + 1; 670 thumbBG.right -= bounds.Height() + 1; 671 } 672 673 buttonFrame.OffsetTo(bounds.right - bounds.Height(), bounds.top); 674 _DrawArrowButton(ARROW_RIGHT, doubleArrows, buttonFrame, updateRect, 675 enabled, fPrivateData->fButtonDown == ARROW4); 676 } else { 677 BRect buttonFrame(bounds.left, bounds.top, bounds.right, bounds.top + bounds.Width()); 678 679 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 680 enabled, fPrivateData->fButtonDown == ARROW1); 681 682 if (doubleArrows) { 683 buttonFrame.OffsetBy(0.0, bounds.Width() + 1); 684 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 685 enabled, fPrivateData->fButtonDown == ARROW2); 686 687 buttonFrame.OffsetTo(bounds.left, bounds.bottom - ((bounds.Width() * 2) + 1)); 688 _DrawArrowButton(ARROW_UP, doubleArrows, buttonFrame, updateRect, 689 enabled, fPrivateData->fButtonDown == ARROW3); 690 691 thumbBG.top += bounds.Width() * 2 + 2; 692 thumbBG.bottom -= bounds.Width() * 2 + 2; 693 } else { 694 thumbBG.top += bounds.Width() + 1; 695 thumbBG.bottom -= bounds.Width() + 1; 696 } 697 698 buttonFrame.OffsetTo(bounds.left, bounds.bottom - bounds.Width()); 699 _DrawArrowButton(ARROW_DOWN, doubleArrows, buttonFrame, updateRect, 700 enabled, fPrivateData->fButtonDown == ARROW4); 701 } 702 703 SetDrawingMode(B_OP_COPY); 704 705 // background for thumb area 706 BRect rect(fPrivateData->fThumbFrame); 707 708 // frame 709 if (fOrientation == B_HORIZONTAL) { 710 int32 totalLines = 0; 711 if (rect.left > thumbBG.left) 712 totalLines += 1; 713 if (rect.left > thumbBG.left + 1) 714 totalLines += 3; 715 if (rect.right < thumbBG.right - 1) 716 totalLines += 3; 717 if (rect.right < thumbBG.right) 718 totalLines += 1; 719 720 if (totalLines > 0) { 721 BeginLineArray(totalLines); 722 if (rect.left > thumbBG.left) { 723 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 724 BPoint(thumbBG.left, thumbBG.top), 725 rect.left > thumbBG.left + 1 ? dark4 : dark); 726 } 727 if (rect.left > thumbBG.left + 1) { 728 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 729 BPoint(thumbBG.left + 1, thumbBG.bottom), dark2); 730 AddLine(BPoint(thumbBG.left + 1, thumbBG.top), 731 BPoint(rect.left - 1, thumbBG.top), dark2); 732 AddLine(BPoint(rect.left - 1, thumbBG.bottom), 733 BPoint(thumbBG.left + 2, thumbBG.bottom), normal); 734 } 735 736 if (rect.right < thumbBG.right - 1) { 737 AddLine(BPoint(rect.right + 2, thumbBG.top + 1), 738 BPoint(rect.right + 2, thumbBG.bottom), dark2); 739 AddLine(BPoint(rect.right + 1, thumbBG.top), 740 BPoint(thumbBG.right, thumbBG.top), dark2); 741 AddLine(BPoint(thumbBG.right - 1, thumbBG.bottom), 742 BPoint(rect.right + 3, thumbBG.bottom), normal); 743 } 744 if (rect.right < thumbBG.right) { 745 AddLine(BPoint(thumbBG.right, thumbBG.top), 746 BPoint(thumbBG.right, thumbBG.bottom), dark); 747 } 748 EndLineArray(); 749 } 750 } else { 751 int32 totalLines = 0; 752 if (rect.top > thumbBG.top) 753 totalLines += 1; 754 if (rect.top > thumbBG.top + 1) 755 totalLines += 3; 756 if (rect.bottom < thumbBG.bottom - 1) 757 totalLines += 3; 758 if (rect.bottom < thumbBG.bottom) 759 totalLines += 1; 760 761 if (totalLines > 0) { 762 BeginLineArray(totalLines); 763 if (rect.top > thumbBG.top) { 764 AddLine(BPoint(thumbBG.left, thumbBG.top), 765 BPoint(thumbBG.right, thumbBG.top), 766 rect.top > thumbBG.top + 1 ? dark4 : dark); 767 } 768 if (rect.top > thumbBG.top + 1) { 769 AddLine(BPoint(thumbBG.left + 1, thumbBG.top + 1), 770 BPoint(thumbBG.right, thumbBG.top + 1), dark2); 771 AddLine(BPoint(thumbBG.left, rect.top - 1), 772 BPoint(thumbBG.left, thumbBG.top + 1), dark2); 773 AddLine(BPoint(thumbBG.right, rect.top - 1), 774 BPoint(thumbBG.right, thumbBG.top + 2), normal); 775 } 776 777 if (rect.bottom < thumbBG.bottom - 1) { 778 AddLine(BPoint(thumbBG.left + 1, rect.bottom + 2), 779 BPoint(thumbBG.right, rect.bottom + 2), dark2); 780 AddLine(BPoint(thumbBG.left, rect.bottom + 1), 781 BPoint(thumbBG.left, thumbBG.bottom - 1), dark2); 782 AddLine(BPoint(thumbBG.right, rect.bottom + 3), 783 BPoint(thumbBG.right, thumbBG.bottom - 1), normal); 784 } 785 if (rect.bottom < thumbBG.bottom) { 786 AddLine(BPoint(thumbBG.left, thumbBG.bottom), 787 BPoint(thumbBG.right, thumbBG.bottom), dark); 788 } 789 EndLineArray(); 790 } 791 } 792 793 SetHighColor(dark1); 794 795 // Draw scroll thumb 796 if (enabled) { 797 // fill and additional dark lines 798 thumbBG.InsetBy(1.0, 1.0); 799 if (fOrientation == B_HORIZONTAL) { 800 BRect leftOfThumb(thumbBG.left + 1, thumbBG.top, rect.left - 1, thumbBG.bottom); 801 if (leftOfThumb.IsValid()) 802 FillRect(leftOfThumb); 803 804 BRect rightOfThumb(rect.right + 3, thumbBG.top, thumbBG.right, thumbBG.bottom); 805 if (rightOfThumb.IsValid()) 806 FillRect(rightOfThumb); 807 808 // dark lines before and after thumb 809 if (rect.left > thumbBG.left) { 810 SetHighColor(dark); 811 StrokeLine(BPoint(rect.left - 1, rect.top), BPoint(rect.left - 1, rect.bottom)); 812 } 813 if (rect.right < thumbBG.right) { 814 SetHighColor(dark4); 815 StrokeLine(BPoint(rect.right + 1, rect.top), BPoint(rect.right + 1, rect.bottom)); 816 } 817 } else { 818 BRect topOfThumb(thumbBG.left, thumbBG.top + 1, thumbBG.right, rect.top - 1); 819 if (topOfThumb.IsValid()) 820 FillRect(topOfThumb); 821 822 BRect bottomOfThumb(thumbBG.left, rect.bottom + 3, thumbBG.right, thumbBG.bottom); 823 if (bottomOfThumb.IsValid()) 824 FillRect(bottomOfThumb); 825 826 // dark lines before and after thumb 827 if (rect.top > thumbBG.top) { 828 SetHighColor(dark); 829 StrokeLine(BPoint(rect.left, rect.top - 1), BPoint(rect.right, rect.top - 1)); 830 } 831 if (rect.bottom < thumbBG.bottom) { 832 SetHighColor(dark4); 833 StrokeLine(BPoint(rect.left, rect.bottom + 1), BPoint(rect.right, rect.bottom + 1)); 834 } 835 } 836 837 BeginLineArray(4); 838 AddLine(BPoint(rect.left, rect.bottom), 839 BPoint(rect.left, rect.top), light); 840 AddLine(BPoint(rect.left + 1, rect.top), 841 BPoint(rect.right, rect.top), light); 842 AddLine(BPoint(rect.right, rect.top + 1), 843 BPoint(rect.right, rect.bottom), dark1); 844 AddLine(BPoint(rect.right - 1, rect.bottom), 845 BPoint(rect.left + 1, rect.bottom), dark1); 846 EndLineArray(); 847 848 // fill 849 rect.InsetBy(1.0, 1.0); 850 /*if (fPrivateData->fButtonDown == THUMB) 851 SetHighColor(tint_color(normal, (B_NO_TINT + B_DARKEN_1_TINT) / 2)); 852 else*/ 853 SetHighColor(normal); 854 855 FillRect(rect); 856 857 // TODO: Add the other thumb styles - dots and lines 858 } else { 859 if (fMin >= fMax || fProportion >= 1.0 || fProportion < 0.0) { 860 // we cannot scroll at all 861 _DrawDisabledBackground(thumbBG, light, dark, dark1); 862 } else { 863 // we could scroll, but we're simply disabled 864 float bgTint = 1.06; 865 rgb_color bgLight = tint_color(light, bgTint * 3); 866 rgb_color bgShadow = tint_color(dark, bgTint); 867 rgb_color bgFill = tint_color(dark1, bgTint); 868 if (fOrientation == B_HORIZONTAL) { 869 // left of thumb 870 BRect besidesThumb(thumbBG); 871 besidesThumb.right = rect.left - 1; 872 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 873 // right of thumb 874 besidesThumb.left = rect.right + 1; 875 besidesThumb.right = thumbBG.right; 876 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 877 } else { 878 // above thumb 879 BRect besidesThumb(thumbBG); 880 besidesThumb.bottom = rect.top - 1; 881 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 882 // below thumb 883 besidesThumb.top = rect.bottom + 1; 884 besidesThumb.bottom = thumbBG.bottom; 885 _DrawDisabledBackground(besidesThumb, bgLight, bgShadow, bgFill); 886 } 887 // thumb bevel 888 BeginLineArray(4); 889 AddLine(BPoint(rect.left, rect.bottom), 890 BPoint(rect.left, rect.top), light); 891 AddLine(BPoint(rect.left + 1, rect.top), 892 BPoint(rect.right, rect.top), light); 893 AddLine(BPoint(rect.right, rect.top + 1), 894 BPoint(rect.right, rect.bottom), dark2); 895 AddLine(BPoint(rect.right - 1, rect.bottom), 896 BPoint(rect.left + 1, rect.bottom), dark2); 897 EndLineArray(); 898 // thumb fill 899 rect.InsetBy(1.0, 1.0); 900 SetHighColor(dark1); 901 FillRect(rect); 902 } 903 } 904 } 905 906 // FrameMoved 907 void 908 BScrollBar::FrameMoved(BPoint newPosition) 909 { 910 BView::FrameMoved(newPosition); 911 } 912 913 // FrameResized 914 void 915 BScrollBar::FrameResized(float new_width, float new_height) 916 { 917 _UpdateThumbFrame(); 918 } 919 920 // ResolveSpecifier 921 BHandler* 922 BScrollBar::ResolveSpecifier(BMessage *msg, int32 index, 923 BMessage *specifier, int32 form, const char *property) 924 { 925 return BView::ResolveSpecifier(msg, index, specifier, form, property); 926 } 927 928 // ResizeToPreferred 929 void 930 BScrollBar::ResizeToPreferred() 931 { 932 BView::ResizeToPreferred(); 933 } 934 935 // GetPreferredSize 936 void 937 BScrollBar::GetPreferredSize(float* _width, float* _height) 938 { 939 if (fOrientation == B_VERTICAL) { 940 if (_width) 941 *_width = B_V_SCROLL_BAR_WIDTH; 942 if (_height) 943 *_height = Bounds().Height(); 944 } else if (fOrientation == B_HORIZONTAL) { 945 if (_width) 946 *_width = Bounds().Width(); 947 if (_height) 948 *_height = B_H_SCROLL_BAR_HEIGHT; 949 } 950 } 951 952 // MakeFocus 953 void 954 BScrollBar::MakeFocus(bool state) 955 { 956 BView::MakeFocus(state); 957 } 958 959 // AllAttached 960 void 961 BScrollBar::AllAttached() 962 { 963 BView::AllAttached(); 964 } 965 966 // AllDetached 967 void 968 BScrollBar::AllDetached() 969 { 970 BView::AllDetached(); 971 } 972 973 // GetSupportedSuites 974 status_t 975 BScrollBar::GetSupportedSuites(BMessage *data) 976 { 977 return B_ERROR; 978 } 979 980 // Perform 981 status_t 982 BScrollBar::Perform(perform_code d, void *arg) 983 { 984 return BView::Perform(d, arg); 985 } 986 987 #if DISABLES_ON_WINDOW_DEACTIVATION 988 void 989 BScrollBar::WindowActivated(bool active) 990 { 991 fPrivateData->fEnabled = active; 992 Invalidate(); 993 } 994 #endif // DISABLES_ON_WINDOW_DEACTIVATION 995 996 void BScrollBar::_ReservedScrollBar1() {} 997 void BScrollBar::_ReservedScrollBar2() {} 998 void BScrollBar::_ReservedScrollBar3() {} 999 void BScrollBar::_ReservedScrollBar4() {} 1000 1001 // operator= 1002 BScrollBar & 1003 BScrollBar::operator=(const BScrollBar &) 1004 { 1005 return *this; 1006 } 1007 1008 // _DoubleArrows 1009 bool 1010 BScrollBar::_DoubleArrows() const 1011 { 1012 if (!fPrivateData->fScrollBarInfo.double_arrows) 1013 return false; 1014 1015 // if there is not enough room, switch to single arrows even though 1016 // double arrows is specified 1017 if (fOrientation == B_HORIZONTAL) 1018 return Bounds().Width() > (Bounds().Height() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1019 else 1020 return Bounds().Height() > (Bounds().Width() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2; 1021 } 1022 1023 // _UpdateThumbFrame 1024 void 1025 BScrollBar::_UpdateThumbFrame() 1026 { 1027 BRect bounds = Bounds(); 1028 bounds.InsetBy(1.0, 1.0); 1029 1030 BRect oldFrame = fPrivateData->fThumbFrame; 1031 fPrivateData->fThumbFrame = bounds; 1032 float minSize = fPrivateData->fScrollBarInfo.min_knob_size; 1033 float maxSize; 1034 float buttonSize; 1035 1036 // assume square buttons 1037 if (fOrientation == B_VERTICAL) { 1038 maxSize = bounds.Height(); 1039 buttonSize = bounds.Width() + 1.0; 1040 } else { 1041 maxSize = bounds.Width(); 1042 buttonSize = bounds.Height() + 1.0; 1043 } 1044 1045 if (_DoubleArrows()) { 1046 // subtract the size of four buttons 1047 maxSize -= buttonSize * 4; 1048 } else { 1049 // subtract the size of two buttons 1050 maxSize -= buttonSize * 2; 1051 } 1052 // visual adjustments (room for darker line between thumb and buttons) 1053 maxSize--; 1054 1055 float thumbSize = minSize; 1056 float proportion = fProportion; 1057 if (fMin == fMax || proportion > 1.0 || proportion < 0.0) 1058 proportion = 1.0; 1059 if (fPrivateData->fScrollBarInfo.proportional) 1060 thumbSize += (maxSize - minSize) * proportion; 1061 thumbSize = floorf(thumbSize + 0.5); 1062 thumbSize--; 1063 1064 // the thumb can be scrolled within the remaining area "maxSize - thumbSize" 1065 float offset = floorf(((fValue - fMin) / (fMax - fMin + 1.0)) * (maxSize - thumbSize)); 1066 1067 if (_DoubleArrows()) { 1068 offset += buttonSize * 2; 1069 } else { 1070 offset += buttonSize; 1071 } 1072 // visual adjustments (room for darker line between thumb and buttons) 1073 offset++; 1074 1075 if (fOrientation == B_VERTICAL) { 1076 fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top + thumbSize; 1077 fPrivateData->fThumbFrame.OffsetBy(0.0, offset); 1078 } else { 1079 fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left + thumbSize; 1080 fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); 1081 } 1082 1083 if (Window()) { 1084 BRect invalid = oldFrame.IsValid() ? oldFrame | fPrivateData->fThumbFrame : fPrivateData->fThumbFrame; 1085 // account for those two dark lines 1086 if (fOrientation == B_HORIZONTAL) 1087 invalid.InsetBy(-2.0, 0.0); 1088 else 1089 invalid.InsetBy(0.0, -2.0); 1090 Invalidate(invalid); 1091 } 1092 } 1093 1094 // _ValueFor 1095 float 1096 BScrollBar::_ValueFor(BPoint where) const 1097 { 1098 BRect bounds = Bounds(); 1099 bounds.InsetBy(1.0, 1.0); 1100 1101 float offset; 1102 float thumbSize; 1103 float maxSize; 1104 float buttonSize; 1105 1106 if (fOrientation == B_VERTICAL) { 1107 offset = where.y; 1108 thumbSize = fPrivateData->fThumbFrame.Height(); 1109 maxSize = bounds.Height(); 1110 buttonSize = bounds.Width() + 1.0; 1111 } else { 1112 offset = where.x; 1113 thumbSize = fPrivateData->fThumbFrame.Width(); 1114 maxSize = bounds.Width(); 1115 buttonSize = bounds.Height() + 1.0; 1116 } 1117 1118 if (_DoubleArrows()) { 1119 // subtract the size of four buttons 1120 maxSize -= buttonSize * 4; 1121 // convert point to inside of area between buttons 1122 offset -= buttonSize * 2; 1123 } else { 1124 // subtract the size of two buttons 1125 maxSize -= buttonSize * 2; 1126 // convert point to inside of area between buttons 1127 offset -= buttonSize; 1128 } 1129 // visual adjustments (room for darker line between thumb and buttons) 1130 maxSize--; 1131 offset++; 1132 1133 float value = fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0)); 1134 if (value >= 0.0) 1135 return floorf(value + 0.5); 1136 else 1137 return ceilf(value - 0.5); 1138 } 1139 1140 // _ButtonFor 1141 int32 1142 BScrollBar::_ButtonFor(BPoint where) const 1143 { 1144 BRect bounds = Bounds(); 1145 bounds.InsetBy(1.0, 1.0); 1146 1147 float buttonSize; 1148 if (fOrientation == B_VERTICAL) { 1149 buttonSize = bounds.Width() + 1.0; 1150 } else { 1151 buttonSize = bounds.Height() + 1.0; 1152 } 1153 1154 BRect rect(bounds.left, bounds.top, 1155 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1156 1157 if (fOrientation == B_VERTICAL) { 1158 if (rect.Contains(where)) 1159 return ARROW1; 1160 if (_DoubleArrows()) { 1161 rect.OffsetBy(0.0, buttonSize); 1162 if (rect.Contains(where)) 1163 return ARROW2; 1164 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); 1165 if (rect.Contains(where)) 1166 return ARROW3; 1167 } 1168 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); 1169 if (rect.Contains(where)) 1170 return ARROW4; 1171 } else { 1172 if (rect.Contains(where)) 1173 return ARROW1; 1174 if (_DoubleArrows()) { 1175 rect.OffsetBy(buttonSize, 0.0); 1176 if (rect.Contains(where)) 1177 return ARROW2; 1178 rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); 1179 if (rect.Contains(where)) 1180 return ARROW3; 1181 } 1182 rect.OffsetTo(bounds.right - buttonSize, bounds.top); 1183 if (rect.Contains(where)) 1184 return ARROW4; 1185 } 1186 1187 return NOARROW; 1188 } 1189 1190 // _ButtonRectFor 1191 BRect 1192 BScrollBar::_ButtonRectFor(int32 button) const 1193 { 1194 BRect bounds = Bounds(); 1195 bounds.InsetBy(1.0, 1.0); 1196 1197 float buttonSize; 1198 if (fOrientation == B_VERTICAL) { 1199 buttonSize = bounds.Width() + 1.0; 1200 } else { 1201 buttonSize = bounds.Height() + 1.0; 1202 } 1203 1204 BRect rect(bounds.left, bounds.top, 1205 bounds.left + buttonSize - 1.0, bounds.top + buttonSize - 1.0); 1206 1207 if (fOrientation == B_VERTICAL) { 1208 switch (button) { 1209 case ARROW1: 1210 break; 1211 case ARROW2: 1212 rect.OffsetBy(0.0, buttonSize); 1213 break; 1214 case ARROW3: 1215 rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); 1216 break; 1217 case ARROW4: 1218 rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); 1219 break; 1220 } 1221 } else { 1222 switch (button) { 1223 case ARROW1: 1224 break; 1225 case ARROW2: 1226 rect.OffsetBy(buttonSize, 0.0); 1227 break; 1228 case ARROW3: 1229 rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); 1230 break; 1231 case ARROW4: 1232 rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); 1233 break; 1234 } 1235 } 1236 1237 return rect; 1238 } 1239 1240 // _UpdateTargetValue 1241 void 1242 BScrollBar::_UpdateTargetValue(BPoint where) 1243 { 1244 if (fOrientation == B_VERTICAL) 1245 fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y - fPrivateData->fThumbFrame.Height() / 2.0)); 1246 else 1247 fPrivateData->fStopValue = _ValueFor(BPoint(where.x - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); 1248 } 1249 1250 // _UpdateArrowButtons 1251 void 1252 BScrollBar::_UpdateArrowButtons() 1253 { 1254 bool upEnabled = fValue > fMin; 1255 if (fPrivateData->fUpArrowsEnabled != upEnabled) { 1256 fPrivateData->fUpArrowsEnabled = upEnabled; 1257 Invalidate(_ButtonRectFor(ARROW1)); 1258 if (_DoubleArrows()) 1259 Invalidate(_ButtonRectFor(ARROW3)); 1260 } 1261 1262 bool downEnabled = fValue < fMax; 1263 if (fPrivateData->fDownArrowsEnabled != downEnabled) { 1264 fPrivateData->fDownArrowsEnabled = downEnabled; 1265 Invalidate(_ButtonRectFor(ARROW4)); 1266 if (_DoubleArrows()) 1267 Invalidate(_ButtonRectFor(ARROW2)); 1268 } 1269 } 1270 1271 // control_scrollbar 1272 status_t 1273 control_scrollbar(scroll_bar_info *info, BScrollBar *bar) 1274 { 1275 if (!bar || !info) 1276 return B_BAD_VALUE; 1277 1278 if (bar->fPrivateData->fScrollBarInfo.double_arrows != info->double_arrows) { 1279 bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; 1280 1281 int8 multiplier = (info->double_arrows) ? 1 : -1; 1282 1283 if (bar->fOrientation == B_VERTICAL) 1284 bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier * B_H_SCROLL_BAR_HEIGHT); 1285 else 1286 bar->fPrivateData->fThumbFrame.OffsetBy(multiplier * B_V_SCROLL_BAR_WIDTH, 0); 1287 } 1288 1289 bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; 1290 1291 // TODO: Figure out how proportional relates to the size of the thumb 1292 1293 // TODO: Add redraw code to reflect the changes 1294 1295 if (info->knob >= 0 && info->knob <= 2) 1296 bar->fPrivateData->fScrollBarInfo.knob = info->knob; 1297 else 1298 return B_BAD_VALUE; 1299 1300 if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) 1301 bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; 1302 else 1303 return B_BAD_VALUE; 1304 1305 return B_OK; 1306 } 1307 1308 // _DrawDisabledBackground 1309 void 1310 BScrollBar::_DrawDisabledBackground(BRect area, 1311 const rgb_color& light, 1312 const rgb_color& dark, 1313 const rgb_color& fill) 1314 { 1315 if (!area.IsValid()) 1316 return; 1317 1318 if (fOrientation == B_VERTICAL) { 1319 int32 height = area.IntegerHeight(); 1320 if (height == 0) { 1321 SetHighColor(dark); 1322 StrokeLine(area.LeftTop(), area.RightTop()); 1323 } else if (height == 1) { 1324 SetHighColor(dark); 1325 FillRect(area); 1326 } else { 1327 BeginLineArray(4); 1328 AddLine(BPoint(area.left, area.top), 1329 BPoint(area.right, area.top), dark); 1330 AddLine(BPoint(area.left, area.bottom - 1), 1331 BPoint(area.left, area.top + 1), light); 1332 AddLine(BPoint(area.left + 1, area.top + 1), 1333 BPoint(area.right, area.top + 1), light); 1334 AddLine(BPoint(area.right, area.bottom), 1335 BPoint(area.left, area.bottom), dark); 1336 EndLineArray(); 1337 area.left++; 1338 area.top += 2; 1339 area.bottom--; 1340 if (area.IsValid()) { 1341 SetHighColor(fill); 1342 FillRect(area); 1343 } 1344 } 1345 } else { 1346 int32 width = area.IntegerWidth(); 1347 if (width == 0) { 1348 SetHighColor(dark); 1349 StrokeLine(area.LeftBottom(), area.LeftTop()); 1350 } else if (width == 1) { 1351 SetHighColor(dark); 1352 FillRect(area); 1353 } else { 1354 BeginLineArray(4); 1355 AddLine(BPoint(area.left, area.bottom), 1356 BPoint(area.left, area.top), dark); 1357 AddLine(BPoint(area.left + 1, area.bottom), 1358 BPoint(area.left + 1, area.top + 1), light); 1359 AddLine(BPoint(area.left + 1, area.top), 1360 BPoint(area.right - 1, area.top), light); 1361 AddLine(BPoint(area.right, area.top), 1362 BPoint(area.right, area.bottom), dark); 1363 EndLineArray(); 1364 area.left += 2; 1365 area.top ++; 1366 area.right--; 1367 if (area.IsValid()) { 1368 SetHighColor(fill); 1369 FillRect(area); 1370 } 1371 } 1372 } 1373 } 1374 1375 // _DrawArrowButton 1376 void 1377 BScrollBar::_DrawArrowButton(int32 direction, bool doubleArrows, BRect r, 1378 const BRect& updateRect, bool enabled, bool down) 1379 { 1380 if (!updateRect.Intersects(r)) 1381 return; 1382 1383 rgb_color c = ui_color(B_PANEL_BACKGROUND_COLOR); 1384 rgb_color light, dark, darker, normal, arrow; 1385 1386 if (down && fPrivateData->fDoRepeat) { 1387 light = tint_color(c, (B_DARKEN_1_TINT + B_DARKEN_2_TINT) / 2.0); 1388 dark = darker = c; 1389 normal = tint_color(c, B_DARKEN_1_TINT); 1390 arrow = tint_color(c, B_DARKEN_MAX_TINT); 1391 1392 } else { 1393 // Add a usability perk - disable buttons if they would not do anything - 1394 // like a left arrow if the value==fMin 1395 // NOTE: disabled because of too much visual noise/distraction 1396 /* if ((direction == ARROW_LEFT || direction == ARROW_UP) && (fValue == fMin) ) 1397 use_enabled_colors = false; 1398 else if ((direction == ARROW_RIGHT || direction == ARROW_DOWN) && (fValue == fMax) ) 1399 use_enabled_colors = false;*/ 1400 1401 if (enabled) { 1402 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1403 dark = tint_color(c, B_DARKEN_1_TINT); 1404 darker = tint_color(c, B_DARKEN_2_TINT); 1405 normal = c; 1406 arrow = tint_color(c, (B_DARKEN_MAX_TINT + B_DARKEN_4_TINT) / 2.0); 1407 } else { 1408 light = tint_color(c, B_LIGHTEN_MAX_TINT); 1409 dark = tint_color(c, B_LIGHTEN_1_TINT); 1410 darker = tint_color(c, B_DARKEN_2_TINT); 1411 normal = tint_color(c, B_LIGHTEN_2_TINT); 1412 arrow = tint_color(c, B_DARKEN_1_TINT); 1413 } 1414 } 1415 1416 BPoint tri1, tri2, tri3; 1417 r.InsetBy(4, 4); 1418 1419 switch (direction) { 1420 case ARROW_LEFT: 1421 tri1.Set(r.right, r.top); 1422 tri2.Set(r.left + 1, (r.top + r.bottom + 1) /2 ); 1423 tri3.Set(r.right, r.bottom + 1); 1424 break; 1425 case ARROW_RIGHT: 1426 tri1.Set(r.left, r.bottom + 1); 1427 tri2.Set(r.right - 1, (r.top + r.bottom + 1) / 2); 1428 tri3.Set(r.left, r.top); 1429 break; 1430 case ARROW_UP: 1431 tri1.Set(r.left, r.bottom); 1432 tri2.Set((r.left + r.right + 1) / 2, r.top + 1); 1433 tri3.Set(r.right + 1, r.bottom); 1434 break; 1435 default: 1436 tri1.Set(r.left, r.top); 1437 tri2.Set((r.left + r.right + 1) / 2, r.bottom - 1); 1438 tri3.Set(r.right + 1, r.top); 1439 break; 1440 } 1441 // offset triangle if down 1442 if (down && fPrivateData->fDoRepeat) { 1443 BPoint offset(1.0, 1.0); 1444 tri1 = tri1 + offset; 1445 tri2 = tri2 + offset; 1446 tri3 = tri3 + offset; 1447 } 1448 1449 r.InsetBy(-3, -3); 1450 SetHighColor(normal); 1451 FillRect(r); 1452 1453 BShape arrowShape; 1454 arrowShape.MoveTo(tri1); 1455 arrowShape.LineTo(tri2); 1456 arrowShape.LineTo(tri3); 1457 1458 SetHighColor(arrow); 1459 SetPenSize(2.0); 1460 StrokeShape(&arrowShape); 1461 SetPenSize(1.0); 1462 1463 r.InsetBy(-1, -1); 1464 BeginLineArray(4); 1465 if (direction == ARROW_LEFT || direction == ARROW_RIGHT) { 1466 // horizontal 1467 if (doubleArrows && direction == ARROW_LEFT) { 1468 // draw in such a way that the arrows are 1469 // more visually separated 1470 AddLine(BPoint(r.left + 1, r.top), 1471 BPoint(r.right - 1, r.top), light); 1472 AddLine(BPoint(r.right, r.top), 1473 BPoint(r.right, r.bottom), darker); 1474 } else { 1475 AddLine(BPoint(r.left + 1, r.top), 1476 BPoint(r.right, r.top), light); 1477 AddLine(BPoint(r.right, r.top + 1), 1478 BPoint(r.right, r.bottom), dark); 1479 } 1480 AddLine(BPoint(r.left, r.bottom), 1481 BPoint(r.left, r.top), light); 1482 AddLine(BPoint(r.right - 1, r.bottom), 1483 BPoint(r.left + 1, r.bottom), dark); 1484 } else { 1485 // vertical 1486 if (doubleArrows && direction == ARROW_UP) { 1487 // draw in such a way that the arrows are 1488 // more visually separated 1489 AddLine(BPoint(r.left, r.bottom - 1), 1490 BPoint(r.left, r.top), light); 1491 AddLine(BPoint(r.right, r.bottom), 1492 BPoint(r.left, r.bottom), darker); 1493 } else { 1494 AddLine(BPoint(r.left, r.bottom), 1495 BPoint(r.left, r.top), light); 1496 AddLine(BPoint(r.right, r.bottom), 1497 BPoint(r.left + 1, r.bottom), dark); 1498 } 1499 AddLine(BPoint(r.left + 1, r.top), 1500 BPoint(r.right, r.top), light); 1501 AddLine(BPoint(r.right, r.top + 1), 1502 BPoint(r.right, r.bottom - 1), dark); 1503 } 1504 EndLineArray(); 1505 } 1506 1507