use std::collections::BTreeSet;
use eframe::egui;
use super::drag::Item;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Gesture {
Plain,
Toggle,
Extend,
}
pub fn gesture(how: &egui::Modifiers) -> Gesture {
if how.command {
return Gesture::Toggle;
}
match how.shift {
true => Gesture::Extend,
false => Gesture::Plain,
}
}
#[derive(Default)]
pub struct Selection {
anchor: Option<Item>,
set: BTreeSet<Item>,
}
impl Selection {
pub fn sole(&self) -> Option<Item> {
match self.set.len() {
1 => self.set.iter().copied().next(),
_ => None,
}
}
pub fn holds(&self, item: Item) -> bool {
self.set.contains(&item)
}
pub fn items(&self) -> impl Iterator<Item = Item> + '_ {
self.set.iter().copied()
}
pub fn locals(&self) -> Vec<u64> {
self.items().filter_map(Item::local).collect()
}
pub fn plain(&mut self, item: Item) {
match self.holds(item) {
true => self.toggle(item),
false => self.only(item),
}
}
pub fn only(&mut self, item: Item) {
self.anchor = Some(item);
self.set.clear();
self.set.insert(item);
}
pub fn clear(&mut self) {
self.set.clear();
self.anchor = None;
}
pub fn toggle(&mut self, item: Item) {
if !self.set.remove(&item) {
self.set.insert(item);
}
self.anchor = Some(item);
}
pub fn extend(&mut self, item: Item, list: &[Item]) {
let span = self
.anchor
.and_then(|anchor| Some((list.iter().position(|held| *held == anchor)?, anchor)))
.and_then(|(from, anchor)| {
Some((from, list.iter().position(|held| *held == item)?, anchor))
});
let Some((from, to, anchor)) = span else {
return self.only(item);
};
let (first, last) = (from.min(to), from.max(to));
self.set = list[first..=last].iter().copied().collect();
self.anchor = Some(anchor);
}
pub fn forget(&mut self, item: Item) {
self.set.remove(&item);
if self.anchor == Some(item) {
self.anchor = None;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nord_usb::{Location, ObjectClass};
fn list(n: u64) -> Vec<Item> {
(0..n).map(Item::Local).collect()
}
#[test]
fn a_plain_click_picks_one_row_and_drops_the_rest() {
let mut selection = Selection::default();
selection.plain(Item::Local(1));
selection.toggle(Item::Local(2));
assert_eq!(selection.items().count(), 2);
selection.plain(Item::Local(3));
assert_eq!(selection.sole(), Some(Item::Local(3)));
}
#[test]
fn a_plain_click_on_a_picked_row_lets_go_of_it() {
let mut selection = Selection::default();
selection.plain(Item::Local(1));
selection.plain(Item::Local(1));
assert_eq!(selection.items().count(), 0, "the sole row let go of");
selection.plain(Item::Local(1));
selection.toggle(Item::Local(2));
selection.plain(Item::Local(2));
assert_eq!(
selection.items().collect::<Vec<_>>(),
vec![Item::Local(1)],
"and one of a set leaves the rest picked"
);
}
#[test]
fn clearing_lets_go_of_the_anchor_too() {
let rows = list(4);
let mut selection = Selection::default();
selection.plain(rows[0]);
selection.clear();
assert_eq!(selection.items().count(), 0);
selection.extend(rows[2], &rows);
assert_eq!(selection.sole(), Some(rows[2]));
}
#[test]
fn a_command_click_puts_a_row_in_or_takes_it_out() {
let mut selection = Selection::default();
selection.only(Item::Local(1));
selection.toggle(Item::Local(2));
assert!(selection.holds(Item::Local(1)) && selection.holds(Item::Local(2)));
assert_eq!(selection.sole(), None, "two rows have no sole row");
selection.toggle(Item::Local(2));
assert_eq!(selection.sole(), Some(Item::Local(1)));
selection.toggle(Item::Local(1));
assert_eq!(
selection.items().count(),
0,
"a row goes out of a set of one, leaving nothing picked"
);
}
#[test]
fn a_shift_click_fills_the_run_from_the_anchor() {
let rows = list(6);
let mut selection = Selection::default();
selection.only(rows[3]);
selection.extend(rows[1], &rows);
assert_eq!(
selection.items().collect::<Vec<_>>(),
vec![rows[1], rows[2], rows[3]]
);
selection.extend(rows[5], &rows);
assert_eq!(
selection.items().collect::<Vec<_>>(),
vec![rows[3], rows[4], rows[5]],
"still anchored at 3, so this one runs the other way from there"
);
}
#[test]
fn a_shift_click_into_another_list_picks_one_row() {
let elsewhere = Item::Slot {
class: ObjectClass::Program,
at: Location { bank: 6, slot: 0 },
};
let rows = list(4);
let mut selection = Selection::default();
selection.only(elsewhere);
selection.extend(rows[2], &rows);
assert_eq!(selection.sole(), Some(rows[2]));
selection.extend(rows[0], &rows);
assert_eq!(
selection.items().count(),
3,
"it re-anchored there, so a second one spans"
);
}
#[test]
fn the_modifiers_decide_which_of_the_three_gestures_a_click_is() {
let with = |command, shift| {
gesture(&egui::Modifiers {
command,
shift,
..egui::Modifiers::NONE
})
};
assert_eq!(with(false, false), Gesture::Plain);
assert_eq!(with(true, false), Gesture::Toggle);
assert_eq!(with(false, true), Gesture::Extend);
assert_eq!(with(true, true), Gesture::Toggle);
}
#[test]
fn a_selection_is_walked_in_one_order_however_it_was_picked() {
let at = |bank, slot| Item::Slot {
class: ObjectClass::Program,
at: Location { bank, slot },
};
let mut selection = Selection::default();
for item in [at(7, 2), Item::Local(9), at(6, 3), Item::Folder(4)] {
selection.toggle(item);
}
assert_eq!(
selection.items().collect::<Vec<_>>(),
vec![Item::Local(9), Item::Folder(4), at(6, 3), at(7, 2)]
);
assert_eq!(selection.locals(), vec![9]);
}
}