hitTest ignore setInterceptsMouseClicks

Seems like in some situations, Components are totally ignoring setInterceptsMouseClicks() after hitTest() has been overridden.

example:

struct InnerComp : Component
{
    InnerComp()
    {
        setInterceptsMouseClicks(false, false);
    }

    void paint(Graphics& g) override
    {
        g.setColour(Colours::blue);
        g.fillRect(internal);
    }

    void mouseDown(const MouseEvent&) override { DBG("ChildClicked"); }
    bool hitTest(int x, int y) override { return internal.contains(x, y); }

    Rectangle<int> internal {20, 20, 100, 100};
};

struct MainComponent : Component
{
    MainComponent()
    {
        addAndMakeVisible(comp);
        setSize(600, 400);
    }

    void mouseDown(const MouseEvent&) override { DBG("Clicked parent"); }
    void resized() override { comp.setBounds(getLocalBounds()); }

    InnerComp comp;
};

While I can solve it locally by forcing the flag check in the super class:

    bool hitTest(int x, int y) override
    {
        return Component::hitTest(x, y) && internal.contains(x, y);
    }

This doesn’t scale, because in my real code I have reusable Components where I want the parent to decide if they’re clickable, and would rather avoid changing them, so this solution fails.

Any ideas?

idk if it’s the most elegant solution but you could put a lambda into hittest so that it only performs its operation if this component’s setIntercerpt is not false, else it returns false. (lambda because the actual operation might vary, idk)

I’m showing that in the thread:

    bool hitTest(int x, int y) override
    {
        return Component::hitTest(x, y) && internal.contains(x, y);
    }

If you look at the implementation of the base class function, it checks those flags. So it ‘works’.

But, for this system to work it has to check the flags for the parent, and going ‘down’ recursively.

For example, it’s possible this Component is defined as setInterceptsMouseClicks(true, true), but the parent is defined as setInterceptsMouseClicks(true, false).

In that case, I would expect the parent’s flags would ‘take over’ and not allow the children to get any events, regardless of their hit test. But JUCE seems to call hitTest() on the child anyway in this case, totally ignoring what the parent has set.

i see. i would expect this to depend on the sequence in which setInterceptsMouseClicks was called. if the parent called it first with (true,false) ofc juce would think all children don’t use incoming mouse events, but if you call (true, true) on a child after that some internal stuff might be overwritten back to that behaviour. maybe that’s why you experience this behaviour. have you tried setting the intercepts for the children first, yet?

The order doesn’t seem to make any difference.
Also IMO it shouldn’t make any difference, I should be able to have both orders in a dynamic system.

For example if I call setVisible(true) on a child, and the parent has setVisible(false) on it, the child should never be visible in any order of calls.

Bumping. Can anyone take a look?

Component::hitTest is implemented like this:

bool Component::hitTest (int x, int y)
{
    if (! flags.ignoresMouseClicksFlag)
        return true;

To get the same sort of mouse handling behaviour, I think each derived class will need to check ignoresMouseClickFlag in the overridden hitTest. This flag can be checked with Component::getInterceptsMouseClicks.

Thanks for the reply @reuk.

I understand that’s how the current implementation works, but I think it’s a bug and not the way the current API is meant to behave/documented.

If the parent sets setInterceptsMouseClicks(true, false) then the child’s hitTest() method should not be called IMO.

Just to be clear, I think the bug here is not in hitTest() but in:
Component::getComponentAt().

That function responds to mouse events by recursively scanning the children and finds the right one by calling hitTest() on them.

However, that function is not looking at the setInterceptsMouseClicks flags at all, which I believe is wrong.

Instead, it does block the deeper recursive action when the visible flag is set to false, but seems to not care about the interception flags.

This is important because even if the child will override that function and check the flags, it doesn’t know about the parent flags which should take precedent, IMO.

2 Likes

I am bumping into a similar issue, maybe the same issue.

My Wheel component has 12 Slice components, and each Slice contains a juce::Slider . Recently I’ve overridden Wheel::hitTest to return true only for certain path-regions (nowhere near the sliders). But now I can’t manipulate the sliders with my mouse.

struct Wheel {
    juce::OwnedArray<Slice> slices;
    Wheel() {
        for(int i=0; i<12; ++i) {
            auto slice = new Slice(i);
            slices.add(slice);
            addAndMakeVisible(slice);
        }
    }
    bool hitTest(int x, int y) final {...}
}

struct Slice {
    Slider slider;
    Slice() {
        setInterceptsMouseClicks(false, true);  // allowClicks allowClicksOnChildComponents
        addChildComponent(slider);  // later I do slider.setVisible(true)

Removing Wheel::hitTest I can now drag the sliders.
So somehow the parent’s hitTest impl. is preventing the child’s slider from getting mouse-events.

Can someone help shake this one loose?

I think it is a separate issue. In my understanding a parent has to include all it’s children. If hitTest of the parent returns false, it will not try the children. Otherwise each mouse click would have to test EVERY component, which would create a lot of overhead.

If you don’t want to deal with the children’s bounds in the parent, you could add this:

bool hitTest (int x, int y) const override
{
    for (auto* child : getChildren())
        if (child->hitTest (x, y))
            return true;

    // do your own stuff or return false, if you don't need your own interaction
    return false;
}
2 Likes