Fixed point cost 3x in layout: `many` went from 0.179 ms a frame to 0.544, and `scroll` from 0.011 to 0.030. The counters said why -- eight more "placed by redrawing" a frame -- and the reason was mine rather than the grid's. A retained drawing belongs to the layer it was made on, which `4e28f10` started enforcing, and `Stack` measures the child that sizes it by drawing it on its own layer and then draws it again on the child layer. So every stacked child redrew twice a frame, forever. `Painter::child_layer_at` addresses a child's layer rather than walking to it, and `Stack` measures on the layer that child ends up on. The second ask is then a reuse. Its glyphs are written once rather than once under the background and once over it. Measured on the same fixture: `scroll` 0.031 ms to 0.020, `many` 0.570 to 0.283, and the scroll phase's counters are back to what they were before fixed point -- 4 widget draws and 12 draw requests a frame, exactly. What is left above that baseline is not this. `ReuseOutcome` could not say "another layer" or "the region-node choice changed"; both returned without a counter, which is why the first look at this said nothing. They have counters now. Checked: fmt, clippy, 105 tests, five shrinker cases at 300 seeds, 100 generated seeds, and the examples byte-identical but for 36 pixels of `random` at one level -- edges that were being drawn twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.2 KiB
Rust
81 lines
2.2 KiB
Rust
use std::marker::PhantomData;
|
|
|
|
use crate::prelude::*;
|
|
|
|
pub struct Stack {
|
|
pub children: Vec<StrongWidget>,
|
|
pub size: StackSize,
|
|
}
|
|
|
|
impl Widget for Stack {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
let sizing = match self.size {
|
|
StackSize::Default => None,
|
|
StackSize::Child(i) => Some(i),
|
|
};
|
|
// Whichever child sizes the stack decides the box every child gets.
|
|
// The stack reports that size, so a child given a longer box would
|
|
// draw outside what the stack says it occupies.
|
|
let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) {
|
|
// On the layer that child ends up on, so the ask below is a reuse
|
|
// rather than a second drawing of it somewhere else: a retained
|
|
// drawing belongs to the layer it was made on.
|
|
Some((i, child)) => {
|
|
painter.child_layer_at(i);
|
|
painter.widget(child).size()
|
|
}
|
|
None => Size::LEFTOVER,
|
|
};
|
|
let region = painter.box_of(size);
|
|
for (i, child) in self.children.iter().enumerate() {
|
|
painter.child_layer_at(i);
|
|
painter.widget_aligned(child, region, RegionAlign::NEAR);
|
|
}
|
|
size
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub enum StackSize {
|
|
#[default]
|
|
Default,
|
|
Child(usize),
|
|
}
|
|
|
|
pub struct StackBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> {
|
|
pub children: Wa,
|
|
pub size: StackSize,
|
|
_pd: PhantomData<(State, Tag)>,
|
|
}
|
|
|
|
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
|
|
for StackBuilder<Rsc, LEN, Wa, Tag>
|
|
{
|
|
type Widget = Stack;
|
|
|
|
#[track_caller]
|
|
fn run(self, rsc: &mut Rsc) -> Self::Widget {
|
|
Stack {
|
|
children: self.children.add(rsc).arr.into_iter().collect(),
|
|
size: self.size,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
|
|
StackBuilder<State, LEN, Wa, Tag>
|
|
{
|
|
pub fn new(children: Wa) -> Self {
|
|
Self {
|
|
children,
|
|
size: StackSize::default(),
|
|
_pd: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn size(mut self, size: StackSize) -> Self {
|
|
self.size = size;
|
|
self
|
|
}
|
|
}
|