Skip to main content

teksilo_widgets/table_view/
row_navigator.rs

1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4//! Trait abstracting per-row navigation for `TableView` and `TreeTableView`.
5//!
6//! Flat tables answer arrow-up/down by stepping a contiguous index;
7//! tree tables need to ask their `TreeSlice` for the next visible
8//! flat-index. The shared keyboard handler in `keyboard.rs` is generic
9//! over this trait so both widgets re-use the same key-matrix logic.
10
11use std::rc::Rc;
12
13/// Per-row navigator. Implementations live alongside the consumer
14/// widget — `FlatNavigator` here for `TableView`; `TreeNavigator` in
15/// `tree_table_view.rs` for `TreeTableView`.
16pub(crate) trait RowNavigator {
17    fn row_count(&self) -> usize;
18
19    fn next_row(&self, current: usize) -> Option<usize> {
20        let n = self.row_count();
21        if n == 0 {
22            return None;
23        }
24        let next = current + 1;
25        if next < n { Some(next) } else { None }
26    }
27
28    fn prev_row(&self, current: usize) -> Option<usize> {
29        if current == 0 {
30            None
31        } else {
32            Some(current - 1)
33        }
34    }
35
36    fn first_row(&self) -> Option<usize> {
37        if self.row_count() == 0 { None } else { Some(0) }
38    }
39
40    fn last_row(&self) -> Option<usize> {
41        let n = self.row_count();
42        if n == 0 { None } else { Some(n - 1) }
43    }
44
45    /// Tree-only — depth of the row in the hierarchy. `None` for
46    /// flat tables. Currently only exercised by `TreeNavigator`'s own
47    /// test path; reserved for upcoming Shift+ArrowLeft "jump to
48    /// parent" navigation in the shared keyboard module.
49    #[allow(dead_code)]
50    fn depth(&self, _row: usize) -> Option<usize> {
51        None
52    }
53
54    fn has_children(&self, _row: usize) -> bool {
55        false
56    }
57
58    fn is_expanded(&self, _row: usize) -> bool {
59        false
60    }
61
62    fn toggle_expanded(&self, _row: usize) {}
63}
64
65/// Trivial navigator over a length-providing closure. Used by
66/// `TableView` directly.
67pub(crate) struct FlatNavigator {
68    pub(crate) len_fn: Rc<dyn Fn() -> usize>,
69}
70
71impl FlatNavigator {
72    pub(crate) fn new(len_fn: Rc<dyn Fn() -> usize>) -> Self {
73        Self { len_fn }
74    }
75}
76
77impl RowNavigator for FlatNavigator {
78    fn row_count(&self) -> usize {
79        (self.len_fn)()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::cell::Cell;
87
88    #[test]
89    fn flat_navigator_steps_through_rows() {
90        let count = Rc::new(Cell::new(5));
91        let c = count.clone();
92        let nav = FlatNavigator::new(Rc::new(move || c.get()));
93        assert_eq!(nav.row_count(), 5);
94        assert_eq!(nav.first_row(), Some(0));
95        assert_eq!(nav.last_row(), Some(4));
96        assert_eq!(nav.next_row(0), Some(1));
97        assert_eq!(nav.next_row(4), None);
98        assert_eq!(nav.prev_row(0), None);
99        assert_eq!(nav.prev_row(2), Some(1));
100    }
101
102    #[test]
103    fn empty_navigator_returns_none_for_first_last() {
104        let nav = FlatNavigator::new(Rc::new(|| 0));
105        assert_eq!(nav.first_row(), None);
106        assert_eq!(nav.last_row(), None);
107        assert_eq!(nav.next_row(0), None);
108        assert_eq!(nav.prev_row(0), None);
109    }
110
111    #[test]
112    fn flat_default_tree_methods_are_noops() {
113        let nav = FlatNavigator::new(Rc::new(|| 5));
114        assert_eq!(nav.depth(0), None);
115        assert!(!nav.has_children(0));
116        assert!(!nav.is_expanded(0));
117        nav.toggle_expanded(0); // no-op
118    }
119}