1use std::hash::Hash;
2use std::ops::Deref;
3
4use ratatui::buffer::Buffer;
5use ratatui::layout::Rect;
6use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::context::TreeMarkState;
12use crate::model::TreeRevision;
13use crate::projection::{ProjectedNode, TreeProjection};
14
15pub use hit::{TreeHit, TreeHitRegion};
16
17mod actions;
18pub mod hit;
19mod marks;
20mod navigation;
21mod visibility;
22
23#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
24struct ExpansionPath<Id> {
25 parent: Option<Id>,
26 id: Id,
27}
28
29impl<Id> ExpansionPath<Id> {
30 const fn new(parent: Option<Id>, id: Id) -> Self {
31 Self { parent, id }
32 }
33}
34
35struct RevisionedSet<T> {
36 values: FxHashSet<T>,
37 revision: TreeRevision,
38}
39
40impl<T: Eq + Hash> RevisionedSet<T> {
41 fn with_capacity(capacity: usize) -> Self {
42 Self {
43 values: FxHashSet::with_capacity_and_hasher(capacity, FxBuildHasher),
44 revision: TreeRevision::INITIAL,
45 }
46 }
47
48 const fn revision(&self) -> TreeRevision {
49 self.revision
50 }
51
52 fn mutate(&mut self, mutation: impl FnOnce(&mut FxHashSet<T>) -> bool) -> bool {
53 let changed = mutation(&mut self.values);
54 if changed {
55 self.revision.advance();
56 }
57 changed
58 }
59
60 fn set_membership(&mut self, value: T, present: bool) -> bool {
61 self.mutate(|values| {
62 if present {
63 values.insert(value)
64 } else {
65 values.remove(&value)
66 }
67 })
68 }
69
70 fn clear(&mut self) -> bool {
71 self.mutate(|values| {
72 if values.is_empty() {
73 false
74 } else {
75 values.clear();
76 true
77 }
78 })
79 }
80
81 fn retain(&mut self, mut keep: impl FnMut(&T) -> bool) -> bool {
82 self.mutate(|values| {
83 let old_len = values.len();
84 values.retain(&mut keep);
85 values.len() != old_len
86 })
87 }
88
89 fn replace(&mut self, values: FxHashSet<T>) -> bool {
90 self.mutate(|current| {
91 if *current == values {
92 false
93 } else {
94 *current = values;
95 true
96 }
97 })
98 }
99}
100
101impl<T> Deref for RevisionedSet<T> {
102 type Target = FxHashSet<T>;
103
104 fn deref(&self) -> &Self::Target {
105 &self.values
106 }
107}
108
109pub struct TreeListViewState<Id> {
111 projection: TreeProjection<Id>,
112 selected: Option<Id>,
113 selected_row: Option<usize>,
114 selection_needs_visibility: bool,
115 offset: usize,
116 selected_column: Option<usize>,
117 column_needs_visibility: bool,
118 horizontal_offset: u16,
119 expanded: RevisionedSet<ExpansionPath<Id>>,
120 manual_marked: RevisionedSet<Id>,
121 mark_states: FxHashMap<Id, TreeMarkState>,
122 mark_stamp: Option<(TreeRevision, TreeRevision)>,
123 draw_lines: bool,
124 pub(crate) hit_map: hit::TreeHitMap,
125 pub(crate) render_buffer: Buffer,
126 #[cfg(feature = "keymap")]
127 keymap: crate::keymap::TreeKeyBindings,
128}
129
130impl<Id: Copy + Eq + Hash> TreeListViewState<Id> {
131 #[must_use]
133 pub fn new() -> Self {
134 Self::with_capacity(0)
135 }
136
137 #[must_use]
139 pub fn with_capacity(capacity: usize) -> Self {
140 Self {
141 projection: TreeProjection::with_capacity(capacity),
142 selected: None,
143 selected_row: None,
144 selection_needs_visibility: false,
145 offset: 0,
146 selected_column: None,
147 column_needs_visibility: false,
148 horizontal_offset: 0,
149 expanded: RevisionedSet::with_capacity(capacity),
150 manual_marked: RevisionedSet::with_capacity(capacity),
151 mark_states: FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher),
152 mark_stamp: None,
153 draw_lines: true,
154 hit_map: hit::TreeHitMap::default(),
155 render_buffer: Buffer::empty(Rect::ZERO),
156 #[cfg(feature = "keymap")]
157 keymap: crate::keymap::TreeKeyBindings::new(),
158 }
159 }
160
161 #[must_use]
163 pub fn from_snapshot(snapshot: TreeListViewSnapshot<Id>) -> Self {
164 let mut state = Self::new();
165 state.restore(snapshot);
166 state
167 }
168
169 #[must_use]
172 pub const fn projection(&self) -> &TreeProjection<Id> {
173 &self.projection
174 }
175
176 #[must_use]
178 pub fn snapshot(&self) -> TreeListViewSnapshot<Id> {
179 TreeListViewSnapshot {
180 expanded: self
181 .expanded
182 .iter()
183 .map(|path| (path.parent, path.id))
184 .collect(),
185 manual_marked: self.manual_marked.iter().copied().collect(),
186 selected: self.selected,
187 selected_column: self.selected_column,
188 offset: self.offset,
189 horizontal_offset: self.horizontal_offset,
190 draw_lines: self.draw_lines,
191 }
192 }
193
194 pub fn restore(&mut self, snapshot: TreeListViewSnapshot<Id>) {
196 self.expanded.replace(
197 snapshot
198 .expanded
199 .into_iter()
200 .map(|(parent, id)| ExpansionPath::new(parent, id))
201 .collect(),
202 );
203 self.manual_marked
204 .replace(snapshot.manual_marked.into_iter().collect());
205 self.selected = snapshot.selected;
206 self.selected_row = None;
207 self.selection_needs_visibility = self.selected.is_some();
208 self.selected_column = snapshot.selected_column;
209 self.column_needs_visibility = self.selected_column.is_some();
210 self.offset = snapshot.offset;
211 self.horizontal_offset = snapshot.horizontal_offset;
212 self.draw_lines = snapshot.draw_lines;
213 }
214
215 #[must_use]
216 pub const fn draw_lines(&self) -> bool {
217 self.draw_lines
218 }
219
220 pub const fn set_draw_lines(&mut self, draw: bool) {
221 self.draw_lines = draw;
222 }
223
224 pub(crate) fn is_expanded(&self, parent: Option<Id>, id: Id) -> bool {
225 self.expanded.contains(&ExpansionPath::new(parent, id))
226 }
227
228 pub(crate) fn mark_state_cached(&self, id: Id) -> TreeMarkState {
229 self.mark_states.get(&id).copied().unwrap_or_default()
230 }
231
232 pub(crate) fn selected_node(&self) -> Option<ProjectedNode<Id>> {
233 let selected = self.selected?;
234 self.selected_row
235 .and_then(|index| self.projection.nodes().get(index))
236 .copied()
237 .filter(|node| node.id() == selected)
238 }
239
240 #[cfg(feature = "keymap")]
241 pub const fn keymap_mut(&mut self) -> &mut crate::keymap::TreeKeyBindings {
243 &mut self.keymap
244 }
245}
246
247impl<Id: Copy + Eq + Hash> Default for TreeListViewState<Id> {
248 fn default() -> Self {
249 Self::new()
250 }
251}
252
253#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
255#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct TreeListViewSnapshot<Id> {
257 pub expanded: Vec<(Option<Id>, Id)>,
258 pub manual_marked: Vec<Id>,
259 pub selected: Option<Id>,
260 pub selected_column: Option<usize>,
261 pub offset: usize,
262 pub horizontal_offset: u16,
263 pub draw_lines: bool,
264}