diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index 6cb01b77a3..e63873753d 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -8,6 +8,7 @@ use std::time::Duration; use accesskit::{NodeBuilder, TreeUpdate}; use parley::{FontContext, LayoutContext}; use tracing::{trace, warn}; +use vello::kurbo::Vec2; use crate::action::Action; use crate::render_root::{MutateCallback, RenderRootSignal, RenderRootState}; @@ -84,6 +85,13 @@ pub struct LayoutCtx<'a> { pub(crate) mouse_pos: Option, } +pub struct ComposeCtx<'a> { + pub(crate) global_state: &'a mut RenderRootState, + pub(crate) widget_state: &'a mut WidgetState, + pub(crate) widget_state_children: ArenaMutChildren<'a, WidgetState>, + pub(crate) widget_children: ArenaMutChildren<'a, Box>, +} + /// A context passed to paint methods of widgets. pub struct PaintCtx<'a> { pub(crate) global_state: &'a mut RenderRootState, @@ -114,6 +122,7 @@ impl_context_method!( EventCtx<'_>, LifeCycleCtx<'_>, LayoutCtx<'_>, + ComposeCtx<'_>, PaintCtx<'_>, AccessCtx<'_>, { @@ -187,6 +196,7 @@ impl_context_method!( MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, + ComposeCtx<'_>, PaintCtx<'_>, AccessCtx<'_>, { @@ -229,6 +239,7 @@ impl_context_method!( MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, + ComposeCtx<'_>, PaintCtx<'_>, AccessCtx<'_>, { @@ -412,6 +423,17 @@ impl_context_method!(MutateCtx<'_>, EventCtx<'_>, LifeCycleCtx<'_>, { self.widget_state.needs_layout = true; } + // TODO - Document better + /// Request a [`compose`] pass. + /// + /// The compose pass is often cheaper than the layout pass, because it can only transform individual widgets' position. + /// [`compose`]: crate::Widget::compose + pub fn request_compose(&mut self) { + trace!("request_compose"); + self.widget_state.needs_compose = true; + self.widget_state.request_compose = true; + } + pub fn request_accessibility_update(&mut self) { trace!("request_accessibility_update"); self.widget_state.needs_accessibility_update = true; @@ -494,6 +516,7 @@ impl_context_method!( EventCtx<'_>, LifeCycleCtx<'_>, LayoutCtx<'_>, + ComposeCtx<'_>, { // TODO - Remove from MutateCtx? /// Queue a callback that will be called with a [`WidgetMut`] for this widget. @@ -847,7 +870,7 @@ impl LayoutCtx<'_> { self.assert_layout_done(child, "place_child"); if origin != self.get_child_state_mut(child).origin { self.get_child_state_mut(child).origin = origin; - self.get_child_state_mut(child).needs_window_origin = true; + self.get_child_state_mut(child).translation_changed = true; } self.get_child_state_mut(child) .is_expecting_place_child_call = false; @@ -859,6 +882,22 @@ impl LayoutCtx<'_> { } } +impl ComposeCtx<'_> { + pub fn needs_compose(&self) -> bool { + self.widget_state.needs_compose + } + + /// Set a translation for the child widget. + /// + /// The translation is applied on top of the position from [`LayoutCtx::place_child`]. + pub fn set_child_translation(&mut self, translation: Vec2) { + if self.widget_state.translation != translation { + self.widget_state.translation = translation; + self.widget_state.translation_changed = true; + } + } +} + // --- MARK: OTHER STUFF --- impl_context_method!(LayoutCtx<'_>, PaintCtx<'_>, { /// Get the contexts needed to build and paint text sections. diff --git a/masonry/src/debug_logger.rs b/masonry/src/debug_logger.rs index f80611c22a..6aacacdbb7 100644 --- a/masonry/src/debug_logger.rs +++ b/masonry/src/debug_logger.rs @@ -207,7 +207,6 @@ impl DebugLogger { StateTree::new("is_explicitly_disabled", w_state.is_explicitly_disabled), StateTree::new("is_hot", w_state.is_hot), StateTree::new("needs_layout", w_state.needs_layout), - StateTree::new("needs_window_origin", w_state.needs_window_origin), StateTree::new("has_focus", w_state.has_focus), StateTree::new("request_anim", w_state.request_anim), StateTree::new("children_changed", w_state.children_changed), diff --git a/masonry/src/event.rs b/masonry/src/event.rs index 09cf9fee3e..cf0ba51166 100644 --- a/masonry/src/event.rs +++ b/masonry/src/event.rs @@ -330,11 +330,6 @@ pub enum InternalLifeCycle { /// Used to route the `DisabledChanged` event to the required widgets. RouteDisabledChanged, - - /// The parents widget origin in window coordinate space has changed. - ParentWindowOrigin { - mouse_pos: Option>, - }, } /// Event indicating status changes within the widget hierarchy. @@ -525,7 +520,6 @@ impl LifeCycle { InternalLifeCycle::RouteWidgetAdded => "RouteWidgetAdded", InternalLifeCycle::RouteFocusChanged { .. } => "RouteFocusChanged", InternalLifeCycle::RouteDisabledChanged => "RouteDisabledChanged", - InternalLifeCycle::ParentWindowOrigin { .. } => "ParentWindowOrigin", }, LifeCycle::WidgetAdded => "WidgetAdded", LifeCycle::AnimFrame(_) => "AnimFrame", @@ -550,7 +544,6 @@ impl InternalLifeCycle { InternalLifeCycle::RouteWidgetAdded | InternalLifeCycle::RouteFocusChanged { .. } | InternalLifeCycle::RouteDisabledChanged => true, - InternalLifeCycle::ParentWindowOrigin { .. } => false, } } } diff --git a/masonry/src/lib.rs b/masonry/src/lib.rs index f1bdab1ed4..f06abf00ee 100644 --- a/masonry/src/lib.rs +++ b/masonry/src/lib.rs @@ -130,8 +130,8 @@ mod tree_arena; pub use action::Action; pub use box_constraints::BoxConstraints; pub use contexts::{ - AccessCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx, RawWrapper, - RawWrapperMut, + AccessCtx, ComposeCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx, + RawWrapper, RawWrapperMut, }; pub use event::{ AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState, diff --git a/masonry/src/passes/compose.rs b/masonry/src/passes/compose.rs new file mode 100644 index 0000000000..59bec7f11e --- /dev/null +++ b/masonry/src/passes/compose.rs @@ -0,0 +1,101 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +use tracing::info_span; +use vello::kurbo::Vec2; + +use crate::render_root::{RenderRoot, RenderRootState}; +use crate::tree_arena::{ArenaMut, ArenaMutChildren}; +use crate::{ComposeCtx, Widget, WidgetId, WidgetState}; + +fn recurse_on_children( + id: WidgetId, + mut widget: ArenaMut<'_, Box>, + mut state: ArenaMutChildren<'_, WidgetState>, + mut callback: impl FnMut(ArenaMut<'_, Box>, ArenaMut<'_, WidgetState>), +) { + let parent_name = widget.item.short_type_name(); + let parent_id = id; + + for child_id in widget.item.children_ids() { + let widget = widget + .children + .get_child_mut(child_id.to_raw()) + .unwrap_or_else(|| { + panic!( + "Error in '{}' #{}: cannot find child #{} returned by children_ids()", + parent_name, + parent_id.to_raw(), + child_id.to_raw() + ) + }); + let state = state.get_child_mut(child_id.to_raw()).unwrap_or_else(|| { + panic!( + "Error in '{}' #{}: cannot find child #{} returned by children_ids()", + parent_name, + parent_id.to_raw(), + child_id.to_raw() + ) + }); + + callback(widget, state); + } +} + +fn compose_widget( + global_state: &mut RenderRootState, + mut widget: ArenaMut<'_, Box>, + mut state: ArenaMut<'_, WidgetState>, + parent_moved: bool, + parent_translation: Vec2, +) { + let moved = parent_moved || state.item.translation_changed; + let translation = parent_translation + state.item.translation + state.item.origin.to_vec2(); + state.item.window_origin = translation.to_point(); + + let mut ctx = ComposeCtx { + global_state, + widget_state: state.item, + widget_state_children: state.children.reborrow_mut(), + widget_children: widget.children.reborrow_mut(), + }; + if ctx.widget_state.request_compose { + widget.item.compose(&mut ctx); + } + + state.item.needs_compose = false; + state.item.request_compose = false; + state.item.translation_changed = false; + + let id = state.item.id; + let parent_state = state.item; + recurse_on_children( + id, + widget.reborrow_mut(), + state.children, + |widget, mut state| { + if !moved && !state.item.translation_changed && !state.item.needs_compose { + return; + } + compose_widget( + global_state, + widget, + state.reborrow_mut(), + moved, + translation, + ); + parent_state.merge_up(state.item); + }, + ); +} + +// ---------------- + +pub fn root_compose(root: &mut RenderRoot, global_root_state: &mut WidgetState) { + let _span = info_span!("compose").entered(); + + let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id()); + compose_widget(&mut root.state, root_widget, root_state, false, Vec2::ZERO); + + global_root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item); +} diff --git a/masonry/src/passes/mod.rs b/masonry/src/passes/mod.rs index 95d77ae165..f8ffaa6f5c 100644 --- a/masonry/src/passes/mod.rs +++ b/masonry/src/passes/mod.rs @@ -4,6 +4,7 @@ use crate::widget::WidgetArena; use crate::WidgetId; +pub mod compose; pub mod event; pub mod mutate; pub mod update; diff --git a/masonry/src/render_root.rs b/masonry/src/render_root.rs index b291b54fe2..8912956302 100644 --- a/masonry/src/render_root.rs +++ b/masonry/src/render_root.rs @@ -20,6 +20,7 @@ use crate::contexts::{LayoutCtx, LifeCycleCtx, PaintCtx}; use crate::debug_logger::DebugLogger; use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; use crate::event::{PointerEvent, TextEvent, WindowEvent}; +use crate::passes::compose::root_compose; use crate::passes::event::{root_on_access_event, root_on_pointer_event, root_on_text_event}; use crate::passes::mutate::{mutate_widget, run_mutate_pass}; use crate::passes::update::run_update_pointer_pass; @@ -603,11 +604,8 @@ impl RenderRoot { self.state.debug_logger.layout_tree.root = Some(self.root.id().to_raw() as u32); } - if self.root_state().needs_window_origin && !self.root_state().needs_layout { - let event = LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin { - mouse_pos: self.last_mouse_pos, - }); - self.root_lifecycle(event); + if self.root_state().needs_compose && !self.root_state().needs_layout { + root_compose(self, widget_state); } // Update the disabled state if necessary diff --git a/masonry/src/testing/helper_widgets.rs b/masonry/src/testing/helper_widgets.rs index 77b2b23cab..493b7eaab9 100644 --- a/masonry/src/testing/helper_widgets.rs +++ b/masonry/src/testing/helper_widgets.rs @@ -30,6 +30,7 @@ pub type AccessEventFn = dyn FnMut(&mut S, &mut EventCtx, &AccessEvent); pub type StatusChangeFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &StatusChange); pub type LifeCycleFn = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle); pub type LayoutFn = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size; +pub type ComposeFn = dyn FnMut(&mut S, &mut ComposeCtx); pub type PaintFn = dyn FnMut(&mut S, &mut PaintCtx, &mut Scene); pub type RoleFn = dyn Fn(&S) -> Role; pub type AccessFn = dyn FnMut(&mut S, &mut AccessCtx); @@ -49,6 +50,7 @@ pub struct ModularWidget { on_status_change: Option>>, lifecycle: Option>>, layout: Option>>, + compose: Option>>, paint: Option>>, role: Option>>, access: Option>>, @@ -96,6 +98,7 @@ pub enum Record { SC(StatusChange), L(LifeCycle), Layout(Size), + Compose, Paint, Access, } @@ -126,6 +129,7 @@ impl ModularWidget { on_status_change: None, lifecycle: None, layout: None, + compose: None, paint: None, role: None, access: None, @@ -181,6 +185,11 @@ impl ModularWidget { self } + pub fn compose_fn(mut self, f: impl FnMut(&mut S, &mut ComposeCtx) + 'static) -> Self { + self.compose = Some(Box::new(f)); + self + } + pub fn paint_fn(mut self, f: impl FnMut(&mut S, &mut PaintCtx, &mut Scene) + 'static) -> Self { self.paint = Some(Box::new(f)); self @@ -205,6 +214,8 @@ impl ModularWidget { } } +// TODO +// #[warn(clippy::missing_trait_methods)] impl Widget for ModularWidget { fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) { if let Some(f) = self.on_pointer_event.as_mut() { @@ -248,6 +259,12 @@ impl Widget for ModularWidget { .unwrap_or_else(|| Size::new(100., 100.)) } + fn compose(&mut self, ctx: &mut ComposeCtx) { + if let Some(f) = self.compose.as_mut() { + f(&mut self.state, ctx); + } + } + fn accessibility_role(&self) -> Role { if let Some(f) = self.role.as_ref() { f(&self.state) @@ -299,17 +316,11 @@ impl Widget for ReplaceChild { self.child.on_event(ctx, event) } - fn on_pointer_event(&mut self, ctx: &mut EventCtx, event: &event::PointerEvent) { - todo!() - } + fn on_pointer_event(&mut self, _ctx: &mut EventCtx, _event: &event::PointerEvent) {} - fn on_text_event(&mut self, ctx: &mut EventCtx, event: &event::TextEvent) { - todo!() - } + fn on_text_event(&mut self, _ctx: &mut EventCtx, _event: &event::TextEvent) {} - fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { - todo!() - } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {} fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) { ctx.request_layout(); @@ -323,6 +334,8 @@ impl Widget for ReplaceChild { self.child.layout(ctx, bc) } + fn compose(&mut self, _ctx: &mut ComposeCtx) {} + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { self.child.paint(ctx, scene); } @@ -403,6 +416,11 @@ impl Widget for Recorder { size } + fn compose(&mut self, ctx: &mut ComposeCtx) { + self.recording.push(Record::Compose); + self.child.compose(ctx); + } + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { self.recording.push(Record::Paint); self.child.paint(ctx, scene); diff --git a/masonry/src/tree_arena.rs b/masonry/src/tree_arena.rs index b2fd7d261e..bd1ce7eb3b 100644 --- a/masonry/src/tree_arena.rs +++ b/masonry/src/tree_arena.rs @@ -97,6 +97,14 @@ pub struct ArenaMutChildren<'a, Item> { // -- MARK: IMPLS --- +impl<'a, Item> Clone for ArenaRef<'a, Item> { + fn clone(&self) -> Self { + *self + } +} + +impl<'a, Item> Copy for ArenaRef<'a, Item> {} + impl<'a, Item> Clone for ArenaRefChildren<'a, Item> { fn clone(&self) -> Self { *self @@ -372,6 +380,40 @@ impl<'a, Item> ArenaMutChildren<'a, Item> { } } +impl<'a, Item> ArenaRef<'a, Item> { + pub fn id(&self) -> u64 { + // ArenaRefChildren always has an id when it's a member of ArenaRef + self.children.id.unwrap() + } +} + +impl<'a, Item> ArenaMut<'a, Item> { + pub fn id(&self) -> u64 { + // ArenaRefChildren always has an id when it's a member of ArenaRef + self.children.id.unwrap() + } + + /// Returns a shared token equivalent to this one. + pub fn reborrow(&mut self) -> ArenaRef<'_, Item> { + ArenaRef { + parent_id: self.parent_id, + item: self.item, + children: self.children.reborrow(), + } + } + + /// Returns a mutable token equivalent to this one. + /// + /// This is sometimes useful to work with the borrow checker. + pub fn reborrow_mut(&mut self) -> ArenaMut<'_, Item> { + ArenaMut { + parent_id: self.parent_id, + item: self.item, + children: self.children.reborrow_mut(), + } + } +} + // This is a sketch of what the unsafe version of this code would look like, // one with an actual arena. #[cfg(FALSE)] 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 0dcb07f120..4500f24514 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 @@ -18,11 +18,5 @@ expression: record Layout( 400.0W×400.0H, ), - L( - Internal( - ParentWindowOrigin { - mouse_pos: None, - }, - ), - ), + Compose, ] diff --git a/masonry/src/widget/widget.rs b/masonry/src/widget/widget.rs index a3a0cad8d6..9ae87fc6df 100644 --- a/masonry/src/widget/widget.rs +++ b/masonry/src/widget/widget.rs @@ -12,6 +12,7 @@ use smallvec::SmallVec; use tracing::{trace_span, Span}; use vello::Scene; +use crate::contexts::ComposeCtx; use crate::event::{AccessEvent, PointerEvent, StatusChange, TextEvent}; use crate::{ AccessCtx, AsAny, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Size, @@ -105,6 +106,8 @@ pub trait Widget: AsAny { /// The layout strategy is strongly inspired by Flutter. fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size; + fn compose(&mut self, ctx: &mut ComposeCtx) {} + /// Paint the widget appearance. /// /// Container widgets can paint a background before recursing to their @@ -308,6 +311,10 @@ impl Widget for Box { self.deref_mut().layout(ctx, bc) } + fn compose(&mut self, ctx: &mut ComposeCtx) { + self.deref_mut().compose(ctx); + } + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { self.deref_mut().paint(ctx, scene); } diff --git a/masonry/src/widget/widget_pod.rs b/masonry/src/widget/widget_pod.rs index a00bd10260..6d68c18882 100644 --- a/masonry/src/widget/widget_pod.rs +++ b/masonry/src/widget/widget_pod.rs @@ -371,12 +371,6 @@ impl WidgetPod { _ => false, } } - InternalLifeCycle::ParentWindowOrigin { .. } => { - state.parent_window_origin = parent_ctx.widget_state.window_origin(); - state.needs_window_origin = false; - // TODO - state.is_hidden - true - } }, LifeCycle::WidgetAdded => { trace!( @@ -554,6 +548,7 @@ impl WidgetPod { } state.needs_layout = false; + state.needs_compose = true; state.is_expecting_place_child_call = true; // TODO - Not everything that has been re-laid out needs to be repainted. state.needs_paint = true; diff --git a/masonry/src/widget/widget_ref.rs b/masonry/src/widget/widget_ref.rs index 0a85fe70a1..28901f3f6a 100644 --- a/masonry/src/widget/widget_ref.rs +++ b/masonry/src/widget/widget_ref.rs @@ -209,7 +209,7 @@ impl<'w> WidgetRef<'w, dyn Widget> { ); } - if after_layout && (self.state().needs_layout || self.state().needs_window_origin) { + if after_layout && self.state().needs_layout { debug_panic!( "Widget '{}' #{} is invalid: widget layout state not cleared", self.deref().short_type_name(), diff --git a/masonry/src/widget/widget_state.rs b/masonry/src/widget/widget_state.rs index 6565113866..3a1e614aee 100644 --- a/masonry/src/widget/widget_state.rs +++ b/masonry/src/widget/widget_state.rs @@ -4,12 +4,16 @@ #![cfg(not(tarpaulin_include))] use std::sync::atomic::{AtomicBool, Ordering}; -use vello::kurbo::{Insets, Point, Rect, Size}; +use vello::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::bloom::Bloom; use crate::text_helpers::TextFieldRegistration; use crate::{CursorIcon, WidgetId}; +// TODO - Sort out names of widget state flags in two categories: +// - request_xxx: means this widget needs the xxx pass to run on it +// - needs_xxx: means this widget or one of its descendants has requested the xxx pass + // FIXME https://github.com/linebender/xilem/issues/376 - Make a note documenting this: the only way to get a &mut WidgetState should be in a pass. // A pass should reborrow the parent widget state (to avoid crossing wires) and call merge_up at // the end so that invalidations are always bubbled up. @@ -37,14 +41,14 @@ pub struct WidgetState { pub(crate) id: WidgetId, // --- LAYOUT --- - /// The size of the child; this is the value returned by the child's layout + /// The size of the widget; this is the value returned by the widget's layout /// method. pub(crate) size: Size, - /// The origin of the child in the parent's coordinate space; together with - /// `size` these constitute the child's layout rect. + /// The origin of the widget in the parent's coordinate space; together with + /// `size` these constitute the widget's layout rect. pub(crate) origin: Point, - /// The origin of the parent in the window coordinate space; - pub(crate) parent_window_origin: Point, + /// The origin of the widget in the window coordinate space; + pub(crate) window_origin: Point, /// The insets applied to the layout rect to generate the paint rect. /// In general, these will be zero; the exception is for things like /// drop shadows or overflowing text. @@ -61,6 +65,11 @@ pub struct WidgetState { // TODO - Document pub(crate) is_portal: bool, + pub(crate) request_compose: bool, + // TODO - Handle matrix transforms + pub(crate) translation: Vec2, + pub(crate) translation_changed: bool, + // --- PASSES --- // TODO: consider using bitflags for the booleans. @@ -76,12 +85,10 @@ pub struct WidgetState { pub(crate) is_explicitly_disabled_new: bool, pub(crate) needs_layout: bool, + pub(crate) needs_compose: bool, pub(crate) needs_paint: bool, pub(crate) needs_accessibility_update: bool, - /// Because of some scrolling or something, `parent_window_origin` needs to be updated. - pub(crate) needs_window_origin: bool, - /// Any descendant has requested an animation frame. pub(crate) request_anim: bool, @@ -137,21 +144,24 @@ impl WidgetState { WidgetState { id, origin: Point::ORIGIN, - parent_window_origin: Point::ORIGIN, + window_origin: Point::ORIGIN, size: Size::ZERO, is_expecting_place_child_call: false, paint_insets: Insets::ZERO, local_paint_rect: Rect::ZERO, is_portal: false, + request_compose: true, + translation: Vec2::ZERO, + translation_changed: false, children_disabled_changed: false, ancestor_disabled: false, is_explicitly_disabled: false, baseline_offset: 0.0, is_hot: false, needs_layout: true, + needs_compose: true, needs_paint: true, needs_accessibility_update: true, - needs_window_origin: true, has_focus: false, request_anim: true, request_accessibility_update: true, @@ -179,7 +189,8 @@ impl WidgetState { needs_layout: false, needs_paint: false, needs_accessibility_update: false, - needs_window_origin: false, + needs_compose: false, + request_compose: false, request_accessibility_update: false, request_anim: false, children_changed: false, @@ -217,8 +228,8 @@ impl WidgetState { /// This method is idempotent and can be called multiple times. pub(crate) fn merge_up(&mut self, child_state: &mut WidgetState) { self.needs_layout |= child_state.needs_layout; + self.needs_compose |= child_state.needs_compose; self.needs_paint |= child_state.needs_paint; - self.needs_window_origin |= child_state.needs_window_origin; self.request_anim |= child_state.request_anim; self.request_accessibility_update |= child_state.request_accessibility_update; self.children_disabled_changed |= child_state.children_disabled_changed; @@ -259,7 +270,7 @@ impl WidgetState { } pub(crate) fn window_origin(&self) -> Point { - self.parent_window_origin + self.origin.to_vec2() + self.window_origin } }