1use std::cell::Cell;
42#[cfg(debug_assertions)]
43use std::cell::RefCell;
44use std::collections::BTreeSet;
45use std::rc::Rc;
46
47use teksilo_core::signal::Signal;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub enum SelectionMode {
52 None,
54 Single,
56 Multi,
58}
59
60pub struct SelectionModel {
65 mode: SelectionMode,
66 selection: Signal<BTreeSet<usize>>,
67 anchor: Rc<Cell<Option<usize>>>,
70 #[cfg(debug_assertions)]
76 debug_adapter_holder: Rc<RefCell<Option<Rc<dyn crate::debug_registry::ModelDebug>>>>,
77}
78
79impl SelectionModel {
80 pub fn new(mode: SelectionMode) -> Self {
82 Self {
83 mode,
84 selection: Signal::new(BTreeSet::new()),
85 anchor: Rc::new(Cell::new(None)),
86 #[cfg(debug_assertions)]
87 debug_adapter_holder: Rc::new(RefCell::new(None)),
88 }
89 }
90
91 pub fn mode(&self) -> SelectionMode {
93 self.mode
94 }
95
96 pub fn selection_signal(&self) -> Signal<BTreeSet<usize>> {
98 self.selection.clone()
99 }
100
101 pub fn is_selected(&self, index: usize) -> bool {
103 self.selection.get().contains(&index)
104 }
105
106 pub fn selected_indices(&self) -> Vec<usize> {
108 self.selection.get().into_iter().collect()
109 }
110
111 pub fn count(&self) -> usize {
113 self.selection.get().len()
114 }
115
116 pub fn select(&self, index: usize) {
120 if self.mode == SelectionMode::None {
121 return;
122 }
123 let mut set = BTreeSet::new();
124 set.insert(index);
125 self.selection.set(set);
126 self.anchor.set(Some(index));
127 }
128
129 pub fn toggle(&self, index: usize) {
132 match self.mode {
133 SelectionMode::None => {}
134 SelectionMode::Single => self.select(index),
135 SelectionMode::Multi => {
136 let mut set = self.selection.get();
137 if set.contains(&index) {
138 set.remove(&index);
139 } else {
140 set.insert(index);
141 }
142 self.selection.set(set);
143 self.anchor.set(Some(index));
144 }
145 }
146 }
147
148 pub fn extend_to(&self, index: usize) {
151 match self.mode {
152 SelectionMode::None => {}
153 SelectionMode::Single => self.select(index),
154 SelectionMode::Multi => {
155 let anchor = self.anchor.get().unwrap_or(index);
156 let start = anchor.min(index);
157 let end = anchor.max(index);
158 let mut set = self.selection.get();
159 for i in start..=end {
160 set.insert(i);
161 }
162 self.selection.set(set);
163 }
165 }
166 }
167
168 pub fn select_indices(&self, indices: impl IntoIterator<Item = usize>, additive: bool) {
173 if self.mode == SelectionMode::None {
174 return;
175 }
176 let mut set = if additive {
177 self.selection.get()
178 } else {
179 BTreeSet::new()
180 };
181 set.extend(indices);
182 if self.mode == SelectionMode::Single {
183 let last = set.iter().next_back().copied();
184 set = last.into_iter().collect();
185 }
186 self.selection.set(set);
187 }
188
189 pub fn select_all(&self, count: usize) {
202 if self.mode == SelectionMode::None || self.mode == SelectionMode::Single {
203 return;
204 }
205 let set: BTreeSet<usize> = (0..count).collect();
206 self.selection.set(set);
207 }
208
209 pub fn clear(&self) {
211 self.selection.set(BTreeSet::new());
212 self.anchor.set(None);
213 }
214
215 pub fn adjust_for_insert(&self, start: usize, count: usize) {
218 let old = self.selection.get();
219 let mut new_set = BTreeSet::new();
220 for &idx in &old {
221 if idx >= start {
222 new_set.insert(idx + count);
223 } else {
224 new_set.insert(idx);
225 }
226 }
227 if new_set != old {
228 self.selection.set(new_set);
229 }
230 if let Some(a) = self.anchor.get()
231 && a >= start
232 {
233 self.anchor.set(Some(a + count));
234 }
235 }
236
237 pub fn adjust_for_remove(&self, start: usize, count: usize) {
240 let old = self.selection.get();
241 let end = start + count;
242 let mut new_set = BTreeSet::new();
243 for &idx in &old {
244 if idx < start {
245 new_set.insert(idx);
246 } else if idx >= end {
247 new_set.insert(idx - count);
248 }
249 }
251 if new_set != old {
252 self.selection.set(new_set);
253 }
254 if let Some(a) = self.anchor.get() {
255 if a >= end {
256 self.anchor.set(Some(a - count));
257 } else if a >= start {
258 self.anchor.set(None);
259 }
260 }
261 }
262
263 pub fn adjust_for_move(&self, from: usize, to: usize, count: usize) {
267 if from == to || count == 0 {
268 return;
269 }
270 let old = self.selection.get();
271 let new_set: BTreeSet<usize> = old
272 .iter()
273 .map(|&idx| crate::map_index_after_move(idx, from, to, count))
274 .collect();
275 if new_set != old {
276 self.selection.set(new_set);
277 }
278 if let Some(a) = self.anchor.get() {
279 self.anchor
280 .set(Some(crate::map_index_after_move(a, from, to, count)));
281 }
282 }
283}
284
285impl Clone for SelectionModel {
286 fn clone(&self) -> Self {
287 Self {
288 mode: self.mode,
289 selection: self.selection.clone(),
290 anchor: self.anchor.clone(),
291 #[cfg(debug_assertions)]
292 debug_adapter_holder: self.debug_adapter_holder.clone(),
293 }
294 }
295}
296
297impl SelectionModel {
298 pub fn debug_named(self, _name: impl Into<String>) -> Self {
307 #[cfg(debug_assertions)]
308 {
309 let adapter: Rc<dyn crate::debug_registry::ModelDebug> = Rc::new(SelectionModelDebug {
310 selection: self.selection.clone(),
311 mode: self.mode,
312 });
313 crate::debug_registry::register(_name.into(), Rc::downgrade(&adapter));
314 *self.debug_adapter_holder.borrow_mut() = Some(adapter);
315 }
316 self
317 }
318}
319
320#[cfg(debug_assertions)]
321struct SelectionModelDebug {
322 selection: Signal<BTreeSet<usize>>,
323 mode: SelectionMode,
324}
325
326#[cfg(debug_assertions)]
327impl crate::debug_registry::ModelDebug for SelectionModelDebug {
328 fn kind(&self) -> &'static str {
329 "SelectionModel"
330 }
331 fn len(&self) -> usize {
332 self.selection.get().len()
333 }
334 fn debug_dump(&self, out: &mut dyn std::fmt::Write) {
335 let _ = writeln!(out, "mode = {:?}", self.mode);
336 let sel = self.selection.get();
337 if sel.is_empty() {
338 let _ = writeln!(out, "(empty)");
339 return;
340 }
341 for i in sel.iter() {
342 let _ = writeln!(out, "[{}]", i);
343 }
344 }
345}
346
347impl std::fmt::Debug for SelectionModel {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 f.debug_struct("SelectionModel")
350 .field("mode", &self.mode)
351 .field("selected_count", &self.selection.get().len())
352 .finish()
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn single_select() {
362 let model = SelectionModel::new(SelectionMode::Single);
363 model.select(2);
364 assert!(model.is_selected(2));
365 assert!(!model.is_selected(0));
366 assert_eq!(model.selected_indices(), vec![2]);
367
368 model.select(5);
369 assert!(!model.is_selected(2));
370 assert!(model.is_selected(5));
371 }
372
373 #[test]
374 fn multi_select_toggle() {
375 let model = SelectionModel::new(SelectionMode::Multi);
376 model.toggle(1);
377 model.toggle(3);
378 assert!(model.is_selected(1));
379 assert!(model.is_selected(3));
380 assert_eq!(model.count(), 2);
381
382 model.toggle(1);
383 assert!(!model.is_selected(1));
384 assert!(model.is_selected(3));
385 }
386
387 #[test]
388 fn multi_select_extend_range() {
389 let model = SelectionModel::new(SelectionMode::Multi);
390 model.select(2); model.extend_to(5); assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
393 }
394
395 #[test]
396 fn extend_backwards() {
397 let model = SelectionModel::new(SelectionMode::Multi);
398 model.select(5);
399 model.extend_to(2);
400 assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
401 }
402
403 #[test]
404 fn select_all() {
405 let model = SelectionModel::new(SelectionMode::Multi);
406 model.select_all(5);
407 assert_eq!(model.selected_indices(), vec![0, 1, 2, 3, 4]);
408 }
409
410 #[test]
411 fn select_indices_replaces_then_adds() {
412 let model = SelectionModel::new(SelectionMode::Multi);
413 model.select(1);
414 model.select_indices([4, 5], false);
416 assert_eq!(model.selected_indices(), vec![4, 5]);
417 model.select_indices([2], true);
419 assert_eq!(model.selected_indices(), vec![2, 4, 5]);
420 }
421
422 #[test]
423 fn clear() {
424 let model = SelectionModel::new(SelectionMode::Multi);
425 model.select_all(3);
426 model.clear();
427 assert_eq!(model.count(), 0);
428 }
429
430 #[test]
431 fn none_mode_ignores_all() {
432 let model = SelectionModel::new(SelectionMode::None);
433 model.select(1);
434 assert_eq!(model.count(), 0);
435 model.toggle(2);
436 assert_eq!(model.count(), 0);
437 model.select_all(10);
438 assert_eq!(model.count(), 0);
439 }
440
441 #[test]
442 fn adjust_for_insert() {
443 let model = SelectionModel::new(SelectionMode::Multi);
444 model.toggle(1);
445 model.toggle(3);
446 model.adjust_for_insert(2, 2);
448 assert_eq!(model.selected_indices(), vec![1, 5]);
450 }
451
452 #[test]
453 fn adjust_for_remove() {
454 let model = SelectionModel::new(SelectionMode::Multi);
455 model.toggle(1);
456 model.toggle(3);
457 model.toggle(5);
458 model.adjust_for_remove(3, 1);
460 assert_eq!(model.selected_indices(), vec![1, 4]);
462 }
463
464 #[test]
465 fn adjust_for_move_follows_the_moved_item() {
466 let model = SelectionModel::new(SelectionMode::Multi);
468 model.toggle(0);
469 model.adjust_for_move(0, 2, 1);
470 assert_eq!(model.selected_indices(), vec![2], "selection followed A");
471 }
472
473 #[test]
474 fn adjust_for_move_shifts_a_bystander_selection() {
475 let model = SelectionModel::new(SelectionMode::Multi);
477 model.toggle(1);
478 model.adjust_for_move(0, 2, 1);
479 assert_eq!(model.selected_indices(), vec![0], "B shifted down to 0");
480 }
481
482 #[test]
483 fn adjust_for_move_backwards() {
484 let model = SelectionModel::new(SelectionMode::Multi);
486 model.toggle(3);
487 model.adjust_for_move(3, 1, 1);
488 assert_eq!(model.selected_indices(), vec![1]);
489 }
490
491 #[test]
492 fn signal_reactivity() {
493 use std::cell::Cell;
494 use std::rc::Rc;
495
496 let model = SelectionModel::new(SelectionMode::Single);
497 let signal = model.selection_signal();
498 let changed = Rc::new(Cell::new(false));
499 let c = changed.clone();
500 let _handle = signal.observe(move |_| c.set(true));
501
502 model.select(3);
503 assert!(changed.get());
504 }
505
506 #[test]
507 fn single_mode_extend_acts_as_select() {
508 let model = SelectionModel::new(SelectionMode::Single);
509 model.select(1);
510 model.extend_to(5);
511 assert_eq!(model.selected_indices(), vec![5]);
513 }
514}