//! Where a frame puts things, with no window to put them in. use iris::harness::{Harness, assert_corners}; use iris::prelude::*; /// A fixed 100 wide, and the rest of the 400 to its neighbour. fn two_rects(h: &mut Harness) -> (WidgetId, WidgetId) { let left = rect(Color::RED).width(100).add(&mut h.rsc); let right = rect(Color::BLUE).add(&mut h.rsc); h.set_root((left, right).span(Dir::RIGHT)); (left.id(), right.id()) } #[test] fn a_span_gives_each_child_the_width_it_asked_for() { let mut h = Harness::new((400, 200)); let (left, right) = two_rects(&mut h); assert_corners!(h, left, (0, 0), (100, 200)); assert_corners!(h, right, (100, 0), (400, 200)); } /// A span places each child in the room left after the one before, because a /// text has to wrap at the width actually there, but the child's region is /// the whole row. So two children asking for half each take the whole row /// between them, however much of it was left when each was asked, and a third /// overflows -- and a span passes its own region on unchanged, so a child of /// a nested span asking for half asks for half of the same row. #[test] fn a_span_reads_a_child_report_as_a_fraction_of_the_row() { let mut h = Harness::new((400, 100)); let half = rect(Color::RED).width(rel(0.5)).add(&mut h.rsc); let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc); let nested = (inner,).span(Dir::RIGHT).add(&mut h.rsc); let tail = rect(Color::BLUE).width(100).add(&mut h.rsc); h.set_root((half, nested, tail).span(Dir::RIGHT).width(rel(1.0))); // The nested span is placed at the length it reported, and its own child // asks for half of the row rather than half of that placement. assert_corners!(h, nested, (200, 0), (400, 100)); assert_corners!(h, inner, (200, 0), (400, 100)); assert_corners!(h, tail, (400, 0), (500, 100)); } /// The same fraction either way round: after a 100 px child in a 400 px row, /// `rel(0.5)` is 100 to 300 whether the child's own rule says so or the child /// drew half of what it was offered and reported that. Half the row, not half /// of the 300 px left of it. #[test] fn a_reported_fraction_is_of_the_row_like_a_declared_one() { let mut declaring = Harness::new((400, 100)); let head = rect(Color::RED).width(100).add(&mut declaring.rsc); let declared = rect(Color::GREEN).width(rel(0.5)).add(&mut declaring.rsc); declaring.set_root((head, declared).span(Dir::RIGHT).width(rel(1.0))); assert_corners!(declaring, declared, (100, 0), (300, 100)); let mut reporting = Harness::new((400, 100)); let head = rect(Color::RED).width(100).add(&mut reporting.rsc); let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut reporting.rsc); let reported = (inner,).span(Dir::RIGHT).add(&mut reporting.rsc); reporting.set_root((head, reported).span(Dir::RIGHT).width(rel(1.0))); assert_corners!(reporting, reported, (100, 0), (300, 100)); } /// What the fraction a child reports is of and what box it is offered are /// two different lengths, and only the first is the whole row: a text still /// wraps at the room actually left after its neighbour, so the same /// paragraph is taller where less of the row is left for it. #[test] fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() { let paragraph = "Wrapping shapes one source into as many lines as the box \ leaves room for, so a paragraph's height is an answer."; let height_after = |head_width: i32| { let mut h = Harness::new((400, 400)); let head = rect(Color::RED).width(head_width).add(&mut h.rsc); let text = wtext(paragraph).size(16).wrap(true).add(&mut h.rsc); h.set_root((head, text).span(Dir::RIGHT).width(rel(1.0))); let region = h.region(&text).unwrap(); (region.bot_right.y - region.top_left.y).to_f32() }; let (crowded, whole_row) = (height_after(300), height_after(0)); assert!(crowded > whole_row, "{crowded} against {whole_row}"); } /// The same reading through a pad: padding goes around what it pads and /// does not narrow what a fraction under it is a fraction of, so half of the /// window plus the padding is what the pad takes and where the next child /// starts. #[test] fn a_pad_puts_its_padding_around_a_fraction_of_the_whole_box() { let mut h = Harness::new((400, 100)); let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc); let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc); let tail = rect(Color::BLUE).width(100).add(&mut h.rsc); // Ruled to the window: a root reporting a fraction of it is otherwise // placed inside it by its own alignment, which is not what is under test. h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0))); assert_corners!(h, inner, (10, 10), (210, 90)); assert_corners!(h, padded, (0, 0), (220, 100)); assert_corners!(h, tail, (220, 0), (320, 100)); } #[test] fn a_span_ruled_across_itself_does_not_measure_its_children_there() { let mut h = Harness::new((400, 200)); let child = rect(Color::RED).height(40).add(&mut h.rsc); let span = (child,).span(Dir::RIGHT).height(rel(1.0)).add(&mut h.rsc); h.set_root(span); assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::rel(1.0)); } #[test] fn a_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).add(&mut h.rsc); h.set_root(span); assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::px(70.0)); } #[test] fn resizing_relays_out_against_the_new_output() { let mut h = Harness::new((400, 200)); let (left, right) = two_rects(&mut h); h.resize((800, 100)); assert!(h.needs_redraw()); h.frame(); assert_corners!(h, left, (0, 0), (100, 100)); assert_corners!(h, right, (100, 0), (800, 100)); } #[test] fn an_empty_widget_takes_a_share_of_a_span() { let mut h = Harness::new((400, 200)); let gap = ().add(&mut h.rsc); let right = rect(Color::BLUE).width(100).add(&mut h.rsc); h.set_root((gap, right).span(Dir::RIGHT)); assert_corners!(h, gap, (0, 0), (300, 200)); assert_corners!(h, right, (300, 0), (400, 200)); } #[test] fn a_child_drawn_twice_moves_once() { let mut h = Harness::new((400, 200)); // The span measures a child and then places it; listing it twice would // move it twice. The span's own fixed total is shorter than the window, // so the span is centred in it and everything under it carries that. let inner = rect(Color::BLUE).add(&mut h.rsc); let centered = inner.center().width(200).add(&mut h.rsc); let left = rect(Color::RED).width(100).add(&mut h.rsc); h.set_root((left, centered).span(Dir::RIGHT)); assert_corners!(h, inner, (150, 0), (350, 200)); h.set_len(left, Axis::X, 150); h.frame(); assert_corners!(h, inner, (175, 0), (375, 200)); } #[test] fn alignment_accepts_an_arbitrary_fraction_and_changes_at_runtime() { let mut h = Harness::new((400, 200)); let fixed = rect(Color::BLUE).sized((100, 100)).add(&mut h.rsc); h.rsc .widgets_mut() .set_alignment(fixed, Axis::X, AxisAlign::new(0.25)); h.rsc .widgets_mut() .set_alignment(fixed, Axis::Y, AxisAlign::NEG); h.set_root(fixed); assert_corners!(h, fixed, (75, 0), (175, 100)); h.rsc .widgets_mut() .set_alignment(fixed, Axis::X, AxisAlign::new(0.75)); h.frame(); assert_corners!(h, fixed, (225, 0), (325, 100)); } #[test] fn a_resize_lands_where_a_cold_start_would() { let build = |h: &mut Harness| { let para = wtext( "Wrapping shapes one source into as many lines as its container leaves room \ for, so the height of a paragraph is an answer rather than a setting.", ) .size(20) .wrap(true) .pad(16) .add(&mut h.rsc); let below = rect(Color::RED).add(&mut h.rsc); let root = (para, below).span(Dir::DOWN).pad(12); h.set_root(root); (para, below) }; let mut cold = Harness::new((900, 1200)); let (cold_para, cold_below) = build(&mut cold); let mut resized = Harness::new((1920, 1200)); let (para, below) = build(&mut resized); resized.resize((900, 1200)); resized.frame(); assert_eq!(resized.region(¶), cold.region(&cold_para), "paragraph"); assert_eq!(resized.region(&below), cold.region(&cold_below), "below"); } #[test] fn a_fixed_box_is_drawn_again_rather_than_stretched() { let mut h = Harness::new((400, 400)); // The panel fills a stack sized by its sibling, so it is first asked in // the whole box and then given the shorter one. Reusing it in that fixed // box afterwards would leave it whatever height it happened to have. let panel = rect(Color::BLUE).add(&mut h.rsc); let leaf = rect(Color::RED).height(100).add(&mut h.rsc); let stack = (panel, leaf) .stack() .size(StackSize::Child(1)) .add(&mut h.rsc); h.set_root(stack.align(Align::TOP)); assert_corners!(h, panel, (0, 0), (400, 100)); h.set_len(leaf, Axis::Y, 250); h.frame(); assert_corners!(h, panel, (0, 0), (400, 250)); } #[test] fn a_moved_subtree_takes_its_children_with_it() { let mut h = Harness::new((400, 400)); let first = rect(Color::RED).height(40).add(&mut h.rsc); let inner = rect(Color::BLUE).add(&mut h.rsc); let row = inner.pad(10).height(40).region_node().add(&mut h.rsc); // 80 of fixed rows in a 400 window, so the span takes 80 and sits in the // middle of what it was given. h.set_root((first, row).span(Dir::DOWN)); assert_corners!(h, inner, (10, 210), (390, 230)); h.set_len(first, Axis::Y, 80); h.frame(); // The row opted into one movable region, so its descendants follow one // entry rather than having their primitive regions rewritten. assert_corners!(h, inner, (10, 230), (390, 250)); } #[test] fn a_fixed_length_child_keeps_it_when_the_box_around_it_grows() { let mut h = Harness::new((400, 200)); let fixed = rect(Color::BLUE).width(50).add(&mut h.rsc); let leftover = rect(Color::GREEN).add(&mut h.rsc); let panel = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc); // Changing the bar's width is the only thing that changes the box the // panel and everything under it was drawn for. let bar = rect(Color::RED).width(100).add(&mut h.rsc); h.set_root((bar, panel).span(Dir::RIGHT)); assert_corners!(h, fixed, (100, 0), (150, 200)); assert_corners!(h, leftover, (150, 0), (400, 200)); h.set_len(bar, Axis::X, 200); h.frame(); // The panel's box is 100 shorter, so the fixed child is the same 50 wide // against its new start and the one taking what is left absorbs the change. assert_corners!(h, fixed, (200, 0), (250, 200)); assert_corners!(h, leftover, (250, 0), (400, 200)); } #[test] fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() { let mut h = Harness::new((400, 200)); // The row is 40 tall whatever happens, which used to make its drawing // impossible to take out of: recovering a fraction of a box needs a // relative extent, and it has none on that 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); // 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).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)); h.set_len(bar, Axis::X, 200); h.frame(); assert_corners!(h, inner, (210, 10), (390, 30)); } #[test] fn only_a_region_node_lengthens_the_chain_and_it_can_be_removed() { let mut h = Harness::new((400, 200)); let leaf = rect(Color::BLUE).add(&mut h.rsc); let buried = leaf.pad(4).pad(4).pad(4).pad(4).add(&mut h.rsc); let bar = rect(Color::RED).width(100).add(&mut h.rsc); h.set_root((bar, buried).span(Dir::RIGHT)); let move_idx = h.render.active[&leaf.id()].parent_move; assert_eq!(h.render.moves.depth(move_idx), 0, "the window is no entry"); h.rsc.widgets_mut().set_region_node(buried, true); h.frame(); let move_idx = h.render.active[&leaf.id()].parent_move; assert_eq!( h.render.moves.depth(move_idx), 1, "the opted-in widget's region alone" ); h.rsc.widgets_mut().set_region_node(buried, false); h.frame(); let move_idx = h.render.active[&leaf.id()].parent_move; assert_eq!(h.render.moves.depth(move_idx), 0); } /// A span that sizes from its children passes their `leftover` weight up /// than collapsing it to one share, so nesting divides the same space instead /// of re-dividing a share of it. #[test] fn nested_spans_divide_the_space_once_however_deep_the_nesting_is() { let mut h = Harness::new((400, 200)); let (a, b, c, d) = ( rect(Color::RED).add(&mut h.rsc), rect(Color::BLUE).add(&mut h.rsc), rect(Color::GREEN).add(&mut h.rsc), rect(Color::WHITE).add(&mut h.rsc), ); let left = (a, b).span(Dir::RIGHT).add(&mut h.rsc); let right = (c, d).span(Dir::RIGHT).add(&mut h.rsc); h.set_root((left, right).span(Dir::RIGHT)); for (i, id) in [a, b, c, d].into_iter().enumerate() { let x = i as f32 * 100.0; assert_corners!(h, id, (x, 0), (x + 100.0, 200)); } } /// The same space, unevenly nested: weights carried up mean a share is a /// share of the whole, not of whatever branch a widget happens to sit in. /// /// Each edge lands on the even division or one step below it, since a share /// is a fraction of the room and a truncating multiply gives up what that /// fraction does not divide. What stays exact is that each share starts /// where the last one ended and the row ends at its own edge. #[test] fn an_uneven_nesting_still_gives_every_share_the_same_length() { let mut h = Harness::new((400, 200)); let (a, b, c, d) = ( rect(Color::RED).add(&mut h.rsc), rect(Color::BLUE).add(&mut h.rsc), rect(Color::GREEN).add(&mut h.rsc), rect(Color::WHITE).add(&mut h.rsc), ); let one = (a,).span(Dir::RIGHT).add(&mut h.rsc); let three = (b, c, d).span(Dir::RIGHT).add(&mut h.rsc); h.set_root((one, three).span(Dir::RIGHT)); let mut start = Px::ZERO; for (i, id) in [a, b, c, d].into_iter().enumerate() { let got = h.region(&id).expect("widget drew nothing"); let even = Px::from_int((i as i32 + 1) * 100); assert_eq!(got.top_left, PxVec2::new(start, Px::ZERO), "share {i}"); assert_eq!(got.bot_right.y, Px::from_int(200), "share {i}"); assert!( got.bot_right.x == even || got.bot_right.x == even.next_down(), "share {i} ends at {:?}, not {even:?}", got.bot_right.x ); start = got.bot_right.x; } assert_eq!( start, Px::from_int(400), "the row stopped short of its edge" ); } /// However many ways a row is divided, the shares add up to the row: each /// one is the fixed parts before it plus a share of the room, rather than a /// step from where the last one ended, so the roundings do not accumulate /// along it. Chained, two hundred of them ended a step short of the edge. #[test] fn a_row_of_equal_shares_fills_it_exactly() { for n in [2usize, 3, 7, 64, 200] { let mut h = Harness::new((1000, 100)); let mut ids = Vec::new(); let mut kids: Vec = Vec::new(); for _ in 0..n { let kid = rect(Color::RED).add(&mut h.rsc); ids.push(kid.id()); kids.push(kid.add_strong(&mut h.rsc)); } let span = Span { children: kids, dir: Dir::RIGHT, gap: Px::ZERO, } .add(&mut h.rsc); h.set_root(span); h.frame(); for (i, id) in ids.iter().enumerate() { let at = h.region(id).expect("a share drew nothing").top_left.x; let want = Px::from_f32(1000.0 * (i as f32) / (n as f32)); assert!( (at - want).abs() <= Px::STEP, "{n} shares: the {i}th starts at {at:?}, not {want:?}" ); } let end = h.region(ids.last().unwrap()).unwrap().bot_right.x; assert_eq!(end, Px::from_int(1000), "{n} shares do not reach the edge"); } } /// Where the shader puts an edge: the fraction resolved against the window /// plus the pixel offset, taken to the boundary it composes to within half /// a step of. Kept in step with `snap_floor` in `prelude.wgsl`. fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) { let active = &h.render.active[&id]; let drawn = active.extent.within(&active.frame_abs); let region = h.render.moves.resolve(active.parent_move, drawn); let dim = h.size().axis(axis); let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor(); let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32()); let span = region.axis(axis); (edge(span.start), edge(span.end)) } fn hairline(h: &mut Harness, marks: &mut Vec) -> StrongWidget { let mark = rect(Color::RED).width(1).add_strong(&mut h.rsc); marks.push(mark.id()); mark } fn share(h: &mut Harness, inner: StrongWidget, ratio: f32) -> StrongWidget { h.set_len(&inner, Axis::X, LayoutLen::leftover(ratio)); inner } /// Shares in weights no binary fraction lands on, a padding on one branch /// and not the other, so an edge falls near an integer as often as it can. fn hairlines(h: &mut Harness, depth: usize, marks: &mut Vec) -> StrongWidget { let mut span = Span::empty(Dir::RIGHT); if depth == 0 { let left = rect(Color::BLUE).add_strong(&mut h.rsc); let left = share(h, left, 3.0); span.push(left); let mark = hairline(h, marks); span.push(mark); let right = rect(Color::BLUE).add_strong(&mut h.rsc); let right = share(h, right, 7.0); span.push(right); return span.add_strong(&mut h.rsc); } let first = hairlines(h, depth - 1, marks); let first = share(h, first, 3.0); span.push(first); let second = hairlines(h, depth - 1, marks); let second = Pad { padding: Padding { left: Px::from_int(3), right: Px::from_int(7), top: Px::ZERO, bottom: Px::ZERO, }, inner: second, } .add_strong(&mut h.rsc); let second = share(h, second, 5.0); span.push(second); span.add_strong(&mut h.rsc) } /// A one-pixel line is a pixel wherever it is drawn. Both edges of a fixed /// length share their box's fraction, so composing the chain moves them /// together and the shader's `floor` cannot round the pixel between them /// away -- only shift it. A separator that disappeared at one window size /// would be a defect no size comparison catches. #[test] fn a_one_pixel_line_keeps_its_pixel_through_a_chain() { let mut h = Harness::new((1920, 1200)); let mut marks = Vec::new(); let root = hairlines(&mut h, 4, &mut marks); h.state.set_root(root); h.frame(); assert_eq!(marks.len(), 16); for size in [(1920, 1200), (1919, 1201), (997, 1003), (1367, 733)] { h.resize(size); h.frame(); for mark in &marks { let (start, end) = drawn_edges(&h, *mark, Axis::X); assert_eq!(end - start, 1.0, "at {size:?}, mark {mark:?}"); } } } /// A span short of room takes it from its shares, which go to nothing and /// then to nothing wider; the fixed lengths between them keep their pixels. /// Collapsing those to make room would delete a separator the caller asked /// for, which is worse than overflowing. #[test] fn a_span_out_of_room_shrinks_its_shares_and_not_its_fixed_lengths() { let mut h = Harness::new((400, 20)); let mut marks = Vec::new(); let mut span = Span::empty(Dir::RIGHT); for _ in 0..3 { let share_of = rect(Color::BLUE).add_strong(&mut h.rsc); let share_of = share(&mut h, share_of, 1.0); span.push(share_of); let mark = hairline(&mut h, &mut marks); span.push(mark); } let root = span.add_strong(&mut h.rsc); h.state.set_root(root); h.frame(); for width in [400, 10, 3, 1] { h.resize((width, 20)); h.frame(); for mark in &marks { let (start, end) = drawn_edges(&h, *mark, Axis::X); assert_eq!(end - start, 1.0, "at {width} wide, mark {mark:?}"); } } } #[test] fn only_a_pure_leftover_child_disappears_when_nothing_is_left() { let mut h = Harness::new((100, 20)); let fixed = rect(Color::RED).width(100).add(&mut h.rsc); let leftover = rect(Color::BLUE).add(&mut h.rsc); h.set_root((fixed, leftover).span(Dir::RIGHT)); assert_corners!(h, fixed, (0, 0), (100, 20)); assert_eq!(h.region(&leftover), None); // An undrawn child remains a dependency of the span, so making room for // it draws it without rebuilding the tree. h.set_len(fixed, Axis::X, 60); h.frame(); assert_corners!(h, leftover, (60, 0), (100, 20)); let mut h = Harness::new((100, 20)); let fixed = rect(Color::RED).width(100).add(&mut h.rsc); let mixed = rect(Color::BLUE) .width(LayoutLen::px(20) + LayoutLen::LEFTOVER) .add(&mut h.rsc); h.set_root((fixed, mixed).span(Dir::RIGHT)); // Pixels and fractions still overflow; only a child whose entire length // is leftover is omitted. assert_corners!(h, mixed, (100, 0), (120, 20)); } #[test] fn leftover_children_disappear_at_the_exact_fixed_content_boundary() { let mut h = Harness::new((100, 100)); let first = rect(Color::RED).height(90).add(&mut h.rsc); let a = rect(Color::GREEN).add(&mut h.rsc); let b = rect(Color::BLUE).add(&mut h.rsc); let inner = (a, b).span(Dir::DOWN).gap(4).add(&mut h.rsc); h.set_root((first, inner).span(Dir::DOWN)); assert!(h.region(&a).is_some()); assert!(h.region(&b).is_some()); h.set_len(first, Axis::Y, 96.0); h.frame(); assert!(h.region(&a).is_none()); assert!(h.region(&b).is_none()); } /// **A stack child smaller than the stack sits where its own alignment /// says.** `Stack` gives every child the box its sizing child defines and /// used to force the near edge on all of them; that override is owed only to /// the sizing child, which has already placed its own content in the box the /// stack derived from its answer. Every other child is handed a box that owes /// nothing to it, so where it sits in one bigger than itself is its own /// business -- and with the override it could not be aligned at all, which is /// what moved the `tabs` example's counters to the wrong corner. #[test] fn a_stack_child_smaller_than_the_stack_keeps_its_own_alignment() { let mut h = Harness::new((400, 200)); let big = rect(Color::BLUE).add(&mut h.rsc); let small = rect(Color::RED).sized((50, 50)).add(&mut h.rsc); h.rsc .widgets_mut() .set_alignment(small.id(), Axis::X, AxisAlign::POS); let (a, b) = (big.add_strong(&mut h.rsc), small.add_strong(&mut h.rsc)); let children: Vec = vec![a, b]; h.set_root(Stack { children, size: StackSize::Default, }); assert_corners!(h, big, (0, 0), (400, 200)); // The far edge on X because it asked for it, the middle on Y because // 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:?}" ); } } } #[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)); } #[test] fn a_fixed_child_is_centered_in_its_wrappers_share() { let mut h = Harness::new((600, 300)); let leaf = rect(Color::RED).sized((100, 100)).center().add(&mut h.rsc); let wrapper = leaf .wrapper() .width(leftover(2)) .height(rel(1.0)) .add(&mut h.rsc); let other = rect(Color::BLUE).width(200).add(&mut h.rsc); h.set_root((other, wrapper).span(Dir::RIGHT)); assert_corners!(h, wrapper, (200, 0), (600, 300)); assert_corners!(h, leaf, (350, 100), (450, 200)); h.resize((900, 400)); h.frame(); assert_corners!(h, wrapper, (200, 0), (900, 400)); assert_corners!(h, leaf, (500, 150), (600, 250)); }