use std::collections::HashSet;
use crate::entity::Entity;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct PickModifiers(pub u32);
impl PickModifiers {
pub const NONE: Self = Self(0);
pub const SHIFT: u32 = 1 << 0;
pub const CTRL: u32 = 1 << 1;
pub const ALT: u32 = 1 << 2;
pub const META: u32 = 1 << 3;
pub fn from_bools(shift: bool, ctrl: bool, alt: bool, meta: bool) -> Self {
let mut bits = 0;
if shift {
bits |= Self::SHIFT;
}
if ctrl {
bits |= Self::CTRL;
}
if alt {
bits |= Self::ALT;
}
if meta {
bits |= Self::META;
}
Self(bits)
}
pub fn shift(self) -> bool {
(self.0 & Self::SHIFT) != 0
}
pub fn ctrl(self) -> bool {
(self.0 & Self::CTRL) != 0
}
pub fn alt(self) -> bool {
(self.0 & Self::ALT) != 0
}
pub fn meta(self) -> bool {
(self.0 & Self::META) != 0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PickPoint {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Default, Debug)]
pub struct Selection {
pub entities: HashSet<Entity>,
pub last_pick: Option<PickPoint>,
}
impl Selection {
pub fn new() -> Self {
Self::default()
}
pub fn apply_pick(
&mut self,
entity: Option<Entity>,
point: Option<PickPoint>,
modifiers: PickModifiers,
) {
if let Some(p) = point {
self.last_pick = Some(p);
}
match (entity, modifiers.0) {
(None, 0) => {
self.entities.clear();
}
(None, _) => {
}
(Some(entity), PickModifiers::SHIFT) => {
if !self.entities.insert(entity) {
self.entities.remove(&entity);
}
}
(Some(entity), PickModifiers::CTRL) => {
self.entities.remove(&entity);
}
(Some(entity), _) => {
self.entities.clear();
self.entities.insert(entity);
}
}
}
pub fn apply_pick_rect<I>(&mut self, entities: I, modifiers: PickModifiers)
where
I: IntoIterator<Item = Entity>,
{
match modifiers.0 {
PickModifiers::SHIFT => {
self.entities.extend(entities);
}
PickModifiers::CTRL => {
for e in entities {
self.entities.remove(&e);
}
}
PickModifiers::ALT => {
let new: HashSet<Entity> = entities.into_iter().collect();
self.entities.retain(|e| new.contains(e));
}
_ => {
self.entities.clear();
self.entities.extend(entities);
}
}
}
pub fn contains(&self, entity: Entity) -> bool {
self.entities.contains(&entity)
}
pub fn len(&self) -> usize {
self.entities.len()
}
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn e(index: u32, generation: u32) -> Entity {
Entity::from_raw(index, generation)
}
#[test]
fn modifiers_round_trip_through_bools() {
let m = PickModifiers::from_bools(true, false, true, false);
assert!(m.shift());
assert!(!m.ctrl());
assert!(m.alt());
assert!(!m.meta());
}
#[test]
fn click_replaces_selection_with_no_modifier() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
sel.apply_pick(Some(e(3, 0)), None, PickModifiers::NONE);
assert_eq!(sel.entities.len(), 1);
assert!(sel.contains(e(3, 0)));
}
#[test]
fn click_on_empty_space_clears_with_no_modifier() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.apply_pick(None, None, PickModifiers::NONE);
assert!(sel.is_empty());
}
#[test]
fn click_on_empty_space_with_modifier_is_noop() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.apply_pick(None, None, PickModifiers(PickModifiers::SHIFT));
assert_eq!(sel.entities.len(), 1);
}
#[test]
fn shift_click_toggles_membership() {
let mut sel = Selection::new();
sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::SHIFT));
sel.apply_pick(Some(e(2, 0)), None, PickModifiers(PickModifiers::SHIFT));
assert_eq!(sel.entities.len(), 2);
sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::SHIFT));
assert!(!sel.contains(e(1, 0)));
assert!(sel.contains(e(2, 0)));
}
#[test]
fn ctrl_click_subtracts() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::CTRL));
assert!(!sel.contains(e(1, 0)));
assert!(sel.contains(e(2, 0)));
}
#[test]
fn shift_plus_other_modifier_falls_through_to_replace() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
let bits = PickModifiers::SHIFT | PickModifiers::CTRL;
sel.apply_pick(Some(e(3, 0)), None, PickModifiers(bits));
assert_eq!(sel.entities.len(), 1);
assert!(sel.contains(e(3, 0)));
}
#[test]
fn shift_plus_other_modifier_on_miss_is_noop() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
let bits = PickModifiers::SHIFT | PickModifiers::ALT;
sel.apply_pick(None, None, PickModifiers(bits));
assert_eq!(sel.entities.len(), 1);
assert!(sel.contains(e(1, 0)));
}
#[test]
fn pick_records_last_world_point() {
let mut sel = Selection::new();
let p = PickPoint {
x: 1.0,
y: 2.0,
z: 3.0,
};
sel.apply_pick(Some(e(1, 0)), Some(p), PickModifiers::NONE);
assert_eq!(sel.last_pick, Some(p));
}
#[test]
fn rect_no_modifier_replaces() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers::NONE);
assert!(!sel.contains(e(1, 0)));
assert!(sel.contains(e(2, 0)));
assert!(sel.contains(e(3, 0)));
}
#[test]
fn rect_shift_adds() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers(PickModifiers::SHIFT));
assert_eq!(sel.entities.len(), 3);
}
#[test]
fn rect_ctrl_subtracts() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
sel.entities.insert(e(3, 0));
sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers(PickModifiers::CTRL));
assert_eq!(sel.entities.len(), 1);
assert!(sel.contains(e(1, 0)));
}
#[test]
fn rect_alt_intersects() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
sel.entities.insert(e(3, 0));
sel.apply_pick_rect(
[e(2, 0), e(3, 0), e(4, 0)],
PickModifiers(PickModifiers::ALT),
);
assert_eq!(sel.entities.len(), 2);
assert!(sel.contains(e(2, 0)));
assert!(sel.contains(e(3, 0)));
assert!(!sel.contains(e(1, 0)));
}
#[test]
fn rect_shift_plus_other_modifier_falls_through_to_replace() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
sel.entities.insert(e(2, 0));
let bits = PickModifiers::SHIFT | PickModifiers::CTRL;
sel.apply_pick_rect([e(3, 0), e(4, 0)], PickModifiers(bits));
assert_eq!(sel.entities.len(), 2);
assert!(sel.contains(e(3, 0)));
assert!(sel.contains(e(4, 0)));
assert!(!sel.contains(e(1, 0)));
assert!(!sel.contains(e(2, 0)));
}
#[test]
fn rect_alt_plus_other_modifier_falls_through_to_replace() {
let mut sel = Selection::new();
sel.entities.insert(e(1, 0));
let bits = PickModifiers::CTRL | PickModifiers::ALT;
sel.apply_pick_rect([e(2, 0)], PickModifiers(bits));
assert_eq!(sel.entities.len(), 1);
assert!(sel.contains(e(2, 0)));
}
}