diff --git a/core/src/layout_diagnostics.rs b/core/src/layout_diagnostics.rs index 47b3976..a4523fd 100644 --- a/core/src/layout_diagnostics.rs +++ b/core/src/layout_diagnostics.rs @@ -54,7 +54,7 @@ pub(crate) enum Counter { TextShapes, TextBreaks, GlyphPlacements, - OutsidePlacement, + OutsidePinnedLen, OutsideFrame, OutsideExtent, } @@ -92,7 +92,7 @@ impl Counter { "text shapes", "text line breaks", "glyph placements", - "reuse outside: the placement it was pinned to", + "reuse outside: the length it was pinned to", "reuse outside: a frame length", "reuse outside: an extent length", ]; diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index 10abcd0..136708b 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -17,7 +17,7 @@ pub struct ActiveData { pub extent: UiRegion, /// That frame in its parent's frame coordinates, before composition: /// forwarded whole by a transparent container, narrowed by a declared - /// length or an inset. Its length is the same on every ask, which is what + /// 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 @@ -77,6 +77,14 @@ pub struct ActiveData { } impl ActiveData { + /// What it answered when its parent measured it, where it has been + /// measured at all. Not `size`, which is what its last drawing reported: + /// a drawing made in the box that answer chose is answering a different + /// question. + pub fn measured(&self) -> Option { + self.answer.map(|(size, _)| size) + } + /// Whether what it answered still stands for a frame of these pixel /// lengths. The answer was given in the box its parent first asked /// about, which is what it is checked against -- `holds` on the record diff --git a/core/src/ui/layout_holds.rs b/core/src/ui/layout_holds.rs index a9675ce..ab38a96 100644 --- a/core/src/ui/layout_holds.rs +++ b/core/src/ui/layout_holds.rs @@ -9,8 +9,9 @@ const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// The symbolic length is a pin rather than a range: a container places its /// children as lengths of its frame measured from where its own box starts, /// so what it draws turns on that box's length and on nothing about where it -/// is. It does not compose into the parent -- a widget pinned this way is -/// checked when it is re-placed. +/// is. It reaches the parent only where the box it pinned is the parent's +/// own; anywhere else the parent chose that length itself, and a widget +/// pinned this way is checked when it is re-placed. #[derive(Clone, Copy, Debug, PartialEq)] pub struct LayoutHolds { pub frame: [Holds; 2], diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index ef0d03e..8071cce 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -20,7 +20,7 @@ pub struct Painter<'a> { /// What a fraction this widget declares or reports is a fraction of, in /// the coordinates of `move_idx`: forwarded from its parent unchanged /// through a span, a stack or a scroll, and narrowed only by what was - /// decided above it -- a declared length, an inset, the root. Its length + /// decided above it -- a declared length, or the root. Its length /// is the same on every ask of the widget, which is what keeps a fraction /// under it from being resolved twice. pub(super) frame: UiRegion, @@ -55,8 +55,8 @@ pub struct Painter<'a> { pub(super) size_deps: Vec, /// What this draw itself read of its frame in pixels, per axis: every /// length until it reads one, then that one, unless it says otherwise. - pub(super) own: [Holds; 2], - /// The same for its extent. + 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, @@ -161,8 +161,8 @@ impl<'a> Painter<'a> { /// [`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 -- an inset's margins -- and a - /// declared length narrows it here. + /// it is for what is decided from above, and a declared length narrows + /// it here. /// /// `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, @@ -176,14 +176,12 @@ impl<'a> Painter<'a> { 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 narrow = AXES.map(|axis| { - let n = axis as usize; - // A rule's fraction is a fraction of the frame the child was - // given, which is the one length the rule can mean. - declared[n] - .map(|len| Len::from_parts(len.rel, len.px).within_len(frame.axis(axis).len())) - }); - let (local, extent) = frame_and_extent(frame, part_of(self.extent, place), narrow, align); + let (local, extent) = frame_and_extent( + frame, + part_of(self.extent, place), + narrowed_by(declared, frame), + align, + ); let within = match local == UiRegion::FULL { true => self.frame, false => local.within(&self.frame), @@ -224,10 +222,6 @@ impl<'a> Painter<'a> { part: extent, place, offer_place, - // The question its parent measured it by, asked again: the - // same widget in the same place, however this draw came - // about. - offer: place == offer_place, px, }, None, @@ -429,7 +423,7 @@ impl<'a> Painter<'a> { /// children. Its own box is a part of this one. pub fn frame_px_len(&mut self, axis: Axis) -> Px { let len = self.px.axis(axis); - let own = &mut self.own[axis as usize]; + let own = &mut self.frame_own[axis as usize]; if *own == Holds::ANY { *own = Holds::at(len); } @@ -447,7 +441,7 @@ impl<'a> Painter<'a> { self.label(), self.id ); - self.own[axis as usize] = holds; + self.frame_own[axis as usize] = holds; } pub fn text_data(&mut self) -> &mut TextData { @@ -663,10 +657,8 @@ pub(crate) fn placed_extent( placed } -/// The part of a widget's extent a `place` names, in the coordinates its -/// extent is in: a span is measured in frame lengths from where the extent -/// starts, so nothing under it depends on where that is, and an extent that -/// moved re-places every child by re-adding its start. +/// The part of a widget's own box a `place` names, in the coordinates that +/// box is in. pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion { let mut part = extent; for axis in AXES { @@ -675,6 +667,16 @@ pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion { part } +/// 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())) + }) +} + /// The frame a child is asked in and the box its drawing goes in, both in /// the coordinates of the widget asking. /// diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index ab5b371..531f113 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -1,9 +1,9 @@ #[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; -use crate::ui::painter::{declared_lens, frame_and_extent, part_of, placed_extent}; +use crate::ui::painter::{declared_lens, frame_and_extent, narrowed_by, part_of, placed_extent}; use crate::{ - ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, - Moves, Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, + ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Moves, + Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, WidgetId, Widgets, util::{HashMap, Vec2}, }; @@ -34,9 +34,6 @@ pub(super) struct DrawInfo { /// given at the parent's first ask of it. See [`Place`]. pub place: [Place; 2], pub offer_place: [Place; 2], - /// Whether this ask is the one the widget's answer is kept from: the - /// first box its parent asked about, in the parent's own measuring draw. - pub offer: bool, /// The frame in pixels: one multiply from the parent's own, which is /// where every pixel length in layout comes from. pub px: PxVec2, @@ -48,6 +45,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 @@ -153,7 +164,6 @@ impl UiRenderState { part: UiRegion::FULL, place: [Place::Within(Part::All); 2], offer_place: [Place::Within(Part::All); 2], - offer: true, px, } } @@ -209,9 +219,7 @@ 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 declared = declared_lens(widgets, id); - let narrow = - AXES.map(|axis| declared[axis as usize].map(|len| Len::from_parts(len.rel, len.px))); + let narrow = narrowed_by(declared_lens(widgets, id), UiRegion::FULL); frame_and_extent( UiRegion::FULL, UiRegion::FULL, @@ -257,13 +265,13 @@ impl UiRenderState { // 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 first asked, and not from - // what a placing evaluation reported: a drawing made in the box that - // answer chose is answering a different question, and placing it by - // that would move the box out from under itself. - let measured = match info.offer { + // 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].answer.map_or(answer.0, |(size, _)| size), + false => self.active[&id].measured().unwrap_or(answer.0), }; let extent = placed_extent( part, @@ -295,7 +303,7 @@ impl UiRenderState { // the same question again from these. active.frame_abs = frame; active.frame = info.frame; - if info.offer { + if info.offer() { active.answer = Some(answer); active.offer_place = info.offer_place; active.offer_part = part; @@ -361,7 +369,7 @@ impl UiRenderState { // 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 at_offer = info.offer(); let mut painter = Painter { state: self, @@ -380,7 +388,7 @@ impl UiRenderState { offered: Vec::new(), at_offer, size_deps: Vec::new(), - own: [Holds::ANY; 2], + frame_own: [Holds::ANY; 2], under: LayoutHolds::ANY, extent_own: [Holds::ANY; 2], answer_under: LayoutHolds::ANY, @@ -417,7 +425,7 @@ impl UiRenderState { offered: _, at_offer: _, size_deps, - own, + frame_own, under, move_idx, layer, @@ -458,7 +466,7 @@ impl UiRenderState { self.moves.remove(idx); } let own_holds = LayoutHolds { - frame: own, + frame: frame_own, extent: extent_own, extent_len, }; @@ -488,7 +496,6 @@ impl UiRenderState { part: UiRegion::FULL, place: [Place::Within(Part::All); 2], offer_place: [Place::Within(Part::All); 2], - offer: false, px, }, rsc, @@ -659,7 +666,7 @@ impl UiRenderState { for axis in AXES { let n = axis as usize; if holds.extent_len[n].is_some_and(|pinned| pinned != extent.axis(axis).len()) { - diag::bump(Counter::OutsidePlacement); + diag::bump(Counter::OutsidePinnedLen); } if !holds.frame[n].contains(info.px.axis(axis)) { diag::bump(Counter::OutsideFrame); @@ -721,13 +728,9 @@ impl UiRenderState { fn place_child(&mut self, child: WidgetId, at: &Placing, rsc: &mut dyn UiRsc) { let active = &self.active[&child]; let (frame, part) = Self::re_ask(active, at.extent, active.place); - // The answer it gave, and not what its last drawing reported: a - // drawing made in the box that answer chose is answering a different - // question. - let answer = active.answer.map_or(active.size, |(size, _)| size); let extent = placed_extent( part, - answer, + active.measured().unwrap_or(active.size), active.declared, active.place.map(Place::fills), active.own_align, @@ -744,10 +747,6 @@ impl UiRenderState { part, place: active.place, offer_place: active.offer_place, - // Putting it back where it was asked about is that ask again, so - // what it answers there is the answer -- and putting it anywhere - // else is not, however the box was arrived at. - offer: active.place == active.offer_place, px: frame.size().to_px(at.px), }; self.place(child, extent, info, rsc); @@ -1197,7 +1196,6 @@ impl UiRenderState { part: Self::re_ask(active, parent_extent, active.place).1, place: active.place, offer_place: active.offer_place, - offer: false, px, }; // The ask that measured it, asked again: the box it was measured in @@ -1207,7 +1205,6 @@ impl UiRenderState { let offered = DrawInfo { place: info.offer_place, part: active.offer_part, - offer: true, ..info }; #[cfg(feature = "layout-diagnostics")] @@ -1224,8 +1221,8 @@ impl UiRenderState { // 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. + // 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()); diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index c7d88f6..05c7fbb 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -16,12 +16,10 @@ impl Widget for Pad { // // The padding goes around what it pads: the frame passes through, so // the inner's fractions mean what they would without it, and only - // the box it draws in is moved in by the pixels. - // A part of this widget's own box, in that box's own lengths: the - // padding is pixels, which are the same pixels wherever the box - // lands, so nothing here reads how long the box is -- and a box - // chosen from this widget's own answer therefore does not feed back - // into that answer. + // the box it draws in is moved in by the pixels. Said as a part of + // this widget's own box in that box's own lengths, so nothing here + // reads how long the box is -- and a box chosen from this widget's + // own answer therefore does not feed back into that answer. let inset = |lead: Px, trail: Px| { Place::Within(Part::Of(UiSpan::new( Len::from_parts(Rel::ZERO, lead),