236 lines
5.5 KiB
TypeScript
236 lines
5.5 KiB
TypeScript
import type {
|
|
AnimationBindingKind,
|
|
AnimationInterpolation,
|
|
AnimationPath,
|
|
AnimationValue,
|
|
NumericSpec,
|
|
} from "@/animation/types";
|
|
import {
|
|
parseEffectParamPath,
|
|
} from "@/animation/effect-param-channel";
|
|
import {
|
|
parseGraphicParamPath,
|
|
} from "@/animation/graphic-param-channel";
|
|
import { effectsRegistry, registerDefaultEffects } from "@/effects";
|
|
import { getGraphicDefinition } from "@/graphics";
|
|
import {
|
|
coerceParamValue,
|
|
getParamDefaultInterpolation,
|
|
getParamNumericRange,
|
|
getParamValueKind,
|
|
type ParamDefinition,
|
|
type ParamValues,
|
|
} from "@/params";
|
|
import {
|
|
getElementParam,
|
|
readElementParamValue,
|
|
writeElementParamValue,
|
|
type ElementParamDefinition,
|
|
} from "@/params/registry";
|
|
import type { TimelineElement } from "@/timeline";
|
|
import { isVisualElement } from "@/timeline/element-utils";
|
|
|
|
export interface AnimationPathDescriptor {
|
|
kind: AnimationBindingKind;
|
|
defaultInterpolation: AnimationInterpolation;
|
|
numericRanges?: Partial<Record<string, NumericSpec>>;
|
|
coerceValue: ({ value }: { value: AnimationValue }) => AnimationValue | null;
|
|
getBaseValue: () => AnimationValue | null;
|
|
setBaseValue: ({ value }: { value: AnimationValue }) => TimelineElement;
|
|
}
|
|
|
|
// Number/discrete bindings expose a single component named "value"
|
|
// (see binding-values.ts). Multi-component kinds (vector2, color) don't carry
|
|
// numeric ranges yet — revisit when one does.
|
|
function paramNumericRanges({
|
|
param,
|
|
}: {
|
|
param: ParamDefinition;
|
|
}): Partial<Record<string, NumericSpec>> | undefined {
|
|
const range = getParamNumericRange({ param });
|
|
return range ? { value: range } : undefined;
|
|
}
|
|
|
|
function buildParamDescriptor({
|
|
param,
|
|
baseParams,
|
|
setParams,
|
|
}: {
|
|
param: ParamDefinition;
|
|
baseParams: ParamValues;
|
|
setParams: (params: ParamValues) => TimelineElement;
|
|
}): AnimationPathDescriptor | null {
|
|
if (param.keyframable === false) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
kind: getParamValueKind({ param }),
|
|
defaultInterpolation: getParamDefaultInterpolation({ param }),
|
|
numericRanges: paramNumericRanges({ param }),
|
|
coerceValue: ({ value }) => coerceParamValue({ param, value }),
|
|
getBaseValue: () => baseParams[param.key] ?? param.default,
|
|
setBaseValue: ({ value }) => {
|
|
const coercedValue = coerceParamValue({ param, value });
|
|
if (coercedValue === null) {
|
|
return setParams(baseParams);
|
|
}
|
|
|
|
return setParams({
|
|
...baseParams,
|
|
[param.key]: coercedValue,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildElementParamDescriptor({
|
|
element,
|
|
paramKey,
|
|
}: {
|
|
element: TimelineElement;
|
|
paramKey: string;
|
|
}): AnimationPathDescriptor | null {
|
|
const param = getElementParam({ element, key: paramKey });
|
|
if (!param) {
|
|
return null;
|
|
}
|
|
|
|
return buildTimelineElementParamDescriptor({ element, param });
|
|
}
|
|
|
|
function buildTimelineElementParamDescriptor({
|
|
element,
|
|
param,
|
|
}: {
|
|
element: TimelineElement;
|
|
param: ElementParamDefinition;
|
|
}): AnimationPathDescriptor | null {
|
|
if (param.keyframable === false) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
kind: getParamValueKind({ param }),
|
|
defaultInterpolation: getParamDefaultInterpolation({ param }),
|
|
numericRanges: paramNumericRanges({ param }),
|
|
coerceValue: ({ value }) => coerceParamValue({ param, value }),
|
|
getBaseValue: () => readElementParamValue({ element, param }),
|
|
setBaseValue: ({ value }) => {
|
|
const coercedValue = coerceParamValue({ param, value });
|
|
if (coercedValue === null) {
|
|
return element;
|
|
}
|
|
|
|
return writeElementParamValue({ element, param, value: coercedValue });
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildGraphicParamDescriptor({
|
|
element,
|
|
paramKey,
|
|
}: {
|
|
element: TimelineElement;
|
|
paramKey: string;
|
|
}): AnimationPathDescriptor | null {
|
|
if (element.type !== "graphic") {
|
|
return null;
|
|
}
|
|
|
|
const definition = getGraphicDefinition({
|
|
definitionId: element.definitionId,
|
|
});
|
|
const param = definition.params.find((candidate) => candidate.key === paramKey);
|
|
if (!param) {
|
|
return null;
|
|
}
|
|
|
|
return buildParamDescriptor({
|
|
param,
|
|
baseParams: element.params,
|
|
setParams: (params) => ({
|
|
...element,
|
|
params,
|
|
}),
|
|
});
|
|
}
|
|
|
|
function buildEffectParamDescriptor({
|
|
element,
|
|
effectId,
|
|
paramKey,
|
|
}: {
|
|
element: TimelineElement;
|
|
effectId: string;
|
|
paramKey: string;
|
|
}): AnimationPathDescriptor | null {
|
|
if (!isVisualElement(element)) {
|
|
return null;
|
|
}
|
|
|
|
const effect = element.effects?.find((candidate) => candidate.id === effectId);
|
|
if (!effect) {
|
|
return null;
|
|
}
|
|
|
|
registerDefaultEffects();
|
|
const definition = effectsRegistry.get(effect.type);
|
|
const param = definition.params.find((candidate) => candidate.key === paramKey);
|
|
if (!param) {
|
|
return null;
|
|
}
|
|
|
|
return buildParamDescriptor({
|
|
param,
|
|
baseParams: effect.params,
|
|
setParams: (params) => ({
|
|
...element,
|
|
effects:
|
|
element.effects?.map((candidate) =>
|
|
candidate.id !== effectId
|
|
? candidate
|
|
: {
|
|
...candidate,
|
|
params,
|
|
},
|
|
) ?? element.effects,
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function resolveAnimationTarget({
|
|
element,
|
|
path,
|
|
}: {
|
|
element: TimelineElement;
|
|
path: AnimationPath;
|
|
}): AnimationPathDescriptor | null {
|
|
const elementParamTarget = buildElementParamDescriptor({
|
|
element,
|
|
paramKey: path,
|
|
});
|
|
if (elementParamTarget) {
|
|
return elementParamTarget;
|
|
}
|
|
|
|
const graphicParamTarget = parseGraphicParamPath({ propertyPath: path });
|
|
if (graphicParamTarget) {
|
|
return buildGraphicParamDescriptor({
|
|
element,
|
|
paramKey: graphicParamTarget.paramKey,
|
|
});
|
|
}
|
|
|
|
const effectParamTarget = parseEffectParamPath({ propertyPath: path });
|
|
if (effectParamTarget) {
|
|
return buildEffectParamDescriptor({
|
|
element,
|
|
effectId: effectParamTarget.effectId,
|
|
paramKey: effectParamTarget.paramKey,
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|