use crate::prelude::*; use std::marker::PhantomData; pub struct Span { pub children: Vec, pub dir: Dir, pub gap: Px, } impl Widget for Span { fn draw(&mut self, painter: &mut Painter) -> Size { let axis = self.dir.axis; // A length for every child before their final boxes are chosen: from // a hint where one exists, and from drawing otherwise. let mut cursor = Len::rel_min(); let mut lens = Vec::with_capacity(self.children.len()); for child in &self.children { let mut span = UiSpan::new(cursor, Len::rel_max()); if self.dir.sign == Sign::Neg { span.flip(); } let region = UiRegion::from_axis(axis, span, UiSpan::FULL); // Offered the room left from the cursor, because a text has to // wrap at the width actually there, but reporting a fraction of // the whole row: `rel(0.5)` is half the span whatever else is in // it and wherever this child sits among them. let len = match painter.known_len(child, axis, region, UiVec2::FULL_SIZE) { Some(len) => len, None => painter .widget_at(child, region, UiVec2::FULL_SIZE, [false; 2]) .len(axis), }; cursor.px += len.px + self.gap; cursor.rel += len.rel; lens.push(len); } let gaps = self .gap .mul_int(self.children.len().saturating_sub(1) as i32); let total = lens.iter().fold( LayoutLen { px: gaps, ..LayoutLen::ZERO }, |sum, len| sum + *len, ); // What is left for the shares to divide: the box less everything // fixed, as a length of the box rather than a number of pixels. let room = Len::rel_max() - Len::from_parts(total.rel, total.px); // Whether anything is left over is a question in pixels: `rel(0.5)` // beside 300 px is full at 600 and overfull at 400. Asked of `room` // itself, and answered back through the same expression, so the // boundary is the drawing's own and not a second way of finding it: // the three cases a rounded division needed -- the fixed parts // growing slower than the box, faster, or exactly with it -- are the // sign of `room.rel`, which `through` already reads. What the // generated oracle checks is the consequence, since which children // exist at all turns on this. let mut shares = false; if total.leftover > Weight::ZERO { shares = room.to_px(painter.px_len(axis)) > Px::ZERO; let holds = match shares { true => Holds::from(Px::STEP..=Px::MAX), false => Holds::from(Px::MIN..=Px::ZERO), }; painter.holds(axis, holds.through(room)); } // 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. A position is one from the other rather than a step // from the last child: the share of the room is rounded, and taking // each from the one before it would carry every rounding along the // row. let mut fixed = Len::rel_min(); let mut taken = Weight::ZERO; let mut start = Len::rel_min(); let mut ortho = LayoutLen::ZERO; for (child, len) in self.children.iter().zip(&lens) { // 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. if len.leftover > Weight::ZERO && len.px == Px::ZERO && len.rel == Rel::ZERO && !shares { painter.undraw(child); fixed.px += self.gap; continue; } let mut span = UiSpan::FULL; span.start = start; if len.leftover > Weight::ZERO && shares { taken += len.leftover; } fixed.px += len.px; fixed.rel += len.rel; start = shared(fixed, taken, total.leftover, room); span.end = start; let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL); if self.dir.sign == Sign::Neg { region.flip(axis); } // Along the row this box is the child's own answer, so the answer // is not placed in it again; across it the child sits where its // alignment says. let placed = painter.widget_at( child, region, UiVec2::FULL_SIZE, [axis == Axis::X, axis == Axis::Y], ); if shrinks { let used = placed.len(!axis); // Choosing between a fixed and a relative length from the // span's own eventual width admits multiple fixed points. // A scalable child therefore makes Children scalable too; // only fixed children are compared with one another. if used.rel != Rel::ZERO || used.leftover != Weight::ZERO { ortho = LayoutLen::LEFTOVER; } else if ortho.leftover == Weight::ZERO { ortho.px = ortho.px.max(used.px); } } fixed.px += self.gap; start = shared(fixed, taken, total.leftover, room); } // Carried whole rather than collapsed to one share: a span that sizes // from its children does not resolve `leftover`, it passes the weight up, // 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 collapsing to `leftover(1)` per level does // not give. Resolution happens at the nearest ancestor with a length, // and the root always has one. let along = total; let ortho = match shrinks { true => ortho, false => LayoutLen::rel(1.0), }; Size::from_axis(axis, along, ortho) } } /// Where a row has reached: everything fixed before this point, which is a /// sum and exact, plus the share of the room the weights so far are worth, /// which is one rounding wherever it is asked for. fn shared(fixed: Len, taken: Weight, weight: Weight, room: Len) -> Len { if taken == Weight::ZERO { return fixed; } fixed + room.scale(Rel::ratio(taken, weight)) } impl Span { 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 { self.children.pop() } } pub struct SpanBuilder, Tag> { pub children: Wa, pub dir: Dir, pub gap: Px, _pd: PhantomData<(State, Tag)>, } impl, Tag> WidgetFnTrait for SpanBuilder { 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, Tag> SpanBuilder { 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; fn deref(&self) -> &Self::Target { &self.children } } impl std::ops::DerefMut for Span { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.children } }