From 9e0da38f75efa33a58032036b1be9d44cd90a951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86motion=20Studio?= <163354043+AEmotionStudio@users.noreply.github.com> Date: Sat, 7 Feb 2026 19:13:09 -0800 Subject: [PATCH] fix: rewrite glass drag to use pointer capture for reliable cursor reset --- src/magnify-glass/UiManager.ts | 46 +++++++++++++++++++++++----------- web/magnify-glass/UiManager.js | 36 +++++++++++++++++--------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/magnify-glass/UiManager.ts b/src/magnify-glass/UiManager.ts index 33d52e4..f6bf734 100644 --- a/src/magnify-glass/UiManager.ts +++ b/src/magnify-glass/UiManager.ts @@ -114,11 +114,15 @@ export class UiManager { this.glassDiv.style.pointerEvents = 'auto'; this.glassDiv.classList.add('drag-mode'); - // Add drag handlers - const onMouseDown = (e: MouseEvent) => { + // Add drag handlers using pointer events + pointer capture for reliable tracking + const onPointerDown = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); + // Capture pointer to this element — ensures all move/up events + // are delivered here even if the pointer leaves the glass element + this.glassDiv!.setPointerCapture(e.pointerId); + // Calculate the fixed offset between mouse and glass top-left // This maintains the "grab point" relative to the glass const rect = this.glassDiv!.getBoundingClientRect(); @@ -135,12 +139,12 @@ export class UiManager { this.glassDiv!.style.transform = 'none'; // Clear any centering transforms if they exist // Set body cursor so it persists even when dragging outside the glass div - document.body.style.cursor = 'all-scroll'; + this.glassDiv!.style.cursor = 'grabbing'; + document.body.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; - const onMouseMove = (moveEvent: MouseEvent) => { + const onPointerMove = (moveEvent: PointerEvent) => { moveEvent.preventDefault(); - moveEvent.stopPropagation(); // 1:1 Movement: Glass moves exactly with mouse // New Pos = Mouse Pos + Initial Grab Offset @@ -153,14 +157,22 @@ export class UiManager { } }; - const onMouseUp = (upEvent: MouseEvent) => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); + const finishDrag = () => { + this.glassDiv!.removeEventListener('pointermove', onPointerMove); + this.glassDiv!.removeEventListener('pointerup', onPointerUp); + this.glassDiv!.removeEventListener('pointercancel', onPointerUp); + this.glassDiv!.removeEventListener('lostpointercapture', onPointerUp); // Reset body cursor and user-select document.body.style.cursor = ''; document.body.style.userSelect = ''; + // Also reset the ComfyUI canvas cursor directly + const canvas = document.querySelector('canvas.graph-canvas-container, #graph-canvas') as HTMLElement; + if (canvas) { + canvas.style.cursor = ''; + } + // Disable drag mode after drop this.state.isDragModeEnabled = false; this.setDragMode(false); @@ -172,24 +184,30 @@ export class UiManager { } }; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); + const onPointerUp = (upEvent: PointerEvent) => { + finishDrag(); + }; + + this.glassDiv!.addEventListener('pointermove', onPointerMove); + this.glassDiv!.addEventListener('pointerup', onPointerUp); + this.glassDiv!.addEventListener('pointercancel', onPointerUp); + this.glassDiv!.addEventListener('lostpointercapture', onPointerUp); }; - this.glassDiv.addEventListener('mousedown', onMouseDown); - (this.glassDiv as any)._dragHandler = onMouseDown; + this.glassDiv.addEventListener('pointerdown', onPointerDown); + (this.glassDiv as any)._dragHandler = onPointerDown; } else { this.glassDiv.style.cursor = ''; this.glassDiv.style.pointerEvents = 'none'; this.glassDiv.classList.remove('drag-mode'); - // Safety: ensure body cursor/userSelect are reset in case mouseup was missed + // Safety: ensure body cursor/userSelect are reset in case pointerup was missed document.body.style.cursor = ''; document.body.style.userSelect = ''; // Remove drag handler if ((this.glassDiv as any)._dragHandler) { - this.glassDiv.removeEventListener('mousedown', (this.glassDiv as any)._dragHandler); + this.glassDiv.removeEventListener('pointerdown', (this.glassDiv as any)._dragHandler); delete (this.glassDiv as any)._dragHandler; } } diff --git a/web/magnify-glass/UiManager.js b/web/magnify-glass/UiManager.js index b70b813..1ace5d1 100644 --- a/web/magnify-glass/UiManager.js +++ b/web/magnify-glass/UiManager.js @@ -87,9 +87,10 @@ class UiManager { this.glassDiv.style.cursor = "all-scroll"; this.glassDiv.style.pointerEvents = "auto"; this.glassDiv.classList.add("drag-mode"); - const onMouseDown = (e) => { + const onPointerDown = (e) => { e.preventDefault(); e.stopPropagation(); + this.glassDiv.setPointerCapture(e.pointerId); const rect = this.glassDiv.getBoundingClientRect(); const grabOffsetX = rect.left - e.clientX; const grabOffsetY = rect.top - e.clientY; @@ -98,11 +99,11 @@ class UiManager { this.glassDiv.style.right = "auto"; this.glassDiv.style.bottom = "auto"; this.glassDiv.style.transform = "none"; - document.body.style.cursor = "all-scroll"; + this.glassDiv.style.cursor = "grabbing"; + document.body.style.cursor = "grabbing"; document.body.style.userSelect = "none"; - const onMouseMove = (moveEvent) => { + const onPointerMove = (moveEvent) => { moveEvent.preventDefault(); - moveEvent.stopPropagation(); const newLeft = moveEvent.clientX + grabOffsetX; const newTop = moveEvent.clientY + grabOffsetY; if (this.glassDiv) { @@ -110,11 +111,17 @@ class UiManager { this.glassDiv.style.top = `${newTop}px`; } }; - const onMouseUp = (upEvent) => { - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); + const finishDrag = () => { + this.glassDiv.removeEventListener("pointermove", onPointerMove); + this.glassDiv.removeEventListener("pointerup", onPointerUp); + this.glassDiv.removeEventListener("pointercancel", onPointerUp); + this.glassDiv.removeEventListener("lostpointercapture", onPointerUp); document.body.style.cursor = ""; document.body.style.userSelect = ""; + const canvas = document.querySelector("canvas.graph-canvas-container, #graph-canvas"); + if (canvas) { + canvas.style.cursor = ""; + } this.state.isDragModeEnabled = false; this.setDragMode(false); const infoPanel = window.infoPanelManager; @@ -122,11 +129,16 @@ class UiManager { infoPanel.uiManager.updateControlStates(); } }; - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); + const onPointerUp = (upEvent) => { + finishDrag(); + }; + this.glassDiv.addEventListener("pointermove", onPointerMove); + this.glassDiv.addEventListener("pointerup", onPointerUp); + this.glassDiv.addEventListener("pointercancel", onPointerUp); + this.glassDiv.addEventListener("lostpointercapture", onPointerUp); }; - this.glassDiv.addEventListener("mousedown", onMouseDown); - this.glassDiv._dragHandler = onMouseDown; + this.glassDiv.addEventListener("pointerdown", onPointerDown); + this.glassDiv._dragHandler = onPointerDown; } else { this.glassDiv.style.cursor = ""; this.glassDiv.style.pointerEvents = "none"; @@ -134,7 +146,7 @@ class UiManager { document.body.style.cursor = ""; document.body.style.userSelect = ""; if (this.glassDiv._dragHandler) { - this.glassDiv.removeEventListener("mousedown", this.glassDiv._dragHandler); + this.glassDiv.removeEventListener("pointerdown", this.glassDiv._dragHandler); delete this.glassDiv._dragHandler; } }