diff --git a/core/src/ui/active.rs b/core/src/ui/active.rs index b5f04a9..10abcd0 100644 --- a/core/src/ui/active.rs +++ b/core/src/ui/active.rs @@ -1,6 +1,6 @@ use crate::{ - DrawRegion, LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, RegionAlign, RetainedPrimitive, - Size, TextureHandle, UiRegion, UiVec2, WidgetId, + LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive, Size, + TextureHandle, UiRegion, WidgetId, }; /// What is kept of a widget its parent has asked about. `drawn` says whether @@ -9,28 +9,35 @@ use crate::{ #[derive(Debug)] pub struct ActiveData { pub id: WidgetId, - /// The box its parent gave it, in `parent_move`'s coordinates: what it - /// was asked about, and what a fraction under it is a fraction of. A - /// local redraw asks here. - pub region: UiRegion, - /// Where its drawing sits inside that box, in the box's own coordinates. - pub placement: UiRegion, - /// The original frame in its parent widget's coordinates. Recomposition - /// and pixel-length evaluation both follow this chain. - pub given_region: UiRegion, - /// The lengths of the box its parent first asked about it in, as - /// lengths of the box the parent was itself offered. Any later box it - /// was given was decided knowing its answer, so this is the question - /// asked again -- and a chain of fractions has no frame in it, which is - /// why a region node between two widgets cannot break it. - pub offer_len: UiVec2, - pub offer_placement: [Option; 2], + /// Its frame in `parent_move`'s coordinates: what a fraction it declares + /// or reports is a fraction of, composed. Everything it draws sits inside + /// this by way of `extent`. + pub frame_abs: UiRegion, + /// Where its drawing goes, in the frame's own coordinates. + pub extent: UiRegion, + /// That frame in its parent's frame coordinates, before composition: + /// forwarded whole by a transparent container, narrowed by a declared + /// length or an inset. Its length is the same on every ask, which is what + /// a local redraw relies on to ask its parent's own question again. + pub frame: UiRegion, + /// What of its parent's extent the drawing was given, and what it was + /// given at the parent's first ask of it -- the question a cold layout + /// asks. A part is a length from the extent's start, so an extent that + /// moved re-places every child by re-adding that start. + pub place: [Place; 2], + pub offer_place: [Place; 2], + /// The box that ask gave it, in its frame's coordinates. Kept rather + /// than worked out again from where its parent's own box is now: a + /// parent drawn again in the box its own answer chose gives its children + /// boxes it never measured anything in, and the measurement this widget + /// answered is the one its parent's layout was built on. + pub offer_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)>, - /// What the widget said it used of its box, the last time it drew. + /// What the widget said it used of its frame, the last time it drew. pub size: Size, - /// The frame, extent and explicit placement reads that this drawing holds for. + /// The frame and extent reads that this drawing holds for. pub holds: LayoutHolds, pub drawn: bool, pub parent: Option, @@ -39,30 +46,24 @@ pub struct ActiveData { /// widget a frame visits and cannot drift while one is being drawn. pub depth: usize, pub textures: Vec, + /// Its primitives, each keeping the box it was written in -- in this + /// widget's extent coordinates, which is what a move recomposes from. pub primitives: Vec, - pub mask_region: Option, - /// The children whose box is a part of this widget's extent rather than - /// of its frame, and which part each was given. Moving the extent - /// re-places them through that part, so the drawing need not depend on - /// where it sits. - pub(crate) extent_children: Vec<(WidgetId, ExtentPlacement)>, + pub mask_region: Option, pub children: Vec, /// The children whose size this widget read while drawing. pub size_deps: Vec, /// The movable region its primitives are positioned through: its own when /// opted in, otherwise the nearest ancestor's. pub move_idx: MoveIdx, - /// The declared lengths whoever drew this widget resolved into its box. + /// The declared lengths whoever drew this widget resolved into its frame. /// A change to one moves a box this widget cannot fix by drawing again, /// and comparing them is what says so. pub declared: [Option; 2], - /// The axes along which its parent chose its box from its own answer, - /// so a local redraw asks the question its parent asked. - pub decided: [bool; 2], /// Its alignment when it was last drawn, which a change to the property /// is found against. pub own_align: RegionAlign, - /// The movable region whose coordinates `region` uses. + /// The movable region whose coordinates `frame_abs` uses. pub parent_move: MoveIdx, /// The mask its drawing is clipped to: one it set itself, or the one it /// inherited from whoever drew it. @@ -76,37 +77,12 @@ pub struct ActiveData { } impl ActiveData { - /// Whether what it answered still stands for a box of these pixel - /// lengths -- the box it was asked in, where `holds` is about the box its - /// answer then chose. - pub fn answers_at(&self, px: crate::PxVec2) -> bool { - self.answer.is_some_and(|(_, holds)| { - holds.contains( - px, - UiRegion { - x: self.offer_placement[0].unwrap_or(crate::UiSpan::FULL), - y: self.offer_placement[1].unwrap_or(crate::UiSpan::FULL), - }, - ) - }) - } -} - -/// What of a container's extent a child was given: the whole of it, for a -/// wrapper whose box is its child's, or a part of it. -#[derive(Clone, Copy, Debug, PartialEq)] -pub(crate) enum ExtentPlacement { - Inherit, - Within(UiRegion), -} - -impl ExtentPlacement { - /// The child's frame in the container's frame coordinates, and the slot - /// the container chose within it. - pub fn resolve(self, extent: UiRegion) -> (UiRegion, [Option; 2]) { - match self { - Self::Inherit => (UiRegion::FULL, [Some(extent.x), Some(extent.y)]), - Self::Within(part) => (part.within(&extent), [None; 2]), - } + /// Whether what it answered still stands for a frame of these pixel + /// lengths. 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, px: crate::PxVec2, part: UiRegion) -> bool { + self.answer + .is_some_and(|(_, holds)| holds.contains(px, part)) } } diff --git a/core/src/ui/draw_region.rs b/core/src/ui/draw_region.rs deleted file mode 100644 index a3bf5df..0000000 --- a/core/src/ui/draw_region.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{PrimitiveHandle, UiRegion}; - -/// Retains which box geometry follows when only the extent changes. -#[derive(Clone, Copy, Debug)] -pub enum DrawRegion { - Frame(UiRegion), - Extent(UiRegion), -} - -impl DrawRegion { - pub(crate) fn resolve(self, frame: UiRegion, extent: UiRegion) -> UiRegion { - match self { - Self::Frame(local) => local.within(&frame), - Self::Extent(local) => local.within(&extent).within(&frame), - } - } - - pub(crate) fn map(self, f: impl FnOnce(UiRegion) -> UiRegion) -> Self { - match self { - Self::Frame(local) => Self::Frame(f(local)), - Self::Extent(local) => Self::Extent(f(local)), - } - } -} - -impl From for DrawRegion { - fn from(region: UiRegion) -> Self { - Self::Frame(region) - } -} - -#[derive(Debug)] -pub struct RetainedPrimitive { - pub handle: PrimitiveHandle, - pub region: DrawRegion, -} diff --git a/core/src/ui/layout_holds.rs b/core/src/ui/layout_holds.rs index abc2d59..a9675ce 100644 --- a/core/src/ui/layout_holds.rs +++ b/core/src/ui/layout_holds.rs @@ -1,63 +1,62 @@ -use crate::{Axis, Holds, PxVec2, UiRegion}; +use crate::{Axis, Holds, Len, PxVec2, UiRegion}; -/// Dependencies of one evaluation, before the frame and extent are composed. +const AXES: [Axis; 2] = [Axis::X, Axis::Y]; + +/// What one evaluation of a widget depends on: the pixel lengths of its +/// frame and of its own box that its drawing and its answer hold for, and +/// the symbolic length of its own box where it read one. +/// +/// The symbolic length is a pin rather than a range: a container places its +/// children as lengths of its frame measured from where its own box starts, +/// so what it draws turns on that box's length and on nothing about where it +/// is. It does not compose into the parent -- a widget pinned this way is +/// checked when it is re-placed. #[derive(Clone, Copy, Debug, PartialEq)] pub struct LayoutHolds { pub frame: [Holds; 2], pub extent: [Holds; 2], - pub placement: Option, + pub extent_len: [Option; 2], } impl LayoutHolds { pub const ANY: Self = Self { frame: [Holds::ANY; 2], extent: [Holds::ANY; 2], - placement: None, + extent_len: [None; 2], }; pub fn and(self, other: Self) -> Self { - debug_assert!( - self.placement.is_none() - || other.placement.is_none() - || self.placement == other.placement - ); - Self { - frame: [ - self.frame[0].and(other.frame[0]), - self.frame[1].and(other.frame[1]), - ], - extent: [ - self.extent[0].and(other.extent[0]), - self.extent[1].and(other.extent[1]), - ], - placement: self.placement.or(other.placement), + let mut result = Self::ANY; + for n in 0..2 { + result.frame[n] = self.frame[n].and(other.frame[n]); + result.extent[n] = self.extent[n].and(other.extent[n]); + debug_assert!( + self.extent_len[n].is_none() + || other.extent_len[n].is_none() + || self.extent_len[n] == other.extent_len[n] + ); + result.extent_len[n] = self.extent_len[n].or(other.extent_len[n]); } + result } pub fn covers(self, other: Self) -> bool { - self.placement - .is_none_or(|placement| other.placement == Some(placement)) - && [0, 1].into_iter().all(|n| { - self.frame[n].lo <= other.frame[n].lo - && self.frame[n].hi >= other.frame[n].hi - && self.extent[n].lo <= other.extent[n].lo - && self.extent[n].hi >= other.extent[n].hi - }) + (0..2).all(|n| { + self.frame[n].lo <= other.frame[n].lo + && self.frame[n].hi >= other.frame[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)) + }) } - pub fn contains(self, px: PxVec2, placement: UiRegion) -> bool { - self.placement.is_none_or(|old| old == placement) - && [Axis::X, Axis::Y].into_iter().all(|axis| { - self.frame[axis as usize].contains(px.axis(axis)) - && self.extent[axis as usize] - .contains(placement.axis(axis).len().to_px(px.axis(axis))) - }) - } - - pub fn in_frame(self, placement: UiRegion) -> [Holds; 2] { - [Axis::X, Axis::Y].map(|axis| { - self.frame[axis as usize] - .and(self.extent[axis as usize].through(placement.axis(axis).len())) + pub fn contains(self, px: PxVec2, extent: UiRegion) -> bool { + AXES.into_iter().all(|axis| { + let n = axis as usize; + let len = extent.axis(axis).len(); + self.frame[n].contains(px.axis(axis)) + && self.extent[n].contains(len.to_px(px.axis(axis))) + && self.extent_len[n].is_none_or(|pinned| pinned == len) }) } } diff --git a/core/src/ui/mod.rs b/core/src/ui/mod.rs index 563691b..e7dfe67 100644 --- a/core/src/ui/mod.rs +++ b/core/src/ui/mod.rs @@ -10,17 +10,17 @@ use crate::{ pub const CHAIN_LIMIT: u32 = 64; mod active; -mod draw_region; mod holds; mod layout_holds; mod painter; +mod place; mod render_state; pub use active::*; -pub use draw_region::*; pub use holds::*; pub use layout_holds::*; pub use painter::{Painter, PrimitiveLike}; +pub use place::*; pub use render_state::*; #[derive(Default)] diff --git a/core/src/ui/painter.rs b/core/src/ui/painter.rs index c6b06a7..60079fa 100644 --- a/core/src/ui/painter.rs +++ b/core/src/ui/painter.rs @@ -1,9 +1,9 @@ #[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter}; use crate::{ - Axis, DrawRegion, ExtentPlacement, Holds, LayoutHolds, LayoutLen, Len, Px, PxVec2, RegionAlign, - RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, - TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, + Axis, Holds, LayoutHolds, LayoutLen, Len, Place, Px, PxVec2, RegionAlign, RenderedText, + RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle, + UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, render::{ GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind, TexturePrimitive, @@ -17,49 +17,47 @@ pub struct Painter<'a> { pub(super) state: &'a mut UiRenderState, pub(super) rsc: &'a mut dyn UiRsc, - /// The box its parent gave it, in the coordinates of `move_idx`: what a - /// fraction of this widget's area is a fraction of, and what every region - /// it writes composes within. The same box on the ask that measures and - /// the ask that places, which is what keeps a fraction under it from - /// being resolved twice. - pub(super) region: UiRegion, - /// Where this widget's drawing sits inside that box, in the box's own - /// coordinates: `FULL` while its answer is not yet known, and the box - /// its answer or its parent chose once one of them has. - pub(super) placement: UiRegion, - /// Whether this draw read its placement, which makes the drawing one - /// that holds for that placement alone -- the way reading a length in - /// pixels makes it hold for that length. - pub(super) reads_placement: bool, - /// That box in pixels, which its children's are a length of: threaded - /// down from the box this widget was given rather than composed back up - /// the chain, so every length in layout is one multiply from its - /// parent's and [`Holds::through`] inverts exactly that. + /// What a fraction this widget declares or reports is a fraction of, in + /// the coordinates of `move_idx`: forwarded from its parent unchanged + /// through a span, a stack or a scroll, and narrowed only by what was + /// decided above it -- a declared length, an inset, the root. Its length + /// is the same on every ask of the widget, which is what keeps a fraction + /// under it from being resolved twice. + pub(super) frame: UiRegion, + /// Where this widget's drawing goes, in the frame's own coordinates. + /// Everything it writes is in these coordinates, and its children are + /// placed as parts of it. + pub(super) extent: UiRegion, + /// The extent's symbolic length where this draw read it, which makes the + /// drawing one that holds for that length alone -- the way reading a + /// length in pixels makes it hold for that number of pixels. + pub(super) extent_len: [Option; 2], + /// The frame in pixels, which its children's frames are a length of: + /// threaded down rather than composed back up the chain, so every length + /// in layout is one multiply from its parent's and [`Holds::through`] + /// inverts exactly that. pub(super) px: PxVec2, pub(super) mask: MaskIdx, pub(super) textures: Vec, pub(super) primitives: Vec, - pub(super) mask_region: Option, - pub(super) extent_children: Vec<(WidgetId, ExtentPlacement)>, - pub(super) extent_own: [Holds; 2], + pub(super) mask_region: Option, /// Only children whose answers were read constrain this widget's answer. pub(super) answer_under: LayoutHolds, pub(super) children: Vec, - /// The children asked about so far, so the first box each was asked in + /// The children asked about so far, so the first place each was asked in /// is the one recorded as its offer. pub(super) offered: Vec, - /// The lengths of the box this widget was first asked about in, in - /// pixels. Its children's offers are a fraction of it. - pub(super) offered_px: PxVec2, - /// Whether this draw is in a box of those lengths, which makes the - /// questions it asks the ones a cold layout asks and their answers the - /// ones to keep. + /// Whether this draw is at the place its parent first asked about, which + /// makes the questions it asks the ones a cold layout asks and their + /// answers the ones to keep. pub(super) at_offer: bool, /// The children whose size this widget read while drawing. pub(super) size_deps: Vec, - /// What this draw itself read of its box in pixels, per axis: every + /// What this draw itself read of its frame in pixels, per axis: every /// length until it reads one, then that one, unless it says otherwise. pub(super) own: [Holds; 2], + /// The same for its extent. + pub(super) extent_own: [Holds; 2], /// Dependencies of every child drawing, including unmeasured overlays. pub(super) under: LayoutHolds, /// The movable region this widget's primitives are positioned through: @@ -74,26 +72,29 @@ pub struct Painter<'a> { } impl<'a> Painter<'a> { - fn primitive_at(&mut self, primitive: P, region: DrawRegion) { + fn primitive_at(&mut self, primitive: P, region: UiRegion) { let kind = self.rsc.ui_mut().primitives.kind::

(); self.write(kind, primitive, region); } /// Takes the kind, for a caller writing many of one primitive. - fn write(&mut self, kind: PrimitiveKind

, primitive: P, region: DrawRegion) { - self.write_resolved( - kind, - primitive, - region, - region.resolve(self.region, self.placement), - ); + fn write(&mut self, kind: PrimitiveKind

, primitive: P, region: UiRegion) { + self.write_resolved(kind, primitive, region, self.resolve(region)); + } + + /// A box in this widget's extent coordinates, composed into the + /// coordinates its move slot is in: through the extent, then through the + /// frame the extent is a part of. The same two steps a recomposition + /// replays, so a moved drawing lands where a cold one does. + fn resolve(&self, region: UiRegion) -> UiRegion { + region.within(&self.extent).within(&self.frame) } fn write_resolved( &mut self, kind: PrimitiveKind

, primitive: P, - region: DrawRegion, + region: UiRegion, resolved: UiRegion, ) { #[cfg(feature = "layout-diagnostics")] @@ -122,135 +123,70 @@ impl<'a> Painter<'a> { /// Writes a primitive over the whole of this widget's own box. pub fn primitive(&mut self, primitive: impl PrimitiveLike) { - let at = DrawRegion::Extent(UiRegion::FULL); let primitive = primitive.into_primitive(self); - self.primitive_at(primitive, at) + self.primitive_at(primitive, UiRegion::FULL) } - /// Writes in the frame by default. `DrawRegion::Extent` keeps the local - /// geometry attached to this widget's box without reading its placement. - pub fn primitive_within( - &mut self, - primitive: impl PrimitiveLike, - region: impl Into, - ) { + /// Writes a primitive in a part of this widget's own box, in that box's + /// coordinates. + pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) { let primitive = primitive.into_primitive(self); - self.primitive_at(primitive, region.into()); + self.primitive_at(primitive, region); } - /// Sets a mask in the selected frame or extent coordinates. - pub fn set_mask(&mut self, region: impl Into) { - let region = region.into(); + /// Sets a mask, in this widget's own box's coordinates. + pub fn set_mask(&mut self, region: UiRegion) { self.mask_region = Some(region); assert!(self.mask == MaskIdx::NONE); + let resolved = self.resolve(region); + let move_idx = self.move_idx; self.mask = self.rsc.ui_mut().masks.push(Mask { - region: region.resolve(self.region, self.placement), - move_idx: self.move_idx, + region: resolved, + move_idx, }); } - /// Draws a widget in the whole of this widget's own box: it gets the - /// same region -- the same area for its fractions to be of -- and is put - /// where this widget was put. What a container that is only a wrapper - /// around one child wants, since its box is the child's. + /// Draws a widget in the whole of this widget's own box, with the frame + /// forwarded unchanged: what a container that is only a wrapper around + /// one child wants, and what every transparent container passes for the + /// frame. pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget) -> DrawResult<'s, 'a, W> { - let own = self.placement; - self.widget_at_inner( - id, - UiRegion::FULL, - [Some(own.x), Some(own.y)], - Some(ExtentPlacement::Inherit), - false, - ) + self.widget_at(id, UiRegion::FULL, [Place::Within(None); 2]) } - /// What a widget's rules declare its lengths to be, which whoever draws - /// it resolves into its box. Reading them depends on nothing -- the box - /// that comes of them is kept on the child, and `redraw` compares it - /// there. - fn declared_lens(&self, id: &StrongWidget) -> [Option; 2] { - declared_lens(self.rsc.widgets(), id.id()) - } - - /// Takes back a child that was drawn only to find out how long it is. - /// Its drawing is dropped and it is not one of this widget's children - /// this frame; what it answered is still something this widget asked. - pub fn undraw(&mut self, id: &StrongWidget) { - self.children.retain(|child| *child != id.id()); - self.extent_children.retain(|(child, _)| *child != id.id()); - self.state.undraw_rec(id.id(), self.rsc); - } - - /// Draws a child in `region`, relative to this widget's frame. The child - /// resolves declared lengths and reports against that frame, then places - /// its drawing by its own alignment. + /// Draws a child, saying what its fractions are of and where its drawing + /// goes. /// - /// `DrawRegion::Extent` gives a part of where this widget's drawing sits - /// instead, for a container whose children belong inside that rather than - /// inside the box it was offered. The part is what is kept, so moving the - /// extent re-places the child rather than drawing this widget again. - pub fn widget_within<'s, W: ?Sized>( - &'s mut self, - id: &'s StrongWidget, - region: impl Into, - ) -> DrawResult<'s, 'a, W> { - match region.into() { - DrawRegion::Frame(region) => self.widget_at(id, region, [None; 2]), - DrawRegion::Extent(part) => { - let within = part.within(&self.placement); - self.widget_at_inner( - id, - within, - [None; 2], - Some(ExtentPlacement::Within(part)), - false, - ) - } - } - } - - /// Draws a widget in `region`, saying where in it the drawing goes. + /// `frame` is that reference, in this widget's own frame coordinates: + /// [`UiRegion::FULL`] forwards this widget's frame, which is what a + /// container that only divides room passes, so a fraction under it means + /// the same wherever it sits and however deeply it is nested. Narrowing + /// it is for what is decided from above -- an inset's margins -- and a + /// declared length narrows it here. /// - /// `region` is the child's own area: what a fraction it declares or - /// reports is a fraction of, and the coordinates the regions it writes - /// compose within. It is the same box on the ask that measures and the - /// ask that places, which is what stops a fraction under it being - /// resolved twice. - /// - /// `placement` is what of that region the child's drawing takes, per - /// axis, wherever this widget is choosing. `None` leaves the axis to the - /// child's own answer and alignment, which is what - /// [`Self::widget_within`] passes. A span passes the whole row as the - /// region, so `rel(0.5)` is half the row wherever the child sits in it, - /// and places the child by passing the slot along its axis. + /// `place` is where the drawing goes, per axis, as a part of this + /// widget's extent: see [`Place`]. A narrowed frame is its own extent, + /// since the narrowing is what said where the drawing goes. pub fn widget_at<'s, W: ?Sized>( &'s mut self, id: &'s StrongWidget, - region: UiRegion, - placement: [Option; 2], + frame: UiRegion, + place: [Place; 2], ) -> DrawResult<'s, 'a, W> { - self.widget_at_inner(id, region, placement, None, false) - } - - fn widget_at_inner<'s, W: ?Sized>( - &'s mut self, - id: &'s StrongWidget, - region: UiRegion, - placement: [Option; 2], - extent: Option, - measuring: bool, - ) -> DrawResult<'s, 'a, W> { - self.extent_children.retain(|(child, _)| *child != id.id()); - if let Some(extent) = extent { - self.extent_children.push((id.id(), extent)); - } let region_node = self.rsc.widgets().is_region_node(id.id()); let declared = self.declared_lens(id); let align = self.rsc.widgets().alignment(id.id()); - let (local, placement) = ask_box(region, declared, align, placement); + let narrow = AXES.map(|axis| { + let n = axis as usize; + // A rule's fraction is a fraction of the frame the child was + // given, which is the one length the rule can mean. + declared[n] + .map(|len| Len::from_parts(len.rel, len.px).within_len(frame.axis(axis).len())) + }); + let (local, extent) = frame_and_extent(frame, part_of(self.extent, place), narrow, align); let within = match local == UiRegion::FULL { - true => self.region, - false => local.within(&self.region), + true => self.frame, + false => local.within(&self.frame), }; #[cfg(feature = "layout-diagnostics")] if region_node { @@ -262,31 +198,20 @@ impl<'a> Painter<'a> { self.children.push(id.id()); } let first_ask = self.offer(id.id()); - let given_len = local.size(); - let offer_len = match first_ask { - true => given_len, - false => self - .state - .active - .get(&id.id()) - .map_or(given_len, |a| a.offer_len), - }; - let offer_placement = if first_ask { - placement + let offer_place = if first_ask { + place } else { self.state .active .get(&id.id()) - .map_or(placement, |a| a.offer_placement) + .map_or(place, |a| a.offer_place) }; - let px = given_len.to_px(self.px); - let offered_px = offer_len.to_px(self.offered_px); - // The answer and what it holds for, both about the box asked in. The - // child's record may say something else once its drawing has been + let px = local.size().to_px(self.px); + // The answer and what it holds for, both about the place asked in. + // The child's record may say something else once its drawing has been // placed: a drawing made again in its placed box holds for that box. let (size, answer_holds, holds) = self.state.draw_inner( id.id(), - within, DrawInfo { layer: self.layer, parent: Some(self.id), @@ -294,76 +219,46 @@ impl<'a> Painter<'a> { parent_move: self.move_idx, region_node, mask: self.mask, - given_region: local, - offer_len, - offer_placement, + frame: local, + frame_abs: within, + part: extent, + place, + offer_place, + // The question its parent measured it by, asked again: the + // same box of the same widget, however this draw came about. + offer: place == offer_place, px, - offered_px, - placement, }, None, - measuring, + false, self.rsc, ); - let in_parent = |holds: LayoutHolds| { - let mut result = LayoutHolds::ANY; - for axis in AXES { - let n = axis as usize; - let chosen = placement[n].unwrap_or(UiSpan::FULL).len(); - match extent { - // Its box is this widget's own, so what its drawing holds - // for is what this widget's extent holds for. - Some(ExtentPlacement::Inherit) if declared[n].is_none() => { - result.frame[n] = holds.frame[n].through(local.axis(axis).len()); - result.extent[n] = holds.extent[n]; - if holds.placement.is_some() { - result.placement = Some(self.placement); - } - } - // Its box is a part of this widget's extent, so what it - // holds for is a range on that extent and none of it a - // range on the frame. Only the part's length reaches it, - // which is what lets the extent move without a redraw. - Some(ExtentPlacement::Within(part)) if declared[n].is_none() => { - result.extent[n] = holds.frame[n] - .and(holds.extent[n].through(chosen)) - .through(part.axis(axis).len()); - } - // Its box is a length of this widget's frame: an - // ordinary ask, or a declared length, which is that - // length wherever the box it sits in came from. - _ => { - result.frame[n] = holds.frame[n].through(local.axis(axis).len()).and( - holds.extent[n] - .through(chosen) - .through(local.axis(axis).len()), - ); - } - } - } - result - }; - self.under = self.under.and(in_parent(holds)); - let mut answer_holds = in_parent(answer_holds); - // What it reports is a fraction of the box it was given, which is a - // part of this widget's extent -- so the same fraction is a different - // length once that extent is, and pixels are not. The answer only: - // the drawing this holds is re-placed rather than made again. - if matches!(extent, Some(ExtentPlacement::Within(_))) - && AXES.into_iter().any(|axis| { - declared[axis as usize].is_none() && size.axis(axis).rel != crate::Rel::ZERO - }) - { - answer_holds.placement = Some(self.placement); - } + let compose = |holds| in_parent(holds, local, extent, place, declared); + self.under = self.under.and(compose(holds)); DrawResult { child: id, painter: self, size: in_parent_frame(size, local.size(), declared), - answer_holds, + answer_holds: compose(answer_holds), } } + /// Takes back a child that was drawn only to find out how long it is. + /// Its drawing is dropped and it is not one of this widget's children + /// this frame; what it answered is still something this widget asked. + pub fn undraw(&mut self, id: &StrongWidget) { + self.children.retain(|child| *child != id.id()); + self.state.undraw_rec(id.id(), self.rsc); + } + + /// What a widget's rules declare its lengths to be, which whoever draws + /// it resolves into its frame. Reading them depends on nothing -- the box + /// that comes of them is kept on the child, and `redraw` compares it + /// there. + fn declared_lens(&self, id: &StrongWidget) -> [Option; 2] { + declared_lens(self.rsc.widgets(), id.id()) + } + /// What a child says its length is without being drawn, if it can say. /// Asking counts as reading its size. pub fn size_hint(&mut self, id: &StrongWidget, axis: Axis) -> Option { @@ -392,57 +287,11 @@ impl<'a> Painter<'a> { } } - /// Measures a child's length from its hint, a retained answer, or `draw`. - /// A fresh draw evaluates the offer without placing its answer. The caller - /// must later place or undraw the child. - pub fn measure_len( - &mut self, - child: &StrongWidget, - axis: Axis, - region: UiRegion, - placement: [Option; 2], - ) -> LayoutLen { - let offered = placement; - let declared = self.declared_lens(child); - let align = self.rsc.widgets().alignment(child.id()); - let (local, placement) = ask_box(region, declared, align, placement); - let first_ask = self.at_offer && !self.offered.contains(&child.id()); - - if let Some(hint) = self.size_hint(child, axis) { - return hint; - } - let px = local.size().to_px(self.px); - let retained = - self.state - .retained_size(child.id(), px, placement, self.move_idx, self.rsc.widgets()); - let Some((size, holds)) = retained else { - return self - .widget_at_inner(child, region, offered, None, true) - .len(axis); - }; - #[cfg(feature = "layout-diagnostics")] - diag::bump(Counter::RetainedSizeHits); - self.depend_on(child); - if first_ask { - self.offered.push(child.id()); - let active = self.state.active.get_mut(&child.id()).unwrap(); - active.offer_len = local.size(); - active.offer_placement = placement; - } - let placement = UiRegion { - x: placement[0].unwrap_or(UiSpan::FULL), - y: placement[1].unwrap_or(UiSpan::FULL), - }; - let holds = holds.in_frame(placement); - for (axis, under) in AXES.into_iter().zip(self.answer_under.frame.iter_mut()) { - *under = under.and(holds[axis as usize].through(local.axis(axis).len())); - } - in_parent_frame(size, local.size(), declared).axis(axis) - } - /// Whether this is the first box a child is asked about in during a draw - /// that is itself in the box it was asked in -- the question a cold - /// layout asks, whose answer is the one to keep. + /// that is itself the one its parent measured -- the question a cold + /// layout asks, whose answer is the one to keep. A drawing made again in + /// a box chosen from an answer asks about that box instead, and what it + /// hears back is not a measurement of anything. fn offer(&mut self, child: WidgetId) -> bool { if !self.at_offer || self.offered.contains(&child) { return false; @@ -471,11 +320,10 @@ impl<'a> Painter<'a> { /// Writes glyphs in the selected frame or extent coordinates. // TODO: merge the text methods into the primitive ones. - pub fn glyphs(&mut self, text: &RenderedText, origin: impl Into) { - let origin = origin.into(); + pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) { // Glyph offsets and sizes are pixels, which compose additively. - // Only the shared origin needs the frame/extent composition. - let resolved = origin.resolve(self.region, self.placement); + // Only the shared origin needs composing through the extent. + let resolved = self.resolve(origin); let kind = self.rsc.ui_mut().primitives.kind::(); for glyph in text.glyphs.iter() { let place = |mut region: UiRegion| { @@ -499,30 +347,20 @@ impl<'a> Painter<'a> { color: text.color, flags: glyph.entry.flags(), }, - origin.map(place), + place(origin), place(resolved), ); } } - /// The box this widget's parent gave it, in the coordinates its own - /// primitives are written in -- so a region composed `within` it may be - /// drawn directly. Its own box is [`Self::placement`] of this one. - pub fn region(&self) -> UiRegion { - self.region - } - - /// Where this widget's drawing goes inside the box it was given, in that - /// box's coordinates: what its own answer took of it, or what its parent - /// chose for it. `FULL` on the ask that measures, since nothing has been - /// placed yet. - /// - /// Reading it is what says the drawing depends on it, so a widget that - /// positions its own content reads it and is drawn again once its box is - /// known, and one that fills whatever it is given never is. - pub fn placement(&mut self) -> UiRegion { - self.reads_placement = true; - self.placement + /// The symbolic length of this widget's own box, in the lengths of its + /// frame that it places its children in. Reading it pins the drawing to + /// that length -- and to nothing about where the box starts, which is + /// what lets a container move without being drawn again. + pub fn extent_len(&mut self) -> UiVec2 { + let len = self.extent.size(); + self.extent_len = [Some(len.x), Some(len.y)]; + len } /// Where this widget sits in a box longer than the length it takes. A @@ -550,14 +388,6 @@ impl<'a> Painter<'a> { .is_some() } - /// The part of this widget's box that something of `size` takes, at the - /// 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 { - let lens = placed_lens(size, [None; 2], [false; 2]); - placed_box(UiRegion::FULL, lens, RegionAlign::NEAR) - } - /// This widget's own box in pixels. Reading it makes the drawing one /// that holds for this box only, until `holds` says how far it goes. pub fn px_size(&mut self) -> PxVec2 { @@ -567,7 +397,7 @@ impl<'a> Painter<'a> { /// One axis of this widget's own box in pixels. Prefer this to /// [`Self::px_size`] when the other axis cannot affect the drawing. pub fn px_len(&mut self, axis: Axis) -> Px { - let part = self.placement.axis(axis).len(); + let part = self.extent.axis(axis).len(); let len = part.to_px(self.px.axis(axis)); let own = &mut self.extent_own[axis as usize]; if *own == Holds::ANY { @@ -581,7 +411,7 @@ impl<'a> Painter<'a> { /// of the box, and the same reported size. A widget that read its length /// in pixels holds for that one alone until it says otherwise. pub fn holds(&mut self, axis: Axis, holds: impl Into) { - let part = self.placement.axis(axis).len(); + let part = self.extent.axis(axis).len(); let holds = holds.into(); debug_assert!( holds.contains(part.to_px(self.px.axis(axis))), @@ -592,10 +422,10 @@ impl<'a> Painter<'a> { self.extent_own[axis as usize] = holds; } - /// One axis of the box this widget's parent gave it, in pixels -- what a - /// fraction of its area resolves against, and so what a container divides - /// among its children. Its own box is a part of this one. - pub fn region_px_len(&mut self, axis: Axis) -> Px { + /// One axis of this widget's frame in pixels -- what a fraction of its + /// area resolves against, and so what a container divides among its + /// children. Its own box is a part of this one. + pub fn frame_px_len(&mut self, axis: Axis) -> Px { let len = self.px.axis(axis); let own = &mut self.own[axis as usize]; if *own == Holds::ANY { @@ -604,14 +434,14 @@ impl<'a> Painter<'a> { len } - /// [`Self::holds`] stated about the region rather than about this - /// widget's own box, for a container whose drawing turns on the box it - /// was given rather than on the part of it it took. - pub fn region_holds(&mut self, axis: Axis, holds: impl Into) { + /// [`Self::holds`] stated about the frame rather than about this + /// widget's own box, for a container whose drawing turns on what its + /// fractions are of rather than on the part of it it took. + pub fn frame_holds(&mut self, axis: Axis, holds: impl Into) { let holds = holds.into(); debug_assert!( holds.contains(self.px.axis(axis)), - "'{}' ({:?}) says its drawing holds for lengths that leave out its region", + "'{}' ({:?}) says its drawing holds for lengths that leave out its frame", self.label(), self.id ); @@ -703,6 +533,52 @@ impl PrimitiveLike for &TextureHandle { } } +/// What a child depends on, said about the boxes the widget that drew it +/// has rather than the ones the child was given. +/// +/// `frame` is the child's frame in this widget's frame coordinates and +/// `extent` the box it was given, in the child's own frame coordinates. Both +/// reach it as one length, so what it holds for maps back through that +/// length exactly -- and where the box it was given is this widget's own, +/// what it says about that box is what this widget can say about its own. +pub(crate) fn in_parent( + holds: LayoutHolds, + frame: UiRegion, + extent: UiRegion, + place: [Place; 2], + declared: [Option; 2], +) -> LayoutHolds { + let mut result = LayoutHolds::ANY; + for axis in AXES { + let n = axis as usize; + let frame_len = frame.axis(axis).len(); + result.frame[n] = holds.frame[n].through(frame_len); + match (place[n].span(), declared[n]) { + // Its box is this widget's own, so what it holds for is a range + // on this widget's own box -- which is what lets that box move + // without a redraw. A length it pinned is this widget's length, + // and pins the same way: where the part is a span instead, that + // span is this widget's own to decide and nothing has to be + // said. + (None, None) => { + result.extent[n] = holds.extent[n]; + result.extent_len[n] = holds.extent_len[n]; + } + // Its box is a part of this widget's frame: a length of the + // frame is all that reaches 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()) + .through(frame_len), + ); + } + } + } + result +} + /// A child's answer as lengths of the parent's own region. A widget reports /// a fraction of its own region, and `of` is that region as a length of this /// one. Pixels come through untouched, being that many pixels wherever they @@ -749,68 +625,85 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option, decided: b reported.leftover != Weight::ZERO || declared.is_some() || decided } -/// What of the box it was given a widget's drawing occupies, as lengths of -/// that box: the size it reported wherever that is a part to be placed, and -/// the whole of the box wherever the answer fills it. +/// Where a widget's drawing goes inside the part its parent gave it: what +/// it reported, on the side of the part its alignment says, and the whole +/// part wherever the answer fills it. /// -/// 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 -/// reporting `rel(1.0)` means all of what it was given, whatever that was a -/// fraction of. So this is a length of the box rather than a length composed -/// into it, and a box in pixels is this step from the given box's pixels. -pub(crate) fn placed_lens( +/// The length it reported is a length of its frame, and the part is one too, +/// so this takes one from the other rather than composing it into the part. +/// That is what makes a fraction the same fraction wherever the part it is +/// placed in sits and however long it is -- the fraction is resolved once, +/// here, against the frame it was reported of. +pub(crate) fn placed_extent( + part: UiRegion, size: Size, declared: [Option; 2], - decided: [bool; 2], -) -> UiVec2 { - let mut lens = UiVec2::FULL_SIZE; - for (axis, (declared, decided)) in AXES.into_iter().zip(declared.into_iter().zip(decided)) { - let reported = size.axis(axis); - if !fills(reported, declared, decided) { - *lens.axis_mut(axis) = Len::from_parts(reported.rel, reported.px); - } - } - lens -} - -/// Where that drawing sits: those lengths taken of the box the widget was -/// asked in, on the side of it that the widget's alignment says. -pub(crate) fn placed_box(region: UiRegion, lens: UiVec2, align: RegionAlign) -> UiRegion { - let mut placed = region; + fill: [bool; 2], + align: RegionAlign, +) -> UiRegion { + let mut placed = part; for axis in AXES { - // The whole of the box is already where it sits, and the arithmetic - // below is the identity for it. - if lens.axis(axis) == Len::FULL { + let n = axis as usize; + let reported = size.axis(axis); + if fills(reported, declared[n], fill[n]) { continue; } + let len = Len::from_parts(reported.rel, reported.px); let span = placed.axis_mut(axis); - let len = lens.axis(axis).within_len(span.len()); span.start += (span.len() - len).scale(align.axis(axis).rel()); span.end = span.start + len; } placed } -/// A declared axis gets a frame of that length, aligned within the parent's -/// slot (or the offer). Undeclared axes keep the offered frame and chosen -/// placement, so their reported fractions retain that reference. -pub(crate) fn ask_box( - mut region: UiRegion, - declared: [Option; 2], - align: RegionAlign, - placement: [Option; 2], -) -> (UiRegion, [Option; 2]) { - let mut placed = [None; 2]; - for (axis, (len, chosen)) in AXES.into_iter().zip(declared.into_iter().zip(placement)) { - let Some(len) = len else { - placed[axis as usize] = chosen; - continue; - }; - let span = region.axis_mut(axis); - let len = Len::from_parts(len.rel, len.px); - let slot = chosen.unwrap_or(*span); - span.start = slot.start + (slot.len() - len).scale(align.axis(axis).rel()); - span.end = span.start + len; +/// The part of a widget's extent a `place` names, in the coordinates its +/// extent is in: a span is measured in frame lengths from where the extent +/// starts, so nothing under it depends on where that is, and an extent that +/// moved re-places every child by re-adding its start. +pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion { + let mut part = extent; + for axis in AXES { + if let Some(span) = place[axis as usize].span() { + let start = extent.axis(axis).start; + *part.axis_mut(axis) = UiSpan::new(start + span.start, start + span.end); + } } - (region, placed) + part +} + +/// The frame a child is asked in and the box its drawing goes in, both in +/// the coordinates of the widget asking. +/// +/// `frame` is what the caller said the child's fractions are of, and `part` +/// what of the caller's own box the drawing takes. `narrow` is the length a +/// declared rule gives the frame, which makes the frame the box the drawing +/// goes in -- a rule is what decided where it goes, and there is nothing +/// left to place inside it. A caller that narrowed the frame itself said the +/// same thing. +/// +/// The length is the caller's to supply so that a widget asked again gets +/// the frame it already has rather than a second resolution of its rule. +pub(crate) fn frame_and_extent( + mut frame: UiRegion, + part: UiRegion, + narrow: [Option; 2], + align: RegionAlign, +) -> (UiRegion, UiRegion) { + let mut extent = part; + for (axis, narrow) in AXES.into_iter().zip(narrow) { + let span = frame.axis_mut(axis); + let narrowed = match narrow { + Some(len) => { + let slot = part.axis(axis); + let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel()); + *span = UiSpan::new(start, start + len); + true + } + None => *span != UiSpan::FULL, + }; + if narrowed { + *extent.axis_mut(axis) = UiSpan::FULL; + } + } + (frame, extent) } diff --git a/core/src/ui/place.rs b/core/src/ui/place.rs new file mode 100644 index 0000000..561726a --- /dev/null +++ b/core/src/ui/place.rs @@ -0,0 +1,37 @@ +use crate::{PrimitiveHandle, UiRegion, UiSpan}; + +/// Where a child goes along one axis, as a part of this widget's extent. +/// Spans are frame lengths from the extent's start, so a span's slot is +/// `from..start` and a moved extent re-places every child by re-adding its +/// start, exactly. `None` is the whole extent. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Place { + /// The child's answer, aligned inside the part by the child's alignment. + Within(Option), + /// Exactly the part; the answer is not placed inside it again. + Fill(Option), +} + +impl Place { + /// The part, where the caller named one rather than giving the whole + /// extent. + pub(crate) fn span(self) -> Option { + match self { + Self::Within(span) | Self::Fill(span) => span, + } + } + + /// Whether the part is the drawing's box outright, rather than the box + /// the answer is placed inside. + pub(crate) fn fills(self) -> bool { + matches!(self, Self::Fill(_)) + } +} + +/// A primitive as it was written: its box in the widget's extent +/// coordinates, which is what a move of that extent re-composes from. +#[derive(Debug)] +pub struct RetainedPrimitive { + pub handle: PrimitiveHandle, + pub region: UiRegion, +} diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 00359e1..6b6e6cb 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -1,9 +1,9 @@ #[cfg(feature = "layout-diagnostics")] use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; -use crate::ui::painter::{ask_box, declared_lens, placed_box, placed_lens}; +use crate::ui::painter::{declared_lens, frame_and_extent, part_of, placed_extent}; use crate::{ - ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Moves, - Painter, PixelRegion, PxVec2, Size, StrongWidget, UiRegion, UiRsc, UiSpan, UiVec2, Weight, + ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, + Moves, Painter, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, WidgetId, Widgets, util::{HashMap, Vec2}, }; @@ -11,7 +11,7 @@ use crate::{ const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// Where a widget is drawn: what its parent decides about the draw besides -/// the box. +/// the boxes themselves. #[derive(Clone, Copy)] pub(super) struct DrawInfo { pub layer: usize, @@ -20,36 +20,48 @@ pub(super) struct DrawInfo { pub parent_move: MoveIdx, pub region_node: bool, pub mask: MaskIdx, - /// The frame in the parent widget's coordinates, before composition. - pub given_region: UiRegion, - /// The original offer's lengths relative to the parent's own offer. - pub offer_len: UiVec2, - pub offer_placement: [Option; 2], - /// This ask's box in pixels, and the offer's: one multiply from the - /// parent's own, which is where every pixel length in layout comes from. + /// The frame in the parent widget's frame coordinates, before + /// composition. Its length is the same on every ask of the widget. + pub frame: UiRegion, + /// That frame composed into `parent_move`'s coordinates, which is what + /// the widget's own drawing is written within. + pub frame_abs: UiRegion, + /// The box the drawing is given, in the frame's own coordinates: the + /// part of the parent's own box that `place` names, before the widget's + /// answer is placed inside it. + pub part: UiRegion, + /// What of the parent's extent the drawing was given, and what it was + /// given at the parent's first ask of it. See [`Place`]. + pub place: [Place; 2], + pub offer_place: [Place; 2], + /// Whether this ask is the one the widget's answer is kept from: the + /// first box its parent asked about, in the parent's own measuring draw. + pub offer: bool, + /// The frame in pixels: one multiply from the parent's own, which is + /// where every pixel length in layout comes from. pub px: PxVec2, - pub offered_px: PxVec2, - /// What of that region the parent chose to put the drawing in, per axis. - /// `None` leaves the axis to the widget's own answer and its alignment. - /// See [`Painter::widget_at`]. - pub placement: [Option; 2], } impl DrawInfo { - /// The axes the parent chose the placement on, which are the axes the - /// answer is not placed inside its region again. - fn decided(&self) -> [bool; 2] { - self.placement.map(|span| span.is_some()) + /// 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) } +} - /// The placement to draw in before the answer is known: what the parent - /// chose, and the whole region on any axis it left open. - fn offered_placement(&self) -> UiRegion { - UiRegion { - x: self.placement[0].unwrap_or(UiSpan::FULL), - y: self.placement[1].unwrap_or(UiSpan::FULL), - } - } +/// 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. +struct Placing { + id: WidgetId, + extent: UiRegion, + /// The widget's frame in the coordinates its children compose within: + /// `FULL` where it is a region node, since its box is that node. + local: UiRegion, + px: PxVec2, + depth: usize, + move_idx: MoveIdx, + mask: MaskIdx, } pub struct UiRenderState { @@ -111,8 +123,11 @@ impl UiRenderState { self.resized = true; let Some(root) = self.old_root else { return }; let stands = self.active.get(&root).is_some_and(|active| { - let px = active.given_region.size().to_px(size); - active.answers_at(px) && active.holds.contains(px, active.placement) + let px = active.frame.size().to_px(size); + // Nothing above the root chose anything, so the box it was first + // asked about is the whole of its frame. + let offer = part_of(UiRegion::FULL, active.offer_place); + active.answers_at(px, offer) && active.holds.contains(px, active.extent) }); if !stands { widgets.needs_redraw.insert(root); @@ -133,12 +148,13 @@ impl UiRenderState { parent_move: MoveIdx::NONE, region_node: false, mask: MaskIdx::NONE, - given_region: region, - offer_len: UiVec2::FULL_SIZE, - offer_placement: [None; 2], + frame: region, + frame_abs: region, + part: UiRegion::FULL, + place: [Place::Within(None); 2], + offer_place: [Place::Within(None); 2], + offer: true, px, - offered_px: px, - placement: [None; 2], } } @@ -186,16 +202,21 @@ impl UiRenderState { if let Some(id) = root { let region = Self::root_region(id.id(), rsc.widgets()); let info = self.root_info(region); - self.draw_inner(id.id(), region, info, None, false, rsc); + self.draw_inner(id.id(), info, None, true, rsc); } } + /// The root's frame: the window, narrowed by the root's own rules. Its + /// extent is that frame, since nothing above it chose anything else. fn root_region(id: WidgetId, widgets: &Widgets) -> UiRegion { - ask_box( + let declared = declared_lens(widgets, id); + let narrow = + AXES.map(|axis| declared[axis as usize].map(|len| Len::from_parts(len.rel, len.px))); + frame_and_extent( UiRegion::FULL, - declared_lens(widgets, id), + UiRegion::FULL, + narrow, widgets.alignment(id), - [None; 2], ) .0 } @@ -203,16 +224,16 @@ impl UiRenderState { pub(super) fn draw_inner( &mut self, id: WidgetId, - region: UiRegion, info: DrawInfo, mut old: Option, - measuring: bool, + place_answer: bool, rsc: &mut dyn UiRsc, ) -> (Size, LayoutHolds, LayoutHolds) { + let (frame, part) = (info.frame_abs, info.part); #[cfg(feature = "layout-diagnostics")] { diag::bump(Counter::DrawRequests); - diag::draw_request(id, info.parent, region, info.px, info.region_node); + diag::draw_request(id, info.parent, frame, info.px, info.region_node); } let align = rsc.widgets().alignment(id); // Nothing this widget measured can be dirty while it draws: layout is @@ -222,64 +243,75 @@ impl UiRenderState { let retained = match stale { true => None, false => self - .retained_answer(id, info) - .or_else(|| self.try_reuse(id, region, info.offered_placement(), info, rsc)), + .retained_answer(id, part, info) + .or_else(|| self.try_reuse(id, frame, part, info, rsc)), }; let answer = retained.unwrap_or_else(|| { if old.is_none() { old = self.remove(id, false, rsc); } - self.draw_at(id, region, info.offered_placement(), info, old.take(), rsc) + self.draw_at(id, part, info, old.take(), rsc) }); - // Where the drawing goes, in the region's own coordinates: what the - // parent chose, and on any axis it left open, what the answer took of - // the region placed by the widget's alignment. The region itself does - // not change, so nothing under it resolves a fraction a second time. - let placement = if measuring { - info.offered_placement() - } else { - let declared = declared_lens(rsc.widgets(), id); - let lens = placed_lens(answer.0, declared, info.decided()); - let own = placed_box(UiRegion::FULL, lens, align); - UiRegion { - x: info.placement[0].unwrap_or(own.x), - y: info.placement[1].unwrap_or(own.y), + // Where the drawing goes, in the frame's own coordinates: the part + // its parent gave it, with the answer placed inside that part on any + // axis the parent left open. The frame itself does not change, so + // nothing under it resolves a fraction a second time. + // + // Placing the answer waits for the end of the parent's draw, or for + // the next ask of this child in it: a child measured in the room its + // parent had left over and then given a slot would otherwise be put + // in a box neither of them chose, between the two. Whoever asks with + // nobody to finish it -- the root, a local redraw -- places here. + let extent = match place_answer { + // From the answer it gave when its parent first asked, and not + // from what a placing evaluation reported: a drawing made in the + // box that answer chose is answering a different question, and + // placing it by that would move the box out from under itself. + true => { + let measured = match info.offer { + true => answer.0, + false => self.active[&id].answer.map_or(answer.0, |(size, _)| size), + }; + placed_extent( + part, + measured, + declared_lens(rsc.widgets(), id), + info.fill(), + align, + ) } + false => part, }; - self.place(id, region, placement, info, rsc); + self.place(id, extent, info, rsc); - // On axes chosen by the parent, measurement and drawing share an - // extent. Otherwise the answer fixes the final extent as a function - // of the frame, so pull that drawing's validity back through it. + // On axes the parent filled, measurement and drawing share an extent. + // Otherwise the answer fixes the final extent as a function of the + // frame, so pull that drawing's validity back through it. let drawing_holds = self.active[&id].holds; let mut settled = answer; for axis in AXES { let n = axis as usize; settled.1.frame[n] = settled.1.frame[n].and(drawing_holds.frame[n]); - if info.placement[n].is_some() { + if info.fill()[n] { settled.1.extent[n] = settled.1.extent[n].and(drawing_holds.extent[n]); } else { settled.1.frame[n] = settled.1.frame[n] - .and(drawing_holds.extent[n].through(placement.axis(axis).len())); + .and(drawing_holds.extent[n].through(extent.axis(axis).len())); } } - if drawing_holds.placement.is_some() && info.placement.iter().any(Option::is_some) { - settled.1.placement = Some(info.offered_placement()); - } - let active = self.active.get_mut(&id).unwrap(); - // Whoever asked owns how the box was reached: the box it stated, and - // what of that box the answer then took. A local redraw asks the - // same question again from these. - active.region = region; - active.given_region = info.given_region; - active.offer_len = info.offer_len; - if info.placement == info.offer_placement && info.px == info.offered_px { + // Whoever asked owns how the boxes were reached: the frame it stated, + // and what of its own box it gave the drawing. A local redraw asks + // the same question again from these. + active.frame_abs = frame; + active.frame = info.frame; + if info.offer { active.answer = Some(answer); - active.offer_placement = info.offer_placement; + active.offer_place = info.offer_place; + active.offer_part = part; } - active.decided = info.decided(); + active.place = info.place; 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 @@ -292,71 +324,61 @@ impl UiRenderState { && let Some(old_parent) = self.active.get_mut(&old_parent) { old_parent.children.retain(|child| *child != id); - old_parent.extent_children.retain(|(child, _)| *child != id); } (answer.0, answer.1, settled.1) } /// Recompose retained geometry when the evaluation still holds at this extent. - fn place( - &mut self, - id: WidgetId, - region: UiRegion, - placement: UiRegion, - info: DrawInfo, - rsc: &mut dyn UiRsc, - ) { - if self.try_reuse(id, region, placement, info, rsc).is_some() { + fn place(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) { + if self + .try_reuse(id, info.frame_abs, extent, info, rsc) + .is_some() + { return; } #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::PlaceRedraws); let old = self.remove(id, false, rsc); - self.draw_at(id, region, placement, info, old, rsc); + self.draw_at(id, extent, info, old, rsc); } - /// Calls a widget's `draw` and keeps what it drew in `region`. + /// Calls a widget's `draw` and keeps what it drew in `extent` of `frame`. fn draw_at( &mut self, id: WidgetId, - region: UiRegion, - placement: UiRegion, + extent: UiRegion, info: DrawInfo, old: Option, rsc: &mut dyn UiRsc, ) -> (Size, LayoutHolds) { + let frame = info.frame_abs; let (move_idx, local, retired_move) = match info.region_node { // Its box becomes its movable region, so it draws in that // region's coordinates and its box is one entry to rewrite. true => ( - self.move_slot(id, info.parent_move, region), + self.move_slot(id, info.parent_move, frame), UiRegion::FULL, None, ), // Keep the old entry alive until every descendant has migrated. // Reusing its index sooner could make an old parent look current. - false => (info.parent_move, region, self.slots.remove(&id)), + false => (info.parent_move, frame, self.slots.remove(&id)), }; - let (old_children, old_answer) = match old { - Some(old) => (old.children, old.answer), - None => (Vec::new(), None), + let (old_children, old_answer, old_offer_part) = match old { + Some(old) => (old.children, old.answer, Some(old.offer_part)), + None => (Vec::new(), None, None), }; rsc.widgets_mut().needs_redraw.remove(&id); // Only evaluation at the original offer establishes the children's // offers. A placing evaluation must not overwrite that question. let px = info.px; - let at_offer = px == info.offered_px - && placement - == UiRegion { - x: info.offer_placement[0].unwrap_or(UiSpan::FULL), - y: info.offer_placement[1].unwrap_or(UiSpan::FULL), - }; + let at_offer = info.offer; let mut painter = Painter { state: self, - region: local, - placement, - reads_placement: false, + frame: local, + extent, + extent_len: [None; 2], px, mask: info.mask, layer: info.layer, @@ -365,10 +387,8 @@ impl UiRenderState { textures: Vec::new(), primitives: Vec::new(), mask_region: None, - extent_children: Vec::new(), children: Vec::new(), offered: Vec::new(), - offered_px: info.offered_px, at_offer, size_deps: Vec::new(), own: [Holds::ANY; 2], @@ -394,20 +414,18 @@ impl UiRenderState { let Painter { state: _, rsc: _, - region: _, - placement: _, - reads_placement, + frame: _, + extent: _, px: _, mask, textures, primitives, mask_region, - extent_children, extent_own, + extent_len, answer_under, children, offered: _, - offered_px: _, at_offer: _, size_deps, own, @@ -442,19 +460,6 @@ impl UiRenderState { "'{}' ({id:?}) clips to {px:?} and reports {size}", rsc.widgets().label(id), ); - let own_holds = LayoutHolds { - frame: own, - extent: extent_own, - placement: reads_placement.then_some(placement), - }; - let answer_holds = own_holds.and(answer_under); - let holds = answer_holds.and(under); - debug_assert!( - holds.contains(px, placement), - "'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}", - rsc.widgets().label(id), - ); - for c in &old_children { if !children.contains(c) { self.undraw_rec(*c, rsc); @@ -463,6 +468,33 @@ impl UiRenderState { if let Some(idx) = retired_move { self.moves.remove(idx); } + // The draw is over, so a child whose box was left to its own answer + // gets that box now. Nothing it did is undone by this: a child it + // asked twice was placed by the second ask, and one that fills what + // it was given is already there. What those drawings need is part of + // what this one does, so it comes before the contract is settled. + let at = Placing { + id, + extent, + local, + px, + depth: info.depth, + move_idx, + mask, + }; + let placed = self.place_answers(&at, &children, rsc); + let own_holds = LayoutHolds { + frame: own, + extent: extent_own, + extent_len, + }; + let answer_holds = own_holds.and(answer_under); + let holds = answer_holds.and(under).and(placed); + debug_assert!( + holds.contains(px, extent), + "'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}", + rsc.widgets().label(id), + ); // What it asked about and did not draw is still something it asked, // and a change there has to reach it. Asking answered whatever mark // it had: a hint is read live, and a drawing is not kept past one. @@ -477,12 +509,13 @@ impl UiRenderState { parent_move: move_idx, region_node: false, mask, - given_region: UiRegion::FULL, - offer_len: UiVec2::FULL_SIZE, - offer_placement: [None; 2], + frame: UiRegion::FULL, + frame_abs: UiRegion::FULL, + part: UiRegion::FULL, + place: [Place::Within(None); 2], + offer_place: [Place::Within(None); 2], + offer: false, px, - offered_px: px, - placement: [None; 2], }, rsc, ); @@ -492,11 +525,12 @@ impl UiRenderState { let active = ActiveData { id, - region, - placement, - given_region: info.given_region, - offer_len: info.offer_len, - offer_placement: info.offer_placement, + frame_abs: frame, + extent, + frame: info.frame, + place: info.place, + offer_place: info.offer_place, + offer_part: old_offer_part.unwrap_or(extent), // Whoever asked writes the answer, if this was the asking. answer: old_answer, size, @@ -507,11 +541,9 @@ impl UiRenderState { textures, primitives, mask_region, - extent_children, children, size_deps, declared: declared_lens(rsc.widgets(), id), - decided: info.decided(), own_align: rsc.widgets().alignment(id), move_idx, parent_move: info.parent_move, @@ -544,39 +576,16 @@ impl UiRenderState { } } - /// A clean widget's retained answer, if that answer holds for a box of - /// `px`. This does not move its drawing, which may already be in the box - /// that answer placed it in. - pub(super) fn retained_size( - &self, - id: WidgetId, - px: PxVec2, - placement: [Option; 2], - parent_move: MoveIdx, - widgets: &Widgets, - ) -> Option<(Size, LayoutHolds)> { - if widgets.needs_redraw.contains(&id) { - return None; - } - let active = self.active.get(&id)?; - let (size, holds) = active.answer?; - let valid = active.drawn - && active.parent_move == parent_move - && holds.contains( - px, - UiRegion { - x: placement[0].unwrap_or(UiSpan::FULL), - y: placement[1].unwrap_or(UiSpan::FULL), - }, - ); - valid.then_some((size, holds)) - } - /// The answer to an ask can be retained independently of where its /// drawing ended up. Alignment is exactly that case: the first box is the /// question and the smaller placed box holds the drawing. Whether the /// answer is stale at all is its caller's question, asked once there. - fn retained_answer(&self, id: WidgetId, info: DrawInfo) -> Option<(Size, LayoutHolds)> { + fn retained_answer( + &self, + id: WidgetId, + part: UiRegion, + info: DrawInfo, + ) -> Option<(Size, LayoutHolds)> { let active = self.active.get(&id)?; let has_region_node = active.move_idx != active.parent_move; if !active.drawn @@ -586,41 +595,26 @@ impl UiRenderState { return None; } let answer = active.answer?; - answer - .1 - .contains(info.px, info.offered_placement()) - .then_some(answer) + answer.1.contains(info.px, part).then_some(answer) } - /// The pixel lengths of the box a widget was given and of the box it was - /// first asked about, which is what a local redraw needs to ask the - /// question its parent asked. + /// The pixel lengths of a widget's frame, which is what a local redraw + /// needs to ask the question its parent asked. /// - /// Both are threaded down from the window a length of a box at a time, - /// and this takes the same steps back up: a widget's box is a length of - /// the box its parent drew in, and its offer a length of the box its - /// parent was itself offered. Neither chain has a coordinate frame in it, - /// so neither breaks at a region node -- and both land on the numbers a - /// cold layout computes, rather than near them. - fn asked_px(&self, id: WidgetId) -> (PxVec2, PxVec2) { + /// It is threaded down from the window a length of a box at a time, and + /// this takes the same steps back up: a widget's frame is a length of its + /// parent's frame, and that chain has no coordinate frame in it, so a + /// region node cannot break it -- and it lands on the number a cold + /// layout computes rather than near it. + fn asked_px(&self, id: WidgetId) -> PxVec2 { let active = &self.active[&id]; // Nothing above the root: the window is where a fraction becomes - // pixels, which is also the whole of the box the root is given. - let (parent_px, parent_offer) = match active.parent.and_then(|p| self.active.get(&p)) { + // pixels, which is also the whole of the frame the root is given. + let parent_px = match active.parent.and_then(|p| self.active.get(&p)) { Some(parent) => self.asked_px(parent.id), - None => (self.output_size, self.output_size), + None => self.output_size, }; - let px = active.given_region.size().to_px(parent_px); - let mut offered = active.offer_len.to_px(parent_offer); - for axis in AXES { - // A declared length is resolved by whoever drew the widget, in - // the box that widget drew in, so the box it has is the box it - // was asked about however the offer above it moved. - if active.declared[axis as usize].is_some() { - *offered.axis_mut(axis) = px.axis(axis); - } - } - (px, offered) + active.frame.size().to_px(parent_px) } /// Reuses the actual drawing in a new box if its retained contract holds @@ -628,8 +622,8 @@ impl UiRenderState { fn try_reuse( &mut self, id: WidgetId, - region: UiRegion, - placement: UiRegion, + frame: UiRegion, + extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc, ) -> Option<(Size, LayoutHolds)> { @@ -678,26 +672,25 @@ impl UiRenderState { } return None; } - // In pixels, because `region` is a fraction of the box its parent - // drew in and that box may be what changed -- an unchanged fraction - // of a box half the size is half the widget. - if !active.holds.contains(info.px, placement) { + // In pixels, because the frame is a fraction of its parent's and + // that may be what changed -- an unchanged fraction of a box half the + // size is half the widget. + if !active.holds.contains(info.px, extent) { #[cfg(feature = "layout-diagnostics")] { // Which of the three said no, so a frame that redraws more // than it should says where to look. They overlap: a drawing // can be outside two of them at once. let holds = active.holds; - if holds.placement.is_some_and(|pinned| pinned != placement) { - diag::bump(Counter::OutsidePlacement); - } for axis in AXES { let n = axis as usize; + if holds.extent_len[n].is_some_and(|pinned| pinned != extent.axis(axis).len()) { + diag::bump(Counter::OutsidePlacement); + } if !holds.frame[n].contains(info.px.axis(axis)) { diag::bump(Counter::OutsideFrame); } - if !holds.extent[n] - .contains(placement.axis(axis).len().to_px(info.px.axis(axis))) + if !holds.extent[n].contains(extent.axis(axis).len().to_px(info.px.axis(axis))) { diag::bump(Counter::OutsideExtent); } @@ -707,24 +700,24 @@ impl UiRenderState { } return None; } - let extent_moved = active.placement != placement; - let moved = active.region != region; + let extent_moved = active.extent != extent; + let moved = active.frame_abs != frame; let (answer, slot) = ((active.size, active.holds), active.move_idx); if moved { if has_region_node { - self.moves.set(slot, region); + self.moves.set(slot, frame); } else { - self.recompose_subtree(id, region, info.parent_move, rsc); + self.recompose_subtree(id, frame, info.parent_move, rsc); } } if extent_moved { - self.reposition(id, region, placement, info, rsc); + self.reposition(id, frame, extent, info, rsc); } self.redepth(id, info.depth); let active = self.active.get_mut(&id).unwrap(); - active.region = region; - active.given_region = info.given_region; - active.offer_len = info.offer_len; + active.frame_abs = frame; + active.frame = info.frame; + active.place = info.place; #[cfg(feature = "layout-diagnostics")] { match (moved, has_region_node) { @@ -748,66 +741,164 @@ impl UiRenderState { Some(answer) } + /// Puts each child of a widget that has just drawn where its answer + /// says, for the axes the ask left open. Doing it here rather than as + /// each child answers is what keeps a child that is asked twice -- a + /// span measuring it and then giving it a slot -- to one drawing and one + /// move, instead of a move to its answer in between that nobody asked + /// for. + /// Answers with what those drawings then depend on, as dependencies of + /// this widget: a child placed here may have been drawn again in the box + /// its answer chose, and what that drawing needs was not known when the + /// child was asked. + fn place_answers( + &mut self, + at: &Placing, + children: &[WidgetId], + rsc: &mut dyn UiRsc, + ) -> LayoutHolds { + let mut under = LayoutHolds::ANY; + for &child in children { + let Some(active) = self.active.get(&child) else { + continue; + }; + if active.place.iter().all(|place| place.fills()) { + continue; + } + self.place_child(child, at, rsc); + under = under.and(self.placed_holds(child)); + } + under + } + + /// What a child's finished drawing needs, as a range on the frame of the + /// widget that drew it. + /// + /// Only the frame: where the box the child ended up in is the answer it + /// gave, that box is a length of this frame and maps back through it + /// exactly, and where the parent chose the box instead, the ask already + /// said what that costs on the parent's own box. + fn placed_holds(&self, child: WidgetId) -> LayoutHolds { + let active = &self.active[&child]; + let mut result = LayoutHolds::ANY; + for axis in AXES { + let n = axis as usize; + let frame_len = active.frame.axis(axis).len(); + result.frame[n] = active.holds.frame[n].through(frame_len); + if !active.place[n].fills() { + result.frame[n] = result.frame[n].and( + active.holds.extent[n] + .through(active.extent.axis(axis).len()) + .through(frame_len), + ); + } + } + result + } + + /// Places one child of `at.id` in the box that widget's own box gives + /// it: its part of the extent, with its answer placed inside that part + /// where the ask left the axis open. + fn place_child(&mut self, child: WidgetId, at: &Placing, rsc: &mut dyn UiRsc) { + let active = &self.active[&child]; + let (frame, part) = Self::re_ask(active, at.extent, active.place); + // The answer it gave, and not what its last drawing reported: a + // drawing made in the box that answer chose is answering a different + // question. + let answer = active.answer.map_or(active.size, |(size, _)| size); + let extent = placed_extent( + part, + answer, + active.declared, + active.place.map(Place::fills), + active.own_align, + ); + let info = DrawInfo { + layer: active.layer, + parent: Some(at.id), + depth: at.depth + 1, + parent_move: at.move_idx, + region_node: active.move_idx != active.parent_move, + mask: at.mask, + frame, + frame_abs: frame.within(&at.local), + part, + place: active.place, + offer_place: active.offer_place, + // Putting it back where it was asked about is that ask again, so + // what it answers there is the answer -- and putting it anywhere + // else is not, however the box was arrived at. + offer: active.place == active.offer_place, + px: frame.size().to_px(at.px), + }; + self.place(child, extent, info, rsc); + } + + /// The frame and the box a widget being asked again is given, from what + /// it already has and where its parent's box is now. A frame's length is + /// the same on every ask, so a declared length is put back where it sits + /// in the part rather than resolved from its rule a second time. + fn re_ask( + active: &ActiveData, + parent_extent: UiRegion, + place: [Place; 2], + ) -> (UiRegion, UiRegion) { + let narrow = AXES.map(|axis| { + let n = axis as usize; + active.declared[n] + .is_some() + .then(|| active.frame.axis(axis).len()) + }); + frame_and_extent( + active.frame, + part_of(parent_extent, place), + narrow, + active.own_align, + ) + } + + /// Re-places everything inside a widget whose own box moved. Every child + /// is placed as a part of that box, so each one's new box is its retained + /// part re-added to the new start -- and a child whose own box then did + /// not change is not touched at all. fn reposition( &mut self, id: WidgetId, - region: UiRegion, - placement: UiRegion, + frame: UiRegion, + extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc, ) { let active = self.active.get_mut(&id).unwrap(); - active.region = region; - active.placement = placement; + active.frame_abs = frame; + active.extent = extent; let local = if info.region_node { UiRegion::FULL } else { - region + frame }; for primitive in &active.primitives { let handle = &primitive.handle; *self.layers[handle.layer].region_mut(handle) = - primitive.region.resolve(local, placement); + primitive.region.within(&extent).within(&local); } if let Some(mask_region) = active.mask_region { - rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.resolve(local, placement); + rsc.ui_mut().masks.get_mut(active.mask).region = + mask_region.within(&extent).within(&local); } - let parent_move = active.move_idx; - let mask = active.mask; - let children = active.extent_children.len(); + let at = Placing { + id, + extent, + local, + px: info.px, + depth: info.depth, + move_idx: active.move_idx, + mask: active.mask, + }; + let children = active.children.len(); for index in 0..children { - let (child, extent) = self.active[&id].extent_children[index]; - let (part, slot) = extent.resolve(placement); - let active = &self.active[&child]; - let (child_local, chosen) = ask_box(part, active.declared, active.own_align, slot); - // What it took of that box is its own answer, which this move did - // not ask again: keep the placement it has on any axis this - // widget is not the one choosing. - let child_placement = UiRegion { - x: chosen[0].unwrap_or(active.placement.x), - y: chosen[1].unwrap_or(active.placement.y), - }; - let child_info = DrawInfo { - layer: active.layer, - parent: Some(id), - depth: info.depth + 1, - parent_move, - region_node: active.move_idx != active.parent_move, - mask, - given_region: child_local, - offer_len: active.offer_len, - offer_placement: active.offer_placement, - px: child_local.size().to_px(info.px), - offered_px: active.offer_len.to_px(info.offered_px), - placement: chosen, - }; - self.place( - child, - child_local.within(&local), - child_placement, - child_info, - rsc, - ); + let child = self.active[&id].children[index]; + self.place_child(child, &at, rsc); } } @@ -834,30 +925,30 @@ impl UiRenderState { fn recompose_subtree( &mut self, id: WidgetId, - region: UiRegion, + frame: UiRegion, parent_move: MoveIdx, rsc: &mut dyn UiRsc, ) { let active = self.active.get_mut(&id).unwrap(); - active.region = region; + active.frame_abs = frame; if active.move_idx != parent_move { - self.moves.set(active.move_idx, region); + self.moves.set(active.move_idx, frame); return; } + let extent = active.extent; for primitive in &active.primitives { let handle = &primitive.handle; *self.layers[handle.layer].region_mut(handle) = - primitive.region.resolve(region, active.placement); + primitive.region.within(&extent).within(&frame); } if let Some(local) = active.mask_region { - rsc.ui_mut().masks.get_mut(active.mask).region = - local.resolve(region, active.placement); + rsc.ui_mut().masks.get_mut(active.mask).region = local.within(&extent).within(&frame); } let children = active.children.len(); for index in 0..children { let child = self.active[&id].children[index]; - let local = self.active[&child].given_region; - self.recompose_subtree(child, local.within(®ion), parent_move, rsc); + let local = self.active[&child].frame; + self.recompose_subtree(child, local.within(&frame), parent_move, rsc); } } @@ -931,11 +1022,12 @@ impl UiRenderState { id, ActiveData { id, - region: UiRegion::FULL, - placement: UiRegion::FULL, - given_region: UiRegion::FULL, - offer_len: UiVec2::FULL_SIZE, - offer_placement: [None; 2], + frame_abs: UiRegion::FULL, + extent: UiRegion::FULL, + frame: UiRegion::FULL, + place: [Place::Within(None); 2], + offer_place: [Place::Within(None); 2], + offer_part: UiRegion::FULL, answer: None, size, holds: LayoutHolds::ANY, @@ -945,12 +1037,10 @@ impl UiRenderState { textures: Vec::new(), primitives: Vec::new(), mask_region: None, - extent_children: Vec::new(), children: Vec::new(), size_deps: Vec::new(), move_idx: info.parent_move, declared: [None; 2], - decided: [false; 2], own_align: rsc.widgets().alignment(id), parent_move: info.parent_move, mask: info.mask, @@ -1119,7 +1209,7 @@ impl UiRenderState { pub fn window_region(&self, id: &impl IdLike) -> Option { let active = self.active.get(&id.id())?; active.drawn.then(|| { - let placed = active.placement.within(&active.region); + let placed = active.extent.within(&active.frame_abs); self.moves .resolve(active.parent_move, placed) .to_px(self.output_size) @@ -1166,20 +1256,16 @@ impl UiRenderState { #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); let old = self.remove(id, false, rsc); - self.draw_inner(id, region, info, old, false, rsc); + self.draw_inner(id, info, old, true, rsc); return true; }; - let (given_px, offered_px) = self.asked_px(id); - // Asked again in the box its parent gave it, which is the question - // its parent asked only while that box is as long as the offer. Any - // other box is a different question, so the parent asks it, with the - // mark left on. Lengths and not whole boxes: what a drawing depends - // on is its lengths, so the same lengths elsewhere is one question. - if given_px != offered_px { - self.mark(id, rsc.widgets_mut()); - self.mark(parent, rsc.widgets_mut()); - return false; - } + let px = self.asked_px(id); + let (was_answer, was_holds) = (active.answer, active.holds); + // The boxes its parent gave it, then and now: its frame is the same + // on every ask, so the question its parent asked is the one this + // asks again -- there is no box here that could be its parent's to + // choose instead. + let parent_extent = self.active[&parent].extent; let info = DrawInfo { layer: active.layer, parent: active.parent, @@ -1187,30 +1273,54 @@ impl UiRenderState { parent_move: active.parent_move, region_node: rsc.widgets().is_region_node(id), mask: active.parent_mask, - given_region: active.given_region, - offer_len: active.offer_len, - offer_placement: active.offer_placement, - px: given_px, - offered_px, - // The same question its parent asked: the axes its parent chose - // the placement on, put back where they were. - placement: AXES - .map(|axis| active.decided[axis as usize].then(|| *active.placement.axis(axis))), + frame: active.frame, + frame_abs: active.frame_abs, + part: Self::re_ask(active, parent_extent, active.place).1, + place: active.place, + offer_place: active.offer_place, + offer: false, + px, + }; + // The ask that measured it, asked again: the box it was measured in + // as its parent left it, rather than where that ask's place resolves + // to now -- a parent drawn again in the box its own answer chose + // gives its children boxes it never measured anything in. + let offered = DrawInfo { + place: info.offer_place, + part: active.offer_part, + offer: true, + ..info }; - let (given, was_answer, was_holds) = (active.region, active.answer, active.holds); #[cfg(feature = "layout-diagnostics")] diag::bump(Counter::LocalRedraws); + // Asked again in the box its parent gave it, which is the question + // its parent asked only while that box is as long as the one it was + // measured in. Any other box is a different question, so the parent + // asks it, with the mark left on. Lengths and not whole boxes: what + // a drawing depends on is its lengths, so the same lengths elsewhere + // is one question. + // + // The frame is the same on every ask now, so this is about the box + // the drawing goes in alone. Removing it -- asking the measuring + // question here and placing the answer afterwards -- is what the + // transparent-frames plan asks for next, and it does not hold yet: + // seeds 104 (align) and 210 (reorder) at depth 5 settle differently + // warm and cold without it. + if info.part.size() != offered.part.size() { + self.mark(id, rsc.widgets_mut()); + self.mark(parent, rsc.widgets_mut()); + return false; + } let old = self.remove(id, false, rsc); - // Refresh the original measurement before restoring the assigned slot. - // Its lengths may differ even though the fraction reference is unchanged. - let offered = DrawInfo { - placement: info.offer_placement, - ..info - }; - let answer = self.draw_inner(id, given, offered, old, false, rsc); - if info.placement != offered.placement { - self.draw_inner(id, given, info, None, false, rsc); + // The original measurement is refreshed before the assigned slot is + // restored: its lengths may differ even though the frame is + // unchanged. Nobody above is drawing, so this places its own answer, + // at the slot it has. + let places = info.place == info.offer_place && info.part == offered.part; + let answer = self.draw_inner(id, offered, old, places, rsc); + if !places { + self.draw_inner(id, info, None, true, rsc); } let active = self.active.get_mut(&id).unwrap(); // A wider contract does not invalidate the guarantee the parent kept. @@ -1221,7 +1331,7 @@ impl UiRenderState { { active.answer = was_answer; } - if active.holds.covers(was_holds) && was_holds.contains(given_px, active.placement) { + if active.holds.covers(was_holds) && was_holds.contains(px, active.extent) { active.holds = was_holds; } if active.answer != was_answer || active.holds != was_holds { diff --git a/src/random.rs b/src/random.rs index 41068de..2e38353 100644 --- a/src/random.rs +++ b/src/random.rs @@ -117,16 +117,19 @@ pub struct Branch { impl Widget for Branch { fn draw(&mut self, painter: &mut Painter) -> Size { - let mut top = UiRegion::FULL; - top.y.end = top.y.start.offset(Px::from_int(40)); - let measured = painter.widget_within(&self.probe, top).len(Axis::X); + let len = painter.extent_len(); + let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); + let top = Place::Within(Some(UiSpan::new(Len::ZERO, cut))); + let measured = painter + .widget_at(&self.probe, UiRegion::FULL, [Place::Within(None), top]) + .len(Axis::X); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); - let mut below = UiRegion::FULL; - below.y.start = below.y.start.offset(Px::from_int(40)); + let below = Place::Within(Some(UiSpan::new(cut, len.y))); + let place = [Place::Within(None), below]; match px > Px::from_f32(self.threshold) { - true => painter.widget_within(&self.wide, below), - false => painter.widget_within(&self.narrow, below), + true => painter.widget_at(&self.wide, UiRegion::FULL, place), + false => painter.widget_at(&self.narrow, UiRegion::FULL, place), }; Size::LEFTOVER } diff --git a/src/widget/mask.rs b/src/widget/mask.rs index 7524991..df302e7 100644 --- a/src/widget/mask.rs +++ b/src/widget/mask.rs @@ -6,7 +6,7 @@ pub struct Masked { impl Widget for Masked { fn draw(&mut self, painter: &mut Painter) -> Size { - painter.set_mask(DrawRegion::Extent(UiRegion::FULL)); + painter.set_mask(UiRegion::FULL); painter.widget(&self.inner); // What it occupies is its box, on both axes, for the reason `Scroll` // reports the same: it clips what is inside to that box, so it can diff --git a/src/widget/position/offset.rs b/src/widget/position/offset.rs index 5f490b6..6870789 100644 --- a/src/widget/position/offset.rs +++ b/src/widget/position/offset.rs @@ -7,7 +7,12 @@ pub struct Offset { impl Widget for Offset { fn draw(&mut self, painter: &mut Painter) -> Size { - let region = UiRegion::FULL.offset(self.amt); - painter.widget_within(&self.inner, region).size() + // The whole of this widget's box, moved: the frame passes through, so + // what the child declares or reports means the same as it would + // without the offset. + let len = painter.extent_len(); + let moved = |len: Len, amt: Len| Place::Within(Some(UiSpan::new(amt, len + amt))); + let place = [moved(len.x, self.amt.x), moved(len.y, self.amt.y)]; + painter.widget_at(&self.inner, UiRegion::FULL, place).size() } } diff --git a/src/widget/position/pad.rs b/src/widget/position/pad.rs index 5782b02..7bce0a8 100644 --- a/src/widget/position/pad.rs +++ b/src/widget/position/pad.rs @@ -13,8 +13,22 @@ impl Widget for Pad { // it; where the box is bigger -- a share of a row, a rule over this // widget -- the slack is the inner's to sit in, and forcing the near // edge pinned it to a corner it had not asked for. - let inside = DrawRegion::Extent(self.padding.region()); - let inner = painter.widget_within(&self.inner, inside).size(); + // + // The padding goes around what it pads: the frame passes through, so + // the inner's fractions mean what they would without it, and only + // the box it draws in is moved in by the pixels. + let len = painter.extent_len(); + let inset = |len: Len, lead: Px, trail: Px| { + Place::Within(Some(UiSpan::new( + Len::from_parts(Rel::ZERO, lead), + len - Len::from_parts(Rel::ZERO, trail), + ))) + }; + let place = [ + inset(len.x, self.padding.left, self.padding.right), + inset(len.y, self.padding.top, self.padding.bottom), + ]; + let inner = painter.widget_at(&self.inner, UiRegion::FULL, place).size(); Size { x: LayoutLen { px: inner.x.px + self.padding.left + self.padding.right, diff --git a/src/widget/position/scroll.rs b/src/widget/position/scroll.rs index 58f31f3..edcd7c8 100644 --- a/src/widget/position/scroll.rs +++ b/src/widget/position/scroll.rs @@ -12,12 +12,11 @@ pub struct Scroll { impl Widget for Scroll { fn draw(&mut self, painter: &mut Painter) -> Size { let container_len = painter.px_len(self.axis); - // Draw in the whole container only when its scrolling-axis length is - // not already known, then draw it at the scrolled offset. + // Measured in the whole viewport, then drawn at the scrolled offset. let whole = UiRegion::FULL; - let own = painter.placement(); - let answer_len = - painter.measure_len(&self.inner, self.axis, whole, [Some(own.x), Some(own.y)]); + let answer_len = painter + .widget_at(&self.inner, whole, [Place::Fill(None); 2]) + .len(self.axis); let content = answer_len.apply_leftover(); self.container_len = container_len; self.content_len = content.to_px(container_len); @@ -46,7 +45,7 @@ impl Widget for Scroll { // have placed the whole scroll in a box longer than it. let slack = (self.container_len - self.content_len).max(Px::ZERO); let anchor = slack.mul(align.rel()); - let mut region = UiRegion::FULL; + let mut content = UiSpan::FULL; // Content that fills the viewport and has not been scrolled is the // viewport, and is handed back as it came. Writing the same box as // its own length in pixels is the same box in another form, and the @@ -55,19 +54,19 @@ impl Widget for Scroll { // each part of it. let moved = anchor != Px::ZERO || self.amt != Px::ZERO; if moved || self.content_len != self.container_len { - let offset = UiVec2::from_axis( - self.axis, - Len::from_parts(Rel::ZERO, anchor - self.amt), - Len::ZERO, - ); - region = region.offset(offset); - region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len); + let start = Len::from_parts(Rel::ZERO, anchor - self.amt); + content = UiSpan::new(start, start.offset(self.content_len)); } - // The viewport is the inner's region, so a fraction it declares or + // The viewport is the inner's frame, so a fraction it declares or // reports is a fraction of what is on screen rather than of the - // content box its own answer decided. Where it is put is the content - // box, scrolled. - painter.widget_at(&self.inner, whole, [Some(region.x), Some(region.y)]); + // content box its own answer decided. Where it is drawn is the + // content box, scrolled. + painter.widget_at( + &self.inner, + whole, + self.axis + .pair(Place::Fill(Some(content)), Place::Fill(None)), + ); // What it occupies is its box, on both axes: it clips its content to // that box, so it can neither take less of one nor honestly ask for // more. The content's length is what it scrolls through, not what it diff --git a/src/widget/position/span.rs b/src/widget/position/span.rs index 795e60d..94e75fe 100644 --- a/src/widget/position/span.rs +++ b/src/widget/position/span.rs @@ -10,34 +10,30 @@ pub struct Span { impl Widget for Span { fn draw(&mut self, painter: &mut Painter) -> Size { let axis = self.dir.axis; - // The row: this span's own box, as a span of the region it was given. - // Its children are laid out along it, and what they declare or report - // is a fraction of the region -- the area this span was told it has, - // which it passes on unchanged. - let own = painter.placement(); - let row = *own.axis(axis); - // Across itself the span's own box is the child's region: a span is - // what contains its children there, and nothing divides that axis. - // Along it the whole region is, so a fraction means the same thing - // for every child however much of the row is left when it is asked. - let region = UiRegion::from_axis(axis, UiSpan::FULL, *own.axis(!axis)); + // The row: this span's own box, as a length of the frame its children + // are laid out against. Its start is nothing's business -- a slot is + // a length from it -- so what this reads is the length alone. + let far = painter.extent_len().axis(axis); let along = |from: Len, to: Len| match self.dir.sign { - Sign::Pos => UiSpan::new(row.start + from, row.start + to), - Sign::Neg => UiSpan::new(row.end - to, row.end - from), + Sign::Pos => UiSpan::new(from, to), + Sign::Neg => UiSpan::new(far - to, far - from), }; - let far = row.len(); - // A length for every child before their final boxes are chosen: from - // a hint where one exists, and from drawing otherwise. + // Across itself the child sits where its own alignment says, in the + // whole of the row: a span is what contains its children there, and + // nothing divides that axis. + let across = Place::Within(None); + // A length for every child before their final slots are chosen. The + // frame passes through unchanged, so `rel(0.5)` is half the area this + // span was given whatever else is in it and wherever this child sits + // among them; what it is drawn in is the room left from the cursor, + // because a text has to wrap at the width actually there. let mut cursor = Len::rel_min(); let mut lens = Vec::with_capacity(self.children.len()); for child in &self.children { - // The whole region is the child's, so `rel(0.5)` is half the area - // this span was given whatever else is in it and wherever this - // child sits among them. What it is placed in is the room left - // from the cursor, because a text has to wrap at the width - // actually there. - let room = axis.pair(Some(along(cursor, far)), None); - let len = painter.measure_len(child, axis, region, room); + let room = Place::Within(Some(along(cursor, far))); + let len = painter + .widget_at(child, UiRegion::FULL, axis.pair(room, across)) + .len(axis); cursor.px += len.px + self.gap; cursor.rel += len.rel; lens.push(len); @@ -55,7 +51,7 @@ impl Widget for Span { ); // What is left for the shares to divide: the row less everything - // fixed, as a length of the region rather than a number of pixels. + // fixed, as a length of the frame rather than a number of pixels. let room = far - Len::from_parts(total.rel, total.px); // Whether anything is left over is a question in pixels: `rel(0.5)` // beside 300 px is full at 600 and overfull at 400. Asked of `room` @@ -68,12 +64,12 @@ impl Widget for Span { // exist at all turns on this. let mut shares = false; if total.leftover > Weight::ZERO { - shares = room.to_px(painter.region_px_len(axis)) > Px::ZERO; + shares = room.to_px(painter.frame_px_len(axis)) > Px::ZERO; let holds = match shares { true => Holds::from(Px::STEP..=Px::MAX), false => Holds::from(Px::MIN..=Px::ZERO), }; - painter.region_holds(axis, holds.through(room)); + painter.frame_holds(axis, holds.through(room)); } // Across itself a span is as long as its longest child -- unless a @@ -109,12 +105,11 @@ impl Widget for Span { fixed.px += len.px; fixed.rel += len.rel; start = shared(fixed, taken, total.leftover, room); - // Along the row the span says where the child goes; across it the - // child sits where its own alignment says. Its region is the - // whole of what this span was given either way, which is what its - // fractions are of. - let placed = - painter.widget_at(child, region, axis.pair(Some(along(from, start)), None)); + // Along the row the span says where the child goes, and that slot + // is the drawing's box outright rather than something to place an + // answer inside again. + let slot = Place::Fill(Some(along(from, start))); + let placed = painter.widget_at(child, UiRegion::FULL, axis.pair(slot, across)); if shrinks { let used = placed.len(!axis); // Choosing between a fixed and a relative length from the diff --git a/src/widget/position/stack.rs b/src/widget/position/stack.rs index a14e389..52bb35c 100644 --- a/src/widget/position/stack.rs +++ b/src/widget/position/stack.rs @@ -13,17 +13,18 @@ impl Widget for Stack { StackSize::Default => None, StackSize::Child(i) => Some(i), }; - // Whichever child sizes the stack keeps the stack's whole region as - // its own -- the stack is the length that child asked for, so taking - // the fraction of the stack's box again would take it twice -- and is - // put where the stack itself is put. + // Whichever child sizes the stack is given the stack's whole box -- + // the stack is the length that child asked for, so placing that + // answer inside the box it decided would apply it twice. let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) { // On the layer that child ends up on, so the ask below is a reuse // rather than a second drawing of it somewhere else: a retained // drawing belongs to the layer it was made on. Some((i, child)) => { painter.child_layer_at(i); - painter.widget(child).size() + painter + .widget_at(child, UiRegion::FULL, [Place::Fill(None); 2]) + .size() } None => Size::LEFTOVER, }; @@ -32,10 +33,9 @@ impl Widget for Stack { continue; } painter.child_layer_at(i); - // Every other child has the stack's own box for its region, since - // the stack is what contains it, and where it sits in one bigger - // than itself is its own business. - painter.widget_within(child, DrawRegion::Extent(UiRegion::FULL)); + // Every other child is drawn in the stack's own box, and where it + // sits in one bigger than itself is its own business. + painter.widget(child); } size } diff --git a/src/widget/text/mod.rs b/src/widget/text/mod.rs index 3ee1c61..08565e2 100644 --- a/src/widget/text/mod.rs +++ b/src/widget/text/mod.rs @@ -80,7 +80,7 @@ impl TextView { // hair under that line, and the break made in it is not the break a // cold layout makes there. let size = Size::from_px(PxVec2::ceil_from_f32(tex.size)); - painter.glyphs(tex, DrawRegion::Extent(region)); + painter.glyphs(tex, region); (region, size) } diff --git a/tests/cases/determinism.rs b/tests/cases/determinism.rs index 721da4e..0b080f6 100644 --- a/tests/cases/determinism.rs +++ b/tests/cases/determinism.rs @@ -21,16 +21,19 @@ struct BranchesOnMeasurement { impl Widget for BranchesOnMeasurement { fn draw(&mut self, painter: &mut Painter) -> Size { - let mut top = UiRegion::FULL; - top.y.end = top.y.start.offset(Px::from_int(40)); - let measured = painter.widget_within(&self.probe, top).len(Axis::X); + let len = painter.extent_len(); + let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); + let top = Place::Within(Some(UiSpan::new(Len::ZERO, cut))); + let measured = painter + .widget_at(&self.probe, UiRegion::FULL, [Place::Within(None), top]) + .len(Axis::X); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); - let mut below = UiRegion::FULL; - below.y.start = below.y.start.offset(Px::from_int(40)); + let below = Place::Within(Some(UiSpan::new(cut, len.y))); + let place = [Place::Within(None), below]; match px > Px::from_f32(self.threshold) { - true => painter.widget_within(&self.wide, below), - false => painter.widget_within(&self.narrow, below), + true => painter.widget_at(&self.wide, UiRegion::FULL, place), + false => painter.widget_at(&self.narrow, UiRegion::FULL, place), }; Size::LEFTOVER } diff --git a/tests/cases/layout.rs b/tests/cases/layout.rs index 3c12bd8..27fd5f1 100644 --- a/tests/cases/layout.rs +++ b/tests/cases/layout.rs @@ -83,11 +83,12 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() { assert!(crowded > whole_row, "{crowded} against {whole_row}"); } -/// The same reading through a pad: its inset is the whole box less the -/// padding, so half of the inset plus the padding is half the box plus one -/// padding, not two. +/// The same reading through a pad: padding goes around what it pads and +/// does not narrow what a fraction under it is a fraction of, so half of the +/// window plus the padding is what the pad takes and where the next child +/// starts. #[test] -fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() { +fn a_pad_puts_its_padding_around_a_fraction_of_the_whole_box() { let mut h = Harness::new((400, 100)); let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc); let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc); @@ -96,8 +97,9 @@ fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() { // placed inside it by its own alignment, which is not what is under test. h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0))); - assert_corners!(h, padded, (0, 0), (210, 100)); - assert_corners!(h, tail, (210, 0), (310, 100)); + assert_corners!(h, inner, (10, 10), (210, 90)); + assert_corners!(h, padded, (0, 0), (220, 100)); + assert_corners!(h, tail, (220, 0), (320, 100)); } #[test] @@ -426,7 +428,8 @@ fn a_row_of_equal_shares_fills_it_exactly() { /// a step of. Kept in step with `snap_floor` in `prelude.wgsl`. fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) { let active = &h.render.active[&id]; - let region = h.render.moves.resolve(active.parent_move, active.region); + let drawn = active.extent.within(&active.frame_abs); + let region = h.render.moves.resolve(active.parent_move, drawn); let dim = h.size().axis(axis); let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor(); let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32()); diff --git a/tests/cases/retained.rs b/tests/cases/retained.rs index cd09686..099257e 100644 --- a/tests/cases/retained.rs +++ b/tests/cases/retained.rs @@ -213,9 +213,12 @@ struct FromHint { impl Widget for FromHint { fn draw(&mut self, painter: &mut Painter) -> Size { let len = painter.size_hint(&self.inner, Axis::Y).unwrap(); - let mut region = UiRegion::FULL; - region.y.end = region.y.start.offset(len.px); - painter.widget_within(&self.inner, region); + let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px)); + painter.widget_at( + &self.inner, + UiRegion::FULL, + [Place::Within(None), Place::Within(Some(top))], + ); Size::LEFTOVER } } @@ -305,6 +308,51 @@ fn a_span_ruled_across_itself_moves_its_child_without_redrawing_it() { assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::rel(1.0)); } +/// A row places its children as lengths from where its own box starts, so a +/// child that grew moves the ones after it and nothing else: each of them is +/// the same box in a new place, which the retained drawing follows without +/// being made again. Both kinds of length: one the row resolves from a rule, +/// and one it takes from what the child reported. +#[test] +fn a_row_moves_what_follows_a_child_that_grew_rather_than_drawing_it() { + for declared in [false, true] { + let mut h = Harness::new((400, 200)); + let first = rect(Color::RED).width(50).add(&mut h.rsc); + let ruled = Rc::new(Cell::new(0)); + let second = Counted { + draws: ruled.clone(), + size: Size::LEFTOVER, + reads_box: false, + }; + let second = match declared { + true => second.width(rel(0.25)).add(&mut h.rsc), + false => second.width(60).add(&mut h.rsc), + }; + let (third, reported) = counted(&mut h, Size::from((70, 20)), false); + h.set_root((first, second, third).span(Dir::RIGHT).width(rel(1.0))); + let (was_ruled, was_reported) = (ruled.get(), reported.get()); + // A quarter of the row is a quarter of the row, wherever it sits in + // it and whatever the first child takes. + let width = match declared { + true => 100, + false => 60, + }; + assert_corners!(h, second, (50, 0), (50 + width, 200)); + + h.set_len(first, Axis::X, 80); + h.frame(); + + assert_eq!(ruled.get(), was_ruled, "the ruled child was drawn again"); + assert_eq!( + reported.get(), + was_reported, + "the reported child was drawn again" + ); + assert_corners!(h, second, (80, 0), (80 + width, 200)); + assert_corners!(h, third, (80 + width, 90), (150 + width, 110)); + } +} + /// The output is the root of the box chain, so a resize is a box that changed /// length like any other -- there is not a second rule for the window. A /// drawing that holds for one length is drawn again whichever box moved. @@ -749,36 +797,6 @@ fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec { .collect() } -#[test] -fn frame_geometry_and_extent_geometry_keep_their_references() { - struct Both(Rc>); - impl Widget for Both { - fn draw(&mut self, painter: &mut Painter) -> Size { - self.0.set(self.0.get() + 1); - painter.primitive_within(RectPrimitive::color(Color::RED), UiRegion::FULL); - painter.primitive(RectPrimitive::color(Color::BLUE)); - Size::LEFTOVER - } - } - for node in [false, true] { - let mut h = Harness::new((400, 200)); - let first = rect(Color::GREEN).width(100).add(&mut h.rsc); - let draws = Rc::new(Cell::new(0)); - let both = Both(draws.clone()).add(&mut h.rsc); - h.rsc.widgets_mut().set_region_node(both, node); - h.set_root((first, both).span(Dir::RIGHT)); - let count = draws.get(); - h.set_len(first, Axis::X, 200); - h.frame(); - assert_eq!(draws.get(), count); - let bounds = primitive_bounds(&h, both.id()); - assert_eq!(bounds[0].top_left.x, Px::ZERO); - assert_eq!(bounds[0].bot_right.x, Px::from_int(400)); - assert_eq!(bounds[1].top_left.x, Px::from_int(200)); - assert_eq!(bounds[1].bot_right.x, Px::from_int(400)); - } -} - #[test] fn changing_an_inherited_extent_keeps_the_original_measurement_offer() { fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget, WeakWidget) { @@ -858,7 +876,14 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() { } impl Widget for Frame { fn draw(&mut self, painter: &mut Painter) -> Size { - painter.widget_within(&self.child, self.region); + painter.widget_at( + &self.child, + UiRegion::FULL, + [ + Place::Within(Some(self.region.x)), + Place::Within(Some(self.region.y)), + ], + ); Size::LEFTOVER } } @@ -866,7 +891,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() { impl Widget for Painted { fn draw(&mut self, painter: &mut Painter) -> Size { self.0.set(self.0.get() + 1); - painter.set_mask(DrawRegion::Extent(UiRegion::FULL)); + painter.set_mask(UiRegion::FULL); painter.primitive(RectPrimitive::color(Color::BLUE)); Size::LEFTOVER } @@ -966,8 +991,7 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() { UiSpan::new(Len::rel(0.23) + Len::px(-7.125), Len::FULL), UiSpan::new(Len::rel(0.37) + Len::px(3.25), Len::FULL), ); - painter.glyphs(text, DrawRegion::Frame(origin)); - painter.glyphs(text, DrawRegion::Extent(origin)); + painter.glyphs(text, origin); Size::LEFTOVER } } @@ -981,7 +1005,10 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() { painter.widget_at( &self.child, self.region, - [Some(self.extent.x), Some(self.extent.y)], + [ + Place::Fill(Some(self.extent.x)), + Place::Fill(Some(self.extent.y)), + ], ); Size::LEFTOVER } @@ -1128,7 +1155,7 @@ fn widening_and_restoring_a_contract_does_not_invalidate_its_reader() { assert_eq!(leaf_draws.get(), settled + 1); } #[test] -fn padding_and_stack_frames_follow_the_extent_without_drawing_again() { +fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() { struct Observed { widget: W, draws: Rc>, @@ -1148,7 +1175,10 @@ fn padding_and_stack_frames_follow_the_extent_without_drawing_again() { painter.widget_at( &self.child, UiRegion::FULL, - [Some(self.extent.x), Some(self.extent.y)], + [ + Place::Fill(Some(self.extent.x)), + Place::Fill(Some(self.extent.y)), + ], ); Size::LEFTOVER } @@ -1179,13 +1209,18 @@ fn padding_and_stack_frames_follow_the_extent_without_drawing_again() { h.set_root(root); (root, leaf, fixed, draws) }; + // The same box in three places. A pad places its child as lengths of + // its own box measured from where that box starts, so moving it is + // nothing to the pad -- where changing its length is a different + // question, and does draw it again. + let at = |start: f32| { + let span = |start: Len| UiSpan::new(start, start + Len::rel(0.4)); + UiRegion::new(span(Len::rel(start) + Len::px(3.125)), span(Len::px(11.25))) + }; let mut warm = Harness::new((403, 211)); - let (root, leaf, fixed, draws) = plant(&mut warm, UiRegion::FULL); - for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] { - let extent = UiRegion::new( - UiSpan::new(Len::rel(start) + Len::px(3.125), Len::rel(end)), - UiSpan::new(Len::px(11.25), Len::rel(end)), - ); + let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13)); + for start in [0.13, -0.17, 0.31] { + let extent = at(start); let before = draws.get(); warm.rsc[root].extent = extent; warm.frame(); @@ -1229,11 +1264,11 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() { &self.child, UiRegion::FULL, [ - Some(UiSpan::new( + Place::Fill(Some(UiSpan::new( Len::px(self.start), Len::px(self.start + 200.0), - )), - Some(UiSpan::FULL), + ))), + Place::Fill(Some(UiSpan::FULL)), ], ); Size::LEFTOVER @@ -1267,7 +1302,14 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() { impl Widget for Container { fn draw(&mut self, painter: &mut Painter) -> Size { painter - .widget_within(&self.child, DrawRegion::Extent(self.region)) + .widget_at( + &self.child, + UiRegion::FULL, + [ + Place::Within(Some(self.region.x)), + Place::Within(Some(self.region.y)), + ], + ) .size() } } @@ -1283,7 +1325,10 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() { .widget_at( &self.child, UiRegion::FULL, - [Some(self.extent.x), Some(self.extent.y)], + [ + Place::Fill(Some(self.extent.x)), + Place::Fill(Some(self.extent.y)), + ], ) .size(), ); diff --git a/tests/cases/scroll.rs b/tests/cases/scroll.rs index be4ad06..cf5f6fe 100644 --- a/tests/cases/scroll.rs +++ b/tests/cases/scroll.rs @@ -71,7 +71,7 @@ fn a_clipping_widget_reporting_more_than_its_box_is_caught() { impl Widget for Clipper { fn draw(&mut self, painter: &mut Painter) -> Size { - painter.set_mask(painter.region()); + painter.set_mask(UiRegion::FULL); painter.widget(&self.0).size() } }