tui_tree_widget_table/tree_state.rs
1use std::collections::HashSet;
2
3use ratatui::layout::{Position, Rect};
4
5use crate::flatten::{flatten, Flattened};
6use crate::tree_item::TreeItem;
7
8/// Keeps the state of what is currently selected and what was opened in a [`Tree`](crate::Tree).
9///
10/// The generic argument `Identifier` is used to keep the state like the currently selected or opened [`TreeItem`]s in the [`TreeState`].
11/// For more information see [`TreeItem`].
12///
13/// # Example
14///
15/// ```
16/// # use tui_tree_widget::TreeState;
17/// type Identifier = usize;
18///
19/// let mut state = TreeState::<Identifier>::default();
20/// ```
21#[must_use]
22#[derive(Debug, Default)]
23pub struct TreeState<Identifier> {
24 pub(super) offset: usize,
25 pub(super) opened: HashSet<Vec<Identifier>>,
26 pub(super) selected: Vec<Identifier>,
27 pub(super) ensure_selected_in_view_on_next_render: bool,
28
29 pub(super) last_area: Rect,
30 pub(super) last_biggest_index: usize,
31 /// All identifiers open on last render
32 pub(super) last_identifiers: Vec<Vec<Identifier>>,
33 /// Identifier rendered at `y` on last render
34 pub(super) last_rendered_identifiers: Vec<(u16, Vec<Identifier>)>,
35}
36
37impl<Identifier> TreeState<Identifier>
38where
39 Identifier: Clone + PartialEq + Eq + core::hash::Hash,
40{
41 #[must_use]
42 pub const fn get_offset(&self) -> usize {
43 self.offset
44 }
45
46 #[must_use]
47 #[deprecated = "Use self.opened()"]
48 pub fn get_all_opened(&self) -> Vec<Vec<Identifier>> {
49 self.opened.iter().cloned().collect()
50 }
51
52 #[must_use]
53 pub const fn opened(&self) -> &HashSet<Vec<Identifier>> {
54 &self.opened
55 }
56
57 #[must_use]
58 pub fn selected(&self) -> &[Identifier] {
59 &self.selected
60 }
61
62 /// Get a flat list of all currently viewable (including by scrolling) [`TreeItem`]s with this `TreeState`.
63 #[must_use]
64 pub fn flatten<'text>(
65 &self,
66 items: &'text [TreeItem<'text, Identifier>],
67 ) -> Vec<Flattened<'text, Identifier>> {
68 flatten(&self.opened, items, &[])
69 }
70
71 /// Selects the given identifier.
72 ///
73 /// Returns `true` when the selection changed.
74 ///
75 /// Clear the selection by passing an empty identifier vector:
76 ///
77 /// ```rust
78 /// # use tui_tree_widget::TreeState;
79 /// # let mut state = TreeState::<usize>::default();
80 /// state.select(Vec::new());
81 /// ```
82 pub fn select(&mut self, identifier: Vec<Identifier>) -> bool {
83 self.ensure_selected_in_view_on_next_render = true;
84 let changed = self.selected != identifier;
85 self.selected = identifier;
86 changed
87 }
88
89 /// Open a tree node.
90 /// Returns `true` when it was closed and has been opened.
91 /// Returns `false` when it was already open.
92 pub fn open(&mut self, identifier: Vec<Identifier>) -> bool {
93 if identifier.is_empty() {
94 false
95 } else {
96 self.opened.insert(identifier)
97 }
98 }
99
100 /// Close a tree node.
101 /// Returns `true` when it was open and has been closed.
102 /// Returns `false` when it was already closed.
103 pub fn close(&mut self, identifier: &[Identifier]) -> bool {
104 self.opened.remove(identifier)
105 }
106
107 /// Toggles a tree node open/close state.
108 /// When it is currently open, then [`close`](Self::close) is called. Otherwise [`open`](Self::open).
109 ///
110 /// Returns `true` when a node is opened / closed.
111 /// As toggle always changes something, this only returns `false` when an empty identifier is given.
112 pub fn toggle(&mut self, identifier: Vec<Identifier>) -> bool {
113 if identifier.is_empty() {
114 false
115 } else if self.opened.contains(&identifier) {
116 self.close(&identifier)
117 } else {
118 self.open(identifier)
119 }
120 }
121
122 /// Toggles the currently selected tree node open/close state.
123 /// See also [`toggle`](Self::toggle)
124 ///
125 /// Returns `true` when a node is opened / closed.
126 /// As toggle always changes something, this only returns `false` when nothing is selected.
127 pub fn toggle_selected(&mut self) -> bool {
128 if self.selected.is_empty() {
129 return false;
130 }
131
132 self.ensure_selected_in_view_on_next_render = true;
133
134 // Reimplement self.close because of multiple different borrows
135 let was_open = self.opened.remove(&self.selected);
136 if was_open {
137 return true;
138 }
139
140 self.open(self.selected.clone())
141 }
142
143 /// Closes all open nodes.
144 ///
145 /// Returns `true` when any node was closed.
146 pub fn close_all(&mut self) -> bool {
147 if self.opened.is_empty() {
148 false
149 } else {
150 self.opened.clear();
151 true
152 }
153 }
154
155 /// Select the first node.
156 ///
157 /// Returns `true` when the selection changed.
158 pub fn select_first(&mut self) -> bool {
159 let identifier = self.last_identifiers.first().cloned().unwrap_or_default();
160 self.select(identifier)
161 }
162
163 /// Select the last node.
164 ///
165 /// Returns `true` when the selection changed.
166 pub fn select_last(&mut self) -> bool {
167 let new_identifier = self.last_identifiers.last().cloned().unwrap_or_default();
168 self.select(new_identifier)
169 }
170
171 /// Select the node on the given index.
172 ///
173 /// Returns `true` when the selection changed.
174 ///
175 /// This can be useful for mouse clicks.
176 #[deprecated = "Prefer self.click_at or self.rendered_at as visible index is hard to predict with height != 1"]
177 pub fn select_visible_index(&mut self, new_index: usize) -> bool {
178 let new_index = new_index.min(self.last_biggest_index);
179 let new_identifier = self
180 .last_identifiers
181 .get(new_index)
182 .cloned()
183 .unwrap_or_default();
184 self.select(new_identifier)
185 }
186
187 /// Move the current selection with the direction/amount by the given function.
188 ///
189 /// Returns `true` when the selection changed.
190 ///
191 /// # Example
192 ///
193 /// ```
194 /// # use tui_tree_widget::TreeState;
195 /// # type Identifier = usize;
196 /// # let mut state = TreeState::<Identifier>::default();
197 /// // Move the selection one down
198 /// state.select_visible_relative(|current| {
199 /// // When nothing is currently selected, select index 0
200 /// // Otherwise select current + 1 (without panicking)
201 /// current.map_or(0, |current| current.saturating_add(1))
202 /// });
203 /// ```
204 ///
205 /// For more examples take a look into the source code of [`key_up`](Self::key_up) or [`key_down`](Self::key_down).
206 /// They are implemented with this method.
207 #[deprecated = "renamed to select_relative"]
208 pub fn select_visible_relative<F>(&mut self, change_function: F) -> bool
209 where
210 F: FnOnce(Option<usize>) -> usize,
211 {
212 let identifiers = &self.last_identifiers;
213 let current_identifier = &self.selected;
214 let current_index = identifiers
215 .iter()
216 .position(|identifier| identifier == current_identifier);
217 let new_index = change_function(current_index).min(self.last_biggest_index);
218 let new_identifier = identifiers.get(new_index).cloned().unwrap_or_default();
219 self.select(new_identifier)
220 }
221
222 /// Move the current selection with the direction/amount by the given function.
223 ///
224 /// Returns `true` when the selection changed.
225 ///
226 /// # Example
227 ///
228 /// ```
229 /// # use tui_tree_widget::TreeState;
230 /// # type Identifier = usize;
231 /// # let mut state = TreeState::<Identifier>::default();
232 /// // Move the selection one down
233 /// state.select_relative(|current| {
234 /// // When nothing is currently selected, select index 0
235 /// // Otherwise select current + 1 (without panicking)
236 /// current.map_or(0, |current| current.saturating_add(1))
237 /// });
238 /// ```
239 ///
240 /// For more examples take a look into the source code of [`key_up`](Self::key_up) or [`key_down`](Self::key_down).
241 /// They are implemented with this method.
242 pub fn select_relative<F>(&mut self, change_function: F) -> bool
243 where
244 F: FnOnce(Option<usize>) -> usize,
245 {
246 let identifiers = &self.last_identifiers;
247 let current_identifier = &self.selected;
248 let current_index = identifiers
249 .iter()
250 .position(|identifier| identifier == current_identifier);
251 let new_index = change_function(current_index).min(self.last_biggest_index);
252 let new_identifier = identifiers.get(new_index).cloned().unwrap_or_default();
253 self.select(new_identifier)
254 }
255
256 /// Get the identifier that was rendered for the given position on last render.
257 #[must_use]
258 pub fn rendered_at(&self, position: Position) -> Option<&[Identifier]> {
259 if !self.last_area.contains(position) {
260 return None;
261 }
262
263 self.last_rendered_identifiers
264 .iter()
265 .rev()
266 .find(|(y, _)| position.y >= *y)
267 .map(|(_, identifier)| identifier.as_ref())
268 }
269
270 /// Select what was rendered at the given position on last render.
271 /// When it is already selected, toggle it.
272 ///
273 /// Returns `true` when the state changed.
274 /// Returns `false` when there was nothing at the given position.
275 pub fn click_at(&mut self, position: Position) -> bool {
276 if let Some(identifier) = self.rendered_at(position) {
277 if identifier == self.selected {
278 self.toggle_selected()
279 } else {
280 self.select(identifier.to_vec())
281 }
282 } else {
283 false
284 }
285 }
286
287 /// Ensure the selected [`TreeItem`] is in view on next render
288 pub fn scroll_selected_into_view(&mut self) {
289 self.ensure_selected_in_view_on_next_render = true;
290 }
291
292 /// Scroll the specified amount of lines up
293 ///
294 /// Returns `true` when the scroll position changed.
295 /// Returns `false` when the scrolling has reached the top.
296 pub fn scroll_up(&mut self, lines: usize) -> bool {
297 let before = self.offset;
298 self.offset = self.offset.saturating_sub(lines);
299 before != self.offset
300 }
301
302 /// Scroll the specified amount of lines down
303 ///
304 /// Returns `true` when the scroll position changed.
305 /// Returns `false` when the scrolling has reached the last [`TreeItem`].
306 pub fn scroll_down(&mut self, lines: usize) -> bool {
307 let before = self.offset;
308 self.offset = self
309 .offset
310 .saturating_add(lines)
311 .min(self.last_biggest_index);
312 before != self.offset
313 }
314
315 /// Handles the up arrow key.
316 /// Moves up in the current depth or to its parent.
317 ///
318 /// Returns `true` when the selection changed.
319 pub fn key_up(&mut self) -> bool {
320 self.select_relative(|current| {
321 // When nothing is selected, fall back to end
322 current.map_or(usize::MAX, |current| current.saturating_sub(1))
323 })
324 }
325
326 /// Handles the down arrow key.
327 /// Moves down in the current depth or into a child node.
328 ///
329 /// Returns `true` when the selection changed.
330 pub fn key_down(&mut self) -> bool {
331 self.select_relative(|current| {
332 // When nothing is selected, fall back to start
333 current.map_or(0, |current| current.saturating_add(1))
334 })
335 }
336
337 /// Handles the left arrow key.
338 /// Closes the currently selected or moves to its parent.
339 ///
340 /// Returns `true` when the selection or the open state changed.
341 pub fn key_left(&mut self) -> bool {
342 self.ensure_selected_in_view_on_next_render = true;
343 // Reimplement self.close because of multiple different borrows
344 let mut changed = self.opened.remove(&self.selected);
345 if !changed {
346 // Select the parent by removing the leaf from selection
347 let popped = self.selected.pop();
348 changed = popped.is_some();
349 }
350 changed
351 }
352
353 /// Handles the right arrow key.
354 /// Opens the currently selected.
355 ///
356 /// Returns `true` when it was closed and has been opened.
357 /// Returns `false` when it was already open or nothing being selected.
358 pub fn key_right(&mut self) -> bool {
359 if self.selected.is_empty() {
360 false
361 } else {
362 self.ensure_selected_in_view_on_next_render = true;
363 self.open(self.selected.clone())
364 }
365 }
366}