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> for Holds { fn from(range: RangeInclusive) -> 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())); } }