Which end of the answer each bound comes from is known from the sign of the fraction before dividing; taking the min and max of four divisions asked the question twice. A division is the most expensive thing in that function and it runs per child per axis. `many` 0.283 ms a frame to 0.278. Small, and strictly less work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
127 lines
4.9 KiB
Rust
127 lines
4.9 KiB
Rust
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:
|
|
/// give the widget any box in this range and it draws the same thing and
|
|
/// reports the same size. A widget that never reads its box in pixels holds
|
|
/// 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.
|
|
///
|
|
/// 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: Px,
|
|
pub hi: Px,
|
|
}
|
|
|
|
impl Holds {
|
|
pub const ANY: Self = Self {
|
|
lo: Px::MIN,
|
|
hi: Px::MAX,
|
|
};
|
|
|
|
pub const fn at(len: Px) -> Self {
|
|
Self { lo: len, hi: len }
|
|
}
|
|
|
|
pub const fn contains(&self, len: Px) -> bool {
|
|
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
|
|
}
|
|
|
|
pub const fn and(self, other: Self) -> Self {
|
|
Self {
|
|
lo: self.lo.max(other.lo),
|
|
hi: self.hi.min(other.hi),
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
///
|
|
/// 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;
|
|
}
|
|
// 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;
|
|
// Dividing by a negative turns the ends around, so which end each
|
|
// bound comes from is decided before dividing rather than by taking
|
|
// the min and max of four divisions.
|
|
match rel > 0 {
|
|
true => Self::raws(div_toward(lo, rel, true), div_toward(hi, rel, false)),
|
|
false => Self::raws(div_toward(hi, rel, true), div_toward(lo, rel, false)),
|
|
}
|
|
}
|
|
|
|
const fn raws(lo: i64, hi: i64) -> Self {
|
|
Self {
|
|
lo: Px::from_raw(narrow(lo)),
|
|
hi: Px::from_raw(narrow(hi)),
|
|
}
|
|
}
|
|
}
|
|
|
|
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() {
|
|
// `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 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()));
|
|
}
|
|
}
|