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

+92 -20
View File
@@ -25,12 +25,46 @@ pub struct Painter<'a> {
/// Previous handles, consumed in draw order and freed if left over.
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
pub(super) children: Vec<WidgetId>,
pub(super) reuse_child_sizes: bool,
pub(super) size_dependencies: Vec<WidgetId>,
pub(super) size: Option<Size>,
/// Whether a retained child's length on each axis is still valid. A
/// child's length may change when the parent's orthogonal extent changes
/// (most importantly, wrapped text gets taller when it gets narrower),
/// but not merely because a content-sized parent grew along that same
/// axis around one of its siblings.
pub(super) reuse_child_sizes: [bool; 2],
pub layer: usize,
pub(super) id: WidgetId,
}
/// A child draw whose size has not necessarily been observed by its parent.
/// Holding this value keeps the painter borrowed, so `.size()` can only name
/// the child from the immediately preceding draw.
pub struct DrawResult<'p, 'a> {
painter: &'p mut Painter<'a>,
child: WidgetId,
}
impl DrawResult<'_, '_> {
/// Return the child's reported size and record the layout dependency.
pub fn size(self) -> Size {
if !self.painter.size_dependencies.contains(&self.child) {
self.painter.size_dependencies.push(self.child);
}
self.painter.state.active[&self.child].size
}
}
impl<'a> Painter<'a> {
/// Record the size this widget used. Every `Widget::draw` calls this
/// exactly once; parents observe it through [`DrawResult::size`].
pub fn set_size(&mut self, size: Size) {
assert!(
self.size.replace(size).is_none(),
"a widget set its size more than once during one draw"
);
}
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.write_primitive(primitive, region, Drawn::Yes);
}
@@ -207,15 +241,19 @@ impl<'a> Painter<'a> {
self.mask = self.own_mask;
}
/// Draws a widget within this widget's region, returning the size it
/// reported using.
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> Size {
/// Draw a widget within this widget's region. Reading the result's size
/// records that this widget's layout depends on the child.
pub fn widget<'p, W: ?Sized>(&'p mut self, id: &StrongWidget<W>) -> DrawResult<'p, 'a> {
self.widget_at(id, self.region)
}
/// Draws a widget somewhere within this one.
/// Useful for drawing child widgets in select areas.
pub fn widget_within<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
pub fn widget_within<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
self.widget_at(id, region.within(&self.region))
}
@@ -260,17 +298,29 @@ impl<'a> Painter<'a> {
}
}
pub fn known_len<W: ?Sized>(&self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
return Some(len.fold_dp(self.density()));
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
Some(len.fold_dp(self.density()))
} else if !self.reuse_child_sizes[match axis {
Axis::X => 0,
Axis::Y => 1,
}] || self.rsc.widgets().needs_redraw.contains(&id.id())
{
None
} else {
self.state.active.get(&id.id()).map(|a| a.size.axis(axis))
};
if len.is_some() && !self.size_dependencies.contains(&id.id()) {
self.size_dependencies.push(id.id());
}
if !self.reuse_child_sizes || self.rsc.widgets().needs_redraw.contains(&id.id()) {
return None;
}
self.state.active.get(&id.id()).map(|a| a.size.axis(axis))
len
}
fn widget_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
fn widget_at<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
self.children.push(id.id());
// Passed directly rather than looked up from `self.active`: this
// widget's own `ActiveData` (which would carry its `move_slot`) is
@@ -289,19 +339,26 @@ impl<'a> Painter<'a> {
self.mask,
Retained::default(),
self.rsc,
)
);
DrawResult {
painter: self,
child: id.id(),
}
}
/// Place an already-drawn child's used area, redrawing only if its size changes.
pub fn place<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
pub fn place<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
let region = region.within(&self.region);
let retained = self
.state
.active
.get(&id.id())
.map(|active| (active.layer, active.mask));
if let Some(size) = self.state.place(id.id(), region, self.rsc) {
size
if self.state.place(id.id(), region, self.rsc).is_some() {
} else if let Some((layer, mask)) = retained {
self.children.push(id.id());
self.rsc.widgets_mut().needs_redraw.insert(id.id());
@@ -315,9 +372,24 @@ impl<'a> Painter<'a> {
mask,
Retained::default(),
self.rsc,
)
);
} else {
self.widget_at(id, region)
self.children.push(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.state.draw_inner(
self.layer,
id.id(),
region,
Some(self.id),
parent_move_slot.idx() as u32,
self.mask,
Retained::default(),
self.rsc,
);
}
DrawResult {
painter: self,
child: id.id(),
}
}
@@ -326,7 +398,7 @@ impl<'a> Painter<'a> {
id: &StrongWidget<W>,
used: Size,
within: UiRegion,
) -> Size {
) -> DrawResult<'_, 'a> {
let region = self.fit_region(used, within);
self.place(id, region)
}