Skip to main content

sequoia_view/
tree.rs

1use std::rc::Rc;
2
3use sequoia_core::Length;
4use sequoia_layout::LayoutStyle;
5use sequoia_reactive::Signal;
6use sequoia_style::TreeStyle;
7
8use crate::any::AnyView;
9use crate::scroll_view::ScrollbarVisibility;
10use crate::view::{EventCtx, View};
11use crate::views::{Row, Spacer, Text, Unit};
12use crate::virtual_list::VirtualList;
13
14use sequoia_input::{Event, PointerButton, PointerEvent};
15
16#[derive(Clone)]
17pub struct TreeNode<T> {
18    pub id: String,
19    pub value: T,
20    pub children: Vec<TreeNode<T>>,
21    pub expanded: Signal<bool>,
22}
23
24impl<T: Clone> TreeNode<T> {
25    pub fn leaf(id: impl Into<String>, value: T) -> Self {
26        Self {
27            id: id.into(),
28            value,
29            children: Vec::new(),
30            expanded: Signal::new(false),
31        }
32    }
33
34    pub fn branch(id: impl Into<String>, value: T, children: Vec<TreeNode<T>>) -> Self {
35        Self {
36            id: id.into(),
37            value,
38            children,
39            expanded: Signal::new(true),
40        }
41    }
42
43    pub fn expanded(self, expanded: bool) -> Self {
44        self.expanded.set(expanded);
45        self
46    }
47}
48
49#[derive(Clone)]
50pub struct FlatRow<T> {
51    pub id: String,
52    pub depth: usize,
53    pub has_children: bool,
54    pub expanded: Signal<bool>,
55    pub value: T,
56}
57
58pub struct TreeView<T> {
59    roots: Rc<dyn Fn() -> Vec<TreeNode<T>>>,
60    row: Rc<dyn Fn(&T, usize) -> AnyView>,
61    row_height: f32,
62    indent: f32,
63    overscan: usize,
64    scrollbar: ScrollbarVisibility,
65    style: Option<TreeStyle>,
66    label: String,
67}
68
69impl<T: Clone + 'static> Clone for TreeView<T> {
70    fn clone(&self) -> Self {
71        Self {
72            roots: self.roots.clone(),
73            row: self.row.clone(),
74            row_height: self.row_height,
75            indent: self.indent,
76            overscan: self.overscan,
77            scrollbar: self.scrollbar,
78            style: self.style,
79            label: self.label.clone(),
80        }
81    }
82}
83
84impl<T: Clone + 'static> TreeView<T> {
85    pub fn new(roots: impl Fn() -> Vec<TreeNode<T>> + 'static) -> Self {
86        Self {
87            roots: Rc::new(roots),
88            row: Rc::new(|_, _| AnyView::new(Unit)),
89            row_height: 28.0,
90            indent: 16.0,
91            overscan: 4,
92            scrollbar: ScrollbarVisibility::Auto,
93            style: None,
94            label: String::new(),
95        }
96    }
97
98    pub fn label(mut self, label: impl Into<String>) -> Self {
99        self.label = label.into();
100        self
101    }
102
103    pub fn style(mut self, style: TreeStyle) -> Self {
104        self.style = Some(style);
105        self
106    }
107
108    fn font_size(&self) -> f32 {
109        self.style.and_then(|s| s.font_size).unwrap_or(12.0)
110    }
111
112    fn arrow_color(&self) -> Option<sequoia_core::Color> {
113        self.style.and_then(|s| s.arrow_color)
114    }
115
116    pub fn row<V: View + 'static>(mut self, f: impl Fn(&T, usize) -> V + 'static) -> Self {
117        self.row = Rc::new(move |v, d| AnyView::new(f(v, d)));
118        self
119    }
120
121    pub fn row_height(mut self, h: f32) -> Self {
122        self.row_height = h.max(1.0);
123        self
124    }
125
126    pub fn indent(mut self, px: f32) -> Self {
127        self.indent = px.max(0.0);
128        self
129    }
130
131    pub fn overscan(mut self, n: usize) -> Self {
132        self.overscan = n;
133        self
134    }
135
136    pub fn flatten(&self) -> Vec<FlatRow<T>> {
137        fn walk<T: Clone>(nodes: &[TreeNode<T>], depth: usize, out: &mut Vec<FlatRow<T>>) {
138            for n in nodes {
139                out.push(FlatRow {
140                    id: n.id.clone(),
141                    depth,
142                    has_children: !n.children.is_empty(),
143                    expanded: n.expanded.clone(),
144                    value: n.value.clone(),
145                });
146                if !n.children.is_empty() && n.expanded.get_untracked() {
147                    walk(&n.children, depth + 1, out);
148                }
149            }
150        }
151
152        let roots = (self.roots)();
153        let mut out = Vec::new();
154        walk(&roots, 0, &mut out);
155        for r in &out {
156            let _ = r.expanded.get();
157        }
158        out
159    }
160}
161
162#[derive(Clone)]
163struct ArrowBtn {
164    expanded: Signal<bool>,
165    has_children: bool,
166    size: f32,
167    color: Option<sequoia_core::Color>,
168}
169
170impl View for ArrowBtn {
171    fn body(&self) -> impl View + 'static {
172        Unit
173    }
174
175    fn children_override(&self) -> Option<Vec<AnyView>> {
176        let glyph = if !self.has_children {
177            "  "
178        } else if self.expanded.get() {
179            "▾ "
180        } else {
181            "▸ "
182        };
183        let mut txt = Text::new(glyph).size(self.size);
184        if let Some(color) = self.color {
185            txt = txt.color(color);
186        }
187        Some(vec![AnyView::new(txt)])
188    }
189
190    fn layout_style(&self) -> LayoutStyle {
191        LayoutStyle::default()
192    }
193
194    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
195        match event {
196            Event::Pointer(PointerEvent::Up {
197                button: PointerButton::Left,
198                pos,
199                ..
200            }) if ctx.rect.contains(*pos) && self.has_children => {
201                self.expanded.update(|e| *e = !*e);
202                ctx.handled();
203            }
204            _ => {}
205        }
206    }
207}
208
209impl<T: Clone + 'static> View for TreeView<T> {
210    fn body(&self) -> impl View + 'static {
211        Unit
212    }
213
214    fn accessibility(&self) -> Option<crate::a11y::Accessible> {
215        Some(crate::a11y::Accessible::new(
216            crate::a11y::AccessibleRole::Tree,
217            self.label.clone(),
218        ))
219    }
220
221    fn children_override(&self) -> Option<Vec<AnyView>> {
222        let me = self.clone();
223        let indent = self.indent;
224        let row_fn2 = self.row.clone();
225        let font_size = self.font_size();
226        let arrow_color = self.arrow_color();
227        let list = VirtualList::new(move || me.flatten())
228            .key(|r: &FlatRow<T>| {
229                let mut h = 0u64;
230                for b in r.id.bytes() {
231                    h = h.wrapping_mul(31).wrapping_add(b as u64);
232                }
233                h
234            })
235            .item(move |r: &FlatRow<T>| {
236                let pad = (r.depth as f32) * indent;
237                Row::new()
238                    .gap(2.0)
239                    .child(Spacer::fixed_width(pad))
240                    .child(ArrowBtn {
241                        expanded: r.expanded.clone(),
242                        has_children: r.has_children,
243                        size: font_size,
244                        color: arrow_color,
245                    })
246                    .child_flex(row_fn2(&r.value, r.depth), 1.0)
247            })
248            .item_height(self.row_height)
249            .overscan(self.overscan)
250            .scrollbar_visibility(self.scrollbar);
251        Some(vec![AnyView::new(list)])
252    }
253
254    fn layout_style(&self) -> LayoutStyle {
255        LayoutStyle {
256            width: Length::Fill(1.0),
257            height: Length::Fill(1.0),
258            ..LayoutStyle::default()
259        }
260    }
261}