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

+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)
}
}