From 3e89d2998588f8e35c524e523ba2ee853f0f6475 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 20 Apr 2026 11:31:17 +0200 Subject: refactor: restructure files to their domains, new preview overlay system, and dep graph --- .../src/timeline/components/timeline-playhead.tsx | 117 +++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 apps/web/src/timeline/components/timeline-playhead.tsx (limited to 'apps/web/src/timeline/components/timeline-playhead.tsx') diff --git a/apps/web/src/timeline/components/timeline-playhead.tsx b/apps/web/src/timeline/components/timeline-playhead.tsx new file mode 100644 index 00000000..4dd3329f --- /dev/null +++ b/apps/web/src/timeline/components/timeline-playhead.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { useRef } from "react"; +import { + getCenteredLineLeft, + TIMELINE_INDICATOR_LINE_WIDTH_PX, + timelineTimeToSnappedPixels, +} from "@/timeline"; +import { useTimelinePlayhead } from "@/timeline/hooks/use-timeline-playhead"; +import { TICKS_PER_SECOND } from "@/wasm"; +import { useEditor } from "@/editor/use-editor"; +import { TIMELINE_SCROLLBAR_SIZE_PX } from "./layout"; +import { TIMELINE_LAYERS } from "./layers"; + +interface TimelinePlayheadProps { + zoomLevel: number; + hasHorizontalScrollbar: boolean; + rulerRef: React.RefObject; + rulerScrollRef: React.RefObject; + tracksScrollRef: React.RefObject; + timelineRef: React.RefObject; + playheadRef?: React.RefObject; + isSnappingToPlayhead?: boolean; +} + +export function TimelinePlayhead({ + zoomLevel, + hasHorizontalScrollbar, + rulerRef, + rulerScrollRef, + tracksScrollRef, + timelineRef, + playheadRef: externalPlayheadRef, + isSnappingToPlayhead = false, +}: TimelinePlayheadProps) { + const editor = useEditor(); + const duration = editor.timeline.getTotalDuration(); + const internalPlayheadRef = useRef(null); + const playheadRef = externalPlayheadRef || internalPlayheadRef; + + const { handlePlayheadMouseDown } = useTimelinePlayhead({ + zoomLevel, + rulerRef, + rulerScrollRef, + tracksScrollRef, + playheadRef, + }); + + const timelineContainerHeight = + timelineRef.current?.clientHeight ?? + tracksScrollRef.current?.clientHeight ?? + 400; + const totalHeight = Math.max( + 0, + timelineContainerHeight - + (hasHorizontalScrollbar ? TIMELINE_SCROLLBAR_SIZE_PX - 5 : 0), + ); + + const currentTime = editor.playback.getCurrentTime(); + const centerPosition = timelineTimeToSnappedPixels({ + time: currentTime, + zoomLevel, + }); + const scrollLeft = tracksScrollRef.current?.scrollLeft ?? 0; + const leftPosition = + getCenteredLineLeft({ centerPixel: centerPosition }) - scrollLeft; + + const handlePlayheadKeyDown = ( + event: React.KeyboardEvent, + ) => { + if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; + + event.preventDefault(); + const fps = editor.project.getActive().settings.fps; + const ticksPerFrame = Math.round( + (TICKS_PER_SECOND * fps.denominator) / fps.numerator, + ); + const direction = event.key === "ArrowRight" ? 1 : -1; + const now = editor.playback.getCurrentTime(); + const nextTime = Math.max( + 0, + Math.min(duration, now + direction * ticksPerFrame), + ); + + editor.playback.seek({ time: nextTime }); + }; + + return ( +
+
+ +
+ ); +} -- cgit v1.3.1