diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index bacceb62..6de05b44 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -450,6 +450,7 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, { pub fn request_anim_frame(&mut self) { trace!("request_anim_frame"); self.widget_state.request_anim = true; + self.widget_state.needs_anim = true; } /// Indicate that your children have changed. diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index 1ec6e790..2f715e8e 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -230,3 +230,60 @@ pub(crate) fn run_update_scroll_pass(root: &mut RenderRoot) { }); } } + +// ---------------- + +fn update_anim_for_widget( + global_state: &mut RenderRootState, + mut widget: ArenaMut<'_, Box>, + mut state: ArenaMut<'_, WidgetState>, + elapsed_ns: u64, +) { + let _span = widget.item.make_trace_span().entered(); + + if !state.item.needs_anim { + return; + } + state.item.needs_anim = false; + + // Most passes reset their `needs` and `request` flags after the call to + // the widget method, but it's valid and expected for `request_anim` to be + // set in response to `AnimFrame`. + if state.item.request_anim { + state.item.request_anim = false; + let mut ctx = LifeCycleCtx { + global_state, + widget_state: state.item, + widget_state_children: state.children.reborrow_mut(), + widget_children: widget.children.reborrow_mut(), + }; + widget + .item + .lifecycle(&mut ctx, &LifeCycle::AnimFrame(elapsed_ns)); + } + + let id = state.item.id; + let parent_state = state.item; + recurse_on_children( + id, + widget.reborrow_mut(), + state.children, + |widget, mut state| { + update_anim_for_widget(global_state, widget, state.reborrow_mut(), elapsed_ns); + parent_state.merge_up(state.item); + }, + ); +} + +/// Run the animation pass. +pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) { + let _span = info_span!("update_anim").entered(); + + let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id()); + update_anim_for_widget( + &mut root.state, + root_widget, + root_state.reborrow_mut(), + elapsed_ns, + ); +} diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index 800d7bb1..390092af 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -26,7 +26,7 @@ use crate::passes::layout::root_layout; use crate::passes::mutate::{mutate_widget, run_mutate_pass}; use crate::passes::paint::root_paint; use crate::passes::update::{ - run_update_disabled_pass, run_update_pointer_pass, run_update_scroll_pass, + run_update_anim_pass, run_update_disabled_pass, run_update_pointer_pass, run_update_scroll_pass, }; use crate::text::TextBrush; use crate::tree_arena::TreeArena; @@ -208,12 +208,17 @@ impl RenderRoot { // See https://github.com/linebender/druid/issues/85 for discussion. let last = self.last_anim.take(); let elapsed_ns = last.map(|t| now.duration_since(t).as_nanos()).unwrap_or(0) as u64; - let root_state = self.root_state(); - if root_state.request_anim { - root_state.request_anim = false; - self.root_lifecycle(LifeCycle::AnimFrame(elapsed_ns)); - self.last_anim = Some(now); - } + + run_update_anim_pass(self, elapsed_ns); + + let mut root_state = self.widget_arena.get_state_mut(self.root.id()).item.clone(); + self.post_event_processing(&mut root_state); + + // If this animation will continue, store the time. + // If a new animation starts, then it will have zero reported elapsed time. + let animation_continues = root_state.needs_anim; + self.last_anim = animation_continues.then_some(now); + Handled::Yes } WindowEvent::RebuildAccessTree => { @@ -272,8 +277,6 @@ impl RenderRoot { // TODO - Xilem's reconciliation logic will have to be called // by the function that calls this - // TODO - if root widget's request_anim is still set by the - // time this is called, emit a warning if self.root_state().needs_layout { self.root_layout(); } diff --git a/masonry/src/widget/widget_pod.rs b/masonry/src/widget/widget_pod.rs index e98eb704..77fa8dea 100644 --- a/masonry/src/widget/widget_pod.rs +++ b/masonry/src/widget/widget_pod.rs @@ -333,12 +333,10 @@ impl WidgetPod { true } - LifeCycle::AnimFrame(_) => { - state.request_anim = false; - true - } // Routing DisabledChanged has been moved to the update_disabled pass LifeCycle::DisabledChanged(_) => false, + // Animations have been moved to the update_anim pass + LifeCycle::AnimFrame(_) => false, LifeCycle::BuildFocusChain => { if state.update_focus_chain { // Replace has_focus to check if the value changed in the meantime diff --git a/masonry/src/widget/widget_state.rs b/masonry/src/widget/widget_state.rs index 927c00ec..86ca5702 100644 --- a/masonry/src/widget/widget_state.rs +++ b/masonry/src/widget/widget_state.rs @@ -99,8 +99,10 @@ pub struct WidgetState { /// The accessibility method must be called on this widget or a descendant pub(crate) needs_accessibility: bool, - /// Any descendant has requested an animation frame. + /// An animation must run on this widget pub(crate) request_anim: bool, + /// An animation must run on this widget or a descendant + pub(crate) needs_anim: bool, /// This widget or a descendant changed its `explicitly_disabled` value pub(crate) needs_update_disabled: bool, @@ -174,6 +176,7 @@ impl WidgetState { needs_accessibility: true, has_focus: false, request_anim: true, + needs_anim: true, needs_update_disabled: true, focus_chain: Vec::new(), children_changed: true, @@ -197,11 +200,12 @@ impl WidgetState { needs_layout: false, request_compose: false, needs_compose: false, - needs_paint: false, request_paint: false, + needs_paint: false, request_accessibility: false, needs_accessibility: false, request_anim: false, + needs_anim: false, needs_update_disabled: false, children_changed: false, update_focus_chain: false, @@ -231,7 +235,7 @@ impl WidgetState { self.needs_layout |= child_state.needs_layout; self.needs_compose |= child_state.needs_compose; self.needs_paint |= child_state.needs_paint; - self.request_anim |= child_state.request_anim; + self.needs_anim |= child_state.needs_anim; self.needs_accessibility |= child_state.needs_accessibility; self.needs_update_disabled |= child_state.needs_update_disabled; self.has_focus |= child_state.has_focus;