Bryan's call, given the measurements in `76aaf06`: `SizeRule::{Min, Max,
Clamp}` holds the length a widget answers and never touches the box it draws
in, and `MaxSize` is the box version.
The split is the difference between a rule and a widget here. A box is
whoever asked's to decide, and the retained machinery hands a widget one by
paths that never ask it anything -- a parent re-placing a child, a subtree
repositioned after its parent's box moved. A rule that read the box was
therefore decided again by whichever path arrived last, which is what the
oracle was refusing. A widget has no such trouble: it is drawn again whenever
its own box changes, so `MaxSize` asks `longer_than` where the answer can be
kept, and `region_len` pins the box lengths its drawing holds for.
What that costs is nothing the app wanted: `a_capped_scroll_takes_its_
viewport_from_the_cap` puts 400 px of content under `.max_height(100)` and
gets a 100 px viewport with 300 to scroll, which is what `MaxSize` gave on the
app's pin, and `.max_width`/`.max_height` are that widget rather than a rule.
A cap narrows the offer and not a declared length, so a child that declares
500 px still draws 500 and the cap holds what `MaxSize` itself answers; a
child that asked for a share takes the box the cap allows and the share passes
up, since whoever divides one is `MaxSize`'s parent.
`.min_width`/`.min_height` stay a rule: answering at least so much is a claim
about the length, and a row honours it without anyone narrowing anything.
Bounds in the generated trees are pixels for now, with the reason written
where the next tree is grown: a fraction in a bound is resolved against the
rel base the widget was asked with, and `place_at` hands a parent a retained
answer without checking that it still holds for the rel base this place
gives. Seeds 4 and 196 at depth 5 are where that showed. The hole is older
than bounds -- an `Exact` rule that is a fraction can reach it too -- and
closing it is a check at the re-place site rather than anything about bounds.
A fraction through `MaxSize` is fine and tested, since the widget compares
against its own box.
Format, clippy with and without layout-diagnostics, and the suite (142 + 19 +
13 + 4) are clean. All three seed scans pass: 400 at depth 5 (62s), 1,000 at
depth 6 (162s), 2,000 at depth 4 (299s). The cold dump is 34,986 boxes and
moves wholesale against `2dba90b`, which is the generator growing rules it
did not grow before rather than a layout change; it is the new baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
2.3 KiB
Rust
61 lines
2.3 KiB
Rust
use crate::prelude::*;
|
|
|
|
/// Asks its child in the shorter of a cap and the box this widget was given,
|
|
/// and answers what the child used, held to the same cap.
|
|
///
|
|
/// A cap on the box is a widget rather than a [`SizeRule`] because a box is
|
|
/// whoever asked's to decide: a rule that read the box it was given would be
|
|
/// decided again by every path that hands a widget one, including the ones
|
|
/// that re-place a drawing without asking it anything, and the decision would
|
|
/// then depend on which path arrived last. A widget is drawn again whenever
|
|
/// its own box changes, so the comparison is made where the answer can be
|
|
/// kept -- `longer_than` narrows the windows this drawing holds for, and
|
|
/// `holds` says the box lengths.
|
|
///
|
|
/// The box is what a text wraps at and what a scroll takes its viewport from,
|
|
/// which is why capping the answer alone is not the same thing.
|
|
pub struct MaxSize {
|
|
pub inner: StrongWidget,
|
|
pub x: Option<Len>,
|
|
pub y: Option<Len>,
|
|
}
|
|
|
|
impl MaxSize {
|
|
fn max(&self, axis: Axis) -> Option<Len> {
|
|
match axis {
|
|
Axis::X => self.x,
|
|
Axis::Y => self.y,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Widget for MaxSize {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
let align = painter.alignment();
|
|
let mut region = UiRegion::FULL;
|
|
for axis in Axis::BOTH {
|
|
let Some(max) = self.max(axis) else {
|
|
continue;
|
|
};
|
|
let own = painter.region_len(axis);
|
|
if painter.longer_than(own, max, axis) {
|
|
region[axis] = max.align(align[axis]);
|
|
}
|
|
}
|
|
let mut size = painter.widget_at(&self.inner, region).size();
|
|
for axis in Axis::BOTH {
|
|
// The child may draw past the box it was given -- a text too tall
|
|
// for it -- and the cap is a promise about the length as well. A
|
|
// share passes through: it is a length only to whoever divides
|
|
// one, and that is this widget's parent rather than this widget,
|
|
// which has already given the share the box the cap allows.
|
|
if let Some(max) = self.max(axis)
|
|
&& painter.longer_than(size[axis].without_leftover(), max, axis)
|
|
{
|
|
size[axis] = max.into();
|
|
}
|
|
}
|
|
size
|
|
}
|
|
}
|