summaryrefslogtreecommitdiff
path: root/apps/web/src/masks/snap.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/masks/snap.ts')
-rw-r--r--apps/web/src/masks/snap.ts179
1 files changed, 109 insertions, 70 deletions
diff --git a/apps/web/src/masks/snap.ts b/apps/web/src/masks/snap.ts
index cff62ebb..519783a9 100644
--- a/apps/web/src/masks/snap.ts
+++ b/apps/web/src/masks/snap.ts
@@ -6,30 +6,20 @@ import {
snapScale,
snapScaleAxes,
type ScaleEdgePreference,
- type SnapLine,
} from "@/preview/preview-snap";
-import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types";
+import type {
+ MaskHandleId,
+ 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",
- "bottom-left",
- "bottom-right",
-]);
-
function getClampedRatio({
next,
base,
@@ -45,28 +35,27 @@ function getClampedRatio({
function getPreferredEdges({
handleId,
}: {
- handleId: string;
+ handleId: MaskHandleId;
}): 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;
+ if (handleId.kind === "edge") {
+ return {
+ left: handleId.side === "left",
+ right: handleId.side === "right",
+ top: handleId.side === "top",
+ bottom: handleId.side === "bottom",
+ };
+ }
- return Object.values(preferredEdges).some(Boolean)
- ? preferredEdges
- : undefined;
+ if (handleId.kind === "corner") {
+ return {
+ left: handleId.corner.x === "left",
+ right: handleId.corner.x === "right",
+ top: handleId.corner.y === "top",
+ bottom: handleId.corner.y === "bottom",
+ };
+ }
+
+ return undefined;
}
function snapMaskPosition({
@@ -75,11 +64,33 @@ function snapMaskPosition({
canvasSize,
snapThreshold,
}: {
- proposedParams: SharedMaskParams;
+ proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
-}): MaskSnapResult<SharedMaskParams> {
+}): MaskSnapResult<RectangleMaskParams>;
+function snapMaskPosition({
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+}: {
+ proposedParams: SplitMaskParams;
+ bounds: ElementBounds;
+ canvasSize: { width: number; height: number };
+ snapThreshold: { x: number; y: number };
+}): 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 +126,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,
});
@@ -142,20 +159,13 @@ function snapBoxMaskSize({
canvasSize,
snapThreshold,
}: {
- handleId: string;
- startParams: SharedMaskParams;
- proposedParams: SharedMaskParams;
+ handleId: MaskHandleId;
+ 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,
@@ -171,7 +181,10 @@ function snapBoxMaskSize({
Math.max(startParams.height, MIN_MASK_DIMENSION) * bounds.height;
const preferredEdges = getPreferredEdges({ handleId });
- if (handleId === "right" || handleId === "left") {
+ if (
+ handleId.kind === "edge" &&
+ (handleId.side === "right" || handleId.side === "left")
+ ) {
const proposedScaleX = getClampedRatio({
next: proposedParams.width,
base: startParams.width,
@@ -201,7 +214,10 @@ function snapBoxMaskSize({
};
}
- if (handleId === "top" || handleId === "bottom") {
+ if (
+ handleId.kind === "edge" &&
+ (handleId.side === "top" || handleId.side === "bottom")
+ ) {
const proposedScaleY = getClampedRatio({
next: proposedParams.height,
base: startParams.height,
@@ -234,7 +250,7 @@ function snapBoxMaskSize({
};
}
- if (handleId === "scale") {
+ if (handleId.kind === "scale") {
const baseScale = Math.max(startParams.scale, MIN_MASK_DIMENSION);
const proposedScale = getClampedRatio({
next: proposedParams.scale,
@@ -264,7 +280,7 @@ function snapBoxMaskSize({
};
}
- if (CORNER_SIZE_HANDLES.has(handleId)) {
+ if (handleId.kind === "corner") {
const proposedScale = getClampedRatio({
next: proposedParams.width,
base: startParams.width,
@@ -297,7 +313,7 @@ function snapBoxMaskSize({
return { params: proposedParams, activeLines: [] };
}
-export function snapMaskInteraction<TParams extends SharedMaskParams>({
+export function snapBoxMaskInteraction({
handleId,
startParams,
proposedParams,
@@ -305,24 +321,24 @@ export function snapMaskInteraction<TParams extends SharedMaskParams>({
canvasSize,
snapThreshold,
}: {
- handleId: string;
- startParams: TParams;
- proposedParams: TParams;
+ handleId: MaskHandleId;
+ startParams: RectangleMaskParams;
+ proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
-}): MaskSnapResult<TParams> {
- if (handleId === "position") {
+}): MaskSnapResult<RectangleMaskParams> {
+ if (handleId.kind === "position") {
return snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
- }) as MaskSnapResult<TParams>;
+ });
}
- if (handleId === "rotation") {
- return snapMaskRotation({ proposedParams }) as MaskSnapResult<TParams>;
+ if (handleId.kind === "rotation") {
+ return snapMaskRotation({ proposedParams });
}
return snapBoxMaskSize({
@@ -332,5 +348,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.kind === "position") {
+ return snapMaskPosition({
+ proposedParams,
+ bounds,
+ canvasSize,
+ snapThreshold,
+ });
+ }
+
+ if (handleId.kind === "rotation") {
+ return snapMaskRotation({ proposedParams });
+ }
+
+ return { params: proposedParams, activeLines: [] };
}