WIP: Pad outside, Inset inside, and a report read raw

`Pad` moves its child's box in rather than shrinking it, `Inset` is the old
behaviour under a new name, and `in_parent_frame`'s composition and the
`reports_of` argument are gone -- a report comes up raw and the parent says
what it is a fraction of, which `Inset` does for itself.

Not landed. Everything passes except the new `Inset` test: a child declaring
`rel(0.5)` under an inset comes out 47.5 px wide of the 190 inside rather
than 95, and I have not accounted for where the second halving is. The
`Pad` half is green on its own -- the three tests that changed to `.inset()`
were using padding as scaffolding -- but landing it without a working
`Inset` would break every `.pad()` that meant inset.
This commit is contained in:
iris-ai committed 2026-09-17 04:38:53 -04:00
1 parent 1c80051d57
commit 950960cccd
4 files changed
+96 -13

No files matched your search

+60 -4
View File
@@ -9,10 +9,10 @@ impl Widget for Pad {
fn draw(&mut self, painter: &mut Painter) -> Size {
// The inner's own alignment, not the near edge. This reports the
// inner's size plus the padding, so where the box is that answer the
// inset box is exactly the inner and alignment has no room to move
// it; where the box is bigger -- a share of a row, a rule over this
// widget -- the slack is the inner's to sit in, and forcing the near
// edge pinned it to a corner it had not asked for.
// inner is exactly what it asked for and alignment has no room to
// move it; where the box is bigger -- a share of a row, a rule over
// this widget -- the slack is the inner's to sit in, and forcing the
// near edge pinned it to a corner it had not asked for.
let inner = painter
.widget_within(&self.inner, self.padding.region())
.size();
@@ -29,6 +29,45 @@ impl Widget for Pad {
}
}
/// Room taken off the inside rather than added round the outside: the child
/// draws in what is left once both edges are gone, and this widget is
/// exactly as long as the box it was given.
///
/// So `rel(1.0)` under an [`Inset`] is the room inside it, where the same
/// rule under a [`Pad`] is the pad's whole box and overflows it by the
/// padding. Both are wanted; which one a layout means is which widget it
/// reaches for.
pub struct Inset {
pub padding: Padding,
pub inner: StrongWidget,
}
impl Widget for Inset {
fn draw(&mut self, painter: &mut Painter) -> Size {
let region = self.padding.inset_region();
let inner = painter.widget_within(&self.inner, region).size();
// What a fraction the child reported is a fraction of is this
// widget's to say, and it says the room inside: the child asked for
// a part of the box it drew in, and that box is shorter than this
// one by both edges. Then the edges go back on, so this widget is
// its child and the room taken off around it.
let (x, y) = (
inner.x.within_len(region.x.len()),
inner.y.within_len(region.y.len()),
);
Size {
x: LayoutLen {
px: x.px + self.padding.left + self.padding.right,
..x
},
y: LayoutLen {
px: y.px + self.padding.top + self.padding.bottom,
..y
},
}
}
}
pub struct Padding {
pub left: Px,
pub right: Px,
@@ -53,7 +92,24 @@ impl Padding {
bottom: amt,
}
}
/// The box a [`Pad`] gives its child: as long as the pad's own, moved in
/// by the near edge. Padding is outside what it pads, so a fraction the
/// child asks for is a fraction of the same length whether a rule beside
/// it states one or it reports one, and its pixels are the same pixels.
/// Shrinking the box instead would make `rel` mean the inner box while
/// `px` meant the outer one. [`Inset`] is the widget that shrinks.
pub fn region(&self) -> UiRegion {
let mut region = UiRegion::FULL;
region.x.start.px += self.left;
region.y.start.px += self.top;
region.x.end.px += self.left;
region.y.end.px += self.top;
region
}
/// The box an [`Inset`] gives its child: shorter than its own by both
/// edges, so what the child fills is the room left inside.
pub fn inset_region(&self) -> UiRegion {
let mut region = UiRegion::FULL;
region.x.start.px += self.left;
region.y.start.px += self.top;
+10
View File
@@ -12,6 +12,16 @@ widget_trait! {
}
}
fn inset(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Inset> {
// Room taken off the inside, where `pad` adds it round the outside:
// this is as long as the box it is given and the child fills what is
// left of it.
|state| Inset {
padding: padding.into(),
inner: self.add_strong(state),
}
}
fn align(self, align: impl Into<Align>) -> impl WidgetIdFn<Rsc, WL::Widget> {
// An axis left out keeps whatever it had, which is centered unless
// something else set it.
+25 -8
View File
@@ -82,11 +82,13 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() {
assert!(crowded > whole_row, "{crowded} against {whole_row}");
}
/// The same reading through a pad: its inset is the whole box less the
/// padding, so half of the inset plus the padding is half the box plus one
/// padding, not two.
/// Padding is outside what it pads, so a fraction under one is a fraction of
/// the box the padding is measured from: half of a 400 px row is 200, and
/// the pad is that plus both edges. Inset it instead and `rel` would mean the
/// inner box while `px` meant the outer one, which is the one thing a length
/// may not do.
#[test]
fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() {
fn a_pad_is_outside_the_fraction_its_child_asked_for() {
let mut h = Harness::new((400, 100));
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc);
let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc);
@@ -95,8 +97,23 @@ fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() {
// placed inside it by its own alignment, which is not what is under test.
h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0)));
assert_corners!(h, padded, (0, 0), (210, 100));
assert_corners!(h, tail, (210, 0), (310, 100));
assert_corners!(h, padded, (0, 0), (220, 100));
assert_corners!(h, tail, (220, 0), (320, 100));
}
/// The other half of the pair: an inset takes its room off the inside, so it
/// is exactly as long as the box it was given and the fraction its child
/// asked for is a fraction of what is left inside. Half of the 380 left in a
/// 400 px row is 190, and the inset is the whole 400.
#[test]
fn an_inset_is_inside_the_fraction_its_child_asked_for() {
let mut h = Harness::new((400, 100));
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc);
let inset = (inner,).span(Dir::RIGHT).inset(10).add(&mut h.rsc);
h.set_root((inset,).span(Dir::RIGHT).width(rel(1.0)));
assert_corners!(h, inset, (0, 0), (200, 100));
assert_corners!(h, inner, (10, 0), (200, 100));
}
#[test]
@@ -237,7 +254,7 @@ fn a_moved_subtree_takes_its_children_with_it() {
let mut h = Harness::new((400, 400));
let first = rect(Color::RED).height(40).add(&mut h.rsc);
let inner = rect(Color::BLUE).add(&mut h.rsc);
let row = inner.pad(10).height(40).region_node().add(&mut h.rsc);
let row = inner.inset(10).height(40).region_node().add(&mut h.rsc);
// 80 of fixed rows in a 400 window, so the span takes 80 and sits in the
// middle of what it was given.
h.set_root((first, row).span(Dir::DOWN));
@@ -280,7 +297,7 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
// impossible to take out of: recovering a fraction of a box needs a
// relative extent, and it has none on that axis.
let inner = rect(Color::BLUE).add(&mut h.rsc);
let row = inner.pad(10).height(40).add(&mut h.rsc);
let row = inner.inset(10).height(40).add(&mut h.rsc);
let filler = rect(Color::GREEN).add(&mut h.rsc);
// This column is an item in a row, so it takes the width left for it
// rather than asking for a full row-width in addition to the bar.
+1 -1
View File
@@ -462,7 +462,7 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), true);
let padded = leaf.pad(10).add(&mut h.rsc);
let below = rect(Color::RED).add(&mut h.rsc);
h.set_root((padded, below).span(Dir::DOWN).pad(12));
h.set_root((padded, below).span(Dir::DOWN).inset(12));
assert_corners!(h, below, (12, 132), (388, 388));
h.rsc[leaf].size = Size::px((100, 200).into());