fret_ui_headless/table/
row_expanding.rs1use std::collections::HashSet;
2use std::iter::FromIterator;
3
4use super::{RowIndex, RowKey, RowModel};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ExpandingState {
14 All,
15 Keys(HashSet<RowKey>),
16}
17
18impl Default for ExpandingState {
19 fn default() -> Self {
20 Self::Keys(HashSet::new())
21 }
22}
23
24impl FromIterator<RowKey> for ExpandingState {
25 fn from_iter<T: IntoIterator<Item = RowKey>>(iter: T) -> Self {
26 Self::Keys(HashSet::from_iter(iter))
27 }
28}
29
30pub fn is_row_expanded(row_key: RowKey, expanded: &ExpandingState) -> bool {
31 match expanded {
32 ExpandingState::All => true,
33 ExpandingState::Keys(keys) => keys.contains(&row_key),
34 }
35}
36
37pub fn is_some_rows_expanded(expanded: &ExpandingState) -> bool {
38 match expanded {
39 ExpandingState::All => true,
40 ExpandingState::Keys(keys) => !keys.is_empty(),
41 }
42}
43
44pub fn set_all_rows_expanded(expanded: &mut ExpandingState, expanded_value: bool) {
45 if expanded_value {
46 *expanded = ExpandingState::All;
47 } else {
48 *expanded = ExpandingState::default();
49 }
50}
51
52pub fn toggle_all_rows_expanded(expanded: &mut ExpandingState, expanded_value: Option<bool>) {
53 let next = expanded_value.unwrap_or(!matches!(expanded, ExpandingState::All));
54 set_all_rows_expanded(expanded, next);
55}
56
57pub fn toggle_row_expanded<'a, TData>(
58 expanded: &mut ExpandingState,
59 row_model: &RowModel<'a, TData>,
60 row_key: RowKey,
61 expanded_value: Option<bool>,
62) {
63 let exists = is_row_expanded(row_key, expanded);
64 let expanded_value = expanded_value.unwrap_or(!exists);
65
66 match expanded {
67 ExpandingState::All => {
68 if expanded_value {
69 return;
70 }
71
72 let mut keys: HashSet<RowKey> = row_model.rows_by_key().keys().copied().collect();
74 keys.remove(&row_key);
75 *expanded = ExpandingState::Keys(keys);
76 }
77 ExpandingState::Keys(keys) => {
78 if expanded_value {
79 keys.insert(row_key);
80 } else {
81 keys.remove(&row_key);
82 }
83 }
84 }
85}
86
87pub fn row_can_expand<TData>(row_model: &RowModel<'_, TData>, row: RowIndex) -> bool {
88 row_model.row(row).is_some_and(|r| !r.sub_rows.is_empty())
89}
90
91pub fn row_is_all_parents_expanded<TData>(
92 row_model: &RowModel<'_, TData>,
93 expanded: &ExpandingState,
94 row: RowIndex,
95) -> bool {
96 let mut current = row_model.row(row);
97 while let Some(r) = current {
98 let Some(parent) = r.parent else {
99 return true;
100 };
101 let Some(parent_row) = row_model.row(parent) else {
102 return true;
103 };
104 if !is_row_expanded(parent_row.key, expanded) {
105 return false;
106 }
107 current = Some(parent_row);
108 }
109 true
110}
111
112pub fn expanded_depth<TData>(row_model: &RowModel<'_, TData>, expanded: &ExpandingState) -> u16 {
113 match expanded {
114 ExpandingState::All => row_model
115 .arena()
116 .iter()
117 .map(|r| r.depth.saturating_add(1))
118 .max()
119 .unwrap_or(0),
120 ExpandingState::Keys(keys) => keys
121 .iter()
122 .filter_map(|k| row_model.row_by_key(*k).and_then(|i| row_model.row(i)))
123 .map(|r| r.depth.saturating_add(1))
124 .max()
125 .unwrap_or(0),
126 }
127}
128
129pub fn expand_row_model<'a, TData>(
137 row_model: &RowModel<'a, TData>,
138 expanded: &ExpandingState,
139 mut row_is_expanded: impl FnMut(RowKey, &TData) -> bool,
140) -> RowModel<'a, TData> {
141 if row_model.root_rows().is_empty() {
142 return row_model.clone();
143 }
144 if !is_some_rows_expanded(expanded) {
145 return row_model.clone();
146 }
147
148 let mut out = row_model.clone();
149 out.root_rows.clear();
150
151 fn push_visible<TData>(
152 source: &RowModel<'_, TData>,
153 row_is_expanded: &mut impl FnMut(RowKey, &TData) -> bool,
154 out: &mut Vec<RowIndex>,
155 row: RowIndex,
156 ) {
157 out.push(row);
158 let Some(r) = source.row(row) else {
159 return;
160 };
161 if r.sub_rows.is_empty() {
162 return;
163 }
164 if !row_is_expanded(r.key, r.original) {
165 return;
166 }
167 for &child in &r.sub_rows {
168 push_visible(source, row_is_expanded, out, child);
169 }
170 }
171
172 for &root in row_model.root_rows() {
173 push_visible(row_model, &mut row_is_expanded, &mut out.root_rows, root);
174 }
175
176 out
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::table::Table;
183
184 #[derive(Debug, Clone)]
185 struct Node {
186 id: u64,
187 children: Vec<Node>,
188 }
189
190 #[test]
191 fn expanded_depth_tracks_max_depth_plus_one() {
192 let data = vec![Node {
193 id: 1,
194 children: vec![Node {
195 id: 10,
196 children: vec![Node {
197 id: 100,
198 children: Vec::new(),
199 }],
200 }],
201 }];
202
203 let table = Table::builder(&data)
204 .get_row_key(|n, _i, _p| RowKey(n.id))
205 .get_sub_rows(|n, _i| Some(n.children.as_slice()))
206 .build();
207 let core = table.core_row_model();
208
209 let expanded = ExpandingState::from_iter([RowKey(1), RowKey(10)]);
210 assert_eq!(expanded_depth(core, &expanded), 2);
211 }
212
213 #[test]
214 fn toggle_all_sets_all_variant() {
215 let mut expanded = ExpandingState::default();
216 toggle_all_rows_expanded(&mut expanded, Some(true));
217 assert!(matches!(expanded, ExpandingState::All));
218 toggle_all_rows_expanded(&mut expanded, Some(false));
219 assert!(matches!(expanded, ExpandingState::Keys(_)));
220 }
221}