diff --git a/tests/layout.rs b/tests/layout.rs index 008c735..1bb3002 100644 --- a/tests/layout.rs +++ b/tests/layout.rs @@ -229,3 +229,123 @@ fn an_uneven_nesting_still_gives_every_share_the_same_length() { assert_corners!(h, id, (x, 0), (x + 100.0, 200)); } } + +/// Where the shader puts an edge: the two parts of a scalar are floored +/// apart, so a fraction and a pixel offset snap independently. +fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) { + let active = &h.render.active[&id]; + let region = h.render.moves.resolve(active.parent_move, active.region); + let dim = h.size().axis(axis); + let edge = |s: UiScalar| (s.rel * dim).floor() + s.px.floor(); + let span = region.axis(axis); + (edge(span.start), edge(span.end)) +} + +fn hairline(h: &mut Harness, marks: &mut Vec) -> StrongWidget { + let inner = rect(Color::RED).add_strong(&mut h.rsc); + let mark = SetSize { + inner, + x: Some(Len::px(1.0)), + y: None, + } + .add_strong(&mut h.rsc); + marks.push(mark.id()); + mark +} + +fn share(h: &mut Harness, inner: StrongWidget, ratio: f32) -> StrongWidget { + SetSize { + inner, + x: Some(Len::rest(ratio)), + y: None, + } + .add_strong(&mut h.rsc) +} + +/// 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: 3.0, + right: 7.0, + top: 0.0, + bottom: 0.0, + }, + 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:?}"); + } + } +}