Range Slider


#1

I had the need to develop a two value slider whereby users can move both min and max values at the same time by clicking on the slider's range and dragging. I'm posting the code here in case anyone might need to develop something similar.

p.s. my screen grabbing software doesn't pick up the mouse cursor, so you'll just have to imagine it's there :)

class RangeSlider : public Slider
{
    
public:
        RangeSlider(String text):
        Slider(text),
        thumbWidth(20)
        {
            setSliderStyle(SliderStyle::TwoValueHorizontal);
        }
        
        ~RangeSlider() {};

private:
        void mouseDown(const MouseEvent& event)
        {
            topThumbDownX = valueToProportionOfLength(getMinValue())*getWidth();
            bottomThumbDownX = valueToProportionOfLength(getMaxValue())*getWidth();
        }

        void mouseDrag(const MouseEvent& event)
        {
            const float sliderMin = getMinValue();
            const float sliderMax = getMaxValue();
            const int minPosition = valueToProportionOfLength(getMinimum())*getWidth();
            const int maxPosition = valueToProportionOfLength(getMaximum())*getWidth();
            const float currentMouseX = event.getPosition().getX();
            const float bottomThumbPosition = valueToProportionOfLength(sliderMin)*getWidth();
            const float topThumbPosition = valueToProportionOfLength(sliderMax)*getWidth();
            const int distanceFromStart = event.getDistanceFromDragStartX();
            
            if(currentMouseX>bottomThumbPosition+thumbWidth && currentMouseX<topThumbPosition-thumbWidth)
            {        
                if(topThumbPosition>minPosition && bottomThumbPosition<maxPosition)
                {
                setMinValue(proportionOfLengthToValue(topThumbDownX+distanceFromStart)/getWidth());        
                setMaxValue(proportionOfLengthToValue(bottomThumbDownX+distanceFromStart)/getWidth());    
                }
            }
            
            else if(event.getPosition().getY()>getHeight()/2.f)
            {
                if(currentMouseX>topThumbPosition-thumbWidth && currentMouseX<topThumbPosition+thumbWidth)
                    setMaxValue(proportionOfLengthToValue(currentMouseX/getWidth()));                                            
            }
            else
            {
                if(currentMouseX>bottomThumbPosition-thumbWidth && currentMouseX<bottomThumbPosition+thumbWidth)
                    setMinValue(proportionOfLengthToValue(currentMouseX/getWidth()));                
            }
            
        }
        
        int topThumbDownX, bottomThumbDownX;
        const int thumbWidth;
};

#2

really cool, thanks! Great for a stereo panner!


#3

Nice! :) Thanks for posting


#4

Wery cool - could be used for altering range of midi notes passed through.


#5

I just made a quick edit; turns out it's a better idea to check to see if the user is moving the range BEFORE checking to see if they are moving the min max values :)


#6

Thank you.

I suggest to change

 setMinValue (proportionOfLengthToValue(topThumbDownX+distanceFromStart)/getWidth());        
 setMaxValue (proportionOfLengthToValue(bottomThumbDownX+distanceFromStart)/getWidth());

to

 setMinValue (proportionOfLengthToValue (float (topThumbDownX + distanceFromStart) / getWidth()));        
 setMaxValue (proportionOfLengthToValue (float (bottomThumbDownX + distanceFromStart) / getWidth()));

( put the getWidth() into the proportionOfLengthToValue(). Add a float convertion, otherwise the argument will always be 0.)

Additionaly, I have changed it to work with fast mouse movements. I also didn't like that you had to click to the upper half to set the min value and the lower half to set the max value.

Here is my take:

class RangeSlider  : public Slider
{
public:
    RangeSlider();
    
    ~RangeSlider();
    
private:
    void mouseDown (const MouseEvent& event) override;
    void mouseDrag (const MouseEvent& event) override;
    void valueChanged() override;

    bool mouseDragBetweenThumbs;
    float xMinAtThumbDown;
    float xMaxAtThumbDown;
};

 

RangeSlider::RangeSlider ()
  : mouseDragBetweenThumbs {false}
{
    setSliderStyle (Slider::TwoValueHorizontal);
}

RangeSlider::~RangeSlider ()
{
}

// To enable the section between the two thumbs to be draggable.
void RangeSlider::mouseDown (const MouseEvent& event)
{
    const float currentMouseX = event.getPosition().getX();
    const int thumbRadius = getLookAndFeel().getSliderThumbRadius (*this);
    xMinAtThumbDown = valueToProportionOfLength (getMinValue()) * getWidth();
    xMaxAtThumbDown = valueToProportionOfLength (getMaxValue()) * getWidth();

    if (currentMouseX > xMinAtThumbDown + thumbRadius && currentMouseX < xMaxAtThumbDown - thumbRadius)
    {
        mouseDragBetweenThumbs = true;
    }
    else
    {
        mouseDragBetweenThumbs = false;
        Slider::mouseDown (event);
    }
}

// To enable the section between the two thumbs to be draggable.
void RangeSlider::mouseDrag (const MouseEvent& event)
{
    const float distanceFromStart = event.getDistanceFromDragStartX();
    
    if (mouseDragBetweenThumbs)
    {        
        setMinValue (proportionOfLengthToValue ((xMinAtThumbDown + distanceFromStart) / getWidth()));   
        setMaxValue (proportionOfLengthToValue ((xMaxAtThumbDown + distanceFromStart) / getWidth()));    
    }
    else
    {
        Slider::mouseDrag (event);
    }  
}

// This makes one thumb slide if the other is moved against it.
void RangeSlider::valueChanged()
{   
    if (getMinValue() == getMaxValue())
    {
        const int minimalIntervalBetweenMinAndMax = 1;
        if (getMaxValue() + minimalIntervalBetweenMinAndMax <= getMaximum())
        {
            setMaxValue(getMaxValue() + minimalIntervalBetweenMinAndMax);
        }
        else
        {
            setMinValue(getMinValue() - minimalIntervalBetweenMinAndMax);
        }
    }
}

#7

Nice one. A prime candidate for a Juce module?