Resolve deferred size comparisons before allocating span slots

This commit is contained in:
iris-ai committed 2026-09-20 16:50:18 -04:00
1 parent de1eb7e406
commit 8780b40bb7
23 files changed
+1471 -130

No files matched your search

+161 -12
View File
@@ -2,8 +2,9 @@
use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign,
Rel, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
Rel, RenderedText, RequestArena, RequestedLen, RetainedPrimitive, Size, SizeRequests,
StrongWidget, TextAttrs, TextBuffer, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2,
Weight, WidgetId, Widgets,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
TexturePrimitive,
@@ -41,6 +42,8 @@ pub struct Painter<'a> {
pub(super) children: Vec<WidgetId>,
/// The children whose size this widget read while drawing.
pub(super) size_deps: Vec<WidgetId>,
pub(super) request_deps: Vec<WidgetId>,
pub(super) scratch: crate::DrawScratch,
/// What this draw itself reads, as against what its children's drawings
/// hold for: every window and every length of its own region until it
/// reads one, then that one unless it says otherwise, and the rel base or
@@ -62,6 +65,106 @@ pub struct Painter<'a> {
}
impl<'a> Painter<'a> {
/// Reuses this widget's allocation buffers across draws. Nested painters
/// have independent buffers, so discovering a child cannot overwrite them.
pub fn with_requests<T>(
&mut self,
f: impl FnOnce(&mut Self, &mut Vec<RequestedLen>, &mut Vec<Px>) -> T,
) -> T {
let mut requests = std::mem::take(&mut self.scratch.requests);
let mut lengths = std::mem::take(&mut self.scratch.lengths);
requests.clear();
lengths.clear();
let result = f(self, &mut requests, &mut lengths);
self.scratch.requests = requests;
self.scratch.lengths = lengths;
result
}
/// Discovers a composable request without painting a provisional box.
pub fn size_request<W: ?Sized>(
&mut self,
child: &StrongWidget<W>,
axis: Axis,
) -> Option<RequestedLen> {
self.request_deps.push(child.id());
if self.rsc.widgets().size_rules(child.id())[axis].bound() == crate::Bound::ANY
&& let Some(len) = self.size_hint(child, axis)
{
return Some(len.into());
}
let rel_base = self.rel_base(axis);
let mut requests = SizeRequests {
arena: &mut self.state.requests,
measured: None,
widgets: self.rsc.widgets(),
dependencies: &mut self.request_deps,
rel_base,
};
let request = requests.widget(child, axis)?;
// An intrinsic fixed answer still draws in the offered room and is
// moved afterwards. Only a declaration or a share chooses its ask box.
(request.has_leftover()
|| matches!(
self.rsc.widgets().size_rules(child.id())[axis],
crate::SizeRule::Request(_)
))
.then_some(request)
}
/// Completes discovery after a child was measured. Only this call may use
/// drawn answers: before the ask they could belong to an obsolete box.
pub fn measured_request<W: ?Sized>(
&mut self,
child: &StrongWidget<W>,
axis: Axis,
len: LayoutLen,
) -> RequestedLen {
let rel_base = self.rel_base(axis);
let bound = self.rsc.widgets().size_rules(child.id())[axis].bound();
let mut requests = SizeRequests {
arena: &mut self.state.requests,
measured: Some(&self.state.active),
widgets: self.rsc.widgets(),
dependencies: &mut self.request_deps,
rel_base,
};
match requests.widget(child, axis) {
Some(request) if request.linear().is_none() && request.has_leftover() => request,
_ if len.leftover > Weight::ZERO => requests.bounded(len.into(), bound),
_ => len.into(),
}
}
/// A deferred comparison reads this window when the allocation is solved.
pub fn allocate(
&mut self,
requests: &[RequestedLen],
room: Len,
axis: Axis,
output: &mut Vec<Px>,
) {
let window = self.window[axis];
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
output.clear();
output.extend(
self.state
.requests
.allocate(requests, room.to_px(window), window),
)
}
pub fn minimum_request(&mut self, request: &RequestedLen, axis: Axis) -> Len {
match request.linear() {
Some(len) => len.without_leftover(),
None => {
let window = self.window[axis];
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
Len::from_parts(Rel::ZERO, self.state.requests.minimum(*request, window))
}
}
}
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let kind = self.rsc.ui_mut().primitives.kind::<P>();
self.write(kind, primitive, region);
@@ -195,9 +298,15 @@ impl<'a> Painter<'a> {
declared,
bounds,
holds: ask_holds,
} = self
.placing()
.ask(self.rsc.widgets(), self.window, id.id(), offer);
inputs,
} = self.placing().ask(
self.rsc.widgets(),
&mut self.state.requests,
self.window,
id.id(),
offer,
);
self.own = self.own.and(inputs);
let region_node = self.rsc.widgets().is_region_node(id.id());
#[cfg(feature = "layout-diagnostics")]
if region_node {
@@ -422,9 +531,10 @@ impl<'a> Painter<'a> {
/// worth anything, since reading one is also what makes its own size
/// depend on it.
pub fn has_exact_size(&self, axis: Axis) -> bool {
self.rsc.widgets().size_rules(self.id)[axis]
.exact()
.is_some()
matches!(
self.rsc.widgets().size_rules(self.id)[axis],
crate::SizeRule::Exact(_) | crate::SizeRule::Request(_)
)
}
/// This widget's own box in pixels. Reading it makes the drawing one
@@ -667,6 +777,9 @@ impl Widgets {
/// share included, since a share is a length only to whoever divides one,
/// and that is the parent rather than this widget.
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
if matches!(self.size_rules(id)[axis], crate::SizeRule::Request(_)) {
return None;
}
self.size_rules(id)[axis].exact().or_else(|| {
// A hint still narrows the box where no rule does, which is how a
// widget with a natural pixel size -- an image, a gap -- gets that
@@ -706,6 +819,9 @@ pub(super) struct Ask {
/// drawing it is part of. Kept on the widget asked about rather than on
/// the asker because the root has no asker.
pub holds: LayoutHolds,
/// Inputs read against the parent before declarations choose a new base.
/// These belong to the asker; the widget's own holds describe its output box.
pub inputs: LayoutHolds,
}
impl Placing {
@@ -719,6 +835,7 @@ impl Placing {
pub(super) fn ask(
&self,
widgets: &Widgets,
requests: &mut RequestArena,
window: PxVec2,
id: WidgetId,
mut place: PlaceDesc,
@@ -726,10 +843,33 @@ impl Placing {
let align = widgets.alignment(id);
let rules = widgets.size_rules(id);
let mut holds = LayoutHolds::ANY;
let declared = widgets.declared_lens(id);
let mut inputs = LayoutHolds::ANY;
let mut declared = widgets.declared_lens(id);
let mut bounds = Bounds::ANY;
for axis in Axis::BOTH {
let base = place.base(axis, self.rel_base);
if let crate::SizeRule::Request(request) = &rules[axis] {
inputs[axis].rel_base = Some(self.rel_base[axis]);
inputs[axis].region_len = Some(self.region[axis].len());
inputs[axis].window = Holds::at(window[axis]);
let offer = place.of(self.region, align)[axis].len();
let len = if place[axis].fit == crate::PlaceFit::Allocated {
offer
} else {
let request = requests.import(request, base);
let len = requests
.allocate(&[request], offer.to_px(window[axis]), window[axis])
.next()
.unwrap();
holds[axis].window = Holds::at(window[axis]);
Len::from_parts(Rel::ZERO, len)
};
let len = Len::from_parts(Rel::ZERO, len.to_px(window[axis]));
place[axis].rel_base = RelBase::Len(len);
declared[axis] = Some(len);
holds[axis].rel_base = Some(len);
continue;
}
// A share fills what the pixels and fraction beside it leave of
// the box and overflows where they are longer, which is the rule
// a span follows with one child. Only the overflow is a box of
@@ -748,7 +888,15 @@ impl Placing {
// anything. Resolved here because only the ask knows the rel base
// a fraction in it is of. `MaxSize` is the box version, and it is
// a widget because a widget is drawn again when its box changes.
bounds[axis] = rules[axis].bound().within_len(base);
let bound = rules[axis].bound();
if [bound.min, bound.max]
.into_iter()
.flatten()
.any(|len| len.rel != Rel::ZERO)
{
inputs[axis].rel_base = Some(self.rel_base[axis]);
}
bounds[axis] = bound.within_len(base);
}
let (rel_base, region) =
place.rel_base_and_region(self.region, self.rel_base, declared, align);
@@ -759,6 +907,7 @@ impl Placing {
declared,
bounds,
holds,
inputs,
}
}
@@ -785,7 +934,7 @@ impl Placing {
// decided, and a parent that divides one has already given the share
// whatever it was owed. Only an offer -- a box with the answer still
// to be placed inside it -- is a box a share reads.
if place[axis].fills {
if place[axis].fit.fills() {
return (None, Holds::ANY);
}
// A share with nothing beside it is the box whatever the box is, so
@@ -838,7 +987,7 @@ impl PlaceDesc {
let mut placed = region;
for axis in Axis::BOTH {
let reported = size[axis];
if reported.fills(declared[axis], self[axis].fills) {
if reported.fills(declared[axis], self[axis].fit.fills()) {
continue;
}
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);