@@ -43,6 +43,11 @@ pub struct BuiltScene<'a> {
4343 /// Lookup table — `components[id as usize]` is the component for `id`.
4444 /// `None` for synthetic boxes (the root scene wrapper).
4545 pub components : Vec < Option < & ' a ChildComponent > > ,
46+ /// Per-node animation delay accumulated from ancestor containers'
47+ /// `stagger` (indexed like `components`). Consumed by the paint
48+ /// dispatcher so internal animations shift by the same amount as the
49+ /// CSS overrides resolved at build time.
50+ pub stagger_delays : Vec < f64 > ,
4651}
4752
4853/// Build a box tree for a flat list of scene-level children at a given
@@ -105,16 +110,19 @@ where
105110 I : IntoIterator < Item = & ' a ChildComponent > ,
106111{
107112 let mut components: Vec < Option < & ' a ChildComponent > > = vec ! [ None ] ;
113+ let mut stagger_delays: Vec < f64 > = vec ! [ 0.0 ] ;
108114 let mut next_id: NodeId = 1 ;
109115
110116 let mut child_boxes = Vec :: new ( ) ;
111117 for ( i, c) in children. into_iter ( ) . enumerate ( ) {
112118 child_boxes. push ( build_child (
113119 c,
114120 & mut components,
121+ & mut stagger_delays,
115122 & mut next_id,
116123 anim,
117124 format ! ( "/children/{i}" ) ,
125+ 0.0 ,
118126 ) ) ;
119127 }
120128
@@ -132,7 +140,11 @@ where
132140 window : None ,
133141 } ;
134142
135- BuiltScene { root, components }
143+ BuiltScene {
144+ root,
145+ components,
146+ stagger_delays,
147+ }
136148}
137149
138150fn default_root_css ( viewport : ( f32 , f32 ) ) -> CssStyle {
@@ -146,16 +158,22 @@ fn default_root_css(viewport: (f32, f32)) -> CssStyle {
146158}
147159
148160/// Convert a single `ChildComponent` to a `BoxNode`. Recurses into containers.
161+ /// `stagger_delay` is the animation delay accumulated from ancestor
162+ /// containers' `stagger` fields.
163+ #[ allow( clippy:: too_many_arguments) ]
149164fn build_child < ' a > (
150165 child : & ' a ChildComponent ,
151166 components : & mut Vec < Option < & ' a ChildComponent > > ,
167+ stagger_delays : & mut Vec < f64 > ,
152168 next_id : & mut NodeId ,
153169 anim : Option < BuildAnimationCtx > ,
154170 path : String ,
171+ stagger_delay : f64 ,
155172) -> BoxNode {
156173 let id = * next_id;
157174 * next_id += 1 ;
158175 components. push ( Some ( child) ) ;
176+ stagger_delays. push ( stagger_delay) ;
159177
160178 let mut css = component_css ( & child. component ) ;
161179
@@ -175,29 +193,12 @@ fn build_child<'a>(
175193 // char_animation remain on the `AnimatedProperties` legacy path.
176194 if let Some ( actx) = anim {
177195 if let Some ( animatable) = child. component . as_animatable ( ) {
178- let effects = animatable. animation_effects ( ) ;
179- let steps = animatable. timeline_steps ( ) ;
180- let props = if steps. is_empty ( ) {
181- if effects. is_empty ( ) {
182- None
183- } else {
184- Some ( resolve_props_for_effects (
185- effects,
186- actx. time ,
187- actx. scene_duration ,
188- ) )
189- }
190- } else {
191- // Timeline steps resolve as their animations shifted by the
192- // step's `at` (merged with the plain `style.animation` list).
193- let merged = merge_timeline_effects ( effects, steps) ;
194- Some ( resolve_props_for_effects (
195- & merged,
196- actx. time ,
197- actx. scene_duration ,
198- ) )
199- } ;
200- if let Some ( props) = props {
196+ if let Some ( effects) = effective_effects (
197+ animatable. animation_effects ( ) ,
198+ animatable. timeline_steps ( ) ,
199+ stagger_delay,
200+ ) {
201+ let props = resolve_props_for_effects ( & effects, actx. time , actx. scene_duration ) ;
201202 if props_has_paint_overrides ( & props) {
202203 apply_animated_props ( & mut css, & props) ;
203204 }
@@ -206,13 +207,27 @@ fn build_child<'a>(
206207 }
207208
208209 // Visibility window (start_at/end_at) — enforced by the paint pass.
210+ // The stagger delay shifts the window too, so a hard-cut child appears
211+ // in step with its staggered siblings.
209212 let window = child. component . as_timed ( ) . and_then ( |t| {
210213 let ( start, end) = t. timing ( ) ;
211- ( start. is_some ( ) || end. is_some ( ) )
212- . then_some ( rustmotion_core:: engine:: box_tree:: PaintWindow { start, end } )
214+ ( start. is_some ( ) || end. is_some ( ) ) . then_some (
215+ rustmotion_core:: engine:: box_tree:: PaintWindow {
216+ start : start. map ( |s| s + stagger_delay) ,
217+ end : end. map ( |e| e + stagger_delay) ,
218+ } ,
219+ )
213220 } ) ;
214221
215- let children_boxes = container_children ( & child. component , components, next_id, anim, & path) ;
222+ let children_boxes = container_children (
223+ & child. component ,
224+ components,
225+ stagger_delays,
226+ next_id,
227+ anim,
228+ & path,
229+ stagger_delay,
230+ ) ;
216231 let intrinsic = component_intrinsic ( & child. component ) ;
217232
218233 BoxNode {
@@ -226,13 +241,18 @@ fn build_child<'a>(
226241 }
227242}
228243
229- /// Flatten `timeline` steps into plain animation effects: each step's
230- /// animations run with `delay += step.at`, appended after the component's
231- /// own `style.animation` list.
232- fn merge_timeline_effects (
233- effects : & [ rustmotion_core:: schema:: AnimationEffect ] ,
244+ /// The full effect list for a component at paint time: `style.animation`,
245+ /// plus `timeline` steps shifted by their `at`, plus the container-stagger
246+ /// delay applied to everything. Returns `None` when there is nothing to
247+ /// resolve, `Some(Cow::Borrowed)` on the no-merge fast path.
248+ pub fn effective_effects < ' a > (
249+ effects : & ' a [ rustmotion_core:: schema:: AnimationEffect ] ,
234250 steps : & [ rustmotion_core:: schema:: TimelineStep ] ,
235- ) -> Vec < rustmotion_core:: schema:: AnimationEffect > {
251+ extra_delay : f64 ,
252+ ) -> Option < std:: borrow:: Cow < ' a , [ rustmotion_core:: schema:: AnimationEffect ] > > {
253+ if steps. is_empty ( ) && extra_delay == 0.0 {
254+ return ( !effects. is_empty ( ) ) . then_some ( std:: borrow:: Cow :: Borrowed ( effects) ) ;
255+ }
236256 let mut merged = effects. to_vec ( ) ;
237257 for step in steps {
238258 for effect in & step. animation {
@@ -241,7 +261,12 @@ fn merge_timeline_effects(
241261 merged. push ( e) ;
242262 }
243263 }
244- merged
264+ if extra_delay != 0.0 {
265+ for e in & mut merged {
266+ e. shift_delay ( extra_delay) ;
267+ }
268+ }
269+ ( !merged. is_empty ( ) ) . then_some ( std:: borrow:: Cow :: Owned ( merged) )
245270}
246271
247272/// Quick gate: does this resolved `AnimatedProperties` carry any property
@@ -286,32 +311,39 @@ fn component_intrinsic(
286311}
287312
288313/// If the component is a container, recurse into its children. Otherwise
289- /// return an empty Vec.
314+ /// return an empty Vec. A container's `stagger` adds `index * stagger`
315+ /// to each child's inherited animation delay (cumulative across nesting).
316+ #[ allow( clippy:: too_many_arguments) ]
290317fn container_children < ' a > (
291318 component : & ' a Component ,
292319 components : & mut Vec < Option < & ' a ChildComponent > > ,
320+ stagger_delays : & mut Vec < f64 > ,
293321 next_id : & mut NodeId ,
294322 anim : Option < BuildAnimationCtx > ,
295323 parent_path : & str ,
324+ inherited_delay : f64 ,
296325) -> Vec < BoxNode > {
297- let children: & [ ChildComponent ] = match component {
298- Component :: Card ( c) => & c. children ,
299- Component :: Flex ( c) => & c. children ,
300- Component :: Grid ( c) => & c. children ,
301- Component :: Container ( c) => & c. children ,
302- Component :: Positioned ( c) => & c. children ,
326+ let ( children, stagger ) : ( & [ ChildComponent ] , Option < f32 > ) = match component {
327+ Component :: Card ( c) => ( & c. children , c . stagger ) ,
328+ Component :: Flex ( c) => ( & c. children , c . stagger ) ,
329+ Component :: Grid ( c) => ( & c. children , c . stagger ) ,
330+ Component :: Container ( c) => ( & c. children , c . stagger ) ,
331+ Component :: Positioned ( c) => ( & c. children , None ) ,
303332 _ => return Vec :: new ( ) ,
304333 } ;
334+ let step = stagger. unwrap_or ( 0.0 ) as f64 ;
305335 children
306336 . iter ( )
307337 . enumerate ( )
308338 . map ( |( j, c) | {
309339 build_child (
310340 c,
311341 components,
342+ stagger_delays,
312343 next_id,
313344 anim,
314345 format ! ( "{parent_path}/children/{j}" ) ,
346+ inherited_delay + j as f64 * step,
315347 )
316348 } )
317349 . collect ( )
0 commit comments