Skip to main content

revue/utils/tree/
navigation.rs

1//! Tree navigation with collapsible sections
2
3use std::collections::HashSet;
4
5use crate::utils::tree::prefix::TreePrefix;
6use crate::utils::tree::types::TreeItem;
7
8/// Tree navigation with collapsible sections
9///
10/// Handles navigation logic for hierarchical tree structures where
11/// parent nodes can be collapsed to hide their children.
12///
13/// # Example
14///
15/// ```ignore
16/// let mut nav = TreeNav::new();
17/// nav.add_item(TreeItem::new(0).collapsible());  // Section
18/// nav.add_item(TreeItem::new(1).with_parent(0)); // Child
19/// nav.add_item(TreeItem::new(2).with_parent(0)); // Child
20///
21/// nav.next();  // Move to next visible item
22/// nav.toggle_collapse();  // Collapse/expand current item
23/// ```
24pub struct TreeNav {
25    items: Vec<TreeItem>,
26    selected: usize,
27    selected_row: usize,
28    collapsed: HashSet<usize>,
29}
30
31impl TreeNav {
32    /// Create a new empty tree navigation
33    pub fn new() -> Self {
34        Self {
35            items: Vec::new(),
36            selected: 0,
37            selected_row: 0,
38            collapsed: HashSet::new(),
39        }
40    }
41
42    /// Add an item to the tree
43    pub fn add_item(&mut self, item: TreeItem) {
44        self.items.push(item);
45    }
46
47    /// Clear all items
48    pub fn clear(&mut self) {
49        self.items.clear();
50        self.selected = 0;
51        self.selected_row = 0;
52    }
53
54    /// Get current selection
55    pub fn selected(&self) -> usize {
56        self.selected
57    }
58
59    /// Get current row within selected item
60    pub fn selected_row(&self) -> usize {
61        self.selected_row
62    }
63
64    /// Check if an item is collapsed
65    pub fn is_collapsed(&self, id: usize) -> bool {
66        self.collapsed.contains(&id)
67    }
68
69    /// Check if an item is visible (not hidden by collapsed parent)
70    pub fn is_visible(&self, id: usize) -> bool {
71        if let Some(item) = self.items.get(id) {
72            if let Some(parent) = item.parent {
73                if self.collapsed.contains(&parent) {
74                    return false;
75                }
76                // Check ancestors
77                return self.is_visible(parent);
78            }
79        }
80        true
81    }
82
83    /// Get visible items
84    pub fn visible_items(&self) -> Vec<&TreeItem> {
85        self.items
86            .iter()
87            .filter(|item| self.is_visible(item.id))
88            .collect()
89    }
90
91    /// Move to next visible item
92    pub fn next(&mut self) {
93        let visible: Vec<_> = self.visible_items().iter().map(|i| i.id).collect();
94        if visible.is_empty() {
95            return;
96        }
97
98        // First try to move within current item's rows
99        if let Some(item) = self.items.get(self.selected) {
100            if self.selected_row + 1 < item.row_count {
101                self.selected_row += 1;
102                return;
103            }
104        }
105
106        // Move to next item
107        if let Some(pos) = visible.iter().position(|&id| id == self.selected) {
108            let next_pos = (pos + 1) % visible.len();
109            self.selected = visible[next_pos];
110            self.selected_row = 0;
111        }
112    }
113
114    /// Move to previous visible item
115    pub fn prev(&mut self) {
116        let visible: Vec<_> = self.visible_items().iter().map(|i| i.id).collect();
117        if visible.is_empty() {
118            return;
119        }
120
121        // First try to move within current item's rows
122        if self.selected_row > 0 {
123            self.selected_row -= 1;
124            return;
125        }
126
127        // Move to previous item
128        if let Some(pos) = visible.iter().position(|&id| id == self.selected) {
129            let prev_pos = if pos == 0 { visible.len() - 1 } else { pos - 1 };
130            self.selected = visible[prev_pos];
131            // Move to last row of previous item
132            if let Some(item) = self.items.get(self.selected) {
133                self.selected_row = item.row_count.saturating_sub(1);
134            }
135        }
136    }
137
138    /// Move to next item (skip rows within item)
139    pub fn next_item(&mut self) {
140        let visible: Vec<_> = self.visible_items().iter().map(|i| i.id).collect();
141        if visible.is_empty() {
142            return;
143        }
144
145        if let Some(pos) = visible.iter().position(|&id| id == self.selected) {
146            let next_pos = (pos + 1) % visible.len();
147            self.selected = visible[next_pos];
148            self.selected_row = 0;
149        }
150    }
151
152    /// Move to previous item (skip rows within item)
153    pub fn prev_item(&mut self) {
154        let visible: Vec<_> = self.visible_items().iter().map(|i| i.id).collect();
155        if visible.is_empty() {
156            return;
157        }
158
159        if let Some(pos) = visible.iter().position(|&id| id == self.selected) {
160            let prev_pos = if pos == 0 { visible.len() - 1 } else { pos - 1 };
161            self.selected = visible[prev_pos];
162            self.selected_row = 0;
163        }
164    }
165
166    /// Toggle collapse state of current item
167    pub fn toggle_collapse(&mut self) {
168        if let Some(item) = self.items.get(self.selected) {
169            if item.collapsible {
170                if self.collapsed.contains(&self.selected) {
171                    self.collapsed.remove(&self.selected);
172                } else {
173                    self.collapsed.insert(self.selected);
174                }
175            }
176        }
177    }
178
179    /// Collapse current item
180    pub fn collapse(&mut self) {
181        if let Some(item) = self.items.get(self.selected) {
182            if item.collapsible {
183                self.collapsed.insert(self.selected);
184            }
185        }
186    }
187
188    /// Expand current item
189    pub fn expand(&mut self) {
190        self.collapsed.remove(&self.selected);
191    }
192
193    /// Collapse all collapsible items
194    pub fn collapse_all(&mut self) {
195        for item in &self.items {
196            if item.collapsible {
197                self.collapsed.insert(item.id);
198            }
199        }
200    }
201
202    /// Expand all items
203    pub fn expand_all(&mut self) {
204        self.collapsed.clear();
205    }
206
207    /// Set selection by id
208    pub fn select(&mut self, id: usize) {
209        if id < self.items.len() && self.is_visible(id) {
210            self.selected = id;
211            self.selected_row = 0;
212        }
213    }
214
215    /// Go to first visible item
216    pub fn first(&mut self) {
217        if let Some(item) = self.visible_items().first() {
218            self.selected = item.id;
219            self.selected_row = 0;
220        }
221    }
222
223    /// Go to last visible item
224    pub fn last(&mut self) {
225        if let Some(item) = self.visible_items().last() {
226            self.selected = item.id;
227            self.selected_row = 0;
228        }
229    }
230
231    /// Get tree prefix for an item
232    ///
233    /// Automatically calculates the correct prefix based on item's position
234    /// in the tree hierarchy.
235    ///
236    /// # Arguments
237    /// * `id` - Item id to get prefix for
238    ///
239    /// # Returns
240    /// Tuple of (prefix_string, is_last)
241    pub fn get_prefix(&self, id: usize) -> (String, bool) {
242        let _visible = self.visible_items();
243        let is_last = self.is_last_sibling(id);
244
245        let mut prefix = TreePrefix::new();
246
247        // Build prefix by walking up the parent chain
248        if let Some(item) = self.items.get(id) {
249            let mut ancestors = Vec::new();
250            let mut current = item.parent;
251
252            while let Some(parent_id) = current {
253                ancestors.push(parent_id);
254                if let Some(parent) = self.items.get(parent_id) {
255                    current = parent.parent;
256                } else {
257                    break;
258                }
259            }
260
261            // Process ancestors from root to leaf
262            for ancestor_id in ancestors.into_iter().rev() {
263                prefix.push(!self.is_last_sibling(ancestor_id));
264            }
265        }
266
267        (prefix.prefix(is_last), is_last)
268    }
269
270    /// Check if item is the last visible sibling at its level
271    pub fn is_last_sibling(&self, id: usize) -> bool {
272        if let Some(item) = self.items.get(id) {
273            let parent = item.parent;
274            let depth = item.depth;
275
276            // Find siblings (same parent and depth)
277            let siblings: Vec<_> = self
278                .items
279                .iter()
280                .filter(|i| i.parent == parent && i.depth == depth && self.is_visible(i.id))
281                .collect();
282
283            siblings.last().map(|last| last.id == id).unwrap_or(true)
284        } else {
285            true
286        }
287    }
288
289    /// Get all visible items with their prefixes for rendering
290    ///
291    /// Returns Vec of (item_ref, prefix_string, is_selected)
292    pub fn render_items(&self) -> Vec<(&TreeItem, String, bool)> {
293        self.visible_items()
294            .into_iter()
295            .map(|item| {
296                let (prefix, _) = self.get_prefix(item.id);
297                let is_selected = item.id == self.selected;
298                (item, prefix, is_selected)
299            })
300            .collect()
301    }
302}
303
304impl Default for TreeNav {
305    fn default() -> Self {
306        Self::new()
307    }
308}