Resolve a declared length where the widget is drawn, not inside it
SetSize took its declared length out of UiRegion::FULL, which is the box it was already given, so a span that had sized that box from the same hint had the fraction taken twice: .width(rel(0.5)) in a 400-wide span drew its child 100 wide. It held under a Pad or the root, which do not honour a hint, and hid under px, where 200 of a 200-wide box is all of it. The text example was 111,923 pixels from upstream/main because of it. Whoever draws a widget now takes its declared length, in its own box, which is what a fraction of one means, and is the identity for a caller that already reserved the space. rest is not taken: a share of what is left over is only a length to the widget dividing one, so it passes up in the size as it does out of a span. SetSize keeps only what it declares. A declared length is then part of the box its parent decided, so changing one has to redraw the parent; the lengths resolved into a box are kept beside it and compared. Assuming instead that any dirty widget which declares a length needs its parent costs 17% of a frame that dirties 130 of 260 widgets, and buys nothing. All five reference renders, the resize render and the image replay are byte-identical to upstream/main, the 100-seed sweep passes, and the resize fixture is 1.286 ms against 1.289 before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
169db7f16f
commit
de9ddc0ad4
4 files changed
+83
-22
No files matched your search
@@ -1,5 +1,6 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::declared_len;
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
||||
Size, StrongWidget, UiRegion, UiRsc, UiScalar, UiSpan, WidgetId, Widgets,
|
||||
@@ -307,6 +308,8 @@ impl UiRenderState {
|
||||
size_deps,
|
||||
size_box_inputs,
|
||||
size_output_inputs,
|
||||
// Written by whoever draws it, which is what resolves them.
|
||||
declared: [None; 2],
|
||||
output_px: self.output_size,
|
||||
move_idx,
|
||||
parent_move,
|
||||
@@ -753,11 +756,28 @@ impl UiRenderState {
|
||||
AXES.into_iter()
|
||||
.any(|axis| pixel_len_changed(active.px.axis(axis), px.axis(axis)))
|
||||
});
|
||||
// A declared length is resolved into this widget's box by whoever
|
||||
// drew it, so a change to one moves a box this widget cannot fix by
|
||||
// drawing again, however its own size comes out. Compared rather
|
||||
// than assumed: a widget dirtied for any other reason declares what
|
||||
// it declared before, and redrawing its parent for that costs 17%.
|
||||
let declared_changed = self.active.get(&id).is_some_and(|active| {
|
||||
rsc.widgets().get_dyn(id).is_some_and(|widget| {
|
||||
AXES.into_iter()
|
||||
.zip(active.declared)
|
||||
.any(|(axis, was)| declared_len(widget, axis) != was)
|
||||
})
|
||||
});
|
||||
let top = match box_changed {
|
||||
true => self.top_reader(id),
|
||||
false => None,
|
||||
}
|
||||
.or_else(|| self.derived_box_reader(id));
|
||||
.or_else(|| self.derived_box_reader(id))
|
||||
.or_else(|| {
|
||||
declared_changed
|
||||
.then(|| self.active.get(&id).and_then(|active| active.parent))
|
||||
.flatten()
|
||||
});
|
||||
if let Some(top) = top {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::EagerReaderRedraws);
|
||||
|
||||
Reference in new issue
Block a user