Skip to content

[Bug]: MacOS addToDesktop w/ nativeWindowToAttachTo handles mouseMove incorrectly #1442

@emezeske

Description

@emezeske

Detailed steps on how to reproduce the bug

This sample code reproduces the problem. On MacOS, create a button as a child NSView of the current component's window, make sure it's visible etc, and then mouse over it. What you'll see is that it may temporarily flash to the highlighted isOver state, it will immediately revert to not being highlighted, as if the mouse exited it (even though it didn't).

 juce::TextButton button{"Mouse over me"};

 button.setOpaque(true);
 button.setVisible(true);
 button.addToDesktop(0, getWindowHandle());
 button.toFront(false);
 button.setBounds(100, 100, 50, 50);

I have debugged this fairly extensively, and I can explain the problem, and I have a possible solution but it needs to be vetted by someone who understands this stuff more than I do.

The problem is in:

lib/JUCE/modules/juce_gui_basics/native/juce_NSViewComponentPeer_mac.mm

What's happening is that both the parent and the child NSView that JUCE creates have NSTrackingAreas installed. These areas are firing mouse events for both the parent and child windows when the mouse is over the child window (despite the NSTrackingInVisibleRect option being set -- it does not prevent this).

Now for methods like redirectMouseEnter() and redirectMouseExit(), this is handled properly by checking that the NSEvent's tracking area is actually the one installed for the current component's NSView.

But for the redirectMouseMove() method, that's not possible as [ev trackingArea] is not defined for this type of event. So what happens is the isWindowAtPoint() method returns true for both the parent and child windows, and they BOTH call sendMouseEvent(), leading to the current component changing repeatedly as the mouse moves. When the current component changes, JUCE calls mouseExit() for the previous component. It so happens that redirectMouseMove() is called in the order of child, then parent, so the button gets exited over and over as JUCE sees the mouseMove in the parent.

My proposed solution is to check if the top-level view at the mouse coordinates is, in fact, the view associated with the NSViewComponentPeer object on which redirectMouseMove() is called. So instead of:

        if (isWindowAtPoint ([ev window], screenPos)) {
            sendMouseEvent (ev);
        } else
            // moved into another window which overlaps this one, so trigger an exit
            handleMouseEvent (...)

It would be:

        if (isWindowAtPoint ([ev window], screenPos)) {
            if ([[[ev window] contentView] hitTest: windowPos] == view) {
              sendMouseEvent (ev);
            }
        } else
            // moved into another window which overlaps this one, so trigger an exit
            handleMouseEvent (...)

This solution does indeed fix things for my test case. But perhaps it causes issues I'm not aware of, or is not general enough, I'm not sure (which is why I didn't submit a pull request).

What is the expected behaviour?

Mousing over the TextButton in the example above should cause it to be highlighted in the isOver state until the mouse is moved out of the bounds of the button.

Operating systems

macOS

What versions of the operating systems?

Sonoma

Architectures

ARM, 64-bit

Stacktrace

No response

Plug-in formats (if applicable)

No response

Plug-in host applications (DAWs) (if applicable)

No response

Testing on the develop branch

The bug is present on the develop branch

Code of Conduct

  • I agree to follow the Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions