mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: implement keyframe animation system
Add keyframe support for transform, opacity, and volume properties. Includes animation engine (interpolation, mutations, resolvers), timeline markers with selection/snapping, properties panel toggles, keyframe-aware renderer, and full undo/redo command support. Also refactor element command constructors to object params, extract timeline pixel math to pixel-utils.ts, and update cursor rules.
This commit is contained in:
@@ -2,8 +2,13 @@ import type { CanvasRenderer } from "../canvas-renderer";
|
||||
import { BaseNode } from "./base-node";
|
||||
import type { BlendMode } from "@/types/rendering";
|
||||
import type { Transform } from "@/types/timeline";
|
||||
|
||||
const VISUAL_EPSILON = 1 / 1000;
|
||||
import type { ElementAnimations } from "@/types/animation";
|
||||
import {
|
||||
getElementLocalTime,
|
||||
resolveOpacityAtTime,
|
||||
resolveTransformAtTime,
|
||||
} from "@/lib/animation";
|
||||
import { TIME_EPSILON_SECONDS } from "@/constants/animation-constants";
|
||||
|
||||
export interface VisualNodeParams {
|
||||
duration: number;
|
||||
@@ -11,6 +16,7 @@ export interface VisualNodeParams {
|
||||
trimStart: number;
|
||||
trimEnd: number;
|
||||
transform: Transform;
|
||||
animations?: ElementAnimations;
|
||||
opacity: number;
|
||||
blendMode?: BlendMode;
|
||||
}
|
||||
@@ -18,14 +24,22 @@ export interface VisualNodeParams {
|
||||
export abstract class VisualNode<
|
||||
Params extends VisualNodeParams = VisualNodeParams,
|
||||
> extends BaseNode<Params> {
|
||||
protected getLocalTime(time: number): number {
|
||||
protected getSourceLocalTime(time: number): number {
|
||||
return time - this.params.timeOffset + this.params.trimStart;
|
||||
}
|
||||
|
||||
protected getAnimationLocalTime(time: number): number {
|
||||
return getElementLocalTime({
|
||||
timelineTime: time,
|
||||
elementStartTime: this.params.timeOffset,
|
||||
elementDuration: this.params.duration,
|
||||
});
|
||||
}
|
||||
|
||||
protected isInRange(time: number): boolean {
|
||||
const localTime = this.getLocalTime(time);
|
||||
const localTime = this.getSourceLocalTime(time);
|
||||
return (
|
||||
localTime >= this.params.trimStart - VISUAL_EPSILON &&
|
||||
localTime >= this.params.trimStart - TIME_EPSILON_SECONDS &&
|
||||
localTime < this.params.trimStart + this.params.duration
|
||||
);
|
||||
}
|
||||
@@ -35,15 +49,27 @@ export abstract class VisualNode<
|
||||
source,
|
||||
sourceWidth,
|
||||
sourceHeight,
|
||||
timelineTime,
|
||||
}: {
|
||||
renderer: CanvasRenderer;
|
||||
source: CanvasImageSource;
|
||||
sourceWidth: number;
|
||||
sourceHeight: number;
|
||||
timelineTime: number;
|
||||
}): void {
|
||||
renderer.context.save();
|
||||
|
||||
const { transform, opacity } = this.params;
|
||||
const animationLocalTime = this.getAnimationLocalTime(timelineTime);
|
||||
const transform = resolveTransformAtTime({
|
||||
baseTransform: this.params.transform,
|
||||
animations: this.params.animations,
|
||||
localTime: animationLocalTime,
|
||||
});
|
||||
const opacity = resolveOpacityAtTime({
|
||||
baseOpacity: this.params.opacity,
|
||||
animations: this.params.animations,
|
||||
localTime: animationLocalTime,
|
||||
});
|
||||
const containScale = Math.min(
|
||||
renderer.width / sourceWidth,
|
||||
renderer.height / sourceHeight,
|
||||
|
||||
Reference in New Issue
Block a user