feat: add canvas-based timeline with playhead, scrubbing, and zoom
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
48
src/lib/timeline/interactions.ts
Normal file
48
src/lib/timeline/interactions.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { type TimelineState, xToTime } from './renderer';
|
||||
|
||||
export type { TimelineState };
|
||||
|
||||
const MIN_VISIBLE_RANGE = 0.1;
|
||||
const MIN_ZOOM = 1;
|
||||
|
||||
export function clampZoom(zoom: number, duration: number): number {
|
||||
const maxZoom = duration / MIN_VISIBLE_RANGE;
|
||||
return Math.max(MIN_ZOOM, Math.min(zoom, maxZoom));
|
||||
}
|
||||
|
||||
export function computeZoom(
|
||||
state: TimelineState,
|
||||
anchorTime: number,
|
||||
factor: number,
|
||||
duration: number
|
||||
): { visibleStart: number; visibleEnd: number; zoom: number } {
|
||||
const currentRange = state.visibleEnd - state.visibleStart;
|
||||
const newRange = currentRange / factor;
|
||||
const clampedRange = Math.max(MIN_VISIBLE_RANGE, Math.min(newRange, duration));
|
||||
|
||||
const anchorRatio = (anchorTime - state.visibleStart) / currentRange;
|
||||
let newStart = anchorTime - anchorRatio * clampedRange;
|
||||
let newEnd = newStart + clampedRange;
|
||||
|
||||
if (newStart < 0) {
|
||||
newStart = 0;
|
||||
newEnd = clampedRange;
|
||||
}
|
||||
if (newEnd > duration) {
|
||||
newEnd = duration;
|
||||
newStart = Math.max(0, newEnd - clampedRange);
|
||||
}
|
||||
|
||||
const newZoom = clampZoom(duration / clampedRange, duration);
|
||||
|
||||
return { visibleStart: newStart, visibleEnd: newEnd, zoom: newZoom };
|
||||
}
|
||||
|
||||
export function handleClick(
|
||||
x: number,
|
||||
state: TimelineState,
|
||||
duration: number
|
||||
): number {
|
||||
const time = xToTime(x, state);
|
||||
return Math.max(0, Math.min(time, duration));
|
||||
}
|
||||
Reference in New Issue
Block a user