summaryrefslogtreecommitdiff
path: root/apps/web/src/masks
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-29 18:41:15 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 03:27:23 +0200
commit79dcec8a2ec2f5cad9be95e5b52fbfa752fb6ac4 (patch)
tree99ff9393c4a550669ebac921c6a0535437a39413 /apps/web/src/masks
parent97f8d1968f186028e41f83b04c0897130e6c69f4 (diff)
downloadopencut-79dcec8a2ec2f5cad9be95e5b52fbfa752fb6ac4.tar.gz
refactor: split builtin and freeform masks
Made-with: Cursor
Diffstat (limited to 'apps/web/src/masks')
-rw-r--r--apps/web/src/masks/__tests__/snap.test.ts60
-rw-r--r--apps/web/src/masks/builtin/box-like.ts (renamed from apps/web/src/masks/definitions/box-like.ts)0
-rw-r--r--apps/web/src/masks/builtin/definitions/cinematic-bars.ts (renamed from apps/web/src/masks/definitions/cinematic-bars.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/diamond.ts (renamed from apps/web/src/masks/definitions/diamond.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/ellipse.ts (renamed from apps/web/src/masks/definitions/ellipse.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/heart.ts (renamed from apps/web/src/masks/definitions/heart.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/index.ts (renamed from apps/web/src/masks/definitions/index.ts)19
-rw-r--r--apps/web/src/masks/builtin/definitions/rectangle.ts (renamed from apps/web/src/masks/definitions/rectangle.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/split.ts (renamed from apps/web/src/masks/definitions/split.ts)4
-rw-r--r--apps/web/src/masks/builtin/definitions/star.ts (renamed from apps/web/src/masks/definitions/star.ts)2
-rw-r--r--apps/web/src/masks/builtin/definitions/text.ts (renamed from apps/web/src/masks/definitions/text.ts)0
-rw-r--r--apps/web/src/masks/components/masks-tab.tsx53
-rw-r--r--apps/web/src/masks/freeform/definition.ts (renamed from apps/web/src/masks/definitions/custom.ts)102
-rw-r--r--apps/web/src/masks/freeform/path.ts (renamed from apps/web/src/masks/custom-path.ts)106
-rw-r--r--apps/web/src/masks/index.ts40
-rw-r--r--apps/web/src/masks/registry.ts23
-rw-r--r--apps/web/src/masks/types.ts32
-rw-r--r--apps/web/src/masks/use-mask-handles.ts50
18 files changed, 279 insertions, 222 deletions
diff --git a/apps/web/src/masks/__tests__/snap.test.ts b/apps/web/src/masks/__tests__/snap.test.ts
index 152b04f9..6f8421a0 100644
--- a/apps/web/src/masks/__tests__/snap.test.ts
+++ b/apps/web/src/masks/__tests__/snap.test.ts
@@ -1,22 +1,22 @@
import { describe, expect, test } from "bun:test";
import {
- findClosestPointOnCustomMaskSegment,
- getCustomMaskClosedStateAfterPointRemoval,
- insertPointIntoCustomMaskSegment,
- removeCustomMaskPoints,
-} from "@/masks/custom-path";
+ findClosestPointOnFreeformSegment,
+ getFreeformPathClosedStateAfterPointRemoval,
+ insertPointIntoFreeformSegment,
+ removeFreeformPathPoints,
+} from "@/masks/freeform/path";
import {
- appendPointToCustomMask,
- customMaskDefinition,
- insertPointOnCustomMaskSegment,
-} from "@/masks/definitions/custom";
-import { getSplitMaskStrokeSegment } from "@/masks/definitions/split";
-import { textMaskDefinition } from "@/masks/definitions/text";
+ appendPointToFreeformPathMask,
+ freeformMaskDefinition,
+ insertPointOnFreeformSegment,
+} from "@/masks/freeform/definition";
+import { getSplitMaskStrokeSegment } from "@/masks/builtin/definitions/split";
+import { textMaskDefinition } from "@/masks/builtin/definitions/text";
import { getMaskSnapGeometry } from "@/masks/geometry";
import { snapBoxMaskInteraction, snapSplitMaskInteraction } from "@/masks/snap";
import type { ElementBounds } from "@/preview/element-bounds";
import type {
- CustomMaskParams,
+ FreeformPathMaskParams,
RectangleMaskParams,
SplitMaskParams,
TextMaskParams,
@@ -100,9 +100,9 @@ function buildTextMaskParams(
};
}
-function buildCustomMaskParams(
- overrides: Partial<CustomMaskParams> = {},
-): CustomMaskParams {
+function buildFreeformPathMaskParams(
+ overrides: Partial<FreeformPathMaskParams> = {},
+): FreeformPathMaskParams {
return {
feather: 0,
inverted: false,
@@ -347,11 +347,11 @@ describe("mask snapping", () => {
});
test("snaps custom mask movement using path geometry bounds", () => {
- const params = buildCustomMaskParams({
+ const params = buildFreeformPathMaskParams({
centerX: 0.03,
centerY: -0.04,
});
- const result = customMaskDefinition.interaction.snap?.({
+ const result = freeformMaskDefinition.interaction.snap?.({
handleId: { kind: "position" },
startParams: params,
proposedParams: params,
@@ -381,11 +381,11 @@ describe("mask snapping", () => {
describe("custom mask creation", () => {
test("anchors the first point at the click position", () => {
- const params = buildCustomMaskParams({
+ const params = buildFreeformPathMaskParams({
path: [],
closed: false,
});
- const next = appendPointToCustomMask({
+ const next = appendPointToFreeformPathMask({
params,
canvasPoint: { x: bounds.cx + 20, y: bounds.cy - 10 },
bounds,
@@ -401,8 +401,8 @@ describe("custom mask creation", () => {
describe("custom mask point deletion", () => {
test("removes the selected points by id", () => {
- const points = buildCustomMaskParams().path;
- const nextPoints = removeCustomMaskPoints({
+ const points = buildFreeformPathMaskParams().path;
+ const nextPoints = removeFreeformPathPoints({
points,
pointIds: ["b"],
});
@@ -411,14 +411,14 @@ describe("custom mask point deletion", () => {
});
test("reopens a closed path once fewer than three points remain", () => {
- const points = buildCustomMaskParams().path;
- const nextPoints = removeCustomMaskPoints({
+ const points = buildFreeformPathMaskParams().path;
+ const nextPoints = removeFreeformPathPoints({
points,
pointIds: ["c"],
});
expect(
- getCustomMaskClosedStateAfterPointRemoval({
+ getFreeformPathClosedStateAfterPointRemoval({
wasClosed: true,
remainingPointCount: nextPoints.length,
}),
@@ -428,9 +428,9 @@ describe("custom mask point deletion", () => {
describe("custom mask point insertion", () => {
test("finds the closest point on the clicked segment", () => {
- const params = buildCustomMaskParams();
+ const params = buildFreeformPathMaskParams();
const points = params.path;
- const closestPoint = findClosestPointOnCustomMaskSegment({
+ const closestPoint = findClosestPointOnFreeformSegment({
points,
segmentIndex: 0,
canvasPoint: { x: bounds.cx, y: bounds.cy - 10 },
@@ -449,8 +449,8 @@ describe("custom mask point insertion", () => {
});
test("splits a segment into two segments at the insertion point", () => {
- const points = buildCustomMaskParams().path;
- const nextPoints = insertPointIntoCustomMaskSegment({
+ const points = buildFreeformPathMaskParams().path;
+ const nextPoints = insertPointIntoFreeformSegment({
points,
segmentIndex: 0,
pointId: "new",
@@ -471,8 +471,8 @@ describe("custom mask point insertion", () => {
});
test("builds updated custom mask params for a clicked segment", () => {
- const result = insertPointOnCustomMaskSegment({
- params: buildCustomMaskParams(),
+ const result = insertPointOnFreeformSegment({
+ params: buildFreeformPathMaskParams(),
segmentIndex: 0,
canvasPoint: { x: bounds.cx, y: bounds.cy - 10 },
bounds,
diff --git a/apps/web/src/masks/definitions/box-like.ts b/apps/web/src/masks/builtin/box-like.ts
index 37706755..37706755 100644
--- a/apps/web/src/masks/definitions/box-like.ts
+++ b/apps/web/src/masks/builtin/box-like.ts
diff --git a/apps/web/src/masks/definitions/cinematic-bars.ts b/apps/web/src/masks/builtin/definitions/cinematic-bars.ts
index 68717c02..0e23fd58 100644
--- a/apps/web/src/masks/definitions/cinematic-bars.ts
+++ b/apps/web/src/masks/builtin/definitions/cinematic-bars.ts
@@ -10,7 +10,7 @@ import {
getDefaultBaseMaskParams,
getStrokeOffset,
rotatePoint,
-} from "./box-like";
+} from "../box-like";
function getDefaultCinematicBarsMaskParams({
elementSize,
diff --git a/apps/web/src/masks/definitions/diamond.ts b/apps/web/src/masks/builtin/definitions/diamond.ts
index fe96dbe9..1effb276 100644
--- a/apps/web/src/masks/definitions/diamond.ts
+++ b/apps/web/src/masks/builtin/definitions/diamond.ts
@@ -7,7 +7,7 @@ import {
getDefaultSquareMaskParams,
getStrokeOffset,
rotatePoint,
-} from "./box-like";
+} from "../box-like";
function buildDiamondPath({
centerX,
diff --git a/apps/web/src/masks/definitions/ellipse.ts b/apps/web/src/masks/builtin/definitions/ellipse.ts
index f57d7444..688a2fa6 100644
--- a/apps/web/src/masks/definitions/ellipse.ts
+++ b/apps/web/src/masks/builtin/definitions/ellipse.ts
@@ -6,7 +6,7 @@ import {
getBoxLikeGeometry,
getDefaultSquareMaskParams,
getStrokeOffset,
-} from "./box-like";
+} from "../box-like";
export const ellipseMaskDefinition: MaskDefinition<"ellipse"> = {
type: "ellipse",
diff --git a/apps/web/src/masks/definitions/heart.ts b/apps/web/src/masks/builtin/definitions/heart.ts
index ec546f18..d1af5eb2 100644
--- a/apps/web/src/masks/definitions/heart.ts
+++ b/apps/web/src/masks/builtin/definitions/heart.ts
@@ -7,7 +7,7 @@ import {
getDefaultSquareMaskParams,
getStrokeOffset,
rotatePoint,
-} from "./box-like";
+} from "../box-like";
function buildHeartPath({
centerX,
diff --git a/apps/web/src/masks/definitions/index.ts b/apps/web/src/masks/builtin/definitions/index.ts
index a5aec404..9f722876 100644
--- a/apps/web/src/masks/definitions/index.ts
+++ b/apps/web/src/masks/builtin/definitions/index.ts
@@ -1,10 +1,9 @@
import {
- masksRegistry,
- type MaskDefinitionForRegistration,
+ builtinMasksRegistry,
+ type BuiltinMaskDefinitionForRegistration,
type MaskIconProps,
-} from "../registry";
+} from "../../registry";
import { cinematicBarsMaskDefinition } from "./cinematic-bars";
-import { customMaskDefinition } from "./custom";
import { diamondMaskDefinition } from "./diamond";
import { ellipseMaskDefinition } from "./ellipse";
import { heartMaskDefinition } from "./heart";
@@ -27,17 +26,17 @@ function registerDefaultMask({
definition,
icon,
}: {
- definition: MaskDefinitionForRegistration;
+ definition: BuiltinMaskDefinitionForRegistration;
icon: MaskIconProps;
}) {
- if (masksRegistry.has(definition.type)) {
+ if (builtinMasksRegistry.has(definition.type)) {
return;
}
- masksRegistry.registerMask({ definition, icon });
+ builtinMasksRegistry.registerMask({ definition, icon });
}
-export function registerDefaultMasks(): void {
+export function registerBuiltinMasks(): void {
registerDefaultMask({
definition: splitMaskDefinition,
icon: { icon: PanelRightDashedIcon, strokeWidth: 1 },
@@ -70,8 +69,4 @@ export function registerDefaultMasks(): void {
definition: textMaskDefinition,
icon: { icon: TextFontIcon },
});
- registerDefaultMask({
- definition: customMaskDefinition,
- icon: { icon: SquareIcon },
- });
}
diff --git a/apps/web/src/masks/definitions/rectangle.ts b/apps/web/src/masks/builtin/definitions/rectangle.ts
index db7a4578..4dd38154 100644
--- a/apps/web/src/masks/definitions/rectangle.ts
+++ b/apps/web/src/masks/builtin/definitions/rectangle.ts
@@ -7,7 +7,7 @@ import {
getDefaultSquareMaskParams,
getStrokeOffset,
rotatePoint,
-} from "./box-like";
+} from "../box-like";
function buildRectanglePath({
centerX,
diff --git a/apps/web/src/masks/definitions/split.ts b/apps/web/src/masks/builtin/definitions/split.ts
index 138db91d..13dd1c09 100644
--- a/apps/web/src/masks/definitions/split.ts
+++ b/apps/web/src/masks/builtin/definitions/split.ts
@@ -1,10 +1,10 @@
-import { computeFeatherUpdate } from "../param-update";
+import { computeFeatherUpdate } from "@/masks/param-update";
import type {
MaskDefinition,
MaskParamUpdateArgs,
SplitMaskParams,
} from "@/masks/types";
-import { halfPlaneSign, lineEdgeIntersection } from "../utils";
+import { halfPlaneSign, lineEdgeIntersection } from "@/masks/utils";
import {
getLineMaskHandlePositions,
getLineMaskOverlay,
diff --git a/apps/web/src/masks/definitions/star.ts b/apps/web/src/masks/builtin/definitions/star.ts
index 2a1a9b7e..62e9cbc4 100644
--- a/apps/web/src/masks/definitions/star.ts
+++ b/apps/web/src/masks/builtin/definitions/star.ts
@@ -7,7 +7,7 @@ import {
getDefaultSquareMaskParams,
getStrokeOffset,
rotatePoint,
-} from "./box-like";
+} from "../box-like";
const STAR_INNER_RADIUS_RATIO = 0.45;
const STAR_VERTEX_COUNT = 10;
diff --git a/apps/web/src/masks/definitions/text.ts b/apps/web/src/masks/builtin/definitions/text.ts
index db2af7e0..db2af7e0 100644
--- a/apps/web/src/masks/definitions/text.ts
+++ b/apps/web/src/masks/builtin/definitions/text.ts
diff --git a/apps/web/src/masks/components/masks-tab.tsx b/apps/web/src/masks/components/masks-tab.tsx
index 97fba3ab..d6729d6d 100644
--- a/apps/web/src/masks/components/masks-tab.tsx
+++ b/apps/web/src/masks/components/masks-tab.tsx
@@ -3,7 +3,11 @@
import type { MaskableElement } from "@/timeline";
import type { Mask, MaskType, TextMask } from "@/masks/types";
import type { NumberParamDefinition, SelectParamDefinition } from "@/params";
-import { masksRegistry, buildDefaultMaskInstance } from "@/masks";
+import {
+ buildDefaultMaskInstance,
+ getMaskDefinition,
+ getMaskDefinitionsForMenu,
+} from "@/masks";
import { useEditor } from "@/editor/use-editor";
import { useElementPreview } from "@/timeline/hooks/use-element-preview";
import { useMenuPreview } from "@/editor/use-menu-preview";
@@ -85,12 +89,43 @@ type PreviewParamHandler = (
key: string,
) => (value: number | string | boolean) => void;
-type RegisteredMaskDefinition = ReturnType<(typeof masksRegistry)["get"]>;
+type RegisteredMaskDefinition = ReturnType<typeof getMaskDefinition>;
function isTextMask(mask: Mask): mask is TextMask {
return mask.type === "text";
}
+function withPreviewedMaskParam({
+ mask,
+ key,
+ value,
+}: {
+ mask: Mask;
+ key: string;
+ value: number | string | boolean;
+}): Mask {
+ switch (mask.type) {
+ case "split":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "cinematic-bars":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "rectangle":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "ellipse":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "heart":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "diamond":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "star":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "text":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ case "freeform":
+ return { ...mask, params: { ...mask.params, [key]: value } };
+ }
+}
+
export function MasksTab({ element, trackId }: MasksTabProps) {
const editor = useEditor();
const { renderElement, previewUpdates, commit } =
@@ -99,7 +134,7 @@ export function MasksTab({ element, trackId }: MasksTabProps) {
elementId: element.id,
fallback: element,
});
- const maskDefs = masksRegistry.getAll();
+ const maskDefs = getMaskDefinitionsForMenu();
const tracks = useEditor(
(e) => e.timeline.getPreviewTracks() ?? e.scenes.getActiveScene().tracks,
);
@@ -210,16 +245,10 @@ export function MasksTab({ element, trackId }: MasksTabProps) {
const updatedMasks = renderMasks.map((existingMask, maskIndex) =>
maskIndex !== index
? existingMask
- : {
- ...existingMask,
- params: {
- ...existingMask.params,
- [key]: value,
- },
- },
+ : withPreviewedMaskParam({ mask: existingMask, key, value }),
);
- previewUpdates({ masks: updatedMasks } as Partial<MaskableElement>);
+ previewUpdates({ masks: updatedMasks });
};
return (
@@ -301,7 +330,7 @@ function MaskItem({
onCommit,
}: MaskItemProps) {
const editor = useEditor();
- const definition = masksRegistry.get(mask.type);
+ const definition = getMaskDefinition(mask.type);
return (
<Section sectionKey={`mask-item:${mask.id}`} showTopBorder={false}>
diff --git a/apps/web/src/masks/definitions/custom.ts b/apps/web/src/masks/freeform/definition.ts
index 08155aae..b657218d 100644
--- a/apps/web/src/masks/definitions/custom.ts
+++ b/apps/web/src/masks/freeform/definition.ts
@@ -3,26 +3,26 @@ import type { ParamDefinition } from "@/params";
import { PEN_CURSOR } from "@/preview/components/cursors";
import type { ElementBounds } from "@/preview/element-bounds";
import type {
- CustomMask,
- CustomMaskParams,
+ FreeformPathMask,
+ FreeformPathMaskParams,
MaskDefinition,
MaskHandlePosition,
MaskOverlay,
MaskParamUpdateArgs,
} from "@/masks/types";
import {
- buildCustomMaskPath2D,
- buildCustomMaskSvgPath,
- customMaskCanvasPointToLocal,
- findClosestPointOnCustomMaskSegment,
- getCustomMaskCanvasSegments,
- getCustomMaskCanvasGeometry,
- getCustomMaskLocalBounds,
- getCustomMaskSegmentCount,
- insertPointIntoCustomMaskSegment,
- recenterCustomMaskPath,
- type CustomMaskPathPoint,
-} from "@/masks/custom-path";
+ buildFreeformPath2D,
+ buildFreeformSvgPath,
+ freeformCanvasPointToLocal,
+ findClosestPointOnFreeformSegment,
+ getFreeformCanvasSegments,
+ getFreeformCanvasGeometry,
+ getFreeformLocalBounds,
+ getFreeformSegmentCount,
+ insertPointIntoFreeformSegment,
+ recenterFreeformPath,
+ type FreeformPathPoint,
+} from "@/masks/freeform/path";
import { getBoxMaskHandlePositions } from "@/masks/handle-positions";
import { computeFeatherUpdate } from "@/masks/param-update";
import {
@@ -40,7 +40,7 @@ const PERCENTAGE_DISPLAY = {
step: 1,
} as const;
-const CUSTOM_MASK_PARAMS: ParamDefinition<keyof CustomMaskParams & string>[] = [
+const FREEFORM_PATH_MASK_PARAMS: ParamDefinition<keyof FreeformPathMaskParams & string>[] = [
{
key: "centerX",
label: "X",
@@ -79,12 +79,12 @@ const CUSTOM_MASK_PARAMS: ParamDefinition<keyof CustomMaskParams & string>[] = [
},
];
-function getCustomMaskDisplayHandles({
+function getFreeformDisplayHandles({
params,
displayScale,
bounds,
}: {
- params: CustomMaskParams;
+ params: FreeformPathMaskParams;
displayScale: number;
bounds: ElementBounds;
}): {
@@ -92,7 +92,7 @@ function getCustomMaskDisplayHandles({
overlays: MaskOverlay[];
} {
const points = params.path;
- const geometry = getCustomMaskCanvasGeometry({
+ const geometry = getFreeformCanvasGeometry({
points,
centerX: params.centerX,
centerY: params.centerY,
@@ -108,7 +108,7 @@ function getCustomMaskDisplayHandles({
overlays.push({
id: "path",
type: "canvas-path",
- pathData: buildCustomMaskSvgPath({
+ pathData: buildFreeformSvgPath({
points,
centerX: params.centerX,
centerY: params.centerY,
@@ -124,7 +124,7 @@ function getCustomMaskDisplayHandles({
if (params.closed) {
const segmentStrokeWidth = 12;
overlays.push(
- ...getCustomMaskCanvasSegments({
+ ...getFreeformCanvasSegments({
points,
centerX: params.centerX,
centerY: params.centerY,
@@ -145,7 +145,7 @@ function getCustomMaskDisplayHandles({
);
}
- const localBounds = getCustomMaskLocalBounds({ points, bounds });
+ const localBounds = getFreeformLocalBounds({ points, bounds });
if (params.closed && localBounds) {
handles.push(
...getBoxMaskHandlePositions({
@@ -179,19 +179,19 @@ function getCustomMaskDisplayHandles({
};
}
-function updateCustomMaskPoint({
+function updateFreeformPathMaskPoint({
points,
pointId,
updater,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
pointId: string;
- updater: (point: CustomMaskPathPoint) => CustomMaskPathPoint;
+ updater: (point: FreeformPathPoint) => FreeformPathPoint;
}) {
return points.map((point) => (point.id === pointId ? updater(point) : point));
}
-function computeCustomMaskParamUpdate({
+function computeFreeformParamUpdate({
handleId,
startParams,
deltaX,
@@ -199,7 +199,7 @@ function computeCustomMaskParamUpdate({
startCanvasX,
startCanvasY,
bounds,
-}: MaskParamUpdateArgs<CustomMaskParams>): Partial<CustomMaskParams> {
+}: MaskParamUpdateArgs<FreeformPathMaskParams>): Partial<FreeformPathMaskParams> {
if (handleId.kind === "position") {
return {
centerX: startParams.centerX + deltaX / bounds.width,
@@ -266,7 +266,7 @@ function computeCustomMaskParamUpdate({
x: startCanvasX + deltaX,
y: startCanvasY + deltaY,
};
- const localPoint = customMaskCanvasPointToLocal({
+ const localPoint = freeformCanvasPointToLocal({
point: currentPoint,
centerX: startParams.centerX,
centerY: startParams.centerY,
@@ -276,7 +276,7 @@ function computeCustomMaskParamUpdate({
});
return {
- path: updateCustomMaskPoint({
+ path: updateFreeformPathMaskPoint({
points,
pointId: handleId.pointId,
updater: (point) => {
@@ -306,15 +306,15 @@ function computeCustomMaskParamUpdate({
};
}
-export const customMaskDefinition: MaskDefinition<"custom"> = {
- type: "custom",
- name: "Custom",
+export const freeformMaskDefinition: MaskDefinition<"freeform"> = {
+ type: "freeform",
+ name: "Pen tool",
features: {
hasPosition: true,
hasRotation: true,
sizeMode: "uniform",
},
- params: CUSTOM_MASK_PARAMS,
+ params: FREEFORM_PATH_MASK_PARAMS,
interaction: {
getInteraction({
params,
@@ -323,7 +323,7 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
scaleX: _scaleX,
scaleY: _scaleY,
}) {
- return getCustomMaskDisplayHandles({ params, bounds, displayScale });
+ return getFreeformDisplayHandles({ params, bounds, displayScale });
},
snap({
handleId,
@@ -334,7 +334,7 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
snapThreshold,
}) {
const points = startParams.path;
- const localBounds = getCustomMaskLocalBounds({ points, bounds });
+ const localBounds = getFreeformLocalBounds({ points, bounds });
if (!startParams.closed || !localBounds) {
return {
params: proposedParams,
@@ -422,9 +422,9 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
};
},
},
- buildDefault(): Omit<CustomMask, "id"> {
+ buildDefault(): Omit<FreeformPathMask, "id"> {
return {
- type: "custom",
+ type: "freeform",
params: {
feather: 0,
inverted: false,
@@ -440,7 +440,7 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
},
};
},
- computeParamUpdate: computeCustomMaskParamUpdate,
+ computeParamUpdate: computeFreeformParamUpdate,
isActive(params) {
return params.closed;
},
@@ -454,7 +454,7 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
return new Path2D();
}
- return buildCustomMaskPath2D({
+ return buildFreeformPath2D({
points,
centerX: params.centerX,
centerY: params.centerY,
@@ -480,7 +480,7 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
}
const points = params.path;
- const path = buildCustomMaskPath2D({
+ const path = buildFreeformPath2D({
points,
centerX: params.centerX,
centerY: params.centerY,
@@ -520,15 +520,15 @@ export const customMaskDefinition: MaskDefinition<"custom"> = {
},
};
-export function appendPointToCustomMask({
+export function appendPointToFreeformPathMask({
params,
canvasPoint,
bounds,
}: {
- params: CustomMaskParams;
+ params: FreeformPathMaskParams;
canvasPoint: { x: number; y: number };
bounds: ElementBounds;
-}): CustomMaskParams {
+}): FreeformPathMaskParams {
const points = params.path;
if (points.length === 0) {
@@ -554,7 +554,7 @@ export function appendPointToCustomMask({
};
}
- const localPoint = customMaskCanvasPointToLocal({
+ const localPoint = freeformCanvasPointToLocal({
point: canvasPoint,
centerX: params.centerX,
centerY: params.centerY,
@@ -574,7 +574,7 @@ export function appendPointToCustomMask({
outY: 0,
},
];
- const recentered = recenterCustomMaskPath({
+ const recentered = recenterFreeformPath({
points: nextPoints,
centerX: params.centerX,
centerY: params.centerY,
@@ -591,25 +591,25 @@ export function appendPointToCustomMask({
};
}
-export function insertPointOnCustomMaskSegment({
+export function insertPointOnFreeformSegment({
params,
segmentIndex,
canvasPoint,
bounds,
pointId = generateUUID(),
}: {
- params: CustomMaskParams;
+ params: FreeformPathMaskParams;
segmentIndex: number;
canvasPoint: { x: number; y: number };
bounds: ElementBounds;
pointId?: string;
-}): { params: CustomMaskParams; pointId: string } | null {
+}): { params: FreeformPathMaskParams; pointId: string } | null {
const points = params.path;
- if (getCustomMaskSegmentCount({ points, closed: params.closed }) === 0) {
+ if (getFreeformSegmentCount({ points, closed: params.closed }) === 0) {
return null;
}
- const closestPoint = findClosestPointOnCustomMaskSegment({
+ const closestPoint = findClosestPointOnFreeformSegment({
points,
segmentIndex,
canvasPoint,
@@ -624,7 +624,7 @@ export function insertPointOnCustomMaskSegment({
return null;
}
- const nextPoints = insertPointIntoCustomMaskSegment({
+ const nextPoints = insertPointIntoFreeformSegment({
points,
segmentIndex,
pointId,
@@ -635,7 +635,7 @@ export function insertPointOnCustomMaskSegment({
return null;
}
- const recentered = recenterCustomMaskPath({
+ const recentered = recenterFreeformPath({
points: nextPoints,
centerX: params.centerX,
centerY: params.centerY,
diff --git a/apps/web/src/masks/custom-path.ts b/apps/web/src/masks/freeform/path.ts
index 363bb3bd..fa6217c2 100644
--- a/apps/web/src/masks/custom-path.ts
+++ b/apps/web/src/masks/freeform/path.ts
@@ -1,6 +1,6 @@
import type { ElementBounds } from "@/preview/element-bounds";
-export interface CustomMaskPathPoint {
+export interface FreeformPathPoint {
id: string;
x: number;
y: number;
@@ -10,7 +10,7 @@ export interface CustomMaskPathPoint {
outY: number;
}
-function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint {
+function isFreeformPathPoint(value: unknown): value is FreeformPathPoint {
if (!value || typeof value !== "object") {
return false;
}
@@ -33,38 +33,38 @@ function isCustomMaskPathPoint(value: unknown): value is CustomMaskPathPoint {
);
}
-export function parseCustomMaskPath({
+export function parseFreeformPath({
path,
}: {
path: string;
-}): CustomMaskPathPoint[] {
+}): FreeformPathPoint[] {
if (!path) {
return [];
}
try {
const parsed = JSON.parse(path);
- return Array.isArray(parsed) ? parsed.filter(isCustomMaskPathPoint) : [];
+ return Array.isArray(parsed) ? parsed.filter(isFreeformPathPoint) : [];
} catch {
return [];
}
}
-export function serializeCustomMaskPath({
+export function serializeFreeformPath({
points,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
}): string {
return JSON.stringify(points);
}
-export function removeCustomMaskPoints({
+export function removeFreeformPathPoints({
points,
pointIds,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
pointIds: string[];
-}): CustomMaskPathPoint[] {
+}): FreeformPathPoint[] {
if (pointIds.length === 0) {
return points;
}
@@ -72,7 +72,7 @@ export function removeCustomMaskPoints({
return points.filter((point) => !pointIdsToRemove.has(point.id));
}
-export function getCustomMaskClosedStateAfterPointRemoval({
+export function getFreeformPathClosedStateAfterPointRemoval({
wasClosed,
remainingPointCount,
}: {
@@ -100,7 +100,7 @@ function rotatePoint({
};
}
-export function getCustomMaskCenterCanvasPoint({
+export function getFreeformCenterCanvasPoint({
centerX,
centerY,
bounds,
@@ -115,7 +115,7 @@ export function getCustomMaskCenterCanvasPoint({
};
}
-export function customMaskLocalPointToCanvas({
+export function freeformLocalPointToCanvas({
point,
centerX,
centerY,
@@ -130,7 +130,7 @@ export function customMaskLocalPointToCanvas({
scale: number;
bounds: ElementBounds;
}): { x: number; y: number } {
- const center = getCustomMaskCenterCanvasPoint({ centerX, centerY, bounds });
+ const center = getFreeformCenterCanvasPoint({ centerX, centerY, bounds });
const scaledLocal = {
x: point.x * bounds.width * scale,
y: point.y * bounds.height * scale,
@@ -147,7 +147,7 @@ export function customMaskLocalPointToCanvas({
};
}
-export function customMaskCanvasPointToLocal({
+export function freeformCanvasPointToLocal({
point,
centerX,
centerY,
@@ -162,7 +162,7 @@ export function customMaskCanvasPointToLocal({
scale: number;
bounds: ElementBounds;
}): { x: number; y: number } {
- const center = getCustomMaskCenterCanvasPoint({ centerX, centerY, bounds });
+ const center = getFreeformCenterCanvasPoint({ centerX, centerY, bounds });
const translated = {
x: point.x - center.x,
y: point.y - center.y,
@@ -179,7 +179,7 @@ export function customMaskCanvasPointToLocal({
};
}
-export function getCustomMaskCanvasGeometry({
+export function getFreeformCanvasGeometry({
points,
centerX,
centerY,
@@ -187,7 +187,7 @@ export function getCustomMaskCanvasGeometry({
scale,
bounds,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
centerX: number;
centerY: number;
rotation: number;
@@ -196,7 +196,7 @@ export function getCustomMaskCanvasGeometry({
}) {
const anchors = points.map((point) => ({
id: point.id,
- anchor: customMaskLocalPointToCanvas({
+ anchor: freeformLocalPointToCanvas({
point: { x: point.x, y: point.y },
centerX,
centerY,
@@ -204,7 +204,7 @@ export function getCustomMaskCanvasGeometry({
scale,
bounds,
}),
- inHandle: customMaskLocalPointToCanvas({
+ inHandle: freeformLocalPointToCanvas({
point: { x: point.x + point.inX, y: point.y + point.inY },
centerX,
centerY,
@@ -212,7 +212,7 @@ export function getCustomMaskCanvasGeometry({
scale,
bounds,
}),
- outHandle: customMaskLocalPointToCanvas({
+ outHandle: freeformLocalPointToCanvas({
point: { x: point.x + point.outX, y: point.y + point.outY },
centerX,
centerY,
@@ -288,7 +288,7 @@ function getCanvasPointBounds({ points }: { points: CanvasPoint[] }): {
};
}
-export interface CustomMaskCanvasSegment {
+export interface FreeformCanvasSegment {
index: number;
startPointId: string;
endPointId: string;
@@ -358,16 +358,16 @@ function evaluateCubicBezier({
};
}
-function getCustomMaskSegmentIndices({
+function getFreeformSegmentIndices({
points,
segmentIndex,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
segmentIndex: number;
closed: boolean;
}): { startIndex: number; endIndex: number } | null {
- const segmentCount = getCustomMaskSegmentCount({ points, closed });
+ const segmentCount = getFreeformSegmentCount({ points, closed });
if (segmentIndex < 0 || segmentIndex >= segmentCount) {
return null;
}
@@ -378,11 +378,11 @@ function getCustomMaskSegmentIndices({
};
}
-export function getCustomMaskSegmentCount({
+export function getFreeformSegmentCount({
points,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
closed: boolean;
}): number {
if (points.length < 2) {
@@ -391,7 +391,7 @@ export function getCustomMaskSegmentCount({
return closed ? points.length : points.length - 1;
}
-export function getCustomMaskCanvasSegments({
+export function getFreeformCanvasSegments({
points,
centerX,
centerY,
@@ -400,15 +400,15 @@ export function getCustomMaskCanvasSegments({
bounds,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
centerX: number;
centerY: number;
rotation: number;
scale: number;
bounds: ElementBounds;
closed: boolean;
-}): CustomMaskCanvasSegment[] {
- const geometry = getCustomMaskCanvasGeometry({
+}): FreeformCanvasSegment[] {
+ const geometry = getFreeformCanvasGeometry({
points,
centerX,
centerY,
@@ -416,7 +416,7 @@ export function getCustomMaskCanvasSegments({
scale,
bounds,
});
- const segmentCount = getCustomMaskSegmentCount({ points, closed });
+ const segmentCount = getFreeformSegmentCount({ points, closed });
return Array.from({ length: segmentCount }, (_, segmentIndex) => {
const start = geometry.anchors[segmentIndex];
@@ -434,7 +434,7 @@ export function getCustomMaskCanvasSegments({
});
}
-export function findClosestPointOnCustomMaskSegment({
+export function findClosestPointOnFreeformSegment({
points,
segmentIndex,
canvasPoint,
@@ -445,7 +445,7 @@ export function findClosestPointOnCustomMaskSegment({
bounds,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
segmentIndex: number;
canvasPoint: CanvasPoint;
centerX: number;
@@ -455,7 +455,7 @@ export function findClosestPointOnCustomMaskSegment({
bounds: ElementBounds;
closed: boolean;
}): { t: number; point: CanvasPoint } | null {
- const segment = getCustomMaskCanvasSegments({
+ const segment = getFreeformCanvasSegments({
points,
centerX,
centerY,
@@ -531,20 +531,20 @@ export function findClosestPointOnCustomMaskSegment({
};
}
-export function insertPointIntoCustomMaskSegment({
+export function insertPointIntoFreeformSegment({
points,
segmentIndex,
pointId,
t,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
segmentIndex: number;
pointId: string;
t: number;
closed: boolean;
-}): CustomMaskPathPoint[] {
- const indices = getCustomMaskSegmentIndices({
+}): FreeformPathPoint[] {
+ const indices = getFreeformSegmentIndices({
points,
segmentIndex,
closed,
@@ -596,11 +596,11 @@ export function insertPointIntoCustomMaskSegment({
return nextPoints;
}
-export function getCustomMaskLocalBounds({
+export function getFreeformLocalBounds({
points,
bounds,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
bounds: ElementBounds;
}) {
if (points.length === 0) {
@@ -632,7 +632,7 @@ export function getCustomMaskLocalBounds({
};
}
-export function recenterCustomMaskPath({
+export function recenterFreeformPath({
points,
centerX,
centerY,
@@ -640,7 +640,7 @@ export function recenterCustomMaskPath({
scale,
bounds,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
centerX: number;
centerY: number;
rotation: number;
@@ -651,7 +651,7 @@ export function recenterCustomMaskPath({
return { centerX, centerY, points };
}
- const geometry = getCustomMaskCanvasGeometry({
+ const geometry = getFreeformCanvasGeometry({
points,
centerX,
centerY,
@@ -676,7 +676,7 @@ export function recenterCustomMaskPath({
};
const nextPoints = geometry.anchors.map((point) => {
- const anchor = customMaskCanvasPointToLocal({
+ const anchor = freeformCanvasPointToLocal({
point: point.anchor,
centerX: nextCenterLocal.x,
centerY: nextCenterLocal.y,
@@ -684,7 +684,7 @@ export function recenterCustomMaskPath({
scale,
bounds,
});
- const inHandle = customMaskCanvasPointToLocal({
+ const inHandle = freeformCanvasPointToLocal({
point: point.inHandle,
centerX: nextCenterLocal.x,
centerY: nextCenterLocal.y,
@@ -692,7 +692,7 @@ export function recenterCustomMaskPath({
scale,
bounds,
});
- const outHandle = customMaskCanvasPointToLocal({
+ const outHandle = freeformCanvasPointToLocal({
point: point.outHandle,
centerX: nextCenterLocal.x,
centerY: nextCenterLocal.y,
@@ -719,7 +719,7 @@ export function recenterCustomMaskPath({
};
}
-export function buildCustomMaskPath2D({
+export function buildFreeformPath2D({
points,
centerX,
centerY,
@@ -728,7 +728,7 @@ export function buildCustomMaskPath2D({
bounds,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
centerX: number;
centerY: number;
rotation: number;
@@ -741,7 +741,7 @@ export function buildCustomMaskPath2D({
return path;
}
- const geometry = getCustomMaskCanvasGeometry({
+ const geometry = getFreeformCanvasGeometry({
points,
centerX,
centerY,
@@ -782,7 +782,7 @@ export function buildCustomMaskPath2D({
return path;
}
-export function buildCustomMaskSvgPath({
+export function buildFreeformSvgPath({
points,
centerX,
centerY,
@@ -791,7 +791,7 @@ export function buildCustomMaskSvgPath({
bounds,
closed,
}: {
- points: CustomMaskPathPoint[];
+ points: FreeformPathPoint[];
centerX: number;
centerY: number;
rotation: number;
@@ -803,7 +803,7 @@ export function buildCustomMaskSvgPath({
return "";
}
- const geometry = getCustomMaskCanvasGeometry({
+ const geometry = getFreeformCanvasGeometry({
points,
centerX,
centerY,
diff --git a/apps/web/src/masks/index.ts b/apps/web/src/masks/index.ts
index df7649e1..be5faef7 100644
--- a/apps/web/src/masks/index.ts
+++ b/apps/web/src/masks/index.ts
@@ -1,9 +1,15 @@
import type { Mask, MaskDefaultContext, MaskType } from "@/masks/types";
-import { masksRegistry } from "./registry";
+import {
+ BASE_MASK_PARAM_DEFINITIONS,
+ builtinMasksRegistry,
+ type RegisteredBuiltinMaskDefinition,
+} from "./registry";
+import { freeformMaskDefinition } from "./freeform/definition";
import { generateUUID } from "@/utils/id";
+import { SquareIcon } from "@hugeicons/core-free-icons";
-export { masksRegistry } from "./registry";
-export { registerDefaultMasks } from "./definitions";
+export { builtinMasksRegistry } from "./registry";
+export { registerBuiltinMasks as registerDefaultMasks } from "./builtin/definitions";
type MaskWithoutId = Mask extends infer TMask
? TMask extends Mask
@@ -29,11 +35,35 @@ function withMaskId({ mask, id }: { mask: MaskWithoutId; id: string }): Mask {
return { ...mask, id };
case "text":
return { ...mask, id };
- case "custom":
+ case "freeform":
return { ...mask, id };
}
}
+export function getMaskDefinition(maskType: MaskType): RegisteredBuiltinMaskDefinition {
+ if (maskType === "freeform") {
+ return {
+ ...freeformMaskDefinition,
+ params: [...freeformMaskDefinition.params, ...BASE_MASK_PARAM_DEFINITIONS],
+ icon: { icon: SquareIcon },
+ } as RegisteredBuiltinMaskDefinition;
+ }
+
+ return builtinMasksRegistry.get(maskType);
+}
+
+export function getMaskDefinitionsForMenu() {
+ return [
+ ...builtinMasksRegistry.getAll(),
+ {
+ ...freeformMaskDefinition,
+ name: "Pen tool",
+ params: [...freeformMaskDefinition.params, ...BASE_MASK_PARAM_DEFINITIONS],
+ icon: { icon: SquareIcon },
+ },
+ ];
+}
+
export function buildDefaultMaskInstance({
maskType,
elementSize,
@@ -41,7 +71,7 @@ export function buildDefaultMaskInstance({
maskType: MaskType;
elementSize?: { width: number; height: number };
}): Mask {
- const definition = masksRegistry.get(maskType);
+ const definition = getMaskDefinition(maskType);
const context: MaskDefaultContext = { elementSize };
return withMaskId({
mask: definition.buildDefault(context),
diff --git a/apps/web/src/masks/registry.ts b/apps/web/src/masks/registry.ts
index 64ab2f60..77861926 100644
--- a/apps/web/src/masks/registry.ts
+++ b/apps/web/src/masks/registry.ts
@@ -2,6 +2,7 @@ import { MAX_FEATHER } from "@/masks/feather";
import type { ParamDefinition } from "@/params";
import type {
BaseMaskParams,
+ BuiltinMaskType,
Mask,
MaskDefaultContext,
MaskDefinition,
@@ -23,11 +24,11 @@ type RegisteredMaskWithoutId = Mask extends infer TMask
: never
: never;
-export type MaskDefinitionForRegistration = {
- [TType in MaskType]: MaskDefinition<TType>;
-}[MaskType];
+export type BuiltinMaskDefinitionForRegistration = {
+ [TType in BuiltinMaskType]: MaskDefinition<TType>;
+}[BuiltinMaskType];
-const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition<
+export const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition<
keyof BaseMaskParams & string
>[] = [
{
@@ -57,7 +58,7 @@ const BASE_MASK_PARAM_DEFINITIONS: ParamDefinition<
},
];
-export interface RegisteredMaskDefinition {
+export interface RegisteredBuiltinMaskDefinition {
type: MaskType;
name: string;
features: MaskDefinition["features"];
@@ -72,9 +73,9 @@ export interface RegisteredMaskDefinition {
icon: MaskIconProps;
}
-export class MasksRegistry extends DefinitionRegistry<
- MaskType,
- RegisteredMaskDefinition
+export class BuiltinMasksRegistry extends DefinitionRegistry<
+ BuiltinMaskType,
+ RegisteredBuiltinMaskDefinition
> {
constructor() {
super("mask");
@@ -84,10 +85,10 @@ export class MasksRegistry extends DefinitionRegistry<
definition,
icon,
}: {
- definition: MaskDefinitionForRegistration;
+ definition: BuiltinMaskDefinitionForRegistration;
icon: MaskIconProps;
}): void {
- const withBaseParams: RegisteredMaskDefinition = {
+ const withBaseParams: RegisteredBuiltinMaskDefinition = {
type: definition.type,
name: definition.name,
features: definition.features,
@@ -106,4 +107,4 @@ export class MasksRegistry extends DefinitionRegistry<
}
}
-export const masksRegistry = new MasksRegistry();
+export const builtinMasksRegistry = new BuiltinMasksRegistry();
diff --git a/apps/web/src/masks/types.ts b/apps/web/src/masks/types.ts
index 261edcaf..59f84f71 100644
--- a/apps/web/src/masks/types.ts
+++ b/apps/web/src/masks/types.ts
@@ -1,14 +1,14 @@
import type { ElementBounds } from "@/preview/element-bounds";
import type { SnapLine } from "@/preview/preview-snap";
import type { ParamDefinition } from "@/params";
-import type { CustomMaskPathPoint } from "@/masks/custom-path";
+import type { FreeformPathPoint } from "@/masks/freeform/path";
import type {
TextDecoration,
TextFontStyle,
TextFontWeight,
} from "@/text/primitives";
-export type MaskType =
+export type BuiltinMaskType =
| "split"
| "cinematic-bars"
| "rectangle"
@@ -16,8 +16,9 @@ export type MaskType =
| "heart"
| "diamond"
| "star"
- | "text"
- | "custom";
+ | "text";
+
+export type MaskType = BuiltinMaskType | "freeform";
export interface BaseMaskParams {
feather: number;
@@ -57,8 +58,8 @@ export interface TextMaskParams extends BaseMaskParams {
scale: number;
}
-export interface CustomMaskParams extends BaseMaskParams {
- path: CustomMaskPathPoint[];
+export interface FreeformPathMaskParams extends BaseMaskParams {
+ path: FreeformPathPoint[];
closed: boolean;
centerX: number;
centerY: number;
@@ -114,13 +115,7 @@ export interface TextMask {
params: TextMaskParams;
}
-export interface CustomMask {
- id: string;
- type: "custom";
- params: CustomMaskParams;
-}
-
-export type Mask =
+export type BuiltinShapeMask =
| SplitMask
| CinematicBarsMask
| RectangleMask
@@ -128,8 +123,15 @@ export type Mask =
| HeartMask
| DiamondMask
| StarMask
- | TextMask
- | CustomMask;
+ | TextMask;
+
+export interface FreeformPathMask {
+ id: string;
+ type: "freeform";
+ params: FreeformPathMaskParams;
+}
+
+export type Mask = BuiltinShapeMask | FreeformPathMask;
export type MaskByType<TType extends MaskType> = Extract<Mask, { type: TType }>;
export type MaskParamsByType<TType extends MaskType> =
diff --git a/apps/web/src/masks/use-mask-handles.ts b/apps/web/src/masks/use-mask-handles.ts
index 00ab0de8..f889e029 100644
--- a/apps/web/src/masks/use-mask-handles.ts
+++ b/apps/web/src/masks/use-mask-handles.ts
@@ -2,8 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePreviewViewport } from "@/preview/components/preview-viewport";
import { useEditor } from "@/editor/use-editor";
import { useShiftKey } from "@/hooks/use-shift-key";
-import { masksRegistry } from "@/masks";
-import { appendPointToCustomMask } from "@/masks/definitions/custom";
+import { getMaskDefinition } from "@/masks";
+import { appendPointToFreeformPathMask } from "@/masks/freeform/definition";
import {
getVisibleElementsWithBounds,
type ElementBounds,
@@ -158,7 +158,7 @@ export function useMaskHandles({
const { handles: baseHandlePositions, overlays }: MaskInteractionResult =
selectedWithMask
? (() => {
- const def = masksRegistry.get(selectedWithMask.mask.type);
+ const def = getMaskDefinition(selectedWithMask.mask.type);
const { x: scaleX, y: scaleY } = viewport.getDisplayScale();
const displayScale = (scaleX + scaleY) / 2;
return def.interaction.getInteraction({
@@ -195,7 +195,7 @@ export function useMaskHandles({
const customMaskPointIds = useMemo(
() =>
- selectedWithMask?.mask.type === "custom"
+ selectedWithMask?.mask.type === "freeform"
? handlePositions
.filter((h) => h.kind === "point")
.map((h) => {
@@ -205,8 +205,8 @@ export function useMaskHandles({
: [],
[handlePositions, selectedWithMask?.mask.type],
);
- const isCreatingCustomMask =
- selectedWithMask?.mask.type === "custom" &&
+ const isCreatingFreeformPathMask =
+ selectedWithMask?.mask.type === "freeform" &&
(!selectedWithMask.mask.params.closed || customMaskPointIds.length === 0);
useEffect(() => {
@@ -215,7 +215,7 @@ export function useMaskHandles({
}
if (
!selectedWithMask ||
- selectedWithMask.mask.type !== "custom" ||
+ selectedWithMask.mask.type !== "freeform" ||
!isMaskSelectionForElement({
trackId: selectedWithMask.trackId,
elementId: selectedWithMask.elementId,
@@ -254,7 +254,7 @@ export function useMaskHandles({
selectedWithMask,
]);
- const updateCustomMaskPointSelection = useCallback(
+ const updateFreeformPathMaskPointSelection = useCallback(
({
pointId,
toggleSelection,
@@ -262,7 +262,7 @@ export function useMaskHandles({
pointId: string;
toggleSelection: boolean;
}) => {
- if (!selectedWithMask || selectedWithMask.mask.type !== "custom") {
+ if (!selectedWithMask || selectedWithMask.mask.type !== "freeform") {
return;
}
@@ -347,21 +347,21 @@ export function useMaskHandles({
if (event.button !== 0) return;
event.stopPropagation();
const anchorHandle =
- selectedWithMask.mask.type === "custom" && handleId.kind === "anchor"
+ selectedWithMask.mask.type === "freeform" && handleId.kind === "anchor"
? handleId
: null;
const segmentHandle =
- selectedWithMask.mask.type === "custom" && handleId.kind === "segment"
+ selectedWithMask.mask.type === "freeform" && handleId.kind === "segment"
? handleId
: null;
- if (isCreatingCustomMask) {
+ if (isCreatingFreeformPathMask) {
const firstPointId = customMaskPointIds[0];
if (
firstPointId &&
handleId.kind === "anchor" &&
handleId.pointId === firstPointId &&
customMaskPointIds.length >= 3 &&
- selectedWithMask.mask.type === "custom"
+ selectedWithMask.mask.type === "freeform"
) {
const updatedMask = withUpdatedMaskParams({
mask: selectedWithMask.mask,
@@ -394,7 +394,7 @@ export function useMaskHandles({
});
if (!pos) return;
- if (segmentHandle && selectedWithMask.mask.type === "custom") {
+ if (segmentHandle && selectedWithMask.mask.type === "freeform") {
setActiveHandleId(handleId);
pendingSegmentInsertRef.current = {
trackId: selectedWithMask.trackId,
@@ -418,14 +418,14 @@ export function useMaskHandles({
}
if (anchorHandle) {
- updateCustomMaskPointSelection({
+ updateFreeformPathMaskPointSelection({
pointId: anchorHandle.pointId,
toggleSelection: event.shiftKey,
});
if (event.shiftKey) {
return;
}
- } else if (selectedWithMask.mask.type === "custom") {
+ } else if (selectedWithMask.mask.type === "freeform") {
editor.selection.clearMaskPointSelection();
}
@@ -449,22 +449,22 @@ export function useMaskHandles({
customMaskPointIds,
editor.selection,
editor.timeline,
- isCreatingCustomMask,
+ isCreatingFreeformPathMask,
selectedWithMask,
- updateCustomMaskPointSelection,
+ updateFreeformPathMaskPointSelection,
viewport,
],
);
const handleCanvasPointerDown = useCallback(
({ event }: { event: React.PointerEvent }) => {
- if (!selectedWithMask || !isCreatingCustomMask) {
+ if (!selectedWithMask || !isCreatingFreeformPathMask) {
return;
}
if (event.button !== 0) {
return;
}
- if (selectedWithMask.mask.type !== "custom") {
+ if (selectedWithMask.mask.type !== "freeform") {
return;
}
@@ -477,7 +477,7 @@ export function useMaskHandles({
return;
}
- const nextParams = appendPointToCustomMask({
+ const nextParams = appendPointToFreeformPathMask({
params: selectedWithMask.mask.params,
canvasPoint: pos,
bounds: selectedWithMask.bounds,
@@ -502,7 +502,7 @@ export function useMaskHandles({
],
});
},
- [editor.timeline, isCreatingCustomMask, selectedWithMask, viewport],
+ [editor.timeline, isCreatingFreeformPathMask, selectedWithMask, viewport],
);
const handlePointerMove = useCallback(
@@ -538,7 +538,7 @@ export function useMaskHandles({
const deltaX = pos.x - drag.startCanvasX;
const deltaY = pos.y - drag.startCanvasY;
- const def = masksRegistry.get(selectedWithMask.mask.type);
+ const def = getMaskDefinition(selectedWithMask.mask.type);
const rawParams = def.computeParamUpdate({
handleId: drag.handleId,
@@ -600,7 +600,7 @@ export function useMaskHandles({
const handlePointerUp = useCallback(() => {
const pendingSegmentInsert = pendingSegmentInsertRef.current;
if (pendingSegmentInsert && !dragStateRef.current) {
- editor.timeline.insertCustomMaskPoint({
+ editor.timeline.insertFreeformPathMaskPoint({
trackId: pendingSegmentInsert.trackId,
elementId: pendingSegmentInsert.elementId,
maskId: pendingSegmentInsert.maskId,
@@ -627,7 +627,7 @@ export function useMaskHandles({
selectedWithMask,
handlePositions,
overlays,
- isCreatingCustomMask,
+ isCreatingFreeformPathMask,
handleCanvasPointerDown,
activeHandleId,
handlePointerDown,