use crate::{UiNum, util::Vec2}; use std::{ fmt::{Debug, Display, Formatter}, ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign}, }; /// A number held as a whole count of `1 / 2^SHIFT`. /// /// Layout reaches one place by more than one route -- a box composed down the /// chain, and the same box summed from what its children asked for -- and has /// to decide whether the two are the same place. In floats they land a few /// bits apart, which is a defect wherever the answer changes what is drawn /// rather than where. Here adding and subtracting are exact and only a /// multiply or a conversion rounds, back onto the same steps, so two routes /// that come within half a step land on one number and everything downstream /// compares for equality instead of for nearness. /// /// `SHIFT` is the number of fractional bits, which is what makes the steps /// divide a whole number: a power of two also converts to `f32` without /// rounding while the value fits in its mantissa. #[repr(transparent)] #[derive( Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, bytemuck::Pod, bytemuck::Zeroable, )] pub struct Fixed(i32); /// A length or a coordinate in pixels, to a sixty-fourth. Finer than anything /// a display can show, and exact in `f32` up to 262,144 px, which is what lets /// the same number reach the GPU. pub type Px = Fixed; /// How many bits of a pixel a [`Px`] keeps. One place, because [`PxVec2`] /// and the shader's own decoding are the same grid or nothing lines up. pub const PX_SHIFT: u32 = 10; /// A share of what a box has left over, which is a weight beside its /// siblings rather than a fraction of anything: a list divides its room by /// the total of these, so the range has to hold a whole list's worth and the /// precision only has to tell two weights apart. pub type Weight = Fixed<16>; /// A fraction of a box. Twenty-four bits of it, which matches `f32` around a /// half and beats it above one -- where anchors actually sit -- and leaves /// +/-128 of range, enough to sum a hundred children each asking for a whole /// box. A `leftover` weight is not one of these: it is a share of what is /// left rather than a fraction of anything, and it sums over a whole list. pub type Rel = Fixed; /// How many bits of a box a [`Rel`] keeps, beside [`PX_SHIFT`] and for the /// same reason. pub const REL_SHIFT: u32 = 24; impl Fixed { pub const ZERO: Self = Self(0); pub const ONE: Self = Self::one(); /// The gap between neighbouring values, which is also how far apart two /// numbers can be and still mean the same place. pub const STEP: Self = Self(1); /// Also what stands in for an unbounded end, since arithmetic saturates /// here rather than wrapping past it. pub const MIN: Self = Self(i32::MIN); pub const MAX: Self = Self(i32::MAX); const fn one() -> Self { assert!(SHIFT < 31, "a Fixed needs a bit for the whole part"); Self(1 << SHIFT) } pub const fn from_raw(raw: i32) -> Self { Self(raw) } /// The count of steps, for a caller that needs the representation rather /// than the number. pub const fn raw(self) -> i32 { self.0 } pub const fn from_int(v: i32) -> Self { Self(v.saturating_mul(Self::one().0)) } /// Rounds to the nearest step, and saturates rather than wrapping. A NaN /// has no nearest step and becomes zero, which is a caller's mistake /// rather than a value worth carrying. /// /// Half-away is written out rather than called through `f32::round`, /// which is not `const`: a layout constant has to stay a constant. pub const fn from_f32(v: f32) -> Self { debug_assert!(!v.is_nan(), "a NaN has no place on the grid"); let scaled = v * Self::one().0 as f32; // Above 2^23 an `f32` has no fractional part left to round, and // adding a half there rounds the number itself up instead. The cast // saturates at both ends and sends NaN to zero, which is the // behaviour wanted at both. const WHOLE: f32 = (1 << 23) as f32; Self(match (scaled >= WHOLE, scaled <= -WHOLE, scaled < 0.0) { (true, _, _) | (_, true, _) => scaled as i32, (_, _, true) => (scaled - 0.5) as i32, _ => (scaled + 0.5) as i32, }) } /// From a number as it is written in source -- `16`, `1.5` -- which is /// the other place a value enters the grid. pub fn from_num(v: impl UiNum) -> Self { Self::from_f32(v.to_f32()) } pub const fn to_f32(self) -> f32 { self.0 as f32 / Self::one().0 as f32 } /// The same value on another grid, rounded where the new one is coarser. pub const fn to_scale(self) -> Fixed { Fixed(match TO >= SHIFT { true => narrow((self.0 as i64) << (TO - SHIFT)), false => narrow(shift_round(self.0 as i64, SHIFT - TO)), }) } pub const fn add(self, rhs: Self) -> Self { Self(self.0.saturating_add(rhs.0)) } pub const fn sub(self, rhs: Self) -> Self { Self(self.0.saturating_sub(rhs.0)) } pub const fn neg(self) -> Self { Self(self.0.saturating_neg()) } /// Scaled by a number on any grid, which is how a length takes a fraction /// of itself and keeps being a length: the product is measured in the /// receiver's steps. pub const fn mul(self, by: Fixed) -> Self { Self(narrow(shift_round(self.0 as i64 * by.0 as i64, BY))) } /// Repeated a whole number of times, which no grid rounds. pub const fn mul_int(self, by: i32) -> Self { Self(narrow(self.0 as i64 * by as i64)) } /// Divided into a whole number of parts, rounded to the nearest step. pub const fn div_int(self, by: i32) -> Self { debug_assert!(by != 0, "no part of nothing"); if by == 0 { return Self::ZERO; } Self(narrow(div_round(self.0 as i64, by as i64))) } /// Divided by a number on any grid. A zero divisor is a caller bug -- a /// box of no length has no fraction of itself -- and saturates so that a /// release build lays out something absurd rather than dying. pub const fn div(self, by: Fixed) -> Self { debug_assert!(by.0 != 0, "dividing by a length of zero"); if by.0 == 0 { return match self.0 < 0 { true => Self::MIN, false => Self::MAX, }; } Self(narrow(div_round((self.0 as i64) << BY, by.0 as i64))) } /// `num / den` on *this* grid rather than on theirs, for weights coarser /// than the share they divide. pub const fn ratio(num: Fixed, den: Fixed) -> Self { debug_assert!(den.0 != 0, "no part of a whole of nothing"); if den.0 == 0 { return Self::ZERO; } Self(narrow(div_round((num.0 as i64) << SHIFT, den.0 as i64))) } /// `from` and `to` a fraction of the way apart, the fraction being the /// receiver -- the argument order [`crate::util::LerpUtil`] already uses. pub const fn lerp(self, from: Fixed, to: Fixed) -> Fixed { from.add(to.sub(from).mul(self)) } pub const fn min(self, other: Self) -> Self { match self.0 < other.0 { true => self, false => other, } } pub const fn max(self, other: Self) -> Self { match self.0 > other.0 { true => self, false => other, } } pub const fn abs(self) -> Self { Self(self.0.saturating_abs()) } pub const fn clamp(self, lo: Self, hi: Self) -> Self { debug_assert!(lo.0 <= hi.0, "an empty clamp has no answer"); self.max(lo).min(hi) } /// The next value along, for an interval that must not admit its own /// boundary. The step is the whole gap, so there is nothing to exclude /// between this and the boundary itself. pub const fn next_up(self) -> Self { Self(self.0.saturating_add(1)) } pub const fn next_down(self) -> Self { Self(self.0.saturating_sub(1)) } } /// Back to a single step, rounding halves away from zero so that a value and /// its negation round to the same distance. const fn shift_round(v: i64, bits: u32) -> i64 { let half = (1i64 << bits) >> 1; match v < 0 { true => -((-v + half) >> bits), false => (v + half) >> bits, } } const fn div_round(num: i64, den: i64) -> i64 { let (q, rem) = (num / den, num % den); match rem.unsigned_abs() * 2 >= den.unsigned_abs() { true => match (num < 0) == (den < 0) { true => q + 1, false => q - 1, }, false => q, } } /// Toward positive infinity when `up`, toward negative infinity otherwise. pub(crate) const fn div_toward(num: i64, den: i64, up: bool) -> i64 { let (q, rem) = (num / den, num % den); if rem == 0 { return q; } match (rem < 0) == (den < 0) { true => q + up as i64, false => q - !up as i64, } } pub(crate) const fn narrow(v: i64) -> i32 { if v > i32::MAX as i64 { return i32::MAX; } if v < i32::MIN as i64 { return i32::MIN; } v as i32 } const impl Add for Fixed { type Output = Self; fn add(self, rhs: Self) -> Self { Fixed::add(self, rhs) } } const impl Sub for Fixed { type Output = Self; fn sub(self, rhs: Self) -> Self { Fixed::sub(self, rhs) } } const impl Neg for Fixed { type Output = Self; fn neg(self) -> Self { Fixed::neg(self) } } const impl AddAssign for Fixed { fn add_assign(&mut self, rhs: Self) { *self = Fixed::add(*self, rhs); } } const impl SubAssign for Fixed { fn sub_assign(&mut self, rhs: Self) { *self = Fixed::sub(*self, rhs); } } const impl Mul> for Fixed { type Output = Self; fn mul(self, rhs: Fixed) -> Self { Fixed::mul(self, rhs) } } const impl Div> for Fixed { type Output = Self; fn div(self, rhs: Fixed) -> Self { Fixed::div(self, rhs) } } impl Display for Fixed { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.to_f32(), f) } } /// Prints the number rather than the count of steps: a failing layout test /// reports boxes, and `1126` is not a height anybody can read. impl Debug for Fixed { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.to_f32(), f) } } /// Two of them, for the places a size or a position needs both axes: a /// window, a box in pixels, a pointer. Held apart from [`crate::util::Vec2`] /// because that one is what the GPU and the platform speak. #[repr(C)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct FixedVec2 { pub x: Fixed, pub y: Fixed, } pub type PxVec2 = FixedVec2; impl FixedVec2 { pub const ZERO: Self = Self::splat(Fixed::ZERO); pub const fn new(x: Fixed, y: Fixed) -> Self { Self { x, y } } pub const fn splat(v: Fixed) -> Self { Self { x: v, y: v } } pub fn from_f32(v: Vec2) -> Self { Self::new(Fixed::from_f32(v.x), Fixed::from_f32(v.y)) } pub fn to_f32(self) -> Vec2 { Vec2::new(self.x.to_f32(), self.y.to_f32()) } pub const fn div_int(self, by: i32) -> Self { Self::new(self.x.div_int(by), self.y.div_int(by)) } pub const fn min(self, other: Self) -> Self { Self::new(self.x.min(other.x), self.y.min(other.y)) } pub const fn max(self, other: Self) -> Self { Self::new(self.x.max(other.x), self.y.max(other.y)) } } // `impl_op!` names one concrete type, and this one is generic. const impl Add for FixedVec2 { type Output = Self; fn add(self, rhs: Self) -> Self { Self::new(self.x.add(rhs.x), self.y.add(rhs.y)) } } const impl Sub for FixedVec2 { type Output = Self; fn sub(self, rhs: Self) -> Self { Self::new(self.x.sub(rhs.x), self.y.sub(rhs.y)) } } const impl AddAssign for FixedVec2 { fn add_assign(&mut self, rhs: Self) { *self = Add::add(*self, rhs); } } const impl SubAssign for FixedVec2 { fn sub_assign(&mut self, rhs: Self) { *self = Sub::sub(*self, rhs); } } impl Debug for FixedVec2 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl Display for FixedVec2 { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "({}, {})", self.x, self.y) } } #[cfg(test)] mod tests { use super::*; #[test] fn a_sum_of_steps_does_not_drift() { let mut at = Px::ZERO; for _ in 0..20_000 { at += Px::from_raw(3); } assert_eq!(at, Px::from_raw(60_000)); for _ in 0..20_000 { at -= Px::from_raw(3); } assert_eq!(at, Px::ZERO); } #[test] fn a_pixel_survives_the_trip_through_f32() { for raw in [0, 1, -1, 64, -1000, 16_777_215, -16_777_215] { let px = Px::from_raw(raw); assert_eq!(Px::from_f32(px.to_f32()), px); } } #[test] fn a_fraction_of_a_length_is_a_length() { let half = Px::from_int(100) * Rel::from_f32(0.5); assert_eq!(half, Px::from_int(50)); assert_eq!(Px::from_int(100) * Rel::ONE, Px::from_int(100)); assert_eq!(Px::from_int(100) * Rel::ZERO, Px::ZERO); } #[test] fn halves_round_away_from_zero_either_side() { // A step and a half of one, which has no step of its own. let step_and_a_half = Rel::from_f32(1.5).div_int(Px::ONE.raw()); assert_eq!(Px::ONE * step_and_a_half, Px::from_raw(2)); assert_eq!(Px::ONE.neg() * step_and_a_half, Px::from_raw(-2)); } #[test] fn dividing_by_a_fraction_undoes_multiplying_by_it() { let third = Rel::ONE / Rel::from_int(3); let len = Px::from_int(300); assert_eq!(len * third / third, len); assert_eq!(Px::from_int(100) / Rel::from_f32(0.5), Px::from_int(200)); } #[test] fn arithmetic_saturates_rather_than_wrapping() { assert_eq!(Px::MAX + Px::ONE, Px::MAX); assert_eq!(Px::MIN - Px::ONE, Px::MIN); assert_eq!(Px::from_f32(1e12), Px::MAX); assert_eq!(Px::from_f32(-1e12), Px::MIN); assert_eq!(Px::from_int(i32::MAX), Px::MAX); } #[test] fn a_coarser_grid_rounds_and_a_finer_one_does_not() { // A third, which neither grid holds exactly. let third = Rel::ONE / Rel::from_int(3); assert_eq!(third.to_scale::<6>(), Fixed::<6>::from_raw(21)); let coarse = Fixed::<6>::from_raw(21); assert_eq!(coarse.to_scale::<24>().to_scale::<6>(), coarse); } #[test] fn lerp_takes_the_fraction_as_the_receiver() { let (from, to) = (Px::from_int(10), Px::from_int(20)); assert_eq!(Rel::ZERO.lerp(from, to), from); assert_eq!(Rel::ONE.lerp(from, to), to); assert_eq!(Rel::from_f32(0.5).lerp(from, to), Px::from_int(15)); assert_eq!(Rel::from_f32(0.5).lerp(to, from), Px::from_int(15)); } #[test] fn a_ratio_is_finer_than_the_weights_it_divides() { let (one, three) = (Weight::ONE, Weight::from_int(3)); // A third, which the weights' own grid could only hold to 1/65536. assert_eq!(Rel::ratio(one, three), Rel::from_raw(5592405)); assert_eq!(Rel::ratio(three, three), Rel::ONE); assert_eq!(Rel::ratio(Weight::ZERO, three), Rel::ZERO); } #[test] fn nothing_sits_between_a_value_and_the_next_one() { let at = Px::from_int(3); assert_eq!(at.next_up().next_down(), at); assert_eq!(at.next_up().raw() - at.raw(), 1); assert!(at.next_down() < at && at < at.next_up()); } #[test] fn it_prints_the_number_rather_than_the_steps() { assert_eq!(format!("{:?}", Px::from_f32(17.59375)), "17.59375"); assert_eq!(format!("{}", Px::from_int(-2)), "-2"); } }