From 6884160bfeb57d46df2644b232da6e9efd20c2dd Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Fri, 4 Sep 2026 17:47:44 -0400 Subject: [PATCH 1/3] Make iris ask for the frame a resize needs `update` redrew everything when `resized` was set, but `needs_redraw` -- which is what decides whether to request a frame at all -- did not know about `resized`. A condition in one and not the other is a frame nobody asks for and a stale window. The two share one `needs_redraw_all` now. Latent on Wayland, because winit requests a redraw after a resize by itself; a resize changes neither the root nor any widget, so nothing else here would have asked. It stops being latent on Android, where the surface work will not have winit underneath it and every rotation and keyboard open is a resize. This is not a fix for the startup defect recorded in RUST.md, where the window keeps its pre-configure layout: that reproduces with this change in place, and the frame it needs is requested and drawn. Co-Authored-By: Claude Opus 5 --- core/src/ui/render_state.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 57e79a6..8bada33 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -52,7 +52,7 @@ impl UiRenderState { ); } let root = root.into(); - if self.root_changed(root) || self.resized { + if self.needs_redraw_all(root) { self.redraw_all(root, rsc); self.old_root = root.map(|r| r.id()); self.resized = false; @@ -218,12 +218,24 @@ impl UiRenderState { root.into().map(|r| r.id()) != self.old_root } + /// What `update` will redraw everything for. Named and shared with + /// `needs_redraw` rather than written out twice, because the two must + /// agree: `needs_redraw` is what asks for the frame that `update` would + /// draw, so a condition in one and not the other is a frame nobody + /// requests and a stale window. `resized` was missing from `needs_redraw`, + /// which is latent on Wayland only because winit asks for a redraw after a + /// resize by itself -- a resize changes neither the root nor any widget, + /// so nothing else here would have asked. + fn needs_redraw_all<'a>(&self, root: impl Into>) -> bool { + self.root_changed(root) || self.resized + } + pub fn needs_redraw<'a>( &self, root: impl Into>, widgets: &Widgets, ) -> bool { - self.root_changed(root) || widgets.has_updates() + self.needs_redraw_all(root) || widgets.has_updates() } pub fn active_widgets(&self) -> usize { @@ -317,3 +329,18 @@ impl Default for UiRenderState { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_resize_requests_a_frame_without_widget_changes() { + let widgets = Widgets::new(); + let mut state = UiRenderState::new(); + + assert!(!state.needs_redraw(None::<&StrongWidget>, &widgets)); + state.resize((800, 600)); + assert!(state.needs_redraw(None::<&StrongWidget>, &widgets)); + } +} From 072f1e31ad497d208fc09c81fc6f46eb3d7a3a96 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sun, 13 Sep 2026 00:36:23 -0400 Subject: [PATCH 2/3] Keep the redraw invariant concise --- core/src/ui/render_state.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 8bada33..4877fc8 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -52,7 +52,7 @@ impl UiRenderState { ); } let root = root.into(); - if self.needs_redraw_all(root) { + if self.needs_full_redraw(root) { self.redraw_all(root, rsc); self.old_root = root.map(|r| r.id()); self.resized = false; @@ -218,15 +218,8 @@ impl UiRenderState { root.into().map(|r| r.id()) != self.old_root } - /// What `update` will redraw everything for. Named and shared with - /// `needs_redraw` rather than written out twice, because the two must - /// agree: `needs_redraw` is what asks for the frame that `update` would - /// draw, so a condition in one and not the other is a frame nobody - /// requests and a stale window. `resized` was missing from `needs_redraw`, - /// which is latent on Wayland only because winit asks for a redraw after a - /// resize by itself -- a resize changes neither the root nor any widget, - /// so nothing else here would have asked. - fn needs_redraw_all<'a>(&self, root: impl Into>) -> bool { + // Scheduling and drawing must use the same full-redraw predicate. + fn needs_full_redraw<'a>(&self, root: impl Into>) -> bool { self.root_changed(root) || self.resized } @@ -235,7 +228,7 @@ impl UiRenderState { root: impl Into>, widgets: &Widgets, ) -> bool { - self.needs_redraw_all(root) || widgets.has_updates() + self.needs_full_redraw(root) || widgets.has_updates() } pub fn active_widgets(&self) -> usize { From 23270e49fb238634c928b9372a426eed797039bb Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sun, 13 Sep 2026 00:45:26 -0400 Subject: [PATCH 3/3] Drop the redundant redraw predicate test --- core/src/ui/render_state.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 4877fc8..ad8afab 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -322,18 +322,3 @@ impl Default for UiRenderState { Self::new() } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn a_resize_requests_a_frame_without_widget_changes() { - let widgets = Widgets::new(); - let mut state = UiRenderState::new(); - - assert!(!state.needs_redraw(None::<&StrongWidget>, &widgets)); - state.resize((800, 600)); - assert!(state.needs_redraw(None::<&StrongWidget>, &widgets)); - } -}