Files
image-studio/input/viewport.ts
2026-07-03 11:31:02 +02:00

80 lines
2.2 KiB
TypeScript

import type { Dispatch } from "@commands/dispatcher";
import { commandIds } from "@commands/ids";
import type { GlobalPointerConsumer, GlobalWheelConsumer, PointerInputEvent, WheelInputEvent } from "./pointer";
export type ViewportPointerPanHandler = {
pointerDown(event: PointerInputEvent): boolean;
pointerMove(event: PointerInputEvent): boolean;
pointerUp(event: PointerInputEvent): boolean;
};
export function createViewportPointerPanHandler(options: {
globalConsumer: GlobalPointerConsumer;
dispatch: Dispatch;
getCurrentZoom: () => number;
canPan?: (event: PointerInputEvent) => boolean;
}): ViewportPointerPanHandler {
let activePointerId: number | undefined;
let lastPosition: PointerInputEvent["position"] | undefined;
const canPan = options.canPan ?? defaultCanPan;
return {
pointerDown(event) {
if (options.globalConsumer(event)) return true;
if (!canPan(event)) return false;
activePointerId = event.pointerId;
lastPosition = event.position;
return true;
},
pointerMove(event) {
if (activePointerId !== event.pointerId || !lastPosition) return false;
const zoom = options.getCurrentZoom();
const screenDelta = {
x: event.position.x - lastPosition.x,
y: event.position.y - lastPosition.y,
};
lastPosition = event.position;
options.dispatch(commandIds.viewportPan, {
delta: {
x: -screenDelta.x / zoom,
y: -screenDelta.y / zoom,
},
});
return true;
},
pointerUp(event) {
if (activePointerId !== event.pointerId) return false;
activePointerId = undefined;
lastPosition = undefined;
return true;
},
};
}
function defaultCanPan(event: PointerInputEvent) {
return event.pointerType !== "mouse" || (event.buttons & 4) === 4;
}
export function handleViewportWheel(options: {
event: WheelInputEvent;
globalConsumer: GlobalWheelConsumer;
dispatch: Dispatch;
currentZoom: number;
}): boolean {
if (options.globalConsumer(options.event)) return true;
const zoomFactor = Math.exp(-options.event.delta.y * 0.001);
options.dispatch(commandIds.viewportZoomAroundPoint, {
zoom: options.currentZoom * zoomFactor,
point: options.event.position,
});
return true;
}