tui_treelistview/state/
visibility.rs1use std::hash::Hash;
2
3use rustc_hash::{FxBuildHasher, FxHashMap};
4use smallvec::SmallVec;
5
6use crate::context::TreeExpansionState;
7use crate::model::{
8 TreeChildren, TreeFilter, TreeModel, TreeQuery, TreeSelectionFallback, TreeSort,
9};
10use crate::projection::{OccurrencePath, ProjectedNode};
11use crate::traversal::TreeWalk;
12
13use super::{ExpansionPath, TreeListViewState};
14
15impl<Id: Copy + Eq + Hash> TreeListViewState<Id> {
16 pub fn ensure_projection<T, F, S>(&mut self, model: &T, query: &TreeQuery<F, S>) -> bool
20 where
21 T: TreeModel<Id = Id>,
22 F: TreeFilter<T>,
23 S: TreeSort<T>,
24 {
25 let expansion_revision = self.expanded.revision();
26 if self.projection.is_current(model, query, expansion_revision) {
27 return false;
28 }
29
30 let old_index = self.selected_row;
31 let old_path = old_index.and_then(|index| self.projection.occurrence_path(index));
32 let expanded = &self.expanded;
33 self.projection
34 .rebuild(model, query, expansion_revision, |parent, id| {
35 expanded.contains(&ExpansionPath::new(parent, id))
36 });
37 self.restore_selection_after_rebuild(
38 old_index,
39 old_path.as_ref(),
40 query.selection_fallback(),
41 );
42 self.selection_needs_visibility = self.selected.is_some();
43 self.clamp_offsets();
44 true
45 }
46
47 pub fn select_by_id<T, F, S>(&mut self, model: &T, query: &TreeQuery<F, S>, id: Id) -> bool
49 where
50 T: TreeModel<Id = Id>,
51 F: TreeFilter<T>,
52 S: TreeSort<T>,
53 {
54 if !self.expand_to(model, id) {
55 return false;
56 }
57 self.ensure_projection(model, query);
58 if let Some(index) = self.projection.index_of(id) {
59 self.selected = Some(id);
60 self.selected_row = Some(index);
61 self.selection_needs_visibility = true;
62 true
63 } else {
64 false
65 }
66 }
67
68 pub fn expand_to<T: TreeModel<Id = Id>>(&mut self, model: &T, target: Id) -> bool {
70 let hint = model.size_hint();
71 let mut parents = FxHashMap::with_capacity_and_hasher(hint, FxBuildHasher);
72 let mut found = false;
73 for node in TreeWalk::forest(model) {
74 parents.insert(node.id, (node.parent, node.children.is_branch()));
75 if node.id == target {
76 found = true;
77 break;
78 }
79 }
80 if !found {
81 return false;
82 }
83
84 let mut path = SmallVec::<[Id; 16]>::new();
85 let mut cursor = Some(target);
86 while let Some(id) = cursor {
87 path.push(id);
88 cursor = parents.get(&id).and_then(|(parent, _)| *parent);
89 }
90 path.reverse();
91
92 self.expanded.mutate(|expanded| {
93 let mut changed = false;
94 for window in path.windows(2) {
95 let (parent, is_branch) = parents[&window[0]];
96 if is_branch {
97 changed |= expanded.insert(ExpansionPath::new(parent, window[0]));
98 }
99 }
100 changed
101 });
102 true
103 }
104
105 pub fn expand_all<T: TreeModel<Id = Id>>(&mut self, model: &T) -> bool {
107 self.expanded.mutate(|expanded| {
108 let mut changed = false;
109 for node in TreeWalk::forest(model) {
110 if let TreeChildren::Loaded(children) = node.children
111 && !children.is_empty()
112 {
113 changed |= expanded.insert(ExpansionPath::new(node.parent, node.id));
114 }
115 }
116 changed
117 })
118 }
119
120 pub fn collapse_all(&mut self) -> bool {
122 self.expanded.clear()
123 }
124
125 pub fn set_expanded(&mut self, id: Id, parent: Option<Id>, expanded: bool) -> bool {
127 let path = ExpansionPath::new(parent, id);
128 self.expanded.set_membership(path, expanded)
129 }
130
131 #[must_use]
133 pub fn node_is_expanded(&self, id: Id, parent: Option<Id>) -> bool {
134 self.is_expanded(parent, id)
135 }
136
137 #[must_use]
139 pub fn effective_expansion(&self, id: Id) -> Option<TreeExpansionState> {
140 self.projection.get_by_id(id).map(ProjectedNode::expansion)
141 }
142
143 pub fn expanded_paths(&self) -> impl Iterator<Item = (Option<Id>, Id)> + '_ {
145 self.expanded.iter().map(|path| (path.parent, path.id))
146 }
147
148 pub(crate) fn set_expanded_recursive<T: TreeModel<Id = Id>>(
149 &mut self,
150 model: &T,
151 root: Id,
152 parent: Option<Id>,
153 expand: bool,
154 ) -> bool {
155 self.expanded.mutate(|expanded| {
156 let mut changed = false;
157 for node in TreeWalk::subtree(model, parent, root) {
158 let path = ExpansionPath::new(node.parent, node.id);
159 if expand {
160 if matches!(node.children, TreeChildren::Loaded(children) if !children.is_empty())
161 {
162 changed |= expanded.insert(path);
163 }
164 } else {
165 changed |= expanded.remove(&path);
166 }
167 }
168 changed
169 })
170 }
171
172 fn restore_selection_after_rebuild(
173 &mut self,
174 old_index: Option<usize>,
175 old_path: Option<&OccurrencePath<Id>>,
176 fallback: TreeSelectionFallback,
177 ) {
178 if let Some(path) = old_path {
179 if let Some(index) = self.projection.index_of_path(path) {
180 self.select_rebuilt_row(Some(index));
181 return;
182 }
183
184 if let Some(index) = self
185 .selected
186 .and_then(|selected| self.projection.index_of(selected))
187 {
188 self.select_rebuilt_row(Some(index));
189 return;
190 }
191
192 if matches!(fallback, TreeSelectionFallback::ParentThenNearest) {
193 for end in (1..path.len()).rev() {
194 if let Some(index) = self.projection.index_of_path_prefix(path, end) {
195 self.select_rebuilt_row(Some(index));
196 return;
197 }
198 }
199 }
200 } else if let Some(index) = self
201 .selected
202 .and_then(|selected| self.projection.index_of(selected))
203 {
204 self.select_rebuilt_row(Some(index));
205 return;
206 }
207
208 let selected_row = match fallback {
209 TreeSelectionFallback::Clear => None,
210 TreeSelectionFallback::Nearest | TreeSelectionFallback::ParentThenNearest => old_index
211 .and_then(|index| {
212 let index = index.min(self.projection.len().saturating_sub(1));
213 self.projection.nodes().get(index).map(|_| index)
214 }),
215 };
216 self.select_rebuilt_row(selected_row);
217 }
218
219 fn select_rebuilt_row(&mut self, selected_row: Option<usize>) {
220 self.selected = selected_row
221 .and_then(|index| self.projection.nodes().get(index))
222 .map(|node| node.id());
223 self.selected_row = selected_row;
224 }
225
226 fn clamp_offsets(&mut self) {
227 self.offset = self.offset.min(self.projection.len().saturating_sub(1));
228 if self.projection.is_empty() {
229 self.offset = 0;
230 }
231 }
232}