hefesto_widgets/popups/tree_popup/
mod.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Style},
5 widgets::StatefulWidget,
6};
7
8use crate::popup::{Popup, PopupSize};
9use crate::popups::AutoSized;
10use crate::tree::{Tree, TreeNode, TreeState};
11use crate::{BadgeStack, BorderType, DotPattern};
12
13#[derive(Clone)]
18pub struct TreePopup<'a> {
19 popup: Popup<'a>,
20 tree: Tree<'a>,
21}
22
23impl<'a> TreePopup<'a> {
24 pub fn new(nodes: Vec<TreeNode<'a>>) -> Self {
25 Self {
26 popup: Popup::new(Color::White)
27 .padding(0)
28 .border_type(BorderType::Rounded),
29 tree: Tree::new(nodes),
30 }
31 }
32
33 pub fn title(mut self, title: &'a str) -> Self {
34 self.popup = self.popup.title(title);
35 self
36 }
37
38 pub fn cursor_style(mut self, style: Style) -> Self {
39 self.tree = self.tree.highlight_style(style);
40 self
41 }
42
43 pub fn border_color(mut self, color: Color) -> Self {
44 self.popup = self.popup.border_color(color);
45 self
46 }
47
48 pub fn border_type(mut self, bt: BorderType) -> Self {
49 self.popup = self.popup.border_type(bt);
50 self
51 }
52
53 pub fn bg_color(mut self, color: Color) -> Self {
54 self.popup = self.popup.bg_color(color);
55 self
56 }
57
58 pub fn width(mut self, w: PopupSize) -> Self {
59 self.popup = self.popup.width(w);
60 self
61 }
62
63 pub fn height(mut self, h: PopupSize) -> Self {
64 self.popup = self.popup.height(h);
65 self
66 }
67
68 pub fn position(mut self, x: u16, y: u16) -> Self {
69 self.popup = self.popup.position(x, y);
70 self
71 }
72
73 pub fn origin(mut self, x: u16, y: u16) -> Self {
74 self.popup = self.popup.origin(x, y);
75 self
76 }
77
78 pub fn header(mut self) -> Self {
79 self.popup = self.popup.header();
80 self
81 }
82
83 pub fn padding(mut self, p: u16) -> Self {
84 self.popup = self.popup.padding(p);
85 self
86 }
87
88 pub fn indent(mut self, n: u16) -> Self {
94 self.tree = self.tree.indent(n);
95 self
96 }
97
98 pub fn expand_icon(mut self, icon: &'a str) -> Self {
99 self.tree = self.tree.expand_icon(icon);
100 self
101 }
102
103 pub fn collapse_icon(mut self, icon: &'a str) -> Self {
104 self.tree = self.tree.collapse_icon(icon);
105 self
106 }
107
108 pub fn leaf_icon(mut self, icon: &'a str) -> Self {
109 self.tree = self.tree.leaf_icon(icon);
110 self
111 }
112
113 pub fn highlight_style(mut self, style: Style) -> Self {
114 self.tree = self.tree.highlight_style(style);
115 self
116 }
117
118 pub fn highlight_symbol(mut self, symbol: &'a str) -> Self {
119 self.tree = self.tree.highlight_symbol(symbol);
120 self
121 }
122
123 pub fn borders(mut self, borders: ratatui::widgets::Borders) -> Self {
124 self.tree = self.tree.borders(borders);
125 self
126 }
127
128 pub fn border_style(mut self, style: Style) -> Self {
129 self.tree = self.tree.border_style(style);
130 self
131 }
132
133 pub fn no_background(mut self) -> Self {
134 self.popup = self.popup.no_background();
135 self
136 }
137
138 pub fn background_dots(mut self, color: Color, symbol: &str, density: u16) -> Self {
139 self.popup = self.popup.background_dots(color, symbol, density);
140 self
141 }
142
143 pub fn background_pattern(mut self, pattern: DotPattern) -> Self {
144 self.popup = self.popup.background_pattern(pattern);
145 self
146 }
147
148 pub fn background_spacing(mut self, density_x: u16, density_y: u16) -> Self {
149 self.popup = self.popup.background_spacing(density_x, density_y);
150 self
151 }
152
153 pub fn badges(mut self, badges: BadgeStack<'a>) -> Self {
154 self.popup = self.popup.badges(badges);
155 self
156 }
157}
158
159impl AutoSized for TreePopup<'_> {
160 type State = TreeState;
161
162 fn auto_height(&self, state: &Self::State, _area: Rect) -> u16 {
163 let header_height: u16 = if self.popup.has_header() { 2 } else { 0 };
164 let tree_height = self.tree.visible_count(&state.expanded) as u16 + 2 + header_height;
165 tree_height.max(5)
166 }
167}
168
169impl<'a> TreePopup<'a> {
170 pub fn item_at(&self, state: &TreeState, popup_rect: Rect, row: u16) -> Option<usize> {
174 let content_y = popup_rect.y + self.popup.content_offset();
175 if row < content_y {
176 return None;
177 }
178 let list_row = (row - content_y) as usize;
179 let idx = list_row + state.scroll_state.list_state.offset();
180 let total = self.tree.visible_count(&state.expanded);
181 if idx < total {
182 Some(idx)
183 } else {
184 None
185 }
186 }
187
188 pub fn resolve_rect(&self, area: Rect, state: &TreeState) -> Rect {
193 if self.popup.get_height() == PopupSize::Auto {
194 self.popup
195 .clone()
196 .height(PopupSize::Fixed(self.auto_height(state, area)))
197 .resolve_rect(area)
198 } else {
199 self.popup.resolve_rect(area)
200 }
201 }
202}
203
204impl StatefulWidget for TreePopup<'_> {
205 type State = TreeState;
206
207 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
208 let auto_h = if self.popup.get_height() == PopupSize::Auto {
209 Some(self.auto_height(state, area))
210 } else {
211 None
212 };
213 let mut popup = self.popup;
214 if let Some(h) = auto_h {
215 popup = popup.height(PopupSize::Fixed(h));
216 }
217 let inner = popup.render_inner(area, buf);
218
219 self.tree.render(inner, buf, state);
220 }
221}
222
223#[cfg(test)]
224mod tests;