JUCE icon indicating copy to clipboard operation
JUCE copied to clipboard

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

Open emezeske opened this issue 1 year ago • 1 comments

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

  • [X] I agree to follow the Code of Conduct

emezeske avatar Oct 10 '24 18:10 emezeske

Actually it seems so far like my proposed fix works. I can't find anything that it breaks, although it's possible that I'm not being creative enough... ? I'm going to submit a pull request.

https://github.com/juce-framework/JUCE/pull/1443

emezeske avatar Oct 10 '24 21:10 emezeske

Created a forum post because contributing.md told me to do so:

https://forum.juce.com/t/bug-macos-addtodesktop-w-nativewindowtoattachto-handles-mousemove-incorrectly/63879

emezeske avatar Oct 21 '24 18:10 emezeske

Thanks for your patience. We've now added a fix for this issue on the develop branch.

The fix we merged is fairly similar to the one you suggested, but we call the NSViewComponentPeer::contains function rather than calling hitTest directly:

https://github.com/juce-framework/JUCE/commit/b108fe26c3c81de6c84f5a1a79e750589fc0b8dd

This also unearthed an issue where the argument to hitTest inside NSViewComponentPeer::contains was not correctly using the superview's coordinate space. That issue is addressed here:

https://github.com/juce-framework/JUCE/commit/fcf62ab10565b5b3c568e26b8f6fa7de94f18c1b

reuk avatar Nov 04 '24 13:11 reuk

Thanks, I can confirm that the fixes work for my use case!

emezeske avatar Nov 04 '24 19:11 emezeske