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 h_anchor(&self) -> HAnchor {
92 self.base.h_anchor
93 }
94 fn v_anchor(&self) -> VAnchor {
95 self.base.v_anchor
96 }
97 fn min_size(&self) -> Size {
98 self.base.min_size
99 }
100 fn max_size(&self) -> Size {
101 self.base.max_size
102 }
103
104 fn layout(&mut self, available: Size) -> Size {
105 Size::new(EXPAND_W, available.height)
106 }
107
108 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
111 if !self.has_children {
112 return;
113 }
114
115 let w = self.bounds.width;
116 let h = self.bounds.height;
117 let cx = w * 0.5;
118 let cy = h * 0.5;
119
120 let v = ctx.visuals();
121 ctx.set_fill_color(Color::rgba(
122 v.text_color.r,
123 v.text_color.g,
124 v.text_color.b,
125 0.55,
126 ));
127 ctx.begin_path();
128 if self.is_expanded {
129 ctx.move_to(cx - 4.5, cy + 2.0);
131 ctx.line_to(cx + 4.5, cy + 2.0);
132 ctx.line_to(cx, cy - 3.0);
133 ctx.close_path();
134 } else {
135 ctx.move_to(cx - 2.5, cy - 4.5);
137 ctx.line_to(cx - 2.5, cy + 4.5);
138 ctx.line_to(cx + 3.5, cy);
139 ctx.close_path();
140 }
141 ctx.fill();
142 }
143
144 fn on_event(&mut self, _: &Event) -> EventResult {
145 EventResult::Ignored
146 }
147}
148
149pub struct NodeIconWidget {
156 bounds: Rect,
157 pub icon: NodeIcon,
158 children: Vec<Box<dyn Widget>>,
159 base: WidgetBase,
160}
161
162impl NodeIconWidget {
163 pub fn new(icon: NodeIcon) -> Self {
164 Self {
165 bounds: Rect::default(),
166 icon,
167 children: Vec::new(),
168 base: WidgetBase::new(),
169 }
170 }
171}
172
173impl Widget for NodeIconWidget {
174 fn type_name(&self) -> &'static str {
175 "NodeIconWidget"
176 }
177 fn bounds(&self) -> Rect {
178 self.bounds
179 }
180 fn set_bounds(&mut self, b: Rect) {
181 self.bounds = b;
182 }
183 fn children(&self) -> &[Box<dyn Widget>] {
184 &self.children
185 }
186 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
187 &mut self.children
188 }
189
190 fn margin(&self) -> Insets {
191 self.base.margin
192 }
193 fn h_anchor(&self) -> HAnchor {
194 self.base.h_anchor
195 }
196 fn v_anchor(&self) -> VAnchor {
197 self.base.v_anchor
198 }
199 fn min_size(&self) -> Size {
200 self.base.min_size
201 }
202 fn max_size(&self) -> Size {
203 self.base.max_size
204 }
205
206 fn layout(&mut self, available: Size) -> Size {
207 Size::new(ICON_W + ICON_GAP, available.height)
208 }
209
210 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
213 let h = self.bounds.height;
214 let iy = (h - ICON_W) * 0.5;
215
216 ctx.set_fill_color(icon_color(self.icon));
217 ctx.begin_path();
218 ctx.rounded_rect(0.0, iy, ICON_W, ICON_W, 2.0);
219 ctx.fill();
220
221 if matches!(self.icon, NodeIcon::Folder) {
222 ctx.begin_path();
224 ctx.rounded_rect(0.0, iy + ICON_W * 0.55, ICON_W * 0.45, ICON_W * 0.5, 1.0);
225 ctx.fill();
226 }
227 }
228
229 fn on_event(&mut self, _: &Event) -> EventResult {
230 EventResult::Ignored
231 }
232}
233
234pub struct TreeRow {
244 bounds: Rect,
245 pub node_idx: usize,
246 pub toggle_local_bounds: Rect,
250 is_selected: bool,
251 is_hovered: bool,
252 focused: bool,
253 children: Vec<Box<dyn Widget>>,
254 base: WidgetBase,
255}
256
257impl TreeRow {
258 #[allow(clippy::too_many_arguments)]
259 pub fn new(
260 node_idx: usize,
261 depth: u32,
262 has_children: bool,
263 is_expanded: bool,
264 is_selected: bool,
265 is_hovered: bool,
266 focused: bool,
267 icon: NodeIcon,
268 label: impl Into<String>,
269 font: Arc<Font>,
270 font_size: f64,
271 indent_width: f64,
272 row_height: f64,
273 ) -> Self {
274 let indent_px = depth as f64 * indent_width;
275 let mut children: Vec<Box<dyn Widget>> = Vec::with_capacity(4);
276 children.push(Box::new(SizedBox::fixed(indent_px, row_height)));
277 children.push(Box::new(ExpandToggle::new(has_children, is_expanded)));
278 children.push(Box::new(NodeIconWidget::new(icon)));
279 children.push(Box::new(Label::new(label, font).with_font_size(font_size)));
280
281 Self {
282 bounds: Rect::default(),
283 node_idx,
284 toggle_local_bounds: Rect::default(),
285 is_selected,
286 is_hovered,
287 focused,
288 children,
289 base: WidgetBase::new(),
290 }
291 }
292}
293
294impl Widget for TreeRow {
295 fn type_name(&self) -> &'static str {
296 "TreeRow"
297 }
298 fn bounds(&self) -> Rect {
299 self.bounds
300 }
301 fn set_bounds(&mut self, b: Rect) {
302 self.bounds = b;
303 }
304 fn children(&self) -> &[Box<dyn Widget>] {
305 &self.children
306 }
307 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
308 &mut self.children
309 }
310
311 fn margin(&self) -> Insets {
312 self.base.margin
313 }
314 fn h_anchor(&self) -> HAnchor {
315 self.base.h_anchor
316 }
317 fn v_anchor(&self) -> VAnchor {
318 self.base.v_anchor
319 }
320 fn min_size(&self) -> Size {
321 self.base.min_size
322 }
323 fn max_size(&self) -> Size {
324 self.base.max_size
325 }
326
327 fn layout(&mut self, available: Size) -> Size {
328 let h = available.height;
329 let total_w = available.width;
330
331 let mut x = 0.0;
334
335 let s0 = self.children[0].layout(Size::new(total_w, h));
337 self.children[0].set_bounds(Rect::new(x, 0.0, s0.width, h));
338 x += s0.width;
339
340 let s1 = self.children[1].layout(Size::new(total_w - x, h));
342 self.children[1].set_bounds(Rect::new(x, 0.0, s1.width, h));
343 self.toggle_local_bounds = Rect::new(x, 0.0, s1.width, h);
344 x += s1.width;
345
346 let s2 = self.children[2].layout(Size::new(total_w - x, h));
348 self.children[2].set_bounds(Rect::new(x, 0.0, s2.width, h));
349 x += s2.width;
350
351 let label_w = (total_w - x).max(0.0);
353 let s3 = self.children[3].layout(Size::new(label_w, h));
354 self.children[3].set_bounds(Rect::new(x, 0.0, s3.width, h));
355
356 Size::new(total_w, h)
357 }
358
359 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
360 let w = self.bounds.width;
361 let h = self.bounds.height;
362 let v = ctx.visuals();
363
364 if self.is_selected {
365 let c = if self.focused {
366 Color::rgba(v.accent.r, v.accent.g, v.accent.b, 0.25)
369 } else {
370 Color::rgba(v.text_color.r, v.text_color.g, v.text_color.b, 0.12)
372 };
373 ctx.set_fill_color(c);
374 ctx.begin_path();
375 ctx.rect(0.0, 0.0, w, h);
376 ctx.fill();
377 } else if self.is_hovered {
378 ctx.set_fill_color(Color::rgba(
379 v.text_color.r,
380 v.text_color.g,
381 v.text_color.b,
382 0.08,
383 ));
384 ctx.begin_path();
385 ctx.rect(0.0, 0.0, w, h);
386 ctx.fill();
387 }
388 }
389
390 fn on_event(&mut self, _: &Event) -> EventResult {
391 EventResult::Ignored
392 }
393}