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:
1 parent
43ce8c7d02
commit
f192f75b25
24 files changed
+610
-523
No files matched your search
+31
-100
@@ -9,12 +9,26 @@ pub struct Span {
|
||||
|
||||
impl Widget for Span {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
let total = self.len_sum(&mut painter.size_ctx());
|
||||
let axis = self.dir.axis;
|
||||
// A length for every child before any is placed: from its own hint
|
||||
// where it has one, and from drawing it where it does not.
|
||||
let lens: Vec<Len> = self
|
||||
.children
|
||||
.iter()
|
||||
.map(|child| match painter.size_hint(child, axis) {
|
||||
Some(len) => len,
|
||||
None => painter.widget(child).len(axis),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
||||
let total = lens.iter().fold(Len::abs(gap), |sum, len| sum + *len);
|
||||
|
||||
let mut start = UiScalar::rel_min();
|
||||
for child in &self.children {
|
||||
let mut ortho = Len::ZERO;
|
||||
for (child, len) in self.children.iter().zip(&lens) {
|
||||
let mut span = UiSpan::FULL;
|
||||
span.start = start;
|
||||
let len = painter.len_axis(child, self.dir.axis);
|
||||
if len.rest > 0.0 {
|
||||
let offset = UiScalar::new(total.rel, total.abs);
|
||||
let rel_end = UiScalar::rel(len.rest / total.rest);
|
||||
@@ -24,27 +38,25 @@ impl Widget for Span {
|
||||
start.abs += len.abs;
|
||||
start.rel += len.rel;
|
||||
span.end = start;
|
||||
let mut child_region = UiRegion::from_axis(self.dir.axis, span, UiSpan::FULL);
|
||||
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||
if self.dir.sign == Sign::Neg {
|
||||
child_region.flip(self.dir.axis);
|
||||
region.flip(axis);
|
||||
}
|
||||
let used = painter.place(child, region).axis(!axis);
|
||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||
if used.rel > 0.0 || used.rest > 0.0 {
|
||||
ortho = Len::REST;
|
||||
} else if ortho.rest == 0.0 {
|
||||
ortho.abs = ortho.abs.max(used.abs);
|
||||
}
|
||||
painter.widget_within(child, child_region);
|
||||
start.abs += self.gap;
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
match self.dir.axis {
|
||||
Axis::X => self.desired_len(ctx),
|
||||
Axis::Y => self.desired_ortho(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
match self.dir.axis {
|
||||
Axis::X => self.desired_ortho(ctx),
|
||||
Axis::Y => self.desired_len(ctx),
|
||||
}
|
||||
let along = match total.rest == 0.0 && total.rel == 0.0 {
|
||||
true => total,
|
||||
false => Len::default(),
|
||||
};
|
||||
painter.set_size(Size::from_axis(axis, along, ortho));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,87 +81,6 @@ impl Span {
|
||||
pub fn pop(&mut self) -> Option<StrongWidget> {
|
||||
self.children.pop()
|
||||
}
|
||||
|
||||
fn len_sum(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
||||
self.children.iter().fold(Len::abs(gap), |mut s, id| {
|
||||
// it's tempting to subtract the abs & rel from the ctx outer,
|
||||
// but that would create inconsistent sizing if you put
|
||||
// a rest first vs last & only speed up in one direction.
|
||||
// I think this is only solvable by restricting how you can
|
||||
// compute size, bc currently you need child to define parent's
|
||||
// sectioning and you need parent's sectioning to define child.
|
||||
// Fortunately, that doesn't matter in most cases
|
||||
let len = ctx.len_axis(id, self.dir.axis);
|
||||
s += len;
|
||||
s
|
||||
})
|
||||
}
|
||||
|
||||
fn desired_len(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
let len = self.len_sum(ctx);
|
||||
if len.rest == 0.0 && len.rel == 0.0 {
|
||||
len
|
||||
} else {
|
||||
Len::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn desired_ortho(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||
// this is a weird hack to get text wrapping to work properly when in a downward span
|
||||
// the correct solution here is to add a function to widget that lets them
|
||||
// request that ctx.outer has an axis "resolved" before checking the other,
|
||||
// and panicking or warning if two request opposite axis (unsolvable in that case)
|
||||
let outer = ctx.outer.axis(self.dir.axis);
|
||||
if self.dir.axis == Axis::X {
|
||||
// so....... this literally copies draw so that the lengths are correctly set in the
|
||||
// context, which makes this slow and not cool
|
||||
let total = self.len_sum(ctx);
|
||||
let mut start = UiScalar::rel_min();
|
||||
let mut ortho_len = Len::ZERO;
|
||||
for child in &self.children {
|
||||
let mut span = UiSpan::FULL;
|
||||
span.start = start;
|
||||
let len = ctx.len_axis(child, self.dir.axis);
|
||||
if len.rest > 0.0 {
|
||||
let offset = UiScalar::new(total.rel, total.abs);
|
||||
let rel_end = UiScalar::rel(len.rest / total.rest);
|
||||
let end = (UiScalar::rel_max() + start) - offset;
|
||||
start = rel_end.within(&start.to(end));
|
||||
}
|
||||
start.abs += len.abs;
|
||||
start.rel += len.rel;
|
||||
span.end = start;
|
||||
|
||||
let scalar = span.len();
|
||||
*ctx.outer.axis_mut(self.dir.axis) = outer.select_len(scalar);
|
||||
let ortho = ctx.len_axis(child, !self.dir.axis);
|
||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||
if ortho.rel > 0.0 || ortho.rest > 0.0 {
|
||||
ortho_len.rest = 1.0;
|
||||
ortho_len.abs = 0.0;
|
||||
break;
|
||||
}
|
||||
ortho_len.abs = ortho_len.abs.max(ortho.abs);
|
||||
start.abs += self.gap;
|
||||
}
|
||||
ortho_len
|
||||
} else {
|
||||
let mut ortho_len = Len::ZERO;
|
||||
let ortho = !self.dir.axis;
|
||||
for child in &self.children {
|
||||
let len = ctx.len_axis(child, ortho);
|
||||
// TODO: rel shouldn't do this, but no easy way before actually calculating pixels
|
||||
if len.rel > 0.0 || len.rest > 0.0 {
|
||||
ortho_len.rest = 1.0;
|
||||
ortho_len.abs = 0.0;
|
||||
break;
|
||||
}
|
||||
ortho_len.abs = ortho_len.abs.max(len.abs);
|
||||
}
|
||||
ortho_len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> {
|
||||
|
||||
Reference in new issue
Block a user