diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 5cfc0a6..06f231c 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -468,6 +468,12 @@ impl UiRenderState { .to_px(self.output_size) } + /// Where a region held in `slot`'s coordinates lands on screen, which is + /// the walk the vertex shader does. + fn px_region(&self, slot: MoveIdx, region: UiRegion) -> PixelRegion { + self.moves.resolve(slot, region).to_px(self.output_size) + } + /// A clean widget's retained answer, if that answer holds for a box of /// `px`. This does not move its drawing, which may already be in the box /// that answer placed it in. @@ -903,15 +909,12 @@ impl UiRenderState { } /// Where a widget is on screen: its box composed through the boxes it - /// sits within, which is the walk the vertex shader does. `None` for one - /// that is not drawn. + /// sits within. `None` for one that is not drawn. pub fn window_region(&self, id: &impl IdLike) -> Option { let active = self.active.get(&id.id())?; - if !active.drawn { - return None; - } - let region = self.moves.resolve(active.parent_move, active.region); - Some(region.to_px(self.output_size)) + active + .drawn + .then(|| self.px_region(active.parent_move, active.region)) } /// Settles a dirty widget: asks it again where its parent asked, and @@ -955,17 +958,15 @@ impl UiRenderState { None => Self::root_region(id, rsc.widgets()), }; let offered_px = self.px_of(active.parent_move, asked_in); - let at_offer = same_px(self.px_of(active.parent_move, region), offered_px); - let parent_must_place = active.parent.is_some() - && (!region_node || active.align_override) - && !same_pixel_region( - self.moves - .resolve(active.parent_move, region) - .to_px(self.output_size), - self.moves - .resolve(active.parent_move, asked_in) - .to_px(self.output_size), - ); + // Whole boxes rather than lengths: an offer as long as the final box + // but somewhere else is a different box, and a region node drawing at + // its offer writes the box it drew in into its own entry. + let at_offer = same_pixel_region( + self.px_region(active.parent_move, region), + self.px_region(active.parent_move, asked_in), + ); + let parent_must_place = + active.parent.is_some() && (!region_node || active.align_override) && !at_offer; // An independently positioned region node can redraw at its offer // and move its slot to its own placement. Every other widget needs // its parent to reproduce a different final position. diff --git a/tests/cases/unsettled.rs b/tests/cases/unsettled.rs index b972206..4736d2e 100644 --- a/tests/cases/unsettled.rs +++ b/tests/cases/unsettled.rs @@ -3,9 +3,10 @@ //! frame that had not settled: a wrapping text shaped at a width it was //! measured in rather than the one it was given. The rest are a widget //! measured again in a box its own answer had decided, where the old answer -//! is a fixed point whatever the content now says. The last is neither: one -//! box length, composed two ways, landing either side of the boundary that -//! decided whether a child was drawn at all. +//! is a fixed point whatever the content now says. The last two are neither: +//! one box length, composed two ways, landing either side of the boundary +//! that decided whether a child was drawn at all, and one box as long as the +//! box a widget was offered but somewhere else. use iris::harness::Harness; use iris::prelude::*; @@ -401,9 +402,8 @@ fn a_box_that_only_rounds_past_its_fixed_children_leaves_nothing_over() { /// Five widgets, shrunk by `tests/shrink.rs` from the 277 the oracle's seed /// 18 grows at depth 6. A scroll inside a scroll, the inner one owning a -/// movable region of its own, and only the text at the bottom marked for -/// redraw. Nothing about the tree changes, so no box may -- and the span -/// lands 76px further down the outer scroll warm than it does cold. +/// movable region of its own, and only its text marked for redraw. Nothing +/// about the tree changes, so no box may. fn plant_nested_scrolls(h: &mut Harness) -> Vec { let text = wtext("one line, overflowing whatever it is given") .size(16) @@ -428,14 +428,14 @@ fn plant_nested_scrolls(h: &mut Harness) -> Vec { vec![text.id(), inner.id(), filler.id(), span.id(), root.id()] } -/// **A known defect, not a passing test.** Bisected to `95fb4f9`, which made -/// `Masked` report its box rather than its inner's size: `Scroll` clips -/// through one, so what the outer scroll is told its content measures now -/// depends on whether the inner subtree was redrawn this frame. Warm the -/// span sits at the top of the outer scroll and cold it sits 24px higher, -/// which is exactly the sized child's height. Un-ignore it with the fix. +/// A local redraw asks a dirty widget in the box its parent asked it in, and +/// then again in the box its parent chose from that answer. Skipping the +/// second ask because the two boxes are the same *length* left this inner +/// scroll, which owns a region node, drawn at its offer. The offer is the +/// outer scroll's whole viewport and the final box is 24px above it -- the +/// height of the sized child the outer scroll snaps to the end of -- so the +/// inner scroll and its text stayed 24px too low. #[test] -#[ignore = "known defect: a partial repaint moves a scrolled span, from 95fb4f9"] fn redrawing_one_widget_does_not_move_what_scrolls_around_it() { let mut warm = Harness::new((900, 1200)); let ids = plant_nested_scrolls(&mut warm);