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