Replace RouteWidgetAdded event with new register_children method (#602)

Make lifecycle method optional.
Remove InternalLifecycle.
This commit is contained in:
Olivier FAURE 2024-09-23 10:25:32 +02:00 committed by GitHub
parent 12ddfa35a5
commit 2642a9e146
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 197 additions and 266 deletions

View File

@ -12,8 +12,8 @@ use masonry::app_driver::{AppDriver, DriverCtx};
use masonry::dpi::LogicalSize;
use masonry::widget::{Align, CrossAxisAlignment, Flex, Label, RootWidget, SizedBox};
use masonry::{
AccessCtx, AccessEvent, Action, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
AccessCtx, AccessEvent, Action, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycleCtx,
PaintCtx, Point, PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId,
WidgetPod,
};
use smallvec::{smallvec, SmallVec};
@ -211,8 +211,8 @@ impl Widget for CalcButton {
}
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.inner.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.inner);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -12,9 +12,9 @@ use masonry::app_driver::{AppDriver, DriverCtx};
use masonry::kurbo::{BezPath, Stroke};
use masonry::widget::{FillStrat, RootWidget};
use masonry::{
AccessCtx, AccessEvent, Action, Affine, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget,
WidgetId,
AccessCtx, AccessEvent, Action, Affine, BoxConstraints, Color, EventCtx, LayoutCtx,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, RegisterCtx, Size, StatusChange, TextEvent,
Widget, WidgetId,
};
use parley::layout::Alignment;
use parley::style::{FontFamily, FontStack, StyleProperty};
@ -42,7 +42,7 @@ impl Widget for CustomWidget {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}

View File

@ -64,6 +64,12 @@ pub struct EventCtx<'a> {
pub(crate) is_handled: bool,
}
/// A context provided to the [`Widget::register_children`] method on widgets.
pub struct RegisterCtx<'a> {
pub(crate) widget_state_children: ArenaMutChildren<'a, WidgetState>,
pub(crate) widget_children: ArenaMutChildren<'a, Box<dyn Widget>>,
}
/// A context provided to the [`lifecycle`] method on widgets.
///
/// [`lifecycle`]: Widget::lifecycle
@ -709,6 +715,24 @@ impl EventCtx<'_> {
}
}
impl RegisterCtx<'_> {
/// Register a child widget.
///
/// Container widgets should call this on all their children in
/// their implementation of [`Widget::register_children`].
pub fn register_child(&mut self, child: &mut WidgetPod<impl Widget>) {
let Some(widget) = child.take_inner() else {
return;
};
let id = child.id().to_raw();
let state = WidgetState::new(child.id(), widget.short_type_name());
self.widget_children.insert_child(id, Box::new(widget));
self.widget_state_children.insert_child(id, state);
}
}
impl LifeCycleCtx<'_> {
/// Register this widget to be eligile to accept focus automatically.
///

View File

@ -301,25 +301,6 @@ pub enum LifeCycle {
/// Called when a child widgets uses
/// [`EventCtx::request_pan_to_this`](crate::EventCtx::request_pan_to_this).
RequestPanToChild(Rect),
/// Internal Masonry lifecycle event.
///
/// This should always be passed down to descendant [`WidgetPod`]s.
///
/// [`WidgetPod`]: crate::WidgetPod
Internal(InternalLifeCycle),
}
/// Internal lifecycle events used by Masonry inside [`WidgetPod`].
///
/// These events are translated into regular [`LifeCycle`] events
/// and should not be used directly.
///
/// [`WidgetPod`]: crate::WidgetPod
#[derive(Debug, Clone)]
pub enum InternalLifeCycle {
/// Used to route the `WidgetAdded` event to the required widgets.
RouteWidgetAdded,
}
/// Event indicating status changes within the widget hierarchy.
@ -498,7 +479,6 @@ impl LifeCycle {
/// [`Event::should_propagate_to_hidden`]: Event::should_propagate_to_hidden
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
LifeCycle::Internal(internal) => internal.should_propagate_to_hidden(),
LifeCycle::WidgetAdded => true,
LifeCycle::AnimFrame(_) => true,
LifeCycle::DisabledChanged(_) => true,
@ -512,9 +492,6 @@ impl LifeCycle {
/// Essentially returns the enum variant name.
pub fn short_name(&self) -> &str {
match self {
LifeCycle::Internal(internal) => match internal {
InternalLifeCycle::RouteWidgetAdded => "RouteWidgetAdded",
},
LifeCycle::WidgetAdded => "WidgetAdded",
LifeCycle::AnimFrame(_) => "AnimFrame",
LifeCycle::DisabledChanged(_) => "DisabledChanged",
@ -523,19 +500,3 @@ impl LifeCycle {
}
}
}
impl InternalLifeCycle {
/// Whether this event should be sent to widgets which are currently not visible and not
/// accessible.
///
/// If a widget changes which children are `hidden` it must call [`children_changed`].
/// For a more detailed explanation of the `hidden` state, see [`Event::should_propagate_to_hidden`].
///
/// [`children_changed`]: crate::EventCtx::children_changed
/// [`Event::should_propagate_to_hidden`]: Event::should_propagate_to_hidden
pub fn should_propagate_to_hidden(&self) -> bool {
match self {
InternalLifeCycle::RouteWidgetAdded => true,
}
}
}

View File

@ -131,11 +131,11 @@ pub use action::Action;
pub use box_constraints::BoxConstraints;
pub use contexts::{
AccessCtx, ComposeCtx, EventCtx, IsContext, LayoutCtx, LifeCycleCtx, MutateCtx, PaintCtx,
RawWrapper, RawWrapperMut,
RawWrapper, RawWrapperMut, RegisterCtx,
};
pub use event::{
AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState,
StatusChange, TextEvent, WindowEvent, WindowTheme,
AccessEvent, LifeCycle, PointerButton, PointerEvent, PointerState, StatusChange, TextEvent,
WindowEvent, WindowTheme,
};
pub use kurbo::{Affine, Insets, Point, Rect, Size, Vec2};
pub use parley::layout::Alignment as TextAlignment;

View File

@ -129,6 +129,11 @@ pub(crate) fn root_on_pointer_event(
handled
}
// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
// - If a Widget gets a keyboard event or an ImeStateChange, then
// focus is on it, its child or its parent.
// - If a Widget has focus, then none of its parents is hidden
pub(crate) fn root_on_text_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,

View File

@ -9,7 +9,7 @@ use tracing::{info_span, trace};
use crate::passes::{merge_state_up, recurse_on_children};
use crate::render_root::{RenderRoot, RenderRootSignal, RenderRootState};
use crate::tree_arena::ArenaMut;
use crate::{LifeCycle, LifeCycleCtx, StatusChange, Widget, WidgetId, WidgetState};
use crate::{LifeCycle, LifeCycleCtx, RegisterCtx, StatusChange, Widget, WidgetId, WidgetState};
fn get_id_path(root: &RenderRoot, widget_id: Option<WidgetId>) -> Vec<WidgetId> {
let Some(widget_id) = widget_id else {
@ -416,17 +416,14 @@ fn update_new_widgets(
}
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,
let mut ctx = RegisterCtx {
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);
// The widget will call `RegisterCtx::register_child` on all its children,
// which will add the new widgets to the arena.
widget.item.register_children(&mut ctx);
}
if state.item.is_new {
@ -459,21 +456,15 @@ fn update_new_widgets(
);
}
pub(crate) fn run_update_new_widgets_pass(
root: &mut RenderRoot,
synthetic_root_state: &mut WidgetState,
) {
pub(crate) fn run_update_new_widgets_pass(root: &mut RenderRoot) {
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,
let mut ctx = RegisterCtx {
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);
ctx.register_child(&mut root.root);
}
let (root_widget, mut root_state) = root.widget_arena.get_pair_mut(root.root.id());
@ -482,6 +473,9 @@ pub(crate) fn run_update_new_widgets_pass(
// ----------------
// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
// - A widget only receives BuildFocusChain if none of its parents are hidden.
// 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.

View File

@ -160,10 +160,10 @@ impl RenderRoot {
rebuild_access_tree: true,
};
// We send WidgetAdded to all widgets right away
let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size());
run_update_new_widgets_pass(&mut root, &mut dummy_state);
// We run a set of passes to initialize the widget tree
run_update_new_widgets_pass(&mut root);
// TODO - Remove this line
let mut dummy_state = WidgetState::synthetic(root.root.id(), root.get_kurbo_size());
root.post_event_processing(&mut dummy_state);
// We run a layout pass right away to have a SetSize signal ready
@ -490,7 +490,7 @@ impl RenderRoot {
// TODO - Update IME handlers
// Send TextFieldRemoved signal
run_update_new_widgets_pass(self, widget_state);
run_update_new_widgets_pass(self);
}
if self.state.debug_logger.layout_tree.root.is_none() {

View File

@ -27,6 +27,7 @@ use crate::*;
pub type PointerEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &PointerEvent);
pub type TextEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &TextEvent);
pub type AccessEventFn<S> = dyn FnMut(&mut S, &mut EventCtx, &AccessEvent);
pub type RegisterChildrenFn<S> = dyn FnMut(&mut S, &mut RegisterCtx);
pub type StatusChangeFn<S> = dyn FnMut(&mut S, &mut LifeCycleCtx, &StatusChange);
pub type LifeCycleFn<S> = dyn FnMut(&mut S, &mut LifeCycleCtx, &LifeCycle);
pub type LayoutFn<S> = dyn FnMut(&mut S, &mut LayoutCtx, &BoxConstraints) -> Size;
@ -47,6 +48,7 @@ pub struct ModularWidget<S> {
on_pointer_event: Option<Box<PointerEventFn<S>>>,
on_text_event: Option<Box<TextEventFn<S>>>,
on_access_event: Option<Box<AccessEventFn<S>>>,
register_children: Option<Box<RegisterChildrenFn<S>>>,
on_status_change: Option<Box<StatusChangeFn<S>>>,
lifecycle: Option<Box<LifeCycleFn<S>>>,
layout: Option<Box<LayoutFn<S>>>,
@ -69,7 +71,7 @@ pub struct ReplaceChild {
///
/// ```
/// # use masonry::widget::Label;
/// # use masonry::{LifeCycle, InternalLifeCycle};
/// # use masonry::{LifeCycle};
/// use masonry::testing::{Recording, Record, TestWidgetExt};
/// use masonry::testing::TestHarness;
/// use assert_matches::assert_matches;
@ -77,7 +79,7 @@ pub struct ReplaceChild {
/// let widget = Label::new("Hello").record(&recording);
///
/// TestHarness::create(widget);
/// assert_matches!(recording.next().unwrap(), Record::L(LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)));
/// assert_matches!(recording.next().unwrap(), Record::RegisterChildren);
/// assert_matches!(recording.next().unwrap(), Record::L(LifeCycle::WidgetAdded));
/// ```
pub struct Recorder<W> {
@ -97,6 +99,7 @@ pub enum Record {
PE(PointerEvent),
TE(TextEvent),
AE(AccessEvent),
RegisterChildren,
SC(StatusChange),
L(LifeCycle),
Layout(Size),
@ -128,6 +131,7 @@ impl<S> ModularWidget<S> {
on_pointer_event: None,
on_text_event: None,
on_access_event: None,
register_children: None,
on_status_change: None,
lifecycle: None,
layout: None,
@ -163,6 +167,14 @@ impl<S> ModularWidget<S> {
self
}
pub fn register_children_fn(
mut self,
f: impl FnMut(&mut S, &mut RegisterCtx) + 'static,
) -> Self {
self.register_children = Some(Box::new(f));
self
}
pub fn status_change_fn(
mut self,
f: impl FnMut(&mut S, &mut LifeCycleCtx, &StatusChange) + 'static,
@ -237,6 +249,12 @@ impl<S: 'static> Widget for ModularWidget<S> {
}
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
if let Some(f) = self.register_children.as_mut() {
f(&mut self.state, ctx);
}
}
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
if let Some(f) = self.on_status_change.as_mut() {
f(&mut self.state, ctx, event);
@ -328,10 +346,12 @@ impl Widget for ReplaceChild {
ctx.request_layout();
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.child.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
ctx.run_layout(&mut self.child, bc)
}
@ -398,6 +418,11 @@ impl<W: Widget> Widget for Recorder<W> {
self.child.on_access_event(ctx, event);
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
self.recording.push(Record::RegisterChildren);
self.child.register_children(ctx);
}
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
self.recording.push(Record::SC(event.clone()));
self.child.on_status_change(ctx, event);

View File

@ -17,8 +17,8 @@ use crate::contexts::AccessCtx;
use crate::paint_scene_helpers::UnitPoint;
use crate::widget::WidgetPod;
use crate::{
AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId,
AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, PointerEvent, Rect,
RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId,
};
// TODO - Have child widget type as generic argument
@ -91,8 +91,8 @@ impl Widget for Align {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.child.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
}
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}

View File

@ -13,8 +13,9 @@ use crate::event::PointerButton;
use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint};
use crate::text::TextStorage;
use crate::widget::{Label, WidgetMut, WidgetPod};
use crate::{
theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, Insets, LayoutCtx, LifeCycle,
theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, Insets, LayoutCtx,
LifeCycleCtx, PaintCtx, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
};
@ -115,8 +116,8 @@ impl Widget for Button {
ctx.request_paint();
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.label.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut crate::RegisterCtx) {
ctx.register_child(&mut self.label);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -13,9 +13,10 @@ use crate::action::Action;
use crate::paint_scene_helpers::{fill_lin_gradient, stroke, UnitPoint};
use crate::text::TextStorage;
use crate::widget::{Label, WidgetMut};
use crate::{
theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx,
PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};
/// A checkbox that can be toggled.
@ -106,8 +107,8 @@ impl Widget for Checkbox {
ctx.request_paint();
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.label.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.label);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -11,9 +11,10 @@ use vello::Scene;
use crate::theme::get_debug_color;
use crate::widget::WidgetMut;
use crate::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Point,
PointerEvent, Rect, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};
/// A container with either horizontal or vertical layout.
@ -631,9 +632,9 @@ impl Widget for Flex {
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
fn register_children(&mut self, ctx: &mut crate::RegisterCtx) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.lifecycle(ctx, event);
ctx.register_child(child);
}
}

View File

@ -10,8 +10,8 @@ use vello::Scene;
use crate::theme::get_debug_color;
use crate::widget::WidgetMut;
use crate::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Point,
PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};
pub struct Grid {
@ -240,9 +240,9 @@ impl Widget for Grid {
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
fn register_children(&mut self, ctx: &mut crate::RegisterCtx) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.lifecycle(ctx, event);
ctx.register_child(child);
}
}

View File

@ -14,7 +14,7 @@ use vello::Scene;
use crate::widget::{FillStrat, WidgetMut};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId,
};
// TODO - Resolve name collision between masonry::Image and peniko::Image
@ -77,6 +77,8 @@ impl Widget for Image {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {}

View File

@ -16,7 +16,7 @@ use crate::text::{TextBrush, TextLayout, TextStorage};
use crate::widget::WidgetMut;
use crate::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId,
PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
};
// added padding between the edges of the widget and the text.
@ -178,6 +178,8 @@ impl Widget for Label {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
#[allow(missing_docs)]
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, event: &StatusChange) {
match event {

View File

@ -14,7 +14,8 @@ use vello::Scene;
use crate::widget::{Axis, ScrollBar, WidgetMut};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, ComposeCtx, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
LifeCycleCtx, PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
WidgetPod,
};
// TODO - refactor - see https://github.com/linebender/xilem/issues/366
@ -330,6 +331,12 @@ impl<W: Widget> Widget for Portal<W> {
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child);
ctx.register_child(&mut self.scrollbar_horizontal);
ctx.register_child(&mut self.scrollbar_vertical);
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
match event {
LifeCycle::WidgetAdded => {
@ -360,10 +367,6 @@ impl<W: Widget> Widget for Portal<W> {
}
_ => {}
}
self.child.lifecycle(ctx, event);
self.scrollbar_horizontal.lifecycle(ctx, event);
self.scrollbar_vertical.lifecycle(ctx, event);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -15,7 +15,7 @@ use crate::text::TextLayout;
use crate::widget::WidgetMut;
use crate::{
theme, AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId,
LifeCycleCtx, PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
};
/// A progress bar
@ -110,6 +110,8 @@ impl Widget for ProgressBar {
// access events unhandled for now
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, _event: &StatusChange) {
ctx.request_paint();
}

View File

@ -19,7 +19,7 @@ use crate::{
text::{TextBrush, TextStorage, TextWithSelection},
widget::label::LABEL_X_PADDING,
AccessCtx, AccessEvent, ArcStr, BoxConstraints, CursorIcon, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId,
LifeCycleCtx, PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
};
/// The prose widget is a widget which displays text which can be
@ -193,6 +193,8 @@ impl Widget for Prose {
// TODO - Handle accesskit::Action::SetTextSelection
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
#[allow(missing_docs)]
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
match event {

View File

@ -9,8 +9,8 @@ use vello::Scene;
use crate::widget::{WidgetMut, WidgetPod};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx,
PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId,
};
// TODO: This is a hack to provide an accessibility node with a Window type.
@ -46,8 +46,8 @@ impl<W: Widget> Widget for RootWidget<W> {
fn on_status_change(&mut self, _: &mut LifeCycleCtx, _: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.pod.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.pod);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -14,7 +14,8 @@ use crate::paint_scene_helpers::{fill_color, stroke};
use crate::widget::WidgetMut;
use crate::{
theme, AccessCtx, AccessEvent, AllowRawMut, BoxConstraints, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
LifeCycleCtx, PaintCtx, Point, PointerEvent, RegisterCtx, Size, StatusChange, TextEvent,
Widget, WidgetId,
};
// RULES
@ -176,6 +177,8 @@ impl Widget for ScrollBar {
// TODO - Handle scroll-related events?
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle) {}

View File

@ -13,8 +13,8 @@ use vello::Scene;
use crate::paint_scene_helpers::stroke;
use crate::widget::{WidgetMut, WidgetPod};
use crate::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Point,
PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId,
};
// FIXME - Improve all doc in this module ASAP.
@ -312,9 +312,9 @@ impl Widget for SizedBox {
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
fn register_children(&mut self, ctx: &mut RegisterCtx) {
if let Some(ref mut child) = self.child {
child.lifecycle(ctx, event);
ctx.register_child(child);
}
}

View File

@ -14,8 +14,8 @@ use vello::Scene;
use crate::widget::WidgetMut;
use crate::{
theme, AccessCtx, AccessEvent, BoxConstraints, Color, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Size, StatusChange, TextEvent, Vec2, Widget,
WidgetId,
LifeCycleCtx, PaintCtx, Point, PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Vec2,
Widget, WidgetId,
};
// TODO - Set color
@ -85,6 +85,8 @@ impl Widget for Spinner {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {

View File

@ -16,7 +16,7 @@ use crate::widget::flex::Axis;
use crate::widget::{WidgetMut, WidgetPod};
use crate::{
theme, AccessCtx, AccessEvent, BoxConstraints, Color, CursorIcon, EventCtx, LayoutCtx,
LifeCycle, LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, Size, StatusChange, TextEvent,
LifeCycleCtx, PaintCtx, Point, PointerEvent, Rect, RegisterCtx, Size, StatusChange, TextEvent,
Widget, WidgetId,
};
@ -443,9 +443,9 @@ impl Widget for Split {
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, _event: &StatusChange) {}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.child1.lifecycle(ctx, event);
self.child2.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.child1);
ctx.register_child(&mut self.child2);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -5,12 +5,14 @@ use smallvec::smallvec;
use crate::testing::{ModularWidget, TestHarness};
use crate::widget::Flex;
use crate::{InternalLifeCycle, LifeCycle, Point, Size, Widget, WidgetPod};
use crate::{LifeCycle, Point, Size, Widget, WidgetPod};
fn make_parent_widget<W: Widget>(child: W) -> ModularWidget<WidgetPod<W>> {
let child = WidgetPod::new(child);
ModularWidget::new(child)
.lifecycle_fn(move |child, ctx, event| child.lifecycle(ctx, event))
.register_children_fn(move |child, ctx| {
ctx.register_child(child);
})
.layout_fn(move |child, ctx, bc| {
let size = ctx.run_layout(child, bc);
ctx.place_child(child, Point::ZERO);
@ -149,48 +151,6 @@ fn allow_non_recurse_oob_paint() {
harness.render();
}
#[test]
fn allow_non_recurse_cursor_stashed() {
let widget = make_parent_widget(Flex::row())
.lifecycle_fn(|child, ctx, event| {
child.lifecycle(ctx, event);
if matches!(
event,
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)
) {
ctx.set_stashed(child, true);
}
})
.pointer_event_fn(|_child, _ctx, _event| {
// We skip calling child.on_pointer_event();
})
.layout_fn(|_child, _ctx, _bc| Size::ZERO);
let mut harness = TestHarness::create(widget);
harness.mouse_move(Point::new(5000.0, 5000.0));
}
#[test]
fn allow_non_recurse_stashed_paint() {
let widget = make_parent_widget(Flex::row())
.lifecycle_fn(|child, ctx, event| {
child.lifecycle(ctx, event);
if matches!(
event,
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)
) {
ctx.set_stashed(child, true);
}
})
.layout_fn(|_child, _ctx, _bc| Size::ZERO)
.paint_fn(|_child, _ctx, _scene| {
// We skip calling child.paint();
});
let mut harness = TestHarness::create_with_size(widget, Size::new(400.0, 400.0));
harness.render();
}
// ---
#[cfg(FALSE)]
@ -303,11 +263,7 @@ fn check_recurse_paint_twice() {
fn check_layout_stashed() {
let widget = make_parent_widget(Flex::row())
.lifecycle_fn(|child, ctx, event| {
child.lifecycle(ctx, event);
if matches!(
event,
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded)
) {
if matches!(event, LifeCycle::WidgetAdded) {
ctx.set_stashed(child, true);
}
})

View File

@ -4,11 +4,7 @@ assertion_line: 22
expression: record
---
[
L(
Internal(
RouteWidgetAdded,
),
),
RegisterChildren,
L(
WidgetAdded,
),

View File

@ -20,7 +20,7 @@ use crate::{
dpi::{LogicalPosition, LogicalSize},
text::{TextBrush, TextEditor, TextStorage, TextWithSelection},
AccessCtx, AccessEvent, BoxConstraints, CursorIcon, EventCtx, LayoutCtx, LifeCycle,
LifeCycleCtx, PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId,
LifeCycleCtx, PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
};
const TEXTBOX_PADDING: f64 = 3.0;
@ -226,6 +226,8 @@ impl Widget for Textbox {
// TODO - Handle accesskit::Action::SetValue
}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
#[allow(missing_docs)]
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
match event {

View File

@ -19,7 +19,7 @@ use crate::text::{Hinting, TextBrush, TextLayout, TextStorage};
use crate::widget::WidgetMut;
use crate::{
AccessCtx, AccessEvent, ArcStr, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx,
PaintCtx, PointerEvent, StatusChange, TextEvent, Widget, WidgetId,
PaintCtx, PointerEvent, RegisterCtx, StatusChange, TextEvent, Widget, WidgetId,
};
use super::LineBreaking;
@ -303,6 +303,8 @@ impl Widget for VariableLabel {
fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) {}
fn register_children(&mut self, _ctx: &mut RegisterCtx) {}
#[allow(missing_docs)]
fn on_status_change(&mut self, _ctx: &mut LifeCycleCtx, event: &StatusChange) {
match event {

View File

@ -15,7 +15,8 @@ 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,
AccessCtx, AsAny, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
RegisterCtx, Size,
};
/// A unique identifier for a single [`Widget`].
@ -79,12 +80,20 @@ pub trait Widget: AsAny {
#[allow(missing_docs)]
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange);
/// Register child widgets with Masonry.
///
/// Leaf widgets can implement this with an empty body.
///
/// Container widgets need to call [`RegisterCtx::register_child`] for all
/// their children. Forgetting to do so is a logic error and may lead to crashes.
fn register_children(&mut self, ctx: &mut RegisterCtx);
/// Handle a lifecycle notification.
///
/// This method is called to notify your widget of certain special events,
/// (available in the [`LifeCycle`] enum) that are generally related to
/// changes in the widget graph or in the state of your specific widget.
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle);
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {}
/// Compute layout.
///
@ -298,6 +307,10 @@ impl Widget for Box<dyn Widget> {
self.deref_mut().on_access_event(ctx, event);
}
fn register_children(&mut self, ctx: &mut RegisterCtx) {
self.deref_mut().register_children(ctx);
}
fn on_status_change(&mut self, ctx: &mut LifeCycleCtx, event: &StatusChange) {
self.deref_mut().on_status_change(ctx, event);
}

View File

@ -1,8 +1,7 @@
// Copyright 2018 the Xilem Authors and the Druid Authors
// SPDX-License-Identifier: Apache-2.0
use crate::widget::WidgetState;
use crate::{InternalLifeCycle, LifeCycle, LifeCycleCtx, Widget, WidgetId};
use crate::{Widget, WidgetId};
// TODO - rewrite links in doc
@ -30,7 +29,6 @@ enum WidgetPodInner<W> {
Inserted,
}
// --- MARK: GETTERS ---
impl<W: Widget> WidgetPod<W> {
/// Create a new widget pod.
///
@ -53,6 +51,13 @@ impl<W: Widget> WidgetPod<W> {
matches!(self.inner, WidgetPodInner::Created(_))
}
pub(crate) fn take_inner(&mut self) -> Option<W> {
match std::mem::replace(&mut self.inner, WidgetPodInner::Inserted) {
WidgetPodInner::Created(widget) => Some(widget),
WidgetPodInner::Inserted => None,
}
}
/// Get the identity of the widget.
pub fn id(&self) -> WidgetId {
self.id
@ -73,74 +78,3 @@ impl<W: Widget + 'static> WidgetPod<W> {
}
}
}
// --- MARK: INTERNALS ---
impl<W: Widget> WidgetPod<W> {
// Notes about hot state:
//
// Hot state (the thing that changes when your mouse hovers over a button) is annoying to implement, because it breaks the convenient abstraction of multiple static passes over the widget tree.
//
// Ideally, what you'd want is "first handle events, then update widget states, then compute layout, then paint", where each 'then' is an indestructible wall that only be crossed in one direction.
//
// Hot state breaks that abstraction, because a change in a widget's layout (eg a button gets bigger) can lead to a change in hot state.
//
// To give an extreme example: suppose you have a button which becomes very small when you hover over it (and forget all the reasons this would be terrible UX). How should its hot state be handled? When the mouse moves over the button, the hot state will get changed, and the button will become smaller. But becoming smaller make it so the mouse no longer hovers over the button, so the hot state will get changed again.
//
// Ideally, this is a UX trap I'd like to warn against; in any case, the fact that it's possible shows we have to account for cases where layout has an influence on previous stages.
//
// In actual Masonry code, that means:
// - `Widget::lifecycle` can be called within `Widget::layout`.
// - `Widget::set_position` can call `Widget::lifecycle` and thus needs to be passed context types, which gives the method a surprising prototype.
//
// We could have `set_position` set a `hot_state_needs_update` flag, but then we'd need to add in another UpdateHotState pass (probably as a variant to the Lifecycle enum).
//
// Another problem is that hot state handling is counter-intuitive for someone writing a Widget implementation. Developers who want to implement "This widget turns red when the mouse is over it" will usually assume they should use the MouseMove event or something similar; when what they actually need is a Lifecycle variant.
//
// 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.
}
impl<W: Widget> WidgetPod<W> {
// --- MARK: ON_XXX_EVENT ---
// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
// - If a Widget gets a keyboard event or an ImeStateChange, then
// focus is on it, its child or its parent.
// - If a Widget has focus, then none of its parents is hidden
// --- MARK: LIFECYCLE ---
// TODO https://github.com/linebender/xilem/issues/376 - Some implicit invariants:
// - 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) {
let widget = std::mem::replace(&mut self.inner, WidgetPodInner::Inserted);
let WidgetPodInner::Created(widget) = widget else {
return;
};
let _span = widget.make_trace_span().entered();
let id = self.id().to_raw();
match event {
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => {}
event => {
debug_panic!(
"Error in '{}' #{id}: method 'lifecycle' called with {event:?} before receiving WidgetAdded.",
widget.short_type_name(),
);
}
}
let state = WidgetState::new(self.id, widget.short_type_name());
parent_ctx
.widget_children
.insert_child(id, Box::new(widget));
parent_ctx.widget_state_children.insert_child(id, state);
}
}

View File

@ -4,8 +4,8 @@
use accesskit::Role;
use masonry::widget::WidgetMut;
use masonry::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Point,
PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};
use smallvec::{smallvec, SmallVec};
use tracing::{trace_span, Span};
@ -82,8 +82,8 @@ impl Widget for DynWidget {
// Intentionally do nothing
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
self.inner.lifecycle(ctx, event);
fn register_children(&mut self, ctx: &mut RegisterCtx) {
ctx.register_child(&mut self.inner);
}
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {

View File

@ -5,8 +5,8 @@
use accesskit::Role;
use masonry::{
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
Point, PointerEvent, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
AccessCtx, AccessEvent, BoxConstraints, EventCtx, LayoutCtx, LifeCycleCtx, PaintCtx, Point,
PointerEvent, RegisterCtx, Size, StatusChange, TextEvent, Widget, WidgetId, WidgetPod,
};
use smallvec::{smallvec, SmallVec};
use vello::Scene;
@ -225,17 +225,17 @@ impl<
// Intentionally do nothing
}
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle) {
fn register_children(&mut self, ctx: &mut RegisterCtx) {
match self {
OneOfWidget::A(w) => w.lifecycle(ctx, event),
OneOfWidget::B(w) => w.lifecycle(ctx, event),
OneOfWidget::C(w) => w.lifecycle(ctx, event),
OneOfWidget::D(w) => w.lifecycle(ctx, event),
OneOfWidget::E(w) => w.lifecycle(ctx, event),
OneOfWidget::F(w) => w.lifecycle(ctx, event),
OneOfWidget::G(w) => w.lifecycle(ctx, event),
OneOfWidget::H(w) => w.lifecycle(ctx, event),
OneOfWidget::I(w) => w.lifecycle(ctx, event),
OneOfWidget::A(w) => ctx.register_child(w),
OneOfWidget::B(w) => ctx.register_child(w),
OneOfWidget::C(w) => ctx.register_child(w),
OneOfWidget::D(w) => ctx.register_child(w),
OneOfWidget::E(w) => ctx.register_child(w),
OneOfWidget::F(w) => ctx.register_child(w),
OneOfWidget::G(w) => ctx.register_child(w),
OneOfWidget::H(w) => ctx.register_child(w),
OneOfWidget::I(w) => ctx.register_child(w),
}
}