agg_gui/widgets/tree_view/
row.rs1use std::sync::Arc;
8
9use crate::color::Color;
10use crate::event::{Event, EventResult};
11use crate::geometry::{Rect, Size};
12use crate::draw_ctx::DrawCtx;
13use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
14use crate::text::Font;
15use crate::widget::Widget;
16use crate::widgets::label::Label;
17use crate::widgets::primitives::SizedBox;
18
19use super::node::NodeIcon;
20
21pub const EXPAND_W: f64 = 18.0; pub const ICON_W: f64 = 14.0;
27pub const ICON_GAP: f64 = 4.0;
28
29pub fn icon_color(icon: NodeIcon) -> Color {
35 match icon {
36 NodeIcon::Folder => Color::rgb(0.90, 0.72, 0.20),
37 NodeIcon::File => Color::rgb(0.55, 0.78, 0.95),
38 NodeIcon::Package => Color::rgb(0.70, 0.60, 0.88),
39 }
40}
41
42pub struct ExpandToggle {
52 bounds: Rect,
53 pub has_children: bool,
54 pub is_expanded: bool,
55 children: Vec<Box<dyn Widget>>,
56 base: WidgetBase,
57}
58
59impl ExpandToggle {
60 pub fn new(has_children: bool, is_expanded: bool) -> Self {
61 Self {
62 bounds: Rect::default(),
63 has_children,
64 is_expanded,
65 children: Vec::new(),
66 base: WidgetBase::new(),
67 }
68 }
69}
70
71impl Widget for ExpandToggle {
72 fn type_name(&self) -> &'static str { "ExpandToggle" }
73 fn bounds(&self) -> Rect { self.bounds }
74 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
75 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
76 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
77
78 fn margin(&self) -> Insets { self.base.margin }
79 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
80 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
81 fn min_size(&self) -> Size { self.base.min_size }
82 fn max_size(&self) -> Size { self.base.max_size }
83
84 fn layout(&mut self, available: Size) -> Size {
85 Size::new(EXPAND_W, available.height)
86 }
87
88 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
91 if !self.has_children { return; }
92
93 let w = self.bounds.width;
94 let h = self.bounds.height;
95 let cx = w * 0.5;
96 let cy = h * 0.5;
97
98 ctx.set_fill_color(Color::rgba(0.0, 0.0, 0.0, 0.45));
99 ctx.begin_path();
100 if self.is_expanded {
101 ctx.move_to(cx - 4.5, cy + 2.0);
103 ctx.line_to(cx + 4.5, cy + 2.0);
104 ctx.line_to(cx, cy - 3.0);
105 ctx.close_path();
106 } else {
107 ctx.move_to(cx - 2.5, cy - 4.5);
109 ctx.line_to(cx - 2.5, cy + 4.5);
110 ctx.line_to(cx + 3.5, cy);
111 ctx.close_path();
112 }
113 ctx.fill();
114 }
115
116 fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
117}
118
119pub struct NodeIconWidget {
126 bounds: Rect,
127 pub icon: NodeIcon,
128 children: Vec<Box<dyn Widget>>,
129 base: WidgetBase,
130}
131
132impl NodeIconWidget {
133 pub fn new(icon: NodeIcon) -> Self {
134 Self {
135 bounds: Rect::default(),
136 icon,
137 children: Vec::new(),
138 base: WidgetBase::new(),
139 }
140 }
141}
142
143impl Widget for NodeIconWidget {
144 fn type_name(&self) -> &'static str { "NodeIconWidget" }
145 fn bounds(&self) -> Rect { self.bounds }
146 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
147 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
148 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
149
150 fn margin(&self) -> Insets { self.base.margin }
151 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
152 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
153 fn min_size(&self) -> Size { self.base.min_size }
154 fn max_size(&self) -> Size { self.base.max_size }
155
156 fn layout(&mut self, available: Size) -> Size {
157 Size::new(ICON_W + ICON_GAP, available.height)
158 }
159
160 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
163 let h = self.bounds.height;
164 let iy = (h - ICON_W) * 0.5;
165
166 ctx.set_fill_color(icon_color(self.icon));
167 ctx.begin_path();
168 ctx.rounded_rect(0.0, iy, ICON_W, ICON_W, 2.0);
169 ctx.fill();
170
171 if matches!(self.icon, NodeIcon::Folder) {
172 ctx.begin_path();
174 ctx.rounded_rect(0.0, iy + ICON_W * 0.55, ICON_W * 0.45, ICON_W * 0.5, 1.0);
175 ctx.fill();
176 }
177 }
178
179 fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
180}
181
182pub struct TreeRow {
192 bounds: Rect,
193 pub node_idx: usize,
194 pub toggle_local_bounds: Rect,
198 is_selected: bool,
199 is_hovered: bool,
200 focused: bool,
201 children: Vec<Box<dyn Widget>>,
202 base: WidgetBase,
203}
204
205impl TreeRow {
206 #[allow(clippy::too_many_arguments)]
207 pub fn new(
208 node_idx: usize,
209 depth: u32,
210 has_children: bool,
211 is_expanded: bool,
212 is_selected: bool,
213 is_hovered: bool,
214 focused: bool,
215 icon: NodeIcon,
216 label: impl Into<String>,
217 font: Arc<Font>,
218 font_size: f64,
219 indent_width: f64,
220 row_height: f64,
221 ) -> Self {
222 let indent_px = depth as f64 * indent_width;
223 let mut children: Vec<Box<dyn Widget>> = Vec::with_capacity(4);
224 children.push(Box::new(SizedBox::fixed(indent_px, row_height)));
225 children.push(Box::new(ExpandToggle::new(has_children, is_expanded)));
226 children.push(Box::new(NodeIconWidget::new(icon)));
227 children.push(Box::new(Label::new(label, font).with_font_size(font_size)));
228
229 Self {
230 bounds: Rect::default(),
231 node_idx,
232 toggle_local_bounds: Rect::default(),
233 is_selected,
234 is_hovered,
235 focused,
236 children,
237 base: WidgetBase::new(),
238 }
239 }
240}
241
242impl Widget for TreeRow {
243 fn type_name(&self) -> &'static str { "TreeRow" }
244 fn bounds(&self) -> Rect { self.bounds }
245 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
246 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
247 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
248
249 fn margin(&self) -> Insets { self.base.margin }
250 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
251 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
252 fn min_size(&self) -> Size { self.base.min_size }
253 fn max_size(&self) -> Size { self.base.max_size }
254
255 fn layout(&mut self, available: Size) -> Size {
256 let h = available.height;
257 let total_w = available.width;
258
259 let mut x = 0.0;
262
263 let s0 = self.children[0].layout(Size::new(total_w, h));
265 self.children[0].set_bounds(Rect::new(x, 0.0, s0.width, h));
266 x += s0.width;
267
268 let s1 = self.children[1].layout(Size::new(total_w - x, h));
270 self.children[1].set_bounds(Rect::new(x, 0.0, s1.width, h));
271 self.toggle_local_bounds = Rect::new(x, 0.0, s1.width, h);
272 x += s1.width;
273
274 let s2 = self.children[2].layout(Size::new(total_w - x, h));
276 self.children[2].set_bounds(Rect::new(x, 0.0, s2.width, h));
277 x += s2.width;
278
279 let label_w = (total_w - x).max(0.0);
281 let s3 = self.children[3].layout(Size::new(label_w, h));
282 self.children[3].set_bounds(Rect::new(x, 0.0, s3.width, h));
283
284 Size::new(total_w, h)
285 }
286
287 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
288 let w = self.bounds.width;
289 let h = self.bounds.height;
290 let v = ctx.visuals();
291
292 if self.is_selected {
293 let c = if self.focused {
294 Color::rgba(v.accent.r, v.accent.g, v.accent.b, 0.25)
297 } else {
298 Color::rgba(v.text_color.r, v.text_color.g, v.text_color.b, 0.12)
300 };
301 ctx.set_fill_color(c);
302 ctx.begin_path();
303 ctx.rect(0.0, 0.0, w, h);
304 ctx.fill();
305 } else if self.is_hovered {
306 ctx.set_fill_color(Color::rgba(
307 v.text_color.r, v.text_color.g, v.text_color.b, 0.08,
308 ));
309 ctx.begin_path();
310 ctx.rect(0.0, 0.0, w, h);
311 ctx.fill();
312 }
313 }
314
315 fn on_event(&mut self, _: &Event) -> EventResult { EventResult::Ignored }
316}