Four rounds over the same idea: an expression that needed a comment to say
what it computed wanted to be a named operation.
The placement description is built by chaining off the value that says it.
`UiSpan::within_desc`/`shifted_desc` and `Len::as_desc` replace the
`PlaceDescAxis::` constructors, `PlaceDescAxis::axis` lifts one axis into a
pair with the whole box across it, and `PlaceDesc::per_axis` covers the case
where the two axes differ. `beside` is dropped: `from_axis` already said it.
Seven module-level functions become methods on the value each took first --
`Widgets::declared_lens`, `LayoutLen::fills`, `PlaceDesc::placement` and
`::rel_base_and_region`, `Size::within_box`, `UiRegion::at_origin` and
`::as_translation`.
`UiSpan::place` is the aligned-placement rule, which was written out three
times; `LayoutLen::without_leftover` is the sibling `apply_leftover` never
had, at six sites; `is_px` and `is_only_leftover` name field comparisons the
surrounding comments had to translate; `Holds::covers` was interval
containment spelled out by hand. A span's `shared` loses the two arguments
that did not vary across its loop.
`LayoutHolds` was four two-element arrays where every other pair here is a
struct of two per-axis values, so nothing it did could be written once.
It becomes `AxisHolds` on `x` and `y`, and `and`, `covers` and `contains`
lose their loops.
Every pair gets `Index<Axis>`/`IndexMut<Axis>` through one macro, and the
eighteen `axis`/`axis_mut` methods go. `const_index` keeps the accessors
usable in const context.
Cold layout is unchanged: `layout_dump` over 400 depth-5 trees is identical
to 58ce74d byte for byte, across all 34,492 boxes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
3.8 KiB
Rust
104 lines
3.8 KiB
Rust
//! A measurement that decides control flow.
|
|
//!
|
|
//! Comparing boxes catches a widget that moved. It does not catch a widget
|
|
//! that measured a child, believed a different answer from the one a cold
|
|
//! start would give, and took the other branch -- which is the same defect
|
|
//! arriving somewhere it cannot be ignored. A widget here branches on what it
|
|
//! measured, so a disagreement shows up as a different tree.
|
|
|
|
use iris::harness::Harness;
|
|
use iris::prelude::*;
|
|
|
|
/// Measures `probe` across `axis` and draws one of two children on the
|
|
/// answer. Its own configuration never changes, so which child is drawn is a
|
|
/// property of the layout alone.
|
|
struct BranchesOnMeasurement {
|
|
probe: StrongWidget,
|
|
wide: StrongWidget,
|
|
narrow: StrongWidget,
|
|
threshold: f32,
|
|
}
|
|
|
|
impl Widget for BranchesOnMeasurement {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
|
let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
|
|
let measured = painter
|
|
.widget_at(&self.probe, top.axis(Axis::Y))
|
|
.len(Axis::X);
|
|
let px = painter.to_px(measured.apply_leftover(), Axis::X);
|
|
|
|
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
|
let place = below.axis(Axis::Y);
|
|
match px > Px::from_f32(self.threshold) {
|
|
true => painter.widget_at(&self.wide, place),
|
|
false => painter.widget_at(&self.narrow, place),
|
|
};
|
|
Size::LEFTOVER
|
|
}
|
|
}
|
|
|
|
fn plant(h: &mut Harness, threshold: f32) -> (WidgetId, WidgetId) {
|
|
let words = "the quick brown fox jumps over the lazy dog and keeps running";
|
|
let probe = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
|
let wide = rect(Color::RED).add(&mut h.rsc);
|
|
let narrow = rect(Color::BLUE).add(&mut h.rsc);
|
|
let branch = BranchesOnMeasurement {
|
|
probe: probe.add_strong(&mut h.rsc),
|
|
wide: wide.add_strong(&mut h.rsc),
|
|
narrow: narrow.add_strong(&mut h.rsc),
|
|
threshold,
|
|
}
|
|
.add(&mut h.rsc);
|
|
let side = rect(Color::GREEN).width(120).add(&mut h.rsc);
|
|
h.set_root((side, branch).span(Dir::RIGHT));
|
|
(wide.id(), narrow.id())
|
|
}
|
|
|
|
/// Which of the two branches drew, as a pair a test can compare.
|
|
fn taken(h: &Harness, wide: WidgetId, narrow: WidgetId) -> (bool, bool) {
|
|
(h.region(&wide).is_some(), h.region(&narrow).is_some())
|
|
}
|
|
|
|
#[test]
|
|
fn a_branch_taken_on_a_measurement_holds_across_repaints() {
|
|
for threshold in [0.0, 200.0, 400.0, 600.0, 779.0, 780.0, 781.0, 2000.0] {
|
|
let mut h = Harness::new((900, 600));
|
|
let (wide, narrow) = plant(&mut h, threshold);
|
|
let first = taken(&h, wide, narrow);
|
|
assert_ne!(first, (false, false), "threshold {threshold}: neither drew");
|
|
|
|
for frame in 0..4 {
|
|
h.rsc.widgets_mut().get_dyn_mut(wide);
|
|
h.rsc.widgets_mut().get_dyn_mut(narrow);
|
|
h.frame();
|
|
assert_eq!(
|
|
taken(&h, wide, narrow),
|
|
first,
|
|
"threshold {threshold}, repaint {frame}: the branch moved when nothing did"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_branch_taken_on_a_measurement_is_the_one_a_cold_start_takes() {
|
|
for threshold in [0.0, 200.0, 400.0, 600.0, 779.0, 780.0, 781.0, 2000.0] {
|
|
let mut warm = Harness::new((900, 600));
|
|
let (wide, narrow) = plant(&mut warm, threshold);
|
|
warm.resize((640, 480));
|
|
warm.frame();
|
|
warm.rsc.widgets_mut().get_dyn_mut(wide);
|
|
warm.frame();
|
|
|
|
let mut cold = Harness::new((640, 480));
|
|
let (cwide, cnarrow) = plant(&mut cold, threshold);
|
|
|
|
assert_eq!(
|
|
taken(&warm, wide, narrow),
|
|
taken(&cold, cwide, cnarrow),
|
|
"threshold {threshold}: warm and cold took different branches"
|
|
);
|
|
}
|
|
}
|