1#![allow(dead_code)]
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct SelectionRange {
10 pub start: u64,
12 pub end: u64,
14}
15
16impl SelectionRange {
17 #[must_use]
23 pub fn new(start: u64, end: u64) -> Self {
24 debug_assert!(end >= start, "SelectionRange: end must be >= start");
25 Self { start, end }
26 }
27
28 #[must_use]
30 pub fn duration(&self) -> u64 {
31 self.end.saturating_sub(self.start)
32 }
33
34 #[must_use]
36 pub fn overlaps(&self, other: &Self) -> bool {
37 self.start < other.end && other.start < self.end
38 }
39
40 #[must_use]
43 pub fn can_merge(&self, other: &Self) -> bool {
44 self.start <= other.end && other.start <= self.end
45 }
46
47 #[must_use]
49 pub fn merge(&self, other: &Self) -> Self {
50 Self {
51 start: self.start.min(other.start),
52 end: self.end.max(other.end),
53 }
54 }
55
56 #[must_use]
58 pub fn contains_time(&self, t: u64) -> bool {
59 t >= self.start && t < self.end
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum SelectionMode {
66 Replace,
68 Add,
70 Subtract,
72 Toggle,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct TimelineClipRef {
80 pub id: u64,
82 pub start: u64,
84 pub end: u64,
86}
87
88impl TimelineClipRef {
89 #[must_use]
91 pub const fn new(id: u64, start: u64, end: u64) -> Self {
92 Self { id, start, end }
93 }
94
95 #[must_use]
97 pub fn overlaps_range(&self, range: &SelectionRange) -> bool {
98 self.start < range.end && range.start < self.end
99 }
100}
101
102#[derive(Debug, Clone, Default)]
109pub struct Selection {
110 ranges: Vec<SelectionRange>,
112 selected_clips: Vec<u64>,
114 selected_tracks: Vec<u32>,
116}
117
118impl Selection {
119 #[must_use]
121 pub fn new() -> Self {
122 Self::default()
123 }
124
125 pub fn select_range(&mut self, range: SelectionRange, mode: SelectionMode) {
131 match mode {
132 SelectionMode::Replace => {
133 self.ranges.clear();
134 if range.duration() > 0 {
135 self.ranges.push(range);
136 }
137 }
138 SelectionMode::Add => {
139 if range.duration() > 0 {
140 self.ranges.push(range);
141 self.merge_overlapping_ranges();
142 }
143 }
144 SelectionMode::Subtract => {
145 self.subtract_range(range);
146 }
147 SelectionMode::Toggle => {
148 if self.ranges.iter().any(|r| r.overlaps(&range)) {
149 self.subtract_range(range);
150 } else {
151 self.ranges.push(range);
152 self.merge_overlapping_ranges();
153 }
154 }
155 }
156 }
157
158 fn subtract_range(&mut self, sub: SelectionRange) {
160 let mut result = Vec::new();
161 for r in self.ranges.drain(..) {
162 if r.end <= sub.start || r.start >= sub.end {
163 result.push(r);
165 } else {
166 if r.start < sub.start {
168 result.push(SelectionRange::new(r.start, sub.start));
169 }
170 if r.end > sub.end {
172 result.push(SelectionRange::new(sub.end, r.end));
173 }
174 }
175 }
176 self.ranges = result;
177 }
178
179 pub fn merge_overlapping_ranges(&mut self) {
181 if self.ranges.len() < 2 {
182 return;
183 }
184 self.ranges.sort_by_key(|r| r.start);
185 let mut merged: Vec<SelectionRange> = Vec::new();
186 for r in self.ranges.drain(..) {
187 if let Some(last) = merged.last_mut() {
188 if last.can_merge(&r) {
189 *last = last.merge(&r);
190 continue;
191 }
192 }
193 merged.push(r);
194 }
195 self.ranges = merged;
196 }
197
198 #[must_use]
200 pub fn selected_duration(&self) -> u64 {
201 self.ranges.iter().map(SelectionRange::duration).sum()
202 }
203
204 #[must_use]
206 pub fn ranges(&self) -> &[SelectionRange] {
207 &self.ranges
208 }
209
210 pub fn select_clip(&mut self, id: u64, mode: SelectionMode) {
216 match mode {
217 SelectionMode::Replace => {
218 self.selected_clips.clear();
219 self.selected_clips.push(id);
220 }
221 SelectionMode::Add => {
222 if !self.selected_clips.contains(&id) {
223 self.selected_clips.push(id);
224 }
225 }
226 SelectionMode::Subtract => {
227 self.selected_clips.retain(|&c| c != id);
228 }
229 SelectionMode::Toggle => {
230 if self.selected_clips.contains(&id) {
231 self.selected_clips.retain(|&c| c != id);
232 } else {
233 self.selected_clips.push(id);
234 }
235 }
236 }
237 }
238
239 #[must_use]
241 pub fn is_clip_selected(&self, id: u64) -> bool {
242 self.selected_clips.contains(&id)
243 }
244
245 #[must_use]
247 pub fn selected_clips(&self) -> &[u64] {
248 &self.selected_clips
249 }
250
251 pub fn select_all_in_range(&mut self, clips: &[TimelineClipRef], range: SelectionRange) {
253 for clip in clips {
254 if clip.overlaps_range(&range) && !self.selected_clips.contains(&clip.id) {
255 self.selected_clips.push(clip.id);
256 }
257 }
258 }
259
260 pub fn select_track(&mut self, track: u32, mode: SelectionMode) {
266 match mode {
267 SelectionMode::Replace => {
268 self.selected_tracks.clear();
269 self.selected_tracks.push(track);
270 }
271 SelectionMode::Add => {
272 if !self.selected_tracks.contains(&track) {
273 self.selected_tracks.push(track);
274 }
275 }
276 SelectionMode::Subtract => {
277 self.selected_tracks.retain(|&t| t != track);
278 }
279 SelectionMode::Toggle => {
280 if self.selected_tracks.contains(&track) {
281 self.selected_tracks.retain(|&t| t != track);
282 } else {
283 self.selected_tracks.push(track);
284 }
285 }
286 }
287 }
288
289 #[must_use]
291 pub fn is_track_selected(&self, index: u32) -> bool {
292 self.selected_tracks.contains(&index)
293 }
294
295 #[must_use]
297 pub fn selected_tracks(&self) -> &[u32] {
298 &self.selected_tracks
299 }
300
301 pub fn clear(&mut self) {
307 self.ranges.clear();
308 self.selected_clips.clear();
309 self.selected_tracks.clear();
310 }
311
312 #[must_use]
314 pub fn is_empty(&self) -> bool {
315 self.ranges.is_empty() && self.selected_clips.is_empty() && self.selected_tracks.is_empty()
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321pub struct SelectionItem {
322 pub clip_id: u64,
324 pub track_id: u32,
326}
327
328impl SelectionItem {
329 #[must_use]
331 pub fn new(clip_id: u64, track_id: u32) -> Self {
332 Self { clip_id, track_id }
333 }
334
335 #[must_use]
337 pub fn same_track(&self, other: &SelectionItem) -> bool {
338 self.track_id == other.track_id
339 }
340}
341
342#[derive(Debug, Clone, Default)]
344pub struct EditSelection {
345 pub items: Vec<SelectionItem>,
347}
348
349impl EditSelection {
350 #[must_use]
352 pub fn new() -> Self {
353 Self::default()
354 }
355
356 pub fn add(&mut self, item: SelectionItem) {
359 if !self.contains(item.clip_id) {
360 self.items.push(item);
361 }
362 }
363
364 pub fn remove(&mut self, clip_id: u64) {
366 self.items.retain(|i| i.clip_id != clip_id);
367 }
368
369 #[must_use]
371 pub fn contains(&self, clip_id: u64) -> bool {
372 self.items.iter().any(|i| i.clip_id == clip_id)
373 }
374
375 pub fn clear(&mut self) {
377 self.items.clear();
378 }
379
380 #[must_use]
382 pub fn count(&self) -> usize {
383 self.items.len()
384 }
385
386 #[must_use]
388 pub fn tracks(&self) -> Vec<u32> {
389 let mut seen: Vec<u32> = Vec::new();
390 for item in &self.items {
391 if !seen.contains(&item.track_id) {
392 seen.push(item.track_id);
393 }
394 }
395 seen
396 }
397}
398
399#[derive(Debug, Clone, Default)]
402pub struct LinkedSelection {
403 pub groups: Vec<Vec<u64>>,
405}
406
407impl LinkedSelection {
408 #[must_use]
410 pub fn new() -> Self {
411 Self::default()
412 }
413
414 pub fn add_linked_group(&mut self, ids: Vec<u64>) {
416 if !ids.is_empty() {
417 self.groups.push(ids);
418 }
419 }
420
421 #[must_use]
424 pub fn linked_clips(&self, clip_id: u64) -> Vec<u64> {
425 for group in &self.groups {
426 if group.contains(&clip_id) {
427 return group.iter().copied().filter(|&id| id != clip_id).collect();
428 }
429 }
430 Vec::new()
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
443 fn test_range_duration() {
444 let r = SelectionRange::new(10, 30);
445 assert_eq!(r.duration(), 20);
446 }
447
448 #[test]
449 fn test_range_overlaps() {
450 let a = SelectionRange::new(0, 10);
451 let b = SelectionRange::new(5, 15);
452 let c = SelectionRange::new(10, 20);
453 assert!(a.overlaps(&b));
454 assert!(!a.overlaps(&c)); }
456
457 #[test]
458 fn test_range_merge() {
459 let a = SelectionRange::new(0, 10);
460 let b = SelectionRange::new(8, 20);
461 let m = a.merge(&b);
462 assert_eq!(m.start, 0);
463 assert_eq!(m.end, 20);
464 }
465
466 #[test]
467 fn test_range_contains_time() {
468 let r = SelectionRange::new(10, 20);
469 assert!(r.contains_time(10));
470 assert!(r.contains_time(15));
471 assert!(!r.contains_time(20)); }
473
474 #[test]
479 fn test_select_range_replace() {
480 let mut s = Selection::new();
481 s.select_range(SelectionRange::new(0, 100), SelectionMode::Add);
482 s.select_range(SelectionRange::new(200, 300), SelectionMode::Replace);
483 assert_eq!(s.ranges().len(), 1);
484 assert_eq!(s.ranges()[0], SelectionRange::new(200, 300));
485 }
486
487 #[test]
488 fn test_select_range_add_merges() {
489 let mut s = Selection::new();
490 s.select_range(SelectionRange::new(0, 50), SelectionMode::Add);
491 s.select_range(SelectionRange::new(40, 100), SelectionMode::Add);
492 assert_eq!(s.ranges().len(), 1);
493 assert_eq!(s.ranges()[0].end, 100);
494 }
495
496 #[test]
497 fn test_select_range_subtract() {
498 let mut s = Selection::new();
499 s.select_range(SelectionRange::new(0, 100), SelectionMode::Add);
500 s.select_range(SelectionRange::new(40, 60), SelectionMode::Subtract);
501 assert_eq!(s.ranges().len(), 2);
502 assert_eq!(s.ranges()[0], SelectionRange::new(0, 40));
503 assert_eq!(s.ranges()[1], SelectionRange::new(60, 100));
504 }
505
506 #[test]
507 fn test_selected_duration() {
508 let mut s = Selection::new();
509 s.select_range(SelectionRange::new(0, 50), SelectionMode::Add);
510 s.select_range(SelectionRange::new(100, 150), SelectionMode::Add);
511 assert_eq!(s.selected_duration(), 100);
512 }
513
514 #[test]
519 fn test_select_clip_replace() {
520 let mut s = Selection::new();
521 s.select_clip(1, SelectionMode::Add);
522 s.select_clip(2, SelectionMode::Add);
523 s.select_clip(3, SelectionMode::Replace);
524 assert_eq!(s.selected_clips().len(), 1);
525 assert!(s.is_clip_selected(3));
526 }
527
528 #[test]
529 fn test_select_clip_add_no_duplicates() {
530 let mut s = Selection::new();
531 s.select_clip(5, SelectionMode::Add);
532 s.select_clip(5, SelectionMode::Add);
533 assert_eq!(s.selected_clips().len(), 1);
534 }
535
536 #[test]
537 fn test_select_clip_toggle() {
538 let mut s = Selection::new();
539 s.select_clip(7, SelectionMode::Toggle);
540 assert!(s.is_clip_selected(7));
541 s.select_clip(7, SelectionMode::Toggle);
542 assert!(!s.is_clip_selected(7));
543 }
544
545 #[test]
546 fn test_select_all_in_range() {
547 let clips = vec![
548 TimelineClipRef::new(1, 0, 50),
549 TimelineClipRef::new(2, 50, 100),
550 TimelineClipRef::new(3, 200, 300),
551 ];
552 let mut s = Selection::new();
553 s.select_all_in_range(&clips, SelectionRange::new(0, 100));
554 assert!(s.is_clip_selected(1));
555 assert!(s.is_clip_selected(2));
556 assert!(!s.is_clip_selected(3));
557 }
558
559 #[test]
564 fn test_clear() {
565 let mut s = Selection::new();
566 s.select_clip(1, SelectionMode::Add);
567 s.select_range(SelectionRange::new(0, 100), SelectionMode::Add);
568 s.select_track(0, SelectionMode::Add);
569 s.clear();
570 assert!(s.is_empty());
571 }
572
573 #[test]
574 fn test_merge_overlapping_three_ranges() {
575 let mut s = Selection::new();
576 s.select_range(SelectionRange::new(0, 30), SelectionMode::Add);
577 s.select_range(SelectionRange::new(20, 60), SelectionMode::Add);
578 s.select_range(SelectionRange::new(55, 100), SelectionMode::Add);
579 assert_eq!(s.ranges().len(), 1);
580 assert_eq!(s.ranges()[0], SelectionRange::new(0, 100));
581 }
582
583 #[test]
584 fn test_is_empty_initially() {
585 let s = Selection::new();
586 assert!(s.is_empty());
587 }
588
589 #[test]
590 fn test_track_selection() {
591 let mut s = Selection::new();
592 s.select_track(0, SelectionMode::Add);
593 s.select_track(1, SelectionMode::Add);
594 assert!(s.is_track_selected(0));
595 assert!(s.is_track_selected(1));
596 s.select_track(0, SelectionMode::Subtract);
597 assert!(!s.is_track_selected(0));
598 }
599
600 #[test]
605 fn test_selection_item_same_track_true() {
606 let a = SelectionItem::new(1, 0);
607 let b = SelectionItem::new(2, 0);
608 assert!(a.same_track(&b));
609 }
610
611 #[test]
612 fn test_selection_item_same_track_false() {
613 let a = SelectionItem::new(1, 0);
614 let b = SelectionItem::new(2, 1);
615 assert!(!a.same_track(&b));
616 }
617
618 #[test]
623 fn test_edit_selection_add_and_contains() {
624 let mut sel = EditSelection::new();
625 sel.add(SelectionItem::new(10, 0));
626 assert!(sel.contains(10));
627 assert!(!sel.contains(99));
628 }
629
630 #[test]
631 fn test_edit_selection_add_no_duplicate() {
632 let mut sel = EditSelection::new();
633 sel.add(SelectionItem::new(5, 1));
634 sel.add(SelectionItem::new(5, 1));
635 assert_eq!(sel.count(), 1);
636 }
637
638 #[test]
639 fn test_edit_selection_remove() {
640 let mut sel = EditSelection::new();
641 sel.add(SelectionItem::new(3, 0));
642 sel.remove(3);
643 assert!(!sel.contains(3));
644 assert_eq!(sel.count(), 0);
645 }
646
647 #[test]
648 fn test_edit_selection_clear() {
649 let mut sel = EditSelection::new();
650 sel.add(SelectionItem::new(1, 0));
651 sel.add(SelectionItem::new(2, 1));
652 sel.clear();
653 assert_eq!(sel.count(), 0);
654 }
655
656 #[test]
657 fn test_edit_selection_tracks_unique() {
658 let mut sel = EditSelection::new();
659 sel.add(SelectionItem::new(1, 0));
660 sel.add(SelectionItem::new(2, 0));
661 sel.add(SelectionItem::new(3, 1));
662 let tracks = sel.tracks();
663 assert_eq!(tracks.len(), 2);
664 assert!(tracks.contains(&0));
665 assert!(tracks.contains(&1));
666 }
667
668 #[test]
669 fn test_edit_selection_count() {
670 let mut sel = EditSelection::new();
671 assert_eq!(sel.count(), 0);
672 sel.add(SelectionItem::new(7, 2));
673 assert_eq!(sel.count(), 1);
674 }
675
676 #[test]
681 fn test_linked_selection_no_links() {
682 let ls = LinkedSelection::new();
683 assert!(ls.linked_clips(42).is_empty());
684 }
685
686 #[test]
687 fn test_linked_selection_add_group() {
688 let mut ls = LinkedSelection::new();
689 ls.add_linked_group(vec![1, 2, 3]);
690 let linked = ls.linked_clips(1);
691 assert!(linked.contains(&2));
692 assert!(linked.contains(&3));
693 assert!(!linked.contains(&1)); }
695
696 #[test]
697 fn test_linked_selection_multiple_groups() {
698 let mut ls = LinkedSelection::new();
699 ls.add_linked_group(vec![10, 11]);
700 ls.add_linked_group(vec![20, 21, 22]);
701 assert_eq!(ls.linked_clips(10), vec![11]);
703 let linked = ls.linked_clips(22);
705 assert!(linked.contains(&20));
706 assert!(linked.contains(&21));
707 }
708
709 #[test]
710 fn test_linked_selection_empty_group_ignored() {
711 let mut ls = LinkedSelection::new();
712 ls.add_linked_group(vec![]);
713 assert_eq!(ls.groups.len(), 0);
714 }
715
716 #[test]
717 fn test_linked_selection_clip_not_in_any_group() {
718 let mut ls = LinkedSelection::new();
719 ls.add_linked_group(vec![1, 2]);
720 assert!(ls.linked_clips(99).is_empty());
721 }
722}