1use std::collections::HashSet;
17
18use crate::entity::Entity;
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
25pub struct PickModifiers(pub u32);
26
27impl PickModifiers {
28 pub const NONE: Self = Self(0);
29 pub const SHIFT: u32 = 1 << 0;
30 pub const CTRL: u32 = 1 << 1;
31 pub const ALT: u32 = 1 << 2;
32 pub const META: u32 = 1 << 3;
33
34 pub fn from_bools(shift: bool, ctrl: bool, alt: bool, meta: bool) -> Self {
36 let mut bits = 0;
37 if shift {
38 bits |= Self::SHIFT;
39 }
40 if ctrl {
41 bits |= Self::CTRL;
42 }
43 if alt {
44 bits |= Self::ALT;
45 }
46 if meta {
47 bits |= Self::META;
48 }
49 Self(bits)
50 }
51
52 pub fn shift(self) -> bool {
53 (self.0 & Self::SHIFT) != 0
54 }
55 pub fn ctrl(self) -> bool {
56 (self.0 & Self::CTRL) != 0
57 }
58 pub fn alt(self) -> bool {
59 (self.0 & Self::ALT) != 0
60 }
61 pub fn meta(self) -> bool {
62 (self.0 & Self::META) != 0
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
68pub struct PickPoint {
69 pub x: f32,
70 pub y: f32,
71 pub z: f32,
72}
73
74#[derive(Default, Debug)]
81pub struct Selection {
82 pub entities: HashSet<Entity>,
84 pub last_pick: Option<PickPoint>,
86}
87
88impl Selection {
89 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn apply_pick(
105 &mut self,
106 entity: Option<Entity>,
107 point: Option<PickPoint>,
108 modifiers: PickModifiers,
109 ) {
110 if let Some(p) = point {
111 self.last_pick = Some(p);
112 }
113 match (entity, modifiers.0) {
118 (None, 0) => {
119 self.entities.clear();
120 }
121 (None, _) => {
122 }
124 (Some(entity), PickModifiers::SHIFT) => {
125 if !self.entities.insert(entity) {
126 self.entities.remove(&entity);
127 }
128 }
129 (Some(entity), PickModifiers::CTRL) => {
130 self.entities.remove(&entity);
131 }
132 (Some(entity), _) => {
133 self.entities.clear();
134 self.entities.insert(entity);
135 }
136 }
137 }
138
139 pub fn apply_pick_rect<I>(&mut self, entities: I, modifiers: PickModifiers)
155 where
156 I: IntoIterator<Item = Entity>,
157 {
158 match modifiers.0 {
159 PickModifiers::SHIFT => {
160 self.entities.extend(entities);
161 }
162 PickModifiers::CTRL => {
163 for e in entities {
164 self.entities.remove(&e);
165 }
166 }
167 PickModifiers::ALT => {
168 let new: HashSet<Entity> = entities.into_iter().collect();
169 self.entities.retain(|e| new.contains(e));
170 }
171 _ => {
172 self.entities.clear();
173 self.entities.extend(entities);
174 }
175 }
176 }
177
178 pub fn contains(&self, entity: Entity) -> bool {
180 self.entities.contains(&entity)
181 }
182
183 pub fn len(&self) -> usize {
185 self.entities.len()
186 }
187
188 pub fn is_empty(&self) -> bool {
190 self.entities.is_empty()
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 fn e(index: u32, generation: u32) -> Entity {
199 Entity::from_raw(index, generation)
200 }
201
202 #[test]
203 fn modifiers_round_trip_through_bools() {
204 let m = PickModifiers::from_bools(true, false, true, false);
205 assert!(m.shift());
206 assert!(!m.ctrl());
207 assert!(m.alt());
208 assert!(!m.meta());
209 }
210
211 #[test]
212 fn click_replaces_selection_with_no_modifier() {
213 let mut sel = Selection::new();
214 sel.entities.insert(e(1, 0));
215 sel.entities.insert(e(2, 0));
216 sel.apply_pick(Some(e(3, 0)), None, PickModifiers::NONE);
217 assert_eq!(sel.entities.len(), 1);
218 assert!(sel.contains(e(3, 0)));
219 }
220
221 #[test]
222 fn click_on_empty_space_clears_with_no_modifier() {
223 let mut sel = Selection::new();
224 sel.entities.insert(e(1, 0));
225 sel.apply_pick(None, None, PickModifiers::NONE);
226 assert!(sel.is_empty());
227 }
228
229 #[test]
230 fn click_on_empty_space_with_modifier_is_noop() {
231 let mut sel = Selection::new();
232 sel.entities.insert(e(1, 0));
233 sel.apply_pick(None, None, PickModifiers(PickModifiers::SHIFT));
234 assert_eq!(sel.entities.len(), 1);
235 }
236
237 #[test]
238 fn shift_click_toggles_membership() {
239 let mut sel = Selection::new();
240 sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::SHIFT));
241 sel.apply_pick(Some(e(2, 0)), None, PickModifiers(PickModifiers::SHIFT));
242 assert_eq!(sel.entities.len(), 2);
243
244 sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::SHIFT));
245 assert!(!sel.contains(e(1, 0)));
246 assert!(sel.contains(e(2, 0)));
247 }
248
249 #[test]
250 fn ctrl_click_subtracts() {
251 let mut sel = Selection::new();
252 sel.entities.insert(e(1, 0));
253 sel.entities.insert(e(2, 0));
254 sel.apply_pick(Some(e(1, 0)), None, PickModifiers(PickModifiers::CTRL));
255 assert!(!sel.contains(e(1, 0)));
256 assert!(sel.contains(e(2, 0)));
257 }
258
259 #[test]
260 fn shift_plus_other_modifier_falls_through_to_replace() {
261 let mut sel = Selection::new();
262 sel.entities.insert(e(1, 0));
263 sel.entities.insert(e(2, 0));
264 let bits = PickModifiers::SHIFT | PickModifiers::CTRL;
267 sel.apply_pick(Some(e(3, 0)), None, PickModifiers(bits));
268 assert_eq!(sel.entities.len(), 1);
269 assert!(sel.contains(e(3, 0)));
270 }
271
272 #[test]
273 fn shift_plus_other_modifier_on_miss_is_noop() {
274 let mut sel = Selection::new();
275 sel.entities.insert(e(1, 0));
276 let bits = PickModifiers::SHIFT | PickModifiers::ALT;
277 sel.apply_pick(None, None, PickModifiers(bits));
278 assert_eq!(sel.entities.len(), 1);
279 assert!(sel.contains(e(1, 0)));
280 }
281
282 #[test]
283 fn pick_records_last_world_point() {
284 let mut sel = Selection::new();
285 let p = PickPoint {
286 x: 1.0,
287 y: 2.0,
288 z: 3.0,
289 };
290 sel.apply_pick(Some(e(1, 0)), Some(p), PickModifiers::NONE);
291 assert_eq!(sel.last_pick, Some(p));
292 }
293
294 #[test]
295 fn rect_no_modifier_replaces() {
296 let mut sel = Selection::new();
297 sel.entities.insert(e(1, 0));
298 sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers::NONE);
299 assert!(!sel.contains(e(1, 0)));
300 assert!(sel.contains(e(2, 0)));
301 assert!(sel.contains(e(3, 0)));
302 }
303
304 #[test]
305 fn rect_shift_adds() {
306 let mut sel = Selection::new();
307 sel.entities.insert(e(1, 0));
308 sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers(PickModifiers::SHIFT));
309 assert_eq!(sel.entities.len(), 3);
310 }
311
312 #[test]
313 fn rect_ctrl_subtracts() {
314 let mut sel = Selection::new();
315 sel.entities.insert(e(1, 0));
316 sel.entities.insert(e(2, 0));
317 sel.entities.insert(e(3, 0));
318 sel.apply_pick_rect([e(2, 0), e(3, 0)], PickModifiers(PickModifiers::CTRL));
319 assert_eq!(sel.entities.len(), 1);
320 assert!(sel.contains(e(1, 0)));
321 }
322
323 #[test]
324 fn rect_alt_intersects() {
325 let mut sel = Selection::new();
326 sel.entities.insert(e(1, 0));
327 sel.entities.insert(e(2, 0));
328 sel.entities.insert(e(3, 0));
329 sel.apply_pick_rect(
330 [e(2, 0), e(3, 0), e(4, 0)],
331 PickModifiers(PickModifiers::ALT),
332 );
333 assert_eq!(sel.entities.len(), 2);
334 assert!(sel.contains(e(2, 0)));
335 assert!(sel.contains(e(3, 0)));
336 assert!(!sel.contains(e(1, 0)));
337 }
338
339 #[test]
340 fn rect_shift_plus_other_modifier_falls_through_to_replace() {
341 let mut sel = Selection::new();
342 sel.entities.insert(e(1, 0));
343 sel.entities.insert(e(2, 0));
344 let bits = PickModifiers::SHIFT | PickModifiers::CTRL;
348 sel.apply_pick_rect([e(3, 0), e(4, 0)], PickModifiers(bits));
349 assert_eq!(sel.entities.len(), 2);
350 assert!(sel.contains(e(3, 0)));
351 assert!(sel.contains(e(4, 0)));
352 assert!(!sel.contains(e(1, 0)));
353 assert!(!sel.contains(e(2, 0)));
354 }
355
356 #[test]
357 fn rect_alt_plus_other_modifier_falls_through_to_replace() {
358 let mut sel = Selection::new();
359 sel.entities.insert(e(1, 0));
360 let bits = PickModifiers::CTRL | PickModifiers::ALT;
363 sel.apply_pick_rect([e(2, 0)], PickModifiers(bits));
364 assert_eq!(sel.entities.len(), 1);
365 assert!(sel.contains(e(2, 0)));
366 }
367}