Drawing a slider with a large thumb

Hi all, I’m trying to draw a slider with a big thumb by overriding getSliderThumbRadius in my L&F.

The problem is that the thumb gets clipped by the component bounds:

This happens when the space between the top (bottom) and the track start (finish) is greater than the radius.

I tried tweaking things in my L&F’s drawLinearSlider (which started as a copy from juce::LookAndFeel_V4) but with no luck - I was able to change the visible length of the track, but the actual behaviour of the slider didn’t change i.e. I was still able to drag the thumb all the way down as in the screenshot above.

Am I missing something?
This topic was discussed in this old post (Slider thumb get top/bottom clipped by component boundaries) but I’m not sure that’s helping right now (I am overriding getSliderThumbRadius already).

Thanks!

This can only work if you make the bounds for the slider bigger.

You could use the setPaintingIsUnclipped() flag which would allow to write over the bounds of the component. But then the background might not be updated when the slider is repainted. So you will see artifacts.

The other drawback is that hitTest also is only called within the bounds, so the parts of the thumb overlapping would be not reacting as well.

Like I said the best solution is to increase the slider bounds and make the track slimmer in turn to keep the desired appearance.

You should use

auto bounds = getLocalBounds().reduced(x);

Or

auto newY = y + (x);
auto newHeight = height - (2 * x);

Using x as the padding amount

Reducing the Slider’s bounds will have the opposite effect, the slider will adapt and be even smaller and still not be able to paint over the component borders.

But that brought me to another idea, have a look at the SliderLayout.
Those bounds are not component bounds but the bounds the slider shall be drawn inside. So if you increase the component bounds but reduce the sliderBounds in this struct by the same amount, your thumb will have a better chance to not be cut off.
You change that by overriding getSliderLayout() in the lookAndFeel.

Hope that helps

1 Like

Thanks a lot for the suggestions - SliderLayout seems to be the way to go!

I still have an issue: despite overriding getSliderLayout() in my Look&Feel, the override is ignored and juce::LookAndFeel_V4::getSliderLayout() is called instead.

By looking at the debugger I realised that Slider::resized (which is the only user of getSliderLayout()) gets called before the custom Look&Feel is set; then, when the Look&Feel is changed, the components are repainted, but not resized.

What’s the best way of fixing that?

My code is structured as follows:

class MyEditor : public AudioProcessorEditor
{
    MyEditor::MyEditor (...)
    {
        setLookAndFeel (&myLookAndFeel);
        ...
    }

    MyLookAndFeel myLookAndFeel;
    Slider slider1, slider2, ...;
}

One option I’m thinking would be using std::unique_ptr<Slider> instead of Slider and constructing them in MyEditor::MyEditor after the call to setLookAndFeel … but maybe there’s something better?

Thanks!

Maybe just put a call to resized(), as the line after setLookAndFeel()?

The situation sounds familiar, I’ll have to look tomorrow to see what I’ve done in my code before…

There are a few things that are strange:

  • When you change the lookAndFeel, the Slider should receive a lookAndFeelChanged() which in turn calls resized() at the end of the function. I couldn’t spot an early return.
  • When you call setSize() of your editor, it should be the last call, because that will trigger the resized() and place all child components correctly.

Some example code puts all setBounds() in the constructor. I would advise never to do that, because if you resize for any reason, the resized() is called and that should do the layout of the children.
The last thing you want is an inconsistency between initial layout and a subsequent call to resized().
And ofc… you don’t want to implement stuff twice.

TL;DR:

  • make sure you don’t inherit Slider but if you do that you didn’t override lookAndFeelChanged()
  • put the slider.setBounds() calls in resized
  • call the editors setSize last, but at least after setLookAndFeel() and all the addAndMakeVisible calls

Hope that helps

1 Like

i’d just make the thumb as big as possible. so basically std::min(getWidth(), getHeight()) / 2 as a radius, you know? then i’d draw this middle line in a way that it starts at radius and ends at getHeight() - radius, so that when you are the min and max-values the thumb is just barely touching the end of the component. (it might be that you have to reduce the thumb’s radius by 1pxl for this to work or something like that.) then you can just define the size of it with setBounds() like you’d do normally with all components. i think i personally wouldn’t use a solution with drawing outside of the bounds, or using look and feel tricks for this, because the more your project grows the more you’ll be thankful that all components more or less work the same as much as possible when re-reading your code

This was my problem! I was calling setSize before addAndMakeVisible!

The best part is that I don’t even need to override getSliderLayout - it just works with the base class’ implementation - given a thumb size (set via getSliderThumbRadius) it will do the right thing.

The problem was that paint was getting the custom (bigger) thumb size, while resized was getting the default (smaller) one. Now that they both get the right value it works just fine.

Thanks everyone for your input!

1 Like