1use std::cmp::Ordering;
15use std::collections::BTreeSet;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum SortDir {
20 Asc,
21 Desc,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum SelectionMode {
27 #[default]
29 None,
30 Single,
32 Multi,
34}
35
36pub fn cycle_sort(current: Option<(usize, SortDir)>, column: usize) -> Option<(usize, SortDir)> {
39 match current {
40 Some((col, SortDir::Asc)) if col == column => Some((column, SortDir::Desc)),
41 Some((col, SortDir::Desc)) if col == column => None,
42 _ => Some((column, SortDir::Asc)),
43 }
44}
45
46pub fn identity_order(len: usize) -> Vec<usize> {
48 (0..len).collect()
49}
50
51pub fn sorted_order<T>(rows: &[T], dir: SortDir, cmp: &dyn Fn(&T, &T) -> Ordering) -> Vec<usize> {
55 let mut order = identity_order(rows.len());
56 order.sort_by(|&a, &b| match dir {
57 SortDir::Asc => cmp(&rows[a], &rows[b]),
58 SortDir::Desc => cmp(&rows[b], &rows[a]),
59 });
60 order
61}
62
63fn position_of(order: &[usize], source: usize) -> Option<usize> {
65 order.iter().position(|&s| s == source)
66}
67
68#[derive(Debug, Clone, Default)]
72pub struct SelectionState {
73 selected: BTreeSet<usize>,
74 anchor: Option<usize>,
75 cursor: Option<usize>,
76}
77
78impl SelectionState {
79 pub fn selected(&self) -> Vec<usize> {
81 self.selected.iter().copied().collect()
82 }
83
84 pub fn is_selected(&self, source: usize) -> bool {
85 self.selected.contains(&source)
86 }
87
88 pub fn cursor(&self) -> Option<usize> {
90 self.cursor
91 }
92
93 pub fn clear(&mut self) -> bool {
95 self.anchor = None;
96 self.cursor = None;
97 if self.selected.is_empty() {
98 return false;
99 }
100 self.selected.clear();
101 true
102 }
103
104 pub fn retain_below(&mut self, len: usize) -> bool {
107 let before = self.selected.len();
108 self.selected.retain(|&s| s < len);
109 self.anchor = self.anchor.filter(|&s| s < len);
110 self.cursor = self.cursor.filter(|&s| s < len);
111 self.selected.len() != before
112 }
113
114 pub fn click(
117 &mut self,
118 mode: SelectionMode,
119 order: &[usize],
120 display: usize,
121 toggle: bool,
122 range: bool,
123 ) {
124 let Some(&source) = order.get(display) else {
125 return;
126 };
127 match mode {
128 SelectionMode::None => {}
129 SelectionMode::Single => {
130 if toggle && self.selected.contains(&source) {
131 self.selected.clear();
132 } else {
133 self.selected.clear();
134 self.selected.insert(source);
135 }
136 self.anchor = Some(source);
137 self.cursor = Some(source);
138 }
139 SelectionMode::Multi => {
140 if range {
141 let from = match self.anchor.and_then(|a| position_of(order, a)) {
142 Some(pos) => pos,
143 None => {
146 self.anchor = Some(source);
147 display
148 }
149 };
150 self.select_span(order, from, display);
151 } else if toggle {
152 if !self.selected.remove(&source) {
153 self.selected.insert(source);
154 }
155 self.anchor = Some(source);
156 } else {
157 self.selected.clear();
158 self.selected.insert(source);
159 self.anchor = Some(source);
160 }
161 self.cursor = Some(source);
162 }
163 }
164 }
165
166 pub fn step(
171 &mut self,
172 mode: SelectionMode,
173 order: &[usize],
174 delta: isize,
175 extend: bool,
176 ) -> Option<usize> {
177 if matches!(mode, SelectionMode::None) || order.is_empty() {
178 return None;
179 }
180 let last = order.len() - 1;
181 let display = match self.cursor.and_then(|c| position_of(order, c)) {
182 Some(pos) => (pos as isize + delta).clamp(0, last as isize) as usize,
183 None if delta < 0 => last,
185 None => 0,
186 };
187 let source = order[display];
188 if extend && matches!(mode, SelectionMode::Multi) {
189 let from = match self.anchor.and_then(|a| position_of(order, a)) {
190 Some(pos) => pos,
191 None => {
194 self.anchor = Some(source);
195 display
196 }
197 };
198 self.select_span(order, from, display);
199 } else {
200 self.selected.clear();
201 self.selected.insert(source);
202 self.anchor = Some(source);
203 }
204 self.cursor = Some(source);
205 Some(display)
206 }
207
208 fn select_span(&mut self, order: &[usize], a: usize, b: usize) {
210 let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
211 self.selected.clear();
212 self.selected.extend(order[lo..=hi].iter().copied());
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn sort_cycles_asc_desc_none() {
222 let s1 = cycle_sort(None, 2);
223 assert_eq!(s1, Some((2, SortDir::Asc)));
224 let s2 = cycle_sort(s1, 2);
225 assert_eq!(s2, Some((2, SortDir::Desc)));
226 assert_eq!(cycle_sort(s2, 2), None);
227 }
228
229 #[test]
230 fn sort_switching_column_restarts_ascending() {
231 let current = Some((0, SortDir::Desc));
232 assert_eq!(cycle_sort(current, 1), Some((1, SortDir::Asc)));
233 }
234
235 #[test]
236 fn sorted_order_never_touches_the_source() {
237 let rows = vec![3, 1, 2];
238 let order = sorted_order(&rows, SortDir::Asc, &|a, b| a.cmp(b));
239 assert_eq!(order, vec![1, 2, 0]);
240 assert_eq!(rows, vec![3, 1, 2]);
241 }
242
243 #[test]
244 fn sorted_order_is_stable_in_both_directions() {
245 let rows = vec![(1, "a"), (0, "b"), (1, "c"), (0, "d")];
247 let cmp = |a: &(i32, &str), b: &(i32, &str)| a.0.cmp(&b.0);
248 assert_eq!(sorted_order(&rows, SortDir::Asc, &cmp), vec![1, 3, 0, 2]);
249 assert_eq!(sorted_order(&rows, SortDir::Desc, &cmp), vec![0, 2, 1, 3]);
250 }
251
252 #[test]
253 fn single_mode_holds_one_row_and_cmd_click_deselects() {
254 let order = identity_order(4);
255 let mut sel = SelectionState::default();
256 sel.click(SelectionMode::Single, &order, 1, false, false);
257 assert_eq!(sel.selected(), vec![1]);
258 sel.click(SelectionMode::Single, &order, 3, false, false);
259 assert_eq!(sel.selected(), vec![3]);
260 sel.click(SelectionMode::Single, &order, 3, true, false);
261 assert_eq!(sel.selected(), Vec::<usize>::new());
262 }
263
264 #[test]
265 fn none_mode_ignores_clicks_and_steps() {
266 let order = identity_order(3);
267 let mut sel = SelectionState::default();
268 sel.click(SelectionMode::None, &order, 0, false, false);
269 assert!(sel.selected().is_empty());
270 assert_eq!(sel.step(SelectionMode::None, &order, 1, false), None);
271 }
272
273 #[test]
274 fn multi_mode_cmd_click_toggles() {
275 let order = identity_order(4);
276 let mut sel = SelectionState::default();
277 sel.click(SelectionMode::Multi, &order, 0, false, false);
278 sel.click(SelectionMode::Multi, &order, 2, true, false);
279 assert_eq!(sel.selected(), vec![0, 2]);
280 sel.click(SelectionMode::Multi, &order, 0, true, false);
281 assert_eq!(sel.selected(), vec![2]);
282 }
283
284 #[test]
285 fn multi_mode_shift_click_selects_a_range_from_the_anchor() {
286 let order = identity_order(6);
287 let mut sel = SelectionState::default();
288 sel.click(SelectionMode::Multi, &order, 1, false, false);
289 sel.click(SelectionMode::Multi, &order, 4, false, true);
290 assert_eq!(sel.selected(), vec![1, 2, 3, 4]);
291 sel.click(SelectionMode::Multi, &order, 0, false, true);
293 assert_eq!(sel.selected(), vec![0, 1]);
294 }
295
296 #[test]
297 fn shift_click_first_establishes_the_anchor() {
298 let order = identity_order(6);
299 let mut sel = SelectionState::default();
300 sel.click(SelectionMode::Multi, &order, 2, false, true);
302 assert_eq!(sel.selected(), vec![2]);
303 sel.click(SelectionMode::Multi, &order, 5, false, true);
305 assert_eq!(sel.selected(), vec![2, 3, 4, 5]);
306 }
307
308 #[test]
309 fn shift_step_first_extends_from_the_entry_row() {
310 let order = identity_order(4);
311 let mut sel = SelectionState::default();
312 assert_eq!(sel.step(SelectionMode::Multi, &order, 1, true), Some(0));
314 assert_eq!(sel.selected(), vec![0]);
315 assert_eq!(sel.step(SelectionMode::Multi, &order, 1, true), Some(1));
317 assert_eq!(sel.selected(), vec![0, 1]);
318 }
319
320 #[test]
321 fn shift_range_follows_display_order_but_stores_source_indices() {
322 let order = vec![3, 2, 1, 0];
324 let mut sel = SelectionState::default();
325 sel.click(SelectionMode::Multi, &order, 0, false, false);
326 assert_eq!(sel.selected(), vec![3]);
327 sel.click(SelectionMode::Multi, &order, 2, false, true);
328 assert_eq!(sel.selected(), vec![1, 2, 3]);
329 }
330
331 #[test]
332 fn selection_survives_resorting() {
333 let mut sel = SelectionState::default();
334 sel.click(SelectionMode::Single, &identity_order(4), 2, false, false);
335 assert_eq!(sel.selected(), vec![2]);
336 assert!(sel.is_selected(2));
338 let order = vec![3, 2, 1, 0]; let display = sel.step(SelectionMode::Single, &order, 1, false);
341 assert_eq!(display, Some(2));
342 assert_eq!(sel.selected(), vec![1]);
343 }
344
345 #[test]
346 fn step_enters_at_the_edges_and_clamps() {
347 let order = identity_order(3);
348 let mut sel = SelectionState::default();
349 assert_eq!(sel.step(SelectionMode::Single, &order, 1, false), Some(0));
350 assert_eq!(sel.step(SelectionMode::Single, &order, -1, false), Some(0));
351 assert_eq!(sel.selected(), vec![0]);
352
353 let mut sel = SelectionState::default();
354 assert_eq!(sel.step(SelectionMode::Single, &order, -1, false), Some(2));
355 assert_eq!(sel.selected(), vec![2]);
356 }
357
358 #[test]
359 fn shift_step_extends_from_the_anchor() {
360 let order = identity_order(5);
361 let mut sel = SelectionState::default();
362 sel.click(SelectionMode::Multi, &order, 2, false, false);
363 sel.step(SelectionMode::Multi, &order, 1, true);
364 sel.step(SelectionMode::Multi, &order, 1, true);
365 assert_eq!(sel.selected(), vec![2, 3, 4]);
366 sel.step(SelectionMode::Multi, &order, -1, true);
368 assert_eq!(sel.selected(), vec![2, 3]);
369 }
370
371 #[test]
372 fn clear_and_retain_report_changes() {
373 let order = identity_order(4);
374 let mut sel = SelectionState::default();
375 assert!(!sel.clear());
376 sel.click(SelectionMode::Multi, &order, 1, false, false);
377 sel.click(SelectionMode::Multi, &order, 3, true, false);
378 assert!(sel.retain_below(2)); assert_eq!(sel.selected(), vec![1]);
380 assert!(!sel.retain_below(2));
381 assert!(sel.clear());
382 assert_eq!(sel.cursor(), None);
383 }
384}