Read a child's answer in the asker's frame, and drop the root move entry
A widget reports a fraction of the box it was given. Span added that
fraction straight into a cursor that counts fractions of the row, and Pad
summed its padding onto it, both right only while the offer had the
parent's whole extent -- which a span's does not after a relative child.
DrawResult::size and known_len now compose the answer through the offer's
length, so a container reads lengths of its own box.
That exposed placed_box scaling a fractional answer against a box the
parent had already chosen from it, halving a nested span twice. The
near-edge alignment override becomes per-axis `decided` flags: a box the
parent chose from the answer is the answer, and is not placed again.
Span decides the row axis; Scroll and Stack's sizing child decide both.
Alignment is always the widget's own property now.
The window is no longer a move entry. Chains bottom out in MoveIdx::NONE
and the window is applied where a fraction becomes pixels, in to_px on the
CPU and by the uniform in the shader, which now snaps the summed coordinate
since a floor does not distribute over a sum. A resize rewrites no entry.
Verified: view, minimal, random, tabs and text render byte-identical at
1920x1200 against 5f16617, a live resize to 1280x800 is identical to a
cold render, and the 100-seed oracle, all fifteen shrinker cases at 400
seeds of depth 5, and 1000 seeds of depth 6 pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f16617511
commit
5b7800264d
11 files changed
+182
-109
No files matched your search
+56
-20
@@ -135,30 +135,34 @@ impl<'a> Painter<'a> {
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, region, None)
|
||||
self.widget_at(id, region, [false; 2])
|
||||
}
|
||||
|
||||
/// Draws a widget with an alignment chosen by its container rather than
|
||||
/// the widget's property. Containers use this when the box they hand down
|
||||
/// already expresses the size they report around the child.
|
||||
pub fn widget_aligned<'s, W: ?Sized>(
|
||||
/// Draws a widget in a box this widget chose from the widget's own
|
||||
/// answer along the `decided` axes. On those the answer is not placed
|
||||
/// inside the box again: it already is the box, and a fraction the
|
||||
/// widget reported of its offer, taken of this box a second time, would
|
||||
/// shrink it twice. A container uses this where it hands back exactly
|
||||
/// what a child asked for -- a span placing a child at the length it
|
||||
/// reported, a scroll giving its content the content's own length.
|
||||
pub fn widget_decided<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
align: RegionAlign,
|
||||
decided: [bool; 2],
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, region, Some(align))
|
||||
self.widget_at(id, region, decided)
|
||||
}
|
||||
|
||||
fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
align_override: Option<RegionAlign>,
|
||||
decided: [bool; 2],
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
let declared = self.declared_lens(id);
|
||||
let align = align_override.unwrap_or_else(|| self.rsc.widgets().alignment(id.id()));
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
// Composing `FULL` through a box is not quite the identity in f32,
|
||||
// so a child with nothing declared keeps the box it would have had.
|
||||
let local = match declared.iter().any(Option::is_some) {
|
||||
@@ -200,7 +204,7 @@ impl<'a> Painter<'a> {
|
||||
offer,
|
||||
offered_px: self.px_within_offer(offer),
|
||||
slot_wide: self.slot_wide,
|
||||
align: align_override,
|
||||
decided,
|
||||
},
|
||||
None,
|
||||
self.rsc,
|
||||
@@ -216,7 +220,7 @@ impl<'a> Painter<'a> {
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size,
|
||||
size: in_parent_frame(size, local, declared),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +286,7 @@ impl<'a> Painter<'a> {
|
||||
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||
}
|
||||
Some(size.axis(axis))
|
||||
Some(in_parent_frame(size, local, declared).axis(axis))
|
||||
}
|
||||
|
||||
/// Whether this is the first box a child is asked about in during a draw
|
||||
@@ -393,7 +397,13 @@ impl<'a> Painter<'a> {
|
||||
/// near edge. A container that reports one child's size gives every child
|
||||
/// this, so what it draws is inside what it says it occupies.
|
||||
pub fn box_of(&self, size: Size) -> UiRegion {
|
||||
placed_box(UiRegion::FULL, size, RegionAlign::NEAR, [None; 2])
|
||||
placed_box(
|
||||
UiRegion::FULL,
|
||||
size,
|
||||
RegionAlign::NEAR,
|
||||
[None; 2],
|
||||
[false; 2],
|
||||
)
|
||||
}
|
||||
|
||||
/// This widget's box in pixels. Reading it makes the drawing one that
|
||||
@@ -517,6 +527,23 @@ impl PrimitiveLike for &TextureHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// A child's answer as lengths of the box it was asked from. A widget reports
|
||||
/// a fraction of the box it was given, and the widget that gave it wants the
|
||||
/// same length as a fraction of its own: one composition apart wherever the
|
||||
/// offer was not the whole of the parent's extent, as a span's is after a
|
||||
/// relative child. A declared axis is already the parent's: it resolved the
|
||||
/// rule in its own box, and the rule is what the report says.
|
||||
fn in_parent_frame(size: Size, local: UiRegion, declared: [Option<LayoutLen>; 2]) -> Size {
|
||||
let mut size = size;
|
||||
for (axis, declared) in AXES.into_iter().zip(declared) {
|
||||
if declared.is_none() {
|
||||
let len = local.axis(axis).len();
|
||||
*size.axis_mut(axis) = size.axis(axis).within_len(len);
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
||||
/// share of what is left over is only a length to the widget dividing one,
|
||||
/// so it passes up in the size instead.
|
||||
@@ -537,12 +564,20 @@ pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLe
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether what a widget reported along an axis is the whole of the box it
|
||||
/// is in rather than a part to be placed inside it. A share fills, because a
|
||||
/// share is a length only to whoever divides one, and whoever did is the one
|
||||
/// that handed down this box. A declared axis does too: `declared_box`
|
||||
/// already placed it, in the parent's box, and the rule's length is what the
|
||||
/// widget reports there. And an axis the parent decided from the answer is
|
||||
/// the answer already.
|
||||
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
|
||||
reported.leftover != Weight::ZERO || declared.is_some() || decided
|
||||
}
|
||||
|
||||
/// The box a drawing occupies: the size the widget reported, on the side of
|
||||
/// the box it was asked in that its alignment says. An axis reported as a
|
||||
/// share fills, because a share is a length only to whoever divides one, and
|
||||
/// whoever did is the one that handed down this box. A declared axis is
|
||||
/// left alone too: `declared_box` already placed it, in the parent's box,
|
||||
/// and the rule's length is what the widget reports there.
|
||||
/// the box it was asked in that its alignment says, on every axis that is
|
||||
/// not simply filled.
|
||||
///
|
||||
/// A reported fraction is a fraction of the box the widget drew in, where a
|
||||
/// declared one is a fraction of the box its parent handed down -- a span
|
||||
@@ -553,11 +588,12 @@ pub(crate) fn placed_box(
|
||||
size: Size,
|
||||
align: RegionAlign,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
decided: [bool; 2],
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for (axis, declared) in AXES.into_iter().zip(declared) {
|
||||
for (axis, (declared, decided)) in AXES.into_iter().zip(declared.into_iter().zip(decided)) {
|
||||
let reported = size.axis(axis);
|
||||
if reported.leftover != Weight::ZERO || declared.is_some() {
|
||||
if fills(reported, declared, decided) {
|
||||
continue;
|
||||
}
|
||||
let span = placed.axis_mut(axis);
|
||||
|
||||
Reference in new issue
Block a user