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, a multiply /// drops to the step below, and a conversion between grids takes the nearest /// one, so two routes to one place 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. /// /// Arithmetic wraps at the ends of the range, the way the `i32` underneath /// does. Saturating instead was measured at a twelfth of layout's /// instructions -- five per add against one -- to keep the ordering of /// coordinates two million pixels out, where nothing draws anyway. A value /// off the end is a defect either way; wrapping makes it an obvious one. /// Only [`Self::from_f32`] clamps, since a float has further to come from. #[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, in steps of `1/1024`. Finer than /// anything a display can show, and exact in `f32` up to 16,384 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: compared against, never /// added to, since arithmetic wraps 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.wrapping_mul(Self::one().0)) } /// Rounds to the nearest step, and clamps to the ends of the grid rather /// than wrapping: this is where a number from outside arrives, and a float /// has the range to be anywhere. 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, }) } /// The first step at or above `v`, where [`Self::from_f32`] takes the /// nearest one and is below it half the time. For a bound that has to /// admit the value it came from: a measurement rounded down is a bound /// that leaves out the thing it was measured from. pub const fn ceil_from_f32(v: f32) -> Self { let nearest = Self::from_f32(v); match nearest.to_f32() < v { true => nearest.next_up(), false => nearest, } } /// 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 => self.0 << (TO - SHIFT), false => shift_round(self.0 as i64, SHIFT - TO) as i32, }) } pub const fn add(self, rhs: Self) -> Self { Self(self.0.wrapping_add(rhs.0)) } pub const fn sub(self, rhs: Self) -> Self { Self(self.0.wrapping_sub(rhs.0)) } pub const fn neg(self) -> Self { Self(self.0.wrapping_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. /// /// Dropped to the step below rather than taken to the nearest one /// (Bryan, 2026-09-16), which costs a share a thousandth of a pixel of /// its row -- less than an even number of pixels draws. Toward negative /// infinity on both sides of zero, since that is a shift and nothing /// else: a value and its negation therefore land different distances /// from where they came, so a flipped span can sit a step from its /// mirror image. pub const fn mul(self, by: Fixed) -> Self { Self(((self.0 as i64 * by.0 as i64) >> BY) as i32) } /// Repeated a whole number of times, which no grid rounds. pub const fn mul_int(self, by: i32) -> Self { Self(self.0.wrapping_mul(by)) } /// 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(div_round(self.0 as i64, by as i64) as i32) } /// 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 answers with the end /// of the range 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(div_round((self.0 as i64) << BY, by.0 as i64) as i32) } /// `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(div_round((num.0 as i64) << SHIFT, den.0 as i64) as i32) } /// `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.wrapping_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.wrapping_add(1)) } pub const fn next_down(self) -> Self { Self(self.0.wrapping_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, } } /// Clamped to the ends, unlike a [`Fixed`]'s own arithmetic: a range of box /// lengths that runs past `i32` really is unbounded. 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)) } /// The first step at or above each part, for a measurement reported as a /// box: what it occupies is not less than what was measured. pub fn ceil_from_f32(v: Vec2) -> Self { Self::new(Fixed::ceil_from_f32(v.x), Fixed::ceil_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); } /// Toward negative infinity on both sides of zero, which is what makes /// it a shift rather than a shift and a sign branch -- and what makes a /// value and its negation land different distances from where they came, /// so a flipped span can sit a step from its mirror image. #[test] fn a_multiply_drops_to_the_step_below_on_both_sides_of_zero() { // 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(1)); assert_eq!(Px::ONE.neg() * step_and_a_half, Px::from_raw(-2)); } /// A division rounds to the nearest step, so it cannot put back the /// steps a truncating multiply dropped: a round trip comes back short, /// never long, and by the few steps the two operations gave up. #[test] fn dividing_by_a_fraction_cannot_undo_a_truncating_multiply() { let third = Rel::ONE / Rel::from_int(3); let len = Px::from_int(300); let back = len * third / third; assert!(back <= len, "{back:?} is longer than {len:?}"); assert!(len - back <= Px::from_raw(3), "{back:?} against {len:?}"); assert_eq!(Px::from_int(100) / Rel::from_f32(0.5), Px::from_int(200)); } /// The bound a greedy line break needs: the width it was measured at is /// not on the grid, and the narrowest box the break still holds for is /// the step at or above it, never the one below. #[test] fn a_ceiling_never_lands_below_the_number_it_came_from() { let step = 1.0 / (1 << PX_SHIFT) as f32; for n in 0..64 { let v = 189.0 + n as f32 * step / 3.0; let up = Px::ceil_from_f32(v); assert!(up.to_f32() >= v, "{up:?} is below {v}"); assert!( up.to_f32() - v < step, "{up:?} is more than a step above {v}" ); } // An exact step is its own ceiling. assert_eq!(Px::ceil_from_f32(189.5), Px::from_f32(189.5)); } #[test] fn a_number_from_outside_is_clamped_to_the_grid() { assert_eq!(Px::from_f32(1e12), Px::MAX); assert_eq!(Px::from_f32(-1e12), Px::MIN); } #[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"); } }