Listening/reacting to mouseEvents outside the component

As the standart PopupMenu doesn’t fit my needs I created my own. However as every other popupmenu it should be closed when theres a mouseclick anywhere outside of the menu.
So is there a way to react to mouseEvents that happen outside of the component?
mouseDown just reacts to what’s happening inside the component itself.

you could let the topLevelComponent always close all menus on mouseDown

I had this idea, too. But there’s 2 problems, first it won’t react to MouseEvents outside of the plugin (which would be not perfect but also no dealbreaker) and second it would also not react to MouseEvents on other components, f.e. if you press a button or drag a slider, etc. it dose not trigger mouseDown in the Editor.

I think you should use focus instead (see Component::focusLost())

1 Like

I tried that but it doesn’t work for me, focusLost() is never triggered.

You need to call setWantsKeyboardFocus( true ) on your custom combobox.
Of course that would not work if you actually do not want it to support keyboard control, but I think it’s expected that it should.

You don’t need anything with keyboard, that is completely unrelated.
Instead use a GlobalMouseListener:

struct ModalComponentCloser : public juce::MouseListener
{
    ModalComponentCloser (juce::Component& componentToClose)
      : owner (&componentToClose)
    {
        juce::Desktop::getInstance().addGlobalMouseListener (this);
    }
    ~ModalComponentCloser()
    {
        juce::Desktop::getInstance().removeGlobalMouseListener (this);        
    }
    void mouseDown (const juce::MouseEvent& event) override
    {
        if (event.originalComponent != component && closeLambda)
            closeLambda();
    }
    std::function<void()> closeLambda;

private:
    juce::Component::SafePointer<juce::Component> component;
};

// member:
ModalComponentCloser modalCloser { *this };

// and setup to close
modalCloser.closeLambda = [this]
{
    myPopup.reset();
};

Untested, but to bring an idea to the table…

Now it seems to work, but only if I call setWantsKeyboardFocus(true) for all components.
Right now I don’t see a problem with this, but I still wonder if this could cause any problems in some situation, since setWantsKeyboardFocus is set to false by default.

Ah thanks. That lead me in the right direction.
However I made it a bit diffrent.
I added
juce::Desktop::getInstance().addGlobalMouseListener (this);
in the constructor of the Editor, that seems to be enough.

Ah right, could work.

But you need to be aware that you get every mouse event now in the editor. Every Component (also the Editor) is already a MouseListener an normally doesn’t check if an event originates from this component.

So you might observe weird behaviour now in the Editor. That’s why I prefer to have a bespoke class that gets only the forwarded events from the desktop.

I realised this. Now i call it in the Menu class and if a mouseEvent happens I check if it happens inside or outside of the area of the component.

    auto xPos = event.getEventRelativeTo(this).x;
    auto yPos = event.getEventRelativeTo(this).y;
    if (xPos>=0 && xPos<=getWidth() && yPos>=0 && yPos<=getHeight()) {...}

The only downside is evenjuce::Desktop::getInstance().addGlobalMouseListener (this)only gets MouseEvents inside the plugin.

1 Like

have you ever considered a reactive programming approach? you could make some sort of event system where every component has the ability to either receive events or send events. you could define event types and one of them might be something like NotificationType::MouseClicked. so in the class that holds your menu, or in the menu itself you could check for this event to appear and do the action (closing the menu, setVisible(false) or setting a unique_ptr to nullptr, whatever)), while all the other components could just ignore this specific event type. the cool thing about this is that you can always add more event types for all kinds of useful stuff. people sometimes say the disadvantage of reactive programming is that it can become rather convoluted with more and more specific event types over time. i have not reached the point where i’d consider that a problem yet, but just so you know. imo this approach works even better if you base all your components on a custom component that already has an instance of an event notifier as well as a reference to the whole event system, because then it’s just available everywhere without you having to think about it too much. that would be very elegant, but once you have to add some juce component that is obviously not based on that base component, like juce::Slider, you’d have to manually add that again to make it work, which i’d personally find a little annoying, but all in all still really good

Did you work this out? I am also needing to access mouse events for mousemove() that are outside of the plugin / standalone window.

No, I ended up realizing that it’s actually not that importend. What I wanted is that the popup will be closed when the user clicks anywhere on the screen outside of the popup itself. But as it is right now it will only react to mousclicks outside the popup but inside the plugin. This is not perfect but also not bad, it’s absolutely useable. I planed to get back to this at some point but totally forgot about it (which shows that it seems to be fine as it is right now haha). So year sorry I didn’t made any progress. However the standart juce::combobox has that behavior that it closes on clicks outside the plugin. You might check out that code.