Skip to main content

dioxuscut_core/
sequence.rs

1//! `<Sequence>` component — time-slices a sub-tree within a composition.
2//!
3//! Equivalent to Remotion's `<Sequence>`.
4//!
5//! A `Sequence` starts rendering its children at frame `from`, and optionally
6//! stops after `duration_in_frames`. Children see a local frame that starts at
7//! `0` when the sequence begins.
8//!
9//! # Example
10//! ```rust,ignore
11//! use dioxuscut_core::Sequence;
12//!
13//! fn MyVideo() -> Element {
14//!     rsx! {
15//!         // First 60 frames: title card
16//!         Sequence { from: 0, duration_in_frames: 60,
17//!             TitleCard {}
18//!         }
19//!         // Frames 60+: main content
20//!         Sequence { from: 60,
21//!             MainContent {}
22//!         }
23//!     }
24//! }
25//! ```
26
27use crate::timeline::context::TimelineContext;
28use dioxus::prelude::*;
29
30/// Layout mode for the sequence container div.
31#[derive(Clone, PartialEq, Debug, Default)]
32pub enum SequenceLayout {
33    /// Fill the parent absolutely (default — matches Remotion's default).
34    #[default]
35    AbsoluteFill,
36    /// No additional styling.
37    None,
38}
39
40/// Props for the `<Sequence>` component.
41#[derive(Props, Clone, PartialEq)]
42pub struct SequenceProps {
43    /// Frame offset where this sequence starts (default: `0`).
44    #[props(default = 0)]
45    pub from: u32,
46
47    /// How many frames this sequence lasts.
48    /// `None` = plays until the end of the parent composition.
49    #[props(default)]
50    pub duration_in_frames: Option<u32>,
51
52    /// Human-readable name shown in the studio timeline.
53    #[props(default)]
54    pub name: Option<String>,
55
56    /// Layout mode for the sequence wrapper.
57    #[props(default)]
58    pub layout: SequenceLayout,
59
60    /// Whether to hide the content (renders nothing).
61    #[props(default = false)]
62    pub hidden: bool,
63
64    /// Children.
65    pub children: Element,
66}
67
68/// A time-sliced segment of a composition.
69///
70/// Children only render during the active window `[from, from + duration)`.
71/// Inside the children, `use_current_frame()` returns a **local** frame
72/// starting at `0` when the sequence begins.
73#[component]
74pub fn Sequence(props: SequenceProps) -> Element {
75    // Read the parent timeline context
76    let parent_signal = use_context::<Signal<TimelineContext>>();
77    let parent_ctx = parent_signal.read().clone();
78    let parent_frame = parent_ctx.frame;
79
80    let from = props.from;
81    let end_frame = props
82        .duration_in_frames
83        .map(|d| from.saturating_add(d))
84        .unwrap_or(u32::MAX);
85
86    // Only render children within the active window
87    let is_active = parent_frame >= from && parent_frame < end_frame;
88
89    if props.hidden || !is_active {
90        return rsx! {};
91    }
92
93    // Provide a child context with the local frame offset applied
94    let child_ctx = TimelineContext::offset_from(&parent_ctx, from);
95
96    let style = match props.layout {
97        SequenceLayout::AbsoluteFill => "position: absolute; top: 0; left: 0; right: 0; bottom: 0;",
98        SequenceLayout::None => "",
99    };
100
101    rsx! {
102        // Inject child context so descendant hooks see the local frame
103        div {
104            style: "{style}",
105            // Temporarily override the context for children.
106            // Dioxus context is provided via use_context_provider at the call site;
107            // here we wrap via a helper inner component.
108            SequenceInner {
109                ctx: child_ctx,
110                children: props.children,
111            }
112        }
113    }
114}
115
116/// Internal helper that injects the child TimelineContext.
117#[derive(Props, Clone, PartialEq)]
118struct SequenceInnerProps {
119    ctx: TimelineContext,
120    children: Element,
121}
122
123#[component]
124fn SequenceInner(props: SequenceInnerProps) -> Element {
125    let mut timeline = use_context_provider(|| Signal::new(props.ctx.clone()));
126    if *timeline.peek() != props.ctx {
127        timeline.set(props.ctx);
128    }
129    rsx! { {props.children} }
130}