Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c9d5aa32 | ||
|
|
60367d806e |
No files matched your search
+30
-23
@@ -10,10 +10,10 @@ use std::{
|
|||||||
/// chain, and the same box summed from what its children asked for -- and has
|
/// 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
|
/// 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
|
/// 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
|
/// rather than where. Here adding and subtracting are exact, a multiply
|
||||||
/// multiply or a conversion rounds, back onto the same steps, so two routes
|
/// drops to the step below, and a conversion between grids takes the nearest
|
||||||
/// that come within half a step land on one number and everything downstream
|
/// one, so two routes to one place land on one number and everything
|
||||||
/// compares for equality instead of for nearness.
|
/// downstream compares for equality instead of for nearness.
|
||||||
///
|
///
|
||||||
/// `SHIFT` is the number of fractional bits, which is what makes the steps
|
/// `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
|
/// divide a whole number: a power of two also converts to `f32` without
|
||||||
@@ -31,9 +31,9 @@ use std::{
|
|||||||
)]
|
)]
|
||||||
pub struct Fixed<const SHIFT: u32>(i32);
|
pub struct Fixed<const SHIFT: u32>(i32);
|
||||||
|
|
||||||
/// A length or a coordinate in pixels, to a sixty-fourth. Finer than anything
|
/// A length or a coordinate in pixels, in steps of `1/1024`. Finer than
|
||||||
/// a display can show, and exact in `f32` up to 262,144 px, which is what lets
|
/// anything a display can show, and exact in `f32` up to 16,384 px, which is
|
||||||
/// the same number reach the GPU.
|
/// what lets the same number reach the GPU.
|
||||||
pub type Px = Fixed<PX_SHIFT>;
|
pub type Px = Fixed<PX_SHIFT>;
|
||||||
|
|
||||||
/// How many bits of a pixel a [`Px`] keeps. One place, because [`PxVec2`]
|
/// How many bits of a pixel a [`Px`] keeps. One place, because [`PxVec2`]
|
||||||
@@ -142,18 +142,16 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
/// Scaled by a number on any grid, which is how a length takes a fraction
|
/// 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
|
/// of itself and keeps being a length: the product is measured in the
|
||||||
/// receiver's steps.
|
/// 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 {
|
pub const fn mul<const BY: u32>(self, by: Fixed<BY>) -> Self {
|
||||||
Self(shift_round(self.0 as i64 * by.0 as i64, BY) as i32)
|
Self(((self.0 as i64 * by.0 as i64) >> BY) as i32)
|
||||||
}
|
|
||||||
|
|
||||||
/// A part of a span that is often nothing: no part of nothing is
|
|
||||||
/// nothing, for the cost of a comparison rather than a widening
|
|
||||||
/// multiply and a rounding.
|
|
||||||
pub const fn scaled<const BY: u32>(self, by: Fixed<BY>) -> Self {
|
|
||||||
match self.0 == 0 {
|
|
||||||
true => self,
|
|
||||||
false => self.mul(by),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repeated a whole number of times, which no grid rounds.
|
/// Repeated a whole number of times, which no grid rounds.
|
||||||
@@ -198,7 +196,7 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
/// `from` and `to` a fraction of the way apart, the fraction being the
|
/// `from` and `to` a fraction of the way apart, the fraction being the
|
||||||
/// receiver -- the argument order [`crate::util::LerpUtil`] already uses.
|
/// 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> {
|
pub const fn lerp<const OF: u32>(self, from: Fixed<OF>, to: Fixed<OF>) -> Fixed<OF> {
|
||||||
from.add(to.sub(from).scaled(self))
|
from.add(to.sub(from).mul(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn min(self, other: Self) -> Self {
|
pub const fn min(self, other: Self) -> Self {
|
||||||
@@ -465,19 +463,28 @@ mod tests {
|
|||||||
assert_eq!(Px::from_int(100) * Rel::ZERO, Px::ZERO);
|
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]
|
#[test]
|
||||||
fn halves_round_away_from_zero_either_side() {
|
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.
|
// 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());
|
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 * step_and_a_half, Px::from_raw(1));
|
||||||
assert_eq!(Px::ONE.neg() * step_and_a_half, Px::from_raw(-2));
|
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]
|
#[test]
|
||||||
fn dividing_by_a_fraction_undoes_multiplying_by_it() {
|
fn dividing_by_a_fraction_cannot_undo_a_truncating_multiply() {
|
||||||
let third = Rel::ONE / Rel::from_int(3);
|
let third = Rel::ONE / Rel::from_int(3);
|
||||||
let len = Px::from_int(300);
|
let len = Px::from_int(300);
|
||||||
assert_eq!(len * third / third, len);
|
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));
|
assert_eq!(Px::from_int(100) / Rel::from_f32(0.5), Px::from_int(200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -282,26 +282,12 @@ impl UiSpan {
|
|||||||
self.end += offset;
|
self.end += offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The whole of the box it sits in: a span that composes to nothing and
|
/// Composing a box through the one it sits in, and the hottest line in
|
||||||
/// a parent that changes nothing.
|
/// layout. It used to skip the multiplies where a span was the whole of
|
||||||
pub const fn is_full(&self) -> bool {
|
/// its parent or the parent the whole of its own; both come out of the
|
||||||
self.start.rel.raw() == Rel::ZERO.raw()
|
/// multiply unchanged anyway, and the body those comparisons cost was
|
||||||
&& self.start.px.raw() == Px::ZERO.raw()
|
/// what kept the inliner from taking this at all.
|
||||||
&& self.end.rel.raw() == Rel::ONE.raw()
|
|
||||||
&& self.end.px.raw() == Px::ZERO.raw()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn within(&self, parent: &Self) -> Self {
|
pub const fn within(&self, parent: &Self) -> Self {
|
||||||
// A part that is the whole box is the box, and a box composed through
|
|
||||||
// the whole of its parent is itself. Both are exact -- multiplying by
|
|
||||||
// one rounds to what it started as -- and both are common enough to
|
|
||||||
// be worth four comparisons rather than four multiplies to find out.
|
|
||||||
if self.is_full() {
|
|
||||||
return *parent;
|
|
||||||
}
|
|
||||||
if parent.is_full() {
|
|
||||||
return *self;
|
|
||||||
}
|
|
||||||
Self {
|
Self {
|
||||||
start: self.start.within(parent),
|
start: self.start.within(parent),
|
||||||
end: self.end.within(parent),
|
end: self.end.within(parent),
|
||||||
|
|||||||
+54
-8
@@ -1,4 +1,4 @@
|
|||||||
use crate::{Len, Px, REL_SHIFT, fixed::div_toward, fixed::narrow};
|
use crate::{Len, Px, REL_SHIFT, Rel, fixed::div_toward, fixed::narrow};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
||||||
@@ -55,15 +55,31 @@ impl Holds {
|
|||||||
if rel == 0 {
|
if rel == 0 {
|
||||||
return Self::ANY;
|
return Self::ANY;
|
||||||
}
|
}
|
||||||
// Three half steps either side -- one for the rounding on the way
|
// In half steps. The box a length was composed down the chain from
|
||||||
// in, two for the difference between a length composed down the
|
// and the box the same length is measured against the window in are
|
||||||
// chain and the same length measured against the window -- and half
|
// two routes to one number, each rounding where the other does not,
|
||||||
// of what a `Rel` counts in, to divide by the fraction. Exact until
|
// and each rounding drops a whole step since `Fixed::mul` truncates:
|
||||||
// the division takes it back to the grid.
|
// two steps either side. The multiply on the way in drops a step of
|
||||||
|
// its own, and only downward, so it is one more step at the top and
|
||||||
|
// nothing at the bottom -- and the whole of a box has no multiply in
|
||||||
|
// it, however many pixels were added to it, since multiplying by one
|
||||||
|
// is exact and taking the pixels off again is too. Allowing for it
|
||||||
|
// there anyway compounded, a step a level down a chain of widgets
|
||||||
|
// each taking the whole of its parent, which is the unsound
|
||||||
|
// direction: a range wider than what a drawing holds for admits
|
||||||
|
// reusing it where it does not hold.
|
||||||
|
//
|
||||||
|
// Shifted by half of what a `Rel` counts in, to divide by the
|
||||||
|
// fraction: exact until the division takes it back to the grid.
|
||||||
|
const ROUTES: i64 = 4;
|
||||||
let px = len.px.raw() as i64;
|
let px = len.px.raw() as i64;
|
||||||
let half_rel = REL_SHIFT - 1;
|
let half_rel = REL_SHIFT - 1;
|
||||||
let lo = ((self.lo.raw() as i64 - px) * 2 - 3) << half_rel;
|
let way_in = match rel == Rel::ONE.raw() as i64 {
|
||||||
let hi = ((self.hi.raw() as i64 - px) * 2 + 3) << half_rel;
|
true => 0,
|
||||||
|
false => 2,
|
||||||
|
};
|
||||||
|
let lo = ((self.lo.raw() as i64 - px) * 2 - ROUTES) << half_rel;
|
||||||
|
let hi = ((self.hi.raw() as i64 - px) * 2 + ROUTES + way_in) << half_rel;
|
||||||
// Dividing by a negative turns the ends around, so which end each
|
// Dividing by a negative turns the ends around, so which end each
|
||||||
// bound comes from is decided before dividing rather than by taking
|
// bound comes from is decided before dividing rather than by taking
|
||||||
// the min and max of four divisions.
|
// the min and max of four divisions.
|
||||||
@@ -116,6 +132,36 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A widget handed the whole of its parent's box, with or without pixels
|
||||||
|
/// taken off it, brings no multiply of its own: only the two routes to
|
||||||
|
/// the same length are left to allow for, and not a rounding that did
|
||||||
|
/// not happen. Widening for it as well grew the interval a level at a
|
||||||
|
/// time down a chain of them.
|
||||||
|
#[test]
|
||||||
|
fn the_whole_of_a_box_widens_by_the_routes_alone() {
|
||||||
|
let at = Px::from_int(956);
|
||||||
|
let two_steps = |len: Px| Holds {
|
||||||
|
lo: len - Px::from_raw(2),
|
||||||
|
hi: len + Px::from_raw(2),
|
||||||
|
};
|
||||||
|
assert_eq!(Holds::at(at).through(Len::FULL), two_steps(at));
|
||||||
|
let less_eight = Len::from_parts(Rel::ONE, Px::from_int(-8));
|
||||||
|
assert_eq!(
|
||||||
|
Holds::at(at).through(less_eight),
|
||||||
|
two_steps(at + Px::from_int(8))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A truncating multiply only ever drops, so the step it needs allowing
|
||||||
|
/// for on the way in belongs at the top of the range and not the bottom.
|
||||||
|
#[test]
|
||||||
|
fn a_fraction_widens_further_up_than_down() {
|
||||||
|
let half = Len::from_parts(Rel::from_f32(0.5), Px::ZERO);
|
||||||
|
let holds = Holds::at(Px::from_int(100)).through(half);
|
||||||
|
let box_len = Px::from_int(200);
|
||||||
|
assert!(holds.hi - box_len > box_len - holds.lo, "{holds:?}");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_boundary_the_next_step_along_does_not_admit_it() {
|
fn a_boundary_the_next_step_along_does_not_admit_it() {
|
||||||
let boundary = Px::from_int(10);
|
let boundary = Px::from_int(10);
|
||||||
|
|||||||
@@ -1147,9 +1147,9 @@ impl AxisRemap {
|
|||||||
true => offset,
|
true => offset,
|
||||||
false => offset / scale.extent,
|
false => offset / scale.extent,
|
||||||
};
|
};
|
||||||
let from_px = scale.from_px + scale.from_px_span.scaled(fraction);
|
let from_px = scale.from_px + scale.from_px_span.mul(fraction);
|
||||||
let to_rel = scale.to_rel + scale.to_rel_span.scaled(fraction);
|
let to_rel = scale.to_rel + scale.to_rel_span.mul(fraction);
|
||||||
let to_px = scale.to_px + scale.to_px_span.scaled(fraction);
|
let to_px = scale.to_px + scale.to_px_span.mul(fraction);
|
||||||
Len::from_parts(to_rel, scalar.px - from_px + to_px)
|
Len::from_parts(to_rel, scalar.px - from_px + to_px)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -266,6 +266,11 @@ fn nested_spans_divide_the_space_once_however_deep_the_nesting_is() {
|
|||||||
|
|
||||||
/// The same space, unevenly nested: weights carried up mean a share is a
|
/// The same space, unevenly nested: weights carried up mean a share is a
|
||||||
/// share of the whole, not of whatever branch a widget happens to sit in.
|
/// share of the whole, not of whatever branch a widget happens to sit in.
|
||||||
|
///
|
||||||
|
/// Each edge lands on the even division or one step below it, since a share
|
||||||
|
/// is a fraction of the room and a truncating multiply gives up what that
|
||||||
|
/// fraction does not divide. What stays exact is that each share starts
|
||||||
|
/// where the last one ended and the row ends at its own edge.
|
||||||
#[test]
|
#[test]
|
||||||
fn an_uneven_nesting_still_gives_every_share_the_same_length() {
|
fn an_uneven_nesting_still_gives_every_share_the_same_length() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
@@ -279,10 +284,24 @@ fn an_uneven_nesting_still_gives_every_share_the_same_length() {
|
|||||||
let three = (b, c, d).span(Dir::RIGHT).add(&mut h.rsc);
|
let three = (b, c, d).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
h.set_root((one, three).span(Dir::RIGHT));
|
h.set_root((one, three).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
let mut start = Px::ZERO;
|
||||||
for (i, id) in [a, b, c, d].into_iter().enumerate() {
|
for (i, id) in [a, b, c, d].into_iter().enumerate() {
|
||||||
let x = i as f32 * 100.0;
|
let got = h.region(&id).expect("widget drew nothing");
|
||||||
assert_corners!(h, id, (x, 0), (x + 100.0, 200));
|
let even = Px::from_int((i as i32 + 1) * 100);
|
||||||
|
assert_eq!(got.top_left, PxVec2::new(start, Px::ZERO), "share {i}");
|
||||||
|
assert_eq!(got.bot_right.y, Px::from_int(200), "share {i}");
|
||||||
|
assert!(
|
||||||
|
got.bot_right.x == even || got.bot_right.x == even.next_down(),
|
||||||
|
"share {i} ends at {:?}, not {even:?}",
|
||||||
|
got.bot_right.x
|
||||||
|
);
|
||||||
|
start = got.bot_right.x;
|
||||||
}
|
}
|
||||||
|
assert_eq!(
|
||||||
|
start,
|
||||||
|
Px::from_int(400),
|
||||||
|
"the row stopped short of its edge"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// However many ways a row is divided, the shares add up to the row: each
|
/// However many ways a row is divided, the shares add up to the row: each
|
||||||
|
|||||||
Reference in new issue
Block a user