|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- class ScrollBar::ScrollbarButton : public Button
- {
- public:
- ScrollbarButton (int direc, ScrollBar& s)
- : Button (String()), direction (direc), owner (s)
- {
- setWantsKeyboardFocus (false);
- }
-
- void paintButton (Graphics& g, bool over, bool down) override
- {
- getLookAndFeel().drawScrollbarButton (g, owner, getWidth(), getHeight(),
- direction, owner.isVertical(), over, down);
- }
-
- void clicked() override
- {
- owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1);
- }
-
- using Button::clicked;
-
- int direction;
-
- private:
- ScrollBar& owner;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollbarButton)
- };
-
-
- //==============================================================================
- ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
- {
- setRepaintsOnMouseActivity (true);
- setFocusContainerType (FocusContainerType::keyboardFocusContainer);
- }
-
- ScrollBar::~ScrollBar()
- {
- upButton.reset();
- downButton.reset();
- }
-
- //==============================================================================
- void ScrollBar::setRangeLimits (Range<double> newRangeLimit, NotificationType notification)
- {
- if (totalRange != newRangeLimit)
- {
- totalRange = newRangeLimit;
- setCurrentRange (visibleRange, notification);
- updateThumbPosition();
- }
- }
-
- void ScrollBar::setRangeLimits (double newMinimum, double newMaximum, NotificationType notification)
- {
- jassert (newMaximum >= newMinimum); // these can't be the wrong way round!
- setRangeLimits (Range<double> (newMinimum, newMaximum), notification);
- }
-
- bool ScrollBar::setCurrentRange (Range<double> newRange, NotificationType notification)
- {
- auto constrainedRange = totalRange.constrainRange (newRange);
-
- if (visibleRange != constrainedRange)
- {
- visibleRange = constrainedRange;
-
- updateThumbPosition();
-
- if (notification != dontSendNotification)
- triggerAsyncUpdate();
-
- if (notification == sendNotificationSync)
- handleUpdateNowIfNeeded();
-
- return true;
- }
-
- return false;
- }
-
- void ScrollBar::setCurrentRange (double newStart, double newSize, NotificationType notification)
- {
- setCurrentRange (Range<double> (newStart, newStart + newSize), notification);
- }
-
- void ScrollBar::setCurrentRangeStart (double newStart, NotificationType notification)
- {
- setCurrentRange (visibleRange.movedToStartAt (newStart), notification);
- }
-
- void ScrollBar::setSingleStepSize (double newSingleStepSize) noexcept
- {
- singleStepSize = newSingleStepSize;
- }
-
- bool ScrollBar::moveScrollbarInSteps (int howManySteps, NotificationType notification)
- {
- return setCurrentRange (visibleRange + howManySteps * singleStepSize, notification);
- }
-
- bool ScrollBar::moveScrollbarInPages (int howManyPages, NotificationType notification)
- {
- return setCurrentRange (visibleRange + howManyPages * visibleRange.getLength(), notification);
- }
-
- bool ScrollBar::scrollToTop (NotificationType notification)
- {
- return setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit()), notification);
- }
-
- bool ScrollBar::scrollToBottom (NotificationType notification)
- {
- return setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit()), notification);
- }
-
- void ScrollBar::setButtonRepeatSpeed (int newInitialDelay,
- int newRepeatDelay,
- int newMinimumDelay)
- {
- initialDelayInMillisecs = newInitialDelay;
- repeatDelayInMillisecs = newRepeatDelay;
- minimumDelayInMillisecs = newMinimumDelay;
-
- if (upButton != nullptr)
- {
- upButton ->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
- downButton->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
- }
- }
-
- //==============================================================================
- void ScrollBar::addListener (Listener* listener)
- {
- listeners.add (listener);
- }
-
- void ScrollBar::removeListener (Listener* listener)
- {
- listeners.remove (listener);
- }
-
- void ScrollBar::handleAsyncUpdate()
- {
- auto start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility)
- listeners.call ([this, start] (Listener& l) { l.scrollBarMoved (this, start); });
- }
-
- //==============================================================================
- void ScrollBar::updateThumbPosition()
- {
- auto minimumScrollBarThumbSize = getLookAndFeel().getMinimumScrollbarThumbSize (*this);
-
- int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength()
- : thumbAreaSize);
-
- if (newThumbSize < minimumScrollBarThumbSize)
- newThumbSize = jmin (minimumScrollBarThumbSize, thumbAreaSize - 1);
-
- if (newThumbSize > thumbAreaSize)
- newThumbSize = thumbAreaSize;
-
- int newThumbStart = thumbAreaStart;
-
- if (totalRange.getLength() > visibleRange.getLength())
- newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize))
- / (totalRange.getLength() - visibleRange.getLength()));
-
- Component::setVisible (getVisibility());
-
- if (thumbStart != newThumbStart || thumbSize != newThumbSize)
- {
- auto repaintStart = jmin (thumbStart, newThumbStart) - 4;
- auto repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart;
-
- if (vertical)
- repaint (0, repaintStart, getWidth(), repaintSize);
- else
- repaint (repaintStart, 0, repaintSize, getHeight());
-
- thumbStart = newThumbStart;
- thumbSize = newThumbSize;
- }
- }
-
- void ScrollBar::setOrientation (bool shouldBeVertical)
- {
- if (vertical != shouldBeVertical)
- {
- vertical = shouldBeVertical;
-
- if (upButton != nullptr)
- {
- upButton->direction = vertical ? 0 : 3;
- downButton->direction = vertical ? 2 : 1;
- }
-
- updateThumbPosition();
- }
- }
-
- void ScrollBar::setAutoHide (bool shouldHideWhenFullRange)
- {
- autohides = shouldHideWhenFullRange;
- updateThumbPosition();
- }
-
- bool ScrollBar::autoHides() const noexcept
- {
- return autohides;
- }
-
- //==============================================================================
- void ScrollBar::paint (Graphics& g)
- {
- if (thumbAreaSize > 0)
- {
- auto& lf = getLookAndFeel();
-
- auto thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this))
- ? thumbSize : 0;
-
- if (vertical)
- lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize,
- vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
- else
- lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(),
- vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
- }
- }
-
- void ScrollBar::lookAndFeelChanged()
- {
- setComponentEffect (getLookAndFeel().getScrollbarEffect());
-
- if (isVisible())
- resized();
- }
-
- void ScrollBar::resized()
- {
- auto length = vertical ? getHeight() : getWidth();
-
- auto& lf = getLookAndFeel();
- bool buttonsVisible = lf.areScrollbarButtonsVisible();
- int buttonSize = 0;
-
- if (buttonsVisible)
- {
- if (upButton == nullptr)
- {
- upButton .reset (new ScrollbarButton (vertical ? 0 : 3, *this));
- downButton.reset (new ScrollbarButton (vertical ? 2 : 1, *this));
-
- addAndMakeVisible (upButton.get());
- addAndMakeVisible (downButton.get());
-
- setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs);
- }
-
- buttonSize = jmin (lf.getScrollbarButtonSize (*this), length / 2);
- }
- else
- {
- upButton.reset();
- downButton.reset();
- }
-
- if (length < 32 + lf.getMinimumScrollbarThumbSize (*this))
- {
- thumbAreaStart = length / 2;
- thumbAreaSize = 0;
- }
- else
- {
- thumbAreaStart = buttonSize;
- thumbAreaSize = length - 2 * buttonSize;
- }
-
- if (upButton != nullptr)
- {
- auto r = getLocalBounds();
-
- if (vertical)
- {
- upButton->setBounds (r.removeFromTop (buttonSize));
- downButton->setBounds (r.removeFromBottom (buttonSize));
- }
- else
- {
- upButton->setBounds (r.removeFromLeft (buttonSize));
- downButton->setBounds (r.removeFromRight (buttonSize));
- }
- }
-
- updateThumbPosition();
- }
-
- void ScrollBar::parentHierarchyChanged()
- {
- lookAndFeelChanged();
- }
-
- void ScrollBar::mouseDown (const MouseEvent& e)
- {
- isDraggingThumb = false;
- lastMousePos = vertical ? e.y : e.x;
- dragStartMousePos = lastMousePos;
- dragStartRange = visibleRange.getStart();
-
- if (dragStartMousePos < thumbStart)
- {
- moveScrollbarInPages (-1);
- startTimer (400);
- }
- else if (dragStartMousePos >= thumbStart + thumbSize)
- {
- moveScrollbarInPages (1);
- startTimer (400);
- }
- else
- {
- isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this))
- && (thumbAreaSize > thumbSize);
- }
- }
-
- void ScrollBar::mouseDrag (const MouseEvent& e)
- {
- auto mousePos = vertical ? e.y : e.x;
-
- if (isDraggingThumb && lastMousePos != mousePos && thumbAreaSize > thumbSize)
- {
- auto deltaPixels = mousePos - dragStartMousePos;
-
- setCurrentRangeStart (dragStartRange
- + deltaPixels * (totalRange.getLength() - visibleRange.getLength())
- / (thumbAreaSize - thumbSize));
- }
-
- lastMousePos = mousePos;
- }
-
- void ScrollBar::mouseUp (const MouseEvent&)
- {
- isDraggingThumb = false;
- stopTimer();
- repaint();
- }
-
- void ScrollBar::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
- {
- float increment = 10.0f * (vertical ? wheel.deltaY : wheel.deltaX);
-
- if (increment < 0)
- increment = jmin (increment, -1.0f);
- else if (increment > 0)
- increment = jmax (increment, 1.0f);
-
- setCurrentRange (visibleRange - singleStepSize * increment);
- }
-
- void ScrollBar::timerCallback()
- {
- if (isMouseButtonDown())
- {
- startTimer (40);
-
- if (lastMousePos < thumbStart)
- setCurrentRange (visibleRange - visibleRange.getLength());
- else if (lastMousePos > thumbStart + thumbSize)
- setCurrentRangeStart (visibleRange.getEnd());
- }
- else
- {
- stopTimer();
- }
- }
-
- bool ScrollBar::keyPressed (const KeyPress& key)
- {
- if (isVisible())
- {
- if (key == KeyPress::upKey || key == KeyPress::leftKey) return moveScrollbarInSteps (-1);
- if (key == KeyPress::downKey || key == KeyPress::rightKey) return moveScrollbarInSteps (1);
- if (key == KeyPress::pageUpKey) return moveScrollbarInPages (-1);
- if (key == KeyPress::pageDownKey) return moveScrollbarInPages (1);
- if (key == KeyPress::homeKey) return scrollToTop();
- if (key == KeyPress::endKey) return scrollToBottom();
- }
-
- return false;
- }
-
- void ScrollBar::setVisible (bool shouldBeVisible)
- {
- if (userVisibilityFlag != shouldBeVisible)
- {
- userVisibilityFlag = shouldBeVisible;
- Component::setVisible (getVisibility());
- }
- }
-
- bool ScrollBar::getVisibility() const noexcept
- {
- if (! userVisibilityFlag)
- return false;
-
- return (! autohides) || (totalRange.getLength() > visibleRange.getLength()
- && visibleRange.getLength() > 0.0);
- }
-
- //==============================================================================
- std::unique_ptr<AccessibilityHandler> ScrollBar::createAccessibilityHandler()
- {
- class ValueInterface : public AccessibilityRangedNumericValueInterface
- {
- public:
- explicit ValueInterface (ScrollBar& scrollBarToWrap) : scrollBar (scrollBarToWrap) {}
-
- bool isReadOnly() const override { return false; }
-
- double getCurrentValue() const override { return scrollBar.getCurrentRangeStart(); }
- void setValue (double newValue) override { scrollBar.setCurrentRangeStart (newValue); }
-
- AccessibleValueRange getRange() const override
- {
- if (scrollBar.getRangeLimit().isEmpty())
- return {};
-
- return { { scrollBar.getMinimumRangeLimit(), scrollBar.getMaximumRangeLimit() },
- scrollBar.getSingleStepSize() };
- }
-
- private:
- ScrollBar& scrollBar;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueInterface)
- };
-
- return std::make_unique<AccessibilityHandler> (*this,
- AccessibilityRole::scrollBar,
- AccessibilityActions{},
- AccessibilityHandler::Interfaces { std::make_unique<ValueInterface> (*this) });
- }
-
- } // namespace juce
|