diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index 43a683c..d5a579e 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -528,3 +528,95 @@ fn a_stack_child_smaller_than_the_stack_keeps_its_own_alignment() { // that is the default. assert_corners!(h, small, (350, 75), (400, 125)); } +/// Five children of one span, buried under three containers that are each a +/// fraction of their parent so no length reaches the window without being +/// composed and rounded on the way. Returns each child's drawn width and +/// each gap between them, in pixels. +fn row_under_fractions(kid: Option, gap: f32, box_w: f32) -> (Vec, Vec) { + let mut h = Harness::new((box_w, 400.0)); + let mut ids = Vec::new(); + let mut kids: Vec = Vec::new(); + for _ in 0..5 { + let r = rect(Color::RED).add(&mut h.rsc); + if let Some(len) = kid { + h.rsc + .widgets_mut() + .set_size_rule(r.id(), Axis::X, SizeRule::Exact(len)); + } + ids.push(r.id()); + kids.push(r.add_strong(&mut h.rsc)); + } + let span = Span { + children: kids, + dir: Dir::RIGHT, + gap: Px::from_f32(gap), + } + .add(&mut h.rsc); + let a = (span.width(rel(0.9)),).span(Dir::RIGHT).add(&mut h.rsc); + let b = (a.width(rel(0.8)),).span(Dir::RIGHT).add(&mut h.rsc); + h.set_root((b.width(rel(0.7)),).span(Dir::RIGHT)); + let boxes: Vec<_> = ids + .iter() + .map(|id| h.region(id).expect("a child drew nothing")) + .collect(); + ( + boxes.iter().map(|b| b.bot_right.x - b.top_left.x).collect(), + boxes + .windows(2) + .map(|p| p[1].top_left.x - p[0].bot_right.x) + .collect(), + ) +} + +/// **A length given in pixels is that many pixels, wherever it ends up.** A +/// gap and a declared width compose additively -- `Len::within` adds a part's +/// own pixels rather than scaling them, and both ends of a gap carry the same +/// fraction, so the multiply that rounds is the same on each -- which is why +/// nesting the row inside fractions of fractions cannot move them. Swept over +/// 2,100 box widths when this was written and exact at every one; five here, +/// including widths that divide badly by five. +#[test] +fn a_length_in_pixels_is_that_many_pixels_however_it_is_nested() { + for box_w in [300.0, 1000.0, 1001.0, 1003.0, 1920.0] { + let want = Px::from_int(7); + let (_, gaps) = row_under_fractions(None, 7.0, box_w); + assert!( + gaps.iter().all(|g| *g == want), + "box {box_w}: gaps between leftover children are {gaps:?}" + ); + let (widths, gaps) = row_under_fractions(Some(LayoutLen::px(100.0)), 7.0, box_w); + assert!( + gaps.iter().all(|g| *g == want), + "box {box_w}: gaps between fixed children are {gaps:?}" + ); + assert!( + widths.iter().all(|w| *w == Px::from_int(100)), + "box {box_w}: declared widths came out {widths:?}" + ); + } +} + +/// **Children asking for the same share of a row are not the same length**, +/// and this pins by how much rather than claiming they are equal. A position +/// is the quantity that gets rounded, so the row fills exactly and no two +/// children leave a seam; what that costs is a step or two between lengths +/// that were asked for identically. Exact composition would shrink the +/// spread, not remove it: five equal lengths cannot fill a row whose step +/// count is not a multiple of five. +#[test] +fn equal_shares_differ_by_at_most_two_steps_and_fill_the_row() { + for kid in [None, Some(LayoutLen::rel(0.2))] { + for box_w in [300.0, 1000.0, 1001.0, 1003.0, 1920.0] { + let (widths, gaps) = row_under_fractions(kid, 0.0, box_w); + let spread = *widths.iter().max().unwrap() - *widths.iter().min().unwrap(); + assert!( + spread <= Px::from_raw(2), + "box {box_w}, {kid:?}: widths {widths:?} spread {spread:?}" + ); + assert!( + gaps.iter().all(|g| *g == Px::ZERO), + "box {box_w}, {kid:?}: children left seams {gaps:?}" + ); + } + } +}