motion_core/authoring/
scene.rs1use super::*;
4
5#[cfg(test)]
6mod authoring_helpers_tests;
7
8pub trait IntoSceneOutput {
10 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 report: Arc<crate::Error>,
31}
32
33impl SceneFailure {
34 fn new(error: crate::Error) -> Self {
35 let error = Arc::new(error);
36 let report = Arc::new(crate::Error::BuildErrors(vec![error.clone()]));
37 Self { error, report }
38 }
39
40 fn diagnostic(&self, context: &DrawContext) -> ValidationError {
41 context.diagnostics().record(self.report.clone());
42 if let Some(error) = self.error.downcast_ref::<ValidationError>() {
43 return error.clone();
44 }
45 ValidationError::invalid("scene", "output", self.error.to_string())
46 }
47}
48
49pub(crate) fn failure(error: impl Into<crate::Error>) -> Node {
50 Node::from(SceneFailure::new(error.into()))
51}
52
53impl Drawable for SceneFailure {
54 fn bounds(&self, _: &DrawContext) -> Rect {
55 Rect::new(0.0, 0.0, 0.0, 0.0)
56 }
57
58 fn try_bounds(&self, context: &DrawContext) -> Result<Rect, ValidationError> {
59 Err(self.diagnostic(context))
60 }
61
62 fn draw(
63 &self,
64 context: &DrawContext,
65 _: &mut crate::Canvas<'_>,
66 ) -> Result<(), ValidationError> {
67 Err(self.diagnostic(context))
68 }
69}
70
71pub fn scene<Build, Output>(build: Build) -> impl Fn(&SceneContext<'_>) -> Node + Send + Sync
73where
74 Build: Fn(&SceneContext<'_>) -> Output + Send + Sync,
75 Output: IntoSceneOutput,
76{
77 move |context| match build(context).into_scene_output() {
78 Ok(node) => node,
79 Err(error) => {
80 let failure = SceneFailure::new(error);
81 if let Some(diagnostics) = context.diagnostics() {
82 diagnostics.record(failure.report.clone());
83 }
84 Node::from(failure)
85 }
86 }
87}
88
89#[macro_export]
91macro_rules! group {
92 () => { $crate::Node::empty() };
93 ($($child:expr),* $(,)?) => {
94 $crate::Node::group([$($crate::Node::from($child)),*])
95 };
96}
97
98pub trait Scene: Send + Sync {
133 fn validate(&self) -> crate::Result<()> {
135 Ok(())
136 }
137
138 fn render(&self, context: &SceneContext<'_>) -> Node;
140
141 fn duration(&self) -> Option<Duration> {
143 None
144 }
145
146 fn audio_clips(&self) -> crate::Result<Vec<crate::AudioClip>> {
149 Ok(Vec::new())
150 }
151
152 fn audio_composition(&self) -> Option<&crate::Timeline> {
154 None
155 }
156
157 fn with_duration(self, duration: Duration) -> WithDuration<Self>
161 where
162 Self: Sized,
163 {
164 assert!(duration >= Time::ZERO, "scene duration must be nonnegative");
165 WithDuration {
166 scene: self,
167 duration,
168 }
169 }
170}
171
172impl<Callback> Scene for Callback
173where
174 Callback: Fn(&SceneContext<'_>) -> Node + Send + Sync,
175{
176 fn render(&self, context: &SceneContext<'_>) -> Node {
177 self(context)
178 }
179}
180
181impl Scene for Arc<dyn Scene> {
182 fn audio_composition(&self) -> Option<&crate::Timeline> {
183 self.as_ref().audio_composition()
184 }
185
186 fn validate(&self) -> crate::Result<()> {
187 self.as_ref().validate()
188 }
189
190 fn render(&self, context: &SceneContext<'_>) -> Node {
191 self.as_ref().render(context)
192 }
193
194 fn duration(&self) -> Option<Duration> {
195 self.as_ref().duration()
196 }
197
198 fn audio_clips(&self) -> crate::Result<Vec<crate::AudioClip>> {
199 self.as_ref().audio_clips()
200 }
201}
202
203pub struct WithDuration<Content> {
205 scene: Content,
206 duration: Duration,
207}
208
209impl<Content: Scene> Scene for WithDuration<Content> {
210 fn audio_composition(&self) -> Option<&crate::Timeline> {
211 self.scene.audio_composition()
212 }
213
214 fn validate(&self) -> crate::Result<()> {
215 self.scene.validate()
216 }
217
218 fn render(&self, context: &SceneContext<'_>) -> Node {
219 self.scene.render(context)
220 }
221
222 fn duration(&self) -> Option<Duration> {
223 Some(self.duration)
224 }
225
226 fn audio_clips(&self) -> crate::Result<Vec<crate::AudioClip>> {
227 self.scene
228 .audio_clips()?
229 .iter()
230 .filter_map(|clip| clip.relocated(Time::ZERO, self.duration).transpose())
231 .collect::<Result<Vec<_>, _>>()
232 .map_err(Into::into)
233 }
234}
235
236pub struct SoundScene<Build> {
238 build: Build,
239 duration: Duration,
240 size: crate::Size,
241 rate: crate::FrameRate,
242 sounds: std::sync::Mutex<Option<Vec<crate::AudioClip>>>,
243}
244
245pub fn scene_with_sound<Build, Output>(
248 build: Build,
249 duration: Duration,
250 size: crate::Size,
251 rate: impl Into<crate::FrameRate>,
252) -> SoundScene<Build>
253where
254 Build: Fn(&SceneContext<'_>) -> Output + Send + Sync,
255 Output: IntoSceneOutput,
256{
257 SoundScene {
258 build,
259 duration,
260 size,
261 rate: rate.into(),
262 sounds: std::sync::Mutex::new(None),
263 }
264}
265
266impl<Build, Output> Scene for SoundScene<Build>
267where
268 Build: Fn(&SceneContext<'_>) -> Output + Send + Sync,
269 Output: IntoSceneOutput,
270{
271 fn validate(&self) -> crate::Result {
272 if self.duration <= Time::ZERO || self.duration.as_seconds() > 86400.0 {
273 return Err(ValidationError::invalid(
274 "scene_audio",
275 "duration",
276 "Require duration in (0,24h]",
277 )
278 .into());
279 }
280 SceneContext::try_new(
281 Time::ZERO,
282 self.rate,
283 self.size,
284 &crate::SceneCache::default(),
285 )?;
286 Ok(())
287 }
288
289 fn duration(&self) -> Option<Duration> {
290 Some(self.duration)
291 }
292
293 fn render(&self, context: &SceneContext<'_>) -> Node {
294 match (self.build)(context).into_scene_output() {
295 Ok(node) => node,
296 Err(error) => failure(error),
297 }
298 }
299
300 fn audio_clips(&self) -> crate::Result<Vec<crate::AudioClip>> {
301 self.validate()?;
302 let mut cached = self.sounds.lock().map_err(|_| {
303 ValidationError::invalid("scene_audio", "lock", "Sound scene cache is poisoned")
304 })?;
305 if let Some(sounds) = cached.as_ref() {
306 return Ok(sounds.clone());
307 }
308 let cache = crate::SceneCache::default();
309 let sounds = std::sync::Mutex::new(Vec::new());
310 let context = SceneContext::try_new(Time::ZERO, self.rate, self.size, &cache)?
311 .with_clip_duration(self.duration)
312 .collecting_sounds(&sounds);
313 (self.build)(&context).into_scene_output()?;
314 let sounds = sounds.into_inner().map_err(|_| {
315 ValidationError::invalid("scene_audio", "lock", "Sound declaration lock is poisoned")
316 })?;
317 *cached = Some(sounds.clone());
318 Ok(sounds)
319 }
320}