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; // The row: this span's own box, as a length of the rel base its children // are laid out against. Its start is nothing's business -- a slot is // a length from it -- so what this reads is the length alone. let far = painter.region_len(axis); let along = |from: Len, to: Len| match self.dir.sign { Sign::Pos => UiSpan::new(from, to), Sign::Neg => UiSpan::new(far - to, far - from), }; // Across itself the child sits where its own alignment says, in the // whole of the row: a span is what contains its children there, and // nothing divides that axis. let across = Place::Within(Part::WHOLE); // A length for every child before their final slots are chosen: from // a hint where one says, and from drawing otherwise. The rel base passes // through unchanged, so `rel(0.5)` is half the area this span was // given whatever else is in it and wherever this child sits among // them; what a drawn child is asked in is the room left from the // cursor, because a text has to wrap at the width actually there. let mut cursor = Len::rel_min(); let mut lens = Vec::with_capacity(self.children.len()); for child in &self.children { let len = match painter.size_hint(child, axis) { Some(len) => len, None => { let room = Place::Within(Part::From(along(cursor, far))); painter .widget_at(child, axis.pair(room, across), None) .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 row less everything // fixed, as a length of the rel base rather than a number of pixels. let room = far - 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 = painter.to_px(room, axis) > Px::ZERO; let holds = match shares { true => Holds::from(Px::STEP..=Px::MAX), false => Holds::from(Px::MIN..=Px::ZERO), }; painter.window_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; start = shared(fixed, taken, total.leftover, room); continue; } let from = 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); // 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 = along(from, start); let place = axis.pair(Place::Fill(Part::From(slot)), across); let narrow = (len.leftover > Weight::ZERO && shares).then(|| axis.pair(Some(slot.len()), None)); let used = painter.place_at(child, place, narrow).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 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 } }