181 lines
4.5 KiB
TypeScript
181 lines
4.5 KiB
TypeScript
import type { ArtboardId } from "@core/id";
|
|
import type { Vec2D } from "@core/geometry";
|
|
import type { Command } from "./command";
|
|
import { commandIds } from "./ids";
|
|
|
|
export type ViewportPanPayload = {
|
|
delta: Vec2D;
|
|
};
|
|
|
|
export type ViewportSetZoomPayload = {
|
|
zoom: number;
|
|
};
|
|
|
|
export type ViewportZoomAroundPointPayload = {
|
|
zoom: number;
|
|
point: Vec2D;
|
|
};
|
|
|
|
export type ViewportSetSizePayload = {
|
|
w: number;
|
|
h: number;
|
|
};
|
|
|
|
export type ViewportFitArtboardPayload = {
|
|
artboardId?: ArtboardId;
|
|
padding?: number;
|
|
};
|
|
|
|
export const viewportPanCommand: Command<ViewportPanPayload> = {
|
|
id: commandIds.viewportPan,
|
|
name: "Pan viewport",
|
|
execute({ state }, payload) {
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
...state.editor.viewport,
|
|
center: {
|
|
x: state.editor.viewport.center.x + payload.delta.x,
|
|
y: state.editor.viewport.center.y + payload.delta.y,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportSetZoomCommand: Command<ViewportSetZoomPayload> = {
|
|
id: commandIds.viewportSetZoom,
|
|
name: "Set viewport zoom",
|
|
execute({ state }, payload) {
|
|
const zoom = Math.max(0.01, payload.zoom);
|
|
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
...state.editor.viewport,
|
|
zoom,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportZoomAroundPointCommand: Command<ViewportZoomAroundPointPayload> = {
|
|
id: commandIds.viewportZoomAroundPoint,
|
|
name: "Zoom viewport around point",
|
|
execute({ state }, payload) {
|
|
const viewport = state.editor.viewport;
|
|
const zoom = Math.max(0.01, payload.zoom);
|
|
const offset = {
|
|
x: payload.point.x - viewport.size.w / 2,
|
|
y: payload.point.y - viewport.size.h / 2,
|
|
};
|
|
const documentPoint = {
|
|
x: viewport.center.x + offset.x / viewport.zoom,
|
|
y: viewport.center.y + offset.y / viewport.zoom,
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
...viewport,
|
|
zoom,
|
|
center: {
|
|
x: documentPoint.x - offset.x / zoom,
|
|
y: documentPoint.y - offset.y / zoom,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportSetSizeCommand: Command<ViewportSetSizePayload> = {
|
|
id: commandIds.viewportSetSize,
|
|
name: "Set viewport size",
|
|
execute({ state }, payload) {
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
...state.editor.viewport,
|
|
size: {
|
|
w: Math.max(0, payload.w),
|
|
h: Math.max(0, payload.h),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportFitArtboardCommand: Command<ViewportFitArtboardPayload | undefined> = {
|
|
id: commandIds.viewportFitArtboard,
|
|
name: "Fit artboard in viewport",
|
|
execute({ state }, payload) {
|
|
const artboardId = payload?.artboardId ?? state.editor.selection.artboardId;
|
|
const artboard = artboardId
|
|
? state.document.artboards.find((candidate) => candidate.id === artboardId)
|
|
: state.document.artboards[0];
|
|
|
|
if (!artboard) return state;
|
|
|
|
const padding = Math.max(0, payload?.padding ?? 48);
|
|
const availableWidth = Math.max(0, state.editor.viewport.size.w - padding * 2);
|
|
const availableHeight = Math.max(0, state.editor.viewport.size.h - padding * 2);
|
|
const canFitZoom = availableWidth > 0 && availableHeight > 0 && artboard.bounds.w > 0 && artboard.bounds.h > 0;
|
|
const zoom = canFitZoom ? Math.max(0.01, Math.min(availableWidth / artboard.bounds.w, availableHeight / artboard.bounds.h)) : state.editor.viewport.zoom;
|
|
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
...state.editor.viewport,
|
|
center: {
|
|
x: artboard.bounds.x + artboard.bounds.w / 2,
|
|
y: artboard.bounds.y + artboard.bounds.h / 2,
|
|
},
|
|
zoom,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportResetCommand: Command = {
|
|
id: commandIds.viewportReset,
|
|
name: "Reset viewport",
|
|
execute({ state }) {
|
|
return {
|
|
...state,
|
|
editor: {
|
|
...state.editor,
|
|
viewport: {
|
|
center: { x: 0, y: 0 },
|
|
zoom: 1,
|
|
rotation: 0,
|
|
size: state.editor.viewport.size,
|
|
},
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export const viewportCommands = [
|
|
viewportPanCommand,
|
|
viewportSetZoomCommand,
|
|
viewportZoomAroundPointCommand,
|
|
viewportSetSizeCommand,
|
|
viewportFitArtboardCommand,
|
|
viewportResetCommand,
|
|
] satisfies Command<unknown>[];
|