diff --git a/core/src/layout_diagnostics.rs b/core/src/layout_diagnostics.rs index a4523fd..409b17e 100644 --- a/core/src/layout_diagnostics.rs +++ b/core/src/layout_diagnostics.rs @@ -32,7 +32,6 @@ pub(crate) enum Counter { SizeReads, HintHits, HintMisses, - RetainedSizeHits, ReuseAttempts, ReuseExact, ReuseMoved, @@ -42,7 +41,6 @@ pub(crate) enum Counter { ReuseOutside, ReuseWrongLayer, ReuseWrongNode, - PlaceRedraws, QueuePops, DepthReads, LocalRedraws, @@ -70,7 +68,6 @@ impl Counter { "draw-result size reads", "hint hits", "hint misses", - "retained size hits", "reuse attempts", "reuse exact", "reuse moved", @@ -80,7 +77,6 @@ impl Counter { "reuse: outside what it holds for", "reuse: another layer", "reuse: region-node choice changed", - "placed by redrawing", "redraw queue pops", "depth reads", "local redraws", diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index 136708b..cb65e9f 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -1,6 +1,6 @@ use crate::{ - LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive, Size, - TextureHandle, UiRegion, WidgetId, + LayerId, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive, + Size, TextureHandle, UiRegion, WidgetId, }; /// What is kept of a widget its parent has asked about. `drawn` says whether @@ -20,17 +20,22 @@ pub struct ActiveData { /// length. Its length is the same on every ask, which is what /// a local redraw relies on to ask its parent's own question again. pub frame: UiRegion, - /// What of its parent's extent the drawing was given, and what it was - /// given at the parent's first ask of it -- the question a cold layout - /// asks. A part is a length from the extent's start, so an extent that - /// moved re-places every child by re-adding that start. + /// The length its frame was narrowed to on each axis, as a length of + /// its parent's frame: a declared rule, or a box its parent decided for + /// it. `None` forwards the parent's frame whole. Kept as a length rather + /// than a position so that every placement puts the frame back in the + /// part it is given. + pub narrow: [Option; 2], + /// Where its drawing was put, as a part of its parent's box, and where + /// it was asked. The two differ where a container asks in one place and + /// places the answer in another -- a row measures from its cursor and + /// puts the child in its slot. A part is a length from the box's start, + /// so a box that moved re-places every child by re-adding that start. pub place: [Place; 2], pub offer_place: [Place; 2], - /// The box that ask gave it, in its frame's coordinates. Kept rather - /// than worked out again from where its parent's own box is now: a - /// parent drawn again in the box its own answer chose gives its children - /// boxes it never measured anything in, and the measurement this widget - /// answered is the one its parent's layout was built on. + /// The box it was asked in, in its frame's coordinates: the box its + /// drawing was made in and the one its contract is about. Its drawing + /// is placed elsewhere by re-expression, never by asking again there. pub offer_part: UiRegion, /// The measured answer and its dependencies. A hint-only dependency or /// a widget first encountered during placement has no measurement yet. diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 8071cce..6c85578 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -1,14 +1,14 @@ #[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter}; use crate::{ - Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, RenderedText, - RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle, - UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, + Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, Rel, + RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, + TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, render::{ GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind, TexturePrimitive, }, - ui::render_state::DrawInfo, + ui::render_state::{DrawInfo, Placing}, }; const AXES: [Axis; 2] = [Axis::X, Axis::Y]; @@ -44,13 +44,6 @@ pub struct Painter<'a> { /// Only children whose answers were read constrain this widget's answer. pub(super) answer_under: LayoutHolds, pub(super) children: Vec, - /// The children asked about so far, so the first place each was asked in - /// is the one recorded as its offer. - pub(super) offered: Vec, - /// Whether this draw is at the place its parent first asked about, which - /// makes the questions it asks the ones a cold layout asks and their - /// answers the ones to keep. - pub(super) at_offer: bool, /// The children whose size this widget read while drawing. pub(super) size_deps: Vec, /// What this draw itself read of its frame in pixels, per axis: every @@ -58,8 +51,9 @@ pub struct Painter<'a> { pub(super) frame_own: [Holds; 2], /// The same for its own box. pub(super) extent_own: [Holds; 2], - /// Dependencies of every child drawing, including unmeasured overlays. - pub(super) under: LayoutHolds, + /// What each child's drawing depends on. Asking a child again replaces + /// its drawing, so it replaces this too rather than narrowing it. + pub(super) under: Vec<(WidgetId, LayoutHolds)>, /// The movable region this widget's primitives are positioned through: /// its own when opted in, otherwise the nearest ancestor's. pub(super) move_idx: MoveIdx, @@ -151,37 +145,40 @@ impl<'a> Painter<'a> { /// one child wants, and what every transparent container passes for the /// frame. pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget) -> DrawResult<'s, 'a, W> { - self.widget_at(id, UiRegion::FULL, [Place::Within(Part::All); 2]) + self.widget_at(id, [None; 2], [Place::Within(Part::All); 2]) } - /// Draws a child, saying what its fractions are of and where its drawing - /// goes. + /// Asks a child, saying what its fractions are of and where it is asked. /// - /// `frame` is that reference, in this widget's own frame coordinates: - /// [`UiRegion::FULL`] forwards this widget's frame, which is what a - /// container that only divides room passes, so a fraction under it means - /// the same wherever it sits and however deeply it is nested. Narrowing - /// it is for what is decided from above, and a declared length narrows - /// it here. + /// `narrow` is a length this widget decided for the child's frame, per + /// axis, as a length of this widget's own frame: a resolved share, or a + /// box a sibling's answer decided. `None` forwards this widget's frame, + /// which is what a container that only divides room passes, so a + /// fraction under it means the same wherever it sits and however deeply + /// it is nested. A declared length narrows the frame here whatever the + /// caller says. A narrowed frame is placed in the part by the child's + /// alignment and is the box the child is asked in. /// - /// `place` is where the drawing goes, per axis, as a part of this - /// widget's extent: see [`Place`]. A narrowed frame is its own extent, - /// since the narrowing is what said where the drawing goes. + /// `place` is where the child is asked, per axis, as a part of this + /// widget's box: see [`Place`]. The child draws once, in that box, and + /// its answer is placed inside it by re-expressing the drawing. Nothing + /// is drawn again in a box an answer chose; a container that puts the + /// answer somewhere else says so with [`Self::place_at`]. pub fn widget_at<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, - frame: UiRegion, + narrow: [Option; 2], place: [Place; 2], ) -> DrawResult<'s, 'a, W> { let region_node = self.rsc.widgets().is_region_node(id.id()); let declared = self.declared_lens(id); let align = self.rsc.widgets().alignment(id.id()); - let (local, extent) = frame_and_extent( - frame, - part_of(self.extent, place), - narrowed_by(declared, frame), - align, - ); + let declared_narrow = narrowed_by(declared); + let narrow = [ + declared_narrow[0].or(narrow[0]), + declared_narrow[1].or(narrow[1]), + ]; + let (local, extent) = frame_and_extent(part_of(self.extent, place), narrow, align); let within = match local == UiRegion::FULL { true => self.frame, false => local.within(&self.frame), @@ -195,19 +192,7 @@ impl<'a> Painter<'a> { if !self.children.contains(&id.id()) { self.children.push(id.id()); } - let first_ask = self.offer(id.id()); - let offer_place = if first_ask { - place - } else { - self.state - .active - .get(&id.id()) - .map_or(place, |a| a.offer_place) - }; let px = local.size().to_px(self.px); - // The answer and what it holds for, both about the place asked in. - // The child's record may say something else once its drawing has been - // placed: a drawing made again in its placed box holds for that box. let (size, answer_holds, holds) = self.state.draw_inner( id.id(), DrawInfo { @@ -221,14 +206,20 @@ impl<'a> Painter<'a> { frame_abs: within, part: extent, place, - offer_place, + offer_place: place, + narrow, px, }, None, self.rsc, ); - let compose = |holds| in_parent(holds, local, extent, place, declared); - self.under = self.under.and(compose(holds)); + let own = self.extent; + let compose = |holds| in_parent(holds, local, extent, place, narrow, own); + let holds = compose(holds); + match self.under.iter_mut().find(|(child, _)| *child == id.id()) { + Some((_, kept)) => *kept = holds, + None => self.under.push((id.id(), holds)), + } DrawResult { child: id, painter: self, @@ -242,9 +233,37 @@ impl<'a> Painter<'a> { /// this frame; what it answered is still something this widget asked. pub fn undraw(&mut self, id: &StrongWidget) { self.children.retain(|child| *child != id.id()); + self.under.retain(|(child, _)| *child != id.id()); self.state.undraw_rec(id.id(), self.rsc); } + /// Puts a child asked about in this draw somewhere else in this + /// widget's box: its answer, placed in this part instead. The drawing + /// is re-expressed there rather than made again -- what a row does once + /// it knows every slot, having measured each child from its cursor. + pub fn place_at(&mut self, id: &StrongWidget, place: [Place; 2]) { + debug_assert!( + self.children.contains(&id.id()), + "'{}' placed a child it did not ask about in this draw", + self.label() + ); + let at = self.placing(); + self.state.place_in(id.id(), &at, place, self.rsc); + } + + /// This widget as the thing its children are placed within. + fn placing(&self) -> Placing { + Placing { + id: self.id, + extent: self.extent, + local: self.frame, + px: self.px, + depth: self.depth, + move_idx: self.move_idx, + mask: self.mask, + } + } + /// What a widget's rules declare its lengths to be, which whoever draws /// it resolves into its frame. Reading them depends on nothing -- the box /// that comes of them is kept on the child, and `redraw` compares it @@ -281,19 +300,6 @@ impl<'a> Painter<'a> { } } - /// Whether this is the first box a child is asked about in during a draw - /// that is itself the one its parent measured -- the question a cold - /// layout asks, whose answer is the one to keep. A drawing made again in - /// a box chosen from an answer asks about that box instead, and what it - /// hears back is not a measurement of anything. - fn offer(&mut self, child: WidgetId) -> bool { - if !self.at_offer || self.offered.contains(&child) { - return false; - } - self.offered.push(child); - true - } - fn depend_on(&mut self, child: &StrongWidget) { if !self.size_deps.contains(&child.id()) { self.size_deps.push(child.id()); @@ -537,19 +543,21 @@ impl PrimitiveLike for &TextureHandle { /// reach it as one length, so what it holds for maps back through that /// length exactly -- and where the box it was given is this widget's own, /// what it says about that box is what this widget can say about its own. +/// `own` is this widget's own box, for a pin that cannot be said exactly. pub(crate) fn in_parent( holds: LayoutHolds, frame: UiRegion, extent: UiRegion, place: [Place; 2], - declared: [Option; 2], + narrow: [Option; 2], + own: UiRegion, ) -> LayoutHolds { let mut result = LayoutHolds::ANY; for axis in AXES { let n = axis as usize; let frame_len = frame.axis(axis).len(); result.frame[n] = holds.frame[n].through(frame_len); - match (place[n].part(), declared[n]) { + match (place[n].part(), narrow[n]) { // Its box is this widget's own, or a part of it in that box's // own lengths: so what it holds for is a range on this widget's // own box, which is what lets that box move without a redraw. A @@ -561,13 +569,22 @@ pub(crate) fn in_parent( } // Its box is a part of this widget's own box, in that box's own // lengths, so what it holds for maps back through that part into - // a range on this widget's box. + // a range on this widget's box. A length it pinned is this + // widget's length less the part's pixels where the part is the + // whole of the box less pixels, which is the one shape that + // inverts exactly; any other part pins this widget's own length. (Part::Of(span), None) => { - result.extent[n] = holds.extent[n].through(span.len()); + let part_len = span.len(); + result.extent[n] = holds.extent[n].through(part_len); + result.extent_len[n] = holds.extent_len[n].map(|pinned| match part_len.rel { + Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px), + _ => own.axis(axis).len(), + }); } - // Its box is a part of this widget's frame: a length of the - // frame is all that reaches it, so what it holds for is a range - // on the frame and none of it on this widget's own box. + // Its box is a part of this widget's frame, or a length of it + // decided here: a length of the frame is all that reaches it, + // so what it holds for is a range on the frame and none of it + // on this widget's own box. _ => { result.frame[n] = result.frame[n].and( holds.extent[n] @@ -670,44 +687,34 @@ pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion { /// The length a rule gives a child's frame, per axis: a fraction in it is a /// fraction of the frame the child was given, which is the one length the /// rule can mean. -pub(crate) fn narrowed_by(declared: [Option; 2], frame: UiRegion) -> [Option; 2] { - AXES.map(|axis| { - declared[axis as usize] - .map(|len| Len::from_parts(len.rel, len.px).within_len(frame.axis(axis).len())) - }) +pub(crate) fn narrowed_by(declared: [Option; 2]) -> [Option; 2] { + declared.map(|declared| declared.map(|len| Len::from_parts(len.rel, len.px))) } -/// The frame a child is asked in and the box its drawing goes in, both in -/// the coordinates of the widget asking. +/// The frame a child is asked in and the box it is asked in, both in the +/// coordinates of the widget asking. /// -/// `frame` is what the caller said the child's fractions are of, and `part` -/// what of the caller's own box the drawing takes. `narrow` is the length a -/// declared rule gives the frame, which makes the frame the box the drawing -/// goes in -- a rule is what decided where it goes, and there is nothing -/// left to place inside it. A caller that narrowed the frame itself said the -/// same thing. +/// `part` is what of the caller's own box the child is given. `narrow` is a +/// length decided for the child's frame -- a rule, a share, a box a sibling +/// decided -- which makes the frame the box the child is asked in: that +/// length is what decided where it goes, placed in the part by the child's +/// alignment. Where nothing narrowed it, the frame is the caller's own and +/// the part is the box. /// -/// The length is the caller's to supply so that a widget asked again gets -/// the frame it already has rather than a second resolution of its rule. +/// A length rather than a position, so that a child placed again is put back +/// in whatever part it is given rather than where it first was. pub(crate) fn frame_and_extent( - mut frame: UiRegion, part: UiRegion, narrow: [Option; 2], align: RegionAlign, ) -> (UiRegion, UiRegion) { + let mut frame = UiRegion::FULL; let mut extent = part; for (axis, narrow) in AXES.into_iter().zip(narrow) { - let span = frame.axis_mut(axis); - let narrowed = match narrow { - Some(len) => { - let slot = part.axis(axis); - let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel()); - *span = UiSpan::new(start, start + len); - true - } - None => *span != UiSpan::FULL, - }; - if narrowed { + if let Some(len) = narrow { + let slot = part.axis(axis); + let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel()); + *frame.axis_mut(axis) = UiSpan::new(start, start + len); *extent.axis_mut(axis) = UiSpan::FULL; } } diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 531f113..1bc879d 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -2,8 +2,8 @@ use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::ui::painter::{declared_lens, frame_and_extent, narrowed_by, part_of, placed_extent}; use crate::{ - ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Moves, - Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, + ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, + Moves, Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, WidgetId, Widgets, util::{HashMap, Vec2}, }; @@ -26,14 +26,18 @@ pub(super) struct DrawInfo { /// That frame composed into `parent_move`'s coordinates, which is what /// the widget's own drawing is written within. pub frame_abs: UiRegion, - /// The box the drawing is given, in the frame's own coordinates: the + /// The box the widget is asked in, in the frame's own coordinates: the /// part of the parent's own box that `place` names, before the widget's /// answer is placed inside it. pub part: UiRegion, - /// What of the parent's extent the drawing was given, and what it was - /// given at the parent's first ask of it. See [`Place`]. + /// Where the widget is put, and where it was asked, as parts of the + /// parent's box. See [`Place`]. The two are one ask's place until the + /// parent places the answer somewhere else. pub place: [Place; 2], pub offer_place: [Place; 2], + /// The length the frame was narrowed to on each axis, as a length of + /// the parent's frame, where anything narrowed it. + pub narrow: [Option; 2], /// The frame in pixels: one multiply from the parent's own, which is /// where every pixel length in layout comes from. pub px: PxVec2, @@ -45,34 +49,20 @@ impl DrawInfo { fn fill(&self) -> [bool; 2] { self.place.map(Place::fills) } - - /// Whether this ask is the one the widget's answer is kept from: the - /// same widget in the place its parent measured it by, however this draw - /// came about. - /// - /// **Open.** A place is a length from where the asking widget's own box - /// starts, so two drawings of that widget -- one in the box its parent - /// measured it in, one in the box its own answer chose -- ask their - /// children in the same places and different boxes, and this cannot tell - /// them apart. Shrinker seeds 2 (`repaint`) and 108 (`reorder`) at depth - /// 5 are where that shows. - fn offer(&self) -> bool { - self.place == self.offer_place - } } /// What a widget's children are placed in: its own box, the coordinates its /// drawing is in, and what else one ask of a child is decided from. -struct Placing { - id: WidgetId, - extent: UiRegion, +pub(super) struct Placing { + pub id: WidgetId, + pub extent: UiRegion, /// The widget's frame in the coordinates its children compose within: /// `FULL` where it is a region node, since its box is that node. - local: UiRegion, - px: PxVec2, - depth: usize, - move_idx: MoveIdx, - mask: MaskIdx, + pub local: UiRegion, + pub px: PxVec2, + pub depth: usize, + pub move_idx: MoveIdx, + pub mask: MaskIdx, } pub struct UiRenderState { @@ -137,8 +127,7 @@ impl UiRenderState { let px = active.frame.size().to_px(size); // Nothing above the root chose anything, so the box it was first // asked about is the whole of its frame. - let offer = part_of(UiRegion::FULL, active.offer_place); - active.answers_at(px, offer) && active.holds.contains(px, active.extent) + active.answers_at(px, active.offer_part) && active.holds.contains(px, active.offer_part) }); if !stands { widgets.needs_redraw.insert(root); @@ -164,6 +153,7 @@ impl UiRenderState { part: UiRegion::FULL, place: [Place::Within(Part::All); 2], offer_place: [Place::Within(Part::All); 2], + narrow: [None; 2], px, } } @@ -219,14 +209,8 @@ impl UiRenderState { /// The root's frame: the window, narrowed by the root's own rules. Its /// extent is that frame, since nothing above it chose anything else. fn root_region(id: WidgetId, widgets: &Widgets) -> UiRegion { - let narrow = narrowed_by(declared_lens(widgets, id), UiRegion::FULL); - frame_and_extent( - UiRegion::FULL, - UiRegion::FULL, - narrow, - widgets.alignment(id), - ) - .0 + let narrow = narrowed_by(declared_lens(widgets, id)); + frame_and_extent(UiRegion::FULL, narrow, widgets.alignment(id)).0 } pub(super) fn draw_inner( @@ -243,71 +227,50 @@ impl UiRenderState { diag::draw_request(id, info.parent, frame, info.px, info.region_node); } let align = rsc.widgets().alignment(id); + let declared = declared_lens(rsc.widgets(), id); // Nothing this widget measured can be dirty while it draws: layout is // one bottom-up walk, so anything deeper has settled or deferred to // its own parent, and a deferred one leaves that parent marked. let stale = rsc.widgets().needs_redraw.contains(&id); - let retained = match stale { - true => None, - false => self - .retained_answer(id, part, info) - .or_else(|| self.try_reuse(id, frame, part, info, rsc)), - }; - let answer = retained.unwrap_or_else(|| { + // The widget draws once, in the box it is asked in, and its answer + // is placed inside that box by re-expressing the drawing. The box the + // answer chose is never a question: nothing is drawn again in it, so + // an answer is kept only with the drawing that gave it, and both + // have to hold for the box asked about. + let reused = (!stale) + .then(|| self.retained_answer(id, part, info)) + .flatten() + .and_then(|answer| { + let extent = placed_extent(part, answer.0, declared, info.fill(), align); + self.try_reuse(id, frame, part, extent, info, rsc) + .map(|()| answer) + }); + let answer = reused.unwrap_or_else(|| { if old.is_none() { old = self.remove(id, false, rsc); } - self.draw_at(id, part, info, old.take(), rsc) + let answer = self.draw_at(id, part, info, old.take(), rsc); + // Where the drawing goes: the part its parent gave it, with the + // answer placed inside that part on any axis the parent left + // open. + let extent = placed_extent(part, answer.0, declared, info.fill(), align); + if extent != part { + self.reposition(id, frame, extent, info, rsc); + } + answer }); - // Where the drawing goes, in the frame's own coordinates: the part - // its parent gave it, with the answer placed inside that part on any - // axis the parent left open. The frame itself does not change, so - // nothing under it resolves a fraction a second time. - // - // From the answer it gave when its parent measured it, and not from - // what a placing evaluation reported: placing a drawing by what it - // said in the box its own answer chose would move the box out from - // under it. - let measured = match info.offer() { - true => answer.0, - false => self.active[&id].measured().unwrap_or(answer.0), - }; - let extent = placed_extent( - part, - measured, - declared_lens(rsc.widgets(), id), - info.fill(), - align, - ); - self.place(id, extent, info, rsc); - - // On axes the parent filled, measurement and drawing share an extent. - // Otherwise the answer fixes the final extent as a function of the - // frame, so pull that drawing's validity back through it. let drawing_holds = self.active[&id].holds; - let mut settled = answer; - for axis in AXES { - let n = axis as usize; - settled.1.frame[n] = settled.1.frame[n].and(drawing_holds.frame[n]); - if info.fill()[n] { - settled.1.extent[n] = settled.1.extent[n].and(drawing_holds.extent[n]); - } else { - settled.1.frame[n] = settled.1.frame[n] - .and(drawing_holds.extent[n].through(extent.axis(axis).len())); - } - } let active = self.active.get_mut(&id).unwrap(); // Whoever asked owns how the boxes were reached: the frame it stated, - // and what of its own box it gave the drawing. A local redraw asks - // the same question again from these. + // and what of its own box it asked in. A local redraw asks the same + // question again from these. active.frame_abs = frame; active.frame = info.frame; - if info.offer() { - active.answer = Some(answer); - active.offer_place = info.offer_place; - active.offer_part = part; - } + active.narrow = info.narrow; + active.answer = Some(answer); + active.offer_place = info.offer_place; + active.offer_part = part; active.place = info.place; active.own_align = align; // A subtree can be reused whole under a different parent -- same box, @@ -322,21 +285,7 @@ impl UiRenderState { { old_parent.children.retain(|child| *child != id); } - (answer.0, answer.1, settled.1) - } - - /// Recompose retained geometry when the evaluation still holds at this extent. - fn place(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) { - if self - .try_reuse(id, info.frame_abs, extent, info, rsc) - .is_some() - { - return; - } - #[cfg(feature = "layout-diagnostics")] - diag::bump(Counter::PlaceRedraws); - let old = self.remove(id, false, rsc); - self.draw_at(id, extent, info, old, rsc); + (answer.0, answer.1, drawing_holds) } /// Calls a widget's `draw` and keeps what it drew in `extent` of `frame`. @@ -361,15 +310,9 @@ impl UiRenderState { // Reusing its index sooner could make an old parent look current. false => (info.parent_move, frame, self.slots.remove(&id)), }; - let (old_children, old_answer, old_offer_part) = match old { - Some(old) => (old.children, old.answer, Some(old.offer_part)), - None => (Vec::new(), None, None), - }; + let old_children = old.map_or_else(Vec::new, |old| old.children); rsc.widgets_mut().needs_redraw.remove(&id); - // Only evaluation at the original offer establishes the children's - // offers. A placing evaluation must not overwrite that question. let px = info.px; - let at_offer = info.offer(); let mut painter = Painter { state: self, @@ -385,11 +328,9 @@ impl UiRenderState { primitives: Vec::new(), mask_region: None, children: Vec::new(), - offered: Vec::new(), - at_offer, size_deps: Vec::new(), frame_own: [Holds::ANY; 2], - under: LayoutHolds::ANY, + under: Vec::new(), extent_own: [Holds::ANY; 2], answer_under: LayoutHolds::ANY, depth: info.depth, @@ -422,8 +363,6 @@ impl UiRenderState { extent_len, answer_under, children, - offered: _, - at_offer: _, size_deps, frame_own, under, @@ -471,7 +410,9 @@ impl UiRenderState { extent_len, }; let answer_holds = own_holds.and(answer_under); - let holds = answer_holds.and(under); + let holds = under + .into_iter() + .fold(answer_holds, |holds, (_, child)| holds.and(child)); debug_assert!( holds.contains(px, extent), "'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}", @@ -496,6 +437,7 @@ impl UiRenderState { part: UiRegion::FULL, place: [Place::Within(Part::All); 2], offer_place: [Place::Within(Part::All); 2], + narrow: [None; 2], px, }, rsc, @@ -509,11 +451,12 @@ impl UiRenderState { frame_abs: frame, extent, frame: info.frame, + narrow: info.narrow, place: info.place, offer_place: info.offer_place, - offer_part: old_offer_part.unwrap_or(extent), - // Whoever asked writes the answer, if this was the asking. - answer: old_answer, + offer_part: extent, + // Whoever asked writes the answer. + answer: None, size, holds, drawn: true, @@ -598,16 +541,17 @@ impl UiRenderState { active.frame.size().to_px(parent_px) } - /// Reuses the actual drawing in a new box if its retained contract holds - /// there. Answers retained from a different ask are handled separately. + /// Keeps the retained drawing if its contract holds for `part`, the box + /// asked about, and puts it at `extent`, where the answer places it. fn try_reuse( &mut self, id: WidgetId, frame: UiRegion, + part: UiRegion, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc, - ) -> Option<(Size, LayoutHolds)> { + ) -> Option<()> { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::ReuseAttempts); if rsc.widgets().needs_redraw.contains(&id) { @@ -656,7 +600,7 @@ impl UiRenderState { // In pixels, because the frame is a fraction of its parent's and // that may be what changed -- an unchanged fraction of a box half the // size is half the widget. - if !active.holds.contains(info.px, extent) { + if !active.holds.contains(info.px, part) { #[cfg(feature = "layout-diagnostics")] { // Which of the three said no, so a frame that redraws more @@ -665,14 +609,13 @@ impl UiRenderState { let holds = active.holds; for axis in AXES { let n = axis as usize; - if holds.extent_len[n].is_some_and(|pinned| pinned != extent.axis(axis).len()) { + if holds.extent_len[n].is_some_and(|pinned| pinned != part.axis(axis).len()) { diag::bump(Counter::OutsidePinnedLen); } if !holds.frame[n].contains(info.px.axis(axis)) { diag::bump(Counter::OutsideFrame); } - if !holds.extent[n].contains(extent.axis(axis).len().to_px(info.px.axis(axis))) - { + if !holds.extent[n].contains(part.axis(axis).len().to_px(info.px.axis(axis))) { diag::bump(Counter::OutsideExtent); } } @@ -681,9 +624,31 @@ impl UiRenderState { } return None; } + self.relocate(id, frame, extent, info, rsc); + Some(()) + } + + /// Puts a retained drawing where its parent now has it, without drawing: + /// a moved frame recomposes the subtree or rewrites its node, and a box + /// of another length re-expresses everything inside it. + fn relocate( + &mut self, + id: WidgetId, + frame: UiRegion, + extent: UiRegion, + info: DrawInfo, + rsc: &mut dyn UiRsc, + ) { + let active = &self.active[&id]; + debug_assert!( + !rsc.widgets().needs_redraw.contains(&id), + "'{}' ({id:?}) placed while marked to draw", + rsc.widgets().label(id) + ); + let has_region_node = active.move_idx != active.parent_move; let extent_moved = active.extent != extent; let moved = active.frame_abs != frame; - let (answer, slot) = ((active.size, active.holds), active.move_idx); + let slot = active.move_idx; if moved { if has_region_node { self.moves.set(slot, frame); @@ -719,20 +684,31 @@ impl UiRenderState { }, ); } - Some(answer) } - /// Places one child of `at.id` in the box that widget's own box gives - /// it: its part of the extent, with its answer placed inside that part - /// where the ask left the axis open. + /// Places one child of `at.id` where that widget's own box now has it. fn place_child(&mut self, child: WidgetId, at: &Placing, rsc: &mut dyn UiRsc) { + let place = self.active[&child].place; + self.place_in(child, at, place, rsc); + } + + /// Puts a child of `at.id` in `place` of that widget's box: its answer + /// placed inside that part where the place leaves the axis open, the + /// drawing re-expressed there. + pub(super) fn place_in( + &mut self, + child: WidgetId, + at: &Placing, + place: [Place; 2], + rsc: &mut dyn UiRsc, + ) { let active = &self.active[&child]; - let (frame, part) = Self::re_ask(active, at.extent, active.place); + let (frame, part) = Self::re_ask(active, at.extent, place); let extent = placed_extent( part, active.measured().unwrap_or(active.size), active.declared, - active.place.map(Place::fills), + place.map(Place::fills), active.own_align, ); let info = DrawInfo { @@ -745,34 +721,24 @@ impl UiRenderState { frame, frame_abs: frame.within(&at.local), part, - place: active.place, + place, offer_place: active.offer_place, + narrow: active.narrow, px: frame.size().to_px(at.px), }; - self.place(child, extent, info, rsc); + self.relocate(child, info.frame_abs, extent, info, rsc); } - /// The frame and the box a widget being asked again is given, from what - /// it already has and where its parent's box is now. A frame's length is - /// the same on every ask, so a declared length is put back where it sits - /// in the part rather than resolved from its rule a second time. + /// The frame and the box a widget is given at `place` of its parent's + /// box, from what it already has. A frame's length is the same on every + /// ask, so a narrowed frame is put back where it sits in the part rather + /// than resolved a second time. fn re_ask( active: &ActiveData, - parent_extent: UiRegion, + parent_box: UiRegion, place: [Place; 2], ) -> (UiRegion, UiRegion) { - let narrow = AXES.map(|axis| { - let n = axis as usize; - active.declared[n] - .is_some() - .then(|| active.frame.axis(axis).len()) - }); - frame_and_extent( - active.frame, - part_of(parent_extent, place), - narrow, - active.own_align, - ) + frame_and_extent(part_of(parent_box, place), active.narrow, active.own_align) } /// Re-places everything inside a widget whose own box moved. Every child @@ -943,6 +909,7 @@ impl UiRenderState { frame_abs: UiRegion::FULL, extent: UiRegion::FULL, frame: UiRegion::FULL, + narrow: [None; 2], place: [Place::Within(Part::All); 2], offer_place: [Place::Within(Part::All); 2], offer_part: UiRegion::FULL, @@ -1178,12 +1145,20 @@ impl UiRenderState { return true; }; let px = self.asked_px(id); - let (was_answer, was_holds) = (active.answer, active.holds); - // The boxes its parent gave it, then and now: its frame is the same - // on every ask, so the question its parent asked is the one this - // asks again -- there is no box here that could be its parent's to - // choose instead. - let parent_extent = self.active[&parent].extent; + let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.place); + // The question its parent asked, asked again: the same place of the + // box the parent was asked in, which is the box the parent's own + // draw ran in and what its children's parts are of. Where the + // parent's answer put its own drawing is not a question anybody + // asked, and nothing is asked in it here either. + let asked = self.placing_of(parent, self.active[&parent].offer_part); + let (frame, part) = Self::re_ask(active, asked.extent, active.offer_place); + debug_assert_eq!( + frame.size(), + active.frame.size(), + "'{}' ({id:?}) asked again in a frame of another length", + rsc.widgets().label(id) + ); let info = DrawInfo { layer: active.layer, parent: active.parent, @@ -1191,51 +1166,19 @@ impl UiRenderState { parent_move: active.parent_move, region_node: rsc.widgets().is_region_node(id), mask: active.parent_mask, - frame: active.frame, - frame_abs: active.frame_abs, - part: Self::re_ask(active, parent_extent, active.place).1, - place: active.place, + frame, + frame_abs: frame.within(&asked.local), + part, + place: active.offer_place, offer_place: active.offer_place, + narrow: active.narrow, px, }; - // The ask that measured it, asked again: the box it was measured in - // as its parent left it, rather than where that ask's place resolves - // to now -- a parent drawn again in the box its own answer chose - // gives its children boxes it never measured anything in. - let offered = DrawInfo { - place: info.offer_place, - part: active.offer_part, - ..info - }; #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); - // Asked again in the box its parent gave it, which is the question - // its parent asked only while that box is as long as the one it was - // measured in. Any other box is a different question, so the parent - // asks it, with the mark left on. Lengths and not whole boxes: what - // a drawing depends on is its lengths, so the same lengths elsewhere - // is one question. - // - // The frame is the same on every ask now, so this is about the box - // the drawing goes in alone. Removing it -- asking the measuring - // question here and placing the answer afterwards -- is what the - // transparent-frames plan asks for next, and it does not hold yet: - // seeds 104 (`align`) and 210 (`reorder`) at depth 5 settle - // differently warm and cold without it. - if info.part.size() != offered.part.size() { - self.mark(id, rsc.widgets_mut()); - self.mark(parent, rsc.widgets_mut()); - return false; - } let old = self.remove(id, false, rsc); - // The original measurement is refreshed before the assigned slot is - // restored: its lengths may differ even though the frame is - // unchanged. - let answer = self.draw_inner(id, offered, old, rsc); - if info.place != info.offer_place || info.part != offered.part { - self.draw_inner(id, info, None, rsc); - } + let answer = self.draw_inner(id, info, old, rsc); let active = self.active.get_mut(&id).unwrap(); // A wider contract does not invalidate the guarantee the parent kept. // Retain that guarantee so widening and narrowing back do not churn it. @@ -1257,9 +1200,34 @@ impl UiRenderState { diag::bump(Counter::ReaderEdges); } self.mark(parent, rsc.widgets_mut()); + } else { + // The answer stands, so where the parent put it stands: the + // fresh drawing goes back there -- the same place, of the box + // the parent's answer chose rather than the one it was asked in. + let at = self.placing_of(parent, self.active[&parent].extent); + self.place_in(id, &at, was_place, rsc); } true } + + /// A drawn widget as the thing its children are placed within, with + /// `extent` as the box their parts are of: the box it was asked in for + /// asking one of them again, the box its answer chose for placing one. + fn placing_of(&self, id: WidgetId, extent: UiRegion) -> Placing { + let active = &self.active[&id]; + Placing { + id, + extent, + local: match active.move_idx != active.parent_move { + true => UiRegion::FULL, + false => active.frame_abs, + }, + px: self.asked_px(id), + depth: active.depth, + move_idx: active.move_idx, + mask: active.mask, + } + } } /// Whether what a widget reports along `axis` is inside the box it drew in. diff --git a/src/random.rs b/src/random.rs index b3dc2c7..2ab983f 100644 --- a/src/random.rs +++ b/src/random.rs @@ -120,15 +120,15 @@ impl Widget for Branch { let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut))); let measured = painter - .widget_at(&self.probe, UiRegion::FULL, [Place::Within(Part::All), top]) + .widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top]) .len(Axis::X); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y)))); let place = [Place::Within(Part::All), below]; match px > Px::from_f32(self.threshold) { - true => painter.widget_at(&self.wide, UiRegion::FULL, place), - false => painter.widget_at(&self.narrow, UiRegion::FULL, place), + true => painter.widget_at(&self.wide, [None; 2], place), + false => painter.widget_at(&self.narrow, [None; 2], place), }; Size::LEFTOVER } diff --git a/src/widget/position/offset.rs b/src/widget/position/offset.rs index b976826..7af5834 100644 --- a/src/widget/position/offset.rs +++ b/src/widget/position/offset.rs @@ -15,6 +15,6 @@ impl Widget for Offset { moved(painter.extent_len(Axis::X), self.amt.x), moved(painter.extent_len(Axis::Y), self.amt.y), ]; - painter.widget_at(&self.inner, UiRegion::FULL, place).size() + painter.widget_at(&self.inner, [None; 2], place).size() } } diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index 05c7fbb..8d10772 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -30,7 +30,7 @@ impl Widget for Pad { inset(self.padding.left, self.padding.right), inset(self.padding.top, self.padding.bottom), ]; - let inner = painter.widget_at(&self.inner, UiRegion::FULL, place).size(); + let inner = painter.widget_at(&self.inner, [None; 2], place).size(); Size { x: LayoutLen { px: inner.x.px + self.padding.left + self.padding.right, diff --git a/src/widget/position/scroll.rs b/src/widget/position/scroll.rs index 8458896..4721f0e 100644 --- a/src/widget/position/scroll.rs +++ b/src/widget/position/scroll.rs @@ -12,10 +12,9 @@ pub struct Scroll { impl Widget for Scroll { fn draw(&mut self, painter: &mut Painter) -> Size { let container_len = painter.px_len(self.axis); - // Measured in the whole viewport, then drawn at the scrolled offset. - let whole = UiRegion::FULL; + // Asked in the whole viewport, then put at the scrolled offset. let answer_len = painter - .widget_at(&self.inner, whole, [Place::Fill(Part::All); 2]) + .widget_at(&self.inner, [None; 2], [Place::Fill(Part::All); 2]) .len(self.axis); let fixed = Len::from_parts(answer_len.rel, answer_len.px).to_px(container_len); self.container_len = container_len; @@ -59,11 +58,10 @@ impl Widget for Scroll { } // The viewport is the inner's frame, so a fraction it declares or // reports is a fraction of what is on screen rather than of the - // content box its own answer decided. Where it is drawn is the - // content box, scrolled. - painter.widget_at( + // content box its own answer decided. Where it goes is the content + // box, scrolled: its drawing moved there, not made again there. + painter.place_at( &self.inner, - whole, self.axis .pair(Place::Fill(Part::From(content)), Place::Fill(Part::All)), ); diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 320fda0..298a94f 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -25,19 +25,23 @@ impl Widget for Span { // A length for every child before their final slots are chosen. The // frame passes through unchanged, so `rel(0.5)` is half the area this // span was given whatever else is in it and wherever this child sits - // among them; what it is drawn in is the room left from the cursor, - // because a text has to wrap at the width actually there. + // among them; what it is asked in is the room left from the cursor, + // because a text has to wrap at the width actually there. This is + // the one ask a fixed child gets: its slot is its answer, and the + // drawing is moved there once the shares are known. let mut cursor = Len::rel_min(); - let mut lens = Vec::with_capacity(self.children.len()); + let mut sizes = Vec::with_capacity(self.children.len()); for child in &self.children { let room = Place::Within(Part::From(along(cursor, far))); - let len = painter - .widget_at(child, UiRegion::FULL, axis.pair(room, across)) - .len(axis); + let size = painter + .widget_at(child, [None; 2], axis.pair(room, across)) + .size(); + let len = size.axis(axis); cursor.px += len.px + self.gap; cursor.rel += len.rel; - lens.push(len); + sizes.push(size); } + let lens: Vec = sizes.iter().map(|size| size.axis(axis)).collect(); let gaps = self .gap @@ -88,7 +92,8 @@ impl Widget for Span { let mut taken = Weight::ZERO; let mut start = Len::rel_min(); let mut ortho = LayoutLen::ZERO; - for (child, len) in self.children.iter().zip(&lens) { + for (child, size) in self.children.iter().zip(&sizes) { + let len = size.axis(axis); // A child asking for nothing but a part of what is left over, // when nothing is, is not drawn at all. One that also asked for // pixels or a fraction keeps those and overflows. @@ -106,12 +111,26 @@ impl Widget for Span { fixed.rel += len.rel; start = shared(fixed, taken, total.leftover, room); // Along the row the span says where the child goes, and that slot - // is the drawing's box outright rather than something to place an - // answer inside again. - let slot = Place::Fill(Part::From(along(from, start))); - let placed = painter.widget_at(child, UiRegion::FULL, axis.pair(slot, across)); + // is the child's box outright rather than something to place an + // answer inside again. A share is decided here and nowhere + // else: its slot narrows its frame, and the child is asked in + // it, since a text wraps at the width it is actually given. A + // fixed child's slot is its own answer, so its drawing is put + // there as it is. + let slot = along(from, start); + let place = axis.pair(Place::Fill(Part::From(slot)), across); + let used = match len.leftover > Weight::ZERO && shares { + true => { + let mut narrow = [None; 2]; + narrow[axis as usize] = Some(slot.len()); + painter.widget_at(child, narrow, place).len(!axis) + } + false => { + painter.place_at(child, place); + size.axis(!axis) + } + }; if shrinks { - let used = placed.len(!axis); // Choosing between a fixed and a relative length from the // span's own eventual width admits multiple fixed points. // A scalable child therefore makes Children scalable too; diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index f83ba09..46de2f0 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -23,19 +23,26 @@ impl Widget for Stack { Some((i, child)) => { painter.child_layer_at(i); painter - .widget_at(child, UiRegion::FULL, [Place::Fill(Part::All); 2]) + .widget_at(child, [None; 2], [Place::Fill(Part::All); 2]) .size() } None => Size::LEFTOVER, }; + // Every other child gets the box the sizing child decided: the + // stack is that length, so that is the box they are asked in, and a + // fraction under them is a fraction of it. A share leaves the axis + // to whoever gave the stack its box. Where a child sits in a box + // bigger than itself is its own business. + let narrow = [Axis::X, Axis::Y].map(|axis| { + let len = size.axis(axis); + (len.leftover == Weight::ZERO).then(|| Len::from_parts(len.rel, len.px)) + }); for (i, child) in self.children.iter().enumerate() { if sizing == Some(i) { continue; } painter.child_layer_at(i); - // Every other child is drawn in the stack's own box, and where it - // sits in one bigger than itself is its own business. - painter.widget(child); + painter.widget_at(child, narrow, [Place::Within(Part::All); 2]); } size } diff --git a/tests/cases/determinism.rs b/tests/cases/determinism.rs index e5240e3..b3d9c3e 100644 --- a/tests/cases/determinism.rs +++ b/tests/cases/determinism.rs @@ -24,15 +24,15 @@ impl Widget for BranchesOnMeasurement { let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut))); let measured = painter - .widget_at(&self.probe, UiRegion::FULL, [Place::Within(Part::All), top]) + .widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top]) .len(Axis::X); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y)))); let place = [Place::Within(Part::All), below]; match px > Px::from_f32(self.threshold) { - true => painter.widget_at(&self.wide, UiRegion::FULL, place), - false => painter.widget_at(&self.narrow, UiRegion::FULL, place), + true => painter.widget_at(&self.wide, [None; 2], place), + false => painter.widget_at(&self.narrow, [None; 2], place), }; Size::LEFTOVER } diff --git a/tests/cases/retained.rs b/tests/cases/retained.rs index 5d97630..3bfee27 100644 --- a/tests/cases/retained.rs +++ b/tests/cases/retained.rs @@ -156,9 +156,9 @@ fn a_span_child_that_declares_its_length_is_drawn_once() { h.set_root((hinted, asked).span(Dir::RIGHT)); assert_eq!(told_draws.get(), 1); - // Only the available length changes: positioning the final slot does - // not invalidate a numeric size read. - assert_eq!(asked_draws.get(), 2); + // Asked once, from the cursor; its slot is its answer and the drawing is + // moved there. + assert_eq!(asked_draws.get(), 1); } #[test] @@ -216,7 +216,7 @@ impl Widget for FromHint { let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px)); painter.widget_at( &self.inner, - UiRegion::FULL, + [None; 2], [Place::Within(Part::All), Place::Within(Part::From(top))], ); Size::LEFTOVER @@ -255,10 +255,9 @@ impl Widget for ReadsBox { /// Reads its box across one axis only, so its drawing holds for a taller /// box on its own and only a wider one is worth a draw. /// -/// Both of these report a quarter of what they read, without saying that the -/// drawing holds there too, so each length they are asked at costs two draws: -/// one to answer, and one in the quarter-sized box that answer places them -/// in. The counts below are in those pairs. +/// Both of these report a quarter of what they read. The quarter-sized box +/// the answer places them in is not a question: the drawing is moved there, +/// so each length they are asked at costs one draw. struct ReadsWidth { draws: Rc>, } @@ -384,7 +383,7 @@ fn a_resize_redraws_what_read_its_box() { h.resize((800, 100)); h.frame(); - assert_eq!(draws.get(), settled + 2); + assert_eq!(draws.get(), settled + 1); } #[test] @@ -404,7 +403,7 @@ fn a_resize_only_redraws_read_axes() { h.resize((800, 300)); h.frame(); - assert_eq!(draws.get(), settled + 2, "width changes its answer"); + assert_eq!(draws.get(), settled + 1, "width changes its answer"); } /// A window is measured onto the grid like everything else, so a resize too @@ -431,7 +430,7 @@ fn a_resize_within_one_step_is_not_a_resize() { h.resize((400.0 + step, 200.0)); h.frame(); - assert_eq!(draws.get(), settled + 2); + assert_eq!(draws.get(), settled + 1); } /// The same for a box that changes because a sibling did: what is compared @@ -878,7 +877,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, - UiRegion::FULL, + [None; 2], [ Place::Within(Part::From(self.region.x)), Place::Within(Part::From(self.region.y)), @@ -967,7 +966,7 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, - self.region, + [Some(self.region.x.len()), None], [ Place::Fill(Part::From(self.extent.x)), Place::Fill(Part::From(self.extent.y)), @@ -1137,7 +1136,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, - UiRegion::FULL, + [None; 2], [ Place::Fill(Part::From(self.extent.x)), Place::Fill(Part::From(self.extent.y)), @@ -1225,7 +1224,7 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() { fn draw(&mut self, painter: &mut Painter) -> Size { painter.widget_at( &self.child, - UiRegion::FULL, + [None; 2], [ Place::Fill(Part::From(UiSpan::new( Len::px(self.start), @@ -1267,7 +1266,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() { painter .widget_at( &self.child, - UiRegion::FULL, + [None; 2], [ Place::Within(Part::From(self.region.x)), Place::Within(Part::From(self.region.y)), @@ -1287,7 +1286,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() { painter .widget_at( &self.child, - UiRegion::FULL, + [None; 2], [ Place::Fill(Part::From(self.extent.x)), Place::Fill(Part::From(self.extent.y)),