use std::collections::HashSet;
#[derive(Debug, Clone, Default)]
pub struct SelectionService {
selected: HashSet<usize>,
primary: Option<usize>,
multi_select_mode: bool,
}
impl SelectionService {
pub fn new() -> Self {
Self::default()
}
pub fn select(&mut self, id: usize) -> bool {
if self.multi_select_mode {
let changed = self.selected.insert(id);
self.primary = Some(id);
changed
} else {
let was_selected = self.selected.contains(&id) && self.selected.len() == 1;
self.selected.clear();
self.selected.insert(id);
self.primary = Some(id);
!was_selected
}
}
pub fn toggle(&mut self, id: usize) -> bool {
if self.selected.contains(&id) {
self.selected.remove(&id);
if self.primary == Some(id) {
self.primary = self.selected.iter().copied().next();
}
true
} else {
self.selected.insert(id);
self.primary = Some(id);
true
}
}
pub fn deselect(&mut self, id: usize) -> bool {
let removed = self.selected.remove(&id);
if self.primary == Some(id) {
self.primary = self.selected.iter().copied().next();
}
removed
}
pub fn deselect_all(&mut self) -> bool {
let had_selection = !self.selected.is_empty();
self.selected.clear();
self.primary = None;
had_selection
}
#[inline]
pub fn is_selected(&self, id: usize) -> bool {
self.selected.contains(&id)
}
#[inline]
pub fn has_selection(&self) -> bool {
!self.selected.is_empty()
}
#[inline]
pub fn primary(&self) -> Option<usize> {
self.primary
}
#[inline]
pub fn selected_ids(&self) -> impl Iterator<Item = usize> + '_ {
self.selected.iter().copied()
}
#[inline]
pub fn selection_count(&self) -> usize {
self.selected.len()
}
pub fn set_multi_select_mode(&mut self, enabled: bool) {
self.multi_select_mode = enabled;
}
#[inline]
pub fn is_multi_select_mode(&self) -> bool {
self.multi_select_mode
}
pub fn select_all(&mut self, ids: impl IntoIterator<Item = usize>) {
for id in ids {
self.selected.insert(id);
if self.primary.is_none() {
self.primary = Some(id);
}
}
}
pub fn prune(&mut self, existing_ids: &HashSet<usize>) {
self.selected.retain(|id| existing_ids.contains(id));
if let Some(p) = self.primary
&& !existing_ids.contains(&p)
{
self.primary = self.selected.iter().copied().next();
}
}
pub fn single_selection(&self) -> Option<usize> {
if self.selected.len() == 1 {
self.selected.iter().copied().next()
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_select() {
let mut sel = SelectionService::new();
assert!(!sel.has_selection());
assert!(sel.select(1));
assert!(sel.is_selected(1));
assert_eq!(sel.primary(), Some(1));
assert!(sel.select(2));
assert!(!sel.is_selected(1));
assert!(sel.is_selected(2));
assert_eq!(sel.selection_count(), 1);
}
#[test]
fn test_multi_select() {
let mut sel = SelectionService::new();
sel.set_multi_select_mode(true);
sel.select(1);
sel.select(2);
sel.select(3);
assert!(sel.is_selected(1));
assert!(sel.is_selected(2));
assert!(sel.is_selected(3));
assert_eq!(sel.selection_count(), 3);
}
#[test]
fn test_toggle() {
let mut sel = SelectionService::new();
sel.toggle(1);
assert!(sel.is_selected(1));
sel.toggle(1);
assert!(!sel.is_selected(1));
}
#[test]
fn test_deselect_all() {
let mut sel = SelectionService::new();
sel.set_multi_select_mode(true);
sel.select(1);
sel.select(2);
assert!(sel.deselect_all());
assert!(!sel.has_selection());
assert_eq!(sel.primary(), None);
}
#[test]
fn test_prune() {
let mut sel = SelectionService::new();
sel.set_multi_select_mode(true);
sel.select(1);
sel.select(2);
sel.select(3);
let existing: HashSet<usize> = [1, 3].into_iter().collect();
sel.prune(&existing);
assert!(sel.is_selected(1));
assert!(!sel.is_selected(2));
assert!(sel.is_selected(3));
assert_eq!(sel.selection_count(), 2);
}
#[test]
fn test_select_all() {
let mut sel = SelectionService::new();
sel.select_all([1, 2, 3]);
assert_eq!(sel.selection_count(), 3);
assert!(sel.primary().is_some());
}
#[test]
fn test_single_selection_legacy() {
let mut sel = SelectionService::new();
assert_eq!(sel.single_selection(), None);
sel.select(5);
assert_eq!(sel.single_selection(), Some(5));
sel.set_multi_select_mode(true);
sel.select(6);
assert_eq!(sel.single_selection(), None);
}
}