agg_gui/widgets/tree_view/
row.rs1use std::sync::Arc;
8
9use crate::color::Color;
10use crate::draw_ctx::DrawCtx;
11use crate::event::{Event, EventResult};
12use crate::geometry::{Rect, Size};
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 {
73 "ExpandToggle"
74 }
75 fn bounds(&self) -> Rect {
76 self.bounds
77 }
78 fn set_bounds(&mut self, b: Rect) {
79 self.bounds = b;
80 }
81 fn children(&self) -> &[Box<dyn Widget>] {
82 &self.children
83 }
84 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
85 &mut self.children
86 }
87
88 fn margin(&self) -> Insets {
89 self.base.margin
90 }
91 fn widget_base(&self) -> Option<&WidgetBase> {
92 Some(&self.base)
93 }
94 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
95 Some(&mut self.base)
96 }
97 fn h_anchor(&self) -> HAnchor {
98 self.base.h_anchor
99 }
100 fn v_anchor(&self) -> VAnchor {
101 self.base.v_anchor
102 }
103 fn min_size(&self) -> Size {
104 self.base.min_size
105 }
106 fn max_size(&self) -> Size {
107 self.base.max_size
108 }
109
110 fn layout(&mut self, available: Size) -> Size {
111 Size::new(EXPAND_W, available.height)
112 }
113
114 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
117 if !self.has_children {
118 return;
119 }
120
121 let w = self.bounds.width;
122 let h = self.bounds.height;
123 let cx = w * 0.5;
124 let cy = h * 0.5;
125
126 let v = ctx.visuals();
127 ctx.set_fill_color(Color::rgba(
128 v.text_color.r,
129 v.text_color.g,
130 v.text_color.b,
131 0.55,
132 ));
133 ctx.begin_path();
134 if self.is_expanded {
135 ctx.move_to(cx - 4.5, cy + 2.0);
137 ctx.line_to(cx + 4.5, cy + 2.0);
138 ctx.line_to(cx, cy - 3.0);
139 ctx.close_path();
140 } else {
141 ctx.move_to(cx - 2.5, cy - 4.5);
143 ctx.line_to(cx - 2.5, cy + 4.5);
144 ctx.line_to(cx + 3.5, cy);
145 ctx.close_path();
146 }
147 ctx.fill();
148 }
149
150 fn on_event(&mut self, _: &Event) -> EventResult {
151 EventResult::Ignored
152 }
153}
154
155pub struct NodeIconWidget {
162 bounds: Rect,
163 pub icon: NodeIcon,
164 children: Vec<Box<dyn Widget>>,
165 base: WidgetBase,
166}
167
168impl NodeIconWidget {
169 pub fn new(icon: NodeIcon) -> Self {
170 Self {
171 bounds: Rect::default(),
172 icon,
173 children: Vec::new(),
174 base: WidgetBase::new(),
175 }
176 }
177}
178
179impl Widget for NodeIconWidget {
180 fn type_name(&self) -> &'static str {
181 "NodeIconWidget"
182 }
183 fn bounds(&self) -> Rect {
184 self.bounds
185 }
186 fn set_bounds(&mut self, b: Rect) {
187 self.bounds = b;
188 }
189 fn children(&self) -> &[Box<dyn Widget>] {
190 &self.children
191 }
192 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
193 &mut self.children
194 }
195
196 fn margin(&self) -> Insets {
197 self.base.margin
198 }
199 fn widget_base(&self) -> Option<&WidgetBase> {
200 Some(&self.base)
201 }
202 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
203 Some(&mut self.base)
204 }
205 fn h_anchor(&self) -> HAnchor {
206 self.base.h_anchor
207 }
208 fn v_anchor(&self) -> VAnchor {
209 self.base.v_anchor
210 }
211 fn min_size(&self) -> Size {
212 self.base.min_size
213 }
214 fn max_size(&self) -> Size {
215 self.base.max_size
216 }
217
218 fn layout(&mut self, available: Size) -> Size {
219 Size::new(ICON_W + ICON_GAP, available.height)
220 }
221
222 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
225 let h = self.bounds.height;
226 let iy = (h - ICON_W) * 0.5;
227
228 ctx.set_fill_color(icon_color(self.icon));
229 ctx.begin_path();
230 ctx.rounded_rect(0.0, iy, ICON_W, ICON_W, 2.0);
231 ctx.fill();
232
233 if matches!(self.icon, NodeIcon::Folder) {
234 ctx.begin_path();
236 ctx.rounded_rect(0.0, iy + ICON_W * 0.55, ICON_W * 0.45, ICON_W * 0.5, 1.0);
237 ctx.fill();
238 }
239 }
240
241 fn on_event(&mut self, _: &Event) -> EventResult {
242 EventResult::Ignored
243 }
244}
245
246pub struct TreeRow {
256 bounds: Rect,
257 pub node_idx: usize,
258 pub toggle_local_bounds: Rect,
262 is_selected: bool,
263 is_hovered: bool,
264 focused: bool,
265 children: Vec<Box<dyn Widget>>,
266 base: WidgetBase,
267}
268
269impl TreeRow {
270 #[allow(clippy::too_many_arguments)]
271 pub fn new(
272 node_idx: usize,
273 depth: u32,
274 has_children: bool,
275 is_expanded: bool,
276 is_selected: bool,
277 is_hovered: bool,
278 focused: bool,
279 icon: NodeIcon,
280 label: impl Into<String>,
281 font: Arc<Font>,
282 font_size: f64,
283 indent_width: f64,
284 row_height: f64,
285 ) -> Self {
286 let indent_px = depth as f64 * indent_width;
287 let mut children: Vec<Box<dyn Widget>> = Vec::with_capacity(4);
288 children.push(Box::new(SizedBox::fixed(indent_px, row_height)));
289 children.push(Box::new(ExpandToggle::new(has_children, is_expanded)));
290 children.push(Box::new(NodeIconWidget::new(icon)));
291 children.push(Box::new(Label::new(label, font).with_font_size(font_size)));
292
293 Self {
294 bounds: Rect::default(),
295 node_idx,
296 toggle_local_bounds: Rect::default(),
297 is_selected,
298 is_hovered,
299 focused,
300 children,
301 base: WidgetBase::new(),
302 }
303 }
304}
305
306impl Widget for TreeRow {
307 fn type_name(&self) -> &'static str {
308 "TreeRow"
309 }
310 fn bounds(&self) -> Rect {
311 self.bounds
312 }
313 fn set_bounds(&mut self, b: Rect) {
314 self.bounds = b;
315 }
316 fn children(&self) -> &[Box<dyn Widget>] {
317 &self.children
318 }
319 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
320 &mut self.children
321 }
322
323 fn margin(&self) -> Insets {
324 self.base.margin
325 }
326 fn widget_base(&self) -> Option<&WidgetBase> {
327 Some(&self.base)
328 }
329 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
330 Some(&mut self.base)
331 }
332 fn h_anchor(&self) -> HAnchor {
333 self.base.h_anchor
334 }
335 fn v_anchor(&self) -> VAnchor {
336 self.base.v_anchor
337 }
338 fn min_size(&self) -> Size {
339 self.base.min_size
340 }
341 fn max_size(&self) -> Size {
342 self.base.max_size
343 }
344
345 fn layout(&mut self, available: Size) -> Size {
346 let h = available.height;
347 let total_w = available.width;
348
349 let mut x = 0.0;
352
353 let s0 = self.children[0].layout(Size::new(total_w, h));
355 self.children[0].set_bounds(Rect::new(x, 0.0, s0.width, h));
356 x += s0.width;
357
358 let s1 = self.children[1].layout(Size::new(total_w - x, h));
360 self.children[1].set_bounds(Rect::new(x, 0.0, s1.width, h));
361 self.toggle_local_bounds = Rect::new(x, 0.0, s1.width, h);
362 x += s1.width;
363
364 let s2 = self.children[2].layout(Size::new(total_w - x, h));
366 self.children[2].set_bounds(Rect::new(x, 0.0, s2.width, h));
367 x += s2.width;
368
369 let label_w = (total_w - x).max(0.0);
371 let s3 = self.children[3].layout(Size::new(label_w, h));
372 self.children[3].set_bounds(Rect::new(x, 0.0, s3.width, h));
373
374 Size::new(total_w, h)
375 }
376
377 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
378 let w = self.bounds.width;
379 let h = self.bounds.height;
380 let v = ctx.visuals();
381
382 if self.is_selected {
383 let c = if self.focused {
384 Color::rgba(v.accent.r, v.accent.g, v.accent.b, 0.25)
387 } else {
388 Color::rgba(v.text_color.r, v.text_color.g, v.text_color.b, 0.12)
390 };
391 ctx.set_fill_color(c);
392 ctx.begin_path();
393 ctx.rect(0.0, 0.0, w, h);
394 ctx.fill();
395 } else if self.is_hovered {
396 ctx.set_fill_color(Color::rgba(
397 v.text_color.r,
398 v.text_color.g,
399 v.text_color.b,
400 0.08,
401 ));
402 ctx.begin_path();
403 ctx.rect(0.0, 0.0, w, h);
404 ctx.fill();
405 }
406 }
407
408 fn on_event(&mut self, _: &Event) -> EventResult {
409 EventResult::Ignored
410 }
411}