viewport_lib/interaction/
selection.rs1use std::collections::HashSet;
8
9pub type NodeId = u64;
11
12#[derive(Debug, Clone)]
14pub struct Selection {
15 selected: HashSet<NodeId>,
16 primary: Option<NodeId>,
17 version: u64,
20}
21
22impl Default for Selection {
23 fn default() -> Self {
24 Self {
25 selected: HashSet::new(),
26 primary: None,
27 version: 0,
28 }
29 }
30}
31
32impl Selection {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn version(&self) -> u64 {
43 self.version
44 }
45
46 pub fn select_one(&mut self, id: NodeId) {
48 self.selected.clear();
49 self.selected.insert(id);
50 self.primary = Some(id);
51 self.version = self.version.wrapping_add(1);
52 }
53
54 pub fn toggle(&mut self, id: NodeId) {
58 if self.selected.contains(&id) {
59 self.selected.remove(&id);
60 if self.primary == Some(id) {
61 self.primary = self.selected.iter().next().copied();
62 }
63 } else {
64 self.selected.insert(id);
65 self.primary = Some(id);
66 }
67 self.version = self.version.wrapping_add(1);
68 }
69
70 pub fn add(&mut self, id: NodeId) {
72 self.selected.insert(id);
73 self.primary = Some(id);
74 self.version = self.version.wrapping_add(1);
75 }
76
77 pub fn remove(&mut self, id: NodeId) {
79 self.selected.remove(&id);
80 if self.primary == Some(id) {
81 self.primary = self.selected.iter().next().copied();
82 }
83 self.version = self.version.wrapping_add(1);
84 }
85
86 pub fn clear(&mut self) {
88 self.selected.clear();
89 self.primary = None;
90 self.version = self.version.wrapping_add(1);
91 }
92
93 pub fn extend(&mut self, ids: impl IntoIterator<Item = NodeId>) {
95 let mut last = None;
96 for id in ids {
97 self.selected.insert(id);
98 last = Some(id);
99 }
100 if let Some(id) = last {
101 self.primary = Some(id);
102 }
103 self.version = self.version.wrapping_add(1);
104 }
105
106 pub fn select_all(&mut self, ids: impl IntoIterator<Item = NodeId>) {
108 self.selected.clear();
109 self.primary = None;
110 let ids: Vec<NodeId> = ids.into_iter().collect();
113 if ids.is_empty() {
114 self.version = self.version.wrapping_add(1);
115 } else {
116 self.extend(ids);
117 }
119 }
120
121 pub fn contains(&self, id: NodeId) -> bool {
123 self.selected.contains(&id)
124 }
125
126 pub fn primary(&self) -> Option<NodeId> {
128 self.primary
129 }
130
131 pub fn iter(&self) -> impl Iterator<Item = &NodeId> {
133 self.selected.iter()
134 }
135
136 pub fn len(&self) -> usize {
138 self.selected.len()
139 }
140
141 pub fn is_empty(&self) -> bool {
143 self.selected.is_empty()
144 }
145
146 pub fn centroid(
151 &self,
152 position_fn: impl Fn(NodeId) -> Option<glam::Vec3>,
153 ) -> Option<glam::Vec3> {
154 let mut sum = glam::Vec3::ZERO;
155 let mut count = 0u32;
156 for &id in &self.selected {
157 if let Some(pos) = position_fn(id) {
158 sum += pos;
159 count += 1;
160 }
161 }
162 if count > 0 {
163 Some(sum / count as f32)
164 } else {
165 None
166 }
167 }
168
169 pub fn diff(&self, previous: &Selection) -> (Vec<NodeId>, Vec<NodeId>) {
174 let added: Vec<NodeId> = self
175 .selected
176 .difference(&previous.selected)
177 .copied()
178 .collect();
179 let removed: Vec<NodeId> = previous
180 .selected
181 .difference(&self.selected)
182 .copied()
183 .collect();
184 (added, removed)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_select_one_clears_others() {
194 let mut sel = Selection::new();
195 sel.add(1);
196 sel.add(2);
197 sel.select_one(3);
198 assert_eq!(sel.len(), 1);
199 assert!(sel.contains(3));
200 assert!(!sel.contains(1));
201 assert!(!sel.contains(2));
202 }
203
204 #[test]
205 fn test_toggle_adds_and_removes() {
206 let mut sel = Selection::new();
207 sel.toggle(1);
208 assert!(sel.contains(1));
209 sel.toggle(1);
210 assert!(!sel.contains(1));
211 assert!(sel.is_empty());
212 }
213
214 #[test]
215 fn test_add_preserves_existing() {
216 let mut sel = Selection::new();
217 sel.add(1);
218 sel.add(2);
219 assert!(sel.contains(1));
220 assert!(sel.contains(2));
221 assert_eq!(sel.len(), 2);
222 }
223
224 #[test]
225 fn test_clear_empties() {
226 let mut sel = Selection::new();
227 sel.add(1);
228 sel.add(2);
229 sel.clear();
230 assert!(sel.is_empty());
231 assert_eq!(sel.primary(), None);
232 }
233
234 #[test]
235 fn test_primary_tracks_last() {
236 let mut sel = Selection::new();
237 sel.add(1);
238 assert_eq!(sel.primary(), Some(1));
239 sel.add(2);
240 assert_eq!(sel.primary(), Some(2));
241 sel.select_one(3);
242 assert_eq!(sel.primary(), Some(3));
243 }
244
245 #[test]
246 fn test_centroid_computes_average() {
247 let mut sel = Selection::new();
248 sel.add(1);
249 sel.add(2);
250 let centroid = sel.centroid(|id| match id {
251 1 => Some(glam::Vec3::new(0.0, 0.0, 0.0)),
252 2 => Some(glam::Vec3::new(4.0, 0.0, 0.0)),
253 _ => None,
254 });
255 let c = centroid.unwrap();
256 assert!((c.x - 2.0).abs() < 1e-5);
257 assert!((c.y).abs() < 1e-5);
258 }
259
260 #[test]
261 fn test_diff_reports_changes() {
262 let mut prev = Selection::new();
263 prev.add(1);
264 prev.add(2);
265
266 let mut curr = Selection::new();
267 curr.add(2);
268 curr.add(3);
269
270 let (added, removed) = curr.diff(&prev);
271 assert_eq!(added, vec![3]);
272 assert_eq!(removed, vec![1]);
273 }
274}