1use std::collections::HashSet;
2use std::sync::Arc;
3
4use fret_ui::element::Elements;
5use fret_ui::{ElementContext, UiHost};
6
7pub type TreeItemId = u64;
8
9#[derive(Debug, Clone, Copy)]
10pub struct TreeRowState {
11 pub selected: bool,
12 pub expanded: bool,
13 pub disabled: bool,
14 pub depth: usize,
15 pub has_children: bool,
16}
17
18pub trait TreeRowRenderer<H: UiHost> {
19 fn render_row(
20 &mut self,
21 cx: &mut ElementContext<'_, H>,
22 entry: &TreeEntry,
23 state: TreeRowState,
24 ) -> Elements;
25
26 fn render_trailing(
27 &mut self,
28 _cx: &mut ElementContext<'_, H>,
29 _entry: &TreeEntry,
30 _state: TreeRowState,
31 ) -> Elements {
32 Elements::default()
33 }
34}
35
36impl<H: UiHost, F, R> TreeRowRenderer<H> for F
37where
38 F: FnMut(&mut ElementContext<'_, H>, &TreeEntry, TreeRowState) -> R,
39 R: Into<Elements>,
40{
41 fn render_row(
42 &mut self,
43 cx: &mut ElementContext<'_, H>,
44 entry: &TreeEntry,
45 state: TreeRowState,
46 ) -> Elements {
47 (self)(cx, entry, state).into()
48 }
49}
50
51#[derive(Debug, Clone)]
52pub struct TreeItem {
53 pub id: TreeItemId,
54 pub label: Arc<str>,
55 pub children: Vec<TreeItem>,
56 pub disabled: bool,
57}
58
59impl TreeItem {
60 pub fn new(id: TreeItemId, label: impl Into<Arc<str>>) -> Self {
61 Self {
62 id,
63 label: label.into(),
64 children: Vec::new(),
65 disabled: false,
66 }
67 }
68
69 pub fn disabled(mut self, disabled: bool) -> Self {
70 self.disabled = disabled;
71 self
72 }
73
74 pub fn child(mut self, child: TreeItem) -> Self {
75 self.children.push(child);
76 self
77 }
78
79 pub fn children(mut self, children: impl IntoIterator<Item = TreeItem>) -> Self {
80 self.children = children.into_iter().collect();
81 self
82 }
83
84 pub fn is_folder(&self) -> bool {
85 !self.children.is_empty()
86 }
87}
88
89#[derive(Debug, Default, Clone)]
90pub struct TreeState {
91 pub selected: Option<TreeItemId>,
92 pub expanded: HashSet<TreeItemId>,
93}
94
95#[derive(Debug, Clone)]
96pub struct TreeEntry {
97 pub id: TreeItemId,
98 pub label: Arc<str>,
99 pub depth: usize,
100 pub parent: Option<TreeItemId>,
101 pub has_children: bool,
102 pub disabled: bool,
103}
104
105pub fn flatten_tree(items: &[TreeItem], expanded: &HashSet<TreeItemId>) -> Vec<TreeEntry> {
106 fn walk(
107 out: &mut Vec<TreeEntry>,
108 items: &[TreeItem],
109 expanded: &HashSet<TreeItemId>,
110 depth: usize,
111 parent: Option<TreeItemId>,
112 ) {
113 for item in items {
114 let has_children = item.is_folder();
115 out.push(TreeEntry {
116 id: item.id,
117 label: Arc::clone(&item.label),
118 depth,
119 parent,
120 has_children,
121 disabled: item.disabled,
122 });
123
124 if has_children && expanded.contains(&item.id) {
125 walk(out, &item.children, expanded, depth + 1, Some(item.id));
126 }
127 }
128 }
129
130 let mut out = Vec::new();
131 walk(&mut out, items, expanded, 0, None);
132 out
133}