From c152e898a7820ddcb9cb19f1eff3a59af91af662 Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Tue, 18 Aug 2026 23:14:39 -0400 Subject: [PATCH] feat(studio): show both sides of the cut while trimming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The trim tools move an edit point, but the preview only ever shows one frame, so the frame you are cutting away and the frame you are cutting to were never on screen together. A two-up panel above the timeline now holds both, live, while the gesture runs. Each pane is its own preview iframe of the current composition seeked to its own time — the pattern the composition cards already use — so the frames track the drag with no server round-trip. The gesture publishes the pair of times it is editing; the panes read them. A finished trim leaves its cut on screen instead of snapping back, since that is the frame you just made. Slip has no edit point, so it shows the clip's own first and last frame, which is what its drag actually changes. Two extra composition renders is a real cost, so the panel only exists while a trim tool is active and a toolbar toggle (persisted) turns it off — both panes and their iframes are torn down when it does. --- .../studio/src/components/TimelineToolbar.tsx | 25 ++- .../src/components/nle/PrecisionTrimView.tsx | 174 ++++++++++++++++++ .../src/components/nle/TimelinePane.tsx | 2 + .../timelineClipDragGestureLifecycle.ts | 3 +- .../player/components/timelineTrimOps.test.ts | 41 +++++ .../src/player/components/timelineTrimOps.ts | 32 ++++ .../player/components/timelineTrimSession.ts | 9 + .../player/components/useTimelineClipDrag.ts | 7 +- .../studio/src/player/store/playerStore.ts | 5 +- .../src/player/store/trimPreviewSlice.ts | 48 +++++ .../studio/src/utils/studioUiPreferences.ts | 2 + 11 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 packages/studio/src/components/nle/PrecisionTrimView.tsx create mode 100644 packages/studio/src/player/store/trimPreviewSlice.ts diff --git a/packages/studio/src/components/TimelineToolbar.tsx b/packages/studio/src/components/TimelineToolbar.tsx index 4793cea64c..fe4370b292 100644 --- a/packages/studio/src/components/TimelineToolbar.tsx +++ b/packages/studio/src/components/TimelineToolbar.tsx @@ -16,9 +16,9 @@ import { import { useTimelineZoom } from "../player/components/useTimelineZoom"; import { usePlayerStore, type TimelineElement } from "../player"; import { Tooltip } from "./ui"; -import { Scissors } from "../icons/SystemIcons"; +import { Compare, Scissors } from "../icons/SystemIcons"; import { TRIM_TOOL_ICONS } from "../icons/TrimToolIcons"; -import { TIMELINE_TRIM_TOOLS } from "../player/components/timelineTrimTools"; +import { activeTrimMode, TIMELINE_TRIM_TOOLS } from "../player/components/timelineTrimTools"; import type { GsapAnimation } from "@hyperframes/core/gsap-parser"; import type { DomEditSelection } from "./editor/domEditingTypes"; import { canSplitElement } from "../utils/timelineElementSplit"; @@ -127,6 +127,8 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool const activeTool = usePlayerStore((s) => s.activeTool); const setActiveTool = usePlayerStore((s) => s.setActiveTool); const timelineSnapEnabled = usePlayerStore((s) => s.timelineSnapEnabled); + const precisionTrimViewEnabled = usePlayerStore((s) => s.precisionTrimViewEnabled); + const setPrecisionTrimViewEnabled = usePlayerStore((s) => s.setPrecisionTrimViewEnabled); const setTimelineSnapEnabled = usePlayerStore((s) => s.setTimelineSnapEnabled); const autoKeyframeEnabled = usePlayerStore((s) => s.autoKeyframeEnabled); const setAutoKeyframeEnabled = usePlayerStore((s) => s.setAutoKeyframeEnabled); @@ -236,6 +238,25 @@ export function TimelineToolbar({ domEditSession, onSplitElement }: TimelineTool ); })} + {activeTrimMode(activeTool) && ( + + + + )} {/* Divider: tool-mode | editing-actions */}