summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-29 18:23:08 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 03:27:23 +0200
commit6ea5a156b6b7f609354a4563cfb2d00ec7f41811 (patch)
treee91a5e474867f5c37299cfe96a99ee24cc0a5866 /apps
parent401cc35743aafc4c8bdfb8055501650390bde88b (diff)
downloadopencut-6ea5a156b6b7f609354a4563cfb2d00ec7f41811.tar.gz
refactor: split mask snap dispatch
Made-with: Cursor
Diffstat (limited to 'apps')
-rw-r--r--apps/web/src/masks/__tests__/snap.test.ts12
-rw-r--r--apps/web/src/masks/definitions/box-like.ts4
-rw-r--r--apps/web/src/masks/definitions/split.ts4
-rw-r--r--apps/web/src/masks/snap.ts110
4 files changed, 85 insertions, 45 deletions
diff --git a/apps/web/src/masks/__tests__/snap.test.ts b/apps/web/src/masks/__tests__/snap.test.ts
index 2f33615f..510c6a27 100644
--- a/apps/web/src/masks/__tests__/snap.test.ts
+++ b/apps/web/src/masks/__tests__/snap.test.ts
@@ -13,7 +13,7 @@ import {
import { getSplitMaskStrokeSegment } from "@/masks/definitions/split";
import { textMaskDefinition } from "@/masks/definitions/text";
import { getMaskSnapGeometry } from "@/masks/geometry";
-import { snapMaskInteraction } from "@/masks/snap";
+import { snapBoxMaskInteraction, snapSplitMaskInteraction } from "@/masks/snap";
import type { ElementBounds } from "@/preview/element-bounds";
import type {
CustomMaskParams,
@@ -230,7 +230,7 @@ describe("mask geometry", () => {
describe("mask snapping", () => {
test("snaps split mask movement using the shared position pipeline", () => {
- const result = snapMaskInteraction({
+ const result = snapSplitMaskInteraction({
handleId: "position",
startParams: buildSplitParams({
centerX: 0.03,
@@ -254,7 +254,7 @@ describe("mask snapping", () => {
});
test("snaps box mask movement against element center and edges", () => {
- const result = snapMaskInteraction({
+ const result = snapBoxMaskInteraction({
handleId: "position",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@@ -275,7 +275,7 @@ describe("mask snapping", () => {
});
test("snaps mask rotation through the shared rotation path", () => {
- const result = snapMaskInteraction({
+ const result = snapBoxMaskInteraction({
handleId: "rotation",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@@ -291,7 +291,7 @@ describe("mask snapping", () => {
});
test("snaps edge resize for box masks", () => {
- const result = snapMaskInteraction({
+ const result = snapBoxMaskInteraction({
handleId: "right",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@@ -307,7 +307,7 @@ describe("mask snapping", () => {
});
test("snaps corner resize for box masks", () => {
- const result = snapMaskInteraction({
+ const result = snapBoxMaskInteraction({
handleId: "bottom-right",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
diff --git a/apps/web/src/masks/definitions/box-like.ts b/apps/web/src/masks/definitions/box-like.ts
index 7db51df7..5462c14f 100644
--- a/apps/web/src/masks/definitions/box-like.ts
+++ b/apps/web/src/masks/definitions/box-like.ts
@@ -16,7 +16,7 @@ import {
getBoxMaskHandlePositions,
getBoxMaskOverlays,
} from "@/masks/handle-positions";
-import { snapMaskInteraction } from "@/masks/snap";
+import { snapBoxMaskInteraction } from "@/masks/snap";
const PERCENTAGE_DISPLAY: Pick<
NumberParamDefinition,
@@ -198,7 +198,7 @@ export function buildBoxMaskInteraction({
};
},
snap(args) {
- return snapMaskInteraction(args);
+ return snapBoxMaskInteraction(args);
},
};
}
diff --git a/apps/web/src/masks/definitions/split.ts b/apps/web/src/masks/definitions/split.ts
index d8fdc789..ddd4e452 100644
--- a/apps/web/src/masks/definitions/split.ts
+++ b/apps/web/src/masks/definitions/split.ts
@@ -9,7 +9,7 @@ import {
getLineMaskHandlePositions,
getLineMaskOverlay,
} from "@/masks/handle-positions";
-import { snapMaskInteraction } from "@/masks/snap";
+import { snapSplitMaskInteraction } from "@/masks/snap";
// cos(π/2) returns ~6e-17 in JS, not 0. Values below this threshold are snapped
// to exactly 0 to prevent opposite-sign float noise on canvas corners that lie
@@ -216,7 +216,7 @@ export const splitMaskDefinition: MaskDefinition<SplitMaskParams> = {
};
},
snap(args) {
- return snapMaskInteraction(args);
+ return snapSplitMaskInteraction(args);
},
},
buildDefault() {
diff --git a/apps/web/src/masks/snap.ts b/apps/web/src/masks/snap.ts
index cff62ebb..67a0225d 100644
--- a/apps/web/src/masks/snap.ts
+++ b/apps/web/src/masks/snap.ts
@@ -6,23 +6,19 @@ import {
snapScale,
snapScaleAxes,
type ScaleEdgePreference,
- type SnapLine,
} from "@/preview/preview-snap";
-import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types";
+import type {
+ MaskSnapArgs,
+ MaskSnapResult,
+ RectangleMaskParams,
+ SplitMaskParams,
+} from "@/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",
@@ -75,11 +71,33 @@ function snapMaskPosition({
canvasSize,
snapThreshold,
}: {
- proposedParams: SharedMaskParams;
+ proposedParams: RectangleMaskParams;
+ bounds: ElementBounds;
+ canvasSize: { width: number; height: number };
+ snapThreshold: { x: number; y: number };
+}): MaskSnapResult<RectangleMaskParams>;
+function snapMaskPosition({
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+}: {
+ proposedParams: SplitMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
-}): MaskSnapResult<SharedMaskParams> {
+}): MaskSnapResult<SplitMaskParams>;
+function snapMaskPosition({
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+}: {
+ proposedParams: RectangleMaskParams | SplitMaskParams;
+ bounds: ElementBounds;
+ canvasSize: { width: number; height: number };
+ snapThreshold: { x: number; y: number };
+}): MaskSnapResult<RectangleMaskParams | SplitMaskParams> {
const geometry = getMaskSnapGeometry({
params: proposedParams,
bounds,
@@ -115,12 +133,18 @@ function snapMaskPosition({
function snapMaskRotation({
proposedParams,
}: {
- proposedParams: SharedMaskParams;
-}): MaskSnapResult<SharedMaskParams> {
- if (typeof proposedParams.rotation !== "number") {
- return { params: proposedParams, activeLines: [] };
- }
-
+ proposedParams: RectangleMaskParams;
+}): MaskSnapResult<RectangleMaskParams>;
+function snapMaskRotation({
+ proposedParams,
+}: {
+ proposedParams: SplitMaskParams;
+}): MaskSnapResult<SplitMaskParams>;
+function snapMaskRotation({
+ proposedParams,
+}: {
+ proposedParams: RectangleMaskParams | SplitMaskParams;
+}): MaskSnapResult<RectangleMaskParams | SplitMaskParams> {
const { snappedRotation } = snapRotation({
proposedRotation: proposedParams.rotation,
});
@@ -143,19 +167,12 @@ function snapBoxMaskSize({
snapThreshold,
}: {
handleId: string;
- startParams: SharedMaskParams;
- proposedParams: SharedMaskParams;
+ startParams: RectangleMaskParams;
+ proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
-}): MaskSnapResult<SharedMaskParams> {
- if (
- !isRectangleMaskParams(startParams) ||
- !isRectangleMaskParams(proposedParams)
- ) {
- return { params: proposedParams, activeLines: [] };
- }
-
+}): MaskSnapResult<RectangleMaskParams> {
const geometry = getMaskSnapGeometry({
params: proposedParams,
bounds,
@@ -297,7 +314,7 @@ function snapBoxMaskSize({
return { params: proposedParams, activeLines: [] };
}
-export function snapMaskInteraction<TParams extends SharedMaskParams>({
+export function snapBoxMaskInteraction({
handleId,
startParams,
proposedParams,
@@ -306,23 +323,23 @@ export function snapMaskInteraction<TParams extends SharedMaskParams>({
snapThreshold,
}: {
handleId: string;
- startParams: TParams;
- proposedParams: TParams;
+ startParams: RectangleMaskParams;
+ proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
-}): MaskSnapResult<TParams> {
+}): MaskSnapResult<RectangleMaskParams> {
if (handleId === "position") {
return snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
- }) as MaskSnapResult<TParams>;
+ });
}
if (handleId === "rotation") {
- return snapMaskRotation({ proposedParams }) as MaskSnapResult<TParams>;
+ return snapMaskRotation({ proposedParams });
}
return snapBoxMaskSize({
@@ -332,5 +349,28 @@ export function snapMaskInteraction<TParams extends SharedMaskParams>({
bounds,
canvasSize,
snapThreshold,
- }) as MaskSnapResult<TParams>;
+ });
+}
+
+export function snapSplitMaskInteraction({
+ handleId,
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+}: MaskSnapArgs<SplitMaskParams>): MaskSnapResult<SplitMaskParams> {
+ if (handleId === "position") {
+ return snapMaskPosition({
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+ });
+ }
+
+ if (handleId === "rotation") {
+ return snapMaskRotation({ proposedParams });
+ }
+
+ return { params: proposedParams, activeLines: [] };
}