A wrapping text reported the width it used rounded to the nearest step, which is under the line it measured half the time. A parent that sizes itself from that report then hands the text back a box its own longest line does not fit in, and breaking there is a different break -- one line more. Two tolerances were hiding it and both go. `TextBuffer::shape` answered a width up to 0.05 px under the longest line from the break in hand, which is a structural decision taken on a hair's breadth: it kept a warm tree self-consistent while a cold tree at the same width broke differently, and 0.05 px is fifty steps of the grid. The `Holds` range the text declares started at the nearest step to its longest line for the same reason, so it admitted boxes the line does not fit in. Both are the line itself now, exactly, because the report no longer lands under it. Found by seeds 1121 and 1839 at depth 4, which fail on `ea6dbae` and every commit before it: a defect older than anything on this branch, reached by running 2000 seeds at a depth the long runs do not use. Shrunk to the eight widgets `a_text_is_given_back_a_box_the_line_it_measured_fits_in` builds. 2000 seeds at depth 4 over all fifteen cases are clean now, as are the three long runs. `text` is the one reference render that moves: its lower paragraph shifts a pixel, the box being a step wider and its left edge crossing a snap boundary. Same words, same lines, same breaks; `tabs`, `view`, `minimal` and `random` are byte-identical.
575 lines
20 KiB
Rust
575 lines
20 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
|
|
}
|
|
|
|
/// The same value on another grid, rounded where the new one is coarser.
|
|
pub const fn to_scale<const TO: u32>(self) -> Fixed<TO> {
|
|
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<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))
|
|
}
|
|
}
|
|
|
|
/// 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<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 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");
|
|
}
|
|
}
|