tui_treelistview/
glyphs.rs1use 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}