summaryrefslogtreecommitdiff
path: root/apps/web/src/lib
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-02 16:36:10 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-02 16:36:10 +0100
commita526066b61f18b082adf6faeaf3e19f3d13cd77d (patch)
treed4a67b7f6177080eae32787967d7a3c166c703b9 /apps/web/src/lib
parentb0165da764971b8e7f37c9657d0aa81f9bc2dcc9 (diff)
downloadopencut-a526066b61f18b082adf6faeaf3e19f3d13cd77d.tar.gz
feat: wire ui to have keyframes for: opacity, text color, and all background properties
Diffstat (limited to 'apps/web/src/lib')
-rw-r--r--apps/web/src/lib/animation/index.ts1
-rw-r--r--apps/web/src/lib/animation/property-registry.ts88
-rw-r--r--apps/web/src/lib/animation/resolve.ts18
-rw-r--r--apps/web/src/lib/preview/element-bounds.ts40
4 files changed, 145 insertions, 2 deletions
diff --git a/apps/web/src/lib/animation/index.ts b/apps/web/src/lib/animation/index.ts
index f6a0b974..c4396bb7 100644
--- a/apps/web/src/lib/animation/index.ts
+++ b/apps/web/src/lib/animation/index.ts
@@ -18,6 +18,7 @@ export {
export {
getElementLocalTime,
resolveColorAtTime,
+ resolveNumberAtTime,
resolveOpacityAtTime,
resolveTransformAtTime,
resolveVolumeAtTime,
diff --git a/apps/web/src/lib/animation/property-registry.ts b/apps/web/src/lib/animation/property-registry.ts
index 69757d49..361ae3ba 100644
--- a/apps/web/src/lib/animation/property-registry.ts
+++ b/apps/web/src/lib/animation/property-registry.ts
@@ -7,6 +7,11 @@ import type {
} from "@/types/animation";
import type { TimelineElement } from "@/types/timeline";
import { MIN_TRANSFORM_SCALE } from "@/constants/animation-constants";
+import {
+ CORNER_RADIUS_MAX,
+ CORNER_RADIUS_MIN,
+ DEFAULT_TEXT_BACKGROUND,
+} from "@/constants/text-constants";
import { isVisualElement } from "@/lib/timeline/element-utils";
interface NumericRange {
@@ -144,6 +149,89 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
}
: element,
},
+ "background.paddingX": {
+ valueKind: "number",
+ defaultInterpolation: "linear",
+ numericRange: { min: 0 },
+ supportsElement: ({ element }) => element.type === "text",
+ getValue: ({ element }) =>
+ element.type === "text"
+ ? (element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX)
+ : null,
+ setValue: ({ element, value }) =>
+ element.type === "text"
+ ? {
+ ...element,
+ background: { ...element.background, paddingX: value as number },
+ }
+ : element,
+ },
+ "background.paddingY": {
+ valueKind: "number",
+ defaultInterpolation: "linear",
+ numericRange: { min: 0 },
+ supportsElement: ({ element }) => element.type === "text",
+ getValue: ({ element }) =>
+ element.type === "text"
+ ? (element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY)
+ : null,
+ setValue: ({ element, value }) =>
+ element.type === "text"
+ ? {
+ ...element,
+ background: { ...element.background, paddingY: value as number },
+ }
+ : element,
+ },
+ "background.offsetX": {
+ valueKind: "number",
+ defaultInterpolation: "linear",
+ supportsElement: ({ element }) => element.type === "text",
+ getValue: ({ element }) =>
+ element.type === "text"
+ ? (element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX)
+ : null,
+ setValue: ({ element, value }) =>
+ element.type === "text"
+ ? {
+ ...element,
+ background: { ...element.background, offsetX: value as number },
+ }
+ : element,
+ },
+ "background.offsetY": {
+ valueKind: "number",
+ defaultInterpolation: "linear",
+ supportsElement: ({ element }) => element.type === "text",
+ getValue: ({ element }) =>
+ element.type === "text"
+ ? (element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY)
+ : null,
+ setValue: ({ element, value }) =>
+ element.type === "text"
+ ? {
+ ...element,
+ background: { ...element.background, offsetY: value as number },
+ }
+ : element,
+ },
+ "background.cornerRadius": {
+ valueKind: "number",
+ defaultInterpolation: "linear",
+ numericRange: { min: CORNER_RADIUS_MIN, max: CORNER_RADIUS_MAX },
+ supportsElement: ({ element }) => element.type === "text",
+ getValue: ({ element }) =>
+ element.type === "text"
+ ? (element.background.cornerRadius ?? CORNER_RADIUS_MIN)
+ : null,
+ setValue: ({ element, value }) =>
+ element.type === "text"
+ ? {
+ ...element,
+ background: { ...element.background, cornerRadius: value as number },
+ }
+ : element,
+ },
};
export function isAnimationPropertyPath({
diff --git a/apps/web/src/lib/animation/resolve.ts b/apps/web/src/lib/animation/resolve.ts
index 450ae983..147be1b3 100644
--- a/apps/web/src/lib/animation/resolve.ts
+++ b/apps/web/src/lib/animation/resolve.ts
@@ -92,6 +92,24 @@ export function resolveOpacityAtTime({
});
}
+export function resolveNumberAtTime({
+ baseValue,
+ animations,
+ propertyPath,
+ localTime,
+}: {
+ baseValue: number;
+ animations: ElementAnimations | undefined;
+ propertyPath: AnimationPropertyPath;
+ localTime: number;
+}): number {
+ return getNumberChannelValueAtTime({
+ channel: getNumberChannelForPath({ animations, propertyPath }),
+ time: Math.max(0, localTime),
+ fallbackValue: baseValue,
+ });
+}
+
export function resolveColorAtTime({
baseColor,
animations,
diff --git a/apps/web/src/lib/preview/element-bounds.ts b/apps/web/src/lib/preview/element-bounds.ts
index 5b1f301d..08ffbfa8 100644
--- a/apps/web/src/lib/preview/element-bounds.ts
+++ b/apps/web/src/lib/preview/element-bounds.ts
@@ -4,10 +4,15 @@ import { isMainTrack } from "@/lib/timeline";
import {
DEFAULT_TEXT_ELEMENT,
DEFAULT_LINE_HEIGHT,
+ DEFAULT_TEXT_BACKGROUND,
FONT_SIZE_SCALE_REFERENCE,
} from "@/constants/text-constants";
import { getTextVisualRect, measureTextBlock } from "@/lib/text/layout";
-import { getElementLocalTime, resolveTransformAtTime } from "@/lib/animation";
+import {
+ getElementLocalTime,
+ resolveTransformAtTime,
+ resolveNumberAtTime,
+} from "@/lib/animation";
export interface ElementBounds {
cx: number;
@@ -147,10 +152,41 @@ export function getElementBounds({
fallbackFontSize: scaledFontSize,
});
const fontSizeRatio = element.fontSize / DEFAULT_TEXT_ELEMENT.fontSize;
+ const resolvedBackground = {
+ ...element.background,
+ paddingX: resolveNumberAtTime({
+ baseValue:
+ element.background.paddingX ?? DEFAULT_TEXT_BACKGROUND.paddingX,
+ animations: element.animations,
+ propertyPath: "background.paddingX",
+ localTime,
+ }),
+ paddingY: resolveNumberAtTime({
+ baseValue:
+ element.background.paddingY ?? DEFAULT_TEXT_BACKGROUND.paddingY,
+ animations: element.animations,
+ propertyPath: "background.paddingY",
+ localTime,
+ }),
+ offsetX: resolveNumberAtTime({
+ baseValue:
+ element.background.offsetX ?? DEFAULT_TEXT_BACKGROUND.offsetX,
+ animations: element.animations,
+ propertyPath: "background.offsetX",
+ localTime,
+ }),
+ offsetY: resolveNumberAtTime({
+ baseValue:
+ element.background.offsetY ?? DEFAULT_TEXT_BACKGROUND.offsetY,
+ animations: element.animations,
+ propertyPath: "background.offsetY",
+ localTime,
+ }),
+ };
const visualRect = getTextVisualRect({
textAlign: element.textAlign,
block,
- background: element.background,
+ background: resolvedBackground,
fontSizeRatio,
});
measuredWidth = visualRect.width;