summaryrefslogtreecommitdiff
path: root/apps/web/src/preview
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-29 01:28:50 +0200
committerMaze Winther <mazewinther@gmail.com>2026-05-02 03:27:23 +0200
commit2ee0a44735d2b930348e9774909638b2c1909738 (patch)
treee73e60097338ed411739f3b04808dad86f7bb192 /apps/web/src/preview
parenta9b9cf6bf50f08723896659634f2e6afc17bcd42 (diff)
downloadopencut-2ee0a44735d2b930348e9774909638b2c1909738.tar.gz
refactor: unify element params
Diffstat (limited to 'apps/web/src/preview')
-rw-r--r--apps/web/src/preview/components/text-edit-overlay.tsx45
-rw-r--r--apps/web/src/preview/controllers/preview-interaction-controller.ts19
-rw-r--r--apps/web/src/preview/controllers/transform-handle-controller.ts87
-rw-r--r--apps/web/src/preview/element-bounds.ts9
4 files changed, 98 insertions, 62 deletions
diff --git a/apps/web/src/preview/components/text-edit-overlay.tsx b/apps/web/src/preview/components/text-edit-overlay.tsx
index dd6364d6..c06d50ed 100644
--- a/apps/web/src/preview/components/text-edit-overlay.tsx
+++ b/apps/web/src/preview/components/text-edit-overlay.tsx
@@ -9,7 +9,12 @@ import {
getElementLocalTime,
} from "@/animation";
import { resolveTransformAtTime } from "@/rendering/animation-values";
+import { buildTransformFromParams } from "@/rendering";
import { resolveTextLayout } from "@/text/primitives";
+import {
+ buildTextBackgroundFromElement,
+ buildTextLayoutParamsFromElement,
+} from "@/text/measure-element";
export function TextEditOverlay({
trackId,
@@ -42,7 +47,7 @@ export function TextEditOverlay({
if (!div) return;
const text = div.innerText;
editor.timeline.previewElements({
- updates: [{ trackId, elementId, updates: { content: text } }],
+ updates: [{ trackId, elementId, updates: { params: { content: text } } }],
});
}, [editor.timeline, trackId, elementId]);
@@ -69,7 +74,7 @@ export function TextEditOverlay({
elementDuration: element.duration,
});
const transform = resolveTransformAtTime({
- baseTransform: element.transform,
+ baseTransform: buildTransformFromParams({ params: element.params }),
animations: element.animations,
localTime,
});
@@ -80,26 +85,17 @@ export function TextEditOverlay({
});
const { x: displayScaleX } = viewport.getDisplayScale();
+ const textParams = buildTextLayoutParamsFromElement({ element });
const resolvedTextLayout = resolveTextLayout({
- text: {
- content: element.content,
- fontSize: element.fontSize,
- fontFamily: element.fontFamily,
- fontWeight: element.fontWeight,
- fontStyle: element.fontStyle,
- textAlign: element.textAlign,
- textDecoration: element.textDecoration,
- letterSpacing: element.letterSpacing,
- lineHeight: element.lineHeight,
- },
+ text: textParams,
canvasHeight: canvasSize.height,
});
- const lineHeight = element.lineHeight ?? DEFAULTS.text.lineHeight;
- const canvasLetterSpacing = element.letterSpacing ?? 0;
+ const lineHeight = textParams.lineHeight ?? DEFAULTS.text.lineHeight;
+ const canvasLetterSpacing = textParams.letterSpacing ?? 0;
const lineHeightPx = resolvedTextLayout.lineHeightPx;
- const bg = element.background;
+ const bg = buildTextBackgroundFromElement({ element });
const shouldShowBackground =
bg.enabled && bg.color && bg.color !== "transparent";
const fontSizeRatio = resolvedTextLayout.fontSizeRatio;
@@ -130,17 +126,20 @@ export function TextEditOverlay({
className="cursor-text select-text outline-none whitespace-pre"
style={{
fontSize: resolvedTextLayout.scaledFontSize,
- fontFamily: element.fontFamily,
- fontWeight: element.fontWeight === "bold" ? "bold" : "normal",
- fontStyle: element.fontStyle === "italic" ? "italic" : "normal",
- textAlign: element.textAlign,
+ fontFamily: textParams.fontFamily,
+ fontWeight: textParams.fontWeight === "bold" ? "bold" : "normal",
+ fontStyle: textParams.fontStyle === "italic" ? "italic" : "normal",
+ textAlign: textParams.textAlign,
letterSpacing: `${canvasLetterSpacing}px`,
lineHeight,
color: "transparent",
- caretColor: element.color,
+ caretColor:
+ typeof element.params.color === "string"
+ ? element.params.color
+ : "#ffffff",
backgroundColor: shouldShowBackground ? bg.color : "transparent",
minHeight: lineHeightPx,
- textDecoration: element.textDecoration ?? "none",
+ textDecoration: textParams.textDecoration ?? "none",
padding: shouldShowBackground
? `${canvasPaddingY}px ${canvasPaddingX}px`
: 0,
@@ -150,7 +149,7 @@ export function TextEditOverlay({
onBlur={onCommit}
onKeyDown={(event) => handleKeyDown({ event })}
>
- {element.content || ""}
+ {textParams.content}
</div>
</div>
);
diff --git a/apps/web/src/preview/controllers/preview-interaction-controller.ts b/apps/web/src/preview/controllers/preview-interaction-controller.ts
index 00573662..118b48ae 100644
--- a/apps/web/src/preview/controllers/preview-interaction-controller.ts
+++ b/apps/web/src/preview/controllers/preview-interaction-controller.ts
@@ -18,7 +18,8 @@ import {
type SnapLine,
} from "@/preview/preview-snap";
import type { TCanvasSize } from "@/project/types";
-import type { Transform } from "@/rendering";
+import type { ParamValues } from "@/params";
+import { buildTransformFromParams, type Transform } from "@/rendering";
import { isVisualElement } from "@/timeline/element-utils";
import type {
ElementRef,
@@ -51,6 +52,7 @@ interface DragElementSnapshot {
readonly trackId: string;
readonly elementId: string;
readonly initialTransform: Transform;
+ readonly initialParams: ParamValues;
}
interface DraggingGesture extends CapturedPointerState {
@@ -218,7 +220,8 @@ function toDragElementSnapshots({
.map(({ track, element }) => ({
trackId: track.id,
elementId: element.id,
- initialTransform: element.transform,
+ initialTransform: buildTransformFromParams({ params: element.params }),
+ initialParams: element.params,
}));
}
@@ -565,16 +568,14 @@ export class PreviewInteractionController {
snappedPosition.y - firstElement.initialTransform.position.y;
this.deps.timeline.previewElements(
- drag.elements.map(({ trackId, elementId, initialTransform }) => ({
+ drag.elements.map(({ trackId, elementId, initialTransform, initialParams }) => ({
trackId,
elementId,
updates: {
- transform: {
- ...initialTransform,
- position: {
- x: initialTransform.position.x + deltaSnappedX,
- y: initialTransform.position.y + deltaSnappedY,
- },
+ params: {
+ ...initialParams,
+ "transform.positionX": initialTransform.position.x + deltaSnappedX,
+ "transform.positionY": initialTransform.position.y + deltaSnappedY,
},
},
})),
diff --git a/apps/web/src/preview/controllers/transform-handle-controller.ts b/apps/web/src/preview/controllers/transform-handle-controller.ts
index 67f677f6..d9b5ef1f 100644
--- a/apps/web/src/preview/controllers/transform-handle-controller.ts
+++ b/apps/web/src/preview/controllers/transform-handle-controller.ts
@@ -23,7 +23,8 @@ import {
setChannel,
} from "@/animation";
import type { ElementAnimations } from "@/animation/types";
-import type { Transform } from "@/rendering";
+import type { ParamValues } from "@/params";
+import { buildTransformFromParams, type Transform } from "@/rendering";
import { resolveTransformAtTime } from "@/rendering/animation-values";
import type {
ElementRef,
@@ -47,6 +48,7 @@ interface CornerScaleSession extends CapturedPointerState {
readonly trackId: string;
readonly elementId: string;
readonly initialTransform: Transform;
+ readonly initialParams: ParamValues;
readonly initialDistance: number;
readonly initialBoundsCx: number;
readonly initialBoundsCy: number;
@@ -62,6 +64,7 @@ interface EdgeScaleSession extends CapturedPointerState {
readonly trackId: string;
readonly elementId: string;
readonly initialTransform: Transform;
+ readonly initialParams: ParamValues;
readonly initialBoundsCx: number;
readonly initialBoundsCy: number;
readonly baseWidth: number;
@@ -76,6 +79,7 @@ interface RotationSession extends CapturedPointerState {
readonly trackId: string;
readonly elementId: string;
readonly initialTransform: Transform;
+ readonly initialParams: ParamValues;
readonly initialAngle: number;
readonly initialBoundsCx: number;
readonly initialBoundsCy: number;
@@ -369,6 +373,7 @@ export class TransformHandleController {
trackId: context.trackId,
elementId: context.elementId,
initialTransform: context.resolvedTransform,
+ initialParams: context.element.params,
initialDistance: getCornerDistance({
bounds: context.bounds,
corner,
@@ -410,6 +415,7 @@ export class TransformHandleController {
trackId: context.trackId,
elementId: context.elementId,
initialTransform: context.resolvedTransform,
+ initialParams: context.element.params,
initialAngle,
initialBoundsCx: context.bounds.cx,
initialBoundsCy: context.bounds.cy,
@@ -447,6 +453,7 @@ export class TransformHandleController {
trackId: context.trackId,
elementId: context.elementId,
initialTransform: context.resolvedTransform,
+ initialParams: context.element.params,
initialBoundsCx: context.bounds.cx,
initialBoundsCy: context.bounds.cy,
baseWidth: context.bounds.width / context.resolvedTransform.scaleX,
@@ -561,7 +568,9 @@ export class TransformHandleController {
element: selectedWithBounds.element,
bounds: selectedWithBounds.bounds,
resolvedTransform: resolveTransformAtTime({
- baseTransform: selectedWithBounds.element.transform,
+ baseTransform: buildTransformFromParams({
+ params: selectedWithBounds.element.params,
+ }),
animations: selectedWithBounds.element.animations,
localTime,
}),
@@ -608,15 +617,18 @@ export class TransformHandleController {
trackId: session.trackId,
elementId: session.elementId,
updates: {
- transform: {
- ...session.initialTransform,
- scaleX: clampScaleNonZero(
- session.initialTransform.scaleX * snappedScale,
- ),
- scaleY: clampScaleNonZero(
- session.initialTransform.scaleY * snappedScale,
- ),
- },
+ params: buildParamsWithTransform({
+ params: session.initialParams,
+ transform: {
+ ...session.initialTransform,
+ scaleX: clampScaleNonZero(
+ session.initialTransform.scaleX * snappedScale,
+ ),
+ scaleY: clampScaleNonZero(
+ session.initialTransform.scaleY * snappedScale,
+ ),
+ },
+ }),
...(session.shouldClearScaleAnimation && {
animations: session.animationsWithoutScale,
}),
@@ -699,17 +711,20 @@ export class TransformHandleController {
trackId: session.trackId,
elementId: session.elementId,
updates: {
- transform: {
- ...session.initialTransform,
- scaleX:
- session.edge === "right" || session.edge === "left"
- ? xSnap.snappedScale
- : session.initialTransform.scaleX,
- scaleY:
- session.edge === "bottom"
- ? ySnap.snappedScale
- : session.initialTransform.scaleY,
- },
+ params: buildParamsWithTransform({
+ params: session.initialParams,
+ transform: {
+ ...session.initialTransform,
+ scaleX:
+ session.edge === "right" || session.edge === "left"
+ ? xSnap.snappedScale
+ : session.initialTransform.scaleX,
+ scaleY:
+ session.edge === "bottom"
+ ? ySnap.snappedScale
+ : session.initialTransform.scaleY,
+ },
+ }),
...(session.shouldClearScaleAnimation && {
animations: session.animationsWithoutScale,
}),
@@ -742,12 +757,32 @@ export class TransformHandleController {
trackId: session.trackId,
elementId: session.elementId,
updates: {
- transform: {
- ...session.initialTransform,
- rotate: snappedRotation,
- },
+ params: buildParamsWithTransform({
+ params: session.initialParams,
+ transform: {
+ ...session.initialTransform,
+ rotate: snappedRotation,
+ },
+ }),
},
},
]);
}
}
+
+function buildParamsWithTransform({
+ params,
+ transform,
+}: {
+ params: ParamValues;
+ transform: Transform;
+}): ParamValues {
+ return {
+ ...params,
+ "transform.positionX": transform.position.x,
+ "transform.positionY": transform.position.y,
+ "transform.scaleX": transform.scaleX,
+ "transform.scaleY": transform.scaleY,
+ "transform.rotate": transform.rotate,
+ };
+}
diff --git a/apps/web/src/preview/element-bounds.ts b/apps/web/src/preview/element-bounds.ts
index 0afc6e4e..181fdf1f 100644
--- a/apps/web/src/preview/element-bounds.ts
+++ b/apps/web/src/preview/element-bounds.ts
@@ -7,6 +7,7 @@ import {
getElementLocalTime,
} from "@/animation";
import { resolveTransformAtTime } from "@/rendering/animation-values";
+import { buildTransformFromParams } from "@/rendering";
export interface ElementBounds {
cx: number;
@@ -123,7 +124,7 @@ function getElementBounds({
if (element.type === "video" || element.type === "image") {
const transform = resolveTransformAtTime({
- baseTransform: element.transform,
+ baseTransform: buildTransformFromParams({ params: element.params }),
animations: element.animations,
localTime,
});
@@ -140,7 +141,7 @@ function getElementBounds({
if (element.type === "sticker") {
const transform = resolveTransformAtTime({
- baseTransform: element.transform,
+ baseTransform: buildTransformFromParams({ params: element.params }),
animations: element.animations,
localTime,
});
@@ -155,7 +156,7 @@ function getElementBounds({
if (element.type === "graphic") {
const transform = resolveTransformAtTime({
- baseTransform: element.transform,
+ baseTransform: buildTransformFromParams({ params: element.params }),
animations: element.animations,
localTime,
});
@@ -170,7 +171,7 @@ function getElementBounds({
if (element.type === "text") {
const transform = resolveTransformAtTime({
- baseTransform: element.transform,
+ baseTransform: buildTransformFromParams({ params: element.params }),
animations: element.animations,
localTime,
});