Hold what a widget answers with a rule, and its box with a widget
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>
This commit is contained in:
1 parent
76aaf06c0b
commit
de1eb7e406
7 files changed
+183
-141
No files matched your search
+13
-27
@@ -1,9 +1,9 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Bound, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2,
|
||||
RegionAlign, Rel, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
Axis, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign,
|
||||
Rel, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -272,13 +272,7 @@ impl<'a> Painter<'a> {
|
||||
let states_rel_base = Axis::BOTH
|
||||
.iter()
|
||||
.any(|&axis| matches!(place[axis].rel_base, RelBase::Len(_)));
|
||||
// A bound is decided against the box the widget is given, so a box
|
||||
// decided here is a question rather than a move: putting the drawing
|
||||
// in it would keep a decision made about the box it was measured in.
|
||||
let bounded = Axis::BOTH
|
||||
.iter()
|
||||
.any(|&axis| self.rsc.widgets().size_rules(id.id())[axis].bound() != Bound::ANY);
|
||||
if states_rel_base || bounded || !self.children.contains(&id.id()) {
|
||||
if states_rel_base || !self.children.contains(&id.id()) {
|
||||
return self.widget_at(id, place);
|
||||
}
|
||||
let at = self.placing();
|
||||
@@ -732,7 +726,7 @@ impl Placing {
|
||||
let align = widgets.alignment(id);
|
||||
let rules = widgets.size_rules(id);
|
||||
let mut holds = LayoutHolds::ANY;
|
||||
let mut declared = widgets.declared_lens(id);
|
||||
let declared = widgets.declared_lens(id);
|
||||
let mut bounds = Bounds::ANY;
|
||||
for axis in Axis::BOTH {
|
||||
let base = place.base(axis, self.rel_base);
|
||||
@@ -747,22 +741,14 @@ impl Placing {
|
||||
if let Some(len) = share {
|
||||
place[axis] = len.as_desc().fills();
|
||||
}
|
||||
// A bound the box falls outside is what the widget's length is
|
||||
// instead, which is a declaration: the box comes to the bound,
|
||||
// and its own answer is held to the same bound where it drew
|
||||
// past that. Asked of the box it would otherwise have -- what it
|
||||
// declares of the place, or what the place gives it.
|
||||
let bound = rules[axis].bound();
|
||||
bounds[axis] = bound.within_len(base);
|
||||
let offered = declared[axis].map_or_else(
|
||||
|| place.of(self.region, align)[axis].len(),
|
||||
|len| len.within_len(base),
|
||||
);
|
||||
let (outside, kept) = bounds[axis].outside(offered, window[axis]);
|
||||
holds[axis].window = holds[axis].window.and(kept);
|
||||
if let Some(outside) = outside {
|
||||
declared[axis] = Some(bound.at(outside));
|
||||
}
|
||||
// A bound holds what the widget answers, not the box it is asked
|
||||
// in: the box it is given is whoever asked's to decide, and a
|
||||
// rule that read it would be decided again by every path that
|
||||
// hands the widget a box -- including the ones that never ask it
|
||||
// anything. Resolved here because only the ask knows the rel base
|
||||
// a fraction in it is of. `MaxSize` is the box version, and it is
|
||||
// a widget because a widget is drawn again when its box changes.
|
||||
bounds[axis] = rules[axis].bound().within_len(base);
|
||||
}
|
||||
let (rel_base, region) =
|
||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||
|
||||
Reference in new issue
Block a user