feat(input): drag artboard transform controls
This commit is contained in:
102
input/transform-controls.ts
Normal file
102
input/transform-controls.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { commandIds } from "@commands/ids";
|
||||
import type { Dispatch } from "@commands/dispatcher";
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import type { Rect, Vec2D } from "@core/geometry";
|
||||
import type { EditorState } from "@editor/state";
|
||||
import type { TransformHandle } from "@editor/transform";
|
||||
import type { PointerInputEvent } from "./pointer";
|
||||
|
||||
export type TransformControlsInputController = {
|
||||
pointerDown(event: PointerInputEvent): boolean;
|
||||
pointerMove(event: PointerInputEvent): boolean;
|
||||
pointerUp(event: PointerInputEvent): boolean;
|
||||
};
|
||||
|
||||
export function createTransformControlsInputController(options: {
|
||||
getDocument: () => ImageDocument;
|
||||
getEditor: () => EditorState;
|
||||
dispatch: Dispatch;
|
||||
}): TransformControlsInputController {
|
||||
return {
|
||||
pointerDown(event) {
|
||||
if (event.pointerType !== "mouse" || (event.buttons & 1) !== 1) return false;
|
||||
|
||||
const editor = options.getEditor();
|
||||
const artboardId = editor.selection.artboardId;
|
||||
if (!artboardId || editor.tools.activeTool !== "select") return false;
|
||||
|
||||
const artboard = options.getDocument().artboards.find((candidate) => candidate.id === artboardId);
|
||||
if (!artboard) return false;
|
||||
|
||||
const handle = hitTestArtboardTransformHandle(event.position, artboard.bounds, editor.viewport);
|
||||
if (!handle) return false;
|
||||
|
||||
options.dispatch(commandIds.transformBegin, {
|
||||
target: { type: "artboard", id: artboard.id },
|
||||
handle,
|
||||
point: viewportPointToDocumentPoint(event.position, editor.viewport),
|
||||
initialBounds: artboard.bounds,
|
||||
});
|
||||
return true;
|
||||
},
|
||||
pointerMove(event) {
|
||||
const editor = options.getEditor();
|
||||
if (!editor.transformSession) return false;
|
||||
|
||||
options.dispatch(commandIds.transformUpdate, { point: viewportPointToDocumentPoint(event.position, editor.viewport) });
|
||||
return true;
|
||||
},
|
||||
pointerUp() {
|
||||
if (!options.getEditor().transformSession) return false;
|
||||
|
||||
options.dispatch(commandIds.transformEnd, undefined);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function hitTestArtboardTransformHandle(position: Vec2D, bounds: Rect, viewport: EditorState["viewport"]): TransformHandle | undefined {
|
||||
const rect = documentRectToViewportRect(bounds, viewport);
|
||||
const handles = transformHandleRects(rect);
|
||||
const handle = handles.find((candidate) => pointInRect(position, candidate.rect));
|
||||
if (handle) return handle.handle;
|
||||
if (pointInRect(position, rect)) return "body";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function transformHandleRects(rect: Rect): { handle: TransformHandle; rect: Rect }[] {
|
||||
const size = 12;
|
||||
const half = size / 2;
|
||||
const points: { handle: TransformHandle; point: Vec2D }[] = [
|
||||
{ handle: "nw", point: { x: rect.x, y: rect.y } },
|
||||
{ handle: "n", point: { x: rect.x + rect.w / 2, y: rect.y } },
|
||||
{ handle: "ne", point: { x: rect.x + rect.w, y: rect.y } },
|
||||
{ handle: "e", point: { x: rect.x + rect.w, y: rect.y + rect.h / 2 } },
|
||||
{ handle: "se", point: { x: rect.x + rect.w, y: rect.y + rect.h } },
|
||||
{ handle: "s", point: { x: rect.x + rect.w / 2, y: rect.y + rect.h } },
|
||||
{ handle: "sw", point: { x: rect.x, y: rect.y + rect.h } },
|
||||
{ handle: "w", point: { x: rect.x, y: rect.y + rect.h / 2 } },
|
||||
];
|
||||
|
||||
return points.map(({ handle, point }) => ({ handle, rect: { x: point.x - half, y: point.y - half, w: size, h: size } }));
|
||||
}
|
||||
|
||||
function viewportPointToDocumentPoint(point: Vec2D, viewport: EditorState["viewport"]): Vec2D {
|
||||
return {
|
||||
x: viewport.center.x + (point.x - viewport.size.w / 2) / viewport.zoom,
|
||||
y: viewport.center.y + (point.y - viewport.size.h / 2) / viewport.zoom,
|
||||
};
|
||||
}
|
||||
|
||||
function documentRectToViewportRect(rect: Rect, viewport: EditorState["viewport"]): Rect {
|
||||
return {
|
||||
x: viewport.size.w / 2 + (rect.x - viewport.center.x) * viewport.zoom,
|
||||
y: viewport.size.h / 2 + (rect.y - viewport.center.y) * viewport.zoom,
|
||||
w: rect.w * viewport.zoom,
|
||||
h: rect.h * viewport.zoom,
|
||||
};
|
||||
}
|
||||
|
||||
function pointInRect(point: Vec2D, rect: Rect) {
|
||||
return point.x >= rect.x && point.x <= rect.x + rect.w && point.y >= rect.y && point.y <= rect.y + rect.h;
|
||||
}
|
||||
Reference in New Issue
Block a user