agg_gui/widgets/inspector/
widget_impl.rs1use std::sync::Arc;
7
8use crate::color::Color;
9use crate::draw_ctx::DrawCtx;
10use crate::event::{Event, EventResult, MouseButton};
11use crate::geometry::{Rect, Size};
12use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
13use crate::widget::{InspectorOverlay, Widget};
14use crate::widgets::tree_view::{NodeIcon, TreeNode};
15
16use super::{
17 c_border, c_dim_text, c_header_bg, c_panel_bg, c_props_bg, c_split_bg, c_text, InspectorPanel,
18 HEADER_H, MIN_PROPS_H, MIN_TREE_H,
19};
20
21impl Widget for InspectorPanel {
22 fn type_name(&self) -> &'static str {
23 "InspectorPanel"
24 }
25 fn bounds(&self) -> Rect {
26 self.bounds
27 }
28 fn set_bounds(&mut self, b: Rect) {
29 self.bounds = b;
30 }
31 fn children(&self) -> &[Box<dyn Widget>] {
32 &self._children
33 }
34 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
35 &mut self._children
36 }
37
38 fn margin(&self) -> Insets {
39 self.base.margin
40 }
41 fn widget_base(&self) -> Option<&WidgetBase> {
42 Some(&self.base)
43 }
44 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
45 Some(&mut self.base)
46 }
47 fn h_anchor(&self) -> HAnchor {
48 self.base.h_anchor
49 }
50 fn v_anchor(&self) -> VAnchor {
51 self.base.v_anchor
52 }
53 fn min_size(&self) -> Size {
54 self.base.min_size
55 }
56 fn max_size(&self) -> Size {
57 self.base.max_size
58 }
59
60 fn layout(&mut self, available: Size) -> Size {
61 self.bounds.width = available.width;
62 self.bounds.height = available.height;
63
64 let nodes = self.nodes.borrow();
65 let nodes_fingerprint = (nodes.as_ptr() as usize, nodes.len());
71 let pending_state = self.pending_expanded.is_some() || self.pending_selected.is_some();
72 let nodes_unchanged = !pending_state
73 && self.last_inspector_nodes_fingerprint == Some(nodes_fingerprint)
74 && !self.tree_view.nodes.is_empty();
75
76 if !nodes_unchanged {
77 let mut old_expanded: Vec<bool> =
79 self.tree_view.nodes.iter().map(|n| n.is_expanded).collect();
80 let mut old_selected: Vec<bool> =
81 self.tree_view.nodes.iter().map(|n| n.is_selected).collect();
82 if let Some(pe) = self.pending_expanded.take() {
83 old_expanded = pe;
84 }
85 if let Some(ps) = self.pending_selected.take() {
86 old_selected = vec![false; old_expanded.len().max(ps.map(|i| i + 1).unwrap_or(0))];
87 if let Some(i) = ps {
88 if i < old_selected.len() {
89 old_selected[i] = true;
90 }
91 }
92 }
93
94 self.tree_view.nodes.clear();
95
96 let mut depth_stack: Vec<usize> = Vec::new();
99 let mut per_parent_counts: std::collections::HashMap<Option<usize>, u32> =
100 std::collections::HashMap::new();
101
102 for (orig_idx, node) in nodes.iter().enumerate() {
103 let parent = if node.depth == 0 {
104 None
105 } else {
106 depth_stack.get(node.depth.saturating_sub(1)).copied()
107 };
108 let order = {
109 let cnt = per_parent_counts.entry(parent).or_insert(0);
110 let o = *cnt;
111 *cnt += 1;
112 o
113 };
114 let b = &node.screen_bounds;
115 let label = format!("{} {:.0}×{:.0}", node.type_name, b.width, b.height);
116 let tv_idx = self.tree_view.nodes.len();
117 self.tree_view
118 .nodes
119 .push(TreeNode::new(label, NodeIcon::Package, parent, order));
120 self.tree_view.nodes[tv_idx].is_expanded =
121 old_expanded.get(orig_idx).copied().unwrap_or(true);
122 self.tree_view.nodes[tv_idx].is_selected =
123 old_selected.get(orig_idx).copied().unwrap_or(false);
124 if depth_stack.len() <= node.depth {
125 depth_stack.resize(node.depth + 1, 0);
126 }
127 depth_stack[node.depth] = tv_idx;
128 }
129 self.last_inspector_nodes_fingerprint = Some(nodes_fingerprint);
130 }
131
132 self.selected = self.tree_view.nodes.iter().position(|n| n.is_selected);
133
134 *self.hovered_bounds.borrow_mut() = self
135 .tree_view
136 .hovered_node_idx()
137 .and_then(|i| nodes.get(i))
138 .map(|n| InspectorOverlay {
139 bounds: n.screen_bounds,
140 margin: n.margin,
141 padding: n.padding,
142 });
143
144 let tree_w = available.width;
145 let tree_bot = self.tree_origin_y();
146 let tree_top = self.list_area_h();
147 let tree_h = (tree_top - tree_bot).max(0.0);
148 self.tree_view
149 .set_bounds(Rect::new(0.0, tree_bot, tree_w, tree_h));
150 self.tree_view.layout(Size::new(tree_w, tree_h));
151
152 self._children[0].set_bounds(self.tree_view.bounds());
155
156 if let Some(cell) = &self.snapshot_out {
157 *cell.borrow_mut() = Some(self.saved_state());
158 }
159
160 available
161 }
162
163 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
164 let w = self.bounds.width;
165 let h = self.bounds.height;
166 let sy = self.split_y();
167 let hdr_y = h - HEADER_H;
168 let v = ctx.visuals().clone();
169
170 ctx.set_fill_color(c_panel_bg(&v));
172 ctx.begin_path();
173 ctx.rect(0.0, 0.0, w, h);
174 ctx.fill();
175
176 ctx.set_stroke_color(c_border(&v));
177 ctx.set_line_width(1.0);
178 ctx.begin_path();
179 ctx.move_to(0.0, 0.0);
180 ctx.line_to(0.0, h);
181 ctx.stroke();
182
183 ctx.set_fill_color(c_header_bg(&v));
185 ctx.begin_path();
186 ctx.rect(0.0, hdr_y, w, HEADER_H);
187 ctx.fill();
188
189 ctx.set_stroke_color(c_border(&v));
190 ctx.set_line_width(1.0);
191 ctx.begin_path();
192 ctx.move_to(0.0, hdr_y);
193 ctx.line_to(w, hdr_y);
194 ctx.stroke();
195
196 ctx.set_font(Arc::clone(&self.font));
197 ctx.set_font_size(13.0);
198 ctx.set_fill_color(c_text(&v));
199 let title = "Widget Inspector";
200 if let Some(m) = ctx.measure_text(title) {
201 ctx.fill_text(
202 title,
203 12.0,
204 hdr_y + (HEADER_H - m.ascent - m.descent) * 0.5 + m.descent,
205 );
206 }
207
208 let count_txt = format!("{} widgets", self.nodes.borrow().len());
209 ctx.set_font_size(11.0);
210 ctx.set_fill_color(c_dim_text(&v));
211 if let Some(m) = ctx.measure_text(&count_txt) {
212 ctx.fill_text(
213 &count_txt,
214 w - m.width - 10.0,
215 hdr_y + (HEADER_H - m.ascent - m.descent) * 0.5 + m.descent,
216 );
217 }
218
219 ctx.set_fill_color(c_props_bg(&v));
221 ctx.begin_path();
222 ctx.rect(0.0, 0.0, w, sy - 2.0);
223 ctx.fill();
224 self.paint_properties(ctx, sy - 2.0);
225
226 ctx.set_fill_color(c_split_bg(&v));
228 ctx.begin_path();
229 ctx.rect(0.0, sy - 2.0, w, 4.0);
230 ctx.fill();
231 ctx.set_stroke_color(c_border(&v));
232 ctx.set_line_width(1.0);
233 ctx.begin_path();
234 ctx.move_to(0.0, sy);
235 ctx.line_to(w, sy);
236 ctx.stroke();
237
238 let tree_bot = self.tree_origin_y();
240 let tree_top = self.list_area_h();
241 let tree_h = (tree_top - tree_bot).max(0.0);
242 if tree_h > 0.0 {
243 ctx.save();
244 ctx.translate(0.0, tree_bot);
245 ctx.clip_rect(0.0, 0.0, w, tree_h);
246 crate::widget::paint_subtree(&mut self.tree_view, ctx);
247 ctx.restore();
248 }
249 }
250
251 fn paint_global_overlay(&mut self, ctx: &mut dyn DrawCtx) {
262 let Some(overlay) = *self.hovered_bounds.borrow() else {
263 return;
264 };
265
266 let mut ox = 0.0;
267 let mut oy = 0.0;
268 ctx.root_transform().transform(&mut ox, &mut oy);
269 let scale = crate::device_scale::device_scale().max(1e-6);
270 let ox = ox / scale;
271 let oy = oy / scale;
272
273 paint_inspector_overlay(ctx, overlay, ox, oy);
274 }
275
276 fn on_event(&mut self, event: &Event) -> EventResult {
277 match event {
278 Event::MouseDown {
279 pos,
280 button: MouseButton::Left,
281 ..
282 } => {
283 if pos.y < self.split_y() - 2.0 && self.try_emit_base_edit_from_click(*pos) {
284 return EventResult::Consumed;
285 }
286 #[cfg(feature = "reflect")]
287 if pos.y < self.split_y() - 2.0 && self.try_emit_edit_from_click(*pos) {
288 return EventResult::Consumed;
289 }
290 if self.on_split_handle(*pos) {
291 self.split_dragging = true;
292 return EventResult::Consumed;
293 }
294 if self.pos_in_tree_area(*pos) {
295 return self.forward_to_tree(event);
296 }
297 EventResult::Ignored
298 }
299 Event::MouseMove { pos } => {
300 if self.split_dragging {
301 self.props_h = pos.y.clamp(
302 MIN_PROPS_H,
303 (self.list_area_h() - MIN_TREE_H).max(MIN_PROPS_H),
304 );
305 crate::animation::request_draw();
306 return EventResult::Consumed;
307 }
308 if self.pos_in_tree_area(*pos) {
309 let _ = self.forward_to_tree(event);
310 } else {
311 self.tree_view.clear_hover();
317 }
318 self.update_hovered_bounds_from_tree();
319 EventResult::Ignored
320 }
321 Event::MouseUp {
322 button: MouseButton::Left,
323 pos,
324 ..
325 } => {
326 if self.split_dragging {
327 self.split_dragging = false;
328 crate::animation::request_draw();
329 return EventResult::Consumed;
330 }
331 if self.pos_in_tree_area(*pos) {
332 return self.forward_to_tree(event);
333 }
334 EventResult::Ignored
335 }
336 Event::MouseWheel { pos, .. } if self.pos_in_tree_area(*pos) => {
337 self.forward_to_tree(event)
338 }
339 _ => EventResult::Ignored,
340 }
341 }
342}
343
344fn paint_inspector_overlay(ctx: &mut dyn DrawCtx, overlay: InspectorOverlay, ox: f64, oy: f64) {
349 let b = overlay.bounds;
350 let m = overlay.margin;
351 let p = overlay.padding;
352
353 let cx = b.x - ox;
354 let cy = b.y - oy;
355 let cw = b.width;
356 let ch = b.height;
357
358 let m_total = m.left + m.right + m.top + m.bottom;
360 if m_total > 0.0 {
361 let mx = cx - m.left;
362 let my = cy - m.bottom;
363 let mw = cw + m.left + m.right;
364 let mh = ch + m.top + m.bottom;
365 ctx.set_fill_color(Color::rgba(0.99, 0.61, 0.20, 0.30));
366 ctx.begin_path();
367 ctx.rect(mx, my, mw, mh);
368 ctx.fill();
369 }
370
371 ctx.set_fill_color(Color::rgba(0.42, 0.66, 1.0, 0.30));
373 ctx.begin_path();
374 ctx.rect(cx, cy, cw, ch);
375 ctx.fill();
376
377 let p_total = p.left + p.right + p.top + p.bottom;
379 if p_total > 0.0 {
380 let px = cx + p.left;
381 let py = cy + p.bottom;
382 let pw = (cw - p.left - p.right).max(0.0);
383 let ph = (ch - p.top - p.bottom).max(0.0);
384 if pw > 0.0 && ph > 0.0 {
385 ctx.set_fill_color(Color::rgba(0.55, 0.86, 0.55, 0.35));
386 ctx.begin_path();
387 ctx.rect(px, py, pw, ph);
388 ctx.fill();
389 }
390 }
391
392 ctx.set_stroke_color(Color::rgba(0.10, 0.45, 0.95, 0.90));
395 ctx.set_line_width(1.0);
396 ctx.begin_path();
397 ctx.rect(cx, cy, cw, ch);
398 ctx.stroke();
399}