diff --git a/src/platform/input/constants.js b/src/platform/input/constants.js index 6c4913c6259..36112abfbe4 100644 --- a/src/platform/input/constants.js +++ b/src/platform/input/constants.js @@ -52,6 +52,28 @@ export const EVENT_MOUSEUP = 'mouseup'; */ export const EVENT_MOUSEWHEEL = 'mousewheel'; +/** + * Name of event fired when the mouse moves out of `Mouse#_target`. + * + * This also fires upon: + * - entering DOM elements on top of `Mouse#_target` + * - moving out of an unfocused PlayCanvas browser window so you may want to check for focus + * with {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus document.hasFocus()}. + * + * @type {string} + */ +export const EVENT_MOUSEOUT = 'mouseout'; + +/** + * Name of event fired when the mouse enters `Mouse#_target`. + * + * This also fires upon entering an unfocused PlayCanvas browser window so you may want to check for focus + * with {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus document.hasFocus()}. + * + * @type {string} + */ +export const EVENT_MOUSEENTER = 'mouseenter'; + /** * Name of event fired when a new touch occurs. For example, a finger is placed on the device. * diff --git a/src/platform/input/mouse-event.js b/src/platform/input/mouse-event.js index 8176449cbf3..65890c3d4a2 100644 --- a/src/platform/input/mouse-event.js +++ b/src/platform/input/mouse-event.js @@ -52,7 +52,7 @@ class MouseEvent { * @type {number} */ this.y = coords.y; - } else if (isMousePointerLocked()) { + } else if (isMousePointerLocked() || event.type === 'mouseout') { this.x = 0; this.y = 0; } else { diff --git a/src/platform/input/mouse.js b/src/platform/input/mouse.js index d9a86b6cfb3..e4f47ece3ef 100644 --- a/src/platform/input/mouse.js +++ b/src/platform/input/mouse.js @@ -1,7 +1,7 @@ import { platform } from '../../core/platform.js'; import { EventHandler } from '../../core/event-handler.js'; -import { EVENT_MOUSEDOWN, EVENT_MOUSEMOVE, EVENT_MOUSEUP, EVENT_MOUSEWHEEL } from './constants.js'; +import { EVENT_MOUSEDOWN, EVENT_MOUSEENTER, EVENT_MOUSEMOVE, EVENT_MOUSEOUT, EVENT_MOUSEUP, EVENT_MOUSEWHEEL } from './constants.js'; import { isMousePointerLocked, MouseEvent } from './mouse-event.js'; /** @@ -17,6 +17,12 @@ import { isMousePointerLocked, MouseEvent } from './mouse-event.js'; * @category Input */ class Mouse extends EventHandler { + /** + * @type {Element|null} + * @private + */ + _target; + /** * Create a new Mouse instance. * @@ -37,6 +43,8 @@ class Mouse extends EventHandler { this._downHandler = this._handleDown.bind(this); this._moveHandler = this._handleMove.bind(this); this._wheelHandler = this._handleWheel.bind(this); + this._enterHandler = this._handleEnter.bind(this); + this._outHandler = this._handleOut.bind(this); this._contextMenuHandler = (event) => { event.preventDefault(); }; @@ -87,7 +95,7 @@ class Mouse extends EventHandler { /** * Attach mouse events to an Element. * - * @param {Element} element - The DOM element to attach the mouse to. + * @param {Element} [element] - The DOM element to attach the mouse to. */ attach(element) { this._target = element; @@ -100,6 +108,10 @@ class Mouse extends EventHandler { window.addEventListener('mousedown', this._downHandler, opts); window.addEventListener('mousemove', this._moveHandler, opts); window.addEventListener('wheel', this._wheelHandler, opts); + if (element) { + element.addEventListener('mouseenter', this._enterHandler, opts); + element.addEventListener('mouseout', this._outHandler, opts); + } } /** @@ -108,13 +120,17 @@ class Mouse extends EventHandler { detach() { if (!this._attached) return; this._attached = false; - this._target = null; 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('mouseenter', this._enterHandler, opts); + this._target.removeEventListener('mouseout', this._outHandler, opts); + this._target = null; + } } /** @@ -291,6 +307,20 @@ class Mouse extends EventHandler { this.fire(EVENT_MOUSEWHEEL, e); } + _handleEnter(event) { + const e = new MouseEvent(this, event); + if (!e.event) return; + + this.fire(EVENT_MOUSEENTER, e); + } + + _handleOut(event) { + const e = new MouseEvent(this, event); + if (!e.event) return; + + this.fire(EVENT_MOUSEOUT, e); + } + _getTargetCoords(event) { const rect = this._target.getBoundingClientRect(); const left = Math.floor(rect.left);