Skip to main content

motion_core/authoring/compile/
inspection.rs

1//! Read-only node inspection for editor overlays, planned like compilation.
2
3use super::*;
4
5/// Read-only device-space layout metadata for editor overlays, in paint order.
6/// This is not raster coverage; bounds picking is intentionally approximate.
7#[derive(Clone, Debug)]
8pub struct NodeInspection {
9    /// Stable occurrence path, including authored names.
10    pub path: String,
11    /// Filter-expanded device-space visual bounds.
12    pub bounds: crate::Rect,
13    /// Composed local-to-device transform.
14    pub transform: crate::Transform,
15    /// Authored filter chain before device conversion.
16    pub filters: Vec<crate::Filter>,
17    /// Device-space clip bounding box, not exact clip coverage.
18    pub clip_bounds: Option<crate::Rect>,
19    /// Whether any local geometric/coverage clipping is present.
20    pub clipped: bool,
21    /// Whether the occurrence has an isolated content mask.
22    pub masked: bool,
23}
24
25impl Node {
26    /// Plans a bounded editor snapshot using the same layout, resources and z order
27    /// as compilation. Drawables may resolve/record; no pixels or devices are used.
28    /// Errors remain separate from normal rendering when used by an editor.
29    pub fn inspect_with_context(
30        &self,
31        design_size: Size,
32        output_size: PixelSize,
33        limits: ValidationLimits,
34        drawing: &DrawContext,
35    ) -> Result<Vec<NodeInspection>, ValidationError> {
36        let (root, scale) = output_size.mapping(design_size)?;
37        let mut context = drawing.clone().with_validation_limits(limits);
38        context.device_scale = scale;
39        let mut state = ValidationState::new(limits)?;
40        let planned = plan(self, "node", 1, &context, &mut state)?;
41        let mut output = Vec::new();
42        planned.inspect(root, &mut output)?;
43        Ok(output)
44    }
45}
46
47impl Plan {
48    fn inspect(
49        &self,
50        parent: Transform,
51        output: &mut Vec<NodeInspection>,
52    ) -> Result<(), ValidationError> {
53        let transform = self.transform.then(parent);
54        let clip_bounds = self
55            .rect_clip
56            .map(|clip| mapped_bounds(clip.bounds, clip.transform.then(transform), &self.path))
57            .transpose()?;
58        output.push(NodeInspection {
59            path: self.path.clone(),
60            bounds: self.visual_bounds(parent)?.rect(&self.path)?,
61            transform,
62            filters: self.filters.clone(),
63            clip_bounds,
64            clipped: self.rect_clip.is_some() || self.clip.is_some() || self.shape_clip.is_some(),
65            masked: self.mask.is_some(),
66        });
67        if let Content::Children(children) = &self.content {
68            for child in children {
69                child.inspect(transform, output)?;
70            }
71        }
72        Ok(())
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::Shape;
80    use crate::{Color, NodeModifiers, Rect};
81
82    #[test]
83    fn inspection_uses_compiled_layout_names_mapping_and_order() {
84        let node = Node::group([
85            Node::from(Rect::new(0.0, 0.0, 10.0, 10.0).fill(Color::WHITE))
86                .named("front")
87                .z_index(2),
88            Node::from(Rect::new(0.0, 0.0, 5.0, 5.0).fill(Color::WHITE)).named("back"),
89        ])
90        .translate(3.0, 4.0);
91        let inspect = node
92            .inspect_with_context(
93                Size::new(20.0, 20.0),
94                PixelSize::new(40, 40),
95                ValidationLimits::default(),
96                &DrawContext::default(),
97            )
98            .unwrap();
99        assert_eq!(inspect.len(), 3);
100        assert!(inspect[1].path.contains("back"));
101        assert!(inspect[2].path.contains("front"));
102        assert_eq!(inspect[2].bounds, Rect::new(6.0, 8.0, 26.0, 28.0));
103        assert!(
104            node.inspect_with_context(
105                Size::new(20.0, 20.0),
106                PixelSize::new(40, 40),
107                ValidationLimits {
108                    max_nodes: 1,
109                    ..ValidationLimits::default()
110                },
111                &DrawContext::default()
112            )
113            .is_err()
114        );
115    }
116}