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