Size a widget while drawing it, not in a pass of its own

`desired_width`/`desired_height`, `WidgetAxisFns`, `SizeCtx` and the size
cache are gone. A widget states what it used with `Painter::set_size`
while it draws, and `Painter::widget` hands back a `DrawResult` whose
`size()` both reads the child and records that this widget's size depends
on it. Reading nothing keeps the parent independent of what the child came
to.

`Span` is what the change is for. It takes each child's `size_hint` where
there is one, draws only the children that cannot answer, allocates the
flexible space, then places everything -- which deletes `desired_ortho`,
whose own comment said it "literally copies draw ... which makes this slow
and not cool".

Invalidation follows the dependency edges the draw recorded: a widget that
needs redrawing hands off to the highest ancestor that read its size,
instead of re-running a measurement to find out whether anything changed.

`Widget::size_dependence(axis)` says how much of its box a widget's
drawing depends on -- none of it, its own extent, or the whole box -- so
the retained path can keep a drawing and write a new box into it. Asked
per axis, because wrapped text depends on the width it is offered and not
on the height. `Internal` does not yet buy more than `External`: keeping a
drawing when only the room around it changed is a translation, which waits
for the move chain.

`tests/retained.rs` covers the second frame rather than the first, which
is where the bugs were: a placed child that was not recorded as one got
pruned as departed on the next draw.

`examples/text.rs` is new, since wrapping was the one thing here with no
way to see it on its own.
This commit is contained in:
iris committed 2026-09-13 22:51:05 -04:00
1 parent 43ce8c7d02
commit f192f75b25
24 files changed
+610 -523

No files matched your search

+35 -18
View File
@@ -1,4 +1,4 @@
use crate::{Axis, AxisT, Len, Painter, SizeCtx};
use crate::{Axis, Len, Painter, Size};
use std::any::Any;
mod data;
@@ -15,32 +15,49 @@ pub use tag::*;
pub use view::*;
pub use widgets::*;
/// How much of the box a widget was handed its drawing depends on, and so
/// what has to change before it must be drawn again. Asked per axis, because
/// wrapped text depends on the width it is offered and not on the height.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SizeDependence {
/// None of it: the box only says where the primitives go, so a new one is
/// written into them instead of drawn.
None,
/// Its own extent, whatever box that sits in. Reusable in any box that
/// leaves that extent unchanged, including a larger one it does not fill.
#[default]
Internal,
/// The extent of the box itself, used or not.
External,
}
pub trait Widget: Any {
/// Draws the widget, and states what it used with `Painter::set_size`.
fn draw(&mut self, painter: &mut Painter);
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len;
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len;
}
pub trait WidgetAxisFns {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len;
}
/// An exact length the widget can give without a painter or its children.
/// Optional, and saves a draw rather than changing one: a hint that
/// disagrees with the eventual draw fails a debug assertion.
fn size_hint(&self, _axis: Axis) -> Option<Len> {
None
}
impl<W: Widget + ?Sized> WidgetAxisFns for W {
fn desired_len<A: AxisT>(&mut self, ctx: &mut SizeCtx) -> Len {
match A::get() {
Axis::X => self.desired_width(ctx),
Axis::Y => self.desired_height(ctx),
}
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
SizeDependence::Internal
}
}
impl Widget for () {
fn draw(&mut self, _: &mut Painter) {}
fn desired_width(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
fn draw(&mut self, painter: &mut Painter) {
painter.set_size(Size::ZERO);
}
fn desired_height(&mut self, _: &mut SizeCtx) -> Len {
Len::ZERO
fn size_hint(&self, _axis: Axis) -> Option<Len> {
Some(Len::ZERO)
}
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
SizeDependence::None
}
}