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

+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,