Mouse event to dismiss a "modal" dialog


#1

Hi everyone, im hoping someone can point me in the right direction with the following problem.

i have an “about” box which i want to be able to dismiss by clicking anywhere outside it.

Plan A:
i call enterModalState' but then i want to, somehow, get visibility of all mouse down events. I tried usingcanModalEventBeSentToComponent’ on the parent component but, this appears to let enter/leave events through and not mouseDown events. in any case, i really want to allow clicking anywhere and not just on the parent and this would be very messy. im wondering if there’s some way for a modal dialog to acquire the focus of ALL events temporarily.

Plan B:
dont go modal at all and use,
getTopLevelComponent()->addMouseListener(…)
this gets mouseDown and works, but the side effect is that the mouseDown is also delivered to its proper outside component which causes other things to happen. is there some way to absorb the event if “used” using this mechanism.

plan C:
the obvious way i didnt think of.

if there is plan C, please point me in the right direction. otherwise plan B nearly works and doesnt have to be modal. for plan A, i dont care if i am modal or not so long as i can see app-wide events somehow.

thanks for any help or ideas.


#2

I think you might be looking for Component::inputAttemptWhenModal()

:wink:


#3

Aha! i’d seen this, but hadn’t realised it gets called on the modal object when attempt is outside it.

so now i `enterModalState’ then

void inputAttemptWhenModal()
{
    exitModalState(0);
}

however, the mouse event right behind the activate is then delivered to the outside window. is there an easy way to prevent this. for example, dispatching an async “ESC” key event instead of `exitModalState’ above and have that in the ESC key handler (which has it anyhow).

?


#4

yeah, personally I’d probably kick off a CallbackMessage that calls exitModalState() asynchronously.


#5

Sorry to dig up an old thread, but I’m experiencing a similar issue where after exiting the modal state, the event that triggered closing the dialog gets passed on to the underlying component.

Here’s some sample code demonstrating my set up:

class MyDialog : public DialogWindow
{
public:
  using CloseButtonHandler = std::function<void()>;

  void setCloseButtonHandler(CloseButtonHandler handler) { closeButtonHandler = handler; }

private:
  void inputAttemptWhenModal() override { closeButtonHandler(); }

  CloseButtonHandler closeButtonHandler;
};

class MyComponent : public Component
{
public:
  void showDialog() 
  {
    d = new MyDialog();
    d->setCloseButtonHandler([this]() {
      MessageManager::getInstance()->callAsync([this](){ d->exitModalState(0); });
    });
    d->enterModalState(true, nullptr, true);
  }

  MyDialog *d;
};

I tried with and without MessageManager::callAsync and in both cases the current thread was the MessageThread anyway, so I’m not sure what benefit it has to call it asynchronously.

I’m trying to see if there’s a way to consume the event that triggered the modal exit but it’s not clear which component handles it first.

Any ideas would be greatly appreciated!

Thanks


#6

I noticed that the CalloutBox class in theory should exhibit the behaviour I’m after.

Though when trying the following code I get the same issue where the components underneath the touch are handling the event.

    MyComponent* content = new MyComponent();
    content->setSize (300, 300);

    CallOutBox& myBox = CallOutBox::launchAsynchronously (content, getScreenBounds(), nullptr);
    myBox.setDismissalMouseClicksAreAlwaysConsumed(true);

Note: I’m testing on iOS with JUCE 4.3.0


#7

My solution to this was a bit more brute force. I just make the component the whole window, then make a sub-rectangle that is the modal view where I need it. (This has the bonus effect of being able to fillAll() an alpha color to “dim” the main view.) On MouseDown, if it isn’t in my subrect, I run my dismissal. Seemed a bit easier than all that stuff above.

if (!boundsExpanded.contains(e.getPosition()))
{
//doDismissalStuff
}

Note that I expand the subrect a bit to account for people “missing” scroll bars or whatever on touch surfaces.


#8

@crandall1 Huh! That is interesting!

I already had a wonky way to dim the surrounding area by getting the main view to display a semi-transparent component.

I tried something similar at first but was constrained to drawing the dialog window within the parent view (a smaller subsection of the screen). I guess you’re getting your main component to handle displaying the dialog?

Will have another attempt with your suggestion, thanks!


#9

Thanks for your insight @crandall1 got this working in the end!

I ended up passing the view I wanted to modalise to a UI singleton that displays a Dialog Window with a full screen semi-transparent background component as it’s owned content. Then adding the view to display as a child of the background comp and setting its bounds accordingly.

Dialog Window
 |--> Semi-Transparent Component
       |--> Content View