summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/timeline/group-move/snap.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-15 05:43:29 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-15 05:43:29 +0200
commitac43a5a73fa905cdac905bc956763b6093d6738e (patch)
tree8d9ff8c3a9ae55bac8860215bec69cc5b6094a1c /apps/web/src/lib/timeline/group-move/snap.ts
parent6312fc5b078689bc82c52fda9b7f92ee0a21d311 (diff)
downloadopencut-ac43a5a73fa905cdac905bc956763b6093d6738e.tar.gz
feat: move/resize multiple elements
Diffstat (limited to 'apps/web/src/lib/timeline/group-move/snap.ts')
-rw-r--r--apps/web/src/lib/timeline/group-move/snap.ts71
1 files changed, 71 insertions, 0 deletions
diff --git a/apps/web/src/lib/timeline/group-move/snap.ts b/apps/web/src/lib/timeline/group-move/snap.ts
new file mode 100644
index 00000000..3502ae30
--- /dev/null
+++ b/apps/web/src/lib/timeline/group-move/snap.ts
@@ -0,0 +1,71 @@
+import type { SceneTracks } from "@/lib/timeline";
+import {
+ findSnapPoints,
+ snapToNearestPoint,
+ type SnapPoint,
+} from "@/lib/timeline/snap-utils";
+import type { MoveGroup } from "./types";
+
+export function snapGroupEdges({
+ group,
+ anchorStartTime,
+ tracks,
+ playheadTime,
+ zoomLevel,
+}: {
+ group: MoveGroup;
+ anchorStartTime: number;
+ tracks: SceneTracks;
+ playheadTime: number;
+ zoomLevel: number;
+}): {
+ snappedAnchorStartTime: number;
+ snapPoint: SnapPoint | null;
+} {
+ const snapPoints = findSnapPoints({
+ tracks,
+ playheadTime,
+ excludeElementIds: new Set(group.members.map((member) => member.elementId)),
+ });
+
+ let closestSnapDistance = Infinity;
+ let snappedAnchorStartTime = anchorStartTime;
+ let snapPoint: SnapPoint | null = null;
+
+ for (const member of group.members) {
+ const memberStartTime = anchorStartTime + member.timeOffset;
+ const memberStartSnap = snapToNearestPoint({
+ targetTime: memberStartTime,
+ snapPoints,
+ zoomLevel,
+ });
+ if (
+ memberStartSnap.snapPoint &&
+ memberStartSnap.snapDistance < closestSnapDistance
+ ) {
+ closestSnapDistance = memberStartSnap.snapDistance;
+ snappedAnchorStartTime = memberStartSnap.snappedTime - member.timeOffset;
+ snapPoint = memberStartSnap.snapPoint;
+ }
+
+ const memberEndSnap = snapToNearestPoint({
+ targetTime: memberStartTime + member.duration,
+ snapPoints,
+ zoomLevel,
+ });
+ if (
+ memberEndSnap.snapPoint &&
+ memberEndSnap.snapDistance < closestSnapDistance
+ ) {
+ closestSnapDistance = memberEndSnap.snapDistance;
+ snappedAnchorStartTime =
+ memberEndSnap.snappedTime - member.duration - member.timeOffset;
+ snapPoint = memberEndSnap.snapPoint;
+ }
+ }
+
+ return {
+ snappedAnchorStartTime,
+ snapPoint,
+ };
+}