Give a length with no share in it its own type again

`UiScalar` was `Len` without the `leftover` weight, which is the separation
canonical `main` already had as `Len` beside `LayoutLen` and this branch
collapsed. It is needed back for the queued clamp: a cap may not contain a
share, because a cap has to read the report a rule otherwise makes moot, and
a share puts the container's division into the same equation -- two
self-consistent assignments, which is the multiple-fixed-point failure
generated seed 13 punished for orthogonal sizing. `min(report, cap)` is not
a `LayoutLen` either: it is a sum of parts, and the smaller of two of them
is not one.

So `UiScalar` is `Len`, what was `Len` is `LayoutLen`, and the two say in
their docs which is which: a `Len` is pixels plus a fraction of a box -- a
position being the length from the box's start, which is why a span is two
of them -- and a `LayoutLen` is a `Len` plus a claim only a container
dividing its room can answer. `From<Len> for LayoutLen` is the one-way step
between them.

Names only; the shader's `UiScalar` is renamed with them. Checked: fmt,
clippy, 105 tests, and `tabs`, `minimal`, `view`, `text` and `random`
byte-identical at 1920x1200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 13:40:15 -04:00
1 parent 4f5e27cba9
commit a8898aaa54
27 files changed
+218 -202

No files matched your search

+3 -3
View File
@@ -15,7 +15,7 @@
//! reuse, size, placement, and text events for one suspicious widget. The
//! selection is a set and survives [`take`] until cleared.
use crate::{Axis, Len, PxVec2, Size, UiRegion, WidgetId};
use crate::{Axis, LayoutLen, PxVec2, Size, UiRegion, WidgetId};
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
@@ -287,7 +287,7 @@ pub enum TraceEvent {
id: WidgetId,
reader: WidgetId,
axis: Axis,
hint: Option<Len>,
hint: Option<LayoutLen>,
},
TextRendered {
id: WidgetId,
@@ -389,7 +389,7 @@ pub(crate) fn size_read(id: WidgetId, reader: WidgetId, size: Size) {
trace(id, TraceEvent::SizeRead { id, reader, size });
}
pub(crate) fn hint_read(id: WidgetId, reader: WidgetId, axis: Axis, hint: Option<Len>) {
pub(crate) fn hint_read(id: WidgetId, reader: WidgetId, axis: Axis, hint: Option<LayoutLen>) {
trace(
id,
TraceEvent::HintRead {
+6 -6
View File
@@ -172,14 +172,14 @@ impl Vec2 {
}
}
impl UiScalar {
impl Len {
pub const fn align(&self, align: AxisAlign) -> UiSpan {
let rel = align.rel();
let rest = Rel::ONE.sub(rel);
let at = UiScalar::from_parts(rel, Px::ZERO);
let at = Len::from_parts(rel, Px::ZERO);
UiSpan {
start: UiScalar::from_parts(at.rel.sub(self.rel.mul(rel)), at.px.sub(self.px.mul(rel))),
end: UiScalar::from_parts(at.rel.add(self.rel.mul(rest)), at.px.add(self.px.mul(rest))),
start: Len::from_parts(at.rel.sub(self.rel.mul(rel)), at.px.sub(self.px.mul(rel))),
end: Len::from_parts(at.rel.add(self.rel.mul(rest)), at.px.add(self.px.mul(rest))),
}
}
}
@@ -221,8 +221,8 @@ impl From<CardinalAlign> for Align {
const impl From<RegionAlign> for UiVec2 {
fn from(align: RegionAlign) -> Self {
Self::new(
UiScalar::from_parts(align.x.rel(), Px::ZERO),
UiScalar::from_parts(align.y.rel(), Px::ZERO),
Len::from_parts(align.x.rel(), Px::ZERO),
Len::from_parts(align.y.rel(), Px::ZERO),
)
}
}
+54 -37
View File
@@ -3,23 +3,28 @@ use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Size {
pub x: Len,
pub y: Len,
pub x: LayoutLen,
pub y: LayoutLen,
}
/// What a widget asks for along one axis: pixels, a fraction of the box it
/// is given, and a share of whatever is left over once everything fixed has
/// been taken. The three add up rather than choosing between one another.
/// What a widget asks for along one axis: a [`Len`] -- pixels and a fraction
/// of the box it is given -- plus a share of whatever is left over once
/// everything fixed has been taken. The parts add up rather than choosing
/// between one another.
///
/// Only a container dividing its room can answer a share, so a length nobody
/// divides is a `Len`: a position, a padding, a cap, anything already
/// resolved.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Len {
pub struct LayoutLen {
pub px: Px,
pub rel: Rel,
pub leftover: Weight,
}
impl<N: UiNum> From<N> for Len {
impl<N: UiNum> From<N> for LayoutLen {
fn from(value: N) -> Self {
Len::px(value.to_f32())
LayoutLen::px(value.to_f32())
}
}
@@ -32,21 +37,33 @@ impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
}
}
impl From<Len> for Size {
fn from(value: Len) -> Self {
/// A length with no share in it is a length a container does not have to
/// divide, which is one it can always give.
impl From<Len> for LayoutLen {
fn from(len: Len) -> Self {
Self {
px: len.px,
rel: len.rel,
leftover: Weight::ZERO,
}
}
}
impl From<LayoutLen> for Size {
fn from(value: LayoutLen) -> Self {
Self { x: value, y: value }
}
}
impl Size {
pub const ZERO: Self = Self {
x: Len::ZERO,
y: Len::ZERO,
x: LayoutLen::ZERO,
y: LayoutLen::ZERO,
};
pub const LEFTOVER: Self = Self {
x: Len::LEFTOVER,
y: Len::LEFTOVER,
x: LayoutLen::LEFTOVER,
y: LayoutLen::LEFTOVER,
};
/// From something measured outside layout -- a texture, a shaped line --
@@ -57,28 +74,28 @@ impl Size {
pub const fn from_px(v: PxVec2) -> Self {
Self {
x: Len {
x: LayoutLen {
px: v.x,
..Len::ZERO
..LayoutLen::ZERO
},
y: Len {
y: LayoutLen {
px: v.y,
..Len::ZERO
..LayoutLen::ZERO
},
}
}
pub fn rel(v: Vec2) -> Self {
Self {
x: Len::rel(v.x),
y: Len::rel(v.y),
x: LayoutLen::rel(v.x),
y: LayoutLen::rel(v.y),
}
}
pub fn leftover(v: Vec2) -> Self {
Self {
x: Len::leftover(v.x),
y: Len::leftover(v.y),
x: LayoutLen::leftover(v.x),
y: LayoutLen::leftover(v.y),
}
}
@@ -89,7 +106,7 @@ impl Size {
}
}
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
pub fn from_axis(axis: Axis, aligned: LayoutLen, ortho: LayoutLen) -> Self {
match axis {
Axis::X => Self {
x: aligned,
@@ -102,7 +119,7 @@ impl Size {
}
}
pub fn axis(&self, axis: Axis) -> Len {
pub fn axis(&self, axis: Axis) -> LayoutLen {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
@@ -110,7 +127,7 @@ impl Size {
}
}
impl Len {
impl LayoutLen {
pub const ZERO: Self = Self {
px: Px::ZERO,
rel: Rel::ZERO,
@@ -126,12 +143,12 @@ impl Len {
/// The whole of what is left over counts as the whole box, which is what
/// a length means to something that is not dividing a box between
/// siblings -- a scroll asking how long its content is.
pub fn apply_leftover(&self) -> UiScalar {
pub fn apply_leftover(&self) -> Len {
let share = match self.leftover > Weight::ZERO {
true => Rel::ONE,
false => Rel::ZERO,
};
UiScalar::from_parts(self.rel.add(share), self.px)
Len::from_parts(self.rel.add(share), self.px)
}
pub fn px(px: impl UiNum) -> Self {
@@ -157,24 +174,24 @@ impl Len {
pub mod len_fns {
use super::*;
pub fn px(px: impl UiNum) -> Len {
Len::px(px)
pub fn px(px: impl UiNum) -> LayoutLen {
LayoutLen::px(px)
}
pub fn rel(rel: impl UiNum) -> Len {
Len::rel(rel)
pub fn rel(rel: impl UiNum) -> LayoutLen {
LayoutLen::rel(rel)
}
pub fn leftover(ratio: impl UiNum) -> Len {
Len::leftover(ratio)
pub fn leftover(ratio: impl UiNum) -> LayoutLen {
LayoutLen::leftover(ratio)
}
}
impl_op!(same Len Add add; px rel leftover);
impl_op!(same Len Sub sub; px rel leftover);
impl_op!(same LayoutLen Add add; px rel leftover);
impl_op!(same LayoutLen Sub sub; px rel leftover);
impl_op!(same Size Add add; x y);
impl_op!(same Size Sub sub; x y);
impl Default for Len {
impl Default for LayoutLen {
fn default() -> Self {
Self::leftover(1.0)
}
@@ -186,7 +203,7 @@ impl std::fmt::Display for Size {
}
}
impl std::fmt::Display for Len {
impl std::fmt::Display for LayoutLen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.px != Px::ZERO {
write!(f, "{} px;", self.px)?;
+42 -38
View File
@@ -6,41 +6,41 @@ use crate::{Px, PxVec2, Rel, UiNum, util::impl_op};
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct UiVec2 {
pub x: UiScalar,
pub y: UiScalar,
pub x: Len,
pub y: Len,
}
impl UiVec2 {
pub const ZERO: Self = Self {
x: UiScalar::ZERO,
y: UiScalar::ZERO,
x: Len::ZERO,
y: Len::ZERO,
};
pub const fn new(x: UiScalar, y: UiScalar) -> Self {
pub const fn new(x: Len, y: Len) -> Self {
Self { x, y }
}
pub const fn px(px: impl const Into<Vec2>) -> Self {
let px = px.into();
Self {
x: UiScalar::px(px.x),
y: UiScalar::px(px.y),
x: Len::px(px.x),
y: Len::px(px.y),
}
}
/// From lengths already on the grid, with no fraction of a box.
pub const fn from_px(px: PxVec2) -> Self {
Self {
x: UiScalar::from_parts(Rel::ZERO, px.x),
y: UiScalar::from_parts(Rel::ZERO, px.y),
x: Len::from_parts(Rel::ZERO, px.x),
y: Len::from_parts(Rel::ZERO, px.y),
}
}
pub const fn rel(rel: impl const Into<Vec2>) -> Self {
let rel = rel.into();
Self {
x: UiScalar::rel(rel.x),
y: UiScalar::rel(rel.y),
x: Len::rel(rel.x),
y: Len::rel(rel.y),
}
}
@@ -61,14 +61,14 @@ impl UiVec2 {
}
}
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
pub fn axis_mut(&mut self, axis: Axis) -> &mut Len {
match axis {
Axis::X => &mut self.x,
Axis::Y => &mut self.y,
}
}
pub fn axis(&self, axis: Axis) -> UiScalar {
pub fn axis(&self, axis: Axis) -> Len {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
@@ -83,7 +83,7 @@ impl UiVec2 {
pub const FULL_SIZE: Self = Self::rel(Vec2::ONE);
pub const fn from_axis(axis: Axis, aligned: UiScalar, ortho: UiScalar) -> Self {
pub const fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
match axis {
Axis::X => Self {
x: aligned,
@@ -129,21 +129,27 @@ where
}
}
/// A position along one axis, as a fraction of the box it sits in plus an
/// offset: `rel * len + px`. Both parts are fixed point, so composing one
/// through a chain of boxes rounds only where it multiplies and lands on the
/// same number as any other route to the same place.
/// A length along one axis: a fraction of the box it is measured in plus an
/// offset, `rel * box + px`. A position is the same number -- the length from
/// the start of the box to the point -- which is why a [`UiSpan`] is two of
/// these. Both parts are fixed point, so composing one through a chain of
/// boxes rounds only where it multiplies, and lands on the same number as any
/// other route to the same place.
///
/// It carries no claim on what a container has left over. That is
/// [`crate::LayoutLen`], which is this plus a weight, and which means nothing
/// to anyone but whoever divides the room.
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, Default, bytemuck::Zeroable)]
pub struct UiScalar {
pub struct Len {
pub rel: Rel,
pub px: Px,
}
impl_op!(same UiScalar Add add; rel px);
impl_op!(same UiScalar Sub sub; rel px);
impl_op!(same Len Add add; rel px);
impl_op!(same Len Sub sub; rel px);
impl UiScalar {
impl Len {
pub const ZERO: Self = Self {
rel: Rel::ZERO,
px: Px::ZERO,
@@ -192,10 +198,8 @@ impl UiScalar {
}
}
/// Both channels by the same factor, which is what a fraction of a
/// length means when the length is part pixels and part a share.
/// Both channels by the same fraction, which is what a part of a length
/// means when the length is part pixels and part a share.
/// Both parts by the same fraction, which is what a part of a length
/// means when the length is part pixels and part a fraction of a box.
pub const fn scale(&self, by: Rel) -> Self {
Self {
rel: self.rel.mul(by),
@@ -215,14 +219,14 @@ impl UiScalar {
}
}
pub fn within_len(&self, len: UiScalar) -> Self {
pub fn within_len(&self, len: Len) -> Self {
self.within(&UiSpan {
start: UiScalar::ZERO,
start: Len::ZERO,
end: len,
})
}
pub fn select_len(&self, len: UiScalar) -> Self {
pub fn select_len(&self, len: Len) -> Self {
len.within_len(*self)
}
@@ -245,24 +249,24 @@ impl UiScalar {
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UiSpan {
pub start: UiScalar,
pub end: UiScalar,
pub start: Len,
pub end: Len,
}
impl UiSpan {
pub const FULL: Self = Self {
start: UiScalar::ZERO,
end: UiScalar::FULL,
start: Len::ZERO,
end: Len::FULL,
};
pub const fn rel(rel: f32) -> Self {
Self {
start: UiScalar::rel(rel),
end: UiScalar::rel(rel),
start: Len::rel(rel),
end: Len::rel(rel),
}
}
pub const fn new(start: UiScalar, end: UiScalar) -> Self {
pub const fn new(start: Len, end: Len) -> Self {
Self { start, end }
}
@@ -273,7 +277,7 @@ impl UiSpan {
std::mem::swap(&mut self.start.px, &mut self.end.px);
}
pub const fn shift(&mut self, offset: UiScalar) {
pub const fn shift(&mut self, offset: Len) {
self.start += offset;
self.end += offset;
}
@@ -285,7 +289,7 @@ impl UiSpan {
}
}
pub const fn len(&self) -> UiScalar {
pub const fn len(&self) -> Len {
self.end - self.start
}
}
+10 -10
View File
@@ -49,16 +49,16 @@ struct RawSpan {
end: RawScalar,
}
fn scalar_of(raw: RawScalar) -> UiScalar {
return UiScalar(f32(raw.rel) * REL_STEP, f32(raw.px) * PX_STEP);
fn scalar_of(raw: RawScalar) -> Len {
return Len(f32(raw.rel) * REL_STEP, f32(raw.px) * PX_STEP);
}
fn span_of(raw: RawSpan) -> UiSpan {
return UiSpan(scalar_of(raw.start), scalar_of(raw.end));
}
fn scalar_of_pair(raw: vec2<i32>) -> UiScalar {
return UiScalar(f32(raw.x) * REL_STEP, f32(raw.y) * PX_STEP);
fn scalar_of_pair(raw: vec2<i32>) -> Len {
return Len(f32(raw.x) * REL_STEP, f32(raw.y) * PX_STEP);
}
struct Region {
@@ -72,12 +72,12 @@ const MOVE_NONE: u32 = 4294967295u;
// resolve a deep one the same way.
const CHAIN_LIMIT: u32 = 64u;
// The same expression `UiScalar::within` uses, in floats rather than on the
// The same expression `Len::within` uses, in floats rather than on the
// CPU's grid: a move is resolved here so that scrolling a subtree writes one
// entry instead of walking it. What has to hold is that this agrees with
// itself frame to frame, not that it matches the CPU to the last bit.
fn scalar_within(s: UiScalar, p: UiSpan) -> UiScalar {
return UiScalar(
fn scalar_within(s: Len, p: UiSpan) -> Len {
return Len(
p.start.rel + (p.end.rel - p.start.rel) * s.rel,
s.px + (p.start.px + (p.end.px - p.start.px) * s.rel),
);
@@ -102,11 +102,11 @@ fn resolve_move(idx: u32, local: Region) -> Region {
}
struct UiSpan {
start: UiScalar,
end: UiScalar,
start: Len,
end: Len,
}
struct UiScalar {
struct Len {
rel: f32,
px: f32,
}
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::{
Holds, LayerId, Len, MaskIdx, MoveIdx, PrimitiveHandle, RegionAlign, Size, TextureHandle,
Holds, LayerId, LayoutLen, MaskIdx, MoveIdx, PrimitiveHandle, RegionAlign, Size, TextureHandle,
UiRegion, WidgetId,
};
@@ -40,7 +40,7 @@ pub struct ActiveData {
/// The declared lengths whoever drew this widget resolved into its box.
/// A change to one moves a box this widget cannot fix by drawing again,
/// and comparing them is what says so.
pub declared: [Option<Len>; 2],
pub declared: [Option<LayoutLen>; 2],
/// The alignment its parent asked it with. A local redraw repeats that
/// question, including an override chosen by a container.
pub align: RegionAlign,
+4 -4
View File
@@ -1,4 +1,4 @@
use crate::{Px, REL_SHIFT, UiScalar, fixed::div_toward, fixed::narrow};
use crate::{Len, Px, REL_SHIFT, fixed::div_toward, fixed::narrow};
use std::ops::RangeInclusive;
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
@@ -50,7 +50,7 @@ impl Holds {
/// the length alone instead gives a point that need not even contain the
/// box the part was drawn in, which is a range excluding the drawing it
/// was made for.
pub const fn through(self, len: UiScalar) -> Self {
pub const fn through(self, len: Len) -> Self {
let rel = len.rel.raw() as i64;
if rel == 0 {
return Self::ANY;
@@ -98,7 +98,7 @@ mod tests {
#[test]
fn through_reverses_a_range_for_a_negative_fraction() {
// `10 - box / 2` is between 20 and 40 for boxes from -60 to -20.
let part = UiScalar::from_parts(Rel::from_f32(-0.5), Px::from_int(10));
let part = Len::from_parts(Rel::from_f32(-0.5), Px::from_int(10));
let holds = Holds::from(Px::from_int(20)..=Px::from_int(40)).through(part);
assert!(holds.contains(Px::from_int(-60)) && holds.contains(Px::from_int(-20)));
assert!(!holds.contains(Px::from_int(-61)) && !holds.contains(Px::from_int(-19)));
@@ -109,7 +109,7 @@ mod tests {
/// box is not a whole number of steps.
#[test]
fn a_part_maps_back_onto_the_box_it_was_measured_in() {
let part = UiScalar::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
let part = Len::from_parts(Rel::from_f32(1.0 / 3.0), Px::from_int(-146));
for box_len in (440..460).map(Px::from_int) {
let holds = Holds::at(part.to_px(box_len)).through(part);
assert!(holds.contains(box_len), "{box_len:?} left out by {holds:?}");
+11 -11
View File
@@ -1,8 +1,8 @@
#[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Holds, Len, Px, PxVec2, RegionAlign, Rel, RenderedText, Size, StrongWidget, TextAttrs,
TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Weight,
Axis, Holds, LayoutLen, Len, Px, PxVec2, RegionAlign, Rel, RenderedText, Size, StrongWidget,
TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight,
WidgetId, Widgets,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
@@ -109,7 +109,7 @@ impl<'a> Painter<'a> {
/// it resolves into its box. Reading them depends on nothing -- the box
/// that comes of them is kept on the child, and `redraw` compares it
/// there.
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<Len>; 2] {
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
declared_lens(self.rsc.widgets(), id.id())
}
@@ -218,7 +218,7 @@ impl<'a> Painter<'a> {
/// What a child says its length is without being drawn, if it can say.
/// Asking counts as reading its size.
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
let widgets = self.rsc.widgets();
// A rule is the answer where there is one: it wins over whatever the
// widget would draw, so it has to win over what the widget says too.
@@ -252,7 +252,7 @@ impl<'a> Painter<'a> {
child: &StrongWidget<W>,
axis: Axis,
region: UiRegion,
) -> Option<Len> {
) -> Option<LayoutLen> {
let declared = self.declared_lens(child);
let align = self.rsc.widgets().alignment(child.id());
let local = declared_box(region, declared, align);
@@ -472,7 +472,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
self.size
}
pub fn len(self, axis: Axis) -> Len {
pub fn len(self, axis: Axis) -> LayoutLen {
self.size().axis(axis)
}
}
@@ -505,7 +505,7 @@ impl PrimitiveLike for &TextureHandle {
/// What a widget declares a length of its box to be. `leftover` is not one: a
/// share of what is left over is only a length to the widget dividing one,
/// so it passes up in the size instead.
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<Len>; 2] {
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
let rules = widgets.size_rules(id);
let widget = widgets.get_dyn(id);
AXES.map(|axis| {
@@ -537,7 +537,7 @@ pub(crate) fn placed_box(
region: UiRegion,
size: Size,
align: RegionAlign,
declared: [Option<Len>; 2],
declared: [Option<LayoutLen>; 2],
) -> UiRegion {
let mut placed = region;
for (axis, declared) in AXES.into_iter().zip(declared) {
@@ -546,7 +546,7 @@ pub(crate) fn placed_box(
continue;
}
let span = placed.axis_mut(axis);
let len = span.len().scale(reported.rel) + UiScalar::from_parts(Rel::ZERO, reported.px);
let len = span.len().scale(reported.rel) + Len::from_parts(Rel::ZERO, reported.px);
span.start += (span.len() - len).scale(align.axis(axis).rel());
span.end = span.start + len;
}
@@ -559,13 +559,13 @@ pub(crate) fn placed_box(
/// space hands back the same length, so this is the identity for it.
pub(crate) fn declared_box(
mut region: UiRegion,
declared: [Option<Len>; 2],
declared: [Option<LayoutLen>; 2],
align: RegionAlign,
) -> UiRegion {
for (axis, len) in AXES.into_iter().zip(declared) {
let Some(len) = len else { continue };
let span = region.axis_mut(axis);
let len = UiScalar::from_parts(len.rel, len.px);
let len = Len::from_parts(len.rel, len.px);
span.start += (span.len() - len).scale(align.axis(axis).rel());
span.end = span.start + len;
}
+11 -17
View File
@@ -2,9 +2,9 @@
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
use crate::ui::painter::{declared_box, declared_lens, placed_box};
use crate::{
ActiveData, Axis, DrawLayers, Holds, IdLike, Len, MaskIdx, MoveIdx, Moves, Painter,
PixelRegion, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiScalar, UiSpan,
Weight, WidgetId, Widgets,
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter,
PixelRegion, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Weight,
WidgetId, Widgets,
util::{HashMap, Vec2},
};
@@ -75,14 +75,8 @@ impl UiRenderState {
/// downstream has to know the output's size to resolve a position.
fn write_root(&mut self) {
let region = UiRegion::new(
UiSpan::new(
UiScalar::ZERO,
UiScalar::from_parts(Rel::ZERO, self.output_size.x),
),
UiSpan::new(
UiScalar::ZERO,
UiScalar::from_parts(Rel::ZERO, self.output_size.y),
),
UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, self.output_size.x)),
UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, self.output_size.y)),
);
match self.root_move == MoveIdx::NONE {
true => self.root_move = self.moves.push(MoveIdx::NONE, region),
@@ -255,8 +249,8 @@ impl UiRenderState {
let reported = answer.0.axis(axis);
let placed_len =
match reported.leftover != Weight::ZERO || declared[axis as usize].is_some() {
true => UiScalar::FULL,
false => UiScalar::from_parts(reported.rel, reported.px),
true => Len::FULL,
false => Len::from_parts(reported.rel, reported.px),
};
settled.1[axis as usize] =
settled.1[axis as usize].and(drawing_holds[axis as usize].through(placed_len));
@@ -765,10 +759,10 @@ impl UiRenderState {
let size = Size {
x: widget
.and_then(|w| w.size_hint(Axis::X))
.unwrap_or(Len::ZERO),
.unwrap_or(LayoutLen::ZERO),
y: widget
.and_then(|w| w.size_hint(Axis::Y))
.unwrap_or(Len::ZERO),
.unwrap_or(LayoutLen::ZERO),
};
self.active.insert(
id,
@@ -1079,7 +1073,7 @@ impl RegionRemap {
}
}
fn apply_scalar(self, scalar: UiScalar, from: UiSpan, to: UiSpan) -> UiScalar {
fn apply_scalar(self, scalar: Len, from: UiSpan, to: UiSpan) -> Len {
let extent = from.end.rel - from.start.rel;
// A box that only moved, or that has no relative extent to divide,
// carries its parts by moving them, which is exact. Dividing to find
@@ -1100,7 +1094,7 @@ impl RegionRemap {
let from_px = fraction.lerp(from.start.px, from.end.px);
let to_rel = fraction.lerp(to.start.rel, to.end.rel);
let to_px = fraction.lerp(to.start.px, to.end.px);
UiScalar::from_parts(to_rel, scalar.px - from_px + to_px)
Len::from_parts(to_rel, scalar.px - from_px + to_px)
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
use crate::{Axis, Len, Painter, Size};
use crate::{Axis, LayoutLen, Painter, Size};
use std::any::Any;
mod data;
@@ -24,7 +24,7 @@ pub trait Widget: Any {
/// An exact length the widget can give without a painter or its children.
/// Optional, and saves a draw rather than changing one: a hint that
/// disagrees with the eventual draw fails a debug assertion.
fn size_hint(&self, _axis: Axis) -> Option<Len> {
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
None
}
}
@@ -35,8 +35,8 @@ impl Widget for () {
Size::default()
}
fn size_hint(&self, _axis: Axis) -> Option<Len> {
Some(Len::default())
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
Some(LayoutLen::default())
}
}
+9 -9
View File
@@ -1,4 +1,4 @@
use crate::{Axis, Len, Weight};
use crate::{Axis, LayoutLen, Weight};
/// What a widget's length on one axis is, as a rule its parent applies where
/// it draws it rather than an answer the widget gives about itself.
@@ -14,7 +14,7 @@ pub enum SizeRule {
#[default]
Free,
/// This length, whatever the widget reports.
Exact(Len),
Exact(LayoutLen),
}
impl SizeRule {
@@ -22,7 +22,7 @@ impl SizeRule {
/// give one. `leftover` is never among them: a share is a length only to
/// whoever divides one, so it passes up in the reported size instead and
/// is resolved there.
pub fn declared(&self) -> Option<Len> {
pub fn declared(&self) -> Option<LayoutLen> {
match self {
Self::Exact(len) if len.leftover == Weight::ZERO => Some(*len),
_ => None,
@@ -33,7 +33,7 @@ impl SizeRule {
/// share is a length the widget's parent still has to divide, so it is
/// known here and resolved there -- unlike `declared`, which is only the
/// ones that give a box directly.
pub fn known(&self) -> Option<Len> {
pub fn known(&self) -> Option<LayoutLen> {
match self {
Self::Free => None,
Self::Exact(len) => Some(*len),
@@ -41,7 +41,7 @@ impl SizeRule {
}
/// The length a widget reporting `reported` ends up with.
pub fn apply(&self, reported: Len) -> Len {
pub fn apply(&self, reported: LayoutLen) -> LayoutLen {
match self {
Self::Free => reported,
Self::Exact(len) => *len,
@@ -49,14 +49,14 @@ impl SizeRule {
}
}
impl From<Len> for SizeRule {
fn from(len: Len) -> Self {
impl From<LayoutLen> for SizeRule {
fn from(len: LayoutLen) -> Self {
Self::Exact(len)
}
}
impl From<Option<Len>> for SizeRule {
fn from(len: Option<Len>) -> Self {
impl From<Option<LayoutLen>> for SizeRule {
fn from(len: Option<LayoutLen>) -> Self {
len.map_or(Self::Free, Self::Exact)
}
}
+1 -1
View File
@@ -165,7 +165,7 @@ impl Harness {
}
/// Changes a length rule after the fact, the way `.width()` sets one.
pub fn set_len(&mut self, id: impl IdLike, axis: Axis, len: impl Into<Len>) {
pub fn set_len(&mut self, id: impl IdLike, axis: Axis, len: impl Into<LayoutLen>) {
self.rsc
.widgets_mut()
.set_size_rule(id, axis, SizeRule::Exact(len.into()));
+5 -5
View File
@@ -9,7 +9,7 @@ use crate::prelude::*;
use std::collections::HashMap;
/// The declared lengths of one widget carrying a size rule, by axis.
pub type Lens = [Option<Len>; 2];
pub type Lens = [Option<LayoutLen>; 2];
/// Where one widget carrying an alignment sits, by axis. `None` uses the
/// centered default.
@@ -181,10 +181,10 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
id
}
fn len(&mut self) -> Option<Len> {
fn len(&mut self) -> Option<LayoutLen> {
match self.rng.below(4) {
0 => Some(Len::px(20.0 + self.rng.below(180) as f32)),
1 => Some(Len::LEFTOVER),
0 => Some(LayoutLen::px(20.0 + self.rng.below(180) as f32)),
1 => Some(LayoutLen::LEFTOVER),
_ => None,
}
}
@@ -367,7 +367,7 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
if dir.axis == Axis::X {
self.rsc
.widgets_mut()
.set_size_rules(id, None, Some(Len::rel(1.0)));
.set_size_rules(id, None, Some(LayoutLen::rel(1.0)));
}
self.tree.ids.push(id.id());
self.tree.spans.push(Spanned { id, spares, grown });
+2 -2
View File
@@ -11,8 +11,8 @@ impl Widget for Image {
Size::px(self.handle.size())
}
fn size_hint(&self, axis: Axis) -> Option<Len> {
Some(Len::px(self.handle.size().axis(axis)))
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
Some(LayoutLen::px(self.handle.size().axis(axis)))
}
}
+2 -2
View File
@@ -11,11 +11,11 @@ impl Widget for Pad {
.widget_aligned(&self.inner, self.padding.region(), RegionAlign::NEAR)
.size();
Size {
x: Len {
x: LayoutLen {
px: inner.x.px + self.padding.left + self.padding.right,
..inner.x
},
y: Len {
y: LayoutLen {
px: inner.y.px + self.padding.top + self.padding.bottom,
..inner.y
},
+2 -2
View File
@@ -57,8 +57,8 @@ impl Widget for Scroll {
if moved || self.content_len != self.container_len {
let offset = UiVec2::from_axis(
self.axis,
UiScalar::from_parts(Rel::ZERO, anchor - self.amt),
UiScalar::ZERO,
Len::from_parts(Rel::ZERO, anchor - self.amt),
Len::ZERO,
);
region = region.offset(offset);
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
+11 -11
View File
@@ -12,10 +12,10 @@ impl Widget for Span {
let axis = self.dir.axis;
// A length for every child before their final boxes are chosen: from
// a hint where one exists, and from drawing otherwise.
let mut cursor = UiScalar::rel_min();
let mut cursor = Len::rel_min();
let mut lens = Vec::with_capacity(self.children.len());
for child in &self.children {
let mut span = UiSpan::new(cursor, UiScalar::rel_max());
let mut span = UiSpan::new(cursor, Len::rel_max());
if self.dir.sign == Sign::Neg {
span.flip();
}
@@ -33,9 +33,9 @@ impl Widget for Span {
.gap
.mul_int(self.children.len().saturating_sub(1) as i32);
let total = lens.iter().fold(
Len {
LayoutLen {
px: gaps,
..Len::ZERO
..LayoutLen::ZERO
},
|sum, len| sum + *len,
);
@@ -89,11 +89,11 @@ impl Widget for Span {
// from the last child: the share of the room is rounded, and taking
// each from the one before it would carry every rounding along the
// row.
let mut fixed = UiScalar::rel_min();
let mut fixed = Len::rel_min();
let mut taken = Weight::ZERO;
let room = UiScalar::rel_max() - UiScalar::from_parts(total.rel, total.px);
let mut start = UiScalar::rel_min();
let mut ortho = Len::ZERO;
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
let mut start = Len::rel_min();
let mut ortho = LayoutLen::ZERO;
for (child, len) in self.children.iter().zip(&lens) {
// A child asking for nothing but a part of what is left over,
// when nothing is, is not drawn at all. One that also asked for
@@ -125,7 +125,7 @@ impl Widget for Span {
// A scalable child therefore makes Children scalable too;
// only fixed children are compared with one another.
if used.rel != Rel::ZERO || used.leftover != Weight::ZERO {
ortho = Len::LEFTOVER;
ortho = LayoutLen::LEFTOVER;
} else if ortho.leftover == Weight::ZERO {
ortho.px = ortho.px.max(used.px);
}
@@ -144,7 +144,7 @@ impl Widget for Span {
let along = total;
let ortho = match shrinks {
true => ortho,
false => Len::rel(1.0),
false => LayoutLen::rel(1.0),
};
Size::from_axis(axis, along, ortho)
}
@@ -153,7 +153,7 @@ impl Widget for Span {
/// Where a row has reached: everything fixed before this point, which is a
/// sum and exact, plus the share of the room the weights so far are worth,
/// which is one rounding wherever it is asked for.
fn shared(fixed: UiScalar, taken: Weight, weight: Weight, room: UiScalar) -> UiScalar {
fn shared(fixed: Len, taken: Weight, weight: Weight, room: Len) -> Len {
if taken == Weight::ZERO {
return fixed;
}
+2 -2
View File
@@ -38,8 +38,8 @@ impl Widget for Rect {
Size::LEFTOVER
}
fn size_hint(&self, _: Axis) -> Option<Len> {
Some(Len::LEFTOVER)
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
Some(LayoutLen::LEFTOVER)
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ widget_trait! {
}
}
fn width(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
fn width(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
let len = len.into();
move |state| {
let id = self.add(state);
@@ -71,7 +71,7 @@ widget_trait! {
}
}
fn height(self, len: impl Into<Len>) -> impl WidgetIdFn<Rsc, WL::Widget> {
fn height(self, len: impl Into<LayoutLen>) -> impl WidgetIdFn<Rsc, WL::Widget> {
let len = len.into();
move |state| {
let id = self.add(state);
+5 -5
View File
@@ -27,7 +27,7 @@ fn a_span_ruled_across_itself_does_not_measure_its_children_there() {
let span = (child,).span(Dir::RIGHT).height(rel(1.0)).add(&mut h.rsc);
h.set_root(span);
assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0));
assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::rel(1.0));
}
#[test]
@@ -38,7 +38,7 @@ fn a_span_reports_its_tallest_fixed_child() {
let span = (short, tall).span(Dir::RIGHT).add(&mut h.rsc);
h.set_root(span);
assert_eq!(h.render.active[&span.id()].size.y, Len::px(70.0));
assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::px(70.0));
}
#[test]
@@ -331,7 +331,7 @@ fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
let region = h.render.moves.resolve(active.parent_move, active.region);
let dim = h.size().axis(axis);
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
let edge = |s: UiScalar| snap(s.rel.to_f32() * dim) + snap(s.px.to_f32());
let edge = |s: Len| snap(s.rel.to_f32() * dim) + snap(s.px.to_f32());
let span = region.axis(axis);
(edge(span.start), edge(span.end))
}
@@ -343,7 +343,7 @@ fn hairline(h: &mut Harness, marks: &mut Vec<WidgetId>) -> StrongWidget {
}
fn share(h: &mut Harness, inner: StrongWidget, ratio: f32) -> StrongWidget {
h.set_len(&inner, Axis::X, Len::leftover(ratio));
h.set_len(&inner, Axis::X, LayoutLen::leftover(ratio));
inner
}
@@ -454,7 +454,7 @@ fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
let mut h = Harness::new((100, 20));
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
let mixed = rect(Color::BLUE)
.width(Len::px(20) + Len::LEFTOVER)
.width(LayoutLen::px(20) + LayoutLen::LEFTOVER)
.add(&mut h.rsc);
h.set_root((fixed, mixed).span(Dir::RIGHT));
+4 -7
View File
@@ -125,10 +125,7 @@ fn moving_an_ordinary_subtree_remaps_its_mask() {
let active = &h.render.active[&masked.id()];
assert_eq!(
h.rsc.ui().masks[active.mask.idx()].region,
UiRegion::new(
UiSpan::new(UiScalar::px(150.0), UiScalar::rel_max()),
UiSpan::FULL,
)
UiRegion::new(UiSpan::new(Len::px(150.0), Len::rel_max()), UiSpan::FULL,)
);
assert_corners!(h, inner, (150, 0), (400, 200));
}
@@ -312,7 +309,7 @@ fn a_span_ruled_across_itself_moves_its_child_without_redrawing_it() {
assert_eq!(draws.get(), settled);
assert_corners!(h, leaf, (0, 0), (400, 100));
assert_eq!(h.render.active[&span.id()].size.y, Len::rel(1.0));
assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::rel(1.0));
}
/// The output is the root of the box chain, so a resize is a box that changed
@@ -407,12 +404,12 @@ fn a_box_change_within_one_step_is_not_a_change() {
let step = Px::STEP.to_f32();
for part in [0.1, 0.2, 0.3] {
h.rsc[first].size.x = Len::px(100.0 + step * part);
h.rsc[first].size.x = LayoutLen::px(100.0 + step * part);
h.frame();
assert_eq!(draws.get(), settled);
}
h.rsc[first].size.x = Len::px(100.0 + step);
h.rsc[first].size.x = LayoutLen::px(100.0 + step);
h.frame();
assert_eq!(draws.get(), settled + 1);
}
+5 -5
View File
@@ -281,11 +281,11 @@ struct Wider {
impl Widget for Wider {
fn draw(&mut self, painter: &mut Painter) -> Size {
Size {
x: Len {
x: LayoutLen {
px: painter.px_len(Axis::X) + Px::from_f32(self.extra),
..Len::ZERO
..LayoutLen::ZERO
},
y: Len::LEFTOVER,
y: LayoutLen::LEFTOVER,
}
}
}
@@ -341,7 +341,7 @@ fn plant_boundary(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
let whole = rect(Color::RED).add(&mut h.rsc);
h.rsc
.widgets_mut()
.set_size_rules(whole, None, Some(Len::rel(1.0)));
.set_size_rules(whole, None, Some(LayoutLen::rel(1.0)));
let mut inner_children: Vec<StrongWidget> = vec![
measured.add_strong(&mut h.rsc),
whole.add_strong(&mut h.rsc),
@@ -357,7 +357,7 @@ fn plant_boundary(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
.add(&mut h.rsc);
h.rsc
.widgets_mut()
.set_size_rules(inner, None, Some(Len::px(198.0)));
.set_size_rules(inner, None, Some(LayoutLen::px(198.0)));
// One more span above it: without a box composed through it, both trees
// round the same way and the boundary is never crossed.
let outer = (inner,).span(Dir::DOWN).add(&mut h.rsc);
+3 -3
View File
@@ -16,8 +16,8 @@
use iris::prelude::*;
use iris_core::{
MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, UiData, UiRegion, UiRenderNode, UiRenderState,
UiScalar, UiSpan,
Len, MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, UiData, UiRegion, UiRenderNode,
UiRenderState, UiSpan,
};
use wgpu::{Color as GpuColor, *};
@@ -79,7 +79,7 @@ fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
slot = render.moves.push(slot, UiRegion::FULL);
}
let px = |v: f32| UiScalar::px(v);
let px = |v: f32| Len::px(v);
for i in 0..INSTANCES {
let x = (i % (SIZE as usize / 2)) as f32 * 2.0;
let y = (i / (SIZE as usize / 2)) as f32;
+2 -2
View File
@@ -61,8 +61,8 @@ fn plant(h: &mut Harness, seed: u64, edits: &Edits) -> Tree {
fn resize_one(h: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Lens {
let lens = [
Some(Len::px(20.0 + rng.below(180) as f32)),
Some(Len::px(20.0 + rng.below(180) as f32)),
Some(LayoutLen::px(20.0 + rng.below(180) as f32)),
Some(LayoutLen::px(20.0 + rng.below(180) as f32)),
];
h.rsc
.widgets_mut()
+1 -1
View File
@@ -216,7 +216,7 @@ fn layout_cost() {
trace_selected(&tree);
let sized = tree.sized[0];
run("size", frames, &mut harness, move |harness, frame| {
let len = Len::px(100.0 + (frame % 2) as f32 * 40.0);
let len = LayoutLen::px(100.0 + (frame % 2) as f32 * 40.0);
harness
.rsc
.widgets_mut()
+5 -1
View File
@@ -94,7 +94,11 @@ fn build(h: &mut Harness, rows: usize) -> Vec<WidgetId> {
let mut col = Span::empty(Dir::DOWN);
for _ in 0..rows {
let mut row = Span::empty(Dir::RIGHT);
row.push(rect(Color::RED).width(Len::px(40.0)).add_strong(&mut h.rsc));
row.push(
rect(Color::RED)
.width(LayoutLen::px(40.0))
.add_strong(&mut h.rsc),
);
let mut body = Span::empty(Dir::DOWN);
let para = wtext(words(&mut rng, 12, 52))
.size(16)
+10 -10
View File
@@ -72,7 +72,7 @@ enum Node {
Stack(Vec<Node>),
Pad(f32, Box<Node>),
Aligned(u8, u8, Box<Node>),
Sized(Option<Len>, Option<Len>, Box<Node>),
Sized(Option<LayoutLen>, Option<LayoutLen>, Box<Node>),
Scroll(bool, Box<Node>),
Branch(Box<Node>, Box<Node>, Box<Node>, f32),
}
@@ -131,7 +131,7 @@ impl Node {
if !*down {
h.rsc
.widgets_mut()
.set_size_rules(handle, None, Some(Len::rel(1.0)));
.set_size_rules(handle, None, Some(LayoutLen::rel(1.0)));
}
spans.push(handle);
handle.add_strong(&mut h.rsc)
@@ -191,7 +191,7 @@ impl Node {
/// The lengths every `Sized` node would carry after `resized`, in the
/// order `build` pushes them.
fn sized_lens(&self, out: &mut Vec<(Option<Len>, Option<Len>)>) {
fn sized_lens(&self, out: &mut Vec<(Option<LayoutLen>, Option<LayoutLen>)>) {
match self {
Node::Text(..) | Node::OneLine | Node::Rect => {}
Node::Span(_, _, kids, _) | Node::Stack(kids) => {
@@ -352,8 +352,8 @@ fn sized(rng: &mut Rng, inner: Node) -> Node {
return inner;
}
let len = |rng: &mut Rng| match rng.below(4) {
0 => Some(Len::px(20.0 + rng.below(180) as f32)),
1 => Some(Len::LEFTOVER),
0 => Some(LayoutLen::px(20.0 + rng.below(180) as f32)),
1 => Some(LayoutLen::LEFTOVER),
_ => None,
};
Node::Sized(len(rng), len(rng), Box::new(inner))
@@ -368,9 +368,9 @@ fn grow(rng: &mut Rng, depth: usize) -> Node {
};
}
let len = |rng: &mut Rng| match rng.below(4) {
0 => Some(Len::px(20.0 + rng.below(180) as f32)),
1 => Some(Len::LEFTOVER),
2 => Some(Len::rel(0.25 + rng.below(3) as f32 * 0.25)),
0 => Some(LayoutLen::px(20.0 + rng.below(180) as f32)),
1 => Some(LayoutLen::LEFTOVER),
2 => Some(LayoutLen::rel(0.25 + rng.below(3) as f32 * 0.25)),
_ => None,
};
let kid = |rng: &mut Rng| {
@@ -408,9 +408,9 @@ enum Case {
/// A different declared length, kept the same kind so the change is to the
/// value alone.
fn resized_len(len: Option<Len>) -> Option<Len> {
fn resized_len(len: Option<LayoutLen>) -> Option<LayoutLen> {
let half = Rel::from_f32(0.5);
len.map(|len| Len {
len.map(|len| LayoutLen {
px: len.px.mul(half) + Px::from_int(13),
rel: len.rel.mul(half),
leftover: len.leftover,