From b831f394fbc77c556bf3d8e475971c53d27ba759 Mon Sep 17 00:00:00 2001 From: zplata Date: Fri, 8 Dec 2023 01:34:50 +0000 Subject: [PATCH] fix: exaggerate pointer exit listener on mouseout to account for hitareas too close to the artboard border [Bug reported](https://2dimensions.slack.com/archives/CLLCU09T6/p1699924347919509) where hitareas for a listener ~2px away from the Artboard border were not getting their `pointer exit` listener invoked. There seems to be a small rounding issue when taking into account the [2px hitRadius](https://github.com/rive-app/rive-cpp/blob/master/src/animation/state_machine_instance.cpp#L336) we use downstream that doesn't invoke the pointer exit accordingly. Flutter web doesn't have this issue because it taps out early before doing this hitRadius detection by doing a check first on the shape's bounds (which [doesn't exist](https://github.com/rive-app/rive-cpp/blob/master/src/animation/state_machine_instance.cpp#L346) in C++). Adding a large buffer to the coordinates when the user "mouseout"'s the canvas to ensure we report the user has effectively `pointer exit`'d. We can't just add/subtract 2 here because if for example, Rive is setup with `fit: cover`, it expands the rive graphic potentially past the canvas bounds. Open to other suggestions here Diffs= f0ca1b0ef fix: exaggerate pointer exit listener on mouseout to account for hitareas too close to the artboard border (#6319) Co-authored-by: Zachary Plata --- .rive_head | 2 +- js/src/utils/registerTouchInteractions.ts | 22 +++++++++++++++++++++- js/test/registerTouchInteractions.test.ts | 13 +++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.rive_head b/.rive_head index 467da65b..90a7a0df 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -53972cc17b532e83ffee7cae7d3420e95f822e8f +f0ca1b0ef3052ecb92f5bf6ca8411e85d4368e86 diff --git a/js/src/utils/registerTouchInteractions.ts b/js/src/utils/registerTouchInteractions.ts index 3a6ca18a..02298a11 100644 --- a/js/src/utils/registerTouchInteractions.ts +++ b/js/src/utils/registerTouchInteractions.ts @@ -112,10 +112,30 @@ export const registerTouchInteractions = ({ forwardMatrix.delete(); switch (event.type) { + /** + * There's a 2px buffer for a hitRadius when translating the pointer coordinates + * down to the state machine. In cases where the hitbox is about that much away + * from the Artboard border, we don't have exact precision on determining pointer + * exit. We're therefore adding to the translated coordinates on mouseout of a canvas + * to ensure that we report the mouse has truly exited the hitarea. + * https://github.com/rive-app/rive-cpp/blob/master/src/animation/state_machine_instance.cpp#L336 + * + * We add/subtract 10000 to account for when the graphic goes beyond the canvas bound + * due to for example, a fit: 'cover'. Not perfect, but helps reliably (for now) ensure + * we report going out of bounds when the mouse is out of the canvas + */ + case "mouseout": + for (const stateMachine of stateMachines) { + stateMachine.pointerMove( + transformedX < 0 ? transformedX - 10000 : transformedX + 10000, + transformedY < 0 ? transformedY - 10000 : transformedY + 10000 + ); + } + break; + // Pointer moving/hovering on the canvas case "touchmove": case "mouseover": - case "mouseout": case "mousemove": { for (const stateMachine of stateMachines) { stateMachine.pointerMove(transformedX, transformedY); diff --git a/js/test/registerTouchInteractions.test.ts b/js/test/registerTouchInteractions.test.ts index a4363934..38d66d7d 100644 --- a/js/test/registerTouchInteractions.test.ts +++ b/js/test/registerTouchInteractions.test.ts @@ -105,3 +105,16 @@ test("touchend event can invoke pointerUp", (): void => { expect(mockStateMachines[0].pointerMove).not.toBeCalledWith(); expect(mockStateMachines[0].pointerUp).toBeCalledWith(100, 100); }); + +test("mouseout event can invoke pointerMove with out of bounds coordinates", (): void => { + canvas.dispatchEvent( + new MouseEvent("mouseout", { + clientX: -1, + clientY: 1, + }) + ); + + expect(mockStateMachines[0].pointerDown).not.toBeCalled(); + expect(mockStateMachines[0].pointerMove).toBeCalledWith(-10001, 10001); + expect(mockStateMachines[0].pointerUp).not.toBeCalled(); +});