Compare commits

..
Author SHA1 Message Date
iris-ai 5b181bc8af 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.
2026-09-19 02:19:14 -04:00
5 changed files with 120 additions and 65 deletions

No files matched your search

+15
View File
@@ -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 { const fn raws(lo: i64, hi: i64) -> Self {
Self { Self {
lo: Px::from_raw(narrow(lo)), lo: Px::from_raw(narrow(lo)),
+50 -37
View File
@@ -267,32 +267,36 @@ impl<'a> Painter<'a> {
let widgets = self.rsc.widgets(); let widgets = self.rsc.widgets();
// A rule is the answer where there is one: it wins over whatever the // A rule is the answer where there is one: it wins over whatever the
// widget would draw, so it has to win over what the widget says too. // widget would draw, so it has to win over what the widget says too.
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| { let hint = widgets
widgets .size_rules(id.id())
.get_dyn(id.id()) .axis(axis)
.and_then(|widget| widget.size_hint(axis)) .exact()
}); .or_else(|| {
let frame = self.frame.axis(axis); widgets
let resolved = hint.map(|hint| hint.within_len(frame)); .get_dyn(id.id())
.and_then(|widget| widget.size_hint(axis))
})
.map(|hint| hint.within_len(self.frame.axis(axis)));
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
{ diag::hint_read(id.id(), self.id, axis, hint);
diag::hint_read(id.id(), self.id, axis, resolved); match hint {
diag::bump(match resolved { Some(hint) => {
Some(_) => Counter::HintHits, #[cfg(feature = "layout-diagnostics")]
None => Counter::HintMisses, diag::bump(Counter::HintHits);
}); self.depend_on(id);
} // A fraction was just resolved against this frame, so what
if let Some(hint) = hint { // this draw does with it is a function of the frame's length.
self.depend_on(id); if hint.rel != Rel::ZERO {
// Resolving a fraction against this frame makes this draw a self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
// function of the frame's length. The fraction to ask about is }
// the child's own: resolved against a frame of pixels, none is Some(hint)
// left to see it by. }
if hint.rel != Rel::ZERO { None => {
self.frame_own_len[axis as usize] = Some(frame); #[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::HintMisses);
None
} }
} }
resolved
} }
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) { fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
@@ -589,20 +593,29 @@ impl Painter<'_> {
result.extent[n] = holds.extent[n]; result.extent[n] = holds.extent[n];
result.extent_len[n] = holds.extent_len[n]; result.extent_len[n] = holds.extent_len[n];
} }
// Its box is a part of this widget's own box, in that box's // Its box is this widget's own less the inset. Where that is
// own lengths, so what it holds for maps back through that // pixels, its box is exactly that many shorter in any window,
// part into a range on this widget's box. A length it pinned // so what it holds for is a range on this widget's box moved
// is this widget's length less the part's pixels where the // by them, and a length it pinned is this widget's length
// part is the whole of the box less pixels, which is the one // less them. An inset with a fraction in it is a different
// shape that inverts exactly; any other part pins this // number of pixels in each window, and taking it off a length
// widget's own length. // rounds once more than taking it off pixels does: there the
(Part::Of(span), false) => { // child's box is a fixed expression of this one, so this
let part_len = span.len(); // widget's length is pinned and the range goes on the window
result.extent[n] = holds.extent[n].through(part_len); // through the child's box, the way a slot's does.
result.extent_len[n] = holds.extent_len[n].map(|pinned| match part_len.rel { (Part::Inset { lead, trail }, false) => {
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px), let inset = lead + trail;
_ => self.extent.axis(axis).len(), 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 // Its box is a length this widget decided, from its own
// frame or from a sibling's answer: no length of this // frame or from a sibling's answer: no length of this
+8 -7
View File
@@ -12,12 +12,13 @@ pub enum Part {
/// here is a fraction of the window and not of the box -- the whole of a /// 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. /// box is [`Self::All`], not a `rel(1.0)` span.
From(UiSpan), From(UiSpan),
/// A part of the box in its own coordinates, which is what a container /// The box less a window length at each end, which is what a container
/// that insets one speaks: taking eleven pixels off the end needs no /// that insets one speaks -- padding, or a row asking a child in the
/// length, where saying the same thing in window lengths would make the /// room left from its cursor. Neither end names the box's length, so a
/// container read its own box -- and a box chosen from its own answer /// container can say "from here to my end" without reading how long it
/// then feeds back into the answer. /// is, and a box chosen from its own answer does not feed back into the
Of(UiSpan), /// answer.
Inset { lead: Len, trail: Len },
/// A box of this length, wherever in the parent's box the child's own /// 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`, /// 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 /// it is a length decided from above rather than a place along a
@@ -32,7 +33,7 @@ impl Part {
match self { match self {
Self::All => extent, Self::All => extent,
Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end), 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) => { Self::Sized(len) => {
let start = extent.start + (extent.len() - len).scale(align.rel()); let start = extent.start + (extent.len() - len).scale(align.rel());
UiSpan::new(start, start + len) UiSpan::new(start, start + len)
+4 -4
View File
@@ -20,10 +20,10 @@ impl Widget for Pad {
// The two stay distinct -- the box can be narrower still, where a row // 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. // asked this widget in the room left, and a text wraps at that.
let inset = |lead: Px, trail: Px| { let inset = |lead: Px, trail: Px| {
Place::Within(Part::Of(UiSpan::new( Place::Within(Part::Inset {
Len::from_parts(Rel::ZERO, lead), lead: Len::from_parts(Rel::ZERO, lead),
Len::from_parts(Rel::ONE, -trail), trail: Len::from_parts(Rel::ZERO, trail),
))) })
}; };
let place = [ let place = [
inset(self.padding.left, self.padding.right), inset(self.padding.left, self.padding.right),
+43 -17
View File
@@ -10,13 +10,18 @@ pub struct Span {
impl Widget for Span { impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let axis = self.dir.axis; let axis = self.dir.axis;
// The row: this span's own box, as a length of the frame its children // The room left from the cursor to the row's end, said without the
// are laid out against. Its start is nothing's business -- a slot is // row's length: a child measured in it does not make this drawing
// a length from it -- so what this reads is the length alone. // depend on how long the row is.
let far = painter.extent_len(axis); let room_from = |cursor: Len| match self.dir.sign {
let along = |from: Len, to: Len| match self.dir.sign { Sign::Pos => Part::Inset {
Sign::Pos => UiSpan::new(from, to), lead: cursor,
Sign::Neg => UiSpan::new(far - to, far - from), trail: Len::ZERO,
},
Sign::Neg => Part::Inset {
lead: Len::ZERO,
trail: cursor,
},
}; };
// Across itself the child sits where its own alignment says, in the // 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 // whole of the row: a span is what contains its children there, and
@@ -28,24 +33,28 @@ impl Widget for Span {
// given whatever else is in it and wherever this child sits among // given whatever else is in it and wherever this child sits among
// them; what a drawn child is asked in is the room left from the // them; what a drawn child is asked in is the room left from the
// cursor, because a text has to wrap at the width actually there. // cursor, because a text has to wrap at the width actually there.
// This is the one ask a drawn fixed child gets: its slot is its
// answer, and the drawing is moved there once the shares are known.
// A hinted child is asked once, in its slot.
let mut cursor = Len::rel_min(); let mut cursor = Len::rel_min();
let mut lens = Vec::with_capacity(self.children.len()); let mut lens = Vec::with_capacity(self.children.len());
let mut drawn_across = Vec::with_capacity(self.children.len()); let mut measured = Vec::with_capacity(self.children.len());
for child in &self.children { for child in &self.children {
let len = match painter.size_hint(child, axis) { let size = match painter.size_hint(child, axis) {
Some(len) => { Some(len) => {
drawn_across.push(None); measured.push(None);
len len
} }
None => { None => {
let room = Place::Within(Part::From(along(cursor, far))); let room = Place::Within(room_from(cursor));
let size = painter let size = painter
.widget_at(child, [None; 2], axis.pair(room, across)) .widget_at(child, [None; 2], axis.pair(room, across))
.size(); .size();
drawn_across.push(Some(size.axis(!axis))); measured.push(Some(size));
size.axis(axis) size.axis(axis)
} }
}; };
let len = size;
cursor.px += len.px + self.gap; cursor.px += len.px + self.gap;
cursor.rel += len.rel; cursor.rel += len.rel;
lens.push(len); lens.push(len);
@@ -62,9 +71,25 @@ impl Widget for Span {
|sum, len| sum + *len, |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 // 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. // 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)` // 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` // beside 300 px is full at 600 and overfull at 400. Asked of `room`
// itself, and answered back through the same expression, so the // itself, and answered back through the same expression, so the
@@ -100,7 +125,8 @@ impl Widget for Span {
let mut taken = Weight::ZERO; let mut taken = Weight::ZERO;
let mut start = Len::rel_min(); let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO; let mut ortho = LayoutLen::ZERO;
for ((child, &len), &across_len) in self.children.iter().zip(&lens).zip(&drawn_across) { for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
let len = *len;
// A child asking for nothing but a part of what is left over, // 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 // when nothing is, is not drawn at all. One that also asked for
// pixels or a fraction keeps those and overflows. // pixels or a fraction keeps those and overflows.
@@ -130,10 +156,10 @@ impl Widget for Span {
if len.leftover > Weight::ZERO && shares { if len.leftover > Weight::ZERO && shares {
narrow[axis as usize] = Some(slot.len()); narrow[axis as usize] = Some(slot.len());
} }
let used = match (across_len, narrow[axis as usize]) { let used = match (measured, narrow[axis as usize]) {
(Some(across_len), None) => { (Some(size), None) => {
painter.place_at(child, place); painter.place_at(child, place);
across_len size.axis(!axis)
} }
_ => painter.widget_at(child, narrow, place).len(!axis), _ => painter.widget_at(child, narrow, place).len(!axis),
}; };