From 9cc7a8fa7de1e4462f50943ab7e8d9ff7870f4c9 Mon Sep 17 00:00:00 2001 From: Josh Tynjala Date: Wed, 31 Jul 2024 14:52:51 -0700 Subject: [PATCH] HTML5Window: allow both onMouseMove and onMouseUp to be dispatched outside the bounds of the parent element, if the mouse button is down Previously, only onMouseUp was allowed, but we want onMouseMove to work too so that dragging objects with the mouse won't look broken. I changed the implementation to use a boolean flag to indicate that the browser window listener should return early, instead of calling event.stopPropagation(), because this allows other JS code in the page to listen to these mouse events, if desired. Our internal implementation shouldn't do something that might break other events. --- .../_internal/backend/html5/HTML5Window.hx | 69 +++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/src/lime/_internal/backend/html5/HTML5Window.hx b/src/lime/_internal/backend/html5/HTML5Window.hx index 6d0869eece..bf1091c6e7 100644 --- a/src/lime/_internal/backend/html5/HTML5Window.hx +++ b/src/lime/_internal/backend/html5/HTML5Window.hx @@ -78,6 +78,8 @@ class HTML5Window private var __focusPending:Bool; + private var __stopMousePropagation = false; + public function new(parent:Window) { this.parent = parent; @@ -655,9 +657,21 @@ class HTML5Window case "mousedown": if (event.currentTarget == parent.element) { - // Release outside browser window + // while the mouse button is down, and the mouse has + // moved outside the bounds of the parent element, we + // want both onMouseMove and onMouseUp to continue to be + // dispatched. otherwise, dragging objects around with + // the mouse will appear broken. + // however, if the mouse button isn't down, and the + // mouse is outside the bounds of the parent element, + // then onMouseMove and onMouseUp don't need to be + // dispatched. + // Flash embedded in HTML worked similarly. Browser.window.addEventListener("mouseup", handleMouseEvent); + Browser.window.addEventListener("mousemove", handleMouseEvent); } + // just to be safe, clear the flag on every mouse down + __stopMousePropagation = false; parent.clickCount = event.detail; parent.onMouseDown.dispatch(x, y, event.button); @@ -691,13 +705,20 @@ class HTML5Window } case "mouseup": - Browser.window.removeEventListener("mouseup", handleMouseEvent); - if (event.currentTarget == parent.element) + // see comment below for mousemove for an explanation of + // what the __stopMousePropagation flag is used for. + if (__stopMousePropagation && event.currentTarget != parent.element) { - event.stopPropagation(); + __stopMousePropagation = false; + return; } + Browser.window.removeEventListener("mouseup", handleMouseEvent); + Browser.window.removeEventListener("mousemove", handleMouseEvent); + + __stopMousePropagation = event.currentTarget == parent.element; + parent.clickCount = event.detail; parent.onMouseUp.dispatch(x, y, event.button); parent.clickCount = 0; @@ -708,6 +729,46 @@ class HTML5Window } case "mousemove": + + // this same listener is added to the parent element and to + // the browser window for both the mousemove and the mouseup + // event types, if mousedown happens first. this allows both + // onMouseMove and onMouseUp to be dispatched if the mouse + // moves outside the bounds of the parent element. + + // since browser mouse events bubble, this listener will be + // called for the parent element first, as long as the mouse + // is still over the parent element. in that case, when the + // listener is called for the browser window, it should + // return early so that onMouseMove or onMouseUp isn't + // dispatched twice. this is done by checking the + // __stopMousePropagation flag when the current target isn't + // the parent element. + + // however, if the mouse isn't over the parent element, the + // listener will be called only for the browser window, and + // not the parent element. in that case, it can proceed to + // dispatch either onMouseMove or onMouseUp, since this + // listener was called only once. + + // again, this applies only if the mouse button is down. if + // the mouse button isn't down, then the listener won't be + // added to the browser window, and event won't be + // dispatched outside the bounds of the parent element. + + if (__stopMousePropagation && event.currentTarget != parent.element) + { + // why not call event.stopPropagation() here? well, + // other JS code in the page may still be interested in + // the event. listening for the same events on both the + // parent element and on the browser window is just an + // implementation detail and shouldn't affect other + // listeners. + __stopMousePropagation = false; + return; + } + __stopMousePropagation = event.currentTarget == parent.element; + if (x != cacheMouseX || y != cacheMouseY) { parent.onMouseMove.dispatch(x, y);