Files
iris/src/widget/position/pad.rs
T
iris-aiandClaude Opus 5 1512d8418b Make the frame a length of the window and the box a region
There is one coordinate unit, the window. Every box in the tree is a
region in window units and a widget's frame is a length in the same
units, which is only what fractions resolve against, so the box need not
be the frame and padding can take from both without either becoming the
other. A region node's entry is a translation -- a rel 1 region anchored
where its box starts -- rather than a box, so nothing composes a frame
back up a chain and a node that moves is one entry write.

Padding is then an inset of both: its pixels come off the frame, so
rel(1.0) under it fills the padded widget rather than overflowing it,
and off the box, so what is drawn sits inside. A length a container
decides for a child's frame is a length of the window like everything
else here -- a row's slot, padding's frame less its pixels, or the box a
stack's sizing child decided, which arrives as Part::Sized -- because a
slot of a row is not a fraction of anything the row can name, the same
reason a node entry is a translation. A declaration is a fraction of
whichever of those reached it, and is the only one that also places the
box.

Frame validity is a pin beside the box's, not a range: a range of window
pixels cannot say which frame an answer is a fraction of, since two
frames are different lengths at the same window size. A widget pins its
frame by reading it or by being answered with it under a fractional
rule, and the pin composes up wherever a length of this frame is what
reached the child.

Also here, because the diagnosis needed them: the shrinker reports the
shrunk tree's own divergence with each level's frame, ask, box and size
warm against cold, and there is a size-resize case -- a change and then a
resize, the order that shows an answer kept as a fraction of the wrong
length, which every other case compares at the window it was made at.

Three defects the reports found, each pinned: a rule changed over two
pads relocated the column under them instead of dividing it again (seed
59, depth 5, resize-size), a share inside padding had the padding taken
off twice, and a root resolved its own rule twice.

fmt and clippy clean with and without layout-diagnostics, 121 suite, 20
core, 11 generated, the 400-seed depth-5 shrinker over all sixteen cases.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 00:14:46 -04:00

158 lines
4.5 KiB
Rust

use crate::prelude::*;
pub struct Pad {
pub padding: Padding,
pub inner: StrongWidget,
}
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.
//
// Padding is an inset of both: it comes off the frame, so `rel(1)`
// under it fills this widget rather than overflowing it by the
// padding, and it comes off the box, so what is drawn sits inside.
// 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),
)))
};
let place = [
inset(self.padding.left, self.padding.right),
inset(self.padding.top, self.padding.bottom),
];
// Read from this widget's own frame rather than written as a
// fraction of it: a frame is a length of the window like everything
// else here, and taking the padding off is the whole of what this
// widget does to it.
let narrow = [
(Axis::X, self.padding.left + self.padding.right),
(Axis::Y, self.padding.top + self.padding.bottom),
]
.map(|(axis, pixels)| Some(painter.frame_len(axis) - Len::from_parts(Rel::ZERO, pixels)));
let inner = painter.widget_at(&self.inner, narrow, place).size();
Size {
x: LayoutLen {
px: inner.x.px + self.padding.left + self.padding.right,
..inner.x
},
y: LayoutLen {
px: inner.y.px + self.padding.top + self.padding.bottom,
..inner.y
},
}
}
}
pub struct Padding {
pub left: Px,
pub right: Px,
pub top: Px,
pub bottom: Px,
}
impl Padding {
pub const ZERO: Self = Self {
left: Px::ZERO,
right: Px::ZERO,
top: Px::ZERO,
bottom: Px::ZERO,
};
pub fn uniform(amt: impl UiNum) -> Self {
let amt = Px::from_num(amt);
Self {
left: amt,
right: amt,
top: amt,
bottom: amt,
}
}
/// `region` less this padding on each side.
pub fn region_of(&self, mut region: UiRegion) -> UiRegion {
region.x.start.px += self.left;
region.y.start.px += self.top;
region.x.end.px -= self.right;
region.y.end.px -= self.bottom;
region
}
pub fn region(&self) -> UiRegion {
self.region_of(UiRegion::FULL)
}
pub fn x(amt: impl UiNum) -> Self {
let amt = Px::from_num(amt);
Self {
left: amt,
right: amt,
..Self::ZERO
}
}
pub fn y(amt: impl UiNum) -> Self {
let amt = Px::from_num(amt);
Self {
top: amt,
bottom: amt,
..Self::ZERO
}
}
pub fn top(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.top = Px::from_num(amt);
s
}
pub fn bottom(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.bottom = Px::from_num(amt);
s
}
pub fn left(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.left = Px::from_num(amt);
s
}
pub fn right(amt: impl UiNum) -> Self {
let mut s = Self::ZERO;
s.right = Px::from_num(amt);
s
}
pub fn with_top(mut self, amt: impl UiNum) -> Self {
self.top = Px::from_num(amt);
self
}
pub fn with_bottom(mut self, amt: impl UiNum) -> Self {
self.bottom = Px::from_num(amt);
self
}
pub fn with_left(mut self, amt: impl UiNum) -> Self {
self.left = Px::from_num(amt);
self
}
pub fn with_right(mut self, amt: impl UiNum) -> Self {
self.right = Px::from_num(amt);
self
}
}
impl<T: UiNum> From<T> for Padding {
fn from(amt: T) -> Self {
Self::uniform(amt)
}
}