1use std::cell::{Cell, RefCell};
42use std::collections::BTreeSet;
43use std::rc::Rc;
44
45use teksilo_core::signal::Signal;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum SelectionMode {
50 None,
52 Single,
54 Multi,
56}
57
58pub struct SelectionModel {
63 mode: SelectionMode,
64 selection: Signal<BTreeSet<usize>>,
65 anchor: Rc<Cell<Option<usize>>>,
68 base: Rc<RefCell<BTreeSet<usize>>>,
84 extending: Rc<Cell<bool>>,
88 #[cfg(debug_assertions)]
94 debug_adapter_holder: Rc<RefCell<Option<Rc<dyn crate::debug_registry::ModelDebug>>>>,
95}
96
97impl SelectionModel {
98 pub fn new(mode: SelectionMode) -> Self {
100 Self {
101 mode,
102 selection: Signal::new(BTreeSet::new()),
103 anchor: Rc::new(Cell::new(None)),
104 base: Rc::new(RefCell::new(BTreeSet::new())),
105 extending: Rc::new(Cell::new(false)),
106 #[cfg(debug_assertions)]
107 debug_adapter_holder: Rc::new(RefCell::new(None)),
108 }
109 }
110
111 fn commit_base(&self, base: BTreeSet<usize>) {
117 *self.base.borrow_mut() = base;
118 self.extending.set(false);
119 }
120
121 pub fn mode(&self) -> SelectionMode {
123 self.mode
124 }
125
126 pub fn selection_signal(&self) -> Signal<BTreeSet<usize>> {
128 self.selection.clone()
129 }
130
131 pub fn is_selected(&self, index: usize) -> bool {
133 self.selection.get().contains(&index)
134 }
135
136 pub fn selected_indices(&self) -> Vec<usize> {
138 self.selection.get().into_iter().collect()
139 }
140
141 pub fn count(&self) -> usize {
143 self.selection.get().len()
144 }
145
146 pub fn select(&self, index: usize) {
150 if self.mode == SelectionMode::None {
151 return;
152 }
153 let mut set = BTreeSet::new();
154 set.insert(index);
155 self.selection.set(set);
156 self.anchor.set(Some(index));
157 self.commit_base(BTreeSet::new());
160 }
161
162 pub fn toggle(&self, index: usize) {
165 match self.mode {
166 SelectionMode::None => {}
167 SelectionMode::Single => self.select(index),
168 SelectionMode::Multi => {
169 let mut set = self.selection.get();
170 if set.contains(&index) {
171 set.remove(&index);
172 } else {
173 set.insert(index);
174 }
175 self.selection.set(set.clone());
176 self.anchor.set(Some(index));
182 self.commit_base(set);
185 }
186 }
187 }
188
189 pub fn extend_to(&self, index: usize) {
199 self.extend_from_base(index, false);
200 }
201
202 pub fn extend_to_additive(&self, index: usize) {
212 self.extend_from_base(index, true);
213 }
214
215 fn extend_from_base(&self, index: usize, additive: bool) {
216 match self.mode {
217 SelectionMode::None => {}
218 SelectionMode::Single => self.select(index),
219 SelectionMode::Multi => {
220 if additive && !self.extending.get() {
221 *self.base.borrow_mut() = self.selection.get();
222 }
223 let anchor = self.anchor.get().unwrap_or(index);
224 let start = anchor.min(index);
225 let end = anchor.max(index);
226 let mut set = self.base.borrow().clone();
227 set.extend(start..=end);
228 self.selection.set(set);
229 self.extending.set(true);
230 }
232 }
233 }
234
235 pub fn select_indices(&self, indices: impl IntoIterator<Item = usize>, additive: bool) {
240 if self.mode == SelectionMode::None {
241 return;
242 }
243 let mut set = if additive {
244 self.selection.get()
245 } else {
246 BTreeSet::new()
247 };
248 set.extend(indices);
249 if self.mode == SelectionMode::Single {
250 let last = set.iter().next_back().copied();
251 set = last.into_iter().collect();
252 }
253 self.selection.set(set.clone());
254 self.commit_base(if additive { set } else { BTreeSet::new() });
257 }
258
259 pub fn select_all(&self, count: usize) {
272 if self.mode == SelectionMode::None || self.mode == SelectionMode::Single {
273 return;
274 }
275 let set: BTreeSet<usize> = (0..count).collect();
276 self.selection.set(set.clone());
277 self.commit_base(set);
278 }
279
280 pub fn clear(&self) {
282 self.selection.set(BTreeSet::new());
283 self.anchor.set(None);
284 self.commit_base(BTreeSet::new());
285 }
286
287 pub fn adjust_for_insert(&self, start: usize, count: usize) {
290 let old = self.selection.get();
291 let mut new_set = BTreeSet::new();
292 for &idx in &old {
293 if idx >= start {
294 new_set.insert(idx + count);
295 } else {
296 new_set.insert(idx);
297 }
298 }
299 if new_set != old {
300 self.selection.set(new_set);
301 }
302 if let Some(a) = self.anchor.get()
303 && a >= start
304 {
305 self.anchor.set(Some(a + count));
306 }
307 self.remap_base(|idx| Some(if idx >= start { idx + count } else { idx }));
311 }
312
313 pub fn adjust_for_remove(&self, start: usize, count: usize) {
316 let old = self.selection.get();
317 let end = start + count;
318 let mut new_set = BTreeSet::new();
319 for &idx in &old {
320 if idx < start {
321 new_set.insert(idx);
322 } else if idx >= end {
323 new_set.insert(idx - count);
324 }
325 }
327 if new_set != old {
328 self.selection.set(new_set);
329 }
330 if let Some(a) = self.anchor.get() {
331 if a >= end {
332 self.anchor.set(Some(a - count));
333 } else if a >= start {
334 self.anchor.set(None);
335 }
336 }
337 self.remap_base(|idx| {
338 if idx < start {
339 Some(idx)
340 } else if idx >= end {
341 Some(idx - count)
342 } else {
343 None
344 }
345 });
346 }
347
348 pub fn adjust_for_move(&self, from: usize, to: usize, count: usize) {
352 if from == to || count == 0 {
353 return;
354 }
355 let old = self.selection.get();
356 let new_set: BTreeSet<usize> = old
357 .iter()
358 .map(|&idx| crate::map_index_after_move(idx, from, to, count))
359 .collect();
360 if new_set != old {
361 self.selection.set(new_set);
362 }
363 if let Some(a) = self.anchor.get() {
364 self.anchor
365 .set(Some(crate::map_index_after_move(a, from, to, count)));
366 }
367 self.remap_base(|idx| Some(crate::map_index_after_move(idx, from, to, count)));
368 }
369
370 fn remap_base(&self, map: impl Fn(usize) -> Option<usize>) {
373 let mut base = self.base.borrow_mut();
374 if base.is_empty() {
375 return;
376 }
377 *base = base.iter().filter_map(|&idx| map(idx)).collect();
378 }
379
380 pub fn invalidate_anchor_from(&self, first_changed: usize) {
393 if self.anchor.get().is_some_and(|a| a >= first_changed) {
394 self.anchor.set(None);
395 }
396 self.remap_base(|idx| (idx < first_changed).then_some(idx));
397 }
398}
399
400impl Clone for SelectionModel {
401 fn clone(&self) -> Self {
402 Self {
403 mode: self.mode,
404 selection: self.selection.clone(),
405 anchor: self.anchor.clone(),
406 base: self.base.clone(),
407 extending: self.extending.clone(),
408 #[cfg(debug_assertions)]
409 debug_adapter_holder: self.debug_adapter_holder.clone(),
410 }
411 }
412}
413
414impl SelectionModel {
415 pub fn debug_named(self, _name: impl Into<String>) -> Self {
424 #[cfg(debug_assertions)]
425 {
426 let adapter: Rc<dyn crate::debug_registry::ModelDebug> = Rc::new(SelectionModelDebug {
427 selection: self.selection.clone(),
428 mode: self.mode,
429 });
430 crate::debug_registry::register(_name.into(), Rc::downgrade(&adapter));
431 *self.debug_adapter_holder.borrow_mut() = Some(adapter);
432 }
433 self
434 }
435}
436
437#[cfg(debug_assertions)]
438struct SelectionModelDebug {
439 selection: Signal<BTreeSet<usize>>,
440 mode: SelectionMode,
441}
442
443#[cfg(debug_assertions)]
444impl crate::debug_registry::ModelDebug for SelectionModelDebug {
445 fn kind(&self) -> &'static str {
446 "SelectionModel"
447 }
448 fn len(&self) -> usize {
449 self.selection.get().len()
450 }
451 fn debug_dump(&self, out: &mut dyn std::fmt::Write) {
452 let _ = writeln!(out, "mode = {:?}", self.mode);
453 let sel = self.selection.get();
454 if sel.is_empty() {
455 let _ = writeln!(out, "(empty)");
456 return;
457 }
458 for i in sel.iter() {
459 let _ = writeln!(out, "[{}]", i);
460 }
461 }
462}
463
464impl std::fmt::Debug for SelectionModel {
465 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
466 f.debug_struct("SelectionModel")
467 .field("mode", &self.mode)
468 .field("selected_count", &self.selection.get().len())
469 .finish()
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn single_select() {
479 let model = SelectionModel::new(SelectionMode::Single);
480 model.select(2);
481 assert!(model.is_selected(2));
482 assert!(!model.is_selected(0));
483 assert_eq!(model.selected_indices(), vec![2]);
484
485 model.select(5);
486 assert!(!model.is_selected(2));
487 assert!(model.is_selected(5));
488 }
489
490 #[test]
491 fn multi_select_toggle() {
492 let model = SelectionModel::new(SelectionMode::Multi);
493 model.toggle(1);
494 model.toggle(3);
495 assert!(model.is_selected(1));
496 assert!(model.is_selected(3));
497 assert_eq!(model.count(), 2);
498
499 model.toggle(1);
500 assert!(!model.is_selected(1));
501 assert!(model.is_selected(3));
502 }
503
504 #[test]
505 fn multi_select_extend_range() {
506 let model = SelectionModel::new(SelectionMode::Multi);
507 model.select(2); model.extend_to(5); assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
510 }
511
512 #[test]
513 fn extend_backwards() {
514 let model = SelectionModel::new(SelectionMode::Multi);
515 model.select(5);
516 model.extend_to(2);
517 assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
518 }
519
520 #[test]
521 fn reversing_a_shift_gesture_shrinks_the_range() {
522 let model = SelectionModel::new(SelectionMode::Multi);
526 model.select(2);
527 model.extend_to(6);
528 assert_eq!(model.selected_indices(), vec![2, 3, 4, 5, 6]);
529 model.extend_to(4);
530 assert_eq!(model.selected_indices(), vec![2, 3, 4]);
531 model.extend_to(2);
532 assert_eq!(model.selected_indices(), vec![2]);
533 }
534
535 #[test]
536 fn extending_across_the_anchor_replaces_rather_than_unions() {
537 let model = SelectionModel::new(SelectionMode::Multi);
538 model.select(5);
539 model.extend_to(8);
540 assert_eq!(model.selected_indices(), vec![5, 6, 7, 8]);
541 model.extend_to(3);
543 assert_eq!(model.selected_indices(), vec![3, 4, 5]);
544 }
545
546 #[test]
547 fn shift_end_then_shift_home_selects_one_row_not_the_whole_list() {
548 let model = SelectionModel::new(SelectionMode::Multi);
551 model.select(4);
552 model.extend_to(9); model.extend_to(0); assert_eq!(model.selected_indices(), vec![0, 1, 2, 3, 4]);
555 }
556
557 #[test]
558 fn a_ctrl_toggle_moves_the_anchor_and_survives_the_next_shift_range() {
559 let model = SelectionModel::new(SelectionMode::Multi);
562 model.select(1);
563 model.toggle(5);
564 model.extend_to(7);
565 assert_eq!(model.selected_indices(), vec![1, 5, 6, 7]);
566 model.extend_to(6);
568 assert_eq!(model.selected_indices(), vec![1, 5, 6]);
569 }
570
571 #[test]
572 fn the_anchor_moves_when_a_toggle_deselects_too() {
573 let model = SelectionModel::new(SelectionMode::Multi);
574 model.select(3);
575 model.toggle(3); model.extend_to(5);
577 assert_eq!(model.selected_indices(), vec![3, 4, 5]);
578 }
579
580 #[test]
581 fn an_additive_extend_keeps_the_range_built_by_the_previous_gesture() {
582 let model = SelectionModel::new(SelectionMode::Multi);
584 model.select(0);
585 model.extend_to(2);
586 assert_eq!(model.selected_indices(), vec![0, 1, 2]);
587 model.toggle(6); model.extend_to_additive(8);
589 assert_eq!(model.selected_indices(), vec![0, 1, 2, 6, 7, 8]);
590 model.extend_to_additive(7);
592 assert_eq!(model.selected_indices(), vec![0, 1, 2, 6, 7]);
593 }
594
595 #[test]
596 fn a_plain_extend_after_a_click_discards_everything_else() {
597 let model = SelectionModel::new(SelectionMode::Multi);
598 model.select_indices([1, 2, 8], false);
599 model.select(4); model.extend_to(6);
601 assert_eq!(model.selected_indices(), vec![4, 5, 6]);
602 }
603
604 #[test]
605 fn an_additive_marquee_is_kept_by_a_following_shift_range() {
606 let model = SelectionModel::new(SelectionMode::Multi);
607 model.select(0);
608 model.select_indices([7, 8], true);
609 model.toggle(2);
610 model.extend_to(4);
611 assert_eq!(model.selected_indices(), vec![0, 2, 3, 4, 7, 8]);
612 }
613
614 #[test]
615 fn the_gesture_base_follows_an_insert_and_a_remove() {
616 let model = SelectionModel::new(SelectionMode::Multi);
617 model.select(1);
618 model.toggle(5); model.adjust_for_insert(0, 2); model.extend_to(9); assert_eq!(model.selected_indices(), vec![3, 7, 8, 9]);
622
623 let model = SelectionModel::new(SelectionMode::Multi);
624 model.select(1);
625 model.toggle(5);
626 model.adjust_for_remove(0, 1); model.extend_to(6);
628 assert_eq!(model.selected_indices(), vec![0, 4, 5, 6]);
629 }
630
631 #[test]
632 fn a_reprojection_drops_an_anchor_it_has_renumbered() {
633 let model = SelectionModel::new(SelectionMode::Multi);
634 model.select(2);
635 model.toggle(6);
636 model.invalidate_anchor_from(4);
638 model.extend_to(8);
641 assert_eq!(model.selected_indices(), vec![2, 8]);
642 }
643
644 #[test]
645 fn single_mode_ignores_the_additive_extend_like_every_other_mutator() {
646 let model = SelectionModel::new(SelectionMode::Single);
647 model.select(3);
648 model.extend_to_additive(7);
649 assert_eq!(model.selected_indices(), vec![7]);
650 }
651
652 #[test]
653 fn select_all() {
654 let model = SelectionModel::new(SelectionMode::Multi);
655 model.select_all(5);
656 assert_eq!(model.selected_indices(), vec![0, 1, 2, 3, 4]);
657 }
658
659 #[test]
660 fn select_indices_replaces_then_adds() {
661 let model = SelectionModel::new(SelectionMode::Multi);
662 model.select(1);
663 model.select_indices([4, 5], false);
665 assert_eq!(model.selected_indices(), vec![4, 5]);
666 model.select_indices([2], true);
668 assert_eq!(model.selected_indices(), vec![2, 4, 5]);
669 }
670
671 #[test]
672 fn clear() {
673 let model = SelectionModel::new(SelectionMode::Multi);
674 model.select_all(3);
675 model.clear();
676 assert_eq!(model.count(), 0);
677 }
678
679 #[test]
680 fn none_mode_ignores_all() {
681 let model = SelectionModel::new(SelectionMode::None);
682 model.select(1);
683 assert_eq!(model.count(), 0);
684 model.toggle(2);
685 assert_eq!(model.count(), 0);
686 model.select_all(10);
687 assert_eq!(model.count(), 0);
688 }
689
690 #[test]
691 fn adjust_for_insert() {
692 let model = SelectionModel::new(SelectionMode::Multi);
693 model.toggle(1);
694 model.toggle(3);
695 model.adjust_for_insert(2, 2);
697 assert_eq!(model.selected_indices(), vec![1, 5]);
699 }
700
701 #[test]
702 fn adjust_for_remove() {
703 let model = SelectionModel::new(SelectionMode::Multi);
704 model.toggle(1);
705 model.toggle(3);
706 model.toggle(5);
707 model.adjust_for_remove(3, 1);
709 assert_eq!(model.selected_indices(), vec![1, 4]);
711 }
712
713 #[test]
714 fn adjust_for_move_follows_the_moved_item() {
715 let model = SelectionModel::new(SelectionMode::Multi);
717 model.toggle(0);
718 model.adjust_for_move(0, 2, 1);
719 assert_eq!(model.selected_indices(), vec![2], "selection followed A");
720 }
721
722 #[test]
723 fn adjust_for_move_shifts_a_bystander_selection() {
724 let model = SelectionModel::new(SelectionMode::Multi);
726 model.toggle(1);
727 model.adjust_for_move(0, 2, 1);
728 assert_eq!(model.selected_indices(), vec![0], "B shifted down to 0");
729 }
730
731 #[test]
732 fn adjust_for_move_backwards() {
733 let model = SelectionModel::new(SelectionMode::Multi);
735 model.toggle(3);
736 model.adjust_for_move(3, 1, 1);
737 assert_eq!(model.selected_indices(), vec![1]);
738 }
739
740 #[test]
741 fn signal_reactivity() {
742 use std::cell::Cell;
743 use std::rc::Rc;
744
745 let model = SelectionModel::new(SelectionMode::Single);
746 let signal = model.selection_signal();
747 let changed = Rc::new(Cell::new(false));
748 let c = changed.clone();
749 let _handle = signal.observe(move |_| c.set(true));
750
751 model.select(3);
752 assert!(changed.get());
753 }
754
755 #[test]
756 fn single_mode_extend_acts_as_select() {
757 let model = SelectionModel::new(SelectionMode::Single);
758 model.select(1);
759 model.extend_to(5);
760 assert_eq!(model.selected_indices(), vec![5]);
762 }
763}