summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/masks/snap.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-20 11:31:17 +0200
commit3e89d2998588f8e35c524e523ba2ee853f0f6475 (patch)
tree57221f96c1259b3b07e3ddf8c1adefc502b9d8eb /apps/web/src/lib/masks/snap.ts
parent729d10592f4e802f91c555c309ff3ba4b119f843 (diff)
downloadopencut-3e89d2998588f8e35c524e523ba2ee853f0f6475.tar.gz
refactor: restructure files to their domains, new preview overlay system, and dep graph
Diffstat (limited to 'apps/web/src/lib/masks/snap.ts')
-rw-r--r--apps/web/src/lib/masks/snap.ts336
1 files changed, 0 insertions, 336 deletions
diff --git a/apps/web/src/lib/masks/snap.ts b/apps/web/src/lib/masks/snap.ts
deleted file mode 100644
index 7396dd82..00000000
--- a/apps/web/src/lib/masks/snap.ts
+++ /dev/null
@@ -1,336 +0,0 @@
-import type { ElementBounds } from "@/lib/preview/element-bounds";
-import { MIN_MASK_DIMENSION } from "@/lib/masks/dimensions";
-import {
- snapPosition,
- snapRotation,
- snapScale,
- snapScaleAxes,
- type ScaleEdgePreference,
- type SnapLine,
-} from "@/lib/preview/preview-snap";
-import type { RectangleMaskParams, SplitMaskParams } from "@/lib/masks/types";
-import {
- isRectangleMaskParams,
- getMaskSnapGeometry,
- setMaskLocalCenter,
- toGlobalMaskSnapLines,
-} from "./geometry";
-
-type SharedMaskParams = SplitMaskParams | RectangleMaskParams;
-
-type MaskSnapResult<TParams extends SharedMaskParams> = {
- params: TParams;
- activeLines: SnapLine[];
-};
-
-const CORNER_SIZE_HANDLES = new Set([
- "top-left",
- "top-right",
- "bottom-left",
- "bottom-right",
-]);
-
-function getClampedRatio({
- next,
- base,
-}: {
- next: number;
- base: number;
-}): number {
- return (
- Math.max(next, MIN_MASK_DIMENSION) / Math.max(base, MIN_MASK_DIMENSION)
- );
-}
-
-function getPreferredEdges({
- handleId,
-}: {
- handleId: string;
-}): ScaleEdgePreference | undefined {
- const preferredEdges = {
- left:
- handleId === "left" ||
- handleId === "top-left" ||
- handleId === "bottom-left",
- right:
- handleId === "right" ||
- handleId === "top-right" ||
- handleId === "bottom-right",
- top:
- handleId === "top" || handleId === "top-left" || handleId === "top-right",
- bottom:
- handleId === "bottom" ||
- handleId === "bottom-left" ||
- handleId === "bottom-right",
- } satisfies ScaleEdgePreference;
-
- return Object.values(preferredEdges).some(Boolean)
- ? preferredEdges
- : undefined;
-}
-
-function snapMaskPosition({
- proposedParams,
- bounds,
- canvasSize,
- snapThreshold,
-}: {
- proposedParams: SharedMaskParams;
- bounds: ElementBounds;
- canvasSize: { width: number; height: number };
- snapThreshold: { x: number; y: number };
-}): MaskSnapResult<SharedMaskParams> {
- const geometry = getMaskSnapGeometry({
- params: proposedParams,
- bounds,
- });
- if (!geometry) {
- return { params: proposedParams, activeLines: [] };
- }
-
- const { snappedPosition, activeLines } = snapPosition({
- proposedPosition: geometry.position,
- canvasSize: bounds,
- elementSize: geometry.size,
- rotation: geometry.rotation,
- snapThreshold,
- });
-
- return {
- params: {
- ...proposedParams,
- ...setMaskLocalCenter({
- center: snappedPosition,
- bounds,
- }),
- },
- activeLines: toGlobalMaskSnapLines({
- lines: activeLines,
- bounds,
- canvasSize,
- }),
- };
-}
-
-function snapMaskRotation({
- proposedParams,
-}: {
- proposedParams: SharedMaskParams;
-}): MaskSnapResult<SharedMaskParams> {
- if (typeof proposedParams.rotation !== "number") {
- return { params: proposedParams, activeLines: [] };
- }
-
- const { snappedRotation } = snapRotation({
- proposedRotation: proposedParams.rotation,
- });
-
- return {
- params: {
- ...proposedParams,
- rotation: snappedRotation,
- },
- activeLines: [],
- };
-}
-
-function snapBoxMaskSize({
- handleId,
- startParams,
- proposedParams,
- bounds,
- canvasSize,
- snapThreshold,
-}: {
- handleId: string;
- startParams: SharedMaskParams;
- proposedParams: SharedMaskParams;
- bounds: ElementBounds;
- canvasSize: { width: number; height: number };
- snapThreshold: { x: number; y: number };
-}): MaskSnapResult<SharedMaskParams> {
- if (
- !isRectangleMaskParams(startParams) ||
- !isRectangleMaskParams(proposedParams)
- ) {
- return { params: proposedParams, activeLines: [] };
- }
-
- const geometry = getMaskSnapGeometry({
- params: proposedParams,
- bounds,
- });
- if (!geometry) {
- return { params: proposedParams, activeLines: [] };
- }
-
- const localCanvasSize = bounds;
- const baseWidth =
- Math.max(startParams.width, MIN_MASK_DIMENSION) * bounds.width;
- const baseHeight =
- Math.max(startParams.height, MIN_MASK_DIMENSION) * bounds.height;
- const preferredEdges = getPreferredEdges({ handleId });
-
- if (handleId === "right" || handleId === "left") {
- const proposedScaleX = getClampedRatio({
- next: proposedParams.width,
- base: startParams.width,
- });
- const { x } = snapScaleAxes({
- proposedScaleX,
- proposedScaleY: 1,
- position: geometry.position,
- baseWidth,
- baseHeight,
- rotation: proposedParams.rotation,
- canvasSize: localCanvasSize,
- snapThreshold,
- preferredEdges,
- });
-
- return {
- params: {
- ...proposedParams,
- width: Math.max(MIN_MASK_DIMENSION, startParams.width * x.snappedScale),
- },
- activeLines: toGlobalMaskSnapLines({
- lines: x.activeLines,
- bounds,
- canvasSize,
- }),
- };
- }
-
- if (handleId === "top" || handleId === "bottom") {
- const proposedScaleY = getClampedRatio({
- next: proposedParams.height,
- base: startParams.height,
- });
- const { y } = snapScaleAxes({
- proposedScaleX: 1,
- proposedScaleY,
- position: geometry.position,
- baseWidth,
- baseHeight,
- rotation: proposedParams.rotation,
- canvasSize: localCanvasSize,
- snapThreshold,
- preferredEdges,
- });
-
- return {
- params: {
- ...proposedParams,
- height: Math.max(
- MIN_MASK_DIMENSION,
- startParams.height * y.snappedScale,
- ),
- },
- activeLines: toGlobalMaskSnapLines({
- lines: y.activeLines,
- bounds,
- canvasSize,
- }),
- };
- }
-
- if (handleId === "scale") {
- const baseScale = Math.max(startParams.scale, MIN_MASK_DIMENSION);
- const proposedScale = getClampedRatio({
- next: proposedParams.scale,
- base: startParams.scale,
- });
- const { snappedScale, activeLines } = snapScale({
- proposedScale,
- position: geometry.position,
- baseWidth: baseWidth * baseScale,
- baseHeight: baseHeight * baseScale,
- rotation: proposedParams.rotation,
- canvasSize: localCanvasSize,
- snapThreshold,
- preferredEdges,
- });
-
- return {
- params: {
- ...proposedParams,
- scale: Math.max(MIN_MASK_DIMENSION, startParams.scale * snappedScale),
- },
- activeLines: toGlobalMaskSnapLines({
- lines: activeLines,
- bounds,
- canvasSize,
- }),
- };
- }
-
- if (CORNER_SIZE_HANDLES.has(handleId)) {
- const proposedScale = getClampedRatio({
- next: proposedParams.width,
- base: startParams.width,
- });
- const { snappedScale, activeLines } = snapScale({
- proposedScale,
- position: geometry.position,
- baseWidth,
- baseHeight,
- rotation: proposedParams.rotation,
- canvasSize: localCanvasSize,
- snapThreshold,
- preferredEdges,
- });
-
- return {
- params: {
- ...proposedParams,
- width: Math.max(MIN_MASK_DIMENSION, startParams.width * snappedScale),
- height: Math.max(MIN_MASK_DIMENSION, startParams.height * snappedScale),
- },
- activeLines: toGlobalMaskSnapLines({
- lines: activeLines,
- bounds,
- canvasSize,
- }),
- };
- }
-
- return { params: proposedParams, activeLines: [] };
-}
-
-export function snapMaskInteraction<TParams extends SharedMaskParams>({
- handleId,
- startParams,
- proposedParams,
- bounds,
- canvasSize,
- snapThreshold,
-}: {
- handleId: string;
- startParams: TParams;
- proposedParams: TParams;
- bounds: ElementBounds;
- canvasSize: { width: number; height: number };
- snapThreshold: { x: number; y: number };
-}): MaskSnapResult<TParams> {
- if (handleId === "position") {
- return snapMaskPosition({
- proposedParams,
- bounds,
- canvasSize,
- snapThreshold,
- }) as MaskSnapResult<TParams>;
- }
-
- if (handleId === "rotation") {
- return snapMaskRotation({ proposedParams }) as MaskSnapResult<TParams>;
- }
-
- return snapBoxMaskSize({
- handleId,
- startParams,
- proposedParams,
- bounds,
- canvasSize,
- snapThreshold,
- }) as MaskSnapResult<TParams>;
-}