1use crate::*;
4
5use crossbeam_channel::Sender;
6use egui::{collapsing_header::CollapsingState, RichText};
7use std::collections::HashSet;
8
9#[derive(Clone, Debug, Default)]
11pub struct Node<T: Entity> {
12 name: String,
14 content: T,
16 open: bool,
18}
19
20impl<T: Entity> Node<T> {
21 fn empty() -> Self {
22 Self {
23 content: T::empty(),
24 open: true,
25 ..Default::default()
26 }
27 }
28
29 fn name(mut self, name: &str) -> Self {
30 self.name = name.to_owned();
31 self
32 }
33
34 fn match_by(&self, pattern: &str) -> bool {
35 self.name.to_lowercase().contains(pattern)
36 }
37}
38
39#[derive(Clone, Debug)]
40pub enum TreeNode<T: Entity> {
44 Root(Node<T>),
45 SubTreeRoot(Node<T>),
46 Leaf(Node<T>),
47}
48
49impl<T: Entity> Default for TreeNode<T> {
50 fn default() -> Self {
52 TreeNode::Leaf(Node::default())
53 }
54}
55
56impl<T: Entity> TreeNode<T> {
57 fn node_name(&self) -> &String {
58 match self {
59 Self::Root(inner) | Self::SubTreeRoot(inner) | Self::Leaf(inner) => &inner.name,
60 }
61 }
62
63 fn open(&self) -> bool {
64 match self {
65 Self::Root(inner) | Self::SubTreeRoot(inner) | Self::Leaf(inner) => inner.open,
66 }
67 }
68
69 fn open_mut(&mut self, open: bool) {
70 match self {
71 Self::Root(inner) | Self::SubTreeRoot(inner) | Self::Leaf(inner) => {
72 inner.open = open;
73 }
74 }
75 }
76
77 fn toggle_collapse_state(&mut self) {
78 match self {
79 Self::Root(inner) | Self::SubTreeRoot(inner) | Self::Leaf(inner) => {
80 inner.open = !inner.open;
81 }
82 }
83 }
84
85 fn selected(&self, selection: &HashSet<T>) -> bool {
86 match self {
87 Self::Root(_) | Self::SubTreeRoot(_) => false,
89 Self::Leaf(inner) => match inner.content.name() {
90 Some(name) => selection
92 .iter()
93 .filter_map(|a| a.name())
94 .collect::<HashSet<&String>>()
95 .contains(name),
96 None => false,
97 },
98 }
99 }
100
101 fn leaf(entity: T) -> TreeNode<T> {
102 TreeNode::Leaf(Node {
103 name: match entity.name() {
104 Some(name) => name.to_owned(),
105 None => EMPTY_NODE_NAME.to_owned(),
106 },
107 content: entity,
108 open: false,
109 })
110 }
111
112 fn subtree_root(group: &str, typ: &ProjectSource) -> TreeNode<T> {
113 TreeNode::SubTreeRoot(Node {
114 name: group.to_owned(),
115 content: T::as_group(group, typ),
116 open: true,
117 })
118 }
119}
120
121#[derive(Clone, Debug, Default)]
123pub struct MkTree<T: Entity>(pub TreeNode<T>, pub Vec<MkTree<T>>);
125
126impl<T: Entity> MkTree<T> {
127 pub fn node_name(&self) -> String {
130 self.0.node_name().clone()
131 }
132
133 fn open(&self) -> bool {
134 self.0.open()
135 }
136
137 fn toggle_collapse_state(&mut self) {
138 self.0.toggle_collapse_state()
139 }
140
141 fn node_content(&self) -> &T {
142 match &self.0 {
143 TreeNode::Root(inner) | TreeNode::SubTreeRoot(inner) | TreeNode::Leaf(inner) => {
144 &inner.content
145 }
146 }
147 }
148
149 fn match_by(&self, pattern: &str) -> bool {
151 match &self.0 {
152 TreeNode::Leaf(inner) => inner.match_by(pattern),
153 TreeNode::SubTreeRoot(_) => self
154 .1
155 .iter()
156 .filter(|n| n.match_by(pattern))
157 .next()
158 .is_some(),
159 TreeNode::Root(_) => true,
160 }
161 }
162
163 fn single_leaf(entity: T) -> Self {
166 Self(TreeNode::leaf(entity), vec![])
167 }
168
169 pub fn leaf_group(group: &str, leaves: Vec<T>, typ: &ProjectSource) -> Self {
171 Self(
172 TreeNode::subtree_root(group, typ),
173 leaves
174 .into_iter()
175 .map(|leaf| Self::single_leaf(leaf))
176 .collect(),
177 )
178 }
179
180 pub fn subtree_group(group: &str, subtrees: Vec<MkTree<T>>, typ: &ProjectSource) -> Self {
181 Self(TreeNode::subtree_root(group, typ), subtrees)
182 }
183
184 pub fn from_node_n_leaves(entity: T, leaves: Vec<T>) -> Self {
186 Self(
187 TreeNode::leaf(entity),
188 leaves.into_iter().map(|c| Self::single_leaf(c)).collect(),
189 )
190 }
191
192 pub fn from_node_n_subtrees(entity: T, subtrees: Vec<MkTree<T>>) -> Self {
193 Self(TreeNode::leaf(entity), subtrees)
194 }
195
196 fn listen(
197 &mut self,
198 ui: &mut egui::Ui,
199 response: egui::Response,
200 selection: &mut HashSet<T>,
201 sender: &Sender<TreeNodeSignal>,
202 ) {
203 if response.clicked() {
204 if !ui.ctx().input(|i| i.modifiers).any() {
205 selection.clear();
206 };
207 match &self.0 {
208 TreeNode::Leaf(_) => {
209 sender
211 .send(TreeNodeSignal::LeafClicked)
212 .expect("Channel of MkTree's selected node response has been disconnected");
213 selection.insert(self.node_content().clone());
215 }
216 _ => {}
218 };
219 };
221 }
222
223 pub fn ui(
225 &mut self,
226 ui: &mut egui::Ui,
227 node_name: &str,
228 depth: usize,
229 filter: &str,
230 selection: &mut HashSet<T>,
231 sender: &Sender<TreeNodeSignal>,
232 ) {
233 let mut state = CollapsingState::load_with_default_open(
234 ui.ctx(),
235 ui.make_persistent_id(node_name),
236 true,
237 );
238
239 state.set_open(self.open());
240
241 let response = state
242 .show_header(ui, |ui| {
243 ui.selectable_label(self.0.selected(selection), node_name)
245 })
249 .body(|ui| self.children_ui(ui, &node_name, depth, filter, selection, sender));
251
252 if response.0.clicked() {
254 self.toggle_collapse_state();
255 };
256
257 self.listen(ui, response.1.inner, selection, sender);
259 }
260
261 fn children_ui(
262 &mut self,
263 ui: &mut egui::Ui,
264 _node_name: &str,
266 depth: usize,
267 filter: &str,
268 selection: &mut HashSet<T>,
269 sender: &Sender<TreeNodeSignal>,
270 ) {
271 let mut tree = std::mem::take(self);
272 tree.1 = tree
274 .1
275 .into_iter()
276 .map(|mut child| {
277 let node_name = child.node_name();
278 if filter.is_empty() || child.match_by(filter) {
279 child.ui(ui, &node_name, depth + 1, filter, selection, sender);
280 };
281 child
282 })
283 .collect();
284 *self = tree
285 }
286
287 fn leaf_len(&self, count: &mut usize) -> usize {
289 if let TreeNode::Leaf(_) = self.0 {
290 *count += 1;
291 };
292 self.1.iter().for_each(|t| {
293 t.leaf_len(count);
294 });
295 *count
296 }
297
298 fn collapse_all(&mut self, open: bool) {
299 match self.0 {
300 TreeNode::Root(_) => {
301 }
303 _ => {
304 self.0.open_mut(open);
305 }
306 }
307 self.1.iter_mut().for_each(|t| t.collapse_all(open));
308 }
309}
310
311#[derive(Debug, Clone, Default)]
312pub struct TreeContainer<T: Entity> {
314 tree: MkTree<T>,
316
317 root_nice_name: String,
318
319 total_leaves: usize,
320
321 filter: String,
322
323 selected_nodes: HashSet<T>,
326
327 batch_collapse: bool,
329}
330
331impl<T: Entity> TreeContainer<T> {
332 pub fn uninitialized_root() -> Self {
333 Self {
334 tree: MkTree(
335 TreeNode::Root(Node::empty().name(TREE_ROOT_UNINITIALIZED_NAME)),
336 vec![],
337 ),
338 ..Default::default()
339 }
340 }
341
342 pub fn subtrees_mut(&mut self, subtrees: Vec<MkTree<T>>, root_name: &str, show_count: bool) {
343 self.tree = MkTree(TreeNode::Root(Node::empty().name(root_name)), subtrees);
344 self.count_leaves();
345 self.make_root_nice_name(root_name, show_count);
346 }
347
348 pub fn selected_nodes(&self) -> &HashSet<T> {
349 &self.selected_nodes
350 }
351
352 pub fn selected_nodes_mut(&mut self, selected_nodes: HashSet<T>) {
353 self.selected_nodes = selected_nodes;
354 }
355
356 pub fn clear_selection(&mut self) {
358 self.selected_nodes = HashSet::new();
359 }
360
361 pub fn filter_ui(&mut self, width: f32, ui: &mut egui::Ui) {
362 ui.horizontal(|ui| {
363 ui.label("Filter by Name:");
364 ui.add(egui::TextEdit::singleline(&mut self.filter).desired_width(width));
365 self.filter = self.filter.to_lowercase();
367 if ui.button("x").clicked() {
368 self.filter.clear();
369 }
370 });
371 }
372
373 pub fn batch_collapse_ui(&mut self, ui: &mut egui::Ui) {
374 let text = RichText::new(if self.batch_collapse {
375 "⏷ Expand Tree"
376 } else {
377 "➖ Collapse Tree"
378 });
379 if ui.button(text).clicked() {
380 self.tree.collapse_all(self.batch_collapse);
381 self.batch_collapse = !self.batch_collapse;
382 };
383 }
384
385 pub fn tree_ui(&mut self, ui: &mut egui::Ui, sender: &Sender<TreeNodeSignal>) {
386 self.tree.ui(
387 ui,
388 &self.root_nice_name,
389 0,
390 &self.filter,
391 &mut self.selected_nodes,
392 sender,
393 )
394 }
395
396 fn count_leaves(&mut self) {
398 self.total_leaves = 0;
400 self.tree.leaf_len(&mut self.total_leaves);
401 }
402
403 fn make_root_nice_name(&mut self, root_name: &str, show_count: bool) {
405 if show_count {
406 self.root_nice_name = format!("{}: total {}", root_name, self.total_leaves);
407 } else {
408 self.root_nice_name = root_name.to_owned();
409 }
410 }
411}