Skip to main content

agg_gui/widgets/inspector/
widget_impl.rs

1//! `Widget` impl for `InspectorPanel` — extracted from `mod.rs` to keep
2//! the parent file under the project's 800-line cap.  All InspectorPanel
3//! state and helpers still live in the parent module; this file only
4//! routes the trait methods (layout / paint / event dispatch) into them.
5
6use std::sync::Arc;
7
8use crate::draw_ctx::DrawCtx;
9use crate::event::{Event, EventResult, MouseButton};
10use crate::geometry::{Rect, Size};
11use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
12use crate::widget::{InspectorOverlay, Widget};
13use crate::widgets::tree_view::{NodeIcon, TreeNode};
14
15use super::{
16    c_border, c_dim_text, c_header_bg, c_panel_bg, c_props_bg, c_split_bg, c_text,
17    InspectorPanel, HEADER_H, MIN_PROPS_H, MIN_TREE_H,
18};
19
20impl Widget for InspectorPanel {
21    fn type_name(&self) -> &'static str {
22        "InspectorPanel"
23    }
24    fn bounds(&self) -> Rect {
25        self.bounds
26    }
27    fn set_bounds(&mut self, b: Rect) {
28        self.bounds = b;
29    }
30    fn children(&self) -> &[Box<dyn Widget>] {
31        &self._children
32    }
33    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
34        &mut self._children
35    }
36
37    fn margin(&self) -> Insets {
38        self.base.margin
39    }
40    fn widget_base(&self) -> Option<&WidgetBase> {
41        Some(&self.base)
42    }
43    fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
44        Some(&mut self.base)
45    }
46    fn h_anchor(&self) -> HAnchor {
47        self.base.h_anchor
48    }
49    fn v_anchor(&self) -> VAnchor {
50        self.base.v_anchor
51    }
52    fn min_size(&self) -> Size {
53        self.base.min_size
54    }
55    fn max_size(&self) -> Size {
56        self.base.max_size
57    }
58
59    fn layout(&mut self, available: Size) -> Size {
60        self.bounds.width = available.width;
61        self.bounds.height = available.height;
62
63        let nodes = self.nodes.borrow();
64        // Fingerprint of the inspector_nodes Vec.  When the harness skips
65        // a snapshot pass (e.g. during a window-resize drag) the Vec is
66        // reused, so the data ptr stays the same — we then skip the
67        // tree_view.nodes rebuild here.  Combined with TreeView's row
68        // caching, this is what makes inspector window resizing cheap.
69        let nodes_fingerprint = (nodes.as_ptr() as usize, nodes.len());
70        let pending_state =
71            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            // Preserve expansion/selection state by index before rebuilding.
78            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 =
87                    vec![false; old_expanded.len().max(ps.map(|i| i + 1).unwrap_or(0))];
88                if let Some(i) = ps {
89                    if i < old_selected.len() {
90                        old_selected[i] = true;
91                    }
92                }
93            }
94
95            self.tree_view.nodes.clear();
96
97            // Convert flat InspectorNode list (with depths) to parent-child
98            // TreeNode structure via a depth stack.
99            let mut depth_stack: Vec<usize> = Vec::new();
100            let mut per_parent_counts: std::collections::HashMap<Option<usize>, u32> =
101                std::collections::HashMap::new();
102
103            for (orig_idx, node) in nodes.iter().enumerate() {
104                let parent = if node.depth == 0 {
105                    None
106                } else {
107                    depth_stack.get(node.depth.saturating_sub(1)).copied()
108                };
109                let order = {
110                    let cnt = per_parent_counts.entry(parent).or_insert(0);
111                    let o = *cnt;
112                    *cnt += 1;
113                    o
114                };
115                let b = &node.screen_bounds;
116                let label =
117                    format!("{}  {:.0}×{:.0}", node.type_name, b.width, b.height);
118                let tv_idx = self.tree_view.nodes.len();
119                self.tree_view
120                    .nodes
121                    .push(TreeNode::new(label, NodeIcon::Package, parent, order));
122                self.tree_view.nodes[tv_idx].is_expanded =
123                    old_expanded.get(orig_idx).copied().unwrap_or(true);
124                self.tree_view.nodes[tv_idx].is_selected =
125                    old_selected.get(orig_idx).copied().unwrap_or(false);
126                if depth_stack.len() <= node.depth {
127                    depth_stack.resize(node.depth + 1, 0);
128                }
129                depth_stack[node.depth] = tv_idx;
130            }
131            self.last_inspector_nodes_fingerprint = Some(nodes_fingerprint);
132        }
133
134        self.selected = self.tree_view.nodes.iter().position(|n| n.is_selected);
135
136        *self.hovered_bounds.borrow_mut() = self
137            .tree_view
138            .hovered_node_idx()
139            .and_then(|i| nodes.get(i))
140            .map(|n| InspectorOverlay {
141                bounds: n.screen_bounds,
142                margin: n.margin,
143                padding: n.padding,
144            });
145
146        let tree_w = available.width;
147        let tree_bot = self.tree_origin_y();
148        let tree_top = self.list_area_h();
149        let tree_h = (tree_top - tree_bot).max(0.0);
150        self.tree_view
151            .set_bounds(Rect::new(0.0, tree_bot, tree_w, tree_h));
152        self.tree_view.layout(Size::new(tree_w, tree_h));
153
154        // Keep the presence node's bounds in sync with the real TreeView so
155        // the inspector displays accurate bounds for this proxy entry.
156        self._children[0].set_bounds(self.tree_view.bounds());
157
158        if let Some(cell) = &self.snapshot_out {
159            *cell.borrow_mut() = Some(self.saved_state());
160        }
161
162        available
163    }
164
165    fn paint(&mut self, ctx: &mut dyn DrawCtx) {
166        let w = self.bounds.width;
167        let h = self.bounds.height;
168        let sy = self.split_y();
169        let hdr_y = h - HEADER_H;
170        let v = ctx.visuals().clone();
171
172        // Panel background
173        ctx.set_fill_color(c_panel_bg(&v));
174        ctx.begin_path();
175        ctx.rect(0.0, 0.0, w, h);
176        ctx.fill();
177
178        ctx.set_stroke_color(c_border(&v));
179        ctx.set_line_width(1.0);
180        ctx.begin_path();
181        ctx.move_to(0.0, 0.0);
182        ctx.line_to(0.0, h);
183        ctx.stroke();
184
185        // Header
186        ctx.set_fill_color(c_header_bg(&v));
187        ctx.begin_path();
188        ctx.rect(0.0, hdr_y, w, HEADER_H);
189        ctx.fill();
190
191        ctx.set_stroke_color(c_border(&v));
192        ctx.set_line_width(1.0);
193        ctx.begin_path();
194        ctx.move_to(0.0, hdr_y);
195        ctx.line_to(w, hdr_y);
196        ctx.stroke();
197
198        ctx.set_font(Arc::clone(&self.font));
199        ctx.set_font_size(13.0);
200        ctx.set_fill_color(c_text(&v));
201        let title = "Widget Inspector";
202        if let Some(m) = ctx.measure_text(title) {
203            ctx.fill_text(
204                title,
205                12.0,
206                hdr_y + (HEADER_H - m.ascent - m.descent) * 0.5 + m.descent,
207            );
208        }
209
210        let count_txt = format!("{} widgets", self.nodes.borrow().len());
211        ctx.set_font_size(11.0);
212        ctx.set_fill_color(c_dim_text(&v));
213        if let Some(m) = ctx.measure_text(&count_txt) {
214            ctx.fill_text(
215                &count_txt,
216                w - m.width - 10.0,
217                hdr_y + (HEADER_H - m.ascent - m.descent) * 0.5 + m.descent,
218            );
219        }
220
221        // Properties pane
222        ctx.set_fill_color(c_props_bg(&v));
223        ctx.begin_path();
224        ctx.rect(0.0, 0.0, w, sy - 2.0);
225        ctx.fill();
226        self.paint_properties(ctx, sy - 2.0);
227
228        // Split handle
229        ctx.set_fill_color(c_split_bg(&v));
230        ctx.begin_path();
231        ctx.rect(0.0, sy - 2.0, w, 4.0);
232        ctx.fill();
233        ctx.set_stroke_color(c_border(&v));
234        ctx.set_line_width(1.0);
235        ctx.begin_path();
236        ctx.move_to(0.0, sy);
237        ctx.line_to(w, sy);
238        ctx.stroke();
239
240        // Tree area
241        let tree_bot = self.tree_origin_y();
242        let tree_top = self.list_area_h();
243        let tree_h = (tree_top - tree_bot).max(0.0);
244        if tree_h > 0.0 {
245            ctx.save();
246            ctx.translate(0.0, tree_bot);
247            ctx.clip_rect(0.0, 0.0, w, tree_h);
248            crate::widget::paint_subtree(&mut self.tree_view, ctx);
249            ctx.restore();
250        }
251    }
252
253    fn on_event(&mut self, event: &Event) -> EventResult {
254        match event {
255            Event::MouseDown {
256                pos,
257                button: MouseButton::Left,
258                ..
259            } => {
260                if pos.y < self.split_y() - 2.0 && self.try_emit_base_edit_from_click(*pos) {
261                    return EventResult::Consumed;
262                }
263                #[cfg(feature = "reflect")]
264                if pos.y < self.split_y() - 2.0 && self.try_emit_edit_from_click(*pos) {
265                    return EventResult::Consumed;
266                }
267                if self.on_split_handle(*pos) {
268                    self.split_dragging = true;
269                    return EventResult::Consumed;
270                }
271                if self.pos_in_tree_area(*pos) {
272                    return self.forward_to_tree(event);
273                }
274                EventResult::Ignored
275            }
276            Event::MouseMove { pos } => {
277                if self.split_dragging {
278                    self.props_h = pos.y.clamp(
279                        MIN_PROPS_H,
280                        (self.list_area_h() - MIN_TREE_H).max(MIN_PROPS_H),
281                    );
282                    crate::animation::request_draw();
283                    return EventResult::Consumed;
284                }
285                if self.pos_in_tree_area(*pos) {
286                    let _ = self.forward_to_tree(event);
287                    self.update_hovered_bounds_from_tree();
288                } else if self.hovered_bounds.borrow().is_some() {
289                    *self.hovered_bounds.borrow_mut() = None;
290                    crate::animation::request_draw_without_invalidation();
291                }
292                EventResult::Ignored
293            }
294            Event::MouseUp {
295                button: MouseButton::Left,
296                pos,
297                ..
298            } => {
299                if self.split_dragging {
300                    self.split_dragging = false;
301                    crate::animation::request_draw();
302                    return EventResult::Consumed;
303                }
304                if self.pos_in_tree_area(*pos) {
305                    return self.forward_to_tree(event);
306                }
307                EventResult::Ignored
308            }
309            Event::MouseWheel { pos, .. } if self.pos_in_tree_area(*pos) => {
310                self.forward_to_tree(event)
311            }
312            _ => EventResult::Ignored,
313        }
314    }
315}