Retain frame and extent dependencies independently

Keep the original measurement placement separate from the assigned slot.
Validate frame and extent lengths before reusing an answer or drawing, and
represent hint-only records as having no measured answer.

Retain primitive and mask coordinates with their frame/extent reference.
Forwarded children follow a reused wrapper's placement without rerunning
valid draw bodies. Keep the single Widget::draw API.

Restore the eight failing suite cases from the region/placement prototype,
with regressions for mixed coordinate references, a changed inherited
extent, the sizing-stack fraction, and an undrawn share becoming visible.

This remains experimental: nested container updates do substantially more
work than e44dea3 despite restoring the leaf and wrapper reuse guarantees.
Do not merge it as a performance improvement.
This commit is contained in:
iris-ai committed 2026-09-17 14:50:09 -04:00
1 parent 5fcace1bfa
commit efb416bbc3
14 files changed
+548 -166

No files matched your search

+112 -69
View File
@@ -1,12 +1,12 @@
#[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Holds, LayoutLen, Len, Px, PxVec2, RegionAlign, RenderedText, Size, StrongWidget,
TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2,
Weight, WidgetId, Widgets,
Axis, DrawRegion, Holds, LayoutLen, Len, Px, PxVec2, RegionAlign, RenderedText,
RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
PrimitiveKind, TexturePrimitive,
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
TexturePrimitive,
},
ui::render_state::DrawInfo,
};
@@ -38,7 +38,11 @@ pub struct Painter<'a> {
pub(super) px: PxVec2,
pub(super) mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<PrimitiveHandle>,
pub(super) primitives: Vec<RetainedPrimitive>,
pub(super) mask_region: Option<DrawRegion>,
pub(super) inherited_children: Vec<WidgetId>,
pub(super) extent_own: [Holds; 2],
pub(super) extent_under: [Holds; 2],
pub(super) children: Vec<WidgetId>,
/// The children asked about so far, so the first box each was asked in
/// is the one recorded as its offer.
@@ -69,13 +73,13 @@ pub struct Painter<'a> {
}
impl<'a> Painter<'a> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: DrawRegion) {
let kind = self.rsc.ui_mut().primitives.kind::<P>();
self.write(kind, primitive, region);
}
/// Takes the kind, for a caller writing many of one primitive.
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: DrawRegion) {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::PrimitiveWrites);
let h = self.state.layers.write(
@@ -84,15 +88,15 @@ impl<'a> Painter<'a> {
kind,
id: self.id,
primitive,
region,
region: region.resolve(self.region, self.placement),
mask_idx: self.mask,
move_idx: self.move_idx,
},
);
self.push_primitive(h);
self.push_primitive(RetainedPrimitive { handle: h, region });
}
fn push_primitive(&mut self, h: PrimitiveHandle) {
fn push_primitive(&mut self, h: RetainedPrimitive) {
if self.mask != MaskIdx::NONE {
// TODO: I have no clue if this works at all :joy:
self.rsc.ui_mut().masks.push_ref(self.mask);
@@ -102,24 +106,29 @@ impl<'a> Painter<'a> {
/// Writes a primitive over the whole of this widget's own box.
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
let at = self.placed();
let at = DrawRegion::Extent(UiRegion::FULL);
let primitive = primitive.into_primitive(self);
self.primitive_at(primitive, at)
}
/// Writes one somewhere in this widget's region. A widget that wants its
/// own box rather than the region it was given composes through
/// [`Self::placement`] first.
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
/// Writes in the frame by default. `DrawRegion::Extent` keeps the local
/// geometry attached to this widget's box without reading its placement.
pub fn primitive_within(
&mut self,
primitive: impl PrimitiveLike,
region: impl Into<DrawRegion>,
) {
let primitive = primitive.into_primitive(self);
self.primitive_at(primitive, region.within(&self.region));
self.primitive_at(primitive, region.into());
}
/// `region` is in this widget's own region, as every region it writes is.
pub fn set_mask(&mut self, region: UiRegion) {
/// Sets a mask in the selected frame or extent coordinates.
pub fn set_mask(&mut self, region: impl Into<DrawRegion>) {
let region = region.into();
self.mask_region = Some(region);
assert!(self.mask == MaskIdx::NONE);
self.mask = self.rsc.ui_mut().masks.push(Mask {
region: region.within(&self.region),
region: region.resolve(self.region, self.placement),
move_idx: self.move_idx,
});
}
@@ -129,8 +138,8 @@ impl<'a> Painter<'a> {
/// where this widget was put. What a container that is only a wrapper
/// around one child wants, since its box is the child's.
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
let own = self.placement();
self.widget_at(id, UiRegion::FULL, [Some(own.x), Some(own.y)])
let own = self.placement;
self.widget_at_inner(id, UiRegion::FULL, [Some(own.x), Some(own.y)], true)
}
/// What a widget's rules declare its lengths to be, which whoever draws
@@ -146,6 +155,7 @@ impl<'a> Painter<'a> {
/// this frame; what it answered is still something this widget asked.
pub fn undraw<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
self.children.retain(|child| *child != id.id());
self.inherited_children.retain(|child| *child != id.id());
self.state.undraw_rec(id.id(), self.rsc);
}
@@ -181,6 +191,23 @@ impl<'a> Painter<'a> {
region: UiRegion,
placement: [Option<UiSpan>; 2],
) -> DrawResult<'s, 'a, W> {
self.widget_at_inner(id, region, placement, false)
}
fn widget_at_inner<'s, W: ?Sized>(
&'s mut self,
id: &'s StrongWidget<W>,
region: UiRegion,
placement: [Option<UiSpan>; 2],
inherited: bool,
) -> DrawResult<'s, 'a, W> {
if inherited {
if !self.inherited_children.contains(&id.id()) {
self.inherited_children.push(id.id());
}
} else {
self.inherited_children.retain(|child| *child != id.id());
}
let region_node = self.rsc.widgets().is_region_node(id.id());
let declared = self.declared_lens(id);
let align = self.rsc.widgets().alignment(id.id());
@@ -208,11 +235,16 @@ impl<'a> Painter<'a> {
.get(&id.id())
.map_or(given_len, |a| a.offer_len),
};
let offer_placement = if first_ask {
placement
} else {
self.state
.active
.get(&id.id())
.map_or(placement, |a| a.offer_placement)
};
let px = given_len.to_px(self.px);
let offered_px = offer_len.to_px(self.offered_px);
// Whether this ask is the child's offer question, which is a question
// about lengths: the same lengths somewhere else is the same question.
let answers_offer = self.at_offer && px == offered_px;
// The answer and what it holds for, both about the box asked in. The
// child's record may say something else once its drawing has been
// placed: a drawing made again in its placed box holds for that box.
@@ -228,6 +260,7 @@ impl<'a> Painter<'a> {
mask: self.mask,
given_len,
offer_len,
offer_placement,
px,
offered_px,
placement,
@@ -235,13 +268,23 @@ impl<'a> Painter<'a> {
None,
self.rsc,
);
if answers_offer {
self.state.active.get_mut(&id.id()).unwrap().answer = (size, holds);
}
// Whatever the child's answer holds for keeps this one to the boxes
// that give the child a length inside it.
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
for axis in AXES {
let n = axis as usize;
let frame = holds.frame[n].through(local.axis(axis).len());
self.under[n] = self.under[n].and(frame);
if inherited && declared[n].is_none() {
self.extent_under[n] = self.extent_under[n].and(holds.extent[n]);
self.reads_placement |= holds.placement.is_some();
} else {
let extent = placement[n].unwrap_or(UiSpan::FULL).len();
self.under[n] = self.under[n].and(
holds.extent[n]
.through(extent)
.through(local.axis(axis).len()),
);
}
}
DrawResult {
child: id,
@@ -286,28 +329,38 @@ impl<'a> Painter<'a> {
child: &StrongWidget<W>,
axis: Axis,
region: UiRegion,
placement: [Option<UiSpan>; 2],
) -> Option<LayoutLen> {
let declared = self.declared_lens(child);
let align = self.rsc.widgets().alignment(child.id());
let (local, _) = ask_box(region, declared, align, [None; 2]);
let first_ask = self.offer(child.id());
if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) {
active.offer_len = local.size();
}
let (local, placement) = ask_box(region, declared, align, placement);
let first_ask = self.at_offer && !self.offered.contains(&child.id());
if let Some(hint) = self.size_hint(child, axis) {
return Some(hint);
}
let px = local.size().to_px(self.px);
let (size, holds) =
self.state
.retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?;
let (size, holds) = self.state.retained_size(
child.id(),
px,
placement,
self.move_idx,
self.rsc.widgets(),
)?;
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::RetainedSizeHits);
self.depend_on(child);
if first_ask {
self.offered.push(child.id());
let active = self.state.active.get_mut(&child.id()).unwrap();
active.answer = (size, holds);
active.offer_len = local.size();
active.offer_placement = placement;
}
let placement = UiRegion {
x: placement[0].unwrap_or(UiSpan::FULL),
y: placement[1].unwrap_or(UiSpan::FULL),
};
let holds = holds.in_frame(placement);
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
}
@@ -343,22 +396,24 @@ impl<'a> Painter<'a> {
ui.text.render(buffer, attrs, width)
}
/// `origin` is in this widget's own region, as every region it writes is.
/// Writes glyphs in the selected frame or extent coordinates.
// TODO: merge the text methods into the primitive ones.
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
let origin = origin.within(&self.region);
pub fn glyphs(&mut self, text: &RenderedText, origin: impl Into<DrawRegion>) {
let origin = origin.into();
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
for glyph in text.glyphs.iter() {
let mut region = origin;
region.x.end = region.x.start;
region.y.end = region.y.start;
let mut region = region.offset(UiVec2::from_px(glyph.offset));
let size = PxVec2::new(
Px::from_int(glyph.entry.width as i32),
Px::from_int(glyph.entry.height as i32),
);
region.x.end = region.x.start.offset(size.x);
region.y.end = region.y.start.offset(size.y);
let region = origin.map(|mut region| {
region.x.end = region.x.start;
region.y.end = region.y.start;
let mut region = region.offset(UiVec2::from_px(glyph.offset));
let size = PxVec2::new(
Px::from_int(glyph.entry.width as i32),
Px::from_int(glyph.entry.height as i32),
);
region.x.end = region.x.start.offset(size.x);
region.y.end = region.y.start.offset(size.y);
region
});
self.write(
kind,
GlyphPrimitive {
@@ -393,15 +448,6 @@ impl<'a> Painter<'a> {
self.placement
}
/// This widget's own box in the coordinates its primitives are written
/// in: its placement composed through the region it was given.
fn placed(&mut self) -> UiRegion {
match self.placement() == UiRegion::FULL {
true => self.region,
false => self.placement.within(&self.region),
}
}
/// Where this widget sits in a box longer than the length it takes. A
/// widget that positions its own content reads it to place that content
/// the way the box around it would have placed the widget.
@@ -444,11 +490,11 @@ impl<'a> Painter<'a> {
/// One axis of this widget's own box in pixels. Prefer this to
/// [`Self::px_size`] when the other axis cannot affect the drawing.
pub fn px_len(&mut self, axis: Axis) -> Px {
let part = self.placement().axis(axis).len();
let part = self.placement.axis(axis).len();
let len = part.to_px(self.px.axis(axis));
let own = &mut self.own[axis as usize];
let own = &mut self.extent_own[axis as usize];
if *own == Holds::ANY {
*own = Holds::at(len).through(part);
*own = Holds::at(len);
}
len
}
@@ -458,7 +504,7 @@ impl<'a> Painter<'a> {
/// of the box, and the same reported size. A widget that read its length
/// in pixels holds for that one alone until it says otherwise.
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let part = self.placement().axis(axis).len();
let part = self.placement.axis(axis).len();
let holds = holds.into();
debug_assert!(
holds.contains(part.to_px(self.px.axis(axis))),
@@ -466,10 +512,7 @@ impl<'a> Painter<'a> {
self.label(),
self.id
);
// Kept as a range of the region's lengths, which is the one variable
// every range here is about: its own box is a part of that box, and
// `through` is the exact preimage of taking the part.
self.own[axis as usize] = holds.through(part);
self.extent_own[axis as usize] = holds;
}
/// One axis of the box this widget's parent gave it, in pixels -- what a