One argument says where a child goes and what its fractions are of

`Place` was a product written as a sum -- a `Part` and a fill flag -- and
`Part` named three operations the geometry already had, under words that did
not match them. `Of` was `UiSpan::within`, `From` was `UiSpan::shift`, and
`Sized` was `placement`'s own body with the length given rather than
reported. Both enums are gone.

`PlaceDescAxis` says one axis, named after the operation it performs:
`within`, `shifted`, `sized`, and `WHOLE`. What is optional is a builder --
`fills` and `rel_base` -- so a caller writes only what it decided, and the
rel base it does not write follows the constructor: a span composed into the
caller's box narrows it, a span along a cursor does not, a decided length is
it. That was the one rule a caller could get wrong with nothing failing.

`PlaceDesc` says both axes with named fields, so `axis`, `axis_mut` and
`from_axis` work the way they do on every other pair here, and the joint
work -- resolving a region, reading the fill flags -- is written once rather
than per axis. `widget_at` and `place_at` take `impl Into<PlaceDesc>`, so a
wrapper passes a `UiRegion` and says nothing else. `widget_within` and
`ActiveData::narrow_rel_base` are deleted; `asked` and `placed` carry the
rel base their ask stated.

Cold layout is byte-identical to `84dad21`.
This commit is contained in:
iris-ai committed 2026-09-19 17:59:26 -04:00
1 parent c55be21761
commit 58ce74dd7d
12 files changed
+367 -271

No files matched your search

+18 -38
View File
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
use crate::ui::painter::{declared_lens, placement, rel_base_and_region};
use crate::{
ActiveData, Axis, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Moves,
Painter, Part, PixelRegion, Place, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan,
Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan,
UiVec2, Weight, WidgetId, Widgets,
util::{HashMap, Vec2},
};
@@ -27,27 +27,16 @@ pub(super) struct DrawInfo {
/// coordinates.
pub region: UiRegion,
/// Where the widget is put, and where it was asked, as parts of the
/// parent's box. See [`Place`]. The two are one ask's place until the
/// parent's box. See [`PlaceDesc`]. The two are one ask's place until the
/// parent puts the answer somewhere else.
pub placed: [Place; 2],
pub asked: [Place; 2],
/// A rel base the parent decided for it on each axis, as a length of the
/// window, which the widget's own declaration is a fraction of.
pub narrow_rel_base: [Option<Len>; 2],
pub placed: PlaceDesc,
pub asked: PlaceDesc,
/// Whether the parent already asked about this widget in this draw.
pub re_asked: bool,
/// The rel base in pixels, resolved once against the window.
pub px: PxVec2,
}
impl DrawInfo {
/// The axes where the part is the drawing's box outright, which are the
/// axes the answer is not placed inside it again.
fn fill(&self) -> [bool; 2] {
self.placed.map(Place::fills)
}
}
/// What a widget's children are placed in: its own box, the coordinates its
/// drawing is in, and what else one ask of a child is decided from.
pub(super) struct Placing {
@@ -133,7 +122,7 @@ impl UiRenderState {
}
}
/// The root is asked about in the output. Its own rules narrow_rel_base both its
/// The root is asked about in the output. Its own rules narrow both its
/// rel base and box; nothing above it chose a different one.
fn root_info(&self, rel_base: UiVec2, region: UiRegion) -> DrawInfo {
let px = rel_base.to_px(self.output_size);
@@ -146,9 +135,8 @@ impl UiRenderState {
mask: MaskIdx::NONE,
rel_base,
region,
placed: [Place::Within(Part::WHOLE); 2],
asked: [Place::Within(Part::WHOLE); 2],
narrow_rel_base: [None; 2],
placed: PlaceDesc::WHOLE,
asked: PlaceDesc::WHOLE,
re_asked: false,
px,
}
@@ -209,8 +197,7 @@ impl UiRenderState {
rel_base_and_region(
UiRegion::FULL,
UiVec2::FULL_SIZE,
[Place::Within(Part::WHOLE); 2],
[None; 2],
PlaceDesc::WHOLE,
declared_lens(widgets, id),
widgets.alignment(id),
)
@@ -248,7 +235,7 @@ impl UiRenderState {
.then(|| self.retained_answer(id, region, info))
.flatten()
.and_then(|answer| {
let placed = placement(region, answer.0, declared, info.fill(), align);
let placed = placement(region, answer.0, declared, info.placed, align);
self.try_reuse(id, region, placed, info, rsc)
.map(|()| answer)
});
@@ -260,7 +247,7 @@ impl UiRenderState {
// Where the drawing goes: the part its parent gave it, with the
// answer placed inside that part on any axis the parent left
// open.
let placed = placement(region, answer.0, declared, info.fill(), align);
let placed = placement(region, answer.0, declared, info.placed, align);
if placed != region {
self.relocate(id, placed, info, rsc);
}
@@ -273,7 +260,6 @@ impl UiRenderState {
// and what of its own box it asked in. A local redraw asks the same
// question again from these.
active.rel_base = info.rel_base;
active.narrow_rel_base = info.narrow_rel_base;
active.re_asked = info.re_asked;
active.answer = Some(answer);
active.asked = info.asked;
@@ -468,9 +454,8 @@ impl UiRenderState {
mask,
rel_base: UiVec2::FULL_SIZE,
region: UiRegion::FULL,
placed: [Place::Within(Part::WHOLE); 2],
asked: [Place::Within(Part::WHOLE); 2],
narrow_rel_base: [None; 2],
placed: PlaceDesc::WHOLE,
asked: PlaceDesc::WHOLE,
re_asked: false,
px,
},
@@ -484,7 +469,6 @@ impl UiRenderState {
id,
placement: region,
rel_base: info.rel_base,
narrow_rel_base: info.narrow_rel_base,
placed: info.placed,
asked: info.asked,
region,
@@ -718,7 +702,7 @@ impl UiRenderState {
&mut self,
child: WidgetId,
at: &Placing,
place: [Place; 2],
place: PlaceDesc,
rsc: &mut dyn UiRsc,
) {
let active = &self.active[&child];
@@ -727,7 +711,7 @@ impl UiRenderState {
region,
active.measured().unwrap_or(active.size),
active.declared,
place.map(Place::fills),
place,
active.own_align,
);
let info = DrawInfo {
@@ -741,7 +725,6 @@ impl UiRenderState {
region,
placed: place,
asked: active.asked,
narrow_rel_base: active.narrow_rel_base,
re_asked: active.re_asked,
px: rel_base.to_px(at.window),
};
@@ -752,12 +735,11 @@ impl UiRenderState {
/// the box its parent is being taken as. What narrowed its rel base and what
/// it declared are its own record's, so both are resolved against that
/// parent's rel base again exactly as the first ask resolved them.
fn ask_again(active: &ActiveData, at: &Placing, place: [Place; 2]) -> (UiVec2, UiRegion) {
fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) {
rel_base_and_region(
at.region,
at.rel_base,
place,
active.narrow_rel_base,
active.declared,
active.own_align,
)
@@ -886,9 +868,8 @@ impl UiRenderState {
id,
placement: UiRegion::FULL,
rel_base: UiVec2::FULL_SIZE,
narrow_rel_base: [None; 2],
placed: [Place::Within(Part::WHOLE); 2],
asked: [Place::Within(Part::WHOLE); 2],
placed: PlaceDesc::WHOLE,
asked: PlaceDesc::WHOLE,
region: UiRegion::FULL,
answer: None,
re_asked: false,
@@ -1146,7 +1127,6 @@ impl UiRenderState {
region,
placed: active.asked,
asked: active.asked,
narrow_rel_base: active.narrow_rel_base,
re_asked: false,
px: rel_base.to_px(self.output_size),
};
@@ -1171,7 +1151,7 @@ impl UiRenderState {
}
if active.answer != was_answer || active.holds != was_holds {
// The parent retains both the answer and the drawing's validity;
// even an unchanged size can narrow_rel_base the range safe for a resize.
// even an unchanged size can narrow the range safe for a resize.
#[cfg(feature = "layout-diagnostics")]
{
diag::bump(Counter::SizeChanges);