179 lines
7.1 KiB
Rust
179 lines
7.1 KiB
Rust
use crate::{Len, Px, REL_SHIFT, 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. The one place a range
|
|
/// is wider than the length it came from is [`Self::through`], and what it is
|
|
/// wider by is the floor that inverting a fraction undoes.
|
|
#[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: the exact preimage of `px + floor(rel * box)`, which is
|
|
/// the one way a box in pixels is reached. A part with no relative extent
|
|
/// is a fixed length -- it was drawn at that length and any box keeps it
|
|
/// there.
|
|
///
|
|
/// The answer is an interval even where this range is a single length,
|
|
/// because the multiply on the way in drops to the step below and many
|
|
/// boxes therefore give one length. That is a floor rather than an
|
|
/// allowance: inverting it is two divisions and nothing else, and the
|
|
/// whole of a box maps back to itself.
|
|
pub const fn through(self, len: Len) -> Self {
|
|
if self.lo.raw() == Px::MIN.raw() && self.hi.raw() == Px::MAX.raw() {
|
|
return Self::ANY;
|
|
}
|
|
let rel = len.rel.raw() as i64;
|
|
if rel == 0 {
|
|
return Self::ANY;
|
|
}
|
|
let px = len.px.raw() as i64;
|
|
// `floor(rel * box) >= lo - px` is `rel * box >= (lo - px) << REL`, and
|
|
// `floor(rel * box) <= hi - px` is `rel * box < (hi - px + 1) << REL`.
|
|
let lo = (self.lo.raw() as i64 - px) << REL_SHIFT;
|
|
let hi = (((self.hi.raw() as i64 - px) + 1) << REL_SHIFT) - 1;
|
|
// Dividing by a negative fraction turns the ends around, so which
|
|
// bound each 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 an_unrestricted_range_stays_unrestricted_through_any_length() {
|
|
for rel in [-2.0, -0.5, 0.0, 0.5, 1.0, 2.0] {
|
|
for px in [-8, 0, 8] {
|
|
let len = Len::from_parts(Rel::from_f32(rel), Px::from_int(px));
|
|
assert_eq!(Holds::ANY.through(len), Holds::ANY);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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 = Len::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 = Len::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:?}");
|
|
}
|
|
}
|
|
|
|
/// A widget handed the whole of its parent's box, with or without pixels
|
|
/// taken off it, has no fraction to invert: multiplying by one is exact
|
|
/// and taking the pixels off again is too, so the box maps back to
|
|
/// itself. Allowing for anything here compounded a step a level down a
|
|
/// chain of widgets each taking the whole of its parent.
|
|
#[test]
|
|
fn the_whole_of_a_box_maps_back_to_itself() {
|
|
let at = Px::from_int(956);
|
|
assert_eq!(Holds::at(at).through(Len::FULL), Holds::at(at));
|
|
let less_eight = Len::from_parts(Rel::ONE, Px::from_int(-8));
|
|
assert_eq!(
|
|
Holds::at(at).through(less_eight),
|
|
Holds::at(at + Px::from_int(8))
|
|
);
|
|
}
|
|
|
|
/// The range is the exact preimage at both ends, so a box one step
|
|
/// outside it really does give a length outside this range. What a wider
|
|
/// range costs is a drawing reused where it does not hold.
|
|
#[test]
|
|
fn a_box_one_step_outside_the_range_is_outside_it() {
|
|
let part = Len::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
|
|
let at = Px::from_int(300);
|
|
let holds = Holds::at(at).through(part);
|
|
for inside in [holds.lo, holds.hi] {
|
|
assert_eq!(part.to_px(inside), at, "{inside:?} left out of {holds:?}");
|
|
}
|
|
for outside in [holds.lo.next_down(), holds.hi.next_up()] {
|
|
assert_ne!(part.to_px(outside), at, "{outside:?} admitted by {holds:?}");
|
|
}
|
|
}
|
|
|
|
/// A truncating multiply only ever drops, so the step it needs allowing
|
|
/// for on the way in belongs at the top of the range and not the bottom.
|
|
#[test]
|
|
fn a_fraction_widens_further_up_than_down() {
|
|
let half = Len::from_parts(Rel::from_f32(0.5), Px::ZERO);
|
|
let holds = Holds::at(Px::from_int(100)).through(half);
|
|
let box_len = Px::from_int(200);
|
|
assert!(holds.hi - box_len > box_len - holds.lo, "{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()));
|
|
}
|
|
}
|