mecomp_tui/ui/widgets/tree/
state.rs1use std::collections::HashSet;
2
3use ratatui::layout::{Position, Rect};
4
5use super::{
6 flatten::{flatten, Flattened},
7 item::CheckTreeItem,
8};
9
10#[derive(Debug, Default, Clone)]
12#[allow(clippy::module_name_repetitions)]
13pub struct CheckTreeState<Identifier> {
14 pub(super) offset: usize,
15 pub(super) opened: HashSet<Vec<Identifier>>,
16 pub(super) selected: Vec<Identifier>,
17 pub(super) checked: HashSet<Vec<Identifier>>,
18 pub(super) ensure_selected_in_view_on_next_render: bool,
19
20 pub(super) last_area: Rect,
21 pub(super) last_biggest_index: usize,
22 pub(super) last_identifiers: Vec<Vec<Identifier>>,
24 pub(super) last_rendered_identifiers: Vec<(u16, Vec<Identifier>)>,
26}
27
28impl<Identifier> CheckTreeState<Identifier>
29where
30 Identifier: Clone + PartialEq + Eq + core::hash::Hash,
31{
32 #[must_use]
33 pub const fn get_offset(&self) -> usize {
34 self.offset
35 }
36
37 #[must_use]
38 pub const fn opened(&self) -> &HashSet<Vec<Identifier>> {
39 &self.opened
40 }
41
42 #[must_use]
44 pub fn selected(&self) -> &[Identifier] {
45 &self.selected
46 }
47
48 #[must_use]
50 pub const fn checked(&self) -> &HashSet<Vec<Identifier>> {
51 &self.checked
52 }
53
54 #[must_use]
56 pub fn flatten<'text>(
57 &self,
58 items: &'text [CheckTreeItem<'text, Identifier>],
59 ) -> Vec<Flattened<'text, Identifier>> {
60 flatten(&self.opened, items, &[])
61 }
62
63 pub fn select(&mut self, identifier: Vec<Identifier>) -> bool {
69 self.ensure_selected_in_view_on_next_render = true;
70 let changed = self.selected != identifier;
71 self.selected = identifier;
72 changed
73 }
74
75 pub fn open(&mut self, identifier: Vec<Identifier>) -> bool {
81 if identifier.is_empty() {
82 false
83 } else {
84 self.opened.insert(identifier)
85 }
86 }
87
88 pub fn close(&mut self, identifier: &[Identifier]) -> bool {
94 self.opened.remove(identifier)
95 }
96
97 pub fn check(&mut self, identifier: Vec<Identifier>) -> bool {
103 if identifier.is_empty() {
104 false
105 } else {
106 self.checked.insert(identifier)
108 }
109 }
110
111 pub fn uncheck(&mut self, identifier: &[Identifier]) -> bool {
117 self.checked.remove(identifier)
118 }
119
120 pub fn toggle(&mut self, identifier: Vec<Identifier>) -> bool {
128 if identifier.is_empty() {
129 false
130 } else if self.opened.contains(&identifier) {
131 self.close(&identifier)
132 } else {
133 self.open(identifier)
134 }
135 }
136
137 pub fn toggle_selected(&mut self) -> bool {
145 if self.selected.is_empty() {
146 return false;
147 }
148
149 self.ensure_selected_in_view_on_next_render = true;
150
151 let was_open = self.opened.remove(&self.selected);
153 if was_open {
154 return true;
155 }
156
157 self.open(self.selected.clone())
158 }
159
160 pub fn toggle_check(&mut self, identifier: Vec<Identifier>) -> bool {
168 if identifier.is_empty() {
169 false
170 } else if self.checked.contains(&identifier) {
171 self.uncheck(&identifier)
172 } else {
173 self.check(identifier)
174 }
175 }
176
177 pub fn toggle_check_selected(&mut self) -> bool {
184 if self.selected.is_empty() {
185 return false;
186 }
187
188 let was_checked = self.checked.remove(&self.selected);
190 if was_checked {
191 return true;
192 }
193
194 self.check(self.selected.clone())
195 }
196
197 pub fn reset(&mut self) -> bool {
201 if self.opened.is_empty() && self.checked.is_empty() {
202 false
203 } else {
204 self.opened.clear();
205 self.checked.clear();
206 true
207 }
208 }
209
210 pub fn select_first(&mut self) -> bool {
214 let identifier = self.last_identifiers.first().cloned().unwrap_or_default();
215 self.select(identifier)
216 }
217
218 pub fn select_last(&mut self) -> bool {
222 let new_identifier = self.last_identifiers.last().cloned().unwrap_or_default();
223 self.select(new_identifier)
224 }
225
226 pub fn select_relative<F>(&mut self, change_function: F) -> bool
233 where
234 F: FnOnce(Option<usize>) -> usize,
235 {
236 let identifiers = &self.last_identifiers;
237 let current_identifier = &self.selected;
238 let current_index = identifiers
239 .iter()
240 .position(|identifier| identifier == current_identifier);
241 let new_index = change_function(current_index).min(self.last_biggest_index);
242 let new_identifier = identifiers.get(new_index).cloned().unwrap_or_default();
243 self.select(new_identifier)
244 }
245
246 #[must_use]
248 pub fn rendered_at(&self, position: Position) -> Option<&[Identifier]> {
249 if !self.last_area.contains(position) {
250 return None;
251 }
252
253 self.last_rendered_identifiers
254 .iter()
255 .rev()
256 .find(|(y, _)| position.y == *y)
257 .map(|(_, identifier)| identifier.as_ref())
258 }
259
260 pub fn scroll_selected_into_view(&mut self) {
262 self.ensure_selected_in_view_on_next_render = true;
263 }
264
265 pub fn scroll_up(&mut self, lines: usize) -> bool {
270 let before = self.offset;
271 self.offset = self.offset.saturating_sub(lines);
272 before != self.offset
273 }
274
275 pub fn scroll_down(&mut self, lines: usize) -> bool {
280 let before = self.offset;
281 self.offset = self
282 .offset
283 .saturating_add(lines)
284 .min(self.last_biggest_index);
285 before != self.offset
286 }
287
288 pub fn key_up(&mut self) -> bool {
293 self.select_relative(|current| {
294 current.map_or(usize::MAX, |current| current.saturating_sub(1))
296 })
297 }
298
299 pub fn key_down(&mut self) -> bool {
304 self.select_relative(|current| {
305 current.map_or(0, |current| current.saturating_add(1))
307 })
308 }
309
310 pub fn key_left(&mut self) -> bool {
315 self.ensure_selected_in_view_on_next_render = true;
316 let mut changed = self.opened.remove(&self.selected);
318 if !changed {
319 let popped = self.selected.pop();
321 changed = popped.is_some();
322 }
323 changed
324 }
325
326 pub fn key_right(&mut self) -> bool {
332 if self.selected.is_empty() {
333 false
334 } else {
335 self.ensure_selected_in_view_on_next_render = true;
336 self.open(self.selected.clone())
337 }
338 }
339
340 pub fn key_space(&mut self) -> bool {
345 self.toggle_check_selected()
346 }
347
348 pub fn mouse_click(&mut self, position: Position) -> bool {
356 let Some(identifier) = self.rendered_at(position) else {
357 self.selected.clear();
359 return false;
360 };
361 self.select(identifier.to_vec());
362
363 self.ensure_selected_in_view_on_next_render = true;
364
365 self.toggle_check_selected() && self.toggle_selected()
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use pretty_assertions::assert_eq;
375
376 #[test]
377 fn test_select() {
378 let mut state: CheckTreeState<&str> = CheckTreeState::default();
379
380 let id: &[&str] = &[];
381 assert_eq!(state.select(id.to_vec()), false);
382 assert_eq!(state.selected(), id);
383
384 let id = &["a"];
385 assert_eq!(state.select(id.clone().to_vec()), true);
386 assert_eq!(state.selected(), id);
387
388 let id = &["a", "b"];
389 assert_eq!(state.select(id.clone().to_vec()), true);
390 assert_eq!(state.selected(), id);
391
392 let id: &[&str] = &[];
393 assert_eq!(state.select(id.to_vec()), true);
394 assert_eq!(state.selected(), id);
395
396 let id: &[&str] = &[];
397 assert_eq!(state.select(id.to_vec()), false);
398 assert_eq!(state.selected(), id);
399 }
400
401 #[test]
402 fn test_open() {
403 let mut state: CheckTreeState<&str> = CheckTreeState::default();
404 let mut expected: HashSet<Vec<&str>> = HashSet::default();
405
406 assert_eq!(state.opened(), &expected);
408
409 assert_eq!(state.open(vec![]), false);
411
412 let id = vec!["a"];
414 assert_eq!(state.open(id.clone()), true);
415 expected.insert(id.clone());
416 assert_eq!(state.opened(), &expected);
417
418 let id = vec!["a"];
420 assert_eq!(state.open(id.clone()), false);
421 assert_eq!(state.opened(), &expected);
422 }
423
424 #[test]
425 fn test_close() {
426 let mut state: CheckTreeState<&str> = CheckTreeState::default();
427
428 assert_eq!(state.close(&["a"]), false);
429
430 state.open(vec!["a"]);
431
432 assert_eq!(state.close(&["a"]), true);
433 assert_eq!(state.close(&["a"]), false);
434 }
435
436 #[test]
437 fn test_reset() {
438 let mut state: CheckTreeState<&str> = CheckTreeState::default();
439
440 assert_eq!(state.reset(), false);
441
442 state.open(vec!["a"]);
443
444 assert_eq!(state.reset(), true);
445 assert_eq!(state.reset(), false);
446
447 state.open(vec!["a"]);
448 state.open(vec!["a", "b"]);
449
450 assert_eq!(state.reset(), true);
451 assert_eq!(state.reset(), false);
452 }
453
454 #[test]
455 fn test_check() {
456 let mut state: CheckTreeState<&str> = CheckTreeState::default();
457 let mut expected: HashSet<Vec<&str>> = HashSet::default();
458
459 assert_eq!(state.checked(), &expected);
461
462 assert_eq!(state.check(vec![]), false);
464
465 let id = vec!["a"];
467 assert_eq!(state.check(id.clone()), true);
468 expected.insert(id.clone());
469 assert_eq!(state.checked(), &expected);
470
471 let id = vec!["a"];
473 assert_eq!(state.check(id.clone()), false);
474 assert_eq!(state.checked(), &expected);
475 }
476
477 #[test]
478 fn test_uncheck() {
479 let mut state: CheckTreeState<&str> = CheckTreeState::default();
480
481 assert_eq!(state.uncheck(&["a"]), false);
482
483 state.check(vec!["a"]);
484
485 assert_eq!(state.uncheck(&["a"]), true);
486 assert_eq!(state.uncheck(&["a"]), false);
487 }
488
489 #[test]
490 fn test_toggle() {
491 let mut state: CheckTreeState<&str> = CheckTreeState::default();
492 let mut expected: HashSet<Vec<&str>> = HashSet::default();
493
494 assert_eq!(state.toggle(vec![]), false);
495
496 let id = vec!["a"];
497 assert_eq!(state.toggle(id.clone()), true);
498 expected.insert(id.clone());
499 assert_eq!(state.opened(), &expected);
500
501 assert_eq!(state.toggle(id.clone()), true);
502 expected.remove(&id);
503 assert_eq!(state.opened(), &expected);
504 }
505
506 #[test]
507 fn test_toggle_selected() {
508 let mut state: CheckTreeState<&str> = CheckTreeState::default();
509 let mut expected: HashSet<Vec<&str>> = HashSet::default();
510
511 assert_eq!(state.toggle_selected(), false);
512
513 let id = vec!["a"];
514 state.select(id.clone());
515
516 assert_eq!(state.toggle_selected(), true);
517 expected.insert(id.clone());
518 assert_eq!(state.opened(), &expected);
519
520 assert_eq!(state.toggle_selected(), true);
521 expected.remove(&id);
522 assert_eq!(state.opened(), &expected);
523 }
524
525 #[test]
526 fn test_toggle_check() {
527 let mut state: CheckTreeState<&str> = CheckTreeState::default();
528 let mut expected: HashSet<Vec<&str>> = HashSet::default();
529
530 assert_eq!(state.toggle_check(vec![]), false);
531
532 let id = vec!["a"];
533 assert_eq!(state.toggle_check(id.clone()), true);
534 expected.insert(id.clone());
535 assert_eq!(state.checked(), &expected);
536
537 assert_eq!(state.toggle_check(id.clone()), true);
538 expected.remove(&id);
539 assert_eq!(state.checked(), &expected);
540 }
541
542 #[test]
543 fn test_toggle_selected_check() {
544 let mut state: CheckTreeState<&str> = CheckTreeState::default();
545 let mut expected: HashSet<Vec<&str>> = HashSet::default();
546
547 assert_eq!(state.toggle_check_selected(), false);
548
549 let id = vec!["a"];
550 state.select(id.clone());
551
552 assert_eq!(state.toggle_check_selected(), true);
553 expected.insert(id.clone());
554 assert_eq!(state.checked(), &expected);
555
556 assert_eq!(state.toggle_check_selected(), true);
557 expected.remove(&id);
558 assert_eq!(state.checked(), &expected);
559 }
560}