summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/commands/timeline/element/retime
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-25 16:47:22 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-29 15:48:22 +0200
commit8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch)
treee9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/lib/commands/timeline/element/retime
parent39ea298a9ccba3104c549c0ca079c231981811f5 (diff)
downloadopencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/lib/commands/timeline/element/retime')
-rw-r--r--apps/web/src/lib/commands/timeline/element/retime/index.ts1
-rw-r--r--apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts114
2 files changed, 115 insertions, 0 deletions
diff --git a/apps/web/src/lib/commands/timeline/element/retime/index.ts b/apps/web/src/lib/commands/timeline/element/retime/index.ts
new file mode 100644
index 00000000..5daa59bf
--- /dev/null
+++ b/apps/web/src/lib/commands/timeline/element/retime/index.ts
@@ -0,0 +1 @@
+export { UpdateElementRetimeCommand } from "./update-element-retime";
diff --git a/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts b/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts
new file mode 100644
index 00000000..d1f859e6
--- /dev/null
+++ b/apps/web/src/lib/commands/timeline/element/retime/update-element-retime.ts
@@ -0,0 +1,114 @@
+import { EditorCore } from "@/core";
+import { clampRetimeRate } from "@/constants/retime-constants";
+import { clampAnimationsToDuration } from "@/lib/animation";
+import { Command } from "@/lib/commands/base-command";
+import { getTimelineDurationForSourceSpan, getSourceSpanAtClipTime } from "@/lib/retime";
+import { isRetimableElement, updateElementInTracks } from "@/lib/timeline";
+import type { RetimeConfig, TimelineTrack } from "@/lib/timeline";
+
+function getSourceDuration({
+ trimStart,
+ trimEnd,
+ duration,
+ sourceDuration,
+ retime,
+}: {
+ trimStart: number;
+ trimEnd: number;
+ duration: number;
+ sourceDuration?: number;
+ retime?: RetimeConfig;
+}): number {
+ if (typeof sourceDuration === "number") {
+ return sourceDuration;
+ }
+
+ return (
+ trimStart +
+ getSourceSpanAtClipTime({
+ clipTime: duration,
+ retime,
+ }) +
+ trimEnd
+ );
+}
+
+export class UpdateElementRetimeCommand extends Command {
+ private savedState: TimelineTrack[] | null = null;
+ private readonly trackId: string;
+ private readonly elementId: string;
+ private readonly retime: RetimeConfig | undefined;
+
+ constructor({
+ trackId,
+ elementId,
+ retime,
+ }: {
+ trackId: string;
+ elementId: string;
+ retime?: RetimeConfig;
+ }) {
+ super();
+ this.trackId = trackId;
+ this.elementId = elementId;
+ this.retime = retime;
+ }
+
+ execute(): void {
+ const editor = EditorCore.getInstance();
+ this.savedState = editor.timeline.getTracks();
+
+ const updatedTracks = updateElementInTracks({
+ tracks: this.savedState,
+ trackId: this.trackId,
+ elementId: this.elementId,
+ elementPredicate: isRetimableElement,
+ update: (element) => {
+ if (!isRetimableElement(element)) {
+ return element;
+ }
+
+ const nextRetime = this.retime
+ ? {
+ ...this.retime,
+ rate: clampRetimeRate({ rate: this.retime.rate }),
+ }
+ : undefined;
+ const sourceDuration = getSourceDuration({
+ trimStart: element.trimStart,
+ trimEnd: element.trimEnd,
+ duration: element.duration,
+ sourceDuration: element.sourceDuration,
+ retime: element.retime,
+ });
+ const visibleSourceSpan = Math.max(
+ 0,
+ sourceDuration - element.trimStart - element.trimEnd,
+ );
+ const nextDuration = getTimelineDurationForSourceSpan({
+ sourceSpan: visibleSourceSpan,
+ retime: nextRetime,
+ });
+
+ return {
+ ...element,
+ retime: nextRetime,
+ duration: nextDuration,
+ animations: clampAnimationsToDuration({
+ animations: element.animations,
+ duration: nextDuration,
+ }),
+ };
+ },
+ });
+
+ editor.timeline.updateTracks(updatedTracks);
+ }
+
+ undo(): void {
+ if (this.savedState) {
+ const editor = EditorCore.getInstance();
+ editor.timeline.updateTracks(this.savedState);
+ }
+ }
+}