A seventh sweep, over the parts no earlier round named: the tree generator
and the scenario harness, `Fixed`, the headless rig, and once more over the
commit the sixth sweep left, which was itself unreviewed.
`Scroll`'s content box is `answer_px.max(container_len)`, so a scroll whose
content fits has nothing to scroll through and `update_amt` has already put
`amt` at zero. The test choosing between the viewport and a scrolled span
asked `amt != ZERO || content_len != container_len`, where the first
disjunct can never decide it -- the same defect `b7b8d09` removed from the
line above, one operand over. A `debug_assert` of the implication held
across the whole suite, including every scrolling test.
`Fixed::to_scale` and its private `shift_round` arrived on this branch with
no caller and never got one; the only thing that called either was the test
written for them.
`Len::align` wrote `Len` arithmetic out a component at a time, around an
`at.px` that is always zero, where `Len::scale` and the `Add`/`Sub` beside
it say the whole rule in two lines. `LayoutLen::without_leftover` took
`self` where the `apply_leftover` its own doc calls the opposite reading of
the same value takes `&self`.
`run-headless.sh --resize` changed the output's mode but not `out_w`/`out_h`,
which is the extent `replay-touch` scales a recording against -- so
`--resize` with `--replay` put every sample of the gesture somewhere else
and still finished like a run that worked. Both come from one function now.
The generator's plan/build split stranded a comment: "a row takes the height
it is given" describes the size rule `build` derives from `dir`, and it was
left above the `gap` draw, which is the one line it is not about and which
does consume randomness.
Format, clippy with and without layout-diagnostics, and the suite (131 + 19
+ 13 + 4) are clean. The cold dump over 400 depth-5 trees is byte-identical
to b7b8d09 across all 34,492 boxes, and all three seed scans pass: 400 at
depth 5 in 62.75s, 1,000 at depth 6 in 162.37s, 2,000 at depth 4 in 305.25s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
548 lines
19 KiB
Rust
548 lines
19 KiB
Rust
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<const SHIFT: u32>(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<PX_SHIFT>;
|
|
|
|
/// 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<REL_SHIFT>;
|
|
|
|
/// How many bits of a box a [`Rel`] keeps, beside [`PX_SHIFT`] and for the
|
|
/// same reason.
|
|
pub const REL_SHIFT: u32 = 24;
|
|
|
|
impl<const SHIFT: u32> Fixed<SHIFT> {
|
|
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
|
|
}
|
|
|
|
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<const BY: u32>(self, by: Fixed<BY>) -> 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<const BY: u32>(self, by: Fixed<BY>) -> 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<const OF: u32>(num: Fixed<OF>, den: Fixed<OF>) -> 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<const OF: u32>(self, from: Fixed<OF>, to: Fixed<OF>) -> Fixed<OF> {
|
|
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))
|
|
}
|
|
}
|
|
|
|
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<const SHIFT: u32> Add for Fixed<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn add(self, rhs: Self) -> Self {
|
|
Fixed::add(self, rhs)
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> Sub for Fixed<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn sub(self, rhs: Self) -> Self {
|
|
Fixed::sub(self, rhs)
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> Neg for Fixed<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn neg(self) -> Self {
|
|
Fixed::neg(self)
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> AddAssign for Fixed<SHIFT> {
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
*self = Fixed::add(*self, rhs);
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> SubAssign for Fixed<SHIFT> {
|
|
fn sub_assign(&mut self, rhs: Self) {
|
|
*self = Fixed::sub(*self, rhs);
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32, const BY: u32> Mul<Fixed<BY>> for Fixed<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn mul(self, rhs: Fixed<BY>) -> Self {
|
|
Fixed::mul(self, rhs)
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32, const BY: u32> Div<Fixed<BY>> for Fixed<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn div(self, rhs: Fixed<BY>) -> Self {
|
|
Fixed::div(self, rhs)
|
|
}
|
|
}
|
|
|
|
impl<const SHIFT: u32> Display for Fixed<SHIFT> {
|
|
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<const SHIFT: u32> Debug for Fixed<SHIFT> {
|
|
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<const SHIFT: u32> {
|
|
pub x: Fixed<SHIFT>,
|
|
pub y: Fixed<SHIFT>,
|
|
}
|
|
|
|
pub type PxVec2 = FixedVec2<PX_SHIFT>;
|
|
|
|
impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
|
pub const ZERO: Self = Self::splat(Fixed::ZERO);
|
|
|
|
pub const fn new(x: Fixed<SHIFT>, y: Fixed<SHIFT>) -> Self {
|
|
Self { x, y }
|
|
}
|
|
|
|
pub const fn splat(v: Fixed<SHIFT>) -> 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<const SHIFT: u32> Add for FixedVec2<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn add(self, rhs: Self) -> Self {
|
|
Self::new(self.x.add(rhs.x), self.y.add(rhs.y))
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> Sub for FixedVec2<SHIFT> {
|
|
type Output = Self;
|
|
|
|
fn sub(self, rhs: Self) -> Self {
|
|
Self::new(self.x.sub(rhs.x), self.y.sub(rhs.y))
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> AddAssign for FixedVec2<SHIFT> {
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
*self = Add::add(*self, rhs);
|
|
}
|
|
}
|
|
|
|
const impl<const SHIFT: u32> SubAssign for FixedVec2<SHIFT> {
|
|
fn sub_assign(&mut self, rhs: Self) {
|
|
*self = Sub::sub(*self, rhs);
|
|
}
|
|
}
|
|
|
|
impl<const SHIFT: u32> Debug for FixedVec2<SHIFT> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "({}, {})", self.x, self.y)
|
|
}
|
|
}
|
|
|
|
impl<const SHIFT: u32> Display for FixedVec2<SHIFT> {
|
|
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 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");
|
|
}
|
|
}
|