Make Iris layout dependencies explicit

This commit is contained in:
iris committed 2026-09-09 22:35:03 -04:00
1 parent 2bc0ff1866
commit 3ae034a47b
25 files changed
+609 -186

No files matched your search

+209 -6
View File
@@ -5,6 +5,7 @@
//! CPU-side layout/move machinery LAYOUT.md is about.
use crate::prelude::*;
use std::{cell::Cell, cell::RefCell, rc::Rc};
/// The minimal `UiRsc` a test needs: just the shared `UiData`, none of the
/// event/window/state plumbing `DefaultRsc` carries. `pub(crate)` so
@@ -26,14 +27,14 @@ impl UiRsc for TestRsc {
struct FixedRect(f32);
impl Widget for FixedRect {
fn draw(&mut self, painter: &mut Painter) -> Size {
fn draw(&mut self, painter: &mut Painter) {
let size = Size::from_axis(Axis::Y, Len::abs(self.0), Len::REST);
painter.primitive_within(
RectPrimitive::color(UiColor::WHITE),
size.to_uivec2(painter.density())
.align(RegionAlign::TOP_LEFT),
);
size
painter.set_size(size);
}
}
@@ -43,12 +44,214 @@ struct ChildOffset {
}
impl Widget for ChildOffset {
fn draw(&mut self, painter: &mut Painter) -> Size {
fn draw(&mut self, painter: &mut Painter) {
painter.set_child_offset(self.offset);
painter.widget(&self.child)
let size = painter.widget(&self.child).size();
painter.set_size(size);
}
}
struct TracedLeaf {
height: f32,
trace: Rc<RefCell<Vec<&'static str>>>,
}
impl Widget for TracedLeaf {
fn draw(&mut self, painter: &mut Painter) {
self.trace.borrow_mut().push("child");
painter.set_size(Size::from_axis(Axis::Y, Len::abs(self.height), Len::REST));
}
}
struct TracedParent {
child: StrongWidget,
reads_child_size: bool,
trace: Rc<RefCell<Vec<&'static str>>>,
}
struct CountedLeaf {
height: f32,
draws: Rc<Cell<u32>>,
}
impl Widget for CountedLeaf {
fn draw(&mut self, painter: &mut Painter) {
self.draws.set(self.draws.get() + 1);
painter.set_size(Size::from_axis(Axis::Y, Len::abs(self.height), Len::REST));
}
}
impl Widget for TracedParent {
fn draw(&mut self, painter: &mut Painter) {
self.trace.borrow_mut().push("parent");
let child = painter.widget(&self.child);
let size = if self.reads_child_size {
child.size()
} else {
Size::REST
};
painter.set_size(size);
}
}
/// Minimal reproduction for a container's child-layer cursor being retained
/// as though it were the layer on which the container itself was entered.
///
/// `Stack` is drawn by `Sized` on layer 0. It advances its painter to layers
/// 1 and 2 for its two children. The retained `ActiveData` must still say the
/// stack itself is on layer 0; otherwise an ordinary redraw of `Sized` asks
/// for the stack on 0 again and turns the invented 2 -> 0 change into a full
/// redraw of the stack and both children.
#[test]
fn a_widget_retains_its_entry_layer_not_its_child_cursor() {
let mut rsc = TestRsc {
ui: UiData::default(),
};
let back = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
let front = rsc.ui.widgets.add_strong(Rect::new(UiColor::RED));
let stack = rsc.ui.widgets.add_strong(Stack {
children: vec![back.any(), front.any()],
size: StackSize::Default,
});
let stack_id = stack.id();
let outer = rsc.ui.widgets.add_strong(Sized {
inner: stack.any(),
x: None,
y: None,
});
let outer_weak = outer.weak();
let root = outer.any();
let mut render = UiRenderState::new();
render.resize((100.0, 100.0));
render.update(&root, &mut rsc);
assert_eq!(
render.active[&stack_id].layer, 0,
"Stack entered on layer 0; layers 1 and 2 belong only to its children"
);
render.take_counters();
rsc.ui.widgets.get_mut(&outer_weak).unwrap().x = None;
render.update(&root, &mut rsc);
assert_eq!(
render.take_counters().0,
1,
"redrawing the parent should retain the unchanged Stack subtree"
);
}
#[test]
fn a_size_dependent_parent_is_invalidated_before_layout_runs_downward() {
let mut rsc = TestRsc {
ui: UiData::default(),
};
let trace = Rc::new(RefCell::new(Vec::new()));
let child = rsc.ui.widgets.add_strong(TracedLeaf {
height: 20.0,
trace: trace.clone(),
});
let child_weak = child.weak();
let parent = rsc.ui.widgets.add_strong(TracedParent {
child: child.any(),
reads_child_size: true,
trace: trace.clone(),
});
let parent_weak = parent.weak();
let root = parent.any();
let mut render = UiRenderState::new();
render.resize((100.0, 100.0));
render.update(&root, &mut rsc);
trace.borrow_mut().clear();
rsc.ui.widgets.get_mut(&child_weak).unwrap().height = 40.0;
render.update(&root, &mut rsc);
assert_eq!(&*trace.borrow(), &["parent", "child"]);
assert_eq!(render.active[&parent_weak.id()].size.y, Len::abs(40.0));
}
#[test]
fn a_parent_that_ignores_child_size_is_not_invalidated_with_it() {
let mut rsc = TestRsc {
ui: UiData::default(),
};
let trace = Rc::new(RefCell::new(Vec::new()));
let child = rsc.ui.widgets.add_strong(TracedLeaf {
height: 20.0,
trace: trace.clone(),
});
let child_weak = child.weak();
let parent = rsc.ui.widgets.add_strong(TracedParent {
child: child.any(),
reads_child_size: false,
trace: trace.clone(),
});
let root = parent.any();
let mut render = UiRenderState::new();
render.resize((100.0, 100.0));
render.update(&root, &mut rsc);
trace.borrow_mut().clear();
rsc.ui.widgets.get_mut(&child_weak).unwrap().height = 40.0;
render.update(&root, &mut rsc);
assert_eq!(&*trace.borrow(), &["child"]);
}
/// A content-sized vertical container grows vertically when one child grows,
/// but that does not invalidate another child's retained height. Its width is
/// the context that could change that height (for example through wrapping),
/// and that stayed fixed.
#[test]
fn a_span_reuses_unchanged_sibling_sizes_when_only_its_along_extent_changes() {
let mut rsc = TestRsc {
ui: UiData::default(),
};
let changed_draws = Rc::new(Cell::new(0));
let sibling_draws = Rc::new(Cell::new(0));
let changed = rsc.ui.widgets.add_strong(CountedLeaf {
height: 20.0,
draws: changed_draws.clone(),
});
let changed_weak = changed.weak();
let sibling = rsc.ui.widgets.add_strong(CountedLeaf {
height: 20.0,
draws: sibling_draws.clone(),
});
let span = rsc.ui.widgets.add_strong(Span {
children: vec![changed.any(), sibling.any()],
dir: Dir::DOWN,
gap: Len::ZERO,
});
// `Sized` settles its child from the full offered box into the content
// height, reproducing the retained-region change a nested content-sized
// row sees when one of its children grows.
let root = rsc
.ui
.widgets
.add_strong(Sized {
inner: span.any(),
x: None,
y: None,
})
.any();
let mut render = UiRenderState::new();
render.resize((100.0, 100.0));
render.update(&root, &mut rsc);
changed_draws.set(0);
sibling_draws.set(0);
rsc.ui.widgets.get_mut(&changed_weak).unwrap().height = 40.0;
render.update(&root, &mut rsc);
assert!(changed_draws.get() > 0, "the changed child was not redrawn");
assert_eq!(
sibling_draws.get(),
0,
"a vertical size change redrew a sibling whose width stayed fixed"
);
}
#[test]
fn a_child_coordinate_offset_moves_only_the_child_subtree() {
let mut rsc = TestRsc {
@@ -721,7 +924,7 @@ struct MoveThenPlace {
}
impl Widget for MoveThenPlace {
fn draw(&mut self, painter: &mut Painter) -> Size {
fn draw(&mut self, painter: &mut Painter) {
let offer = UiRegion::new(
UiSpan::FULL,
UiSpan::new(
@@ -738,7 +941,7 @@ impl Widget for MoveThenPlace {
),
);
painter.place(&self.inner, place);
Size::default()
painter.set_size(Size::default());
}
}