/* * Copyright 2001-2020 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT license. * * Authors: * Stephan Aßmus, superstippi@gmx.de * Stefano Ceccherini, burton666@libero.it * DarkWyrm, bpmagic@columbus.rr.com * Marc Flerackers, mflerackers@androme.be * John Scipione, jscipione@gmail.com */ #include #include #include #include #include #include #include #include #include #include #include #include #include //#define TRACE_SCROLLBAR #ifdef TRACE_SCROLLBAR # define TRACE(x...) printf(x) #else # define TRACE(x...) #endif typedef enum { ARROW_LEFT = 0, ARROW_RIGHT, ARROW_UP, ARROW_DOWN, ARROW_NONE } arrow_direction; #define SCROLL_BAR_MAXIMUM_KNOB_SIZE 50 #define SCROLL_BAR_MINIMUM_KNOB_SIZE 9 #define SBC_SCROLLBYVALUE 0 #define SBC_SETDOUBLE 1 #define SBC_SETPROPORTIONAL 2 #define SBC_SETSTYLE 3 // Quick constants for determining which arrow is down and are defined with // respect to double arrow mode. ARROW1 and ARROW4 refer to the outer pair of // arrows and ARROW2 and ARROW3 refer to the inner ones. ARROW1 points left/up // and ARROW4 points right/down. #define ARROW1 0 #define ARROW2 1 #define ARROW3 2 #define ARROW4 3 #define THUMB 4 #define NOARROW -1 static const bigtime_t kRepeatDelay = 300000; // Because the R5 version kept a lot of data on server-side, we need to kludge // our way into binary compatibility class BScrollBar::Private { public: Private(BScrollBar* scrollBar) : fScrollBar(scrollBar), fEnabled(true), fRepeaterThread(-1), fExitRepeater(false), fRepeaterDelay(0), fThumbFrame(0.0, 0.0, -1.0, -1.0), fDoRepeat(false), fClickOffset(0.0, 0.0), fThumbInc(0.0), fStopValue(0.0), fUpArrowsEnabled(true), fDownArrowsEnabled(true), fBorderHighlighted(false), fButtonDown(NOARROW) { #ifdef TEST_MODE fScrollBarInfo.proportional = true; fScrollBarInfo.double_arrows = true; fScrollBarInfo.knob = 0; fScrollBarInfo.min_knob_size = 15; #else get_scroll_bar_info(&fScrollBarInfo); #endif fScrollBarInfo.min_knob_size = (int32)(fScrollBarInfo.min_knob_size * (be_plain_font->Size() / 12.0f)); } ~Private() { if (fRepeaterThread >= 0) { status_t dummy; fExitRepeater = true; wait_for_thread(fRepeaterThread, &dummy); } } void DrawScrollBarButton(BScrollBar* owner, arrow_direction direction, BRect frame, bool down = false); static int32 button_repeater_thread(void* data); int32 ButtonRepeaterThread(); BScrollBar* fScrollBar; bool fEnabled; // TODO: This should be a static, initialized by // _init_interface_kit() at application startup-time, // like BMenu::sMenuInfo scroll_bar_info fScrollBarInfo; thread_id fRepeaterThread; volatile bool fExitRepeater; bigtime_t fRepeaterDelay; BRect fThumbFrame; volatile bool fDoRepeat; BPoint fClickOffset; float fThumbInc; float fStopValue; bool fUpArrowsEnabled; bool fDownArrowsEnabled; bool fBorderHighlighted; int8 fButtonDown; }; // This thread is spawned when a button is initially pushed and repeatedly scrolls // the scrollbar by a little bit after a short delay int32 BScrollBar::Private::button_repeater_thread(void* data) { BScrollBar::Private* privateData = (BScrollBar::Private*)data; return privateData->ButtonRepeaterThread(); } int32 BScrollBar::Private::ButtonRepeaterThread() { // Wait a bit before auto scrolling starts. As long as the user releases // and presses the button again while the repeat delay has not yet // triggered, the value is pushed into the future, so we need to loop such // that repeating starts at exactly the correct delay after the last // button press. while (fRepeaterDelay > system_time() && !fExitRepeater) snooze_until(fRepeaterDelay, B_SYSTEM_TIMEBASE); // repeat loop while (!fExitRepeater) { if (fScrollBar->LockLooper()) { if (fDoRepeat) { float value = fScrollBar->Value() + fThumbInc; if (fButtonDown == NOARROW) { // in this case we want to stop when we're under the mouse if (fThumbInc > 0.0 && value <= fStopValue) fScrollBar->SetValue(value); if (fThumbInc < 0.0 && value >= fStopValue) fScrollBar->SetValue(value); } else fScrollBar->SetValue(value); } fScrollBar->UnlockLooper(); } snooze(25000); } // tell scrollbar we're gone if (fScrollBar->LockLooper()) { fRepeaterThread = -1; fScrollBar->UnlockLooper(); } return 0; } // #pragma mark - BScrollBar BScrollBar::BScrollBar(BRect frame, const char* name, BView* target, float min, float max, orientation direction) : BView(frame, name, B_FOLLOW_NONE, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), fMin(min), fMax(max), fSmallStep(1.0f), fLargeStep(10.0f), fValue(0), fProportion(0.0f), fTarget(NULL), fOrientation(direction) { SetViewColor(B_TRANSPARENT_COLOR); fPrivateData = new BScrollBar::Private(this); SetTarget(target); SetEventMask(B_NO_POINTER_HISTORY); _UpdateThumbFrame(); _UpdateArrowButtons(); SetResizingMode(direction == B_VERTICAL ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); } BScrollBar::BScrollBar(const char* name, BView* target, float min, float max, orientation direction) : BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS), fMin(min), fMax(max), fSmallStep(1.0f), fLargeStep(10.0f), fValue(0), fProportion(0.0f), fTarget(NULL), fOrientation(direction) { SetViewColor(B_TRANSPARENT_COLOR); fPrivateData = new BScrollBar::Private(this); SetTarget(target); SetEventMask(B_NO_POINTER_HISTORY); _UpdateThumbFrame(); _UpdateArrowButtons(); } BScrollBar::BScrollBar(BMessage* data) : BView(data), fTarget(NULL) { fPrivateData = new BScrollBar::Private(this); // TODO: Does the BeOS implementation try to find the target // by name again? Does it archive the name at all? if (data->FindFloat("_range", 0, &fMin) < B_OK) fMin = 0.0f; if (data->FindFloat("_range", 1, &fMax) < B_OK) fMax = 0.0f; if (data->FindFloat("_steps", 0, &fSmallStep) < B_OK) fSmallStep = 1.0f; if (data->FindFloat("_steps", 1, &fLargeStep) < B_OK) fLargeStep = 10.0f; if (data->FindFloat("_val", &fValue) < B_OK) fValue = 0.0; int32 orientation; if (data->FindInt32("_orient", &orientation) < B_OK) { fOrientation = B_VERTICAL; } else fOrientation = (enum orientation)orientation; if ((Flags() & B_SUPPORTS_LAYOUT) == 0) { // just to make sure SetResizingMode(fOrientation == B_VERTICAL ? B_FOLLOW_TOP_BOTTOM | B_FOLLOW_RIGHT : B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM); } if (data->FindFloat("_prop", &fProportion) < B_OK) fProportion = 0.0; _UpdateThumbFrame(); _UpdateArrowButtons(); } BScrollBar::~BScrollBar() { SetTarget((BView*)NULL); delete fPrivateData; } BArchivable* BScrollBar::Instantiate(BMessage* data) { if (validate_instantiation(data, "BScrollBar")) return new BScrollBar(data); return NULL; } status_t BScrollBar::Archive(BMessage* data, bool deep) const { status_t err = BView::Archive(data, deep); if (err != B_OK) return err; err = data->AddFloat("_range", fMin); if (err != B_OK) return err; err = data->AddFloat("_range", fMax); if (err != B_OK) return err; err = data->AddFloat("_steps", fSmallStep); if (err != B_OK) return err; err = data->AddFloat("_steps", fLargeStep); if (err != B_OK) return err; err = data->AddFloat("_val", fValue); if (err != B_OK) return err; err = data->AddInt32("_orient", (int32)fOrientation); if (err != B_OK) return err; err = data->AddFloat("_prop", fProportion); return err; } void BScrollBar::AllAttached() { BView::AllAttached(); } void BScrollBar::AllDetached() { BView::AllDetached(); } void BScrollBar::AttachedToWindow() { BView::AttachedToWindow(); } void BScrollBar::DetachedFromWindow() { BView::DetachedFromWindow(); } void BScrollBar::Draw(BRect updateRect) { rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); uint32 flags = 0; bool scrollingEnabled = fMin < fMax && fProportion >= 0.0f && fProportion < 1.0f; if (scrollingEnabled) flags |= BControlLook::B_PARTIALLY_ACTIVATED; if (!fPrivateData->fEnabled || !scrollingEnabled) flags |= BControlLook::B_DISABLED; bool isFocused = fPrivateData->fBorderHighlighted; if (isFocused) flags |= BControlLook::B_FOCUSED; bool doubleArrows = _DoubleArrows(); // draw border BRect rect = Bounds(); be_control_look->DrawScrollBarBorder(this, rect, updateRect, base, flags, fOrientation); // inset past border rect.InsetBy(1, 1); // draw arrows buttons BRect thumbBG = rect; if (fOrientation == B_HORIZONTAL) { BRect buttonFrame(rect.left, rect.top, rect.left + rect.Height(), rect.bottom); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW1 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_LEFT_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW1); if (doubleArrows) { buttonFrame.OffsetBy(rect.Height() + 1, 0.0f); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW2 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_RIGHT_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW2); buttonFrame.OffsetTo(rect.right - ((rect.Height() * 2) + 1), rect.top); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW3 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_LEFT_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW3); thumbBG.left += rect.Height() * 2 + 2; thumbBG.right -= rect.Height() * 2 + 2; } else { thumbBG.left += rect.Height() + 1; thumbBG.right -= rect.Height() + 1; } buttonFrame.OffsetTo(rect.right - rect.Height(), rect.top); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW4 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_RIGHT_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW4); } else { BRect buttonFrame(rect.left, rect.top, rect.right, rect.top + rect.Width()); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW1 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_UP_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW1); if (doubleArrows) { buttonFrame.OffsetBy(0, rect.Width() + 1); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW2 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_DOWN_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW2); buttonFrame.OffsetTo(rect.left, rect.bottom - ((rect.Width() * 2) + 1)); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW3 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_UP_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW3); thumbBG.top += rect.Width() * 2 + 2; thumbBG.bottom -= rect.Width() * 2 + 2; } else { thumbBG.top += rect.Width() + 1; thumbBG.bottom -= rect.Width() + 1; } buttonFrame.OffsetTo(rect.left, rect.bottom - rect.Width()); be_control_look->DrawScrollBarButton(this, buttonFrame, updateRect, base, flags | (fPrivateData->fButtonDown == ARROW4 ? BControlLook::B_ACTIVATED : 0), BControlLook::B_DOWN_ARROW, fOrientation, fPrivateData->fButtonDown == ARROW4); } // fill background besides the thumb rect = fPrivateData->fThumbFrame; if (fOrientation == B_HORIZONTAL) { BRect leftOfThumb(thumbBG.left, thumbBG.top, rect.left - 1, thumbBG.bottom); BRect rightOfThumb(rect.right + 1, thumbBG.top, thumbBG.right, thumbBG.bottom); be_control_look->DrawScrollBarBackground(this, leftOfThumb, rightOfThumb, updateRect, base, flags, fOrientation); } else { BRect topOfThumb(thumbBG.left, thumbBG.top, thumbBG.right, rect.top - 1); BRect bottomOfThumb(thumbBG.left, rect.bottom + 1, thumbBG.right, thumbBG.bottom); be_control_look->DrawScrollBarBackground(this, topOfThumb, bottomOfThumb, updateRect, base, flags, fOrientation); } // draw thumb rect = fPrivateData->fThumbFrame; be_control_look->DrawScrollBarThumb(this, rect, updateRect, ui_color(B_SCROLL_BAR_THUMB_COLOR), flags, fOrientation, fPrivateData->fScrollBarInfo.knob); } void BScrollBar::FrameMoved(BPoint newPosition) { BView::FrameMoved(newPosition); } void BScrollBar::FrameResized(float newWidth, float newHeight) { _UpdateThumbFrame(); } void BScrollBar::MessageReceived(BMessage* message) { switch(message->what) { case B_VALUE_CHANGED: { int32 value; if (message->FindInt32("value", &value) == B_OK) ValueChanged(value); break; } case B_MOUSE_WHEEL_CHANGED: { // Must handle this here since BView checks for the existence of // scrollbars, which a scrollbar itself does not have float deltaX = 0.0f; float deltaY = 0.0f; message->FindFloat("be:wheel_delta_x", &deltaX); message->FindFloat("be:wheel_delta_y", &deltaY); if (deltaX == 0.0f && deltaY == 0.0f) break; if (deltaX != 0.0f && deltaY == 0.0f) deltaY = deltaX; ScrollWithMouseWheelDelta(this, deltaY); } default: BView::MessageReceived(message); } } void BScrollBar::MouseDown(BPoint where) { if (!fPrivateData->fEnabled || fMin == fMax) return; SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); int32 buttons; if (Looper() == NULL || Looper()->CurrentMessage() == NULL || Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK) { buttons = B_PRIMARY_MOUSE_BUTTON; } if (buttons & B_SECONDARY_MOUSE_BUTTON) { // special absolute scrolling: move thumb to where we clicked fPrivateData->fButtonDown = THUMB; fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; if (Orientation() == B_HORIZONTAL) { fPrivateData->fClickOffset.x = -fPrivateData->fThumbFrame.Width() / 2; } else { fPrivateData->fClickOffset.y = -fPrivateData->fThumbFrame.Height() / 2; } SetValue(_ValueFor(where + fPrivateData->fClickOffset)); return; } // hit test for the thumb if (fPrivateData->fThumbFrame.Contains(where)) { fPrivateData->fButtonDown = THUMB; fPrivateData->fClickOffset = fPrivateData->fThumbFrame.LeftTop() - where; Invalidate(fPrivateData->fThumbFrame); return; } // hit test for arrows or empty area float scrollValue = 0.0; // pressing the shift key scrolls faster float buttonStepSize = (modifiers() & B_SHIFT_KEY) != 0 ? fLargeStep : fSmallStep; fPrivateData->fButtonDown = _ButtonFor(where); switch (fPrivateData->fButtonDown) { case ARROW1: scrollValue = -buttonStepSize; break; case ARROW2: scrollValue = buttonStepSize; break; case ARROW3: scrollValue = -buttonStepSize; break; case ARROW4: scrollValue = buttonStepSize; break; case NOARROW: // we hit the empty area, figure out which side of the thumb if (fOrientation == B_VERTICAL) { if (where.y < fPrivateData->fThumbFrame.top) scrollValue = -fLargeStep; else scrollValue = fLargeStep; } else { if (where.x < fPrivateData->fThumbFrame.left) scrollValue = -fLargeStep; else scrollValue = fLargeStep; } _UpdateTargetValue(where); break; } if (scrollValue != 0.0) { SetValue(fValue + scrollValue); Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); // launch the repeat thread if (fPrivateData->fRepeaterThread == -1) { fPrivateData->fExitRepeater = false; fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; fPrivateData->fThumbInc = scrollValue; fPrivateData->fDoRepeat = true; fPrivateData->fRepeaterThread = spawn_thread( fPrivateData->button_repeater_thread, "scroll repeater", B_NORMAL_PRIORITY, fPrivateData); resume_thread(fPrivateData->fRepeaterThread); } else { fPrivateData->fExitRepeater = false; fPrivateData->fRepeaterDelay = system_time() + kRepeatDelay; fPrivateData->fDoRepeat = true; } } } void BScrollBar::MouseMoved(BPoint where, uint32 code, const BMessage* dragMessage) { if (!fPrivateData->fEnabled || fMin >= fMax || fProportion >= 1.0f || fProportion < 0.0f) { return; } if (fPrivateData->fButtonDown != NOARROW) { if (fPrivateData->fButtonDown == THUMB) { SetValue(_ValueFor(where + fPrivateData->fClickOffset)); } else { // suspend the repeating if the mouse is not over the button bool repeat = _ButtonRectFor(fPrivateData->fButtonDown).Contains( where); if (fPrivateData->fDoRepeat != repeat) { fPrivateData->fDoRepeat = repeat; Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); } } } else { // update the value at which we want to stop repeating if (fPrivateData->fDoRepeat) { _UpdateTargetValue(where); // we might have to turn arround if ((fValue < fPrivateData->fStopValue && fPrivateData->fThumbInc < 0) || (fValue > fPrivateData->fStopValue && fPrivateData->fThumbInc > 0)) { fPrivateData->fThumbInc = -fPrivateData->fThumbInc; } } } } void BScrollBar::MouseUp(BPoint where) { if (fPrivateData->fButtonDown == THUMB) Invalidate(fPrivateData->fThumbFrame); else Invalidate(_ButtonRectFor(fPrivateData->fButtonDown)); fPrivateData->fButtonDown = NOARROW; fPrivateData->fExitRepeater = true; fPrivateData->fDoRepeat = false; } void BScrollBar::WindowActivated(bool active) { fPrivateData->fEnabled = active; Invalidate(); } void BScrollBar::SetValue(float value) { if (value > fMax) value = fMax; else if (value < fMin) value = fMin; else if (isnan(value) || isinf(value)) return; value = roundf(value); if (value == fValue) return; TRACE("BScrollBar(%s)::SetValue(%.1f)\n", Name(), value); fValue = value; _UpdateThumbFrame(); _UpdateArrowButtons(); ValueChanged(fValue); } float BScrollBar::Value() const { return fValue; } void BScrollBar::ValueChanged(float newValue) { TRACE("BScrollBar(%s)::ValueChanged(%.1f)\n", Name(), newValue); if (fTarget != NULL) { // cache target bounds BRect targetBounds = fTarget->Bounds(); // if vertical, check bounds top and scroll if different from newValue if (fOrientation == B_VERTICAL && targetBounds.top != newValue) fTarget->ScrollBy(0.0, newValue - targetBounds.top); // if horizontal, check bounds left and scroll if different from newValue if (fOrientation == B_HORIZONTAL && targetBounds.left != newValue) fTarget->ScrollBy(newValue - targetBounds.left, 0.0); } TRACE(" -> %.1f\n", newValue); SetValue(newValue); } void BScrollBar::SetProportion(float value) { if (value < 0.0f) value = 0.0f; else if (value > 1.0f) value = 1.0f; if (value == fProportion) return; TRACE("BScrollBar(%s)::SetProportion(%.1f)\n", Name(), value); bool oldEnabled = fPrivateData->fEnabled && fMin < fMax && fProportion < 1.0f && fProportion >= 0.0f; fProportion = value; bool newEnabled = fPrivateData->fEnabled && fMin < fMax && fProportion < 1.0f && fProportion >= 0.0f; _UpdateThumbFrame(); if (oldEnabled != newEnabled) Invalidate(); } float BScrollBar::Proportion() const { return fProportion; } void BScrollBar::SetRange(float min, float max) { if (min > max || isnanf(min) || isnanf(max) || isinff(min) || isinff(max)) { min = 0.0f; max = 0.0f; } min = roundf(min); max = roundf(max); if (fMin == min && fMax == max) return; TRACE("BScrollBar(%s)::SetRange(min=%.1f, max=%.1f)\n", Name(), min, max); fMin = min; fMax = max; if (fValue < fMin || fValue > fMax) SetValue(fValue); else { _UpdateThumbFrame(); Invalidate(); } } void BScrollBar::GetRange(float* min, float* max) const { if (min != NULL) *min = fMin; if (max != NULL) *max = fMax; } void BScrollBar::SetSteps(float smallStep, float largeStep) { // Under R5, steps can be set only after being attached to a window, // probably because the data is kept server-side. We'll just remove // that limitation... :P // The BeBook also says that we need to specify an integer value even // though the step values are floats. For the moment, we'll just make // sure that they are integers smallStep = roundf(smallStep); largeStep = roundf(largeStep); if (fSmallStep == smallStep && fLargeStep == largeStep) return; TRACE("BScrollBar(%s)::SetSteps(small=%.1f, large=%.1f)\n", Name(), smallStep, largeStep); fSmallStep = smallStep; fLargeStep = largeStep; if (fProportion == 0.0) { // special case, proportion is based on fLargeStep if it was never // set, so it means we need to invalidate here _UpdateThumbFrame(); Invalidate(); } // TODO: test use of fractional values and make them work properly if // they don't } void BScrollBar::GetSteps(float* smallStep, float* largeStep) const { if (smallStep != NULL) *smallStep = fSmallStep; if (largeStep != NULL) *largeStep = fLargeStep; } void BScrollBar::SetTarget(BView* target) { if (fTarget) { // unset the previous target's scrollbar pointer if (fOrientation == B_VERTICAL) fTarget->fVerScroller = NULL; else fTarget->fHorScroller = NULL; } fTarget = target; if (fTarget) { if (fOrientation == B_VERTICAL) fTarget->fVerScroller = this; else fTarget->fHorScroller = this; } } void BScrollBar::SetTarget(const char* targetName) { // NOTE 1: BeOS implementation crashes for targetName == NULL // NOTE 2: BeOS implementation also does not modify the target // if it can't be found if (targetName == NULL) return; if (Window() == NULL) debugger("Method requires window and doesn't have one"); BView* target = Window()->FindView(targetName); if (target != NULL) SetTarget(target); } BView* BScrollBar::Target() const { return fTarget; } void BScrollBar::SetOrientation(orientation direction) { if (fOrientation == direction) return; fOrientation = direction; InvalidateLayout(); Invalidate(); } orientation BScrollBar::Orientation() const { return fOrientation; } status_t BScrollBar::SetBorderHighlighted(bool highlight) { if (fPrivateData->fBorderHighlighted == highlight) return B_OK; fPrivateData->fBorderHighlighted = highlight; BRect dirty(Bounds()); if (fOrientation == B_HORIZONTAL) dirty.bottom = dirty.top; else dirty.right = dirty.left; Invalidate(dirty); return B_OK; } void BScrollBar::GetPreferredSize(float* _width, float* _height) { if (fOrientation == B_VERTICAL) { if (_width) *_width = be_control_look->GetScrollBarWidth(B_VERTICAL); if (_height) *_height = _MinSize().Height(); } else if (fOrientation == B_HORIZONTAL) { if (_width) *_width = _MinSize().Width(); if (_height) *_height = be_control_look->GetScrollBarWidth(B_HORIZONTAL); } } void BScrollBar::ResizeToPreferred() { BView::ResizeToPreferred(); } void BScrollBar::MakeFocus(bool focus) { BView::MakeFocus(focus); } BSize BScrollBar::MinSize() { return BLayoutUtils::ComposeSize(ExplicitMinSize(), _MinSize()); } BSize BScrollBar::MaxSize() { BSize maxSize; GetPreferredSize(&maxSize.width, &maxSize.height); if (fOrientation == B_HORIZONTAL) maxSize.width = B_SIZE_UNLIMITED; else maxSize.height = B_SIZE_UNLIMITED; return BLayoutUtils::ComposeSize(ExplicitMaxSize(), maxSize); } BSize BScrollBar::PreferredSize() { BSize preferredSize; GetPreferredSize(&preferredSize.width, &preferredSize.height); return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), preferredSize); } status_t BScrollBar::GetSupportedSuites(BMessage* message) { return BView::GetSupportedSuites(message); } BHandler* BScrollBar::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, int32 what, const char* property) { return BView::ResolveSpecifier(message, index, specifier, what, property); } status_t BScrollBar::Perform(perform_code code, void* _data) { switch (code) { case PERFORM_CODE_MIN_SIZE: ((perform_data_min_size*)_data)->return_value = BScrollBar::MinSize(); return B_OK; case PERFORM_CODE_MAX_SIZE: ((perform_data_max_size*)_data)->return_value = BScrollBar::MaxSize(); return B_OK; case PERFORM_CODE_PREFERRED_SIZE: ((perform_data_preferred_size*)_data)->return_value = BScrollBar::PreferredSize(); return B_OK; case PERFORM_CODE_LAYOUT_ALIGNMENT: ((perform_data_layout_alignment*)_data)->return_value = BScrollBar::LayoutAlignment(); return B_OK; case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: ((perform_data_has_height_for_width*)_data)->return_value = BScrollBar::HasHeightForWidth(); return B_OK; case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: { perform_data_get_height_for_width* data = (perform_data_get_height_for_width*)_data; BScrollBar::GetHeightForWidth(data->width, &data->min, &data->max, &data->preferred); return B_OK; } case PERFORM_CODE_SET_LAYOUT: { perform_data_set_layout* data = (perform_data_set_layout*)_data; BScrollBar::SetLayout(data->layout); return B_OK; } case PERFORM_CODE_LAYOUT_INVALIDATED: { perform_data_layout_invalidated* data = (perform_data_layout_invalidated*)_data; BScrollBar::LayoutInvalidated(data->descendants); return B_OK; } case PERFORM_CODE_DO_LAYOUT: { BScrollBar::DoLayout(); return B_OK; } } return BView::Perform(code, _data); } void BScrollBar::_ReservedScrollBar1() {} void BScrollBar::_ReservedScrollBar2() {} void BScrollBar::_ReservedScrollBar3() {} void BScrollBar::_ReservedScrollBar4() {} BScrollBar& BScrollBar::operator=(const BScrollBar&) { return *this; } bool BScrollBar::_DoubleArrows() const { if (!fPrivateData->fScrollBarInfo.double_arrows) return false; // if there is not enough room, switch to single arrows even though // double arrows is specified if (fOrientation == B_HORIZONTAL) { return Bounds().Width() > (Bounds().Height() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2; } else { return Bounds().Height() > (Bounds().Width() + 1) * 4 + fPrivateData->fScrollBarInfo.min_knob_size * 2; } } void BScrollBar::_UpdateThumbFrame() { BRect bounds = Bounds(); bounds.InsetBy(1.0, 1.0); BRect oldFrame = fPrivateData->fThumbFrame; fPrivateData->fThumbFrame = bounds; float minSize = fPrivateData->fScrollBarInfo.min_knob_size; float maxSize; float buttonSize; // assume square buttons if (fOrientation == B_VERTICAL) { maxSize = bounds.Height(); buttonSize = bounds.Width() + 1.0; } else { maxSize = bounds.Width(); buttonSize = bounds.Height() + 1.0; } if (_DoubleArrows()) { // subtract the size of four buttons maxSize -= buttonSize * 4; } else { // subtract the size of two buttons maxSize -= buttonSize * 2; } // visual adjustments (room for darker line between thumb and buttons) maxSize--; float thumbSize; if (fPrivateData->fScrollBarInfo.proportional) { float proportion = fProportion; if (fMin >= fMax || proportion > 1.0 || proportion < 0.0) proportion = 1.0; if (proportion == 0.0) { // Special case a proportion of 0.0, use the large step value // in that case (NOTE: fMin == fMax already handled above) // This calculation is based on the assumption that "large step" // scrolls by one "page size". proportion = fLargeStep / (2 * (fMax - fMin)); if (proportion > 1.0) proportion = 1.0; } thumbSize = maxSize * proportion; if (thumbSize < minSize) thumbSize = minSize; } else thumbSize = minSize; thumbSize = floorf(thumbSize + 0.5); thumbSize--; // the thumb can be scrolled within the remaining area "maxSize - thumbSize - 1.0" float offset = 0.0; if (fMax > fMin) { offset = floorf(((fValue - fMin) / (fMax - fMin)) * (maxSize - thumbSize - 1.0)); } if (_DoubleArrows()) { offset += buttonSize * 2; } else offset += buttonSize; // visual adjustments (room for darker line between thumb and buttons) offset++; if (fOrientation == B_VERTICAL) { fPrivateData->fThumbFrame.bottom = fPrivateData->fThumbFrame.top + thumbSize; fPrivateData->fThumbFrame.OffsetBy(0.0, offset); } else { fPrivateData->fThumbFrame.right = fPrivateData->fThumbFrame.left + thumbSize; fPrivateData->fThumbFrame.OffsetBy(offset, 0.0); } if (Window() != NULL) { BRect invalid = oldFrame.IsValid() ? oldFrame | fPrivateData->fThumbFrame : fPrivateData->fThumbFrame; // account for those two dark lines if (fOrientation == B_HORIZONTAL) invalid.InsetBy(-2.0, 0.0); else invalid.InsetBy(0.0, -2.0); Invalidate(invalid); } } float BScrollBar::_ValueFor(BPoint where) const { BRect bounds = Bounds(); bounds.InsetBy(1.0f, 1.0f); float offset; float thumbSize; float maxSize; float buttonSize; if (fOrientation == B_VERTICAL) { offset = where.y; thumbSize = fPrivateData->fThumbFrame.Height(); maxSize = bounds.Height(); buttonSize = bounds.Width() + 1.0f; } else { offset = where.x; thumbSize = fPrivateData->fThumbFrame.Width(); maxSize = bounds.Width(); buttonSize = bounds.Height() + 1.0f; } if (_DoubleArrows()) { // subtract the size of four buttons maxSize -= buttonSize * 4; // convert point to inside of area between buttons offset -= buttonSize * 2; } else { // subtract the size of two buttons maxSize -= buttonSize * 2; // convert point to inside of area between buttons offset -= buttonSize; } // visual adjustments (room for darker line between thumb and buttons) maxSize--; offset++; return roundf(fMin + (offset / (maxSize - thumbSize) * (fMax - fMin + 1.0f))); } int32 BScrollBar::_ButtonFor(BPoint where) const { BRect bounds = Bounds(); bounds.InsetBy(1.0f, 1.0f); float buttonSize = fOrientation == B_VERTICAL ? bounds.Width() + 1.0f : bounds.Height() + 1.0f; BRect rect(bounds.left, bounds.top, bounds.left + buttonSize, bounds.top + buttonSize); if (fOrientation == B_VERTICAL) { if (rect.Contains(where)) return ARROW1; if (_DoubleArrows()) { rect.OffsetBy(0.0, buttonSize); if (rect.Contains(where)) return ARROW2; rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize); if (rect.Contains(where)) return ARROW3; } rect.OffsetTo(bounds.left, bounds.bottom - buttonSize); if (rect.Contains(where)) return ARROW4; } else { if (rect.Contains(where)) return ARROW1; if (_DoubleArrows()) { rect.OffsetBy(buttonSize, 0.0); if (rect.Contains(where)) return ARROW2; rect.OffsetTo(bounds.right - 2 * buttonSize, bounds.top); if (rect.Contains(where)) return ARROW3; } rect.OffsetTo(bounds.right - buttonSize, bounds.top); if (rect.Contains(where)) return ARROW4; } return NOARROW; } BRect BScrollBar::_ButtonRectFor(int32 button) const { BRect bounds = Bounds(); bounds.InsetBy(1.0f, 1.0f); float buttonSize = fOrientation == B_VERTICAL ? bounds.Width() + 1.0f : bounds.Height() + 1.0f; BRect rect(bounds.left, bounds.top, bounds.left + buttonSize - 1.0f, bounds.top + buttonSize - 1.0f); if (fOrientation == B_VERTICAL) { switch (button) { case ARROW1: break; case ARROW2: rect.OffsetBy(0.0, buttonSize); break; case ARROW3: rect.OffsetTo(bounds.left, bounds.bottom - 2 * buttonSize + 1); break; case ARROW4: rect.OffsetTo(bounds.left, bounds.bottom - buttonSize + 1); break; } } else { switch (button) { case ARROW1: break; case ARROW2: rect.OffsetBy(buttonSize, 0.0); break; case ARROW3: rect.OffsetTo(bounds.right - 2 * buttonSize + 1, bounds.top); break; case ARROW4: rect.OffsetTo(bounds.right - buttonSize + 1, bounds.top); break; } } return rect; } void BScrollBar::_UpdateTargetValue(BPoint where) { if (fOrientation == B_VERTICAL) { fPrivateData->fStopValue = _ValueFor(BPoint(where.x, where.y - fPrivateData->fThumbFrame.Height() / 2.0)); } else { fPrivateData->fStopValue = _ValueFor(BPoint(where.x - fPrivateData->fThumbFrame.Width() / 2.0, where.y)); } } void BScrollBar::_UpdateArrowButtons() { bool upEnabled = fValue > fMin; if (fPrivateData->fUpArrowsEnabled != upEnabled) { fPrivateData->fUpArrowsEnabled = upEnabled; Invalidate(_ButtonRectFor(ARROW1)); if (_DoubleArrows()) Invalidate(_ButtonRectFor(ARROW3)); } bool downEnabled = fValue < fMax; if (fPrivateData->fDownArrowsEnabled != downEnabled) { fPrivateData->fDownArrowsEnabled = downEnabled; Invalidate(_ButtonRectFor(ARROW4)); if (_DoubleArrows()) Invalidate(_ButtonRectFor(ARROW2)); } } status_t control_scrollbar(scroll_bar_info* info, BScrollBar* bar) { if (bar == NULL || info == NULL) return B_BAD_VALUE; if (bar->fPrivateData->fScrollBarInfo.double_arrows != info->double_arrows) { bar->fPrivateData->fScrollBarInfo.double_arrows = info->double_arrows; int8 multiplier = (info->double_arrows) ? 1 : -1; if (bar->fOrientation == B_VERTICAL) { bar->fPrivateData->fThumbFrame.OffsetBy(0, multiplier * B_H_SCROLL_BAR_HEIGHT); } else { bar->fPrivateData->fThumbFrame.OffsetBy(multiplier * B_V_SCROLL_BAR_WIDTH, 0); } } bar->fPrivateData->fScrollBarInfo.proportional = info->proportional; // TODO: Figure out how proportional relates to the size of the thumb // TODO: Add redraw code to reflect the changes if (info->knob >= 0 && info->knob <= 2) bar->fPrivateData->fScrollBarInfo.knob = info->knob; else return B_BAD_VALUE; if (info->min_knob_size >= SCROLL_BAR_MINIMUM_KNOB_SIZE && info->min_knob_size <= SCROLL_BAR_MAXIMUM_KNOB_SIZE) { bar->fPrivateData->fScrollBarInfo.min_knob_size = info->min_knob_size; } else return B_BAD_VALUE; return B_OK; } BSize BScrollBar::_MinSize() const { BSize minSize; if (fOrientation == B_HORIZONTAL) { minSize.width = 2 * B_V_SCROLL_BAR_WIDTH + 2 * fPrivateData->fScrollBarInfo.min_knob_size; minSize.height = B_H_SCROLL_BAR_HEIGHT; } else { minSize.width = B_V_SCROLL_BAR_WIDTH; minSize.height = 2 * B_H_SCROLL_BAR_HEIGHT + 2 * fPrivateData->fScrollBarInfo.min_knob_size; } return minSize; }