diff --git a/src/random.rs b/src/random.rs index 5e179d0..96e64b2 100644 --- a/src/random.rs +++ b/src/random.rs @@ -308,6 +308,12 @@ impl Grow<'_, Rsc> { children, dir, gap: self.rng.below(3) as f32 * 4.0, + // Derive this from an existing choice: a seed must keep growing + // the same tree when the generator gains another configuration. + ortho: match dir.axis { + Axis::X => OrthoSize::Full, + Axis::Y => OrthoSize::Children, + }, } .add(self.rsc); self.tree.ids.push(id.id()); diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index dab60b0..6db1365 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -1,10 +1,21 @@ use crate::prelude::*; use std::marker::PhantomData; +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum OrthoSize { + /// Reports one full relative length across the span's orthogonal axis, + /// whether or not its siblings also take space. + Full, + /// Reports its longest fixed child, or leftover space if any child scales. + #[default] + Children, +} + pub struct Span { pub children: Vec, pub dir: Dir, pub gap: f32, + pub ortho: OrthoSize, } impl Widget for Span { @@ -82,12 +93,18 @@ impl Widget for Span { if self.dir.sign == Sign::Neg { region.flip(axis); } - let used = painter.place(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.leftover > 0.0 { - ortho = Len::LEFTOVER; - } else if ortho.leftover == 0.0 { - ortho.px = ortho.px.max(used.px); + let placed = painter.place(child, region); + if self.ortho == OrthoSize::Children { + 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 != 0.0 || used.leftover != 0.0 { + ortho = Len::LEFTOVER; + } else if ortho.leftover == 0.0 { + ortho.px = ortho.px.max(used.px); + } } start.px += self.gap; } @@ -100,6 +117,10 @@ impl Widget for Span { // not give. Resolution happens at the nearest ancestor with a length, // and the root always has one. let along = total; + let ortho = match self.ortho { + OrthoSize::Full => Len::rel(1.0), + OrthoSize::Children => ortho, + }; Size::from_axis(axis, along, ortho) } } @@ -110,6 +131,7 @@ impl Span { children: Vec::new(), dir, gap: 0.0, + ortho: OrthoSize::Children, } } @@ -118,6 +140,11 @@ impl Span { self } + pub fn ortho(mut self, ortho: OrthoSize) -> Self { + self.ortho = ortho; + self + } + pub fn push(&mut self, w: StrongWidget) { self.children.push(w); } @@ -131,6 +158,7 @@ pub struct SpanBuilder, } @@ -145,6 +173,7 @@ impl, Tag> WidgetFnTrait children: self.children.add(rsc).arr.into_iter().collect(), dir: self.dir, gap: self.gap, + ortho: self.ortho, } } } @@ -157,6 +186,7 @@ impl, Tag> children, dir, gap: 0.0, + ortho: OrthoSize::Children, _pd: PhantomData, } } @@ -165,6 +195,11 @@ impl, Tag> self.gap = gap.to_f32(); self } + + pub fn ortho(mut self, ortho: OrthoSize) -> Self { + self.ortho = ortho; + self + } } impl std::ops::Deref for Span { diff --git a/tests/generated.rs b/tests/generated.rs index b4d72a1..83c0b59 100644 --- a/tests/generated.rs +++ b/tests/generated.rs @@ -190,9 +190,10 @@ fn describe(id: WidgetId, h: &Harness) -> String { if let Some(w) = any.downcast_ref::() { let sign = if w.dir.sign == Sign::Neg { "-" } else { "+" }; return format!( - "Span{{dir:{:?}{sign},gap:{},n:{}}}", + "Span{{dir:{:?}{sign},gap:{},ortho:{:?},n:{}}}", w.dir.axis, w.gap, + w.ortho, w.children.len() ); } diff --git a/tests/layout.rs b/tests/layout.rs index ad33146..85f7690 100644 --- a/tests/layout.rs +++ b/tests/layout.rs @@ -20,6 +20,33 @@ fn a_span_gives_each_child_the_width_it_asked_for() { assert_corners!(h, right, (100, 0), (400, 200)); } +#[test] +fn a_full_ortho_span_reports_relative_full() { + let mut h = Harness::new((400, 200)); + let child = rect(Color::RED).height(40).add(&mut h.rsc); + let span = (child,) + .span(Dir::RIGHT) + .ortho(OrthoSize::Full) + .add(&mut h.rsc); + h.set_root(span); + + assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0)); +} + +#[test] +fn a_children_ortho_span_reports_its_tallest_fixed_child() { + let mut h = Harness::new((400, 200)); + let short = rect(Color::RED).height(40).add(&mut h.rsc); + let tall = rect(Color::BLUE).height(70).add(&mut h.rsc); + let span = (short, tall) + .span(Dir::RIGHT) + .ortho(OrthoSize::Children) + .add(&mut h.rsc); + h.set_root(span); + + assert_eq!(h.render.active[&span.id()].size.y, Len::px(70.0)); +} + #[test] fn resizing_relays_out_against_the_new_output() { let mut h = Harness::new((400, 200)); @@ -158,7 +185,12 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() { let inner = rect(Color::BLUE).add(&mut h.rsc); let row = inner.pad(10).height(40).add(&mut h.rsc); let filler = rect(Color::GREEN).add(&mut h.rsc); - let column = (row, filler).span(Dir::DOWN).add(&mut h.rsc); + // This column is an item in a row, so it takes the width left for it + // rather than asking for a full row-width in addition to the bar. + let column = (row, filler) + .span(Dir::DOWN) + .ortho(OrthoSize::Children) + .add(&mut h.rsc); let bar = rect(Color::RED).width(100).add(&mut h.rsc); h.set_root((bar, column).span(Dir::RIGHT)); assert_corners!(h, inner, (110, 10), (390, 30)); diff --git a/tests/retained.rs b/tests/retained.rs index 54e11c0..6f841a2 100644 --- a/tests/retained.rs +++ b/tests/retained.rs @@ -264,6 +264,25 @@ fn a_resize_does_not_redraw_what_the_shader_can_move() { assert_corners!(h, leaf, (0, 0), (800, 100)); } +#[test] +fn a_full_ortho_span_moves_its_child_without_redrawing_it() { + let mut h = Harness::new((400, 200)); + let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false); + let span = (leaf,) + .span(Dir::RIGHT) + .ortho(OrthoSize::Full) + .add(&mut h.rsc); + h.set_root(span); + let settled = draws.get(); + + h.resize((400, 100)); + h.frame(); + + assert_eq!(draws.get(), settled); + assert_corners!(h, leaf, (0, 0), (400, 100)); + assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0)); +} + /// The output is the root of the box chain, so a resize is a box that changed /// length like any other -- there is not a second rule for the window. A /// drawing that holds for one length is drawn again whichever box moved. diff --git a/tests/shrink.rs b/tests/shrink.rs index 7bf2f5e..213d9f2 100644 --- a/tests/shrink.rs +++ b/tests/shrink.rs @@ -116,6 +116,10 @@ impl Node { children, dir: dir(*down), gap: *gap, + ortho: match down { + true => OrthoSize::Children, + false => OrthoSize::Full, + }, } .add(&mut h.rsc); spans.push(handle); diff --git a/tests/unsettled.rs b/tests/unsettled.rs index f55c1d5..baa02ac 100644 --- a/tests/unsettled.rs +++ b/tests/unsettled.rs @@ -173,6 +173,7 @@ fn plant_pair(h: &mut Harness, swapped: bool) -> (Vec, WeakWidget (Vec, [WeakWidget children: inner_children, dir: Dir::RIGHT, gap: 0.0, + ortho: OrthoSize::Children, } .add(&mut h.rsc); let block = rect(Color::RED).add(&mut h.rsc); @@ -248,6 +250,7 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec, [WeakWidget children: outer_children, dir: Dir::RIGHT, gap: 0.0, + ortho: OrthoSize::Children, } .add(&mut h.rsc); let through = SetSize {