dioxuscut_core/composition.rs
1//! `<Composition>` component — defines a named video composition.
2//!
3//! Equivalent to Remotion's `<Composition>` component. Provides a
4//! `TimelineContext` and `VideoConfigContext` to all descendants.
5//!
6//! # Example
7//! ```rust,ignore
8//! use dioxuscut_core::{Composition, CompositionProps};
9//!
10//! fn App() -> Element {
11//! rsx! {
12//! Composition {
13//! id: "MyVideo",
14//! width: 1920,
15//! height: 1080,
16//! fps: 30.0,
17//! duration_in_frames: 150,
18//! MyVideoComponent {}
19//! }
20//! }
21//! }
22//! ```
23
24use crate::timeline::context::{TimelineContext, VideoConfigContext};
25use crate::types::VideoConfig;
26use dioxus::prelude::*;
27
28/// Props for the `<Composition>` component.
29#[derive(Props, Clone, PartialEq)]
30pub struct CompositionProps {
31 /// Unique ID of this composition.
32 pub id: String,
33 /// Width in pixels.
34 pub width: u32,
35 /// Height in pixels.
36 pub height: u32,
37 /// Frames per second.
38 pub fps: f64,
39 /// Total frame count.
40 pub duration_in_frames: u32,
41 /// Current frame to render (driven by the player or renderer).
42 #[props(default = 0)]
43 pub frame: u32,
44 /// Child elements (the actual video content).
45 pub children: Element,
46}
47
48/// A named video composition.
49///
50/// Sets up the [`TimelineContext`] and [`VideoConfigContext`] so that
51/// all descendant components can call `use_current_frame()` and
52/// `use_video_config()`.
53#[component]
54pub fn Composition(props: CompositionProps) -> Element {
55 let config = VideoConfig {
56 id: props.id.clone(),
57 width: props.width,
58 height: props.height,
59 fps: props.fps,
60 duration_in_frames: props.duration_in_frames,
61 default_codec: None,
62 default_pixel_format: None,
63 };
64
65 // Clamp the current frame to valid range
66 let frame = props.frame.min(props.duration_in_frames.saturating_sub(1));
67
68 let mut timeline = use_context_provider(|| Signal::new(TimelineContext::new(frame)));
69 if timeline.peek().frame != frame {
70 timeline.set(TimelineContext::new(frame));
71 }
72
73 let mut video_config = use_context_provider(|| Signal::new(VideoConfigContext(config.clone())));
74 if video_config.peek().0 != config {
75 video_config.set(VideoConfigContext(config));
76 }
77
78 rsx! {
79 div {
80 style: "position: relative; width: {props.width}px; height: {props.height}px; overflow: hidden;",
81 {props.children}
82 }
83 }
84}