mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
1419 lines
44 KiB
C++
1419 lines
44 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
|
Copyright 2004-11 by Raw Material Software Ltd.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
JUCE can be redistributed and/or modified under the terms of the GNU General
|
|
Public License (Version 2), as published by the Free Software Foundation.
|
|
A copy of the license is included in the JUCE distribution, or can be found
|
|
online at www.gnu.org/licenses.
|
|
|
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.rawmaterialsoftware.com/juce for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
BEGIN_JUCE_NAMESPACE
|
|
|
|
//==============================================================================
|
|
class Slider::PopupDisplayComponent : public BubbleComponent,
|
|
public Timer
|
|
{
|
|
public:
|
|
PopupDisplayComponent (Slider& owner_)
|
|
: owner (owner_),
|
|
font (15.0f, Font::bold)
|
|
{
|
|
setAlwaysOnTop (true);
|
|
}
|
|
|
|
void paintContent (Graphics& g, int w, int h)
|
|
{
|
|
g.setFont (font);
|
|
g.setColour (findColour (TooltipWindow::textColourId, true));
|
|
g.drawFittedText (text, 0, 0, w, h, Justification::centred, 1);
|
|
}
|
|
|
|
void getContentSize (int& w, int& h)
|
|
{
|
|
w = font.getStringWidth (text) + 18;
|
|
h = (int) (font.getHeight() * 1.6f);
|
|
}
|
|
|
|
void updatePosition (const String& newText)
|
|
{
|
|
text = newText;
|
|
BubbleComponent::setPosition (&owner);
|
|
repaint();
|
|
}
|
|
|
|
void timerCallback()
|
|
{
|
|
owner.popupDisplay = nullptr;
|
|
}
|
|
|
|
private:
|
|
Slider& owner;
|
|
Font font;
|
|
String text;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PopupDisplayComponent);
|
|
};
|
|
|
|
//==============================================================================
|
|
Slider::Slider (const String& name)
|
|
: Component (name),
|
|
lastCurrentValue (0),
|
|
lastValueMin (0),
|
|
lastValueMax (0),
|
|
minimum (0),
|
|
maximum (10),
|
|
interval (0),
|
|
skewFactor (1.0),
|
|
velocityModeSensitivity (1.0),
|
|
velocityModeOffset (0.0),
|
|
velocityModeThreshold (1),
|
|
rotaryStart (float_Pi * 1.2f),
|
|
rotaryEnd (float_Pi * 2.8f),
|
|
numDecimalPlaces (7),
|
|
sliderRegionStart (0),
|
|
sliderRegionSize (1),
|
|
sliderBeingDragged (-1),
|
|
pixelsForFullDragExtent (250),
|
|
style (LinearHorizontal),
|
|
textBoxPos (TextBoxLeft),
|
|
textBoxWidth (80),
|
|
textBoxHeight (20),
|
|
incDecButtonMode (incDecButtonsNotDraggable),
|
|
editableText (true),
|
|
doubleClickToValue (false),
|
|
isVelocityBased (false),
|
|
userKeyOverridesVelocity (true),
|
|
rotaryStop (true),
|
|
incDecButtonsSideBySide (false),
|
|
sendChangeOnlyOnRelease (false),
|
|
popupDisplayEnabled (false),
|
|
menuEnabled (false),
|
|
menuShown (false),
|
|
scrollWheelEnabled (true),
|
|
snapsToMousePos (true),
|
|
parentForPopupDisplay (nullptr)
|
|
{
|
|
setWantsKeyboardFocus (false);
|
|
setRepaintsOnMouseActivity (true);
|
|
|
|
Slider::lookAndFeelChanged();
|
|
updateText();
|
|
|
|
currentValue.addListener (this);
|
|
valueMin.addListener (this);
|
|
valueMax.addListener (this);
|
|
}
|
|
|
|
Slider::~Slider()
|
|
{
|
|
currentValue.removeListener (this);
|
|
valueMin.removeListener (this);
|
|
valueMax.removeListener (this);
|
|
popupDisplay = nullptr;
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
void Slider::handleAsyncUpdate()
|
|
{
|
|
cancelPendingUpdate();
|
|
|
|
Component::BailOutChecker checker (this);
|
|
listeners.callChecked (checker, &SliderListener::sliderValueChanged, this); // (can't use Slider::Listener due to idiotic VC2005 bug)
|
|
}
|
|
|
|
void Slider::sendDragStart()
|
|
{
|
|
startedDragging();
|
|
|
|
Component::BailOutChecker checker (this);
|
|
listeners.callChecked (checker, &SliderListener::sliderDragStarted, this);
|
|
}
|
|
|
|
void Slider::sendDragEnd()
|
|
{
|
|
stoppedDragging();
|
|
|
|
sliderBeingDragged = -1;
|
|
|
|
Component::BailOutChecker checker (this);
|
|
listeners.callChecked (checker, &SliderListener::sliderDragEnded, this);
|
|
}
|
|
|
|
void Slider::addListener (SliderListener* const listener)
|
|
{
|
|
listeners.add (listener);
|
|
}
|
|
|
|
void Slider::removeListener (SliderListener* const listener)
|
|
{
|
|
listeners.remove (listener);
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::setSliderStyle (const SliderStyle newStyle)
|
|
{
|
|
if (style != newStyle)
|
|
{
|
|
style = newStyle;
|
|
repaint();
|
|
lookAndFeelChanged();
|
|
}
|
|
}
|
|
|
|
void Slider::setRotaryParameters (const float startAngleRadians,
|
|
const float endAngleRadians,
|
|
const bool stopAtEnd)
|
|
{
|
|
// make sure the values are sensible..
|
|
jassert (rotaryStart >= 0 && rotaryEnd >= 0);
|
|
jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f);
|
|
jassert (rotaryStart < rotaryEnd);
|
|
|
|
rotaryStart = startAngleRadians;
|
|
rotaryEnd = endAngleRadians;
|
|
rotaryStop = stopAtEnd;
|
|
}
|
|
|
|
void Slider::setVelocityBasedMode (const bool velBased)
|
|
{
|
|
isVelocityBased = velBased;
|
|
}
|
|
|
|
void Slider::setVelocityModeParameters (const double sensitivity,
|
|
const int threshold,
|
|
const double offset,
|
|
const bool userCanPressKeyToSwapMode)
|
|
{
|
|
jassert (threshold >= 0);
|
|
jassert (sensitivity > 0);
|
|
jassert (offset >= 0);
|
|
|
|
velocityModeSensitivity = sensitivity;
|
|
velocityModeOffset = offset;
|
|
velocityModeThreshold = threshold;
|
|
userKeyOverridesVelocity = userCanPressKeyToSwapMode;
|
|
}
|
|
|
|
void Slider::setSkewFactor (const double factor)
|
|
{
|
|
skewFactor = factor;
|
|
}
|
|
|
|
void Slider::setSkewFactorFromMidPoint (const double sliderValueToShowAtMidPoint)
|
|
{
|
|
if (maximum > minimum)
|
|
skewFactor = log (0.5) / log ((sliderValueToShowAtMidPoint - minimum)
|
|
/ (maximum - minimum));
|
|
}
|
|
|
|
void Slider::setMouseDragSensitivity (const int distanceForFullScaleDrag)
|
|
{
|
|
jassert (distanceForFullScaleDrag > 0);
|
|
|
|
pixelsForFullDragExtent = distanceForFullScaleDrag;
|
|
}
|
|
|
|
void Slider::setIncDecButtonsMode (const IncDecButtonMode mode)
|
|
{
|
|
if (incDecButtonMode != mode)
|
|
{
|
|
incDecButtonMode = mode;
|
|
lookAndFeelChanged();
|
|
}
|
|
}
|
|
|
|
void Slider::setTextBoxStyle (const TextEntryBoxPosition newPosition,
|
|
const bool isReadOnly,
|
|
const int textEntryBoxWidth,
|
|
const int textEntryBoxHeight)
|
|
{
|
|
if (textBoxPos != newPosition
|
|
|| editableText != (! isReadOnly)
|
|
|| textBoxWidth != textEntryBoxWidth
|
|
|| textBoxHeight != textEntryBoxHeight)
|
|
{
|
|
textBoxPos = newPosition;
|
|
editableText = ! isReadOnly;
|
|
textBoxWidth = textEntryBoxWidth;
|
|
textBoxHeight = textEntryBoxHeight;
|
|
|
|
repaint();
|
|
lookAndFeelChanged();
|
|
}
|
|
}
|
|
|
|
void Slider::setTextBoxIsEditable (const bool shouldBeEditable)
|
|
{
|
|
editableText = shouldBeEditable;
|
|
|
|
if (valueBox != nullptr)
|
|
valueBox->setEditable (shouldBeEditable && isEnabled());
|
|
}
|
|
|
|
void Slider::showTextBox()
|
|
{
|
|
jassert (editableText); // this should probably be avoided in read-only sliders.
|
|
|
|
if (valueBox != nullptr)
|
|
valueBox->showEditor();
|
|
}
|
|
|
|
void Slider::hideTextBox (const bool discardCurrentEditorContents)
|
|
{
|
|
if (valueBox != nullptr)
|
|
{
|
|
valueBox->hideEditor (discardCurrentEditorContents);
|
|
|
|
if (discardCurrentEditorContents)
|
|
updateText();
|
|
}
|
|
}
|
|
|
|
void Slider::setChangeNotificationOnlyOnRelease (const bool onlyNotifyOnRelease)
|
|
{
|
|
sendChangeOnlyOnRelease = onlyNotifyOnRelease;
|
|
}
|
|
|
|
void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse)
|
|
{
|
|
snapsToMousePos = shouldSnapToMouse;
|
|
}
|
|
|
|
void Slider::setPopupDisplayEnabled (const bool enabled,
|
|
Component* const parentComponentToUse)
|
|
{
|
|
popupDisplayEnabled = enabled;
|
|
parentForPopupDisplay = parentComponentToUse;
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::colourChanged()
|
|
{
|
|
lookAndFeelChanged();
|
|
}
|
|
|
|
void Slider::lookAndFeelChanged()
|
|
{
|
|
LookAndFeel& lf = getLookAndFeel();
|
|
|
|
if (textBoxPos != NoTextBox)
|
|
{
|
|
const String previousTextBoxContent (valueBox != nullptr ? valueBox->getText()
|
|
: getTextFromValue (currentValue.getValue()));
|
|
|
|
valueBox = nullptr;
|
|
addAndMakeVisible (valueBox = getLookAndFeel().createSliderTextBox (*this));
|
|
|
|
valueBox->setWantsKeyboardFocus (false);
|
|
valueBox->setText (previousTextBoxContent, false);
|
|
|
|
if (valueBox->isEditable() != editableText) // (avoid overriding the single/double click flags unless we have to)
|
|
valueBox->setEditable (editableText && isEnabled());
|
|
|
|
valueBox->addListener (this);
|
|
|
|
if (style == LinearBar)
|
|
valueBox->addMouseListener (this, false);
|
|
|
|
valueBox->setTooltip (getTooltip());
|
|
}
|
|
else
|
|
{
|
|
valueBox = nullptr;
|
|
}
|
|
|
|
if (style == IncDecButtons)
|
|
{
|
|
addAndMakeVisible (incButton = lf.createSliderButton (true));
|
|
incButton->addListener (this);
|
|
|
|
addAndMakeVisible (decButton = lf.createSliderButton (false));
|
|
decButton->addListener (this);
|
|
|
|
if (incDecButtonMode != incDecButtonsNotDraggable)
|
|
{
|
|
incButton->addMouseListener (this, false);
|
|
decButton->addMouseListener (this, false);
|
|
}
|
|
else
|
|
{
|
|
incButton->setRepeatSpeed (300, 100, 20);
|
|
incButton->addMouseListener (decButton, false);
|
|
|
|
decButton->setRepeatSpeed (300, 100, 20);
|
|
decButton->addMouseListener (incButton, false);
|
|
}
|
|
|
|
incButton->setTooltip (getTooltip());
|
|
decButton->setTooltip (getTooltip());
|
|
}
|
|
else
|
|
{
|
|
incButton = nullptr;
|
|
decButton = nullptr;
|
|
}
|
|
|
|
setComponentEffect (lf.getSliderEffect());
|
|
|
|
resized();
|
|
repaint();
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::setRange (const double newMin,
|
|
const double newMax,
|
|
const double newInt)
|
|
{
|
|
if (minimum != newMin
|
|
|| maximum != newMax
|
|
|| interval != newInt)
|
|
{
|
|
minimum = newMin;
|
|
maximum = newMax;
|
|
interval = newInt;
|
|
|
|
// figure out the number of DPs needed to display all values at this
|
|
// interval setting.
|
|
numDecimalPlaces = 7;
|
|
|
|
if (newInt != 0)
|
|
{
|
|
int v = abs ((int) (newInt * 10000000));
|
|
|
|
while ((v % 10) == 0)
|
|
{
|
|
--numDecimalPlaces;
|
|
v /= 10;
|
|
}
|
|
}
|
|
|
|
// keep the current values inside the new range..
|
|
if (style != TwoValueHorizontal && style != TwoValueVertical)
|
|
{
|
|
setValue (getValue(), false, false);
|
|
}
|
|
else
|
|
{
|
|
setMinValue (getMinValue(), false, false);
|
|
setMaxValue (getMaxValue(), false, false);
|
|
}
|
|
|
|
updateText();
|
|
}
|
|
}
|
|
|
|
void Slider::triggerChangeMessage (const bool synchronous)
|
|
{
|
|
if (synchronous)
|
|
handleAsyncUpdate();
|
|
else
|
|
triggerAsyncUpdate();
|
|
|
|
valueChanged();
|
|
}
|
|
|
|
void Slider::valueChanged (Value& value)
|
|
{
|
|
if (value.refersToSameSourceAs (currentValue))
|
|
{
|
|
if (style != TwoValueHorizontal && style != TwoValueVertical)
|
|
setValue (currentValue.getValue(), false, false);
|
|
}
|
|
else if (value.refersToSameSourceAs (valueMin))
|
|
setMinValue (valueMin.getValue(), false, false, true);
|
|
else if (value.refersToSameSourceAs (valueMax))
|
|
setMaxValue (valueMax.getValue(), false, false, true);
|
|
}
|
|
|
|
double Slider::getValue() const
|
|
{
|
|
// for a two-value style slider, you should use the getMinValue() and getMaxValue()
|
|
// methods to get the two values.
|
|
jassert (style != TwoValueHorizontal && style != TwoValueVertical);
|
|
|
|
return currentValue.getValue();
|
|
}
|
|
|
|
void Slider::setValue (double newValue,
|
|
const bool sendUpdateMessage,
|
|
const bool sendMessageSynchronously)
|
|
{
|
|
// for a two-value style slider, you should use the setMinValue() and setMaxValue()
|
|
// methods to set the two values.
|
|
jassert (style != TwoValueHorizontal && style != TwoValueVertical);
|
|
|
|
newValue = constrainedValue (newValue);
|
|
|
|
if (style == ThreeValueHorizontal || style == ThreeValueVertical)
|
|
{
|
|
jassert ((double) valueMin.getValue() <= (double) valueMax.getValue());
|
|
|
|
newValue = jlimit ((double) valueMin.getValue(),
|
|
(double) valueMax.getValue(),
|
|
newValue);
|
|
}
|
|
|
|
if (newValue != lastCurrentValue)
|
|
{
|
|
if (valueBox != nullptr)
|
|
valueBox->hideEditor (true);
|
|
|
|
lastCurrentValue = newValue;
|
|
|
|
// (need to do this comparison because the Value will use equalsWithSameType to compare
|
|
// the new and old values, so will generate unwanted change events if the type changes)
|
|
if (currentValue != newValue)
|
|
currentValue = newValue;
|
|
|
|
updateText();
|
|
repaint();
|
|
|
|
if (popupDisplay != nullptr)
|
|
popupDisplay->updatePosition (getTextFromValue (newValue));
|
|
|
|
if (sendUpdateMessage)
|
|
triggerChangeMessage (sendMessageSynchronously);
|
|
}
|
|
}
|
|
|
|
double Slider::getMinValue() const
|
|
{
|
|
// The minimum value only applies to sliders that are in two- or three-value mode.
|
|
jassert (style == TwoValueHorizontal || style == TwoValueVertical
|
|
|| style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
return valueMin.getValue();
|
|
}
|
|
|
|
double Slider::getMaxValue() const
|
|
{
|
|
// The maximum value only applies to sliders that are in two- or three-value mode.
|
|
jassert (style == TwoValueHorizontal || style == TwoValueVertical
|
|
|| style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
return valueMax.getValue();
|
|
}
|
|
|
|
void Slider::setMinValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues)
|
|
{
|
|
// The minimum value only applies to sliders that are in two- or three-value mode.
|
|
jassert (style == TwoValueHorizontal || style == TwoValueVertical
|
|
|| style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
newValue = constrainedValue (newValue);
|
|
|
|
if (style == TwoValueHorizontal || style == TwoValueVertical)
|
|
{
|
|
if (allowNudgingOfOtherValues && newValue > (double) valueMax.getValue())
|
|
setMaxValue (newValue, sendUpdateMessage, sendMessageSynchronously);
|
|
|
|
newValue = jmin ((double) valueMax.getValue(), newValue);
|
|
}
|
|
else
|
|
{
|
|
if (allowNudgingOfOtherValues && newValue > lastCurrentValue)
|
|
setValue (newValue, sendUpdateMessage, sendMessageSynchronously);
|
|
|
|
newValue = jmin (lastCurrentValue, newValue);
|
|
}
|
|
|
|
if (lastValueMin != newValue)
|
|
{
|
|
lastValueMin = newValue;
|
|
valueMin = newValue;
|
|
repaint();
|
|
|
|
if (popupDisplay != nullptr)
|
|
popupDisplay->updatePosition (getTextFromValue (newValue));
|
|
|
|
if (sendUpdateMessage)
|
|
triggerChangeMessage (sendMessageSynchronously);
|
|
}
|
|
}
|
|
|
|
void Slider::setMaxValue (double newValue, const bool sendUpdateMessage, const bool sendMessageSynchronously, const bool allowNudgingOfOtherValues)
|
|
{
|
|
// The maximum value only applies to sliders that are in two- or three-value mode.
|
|
jassert (style == TwoValueHorizontal || style == TwoValueVertical
|
|
|| style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
newValue = constrainedValue (newValue);
|
|
|
|
if (style == TwoValueHorizontal || style == TwoValueVertical)
|
|
{
|
|
if (allowNudgingOfOtherValues && newValue < (double) valueMin.getValue())
|
|
setMinValue (newValue, sendUpdateMessage, sendMessageSynchronously);
|
|
|
|
newValue = jmax ((double) valueMin.getValue(), newValue);
|
|
}
|
|
else
|
|
{
|
|
if (allowNudgingOfOtherValues && newValue < lastCurrentValue)
|
|
setValue (newValue, sendUpdateMessage, sendMessageSynchronously);
|
|
|
|
newValue = jmax (lastCurrentValue, newValue);
|
|
}
|
|
|
|
if (lastValueMax != newValue)
|
|
{
|
|
lastValueMax = newValue;
|
|
valueMax = newValue;
|
|
repaint();
|
|
|
|
if (popupDisplay != nullptr)
|
|
popupDisplay->updatePosition (getTextFromValue (valueMax.getValue()));
|
|
|
|
if (sendUpdateMessage)
|
|
triggerChangeMessage (sendMessageSynchronously);
|
|
}
|
|
}
|
|
|
|
void Slider::setMinAndMaxValues (double newMinValue, double newMaxValue, bool sendUpdateMessage, bool sendMessageSynchronously)
|
|
{
|
|
// The maximum value only applies to sliders that are in two- or three-value mode.
|
|
jassert (style == TwoValueHorizontal || style == TwoValueVertical
|
|
|| style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
if (newMaxValue < newMinValue)
|
|
std::swap (newMaxValue, newMinValue);
|
|
|
|
newMinValue = constrainedValue (newMinValue);
|
|
newMaxValue = constrainedValue (newMaxValue);
|
|
|
|
if (lastValueMax != newMaxValue || lastValueMin != newMinValue)
|
|
{
|
|
lastValueMax = newMaxValue;
|
|
lastValueMin = newMinValue;
|
|
valueMin = newMinValue;
|
|
valueMax = newMaxValue;
|
|
repaint();
|
|
|
|
if (sendUpdateMessage)
|
|
triggerChangeMessage (sendMessageSynchronously);
|
|
}
|
|
}
|
|
|
|
void Slider::setDoubleClickReturnValue (const bool isDoubleClickEnabled,
|
|
const double valueToSetOnDoubleClick)
|
|
{
|
|
doubleClickToValue = isDoubleClickEnabled;
|
|
doubleClickReturnValue = valueToSetOnDoubleClick;
|
|
}
|
|
|
|
double Slider::getDoubleClickReturnValue (bool& isEnabled_) const
|
|
{
|
|
isEnabled_ = doubleClickToValue;
|
|
return doubleClickReturnValue;
|
|
}
|
|
|
|
void Slider::updateText()
|
|
{
|
|
if (valueBox != nullptr)
|
|
valueBox->setText (getTextFromValue (currentValue.getValue()), false);
|
|
}
|
|
|
|
void Slider::setTextValueSuffix (const String& suffix)
|
|
{
|
|
if (textSuffix != suffix)
|
|
{
|
|
textSuffix = suffix;
|
|
updateText();
|
|
}
|
|
}
|
|
|
|
String Slider::getTextValueSuffix() const
|
|
{
|
|
return textSuffix;
|
|
}
|
|
|
|
const String Slider::getTextFromValue (double v)
|
|
{
|
|
if (getNumDecimalPlacesToDisplay() > 0)
|
|
return String (v, getNumDecimalPlacesToDisplay()) + getTextValueSuffix();
|
|
else
|
|
return String (roundToInt (v)) + getTextValueSuffix();
|
|
}
|
|
|
|
double Slider::getValueFromText (const String& text)
|
|
{
|
|
String t (text.trimStart());
|
|
|
|
if (t.endsWith (textSuffix))
|
|
t = t.substring (0, t.length() - textSuffix.length());
|
|
|
|
while (t.startsWithChar ('+'))
|
|
t = t.substring (1).trimStart();
|
|
|
|
return t.initialSectionContainingOnly ("0123456789.,-")
|
|
.getDoubleValue();
|
|
}
|
|
|
|
double Slider::proportionOfLengthToValue (double proportion)
|
|
{
|
|
if (skewFactor != 1.0 && proportion > 0.0)
|
|
proportion = exp (log (proportion) / skewFactor);
|
|
|
|
return minimum + (maximum - minimum) * proportion;
|
|
}
|
|
|
|
double Slider::valueToProportionOfLength (double value)
|
|
{
|
|
const double n = (value - minimum) / (maximum - minimum);
|
|
|
|
return skewFactor == 1.0 ? n : pow (n, skewFactor);
|
|
}
|
|
|
|
double Slider::snapValue (double attemptedValue, const bool)
|
|
{
|
|
return attemptedValue;
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::startedDragging()
|
|
{
|
|
}
|
|
|
|
void Slider::stoppedDragging()
|
|
{
|
|
}
|
|
|
|
void Slider::valueChanged()
|
|
{
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::enablementChanged()
|
|
{
|
|
repaint();
|
|
}
|
|
|
|
void Slider::setPopupMenuEnabled (const bool menuEnabled_)
|
|
{
|
|
menuEnabled = menuEnabled_;
|
|
}
|
|
|
|
void Slider::setScrollWheelEnabled (const bool enabled)
|
|
{
|
|
scrollWheelEnabled = enabled;
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::labelTextChanged (Label* label)
|
|
{
|
|
const double newValue = snapValue (getValueFromText (label->getText()), false);
|
|
|
|
if (newValue != (double) currentValue.getValue())
|
|
{
|
|
sendDragStart();
|
|
setValue (newValue, true, true);
|
|
sendDragEnd();
|
|
}
|
|
|
|
updateText(); // force a clean-up of the text, needed in case setValue() hasn't done this.
|
|
}
|
|
|
|
void Slider::buttonClicked (Button* button)
|
|
{
|
|
if (style == IncDecButtons)
|
|
{
|
|
sendDragStart();
|
|
|
|
if (button == incButton)
|
|
setValue (snapValue (getValue() + interval, false), true, true);
|
|
else if (button == decButton)
|
|
setValue (snapValue (getValue() - interval, false), true, true);
|
|
|
|
sendDragEnd();
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
double Slider::constrainedValue (double value) const
|
|
{
|
|
if (interval > 0)
|
|
value = minimum + interval * std::floor ((value - minimum) / interval + 0.5);
|
|
|
|
if (value <= minimum || maximum <= minimum)
|
|
value = minimum;
|
|
else if (value >= maximum)
|
|
value = maximum;
|
|
|
|
return value;
|
|
}
|
|
|
|
float Slider::getLinearSliderPos (const double value)
|
|
{
|
|
double sliderPosProportional;
|
|
|
|
if (maximum > minimum)
|
|
{
|
|
if (value < minimum)
|
|
{
|
|
sliderPosProportional = 0.0;
|
|
}
|
|
else if (value > maximum)
|
|
{
|
|
sliderPosProportional = 1.0;
|
|
}
|
|
else
|
|
{
|
|
sliderPosProportional = valueToProportionOfLength (value);
|
|
jassert (sliderPosProportional >= 0 && sliderPosProportional <= 1.0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sliderPosProportional = 0.5;
|
|
}
|
|
|
|
if (isVertical() || style == IncDecButtons)
|
|
sliderPosProportional = 1.0 - sliderPosProportional;
|
|
|
|
return (float) (sliderRegionStart + sliderPosProportional * sliderRegionSize);
|
|
}
|
|
|
|
bool Slider::isHorizontal() const
|
|
{
|
|
return style == LinearHorizontal
|
|
|| style == LinearBar
|
|
|| style == TwoValueHorizontal
|
|
|| style == ThreeValueHorizontal;
|
|
}
|
|
|
|
bool Slider::isVertical() const
|
|
{
|
|
return style == LinearVertical
|
|
|| style == TwoValueVertical
|
|
|| style == ThreeValueVertical;
|
|
}
|
|
|
|
bool Slider::incDecDragDirectionIsHorizontal() const
|
|
{
|
|
return incDecButtonMode == incDecButtonsDraggable_Horizontal
|
|
|| (incDecButtonMode == incDecButtonsDraggable_AutoDirection && incDecButtonsSideBySide);
|
|
}
|
|
|
|
float Slider::getPositionOfValue (const double value)
|
|
{
|
|
if (isHorizontal() || isVertical())
|
|
{
|
|
return getLinearSliderPos (value);
|
|
}
|
|
else
|
|
{
|
|
jassertfalse; // not a valid call on a slider that doesn't work linearly!
|
|
return 0.0f;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void Slider::paint (Graphics& g)
|
|
{
|
|
if (style != IncDecButtons)
|
|
{
|
|
if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
|
|
{
|
|
const float sliderPos = (float) valueToProportionOfLength (lastCurrentValue);
|
|
jassert (sliderPos >= 0 && sliderPos <= 1.0f);
|
|
|
|
getLookAndFeel().drawRotarySlider (g,
|
|
sliderRect.getX(),
|
|
sliderRect.getY(),
|
|
sliderRect.getWidth(),
|
|
sliderRect.getHeight(),
|
|
sliderPos,
|
|
rotaryStart, rotaryEnd,
|
|
*this);
|
|
}
|
|
else
|
|
{
|
|
getLookAndFeel().drawLinearSlider (g,
|
|
sliderRect.getX(),
|
|
sliderRect.getY(),
|
|
sliderRect.getWidth(),
|
|
sliderRect.getHeight(),
|
|
getLinearSliderPos (lastCurrentValue),
|
|
getLinearSliderPos (lastValueMin),
|
|
getLinearSliderPos (lastValueMax),
|
|
style,
|
|
*this);
|
|
}
|
|
|
|
if (style == LinearBar && valueBox == nullptr)
|
|
{
|
|
g.setColour (findColour (Slider::textBoxOutlineColourId));
|
|
g.drawRect (0, 0, getWidth(), getHeight(), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Slider::resized()
|
|
{
|
|
int minXSpace = 0;
|
|
int minYSpace = 0;
|
|
|
|
if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
|
|
minXSpace = 30;
|
|
else
|
|
minYSpace = 15;
|
|
|
|
const int tbw = jmax (0, jmin (textBoxWidth, getWidth() - minXSpace));
|
|
const int tbh = jmax (0, jmin (textBoxHeight, getHeight() - minYSpace));
|
|
|
|
if (style == LinearBar)
|
|
{
|
|
if (valueBox != nullptr)
|
|
valueBox->setBounds (getLocalBounds());
|
|
}
|
|
else
|
|
{
|
|
if (textBoxPos == NoTextBox)
|
|
{
|
|
sliderRect = getLocalBounds();
|
|
}
|
|
else if (textBoxPos == TextBoxLeft)
|
|
{
|
|
valueBox->setBounds (0, (getHeight() - tbh) / 2, tbw, tbh);
|
|
sliderRect.setBounds (tbw, 0, getWidth() - tbw, getHeight());
|
|
}
|
|
else if (textBoxPos == TextBoxRight)
|
|
{
|
|
valueBox->setBounds (getWidth() - tbw, (getHeight() - tbh) / 2, tbw, tbh);
|
|
sliderRect.setBounds (0, 0, getWidth() - tbw, getHeight());
|
|
}
|
|
else if (textBoxPos == TextBoxAbove)
|
|
{
|
|
valueBox->setBounds ((getWidth() - tbw) / 2, 0, tbw, tbh);
|
|
sliderRect.setBounds (0, tbh, getWidth(), getHeight() - tbh);
|
|
}
|
|
else if (textBoxPos == TextBoxBelow)
|
|
{
|
|
valueBox->setBounds ((getWidth() - tbw) / 2, getHeight() - tbh, tbw, tbh);
|
|
sliderRect.setBounds (0, 0, getWidth(), getHeight() - tbh);
|
|
}
|
|
}
|
|
|
|
const int indent = getLookAndFeel().getSliderThumbRadius (*this);
|
|
|
|
if (style == LinearBar)
|
|
{
|
|
const int barIndent = 1;
|
|
sliderRegionStart = barIndent;
|
|
sliderRegionSize = getWidth() - barIndent * 2;
|
|
|
|
sliderRect.setBounds (sliderRegionStart, barIndent,
|
|
sliderRegionSize, getHeight() - barIndent * 2);
|
|
}
|
|
else if (isHorizontal())
|
|
{
|
|
sliderRegionStart = sliderRect.getX() + indent;
|
|
sliderRegionSize = jmax (1, sliderRect.getWidth() - indent * 2);
|
|
|
|
sliderRect.setBounds (sliderRegionStart, sliderRect.getY(),
|
|
sliderRegionSize, sliderRect.getHeight());
|
|
}
|
|
else if (isVertical())
|
|
{
|
|
sliderRegionStart = sliderRect.getY() + indent;
|
|
sliderRegionSize = jmax (1, sliderRect.getHeight() - indent * 2);
|
|
|
|
sliderRect.setBounds (sliderRect.getX(), sliderRegionStart,
|
|
sliderRect.getWidth(), sliderRegionSize);
|
|
}
|
|
else
|
|
{
|
|
sliderRegionStart = 0;
|
|
sliderRegionSize = 100;
|
|
}
|
|
|
|
if (style == IncDecButtons)
|
|
{
|
|
Rectangle<int> buttonRect (sliderRect);
|
|
|
|
if (textBoxPos == TextBoxLeft || textBoxPos == TextBoxRight)
|
|
buttonRect.expand (-2, 0);
|
|
else
|
|
buttonRect.expand (0, -2);
|
|
|
|
incDecButtonsSideBySide = buttonRect.getWidth() > buttonRect.getHeight();
|
|
|
|
if (incDecButtonsSideBySide)
|
|
{
|
|
decButton->setBounds (buttonRect.removeFromLeft (buttonRect.getWidth() / 2));
|
|
decButton->setConnectedEdges (Button::ConnectedOnRight);
|
|
incButton->setConnectedEdges (Button::ConnectedOnLeft);
|
|
}
|
|
else
|
|
{
|
|
decButton->setBounds (buttonRect.removeFromBottom (buttonRect.getHeight() / 2));
|
|
decButton->setConnectedEdges (Button::ConnectedOnTop);
|
|
incButton->setConnectedEdges (Button::ConnectedOnBottom);
|
|
}
|
|
|
|
incButton->setBounds (buttonRect);
|
|
}
|
|
}
|
|
|
|
void Slider::focusOfChildComponentChanged (FocusChangeType)
|
|
{
|
|
repaint();
|
|
}
|
|
|
|
namespace SliderHelpers
|
|
{
|
|
double smallestAngleBetween (double a1, double a2) noexcept
|
|
{
|
|
return jmin (std::abs (a1 - a2),
|
|
std::abs (a1 + double_Pi * 2.0 - a2),
|
|
std::abs (a2 + double_Pi * 2.0 - a1));
|
|
}
|
|
|
|
void sliderMenuCallback (int result, Slider* slider)
|
|
{
|
|
if (slider != nullptr)
|
|
{
|
|
switch (result)
|
|
{
|
|
case 1: slider->setVelocityBasedMode (! slider->getVelocityBasedMode()); break;
|
|
case 2: slider->setSliderStyle (Slider::Rotary); break;
|
|
case 3: slider->setSliderStyle (Slider::RotaryHorizontalDrag); break;
|
|
case 4: slider->setSliderStyle (Slider::RotaryVerticalDrag); break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Slider::showPopupMenu()
|
|
{
|
|
menuShown = true;
|
|
|
|
PopupMenu m;
|
|
m.setLookAndFeel (&getLookAndFeel());
|
|
m.addItem (1, TRANS ("velocity-sensitive mode"), true, isVelocityBased);
|
|
m.addSeparator();
|
|
|
|
if (style == Rotary || style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
|
|
{
|
|
PopupMenu rotaryMenu;
|
|
rotaryMenu.addItem (2, TRANS ("use circular dragging"), true, style == Rotary);
|
|
rotaryMenu.addItem (3, TRANS ("use left-right dragging"), true, style == RotaryHorizontalDrag);
|
|
rotaryMenu.addItem (4, TRANS ("use up-down dragging"), true, style == RotaryVerticalDrag);
|
|
|
|
m.addSubMenu (TRANS ("rotary mode"), rotaryMenu);
|
|
}
|
|
|
|
m.showMenuAsync (PopupMenu::Options(),
|
|
ModalCallbackFunction::forComponent (SliderHelpers::sliderMenuCallback, this));
|
|
}
|
|
|
|
int Slider::getThumbIndexAt (const MouseEvent& e)
|
|
{
|
|
const bool isTwoValue = (style == TwoValueHorizontal || style == TwoValueVertical);
|
|
const bool isThreeValue = (style == ThreeValueHorizontal || style == ThreeValueVertical);
|
|
|
|
if (isTwoValue || isThreeValue)
|
|
{
|
|
const float mousePos = (float) (isVertical() ? e.y : e.x);
|
|
|
|
const float normalPosDistance = std::abs (getLinearSliderPos (currentValue.getValue()) - mousePos);
|
|
const float minPosDistance = std::abs (getLinearSliderPos (valueMin.getValue()) - 0.1f - mousePos);
|
|
const float maxPosDistance = std::abs (getLinearSliderPos (valueMax.getValue()) + 0.1f - mousePos);
|
|
|
|
if (isTwoValue)
|
|
return maxPosDistance <= minPosDistance ? 2 : 1;
|
|
|
|
if (normalPosDistance >= minPosDistance && maxPosDistance >= minPosDistance)
|
|
return 1;
|
|
else if (normalPosDistance >= maxPosDistance)
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Slider::mouseDown (const MouseEvent& e)
|
|
{
|
|
mouseWasHidden = false;
|
|
incDecDragged = false;
|
|
mouseDragStartPos = mousePosWhenLastDragged = e.getPosition();
|
|
|
|
if (isEnabled())
|
|
{
|
|
if (e.mods.isPopupMenu() && menuEnabled)
|
|
{
|
|
showPopupMenu();
|
|
}
|
|
else if (maximum > minimum)
|
|
{
|
|
menuShown = false;
|
|
|
|
if (valueBox != nullptr)
|
|
valueBox->hideEditor (true);
|
|
|
|
sliderBeingDragged = getThumbIndexAt (e);
|
|
|
|
minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
|
|
|
|
lastAngle = rotaryStart + (rotaryEnd - rotaryStart)
|
|
* valueToProportionOfLength (currentValue.getValue());
|
|
|
|
valueWhenLastDragged = (sliderBeingDragged == 2 ? valueMax
|
|
: (sliderBeingDragged == 1 ? valueMin
|
|
: currentValue)).getValue();
|
|
valueOnMouseDown = valueWhenLastDragged;
|
|
|
|
if (popupDisplayEnabled)
|
|
{
|
|
PopupDisplayComponent* const popup = new PopupDisplayComponent (*this);
|
|
popupDisplay = popup;
|
|
|
|
if (parentForPopupDisplay != nullptr)
|
|
parentForPopupDisplay->addChildComponent (popup);
|
|
else
|
|
popup->addToDesktop (0);
|
|
|
|
popup->setVisible (true);
|
|
}
|
|
|
|
sendDragStart();
|
|
mouseDrag (e);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Slider::mouseUp (const MouseEvent&)
|
|
{
|
|
if (isEnabled()
|
|
&& (! menuShown)
|
|
&& (maximum > minimum)
|
|
&& (style != IncDecButtons || incDecDragged))
|
|
{
|
|
restoreMouseIfHidden();
|
|
|
|
if (sendChangeOnlyOnRelease && valueOnMouseDown != (double) currentValue.getValue())
|
|
triggerChangeMessage (false);
|
|
|
|
sendDragEnd();
|
|
popupDisplay = nullptr;
|
|
|
|
if (style == IncDecButtons)
|
|
{
|
|
incButton->setState (Button::buttonNormal);
|
|
decButton->setState (Button::buttonNormal);
|
|
}
|
|
}
|
|
else if (popupDisplay != nullptr)
|
|
{
|
|
popupDisplay->startTimer (2000);
|
|
}
|
|
}
|
|
|
|
void Slider::restoreMouseIfHidden()
|
|
{
|
|
if (mouseWasHidden)
|
|
{
|
|
mouseWasHidden = false;
|
|
|
|
for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;)
|
|
Desktop::getInstance().getMouseSource(i)->enableUnboundedMouseMovement (false);
|
|
|
|
const double pos = sliderBeingDragged == 2 ? getMaxValue()
|
|
: (sliderBeingDragged == 1 ? getMinValue()
|
|
: (double) currentValue.getValue());
|
|
Point<int> mousePos;
|
|
|
|
if (style == RotaryHorizontalDrag || style == RotaryVerticalDrag)
|
|
{
|
|
mousePos = Desktop::getLastMouseDownPosition();
|
|
|
|
if (style == RotaryHorizontalDrag)
|
|
{
|
|
const double posDiff = valueToProportionOfLength (pos) - valueToProportionOfLength (valueOnMouseDown);
|
|
mousePos += Point<int> (roundToInt (pixelsForFullDragExtent * posDiff), 0);
|
|
}
|
|
else
|
|
{
|
|
const double posDiff = valueToProportionOfLength (valueOnMouseDown) - valueToProportionOfLength (pos);
|
|
mousePos += Point<int> (0, roundToInt (pixelsForFullDragExtent * posDiff));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int pixelPos = (int) getLinearSliderPos (pos);
|
|
|
|
mousePos = localPointToGlobal (Point<int> (isHorizontal() ? pixelPos : (getWidth() / 2),
|
|
isVertical() ? pixelPos : (getHeight() / 2)));
|
|
}
|
|
|
|
Desktop::setMousePosition (mousePos);
|
|
}
|
|
}
|
|
|
|
void Slider::modifierKeysChanged (const ModifierKeys& modifiers)
|
|
{
|
|
if (isEnabled()
|
|
&& style != IncDecButtons
|
|
&& style != Rotary
|
|
&& isVelocityBased == modifiers.isAnyModifierKeyDown())
|
|
{
|
|
restoreMouseIfHidden();
|
|
}
|
|
}
|
|
|
|
void Slider::handleRotaryDrag (const MouseEvent& e)
|
|
{
|
|
const int dx = e.x - sliderRect.getCentreX();
|
|
const int dy = e.y - sliderRect.getCentreY();
|
|
|
|
if (dx * dx + dy * dy > 25)
|
|
{
|
|
double angle = std::atan2 ((double) dx, (double) -dy);
|
|
while (angle < 0.0)
|
|
angle += double_Pi * 2.0;
|
|
|
|
if (rotaryStop && ! e.mouseWasClicked())
|
|
{
|
|
if (std::abs (angle - lastAngle) > double_Pi)
|
|
{
|
|
if (angle >= lastAngle)
|
|
angle -= double_Pi * 2.0;
|
|
else
|
|
angle += double_Pi * 2.0;
|
|
}
|
|
|
|
if (angle >= lastAngle)
|
|
angle = jmin (angle, (double) jmax (rotaryStart, rotaryEnd));
|
|
else
|
|
angle = jmax (angle, (double) jmin (rotaryStart, rotaryEnd));
|
|
}
|
|
else
|
|
{
|
|
while (angle < rotaryStart)
|
|
angle += double_Pi * 2.0;
|
|
|
|
if (angle > rotaryEnd)
|
|
{
|
|
if (SliderHelpers::smallestAngleBetween (angle, rotaryStart)
|
|
<= SliderHelpers::smallestAngleBetween (angle, rotaryEnd))
|
|
angle = rotaryStart;
|
|
else
|
|
angle = rotaryEnd;
|
|
}
|
|
}
|
|
|
|
const double proportion = (angle - rotaryStart) / (rotaryEnd - rotaryStart);
|
|
valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, proportion));
|
|
lastAngle = angle;
|
|
}
|
|
}
|
|
|
|
void Slider::handleAbsoluteDrag (const MouseEvent& e)
|
|
{
|
|
const int mousePos = (isHorizontal() || style == RotaryHorizontalDrag) ? e.x : e.y;
|
|
|
|
double scaledMousePos = (mousePos - sliderRegionStart) / (double) sliderRegionSize;
|
|
|
|
if (style == RotaryHorizontalDrag
|
|
|| style == RotaryVerticalDrag
|
|
|| style == IncDecButtons
|
|
|| ((style == LinearHorizontal || style == LinearVertical || style == LinearBar)
|
|
&& ! snapsToMousePos))
|
|
{
|
|
const int mouseDiff = (style == RotaryHorizontalDrag
|
|
|| style == LinearHorizontal
|
|
|| style == LinearBar
|
|
|| (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
|
|
? e.x - mouseDragStartPos.getX()
|
|
: mouseDragStartPos.getY() - e.y;
|
|
|
|
double newPos = valueToProportionOfLength (valueOnMouseDown)
|
|
+ mouseDiff * (1.0 / pixelsForFullDragExtent);
|
|
|
|
valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, newPos));
|
|
|
|
if (style == IncDecButtons)
|
|
{
|
|
incButton->setState (mouseDiff < 0 ? Button::buttonNormal : Button::buttonDown);
|
|
decButton->setState (mouseDiff > 0 ? Button::buttonNormal : Button::buttonDown);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isVertical())
|
|
scaledMousePos = 1.0 - scaledMousePos;
|
|
|
|
valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, scaledMousePos));
|
|
}
|
|
}
|
|
|
|
void Slider::handleVelocityDrag (const MouseEvent& e)
|
|
{
|
|
const int mouseDiff = (isHorizontal() || style == RotaryHorizontalDrag
|
|
|| (style == IncDecButtons && incDecDragDirectionIsHorizontal()))
|
|
? e.x - mousePosWhenLastDragged.getX()
|
|
: e.y - mousePosWhenLastDragged.getY();
|
|
|
|
const double maxSpeed = jmax (200, sliderRegionSize);
|
|
double speed = jlimit (0.0, maxSpeed, (double) abs (mouseDiff));
|
|
|
|
if (speed != 0)
|
|
{
|
|
speed = 0.2 * velocityModeSensitivity
|
|
* (1.0 + std::sin (double_Pi * (1.5 + jmin (0.5, velocityModeOffset
|
|
+ jmax (0.0, (double) (speed - velocityModeThreshold))
|
|
/ maxSpeed))));
|
|
|
|
if (mouseDiff < 0)
|
|
speed = -speed;
|
|
|
|
if (isVertical() || style == RotaryVerticalDrag
|
|
|| (style == IncDecButtons && ! incDecDragDirectionIsHorizontal()))
|
|
speed = -speed;
|
|
|
|
const double currentPos = valueToProportionOfLength (valueWhenLastDragged);
|
|
|
|
valueWhenLastDragged = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + speed));
|
|
|
|
e.source.enableUnboundedMouseMovement (true, false);
|
|
mouseWasHidden = true;
|
|
}
|
|
}
|
|
|
|
void Slider::mouseDrag (const MouseEvent& e)
|
|
{
|
|
if (isEnabled()
|
|
&& (! menuShown)
|
|
&& (maximum > minimum)
|
|
&& ! (style == LinearBar && e.mouseWasClicked() && valueBox != nullptr && valueBox->isEditable()))
|
|
{
|
|
if (style == Rotary)
|
|
{
|
|
handleRotaryDrag (e);
|
|
}
|
|
else
|
|
{
|
|
if (style == IncDecButtons && ! incDecDragged)
|
|
{
|
|
if (e.getDistanceFromDragStart() < 10 || e.mouseWasClicked())
|
|
return;
|
|
|
|
incDecDragged = true;
|
|
mouseDragStartPos = e.getPosition();
|
|
}
|
|
|
|
if (isVelocityBased == (userKeyOverridesVelocity && e.mods.testFlags (ModifierKeys::ctrlModifier
|
|
| ModifierKeys::commandModifier
|
|
| ModifierKeys::altModifier))
|
|
|| (maximum - minimum) / sliderRegionSize < interval)
|
|
handleAbsoluteDrag (e);
|
|
else
|
|
handleVelocityDrag (e);
|
|
}
|
|
|
|
valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged);
|
|
|
|
if (sliderBeingDragged == 0)
|
|
{
|
|
setValue (snapValue (valueWhenLastDragged, true),
|
|
! sendChangeOnlyOnRelease, true);
|
|
}
|
|
else if (sliderBeingDragged == 1)
|
|
{
|
|
setMinValue (snapValue (valueWhenLastDragged, true),
|
|
! sendChangeOnlyOnRelease, false, true);
|
|
|
|
if (e.mods.isShiftDown())
|
|
setMaxValue (getMinValue() + minMaxDiff, false, false, true);
|
|
else
|
|
minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
|
|
}
|
|
else if (sliderBeingDragged == 2)
|
|
{
|
|
setMaxValue (snapValue (valueWhenLastDragged, true),
|
|
! sendChangeOnlyOnRelease, false, true);
|
|
|
|
if (e.mods.isShiftDown())
|
|
setMinValue (getMaxValue() - minMaxDiff, false, false, true);
|
|
else
|
|
minMaxDiff = (double) valueMax.getValue() - (double) valueMin.getValue();
|
|
}
|
|
|
|
mousePosWhenLastDragged = e.getPosition();
|
|
}
|
|
}
|
|
|
|
void Slider::mouseDoubleClick (const MouseEvent&)
|
|
{
|
|
if (doubleClickToValue
|
|
&& isEnabled()
|
|
&& style != IncDecButtons
|
|
&& minimum <= doubleClickReturnValue
|
|
&& maximum >= doubleClickReturnValue)
|
|
{
|
|
sendDragStart();
|
|
setValue (doubleClickReturnValue, true, true);
|
|
sendDragEnd();
|
|
}
|
|
}
|
|
|
|
void Slider::mouseWheelMove (const MouseEvent& e, float wheelIncrementX, float wheelIncrementY)
|
|
{
|
|
if (scrollWheelEnabled && isEnabled()
|
|
&& style != TwoValueHorizontal
|
|
&& style != TwoValueVertical)
|
|
{
|
|
if (maximum > minimum && ! e.mods.isAnyMouseButtonDown())
|
|
{
|
|
if (valueBox != nullptr)
|
|
valueBox->hideEditor (false);
|
|
|
|
const double value = (double) currentValue.getValue();
|
|
const double proportionDelta = (wheelIncrementX != 0 ? -wheelIncrementX : wheelIncrementY) * 0.15f;
|
|
const double currentPos = valueToProportionOfLength (value);
|
|
const double newValue = proportionOfLengthToValue (jlimit (0.0, 1.0, currentPos + proportionDelta));
|
|
|
|
double delta = (newValue != value)
|
|
? jmax (std::abs (newValue - value), interval) : 0;
|
|
|
|
if (value > newValue)
|
|
delta = -delta;
|
|
|
|
sendDragStart();
|
|
setValue (snapValue (value + delta, false), true, true);
|
|
sendDragEnd();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Component::mouseWheelMove (e, wheelIncrementX, wheelIncrementY);
|
|
}
|
|
}
|
|
|
|
void SliderListener::sliderDragStarted (Slider*) // (can't write Slider::Listener due to idiotic VC2005 bug)
|
|
{
|
|
}
|
|
|
|
void SliderListener::sliderDragEnded (Slider*)
|
|
{
|
|
}
|
|
|
|
|
|
END_JUCE_NAMESPACE
|