diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 6d19417..74f7721 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -168,10 +168,20 @@ impl<'a> Painter<'a> { let region_node = self.rsc.widgets().is_region_node(id.id()); let declared = self.declared_lens(id); let align = self.rsc.widgets().alignment(id.id()); + // A rule this box was already chosen from is not resolved into it a + // second time. The box is that rule's length already, so resolving + // it again takes the fraction twice -- a widget declaring half of a + // stack, in the stack its own answer made half a row, is a quarter + // of the row. Pixels survive it, being the same length wherever they + // are taken from, which is why only a share ever shrank. + let resolve = AXES.map(|axis| match decided[axis as usize] { + true => None, + false => declared[axis as usize], + }); // Composing `FULL` through a box is not quite the identity in f32, // so a child with nothing declared keeps the box it would have had. - let local = match declared.iter().any(Option::is_some) { - true => declared_box(region, declared, align), + let local = match resolve.iter().any(Option::is_some) { + true => declared_box(region, resolve, align), false => region, }; let within = match local == UiRegion::FULL { @@ -393,14 +403,6 @@ impl<'a> Painter<'a> { .is_some() } - /// The part of this widget's box that something of `size` takes, at the - /// near edge. A container that reports one child's size gives every child - /// this, so what it draws is inside what it says it occupies. - pub fn box_of(&self, size: Size) -> UiRegion { - let lens = placed_lens(size, [None; 2], [false; 2]); - placed_box(UiRegion::FULL, lens, RegionAlign::NEAR) - } - /// This widget's box in pixels. Reading it makes the drawing one that /// holds for this box only, until `holds` says how far it goes. pub fn px_size(&mut self) -> PxVec2 { diff --git a/examples/text.rs b/examples/text.rs index 3686ba6..5d5b1d2 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -26,6 +26,7 @@ impl DefaultAppState for State { .wrap(true) .text_align(Align::LEFT) .pad(16) + .width(rel(1.0)) .background(panel()); // Each one takes the whole width, because `text_align` puts the diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index 2fd06e5..5fbf905 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -13,31 +13,42 @@ impl Widget for Stack { 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. + // Every child gets the whole of this stack's box, the sizing one + // included, and the stack is then handed a box of the length that + // child asked for. Not the part of the box that length takes: the + // stack's own box becomes that length, and taking the fraction of it + // again is the fraction twice -- a child asking for half of a stack + // that is already half a row would have a quarter of the row. + // + // It cannot be told apart by asking whether this box is the answer + // yet, either. A drawing has to be a function of the box alone, since + // moving the stack into the box it asked for reuses the drawing by + // scaling it, and a drawing made a fraction of one box is right in + // any other. So: fractions of this box throughout, and the move is + // the whole of the difference. + let region = UiRegion::FULL; + // Whichever child sizes the stack is asked here and not again below, + // on the layer it ends up on: a retained drawing belongs to the layer + // it was made on, so measuring it anywhere else costs a second + // drawing of it. Its box is its own answer, so the answer is not + // placed inside it again. 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() + painter + .widget_at(child, region, region.size(), [true; 2]) + .size() } None => Size::LEFTOVER, }; - let region = painter.box_of(size); for (i, child) in self.children.iter().enumerate() { + if sizing == Some(i) { + continue; + } painter.child_layer_at(i); - // The sizing child placed its own content in the box its answer - // decided, and this box was derived from that answer, so applying - // its alignment again here would place it twice. Every other - // child is handed a box that owes nothing to its own answer, and - // where it sits in one bigger than itself is its own business. - match sizing == Some(i) { - true => painter.widget_at(child, region, region.size(), [true; 2]), - false => painter.widget_within(child, region), - }; + // A box that owes nothing to this child's own answer: where it + // sits in one bigger than itself is its own business. + painter.widget_within(child, region); } size } diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index acd7e31..601d204 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -82,6 +82,31 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() { assert!(crowded > whole_row, "{crowded} against {whole_row}"); } +/// A stack takes its size from one child and gives every child that size, so +/// a child asking for half of it is asking for half of what it is itself the +/// size of. Once the stack has been placed at the length it reported that +/// length is the box, and taking the fraction of it again takes it twice: +/// half a row became a quarter, and a further stack around it a further half. +/// Nothing pinned it because a pixel is the same length wherever it is taken +/// from, so only a share ever shrank -- and warm and cold shrink alike, so no +/// oracle saw it either. +#[test] +fn a_stack_sized_by_a_child_does_not_take_that_childs_fraction_twice() { + let mut h = Harness::new((400, 200)); + let half = rect(Color::RED).width(rel(0.5)).add(&mut h.rsc); + let behind = rect(Color::BLUE).add(&mut h.rsc); + let stack = Stack { + children: vec![behind.add_strong(&mut h.rsc), half.add_strong(&mut h.rsc)], + size: StackSize::Child(1), + } + .add(&mut h.rsc); + h.set_root((stack,).span(Dir::RIGHT).width(rel(1.0))); + + assert_corners!(h, stack, (0, 0), (200, 200)); + assert_corners!(h, half, (0, 0), (200, 200)); + assert_corners!(h, behind, (0, 0), (200, 200)); +} + /// The same reading through a pad: its inset is the whole box less the /// padding, so half of the inset plus the padding is half the box plus one /// padding, not two.