summaryrefslogtreecommitdiff
path: root/apps/web/src/timeline/snapping
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/timeline/snapping')
-rw-r--r--apps/web/src/timeline/snapping/build.ts17
-rw-r--r--apps/web/src/timeline/snapping/index.ts9
-rw-r--r--apps/web/src/timeline/snapping/resolve.ts28
-rw-r--r--apps/web/src/timeline/snapping/threshold.ts15
-rw-r--r--apps/web/src/timeline/snapping/types.ts21
5 files changed, 90 insertions, 0 deletions
diff --git a/apps/web/src/timeline/snapping/build.ts b/apps/web/src/timeline/snapping/build.ts
new file mode 100644
index 00000000..87d3db2e
--- /dev/null
+++ b/apps/web/src/timeline/snapping/build.ts
@@ -0,0 +1,17 @@
+import type { SnapPoint, TimelineSnapPointSource } from "./types";
+
+export function buildTimelineSnapPoints({
+ sources,
+}: {
+ sources: TimelineSnapPointSource[];
+}): SnapPoint[] {
+ const snapPoints: SnapPoint[] = [];
+
+ for (const source of sources) {
+ for (const snapPoint of source()) {
+ snapPoints.push(snapPoint);
+ }
+ }
+
+ return snapPoints;
+}
diff --git a/apps/web/src/timeline/snapping/index.ts b/apps/web/src/timeline/snapping/index.ts
new file mode 100644
index 00000000..73f49272
--- /dev/null
+++ b/apps/web/src/timeline/snapping/index.ts
@@ -0,0 +1,9 @@
+export { buildTimelineSnapPoints } from "./build";
+export { resolveTimelineSnap } from "./resolve";
+export { getTimelineSnapThresholdInTicks } from "./threshold";
+export type {
+ SnapPoint,
+ SnapPointType,
+ SnapResult,
+ TimelineSnapPointSource,
+} from "./types";
diff --git a/apps/web/src/timeline/snapping/resolve.ts b/apps/web/src/timeline/snapping/resolve.ts
new file mode 100644
index 00000000..2f00d081
--- /dev/null
+++ b/apps/web/src/timeline/snapping/resolve.ts
@@ -0,0 +1,28 @@
+import type { SnapPoint, SnapResult } from "./types";
+
+export function resolveTimelineSnap({
+ targetTime,
+ snapPoints,
+ maxSnapDistance,
+}: {
+ targetTime: number;
+ snapPoints: SnapPoint[];
+ maxSnapDistance: number;
+}): SnapResult {
+ let closestSnapPoint: SnapPoint | null = null;
+ let closestDistance = Infinity;
+
+ for (const snapPoint of snapPoints) {
+ const distance = Math.abs(targetTime - snapPoint.time);
+ if (distance <= maxSnapDistance && distance < closestDistance) {
+ closestDistance = distance;
+ closestSnapPoint = snapPoint;
+ }
+ }
+
+ return {
+ snappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime,
+ snapPoint: closestSnapPoint,
+ snapDistance: closestDistance,
+ };
+}
diff --git a/apps/web/src/timeline/snapping/threshold.ts b/apps/web/src/timeline/snapping/threshold.ts
new file mode 100644
index 00000000..f03111dc
--- /dev/null
+++ b/apps/web/src/timeline/snapping/threshold.ts
@@ -0,0 +1,15 @@
+import { BASE_TIMELINE_PIXELS_PER_SECOND } from "@/timeline/scale";
+import { TICKS_PER_SECOND } from "@/wasm";
+
+const DEFAULT_TIMELINE_SNAP_THRESHOLD_PX = 10;
+
+export function getTimelineSnapThresholdInTicks({
+ zoomLevel,
+ snapThresholdPx = DEFAULT_TIMELINE_SNAP_THRESHOLD_PX,
+}: {
+ zoomLevel: number;
+ snapThresholdPx?: number;
+}): number {
+ const pixelsPerSecond = BASE_TIMELINE_PIXELS_PER_SECOND * zoomLevel;
+ return (snapThresholdPx / pixelsPerSecond) * TICKS_PER_SECOND;
+}
diff --git a/apps/web/src/timeline/snapping/types.ts b/apps/web/src/timeline/snapping/types.ts
new file mode 100644
index 00000000..2a6d0903
--- /dev/null
+++ b/apps/web/src/timeline/snapping/types.ts
@@ -0,0 +1,21 @@
+export type SnapPointType =
+ | "element-start"
+ | "element-end"
+ | "playhead"
+ | "bookmark"
+ | "keyframe";
+
+export interface SnapPoint {
+ time: number;
+ type: SnapPointType;
+ elementId?: string;
+ trackId?: string;
+}
+
+export interface SnapResult {
+ snappedTime: number;
+ snapPoint: SnapPoint | null;
+ snapDistance: number;
+}
+
+export type TimelineSnapPointSource = () => Iterable<SnapPoint>;