Let a rule beat a hint, and name marking a widget for redraw
A ninth sweep, over the part no earlier round named -- the widget vocabulary and the builder methods, `Widgets`, the examples, the `util` additions and the manifests -- and once more over `77ed7a2`, the eighth sweep's own commit and so itself unreviewed. A hint overrode a rule. `declared_lens` asked `rules[axis].declared()` first and fell through to the widget's own `size_hint` whenever that answered `None` -- which it does for a share, since a share is not a declaration. So a widget carrying `width(leftover(1))` and hinting a pixel length of its own was handed a box of the hint, against the rule and against the comment inside the function: "a hint still narrows the box where no rule does". `Painter::size_hint` spells the same rule-else-hint step three hundred lines up and gets it right, with the reason written on it; both read `Widgets::exact_len` now, and `declared_lens` is the part of its answer that needs nobody to divide it. `Image` is the only widget here whose hint is a declared length, and neither the tests nor the generator builds one, so nothing in this repository could reach the difference -- which is why the dump is unchanged and why the test builds a widget of its own. It records the box it was asked in: 400 with the rule and 50 without, and 50 either way before this. Marking a widget for redraw had no name. Twenty-one sites under `tests/` said it as `widgets_mut().get_dyn_mut(id);` with the widget thrown away, five with a `let _ =` in front, one with a comment explaining what the line was for, and one wrapped in a local function called `mark`. `Widgets::mark_for_redraw` says it. `revision_cost.rs` keeps the long spelling and now says why in place: it is deliberately in the API subset an old worktree also has. `assert_same_regions` could not see the defect the eighth sweep had just fixed. It zips the warm and cold id lists, so a list naming one widget twice -- which is what `width`, `sized` and `align` giving back their own argument produces -- compares fewer boxes than it lists and says nothing about it. It now rejects a repeated id and two lists of different lengths, which also checks the nine fixtures that round left alone: all eighteen cases pass. Bare pairs where the framework has named ones. `random.rs`'s `Lens` and `Aligns` were `[Option<LayoutLen>; 2]` and `[Option<AxisAlign>; 2]`, read as `[0]`/`[1]` and zipped against a hand-written `[Axis::X, Axis::Y]`. They are `SizeRules` and `Align`; `Align` took the `Index<Axis>` every other per-axis pair on this branch has, and `RegionAlign::from` does the "an axis left out is centred" step two rigs were spelling per axis. The three sites that wrote the axis pair out say `Axis::BOTH`, which is what the rest of the layout code says. `BothAxis<T>`, `AxisT`, `XAxis` and `YAxis` -- 45 lines with a const trait, two marker types and three accessors -- have no user anywhere in the workspace. They are the mechanism `impl_axis_index!` replaced, in the file this branch took `Vec2::axis`/`axis_mut` out of. Deleted, which is a drive-by in a block the branch was already rewriting; drop it if the scope matters more. Smaller things, each in its own place: `Wrapper` arrived beside core's `WidgetWrapper`, one word for a widget that wraps a child and for a dynamic borrow guard, so the alias is gone and its two uses name `DynBorrower` -- which is what they are. `Wrapper::new`, `Wrapper::empty` and its `Default` were three names for one value, two of them unused. `Arena::get_mut` was the only `pub(crate)` among `pub` siblings on a public type. `Selector` rounded the pointer onto the pixel grid to do arithmetic on two values already there, losing the precision the platform gave it for nothing; the step between the regions is taken on the grid instead. And the two `debug` profile settings carry their reason where the next reader looks rather than only in the commit that made them, one of which was about renaming `rest`. Format, clippy with and without layout-diagnostics, and the suite (132 + 19 + 13 + 4) are clean. The cold dump over 400 depth-5 trees is byte-identical to `77ed7a2` across all 34,488 boxes, and all three seed scans pass: 400 at depth 5 in 63.27s, 1,000 at depth 6 in 160.45s, 2,000 at depth 4 in 302.52s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
77ed7a24c0
commit
c2b8bf83de
21 files changed
+190
-152
No files matched your search
+22
-26
@@ -291,8 +291,8 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// What a rule or a hint declares a widget's lengths to be, which whoever
|
||||
/// draws it resolves into its rel base. Reading them depends on nothing -- the box
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
|
||||
@@ -304,14 +304,7 @@ impl<'a> Painter<'a> {
|
||||
/// against this widget's rel base, which is the rel base a child asked with
|
||||
/// nothing narrowed gets. Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||
let widgets = self.rsc.widgets();
|
||||
// A rule is the answer where there is one: it wins over whatever the
|
||||
// widget would draw, so it has to win over what the widget says too.
|
||||
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
});
|
||||
let hint = self.rsc.widgets().exact_len(id.id(), axis);
|
||||
let rel_base = self.rel_base[axis];
|
||||
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -647,24 +640,27 @@ impl Painter<'_> {
|
||||
}
|
||||
|
||||
impl Widgets {
|
||||
/// What a widget's box is where a rule or its own hint says so outright.
|
||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||
let rules = self.size_rules(id);
|
||||
let widget = self.get_dyn(id);
|
||||
Declared::from_axes(|axis| {
|
||||
rules[axis].declared().or_else(|| {
|
||||
// A hint still narrows the box where no rule does, which is
|
||||
// how a widget with a natural pixel size -- an image, a gap
|
||||
// -- gets that size rather than the whole offer. That is the
|
||||
// offer's business rather than a declaration's, and this
|
||||
// falls away once a widget occupies its reported size inside
|
||||
// the box it was offered.
|
||||
widget
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
.and_then(|len| len.declared())
|
||||
})
|
||||
/// What says a widget's length on one axis without drawing it, if anything
|
||||
/// does. A rule is the answer where there is one: it wins over whatever the
|
||||
/// widget would draw, so it has to win over what the widget says too -- a
|
||||
/// share included, since a share is a length only to whoever divides one,
|
||||
/// and that is the parent rather than this widget.
|
||||
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
|
||||
self.size_rules(id)[axis].exact().or_else(|| {
|
||||
// A hint still narrows the box where no rule does, which is how a
|
||||
// widget with a natural pixel size -- an image, a gap -- gets that
|
||||
// size rather than the whole offer. That is the offer's business
|
||||
// rather than a declaration's, and this falls away once a widget
|
||||
// occupies its reported size inside the box it was offered.
|
||||
self.get_dyn(id)?.size_hint(axis)
|
||||
})
|
||||
}
|
||||
|
||||
/// What a widget's box is where a rule or its own hint gives one outright,
|
||||
/// rather than a share for whoever draws it to divide.
|
||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||
Declared::from_axes(|axis| self.exact_len(id, axis)?.declared())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutLen {
|
||||
|
||||
Reference in new issue
Block a user