diff --git a/frontend/src/editor.ts b/frontend/src/editor.ts index 8fe2aebdb3..400952be6d 100644 --- a/frontend/src/editor.ts +++ b/frontend/src/editor.ts @@ -1,4 +1,5 @@ // import { panicProxy } from "@graphite/utility-functions/panic-proxy"; + import { type JsMessageType } from "@graphite/messages"; import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/subscription-router"; import init, { setRandomSeed, wasmMemory, EditorHandle, receiveNativeMessage } from "@graphite-frontend/wasm/pkg/graphite_wasm.js"; diff --git a/frontend/src/io-managers/input.ts b/frontend/src/io-managers/input.ts index 6da2dddcda..06baf28798 100644 --- a/frontend/src/io-managers/input.ts +++ b/frontend/src/io-managers/input.ts @@ -39,6 +39,10 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli const shakeSamples: { x: number; y: number; time: number }[] = []; let lastShakeTime = 0; + // Get device pixel ratio for coordinate transformation + // This fixes the offset issue on high-DPI devices like iPad + const getDevicePixelRatio = () => window.devicePixelRatio || 1; + // Event listeners // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -160,9 +164,14 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli const inGraphOverlay = get(document).graphViewOverlayOpen; if (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return; + // Scale coordinates by device pixel ratio to fix offset on high-DPI devices like iPad + const dpr = getDevicePixelRatio(); + const scaledX = e.clientX * dpr; + const scaledY = e.clientY * dpr; + const modifiers = makeKeyboardModifiersBitfield(e); - if (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers); - editor.handle.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers); + if (detectShake(e)) editor.handle.onMouseShake(scaledX, scaledY, e.buttons, modifiers); + editor.handle.onMouseMove(scaledX, scaledY, e.buttons, modifiers); } function onPointerDown(e: PointerEvent) { @@ -190,8 +199,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli } if (viewportPointerInteractionOngoing && isTargetingCanvas instanceof Element) { + // Scale coordinates by device pixel ratio to fix offset on high-DPI devices like iPad + const dpr = getDevicePixelRatio(); + const scaledX = e.clientX * dpr; + const scaledY = e.clientY * dpr; + const modifiers = makeKeyboardModifiersBitfield(e); - editor.handle.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers); + editor.handle.onMouseDown(scaledX, scaledY, e.buttons, modifiers); } } @@ -208,8 +222,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli if (textToolInteractiveInputElement) return; + // Scale coordinates by device pixel ratio to fix offset on high-DPI devices like iPad + const dpr = getDevicePixelRatio(); + const scaledX = e.clientX * dpr; + const scaledY = e.clientY * dpr; + const modifiers = makeKeyboardModifiersBitfield(e); - editor.handle.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers); + editor.handle.onMouseUp(scaledX, scaledY, e.buttons, modifiers); } // Mouse events @@ -233,8 +252,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli if (e.button === BUTTON_BACK) buttons = 8; // Back if (e.button === BUTTON_FORWARD) buttons = 16; // Forward + // Scale coordinates by device pixel ratio to fix offset on high-DPI devices like iPad + const dpr = getDevicePixelRatio(); + const scaledX = e.clientX * dpr; + const scaledY = e.clientY * dpr; + const modifiers = makeKeyboardModifiersBitfield(e); - editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers); + editor.handle.onDoubleClick(scaledX, scaledY, buttons, modifiers); } function onMouseDown(e: MouseEvent) { @@ -268,8 +292,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli if (isTargetingCanvas) { e.preventDefault(); + // Scale coordinates by device pixel ratio to fix offset on high-DPI devices like iPad + const dpr = getDevicePixelRatio(); + const scaledX = e.clientX * dpr; + const scaledY = e.clientY * dpr; + const modifiers = makeKeyboardModifiersBitfield(e); - editor.handle.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers); + editor.handle.onWheelScroll(scaledX, scaledY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers); } } diff --git a/frontend/src/utility-functions/viewports.ts b/frontend/src/utility-functions/viewports.ts index 134ece0b31..9570101c0d 100644 --- a/frontend/src/utility-functions/viewports.ts +++ b/frontend/src/utility-functions/viewports.ts @@ -2,9 +2,14 @@ import { type Editor } from "@graphite/editor"; export function updateBoundsOfViewports(editor: Editor) { const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]")); + + // Get device pixel ratio to scale bounds for high-DPI devices like iPad + const dpr = window.devicePixelRatio || 1; + const boundsOfViewports = viewports.map((canvas) => { const bounds = canvas.getBoundingClientRect(); - return [bounds.left, bounds.top, bounds.right, bounds.bottom]; + // Scale bounds by device pixel ratio to match scaled pointer coordinates + return [bounds.left * dpr, bounds.top * dpr, bounds.right * dpr, bounds.bottom * dpr]; }); const flattened = boundsOfViewports.flat();