Decide layout on the grid end to end, and delete the tolerance

`Px` and `PxVec2` reach the last places a pixel was a float: the window, the
box a widget reads, the box it is compared against, and `PixelRegion`. A
pointer, a wheel notch and a shaped glyph advance still arrive as floats,
and each is put on the grid where it arrives.

`Holds` is an interval of `Px`. `HOLDS_EPSILON_PX` is gone with the
`exact`/tolerant split it existed for: `at` is the length a widget read, an
open end is the next step along, and `same_px` is equality. `Span`'s margin
from `5ed9e87` goes too -- the box a parent hands back and the sum of what
its children asked for are counts of the same step, so the boundary decides
the same way from either side.

Three things had to be true for that, and were not:

`Holds::through` inverts `px + rel * box`, which rounds -- so a part of a
given length came from a range of boxes, and inverting the length alone gave
a point that need not contain the box the part was drawn in. It now maps the
half step either side, and one more for a length composed down the chain
against the same length measured against the window.

`RegionRemap` translates when a box only moved, rather than dividing to find
each part's fraction and multiplying to place it again. Two roundings landed
a step from where growing the tree that way does; a move is exact on a grid,
which is the whole reason `tests/drift.rs` was written.

A pixel is `1/1024` rather than `1/64`. At `1/64` the residue of a length
reached two ways was one step, and one step was 0.016 px -- enough to move
a box. `PX_SHIFT` and `REL_SHIFT` are the only statement of the grid now,
and the shader's copy is prepended from them rather than written twice.

Checked: fmt, clippy, 102 tests, 100 generated seeds in 75 s, all five
shrinker cases at 300 seeds, and `tabs`, `view`, `minimal`, `text` and
`random` byte-identical at 1920x1200.

What the fuzzers ask for is now a step, not a twentieth of a pixel: the
shrinker's five cases agree within one (`resize` exactly), and the oracle's
two-operation cases within two. The residue is a single rounding either way
-- it scales with the grid rather than accumulating, which is why it is a
thousandth of a pixel now. Closing it means one way of asking how long a box
is, rather than a chain composed down and a length measured against the
window; that is a bigger change than this one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 02:56:48 -04:00
1 parent bd6de71a55
commit 39e4ca20e6
24 files changed
+420 -194

No files matched your search

+74 -48
View File
@@ -1,4 +1,4 @@
use crate::{Rel, UiScalar};
use crate::{Px, REL_SHIFT, UiScalar, fixed::div_toward, fixed::narrow};
use std::ops::RangeInclusive;
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
@@ -7,48 +7,33 @@ use std::ops::RangeInclusive;
/// for every length; one that does holds for the one it read unless it says
/// otherwise, and a parent holds for whatever keeps every child it asked
/// about or drew inside its own range.
#[derive(Clone, Copy, Debug, PartialEq)]
///
/// The ends are lengths on the grid rather than floats with a tolerance
/// around them: a box offered back at the length a widget reported comes back
/// as the same number, so a range means what it says. What widening there is
/// belongs to [`Self::through`], which has a rounding to undo, and is derived
/// from that rounding rather than chosen.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Holds {
pub lo: f32,
pub hi: f32,
pub lo: Px,
pub hi: Px,
}
/// How far outside a range a length may fall and still be inside it: a box
/// offered back to a widget at the length it reported comes back through the
/// chain a few bits off, and nothing a reader could see lives in that gap.
pub const HOLDS_EPSILON_PX: f32 = 0.05;
impl Holds {
pub const ANY: Self = Self {
lo: f32::NEG_INFINITY,
hi: f32::INFINITY,
lo: Px::MIN,
hi: Px::MAX,
};
pub const fn at(len: f32) -> Self {
Self::tolerant(len, len)
pub const fn at(len: Px) -> Self {
Self { lo: len, hi: len }
}
const fn tolerant(lo: f32, hi: f32) -> Self {
Self {
lo: lo - HOLDS_EPSILON_PX,
hi: hi + HOLDS_EPSILON_PX,
}
pub const fn contains(&self, len: Px) -> bool {
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
}
/// A range whose endpoints are exact, for a widget decision with a hard
/// boundary rather than an accumulated coordinate-rounding difference.
pub fn exact(range: RangeInclusive<f32>) -> Self {
Self {
lo: *range.start(),
hi: *range.end(),
}
}
pub fn contains(&self, len: f32) -> bool {
len >= self.lo && len <= self.hi
}
pub fn and(self, other: Self) -> Self {
pub const fn and(self, other: Self) -> Self {
Self {
lo: self.lo.max(other.lo),
hi: self.hi.min(other.hi),
@@ -58,41 +43,82 @@ impl Holds {
/// What a box has to be for a part of it, `len` of the box long, to stay
/// in this range. A part with no relative extent is a fixed length: it
/// was drawn at that length and any box keeps it there.
pub fn through(self, len: UiScalar) -> Self {
if len.rel == Rel::ZERO {
///
/// The way in is `px + rel * box` taken to the nearest step, so a part
/// of exactly `lo` came from anything within half a step of it and the
/// answer is an interval even where this range is one length. Inverting
/// the length alone instead gives a point that need not even contain the
/// box the part was drawn in, which is a range excluding the drawing it
/// was made for.
pub const fn through(self, len: UiScalar) -> Self {
let rel = len.rel.raw() as i64;
if rel == 0 {
return Self::ANY;
}
let (rel, px) = (len.rel.to_f32(), len.px.to_f32());
let a = (self.lo - px) / rel;
let b = (self.hi - px) / rel;
// Three half steps either side -- one for the rounding on the way
// in, two for the difference between a length composed down the
// chain and the same length measured against the window -- and half
// of what a `Rel` counts in, to divide by the fraction. Exact until
// the division takes it back to the grid.
let px = len.px.raw() as i64;
let half_rel = REL_SHIFT - 1;
let lo = ((self.lo.raw() as i64 - px) * 2 - 3) << half_rel;
let hi = ((self.hi.raw() as i64 - px) * 2 + 3) << half_rel;
let (a, b) = (div_toward(lo, rel, true), div_toward(hi, rel, false));
let (c, d) = (div_toward(lo, rel, false), div_toward(hi, rel, true));
match rel > 0 {
true => Self::raws(a, b),
false => Self::raws(d, c),
}
}
const fn raws(lo: i64, hi: i64) -> Self {
Self {
lo: a.min(b),
hi: a.max(b),
lo: Px::from_raw(narrow(lo)),
hi: Px::from_raw(narrow(hi)),
}
}
}
impl From<RangeInclusive<f32>> for Holds {
fn from(range: RangeInclusive<f32>) -> Self {
Self::tolerant(*range.start(), *range.end())
impl From<RangeInclusive<Px>> for Holds {
fn from(range: RangeInclusive<Px>) -> Self {
Self {
lo: *range.start(),
hi: *range.end(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Rel;
#[test]
fn through_reverses_a_range_for_a_negative_fraction() {
let holds = Holds::from(20.0..=40.0).through(UiScalar::new(-0.5, 10.0));
assert!((holds.lo - -60.1).abs() < 0.001);
assert!((holds.hi - -19.9).abs() < 0.001);
// `10 - box / 2` is between 20 and 40 for boxes from -60 to -20.
let part = UiScalar::from_parts(Rel::from_f32(-0.5), Px::from_int(10));
let holds = Holds::from(Px::from_int(20)..=Px::from_int(40)).through(part);
assert!(holds.contains(Px::from_int(-60)) && holds.contains(Px::from_int(-20)));
assert!(!holds.contains(Px::from_int(-61)) && !holds.contains(Px::from_int(-19)));
}
/// The case the widening is for: a part that holds only for the length it
/// was drawn at has to hold for the box it was drawn in, and a third of a
/// box is not a whole number of steps.
#[test]
fn a_part_maps_back_onto_the_box_it_was_measured_in() {
let part = UiScalar::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
for box_len in (440..460).map(Px::from_int) {
let holds = Holds::at(part.to_px(box_len)).through(part);
assert!(holds.contains(box_len), "{box_len:?} left out by {holds:?}");
}
}
#[test]
fn an_exact_open_boundary_does_not_admit_the_boundary() {
let boundary = 10.0_f32;
let above = Holds::exact(boundary.next_up()..=f32::INFINITY);
fn a_boundary_the_next_step_along_does_not_admit_it() {
let boundary = Px::from_int(10);
let above = Holds::from(boundary.next_up()..=Px::MAX);
assert!(!above.contains(boundary));
assert!(above.contains(boundary.next_up()));
}