slider.hitTest(x, y) always returning true

My app looks like this:

I refactored my UI code and am bumping into some hitTest troubles.

Wheel has 12 spokes.
Each spoke has a Path “clickable” and a slider (rotated using AffineTransform).

Here’s the wheel:

struct PaneWheel : public Component {
    juce::OwnedArray<Slice> slices;
    bool hitTest(int x, int y) final {
        for(auto slice : slices)
            if(slice->hitTest(x, y))
                return true;
        return false;
}

Given I’m manually overriding hitTest IIUC setInterceptsMouseClicks would be redundant.

Here’s a spoke:

struct Slice : Component
    Slider slider;
    Path buttonOutline;
    bool hitTest(int x, int y) final {
        return buttonOutline.contains(x, y) || slider.hitTest(x, y);

Running the new code, I’m no longer able to click the Path objects, and only slider #11 can be changed by L-mouse-down+drag.

I suppose slider #11 is rendered last, so is on top. And it’s eating all mouse events by the looks of it.

It appears that slider.hitTest(x, y) is always returning true, for ALL x,y and for ALL 12 sliders.

Changing this line:

    bool hitTest(int x, int y) final {
        return buttonOutline.contains(x, y); // || slider.hitTest(x, y);

… now the Path shapes work, but of course none of the sliders respond.

I can’t figure out why this is failing, yet the pre-refactored code worked.
In that code (in the wheel) I had:

struct Slice : Component {
    Slice(int i) {
        setInterceptsMouseClicks(false, true);  // allowClicks allowClicksOnChildComponents
    }
}

struct PaneWheel : public Component {
    juce::OwnedArray<Slice> slices;
    OwnedArray<Path> paths;

    bool hitTest(int x, int y) final {
        for(auto p : paths)
            if(p->contains(x, y))
                return true;

        // https://forum.juce.com/t/hittest-ignore-setinterceptsmouseclicks/48006/11
        //   > If hitTest of the parent returns false, it will not try the children
        for (auto* child : getChildren())
            if (child->hitTest (x, y))
                return true;
        return false;
    }

    void mouseUp(const MouseEvent& event) final
    {
        auto xy = event.getPosition().toFloat();
        for(int i=0; i<12; ++i)
            if(paths[i]->contains(xy))
                DBG("MouseUp:" + String(i));
}

My refactor was to move the Path for each of the 12 buttons from the PaneWheel component into the Slice.

Can anyone see what I’m doing wrong?

Could there be some problem passing x, y to hitTest on an affineTransformed slider? e.g. might I need to manually transform x and y into the slider’s coord-system?

EDIT: I tried:

        float _x = x, _y = y;
        slider.getTransform().transformPoint(_x, _y);
        if(slider.hitTest(_x, _y))
            return true;

… in my hitTest but still it is always returning true.

So, thanks to Eggnog on TheAudioProgrammer Discord #juce, this solves it:

slider.getBoundsInParent().contains(x, y);

… instead of slider.hitTest(x, y)

Furthermore I am now able to remove the hitTest override from PaneWheel.

Now code looks like this:

struct PaneWheel : public Component {
    juce::OwnedArray<Slice> slices;
    PaneWheel() {
        setInterceptsMouseClicks(false, true);
    }
};

struct Slice : Component
    Slider slider;
    Path buttonOutline;
    Slice(int i) : index(i) {
        setInterceptsMouseClicks(false, true);
    }
    bool hitTest(int x, int y) final {
        if(buttonOutline.contains(x, y))
            return true;
        if(slider.getBoundsInParent().contains(x, y))
            return true;
        return false;
    }

I was under the impression that setInterceptsMouseClicks(false, ... renders any hitTest override intert, i.e. it will never be called.

However that’s clearly not the case.

I suspect that moving the Path-clickable-button code into a fresh component and adding that as a child of Slice would render this “trick” unnecessary. Maybe the problem is coming from having a component simultaneously hold children that eat clicks, while also eating certain clicks itself.

This is all pretty thorny, considering how simple the desired behaviour is. Yes it’s possible to get the result, but it’s not straightforward, and “nudging it 'til it works” is not a satisfying experience.

Could we maybe have some comprehensive exposition of setInterceptsMouseClicks and hitTest and friends?

The JUCE doc documents every API method in-place, which AFAICS means there’s no logical place for documenting of architecture.

Might it make sense to have a /doc/overviews/ folder in JUCE, to which various doc-topics could point? The idea is that everything’s still held together in the JUCE repo, but the relative hyperlinks in the docstrings for various methods could point to realated overviews.

1 Like