From 5b181bc8af0336cd32dfe7df07fa826b9de02597 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Sat, 19 Sep 2026 02:19:14 -0400 Subject: [PATCH] Experiment: say the room from the cursor as an inset, and read the row's length only where a slot depends on it Measured identical to its parent at every phase of the cost rig; kept as evidence, not proposed for landing. --- core/src/ui/holds.rs | 15 ++++++++++++++ core/src/ui/painter.rs | 37 ++++++++++++++++++++++------------- core/src/ui/place.rs | 15 +++++++------- src/widget/position/pad.rs | 8 ++++---- src/widget/position/span.rs | 39 ++++++++++++++++++++++++++++--------- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/core/src/ui/holds.rs b/core/src/ui/holds.rs index 067ef31..3722d9f 100644 --- a/core/src/ui/holds.rs +++ b/core/src/ui/holds.rs @@ -73,6 +73,21 @@ impl Holds { } } + /// What a box has to be for a part of it, this many pixels shorter, to + /// stay in this range: the range moved by that much, an end that was + /// unbounded staying so. + pub const fn longer_by(self, px: Px) -> Self { + let lo = match self.lo.raw() == Px::MIN.raw() { + true => self.lo, + false => self.lo.add(px), + }; + let hi = match self.hi.raw() == Px::MAX.raw() { + true => self.hi, + false => self.hi.add(px), + }; + Self { lo, hi } + } + const fn raws(lo: i64, hi: i64) -> Self { Self { lo: Px::from_raw(narrow(lo)), diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index c625923..2124e8a 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -593,20 +593,29 @@ impl Painter<'_> { result.extent[n] = holds.extent[n]; result.extent_len[n] = holds.extent_len[n]; } - // 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 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), false) => { - 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), - _ => self.extent.axis(axis).len(), - }); + // Its box is this widget's own less the inset. Where that is + // pixels, its box is exactly that many shorter in any window, + // so what it holds for is a range on this widget's box moved + // by them, and a length it pinned is this widget's length + // less them. An inset with a fraction in it is a different + // number of pixels in each window, and taking it off a length + // rounds once more than taking it off pixels does: there the + // child's box is a fixed expression of this one, so this + // widget's length is pinned and the range goes on the window + // through the child's box, the way a slot's does. + (Part::Inset { lead, trail }, false) => { + let inset = lead + trail; + match inset.rel == Rel::ZERO { + true => { + result.extent[n] = holds.extent[n].longer_by(inset.px); + result.extent_len[n] = holds.extent_len[n].map(|pinned| pinned + inset); + } + false => { + result.window[n] = result.window[n] + .and(holds.extent[n].through(extent.axis(axis).len())); + result.extent_len[n] = Some(self.extent.axis(axis).len()); + } + } } // Its box is a length this widget decided, from its own // frame or from a sibling's answer: no length of this diff --git a/core/src/ui/place.rs b/core/src/ui/place.rs index 95ff5e3..27499ed 100644 --- a/core/src/ui/place.rs +++ b/core/src/ui/place.rs @@ -12,12 +12,13 @@ pub enum Part { /// here is a fraction of the window and not of the box -- the whole of a /// box is [`Self::All`], not a `rel(1.0)` span. From(UiSpan), - /// A part of the box in its own coordinates, which is what a container - /// that insets one speaks: taking eleven pixels off the end needs no - /// length, where saying the same thing in window lengths would make the - /// container read its own box -- and a box chosen from its own answer - /// then feeds back into the answer. - Of(UiSpan), + /// The box less a window length at each end, which is what a container + /// that insets one speaks -- padding, or a row asking a child in the + /// room left from its cursor. Neither end names the box's length, so a + /// container can say "from here to my end" without reading how long it + /// is, and a box chosen from its own answer does not feed back into the + /// answer. + Inset { lead: Len, trail: Len }, /// A box of this length, wherever in the parent's box the child's own /// alignment puts it, and that same length as its frame. Unlike `From`, /// it is a length decided from above rather than a place along a @@ -32,7 +33,7 @@ impl Part { match self { Self::All => extent, Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end), - Self::Of(span) => span.within(&extent), + Self::Inset { lead, trail } => UiSpan::new(extent.start + lead, extent.end - trail), Self::Sized(len) => { let start = extent.start + (extent.len() - len).scale(align.rel()); UiSpan::new(start, start + len) diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index 474c993..4ac0b0a 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -20,10 +20,10 @@ impl Widget for Pad { // The two stay distinct -- the box can be narrower still, where a row // asked this widget in the room left, and a text wraps at that. let inset = |lead: Px, trail: Px| { - Place::Within(Part::Of(UiSpan::new( - Len::from_parts(Rel::ZERO, lead), - Len::from_parts(Rel::ONE, -trail), - ))) + Place::Within(Part::Inset { + lead: Len::from_parts(Rel::ZERO, lead), + trail: Len::from_parts(Rel::ZERO, trail), + }) }; let place = [ inset(self.padding.left, self.padding.right), diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 0140469..06d854e 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -10,13 +10,18 @@ pub struct Span { impl Widget for Span { fn draw(&mut self, painter: &mut Painter) -> Size { let axis = self.dir.axis; - // The row: this span's own box, as a length of the frame its children - // are laid out against. Its start is nothing's business -- a slot is - // a length from it -- so what this reads is the length alone. - let far = painter.extent_len(axis); - let along = |from: Len, to: Len| match self.dir.sign { - Sign::Pos => UiSpan::new(from, to), - Sign::Neg => UiSpan::new(far - to, far - from), + // The room left from the cursor to the row's end, said without the + // row's length: a child measured in it does not make this drawing + // depend on how long the row is. + let room_from = |cursor: Len| match self.dir.sign { + Sign::Pos => Part::Inset { + lead: cursor, + trail: Len::ZERO, + }, + Sign::Neg => Part::Inset { + lead: Len::ZERO, + trail: cursor, + }, }; // Across itself the child sits where its own alignment says, in the // whole of the row: a span is what contains its children there, and @@ -41,7 +46,7 @@ impl Widget for Span { len } None => { - let room = Place::Within(Part::From(along(cursor, far))); + let room = Place::Within(room_from(cursor)); let size = painter .widget_at(child, [None; 2], axis.pair(room, across)) .size(); @@ -66,9 +71,25 @@ impl Widget for Span { |sum, len| sum + *len, ); + // The row: this span's own box as a length of the window, read only + // where a slot depends on it -- shares divide what is left of it, + // and a negative row counts from its end. Reading it pins the + // drawing to this length; a positive row of fixed children is not + // pinned and holds for any length its children do. Its start is + // nothing's business: a slot is a length from it. + let far = (total.leftover > Weight::ZERO || self.dir.sign == Sign::Neg) + .then(|| painter.extent_len(axis)); + let along = |from: Len, to: Len| match self.dir.sign { + Sign::Pos => UiSpan::new(from, to), + Sign::Neg => { + let far = far.expect("a negative row reads its length"); + UiSpan::new(far - to, far - from) + } + }; // What is left for the shares to divide: the row less everything // fixed, as a length of the frame rather than a number of pixels. - let room = far - Len::from_parts(total.rel, total.px); + // Nothing where there are no shares, and nothing reads it there. + let room = far.map_or(Len::ZERO, |far| far - Len::from_parts(total.rel, total.px)); // Whether anything is left over is a question in pixels: `rel(0.5)` // beside 300 px is full at 600 and overfull at 400. Asked of `room` // itself, and answered back through the same expression, so the