Review response. `Painter::set_size` is gone: `Widget::draw` returns the `Size` instead, so a widget that does not say what it used cannot compile rather than panicking at the widget that forgot. That also settles setting it twice -- a branch that learns something late just returns a different value. `Painter::place` and `UiRenderState::place` are gone too. `draw_inner` already tried to reuse an active widget's drawing before redrawing it, so `place` was `widget_within` with its own bookkeeping bolted on; drawing a child a second time now *is* how a parent puts it where it belongs, and a child is deduplicated in `children` because listing one twice would move it twice. The unification also drops `place`'s use of `ActiveData::layer`, which is the layer a widget's own `child_layer()` left the painter on rather than the layer it was drawn into. What `place` did unconditionally and `widget_within` did not is record the size dependency, so `Painter::size_hint` now records one: reading a child's length to lay out around it is reading its size, whether it came from a draw or from a hint. `tests/retained.rs` has a parent that only ever reads the hint, which is the case no existing widget exercises. `()` sizes itself `Size::default()` -- rest -- rather than zero, so it is a gap that takes an even share of a span; `WidgetPtr` with nothing in it does the same, since it is the same situation. `Widget::on_resize`'s default body said `Translate` while the enum's `#[default]` said `Redraw`; it now defers to the enum. `was` is `old` throughout, the `OnResize` variant comments are gone, and so are two empty `impl` blocks. Checked: fmt, clippy and 27 tests across the workspace; `tabs` on each of its five tabs, and `tabs` with a replay that adds two images, all byte-identical to `upstream/main`; `view` and `minimal` likewise; and the `text` example rendered at 1920x1200 and 900x1200 to see the paragraph reflow and its container follow.
139 lines
3.8 KiB
Rust
139 lines
3.8 KiB
Rust
use crate::prelude::*;
|
|
use std::marker::PhantomData;
|
|
|
|
pub struct Span {
|
|
pub children: Vec<StrongWidget>,
|
|
pub dir: Dir,
|
|
pub gap: f32,
|
|
}
|
|
|
|
impl Widget for Span {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
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();
|
|
let mut ortho = Len::ZERO;
|
|
for (child, len) in self.children.iter().zip(&lens) {
|
|
let mut span = UiSpan::FULL;
|
|
span.start = start;
|
|
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 mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
|
if self.dir.sign == Sign::Neg {
|
|
region.flip(axis);
|
|
}
|
|
let used = painter.widget_within(child, region).size().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);
|
|
}
|
|
start.abs += self.gap;
|
|
}
|
|
|
|
let along = match total.rest == 0.0 && total.rel == 0.0 {
|
|
true => total,
|
|
false => Len::default(),
|
|
};
|
|
Size::from_axis(axis, along, ortho)
|
|
}
|
|
}
|
|
|
|
impl Span {
|
|
pub fn empty(dir: Dir) -> Self {
|
|
Self {
|
|
children: Vec::new(),
|
|
dir,
|
|
gap: 0.0,
|
|
}
|
|
}
|
|
|
|
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
self.gap = gap.to_f32();
|
|
self
|
|
}
|
|
|
|
pub fn push(&mut self, w: StrongWidget) {
|
|
self.children.push(w);
|
|
}
|
|
|
|
pub fn pop(&mut self) -> Option<StrongWidget> {
|
|
self.children.pop()
|
|
}
|
|
}
|
|
|
|
pub struct SpanBuilder<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag> {
|
|
pub children: Wa,
|
|
pub dir: Dir,
|
|
pub gap: f32,
|
|
_pd: PhantomData<(State, Tag)>,
|
|
}
|
|
|
|
impl<Rsc, const LEN: usize, Wa: WidgetArrLike<Rsc, LEN, Tag>, Tag> WidgetFnTrait<Rsc>
|
|
for SpanBuilder<Rsc, LEN, Wa, Tag>
|
|
{
|
|
type Widget = Span;
|
|
|
|
#[track_caller]
|
|
fn run(self, rsc: &mut Rsc) -> Self::Widget {
|
|
Span {
|
|
children: self.children.add(rsc).arr.into_iter().collect(),
|
|
dir: self.dir,
|
|
gap: self.gap,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<State, const LEN: usize, Wa: WidgetArrLike<State, LEN, Tag>, Tag>
|
|
SpanBuilder<State, LEN, Wa, Tag>
|
|
{
|
|
pub fn new(children: Wa, dir: Dir) -> Self {
|
|
Self {
|
|
children,
|
|
dir,
|
|
gap: 0.0,
|
|
_pd: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
self.gap = gap.to_f32();
|
|
self
|
|
}
|
|
}
|
|
|
|
impl std::ops::Deref for Span {
|
|
type Target = Vec<StrongWidget>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.children
|
|
}
|
|
}
|
|
|
|
impl std::ops::DerefMut for Span {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.children
|
|
}
|
|
}
|