mirror of https://github.com/linebender/xilem
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.
This commit is contained in:
parent
b54266d605
commit
01cca4610c
|
@ -5,10 +5,12 @@
|
||||||
//! before any translations applied in [`compose`](crate::passes::compose).
|
//! before any translations applied in [`compose`](crate::passes::compose).
|
||||||
//! Most of the logic for this pass happens in [`Widget::layout`] implementations.
|
//! Most of the logic for this pass happens in [`Widget::layout`] implementations.
|
||||||
|
|
||||||
|
use smallvec::SmallVec;
|
||||||
use tracing::{info_span, trace};
|
use tracing::{info_span, trace};
|
||||||
use vello::kurbo::{Point, Rect, Size};
|
use vello::kurbo::{Point, Rect, Size};
|
||||||
|
|
||||||
use crate::render_root::RenderRoot;
|
use crate::render_root::RenderRoot;
|
||||||
|
use crate::tree_arena::ArenaRefChildren;
|
||||||
use crate::widget::WidgetState;
|
use crate::widget::WidgetState;
|
||||||
use crate::{BoxConstraints, LayoutCtx, Widget, WidgetPod};
|
use crate::{BoxConstraints, LayoutCtx, Widget, WidgetPod};
|
||||||
|
|
||||||
|
@ -22,6 +24,113 @@ fn rect_contains(larger: &Rect, smaller: &Rect) -> bool {
|
||||||
&& smaller.y1 <= larger.y1
|
&& 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<W: Widget, Ctx>(
|
||||||
|
pod: &mut WidgetPod<W>,
|
||||||
|
method_name: &str,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
get_tokens: impl Fn(
|
||||||
|
&mut Ctx,
|
||||||
|
) -> (
|
||||||
|
ArenaRefChildren<'_, WidgetState>,
|
||||||
|
ArenaRefChildren<'_, Box<dyn Widget>>,
|
||||||
|
),
|
||||||
|
visit: impl FnOnce(&mut WidgetPod<W>, &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
|
// 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)
|
// need to be run. (See 'called_widget' in WidgetPod::call_widget_method_with_checks)
|
||||||
pub(crate) fn run_layout_inner<W: Widget>(
|
pub(crate) fn run_layout_inner<W: Widget>(
|
||||||
|
@ -104,26 +213,26 @@ pub(crate) fn run_layout_inner<W: Widget>(
|
||||||
let child_state = child_state_mut.item;
|
let child_state = child_state_mut.item;
|
||||||
if child_state.is_expecting_place_child_call {
|
if child_state.is_expecting_place_child_call {
|
||||||
debug_panic!(
|
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.",
|
"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(),
|
widget.short_type_name(),
|
||||||
id,
|
id,
|
||||||
child_state.widget_name,
|
child_state.widget_name,
|
||||||
child_state.id.to_raw(),
|
child_state.id.to_raw(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO - This check might be redundant with the code updating local_paint_rect
|
// TODO - This check might be redundant with the code updating local_paint_rect
|
||||||
let child_rect = child_state.paint_rect();
|
let child_rect = child_state.paint_rect();
|
||||||
if !rect_contains(&state.local_paint_rect, &child_rect) && !state.is_portal {
|
if !rect_contains(&state.local_paint_rect, &child_rect) && !state.is_portal {
|
||||||
debug_panic!(
|
debug_panic!(
|
||||||
"Error in '{}' #{}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' #{}",
|
"Error in '{}' #{}: paint_rect {:?} doesn't contain paint_rect {:?} of child widget '{}' #{}",
|
||||||
widget.short_type_name(),
|
widget.short_type_name(),
|
||||||
id,
|
id,
|
||||||
state.local_paint_rect,
|
state.local_paint_rect,
|
||||||
child_rect,
|
child_rect,
|
||||||
child_state.widget_name,
|
child_state.widget_name,
|
||||||
child_state.id.to_raw(),
|
child_state.id.to_raw(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +281,8 @@ pub(crate) fn run_layout_on<W: Widget>(
|
||||||
pod: &mut WidgetPod<W>,
|
pod: &mut WidgetPod<W>,
|
||||||
bc: &BoxConstraints,
|
bc: &BoxConstraints,
|
||||||
) -> Size {
|
) -> Size {
|
||||||
pod.call_widget_method_with_checks(
|
call_widget_method_with_checks(
|
||||||
|
pod,
|
||||||
"layout",
|
"layout",
|
||||||
parent_ctx,
|
parent_ctx,
|
||||||
|ctx| {
|
|ctx| {
|
||||||
|
|
|
@ -401,3 +401,160 @@ pub(crate) fn run_update_anim_pass(root: &mut RenderRoot, elapsed_ns: u64) {
|
||||||
elapsed_ns,
|
elapsed_ns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------
|
||||||
|
|
||||||
|
fn update_new_widgets(
|
||||||
|
global_state: &mut RenderRootState,
|
||||||
|
mut widget: ArenaMut<'_, Box<dyn Widget>>,
|
||||||
|
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<dyn Widget>>,
|
||||||
|
mut state: ArenaMut<'_, WidgetState>,
|
||||||
|
parent_focus_chain: &mut Vec<WidgetId>,
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::collections::{HashMap, VecDeque};
|
||||||
use accesskit::{ActionRequest, Tree, TreeUpdate};
|
use accesskit::{ActionRequest, Tree, TreeUpdate};
|
||||||
use parley::fontique::{self, Collection, CollectionOptions};
|
use parley::fontique::{self, Collection, CollectionOptions};
|
||||||
use parley::{FontContext, LayoutContext};
|
use parley::{FontContext, LayoutContext};
|
||||||
use tracing::{info_span, warn};
|
use tracing::warn;
|
||||||
use vello::kurbo::{self, Rect};
|
use vello::kurbo::{self, Rect};
|
||||||
use vello::Scene;
|
use vello::Scene;
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ use std::time::Instant;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use web_time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
use crate::contexts::LifeCycleCtx;
|
|
||||||
use crate::debug_logger::DebugLogger;
|
use crate::debug_logger::DebugLogger;
|
||||||
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
use crate::dpi::{LogicalPosition, LogicalSize, PhysicalSize};
|
||||||
use crate::event::{PointerEvent, TextEvent, WindowEvent};
|
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::mutate::{mutate_widget, run_mutate_pass};
|
||||||
use crate::passes::paint::root_paint;
|
use crate::passes::paint::root_paint;
|
||||||
use crate::passes::update::{
|
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,
|
run_update_scroll_pass,
|
||||||
};
|
};
|
||||||
use crate::text::TextBrush;
|
use crate::text::TextBrush;
|
||||||
|
@ -34,8 +34,7 @@ use crate::tree_arena::TreeArena;
|
||||||
use crate::widget::WidgetArena;
|
use crate::widget::WidgetArena;
|
||||||
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
|
use crate::widget::{WidgetMut, WidgetRef, WidgetState};
|
||||||
use crate::{
|
use crate::{
|
||||||
AccessEvent, Action, BoxConstraints, CursorIcon, Handled, InternalLifeCycle, LifeCycle, Widget,
|
AccessEvent, Action, BoxConstraints, CursorIcon, Handled, Widget, WidgetId, WidgetPod,
|
||||||
WidgetId, WidgetPod,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- MARK: STRUCTS ---
|
// --- MARK: STRUCTS ---
|
||||||
|
@ -163,7 +162,10 @@ impl RenderRoot {
|
||||||
};
|
};
|
||||||
|
|
||||||
// We send WidgetAdded to all widgets right away
|
// 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
|
// We run a layout pass right away to have a SetSize signal ready
|
||||||
if size_policy == WindowSizePolicy::Content {
|
if size_policy == WindowSizePolicy::Content {
|
||||||
|
@ -429,34 +431,6 @@ impl RenderRoot {
|
||||||
self.get_root_widget().debug_validate(false);
|
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 ---
|
// --- MARK: LAYOUT ---
|
||||||
pub(crate) fn root_layout(&mut self) {
|
pub(crate) fn root_layout(&mut self) {
|
||||||
let window_size = self.get_kurbo_size();
|
let window_size = self.get_kurbo_size();
|
||||||
|
@ -517,7 +491,7 @@ impl RenderRoot {
|
||||||
// TODO - Update IME handlers
|
// TODO - Update IME handlers
|
||||||
// Send TextFieldRemoved signal
|
// 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() {
|
if self.state.debug_logger.layout_tree.root.is_none() {
|
||||||
|
@ -541,8 +515,7 @@ impl RenderRoot {
|
||||||
// Update the focus-chain if necessary
|
// Update the focus-chain if necessary
|
||||||
// Always do this before sending focus change, since this event updates the focus chain.
|
// Always do this before sending focus change, since this event updates the focus chain.
|
||||||
if self.root_state().update_focus_chain {
|
if self.root_state().update_focus_chain {
|
||||||
let event = LifeCycle::BuildFocusChain;
|
run_update_focus_chain_pass(self);
|
||||||
self.root_lifecycle(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.root_state().request_anim {
|
if self.root_state().request_anim {
|
||||||
|
|
|
@ -69,14 +69,16 @@ pub struct ReplaceChild {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use masonry::widget::Label;
|
/// # use masonry::widget::Label;
|
||||||
/// # use masonry::LifeCycle;
|
/// # use masonry::{LifeCycle, InternalLifeCycle};
|
||||||
/// use masonry::testing::{Recording, Record, TestWidgetExt};
|
/// use masonry::testing::{Recording, Record, TestWidgetExt};
|
||||||
/// use masonry::testing::TestHarness;
|
/// use masonry::testing::TestHarness;
|
||||||
|
/// use assert_matches::assert_matches;
|
||||||
/// let recording = Recording::default();
|
/// let recording = Recording::default();
|
||||||
/// let widget = Label::new("Hello").record(&recording);
|
/// let widget = Label::new("Hello").record(&recording);
|
||||||
///
|
///
|
||||||
/// TestHarness::create(widget);
|
/// 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<W> {
|
pub struct Recorder<W> {
|
||||||
recording: Recording,
|
recording: Recording,
|
||||||
|
|
|
@ -31,6 +31,7 @@ fn check_forget_to_recurse_text_event() {
|
||||||
harness.mouse_move(Point::ZERO);
|
harness.mouse_move(Point::ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(FALSE)]
|
||||||
#[should_panic(expected = "not added in method lifecycle")]
|
#[should_panic(expected = "not added in method lifecycle")]
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
|
|
@ -4,14 +4,14 @@ assertion_line: 22
|
||||||
expression: record
|
expression: record
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
L(
|
|
||||||
WidgetAdded,
|
|
||||||
),
|
|
||||||
L(
|
L(
|
||||||
Internal(
|
Internal(
|
||||||
RouteWidgetAdded,
|
RouteWidgetAdded,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
L(
|
||||||
|
WidgetAdded,
|
||||||
|
),
|
||||||
L(
|
L(
|
||||||
BuildFocusChain,
|
BuildFocusChain,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
// Copyright 2018 the Xilem Authors and the Druid Authors
|
// Copyright 2018 the Xilem Authors and the Druid Authors
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
use tracing::trace;
|
|
||||||
|
|
||||||
use crate::tree_arena::ArenaRefChildren;
|
|
||||||
use crate::widget::WidgetState;
|
use crate::widget::WidgetState;
|
||||||
use crate::{InternalLifeCycle, LifeCycle, LifeCycleCtx, Widget, WidgetId};
|
use crate::{InternalLifeCycle, LifeCycle, LifeCycleCtx, Widget, WidgetId};
|
||||||
|
|
||||||
|
@ -53,6 +49,10 @@ impl<W: Widget> WidgetPod<W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn incomplete(&self) -> bool {
|
||||||
|
matches!(self.inner, WidgetPodInner::Created(_))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the identity of the widget.
|
/// Get the identity of the widget.
|
||||||
pub fn id(&self) -> WidgetId {
|
pub fn id(&self) -> WidgetId {
|
||||||
self.id
|
self.id
|
||||||
|
@ -99,111 +99,6 @@ impl<W: Widget> WidgetPod<W> {
|
||||||
// Other things hot state is missing:
|
// 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).
|
// - A concept of "cursor moved to inner widget" (though I think that's not super useful outside the browser).
|
||||||
// - Multiple pointers handling.
|
// - 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<Ctx>(
|
|
||||||
&mut self,
|
|
||||||
method_name: &str,
|
|
||||||
ctx: &mut Ctx,
|
|
||||||
get_tokens: impl Fn(
|
|
||||||
&mut Ctx,
|
|
||||||
) -> (
|
|
||||||
ArenaRefChildren<'_, WidgetState>,
|
|
||||||
ArenaRefChildren<'_, Box<dyn Widget>>,
|
|
||||||
),
|
|
||||||
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<W: Widget> WidgetPod<W> {
|
impl<W: Widget> WidgetPod<W> {
|
||||||
|
@ -220,46 +115,16 @@ impl<W: Widget> WidgetPod<W> {
|
||||||
// - A widget only receives BuildFocusChain if none of its parents are hidden.
|
// - A widget only receives BuildFocusChain if none of its parents are hidden.
|
||||||
|
|
||||||
/// Propagate a [`LifeCycle`] event.
|
/// Propagate a [`LifeCycle`] event.
|
||||||
|
///
|
||||||
|
/// Currently only used for [`InternalLifeCycle::RouteWidgetAdded`].
|
||||||
pub fn lifecycle(&mut self, parent_ctx: &mut LifeCycleCtx, event: &LifeCycle) {
|
pub fn lifecycle(&mut self, parent_ctx: &mut LifeCycleCtx, event: &LifeCycle) {
|
||||||
if matches!(self.inner, WidgetPodInner::Created(_)) {
|
let widget = std::mem::replace(&mut self.inner, WidgetPodInner::Inserted);
|
||||||
let early_return = self.lifecycle_inner_added(parent_ctx, event);
|
let WidgetPodInner::Created(widget) = widget else {
|
||||||
if early_return {
|
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 id = self.id().to_raw();
|
|
||||||
|
|
||||||
let _span = widget.make_trace_span().entered();
|
let _span = widget.make_trace_span().entered();
|
||||||
|
let id = self.id().to_raw();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => {}
|
LifeCycle::Internal(InternalLifeCycle::RouteWidgetAdded) => {}
|
||||||
|
@ -277,107 +142,5 @@ impl<W: Widget> WidgetPod<W> {
|
||||||
.widget_children
|
.widget_children
|
||||||
.insert_child(id, Box::new(widget));
|
.insert_child(id, Box::new(widget));
|
||||||
parent_ctx.widget_state_children.insert_child(id, state);
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ use crate::{CursorIcon, WidgetId};
|
||||||
// &mut WidgetState as a parameter. Because passes reborrow the parent WidgetState, the only
|
// &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.
|
// 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.
|
/// Generic state for all widgets in the hierarchy.
|
||||||
///
|
///
|
||||||
/// This struct contains the widget's layout rect, flags
|
/// This struct contains the widget's layout rect, flags
|
||||||
|
@ -74,8 +76,9 @@ pub struct WidgetState {
|
||||||
pub(crate) translation_changed: bool,
|
pub(crate) translation_changed: bool,
|
||||||
|
|
||||||
// --- PASSES ---
|
// --- 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`.
|
/// A flag used to track and debug missing calls to `place_child`.
|
||||||
pub(crate) is_expecting_place_child_call: bool,
|
pub(crate) is_expecting_place_child_call: bool,
|
||||||
|
|
||||||
|
@ -165,6 +168,7 @@ impl WidgetState {
|
||||||
is_explicitly_disabled: false,
|
is_explicitly_disabled: false,
|
||||||
is_disabled: false,
|
is_disabled: false,
|
||||||
baseline_offset: 0.0,
|
baseline_offset: 0.0,
|
||||||
|
is_new: true,
|
||||||
is_hot: false,
|
is_hot: false,
|
||||||
request_layout: true,
|
request_layout: true,
|
||||||
needs_layout: true,
|
needs_layout: true,
|
||||||
|
@ -197,6 +201,7 @@ impl WidgetState {
|
||||||
pub(crate) fn synthetic(id: WidgetId, size: Size) -> WidgetState {
|
pub(crate) fn synthetic(id: WidgetId, size: Size) -> WidgetState {
|
||||||
WidgetState {
|
WidgetState {
|
||||||
size,
|
size,
|
||||||
|
is_new: false,
|
||||||
needs_layout: false,
|
needs_layout: false,
|
||||||
request_compose: false,
|
request_compose: false,
|
||||||
needs_compose: false,
|
needs_compose: false,
|
||||||
|
|
Loading…
Reference in New Issue