use std::cell::Cell;
#[cfg(debug_assertions)]
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::rc::Rc;
use teksilo_core::signal::Signal;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionMode {
None,
Single,
Multi,
}
pub struct SelectionModel {
mode: SelectionMode,
selection: Signal<BTreeSet<usize>>,
anchor: Rc<Cell<Option<usize>>>,
#[cfg(debug_assertions)]
debug_adapter_holder: Rc<RefCell<Option<Rc<dyn crate::debug_registry::ModelDebug>>>>,
}
impl SelectionModel {
pub fn new(mode: SelectionMode) -> Self {
Self {
mode,
selection: Signal::new(BTreeSet::new()),
anchor: Rc::new(Cell::new(None)),
#[cfg(debug_assertions)]
debug_adapter_holder: Rc::new(RefCell::new(None)),
}
}
pub fn mode(&self) -> SelectionMode {
self.mode
}
pub fn selection_signal(&self) -> Signal<BTreeSet<usize>> {
self.selection.clone()
}
pub fn is_selected(&self, index: usize) -> bool {
self.selection.get().contains(&index)
}
pub fn selected_indices(&self) -> Vec<usize> {
self.selection.get().into_iter().collect()
}
pub fn count(&self) -> usize {
self.selection.get().len()
}
pub fn select(&self, index: usize) {
if self.mode == SelectionMode::None {
return;
}
let mut set = BTreeSet::new();
set.insert(index);
self.selection.set(set);
self.anchor.set(Some(index));
}
pub fn toggle(&self, index: usize) {
match self.mode {
SelectionMode::None => {}
SelectionMode::Single => self.select(index),
SelectionMode::Multi => {
let mut set = self.selection.get();
if set.contains(&index) {
set.remove(&index);
} else {
set.insert(index);
}
self.selection.set(set);
self.anchor.set(Some(index));
}
}
}
pub fn extend_to(&self, index: usize) {
match self.mode {
SelectionMode::None => {}
SelectionMode::Single => self.select(index),
SelectionMode::Multi => {
let anchor = self.anchor.get().unwrap_or(index);
let start = anchor.min(index);
let end = anchor.max(index);
let mut set = self.selection.get();
for i in start..=end {
set.insert(i);
}
self.selection.set(set);
}
}
}
pub fn select_indices(&self, indices: impl IntoIterator<Item = usize>, additive: bool) {
if self.mode == SelectionMode::None {
return;
}
let mut set = if additive {
self.selection.get()
} else {
BTreeSet::new()
};
set.extend(indices);
if self.mode == SelectionMode::Single {
let last = set.iter().next_back().copied();
set = last.into_iter().collect();
}
self.selection.set(set);
}
pub fn select_all(&self, count: usize) {
if self.mode == SelectionMode::None || self.mode == SelectionMode::Single {
return;
}
let set: BTreeSet<usize> = (0..count).collect();
self.selection.set(set);
}
pub fn clear(&self) {
self.selection.set(BTreeSet::new());
self.anchor.set(None);
}
pub fn adjust_for_insert(&self, start: usize, count: usize) {
let old = self.selection.get();
let mut new_set = BTreeSet::new();
for &idx in &old {
if idx >= start {
new_set.insert(idx + count);
} else {
new_set.insert(idx);
}
}
if new_set != old {
self.selection.set(new_set);
}
if let Some(a) = self.anchor.get()
&& a >= start
{
self.anchor.set(Some(a + count));
}
}
pub fn adjust_for_remove(&self, start: usize, count: usize) {
let old = self.selection.get();
let end = start + count;
let mut new_set = BTreeSet::new();
for &idx in &old {
if idx < start {
new_set.insert(idx);
} else if idx >= end {
new_set.insert(idx - count);
}
}
if new_set != old {
self.selection.set(new_set);
}
if let Some(a) = self.anchor.get() {
if a >= end {
self.anchor.set(Some(a - count));
} else if a >= start {
self.anchor.set(None);
}
}
}
pub fn adjust_for_move(&self, from: usize, to: usize, count: usize) {
if from == to || count == 0 {
return;
}
let old = self.selection.get();
let new_set: BTreeSet<usize> = old
.iter()
.map(|&idx| crate::map_index_after_move(idx, from, to, count))
.collect();
if new_set != old {
self.selection.set(new_set);
}
if let Some(a) = self.anchor.get() {
self.anchor
.set(Some(crate::map_index_after_move(a, from, to, count)));
}
}
}
impl Clone for SelectionModel {
fn clone(&self) -> Self {
Self {
mode: self.mode,
selection: self.selection.clone(),
anchor: self.anchor.clone(),
#[cfg(debug_assertions)]
debug_adapter_holder: self.debug_adapter_holder.clone(),
}
}
}
impl SelectionModel {
pub fn debug_named(self, _name: impl Into<String>) -> Self {
#[cfg(debug_assertions)]
{
let adapter: Rc<dyn crate::debug_registry::ModelDebug> = Rc::new(SelectionModelDebug {
selection: self.selection.clone(),
mode: self.mode,
});
crate::debug_registry::register(_name.into(), Rc::downgrade(&adapter));
*self.debug_adapter_holder.borrow_mut() = Some(adapter);
}
self
}
}
#[cfg(debug_assertions)]
struct SelectionModelDebug {
selection: Signal<BTreeSet<usize>>,
mode: SelectionMode,
}
#[cfg(debug_assertions)]
impl crate::debug_registry::ModelDebug for SelectionModelDebug {
fn kind(&self) -> &'static str {
"SelectionModel"
}
fn len(&self) -> usize {
self.selection.get().len()
}
fn debug_dump(&self, out: &mut dyn std::fmt::Write) {
let _ = writeln!(out, "mode = {:?}", self.mode);
let sel = self.selection.get();
if sel.is_empty() {
let _ = writeln!(out, "(empty)");
return;
}
for i in sel.iter() {
let _ = writeln!(out, "[{}]", i);
}
}
}
impl std::fmt::Debug for SelectionModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SelectionModel")
.field("mode", &self.mode)
.field("selected_count", &self.selection.get().len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_select() {
let model = SelectionModel::new(SelectionMode::Single);
model.select(2);
assert!(model.is_selected(2));
assert!(!model.is_selected(0));
assert_eq!(model.selected_indices(), vec![2]);
model.select(5);
assert!(!model.is_selected(2));
assert!(model.is_selected(5));
}
#[test]
fn multi_select_toggle() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(1);
model.toggle(3);
assert!(model.is_selected(1));
assert!(model.is_selected(3));
assert_eq!(model.count(), 2);
model.toggle(1);
assert!(!model.is_selected(1));
assert!(model.is_selected(3));
}
#[test]
fn multi_select_extend_range() {
let model = SelectionModel::new(SelectionMode::Multi);
model.select(2); model.extend_to(5); assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
}
#[test]
fn extend_backwards() {
let model = SelectionModel::new(SelectionMode::Multi);
model.select(5);
model.extend_to(2);
assert_eq!(model.selected_indices(), vec![2, 3, 4, 5]);
}
#[test]
fn select_all() {
let model = SelectionModel::new(SelectionMode::Multi);
model.select_all(5);
assert_eq!(model.selected_indices(), vec![0, 1, 2, 3, 4]);
}
#[test]
fn select_indices_replaces_then_adds() {
let model = SelectionModel::new(SelectionMode::Multi);
model.select(1);
model.select_indices([4, 5], false);
assert_eq!(model.selected_indices(), vec![4, 5]);
model.select_indices([2], true);
assert_eq!(model.selected_indices(), vec![2, 4, 5]);
}
#[test]
fn clear() {
let model = SelectionModel::new(SelectionMode::Multi);
model.select_all(3);
model.clear();
assert_eq!(model.count(), 0);
}
#[test]
fn none_mode_ignores_all() {
let model = SelectionModel::new(SelectionMode::None);
model.select(1);
assert_eq!(model.count(), 0);
model.toggle(2);
assert_eq!(model.count(), 0);
model.select_all(10);
assert_eq!(model.count(), 0);
}
#[test]
fn adjust_for_insert() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(1);
model.toggle(3);
model.adjust_for_insert(2, 2);
assert_eq!(model.selected_indices(), vec![1, 5]);
}
#[test]
fn adjust_for_remove() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(1);
model.toggle(3);
model.toggle(5);
model.adjust_for_remove(3, 1);
assert_eq!(model.selected_indices(), vec![1, 4]);
}
#[test]
fn adjust_for_move_follows_the_moved_item() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(0);
model.adjust_for_move(0, 2, 1);
assert_eq!(model.selected_indices(), vec![2], "selection followed A");
}
#[test]
fn adjust_for_move_shifts_a_bystander_selection() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(1);
model.adjust_for_move(0, 2, 1);
assert_eq!(model.selected_indices(), vec![0], "B shifted down to 0");
}
#[test]
fn adjust_for_move_backwards() {
let model = SelectionModel::new(SelectionMode::Multi);
model.toggle(3);
model.adjust_for_move(3, 1, 1);
assert_eq!(model.selected_indices(), vec![1]);
}
#[test]
fn signal_reactivity() {
use std::cell::Cell;
use std::rc::Rc;
let model = SelectionModel::new(SelectionMode::Single);
let signal = model.selection_signal();
let changed = Rc::new(Cell::new(false));
let c = changed.clone();
let _handle = signal.observe(move |_| c.set(true));
model.select(3);
assert!(changed.get());
}
#[test]
fn single_mode_extend_acts_as_select() {
let model = SelectionModel::new(SelectionMode::Single);
model.select(1);
model.extend_to(5);
assert_eq!(model.selected_indices(), vec![5]);
}
}