Carry a length as a rule beside a widget, not a widget around it
`.width()` built a `SetSize` whose whole job was to answer `size_hint`, so every declared length cost a widget, an `ActiveData` and a link of chain to say one number. It is now a `SizeRule` per axis on `WidgetData`, beside `region_node`, resolved by `Painter` where the widget is drawn. `SetSize` and `MaxSize` are gone; `MaxSize` had no caller but its own builders. That settles which of two answers is the size. A rule wins on the axis it names and the `Size` returned by `draw` answers the rest, applied once in `draw_inner` rather than by each widget that could carry one -- so the widget under a rule never learns of it. `Painter::size_hint` reads the rule first for the same reason: a rule that beats what a widget would draw has to beat what it says about itself. `declared_lens` still falls back to a non-leftover `size_hint`, which is how an image or a gap gets its own pixel size rather than the whole offer. That is the offer's business rather than a declaration's, and it falls away when a widget occupies its reported size inside the box it was offered. `known` and `declared` are separate because a share is a length to whoever divides one and not to whoever composes a box: `.width(leftover(3))` is known without drawing but cannot narrow anything. Checked: fmt, clippy, 85 tests, and 100 generated seeds agreeing warm against cold in 67.6 s. `minimal`, `text` and `view` render byte-identical at 1920x1200; `tabs` differs only in the widget count it prints about itself, which is two wrapper types smaller.
This commit is contained in:
1 parent
0283c9d6c7
commit
8220a78d4a
21 files changed
+252
-214
No files matched your search
+29
-17
@@ -2,7 +2,7 @@
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Holds, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||
PrimitiveKind, TexturePrimitive,
|
||||
@@ -111,16 +111,12 @@ impl<'a> Painter<'a> {
|
||||
self.widget_at(id, region)
|
||||
}
|
||||
|
||||
/// What a widget declares its lengths to be, which whoever draws it
|
||||
/// resolves into its box. `leftover` is not among them: a part of what is
|
||||
/// left over is only a length to the widget dividing one, so it passes
|
||||
/// up in the size instead. Reading it depends on nothing -- the box that
|
||||
/// comes of it is kept on the child, and `redraw` compares it there.
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its box. 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>) -> [Option<Len>; 2] {
|
||||
let Some(widget) = self.rsc.widgets().get_dyn(id.id()) else {
|
||||
return [None; 2];
|
||||
};
|
||||
AXES.map(|axis| declared_len(widget, axis))
|
||||
declared_lens(self.rsc.widgets(), id.id())
|
||||
}
|
||||
|
||||
/// Takes back a child that was drawn only to find out how long it is.
|
||||
@@ -201,11 +197,14 @@ impl<'a> Painter<'a> {
|
||||
/// What a child says its length is without being drawn, if it can say.
|
||||
/// Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||
let hint = self
|
||||
.rsc
|
||||
.widgets()
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis));
|
||||
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(axis).known().or_else(|| {
|
||||
widgets
|
||||
.get_dyn(id.id())
|
||||
.and_then(|widget| widget.size_hint(axis))
|
||||
});
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::hint_read(id.id(), self.id, axis, hint);
|
||||
match hint {
|
||||
@@ -439,8 +438,21 @@ impl PrimitiveLike for &TextureHandle {
|
||||
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
||||
/// share of what is left over is only a length to the widget dividing one,
|
||||
/// so it passes up in the size instead.
|
||||
pub(crate) fn declared_len(widget: &dyn Widget, axis: Axis) -> Option<Len> {
|
||||
widget.size_hint(axis).filter(|len| len.leftover == 0.0)
|
||||
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<Len>; 2] {
|
||||
let rules = widgets.size_rules(id);
|
||||
let widget = widgets.get_dyn(id);
|
||||
AXES.map(|axis| {
|
||||
rules.axis(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))
|
||||
.filter(|len| len.leftover == 0.0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Takes a widget's declared lengths in the box `region` is given in, since a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::declared_len;
|
||||
use crate::ui::painter::declared_lens;
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, Holds, IdLike, Len, MaskIdx, MoveIdx, Moves, Painter,
|
||||
PixelRegion, Size, StrongWidget, UiRegion, UiRsc, UiScalar, UiSpan, WidgetId, Widgets,
|
||||
@@ -256,6 +256,14 @@ impl UiRenderState {
|
||||
"'{}' ({id:?}) drew a size its size_hint disagrees with",
|
||||
rsc.widgets().label(id)
|
||||
);
|
||||
// A rule wins on the axis it names, and the draw answers the rest.
|
||||
// Applied here so it is one place rather than every widget that could
|
||||
// carry one, and so the widget under a rule never learns of it.
|
||||
let rules = rsc.widgets().size_rules(id);
|
||||
let size = Size {
|
||||
x: rules.x.apply(size.x),
|
||||
y: rules.y.apply(size.y),
|
||||
};
|
||||
let holds = [own[0].and(under[0]), own[1].and(under[1])];
|
||||
debug_assert!(
|
||||
holds[0].contains(px.x) && holds[1].contains(px.y),
|
||||
@@ -739,11 +747,7 @@ impl UiRenderState {
|
||||
// whether to draw it at all, so a change to either is the parent's
|
||||
// to draw -- with the mark left on, so the parent draws it rather
|
||||
// than keeping it.
|
||||
let declared_changed = rsc.widgets().get_dyn(id).is_some_and(|widget| {
|
||||
AXES.into_iter()
|
||||
.zip(active.declared)
|
||||
.any(|(axis, was)| declared_len(widget, axis) != was)
|
||||
});
|
||||
let declared_changed = declared_lens(rsc.widgets(), id) != active.declared;
|
||||
if let Some(parent) = active.parent
|
||||
&& (declared_changed || !active.drawn)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user