mirror of https://github.com/linebender/xilem
Implement fixpoint loop in run_rewrite_passes (#696)
This implements the last feature from the Pass Specification RFC 🎉
This commit is contained in:
parent
40d1987161
commit
f403337219
|
@ -35,10 +35,9 @@ impl<'a> DriverCtx<'a> {
|
|||
}
|
||||
|
||||
pub fn content_changed(&self) -> bool {
|
||||
self.main_root_widget
|
||||
.ctx
|
||||
.widget_state
|
||||
.needs_rewrite_passes()
|
||||
|| self.main_root_widget.ctx.global_state.focus_changed()
|
||||
let ctx = &self.main_root_widget.ctx;
|
||||
ctx.widget_state.needs_rewrite_passes()
|
||||
|| ctx.widget_state.needs_render()
|
||||
|| ctx.global_state.focus_changed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -653,6 +653,8 @@ impl MasonryState<'_> {
|
|||
tracing::warn!("Tried to handle a signal whilst suspended or before window created");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut needs_redraw = false;
|
||||
while let Some(signal) = self.render_root.pop_signal() {
|
||||
match signal {
|
||||
render_root::RenderRootSignal::Action(action, widget_id) => {
|
||||
|
@ -674,11 +676,11 @@ impl MasonryState<'_> {
|
|||
window.set_ime_cursor_area(position, size);
|
||||
}
|
||||
render_root::RenderRootSignal::RequestRedraw => {
|
||||
window.request_redraw();
|
||||
needs_redraw = true;
|
||||
}
|
||||
render_root::RenderRootSignal::RequestAnimFrame => {
|
||||
// TODO
|
||||
window.request_redraw();
|
||||
needs_redraw = true;
|
||||
}
|
||||
render_root::RenderRootSignal::TakeFocus => {
|
||||
window.focus_window();
|
||||
|
@ -695,6 +697,12 @@ impl MasonryState<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're processing a lot of actions, we may have a lot of pending redraws.
|
||||
// We batch them up to avoid redundant requests.
|
||||
if needs_redraw {
|
||||
window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_window_state(&self) -> &WindowState {
|
||||
|
|
|
@ -75,6 +75,12 @@ fn compose_widget(
|
|||
pub(crate) fn run_compose_pass(root: &mut RenderRoot) {
|
||||
let _span = info_span!("compose").entered();
|
||||
|
||||
// If widgets are moved, pointer-related info may be stale.
|
||||
// For instance, the "hovered" widget may have moved and no longer be under the pointer.
|
||||
if root.root_state().needs_compose {
|
||||
root.global_state.needs_pointer_pass = true;
|
||||
}
|
||||
|
||||
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
|
||||
compose_widget(
|
||||
&mut root.global_state,
|
||||
|
|
|
@ -83,7 +83,10 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
|
|||
debug!("Running ON_POINTER_EVENT pass with {}", event.short_name());
|
||||
}
|
||||
|
||||
root.last_mouse_pos = event.position();
|
||||
if event.position() != root.last_mouse_pos {
|
||||
root.global_state.needs_pointer_pass = true;
|
||||
root.last_mouse_pos = event.position();
|
||||
}
|
||||
|
||||
let target_widget_id = get_target_widget(root, event.position());
|
||||
|
||||
|
|
|
@ -211,6 +211,7 @@ pub(crate) fn run_layout_pass(root: &mut RenderRoot) {
|
|||
}
|
||||
|
||||
let _span = info_span!("layout").entered();
|
||||
root.global_state.needs_pointer_pass = true;
|
||||
|
||||
let window_size = root.get_kurbo_size();
|
||||
let bc = match root.size_policy {
|
||||
|
|
|
@ -520,6 +520,11 @@ pub(crate) fn run_update_scroll_pass(root: &mut RenderRoot) {
|
|||
|
||||
// --- MARK: UPDATE POINTER ---
|
||||
pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
|
||||
if !root.global_state.needs_pointer_pass {
|
||||
return;
|
||||
}
|
||||
root.global_state.needs_pointer_pass = false;
|
||||
|
||||
let pointer_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into());
|
||||
|
||||
// Release pointer capture if target can no longer hold it.
|
||||
|
|
|
@ -74,6 +74,8 @@ pub(crate) struct RenderRootState {
|
|||
pub(crate) mutate_callbacks: Vec<MutateCallback>,
|
||||
pub(crate) is_ime_active: bool,
|
||||
pub(crate) scenes: HashMap<WidgetId, Scene>,
|
||||
/// Whether data set in the pointer pass has been invalidated.
|
||||
pub(crate) needs_pointer_pass: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct MutateCallback {
|
||||
|
@ -161,6 +163,7 @@ impl RenderRoot {
|
|||
mutate_callbacks: Vec::new(),
|
||||
is_ime_active: false,
|
||||
scenes: HashMap::new(),
|
||||
needs_pointer_pass: false,
|
||||
},
|
||||
widget_arena: WidgetArena {
|
||||
widgets: TreeArena::new(),
|
||||
|
@ -428,23 +431,38 @@ impl RenderRoot {
|
|||
///
|
||||
/// See Pass Spec RFC for details. (TODO - Link to doc instead.)
|
||||
pub(crate) fn run_rewrite_passes(&mut self) {
|
||||
// TODO - Rerun passes if invalidation flags are still set
|
||||
const REWRITE_PASSES_MAX: usize = 4;
|
||||
|
||||
// Note: this code doesn't do any short-circuiting, because each pass is
|
||||
// expected to have its own early exits.
|
||||
// Calling a run_xxx_pass (or root_xxx) should always be very fast if
|
||||
// the pass doesn't need to do anything.
|
||||
for _ in 0..REWRITE_PASSES_MAX {
|
||||
// Note: this code doesn't do any short-circuiting, because each pass is
|
||||
// expected to have its own early exits.
|
||||
// Calling a run_xxx_pass (or root_xxx) should always be very fast if
|
||||
// the pass doesn't need to do anything.
|
||||
|
||||
run_mutate_pass(self);
|
||||
run_update_widget_tree_pass(self);
|
||||
run_update_disabled_pass(self);
|
||||
run_update_stashed_pass(self);
|
||||
run_update_focus_chain_pass(self);
|
||||
run_update_focus_pass(self);
|
||||
run_layout_pass(self);
|
||||
run_update_scroll_pass(self);
|
||||
run_compose_pass(self);
|
||||
run_update_pointer_pass(self);
|
||||
run_mutate_pass(self);
|
||||
run_update_widget_tree_pass(self);
|
||||
run_update_disabled_pass(self);
|
||||
run_update_stashed_pass(self);
|
||||
run_update_focus_chain_pass(self);
|
||||
run_update_focus_pass(self);
|
||||
run_layout_pass(self);
|
||||
run_update_scroll_pass(self);
|
||||
run_compose_pass(self);
|
||||
run_update_pointer_pass(self);
|
||||
|
||||
if !self.root_state().needs_rewrite_passes()
|
||||
&& !self.global_state.needs_rewrite_passes()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if self.root_state().needs_rewrite_passes() || self.global_state.needs_rewrite_passes() {
|
||||
warn!("All rewrite passes have run {REWRITE_PASSES_MAX} times, but invalidations are still set");
|
||||
// To avoid an infinite loop, we delay re-running the passes until the next frame.
|
||||
self.global_state
|
||||
.emit_signal(RenderRootSignal::RequestRedraw);
|
||||
}
|
||||
|
||||
if self.root_state().needs_anim {
|
||||
self.global_state
|
||||
|
@ -455,10 +473,7 @@ impl RenderRoot {
|
|||
// tree needs to be rebuilt. Usually both happen at the same time.
|
||||
// A redraw will trigger a rebuild of the accessibility tree.
|
||||
// TODO - We assume that a relayout will trigger a repaint
|
||||
if self.root_state().needs_paint
|
||||
|| self.root_state().needs_accessibility
|
||||
|| self.root_state().needs_layout
|
||||
{
|
||||
if self.root_state().needs_paint || self.root_state().needs_accessibility {
|
||||
self.global_state
|
||||
.emit_signal(RenderRootSignal::RequestRedraw);
|
||||
}
|
||||
|
@ -556,6 +571,10 @@ impl RenderRootState {
|
|||
pub(crate) fn is_focused(&self, id: WidgetId) -> bool {
|
||||
self.focused_widget == Some(id)
|
||||
}
|
||||
|
||||
pub(crate) fn needs_rewrite_passes(&self) -> bool {
|
||||
self.needs_pointer_pass || self.focused_widget != self.next_focused_widget
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderRootSignal {
|
||||
|
|
|
@ -293,10 +293,11 @@ impl WidgetState {
|
|||
pub(crate) fn needs_rewrite_passes(&self) -> bool {
|
||||
self.needs_layout
|
||||
|| self.needs_compose
|
||||
|| self.needs_paint
|
||||
|| self.needs_accessibility
|
||||
|| self.needs_anim
|
||||
|| self.needs_update_disabled
|
||||
|| self.needs_update_stashed
|
||||
}
|
||||
|
||||
pub(crate) fn needs_render(&self) -> bool {
|
||||
self.needs_anim || self.needs_paint || self.needs_accessibility
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue