Plus/Minus Dials

Introduction

In this article, I will explain how to implement a RotarySlider that can move the knob left and right from the center position as shown below:

Downloading

The basic slider above can be downloaded from the repository below. This has not yet been implemented so that it can be moved in the plus-minus directions.

GitHub - szkkng/ModernDial: A modern dial designed by suzuki kentaro
A modern dial designed by suzuki kentaro. Contribute to szkkng/ModernDial development by creating an account on GitHub.
$ git clone https://github.com/szkkng/ModernDial.git

In this article, I will not explain the detailed implementation of this slider, but will focus only on the subject of this article. If you want to know more about the basic implementation of this slider, please check out the following article:

Dial Customization

Improving drawRotarySlider()

At the moment, even if you set the dial to take a range of plus-minus, the starting point will be from the left, as shown below:

Let’s improve the drawRotarySlider() of the LookAndFeel class to make the center the starting point. The following part of the CustomLookAndFeel.cpp file needs to be changed:

void CustomLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
                                          const float rotaryStartAngle, const float rotaryEndAngle, juce::Slider& slider)
{
・・・
    juce::Path valueArc;
    valueArc.addCentredArc (bounds.getCentreX(),
                            bounds.getCentreY(),
                            arcRadius,
                            arcRadius,
                            0.0f,
                            rotaryStartAngle,
                            toAngle,
                            true);
        
    g.setColour (fill);
    g.strokePath (valueArc, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
・・・
}

Use the if statement to rewrite this part as follows:

void CustomLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
                                          const float rotaryStartAngle, const float rotaryEndAngle, juce::Slider& slider)
{
・・・
    if (slider.getMinimum() < 0)
    {
        auto radian = juce::MathConstants<float>::twoPi - rotaryStartAngle;
        
        if (slider.getValue() < 0)
        {
            juce::Path valueArcMinus;
            valueArcMinus.addCentredArc (bounds.getCentreX(),
                                         bounds.getCentreY(),
                                         arcRadius,
                                         arcRadius,
                                         0.0f,
                                         0.0f,
                                         juce::jmap (sliderPos, 0.5f, 0.0f, 0.0f, -radian),
                                         true);

            g.setColour (fill);
            g.strokePath (valueArcMinus, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
        }
        else
        {
            juce::Path valueArcPlus;
            valueArcPlus.addCentredArc (bounds.getCentreX(),
                                        bounds.getCentreY(),
                                        arcRadius,
                                        arcRadius,
                                        0.0f,
                                        0.0f,
                                        juce::jmap (sliderPos, 0.5f, 1.0f, 0.0f, radian),
                                        true);

            g.setColour (fill);
            g.strokePath (valueArcPlus, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
        }
    }
    else
    {
        juce::Path valueArc;
        valueArc.addCentredArc (bounds.getCentreX(),
                                bounds.getCentreY(),
                                arcRadius,
                                arcRadius,
                                0.0f,
                                rotaryStartAngle,
                                toAngle,
                                true);
        
        g.setColour (fill);
        g.strokePath (valueArc, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
    }
・・・
}

What we did was very simple. If the range of the dial includes minus, the center will be the starting point; otherwise, the left will be the starting point as usual. In the former case, there is a further conditional split between the case where the value is minus and plus. The particularly important part is in juce::jmap(). In this function, we map the range that the sliderPos variable (0.0f ~ 1.0f), which stores the value of the knob’s position, can take and the range that it can move in the plus-minus direction.

Changing dial settings

Then, let’s change the range of the dial to include minus values.

ModernDial::ModernDial()
{
・・・
    setRange (-24.0, 24.0, 0.1);
    setValue (0.0);
    setDoubleClickReturnValue (true, 0.0);
    setTextValueSuffix (" dB");
    setNumDecimalPlacesToDisplay (1);
    onValueChange = [&]()
    {
        if (-10 < getValue() && getValue() < 10)
            setNumDecimalPlacesToDisplay (1);
        else
            setNumDecimalPlacesToDisplay (0);
    };
}

Building

Now let’s build and try to move the knob. The knobs on the slider should move from the center. You can also change the range of the slider back to 0~100 as shown below:

Conclusion

In this article, I explained how to implement the dial knob so that it can take a plus-minus range starting from the center. This article was shorter than usual, but I’ll try to write more articles about these little tips in the future.

Finally, if you have any suggestions on how to implement this more efficiently, please let me know in the comments or DM. Thank you for reading to the end!

References

Comments

Copied title and URL