Skip to main content

motion_core/authoring/
scene.rs

1//! Stateless scenes and explicit durations.
2
3use super::*;
4
5#[cfg(test)]
6mod authoring_helpers_tests;
7
8/// Converts ordinary drawable snapshots or recoverable scene failures.
9pub trait IntoSceneOutput {
10    /// Preserves the original failure category and source.
11    fn into_scene_output(self) -> crate::Result<Node>;
12}
13
14impl<Output: Into<Node>> IntoSceneOutput for Output {
15    fn into_scene_output(self) -> crate::Result<Node> {
16        Ok(self.into())
17    }
18}
19
20impl<Output: Into<Node>, Failure: Into<crate::Error>> IntoSceneOutput
21    for std::result::Result<Output, Failure>
22{
23    fn into_scene_output(self) -> crate::Result<Node> {
24        self.map(Into::into).map_err(Into::into)
25    }
26}
27
28struct SceneFailure {
29    error: Arc<crate::Error>,
30}
31
32impl SceneFailure {
33    fn new(error: crate::Error) -> Self {
34        Self {
35            error: Arc::new(error),
36        }
37    }
38
39    fn diagnostic(&self, context: &DrawContext) -> ValidationError {
40        context.diagnostics().record_scene(self.error.clone());
41        if let Some(error) = self.error.downcast_ref::<ValidationError>() {
42            return error.clone();
43        }
44        ValidationError::invalid("scene", "output", self.error.to_string())
45    }
46}
47
48pub(crate) fn failure(error: impl Into<crate::Error>) -> Node {
49    Node::from(SceneFailure::new(error.into()))
50}
51
52impl Drawable for SceneFailure {
53    fn bounds(&self, context: &DrawContext) -> Result<Rect, ValidationError> {
54        Err(self.diagnostic(context))
55    }
56
57    fn draw(
58        &self,
59        context: &DrawContext,
60        _: &mut crate::Canvas<'_>,
61    ) -> Result<(), ValidationError> {
62        Err(self.diagnostic(context))
63    }
64}
65
66/// Gives closures the context bound needed for inference and defers failures to rendering.
67pub fn scene<Build, Output>(build: Build) -> impl Fn(&SceneContext<'_>) -> Node + Send + Sync
68where
69    Build: Fn(&SceneContext<'_>) -> Output + Send + Sync,
70    Output: IntoSceneOutput,
71{
72    move |context| match build(context).into_scene_output() {
73        Ok(node) => node,
74        Err(error) => {
75            let failure = SceneFailure::new(error);
76            context.diagnostics().record_scene(failure.error.clone());
77            Node::from(failure)
78        }
79    }
80}
81
82/// Groups heterogeneous drawable or node values in source order.
83#[macro_export]
84macro_rules! group {
85    () => { $crate::Node::empty() };
86    ($($child:expr),* $(,)?) => {
87        $crate::Node::group([$($crate::Node::from($child)),*])
88    };
89}
90
91/// A stateless scene evaluated from explicit authoring inputs.
92///
93/// Closures implement this canonical root [`crate::Scene`] protocol.
94///
95/// ```
96/// use motion_core::prelude::*;
97/// use motion_core::{
98///     DisplayList, RasterLimits, ResourceStore, ValidatedDisplayList, MAX_PATH_VERBS,
99///     MAX_STROKE_VERBS, MAX_SVG_PATH_BYTES, sample_offsets,
100/// };
101///
102/// let child = (|context: &SceneContext<'_>| {
103///     assert_eq!(context.time(), 1.seconds().after_origin());
104///     assert_eq!(context.global_time(), 2.seconds().after_origin());
105///     Node::from(Rect::new(0.0, 0.0, 32.0, 24.0).fill(Color::rgb(20, 150, 125)))
106/// })
107/// .with_duration(2.seconds());
108/// let cache = SceneCache::new(0);
109/// let context = SceneContext::new(
110///     2.seconds().after_origin(),
111///     fps!(30),
112///     Size::new(640.0, 360.0),
113///     &cache,
114/// );
115/// let node = context.during(Time::ZERO, 4.seconds(), |local| {
116///     local.embed(&child, 1.seconds().after_origin())
117/// });
118/// let frame = node.compile(
119///     context.size(),
120///     PixelSize::new(64, 36),
121///     ValidationLimits::default(),
122/// )?;
123/// # Ok::<(), motion_core::ValidationError>(())
124/// ```
125pub trait Scene: Send + Sync {
126    /// Validates deferred authoring errors without evaluating a frame.
127    fn validate(&self) -> crate::Result<()> {
128        Ok(())
129    }
130
131    /// Builds one snapshot without retaining previous-frame state.
132    fn render(&self, context: &SceneContext<'_>) -> Node;
133
134    /// Natural finite duration, or None when a caller must provide one.
135    fn duration(&self) -> Option<Duration> {
136        None
137    }
138
139    /// Overrides clip metadata without retiming or clamping the callback.
140    ///
141    /// Zero duration has an empty active interval.
142    fn with_duration(self, duration: Duration) -> WithDuration<Self>
143    where
144        Self: Sized,
145    {
146        WithDuration {
147            scene: self,
148            duration,
149        }
150    }
151}
152
153impl<Callback> Scene for Callback
154where
155    Callback: Fn(&SceneContext<'_>) -> Node + Send + Sync,
156{
157    fn render(&self, context: &SceneContext<'_>) -> Node {
158        self(context)
159    }
160}
161
162impl Scene for Arc<dyn Scene> {
163    fn validate(&self) -> crate::Result<()> {
164        self.as_ref().validate()
165    }
166
167    fn render(&self, context: &SceneContext<'_>) -> Node {
168        self.as_ref().render(context)
169    }
170
171    fn duration(&self) -> Option<Duration> {
172        self.as_ref().duration()
173    }
174}
175
176/// A scene with an explicit finite authoring duration.
177pub struct WithDuration<Content> {
178    scene: Content,
179    duration: Duration,
180}
181
182impl<Content: Scene> Scene for WithDuration<Content> {
183    fn validate(&self) -> crate::Result<()> {
184        self.scene.validate()
185    }
186
187    fn render(&self, context: &SceneContext<'_>) -> Node {
188        self.scene.render(context)
189    }
190
191    fn duration(&self) -> Option<Duration> {
192        Some(self.duration)
193    }
194}