Center area of a rotary slider

gui

#1

The rotary slider has a circular area in its center that triggers the sliderDragStarted event when clicked but does not change the slider value.
I have been struggeling for a while without success to find the code that sets the size of that area.
For my application I need to enlarge that area a bit and make it stand out visually.
Where would I have to look?
Thanks!


#2

Unfortunately it seems, it is not configurable by look and feel classes. It is hardcoded here:


#3

You could always use a hitTest to make a larger area non-responsive.

The hard-coded value is really just there to avoid out-of-range values, because a click that’s any closer to the centre would make it impossible to accurately judge the starting angle, so it’d go nuts when you drag it.


#4

Thanks Daniel for digging that up. At least I know now where to start.
Since the implementation is part of Slider::Pimpl I will have to subclass both that and the Slider class. From then on it does not look to difficult to take control.

Thanks for the suggestion. I am afraid though I won’t be able to use it. I understand the intention of the original implementation. However my use case is a bit different from that.
I am currently porting my existing plugins to the Juce Platform. All of them use a rotary slider that also works as a Button (kind of). I need to recreate that same behaviour with the new framework.
A defined and sufficiently large center area allows the user to toggle the button state without changing the slider value.
Now that I know where and how the value is set It won’t be a problem to implement a solution.


#5

In that case I would stack a Button on top of the slider and override its hitTest to react to a round area, like Jules proposed.
If the button already handled the mouse events, the slider won’t get them. Just make sure to add the button before the slider, I don’t know how the components are traversed.
I think there exists a z-order for components as well IIRC?


#6

No it is much easier. I don’t need an actual button because I can use the sliderDragStarted event in place of the button click. It is fired whenever the slider is clicked on. I have already made a test implemetation where clicking the slider (anywhere, not only in the center) activates a page with sub controls. It also connects the slider to a shared status bar label. This allows to read and set values for the currently activated slider without having to have a dedicated label next to each slider. It works like charm. All that’s left to do is to enlarge the area that triggers the sliderDragStarted event and not the sliderValueChanged event.


#7

I see. That’s up to you.
The problem is, that the pimpl class has no virtual methods to override, so you end up rewriting/copying a lot. That cuts you off from further slider goodies and evtl. bugfixes.

I dont see, how adding a round shaped button placed on top is more difficult, but as I said, it’s up to you.
You can even leave it transparent…

class RoundButton : public TextButton {
public:
    bool hitTest (int x, int y) override {
        return getBounds().getCentre().getDistanceFrom (Point<int> (x, y)) < 45;
    }
    void paint (Graphics&) override {}
}

ScopedPointer<RoundButton> button = new RoundButton();
button->setOpaque (false);
addAndMakeVisible (button);
ScopedPointer<Slider> slider = new Slider();
addAndMakeVisible (slider);
slider->toBehind (button);

#8

I have now taken yet another road. Instead of subclassing Slider and Slider::Pimpl I have changed the implementation. My additions are so uninvasive and small that i will be able to maintain my fork and merge with future updates.
(And maybe there is even a chance to merge upstream?)

Advantages over other suggested approaches:

  • Mousewheel still works in the center of the slider
  • Clicking the center and dragging outside still changes slider value
  • much tidier and easier to use and maintain.

I can now control the radius of the inner area as easy as that:

class customSlider : public Slider
{
    (...)
public:
    void resized() override {
        Slider::resized();
        float radius = 0.5 * std::min(getBounds().getWidth(), 
            getBounds().getHeight());
        setInnerRadius(0.6 * radius);
    }
};

Well here is what I have added:

juce_Slider.h

    /** Changes the radius of rotary slider center area. 
        Clicking/Dragging within that area will not change 
        the slider value.

        @param newStyle         innerRadius
    */
    void setInnerRadius(float innerRadius);

juce_Slider.cpp

    float squareInnerRadius = 25.0f;
    
    void handleRotaryDrag (const MouseEvent& e)
    {
        (...)
        if (dx * dx + dy * dy > squareInnerRadius)
        {
            (...)
(...)

void Slider::setInnerRadius(float innerRadius) {
    pimpl->squareInnerRadius = innerRadius * innerRadius;
}

#9

Components are always added from the back to the front, so if you need to have the slider behind the button, you should add the slider, and then the button.
The addChildComponent() and addAndMakeVisible() methods have a second default argument which can alter this default behavior, should you have the need to do so.

@soundbytes1, have you considered the possibility of registering a MouseListener for your Slider? You could implement your “click” logic in that listener rather than altering the Slider class itself


#10

Thanks for the suggestion. I don’t think that would work.
The Slider:Pimpl is unaccessible to derived classes. So I have no chance to even get the SliderRect which I would need to check if the mouse drag is inside the center area.
Frankly I would like to learn the reason for usage of the pimpl ideom in that class.