2 * Copyright 2001-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
6 * Stephan Aßmus, superstippi@gmx.de
7 * Stefano Ceccherini, burton666@libero.it
8 * DarkWyrm, bpmagic@columbus.rr.com
9 * Marc Flerackers, mflerackers@androme.be
10 * John Scipione, jscipione@gmail.com
14 #include <ScrollBar.h>
21 #include <ControlLook.h>
22 #include <LayoutUtils.h>
28 #include <binary_compatibility/Interface.h>
31 //#define TRACE_SCROLLBAR
32 #ifdef TRACE_SCROLLBAR
33 # define TRACE(x...) printf(x)
48 #define SBC_SCROLLBYVALUE 0
49 #define SBC_SETDOUBLE 1
50 #define SBC_SETPROPORTIONAL 2
51 #define SBC_SETSTYLE 3
53 // Quick constants for determining which arrow is down and are defined with
54 // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of
55 // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up
56 // and ARROW4 points right/down.
65 static const bigtime_t kRepeatDelay
= 300000;
68 // Because the R5 version kept a lot of data on server-side, we need to kludge
69 // our way into binary compatibility
70 class BScrollBar::Private
{
72 Private(BScrollBar
* scrollBar
)
74 fScrollBar(scrollBar
),
79 fThumbFrame(0.0, 0.0, -1.0, -1.0),
81 fClickOffset(0.0, 0.0),
84 fUpArrowsEnabled(true),
85 fDownArrowsEnabled(true),
86 fBorderHighlighted(false),
90 fScrollBarInfo
.proportional
= true;
91 fScrollBarInfo
.double_arrows
= true;
92 fScrollBarInfo
.knob
= 0;
93 fScrollBarInfo
.min_knob_size
= 15;
95 get_scroll_bar_info(&fScrollBarInfo
);
101 if (fRepeaterThread
>= 0) {
103 fExitRepeater
= true;
104 wait_for_thread(fRepeaterThread
, &dummy
);
108 void DrawScrollBarButton(BScrollBar
* owner
, arrow_direction direction
,
109 BRect frame
, bool down
= false);
111 static int32
button_repeater_thread(void* data
);
113 int32
ButtonRepeaterThread();
115 BScrollBar
* fScrollBar
;
118 // TODO: This should be a static, initialized by
119 // _init_interface_kit() at application startup-time,
120 // like BMenu::sMenuInfo
121 scroll_bar_info fScrollBarInfo
;
123 thread_id fRepeaterThread
;
124 volatile bool fExitRepeater
;
125 bigtime_t fRepeaterDelay
;
128 volatile bool fDoRepeat
;
134 bool fUpArrowsEnabled
;
135 bool fDownArrowsEnabled
;
137 bool fBorderHighlighted
;
143 // This thread is spawned when a button is initially pushed and repeatedly scrolls
144 // the scrollbar by a little bit after a short delay
146 BScrollBar::Private::button_repeater_thread(void* data
)
148 BScrollBar::Private
* privateData
= (BScrollBar::Private
*)data
;
149 return privateData
->ButtonRepeaterThread();
154 BScrollBar::Private::ButtonRepeaterThread()
156 // Wait a bit before auto scrolling starts. As long as the user releases
157 // and presses the button again while the repeat delay has not yet
158 // triggered, the value is pushed into the future, so we need to loop such
159 // that repeating starts at exactly the correct delay after the last
161 while (fRepeaterDelay
> system_time() && !fExitRepeater
)
162 snooze_until(fRepeaterDelay
, B_SYSTEM_TIMEBASE
);
165 while (!fExitRepeater
) {
166 if (fScrollBar
->LockLooper()) {
168 float value
= fScrollBar
->Value() + fThumbInc
;
169 if (fButtonDown
== NOARROW
) {
170 // in this case we want to stop when we're under the mouse
171 if (fThumbInc
> 0.0 && value
<= fStopValue
)
172 fScrollBar
->SetValue(value
);
173 if (fThumbInc
< 0.0 && value
>= fStopValue
)
174 fScrollBar
->SetValue(value
);
176 fScrollBar
->SetValue(value
);
179 fScrollBar
->UnlockLooper();
185 // tell scrollbar we're gone
186 if (fScrollBar
->LockLooper()) {
187 fRepeaterThread
= -1;
188 fScrollBar
->UnlockLooper();
195 // #pragma mark - BScrollBar
198 BScrollBar::BScrollBar(BRect frame
, const char* name
, BView
* target
,
199 float min
, float max
, orientation direction
)
201 BView(frame
, name
, B_FOLLOW_NONE
,
202 B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS
),
210 fOrientation(direction
)
212 SetViewColor(B_TRANSPARENT_COLOR
);
214 fPrivateData
= new BScrollBar::Private(this);
217 SetEventMask(B_NO_POINTER_HISTORY
);
220 _UpdateArrowButtons();
222 SetResizingMode(direction
== B_VERTICAL
223 ? B_FOLLOW_TOP_BOTTOM
| B_FOLLOW_RIGHT
224 : B_FOLLOW_LEFT_RIGHT
| B_FOLLOW_BOTTOM
);
228 BScrollBar::BScrollBar(const char* name
, BView
* target
,
229 float min
, float max
, orientation direction
)
231 BView(name
, B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS
),
239 fOrientation(direction
)
241 SetViewColor(B_TRANSPARENT_COLOR
);
243 fPrivateData
= new BScrollBar::Private(this);
246 SetEventMask(B_NO_POINTER_HISTORY
);
249 _UpdateArrowButtons();
253 BScrollBar::BScrollBar(BMessage
* data
)
258 fPrivateData
= new BScrollBar::Private(this);
260 // TODO: Does the BeOS implementation try to find the target
261 // by name again? Does it archive the name at all?
262 if (data
->FindFloat("_range", 0, &fMin
) < B_OK
)
265 if (data
->FindFloat("_range", 1, &fMax
) < B_OK
)
268 if (data
->FindFloat("_steps", 0, &fSmallStep
) < B_OK
)
271 if (data
->FindFloat("_steps", 1, &fLargeStep
) < B_OK
)
274 if (data
->FindFloat("_val", &fValue
) < B_OK
)
278 if (data
->FindInt32("_orient", &orientation
) < B_OK
) {
279 fOrientation
= B_VERTICAL
;
280 if ((Flags() & B_SUPPORTS_LAYOUT
) == 0) {
282 SetResizingMode(fOrientation
== B_VERTICAL
283 ? B_FOLLOW_TOP_BOTTOM
| B_FOLLOW_RIGHT
284 : B_FOLLOW_LEFT_RIGHT
| B_FOLLOW_BOTTOM
);
287 fOrientation
= (enum orientation
)orientation
;
289 if (data
->FindFloat("_prop", &fProportion
) < B_OK
)
293 _UpdateArrowButtons();
297 BScrollBar::~BScrollBar()
299 SetTarget((BView
*)NULL
);
305 BScrollBar::Instantiate(BMessage
* data
)
307 if (validate_instantiation(data
, "BScrollBar"))
308 return new BScrollBar(data
);
314 BScrollBar::Archive(BMessage
* data
, bool deep
) const
316 status_t err
= BView::Archive(data
, deep
);
320 err
= data
->AddFloat("_range", fMin
);
324 err
= data
->AddFloat("_range", fMax
);
328 err
= data
->AddFloat("_steps", fSmallStep
);
332 err
= data
->AddFloat("_steps", fLargeStep
);
336 err
= data
->AddFloat("_val", fValue
);
340 err
= data
->AddInt32("_orient", (int32
)fOrientation
);
344 err
= data
->AddFloat("_prop", fProportion
);
351 BScrollBar::AllAttached()
353 BView::AllAttached();
358 BScrollBar::AllDetached()
360 BView::AllDetached();
365 BScrollBar::AttachedToWindow()
367 BView::AttachedToWindow();
372 BScrollBar::DetachedFromWindow()
374 BView::DetachedFromWindow();
379 BScrollBar::Draw(BRect updateRect
)
381 BRect bounds
= Bounds();
383 rgb_color normal
= ui_color(B_PANEL_BACKGROUND_COLOR
);
385 // stroke a dark frame around the entire scrollbar
386 // (independent of enabled state)
387 // take care of border highlighting (scroll target is focus view)
388 SetHighColor(tint_color(normal
, B_DARKEN_2_TINT
));
389 if (fPrivateData
->fBorderHighlighted
&& fPrivateData
->fEnabled
) {
390 rgb_color borderColor
= HighColor();
391 rgb_color highlightColor
= ui_color(B_KEYBOARD_NAVIGATION_COLOR
);
393 AddLine(BPoint(bounds
.left
+ 1, bounds
.bottom
),
394 BPoint(bounds
.right
, bounds
.bottom
), borderColor
);
395 AddLine(BPoint(bounds
.right
, bounds
.top
+ 1),
396 BPoint(bounds
.right
, bounds
.bottom
- 1), borderColor
);
397 if (fOrientation
== B_HORIZONTAL
) {
398 AddLine(BPoint(bounds
.left
, bounds
.top
+ 1),
399 BPoint(bounds
.left
, bounds
.bottom
), borderColor
);
401 AddLine(BPoint(bounds
.left
, bounds
.top
),
402 BPoint(bounds
.left
, bounds
.bottom
), highlightColor
);
404 if (fOrientation
== B_HORIZONTAL
) {
405 AddLine(BPoint(bounds
.left
, bounds
.top
),
406 BPoint(bounds
.right
, bounds
.top
), highlightColor
);
408 AddLine(BPoint(bounds
.left
+ 1, bounds
.top
),
409 BPoint(bounds
.right
, bounds
.top
), borderColor
);
415 bounds
.InsetBy(1.0f
, 1.0f
);
417 bool enabled
= fPrivateData
->fEnabled
&& fMin
< fMax
418 && fProportion
< 1.0f
&& fProportion
>= 0.0f
;
420 rgb_color light
, dark
, dark1
, dark2
;
422 light
= tint_color(normal
, B_LIGHTEN_MAX_TINT
);
423 dark
= tint_color(normal
, B_DARKEN_3_TINT
);
424 dark1
= tint_color(normal
, B_DARKEN_1_TINT
);
425 dark2
= tint_color(normal
, B_DARKEN_2_TINT
);
427 light
= tint_color(normal
, B_LIGHTEN_MAX_TINT
);
428 dark
= tint_color(normal
, B_DARKEN_2_TINT
);
429 dark1
= tint_color(normal
, B_LIGHTEN_2_TINT
);
430 dark2
= tint_color(normal
, B_LIGHTEN_1_TINT
);
433 SetDrawingMode(B_OP_OVER
);
435 BRect thumbBG
= bounds
;
436 bool doubleArrows
= _DoubleArrows();
439 if (fOrientation
== B_HORIZONTAL
) {
440 BRect
buttonFrame(bounds
.left
, bounds
.top
,
441 bounds
.left
+ bounds
.Height(), bounds
.bottom
);
443 _DrawArrowButton(ARROW_LEFT
, doubleArrows
, buttonFrame
, updateRect
,
444 enabled
, fPrivateData
->fButtonDown
== ARROW1
);
447 buttonFrame
.OffsetBy(bounds
.Height() + 1, 0.0f
);
448 _DrawArrowButton(ARROW_RIGHT
, doubleArrows
, buttonFrame
, updateRect
,
449 enabled
, fPrivateData
->fButtonDown
== ARROW2
);
451 buttonFrame
.OffsetTo(bounds
.right
- ((bounds
.Height() * 2) + 1),
453 _DrawArrowButton(ARROW_LEFT
, doubleArrows
, buttonFrame
, updateRect
,
454 enabled
, fPrivateData
->fButtonDown
== ARROW3
);
456 thumbBG
.left
+= bounds
.Height() * 2 + 2;
457 thumbBG
.right
-= bounds
.Height() * 2 + 2;
459 thumbBG
.left
+= bounds
.Height() + 1;
460 thumbBG
.right
-= bounds
.Height() + 1;
463 buttonFrame
.OffsetTo(bounds
.right
- bounds
.Height(), bounds
.top
);
464 _DrawArrowButton(ARROW_RIGHT
, doubleArrows
, buttonFrame
, updateRect
,
465 enabled
, fPrivateData
->fButtonDown
== ARROW4
);
467 BRect
buttonFrame(bounds
.left
, bounds
.top
, bounds
.right
,
468 bounds
.top
+ bounds
.Width());
470 _DrawArrowButton(ARROW_UP
, doubleArrows
, buttonFrame
, updateRect
,
471 enabled
, fPrivateData
->fButtonDown
== ARROW1
);
474 buttonFrame
.OffsetBy(0.0f
, bounds
.Width() + 1);
475 _DrawArrowButton(ARROW_DOWN
, doubleArrows
, buttonFrame
, updateRect
,
476 enabled
, fPrivateData
->fButtonDown
== ARROW2
);
478 buttonFrame
.OffsetTo(bounds
.left
, bounds
.bottom
479 - ((bounds
.Width() * 2) + 1));
480 _DrawArrowButton(ARROW_UP
, doubleArrows
, buttonFrame
, updateRect
,
481 enabled
, fPrivateData
->fButtonDown
== ARROW3
);
483 thumbBG
.top
+= bounds
.Width() * 2 + 2;
484 thumbBG
.bottom
-= bounds
.Width() * 2 + 2;
486 thumbBG
.top
+= bounds
.Width() + 1;
487 thumbBG
.bottom
-= bounds
.Width() + 1;
490 buttonFrame
.OffsetTo(bounds
.left
, bounds
.bottom
- bounds
.Width());
491 _DrawArrowButton(ARROW_DOWN
, doubleArrows
, buttonFrame
, updateRect
,
492 enabled
, fPrivateData
->fButtonDown
== ARROW4
);
495 SetDrawingMode(B_OP_COPY
);
497 // background for thumb area
498 BRect
rect(fPrivateData
->fThumbFrame
);
504 flags
|= BControlLook::B_DISABLED
;
506 // fill background besides the thumb
507 if (fOrientation
== B_HORIZONTAL
) {
508 BRect
leftOfThumb(thumbBG
.left
, thumbBG
.top
, rect
.left
- 1,
510 BRect
rightOfThumb(rect
.right
+ 1, thumbBG
.top
, thumbBG
.right
,
513 be_control_look
->DrawScrollBarBackground(this, leftOfThumb
,
514 rightOfThumb
, updateRect
, normal
, flags
, fOrientation
);
516 BRect
topOfThumb(thumbBG
.left
, thumbBG
.top
,
517 thumbBG
.right
, rect
.top
- 1);
519 BRect
bottomOfThumb(thumbBG
.left
, rect
.bottom
+ 1,
520 thumbBG
.right
, thumbBG
.bottom
);
522 be_control_look
->DrawScrollBarBackground(this, topOfThumb
,
523 bottomOfThumb
, updateRect
, normal
, flags
, fOrientation
);
526 rgb_color thumbColor
= ui_color(B_SCROLL_BAR_THUMB_COLOR
);
530 // fill the clickable surface of the thumb
531 be_control_look
->DrawButtonBackground(this, rect
, updateRect
,
532 thumbColor
, 0, BControlLook::B_ALL_BORDERS
, fOrientation
);
533 // TODO: Add the other thumb styles - dots and lines
535 if (fMin
>= fMax
|| fProportion
>= 1.0f
|| fProportion
< 0.0f
) {
536 // we cannot scroll at all
537 _DrawDisabledBackground(thumbBG
, light
, dark
, dark1
);
539 // we could scroll, but we're simply disabled
541 rgb_color bgLight
= tint_color(light
, bgTint
* 3);
542 rgb_color bgShadow
= tint_color(dark
, bgTint
);
543 rgb_color bgFill
= tint_color(dark1
, bgTint
);
544 if (fOrientation
== B_HORIZONTAL
) {
546 BRect
besidesThumb(thumbBG
);
547 besidesThumb
.right
= rect
.left
- 1;
548 _DrawDisabledBackground(besidesThumb
, bgLight
, bgShadow
, bgFill
);
550 besidesThumb
.left
= rect
.right
+ 1;
551 besidesThumb
.right
= thumbBG
.right
;
552 _DrawDisabledBackground(besidesThumb
, bgLight
, bgShadow
, bgFill
);
555 BRect
besidesThumb(thumbBG
);
556 besidesThumb
.bottom
= rect
.top
- 1;
557 _DrawDisabledBackground(besidesThumb
, bgLight
, bgShadow
, bgFill
);
559 besidesThumb
.top
= rect
.bottom
+ 1;
560 besidesThumb
.bottom
= thumbBG
.bottom
;
561 _DrawDisabledBackground(besidesThumb
, bgLight
, bgShadow
, bgFill
);
565 AddLine(BPoint(rect
.left
, rect
.bottom
),
566 BPoint(rect
.left
, rect
.top
), light
);
567 AddLine(BPoint(rect
.left
+ 1, rect
.top
),
568 BPoint(rect
.right
, rect
.top
), light
);
569 AddLine(BPoint(rect
.right
, rect
.top
+ 1),
570 BPoint(rect
.right
, rect
.bottom
), dark2
);
571 AddLine(BPoint(rect
.right
- 1, rect
.bottom
),
572 BPoint(rect
.left
+ 1, rect
.bottom
), dark2
);
575 rect
.InsetBy(1.0, 1.0);
584 BScrollBar::FrameMoved(BPoint newPosition
)
586 BView::FrameMoved(newPosition
);
591 BScrollBar::FrameResized(float newWidth
, float newHeight
)
598 BScrollBar::MessageReceived(BMessage
* message
)
600 switch(message
->what
) {
601 case B_VALUE_CHANGED
:
604 if (message
->FindInt32("value", &value
) == B_OK
)
610 case B_MOUSE_WHEEL_CHANGED
:
612 // Must handle this here since BView checks for the existence of
613 // scrollbars, which a scrollbar itself does not have
616 message
->FindFloat("be:wheel_delta_x", &deltaX
);
617 message
->FindFloat("be:wheel_delta_y", &deltaY
);
619 if (deltaX
== 0.0f
&& deltaY
== 0.0f
)
622 if (deltaX
!= 0.0f
&& deltaY
== 0.0f
)
625 ScrollWithMouseWheelDelta(this, deltaY
);
629 BView::MessageReceived(message
);
635 BScrollBar::MouseDown(BPoint where
)
637 if (!fPrivateData
->fEnabled
|| fMin
== fMax
)
640 SetMouseEventMask(B_POINTER_EVENTS
, B_LOCK_WINDOW_FOCUS
);
643 if (Looper() == NULL
|| Looper()->CurrentMessage() == NULL
644 || Looper()->CurrentMessage()->FindInt32("buttons", &buttons
) != B_OK
) {
645 buttons
= B_PRIMARY_MOUSE_BUTTON
;
648 if (buttons
& B_SECONDARY_MOUSE_BUTTON
) {
649 // special absolute scrolling: move thumb to where we clicked
650 fPrivateData
->fButtonDown
= THUMB
;
651 fPrivateData
->fClickOffset
= fPrivateData
->fThumbFrame
.LeftTop() - where
;
652 if (Orientation() == B_HORIZONTAL
)
653 fPrivateData
->fClickOffset
.x
= -fPrivateData
->fThumbFrame
.Width() / 2;
655 fPrivateData
->fClickOffset
.y
= -fPrivateData
->fThumbFrame
.Height() / 2;
657 SetValue(_ValueFor(where
+ fPrivateData
->fClickOffset
));
661 // hit test for the thumb
662 if (fPrivateData
->fThumbFrame
.Contains(where
)) {
663 fPrivateData
->fButtonDown
= THUMB
;
664 fPrivateData
->fClickOffset
= fPrivateData
->fThumbFrame
.LeftTop() - where
;
665 Invalidate(fPrivateData
->fThumbFrame
);
669 // hit test for arrows or empty area
670 float scrollValue
= 0.0;
672 // pressing the shift key scrolls faster
674 = (modifiers() & B_SHIFT_KEY
) != 0 ? fLargeStep
: fSmallStep
;
676 fPrivateData
->fButtonDown
= _ButtonFor(where
);
677 switch (fPrivateData
->fButtonDown
) {
679 scrollValue
= -buttonStepSize
;
683 scrollValue
= buttonStepSize
;
687 scrollValue
= -buttonStepSize
;
691 scrollValue
= buttonStepSize
;
695 // we hit the empty area, figure out which side of the thumb
696 if (fOrientation
== B_VERTICAL
) {
697 if (where
.y
< fPrivateData
->fThumbFrame
.top
)
698 scrollValue
= -fLargeStep
;
700 scrollValue
= fLargeStep
;
702 if (where
.x
< fPrivateData
->fThumbFrame
.left
)
703 scrollValue
= -fLargeStep
;
705 scrollValue
= fLargeStep
;
707 _UpdateTargetValue(where
);
710 if (scrollValue
!= 0.0) {
711 SetValue(fValue
+ scrollValue
);
712 Invalidate(_ButtonRectFor(fPrivateData
->fButtonDown
));
714 // launch the repeat thread
715 if (fPrivateData
->fRepeaterThread
== -1) {
716 fPrivateData
->fExitRepeater
= false;
717 fPrivateData
->fRepeaterDelay
= system_time() + kRepeatDelay
;
718 fPrivateData
->fThumbInc
= scrollValue
;
719 fPrivateData
->fDoRepeat
= true;
720 fPrivateData
->fRepeaterThread
= spawn_thread(
721 fPrivateData
->button_repeater_thread
, "scroll repeater",
722 B_NORMAL_PRIORITY
, fPrivateData
);
723 resume_thread(fPrivateData
->fRepeaterThread
);
725 fPrivateData
->fExitRepeater
= false;
726 fPrivateData
->fRepeaterDelay
= system_time() + kRepeatDelay
;
727 fPrivateData
->fDoRepeat
= true;
734 BScrollBar::MouseMoved(BPoint where
, uint32 code
, const BMessage
* dragMessage
)
736 if (!fPrivateData
->fEnabled
|| fMin
>= fMax
|| fProportion
>= 1.0f
737 || fProportion
< 0.0f
) {
741 if (fPrivateData
->fButtonDown
!= NOARROW
) {
742 if (fPrivateData
->fButtonDown
== THUMB
) {
743 SetValue(_ValueFor(where
+ fPrivateData
->fClickOffset
));
745 // suspend the repeating if the mouse is not over the button
746 bool repeat
= _ButtonRectFor(fPrivateData
->fButtonDown
).Contains(
748 if (fPrivateData
->fDoRepeat
!= repeat
) {
749 fPrivateData
->fDoRepeat
= repeat
;
750 Invalidate(_ButtonRectFor(fPrivateData
->fButtonDown
));
754 // update the value at which we want to stop repeating
755 if (fPrivateData
->fDoRepeat
) {
756 _UpdateTargetValue(where
);
757 // we might have to turn arround
758 if ((fValue
< fPrivateData
->fStopValue
759 && fPrivateData
->fThumbInc
< 0)
760 || (fValue
> fPrivateData
->fStopValue
761 && fPrivateData
->fThumbInc
> 0)) {
762 fPrivateData
->fThumbInc
= -fPrivateData
->fThumbInc
;
770 BScrollBar::MouseUp(BPoint where
)
772 if (fPrivateData
->fButtonDown
== THUMB
)
773 Invalidate(fPrivateData
->fThumbFrame
);
775 Invalidate(_ButtonRectFor(fPrivateData
->fButtonDown
));
777 fPrivateData
->fButtonDown
= NOARROW
;
778 fPrivateData
->fExitRepeater
= true;
779 fPrivateData
->fDoRepeat
= false;
783 #if DISABLES_ON_WINDOW_DEACTIVATION
785 BScrollBar::WindowActivated(bool active
)
787 fPrivateData
->fEnabled
= active
;
790 #endif // DISABLES_ON_WINDOW_DEACTIVATION
794 BScrollBar::SetValue(float value
)
798 else if (value
< fMin
)
800 else if (isnan(value
) || isinf(value
))
803 value
= roundf(value
);
807 TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value
);
812 _UpdateArrowButtons();
814 ValueChanged(fValue
);
819 BScrollBar::Value() const
826 BScrollBar::ValueChanged(float newValue
)
828 TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue
);
830 if (fTarget
!= NULL
) {
831 // cache target bounds
832 BRect targetBounds
= fTarget
->Bounds();
833 // if vertical, check bounds top and scroll if different from newValue
834 if (fOrientation
== B_VERTICAL
&& targetBounds
.top
!= newValue
)
835 fTarget
->ScrollBy(0.0, newValue
- targetBounds
.top
);
837 // if horizontal, check bounds left and scroll if different from newValue
838 if (fOrientation
== B_HORIZONTAL
&& targetBounds
.left
!= newValue
)
839 fTarget
->ScrollBy(newValue
- targetBounds
.left
, 0.0);
842 TRACE(" -> %.1f\n", newValue
);
849 BScrollBar::SetProportion(float value
)
853 else if (value
> 1.0f
)
856 if (value
== fProportion
)
859 TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value
);
861 bool oldEnabled
= fPrivateData
->fEnabled
&& fMin
< fMax
862 && fProportion
< 1.0f
&& fProportion
>= 0.0f
;
866 bool newEnabled
= fPrivateData
->fEnabled
&& fMin
< fMax
867 && fProportion
< 1.0f
&& fProportion
>= 0.0f
;
871 if (oldEnabled
!= newEnabled
)
877 BScrollBar::Proportion() const
884 BScrollBar::SetRange(float min
, float max
)
886 if (min
> max
|| isnanf(min
) || isnanf(max
)
887 || isinff(min
) || isinff(max
)) {
895 if (fMin
== min
&& fMax
== max
)
898 TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min
, max
);
903 if (fValue
< fMin
|| fValue
> fMax
)
913 BScrollBar::GetRange(float* min
, float* max
) const
924 BScrollBar::SetSteps(float smallStep
, float largeStep
)
926 // Under R5, steps can be set only after being attached to a window,
927 // probably because the data is kept server-side. We'll just remove
928 // that limitation... :P
930 // The BeBook also says that we need to specify an integer value even
931 // though the step values are floats. For the moment, we'll just make
932 // sure that they are integers
933 smallStep
= roundf(smallStep
);
934 largeStep
= roundf(largeStep
);
935 if (fSmallStep
== smallStep
&& fLargeStep
== largeStep
)
938 TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(),
939 smallStep
, largeStep
);
941 fSmallStep
= smallStep
;
942 fLargeStep
= largeStep
;
944 if (fProportion
== 0.0) {
945 // special case, proportion is based on fLargeStep if it was never
946 // set, so it means we need to invalidate here
951 // TODO: test use of fractional values and make them work properly if
957 BScrollBar::GetSteps(float* smallStep
, float* largeStep
) const
959 if (smallStep
!= NULL
)
960 *smallStep
= fSmallStep
;
962 if (largeStep
!= NULL
)
963 *largeStep
= fLargeStep
;
968 BScrollBar::SetTarget(BView
* target
)
971 // unset the previous target's scrollbar pointer
972 if (fOrientation
== B_VERTICAL
)
973 fTarget
->fVerScroller
= NULL
;
975 fTarget
->fHorScroller
= NULL
;
980 if (fOrientation
== B_VERTICAL
)
981 fTarget
->fVerScroller
= this;
983 fTarget
->fHorScroller
= this;
989 BScrollBar::SetTarget(const char* targetName
)
991 // NOTE 1: BeOS implementation crashes for targetName == NULL
992 // NOTE 2: BeOS implementation also does not modify the target
993 // if it can't be found
994 if (targetName
== NULL
)
997 if (Window() == NULL
)
998 debugger("Method requires window and doesn't have one");
1000 BView
* target
= Window()->FindView(targetName
);
1007 BScrollBar::Target() const
1014 BScrollBar::SetOrientation(orientation direction
)
1016 if (fOrientation
== direction
)
1019 fOrientation
= direction
;
1026 BScrollBar::Orientation() const
1028 return fOrientation
;
1033 BScrollBar::SetBorderHighlighted(bool highlight
)
1035 if (fPrivateData
->fBorderHighlighted
== highlight
)
1038 fPrivateData
->fBorderHighlighted
= highlight
;
1040 BRect
dirty(Bounds());
1041 if (fOrientation
== B_HORIZONTAL
)
1042 dirty
.bottom
= dirty
.top
;
1044 dirty
.right
= dirty
.left
;
1053 BScrollBar::GetPreferredSize(float* _width
, float* _height
)
1055 if (fOrientation
== B_VERTICAL
) {
1057 *_width
= B_V_SCROLL_BAR_WIDTH
;
1060 *_height
= Bounds().Height();
1061 } else if (fOrientation
== B_HORIZONTAL
) {
1063 *_width
= Bounds().Width();
1066 *_height
= B_H_SCROLL_BAR_HEIGHT
;
1072 BScrollBar::ResizeToPreferred()
1074 BView::ResizeToPreferred();
1080 BScrollBar::MakeFocus(bool focus
)
1082 BView::MakeFocus(focus
);
1087 BScrollBar::MinSize()
1089 return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize());
1094 BScrollBar::MaxSize()
1096 BSize maxSize
= _MinSize();
1097 if (fOrientation
== B_HORIZONTAL
)
1098 maxSize
.width
= B_SIZE_UNLIMITED
;
1100 maxSize
.height
= B_SIZE_UNLIMITED
;
1101 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize
);
1106 BScrollBar::PreferredSize()
1108 BSize preferredSize
= _MinSize();
1109 if (fOrientation
== B_HORIZONTAL
)
1110 preferredSize
.width
*= 2;
1112 preferredSize
.height
*= 2;
1114 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize
);
1119 BScrollBar::GetSupportedSuites(BMessage
* message
)
1121 return BView::GetSupportedSuites(message
);
1126 BScrollBar::ResolveSpecifier(BMessage
* message
, int32 index
,
1127 BMessage
* specifier
, int32 what
, const char* property
)
1129 return BView::ResolveSpecifier(message
, index
, specifier
, what
, property
);
1134 BScrollBar::Perform(perform_code code
, void* _data
)
1137 case PERFORM_CODE_MIN_SIZE
:
1138 ((perform_data_min_size
*)_data
)->return_value
1139 = BScrollBar::MinSize();
1143 case PERFORM_CODE_MAX_SIZE
:
1144 ((perform_data_max_size
*)_data
)->return_value
1145 = BScrollBar::MaxSize();
1149 case PERFORM_CODE_PREFERRED_SIZE
:
1150 ((perform_data_preferred_size
*)_data
)->return_value
1151 = BScrollBar::PreferredSize();
1155 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1156 ((perform_data_layout_alignment
*)_data
)->return_value
1157 = BScrollBar::LayoutAlignment();
1161 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1162 ((perform_data_has_height_for_width
*)_data
)->return_value
1163 = BScrollBar::HasHeightForWidth();
1167 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
1169 perform_data_get_height_for_width
* data
1170 = (perform_data_get_height_for_width
*)_data
;
1171 BScrollBar::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
1177 case PERFORM_CODE_SET_LAYOUT
:
1179 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1180 BScrollBar::SetLayout(data
->layout
);
1185 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1187 perform_data_layout_invalidated
* data
1188 = (perform_data_layout_invalidated
*)_data
;
1189 BScrollBar::LayoutInvalidated(data
->descendants
);
1194 case PERFORM_CODE_DO_LAYOUT
:
1196 BScrollBar::DoLayout();
1202 return BView::Perform(code
, _data
);
1206 void BScrollBar::_ReservedScrollBar1() {}
1207 void BScrollBar::_ReservedScrollBar2() {}
1208 void BScrollBar::_ReservedScrollBar3() {}
1209 void BScrollBar::_ReservedScrollBar4() {}
1213 BScrollBar::operator=(const BScrollBar
&)
1220 BScrollBar::_DoubleArrows() const
1222 if (!fPrivateData
->fScrollBarInfo
.double_arrows
)
1225 // if there is not enough room, switch to single arrows even though
1226 // double arrows is specified
1227 if (fOrientation
== B_HORIZONTAL
) {
1228 return Bounds().Width() > (Bounds().Height() + 1) * 4
1229 + fPrivateData
->fScrollBarInfo
.min_knob_size
* 2;
1231 return Bounds().Height() > (Bounds().Width() + 1) * 4
1232 + fPrivateData
->fScrollBarInfo
.min_knob_size
* 2;
1238 BScrollBar::_UpdateThumbFrame()
1240 BRect bounds
= Bounds();
1241 bounds
.InsetBy(1.0, 1.0);
1243 BRect oldFrame
= fPrivateData
->fThumbFrame
;
1244 fPrivateData
->fThumbFrame
= bounds
;
1245 float minSize
= fPrivateData
->fScrollBarInfo
.min_knob_size
;
1249 // assume square buttons
1250 if (fOrientation
== B_VERTICAL
) {
1251 maxSize
= bounds
.Height();
1252 buttonSize
= bounds
.Width() + 1.0;
1254 maxSize
= bounds
.Width();
1255 buttonSize
= bounds
.Height() + 1.0;
1258 if (_DoubleArrows()) {
1259 // subtract the size of four buttons
1260 maxSize
-= buttonSize
* 4;
1262 // subtract the size of two buttons
1263 maxSize
-= buttonSize
* 2;
1265 // visual adjustments (room for darker line between thumb and buttons)
1269 if (fPrivateData
->fScrollBarInfo
.proportional
) {
1270 float proportion
= fProportion
;
1271 if (fMin
>= fMax
|| proportion
> 1.0 || proportion
< 0.0)
1274 if (proportion
== 0.0) {
1275 // Special case a proportion of 0.0, use the large step value
1276 // in that case (NOTE: fMin == fMax already handled above)
1277 // This calculation is based on the assumption that "large step"
1278 // scrolls by one "page size".
1279 proportion
= fLargeStep
/ (2 * (fMax
- fMin
));
1280 if (proportion
> 1.0)
1283 thumbSize
= maxSize
* proportion
;
1284 if (thumbSize
< minSize
)
1285 thumbSize
= minSize
;
1287 thumbSize
= minSize
;
1289 thumbSize
= floorf(thumbSize
+ 0.5);
1292 // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0"
1295 offset
= floorf(((fValue
- fMin
) / (fMax
- fMin
))
1296 * (maxSize
- thumbSize
- 1.0));
1299 if (_DoubleArrows()) {
1300 offset
+= buttonSize
* 2;
1302 offset
+= buttonSize
;
1304 // visual adjustments (room for darker line between thumb and buttons)
1307 if (fOrientation
== B_VERTICAL
) {
1308 fPrivateData
->fThumbFrame
.bottom
= fPrivateData
->fThumbFrame
.top
1310 fPrivateData
->fThumbFrame
.OffsetBy(0.0, offset
);
1312 fPrivateData
->fThumbFrame
.right
= fPrivateData
->fThumbFrame
.left
1314 fPrivateData
->fThumbFrame
.OffsetBy(offset
, 0.0);
1317 if (Window() != NULL
) {
1318 BRect invalid
= oldFrame
.IsValid()
1319 ? oldFrame
| fPrivateData
->fThumbFrame
1320 : fPrivateData
->fThumbFrame
;
1321 // account for those two dark lines
1322 if (fOrientation
== B_HORIZONTAL
)
1323 invalid
.InsetBy(-2.0, 0.0);
1325 invalid
.InsetBy(0.0, -2.0);
1327 Invalidate(invalid
);
1333 BScrollBar::_ValueFor(BPoint where
) const
1335 BRect bounds
= Bounds();
1336 bounds
.InsetBy(1.0f
, 1.0f
);
1343 if (fOrientation
== B_VERTICAL
) {
1345 thumbSize
= fPrivateData
->fThumbFrame
.Height();
1346 maxSize
= bounds
.Height();
1347 buttonSize
= bounds
.Width() + 1.0f
;
1350 thumbSize
= fPrivateData
->fThumbFrame
.Width();
1351 maxSize
= bounds
.Width();
1352 buttonSize
= bounds
.Height() + 1.0f
;
1355 if (_DoubleArrows()) {
1356 // subtract the size of four buttons
1357 maxSize
-= buttonSize
* 4;
1358 // convert point to inside of area between buttons
1359 offset
-= buttonSize
* 2;
1361 // subtract the size of two buttons
1362 maxSize
-= buttonSize
* 2;
1363 // convert point to inside of area between buttons
1364 offset
-= buttonSize
;
1366 // visual adjustments (room for darker line between thumb and buttons)
1370 return roundf(fMin
+ (offset
/ (maxSize
- thumbSize
)
1371 * (fMax
- fMin
+ 1.0f
)));
1376 BScrollBar::_ButtonFor(BPoint where
) const
1378 BRect bounds
= Bounds();
1379 bounds
.InsetBy(1.0f
, 1.0f
);
1381 float buttonSize
= fOrientation
== B_VERTICAL
1382 ? bounds
.Width() + 1.0f
1383 : bounds
.Height() + 1.0f
;
1385 BRect
rect(bounds
.left
, bounds
.top
,
1386 bounds
.left
+ buttonSize
, bounds
.top
+ buttonSize
);
1388 if (fOrientation
== B_VERTICAL
) {
1389 if (rect
.Contains(where
))
1392 if (_DoubleArrows()) {
1393 rect
.OffsetBy(0.0, buttonSize
);
1394 if (rect
.Contains(where
))
1397 rect
.OffsetTo(bounds
.left
, bounds
.bottom
- 2 * buttonSize
);
1398 if (rect
.Contains(where
))
1401 rect
.OffsetTo(bounds
.left
, bounds
.bottom
- buttonSize
);
1402 if (rect
.Contains(where
))
1405 if (rect
.Contains(where
))
1408 if (_DoubleArrows()) {
1409 rect
.OffsetBy(buttonSize
, 0.0);
1410 if (rect
.Contains(where
))
1413 rect
.OffsetTo(bounds
.right
- 2 * buttonSize
, bounds
.top
);
1414 if (rect
.Contains(where
))
1417 rect
.OffsetTo(bounds
.right
- buttonSize
, bounds
.top
);
1418 if (rect
.Contains(where
))
1427 BScrollBar::_ButtonRectFor(int32 button
) const
1429 BRect bounds
= Bounds();
1430 bounds
.InsetBy(1.0f
, 1.0f
);
1432 float buttonSize
= fOrientation
== B_VERTICAL
1433 ? bounds
.Width() + 1.0f
1434 : bounds
.Height() + 1.0f
;
1436 BRect
rect(bounds
.left
, bounds
.top
,
1437 bounds
.left
+ buttonSize
- 1.0f
, bounds
.top
+ buttonSize
- 1.0f
);
1439 if (fOrientation
== B_VERTICAL
) {
1445 rect
.OffsetBy(0.0, buttonSize
);
1449 rect
.OffsetTo(bounds
.left
, bounds
.bottom
- 2 * buttonSize
+ 1);
1453 rect
.OffsetTo(bounds
.left
, bounds
.bottom
- buttonSize
+ 1);
1462 rect
.OffsetBy(buttonSize
, 0.0);
1466 rect
.OffsetTo(bounds
.right
- 2 * buttonSize
+ 1, bounds
.top
);
1470 rect
.OffsetTo(bounds
.right
- buttonSize
+ 1, bounds
.top
);
1480 BScrollBar::_UpdateTargetValue(BPoint where
)
1482 if (fOrientation
== B_VERTICAL
) {
1483 fPrivateData
->fStopValue
= _ValueFor(BPoint(where
.x
, where
.y
1484 - fPrivateData
->fThumbFrame
.Height() / 2.0));
1486 fPrivateData
->fStopValue
= _ValueFor(BPoint(where
.x
1487 - fPrivateData
->fThumbFrame
.Width() / 2.0, where
.y
));
1493 BScrollBar::_UpdateArrowButtons()
1495 bool upEnabled
= fValue
> fMin
;
1496 if (fPrivateData
->fUpArrowsEnabled
!= upEnabled
) {
1497 fPrivateData
->fUpArrowsEnabled
= upEnabled
;
1498 Invalidate(_ButtonRectFor(ARROW1
));
1499 if (_DoubleArrows())
1500 Invalidate(_ButtonRectFor(ARROW3
));
1503 bool downEnabled
= fValue
< fMax
;
1504 if (fPrivateData
->fDownArrowsEnabled
!= downEnabled
) {
1505 fPrivateData
->fDownArrowsEnabled
= downEnabled
;
1506 Invalidate(_ButtonRectFor(ARROW4
));
1507 if (_DoubleArrows())
1508 Invalidate(_ButtonRectFor(ARROW2
));
1514 control_scrollbar(scroll_bar_info
* info
, BScrollBar
* bar
)
1516 if (bar
== NULL
|| info
== NULL
)
1519 if (bar
->fPrivateData
->fScrollBarInfo
.double_arrows
1520 != info
->double_arrows
) {
1521 bar
->fPrivateData
->fScrollBarInfo
.double_arrows
= info
->double_arrows
;
1523 int8 multiplier
= (info
->double_arrows
) ? 1 : -1;
1525 if (bar
->fOrientation
== B_VERTICAL
) {
1526 bar
->fPrivateData
->fThumbFrame
.OffsetBy(0, multiplier
1527 * B_H_SCROLL_BAR_HEIGHT
);
1529 bar
->fPrivateData
->fThumbFrame
.OffsetBy(multiplier
1530 * B_V_SCROLL_BAR_WIDTH
, 0);
1534 bar
->fPrivateData
->fScrollBarInfo
.proportional
= info
->proportional
;
1536 // TODO: Figure out how proportional relates to the size of the thumb
1538 // TODO: Add redraw code to reflect the changes
1540 if (info
->knob
>= 0 && info
->knob
<= 2)
1541 bar
->fPrivateData
->fScrollBarInfo
.knob
= info
->knob
;
1545 if (info
->min_knob_size
>= SCROLL_BAR_MINIMUM_KNOB_SIZE
1546 && info
->min_knob_size
<= SCROLL_BAR_MAXIMUM_KNOB_SIZE
) {
1547 bar
->fPrivateData
->fScrollBarInfo
.min_knob_size
= info
->min_knob_size
;
1556 BScrollBar::_DrawDisabledBackground(BRect area
, const rgb_color
& light
,
1557 const rgb_color
& dark
, const rgb_color
& fill
)
1559 if (!area
.IsValid())
1562 if (fOrientation
== B_VERTICAL
) {
1563 int32 height
= area
.IntegerHeight();
1566 StrokeLine(area
.LeftTop(), area
.RightTop());
1567 } else if (height
== 1) {
1572 AddLine(BPoint(area
.left
, area
.top
),
1573 BPoint(area
.right
, area
.top
), dark
);
1574 AddLine(BPoint(area
.left
, area
.bottom
- 1),
1575 BPoint(area
.left
, area
.top
+ 1), light
);
1576 AddLine(BPoint(area
.left
+ 1, area
.top
+ 1),
1577 BPoint(area
.right
, area
.top
+ 1), light
);
1578 AddLine(BPoint(area
.right
, area
.bottom
),
1579 BPoint(area
.left
, area
.bottom
), dark
);
1584 if (area
.IsValid()) {
1590 int32 width
= area
.IntegerWidth();
1593 StrokeLine(area
.LeftBottom(), area
.LeftTop());
1594 } else if (width
== 1) {
1599 AddLine(BPoint(area
.left
, area
.bottom
),
1600 BPoint(area
.left
, area
.top
), dark
);
1601 AddLine(BPoint(area
.left
+ 1, area
.bottom
),
1602 BPoint(area
.left
+ 1, area
.top
+ 1), light
);
1603 AddLine(BPoint(area
.left
+ 1, area
.top
),
1604 BPoint(area
.right
- 1, area
.top
), light
);
1605 AddLine(BPoint(area
.right
, area
.top
),
1606 BPoint(area
.right
, area
.bottom
), dark
);
1611 if (area
.IsValid()) {
1621 BScrollBar::_DrawArrowButton(int32 direction
, bool doubleArrows
, BRect rect
,
1622 const BRect
& updateRect
, bool enabled
, bool down
)
1624 if (!updateRect
.Intersects(rect
))
1629 flags
|= BControlLook::B_DISABLED
;
1631 if (down
&& fPrivateData
->fDoRepeat
)
1632 flags
|= BControlLook::B_ACTIVATED
;
1634 // TODO: Why does BControlLook need this as the base color for the
1635 // scrollbar to look right?
1636 rgb_color baseColor
= tint_color(ui_color(B_PANEL_BACKGROUND_COLOR
),
1639 be_control_look
->DrawButtonBackground(this, rect
, updateRect
, baseColor
,
1640 flags
, BControlLook::B_ALL_BORDERS
, fOrientation
);
1642 // TODO: Why does BControlLook need this negative inset for the arrow to
1644 rect
.InsetBy(-1.0f
, -1.0f
);
1645 be_control_look
->DrawArrowShape(this, rect
, updateRect
,
1646 baseColor
, direction
, flags
, B_DARKEN_MAX_TINT
);
1651 BScrollBar::_MinSize() const
1654 if (fOrientation
== B_HORIZONTAL
) {
1655 minSize
.width
= 2 * B_V_SCROLL_BAR_WIDTH
1656 + 2 * fPrivateData
->fScrollBarInfo
.min_knob_size
;
1657 minSize
.height
= B_H_SCROLL_BAR_HEIGHT
;
1659 minSize
.width
= B_V_SCROLL_BAR_WIDTH
;
1660 minSize
.height
= 2 * B_H_SCROLL_BAR_HEIGHT
1661 + 2 * fPrivateData
->fScrollBarInfo
.min_knob_size
;