use std::collections::HashSet;
#[derive(Clone, Debug, Default)]
pub struct SelectionState {
pub selected: HashSet<u64>,
pub anchor: Option<u64>,
pub cursor: Option<u64>,
pub dragging: bool,
}
impl SelectionState {
pub fn clear(&mut self) {
self.selected.clear();
self.anchor = None;
self.cursor = None;
self.dragging = false;
}
pub fn select_all(&mut self, total_rows: u64) {
self.selected = (0..total_rows).collect();
self.anchor = Some(0);
self.cursor = total_rows.checked_sub(1);
}
pub fn is_selected(&self, row: u64) -> bool {
self.selected.contains(&row)
}
pub fn count(&self) -> usize {
self.selected.len()
}
pub fn on_pointer_down(&mut self, row: u64, ctrl: bool, shift: bool, total_rows: u64) {
if ctrl {
if !self.selected.remove(&row) {
self.selected.insert(row);
}
self.cursor = Some(row);
} else if shift {
let from = self.anchor.or(self.cursor).unwrap_or(row);
let lo = from.min(row);
let hi = from.max(row).min(total_rows.saturating_sub(1));
self.selected.clear();
for i in lo..=hi {
self.selected.insert(i);
}
self.cursor = Some(row);
} else {
self.selected.clear();
self.selected.insert(row);
self.anchor = Some(row);
self.cursor = Some(row);
self.dragging = true;
}
}
pub fn on_pointer_enter_drag(&mut self, row: u64, total_rows: u64) {
if !self.dragging {
return;
}
let anchor = self.anchor.or(self.cursor).unwrap_or(row);
let lo = anchor.min(row);
let hi = anchor.max(row).min(total_rows.saturating_sub(1));
self.selected.clear();
for i in lo..=hi {
self.selected.insert(i);
}
self.cursor = Some(row);
}
pub fn on_pointer_up(&mut self) {
self.dragging = false;
}
pub fn on_context_menu(&mut self, row: u64) {
if !self.selected.contains(&row) {
self.selected.clear();
self.selected.insert(row);
self.anchor = Some(row);
self.cursor = Some(row);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_click() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
assert!(s.is_selected(5));
assert_eq!(s.count(), 1);
assert_eq!(s.anchor, Some(5));
}
#[test]
fn ctrl_click_toggle() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
s.on_pointer_up();
s.on_pointer_down(10, true, false, 100);
assert!(s.is_selected(5));
assert!(s.is_selected(10));
assert_eq!(s.count(), 2);
s.on_pointer_down(5, true, false, 100);
assert!(!s.is_selected(5));
assert_eq!(s.count(), 1);
}
#[test]
fn shift_click_range() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
s.on_pointer_up();
s.on_pointer_down(10, false, true, 100);
assert_eq!(s.count(), 6); for i in 5..=10 {
assert!(s.is_selected(i));
}
}
#[test]
fn drag_select() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
s.on_pointer_enter_drag(8, 100);
assert_eq!(s.count(), 4); s.on_pointer_up();
assert!(!s.dragging);
}
#[test]
fn select_all() {
let mut s = SelectionState::default();
s.select_all(5);
assert_eq!(s.count(), 5);
assert_eq!(s.anchor, Some(0));
assert_eq!(s.cursor, Some(4));
}
#[test]
fn context_menu_unselected_drops() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
s.on_pointer_up();
s.on_pointer_down(10, true, false, 100);
assert_eq!(s.count(), 2);
s.on_context_menu(20);
assert_eq!(s.count(), 1);
assert!(s.is_selected(20));
}
#[test]
fn context_menu_selected_keeps() {
let mut s = SelectionState::default();
s.on_pointer_down(5, false, false, 100);
s.on_pointer_up();
s.on_pointer_down(10, true, false, 100);
assert_eq!(s.count(), 2);
s.on_context_menu(5);
assert_eq!(s.count(), 2);
}
#[test]
fn clear() {
let mut s = SelectionState::default();
s.select_all(100);
s.clear();
assert_eq!(s.count(), 0);
assert!(s.anchor.is_none());
assert!(s.cursor.is_none());
}
}