Skip to main content

rustmotion_core/engine/
box_tree.rs

1//! Box tree — the intermediate representation between a JSON scenario and
2//! the layout/paint passes. Each node carries a resolved [`CssStyle`], a
3//! discriminator pointing to the source component, and an optional intrinsic
4//! measurement callback (text, image, codeblock, chart, ...).
5
6use std::sync::Arc;
7
8use crate::css::CssStyle;
9
10/// Stable identifier for a node within a single layout pass.
11pub type NodeId = u32;
12
13/// A renderable box. `kind` is opaque to the engine; the painter dispatch
14/// downcasts the inner `Arc<dyn Any>` to the concrete component type when
15/// invoked.
16pub struct BoxNode {
17    pub id: NodeId,
18    pub kind: BoxKind,
19    pub css: CssStyle,
20    pub children: Vec<BoxNode>,
21    /// Optional intrinsic measurement (used by taffy's `measure_fn` for
22    /// leaves like text / codeblock / image). `None` = pure container.
23    pub intrinsic: Option<Arc<dyn IntrinsicMeasure>>,
24    /// JSON path of this node relative to its scene's `children` array, e.g.
25    /// "/children/2/children/0". `None` for synthetic nodes (the scene root).
26    pub source_path: Option<String>,
27    /// Visibility window from the component's `start_at`/`end_at` (seconds,
28    /// scene-relative). Outside the window the node and its subtree are not
29    /// painted but still occupy layout space (CSS `visibility` semantics —
30    /// siblings must not jump when the component appears).
31    pub window: Option<PaintWindow>,
32}
33
34/// Half-open visibility window `[start, end)`; `None` bounds are unbounded.
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub struct PaintWindow {
37    pub start: Option<f64>,
38    pub end: Option<f64>,
39}
40
41impl PaintWindow {
42    pub fn contains(&self, t: f64) -> bool {
43        self.start.is_none_or(|s| t >= s) && self.end.is_none_or(|e| t < e)
44    }
45}
46
47impl BoxNode {
48    pub fn container(css: CssStyle, children: Vec<BoxNode>) -> Self {
49        Self {
50            id: 0,
51            kind: BoxKind::Container,
52            css,
53            children,
54            intrinsic: None,
55            source_path: None,
56            window: None,
57        }
58    }
59
60    pub fn leaf(css: CssStyle, intrinsic: Arc<dyn IntrinsicMeasure>) -> Self {
61        Self {
62            id: 0,
63            kind: BoxKind::Container,
64            css,
65            children: Vec::new(),
66            intrinsic: Some(intrinsic),
67            source_path: None,
68            window: None,
69        }
70    }
71
72    /// Walk the tree and assign sequential `id` values to each node.
73    /// Returns the next free id.
74    pub fn assign_ids(&mut self, mut next: NodeId) -> NodeId {
75        self.id = next;
76        next += 1;
77        for c in self.children.iter_mut() {
78            next = c.assign_ids(next);
79        }
80        next
81    }
82
83    /// Find a node by id.
84    pub fn find(&self, id: NodeId) -> Option<&BoxNode> {
85        if self.id == id {
86            return Some(self);
87        }
88        for c in &self.children {
89            if let Some(r) = c.find(id) {
90                return Some(r);
91            }
92        }
93        None
94    }
95}
96
97/// Discriminates the source component without coupling the engine to its
98/// concrete types.
99#[derive(Clone)]
100pub enum BoxKind {
101    /// Generic container — no custom paint, only box decorations.
102    Container,
103    /// Component-backed leaf or container. Holds an opaque payload that the
104    /// dispatcher knows how to handle.
105    Component(Arc<dyn std::any::Any + Send + Sync>),
106    /// Temporal ghost for motion-blur / trail effects. Painted exactly like
107    /// `Component` (same payload, same dispatcher dispatch) but excluded from
108    /// the hit-map so the studio never selects a ghost node.
109    Ghost(Arc<dyn std::any::Any + Send + Sync>),
110}
111
112/// Trait implemented by leaves whose intrinsic size depends on their content.
113/// Called by taffy during layout.
114pub trait IntrinsicMeasure: Send + Sync {
115    /// Measure intrinsic size given the available width/height.
116    /// Either side may be `None` if unconstrained (e.g. min-content pass).
117    fn measure(
118        &self,
119        known: (Option<f32>, Option<f32>),
120        available: (AvailableSpace, AvailableSpace),
121    ) -> (f32, f32);
122}
123
124/// CSS available-space hint, mirrored from taffy.
125#[derive(Debug, Clone, Copy, PartialEq)]
126pub enum AvailableSpace {
127    Definite(f32),
128    MinContent,
129    MaxContent,
130}
131
132impl From<taffy::AvailableSpace> for AvailableSpace {
133    fn from(v: taffy::AvailableSpace) -> Self {
134        match v {
135            taffy::AvailableSpace::Definite(p) => Self::Definite(p),
136            taffy::AvailableSpace::MinContent => Self::MinContent,
137            taffy::AvailableSpace::MaxContent => Self::MaxContent,
138        }
139    }
140}