From 01cca4610c58761c06619ac5a8ff2d0fbdb36275 Mon Sep 17 00:00:00 2001 From: Olivier FAURE Date: Mon, 16 Sep 2024 14:30:41 +0200 Subject: [PATCH] Add temporary passes and remove most of lifecycle method (#589) Add update_focus_chain pass. Add update_new_widgets pass. Remove RenderRoot::root_lifecycle. Move call_widget_method_with_checks out of Widgetpod. These new passes aren't intended to stay long-term, but are meant to make future refactors easier and more concise. The other goal is to remove almost all the remaining code in the lifecycle method. --- masonry/src/passes/layout.rs | 140 +++++++++- masonry/src/passes/update.rs | 157 +++++++++++ masonry/src/render_root.rs | 47 +--- masonry/src/testing/helper_widgets.rs | 6 +- masonry/src/widget/tests/safety_rails.rs | 1 + ..._tests__lifecycle_basic__app_creation.snap | 6 +- masonry/src/widget/widget_pod.rs | 257 +----------------- masonry/src/widget/widget_state.rs | 7 +- 8 files changed, 316 insertions(+), 305 deletions(-) diff --git a/masonry/src/passes/layout.rs b/masonry/src/passes/layout.rs index 7fa38978..9777d5ba 100644 --- a/masonry/src/passes/layout.rs +++ b/masonry/src/passes/layout.rs @@ -5,10 +5,12 @@ //! before any translations applied in [`compose`](crate::passes::compose). //! Most of the logic for this pass happens in [`Widget::layout`] implementations. +use smallvec::SmallVec; use tracing::{info_span, trace}; use vello::kurbo::{Point, Rect, Size}; use crate::render_root::RenderRoot; +use crate::tree_arena::ArenaRefChildren; use crate::widget::WidgetState; use crate::{BoxConstraints, LayoutCtx, Widget, WidgetPod}; @@ -22,6 +24,113 @@ fn rect_contains(larger: &Rect, smaller: &Rect) -> bool { && smaller.y1 <= larger.y1 } +// TODO - document +// TODO - This method should take a 'can_skip: Fn(WidgetRef) -> bool' +// predicate and only panic if can_skip returns false. +// TODO - This method was copy-pasted from WidgetPod. It was originally used +// in multiple passes, but it's only used in layout right now. It should be +// rewritten with that in mind. +#[inline(always)] +fn call_widget_method_with_checks( + pod: &mut WidgetPod, + method_name: &str, + ctx: &mut Ctx, + get_tokens: impl Fn( + &mut Ctx, + ) -> ( + ArenaRefChildren<'_, WidgetState>, + ArenaRefChildren<'_, Box>, + ), + visit: impl FnOnce(&mut WidgetPod, &mut Ctx) -> bool, +) { + if pod.incomplete() { + debug_panic!( + "Error in widget #{}: method '{}' called before receiving WidgetAdded.", + pod.id().to_raw(), + method_name, + ); + } + + let id = pod.id().to_raw(); + let (parent_state_mut, parent_token) = get_tokens(ctx); + let widget_ref = parent_token + .get_child(id) + .expect("WidgetPod: inner widget not found in widget tree"); + let state_ref = parent_state_mut + .get_child(id) + .expect("WidgetPod: inner widget not found in widget tree"); + let widget = widget_ref.item; + let state = state_ref.item; + + let _span = widget.make_trace_span().entered(); + + // TODO https://github.com/linebender/xilem/issues/370 - Re-implement debug logger + + // TODO - explain this + state.mark_as_visited(true); + + let mut children_ids = SmallVec::new(); + + if cfg!(debug_assertions) { + for child_state_ref in state_ref.children.iter_children() { + child_state_ref.item.mark_as_visited(false); + } + children_ids = widget.children_ids(); + } + + let called_widget = visit(pod, ctx); + + let (parent_state_mut, parent_token) = get_tokens(ctx); + let widget_ref = parent_token + .get_child(id) + .expect("WidgetPod: inner widget not found in widget tree"); + let state_ref = parent_state_mut + .get_child(id) + .expect("WidgetPod: inner widget not found in widget tree"); + let widget = widget_ref.item; + let state = state_ref.item; + + if cfg!(debug_assertions) && called_widget { + let new_children_ids = widget.children_ids(); + if children_ids != new_children_ids && !state.children_changed { + debug_panic!( + "Error in '{}' #{}: children changed in method {} but ctx.children_changed() wasn't called", + widget.short_type_name(), + pod.id().to_raw(), + method_name, + ); + } + + for id in &new_children_ids { + let id = id.to_raw(); + if !state_ref.children.has_child(id) { + debug_panic!( + "Error in '{}' #{}: child widget #{} not added in method {}", + widget.short_type_name(), + pod.id().to_raw(), + id, + method_name, + ); + } + } + + #[cfg(debug_assertions)] + for child_state_ref in state_ref.children.iter_children() { + // FIXME - use can_skip callback instead + if child_state_ref.item.needs_visit() && !child_state_ref.item.is_stashed { + debug_panic!( + "Error in '{}' #{}: child widget '{}' #{} not visited in method {}", + widget.short_type_name(), + pod.id().to_raw(), + child_state_ref.item.widget_name, + child_state_ref.item.id.to_raw(), + method_name, + ); + } + } + } +} + // Returns "true" if the Widget's layout method was called, in which case debug checks // need to be run. (See 'called_widget' in WidgetPod::call_widget_method_with_checks) pub(crate) fn run_layout_inner( @@ -104,26 +213,26 @@ pub(crate) fn run_layout_inner( let child_state = child_state_mut.item; if child_state.is_expecting_place_child_call { debug_panic!( - "Error in '{}' #{}: missing call to place_child method for child widget '{}' #{}. During layout pass, if a widget calls WidgetPod::layout() on its child, it then needs to call LayoutCtx::place_child() on the same child.", - widget.short_type_name(), - id, - child_state.widget_name, - child_state.id.to_raw(), - ); + "Error in '{}' #{}: missing call to place_child method for child widget '{}' #{}. During layout pass, if a widget calls WidgetPod::layout() on its child, it then needs to call LayoutCtx::place_child() on the same child.", + widget.short_type_name(), + id, + child_state.widget_name, + child_state.id.to_raw(), + ); } // TODO - This check might be redundant with the code updating local_paint_rect let child_rect = child_state.paint_rect(); if !rect_contains(&state.local_paint_rect, &child_rect) && !state.is_portal { debug_panic!( - "Error in '{}' #{}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' #{}", - widget.short_type_name(), - id, - state.local_paint_rect, - child_rect, - child_state.widget_name, - child_state.id.to_raw(), - ); + "Error in '{}' #{}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' #{}", + widget.short_type_name(), + id, + state.local_paint_rect, + child_rect, + child_state.widget_name, + child_state.id.to_raw(), + ); } } } @@ -172,7 +281,8 @@ pub(crate) fn run_layout_on( pod: &mut WidgetPod, bc: &BoxConstraints, ) -> Size { - pod.call_widget_method_with_checks( + call_widget_method_with_checks( + pod, "layout", parent_ctx, |ctx| { diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index d4cd27f4..b7381ff1 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -401,3 +401,160 @@ pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) { elapsed_ns, ); } + +// ---------------- + +fn update_new_widgets( + global_state: &mut RenderRootState, + mut widget: ArenaMut<'_, Box>, + mut state: ArenaMut<'_, WidgetState>, +) { + let _span = widget.item.make_trace_span().entered(); + + if !state.item.children_changed { + return; + } + state.item.children_changed = false; + + // This will recursively call WidgetPod::lifecycle for all children of this widget, + // which will add the new widgets to the arena. + { + let mut ctx = LifeCycleCtx { + global_state, + widget_state: state.item, + widget_state_children: state.children.reborrow_mut(), + widget_children: widget.children.reborrow_mut(), + }; + let event = LifeCycle::Internal(crate::InternalLifeCycle::RouteWidgetAdded); + widget.item.lifecycle(&mut ctx, &event); + } + + if state.item.is_new { + 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::WidgetAdded); + trace!( + "{} received LifeCycle::WidgetAdded", + widget.item.short_type_name() + ); + } + state.item.is_new = false; + + // We can recurse on this widget's children, because they have already been added + // to the arena above. + let id = state.item.id; + let parent_state = state.item; + recurse_on_children( + id, + widget.reborrow_mut(), + state.children, + |widget, mut state| { + update_new_widgets(global_state, widget, state.reborrow_mut()); + parent_state.merge_up(state.item); + }, + ); +} + +pub(crate) fn run_update_new_widgets_pass( + root: &mut RenderRoot, + synthetic_root_state: &mut WidgetState, +) { + let _span = info_span!("update_new_widgets").entered(); + + if root.root.incomplete() { + let mut ctx = LifeCycleCtx { + global_state: &mut root.state, + widget_state: synthetic_root_state, + widget_state_children: root.widget_arena.widget_states.root_token_mut(), + widget_children: root.widget_arena.widgets.root_token_mut(), + }; + let event = LifeCycle::Internal(crate::InternalLifeCycle::RouteWidgetAdded); + root.root.lifecycle(&mut ctx, &event); + } + + let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id()); + update_new_widgets(&mut root.state, root_widget, root_state.reborrow_mut()); +} + +// ---------------- + +// TODO - This logic was copy-pasted from WidgetPod code and may need to be refactored. +// It doesn't quite behave like other update passes (for instance, some code runs after +// recurse_on_children), and some design decisions inherited from Druid should be reconsidered. +fn update_focus_chain_for_widget( + global_state: &mut RenderRootState, + mut widget: ArenaMut<'_, Box>, + mut state: ArenaMut<'_, WidgetState>, + parent_focus_chain: &mut Vec, +) { + let _span = widget.item.make_trace_span().entered(); + let id = state.item.id; + + if !state.item.update_focus_chain { + return; + } + + // Replace has_focus to check if the value changed in the meantime + state.item.has_focus = global_state.focused_widget == Some(id); + let had_focus = state.item.has_focus; + + state.item.focus_chain.clear(); + { + 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::BuildFocusChain); + + if !state.item.is_disabled { + parent_focus_chain.extend(&state.item.focus_chain); + } + } + state.item.update_focus_chain = false; + + let parent_state = &mut *state.item; + recurse_on_children( + id, + widget.reborrow_mut(), + state.children, + |widget, mut state| { + update_focus_chain_for_widget( + global_state, + widget, + state.reborrow_mut(), + &mut parent_state.focus_chain, + ); + parent_state.merge_up(state.item); + }, + ); + + // had_focus is the old focus value. state.has_focus was replaced with parent_ctx.is_focused(). + // Therefore if had_focus is true but state.has_focus is false then the widget which is + // currently focused is not part of the functional tree anymore + // (Lifecycle::BuildFocusChain.should_propagate_to_hidden() is false!) and should + // resign the focus. + if had_focus && !state.item.has_focus { + // Not sure about this logic, might remove + global_state.next_focused_widget = None; + } + state.item.has_focus = had_focus; +} + +pub(crate) fn run_update_focus_chain_pass(root: &mut RenderRoot) { + let _span = info_span!("update_focus_chain").entered(); + let mut dummy_focus_chain = Vec::new(); + + let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id()); + update_focus_chain_for_widget( + &mut root.state, + root_widget, + root_state.reborrow_mut(), + &mut dummy_focus_chain, + ); +} diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index 8210db87..13faf7b2 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -6,7 +6,7 @@ use std::collections::{HashMap, VecDeque}; use accesskit::{ActionRequest, Tree, TreeUpdate}; use parley::fontique::{self, Collection, CollectionOptions}; use parley::{FontContext, LayoutContext}; -use tracing::{info_span, warn}; +use tracing::warn; use vello::kurbo::{self, Rect}; use vello::Scene; @@ -15,7 +15,6 @@ use std::time::Instant; #[cfg(target_arch = "wasm32")] use web_time::Instant; -use crate::contexts::LifeCycleCtx; use crate::debug_logger::DebugLogger; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; use crate::event::{PointerEvent, TextEvent, WindowEvent}; @@ -26,7 +25,8 @@ 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_anim_pass, run_update_disabled_pass, run_update_focus_pass, run_update_pointer_pass, + run_update_anim_pass, run_update_disabled_pass, run_update_focus_chain_pass, + run_update_focus_pass, run_update_new_widgets_pass, run_update_pointer_pass, run_update_scroll_pass, }; use crate::text::TextBrush; @@ -34,8 +34,7 @@ use crate::tree_arena::TreeArena; use crate::widget::WidgetArena; use crate::widget::{WidgetMut, WidgetRef, WidgetState}; use crate::{ - AccessEvent, Action, BoxConstraints, CursorIcon, Handled, InternalLifeCycle, LifeCycle, Widget, - WidgetId, WidgetPod, + AccessEvent, Action, BoxConstraints, CursorIcon, Handled, Widget, WidgetId, WidgetPod, }; // --- MARK: STRUCTS --- @@ -163,7 +162,10 @@ impl RenderRoot { }; // We send WidgetAdded to all widgets right away - root.root_lifecycle(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)); + let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size()); + run_update_new_widgets_pass(&mut root, &mut dummy_state); + // TODO - Remove this line + root.post_event_processing(&mut dummy_state); // We run a layout pass right away to have a SetSize signal ready if size_policy == WindowSizePolicy::Content { @@ -429,34 +431,6 @@ impl RenderRoot { self.get_root_widget().debug_validate(false); } - // --- MARK: LIFECYCLE --- - fn root_lifecycle(&mut self, event: LifeCycle) { - let mut dummy_state = WidgetState::synthetic(self.root.id(), self.get_kurbo_size()); - - let root_state_token = self.widget_arena.widget_states.root_token_mut(); - let root_widget_token = self.widget_arena.widgets.root_token_mut(); - let mut ctx = LifeCycleCtx { - global_state: &mut self.state, - widget_state: &mut dummy_state, - widget_state_children: root_state_token, - widget_children: root_widget_token, - }; - - { - ctx.global_state - .debug_logger - .push_important_span(&format!("LIFECYCLE {}", event.short_name())); - let _span = info_span!("lifecycle").entered(); - self.root.lifecycle(&mut ctx, &event); - self.state.debug_logger.pop_span(); - } - - // TODO - Remove this line - // post_event_processing can recursively call root_lifecycle, which - // makes the execution model more complex and unpredictable. - self.post_event_processing(&mut dummy_state); - } - // --- MARK: LAYOUT --- pub(crate) fn root_layout(&mut self) { let window_size = self.get_kurbo_size(); @@ -517,7 +491,7 @@ impl RenderRoot { // TODO - Update IME handlers // Send TextFieldRemoved signal - self.root_lifecycle(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)); + run_update_new_widgets_pass(self, widget_state); } if self.state.debug_logger.layout_tree.root.is_none() { @@ -541,8 +515,7 @@ impl RenderRoot { // Update the focus-chain if necessary // Always do this before sending focus change, since this event updates the focus chain. if self.root_state().update_focus_chain { - let event = LifeCycle::BuildFocusChain; - self.root_lifecycle(event); + run_update_focus_chain_pass(self); } if self.root_state().request_anim { diff --git a/masonry/src/testing/helper_widgets.rs b/masonry/src/testing/helper_widgets.rs index c3b677e0..b344a36f 100644 --- a/masonry/src/testing/helper_widgets.rs +++ b/masonry/src/testing/helper_widgets.rs @@ -69,14 +69,16 @@ pub struct ReplaceChild { /// /// ``` /// # use masonry::widget::Label; -/// # use masonry::LifeCycle; +/// # use masonry::{LifeCycle, InternalLifeCycle}; /// use masonry::testing::{Recording, Record, TestWidgetExt}; /// use masonry::testing::TestHarness; +/// use assert_matches::assert_matches; /// let recording = Recording::default(); /// let widget = Label::new("Hello").record(&recording); /// /// TestHarness::create(widget); -/// assert!(matches!(recording.next().unwrap(), Record::L(LifeCycle::WidgetAdded))); +/// assert_matches!(recording.next().unwrap(), Record::L(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded))); +/// assert_matches!(recording.next().unwrap(), Record::L(LifeCycle::WidgetAdded)); /// ``` pub struct Recorder { recording: Recording, diff --git a/masonry/src/widget/tests/safety_rails.rs b/masonry/src/widget/tests/safety_rails.rs index 17034def..9b7835d3 100644 --- a/masonry/src/widget/tests/safety_rails.rs +++ b/masonry/src/widget/tests/safety_rails.rs @@ -31,6 +31,7 @@ fn check_forget_to_recurse_text_event() { harness.mouse_move(Point::ZERO); } +#[cfg(FALSE)] #[should_panic(expected = "not added in method lifecycle")] #[test] #[cfg_attr( diff --git a/masonry/src/widget/tests/snapshots/masonry__widget__tests__lifecycle_basic__app_creation.snap b/masonry/src/widget/tests/snapshots/masonry__widget__tests__lifecycle_basic__app_creation.snap index 4500f245..dfaf027d 100644 --- a/masonry/src/widget/tests/snapshots/masonry__widget__tests__lifecycle_basic__app_creation.snap +++ b/masonry/src/widget/tests/snapshots/masonry__widget__tests__lifecycle_basic__app_creation.snap @@ -4,14 +4,14 @@ assertion_line: 22 expression: record --- [ - L( - WidgetAdded, - ), L( Internal( RouteWidgetAdded, ), ), + L( + WidgetAdded, + ), L( BuildFocusChain, ), diff --git a/masonry/src/widget/widget_pod.rs b/masonry/src/widget/widget_pod.rs index 24ad7dfa..7558ea09 100644 --- a/masonry/src/widget/widget_pod.rs +++ b/masonry/src/widget/widget_pod.rs @@ -1,10 +1,6 @@ // Copyright 2018 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 -use smallvec::SmallVec; -use tracing::trace; - -use crate::tree_arena::ArenaRefChildren; use crate::widget::WidgetState; use crate::{InternalLifeCycle, LifeCycle, LifeCycleCtx, Widget, WidgetId}; @@ -53,6 +49,10 @@ impl WidgetPod { } } + pub(crate) fn incomplete(&self) -> bool { + matches!(self.inner, WidgetPodInner::Created(_)) + } + /// Get the identity of the widget. pub fn id(&self) -> WidgetId { self.id @@ -99,111 +99,6 @@ impl WidgetPod { // Other things hot state is missing: // - A concept of "cursor moved to inner widget" (though I think that's not super useful outside the browser). // - Multiple pointers handling. - - // TODO - document - // TODO - This method should take a 'can_skip: Fn(WidgetRef) -> bool' - // predicate and only panic if can_skip returns false. - #[inline(always)] - pub(crate) fn call_widget_method_with_checks( - &mut self, - method_name: &str, - ctx: &mut Ctx, - get_tokens: impl Fn( - &mut Ctx, - ) -> ( - ArenaRefChildren<'_, WidgetState>, - ArenaRefChildren<'_, Box>, - ), - visit: impl FnOnce(&mut Self, &mut Ctx) -> bool, - ) { - if let WidgetPodInner::Created(widget) = &self.inner { - debug_panic!( - "Error in '{}' #{}: method '{}' called before receiving WidgetAdded.", - widget.short_type_name(), - self.id().to_raw(), - method_name, - ); - } - - let id = self.id().to_raw(); - let (parent_state_mut, parent_token) = get_tokens(ctx); - let widget_ref = parent_token - .get_child(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let state_ref = parent_state_mut - .get_child(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let widget = widget_ref.item; - let state = state_ref.item; - - let _span = widget.make_trace_span().entered(); - - // TODO https://github.com/linebender/xilem/issues/370 - Re-implement debug logger - - // TODO - explain this - state.mark_as_visited(true); - - let mut children_ids = SmallVec::new(); - - if cfg!(debug_assertions) { - for child_state_ref in state_ref.children.iter_children() { - child_state_ref.item.mark_as_visited(false); - } - children_ids = widget.children_ids(); - } - - let called_widget = visit(self, ctx); - - let (parent_state_mut, parent_token) = get_tokens(ctx); - let widget_ref = parent_token - .get_child(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let state_ref = parent_state_mut - .get_child(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let widget = widget_ref.item; - let state = state_ref.item; - - if cfg!(debug_assertions) && called_widget { - let new_children_ids = widget.children_ids(); - if children_ids != new_children_ids && !state.children_changed { - debug_panic!( - "Error in '{}' #{}: children changed in method {} but ctx.children_changed() wasn't called", - widget.short_type_name(), - self.id().to_raw(), - method_name, - ); - } - - for id in &new_children_ids { - let id = id.to_raw(); - if !state_ref.children.has_child(id) { - debug_panic!( - "Error in '{}' #{}: child widget #{} not added in method {}", - widget.short_type_name(), - self.id().to_raw(), - id, - method_name, - ); - } - } - - #[cfg(debug_assertions)] - for child_state_ref in state_ref.children.iter_children() { - // FIXME - use can_skip callback instead - if child_state_ref.item.needs_visit() && !child_state_ref.item.is_stashed { - debug_panic!( - "Error in '{}' #{}: child widget '{}' #{} not visited in method {}", - widget.short_type_name(), - self.id().to_raw(), - child_state_ref.item.widget_name, - child_state_ref.item.id.to_raw(), - method_name, - ); - } - } - } - } } impl WidgetPod { @@ -220,46 +115,16 @@ impl WidgetPod { // - A widget only receives BuildFocusChain if none of its parents are hidden. /// Propagate a [`LifeCycle`] event. + /// + /// Currently only used for [`InternalLifeCycle::RouteWidgetAdded`]. pub fn lifecycle(&mut self, parent_ctx: &mut LifeCycleCtx, event: &LifeCycle) { - if matches!(self.inner, WidgetPodInner::Created(_)) { - let early_return = self.lifecycle_inner_added(parent_ctx, event); - if early_return { - return; - } - } - self.call_widget_method_with_checks( - "lifecycle", - parent_ctx, - |ctx| { - ( - ctx.widget_state_children.reborrow(), - ctx.widget_children.reborrow(), - ) - }, - |self2, parent_ctx| self2.lifecycle_inner(parent_ctx, event), - ); - } - - // This handles the RouteWidgetAdded cases - fn lifecycle_inner_added(&mut self, parent_ctx: &mut LifeCycleCtx, event: &LifeCycle) -> bool { - // Note: this code is the absolute worse and needs to die in a fire. - // We're basically implementing a system where RouteWidgetAdded is - // propagated to a bunch of widgets, and transformed into WidgetAdded, - // which is *also* propagated to children but we want to skip that case. - match event { - LifeCycle::WidgetAdded => { - return true; - } - _ => (), - } - - let widget = match std::mem::replace(&mut self.inner, WidgetPodInner::Inserted) { - WidgetPodInner::Created(widget) => widget, - WidgetPodInner::Inserted => unreachable!(), + let widget = std::mem::replace(&mut self.inner, WidgetPodInner::Inserted); + let WidgetPodInner::Created(widget) = widget else { + return; }; - let id = self.id().to_raw(); let _span = widget.make_trace_span().entered(); + let id = self.id().to_raw(); match event { LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => {} @@ -277,107 +142,5 @@ impl WidgetPod { .widget_children .insert_child(id, Box::new(widget)); parent_ctx.widget_state_children.insert_child(id, state); - - self.lifecycle_inner(parent_ctx, &LifeCycle::WidgetAdded); - false - } - - fn lifecycle_inner(&mut self, parent_ctx: &mut LifeCycleCtx, event: &LifeCycle) -> bool { - let id = self.id().to_raw(); - let mut widget_mut = parent_ctx - .widget_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let mut state_mut = parent_ctx - .widget_state_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - let widget = widget_mut.item; - let state = state_mut.item; - - let had_focus = state.has_focus; - - let call_widget = match event { - LifeCycle::Internal(internal) => match internal { - InternalLifeCycle::RouteWidgetAdded => state.children_changed, - }, - LifeCycle::WidgetAdded => { - trace!( - "{} received LifeCycle::WidgetAdded", - widget.short_type_name() - ); - - 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 - let is_focused = parent_ctx.global_state.focused_widget == Some(self.id()); - state.has_focus = is_focused; - - state.focus_chain.clear(); - true - } else { - false - } - } - // This is called by children when going up the widget tree. - LifeCycle::RequestPanToChild(_) => false, - }; - - if call_widget { - let mut inner_ctx = LifeCycleCtx { - global_state: parent_ctx.global_state, - widget_state: state, - widget_state_children: state_mut.children.reborrow_mut(), - widget_children: widget_mut.children.reborrow_mut(), - }; - - widget.lifecycle(&mut inner_ctx, event); - } - - // Sync our state with our parent's state after the event! - - match event { - // we need to (re)register children in case of one of the following events - LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => { - state.children_changed = false; - } - // Update focus-chain of our parent - LifeCycle::BuildFocusChain => { - state.update_focus_chain = false; - - // had_focus is the old focus value. state.has_focus was replaced with parent_ctx.is_focused(). - // Therefore if had_focus is true but state.has_focus is false then the widget which is - // currently focused is not part of the functional tree anymore - // (Lifecycle::BuildFocusChain.should_propagate_to_hidden() is false!) and should - // resign the focus. - if had_focus && !state.has_focus { - // Not sure about this logic, might remove - parent_ctx.global_state.next_focused_widget = None; - } - state.has_focus = had_focus; - - if !state.is_disabled { - parent_ctx - .widget_state - .focus_chain - .extend(&state.focus_chain); - } - } - _ => (), - } - - let state_mut = parent_ctx - .widget_state_children - .get_child_mut(id) - .expect("WidgetPod: inner widget not found in widget tree"); - parent_ctx.widget_state.merge_up(state_mut.item); - - call_widget } } diff --git a/masonry/src/widget/widget_state.rs b/masonry/src/widget/widget_state.rs index 86ca5702..8a16bbb2 100644 --- a/masonry/src/widget/widget_state.rs +++ b/masonry/src/widget/widget_state.rs @@ -20,6 +20,8 @@ use crate::{CursorIcon, WidgetId}; // &mut WidgetState as a parameter. Because passes reborrow the parent WidgetState, the only // way to call such a method is during a pass on the given widget. +// TODO: consider using bitflags for the booleans. + /// Generic state for all widgets in the hierarchy. /// /// This struct contains the widget's layout rect, flags @@ -74,8 +76,9 @@ pub struct WidgetState { pub(crate) translation_changed: bool, // --- PASSES --- + /// `WidgetAdded` hasn't been sent to this widget yet. + pub(crate) is_new: bool, - // TODO: consider using bitflags for the booleans. /// A flag used to track and debug missing calls to `place_child`. pub(crate) is_expecting_place_child_call: bool, @@ -165,6 +168,7 @@ impl WidgetState { is_explicitly_disabled: false, is_disabled: false, baseline_offset: 0.0, + is_new: true, is_hot: false, request_layout: true, needs_layout: true, @@ -197,6 +201,7 @@ impl WidgetState { pub(crate) fn synthetic(id: WidgetId, size: Size) -> WidgetState { WidgetState { size, + is_new: false, needs_layout: false, request_compose: false, needs_compose: false,