Manipulate draw order of lookandfeel

if 2 knobs share the same lookAndFeel can i manipulate the order in which they are being repainted? (because i need the bigger one to share information with the smaller one)

Unbenannt

it’s about the thicness of these 2 knobs. i don’t find it very pretty that the smaller one has a smaller thicness. but i can’t just use an absolute value. it must depend on the circle’s radius so it has the same feel on each resize

They are painted back to front.
You should be able to call “toFront()” on the smaller one.
This is the same as calling addAndMakeVisible() first for the bigger one and then the smaller one, since the list of children is simply iterated forward. Calling toFront() just moves it last in the painting queue.
There is also a zOrder you can specify in addChildComponent and addAndMakeVisible, but I never used that.

1 Like

i see. i think then i should experiment with the toFront()-method a bit… but first i need to find out how this zOrder-thing works because i probably have to delete this stuff that currently handles which slider gets mouseListener priority:

bool hitTest(int x, int y) override {
        auto mousePos = Point<float>(float(x), float(y));
        auto distance = Line<float>(centrePos, mousePos);
        if (inRange(x,y)) {
            if (distance.getLength() < hitTestRadius) {
                knob[1].toFront(true);
                return true;
            }
            knob[0].toFront(true);
            return true;
        }
        return false;
    }

the only way i see in the docs to set the z-order is in addAndMakeVisible. but i don’t wanna “readd” my slider all the time, just change that order.

The hitTest is probed in the opposite order, so it will hit the front most component first.
No need to change the order dynamically, especially not while an event is processed (read: you shouldn’t).

You are changing the container of children while it is being iterated. Luckily JUCE has the BailOutChecker, so it will probably just do nothing.

1 Like

ok wait… that’s hard to grasp for me. you see the idea of this is that you can better click at the outter knob. that’s why i override hittest so it doesn’t check for a rectangle, but for an actual circle. but toFront() doesn’t just change the mouseListener priority, but also the redrawing order. what is the correct way to solve this?

The smaller knob is in front, right?

So add this after you added the bigger one.
The smaller gets now the hitTest first and checks, if the hit is inside the circle. If not, it falls through to the bigger one. The bigger one will not need to test the inner circle, because otherwise it wouldn’t have got the event in the first place.
It will work just because the bigger one has larger bounds.

And painting also works naturally: the bigger one is drawn, and afterwards the smaller one is drawn on top.

1 Like

oh, so you’re idea is basically to not hittest in the component that holds both sliders, but only in the component of the inner smaller slider?

Each Component decides using hitTest, if it is appropriate to react. Default is return true, so it reacts to the whole getBounds() rectangle. You need now to make it more specific to only return true, when it is inside the actual knob to not obstruct most of the outer knob/ring.
Both Sliders can have the same implementation, just use

bool hitTest (int x, int y) override
{
    auto pos = Point<int>(x, y);
    auto radius = std::min (getWidth(), getHeight()) / 2; // adapt as appropriate to your design
    return (getLocalBounds().getCentre().getDistanceFrom (pos) < radius);
}
1 Like

currently trying your ideas but stuck on a side question:

since i don’t wanna getLocalBounds().getCentre() all the time but only when the knob has been resized i thought why not override resized() but when i do that everything disappears. do i have to reimplement something special there? apparently just calling repaint() also doesn’t work

Sounds like a micro optimisation to me. That is really not a costly operation and it is “only” GUI. It is probably even inlined automatically, since it is not virtual.

Have a look at which resized() you override. I assume you inherit Slider, and maybe that has an implementation for resized() that you removed by overriding.
If that is the case, just call Slider::resized(); at the end of your resized().

1 Like

yes this worked! now i can actually give the bigger knob a background without drawing it over the smaller knob. that’s cool but… i still don’t see how i can now let my smaller knob have the bigger knob’s thicness. when i set toFront on the outter knob the inner knob has no focus anymore, no matter if i give it true or false, but i only wanna let it draw first on each repaint so i can basically let it save its thicness then for the next knob, which will be smaller ofc. or maybe that’s just a bad idea and this should be solved entirely differently

Yes, it is a bad idea for two reasons:

  • The paint() shall have no side effects (so the outer should not rely on the paint() of the inner)
  • It makes your solution hard to re-use

I would use the component.getProperties() to store the track width, and use that in the LookAndFeel:

in parent resized:

auto trackWidth = getWidth() * 0.1; // however you want to calculate that
innerSlider.getProperties().set ("track", trackWidth);
outerSlider.getProperties().set ("track", trackWidth);

now you can use that in your LookAndFeel:

auto trackWidth = slider.getProperties().getWithDefault ("track", std::min (getWidth(), getHeight()) * 0.1f);
// or use whatever other fallback you want to provide as default, if no value was set
1 Like

thanks! that worked too. i actually wrote this now:

auto tRatio = knobArea[0].getWidth() / knobArea[1].getWidth();
knobIn.getProperties().set("tRatio", tRatio);

in the resized of the whole component and:

auto thicness = radius * .1f;
auto tRatio = float(slider.getProperties().getWithDefault("tRatio", 1.f));
thicness *= tRatio;

in the lookAndFeel. this just got me the idea i could also probably just let tRatio be member of the slider and then i don’t have to have this string-check float-cast from “var” whatever that is…

however! thanks for everything. here’s a little image of the current state:

Unbenannt

here’s the repo in case anyone needs such a slider: https://github.com/HDLAudio/DoppelKnob

@Mrugalla I’m getting a 404 on the repo link :face_with_head_bandage:

i should get into the habit of copying stuff into seperate repos before posting them somewhere…

anyway it’s easy peasy:

you just make 2 components (knobs like juce::Slider) and they lay on top of each other, but the one that is added to their parent first is the bigger one. the smaller one overrides hittest with something that only returns true if the distance between the center of the component and the current mouse event is smaller than std::min(getWidth(), getHeight()), because that’s the radius of the knob.

sidenote: i thought this was a smart thing to do first but turns out once you make these knobs reasonably small it’s kinda hard to grab them consistently well. that’s why in a lot of plugins with outter knob-ish features it’s not really an outter knob you’re touching, but rather a 2nd knob next to the main knob, like in serum when a modulation is active on a knob and that little thingie appears on the topleft of the main knob

1 Like