Hold a clipping widget to its box, and check that it is
`Scroll` reports `LEFTOVER` on both axes because it clips its content to its box: it can neither take less of one nor honestly ask for more. `Masked` is the other widget that clips and was passing its inner's size up, so a mask over something taller than its box asked to be placed at the length it had just cut off. It reports its box now, for the same reason. The `debug_assert` the handoff has been asking for is the one that would have caught both, narrowed to what is actually true: a widget that set a mask this draw has to report inside the box it drew in. Reported as "does not exceed the box" it fires on ordinary overflow instead -- measured, a hundred fuzzer trees produce thousands of them, every one a text too tall for the box it was offered, which is what a text is meant to say. `tests/cases/scroll.rs` has a clipping widget that reports its content, to show the assertion catches it. Checked: fmt, clippy, 104 tests, all five shrinker cases at 300 seeds, 100 generated seeds, five examples byte-identical at 1920x1200. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bdab55824f
commit
95fb4f962c
3 files changed
+49
-1
No files matched your search
@@ -366,6 +366,16 @@ impl UiRenderState {
|
|||||||
x: rules.x.apply(size.x),
|
x: rules.x.apply(size.x),
|
||||||
y: rules.y.apply(size.y),
|
y: rules.y.apply(size.y),
|
||||||
};
|
};
|
||||||
|
// A widget that clipped its contents to its box drew nothing outside
|
||||||
|
// it, so reporting more than the box asks to be placed at a length it
|
||||||
|
// does not occupy -- and its parent would place the part it cut off.
|
||||||
|
// Overflowing is otherwise ordinary: a text too tall for the box it
|
||||||
|
// was offered reports the height it needs.
|
||||||
|
debug_assert!(
|
||||||
|
mask == info.mask || AXES.into_iter().all(|axis| within_box(size, px, axis)),
|
||||||
|
"'{}' ({id:?}) clips to {px:?} and reports {size}",
|
||||||
|
rsc.widgets().label(id),
|
||||||
|
);
|
||||||
let holds = [own[0].and(under[0]), own[1].and(under[1])];
|
let holds = [own[0].and(under[0]), own[1].and(under[1])];
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds[0].contains(px.x) && holds[1].contains(px.y),
|
holds[0].contains(px.x) && holds[1].contains(px.y),
|
||||||
@@ -1011,6 +1021,15 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether what a widget reports along `axis` is inside the box it drew in.
|
||||||
|
/// A share is a length only to whoever divides one, so it is not a claim
|
||||||
|
/// about this box and cannot exceed it.
|
||||||
|
fn within_box(size: Size, px: PxVec2, axis: Axis) -> bool {
|
||||||
|
let len = size.axis(axis);
|
||||||
|
let box_len = px.axis(axis);
|
||||||
|
len.leftover != Weight::ZERO || box_len.mul(len.rel) + len.px <= box_len
|
||||||
|
}
|
||||||
|
|
||||||
/// The same box is the same number of steps, both of these being lengths on
|
/// The same box is the same number of steps, both of these being lengths on
|
||||||
/// the grid rather than floats to be compared for nearness.
|
/// the grid rather than floats to be compared for nearness.
|
||||||
fn same_px(a: PxVec2, b: PxVec2) -> bool {
|
fn same_px(a: PxVec2, b: PxVec2) -> bool {
|
||||||
|
|||||||
+7
-1
@@ -7,6 +7,12 @@ pub struct Masked {
|
|||||||
impl Widget for Masked {
|
impl Widget for Masked {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.set_mask(painter.region());
|
painter.set_mask(painter.region());
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner);
|
||||||
|
// What it occupies is its box, on both axes, for the reason `Scroll`
|
||||||
|
// reports the same: it clips what is inside to that box, so it can
|
||||||
|
// neither take less of one nor honestly ask for more. Passing the
|
||||||
|
// inner size up instead asks to be placed at a length it does not
|
||||||
|
// draw, and the framework would place the drawing it clipped away.
|
||||||
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,3 +59,26 @@ fn a_wheel_scrolls_the_content_and_stops_at_its_end() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
assert_corners!(h, top, (0, 0), (400, 200));
|
assert_corners!(h, top, (0, 0), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A widget that clips to its box may not report more than the box: its
|
||||||
|
/// parent would place the part it cut off, and the framework would put a
|
||||||
|
/// drawing longer than its box somewhere. `Masked` is the second of these
|
||||||
|
/// after `Scroll`, and the assertion in `draw_at` is what says so.
|
||||||
|
#[test]
|
||||||
|
#[should_panic = "clips to"]
|
||||||
|
fn a_clipping_widget_reporting_more_than_its_box_is_caught() {
|
||||||
|
struct Clipper(StrongWidget);
|
||||||
|
|
||||||
|
impl Widget for Clipper {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
painter.set_mask(painter.region());
|
||||||
|
painter.widget(&self.0).size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut h = Harness::new((100, 100));
|
||||||
|
let tall = rect(Color::RED).height(400).add_strong(&mut h.rsc);
|
||||||
|
let clipper = Clipper(tall).add(&mut h.rsc);
|
||||||
|
h.set_root(clipper);
|
||||||
|
h.frame();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user