From 8f31173f4f7b4018a4ed75a8b030ff36f4131568 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Thu, 4 Sep 2025 14:37:21 -0700 Subject: [PATCH] Make mouse dragging pixel-perfect --- .../pages/PageDraw/hooks/useGlobalMouseUp.ts | 48 +++++++------ .../src/pages/PageEdit/EditPaintSurface.tsx | 70 ++++++++++--------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/frontend/apps/editor3d/app/src/pages/PageDraw/hooks/useGlobalMouseUp.ts b/frontend/apps/editor3d/app/src/pages/PageDraw/hooks/useGlobalMouseUp.ts index 835c9c060d..32b8ce5ba5 100644 --- a/frontend/apps/editor3d/app/src/pages/PageDraw/hooks/useGlobalMouseUp.ts +++ b/frontend/apps/editor3d/app/src/pages/PageDraw/hooks/useGlobalMouseUp.ts @@ -1,28 +1,30 @@ +import { Vector2d } from 'konva/lib/types'; import { useEffect } from 'react'; -// This piece of code ensures that when you are selecting or dragging and go off the stage, +import { DragState } from '~/pages/PageEdit/EditPaintSurface'; +// This piece of code ensures that when you are selecting or dragging and go off the stage, // it releases the controls mouse drag. export const useGlobalMouseUp = ( - setIsDragging: (value: boolean) => void, - setIsDrawing: (value: boolean) => void, - setCurrentLineId: (value: string | null) => void, - setIsSelecting: (value: boolean) => void, - isSelectingRef: React.MutableRefObject, - setSelectionRect: (value: any) => void, - setLastPoint: (value: { x: number, y: number } | null) => void, - onSelectionChange?: (isSelecting: boolean) => void, - ) => { - useEffect(() => { - const handleGlobalMouseUp = () => { - // Always reset all states when mouse is released anywhere - setIsDragging(false); - setIsDrawing(false); - setCurrentLineId(null); - setIsSelecting(false); - isSelectingRef.current = false; - onSelectionChange?.(false); - setSelectionRect(null); - setLastPoint(null); - }; + setIsDragging: (value: DragState | undefined) => void, + setIsDrawing: (value: boolean) => void, + setCurrentLineId: (value: string | null) => void, + setIsSelecting: (value: boolean) => void, + isSelectingRef: React.MutableRefObject, + setSelectionRect: (value: any) => void, + setLastPoint: (value: { x: number, y: number } | null) => void, + onSelectionChange?: (isSelecting: boolean) => void, +) => { + useEffect(() => { + const handleGlobalMouseUp = () => { + // Always reset all states when mouse is released anywhere + setIsDragging(undefined); + setIsDrawing(false); + setCurrentLineId(null); + setIsSelecting(false); + isSelectingRef.current = false; + onSelectionChange?.(false); + setSelectionRect(null); + setLastPoint(null); + }; // Listen to mouseup on the entire window window.addEventListener('mouseup', handleGlobalMouseUp); @@ -33,4 +35,4 @@ export const useGlobalMouseUp = ( window.removeEventListener('touchend', handleGlobalMouseUp); }; }, [onSelectionChange]); -} \ No newline at end of file +} diff --git a/frontend/apps/editor3d/app/src/pages/PageEdit/EditPaintSurface.tsx b/frontend/apps/editor3d/app/src/pages/PageEdit/EditPaintSurface.tsx index a80bb0ec7d..727d182a58 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEdit/EditPaintSurface.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEdit/EditPaintSurface.tsx @@ -22,6 +22,12 @@ import { LineNode } from "../PageDraw/stores/SceneState"; import { Node } from "../PageDraw/Node"; import { checkerboard } from "@storyteller/common"; import { loadImageFromUrl } from "~/Helpers/ImageHelpers"; +import { Vector2d } from "konva/lib/types"; + +export interface DragState extends Vector2d { + anchorX: number; + anchorY: number; +} export type EditPaintSurfaceProps = { nodes: Node[]; @@ -87,7 +93,7 @@ export const EditPaintSurface = ({ endY: number; } | null>(null); const [isSelecting, setIsSelecting] = useState(false); - const [isDragging, setIsDragging] = useState(false); + const [isDragging, setIsDragging] = useState(undefined); // Add a ref to track if we're currently selecting const isSelectingRef = React.useRef(false); @@ -188,7 +194,7 @@ export const EditPaintSurface = ({ e.target.name()?.includes("rotater"); if (!isWithinLeftPanel(stagePoint) && !isTransformerTarget) { - setIsDragging(true); + setIsDragging({ ...point, anchorX: stage.x(), anchorY: stage.y() }); return; } @@ -199,14 +205,14 @@ export const EditPaintSurface = ({ if (mouseEvent.button === 1) { // Middle mouse button setIsMiddleMousePressed(true); - setIsDragging(true); + setIsDragging({ ...point, anchorX: stage.x(), anchorY: stage.y() }); return; } if (mouseEvent.button === 2) { // Right mouse button if (!isTransformerTarget) { - setIsDragging(true); + setIsDragging({ ...point, anchorX: stage.x(), anchorY: stage.y() }); } return; } @@ -301,28 +307,28 @@ export const EditPaintSurface = ({ } store.setCursorVisible(false); } - } - // Handle panning with improved dragging - if (isDragging) { - const currentStage = e.target.getStage(); - if (!currentStage) return; - currentStage.container().style.cursor = "grabbing"; + // Handle panning with improved dragging + if (isDragging) { + const currentStage = e.target.getStage(); + if (!currentStage) return; - const dragSensitivity = 3.0; - let newPos = { x: currentStage.x(), y: currentStage.y() }; + currentStage.container().style.cursor = "grabbing"; - if ("movementX" in e.evt && "movementY" in e.evt) { - const mouseEvent = e.evt as MouseEvent; - newPos = { - x: currentStage.x() + mouseEvent.movementX * dragSensitivity, - y: currentStage.y() + mouseEvent.movementY * dragSensitivity, + const displacement = { + x: pointer.x - isDragging.x, + y: pointer.y - isDragging.y + } + const newPos = { + x: isDragging.anchorX + displacement.x, + y: isDragging.anchorY + displacement.y }; - } + console.log(isDragging, displacement, newPos); + currentStage.position(newPos); - currentStage.position(newPos); - return; + return; + } } // Handle drawing - only add points if within bounds @@ -376,11 +382,11 @@ export const EditPaintSurface = ({ setSelectionRect((prev) => prev ? { - // Ensure prev is not null - ...prev, - endX: clampedPoint.x, - endY: clampedPoint.y, - } + // Ensure prev is not null + ...prev, + endX: clampedPoint.x, + endY: clampedPoint.y, + } : null, ); } @@ -441,7 +447,7 @@ export const EditPaintSurface = ({ } // Always reset all states - setIsDragging(false); + setIsDragging(undefined); setIsDrawing(false); setCurrentLineId(null); // Clear the current line ID setIsSelecting(false); @@ -575,7 +581,7 @@ export const EditPaintSurface = ({ const touch2 = touches[1]; return Math.sqrt( Math.pow(touch2.clientX - touch1.clientX, 2) + - Math.pow(touch2.clientY - touch1.clientY, 2), + Math.pow(touch2.clientY - touch1.clientY, 2), ); }; @@ -1337,11 +1343,11 @@ export const EditPaintSurface = ({ scaleY={previewScale} x={0} y={0} - // style={{ - // background: '#f5f5f5', - // border: '1px solid #ddd', - // borderRadius: '8px', - // }} + // style={{ + // background: '#f5f5f5', + // border: '1px solid #ddd', + // borderRadius: '8px', + // }} >