mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: implement keyframe support for more properties at the core (no ui yet)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import type {
|
||||
AnimationPropertyPath,
|
||||
ColorAnimationChannel,
|
||||
ElementAnimations,
|
||||
} from "@/types/animation";
|
||||
|
||||
export function getColorChannelForPath({
|
||||
animations,
|
||||
propertyPath,
|
||||
}: {
|
||||
animations: ElementAnimations | undefined;
|
||||
propertyPath: AnimationPropertyPath;
|
||||
}): ColorAnimationChannel | undefined {
|
||||
const channel = animations?.channels[propertyPath];
|
||||
if (!channel || channel.valueKind !== "color") {
|
||||
return undefined;
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export {
|
||||
|
||||
export {
|
||||
getElementLocalTime,
|
||||
resolveColorAtTime,
|
||||
resolveOpacityAtTime,
|
||||
resolveTransformAtTime,
|
||||
resolveVolumeAtTime,
|
||||
|
||||
@@ -277,7 +277,7 @@ export function getNumberChannelValueAtTime({
|
||||
});
|
||||
}
|
||||
|
||||
function getColorValueAtTime({
|
||||
export function getColorValueAtTime({
|
||||
channel,
|
||||
time,
|
||||
fallbackValue,
|
||||
|
||||
@@ -19,13 +19,13 @@ interface AnimationPropertyDefinition {
|
||||
defaultInterpolation: AnimationInterpolation;
|
||||
numericRange?: NumericRange;
|
||||
supportsElement: ({ element }: { element: TimelineElement }) => boolean;
|
||||
getValue: ({ element }: { element: TimelineElement }) => number | null;
|
||||
getValue: ({ element }: { element: TimelineElement }) => AnimationValue | null;
|
||||
setValue: ({
|
||||
element,
|
||||
value,
|
||||
}: {
|
||||
element: TimelineElement;
|
||||
value: number;
|
||||
value: AnimationValue;
|
||||
}) => TimelineElement;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
...element,
|
||||
transform: {
|
||||
...element.transform,
|
||||
position: { ...element.transform.position, x: value },
|
||||
position: { ...element.transform.position, x: value as number },
|
||||
},
|
||||
}
|
||||
: element,
|
||||
@@ -62,7 +62,7 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
...element,
|
||||
transform: {
|
||||
...element.transform,
|
||||
position: { ...element.transform.position, y: value },
|
||||
position: { ...element.transform.position, y: value as number },
|
||||
},
|
||||
}
|
||||
: element,
|
||||
@@ -76,7 +76,10 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
isVisualElement(element) ? element.transform.scale : null,
|
||||
setValue: ({ element, value }) =>
|
||||
isVisualElement(element)
|
||||
? { ...element, transform: { ...element.transform, scale: value } }
|
||||
? {
|
||||
...element,
|
||||
transform: { ...element.transform, scale: value as number },
|
||||
}
|
||||
: element,
|
||||
},
|
||||
"transform.rotate": {
|
||||
@@ -87,7 +90,10 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
isVisualElement(element) ? element.transform.rotate : null,
|
||||
setValue: ({ element, value }) =>
|
||||
isVisualElement(element)
|
||||
? { ...element, transform: { ...element.transform, rotate: value } }
|
||||
? {
|
||||
...element,
|
||||
transform: { ...element.transform, rotate: value as number },
|
||||
}
|
||||
: element,
|
||||
},
|
||||
opacity: {
|
||||
@@ -98,7 +104,9 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
getValue: ({ element }) =>
|
||||
isVisualElement(element) ? element.opacity : null,
|
||||
setValue: ({ element, value }) =>
|
||||
isVisualElement(element) ? { ...element, opacity: value } : element,
|
||||
isVisualElement(element)
|
||||
? { ...element, opacity: value as number }
|
||||
: element,
|
||||
},
|
||||
volume: {
|
||||
valueKind: "number",
|
||||
@@ -108,7 +116,33 @@ const ANIMATION_PROPERTY_REGISTRY: Record<
|
||||
getValue: ({ element }) =>
|
||||
element.type === "audio" ? element.volume : null,
|
||||
setValue: ({ element, value }) =>
|
||||
element.type === "audio" ? { ...element, volume: value } : element,
|
||||
element.type === "audio"
|
||||
? { ...element, volume: value as number }
|
||||
: element,
|
||||
},
|
||||
color: {
|
||||
valueKind: "color",
|
||||
defaultInterpolation: "linear",
|
||||
supportsElement: ({ element }) => element.type === "text",
|
||||
getValue: ({ element }) => (element.type === "text" ? element.color : null),
|
||||
setValue: ({ element, value }) =>
|
||||
element.type === "text"
|
||||
? { ...element, color: value as string }
|
||||
: element,
|
||||
},
|
||||
"background.color": {
|
||||
valueKind: "color",
|
||||
defaultInterpolation: "linear",
|
||||
supportsElement: ({ element }) => element.type === "text",
|
||||
getValue: ({ element }) =>
|
||||
element.type === "text" ? element.background.color : null,
|
||||
setValue: ({ element, value }) =>
|
||||
element.type === "text"
|
||||
? {
|
||||
...element,
|
||||
background: { ...element.background, color: value as string },
|
||||
}
|
||||
: element,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -163,7 +197,7 @@ export function withElementBaseValueForProperty({
|
||||
value: AnimationValue;
|
||||
}): TimelineElement {
|
||||
const coercedValue = coerceAnimationValueForProperty({ propertyPath, value });
|
||||
if (coercedValue === null || typeof coercedValue !== "number") {
|
||||
if (coercedValue === null) {
|
||||
return element;
|
||||
}
|
||||
const definition = getAnimationPropertyDefinition({ propertyPath });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ElementAnimations } from "@/types/animation";
|
||||
import type { AnimationPropertyPath, ElementAnimations } from "@/types/animation";
|
||||
import type { Transform } from "@/types/timeline";
|
||||
import { getNumberChannelValueAtTime } from "./interpolation";
|
||||
import { getColorValueAtTime, getNumberChannelValueAtTime } from "./interpolation";
|
||||
import { getColorChannelForPath } from "./color-channel";
|
||||
import { getNumberChannelForPath } from "./number-channel";
|
||||
|
||||
export function getElementLocalTime({
|
||||
@@ -91,6 +92,24 @@ export function resolveOpacityAtTime({
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveColorAtTime({
|
||||
baseColor,
|
||||
animations,
|
||||
propertyPath,
|
||||
localTime,
|
||||
}: {
|
||||
baseColor: string;
|
||||
animations: ElementAnimations | undefined;
|
||||
propertyPath: AnimationPropertyPath;
|
||||
localTime: number;
|
||||
}): string {
|
||||
return getColorValueAtTime({
|
||||
channel: getColorChannelForPath({ animations, propertyPath }),
|
||||
time: Math.max(0, localTime),
|
||||
fallbackValue: baseColor,
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveVolumeAtTime({
|
||||
baseVolume,
|
||||
animations,
|
||||
|
||||
Reference in New Issue
Block a user