From a30971e4c5f650ef485c4a53886e04ebf3134fe4 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Sat, 19 Sep 2026 00:40:23 -0400 Subject: [PATCH] Call the record's boxes what they are The offer names are from the protocol before this one, where a widget was drawn twice and the record had to say which drawing was the question. It is asked once now, so offer_part is the part it was asked in, offer_place the place it was asked at, and place where its drawing was put: part, asked and placed. LayoutHolds::frame is a range on the window since the frame became a length of one, and the frame's own entry is the frame_len pin beside it, so it is window; Painter::frame_own goes with it. answers_at had one caller and said less than the line that replaces it. Co-Authored-By: Claude Opus 5 --- core/src/ui/active.rs | 28 +++++---------- core/src/ui/layout_holds.rs | 12 +++---- core/src/ui/painter.rs | 22 ++++++------ core/src/ui/render_state.rs | 70 ++++++++++++++++++++----------------- tests/cases/layout.rs | 4 +-- tests/scenario/mod.rs | 2 +- 6 files changed, 67 insertions(+), 71 deletions(-) diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index a3749e3..68204d6 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -19,17 +19,17 @@ pub struct ActiveData { /// forwards the parent's frame. What it declared is kept separately in /// `declared` and is a fraction of whichever of the two reached it. pub narrow: [Option; 2], - /// Where its drawing was put, as a part of its parent's box, and where - /// it was asked. The two differ where a container asks in one place and - /// places the answer in another -- a row measures from its cursor and + /// Where its drawing was put, and where it was asked, each as a part of + /// its parent's box. The two differ where a container asks in one place + /// and puts the answer in another -- a row measures from its cursor and /// puts the child in its slot. A part is a length from the box's start, /// so a box that moved re-places every child by re-adding that start. - pub place: [Place; 2], - pub offer_place: [Place; 2], + pub placed: [Place; 2], + pub asked: [Place; 2], /// The box it was asked in, in the parent's region-node coordinates: the /// box its drawing was made in and the one its contract is about. Its /// drawing is placed elsewhere by re-expression, never by asking again. - pub offer_part: UiRegion, + pub part: UiRegion, /// The measured answer and its dependencies. A hint-only dependency or /// a widget first encountered during placement has no measurement yet. pub answer: Option<(Size, LayoutHolds)>, @@ -82,20 +82,10 @@ pub struct ActiveData { } impl ActiveData { - /// What it answered when its parent measured it, where it has been - /// measured at all. Not `size`, which is what its last drawing reported: - /// a drawing made in the box that answer chose is answering a different - /// question. + /// What it answered when its parent asked, where it has been asked at + /// all. Not `size`, which is what its last drawing reported: a drawing + /// re-expressed in the box that answer chose is not a second answer. pub fn measured(&self) -> Option { self.answer.map(|(size, _)| size) } - - /// Whether what it answered still stands in this window, for the frame - /// and the box it was asked in. The answer was given in the box its - /// parent first asked about, which is what it is checked against -- - /// `holds` on the record is about the box the answer then chose. - pub fn answers_at(&self, window: crate::PxVec2, part: UiRegion) -> bool { - self.answer - .is_some_and(|(_, holds)| holds.contains(window, self.frame, part)) - } } diff --git a/core/src/ui/layout_holds.rs b/core/src/ui/layout_holds.rs index c108685..2216414 100644 --- a/core/src/ui/layout_holds.rs +++ b/core/src/ui/layout_holds.rs @@ -20,7 +20,7 @@ const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// whatever the frame turns out to be. #[derive(Clone, Copy, Debug, PartialEq)] pub struct LayoutHolds { - pub frame: [Holds; 2], + pub window: [Holds; 2], pub frame_len: [Option; 2], pub extent: [Holds; 2], pub extent_len: [Option; 2], @@ -28,7 +28,7 @@ pub struct LayoutHolds { impl LayoutHolds { pub const ANY: Self = Self { - frame: [Holds::ANY; 2], + window: [Holds::ANY; 2], frame_len: [None; 2], extent: [Holds::ANY; 2], extent_len: [None; 2], @@ -37,7 +37,7 @@ impl LayoutHolds { pub fn and(self, other: Self) -> Self { let mut result = Self::ANY; for n in 0..2 { - result.frame[n] = self.frame[n].and(other.frame[n]); + result.window[n] = self.window[n].and(other.window[n]); result.extent[n] = self.extent[n].and(other.extent[n]); debug_assert!( self.extent_len[n].is_none() @@ -57,8 +57,8 @@ impl LayoutHolds { pub fn covers(self, other: Self) -> bool { (0..2).all(|n| { - self.frame[n].lo <= other.frame[n].lo - && self.frame[n].hi >= other.frame[n].hi + self.window[n].lo <= other.window[n].lo + && self.window[n].hi >= other.window[n].hi && self.extent[n].lo <= other.extent[n].lo && self.extent[n].hi >= other.extent[n].hi && self.extent_len[n].is_none_or(|len| other.extent_len[n] == Some(len)) @@ -70,7 +70,7 @@ impl LayoutHolds { AXES.into_iter().all(|axis| { let n = axis as usize; let len = extent.axis(axis).len(); - self.frame[n].contains(window.axis(axis)) + self.window[n].contains(window.axis(axis)) && self.frame_len[n].is_none_or(|pinned| pinned == frame.axis(axis)) && self.extent[n].contains(len.to_px(window.axis(axis))) && self.extent_len[n].is_none_or(|pinned| pinned == len) diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index 86fbf0f..c09ab0d 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -42,7 +42,7 @@ pub struct Painter<'a> { pub(super) size_deps: Vec, /// What this draw itself read of the window in pixels, per axis: every /// window until it reads one, then that one, unless it says otherwise. - pub(super) frame_own: [Holds; 2], + pub(super) window_own: [Holds; 2], /// Its frame's symbolic length where this draw read it, which makes the /// drawing one that holds for that frame alone. pub(super) frame_own_len: [Option; 2], @@ -192,8 +192,8 @@ impl<'a> Painter<'a> { mask: self.mask, frame, part: extent, - place, - offer_place: place, + placed: place, + asked: place, narrow, re_asked, px, @@ -429,7 +429,7 @@ impl<'a> Painter<'a> { pub fn to_px(&mut self, len: Len, axis: Axis) -> Px { let window = self.window.axis(axis); if len.rel != Rel::ZERO { - let own = &mut self.frame_own[axis as usize]; + let own = &mut self.window_own[axis as usize]; if *own == Holds::ANY { *own = Holds::at(window); } @@ -437,8 +437,10 @@ impl<'a> Painter<'a> { len.to_px(window) } - /// A validity range already stated about the window. Containers use - /// this after branching on a window-unit length. + /// The windows this drawing holds for, stated rather than taken: a + /// container that branched on a length in pixels says which side of the + /// boundary it was on, which is wider than the one window reading that + /// length pins, and replaces it. pub fn window_holds(&mut self, axis: Axis, holds: impl Into) { let holds = holds.into(); debug_assert!( @@ -447,7 +449,7 @@ impl<'a> Painter<'a> { self.label(), self.id ); - self.frame_own[axis as usize] = holds; + self.window_own[axis as usize] = holds; } pub fn text_data(&mut self) -> &mut TextData { @@ -563,7 +565,7 @@ impl Painter<'_> { let n = axis as usize; // Every frame range is already a range on the window: the // widget's own read converted through its frame exactly once. - result.frame[n] = holds.frame[n]; + result.window[n] = holds.window[n]; let reaches = narrow[n].is_none() && !matches!(place[n].part(), Part::Sized(_)) && declared[n].is_none_or(|len| len.rel != Rel::ZERO); @@ -599,8 +601,8 @@ impl Painter<'_> { // it, so what it holds for is a range on the frame and none // of it on this widget's own box. _ => { - result.frame[n] = - result.frame[n].and(holds.extent[n].through(extent.axis(axis).len())); + result.window[n] = + result.window[n].and(holds.extent[n].through(extent.axis(axis).len())); } } } diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 42fef88..2f8e5b7 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -28,9 +28,9 @@ pub(super) struct DrawInfo { pub part: 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 places the answer somewhere else. - pub place: [Place; 2], - pub offer_place: [Place; 2], + /// parent puts the answer somewhere else. + pub placed: [Place; 2], + pub asked: [Place; 2], /// A frame 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: [Option; 2], @@ -44,7 +44,7 @@ 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.place.map(Place::fills) + self.placed.map(Place::fills) } } @@ -120,9 +120,13 @@ impl UiRenderState { let Some(root) = self.old_root else { return }; let stands = self.active.get(&root).is_some_and(|active| { // Nothing above the root chose anything, so the box it was first - // asked about is the whole of its frame. - active.answers_at(size, active.offer_part) - && active.holds.contains(size, active.frame, active.offer_part) + // asked about is the whole of its frame. Both its answer and its + // drawing have to stand in the new window, since nothing above + // it will ask either again. + let answer = active + .answer + .is_some_and(|(_, holds)| holds.contains(size, active.frame, active.part)); + answer && active.holds.contains(size, active.frame, active.part) }); if !stands { widgets.needs_redraw.insert(root); @@ -142,8 +146,8 @@ impl UiRenderState { mask: MaskIdx::NONE, frame, part: extent, - place: [Place::Within(Part::All); 2], - offer_place: [Place::Within(Part::All); 2], + placed: [Place::Within(Part::All); 2], + asked: [Place::Within(Part::All); 2], narrow: [None; 2], re_asked: false, px, @@ -267,9 +271,9 @@ impl UiRenderState { active.narrow = info.narrow; active.re_asked = info.re_asked; active.answer = Some(answer); - active.offer_place = info.offer_place; - active.offer_part = part; - active.place = info.place; + active.asked = info.asked; + active.part = part; + active.placed = info.placed; active.own_align = align; // A subtree can be reused whole under a different parent -- same box, // same layer, same region node -- and nothing in the drawing says it @@ -328,7 +332,7 @@ impl UiRenderState { mask_region: None, children: Vec::new(), size_deps: Vec::new(), - frame_own: [Holds::ANY; 2], + window_own: [Holds::ANY; 2], frame_own_len: [None; 2], under: Vec::new(), extent_own: [Holds::ANY; 2], @@ -364,7 +368,7 @@ impl UiRenderState { answer_under, children, size_deps, - frame_own, + window_own, frame_own_len, under, move_idx, @@ -435,7 +439,7 @@ impl UiRenderState { } }); let own_holds = LayoutHolds { - frame: frame_own, + window: window_own, frame_len, extent: extent_own, extent_len, @@ -465,8 +469,8 @@ impl UiRenderState { mask, frame: UiVec2::FULL_SIZE, part: UiRegion::FULL, - place: [Place::Within(Part::All); 2], - offer_place: [Place::Within(Part::All); 2], + placed: [Place::Within(Part::All); 2], + asked: [Place::Within(Part::All); 2], narrow: [None; 2], re_asked: false, px, @@ -482,9 +486,9 @@ impl UiRenderState { extent, frame: info.frame, narrow: info.narrow, - place: info.place, - offer_place: info.offer_place, - offer_part: extent, + placed: info.placed, + asked: info.asked, + part: extent, // Whoever asked writes the answer. answer: None, re_asked: info.re_asked, @@ -626,7 +630,7 @@ impl UiRenderState { if holds.extent_len[n].is_some_and(|pinned| pinned != part.axis(axis).len()) { diag::bump(Counter::OutsidePinnedLen); } - if !holds.frame[n].contains(self.output_size.axis(axis)) + if !holds.window[n].contains(self.output_size.axis(axis)) || holds.frame_len[n].is_some_and(|pinned| pinned != info.frame.axis(axis)) { diag::bump(Counter::OutsideFrame); @@ -672,7 +676,7 @@ impl UiRenderState { self.redepth(id, info.depth); let active = self.active.get_mut(&id).unwrap(); active.frame = info.frame; - active.place = info.place; + active.placed = info.placed; #[cfg(feature = "layout-diagnostics")] { match (moved, has_region_node) { @@ -697,7 +701,7 @@ impl UiRenderState { /// Places one child of `at.id` where that widget's own box now has it. fn place_child(&mut self, child: WidgetId, at: &Placing, rsc: &mut dyn UiRsc) { - let place = self.active[&child].place; + let place = self.active[&child].placed; self.place_in(child, at, place, rsc); } @@ -729,8 +733,8 @@ impl UiRenderState { mask: at.mask, frame, part, - place, - offer_place: active.offer_place, + placed: place, + asked: active.asked, narrow: active.narrow, re_asked: active.re_asked, px: frame.to_px(at.window), @@ -874,9 +878,9 @@ impl UiRenderState { extent: UiRegion::FULL, frame: UiVec2::FULL_SIZE, narrow: [None; 2], - place: [Place::Within(Part::All); 2], - offer_place: [Place::Within(Part::All); 2], - offer_part: UiRegion::FULL, + placed: [Place::Within(Part::All); 2], + asked: [Place::Within(Part::All); 2], + part: UiRegion::FULL, answer: None, re_asked: false, size, @@ -1113,14 +1117,14 @@ impl UiRenderState { self.draw_inner(id, info, old, rsc); return true; }; - let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.place); + let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.placed); // The question its parent asked, asked again: the same place of the // box the parent was asked in, which is the box the parent's own // draw ran in and what its children's parts are of. Where the // parent's answer put its own drawing is not a question anybody // asked, and nothing is asked in it here either. - let asked = self.placing_of(parent, self.active[&parent].offer_part); - let (frame, part) = Self::ask_again(active, &asked, active.offer_place); + let parent_at = self.placing_of(parent, self.active[&parent].part); + let (frame, part) = Self::ask_again(active, &parent_at, active.asked); let info = DrawInfo { layer: active.layer, parent: active.parent, @@ -1130,8 +1134,8 @@ impl UiRenderState { mask: active.parent_mask, frame, part, - place: active.offer_place, - offer_place: active.offer_place, + placed: active.asked, + asked: active.asked, narrow: active.narrow, re_asked: false, px: frame.to_px(self.output_size), diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index 3d97eb7..917e799 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -124,7 +124,7 @@ fn padding_keeps_the_frame_distinct_from_the_room_left_in_a_row() { h.set_root((icon, padded).span(Dir::RIGHT).width(rel(1.0))); let active = &h.render.active[&text.id()]; let window = h.render.output_size().x; - let asked = active.offer_part.x.len().to_px(window); + let asked = active.part.x.len().to_px(window); assert_eq!(active.frame.x.to_px(window), Px::from_int(868)); assert_eq!(asked, Px::from_int(844)); } @@ -171,7 +171,7 @@ fn padding_narrows_both_frame_and_box_inside_a_share() { let active = &h.render.active[&text.id()]; let window = h.render.output_size().x; assert_eq!(active.frame.x.to_px(window), Px::from_int(418)); - assert_eq!(active.offer_part.x.len().to_px(window), Px::from_int(418)); + assert_eq!(active.part.x.len().to_px(window), Px::from_int(418)); } #[test] diff --git a/tests/scenario/mod.rs b/tests/scenario/mod.rs index caefb14..555d978 100644 --- a/tests/scenario/mod.rs +++ b/tests/scenario/mod.rs @@ -407,7 +407,7 @@ fn record(id: WidgetId, h: &Harness) -> String { let active = &h.render.active[&id]; format!( "frame {} ask {} box {} size {}", - active.frame, active.offer_part, active.extent, active.size, + active.frame, active.part, active.extent, active.size, ) }