use crate::canvas::shapes::Shapes;
use egui::emath::TSTransform;
use egui::{Color32, Modifiers, Pos2, Rect, Stroke, StrokeKind};
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum BoxIntent {
Replace,
Add,
Subtract,
}
impl BoxIntent {
pub(crate) fn from_modifiers(modifiers: Modifiers) -> Self {
if modifiers.alt {
Self::Subtract
} else if modifiers.shift || modifiers.command {
Self::Add
} else {
Self::Replace
}
}
fn combine<K: Ord + Clone>(
self,
base: &BTreeSet<K>,
covered: impl IntoIterator<Item = K>,
) -> BTreeSet<K> {
let mut selection = match self {
Self::Replace => BTreeSet::new(),
Self::Add | Self::Subtract => base.clone(),
};
for key in covered {
match self {
Self::Subtract => selection.remove(&key),
Self::Replace | Self::Add => selection.insert(key),
};
}
selection
}
}
#[derive(Debug, Clone)]
pub(crate) struct Band<K> {
pub(crate) start: Pos2,
pub(crate) current: Pos2,
base: BTreeSet<K>,
intent: BoxIntent,
}
impl<K: Ord + Clone> Band<K> {
pub(crate) fn start(at: Pos2, selection: &mut BTreeSet<K>, modifiers: Modifiers) -> Self {
let intent = BoxIntent::from_modifiers(modifiers);
let base = selection.clone();
if intent == BoxIntent::Replace {
selection.clear();
}
Self {
start: at,
current: at,
base,
intent,
}
}
pub(crate) fn rect(&self) -> Rect {
Rect::from_two_pos(self.start, self.current)
}
pub(crate) fn selection(&self, covered: impl IntoIterator<Item = K>) -> BTreeSet<K> {
self.intent.combine(&self.base, covered)
}
}
pub(crate) fn draw_band(overlay: &mut Shapes, scene_transform: &TSTransform, band: Rect) {
overlay.rect(
Rect::from_min_max(*scene_transform * band.min, *scene_transform * band.max),
0.0,
Color32::from_rgba_unmultiplied(100, 150, 255, 30),
Stroke::new(1.5, Color32::from_rgba_unmultiplied(100, 150, 255, 200)),
StrokeKind::Outside,
);
}
#[cfg(test)]
mod tests {
use super::*;
fn set(keys: &[usize]) -> BTreeSet<usize> {
keys.iter().copied().collect()
}
#[test]
fn a_band_replaces_adds_to_or_subtracts_from_its_base() {
let base = set(&[1, 2]);
assert_eq!(BoxIntent::Replace.combine(&base, [3]), set(&[3]));
assert_eq!(BoxIntent::Add.combine(&base, [3]), set(&[1, 2, 3]));
assert_eq!(BoxIntent::Subtract.combine(&base, [2]), set(&[1]));
}
}