Check that a one-pixel line keeps its pixel through the chain
Both edges of a fixed length share their box's fraction, so composing the chain moves them together and the shader's floor can shift the pixel between them but not round it away. The second test is the case that makes the first one worth having: a span short of room takes it from its shares, which go to nothing and then past it, and never from the fixed lengths between them. Expressing the same line as a fraction of the output fails both, which is what the tests are there to keep visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f61e8936f1
commit
4063635f39
1 file changed
+120
+120
@@ -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));
|
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<WidgetId>) -> 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<WidgetId>) -> 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:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user