mecomp_tui/ui/widgets/tree/
state.rs1use std::collections::HashSet;
2
3use ratatui::layout::{Position, Rect};
4
5use super::{
6 flatten::{Flattened, flatten},
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 #[allow(clippy::missing_const_for_fn)] pub fn selected(&self) -> &[Identifier] {
46 &self.selected
47 }
48
49 #[must_use]
51 pub const fn checked(&self) -> &HashSet<Vec<Identifier>> {
52 &self.checked
53 }
54
55 #[must_use]
57 pub fn flatten<'text>(
58 &self,
59 items: &'text [CheckTreeItem<'text, Identifier>],
60 ) -> Vec<Flattened<'text, Identifier>> {
61 flatten(&self.opened, items, &[])
62 }
63
64 pub fn select(&mut self, identifier: Vec<Identifier>) -> bool {
70 self.ensure_selected_in_view_on_next_render = true;
71 let changed = self.selected != identifier;
72 self.selected = identifier;
73 changed
74 }
75
76 pub fn open(&mut self, identifier: Vec<Identifier>) -> bool {
82 if identifier.is_empty() {
83 false
84 } else {
85 self.opened.insert(identifier)
86 }
87 }
88
89 pub fn close(&mut self, identifier: &[Identifier]) -> bool {
95 self.opened.remove(identifier)
96 }
97
98 pub fn check(&mut self, identifier: Vec<Identifier>) -> bool {
104 if identifier.is_empty() {
105 false
106 } else {
107 self.checked.insert(identifier)
109 }
110 }
111
112 pub fn uncheck(&mut self, identifier: &[Identifier]) -> bool {
118 self.checked.remove(identifier)
119 }
120
121 pub fn toggle(&mut self, identifier: Vec<Identifier>) -> bool {
129 if identifier.is_empty() {
130 false
131 } else if self.opened.contains(&identifier) {
132 self.close(&identifier)
133 } else {
134 self.open(identifier)
135 }
136 }
137
138 pub fn toggle_selected(&mut self) -> bool {
146 if self.selected.is_empty() {
147 return false;
148 }
149
150 self.ensure_selected_in_view_on_next_render = true;
151
152 let was_open = self.opened.remove(&self.selected);
154 if was_open {
155 return true;
156 }
157
158 self.open(self.selected.clone())
159 }
160
161 pub fn toggle_check(&mut self, identifier: Vec<Identifier>) -> bool {
169 if identifier.is_empty() {
170 false
171 } else if self.checked.contains(&identifier) {
172 self.uncheck(&identifier)
173 } else {
174 self.check(identifier)
175 }
176 }
177
178 pub fn toggle_check_selected(&mut self) -> bool {
185 if self.selected.is_empty() {
186 return false;
187 }
188
189 let was_checked = self.checked.remove(&self.selected);
191 if was_checked {
192 return true;
193 }
194
195 self.check(self.selected.clone())
196 }
197
198 pub fn reset(&mut self) -> bool {
202 if self.opened.is_empty() && self.checked.is_empty() {
203 false
204 } else {
205 self.opened.clear();
206 self.checked.clear();
207 true
208 }
209 }
210
211 pub fn select_first(&mut self) -> bool {
215 let identifier = self.last_identifiers.first().cloned().unwrap_or_default();
216 self.select(identifier)
217 }
218
219 pub fn select_last(&mut self) -> bool {
223 let new_identifier = self.last_identifiers.last().cloned().unwrap_or_default();
224 self.select(new_identifier)
225 }
226
227 pub fn select_relative<F>(&mut self, change_function: F) -> bool
234 where
235 F: FnOnce(Option<usize>) -> usize,
236 {
237 let identifiers = &self.last_identifiers;
238 let current_identifier = &self.selected;
239 let current_index = identifiers
240 .iter()
241 .position(|identifier| identifier == current_identifier);
242 let new_index = change_function(current_index).min(self.last_biggest_index);
243 let new_identifier = identifiers.get(new_index).cloned().unwrap_or_default();
244 self.select(new_identifier)
245 }
246
247 #[must_use]
249 pub fn rendered_at(&self, position: Position) -> Option<&[Identifier]> {
250 if !self.last_area.contains(position) {
251 return None;
252 }
253
254 self.last_rendered_identifiers
255 .iter()
256 .rev()
257 .find(|(y, _)| position.y == *y)
258 .map(|(_, identifier)| identifier.as_ref())
259 }
260
261 pub const fn scroll_selected_into_view(&mut self) {
263 self.ensure_selected_in_view_on_next_render = true;
264 }
265
266 pub const fn scroll_up(&mut self, lines: usize) -> bool {
271 let before = self.offset;
272 self.offset = self.offset.saturating_sub(lines);
273 before != self.offset
274 }
275
276 pub fn scroll_down(&mut self, lines: usize) -> bool {
281 let before = self.offset;
282 self.offset = self
283 .offset
284 .saturating_add(lines)
285 .min(self.last_biggest_index);
286 before != self.offset
287 }
288
289 pub fn key_up(&mut self) -> bool {
294 self.select_relative(|current| {
295 current.map_or(usize::MAX, |current| current.saturating_sub(1))
297 })
298 }
299
300 pub fn key_down(&mut self) -> bool {
305 self.select_relative(|current| {
306 current.map_or(0, |current| current.saturating_add(1))
308 })
309 }
310
311 pub fn key_left(&mut self) -> bool {
316 self.ensure_selected_in_view_on_next_render = true;
317 let mut changed = self.opened.remove(&self.selected);
319 if !changed {
320 let popped = self.selected.pop();
322 changed = popped.is_some();
323 }
324 changed
325 }
326
327 pub fn key_right(&mut self) -> bool {
333 if self.selected.is_empty() {
334 false
335 } else {
336 self.ensure_selected_in_view_on_next_render = true;
337 self.open(self.selected.clone())
338 }
339 }
340
341 pub fn key_space(&mut self) -> bool {
346 self.toggle_check_selected()
347 }
348
349 pub fn mouse_click(&mut self, position: Position) -> bool {
357 let Some(identifier) = self.rendered_at(position) else {
358 self.selected.clear();
360 return false;
361 };
362 self.select(identifier.to_vec());
363
364 self.ensure_selected_in_view_on_next_render = true;
365
366 self.toggle_check_selected() && self.toggle_selected()
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use pretty_assertions::assert_eq;
376
377 #[test]
378 fn test_select() {
379 let mut state: CheckTreeState<&str> = CheckTreeState::default();
380
381 let id: &[&str] = &[];
382 assert_eq!(state.select(id.to_vec()), false);
383 assert_eq!(state.selected(), id);
384
385 let id = &["a"];
386 assert_eq!(state.select(id.clone().to_vec()), true);
387 assert_eq!(state.selected(), id);
388
389 let id = &["a", "b"];
390 assert_eq!(state.select(id.clone().to_vec()), true);
391 assert_eq!(state.selected(), id);
392
393 let id: &[&str] = &[];
394 assert_eq!(state.select(id.to_vec()), true);
395 assert_eq!(state.selected(), id);
396
397 let id: &[&str] = &[];
398 assert_eq!(state.select(id.to_vec()), false);
399 assert_eq!(state.selected(), id);
400 }
401
402 #[test]
403 fn test_open() {
404 let mut state: CheckTreeState<&str> = CheckTreeState::default();
405 let mut expected: HashSet<Vec<&str>> = HashSet::default();
406
407 assert_eq!(state.opened(), &expected);
409
410 assert_eq!(state.open(vec![]), false);
412
413 let id = vec!["a"];
415 assert_eq!(state.open(id.clone()), true);
416 expected.insert(id.clone());
417 assert_eq!(state.opened(), &expected);
418
419 let id = vec!["a"];
421 assert_eq!(state.open(id.clone()), false);
422 assert_eq!(state.opened(), &expected);
423 }
424
425 #[test]
426 fn test_close() {
427 let mut state: CheckTreeState<&str> = CheckTreeState::default();
428
429 assert_eq!(state.close(&["a"]), false);
430
431 state.open(vec!["a"]);
432
433 assert_eq!(state.close(&["a"]), true);
434 assert_eq!(state.close(&["a"]), false);
435 }
436
437 #[test]
438 fn test_reset() {
439 let mut state: CheckTreeState<&str> = CheckTreeState::default();
440
441 assert_eq!(state.reset(), false);
442
443 state.open(vec!["a"]);
444
445 assert_eq!(state.reset(), true);
446 assert_eq!(state.reset(), false);
447
448 state.open(vec!["a"]);
449 state.open(vec!["a", "b"]);
450
451 assert_eq!(state.reset(), true);
452 assert_eq!(state.reset(), false);
453 }
454
455 #[test]
456 fn test_check() {
457 let mut state: CheckTreeState<&str> = CheckTreeState::default();
458 let mut expected: HashSet<Vec<&str>> = HashSet::default();
459
460 assert_eq!(state.checked(), &expected);
462
463 assert_eq!(state.check(vec![]), false);
465
466 let id = vec!["a"];
468 assert_eq!(state.check(id.clone()), true);
469 expected.insert(id.clone());
470 assert_eq!(state.checked(), &expected);
471
472 let id = vec!["a"];
474 assert_eq!(state.check(id.clone()), false);
475 assert_eq!(state.checked(), &expected);
476 }
477
478 #[test]
479 fn test_uncheck() {
480 let mut state: CheckTreeState<&str> = CheckTreeState::default();
481
482 assert_eq!(state.uncheck(&["a"]), false);
483
484 state.check(vec!["a"]);
485
486 assert_eq!(state.uncheck(&["a"]), true);
487 assert_eq!(state.uncheck(&["a"]), false);
488 }
489
490 #[test]
491 fn test_toggle() {
492 let mut state: CheckTreeState<&str> = CheckTreeState::default();
493 let mut expected: HashSet<Vec<&str>> = HashSet::default();
494
495 assert_eq!(state.toggle(vec![]), false);
496
497 let id = vec!["a"];
498 assert_eq!(state.toggle(id.clone()), true);
499 expected.insert(id.clone());
500 assert_eq!(state.opened(), &expected);
501
502 assert_eq!(state.toggle(id.clone()), true);
503 expected.remove(&id);
504 assert_eq!(state.opened(), &expected);
505 }
506
507 #[test]
508 fn test_toggle_selected() {
509 let mut state: CheckTreeState<&str> = CheckTreeState::default();
510 let mut expected: HashSet<Vec<&str>> = HashSet::default();
511
512 assert_eq!(state.toggle_selected(), false);
513
514 let id = vec!["a"];
515 state.select(id.clone());
516
517 assert_eq!(state.toggle_selected(), true);
518 expected.insert(id.clone());
519 assert_eq!(state.opened(), &expected);
520
521 assert_eq!(state.toggle_selected(), true);
522 expected.remove(&id);
523 assert_eq!(state.opened(), &expected);
524 }
525
526 #[test]
527 fn test_toggle_check() {
528 let mut state: CheckTreeState<&str> = CheckTreeState::default();
529 let mut expected: HashSet<Vec<&str>> = HashSet::default();
530
531 assert_eq!(state.toggle_check(vec![]), false);
532
533 let id = vec!["a"];
534 assert_eq!(state.toggle_check(id.clone()), true);
535 expected.insert(id.clone());
536 assert_eq!(state.checked(), &expected);
537
538 assert_eq!(state.toggle_check(id.clone()), true);
539 expected.remove(&id);
540 assert_eq!(state.checked(), &expected);
541 }
542
543 #[test]
544 fn test_toggle_selected_check() {
545 let mut state: CheckTreeState<&str> = CheckTreeState::default();
546 let mut expected: HashSet<Vec<&str>> = HashSet::default();
547
548 assert_eq!(state.toggle_check_selected(), false);
549
550 let id = vec!["a"];
551 state.select(id.clone());
552
553 assert_eq!(state.toggle_check_selected(), true);
554 expected.insert(id.clone());
555 assert_eq!(state.checked(), &expected);
556
557 assert_eq!(state.toggle_check_selected(), true);
558 expected.remove(&id);
559 assert_eq!(state.checked(), &expected);
560 }
561}