tui_treelistview/
glyphs.rs

1use std::borrow::Cow;
2
3use ratatui::text::{Line, Span};
4use ratatui::widgets::Cell;
5
6use crate::context::TreeRowContext;
7use crate::model::TreeModel;
8
9#[derive(Clone, Copy)]
10pub struct TreeGlyphs<'a> {
11    pub indent: &'a str,
12    pub branch_last: &'a str,
13    pub branch: &'a str,
14    pub vert: &'a str,
15    pub empty: &'a str,
16    pub leaf: &'a str,
17    pub expanded: &'a str,
18    pub collapsed: &'a str,
19}
20
21impl TreeGlyphs<'static> {
22    pub const fn unicode() -> Self {
23        Self {
24            indent: "   ",
25            branch_last: "└──",
26            branch: "├──",
27            vert: "│  ",
28            empty: "   ",
29            leaf: "•",
30            expanded: "▼",
31            collapsed: "▶",
32        }
33    }
34
35    pub const fn ascii() -> Self {
36        Self {
37            indent: "   ",
38            branch_last: "`--",
39            branch: "|--",
40            vert: "|  ",
41            empty: "   ",
42            leaf: "*",
43            expanded: "v",
44            collapsed: ">",
45        }
46    }
47}
48
49#[derive(Clone)]
50pub struct TreeLabelPrefix<'a> {
51    pub name: &'a str,
52    pub prefix: Option<Cow<'a, str>>,
53}
54
55pub trait TreeLabelProvider<T: TreeModel> {
56    fn label_parts<'a>(&'a self, model: &'a T, id: T::Id) -> TreeLabelPrefix<'a>;
57}
58
59pub trait TreeLabelRenderer<T: TreeModel> {
60    fn cell<'a>(
61        &'a self,
62        model: &'a T,
63        id: T::Id,
64        ctx: &TreeRowContext,
65        glyphs: &TreeGlyphs<'a>,
66    ) -> Cell<'a>;
67}
68
69impl<T, P> TreeLabelRenderer<T> for P
70where
71    T: TreeModel,
72    P: TreeLabelProvider<T>,
73{
74    fn cell<'a>(
75        &'a self,
76        model: &'a T,
77        id: T::Id,
78        ctx: &TreeRowContext,
79        glyphs: &TreeGlyphs<'a>,
80    ) -> Cell<'a> {
81        let parts = self.label_parts(model, id);
82        tree_name_cell(ctx, parts, glyphs)
83    }
84}
85
86pub fn tree_label_line<'a>(
87    ctx: &TreeRowContext<'_>,
88    parts: TreeLabelPrefix<'a>,
89    glyphs: &TreeGlyphs<'a>,
90) -> Line<'a> {
91    let TreeLabelPrefix { name, prefix: op } = parts;
92    let op = op.filter(|value| !value.is_empty());
93
94    if ctx.level == 0 || !ctx.draw_lines {
95        let expander = if ctx.has_children {
96            if ctx.is_expanded {
97                glyphs.expanded
98            } else {
99                glyphs.collapsed
100            }
101        } else if ctx.level == 0 {
102            ""
103        } else {
104            glyphs.leaf
105        };
106
107        let mut spans = Vec::with_capacity(ctx.level as usize + 6);
108        if ctx.level > 0 {
109            for _ in 0..ctx.level {
110                spans.push(Span::raw(glyphs.empty));
111            }
112        }
113        if !expander.is_empty() {
114            spans.push(Span::raw(expander));
115        }
116        if let Some(op) = op {
117            spans.push(Span::raw(op));
118        }
119        spans.push(Span::raw(" "));
120        spans.push(Span::raw(name));
121        return Line::from(spans);
122    }
123
124    let mut name_spans = Vec::with_capacity(ctx.is_tail_stack.len() + 6);
125
126    for (l, is_last) in ctx.is_tail_stack.iter().enumerate() {
127        let part = if l == (ctx.level as usize) - 1 {
128            if *is_last {
129                glyphs.branch_last
130            } else {
131                glyphs.branch
132            }
133        } else if ctx.is_tail_stack[l] {
134            glyphs.indent
135        } else {
136            glyphs.vert
137        };
138        name_spans.push(Span::styled(part, ctx.line_style));
139    }
140
141    let expander = if ctx.has_children {
142        if ctx.is_expanded {
143            glyphs.expanded
144        } else {
145            glyphs.collapsed
146        }
147    } else {
148        glyphs.leaf
149    };
150
151    if !expander.is_empty() {
152        name_spans.push(Span::raw(expander));
153        name_spans.push(Span::raw(" "));
154    }
155
156    if let Some(op) = op {
157        name_spans.push(Span::raw(op));
158        name_spans.push(Span::raw(" "));
159    }
160
161    name_spans.push(Span::raw(name));
162    Line::from(name_spans)
163}
164
165pub fn tree_name_cell<'a>(
166    ctx: &TreeRowContext<'_>,
167    parts: TreeLabelPrefix<'a>,
168    glyphs: &TreeGlyphs<'a>,
169) -> Cell<'a> {
170    Cell::from(tree_label_line(ctx, parts, glyphs))
171}