From 950960cccd8b6a7a0cc48bc09c2c55788a275121 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Thu, 17 Sep 2026 04:38:53 -0400 Subject: [PATCH] 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. --- src/widget/position/pad.rs | 64 +++++++++++++++++++++++++++++++++++--- src/widget/trait_fns.rs | 10 ++++++ tests/cases/layout.rs | 33 +++++++++++++++----- tests/cases/retained.rs | 2 +- 4 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index 7141a4d..3258642 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -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; diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index bf7efad..87ae6b7 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -12,6 +12,16 @@ widget_trait! { } } + fn inset(self, padding: impl Into) -> impl WidgetFn { + // 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) -> impl WidgetIdFn { // An axis left out keeps whatever it had, which is centered unless // something else set it. diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index acd7e31..96235f7 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -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. diff --git a/tests/cases/retained.rs b/tests/cases/retained.rs index 06dcd72..cee9399 100644 --- a/tests/cases/retained.rs +++ b/tests/cases/retained.rs @@ -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());