opened 06:07PM - 10 Oct 24 UTC
### 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
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 sent out a PR to fix this:
juce-framework:develop ← emezeske:develop
opened 09:00PM - 10 Oct 24 UTC
When handling mouseMove on MacOS, check that the current peer is the topmost NSV… iew before forwarding the events. Without this, a Component created via addDocumentWindow() with the nativeWindowToAttachTo argument set (i.e., creating a sub-NSView) would receive incorrect mouseExit events, as both it *and* its parent component would handle these mouseMove events, rapidly sending enter/exit messages back and forth between the two windows while the mouse moved.
But I have gotten no traction on either the bug report or the PR, so I’m posting here to the forums as that’s what contributing.md suggests I’m supposed to do.
'Twould be lovely if someone working on JUCE could take a look at this. not sure how I could be a better open-source citizen than to report a bug with a repro case and a PR to fix it…
1 Like
reuk
November 4, 2024, 1:50pm
3
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:
committed 11:10AM - 04 Nov 24 UTC
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:
committed 11:10AM - 04 Nov 24 UTC
The argument to hitTest must be in the superview's coordinate system.
The old im… plementation would sometimes break in the presence of nested
NSViews.
1 Like
Looks great; very glad to hear you found and fixed some other issues here as well. Thank you!