A quality sweep over the deferred request system, which no earlier round has reviewed. `Bound::outside` said which end a length fell outside and left the caller to look that end up through `Bound::at`, which `expect`s an end the value it is given does not promise: only the pairing of the two calls kept `at(Shorter)` off a bound with no floor. It already had the length in hand, so it returns that, and `Outside` and `at` go with the state that could panic. `measured_request` pinned the rel base for any bound at all, so a measured share under a cap in pixels was invalidated by a change to a base its answer cannot depend on. That question is `Bound::has_fraction` now, which is also the one `Placing::ask` and `SizeRule::has_fraction` were each writing out over a bare array. The rest is one name where there were several spellings: `Span::gaps`, `Padding::along`, `Plan::drop_bounds` behind one `IRIS_UNBOUNDED` in both rigs that had grown their own, and `Stack::size_request` resolving its sizing child the way its draw already does. `Span`'s placement loop asked three times whether the row was allocated, twice to decide one child's length; one match answers all three, so the allocated and plain rules are read side by side. The buffers `draw_at` now reuses for their capacity are empty only because every path to it drains them in `remove`; a `debug_assert` says so, since a drawing over primitives left in one would record them twice. Comments: `with_requests` named discovery as the hazard where it is a child drawn mid-row, `Painter::allocate` documented the window it holds for rather than what it does, `minimum_request` had none, and the note saying a span carries its children's weight whole -- which is still what the unallocated path does, and still the surprising part -- had been replaced by one about the other path.
302 lines
11 KiB
Rust
302 lines
11 KiB
Rust
use crate::prelude::*;
|
|
use std::marker::PhantomData;
|
|
|
|
pub struct Span {
|
|
pub children: Vec<StrongWidget>,
|
|
pub dir: Dir,
|
|
pub gap: Px,
|
|
}
|
|
|
|
impl Widget for Span {
|
|
fn size_request(&self, requests: &mut SizeRequests, axis: Axis) -> Option<RequestedLen> {
|
|
if axis != self.dir.axis {
|
|
// A share can be hidden when the other axis has no room. Its
|
|
// cross-axis length then contributes nothing to the drawn answer.
|
|
return None;
|
|
}
|
|
let mut total = RequestedLen::from(Len::from_parts(Rel::ZERO, self.gaps()));
|
|
for child in &self.children {
|
|
let child = requests.widget(child, axis)?;
|
|
total = requests.sum(total, child);
|
|
}
|
|
Some(total)
|
|
}
|
|
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
painter.with_requests(|painter, lens, values| self.layout(painter, lens, values))
|
|
}
|
|
}
|
|
|
|
impl Span {
|
|
fn layout(
|
|
&self,
|
|
painter: &mut Painter,
|
|
lens: &mut Vec<RequestedLen>,
|
|
values: &mut Vec<Px>,
|
|
) -> Size {
|
|
let axis = self.dir.axis;
|
|
// The row this span lays its children out along, as a length of the
|
|
// rel base they are laid out against. Where it starts is nothing's
|
|
// business -- a slot is a length from there -- so what this reads is
|
|
// the length alone.
|
|
let row = painter.region_len(axis);
|
|
self.collect(painter, row, lens, true);
|
|
let gaps = self.gaps();
|
|
let fixed = lens
|
|
.iter()
|
|
.try_fold(Len::from_parts(Rel::ZERO, gaps), |sum, len| {
|
|
Some(sum + len.linear()?.without_leftover())
|
|
});
|
|
if let Some(fixed) = fixed
|
|
&& lens.iter().any(|len| len.has_leftover())
|
|
&& !painter.longer_than(row, fixed, axis)
|
|
{
|
|
// With no share to assign, intrinsic drawings keep the remaining
|
|
// offer, including overflow. Their answer is only moved into a slot.
|
|
self.collect(painter, row, lens, false);
|
|
}
|
|
let nonlinear = lens.iter().any(|len| len.linear().is_none());
|
|
if nonlinear {
|
|
painter.allocate(lens, row - Len::from_parts(Rel::ZERO, gaps), axis, values);
|
|
}
|
|
let allocated = nonlinear.then(|| &values[..]);
|
|
let total = match allocated {
|
|
Some(allocated) => LayoutLen {
|
|
px: allocated.iter().fold(gaps, |sum, len| sum + *len),
|
|
..LayoutLen::ZERO
|
|
},
|
|
None => lens.iter().fold(
|
|
LayoutLen {
|
|
px: gaps,
|
|
..LayoutLen::ZERO
|
|
},
|
|
|sum, len| sum + len.linear().unwrap(),
|
|
),
|
|
};
|
|
let all_fixed = total.without_leftover();
|
|
let room = row - all_fixed;
|
|
let any_leftover = total.leftover > Weight::ZERO;
|
|
let has_room = any_leftover && painter.longer_than(row, all_fixed, axis);
|
|
|
|
// Across itself a span is as long as its longest child -- unless a
|
|
// rule beside it gives that length outright, and then reading them
|
|
// answers nothing and makes its size depend on theirs for it. A rule
|
|
// that only bounds the length does not count: the answer is still
|
|
// this span's to give.
|
|
let shrinks = !painter.has_exact_size(!axis);
|
|
// What the fixed parts and the gaps before here take, which is a sum
|
|
// of lengths and exact, and how much of the leftover weight is
|
|
// spoken for. Both ends of a slot are read from those two rather
|
|
// than stepped from the last child: the share of the room is
|
|
// rounded, and taking each end from the one before it would carry
|
|
// every rounding along the row.
|
|
let mut fixed = Len::ZERO;
|
|
let mut taken = Weight::ZERO;
|
|
let mut ortho = LayoutLen::ZERO;
|
|
// Nothing divides the room where no child asked for any of it, and a
|
|
// ratio of a whole of nothing has no answer.
|
|
let reached = |fixed: Len, taken: Weight| match any_leftover {
|
|
false => fixed,
|
|
true => fixed + room.scale(Rel::ratio(taken, total.leftover)),
|
|
};
|
|
for (index, (child, request)) in self.children.iter().zip(lens.iter()).enumerate() {
|
|
// An allocated row already has a length for every child; without
|
|
// one the request is the length and the room is divided here.
|
|
// Either way a child asking for nothing but a part of what is
|
|
// left over, when nothing is, is not drawn at all -- one that
|
|
// also asked for pixels or a fraction keeps those and overflows.
|
|
let (len, shares, nothing_left) = match allocated {
|
|
Some(allocated) => {
|
|
let len = LayoutLen {
|
|
px: allocated[index],
|
|
..LayoutLen::ZERO
|
|
};
|
|
let shares = request.has_leftover();
|
|
(len, shares, shares && len.px == Px::ZERO)
|
|
}
|
|
None => {
|
|
let len = request.linear().unwrap();
|
|
let shares = len.leftover > Weight::ZERO && has_room;
|
|
(len, shares, len.is_only_leftover() && !has_room)
|
|
}
|
|
};
|
|
if nothing_left {
|
|
painter.undraw(child);
|
|
fixed.px += self.gap;
|
|
continue;
|
|
}
|
|
let from = reached(fixed, taken);
|
|
if shares {
|
|
taken += len.leftover;
|
|
}
|
|
fixed += len.without_leftover();
|
|
let to = reached(fixed, taken);
|
|
// Along the row the span says where the child goes, and that slot
|
|
// is the child's box outright rather than something to place an
|
|
// answer inside again. A share is decided here and nowhere
|
|
// else: its slot narrows its rel base, and the child is asked in
|
|
// it, since a text wraps at the width it is actually given. A
|
|
// fixed child's slot is its own answer, so a drawing made in the
|
|
// room is put there as it is, and one not made yet is made here.
|
|
let slot = self.slot(row, from, to);
|
|
let mut place = slot.shifted_desc().allocated().on_axis(axis);
|
|
if shares {
|
|
place = place.rel_base(axis, slot.len());
|
|
}
|
|
let used = painter.place_at(child, place).len(!axis);
|
|
if shrinks {
|
|
// Choosing between a fixed and a relative length from the
|
|
// span's own eventual width admits multiple fixed points.
|
|
// A scalable child therefore makes the span scalable too;
|
|
// only fixed children are compared with one another.
|
|
if !used.is_px() {
|
|
ortho = LayoutLen::LEFTOVER;
|
|
} else if ortho.leftover == Weight::ZERO {
|
|
ortho.px = ortho.px.max(used.px);
|
|
}
|
|
}
|
|
fixed.px += self.gap;
|
|
}
|
|
|
|
// Where nothing was allocated the weight is carried whole rather
|
|
// than collapsed to one share, so nesting spans divides the same
|
|
// space rather than re-dividing a share of it: four `leftover(1)`
|
|
// children under two spans under one span get a quarter each, which
|
|
// one share per level does not give. Resolution happens at the
|
|
// nearest ancestor with a length, and the root always has one --
|
|
// or, where a comparison deferred the row, at the ancestor whose
|
|
// allocation discovery carried these requests to, and `total` is
|
|
// pixels by the time it gets here.
|
|
let ortho = match shrinks {
|
|
true => ortho,
|
|
false => LayoutLen::rel(1.0),
|
|
};
|
|
Size::from_axis(axis, total, ortho)
|
|
}
|
|
}
|
|
|
|
impl Span {
|
|
/// What the gaps between this span's children take, which is a length of
|
|
/// the row before anything is divided.
|
|
fn gaps(&self) -> Px {
|
|
self.gap
|
|
.mul_int(self.children.len().saturating_sub(1) as i32)
|
|
}
|
|
|
|
fn collect(
|
|
&self,
|
|
painter: &mut Painter,
|
|
row: Len,
|
|
lens: &mut Vec<RequestedLen>,
|
|
discover: bool,
|
|
) {
|
|
let axis = self.dir.axis;
|
|
let mut cursor = Len::ZERO;
|
|
lens.clear();
|
|
for child in &self.children {
|
|
let request = if discover {
|
|
painter.size_request(child, axis)
|
|
} else {
|
|
painter.size_hint(child, axis).map(Into::into)
|
|
};
|
|
let len = match request {
|
|
Some(len) => len,
|
|
None => {
|
|
let room = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
|
|
let len = painter.widget_at(child, room).len(axis);
|
|
painter.measured_request(child, axis, len)
|
|
}
|
|
};
|
|
cursor += painter.minimum_request(&len, axis);
|
|
cursor.px += self.gap;
|
|
lens.push(len);
|
|
}
|
|
}
|
|
|
|
/// The stretch of the row between two distances from where this span
|
|
/// starts laying children out, as a span of its own box. A negative
|
|
/// direction lays out from the far end, so the same two distances mirror
|
|
/// in a row `row` long.
|
|
fn slot(&self, row: Len, from: Len, to: Len) -> UiSpan {
|
|
match self.dir.sign {
|
|
Sign::Pos => from.to(to),
|
|
Sign::Neg => (row - to).to(row - from),
|
|
}
|
|
}
|
|
|
|
pub fn empty(dir: Dir) -> Self {
|
|
Self {
|
|
children: Vec::new(),
|
|
dir,
|
|
gap: Px::ZERO,
|
|
}
|
|
}
|
|
|
|
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
self.gap = Px::from_num(gap);
|
|
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: Px,
|
|
_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: Px::ZERO,
|
|
_pd: PhantomData,
|
|
}
|
|
}
|
|
|
|
pub fn gap(mut self, gap: impl UiNum) -> Self {
|
|
self.gap = Px::from_num(gap);
|
|
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
|
|
}
|
|
}
|