oximedia_edit/
insert_mode.rs1#![allow(dead_code)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum InsertMode {
7 Overwrite,
9 Ripple,
11 PushPull,
13}
14
15impl InsertMode {
16 #[must_use]
18 pub fn description(&self) -> &'static str {
19 match self {
20 InsertMode::Overwrite => "Overwrite existing clips",
21 InsertMode::Ripple => "Ripple downstream clips to make room",
22 InsertMode::PushPull => "Push/pull downstream with handle preservation",
23 }
24 }
25
26 #[must_use]
28 pub fn shifts_clips(&self) -> bool {
29 matches!(self, InsertMode::Ripple | InsertMode::PushPull)
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct InsertPoint {
36 pub frame: i64,
38 pub snapped: bool,
40 pub requested_frame: i64,
42}
43
44impl InsertPoint {
45 #[must_use]
47 pub fn new(frame: i64) -> Self {
48 Self {
49 frame,
50 snapped: false,
51 requested_frame: frame,
52 }
53 }
54
55 #[must_use]
58 pub fn snap_to_nearest(mut self, candidates: &[i64], threshold: i64) -> Self {
59 if candidates.is_empty() {
60 return self;
61 }
62 let (nearest, dist) = candidates
63 .iter()
64 .fold((candidates[0], i64::MAX), |best, &c| {
65 let d = (c - self.frame).abs();
66 if d < best.1 {
67 (c, d)
68 } else {
69 best
70 }
71 });
72 if dist <= threshold {
73 self.requested_frame = self.frame;
74 self.frame = nearest;
75 self.snapped = true;
76 }
77 self
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct InsertOperation {
84 pub mode: InsertMode,
86 pub point: InsertPoint,
88 pub clip_duration: u64,
90 pub timeline_duration_before: u64,
92}
93
94impl InsertOperation {
95 #[must_use]
97 pub fn new(
98 mode: InsertMode,
99 point: InsertPoint,
100 clip_duration: u64,
101 timeline_duration_before: u64,
102 ) -> Self {
103 Self {
104 mode,
105 point,
106 clip_duration,
107 timeline_duration_before,
108 }
109 }
110
111 #[must_use]
113 pub fn resulting_duration(&self) -> u64 {
114 match self.mode {
115 InsertMode::Overwrite => {
116 let end = self.point.frame.max(0) as u64 + self.clip_duration;
118 self.timeline_duration_before.max(end)
119 }
120 InsertMode::Ripple | InsertMode::PushPull => {
121 self.timeline_duration_before + self.clip_duration
123 }
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_overwrite_description() {
134 assert_eq!(
135 InsertMode::Overwrite.description(),
136 "Overwrite existing clips"
137 );
138 }
139
140 #[test]
141 fn test_ripple_description() {
142 assert!(InsertMode::Ripple.description().contains("Ripple"));
143 }
144
145 #[test]
146 fn test_push_pull_description() {
147 assert!(InsertMode::PushPull.description().contains("Push/pull"));
148 }
149
150 #[test]
151 fn test_overwrite_does_not_shift() {
152 assert!(!InsertMode::Overwrite.shifts_clips());
153 }
154
155 #[test]
156 fn test_ripple_shifts() {
157 assert!(InsertMode::Ripple.shifts_clips());
158 }
159
160 #[test]
161 fn test_push_pull_shifts() {
162 assert!(InsertMode::PushPull.shifts_clips());
163 }
164
165 #[test]
166 fn test_insert_point_new() {
167 let p = InsertPoint::new(100);
168 assert_eq!(p.frame, 100);
169 assert!(!p.snapped);
170 }
171
172 #[test]
173 fn test_snap_to_nearest_within_threshold() {
174 let p = InsertPoint::new(98).snap_to_nearest(&[100, 200, 50], 5);
175 assert_eq!(p.frame, 100);
176 assert!(p.snapped);
177 assert_eq!(p.requested_frame, 98);
178 }
179
180 #[test]
181 fn test_snap_outside_threshold_no_snap() {
182 let p = InsertPoint::new(50).snap_to_nearest(&[100], 10);
183 assert_eq!(p.frame, 50);
184 assert!(!p.snapped);
185 }
186
187 #[test]
188 fn test_snap_empty_candidates() {
189 let p = InsertPoint::new(30).snap_to_nearest(&[], 5);
190 assert_eq!(p.frame, 30);
191 }
192
193 #[test]
194 fn test_ripple_resulting_duration() {
195 let pt = InsertPoint::new(50);
196 let op = InsertOperation::new(InsertMode::Ripple, pt, 30, 200);
197 assert_eq!(op.resulting_duration(), 230);
198 }
199
200 #[test]
201 fn test_overwrite_within_timeline() {
202 let pt = InsertPoint::new(10);
203 let op = InsertOperation::new(InsertMode::Overwrite, pt, 20, 200);
204 assert_eq!(op.resulting_duration(), 200);
206 }
207
208 #[test]
209 fn test_overwrite_extending_timeline() {
210 let pt = InsertPoint::new(190);
211 let op = InsertOperation::new(InsertMode::Overwrite, pt, 30, 200);
212 assert_eq!(op.resulting_duration(), 220);
214 }
215
216 #[test]
217 fn test_push_pull_resulting_duration() {
218 let pt = InsertPoint::new(0);
219 let op = InsertOperation::new(InsertMode::PushPull, pt, 48, 100);
220 assert_eq!(op.resulting_duration(), 148);
221 }
222}