Skip to main content

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}