teksilo_data/
keyed_selection_model.rs1use std::cell::RefCell;
42use std::collections::HashSet;
43use std::rc::Rc;
44
45use teksilo_core::signal::Signal;
46
47use crate::dnd_types::ItemKey;
48use crate::selection_model::SelectionMode;
49
50pub struct KeyedSelectionModel<K: ItemKey> {
58 mode: SelectionMode,
59 selection: Signal<HashSet<K>>,
60 anchor: Rc<RefCell<Option<K>>>,
61 #[cfg(debug_assertions)]
64 debug_adapter_holder: Rc<RefCell<Option<Rc<dyn crate::debug_registry::ModelDebug>>>>,
65}
66
67impl<K: ItemKey> KeyedSelectionModel<K> {
68 pub fn new(mode: SelectionMode) -> Self {
70 Self {
71 mode,
72 selection: Signal::new(HashSet::new()),
73 anchor: Rc::new(RefCell::new(None)),
74 #[cfg(debug_assertions)]
75 debug_adapter_holder: Rc::new(RefCell::new(None)),
76 }
77 }
78
79 pub fn mode(&self) -> SelectionMode {
81 self.mode
82 }
83
84 pub fn selection_signal(&self) -> Signal<HashSet<K>> {
86 self.selection.clone()
87 }
88
89 pub fn is_selected(&self, key: &K) -> bool {
91 self.selection.get().contains(key)
92 }
93
94 pub fn selected_keys(&self) -> Vec<K> {
96 self.selection.get().into_iter().collect()
97 }
98
99 pub fn count(&self) -> usize {
101 self.selection.get().len()
102 }
103
104 pub fn select(&self, key: K) {
106 if self.mode == SelectionMode::None {
107 return;
108 }
109 let mut set = HashSet::new();
110 set.insert(key.clone());
111 self.selection.set(set);
112 *self.anchor.borrow_mut() = Some(key);
113 }
114
115 pub fn toggle(&self, key: K) {
117 match self.mode {
118 SelectionMode::None => {}
119 SelectionMode::Single => self.select(key),
120 SelectionMode::Multi => {
121 let mut set = self.selection.get();
122 if set.contains(&key) {
123 set.remove(&key);
124 } else {
125 set.insert(key.clone());
126 }
127 self.selection.set(set);
128 *self.anchor.borrow_mut() = Some(key);
129 }
130 }
131 }
132
133 pub fn extend_to(&self, target: K, ordered_keys: &[K]) {
138 match self.mode {
139 SelectionMode::None => {}
140 SelectionMode::Single => self.select(target),
141 SelectionMode::Multi => {
142 let anchor = self.anchor.borrow().clone();
143 let Some(anchor) = anchor else {
144 self.select(target);
145 return;
146 };
147 let a = ordered_keys.iter().position(|k| *k == anchor);
148 let t = ordered_keys.iter().position(|k| *k == target);
149 match (a, t) {
150 (Some(a), Some(t)) => {
151 let (lo, hi) = (a.min(t), a.max(t));
152 let mut set = self.selection.get();
153 for k in &ordered_keys[lo..=hi] {
154 set.insert(k.clone());
155 }
156 self.selection.set(set);
157 }
159 _ => self.select(target),
160 }
161 }
162 }
163 }
164
165 pub fn select_keys(&self, keys: impl IntoIterator<Item = K>, additive: bool) {
168 if self.mode == SelectionMode::None {
169 return;
170 }
171 let mut set = if additive {
172 self.selection.get()
173 } else {
174 HashSet::new()
175 };
176 set.extend(keys);
177 if self.mode == SelectionMode::Single && set.len() > 1 {
178 let keep = set.iter().next().cloned();
179 set = keep.into_iter().collect();
180 }
181 self.selection.set(set);
182 }
183
184 pub fn clear(&self) {
186 self.selection.set(HashSet::new());
187 *self.anchor.borrow_mut() = None;
188 }
189
190 pub fn prune_missing(&self, exists: impl Fn(&K) -> bool) {
195 let old = self.selection.get();
196 let new: HashSet<K> = old.iter().filter(|k| exists(k)).cloned().collect();
197 if new.len() != old.len() {
198 self.selection.set(new);
199 }
200 let drop_anchor = self.anchor.borrow().as_ref().is_some_and(|a| !exists(a));
201 if drop_anchor {
202 *self.anchor.borrow_mut() = None;
203 }
204 }
205}
206
207impl<K: ItemKey> Clone for KeyedSelectionModel<K> {
208 fn clone(&self) -> Self {
209 Self {
210 mode: self.mode,
211 selection: self.selection.clone(),
212 anchor: self.anchor.clone(),
213 #[cfg(debug_assertions)]
214 debug_adapter_holder: self.debug_adapter_holder.clone(),
215 }
216 }
217}
218
219impl<K: ItemKey> KeyedSelectionModel<K> {
220 pub fn debug_named(self, _name: impl Into<String>) -> Self {
223 #[cfg(debug_assertions)]
224 {
225 let adapter: Rc<dyn crate::debug_registry::ModelDebug> =
226 Rc::new(KeyedSelectionModelDebug {
227 selection: self.selection.clone(),
228 mode: self.mode,
229 });
230 crate::debug_registry::register(_name.into(), Rc::downgrade(&adapter));
231 *self.debug_adapter_holder.borrow_mut() = Some(adapter);
232 }
233 self
234 }
235}
236
237#[cfg(debug_assertions)]
238struct KeyedSelectionModelDebug<K: ItemKey> {
239 selection: Signal<HashSet<K>>,
240 mode: SelectionMode,
241}
242
243#[cfg(debug_assertions)]
244impl<K: ItemKey> crate::debug_registry::ModelDebug for KeyedSelectionModelDebug<K> {
245 fn kind(&self) -> &'static str {
246 "KeyedSelectionModel"
247 }
248 fn len(&self) -> usize {
249 self.selection.get().len()
250 }
251 fn debug_dump(&self, out: &mut dyn std::fmt::Write) {
252 let _ = writeln!(out, "mode = {:?}", self.mode);
253 let sel = self.selection.get();
254 if sel.is_empty() {
255 let _ = writeln!(out, "(empty)");
256 return;
257 }
258 for k in sel.iter() {
259 let _ = writeln!(out, "{:?}", k);
260 }
261 }
262}
263
264impl<K: ItemKey> std::fmt::Debug for KeyedSelectionModel<K> {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 f.debug_struct("KeyedSelectionModel")
267 .field("mode", &self.mode)
268 .field("selected_count", &self.selection.get().len())
269 .finish()
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn single_select_by_key() {
279 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Single);
280 m.select(10);
281 assert!(m.is_selected(&10));
282 m.select(20);
283 assert!(!m.is_selected(&10));
284 assert!(m.is_selected(&20));
285 }
286
287 #[test]
288 fn multi_toggle_and_count() {
289 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Multi);
290 m.toggle(1);
291 m.toggle(3);
292 assert_eq!(m.count(), 2);
293 m.toggle(1);
294 assert!(!m.is_selected(&1));
295 assert!(m.is_selected(&3));
296 }
297
298 #[test]
299 fn extend_to_over_visible_order() {
300 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Multi);
301 let order = vec![10_u64, 20, 30, 40, 50];
302 m.select(20); m.extend_to(40, &order);
304 let mut got = m.selected_keys();
305 got.sort();
306 assert_eq!(got, vec![20, 30, 40]);
307 }
308
309 #[test]
310 fn selection_survives_reorder_of_visible_order() {
311 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Multi);
314 m.toggle(30);
315 m.toggle(10);
316 assert!(m.is_selected(&10));
318 assert!(m.is_selected(&30));
319 assert!(!m.is_selected(&20));
320 }
321
322 #[test]
323 fn prune_missing_drops_deleted_keys_and_anchor() {
324 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Multi);
325 m.toggle(1);
326 m.toggle(2);
327 m.toggle(3); let live: HashSet<u64> = [1_u64, 4, 5].into_iter().collect();
330 m.prune_missing(|k| live.contains(k));
331 assert!(m.is_selected(&1));
332 assert!(!m.is_selected(&2));
333 assert!(!m.is_selected(&3));
334 m.extend_to(5, &[1, 4, 5]);
336 assert!(m.is_selected(&5));
337 }
338
339 #[test]
340 fn anchor_not_visible_falls_back_to_single() {
341 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::Multi);
342 m.select(99); m.extend_to(20, &[10, 20, 30]);
344 assert_eq!(m.selected_keys(), vec![20]);
346 }
347
348 #[test]
349 fn none_mode_ignores() {
350 let m: KeyedSelectionModel<u64> = KeyedSelectionModel::new(SelectionMode::None);
351 m.select(1);
352 m.toggle(2);
353 assert_eq!(m.count(), 0);
354 }
355}