diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index b245ad5..46a27a7 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -43,7 +43,9 @@ pub struct UiRenderState { old_root: Option, /// Whether the output has changed since the last update. A frame is - /// owed for that whether or not anything has to be drawn again. + /// owed for that whether or not anything has to be drawn again: every + /// fraction becomes pixels against the output, in the shader's uniform + /// as well as here. resized: bool, /// A widget's move slot, which outlives any one `ActiveData`: a redraw /// replaces that while its children go on pointing at the slot. @@ -83,13 +85,30 @@ impl UiRenderState { /// size is applied where a fraction becomes pixels -- here in `to_px`, /// and in the shader by its uniform. A resize therefore rewrites no /// retained entry at all. - pub fn resize(&mut self, size: impl Into) { + /// + /// The root is the only widget a resize marks, and only where the new + /// output is outside what its answer holds for. Its range is the + /// intersection of everything under it, so admitting the new output says + /// the whole tree still stands -- and nothing above the root moved, the + /// window being no entry to rewrite. Where it does not admit it, the + /// ordinary bottom-up walk draws the root and `Holds` decides, per widget + /// and as the walk reaches it, how far down the new length reaches. + pub fn resize(&mut self, size: impl Into, widgets: &mut Widgets) { let size = PxVec2::from_f32(size.into()); if size == self.output_size { return; } self.output_size = size; self.resized = true; + let Some(root) = self.old_root else { return }; + let Some(active) = self.active.get(&root) else { + return; + }; + let px = active.given_len.to_px(size); + let holds = active.answer.1; + if !(holds[0].contains(px.x) && holds[1].contains(px.y)) { + widgets.needs_redraw.insert(root); + } } /// The root is asked about in the output: the window is where a fraction @@ -143,17 +162,6 @@ impl UiRenderState { if self.root_changed(root) { self.redraw_all(root, rsc); self.old_root = root.map(|r| r.id()); - } else if let Some(root) = root - && self.resized - { - // The output is the root's box, so a resize is that box changing - // length, found the way every other box change is found. Before - // anything dirty settles, so that whatever a new output draws - // again is drawn once, in the box it will have. - let region = Self::root_region(root.id(), rsc.widgets()); - let info = self.root_info(region); - let answer = self.draw_inner(root.id(), region, info, None, rsc); - self.active.get_mut(&root.id()).unwrap().answer = answer; } self.resized = false; if rsc.widgets().has_updates() { @@ -196,22 +204,11 @@ impl UiRenderState { diag::draw_request(id, info.parent, region, info.px, info.region_node); } let align = rsc.widgets().alignment(id); - // Nothing this widget has is an answer while something it measured - // is dirty: settling that changes what it would report, and a widget - // settled inside its parent's draw tells nobody -- the comparison - // that marks a reader is in `redraw`, which is not what asked here. - // Both retained routes are an answer, so the question is asked once - // rather than by each of them. - // - // Since `a92c6ac` settles a frame strictly bottom-up, no fuzzer can - // tell whether the second half of this still does anything: dropping - // `dirty_size_under` passes the suite, the shrinker at 400 seeds of - // depth 5, the oracle at 1000 of depth 6 and 2000 seeds at depth 4. - // It stays because `update` draws the root for a resize before - // `redraw_updates` runs at all, which that ordering does not reach -- - // a hole that is reasoned rather than measured. - let stale = - rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets()); + // Only the widget's own mark is asked about. Nothing it measured can + // be dirty while it draws: layout is one bottom-up walk, so anything + // deeper has already settled or deferred to its own parent, and a + // deferred one leaves that parent marked. + let stale = rsc.widgets().needs_redraw.contains(&id); let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale); let retained = match replace_answer || stale { true => None, @@ -492,7 +489,7 @@ impl UiRenderState { parent_move: MoveIdx, widgets: &Widgets, ) -> Option<(Size, [Holds; 2])> { - if widgets.needs_redraw.contains(&id) || self.dirty_size_under(id, widgets) { + if widgets.needs_redraw.contains(&id) { return None; } let active = self.active.get(&id)?; @@ -521,20 +518,6 @@ impl UiRenderState { (holds[0].contains(info.px.x) && holds[1].contains(info.px.y)).then_some((size, holds)) } - /// Whether anything whose size this widget's own size was read from is - /// dirty, which makes what it would answer not yet known. It also keeps - /// a reader that asks first from laying out twice, which is all it was - /// here for while a changed size was thought to reach its reader in any - /// order; it does not, where the change settles inside the reader's own - /// draw. - fn dirty_size_under(&self, id: WidgetId, widgets: &Widgets) -> bool { - self.active.get(&id).is_some_and(|active| { - active.size_deps.iter().any(|child| { - widgets.needs_redraw.contains(child) || self.dirty_size_under(*child, widgets) - }) - }) - } - /// The pixel lengths of the box a widget was given and of the box it was /// first asked about, which is what a local redraw needs to ask the /// question its parent asked. diff --git a/src/default/mod.rs b/src/default/mod.rs index cf25d56..7aa41a2 100644 --- a/src/default/mod.rs +++ b/src/default/mod.rs @@ -251,7 +251,7 @@ impl AppState for DefaultApp { ui_state.renderer.draw(); } WindowEvent::Resized(size) => { - render.resize((size.width, size.height)); + render.resize((size.width, size.height), rsc.widgets_mut()); ui_state.renderer.resize(size) } WindowEvent::KeyboardInput { event, .. } => { diff --git a/src/harness.rs b/src/harness.rs index 9d5d796..d5e00fe 100644 --- a/src/harness.rs +++ b/src/harness.rs @@ -144,9 +144,9 @@ impl Harness { // bound that comes with `SyncSender` is far past anything a test // leaves unread. let (send, updates) = sync_channel(1024); - let rsc = DefaultRsc::init(Arc::new(Queue(send))); + let mut rsc = DefaultRsc::init(Arc::new(Queue(send))); let mut render = UiRenderState::new(); - render.resize(size); + render.resize(size, rsc.widgets_mut()); Self { rsc, render, @@ -161,7 +161,7 @@ impl Harness { } pub fn resize(&mut self, size: impl Into) { - self.render.resize(size); + self.render.resize(size, self.rsc.widgets_mut()); } /// Changes a length rule after the fact, the way `.width()` sets one.