Files
iris/core/src/ui/holds.rs
T
iris-aiandClaude Opus 5 f11f5f4825 Divide twice in a range's inverse, not four times
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>
2026-09-16 04:06:46 -04:00

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()));
}
}