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}