Hold a bound's length where it is decided, and say each thing once
A quality sweep over the deferred request system, which no earlier round has reviewed. `Bound::outside` said which end a length fell outside and left the caller to look that end up through `Bound::at`, which `expect`s an end the value it is given does not promise: only the pairing of the two calls kept `at(Shorter)` off a bound with no floor. It already had the length in hand, so it returns that, and `Outside` and `at` go with the state that could panic. `measured_request` pinned the rel base for any bound at all, so a measured share under a cap in pixels was invalidated by a change to a base its answer cannot depend on. That question is `Bound::has_fraction` now, which is also the one `Placing::ask` and `SizeRule::has_fraction` were each writing out over a bare array. The rest is one name where there were several spellings: `Span::gaps`, `Padding::along`, `Plan::drop_bounds` behind one `IRIS_UNBOUNDED` in both rigs that had grown their own, and `Stack::size_request` resolving its sizing child the way its draw already does. `Span`'s placement loop asked three times whether the row was allocated, twice to decide one child's length; one match answers all three, so the allocated and plain rules are read side by side. The buffers `draw_at` now reuses for their capacity are empty only because every path to it drains them in `remove`; a `debug_assert` says so, since a drawing over primitives left in one would record them twice. Comments: `with_requests` named discovery as the hazard where it is a child drawn mid-row, `Painter::allocate` documented the window it holds for rather than what it does, `minimum_request` had none, and the note saying a span carries its children's weight whole -- which is still what the unallocated path does, and still the surprising part -- had been replaced by one about the other path.
This commit is contained in:
1 parent
0e838e9dd1
commit
4cb6f6882a
12 files changed
+192
-160
No files matched your search
+37
-30
@@ -1,10 +1,10 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Bounds, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign,
|
||||
Rel, RenderedText, RequestArena, RequestedLen, RetainedPrimitive, Size, SizeRequests,
|
||||
StrongWidget, TextAttrs, TextBuffer, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2,
|
||||
Weight, WidgetId, Widgets,
|
||||
Axis, Bound, Bounds, Declared, DrawScratch, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc,
|
||||
PlaceFit, Px, PxVec2, RegionAlign, Rel, RenderedText, RequestArena, RequestedLen,
|
||||
RetainedPrimitive, Size, SizeRequests, SizeRule, StrongWidget, TextAttrs, TextBuffer,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -43,7 +43,7 @@ pub struct Painter<'a> {
|
||||
/// 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,
|
||||
pub(super) scratch: 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
|
||||
@@ -65,8 +65,9 @@ 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.
|
||||
/// Reuses this widget's allocation buffers across draws. A child drawn
|
||||
/// part way through gets a painter of its own, with buffers of its own,
|
||||
/// so nothing it does while this one is mid-row can reach these.
|
||||
pub fn with_requests<T>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self, &mut Vec<RequestedLen>, &mut Vec<Px>) -> T,
|
||||
@@ -87,7 +88,7 @@ impl<'a> Painter<'a> {
|
||||
child: &StrongWidget<W>,
|
||||
axis: Axis,
|
||||
) -> Option<RequestedLen> {
|
||||
if self.rsc.widgets().size_rules(child.id())[axis].bound() == crate::Bound::ANY
|
||||
if self.rsc.widgets().size_rules(child.id())[axis].bound() == Bound::ANY
|
||||
&& let Some(len) = self.size_hint(child, axis)
|
||||
{
|
||||
self.request_deps.push(child.id());
|
||||
@@ -107,7 +108,7 @@ impl<'a> Painter<'a> {
|
||||
request.has_leftover()
|
||||
|| matches!(
|
||||
self.rsc.widgets().size_rules(child.id())[axis],
|
||||
crate::SizeRule::Request(_)
|
||||
SizeRule::Request(_)
|
||||
)
|
||||
});
|
||||
if request.is_some() {
|
||||
@@ -144,19 +145,24 @@ impl<'a> Painter<'a> {
|
||||
self.rel_base(axis);
|
||||
return request;
|
||||
}
|
||||
let request = if len.leftover > Weight::ZERO {
|
||||
requests.bounded(len.into(), bound)
|
||||
} else {
|
||||
len.into()
|
||||
let shares = len.leftover > Weight::ZERO;
|
||||
let request = match shares {
|
||||
true => requests.bounded(len.into(), bound),
|
||||
false => len.into(),
|
||||
};
|
||||
self.request_deps.truncate(start);
|
||||
if len.leftover > Weight::ZERO && bound != crate::Bound::ANY {
|
||||
// Only a bound that is a fraction was read against the rel base; one
|
||||
// in pixels binds at the same length under any of them.
|
||||
if shares && bound.has_fraction() {
|
||||
self.rel_base(axis);
|
||||
}
|
||||
request
|
||||
}
|
||||
|
||||
/// A deferred comparison reads this window when the allocation is solved.
|
||||
/// Divides `room` between `requests`, one length per request in
|
||||
/// `output`. A deferred comparison is decided here, against this window:
|
||||
/// which side of a crossing the solution falls is a question in pixels,
|
||||
/// so the drawing holds only for the window that answered it.
|
||||
pub fn allocate(
|
||||
&mut self,
|
||||
requests: &[RequestedLen],
|
||||
@@ -174,6 +180,8 @@ impl<'a> Painter<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
/// The least a request can come to, which is what it takes of the row
|
||||
/// before anything is divided. A comparison is read at no share at all.
|
||||
pub fn minimum_request(&mut self, request: &RequestedLen, axis: Axis) -> Len {
|
||||
match request.linear() {
|
||||
Some(len) => len.without_leftover(),
|
||||
@@ -553,7 +561,7 @@ impl<'a> Painter<'a> {
|
||||
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
||||
matches!(
|
||||
self.rsc.widgets().size_rules(self.id)[axis],
|
||||
crate::SizeRule::Exact(_) | crate::SizeRule::Request(_)
|
||||
SizeRule::Exact(_) | SizeRule::Request(_)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -797,7 +805,11 @@ 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(_)) {
|
||||
// A request is a length the rule gives, and the hint below must not
|
||||
// narrow the box in its place: what the request comes to is not known
|
||||
// until the parent allocates, and it is the parent's answer, not this
|
||||
// widget's.
|
||||
if matches!(self.size_rules(id)[axis], SizeRule::Request(_)) {
|
||||
return None;
|
||||
}
|
||||
self.size_rules(id)[axis].exact().or_else(|| {
|
||||
@@ -868,23 +880,22 @@ impl Placing {
|
||||
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] {
|
||||
if let 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
|
||||
let px = if place[axis].fit == PlaceFit::Allocated {
|
||||
offer.to_px(window[axis])
|
||||
} else {
|
||||
let request = requests.import(request, base);
|
||||
let len = requests
|
||||
holds[axis].window = Holds::at(window[axis]);
|
||||
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)
|
||||
.unwrap()
|
||||
};
|
||||
let len = Len::from_parts(Rel::ZERO, len.to_px(window[axis]));
|
||||
let len = Len::from_parts(Rel::ZERO, px);
|
||||
place[axis].rel_base = RelBase::Len(len);
|
||||
declared[axis] = Some(len);
|
||||
holds[axis].rel_base = Some(len);
|
||||
@@ -909,11 +920,7 @@ impl Placing {
|
||||
// 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.
|
||||
let bound = rules[axis].bound();
|
||||
if [bound.min, bound.max]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.any(|len| len.rel != Rel::ZERO)
|
||||
{
|
||||
if bound.has_fraction() {
|
||||
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
||||
}
|
||||
bounds[axis] = bound.within_len(base);
|
||||
|
||||
Reference in new issue
Block a user