Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make react-juce onMouseEnter and onMouseLeave events behave like their web counterparts #230

Open
androidStern opened this issue Feb 10, 2021 · 6 comments
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed
Milestone

Comments

@androidStern
Copy link

androidStern commented Feb 10, 2021

The Problem

react-juce mouseEnter and mouseLeave events don't behave like their web counterparts

Background

Juce's mouseEnter and mouseExit events differ from their browser counterparts in a few ways (detailed below). Because react-juce simply forwards Juce mouse events onto the javascript engine, the react environment has inherited Juce's behavior which is unintuitive for react developers. So, the goal here is to make the behavior in react-juce components conform to the web behaviors as much as possible.

Behavior Differences

  1. When moving from a parent to a child element
    • Juce (and by extension, react-juce) will fire a mouseLeave on the Parent and a mouseEnter on the Child
    • Browsers will only fire a mouseEnter on the Child
  2. When moving from a child to it's parent
    • Juce will fire a mouse leave on the Child and a mouseEnter on it's Parent
    • Browsers will only fire a mouseLeave on the child
  3. When entering an exiting elements while holding the mouse button down
    • Juce will not fire any enter or exit events
    • Browsers will fire both events as described above
  4. mouseEnter and mouseLeave events dont bubble in browser but do in current react-juce

TLDR for points 1 & 2: From the browser's perspective, the mouse is still inside an element if it is inside any of its children and mouse buttons have no effect.

Extra Stuff

To get a feel for how these events behave in the browser you can check out this page.

See this discord thread for additional context

Finally, I haven't researched what the expected behavior is when the DOM hierarchy changes while the mouse is moving but that's something to consider as well.

@nick-thompson
Copy link
Collaborator

Excellent report @androidStern thank you.

I'm thinking that 1 & 2 we can do our own small check right here to say "if we're leaving but the place we're leaving to is a child component, don't fire the event" and similar for the other direction.

I think (3) will probably be a game of just finding and implementing other details of the juce::Component spec.

And (4), great catch, we can filter which types of events get bubbled right here.

I'm going to leave this one here and mark it as a good first issue for a new contributor, this is a great way to dip your toes into the project

@nick-thompson nick-thompson added bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed labels Feb 11, 2021
@nick-thompson nick-thompson added this to the v1.0 milestone Feb 11, 2021
@androidStern
Copy link
Author

androidStern commented Feb 12, 2021

Regarding # 3: I'm not aware of any juce::component features that will solve this mousebutton problem. Some members of the juce discord forum suggested using DragAndDropContainer and DADTarget since they provide a dragEnter and dragExit callback that fire when mouse is held down and enter occurs. Imo, co-opting D&DContainer is icky and brings its own set of unwanted behaviors. DragAndDropContainer code just handles drag events at a parent component and manually dispatches enter and exit events to the appropriate children which isnt complicated to do ourselves. Still on the lookout for a native juce solution though.

@androidStern
Copy link
Author

Regarding # 2: The tricky bit is disambiguating mouseEnters caused by moving from a child out to its parent (we want to stifle these) and mouseEnters caused by moving from parent into its child (we these to fire on the child). Inside a mouseEnter callback you're saying "which direction am I coming from. there's not enough state on myself or the mouse event to tell me".

So one solution is to add and maintain some state like bool was_really_mouse_over_according_to_browser_spec to each component so they can figure it out in their mouseEnter and mouseExit callbacks. But I don't understand enough about react-juce rendering to know if this works. Are components stable across react renders? If not, I'll lose my mouse state and components are the wrong place to keep it.

@nick-thompson
Copy link
Collaborator

Regarding # 3: I'm not aware of any juce::component features that will solve this mousebutton problem. Some members of the juce discord forum suggested using DragAndDropContainer and DADTarget since they provide a dragEnter and dragExit callback that fire when mouse is held down and enter occurs. Imo, co-opting D&DContainer is icky and brings its own set of unwanted behaviors. DragAndDropContainer code just handles drag events at a parent component and manually dispatches enter and exit events to the appropriate children which isnt complicated to do ourselves. Still on the lookout for a native juce solution though.

Yea, I don't feel great about the idea of co-opting the DAD stuff for this, although @JoshMarler and I are planning some drag and drop behavior that sits a DADContainer at the ReactAppRoot level, so in a sense, having View generically inherit from DADTarget is not a major problem. Would need to prove that out and make sure there's no performance impact, but it wouldn't be a crazy lift to do all of this. Still, I really want to believe there's a simpler way to get correct mouseEnter/mouseExit callbacks when the mouse button is down...

Regarding # 2: The tricky bit is disambiguating mouseEnters caused by moving from a child out to its parent (we want to stifle these) and mouseEnters caused by moving from parent into its child (we these to fire on the child). Inside a mouseEnter callback you're saying "which direction am I coming from. there's not enough state on myself or the mouse event to tell me".

Right, I'm wondering if there's a way to, for example, catch the mouseExit event and check if the mouse is now over a child component, in which case we won't push the mouseExit into js. We can do some "check the current position of the mouse" logic here I think, without necessarily needing to maintain any additional state on View. But, I'm taking a bit of a guess here, I definitely haven't worked out the details on that idea ;)

@JoshMarler
Copy link
Owner

Yeah the mouse button down thing is a pain. @androidStern maybe throw your suggested fix we discussed for #2 here as you could potentially try to leverage the mouseDrag event and workout the Viewinstance under the mouse using the MoustEvent's absolute position. Then during this callback you could potentially try to write/inspect your wasMouseOver flag in some way. I need to think more on this one.

Feels like there should be an easier way to just respond to the MouseInputSource stuff here ...

@androidStern androidStern mentioned this issue Feb 16, 2021
Draft
@androidStern
Copy link
Author

@JoshMarler: #238 here's a draft of what we talked about this morning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants