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

Add/implement pc.EVENT_MOUSEOUT and pc.EVENT_MOUSEENTER #4920

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

kungfooman
Copy link
Collaborator

@kungfooman kungfooman commented Dec 10, 2022

Fixes: #739

This event is missing, so devs always had to add custom event handling in their scripts, like here:

var onMouseOut = function (e) {
self.onMouseOut(e);
};
this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
this.app.mouse.on(pc.EVENT_MOUSEWHEEL, this.onMouseWheel, this);
// Listen to when the mouse travels out of the window
window.addEventListener('mouseout', onMouseOut, false);

This will also simplify event handling later on based on #4910, in short ScriptType#listen provides:

  • Less code
  • Automatically removing events on disable/destroy
  • Automatically adding them back on enable
  • Harder to get it wrong

I confirm I have read the contributing guidelines and signed the Contributor License Agreement.

@yaustar yaustar self-assigned this Dec 12, 2022
@mvaligursky mvaligursky added the area: input Input related issue label Dec 13, 2022
@@ -50,7 +50,7 @@ class MouseEvent {
* @type {number}
*/
this.y = coords.y;
} else if (isMousePointerLocked()) {
} else if (isMousePointerLocked() || event.type === 'mouseout') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the same note, what values are coords.x/y on a mouse out event?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary?

coords = mouse._getTargetCoords(event); returns null and then the constructor just ends here:

} else {
return;
}

Resulting in the entire event being "uninitialized".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the same note, what values are coords.x/y on a mouse out event?

@yaustar There are two cases:

  1. When mouse is inside the canvas while entering another DOM element, mouseout is fired and then its the normal x/y
  2. When mouse leaves the window, _getTargetCoords returns null and x/y sticks to 0 (because _getTargetCoords returns some proper values).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see 🤔 This seems fine to me

@yaustar
Copy link
Contributor

yaustar commented Dec 13, 2022

I also noticed that mouse out event fires in unexpected situations such as:

When I click drag and move out of the window, I get the event on the move out and also on button release

Kapture.2022-12-13.at.12.45.54.mp4

When the PlayCanvas window is not in focus and moving between the active browser window and the PlayCanvas one

Kapture.2022-12-13.at.12.49.39.mp4

…t talking about pc Elements

Co-authored-by: Steven <yaustar@users.noreply.github.com>
@kungfooman
Copy link
Collaborator Author

@yaustar Thank you for checking, it seems to be default behaviour, I get the same in Firefox and Chrome.

The easiest way to test it is any empty page and then just:

window.onmouseout = e => console.log(e.target.tagName);

image

I don't know why browsers do it, but do you think PC events should try to filter such cases?

@yaustar
Copy link
Contributor

yaustar commented Dec 13, 2022

I don't know why browsers do it, but do you think PC events should try to filter such cases?

I'm in two minds about this. On one hand, it makes sense to mirror the native events like we do everywhere else. On the other, it would be nice to have mouse in and mouse out instead of both being on the same event?

@kungfooman
Copy link
Collaborator Author

On the other, it would be nice to have mouse in and mouse out instead of both being on the same event?

Yea, I share your concerns here, seems like this works best:

document.body.onmouseout = console.log;

It isn't confused between BODY/HTML, but what I don't like is that it's accessing body which can be undefined (coming from a <script> in <head> e.g.)

@yaustar
Copy link
Contributor

yaustar commented Dec 13, 2022

document.body.onmouseout = console.log;

Yeah, let's not do that as the mouse DOM element can be any DOM element too on attach.

Wait, is target element not used in the mouse input? 👀 We just listen the window regardless?

@kungfooman
Copy link
Collaborator Author

Wait, is target element not used in the mouse input? 👀 We just listen the window regardless?

Right, _target is just used for some context menu handling and position calculations, the rest attaches events onto window:

attach(element) {
this._target = element;
if (this._attached) return;
this._attached = true;
const opts = platform.passiveEvents ? { passive: false } : false;
window.addEventListener('mouseup', this._upHandler, opts);
window.addEventListener('mousedown', this._downHandler, opts);
window.addEventListener('mousemove', this._moveHandler, opts);
window.addEventListener('wheel', this._wheelHandler, opts);
}

@yaustar
Copy link
Contributor

yaustar commented Dec 13, 2022

Can we use it to know if we are 'mousing out' or 'in'?

@kungfooman
Copy link
Collaborator Author

Can we use it to know if we are 'mousing out' or 'in'?

You mean to have e.g. a small 100x100px canvas and then trigger mouseout when this small area is left and not the window?

It should be possible, just that it would deviate from how e.g. the OrbitCamera script used to use it (if we speak about the same thing here).

I tested window.onmouseenter but that doesn't fires at all for me. Having a pc.EVENT_MOUSEIN should still be quite easy using the mouse move events tho (or _target of pc.app.mouse)

@yaustar
Copy link
Contributor

yaustar commented Dec 13, 2022

It should be possible, just that it would deviate from how e.g. the OrbitCamera script used to use it (if we speak about the same thing here).

That's fine, I wrote that years ago when I wasn't really thinking that closely where I wanted mouse out 😅

@kungfooman
Copy link
Collaborator Author

Yea, it seems more sensible to have it on Mouse#_target as it will behave less surprisingly.

(I can rewrite it tomorrow if no one comes up with a counter-argument 😅)

@kungfooman kungfooman changed the title Add/implement pc.EVENT_MOUSEOUT Add/implement pc.EVENT_MOUSEOUT and pc.EVENT_MOUSEENTER Dec 14, 2022
@yaustar
Copy link
Contributor

yaustar commented Dec 22, 2022

@kungfooman Is this ready for a re-review?

@kungfooman
Copy link
Collaborator Author

kungfooman commented Dec 22, 2022

@kungfooman Is this ready for a re-review?

Yep, I would like that, quick intro:

pc.app.mouse.on(pc.EVENT_MOUSEENTER, console.log);
pc.app.mouse.on(pc.EVENT_MOUSEOUT  , console.log);

or:

pc.app.mouse.on(pc.EVENT_MOUSEENTER, _ => console.log("mouse enter", _));
pc.app.mouse.on(pc.EVENT_MOUSEOUT  , _ => console.log("mouse out"  , _));

@yaustar
Copy link
Contributor

yaustar commented Dec 23, 2022

If the window is not in focus, when the mouse enters, it fires both the ENTER and OUT events (see end of the video). Was this intended?

Kapture.2022-12-23.at.14.12.21.mp4

@kungfooman
Copy link
Collaborator Author

If the window is not in focus, when the mouse enters, it fires both the ENTER and OUT events (see end of the video). Was this intended?

Thank you for testing! It might be a Chrome bug on Mac, I can't reproduce on Linux/Chrome:

Peek 2022-12-24 11-36

@yaustar
Copy link
Contributor

yaustar commented Jan 5, 2023

Definitely Chrome specific but it's difficult to ship this when it 'doesn't work' with the major browser user share 😅

Edit: Wondering if we should only fire the event if the window is in focus?

@LeXXik
Copy link
Contributor

LeXXik commented Jan 5, 2023

Yeah, I also remember having issues with onmouseout in the past. At least on Windows. Have you tried onmouseleave?

@kungfooman
Copy link
Collaborator Author

I reported mouse issues in the past and it usually takes some time until issues are fixed, but imo that is the way to go - just someone needs to create a Chrome issue and keep up with it.

Issue 356090: mouseout on element triggered on mouseup after entering the element with the mouse button pressed

Sounds like they had it before, apparently "fixed", but not quite it seems.

For my sake it works good enough besides this have-another-window-over-canvas and I'm not trying to find Chrome specific hacks for that case.

Edit: Wondering if we should only fire the event if the window is in focus?

Just for testing:

setInterval(()=>console.log(document.hasFocus()), 1000)

But I don't see how that would work out nicely, as soon you click outside the tab you won't have any "entering" events anymore?

@yaustar
Copy link
Contributor

yaustar commented Jan 5, 2023

But I don't see how that would work out nicely, as soon you click outside the tab you won't have any "entering" events anymore?

There aren't any mousemove events on the app if the tab isn't in focus either so I would argue it's consistent behaviour?

@kungfooman
Copy link
Collaborator Author

There aren't any mousemove events on the app if the tab isn't in focus either so I would argue it's consistent behaviour?

The mousemove events are always fired, focused or not:

pc.app.mouse.on("mousemove", console.log);

E.g. for devtools in https://playcanvas.com/viewer if you want to test. Or do you mean something different?

@yaustar
Copy link
Contributor

yaustar commented Jan 6, 2023

Hmm, I get different behaviour on Chrome/Mac

Kapture.2023-01-06.at.10.48.54.mp4

@kungfooman
Copy link
Collaborator Author

Funny how different the same browser is on different platforms:

Peek.2023-01-06.12-49.mp4

IIRC Chrome on Windows also sends mousemove even though the window is unfocused, so Mac is the oddball here.

Technically it imo makes more sense to not filter events too early, since once they are filtered, there is no way back. And for a dev it would be simple enough to just if (!document.hasFocus()) return; if they want that?

@yaustar
Copy link
Contributor

yaustar commented Jan 6, 2023

Technically it imo makes more sense to not filter events too early, since once they are filtered, there is no way back. And for a dev it would be simple enough to just if (!document.hasFocus()) return; if they want that?

Part of me feels like the engine should be as consistent as possible in the events it fires. If a developer wants the raw events, then there's nothing stopping them registering events on the DOM

@yaustar yaustar added requires: discussion It isn't clear what needs to be done feature request labels Jan 6, 2023
@kungfooman
Copy link
Collaborator Author

Part of me feels like the engine should be as consistent as possible in the events it fires.

So mousemove shall also be filtered now to make everything work like on a Chrome Mac?

@yaustar
Copy link
Contributor

yaustar commented Jan 6, 2023

So mousemove shall also be filtered now to make everything work like on a Chrome Mac?

Ideally, it should be consistent otherwise we would end up with bug reports in the engine.

The fact that mouse move is different on a per OS basis is frustrating and truthfully, I don't know what the best way forward here is.

We could ship this as is where the behaviour is consistent across OS's browser when the tab is in focus by the user. I think that would be okay given noone has reported about mouse move being an issue when the browser is not in focus.

@yaustar
Copy link
Contributor

yaustar commented May 11, 2023

Thank you for testing! It might be a Chrome bug on Mac, I can't reproduce on Linux/Chrome:

It works fine if the active window is not another Chrome window. That's probably why you can't reproduce it as you are checking against VSCode

@@ -52,6 +52,20 @@ export const EVENT_MOUSEUP = 'mouseup';
*/
export const EVENT_MOUSEWHEEL = 'mousewheel';

/**
* Name of event fired when the mouse moves out or enters another DOM element.
Copy link
Contributor

@yaustar yaustar May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Name of event fired when the mouse moves out or enters another DOM element.
* Name of event fired when the mouse moves out or enters another DOM element. This can fire on entering a PlayCanvas browser window not in focus so you may want to check with {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus document.hasFocus()}.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really "defocused" or "unfocused"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe 'background' is a better term?

Copy link
Contributor

@yaustar yaustar May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated suggested change with something with less jargon.

Edit: I just saw the commit, that looks fine too :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just changed it via normal commit, GitHub is having some strange issues:

image

(for my sake it sounds good enough, but you can make a suggestion if needed)

Copy link
Contributor

@yaustar yaustar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with comment

Copy link
Contributor

@yaustar yaustar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with comment


const opts = platform.passiveEvents ? { passive: false } : false;
window.removeEventListener('mouseup', this._upHandler, opts);
window.removeEventListener('mousedown', this._downHandler, opts);
window.removeEventListener('mousemove', this._moveHandler, opts);
window.removeEventListener('wheel', this._wheelHandler, opts);
if (this._target) {
this._target.removeEventListener('mousein', this._enterHandler, opts);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be mouseenter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, nice catch, absolutely and thank you!

@kungfooman
Copy link
Collaborator Author

It works fine if the active window is not another Chrome window.

Did you test that on Linux? I tried with another tab of Chrome and I couldn't trigger the bug either. Also tried it with other apps like KolourPaint and it worked nicely.

So far I believe we could just point out it only happens on Chrome/Mac, which should be fixed in Chrome/Mac's OS code (until someone brings this issue to some Mac developer attention).

@yaustar
Copy link
Contributor

yaustar commented May 11, 2023

Did you test that on Linux? I tried with another tab of Chrome and I couldn't trigger the bug either. Also tried it with other apps like KolourPaint and it worked nicely.

On have access to Mac unfortunately 😅 Either way, I don't think it's a blocker for the PR

@GSterbrant GSterbrant added the open source contribution Issues related to contributions from people outside the PlayCanvas team label May 12, 2023
export const EVENT_MOUSEOUT = 'mouseout';

/**
* Name of event fired when the mouse moves out or enters another DOM element.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly confused here since the description is identical to the first sentence of the description for EVENT_MOUSEOUT.

@mvaligursky
Copy link
Contributor

@kungfooman - any thoughts on the last comments here?

Co-authored-by: Will Eastcott <will@playcanvas.com>
@kungfooman
Copy link
Collaborator Author

Yea, it's kinda confusing, because mouseout triggers when entering other elements:

mouseenter mouseout

However, good catch @willeastcott, because the EVENT_MOUSEENTER description should be more specific.

I will rewrite it to make it clearer, also happy about suggestions.

@kungfooman
Copy link
Collaborator Author

Looking like this in VSCode:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: input Input related issue feature request open source contribution Issues related to contributions from people outside the PlayCanvas team requires: discussion It isn't clear what needs to be done
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for mouseenter and mouseleave events
7 participants