Compare commits

..
Author SHA1 Message Date
iris-aiandClaude Fable 5.1 62a16b5608 Re-ask a dirty widget at its offer locally instead of deferring to its parent
A local redraw refused any widget whose given box was not as long as its
offer and marked its parent instead. Under the frame/extent protocol that
is nearly every widget beneath a self-sized container: a span hands its
children its own placement across itself, which is `FULL` while the span
is measured and its answer once it is placed, so the children's offer and
given frames differ on every such axis. A `many` frame at seed 13, depth
8 escalated 43 marks along chains up to seven levels and redrew 508 of
583 active widgets where e44dea3 redraws 159.

Retain the offer's frame beside the given one and ask the offer question
locally: the offer frame composed where the given one is, at the offer's
lengths and placement, then place at the given box where the two differ.
Seed 13 `many` goes from 4.73 ms to 1.28 ms against e44dea3's 0.90, and
294 distinct widgets a frame; size, scroll and repaint are unchanged.

Not sound yet: the suite, the debug oracle and the shrinker at 400 trees
of depth 5 pass, but the oracle at 1000 seeds of depth 6 diverges on seed
532 under reorder and seed 398 under every-size. Both reduce to a
self-sized container whose answer changes under a local redraw; the
reduced plans are in docs/HANDOFF.md of ai-app-2.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 20:51:36 -04:00
19 changed files with 1005 additions and 931 deletions

No files matched your search

+66 -39
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive, Size, DrawRegion, LayerId, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, RegionAlign, RetainedPrimitive,
TextureHandle, UiRegion, WidgetId, Size, TextureHandle, UiRegion, UiVec2, WidgetId,
}; };
/// What is kept of a widget its parent has asked about. `drawn` says whether /// What is kept of a widget its parent has asked about. `drawn` says whether
@@ -9,35 +9,31 @@ use crate::{
#[derive(Debug)] #[derive(Debug)]
pub struct ActiveData { pub struct ActiveData {
pub id: WidgetId, pub id: WidgetId,
/// Its frame in `parent_move`'s coordinates: what a fraction it declares /// The box its parent gave it, in `parent_move`'s coordinates: what it
/// or reports is a fraction of, composed. Everything it draws sits inside /// was asked about, and what a fraction under it is a fraction of. A
/// this by way of `extent`. /// local redraw asks here.
pub frame_abs: UiRegion, pub region: UiRegion,
/// Where its drawing goes, in the frame's own coordinates. /// Where its drawing sits inside that box, in the box's own coordinates.
pub extent: UiRegion, pub placement: UiRegion,
/// That frame in its parent's frame coordinates, before composition: /// The original frame in its parent widget's coordinates. Recomposition
/// forwarded whole by a transparent container, narrowed by a declared /// and pixel-length evaluation both follow this chain.
/// length or an inset. Its length is the same on every ask, which is what pub given_region: UiRegion,
/// a local redraw relies on to ask its parent's own question again. /// The frame it was first asked in, in the same coordinates: the offer's
pub frame: UiRegion, /// frame, which its parent's placing draw may since have narrowed.
/// What of its parent's extent the drawing was given, and what it was pub offer_region: UiRegion,
/// given at the parent's first ask of it -- the question a cold layout /// The lengths of the box its parent first asked about it in, as
/// asks. A part is a length from the extent's start, so an extent that /// lengths of the box the parent was itself offered. Any later box it
/// moved re-places every child by re-adding that start. /// was given was decided knowing its answer, so this is the question
pub place: [Place; 2], /// asked again -- and a chain of fractions has no frame in it, which is
pub offer_place: [Place; 2], /// why a region node between two widgets cannot break it.
/// The box that ask gave it, in its frame's coordinates. Kept rather pub offer_len: UiVec2,
/// than worked out again from where its parent's own box is now: a pub offer_placement: [Option<crate::UiSpan>; 2],
/// 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 /// The measured answer and its dependencies. A hint-only dependency or
/// a widget first encountered during placement has no measurement yet. /// a widget first encountered during placement has no measurement yet.
pub answer: Option<(Size, LayoutHolds)>, pub answer: Option<(Size, LayoutHolds)>,
/// What the widget said it used of its frame, the last time it drew. /// What the widget said it used of its box, the last time it drew.
pub size: Size, pub size: Size,
/// The frame and extent reads that this drawing holds for. /// The frame, extent and explicit placement reads that this drawing holds for.
pub holds: LayoutHolds, pub holds: LayoutHolds,
pub drawn: bool, pub drawn: bool,
pub parent: Option<WidgetId>, pub parent: Option<WidgetId>,
@@ -46,24 +42,30 @@ pub struct ActiveData {
/// widget a frame visits and cannot drift while one is being drawn. /// widget a frame visits and cannot drift while one is being drawn.
pub depth: usize, pub depth: usize,
pub textures: Vec<TextureHandle>, pub textures: Vec<TextureHandle>,
/// 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<RetainedPrimitive>, pub primitives: Vec<RetainedPrimitive>,
pub mask_region: Option<UiRegion>, pub mask_region: Option<DrawRegion>,
/// 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 children: Vec<WidgetId>, pub children: Vec<WidgetId>,
/// The children whose size this widget read while drawing. /// The children whose size this widget read while drawing.
pub size_deps: Vec<WidgetId>, pub size_deps: Vec<WidgetId>,
/// The movable region its primitives are positioned through: its own when /// The movable region its primitives are positioned through: its own when
/// opted in, otherwise the nearest ancestor's. /// opted in, otherwise the nearest ancestor's.
pub move_idx: MoveIdx, pub move_idx: MoveIdx,
/// The declared lengths whoever drew this widget resolved into its frame. /// The declared lengths whoever drew this widget resolved into its box.
/// A change to one moves a box this widget cannot fix by drawing again, /// A change to one moves a box this widget cannot fix by drawing again,
/// and comparing them is what says so. /// and comparing them is what says so.
pub declared: [Option<LayoutLen>; 2], pub declared: [Option<LayoutLen>; 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 /// Its alignment when it was last drawn, which a change to the property
/// is found against. /// is found against.
pub own_align: RegionAlign, pub own_align: RegionAlign,
/// The movable region whose coordinates `frame_abs` uses. /// The movable region whose coordinates `region` uses.
pub parent_move: MoveIdx, pub parent_move: MoveIdx,
/// The mask its drawing is clipped to: one it set itself, or the one it /// The mask its drawing is clipped to: one it set itself, or the one it
/// inherited from whoever drew it. /// inherited from whoever drew it.
@@ -77,12 +79,37 @@ pub struct ActiveData {
} }
impl ActiveData { impl ActiveData {
/// Whether what it answered still stands for a frame of these pixel /// Whether what it answered still stands for a box of these pixel
/// lengths. The answer was given in the box its parent first asked /// lengths -- the box it was asked in, where `holds` is about the box its
/// about, which is what it is checked against -- `holds` on the record /// answer then chose.
/// is about the box the answer then chose. pub fn answers_at(&self, px: crate::PxVec2) -> bool {
pub fn answers_at(&self, px: crate::PxVec2, part: UiRegion) -> bool { self.answer.is_some_and(|(_, holds)| {
self.answer holds.contains(
.is_some_and(|(_, holds)| holds.contains(px, part)) 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<crate::UiSpan>; 2]) {
match self {
Self::Inherit => (UiRegion::FULL, [Some(extent.x), Some(extent.y)]),
Self::Within(part) => (part.within(&extent), [None; 2]),
}
} }
} }
+36
View File
@@ -0,0 +1,36 @@
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<UiRegion> for DrawRegion {
fn from(region: UiRegion) -> Self {
Self::Frame(region)
}
}
#[derive(Debug)]
pub struct RetainedPrimitive {
pub handle: PrimitiveHandle,
pub region: DrawRegion,
}
+33 -32
View File
@@ -1,62 +1,63 @@
use crate::{Axis, Holds, Len, PxVec2, UiRegion}; use crate::{Axis, Holds, PxVec2, UiRegion};
const AXES: [Axis; 2] = [Axis::X, Axis::Y]; /// Dependencies of one evaluation, before the frame and extent are composed.
/// 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)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct LayoutHolds { pub struct LayoutHolds {
pub frame: [Holds; 2], pub frame: [Holds; 2],
pub extent: [Holds; 2], pub extent: [Holds; 2],
pub extent_len: [Option<Len>; 2], pub placement: Option<UiRegion>,
} }
impl LayoutHolds { impl LayoutHolds {
pub const ANY: Self = Self { pub const ANY: Self = Self {
frame: [Holds::ANY; 2], frame: [Holds::ANY; 2],
extent: [Holds::ANY; 2], extent: [Holds::ANY; 2],
extent_len: [None; 2], placement: None,
}; };
pub fn and(self, other: Self) -> Self { pub fn and(self, other: Self) -> Self {
let mut result = Self::ANY;
for n in 0..2 {
result.frame[n] = self.frame[n].and(other.frame[n]);
result.extent[n] = self.extent[n].and(other.extent[n]);
debug_assert!( debug_assert!(
self.extent_len[n].is_none() self.placement.is_none()
|| other.extent_len[n].is_none() || other.placement.is_none()
|| self.extent_len[n] == other.extent_len[n] || self.placement == other.placement
); );
result.extent_len[n] = self.extent_len[n].or(other.extent_len[n]); 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),
} }
result
} }
pub fn covers(self, other: Self) -> bool { pub fn covers(self, other: Self) -> bool {
(0..2).all(|n| { 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].lo <= other.frame[n].lo
&& self.frame[n].hi >= other.frame[n].hi && self.frame[n].hi >= other.frame[n].hi
&& self.extent[n].lo <= other.extent[n].lo && self.extent[n].lo <= other.extent[n].lo
&& self.extent[n].hi >= other.extent[n].hi && 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, extent: UiRegion) -> bool { pub fn contains(self, px: PxVec2, placement: UiRegion) -> bool {
AXES.into_iter().all(|axis| { self.placement.is_none_or(|old| old == placement)
let n = axis as usize; && [Axis::X, Axis::Y].into_iter().all(|axis| {
let len = extent.axis(axis).len(); self.frame[axis as usize].contains(px.axis(axis))
self.frame[n].contains(px.axis(axis)) && self.extent[axis as usize]
&& self.extent[n].contains(len.to_px(px.axis(axis))) .contains(placement.axis(axis).len().to_px(px.axis(axis)))
&& self.extent_len[n].is_none_or(|pinned| pinned == len) })
}
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()))
}) })
} }
} }
+2 -2
View File
@@ -10,17 +10,17 @@ use crate::{
pub const CHAIN_LIMIT: u32 = 64; pub const CHAIN_LIMIT: u32 = 64;
mod active; mod active;
mod draw_region;
mod holds; mod holds;
mod layout_holds; mod layout_holds;
mod painter; mod painter;
mod place;
mod render_state; mod render_state;
pub use active::*; pub use active::*;
pub use draw_region::*;
pub use holds::*; pub use holds::*;
pub use layout_holds::*; pub use layout_holds::*;
pub use painter::{Painter, PrimitiveLike}; pub use painter::{Painter, PrimitiveLike};
pub use place::*;
pub use render_state::*; pub use render_state::*;
#[derive(Default)] #[derive(Default)]
+370 -257
View File
@@ -1,9 +1,9 @@
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter}; use crate::layout_diagnostics::{self as diag, Counter};
use crate::{ use crate::{
Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, RenderedText, Axis, DrawRegion, ExtentPlacement, Holds, LayoutHolds, LayoutLen, Len, Px, PxVec2, RegionAlign,
RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle, RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets, TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
render::{ render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind, GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
TexturePrimitive, TexturePrimitive,
@@ -17,47 +17,49 @@ pub struct Painter<'a> {
pub(super) state: &'a mut UiRenderState, pub(super) state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc, pub(super) rsc: &'a mut dyn UiRsc,
/// What a fraction this widget declares or reports is a fraction of, in /// The box its parent gave it, in the coordinates of `move_idx`: what a
/// the coordinates of `move_idx`: forwarded from its parent unchanged /// fraction of this widget's area is a fraction of, and what every region
/// through a span, a stack or a scroll, and narrowed only by what was /// it writes composes within. The same box on the ask that measures and
/// decided above it -- a declared length, an inset, the root. Its length /// the ask that places, which is what keeps a fraction under it from
/// is the same on every ask of the widget, which is what keeps a fraction /// being resolved twice.
/// under it from being resolved twice. pub(super) region: UiRegion,
pub(super) frame: UiRegion, /// Where this widget's drawing sits inside that box, in the box's own
/// Where this widget's drawing goes, in the frame's own coordinates. /// coordinates: `FULL` while its answer is not yet known, and the box
/// Everything it writes is in these coordinates, and its children are /// its answer or its parent chose once one of them has.
/// placed as parts of it. pub(super) placement: UiRegion,
pub(super) extent: UiRegion, /// Whether this draw read its placement, which makes the drawing one
/// The extent's symbolic length where this draw read it, which makes the /// that holds for that placement alone -- the way reading a length in
/// drawing one that holds for that length alone -- the way reading a /// pixels makes it hold for that length.
/// length in pixels makes it hold for that number of pixels. pub(super) reads_placement: bool,
pub(super) extent_len: [Option<Len>; 2], /// That box in pixels, which its children's are a length of: threaded
/// The frame in pixels, which its children's frames are a length of: /// down from the box this widget was given rather than composed back up
/// threaded down rather than composed back up the chain, so every length /// the chain, so every length in layout is one multiply from its
/// in layout is one multiply from its parent's and [`Holds::through`] /// parent's and [`Holds::through`] inverts exactly that.
/// inverts exactly that.
pub(super) px: PxVec2, pub(super) px: PxVec2,
pub(super) mask: MaskIdx, pub(super) mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>, pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<RetainedPrimitive>, pub(super) primitives: Vec<RetainedPrimitive>,
pub(super) mask_region: Option<UiRegion>, pub(super) mask_region: Option<DrawRegion>,
pub(super) extent_children: Vec<(WidgetId, ExtentPlacement)>,
pub(super) extent_own: [Holds; 2],
/// Only children whose answers were read constrain this widget's answer. /// Only children whose answers were read constrain this widget's answer.
pub(super) answer_under: LayoutHolds, pub(super) answer_under: LayoutHolds,
pub(super) children: Vec<WidgetId>, pub(super) children: Vec<WidgetId>,
/// The children asked about so far, so the first place each was asked in /// The children asked about so far, so the first box each was asked in
/// is the one recorded as its offer. /// is the one recorded as its offer.
pub(super) offered: Vec<WidgetId>, pub(super) offered: Vec<WidgetId>,
/// Whether this draw is at the place its parent first asked about, which /// The lengths of the box this widget was first asked about in, in
/// makes the questions it asks the ones a cold layout asks and their /// pixels. Its children's offers are a fraction of it.
/// answers the ones to keep. 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.
pub(super) at_offer: bool, pub(super) at_offer: bool,
/// The children whose size this widget read while drawing. /// The children whose size this widget read while drawing.
pub(super) size_deps: Vec<WidgetId>, pub(super) size_deps: Vec<WidgetId>,
/// What this draw itself read of its frame in pixels, per axis: every /// What this draw itself read of its box in pixels, per axis: every
/// length until it reads one, then that one, unless it says otherwise. /// length until it reads one, then that one, unless it says otherwise.
pub(super) own: [Holds; 2], pub(super) own: [Holds; 2],
/// The same for its extent.
pub(super) extent_own: [Holds; 2],
/// Dependencies of every child drawing, including unmeasured overlays. /// Dependencies of every child drawing, including unmeasured overlays.
pub(super) under: LayoutHolds, pub(super) under: LayoutHolds,
/// The movable region this widget's primitives are positioned through: /// The movable region this widget's primitives are positioned through:
@@ -72,29 +74,26 @@ pub struct Painter<'a> {
} }
impl<'a> Painter<'a> { impl<'a> Painter<'a> {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) { fn primitive_at<P: Primitive>(&mut self, primitive: P, region: DrawRegion) {
let kind = self.rsc.ui_mut().primitives.kind::<P>(); let kind = self.rsc.ui_mut().primitives.kind::<P>();
self.write(kind, primitive, region); self.write(kind, primitive, region);
} }
/// Takes the kind, for a caller writing many of one primitive. /// Takes the kind, for a caller writing many of one primitive.
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) { fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: DrawRegion) {
self.write_resolved(kind, primitive, region, self.resolve(region)); self.write_resolved(
} kind,
primitive,
/// A box in this widget's extent coordinates, composed into the region,
/// coordinates its move slot is in: through the extent, then through the region.resolve(self.region, self.placement),
/// 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<P: Primitive>( fn write_resolved<P: Primitive>(
&mut self, &mut self,
kind: PrimitiveKind<P>, kind: PrimitiveKind<P>,
primitive: P, primitive: P,
region: UiRegion, region: DrawRegion,
resolved: UiRegion, resolved: UiRegion,
) { ) {
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
@@ -123,70 +122,135 @@ impl<'a> Painter<'a> {
/// Writes a primitive over the whole of this widget's own box. /// Writes a primitive over the whole of this widget's own box.
pub fn primitive(&mut self, primitive: impl PrimitiveLike) { pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
let at = DrawRegion::Extent(UiRegion::FULL);
let primitive = primitive.into_primitive(self); let primitive = primitive.into_primitive(self);
self.primitive_at(primitive, UiRegion::FULL) self.primitive_at(primitive, at)
} }
/// Writes a primitive in a part of this widget's own box, in that box's /// Writes in the frame by default. `DrawRegion::Extent` keeps the local
/// coordinates. /// geometry attached to this widget's box without reading its placement.
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) { pub fn primitive_within(
&mut self,
primitive: impl PrimitiveLike,
region: impl Into<DrawRegion>,
) {
let primitive = primitive.into_primitive(self); let primitive = primitive.into_primitive(self);
self.primitive_at(primitive, region); self.primitive_at(primitive, region.into());
} }
/// Sets a mask, in this widget's own box's coordinates. /// Sets a mask in the selected frame or extent coordinates.
pub fn set_mask(&mut self, region: UiRegion) { pub fn set_mask(&mut self, region: impl Into<DrawRegion>) {
let region = region.into();
self.mask_region = Some(region); self.mask_region = Some(region);
assert!(self.mask == MaskIdx::NONE); 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 { self.mask = self.rsc.ui_mut().masks.push(Mask {
region: resolved, region: region.resolve(self.region, self.placement),
move_idx, move_idx: self.move_idx,
}); });
} }
/// Draws a widget in the whole of this widget's own box, with the frame /// Draws a widget in the whole of this widget's own box: it gets the
/// forwarded unchanged: what a container that is only a wrapper around /// same region -- the same area for its fractions to be of -- and is put
/// one child wants, and what every transparent container passes for the /// where this widget was put. What a container that is only a wrapper
/// frame. /// around one child wants, since its box is the child's.
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> { pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
self.widget_at(id, UiRegion::FULL, [Place::Within(Part::All); 2]) let own = self.placement;
self.widget_at_inner(
id,
UiRegion::FULL,
[Some(own.x), Some(own.y)],
Some(ExtentPlacement::Inherit),
false,
)
} }
/// Draws a child, saying what its fractions are of and where its drawing /// What a widget's rules declare its lengths to be, which whoever draws
/// goes. /// 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<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 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<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
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.
/// ///
/// `frame` is that reference, in this widget's own frame coordinates: /// `DrawRegion::Extent` gives a part of where this widget's drawing sits
/// [`UiRegion::FULL`] forwards this widget's frame, which is what a /// instead, for a container whose children belong inside that rather than
/// container that only divides room passes, so a fraction under it means /// inside the box it was offered. The part is what is kept, so moving the
/// the same wherever it sits and however deeply it is nested. Narrowing /// extent re-places the child rather than drawing this widget again.
/// it is for what is decided from above -- an inset's margins -- and a pub fn widget_within<'s, W: ?Sized>(
/// declared length narrows it here. &'s mut self,
id: &'s StrongWidget<W>,
region: impl Into<DrawRegion>,
) -> 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.
/// ///
/// `place` is where the drawing goes, per axis, as a part of this /// `region` is the child's own area: what a fraction it declares or
/// widget's extent: see [`Place`]. A narrowed frame is its own extent, /// reports is a fraction of, and the coordinates the regions it writes
/// since the narrowing is what said where the drawing goes. /// 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.
pub fn widget_at<'s, W: ?Sized>( pub fn widget_at<'s, W: ?Sized>(
&'s mut self, &'s mut self,
id: &'s StrongWidget<W>, id: &'s StrongWidget<W>,
frame: UiRegion, region: UiRegion,
place: [Place; 2], placement: [Option<UiSpan>; 2],
) -> DrawResult<'s, 'a, W> { ) -> 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<W>,
region: UiRegion,
placement: [Option<UiSpan>; 2],
extent: Option<ExtentPlacement>,
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 region_node = self.rsc.widgets().is_region_node(id.id());
let declared = self.declared_lens(id); let declared = self.declared_lens(id);
let align = self.rsc.widgets().alignment(id.id()); let align = self.rsc.widgets().alignment(id.id());
let narrow = AXES.map(|axis| { let (local, placement) = ask_box(region, declared, align, placement);
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 { let within = match local == UiRegion::FULL {
true => self.frame, true => self.region,
false => local.within(&self.frame), false => local.within(&self.region),
}; };
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
if region_node { if region_node {
@@ -198,20 +262,39 @@ impl<'a> Painter<'a> {
self.children.push(id.id()); self.children.push(id.id());
} }
let first_ask = self.offer(id.id()); let first_ask = self.offer(id.id());
let offer_place = if first_ask { let given_len = local.size();
place 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_region = match first_ask {
true => local,
false => self
.state
.active
.get(&id.id())
.map_or(local, |a| a.offer_region),
};
let offer_placement = if first_ask {
placement
} else { } else {
self.state self.state
.active .active
.get(&id.id()) .get(&id.id())
.map_or(place, |a| a.offer_place) .map_or(placement, |a| a.offer_placement)
}; };
let px = local.size().to_px(self.px); let px = given_len.to_px(self.px);
// The answer and what it holds for, both about the place asked in. let offered_px = offer_len.to_px(self.offered_px);
// The child's record may say something else once its drawing has been // 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
// placed: a drawing made again in its placed box holds for that box. // placed: a drawing made again in its placed box holds for that box.
let (size, answer_holds, holds) = self.state.draw_inner( let (size, answer_holds, holds) = self.state.draw_inner(
id.id(), id.id(),
within,
DrawInfo { DrawInfo {
layer: self.layer, layer: self.layer,
parent: Some(self.id), parent: Some(self.id),
@@ -219,46 +302,77 @@ impl<'a> Painter<'a> {
parent_move: self.move_idx, parent_move: self.move_idx,
region_node, region_node,
mask: self.mask, mask: self.mask,
frame: local, given_region: local,
frame_abs: within, offer_region,
part: extent, offer_len,
place, offer_placement,
offer_place,
// The question its parent measured it by, asked again: the
// same widget in the same place, however this draw came
// about.
offer: place == offer_place,
px, px,
offered_px,
placement,
}, },
None, None,
measuring,
self.rsc, self.rsc,
); );
let compose = |holds| in_parent(holds, local, extent, place, declared); let in_parent = |holds: LayoutHolds| {
self.under = self.under.and(compose(holds)); 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);
}
DrawResult { DrawResult {
child: id, child: id,
painter: self, painter: self,
size: in_parent_frame(size, local.size(), declared), size: in_parent_frame(size, local.size(), declared),
answer_holds: compose(answer_holds), 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<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
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<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
declared_lens(self.rsc.widgets(), id.id())
}
/// What a child says its length is without being drawn, if it can say. /// What a child says its length is without being drawn, if it can say.
/// Asking counts as reading its size. /// Asking counts as reading its size.
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> { pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
@@ -287,11 +401,58 @@ 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<W: ?Sized>(
&mut self,
child: &StrongWidget<W>,
axis: Axis,
region: UiRegion,
placement: [Option<UiSpan>; 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_region = local;
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 /// Whether this is the first box a child is asked about in during a draw
/// that is itself the one its parent measured -- the question a cold /// that is itself in the box it was asked in -- the question a cold
/// layout asks, whose answer is the one to keep. A drawing made again in /// layout asks, whose answer is the one to keep.
/// 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 { fn offer(&mut self, child: WidgetId) -> bool {
if !self.at_offer || self.offered.contains(&child) { if !self.at_offer || self.offered.contains(&child) {
return false; return false;
@@ -320,10 +481,11 @@ impl<'a> Painter<'a> {
/// Writes glyphs in the selected frame or extent coordinates. /// Writes glyphs in the selected frame or extent coordinates.
// TODO: merge the text methods into the primitive ones. // TODO: merge the text methods into the primitive ones.
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) { pub fn glyphs(&mut self, text: &RenderedText, origin: impl Into<DrawRegion>) {
let origin = origin.into();
// Glyph offsets and sizes are pixels, which compose additively. // Glyph offsets and sizes are pixels, which compose additively.
// Only the shared origin needs composing through the extent. // Only the shared origin needs the frame/extent composition.
let resolved = self.resolve(origin); let resolved = origin.resolve(self.region, self.placement);
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>(); let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
for glyph in text.glyphs.iter() { for glyph in text.glyphs.iter() {
let place = |mut region: UiRegion| { let place = |mut region: UiRegion| {
@@ -347,22 +509,30 @@ impl<'a> Painter<'a> {
color: text.color, color: text.color,
flags: glyph.entry.flags(), flags: glyph.entry.flags(),
}, },
place(origin), origin.map(place),
place(resolved), place(resolved),
); );
} }
} }
/// The symbolic length of this widget's own box along one axis, in the /// The box this widget's parent gave it, in the coordinates its own
/// lengths of its frame that it places its children in. Reading it pins /// primitives are written in -- so a region composed `within` it may be
/// the drawing to that length -- and to nothing about where the box /// drawn directly. Its own box is [`Self::placement`] of this one.
/// starts, which is what lets a container move without being drawn pub fn region(&self) -> UiRegion {
/// again. One axis at a time, because a container that divides one axis self.region
/// holds for any length of the other. }
pub fn extent_len(&mut self, axis: Axis) -> Len {
let len = self.extent.axis(axis).len(); /// Where this widget's drawing goes inside the box it was given, in that
self.extent_len[axis as usize] = Some(len); /// box's coordinates: what its own answer took of it, or what its parent
len /// 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
} }
/// Where this widget sits in a box longer than the length it takes. A /// Where this widget sits in a box longer than the length it takes. A
@@ -390,6 +560,14 @@ impl<'a> Painter<'a> {
.is_some() .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 /// 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. /// that holds for this box only, until `holds` says how far it goes.
pub fn px_size(&mut self) -> PxVec2 { pub fn px_size(&mut self) -> PxVec2 {
@@ -399,7 +577,7 @@ impl<'a> Painter<'a> {
/// One axis of this widget's own box in pixels. Prefer this to /// One axis of this widget's own box in pixels. Prefer this to
/// [`Self::px_size`] when the other axis cannot affect the drawing. /// [`Self::px_size`] when the other axis cannot affect the drawing.
pub fn px_len(&mut self, axis: Axis) -> Px { pub fn px_len(&mut self, axis: Axis) -> Px {
let part = self.extent.axis(axis).len(); let part = self.placement.axis(axis).len();
let len = part.to_px(self.px.axis(axis)); let len = part.to_px(self.px.axis(axis));
let own = &mut self.extent_own[axis as usize]; let own = &mut self.extent_own[axis as usize];
if *own == Holds::ANY { if *own == Holds::ANY {
@@ -413,7 +591,7 @@ impl<'a> Painter<'a> {
/// of the box, and the same reported size. A widget that read its length /// 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. /// in pixels holds for that one alone until it says otherwise.
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) { pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let part = self.extent.axis(axis).len(); let part = self.placement.axis(axis).len();
let holds = holds.into(); let holds = holds.into();
debug_assert!( debug_assert!(
holds.contains(part.to_px(self.px.axis(axis))), holds.contains(part.to_px(self.px.axis(axis))),
@@ -424,10 +602,10 @@ impl<'a> Painter<'a> {
self.extent_own[axis as usize] = holds; self.extent_own[axis as usize] = holds;
} }
/// One axis of this widget's frame in pixels -- what a fraction of its /// One axis of the box this widget's parent gave it, in pixels -- what a
/// area resolves against, and so what a container divides among its /// fraction of its area resolves against, and so what a container divides
/// children. Its own box is a part of this one. /// among its children. Its own box is a part of this one.
pub fn frame_px_len(&mut self, axis: Axis) -> Px { pub fn region_px_len(&mut self, axis: Axis) -> Px {
let len = self.px.axis(axis); let len = self.px.axis(axis);
let own = &mut self.own[axis as usize]; let own = &mut self.own[axis as usize];
if *own == Holds::ANY { if *own == Holds::ANY {
@@ -436,14 +614,14 @@ impl<'a> Painter<'a> {
len len
} }
/// [`Self::holds`] stated about the frame rather than about this /// [`Self::holds`] stated about the region rather than about this
/// widget's own box, for a container whose drawing turns on what its /// widget's own box, for a container whose drawing turns on the box it
/// fractions are of rather than on the part of it it took. /// was given rather than on the part of it it took.
pub fn frame_holds(&mut self, axis: Axis, holds: impl Into<Holds>) { pub fn region_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let holds = holds.into(); let holds = holds.into();
debug_assert!( debug_assert!(
holds.contains(self.px.axis(axis)), holds.contains(self.px.axis(axis)),
"'{}' ({:?}) says its drawing holds for lengths that leave out its frame", "'{}' ({:?}) says its drawing holds for lengths that leave out its region",
self.label(), self.label(),
self.id self.id
); );
@@ -535,57 +713,6 @@ 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<LayoutLen>; 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].part(), declared[n]) {
// Its box is this widget's own, or a part of it in that box's
// own lengths: 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 wherever the part is
// the whole of it, and pins the same way.
(Part::All, None) => {
result.extent[n] = holds.extent[n];
result.extent_len[n] = holds.extent_len[n];
}
// Its box is a part of this widget's own box, in that box's own
// lengths, so what it holds for maps back through that part into
// a range on this widget's box.
(Part::Of(span), None) => {
result.extent[n] = holds.extent[n].through(span.len());
}
// 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 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 /// 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 /// one. Pixels come through untouched, being that many pixels wherever they
@@ -632,82 +759,68 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: b
reported.leftover != Weight::ZERO || declared.is_some() || decided reported.leftover != Weight::ZERO || declared.is_some() || decided
} }
/// Where a widget's drawing goes inside the part its parent gave it: what /// What of the box it was given a widget's drawing occupies, as lengths of
/// it reported, on the side of the part its alignment says, and the whole /// that box: the size it reported wherever that is a part to be placed, and
/// part wherever the answer fills it. /// the whole of the box wherever the answer fills it.
/// ///
/// The length it reported is a length of its frame, and the part is one too, /// A reported fraction is a fraction of the box the widget drew in, where a
/// so this takes one from the other rather than composing it into the part. /// declared one is a fraction of the box its parent handed down -- a span
/// That is what makes a fraction the same fraction wherever the part it is /// reporting `rel(1.0)` means all of what it was given, whatever that was a
/// placed in sits and however long it is -- the fraction is resolved once, /// fraction of. So this is a length of the box rather than a length composed
/// here, against the frame it was reported of. /// into it, and a box in pixels is this step from the given box's pixels.
pub(crate) fn placed_extent( pub(crate) fn placed_lens(
part: UiRegion,
size: Size, size: Size,
declared: [Option<LayoutLen>; 2], declared: [Option<LayoutLen>; 2],
fill: [bool; 2], decided: [bool; 2],
align: RegionAlign, ) -> UiVec2 {
) -> UiRegion { let mut lens = UiVec2::FULL_SIZE;
let mut placed = part; for (axis, (declared, decided)) in AXES.into_iter().zip(declared.into_iter().zip(decided)) {
for axis in AXES {
let n = axis as usize;
let reported = size.axis(axis); let reported = size.axis(axis);
if fills(reported, declared[n], fill[n]) { 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;
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 {
continue; continue;
} }
let len = Len::from_parts(reported.rel, reported.px);
let span = placed.axis_mut(axis); 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.start += (span.len() - len).scale(align.axis(axis).rel());
span.end = span.start + len; span.end = span.start + len;
} }
placed placed
} }
/// The part of a widget's extent a `place` names, in the coordinates its /// A declared axis gets a frame of that length, aligned within the parent's
/// extent is in: a span is measured in frame lengths from where the extent /// slot (or the offer). Undeclared axes keep the offered frame and chosen
/// starts, so nothing under it depends on where that is, and an extent that /// placement, so their reported fractions retain that reference.
/// moved re-places every child by re-adding its start. pub(crate) fn ask_box(
pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion { mut region: UiRegion,
let mut part = extent; declared: [Option<LayoutLen>; 2],
for axis in AXES {
*part.axis_mut(axis) = place[axis as usize].part().of(*extent.axis(axis));
}
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<Len>; 2],
align: RegionAlign, align: RegionAlign,
) -> (UiRegion, UiRegion) { placement: [Option<UiSpan>; 2],
let mut extent = part; ) -> (UiRegion, [Option<UiSpan>; 2]) {
for (axis, narrow) in AXES.into_iter().zip(narrow) { let mut placed = [None; 2];
let span = frame.axis_mut(axis); for (axis, (len, chosen)) in AXES.into_iter().zip(declared.into_iter().zip(placement)) {
let narrowed = match narrow { let Some(len) = len else {
Some(len) => { placed[axis as usize] = chosen;
let slot = part.axis(axis); continue;
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 { let span = region.axis_mut(axis);
*extent.axis_mut(axis) = UiSpan::FULL; 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;
} }
} (region, placed)
(frame, extent)
} }
-61
View File
@@ -1,61 +0,0 @@
use crate::{PrimitiveHandle, UiRegion, UiSpan};
/// What of a widget's own box a child is given, along one axis.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Part {
/// The whole of it.
All,
/// Frame lengths from where the box starts, which is what a container
/// dividing room among its children speaks: a child's report is a length
/// of the frame, so the cursor that sums those reports is one too. A
/// moved box re-places every child by re-adding its start, exactly.
From(UiSpan),
/// A part of the box in its own coordinates, which is what a container
/// that insets one speaks: taking eleven pixels off the end needs no
/// length, where saying the same thing in frame lengths would make the
/// container read its own box -- and a box chosen from its own answer
/// then feeds back into the answer.
Of(UiSpan),
}
impl Part {
/// Where it lands in the coordinates `extent` is in.
pub(crate) fn of(self, extent: UiSpan) -> UiSpan {
match self {
Self::All => extent,
Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end),
Self::Of(span) => span.within(&extent),
}
}
}
/// Where a child goes along one axis, as a part of this widget's box.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Place {
/// The child's answer, aligned inside the part by the child's alignment.
Within(Part),
/// Exactly the part; the answer is not placed inside it again.
Fill(Part),
}
impl Place {
pub(crate) fn part(self) -> Part {
match self {
Self::Within(part) | Self::Fill(part) => part,
}
}
/// 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 own box's
/// coordinates, which is what a move of that box re-composes from.
#[derive(Debug)]
pub struct RetainedPrimitive {
pub handle: PrimitiveHandle,
pub region: UiRegion,
}
+323 -331
View File
@@ -1,9 +1,9 @@
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
use crate::ui::painter::{declared_lens, frame_and_extent, part_of, placed_extent}; use crate::ui::painter::{ask_box, declared_lens, placed_box, placed_lens};
use crate::{ use crate::{
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, MaskIdx, MoveIdx, Moves,
Moves, Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight, Painter, PixelRegion, PxVec2, Size, StrongWidget, UiRegion, UiRsc, UiSpan, UiVec2, Weight,
WidgetId, Widgets, WidgetId, Widgets,
util::{HashMap, Vec2}, util::{HashMap, Vec2},
}; };
@@ -11,7 +11,7 @@ use crate::{
const AXES: [Axis; 2] = [Axis::X, Axis::Y]; const AXES: [Axis; 2] = [Axis::X, Axis::Y];
/// Where a widget is drawn: what its parent decides about the draw besides /// Where a widget is drawn: what its parent decides about the draw besides
/// the boxes themselves. /// the box.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(super) struct DrawInfo { pub(super) struct DrawInfo {
pub layer: usize, pub layer: usize,
@@ -20,48 +20,38 @@ pub(super) struct DrawInfo {
pub parent_move: MoveIdx, pub parent_move: MoveIdx,
pub region_node: bool, pub region_node: bool,
pub mask: MaskIdx, pub mask: MaskIdx,
/// The frame in the parent widget's frame coordinates, before /// The frame in the parent widget's coordinates, before composition.
/// composition. Its length is the same on every ask of the widget. pub given_region: UiRegion,
pub frame: UiRegion, /// The original offer's lengths relative to the parent's own offer.
/// That frame composed into `parent_move`'s coordinates, which is what pub offer_len: UiVec2,
/// the widget's own drawing is written within. /// The offer's frame in the parent widget's coordinates.
pub frame_abs: UiRegion, pub offer_region: UiRegion,
/// The box the drawing is given, in the frame's own coordinates: the pub offer_placement: [Option<UiSpan>; 2],
/// part of the parent's own box that `place` names, before the widget's /// This ask's box in pixels, and the offer's: one multiply from the
/// answer is placed inside it. /// parent's own, which is where every pixel length in layout comes from.
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 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<UiSpan>; 2],
} }
impl DrawInfo { impl DrawInfo {
/// The axes where the part is the drawing's box outright, which are the /// The axes the parent chose the placement on, which are the axes the
/// axes the answer is not placed inside it again. /// answer is not placed inside its region again.
fn fill(&self) -> [bool; 2] { fn decided(&self) -> [bool; 2] {
self.place.map(Place::fills) self.placement.map(|span| span.is_some())
} }
}
/// What a widget's children are placed in: its own box, the coordinates its /// The placement to draw in before the answer is known: what the parent
/// drawing is in, and what else one ask of a child is decided from. /// chose, and the whole region on any axis it left open.
struct Placing { fn offered_placement(&self) -> UiRegion {
id: WidgetId, UiRegion {
extent: UiRegion, x: self.placement[0].unwrap_or(UiSpan::FULL),
/// The widget's frame in the coordinates its children compose within: y: self.placement[1].unwrap_or(UiSpan::FULL),
/// `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 { pub struct UiRenderState {
@@ -123,11 +113,8 @@ impl UiRenderState {
self.resized = true; self.resized = true;
let Some(root) = self.old_root else { return }; let Some(root) = self.old_root else { return };
let stands = self.active.get(&root).is_some_and(|active| { let stands = self.active.get(&root).is_some_and(|active| {
let px = active.frame.size().to_px(size); let px = active.given_region.size().to_px(size);
// Nothing above the root chose anything, so the box it was first active.answers_at(px) && active.holds.contains(px, active.placement)
// 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 { if !stands {
widgets.needs_redraw.insert(root); widgets.needs_redraw.insert(root);
@@ -148,13 +135,13 @@ impl UiRenderState {
parent_move: MoveIdx::NONE, parent_move: MoveIdx::NONE,
region_node: false, region_node: false,
mask: MaskIdx::NONE, mask: MaskIdx::NONE,
frame: region, given_region: region,
frame_abs: region, offer_region: region,
part: UiRegion::FULL, offer_len: UiVec2::FULL_SIZE,
place: [Place::Within(Part::All); 2], offer_placement: [None; 2],
offer_place: [Place::Within(Part::All); 2],
offer: true,
px, px,
offered_px: px,
placement: [None; 2],
} }
} }
@@ -202,21 +189,16 @@ impl UiRenderState {
if let Some(id) = root { if let Some(id) = root {
let region = Self::root_region(id.id(), rsc.widgets()); let region = Self::root_region(id.id(), rsc.widgets());
let info = self.root_info(region); let info = self.root_info(region);
self.draw_inner(id.id(), info, None, rsc); self.draw_inner(id.id(), region, info, None, false, 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 { fn root_region(id: WidgetId, widgets: &Widgets) -> UiRegion {
let declared = declared_lens(widgets, id); ask_box(
let narrow =
AXES.map(|axis| declared[axis as usize].map(|len| Len::from_parts(len.rel, len.px)));
frame_and_extent(
UiRegion::FULL, UiRegion::FULL,
UiRegion::FULL, declared_lens(widgets, id),
narrow,
widgets.alignment(id), widgets.alignment(id),
[None; 2],
) )
.0 .0
} }
@@ -224,15 +206,16 @@ impl UiRenderState {
pub(super) fn draw_inner( pub(super) fn draw_inner(
&mut self, &mut self,
id: WidgetId, id: WidgetId,
region: UiRegion,
info: DrawInfo, info: DrawInfo,
mut old: Option<ActiveData>, mut old: Option<ActiveData>,
measuring: bool,
rsc: &mut dyn UiRsc, rsc: &mut dyn UiRsc,
) -> (Size, LayoutHolds, LayoutHolds) { ) -> (Size, LayoutHolds, LayoutHolds) {
let (frame, part) = (info.frame_abs, info.part);
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
{ {
diag::bump(Counter::DrawRequests); diag::bump(Counter::DrawRequests);
diag::draw_request(id, info.parent, frame, info.px, info.region_node); diag::draw_request(id, info.parent, region, info.px, info.region_node);
} }
let align = rsc.widgets().alignment(id); let align = rsc.widgets().alignment(id);
// Nothing this widget measured can be dirty while it draws: layout is // Nothing this widget measured can be dirty while it draws: layout is
@@ -242,65 +225,65 @@ impl UiRenderState {
let retained = match stale { let retained = match stale {
true => None, true => None,
false => self false => self
.retained_answer(id, part, info) .retained_answer(id, info)
.or_else(|| self.try_reuse(id, frame, part, info, rsc)), .or_else(|| self.try_reuse(id, region, info.offered_placement(), info, rsc)),
}; };
let answer = retained.unwrap_or_else(|| { let answer = retained.unwrap_or_else(|| {
if old.is_none() { if old.is_none() {
old = self.remove(id, false, rsc); old = self.remove(id, false, rsc);
} }
self.draw_at(id, part, info, old.take(), rsc) self.draw_at(id, region, info.offered_placement(), info, old.take(), rsc)
}); });
// Where the drawing goes, in the frame's own coordinates: the part // Where the drawing goes, in the region's own coordinates: what the
// its parent gave it, with the answer placed inside that part on any // parent chose, and on any axis it left open, what the answer took of
// axis the parent left open. The frame itself does not change, so // the region placed by the widget's alignment. The region itself does
// nothing under it resolves a fraction a second time. // not change, so nothing under it resolves a fraction a second time.
// let placement = if measuring {
// From the answer it gave when its parent first asked, and not from info.offered_placement()
// what a placing evaluation reported: a drawing made in the box that } else {
// answer chose is answering a different question, and placing it by let declared = declared_lens(rsc.widgets(), id);
// that would move the box out from under itself. let lens = placed_lens(answer.0, declared, info.decided());
let measured = match info.offer { let own = placed_box(UiRegion::FULL, lens, align);
true => answer.0, UiRegion {
false => self.active[&id].answer.map_or(answer.0, |(size, _)| size), x: info.placement[0].unwrap_or(own.x),
y: info.placement[1].unwrap_or(own.y),
}
}; };
let extent = placed_extent( self.place(id, region, placement, info, rsc);
part,
measured,
declared_lens(rsc.widgets(), id),
info.fill(),
align,
);
self.place(id, extent, info, rsc);
// On axes the parent filled, measurement and drawing share an extent. // On axes chosen by the parent, measurement and drawing share an
// Otherwise the answer fixes the final extent as a function of the // extent. Otherwise the answer fixes the final extent as a function
// frame, so pull that drawing's validity back through it. // of the frame, so pull that drawing's validity back through it.
let drawing_holds = self.active[&id].holds; let drawing_holds = self.active[&id].holds;
let mut settled = answer; let mut settled = answer;
for axis in AXES { for axis in AXES {
let n = axis as usize; let n = axis as usize;
settled.1.frame[n] = settled.1.frame[n].and(drawing_holds.frame[n]); settled.1.frame[n] = settled.1.frame[n].and(drawing_holds.frame[n]);
if info.fill()[n] { if info.placement[n].is_some() {
settled.1.extent[n] = settled.1.extent[n].and(drawing_holds.extent[n]); settled.1.extent[n] = settled.1.extent[n].and(drawing_holds.extent[n]);
} else { } else {
settled.1.frame[n] = settled.1.frame[n] settled.1.frame[n] = settled.1.frame[n]
.and(drawing_holds.extent[n].through(extent.axis(axis).len())); .and(drawing_holds.extent[n].through(placement.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(); let active = self.active.get_mut(&id).unwrap();
// Whoever asked owns how the boxes were reached: the frame it stated, // Whoever asked owns how the box was reached: the box it stated, and
// and what of its own box it gave the drawing. A local redraw asks // what of that box the answer then took. A local redraw asks the
// the same question again from these. // same question again from these.
active.frame_abs = frame; active.region = region;
active.frame = info.frame; active.given_region = info.given_region;
if info.offer { active.offer_region = info.offer_region;
active.offer_len = info.offer_len;
if info.placement == info.offer_placement && info.px == info.offered_px {
active.answer = Some(answer); active.answer = Some(answer);
active.offer_place = info.offer_place; active.offer_placement = info.offer_placement;
active.offer_part = part;
} }
active.place = info.place; active.decided = info.decided();
active.own_align = align; active.own_align = align;
// A subtree can be reused whole under a different parent -- same box, // A subtree can be reused whole under a different parent -- same box,
// same layer, same region node -- and nothing in the drawing says it // same layer, same region node -- and nothing in the drawing says it
@@ -313,61 +296,71 @@ impl UiRenderState {
&& let Some(old_parent) = self.active.get_mut(&old_parent) && let Some(old_parent) = self.active.get_mut(&old_parent)
{ {
old_parent.children.retain(|child| *child != id); old_parent.children.retain(|child| *child != id);
old_parent.extent_children.retain(|(child, _)| *child != id);
} }
(answer.0, answer.1, settled.1) (answer.0, answer.1, settled.1)
} }
/// Recompose retained geometry when the evaluation still holds at this extent. /// Recompose retained geometry when the evaluation still holds at this extent.
fn place(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) { fn place(
if self &mut self,
.try_reuse(id, info.frame_abs, extent, info, rsc) id: WidgetId,
.is_some() region: UiRegion,
{ placement: UiRegion,
info: DrawInfo,
rsc: &mut dyn UiRsc,
) {
if self.try_reuse(id, region, placement, info, rsc).is_some() {
return; return;
} }
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::PlaceRedraws); diag::bump(Counter::PlaceRedraws);
let old = self.remove(id, false, rsc); let old = self.remove(id, false, rsc);
self.draw_at(id, extent, info, old, rsc); self.draw_at(id, region, placement, info, old, rsc);
} }
/// Calls a widget's `draw` and keeps what it drew in `extent` of `frame`. /// Calls a widget's `draw` and keeps what it drew in `region`.
fn draw_at( fn draw_at(
&mut self, &mut self,
id: WidgetId, id: WidgetId,
extent: UiRegion, region: UiRegion,
placement: UiRegion,
info: DrawInfo, info: DrawInfo,
old: Option<ActiveData>, old: Option<ActiveData>,
rsc: &mut dyn UiRsc, rsc: &mut dyn UiRsc,
) -> (Size, LayoutHolds) { ) -> (Size, LayoutHolds) {
let frame = info.frame_abs;
let (move_idx, local, retired_move) = match info.region_node { let (move_idx, local, retired_move) = match info.region_node {
// Its box becomes its movable region, so it draws in that // Its box becomes its movable region, so it draws in that
// region's coordinates and its box is one entry to rewrite. // region's coordinates and its box is one entry to rewrite.
true => ( true => (
self.move_slot(id, info.parent_move, frame), self.move_slot(id, info.parent_move, region),
UiRegion::FULL, UiRegion::FULL,
None, None,
), ),
// Keep the old entry alive until every descendant has migrated. // Keep the old entry alive until every descendant has migrated.
// Reusing its index sooner could make an old parent look current. // Reusing its index sooner could make an old parent look current.
false => (info.parent_move, frame, self.slots.remove(&id)), false => (info.parent_move, region, self.slots.remove(&id)),
}; };
let (old_children, old_answer, old_offer_part) = match old { let (old_children, old_answer) = match old {
Some(old) => (old.children, old.answer, Some(old.offer_part)), Some(old) => (old.children, old.answer),
None => (Vec::new(), None, None), None => (Vec::new(), None),
}; };
rsc.widgets_mut().needs_redraw.remove(&id); rsc.widgets_mut().needs_redraw.remove(&id);
// Only evaluation at the original offer establishes the children's // Only evaluation at the original offer establishes the children's
// offers. A placing evaluation must not overwrite that question. // offers. A placing evaluation must not overwrite that question.
let px = info.px; let px = info.px;
let at_offer = info.offer; 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 mut painter = Painter { let mut painter = Painter {
state: self, state: self,
frame: local, region: local,
extent, placement,
extent_len: [None; 2], reads_placement: false,
px, px,
mask: info.mask, mask: info.mask,
layer: info.layer, layer: info.layer,
@@ -376,8 +369,10 @@ impl UiRenderState {
textures: Vec::new(), textures: Vec::new(),
primitives: Vec::new(), primitives: Vec::new(),
mask_region: None, mask_region: None,
extent_children: Vec::new(),
children: Vec::new(), children: Vec::new(),
offered: Vec::new(), offered: Vec::new(),
offered_px: info.offered_px,
at_offer, at_offer,
size_deps: Vec::new(), size_deps: Vec::new(),
own: [Holds::ANY; 2], own: [Holds::ANY; 2],
@@ -403,18 +398,20 @@ impl UiRenderState {
let Painter { let Painter {
state: _, state: _,
rsc: _, rsc: _,
frame: _, region: _,
extent: _, placement: _,
reads_placement,
px: _, px: _,
mask, mask,
textures, textures,
primitives, primitives,
mask_region, mask_region,
extent_children,
extent_own, extent_own,
extent_len,
answer_under, answer_under,
children, children,
offered: _, offered: _,
offered_px: _,
at_offer: _, at_offer: _,
size_deps, size_deps,
own, own,
@@ -449,6 +446,19 @@ impl UiRenderState {
"'{}' ({id:?}) clips to {px:?} and reports {size}", "'{}' ({id:?}) clips to {px:?} and reports {size}",
rsc.widgets().label(id), 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 { for c in &old_children {
if !children.contains(c) { if !children.contains(c) {
self.undraw_rec(*c, rsc); self.undraw_rec(*c, rsc);
@@ -457,18 +467,6 @@ impl UiRenderState {
if let Some(idx) = retired_move { if let Some(idx) = retired_move {
self.moves.remove(idx); self.moves.remove(idx);
} }
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);
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, // 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 // 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. // it had: a hint is read live, and a drawing is not kept past one.
@@ -483,13 +481,13 @@ impl UiRenderState {
parent_move: move_idx, parent_move: move_idx,
region_node: false, region_node: false,
mask, mask,
frame: UiRegion::FULL, given_region: UiRegion::FULL,
frame_abs: UiRegion::FULL, offer_region: UiRegion::FULL,
part: UiRegion::FULL, offer_len: UiVec2::FULL_SIZE,
place: [Place::Within(Part::All); 2], offer_placement: [None; 2],
offer_place: [Place::Within(Part::All); 2],
offer: false,
px, px,
offered_px: px,
placement: [None; 2],
}, },
rsc, rsc,
); );
@@ -499,12 +497,12 @@ impl UiRenderState {
let active = ActiveData { let active = ActiveData {
id, id,
frame_abs: frame, region,
extent, placement,
frame: info.frame, given_region: info.given_region,
place: info.place, offer_region: info.offer_region,
offer_place: info.offer_place, offer_len: info.offer_len,
offer_part: old_offer_part.unwrap_or(extent), offer_placement: info.offer_placement,
// Whoever asked writes the answer, if this was the asking. // Whoever asked writes the answer, if this was the asking.
answer: old_answer, answer: old_answer,
size, size,
@@ -515,9 +513,11 @@ impl UiRenderState {
textures, textures,
primitives, primitives,
mask_region, mask_region,
extent_children,
children, children,
size_deps, size_deps,
declared: declared_lens(rsc.widgets(), id), declared: declared_lens(rsc.widgets(), id),
decided: info.decided(),
own_align: rsc.widgets().alignment(id), own_align: rsc.widgets().alignment(id),
move_idx, move_idx,
parent_move: info.parent_move, parent_move: info.parent_move,
@@ -550,16 +550,39 @@ 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<UiSpan>; 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 /// 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 /// drawing ended up. Alignment is exactly that case: the first box is the
/// question and the smaller placed box holds the drawing. Whether 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. /// answer is stale at all is its caller's question, asked once there.
fn retained_answer( fn retained_answer(&self, id: WidgetId, info: DrawInfo) -> Option<(Size, LayoutHolds)> {
&self,
id: WidgetId,
part: UiRegion,
info: DrawInfo,
) -> Option<(Size, LayoutHolds)> {
let active = self.active.get(&id)?; let active = self.active.get(&id)?;
let has_region_node = active.move_idx != active.parent_move; let has_region_node = active.move_idx != active.parent_move;
if !active.drawn if !active.drawn
@@ -569,26 +592,41 @@ impl UiRenderState {
return None; return None;
} }
let answer = active.answer?; let answer = active.answer?;
answer.1.contains(info.px, part).then_some(answer) answer
.1
.contains(info.px, info.offered_placement())
.then_some(answer)
} }
/// The pixel lengths of a widget's frame, which is what a local redraw /// The pixel lengths of the box a widget was given and of the box it was
/// needs to ask the question its parent asked. /// first asked about, which is what a local redraw needs to ask the
/// question its parent asked.
/// ///
/// It is threaded down from the window a length of a box at a time, and /// Both are threaded down from the window a length of a box at a time,
/// this takes the same steps back up: a widget's frame is a length of its /// and this takes the same steps back up: a widget's box is a length of
/// parent's frame, and that chain has no coordinate frame in it, so a /// the box its parent drew in, and its offer a length of the box its
/// region node cannot break it -- and it lands on the number a cold /// parent was itself offered. Neither chain has a coordinate frame in it,
/// layout computes rather than near it. /// so neither breaks at a region node -- and both land on the numbers a
fn asked_px(&self, id: WidgetId) -> PxVec2 { /// cold layout computes, rather than near them.
fn asked_px(&self, id: WidgetId) -> (PxVec2, PxVec2) {
let active = &self.active[&id]; let active = &self.active[&id];
// Nothing above the root: the window is where a fraction becomes // Nothing above the root: the window is where a fraction becomes
// pixels, which is also the whole of the frame the root is given. // pixels, which is also the whole of the box the root is given.
let parent_px = match active.parent.and_then(|p| self.active.get(&p)) { let (parent_px, parent_offer) = match active.parent.and_then(|p| self.active.get(&p)) {
Some(parent) => self.asked_px(parent.id), Some(parent) => self.asked_px(parent.id),
None => self.output_size, None => (self.output_size, self.output_size),
}; };
active.frame.size().to_px(parent_px) 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)
} }
/// Reuses the actual drawing in a new box if its retained contract holds /// Reuses the actual drawing in a new box if its retained contract holds
@@ -596,8 +634,8 @@ impl UiRenderState {
fn try_reuse( fn try_reuse(
&mut self, &mut self,
id: WidgetId, id: WidgetId,
frame: UiRegion, region: UiRegion,
extent: UiRegion, placement: UiRegion,
info: DrawInfo, info: DrawInfo,
rsc: &mut dyn UiRsc, rsc: &mut dyn UiRsc,
) -> Option<(Size, LayoutHolds)> { ) -> Option<(Size, LayoutHolds)> {
@@ -646,25 +684,26 @@ impl UiRenderState {
} }
return None; return None;
} }
// In pixels, because the frame is a fraction of its parent's and // In pixels, because `region` is a fraction of the box its parent
// that may be what changed -- an unchanged fraction of a box half the // drew in and that box may be what changed -- an unchanged fraction
// size is half the widget. // of a box half the size is half the widget.
if !active.holds.contains(info.px, extent) { if !active.holds.contains(info.px, placement) {
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
{ {
// Which of the three said no, so a frame that redraws more // Which of the three said no, so a frame that redraws more
// than it should says where to look. They overlap: a drawing // than it should says where to look. They overlap: a drawing
// can be outside two of them at once. // can be outside two of them at once.
let holds = active.holds; let holds = active.holds;
for axis in AXES { if holds.placement.is_some_and(|pinned| pinned != placement) {
let n = axis as usize;
if holds.extent_len[n].is_some_and(|pinned| pinned != extent.axis(axis).len()) {
diag::bump(Counter::OutsidePlacement); diag::bump(Counter::OutsidePlacement);
} }
for axis in AXES {
let n = axis as usize;
if !holds.frame[n].contains(info.px.axis(axis)) { if !holds.frame[n].contains(info.px.axis(axis)) {
diag::bump(Counter::OutsideFrame); diag::bump(Counter::OutsideFrame);
} }
if !holds.extent[n].contains(extent.axis(axis).len().to_px(info.px.axis(axis))) if !holds.extent[n]
.contains(placement.axis(axis).len().to_px(info.px.axis(axis)))
{ {
diag::bump(Counter::OutsideExtent); diag::bump(Counter::OutsideExtent);
} }
@@ -674,24 +713,25 @@ impl UiRenderState {
} }
return None; return None;
} }
let extent_moved = active.extent != extent; let extent_moved = active.placement != placement;
let moved = active.frame_abs != frame; let moved = active.region != region;
let (answer, slot) = ((active.size, active.holds), active.move_idx); let (answer, slot) = ((active.size, active.holds), active.move_idx);
if moved { if moved {
if has_region_node { if has_region_node {
self.moves.set(slot, frame); self.moves.set(slot, region);
} else { } else {
self.recompose_subtree(id, frame, info.parent_move, rsc); self.recompose_subtree(id, region, info.parent_move, rsc);
} }
} }
if extent_moved { if extent_moved {
self.reposition(id, frame, extent, info, rsc); self.reposition(id, region, placement, info, rsc);
} }
self.redepth(id, info.depth); self.redepth(id, info.depth);
let active = self.active.get_mut(&id).unwrap(); let active = self.active.get_mut(&id).unwrap();
active.frame_abs = frame; active.region = region;
active.frame = info.frame; active.given_region = info.given_region;
active.place = info.place; active.offer_region = info.offer_region;
active.offer_len = info.offer_len;
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
{ {
match (moved, has_region_node) { match (moved, has_region_node) {
@@ -715,109 +755,67 @@ impl UiRenderState {
Some(answer) Some(answer)
} }
/// 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( fn reposition(
&mut self, &mut self,
id: WidgetId, id: WidgetId,
frame: UiRegion, region: UiRegion,
extent: UiRegion, placement: UiRegion,
info: DrawInfo, info: DrawInfo,
rsc: &mut dyn UiRsc, rsc: &mut dyn UiRsc,
) { ) {
let active = self.active.get_mut(&id).unwrap(); let active = self.active.get_mut(&id).unwrap();
active.frame_abs = frame; active.region = region;
active.extent = extent; active.placement = placement;
let local = if info.region_node { let local = if info.region_node {
UiRegion::FULL UiRegion::FULL
} else { } else {
frame region
}; };
for primitive in &active.primitives { for primitive in &active.primitives {
let handle = &primitive.handle; let handle = &primitive.handle;
*self.layers[handle.layer].region_mut(handle) = *self.layers[handle.layer].region_mut(handle) =
primitive.region.within(&extent).within(&local); primitive.region.resolve(local, placement);
} }
if let Some(mask_region) = active.mask_region { if let Some(mask_region) = active.mask_region {
rsc.ui_mut().masks.get_mut(active.mask).region = rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.resolve(local, placement);
mask_region.within(&extent).within(&local);
} }
let at = Placing { let parent_move = active.move_idx;
id, let mask = active.mask;
extent, let children = active.extent_children.len();
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 { for index in 0..children {
let child = self.active[&id].children[index]; let (child, extent) = self.active[&id].extent_children[index];
self.place_child(child, &at, rsc); 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_region: active.offer_region,
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,
);
} }
} }
@@ -844,30 +842,30 @@ impl UiRenderState {
fn recompose_subtree( fn recompose_subtree(
&mut self, &mut self,
id: WidgetId, id: WidgetId,
frame: UiRegion, region: UiRegion,
parent_move: MoveIdx, parent_move: MoveIdx,
rsc: &mut dyn UiRsc, rsc: &mut dyn UiRsc,
) { ) {
let active = self.active.get_mut(&id).unwrap(); let active = self.active.get_mut(&id).unwrap();
active.frame_abs = frame; active.region = region;
if active.move_idx != parent_move { if active.move_idx != parent_move {
self.moves.set(active.move_idx, frame); self.moves.set(active.move_idx, region);
return; return;
} }
let extent = active.extent;
for primitive in &active.primitives { for primitive in &active.primitives {
let handle = &primitive.handle; let handle = &primitive.handle;
*self.layers[handle.layer].region_mut(handle) = *self.layers[handle.layer].region_mut(handle) =
primitive.region.within(&extent).within(&frame); primitive.region.resolve(region, active.placement);
} }
if let Some(local) = active.mask_region { if let Some(local) = active.mask_region {
rsc.ui_mut().masks.get_mut(active.mask).region = local.within(&extent).within(&frame); rsc.ui_mut().masks.get_mut(active.mask).region =
local.resolve(region, active.placement);
} }
let children = active.children.len(); let children = active.children.len();
for index in 0..children { for index in 0..children {
let child = self.active[&id].children[index]; let child = self.active[&id].children[index];
let local = self.active[&child].frame; let local = self.active[&child].given_region;
self.recompose_subtree(child, local.within(&frame), parent_move, rsc); self.recompose_subtree(child, local.within(&region), parent_move, rsc);
} }
} }
@@ -941,12 +939,12 @@ impl UiRenderState {
id, id,
ActiveData { ActiveData {
id, id,
frame_abs: UiRegion::FULL, region: UiRegion::FULL,
extent: UiRegion::FULL, placement: UiRegion::FULL,
frame: UiRegion::FULL, given_region: UiRegion::FULL,
place: [Place::Within(Part::All); 2], offer_region: UiRegion::FULL,
offer_place: [Place::Within(Part::All); 2], offer_len: UiVec2::FULL_SIZE,
offer_part: UiRegion::FULL, offer_placement: [None; 2],
answer: None, answer: None,
size, size,
holds: LayoutHolds::ANY, holds: LayoutHolds::ANY,
@@ -956,10 +954,12 @@ impl UiRenderState {
textures: Vec::new(), textures: Vec::new(),
primitives: Vec::new(), primitives: Vec::new(),
mask_region: None, mask_region: None,
extent_children: Vec::new(),
children: Vec::new(), children: Vec::new(),
size_deps: Vec::new(), size_deps: Vec::new(),
move_idx: info.parent_move, move_idx: info.parent_move,
declared: [None; 2], declared: [None; 2],
decided: [false; 2],
own_align: rsc.widgets().alignment(id), own_align: rsc.widgets().alignment(id),
parent_move: info.parent_move, parent_move: info.parent_move,
mask: info.mask, mask: info.mask,
@@ -1128,7 +1128,7 @@ impl UiRenderState {
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> { pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
let active = self.active.get(&id.id())?; let active = self.active.get(&id.id())?;
active.drawn.then(|| { active.drawn.then(|| {
let placed = active.extent.within(&active.frame_abs); let placed = active.placement.within(&active.region);
self.moves self.moves
.resolve(active.parent_move, placed) .resolve(active.parent_move, placed)
.to_px(self.output_size) .to_px(self.output_size)
@@ -1175,16 +1175,10 @@ impl UiRenderState {
#[cfg(feature = "layout-diagnostics")] #[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::LocalRedraws); diag::bump(Counter::LocalRedraws);
let old = self.remove(id, false, rsc); let old = self.remove(id, false, rsc);
self.draw_inner(id, info, old, rsc); self.draw_inner(id, region, info, old, false, rsc);
return true; return true;
}; };
let px = self.asked_px(id); let (given_px, offered_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 { let info = DrawInfo {
layer: active.layer, layer: active.layer,
parent: active.parent, parent: active.parent,
@@ -1192,52 +1186,50 @@ impl UiRenderState {
parent_move: active.parent_move, parent_move: active.parent_move,
region_node: rsc.widgets().is_region_node(id), region_node: rsc.widgets().is_region_node(id),
mask: active.parent_mask, mask: active.parent_mask,
frame: active.frame, given_region: active.given_region,
frame_abs: active.frame_abs, offer_region: active.offer_region,
part: Self::re_ask(active, parent_extent, active.place).1, offer_len: active.offer_len,
place: active.place, offer_placement: active.offer_placement,
offer_place: active.offer_place, px: given_px,
offer: false, offered_px,
px, // The same question its parent asked: the axes its parent chose
}; // the placement on, put back where they were.
// The ask that measured it, asked again: the box it was measured in placement: AXES
// as its parent left it, rather than where that ask's place resolves .map(|axis| active.decided[axis as usize].then(|| *active.placement.axis(axis))),
// 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")] #[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::LocalRedraws); 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); let old = self.remove(id, false, rsc);
// The original measurement is refreshed before the assigned slot is // Asked again where its parent asked: the offer's frame, composed
// restored: its lengths may differ even though the frame is // where the given one is, at the offer's lengths and placement. That
// unchanged. // is the question its answer came from, whatever box the parent then
let answer = self.draw_inner(id, offered, old, rsc); // chose from the answer -- which is often a different frame, since a
if info.place != info.offer_place || info.part != offered.part { // span hands its children its own placement across itself. The
self.draw_inner(id, info, None, rsc); // parent draws in its own frame, or in `FULL` where it is a region
// node.
let parent_frame = match self.active.get(&parent) {
Some(p) if p.move_idx == p.parent_move => p.region,
_ => UiRegion::FULL,
};
let offer_frame = match info.offer_region == UiRegion::FULL {
true => parent_frame,
false => info.offer_region.within(&parent_frame),
};
let offered = DrawInfo {
placement: info.offer_placement,
given_region: info.offer_region,
px: offered_px,
..info
};
// Where the given differs from the offer, the first draw is only the
// measurement and the second puts the drawing where the parent did.
let placed_apart =
info.placement != offered.placement || info.px != offered.px || given != offer_frame;
let answer = self.draw_inner(id, offer_frame, offered, old, placed_apart, rsc);
if placed_apart {
self.draw_inner(id, given, info, None, false, rsc);
} }
let active = self.active.get_mut(&id).unwrap(); let active = self.active.get_mut(&id).unwrap();
// A wider contract does not invalidate the guarantee the parent kept. // A wider contract does not invalidate the guarantee the parent kept.
@@ -1248,7 +1240,7 @@ impl UiRenderState {
{ {
active.answer = was_answer; active.answer = was_answer;
} }
if active.holds.covers(was_holds) && was_holds.contains(px, active.extent) { if active.holds.covers(was_holds) && was_holds.contains(given_px, active.placement) {
active.holds = was_holds; active.holds = was_holds;
} }
if active.answer != was_answer || active.holds != was_holds { if active.answer != was_answer || active.holds != was_holds {
+7 -9
View File
@@ -117,18 +117,16 @@ pub struct Branch {
impl Widget for Branch { impl Widget for Branch {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let mut top = UiRegion::FULL;
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut))); top.y.end = top.y.start.offset(Px::from_int(40));
let measured = painter let measured = painter.widget_within(&self.probe, top).len(Axis::X);
.widget_at(&self.probe, UiRegion::FULL, [Place::Within(Part::All), top])
.len(Axis::X);
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y)))); let mut below = UiRegion::FULL;
let place = [Place::Within(Part::All), below]; below.y.start = below.y.start.offset(Px::from_int(40));
match px > Px::from_f32(self.threshold) { match px > Px::from_f32(self.threshold) {
true => painter.widget_at(&self.wide, UiRegion::FULL, place), true => painter.widget_within(&self.wide, below),
false => painter.widget_at(&self.narrow, UiRegion::FULL, place), false => painter.widget_within(&self.narrow, below),
}; };
Size::LEFTOVER Size::LEFTOVER
} }
+1 -1
View File
@@ -6,7 +6,7 @@ pub struct Masked {
impl Widget for Masked { impl Widget for Masked {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
painter.set_mask(UiRegion::FULL); painter.set_mask(DrawRegion::Extent(UiRegion::FULL));
painter.widget(&self.inner); painter.widget(&self.inner);
// What it occupies is its box, on both axes, for the reason `Scroll` // 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 // reports the same: it clips what is inside to that box, so it can
+2 -9
View File
@@ -7,14 +7,7 @@ pub struct Offset {
impl Widget for Offset { impl Widget for Offset {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
// The whole of this widget's box, moved: the frame passes through, so let region = UiRegion::FULL.offset(self.amt);
// what the child declares or reports means the same as it would painter.widget_within(&self.inner, region).size()
// without the offset.
let moved = |len: Len, amt: Len| Place::Within(Part::From(UiSpan::new(amt, len + amt)));
let place = [
moved(painter.extent_len(Axis::X), self.amt.x),
moved(painter.extent_len(Axis::Y), self.amt.y),
];
painter.widget_at(&self.inner, UiRegion::FULL, place).size()
} }
} }
+2 -20
View File
@@ -13,26 +13,8 @@ impl Widget for Pad {
// it; where the box is bigger -- a share of a row, a rule over this // 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 // 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. // edge pinned it to a corner it had not asked for.
// let inside = DrawRegion::Extent(self.padding.region());
// The padding goes around what it pads: the frame passes through, so let inner = painter.widget_within(&self.inner, inside).size();
// the inner's fractions mean what they would without it, and only
// the box it draws in is moved in by the pixels.
// A part of this widget's own box, in that box's own lengths: the
// padding is pixels, which are the same pixels wherever the box
// lands, so nothing here reads how long the box is -- and a box
// chosen from this widget's own answer therefore does not feed back
// into that answer.
let inset = |lead: Px, trail: Px| {
Place::Within(Part::Of(UiSpan::new(
Len::from_parts(Rel::ZERO, lead),
Len::from_parts(Rel::ONE, -trail),
)))
};
let place = [
inset(self.padding.left, self.padding.right),
inset(self.padding.top, self.padding.bottom),
];
let inner = painter.widget_at(&self.inner, UiRegion::FULL, place).size();
Size { Size {
x: LayoutLen { x: LayoutLen {
px: inner.x.px + self.padding.left + self.padding.right, px: inner.x.px + self.padding.left + self.padding.right,
+18 -17
View File
@@ -12,11 +12,12 @@ pub struct Scroll {
impl Widget for Scroll { impl Widget for Scroll {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let container_len = painter.px_len(self.axis); let container_len = painter.px_len(self.axis);
// Measured in the whole viewport, then drawn at the scrolled offset. // Draw in the whole container only when its scrolling-axis length is
// not already known, then draw it at the scrolled offset.
let whole = UiRegion::FULL; let whole = UiRegion::FULL;
let answer_len = painter let own = painter.placement();
.widget_at(&self.inner, whole, [Place::Fill(Part::All); 2]) let answer_len =
.len(self.axis); painter.measure_len(&self.inner, self.axis, whole, [Some(own.x), Some(own.y)]);
let content = answer_len.apply_leftover(); let content = answer_len.apply_leftover();
self.container_len = container_len; self.container_len = container_len;
self.content_len = content.to_px(container_len); self.content_len = content.to_px(container_len);
@@ -45,7 +46,7 @@ impl Widget for Scroll {
// have placed the whole scroll in a box longer than it. // have placed the whole scroll in a box longer than it.
let slack = (self.container_len - self.content_len).max(Px::ZERO); let slack = (self.container_len - self.content_len).max(Px::ZERO);
let anchor = slack.mul(align.rel()); let anchor = slack.mul(align.rel());
let mut content = UiSpan::FULL; let mut region = UiRegion::FULL;
// Content that fills the viewport and has not been scrolled is the // 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 // 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 // its own length in pixels is the same box in another form, and the
@@ -54,19 +55,19 @@ impl Widget for Scroll {
// each part of it. // each part of it.
let moved = anchor != Px::ZERO || self.amt != Px::ZERO; let moved = anchor != Px::ZERO || self.amt != Px::ZERO;
if moved || self.content_len != self.container_len { if moved || self.content_len != self.container_len {
let start = Len::from_parts(Rel::ZERO, anchor - self.amt); let offset = UiVec2::from_axis(
content = UiSpan::new(start, start.offset(self.content_len)); self.axis,
} Len::from_parts(Rel::ZERO, anchor - self.amt),
// The viewport is the inner's frame, so a fraction it declares or Len::ZERO,
// reports is a fraction of what is on screen rather than of the
// 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(Part::From(content)), Place::Fill(Part::All)),
); );
region = region.offset(offset);
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
}
// The viewport is the inner's region, 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)]);
// What it occupies is its box, on both axes: it clips its content to // 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 // 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 // more. The content's length is what it scrolls through, not what it
+32 -27
View File
@@ -10,30 +10,34 @@ pub struct Span {
impl Widget for Span { impl Widget for Span {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let axis = self.dir.axis; let axis = self.dir.axis;
// The row: this span's own box, as a length of the frame its children // The row: this span's own box, as a span of the region it was given.
// are laid out against. Its start is nothing's business -- a slot is // Its children are laid out along it, and what they declare or report
// a length from it -- so what this reads is the length alone. // is a fraction of the region -- the area this span was told it has,
let far = painter.extent_len(axis); // 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));
let along = |from: Len, to: Len| match self.dir.sign { let along = |from: Len, to: Len| match self.dir.sign {
Sign::Pos => UiSpan::new(from, to), Sign::Pos => UiSpan::new(row.start + from, row.start + to),
Sign::Neg => UiSpan::new(far - to, far - from), Sign::Neg => UiSpan::new(row.end - to, row.end - from),
}; };
// Across itself the child sits where its own alignment says, in the let far = row.len();
// whole of the row: a span is what contains its children there, and // A length for every child before their final boxes are chosen: from
// nothing divides that axis. // a hint where one exists, and from drawing otherwise.
let across = Place::Within(Part::All);
// 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 cursor = Len::rel_min();
let mut lens = Vec::with_capacity(self.children.len()); let mut lens = Vec::with_capacity(self.children.len());
for child in &self.children { for child in &self.children {
let room = Place::Within(Part::From(along(cursor, far))); // The whole region is the child's, so `rel(0.5)` is half the area
let len = painter // this span was given whatever else is in it and wherever this
.widget_at(child, UiRegion::FULL, axis.pair(room, across)) // child sits among them. What it is placed in is the room left
.len(axis); // 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);
cursor.px += len.px + self.gap; cursor.px += len.px + self.gap;
cursor.rel += len.rel; cursor.rel += len.rel;
lens.push(len); lens.push(len);
@@ -51,7 +55,7 @@ impl Widget for Span {
); );
// What is left for the shares to divide: the row less everything // What is left for the shares to divide: the row less everything
// fixed, as a length of the frame rather than a number of pixels. // fixed, as a length of the region rather than a number of pixels.
let room = far - Len::from_parts(total.rel, total.px); let room = far - Len::from_parts(total.rel, total.px);
// Whether anything is left over is a question in pixels: `rel(0.5)` // 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` // beside 300 px is full at 600 and overfull at 400. Asked of `room`
@@ -64,12 +68,12 @@ impl Widget for Span {
// exist at all turns on this. // exist at all turns on this.
let mut shares = false; let mut shares = false;
if total.leftover > Weight::ZERO { if total.leftover > Weight::ZERO {
shares = room.to_px(painter.frame_px_len(axis)) > Px::ZERO; shares = room.to_px(painter.region_px_len(axis)) > Px::ZERO;
let holds = match shares { let holds = match shares {
true => Holds::from(Px::STEP..=Px::MAX), true => Holds::from(Px::STEP..=Px::MAX),
false => Holds::from(Px::MIN..=Px::ZERO), false => Holds::from(Px::MIN..=Px::ZERO),
}; };
painter.frame_holds(axis, holds.through(room)); painter.region_holds(axis, holds.through(room));
} }
// Across itself a span is as long as its longest child -- unless a // Across itself a span is as long as its longest child -- unless a
@@ -105,11 +109,12 @@ impl Widget for Span {
fixed.px += len.px; fixed.px += len.px;
fixed.rel += len.rel; fixed.rel += len.rel;
start = shared(fixed, taken, total.leftover, room); start = shared(fixed, taken, total.leftover, room);
// Along the row the span says where the child goes, and that slot // Along the row the span says where the child goes; across it the
// is the drawing's box outright rather than something to place an // child sits where its own alignment says. Its region is the
// answer inside again. // whole of what this span was given either way, which is what its
let slot = Place::Fill(Part::From(along(from, start))); // fractions are of.
let placed = painter.widget_at(child, UiRegion::FULL, axis.pair(slot, across)); let placed =
painter.widget_at(child, region, axis.pair(Some(along(from, start)), None));
if shrinks { if shrinks {
let used = placed.len(!axis); let used = placed.len(!axis);
// Choosing between a fixed and a relative length from the // Choosing between a fixed and a relative length from the
+9 -9
View File
@@ -13,18 +13,17 @@ impl Widget for Stack {
StackSize::Default => None, StackSize::Default => None,
StackSize::Child(i) => Some(i), StackSize::Child(i) => Some(i),
}; };
// Whichever child sizes the stack is given the stack's whole box -- // Whichever child sizes the stack keeps the stack's whole region as
// the stack is the length that child asked for, so placing that // its own -- the stack is the length that child asked for, so taking
// answer inside the box it decided would apply it twice. // the fraction of the stack's box again would take it twice -- and is
// put where the stack itself is put.
let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) { 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 // 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 // rather than a second drawing of it somewhere else: a retained
// drawing belongs to the layer it was made on. // drawing belongs to the layer it was made on.
Some((i, child)) => { Some((i, child)) => {
painter.child_layer_at(i); painter.child_layer_at(i);
painter painter.widget(child).size()
.widget_at(child, UiRegion::FULL, [Place::Fill(Part::All); 2])
.size()
} }
None => Size::LEFTOVER, None => Size::LEFTOVER,
}; };
@@ -33,9 +32,10 @@ impl Widget for Stack {
continue; continue;
} }
painter.child_layer_at(i); painter.child_layer_at(i);
// Every other child is drawn in the stack's own box, and where it // Every other child has the stack's own box for its region, since
// sits in one bigger than itself is its own business. // the stack is what contains it, and where it sits in one bigger
painter.widget(child); // than itself is its own business.
painter.widget_within(child, DrawRegion::Extent(UiRegion::FULL));
} }
size size
} }
+1 -1
View File
@@ -80,7 +80,7 @@ impl TextView {
// hair under that line, and the break made in it is not the break a // hair under that line, and the break made in it is not the break a
// cold layout makes there. // cold layout makes there.
let size = Size::from_px(PxVec2::ceil_from_f32(tex.size)); let size = Size::from_px(PxVec2::ceil_from_f32(tex.size));
painter.glyphs(tex, region); painter.glyphs(tex, DrawRegion::Extent(region));
(region, size) (region, size)
} }
+7 -9
View File
@@ -21,18 +21,16 @@ struct BranchesOnMeasurement {
impl Widget for BranchesOnMeasurement { impl Widget for BranchesOnMeasurement {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40)); let mut top = UiRegion::FULL;
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut))); top.y.end = top.y.start.offset(Px::from_int(40));
let measured = painter let measured = painter.widget_within(&self.probe, top).len(Axis::X);
.widget_at(&self.probe, UiRegion::FULL, [Place::Within(Part::All), top])
.len(Axis::X);
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X)); let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y)))); let mut below = UiRegion::FULL;
let place = [Place::Within(Part::All), below]; below.y.start = below.y.start.offset(Px::from_int(40));
match px > Px::from_f32(self.threshold) { match px > Px::from_f32(self.threshold) {
true => painter.widget_at(&self.wide, UiRegion::FULL, place), true => painter.widget_within(&self.wide, below),
false => painter.widget_at(&self.narrow, UiRegion::FULL, place), false => painter.widget_within(&self.narrow, below),
}; };
Size::LEFTOVER Size::LEFTOVER
} }
+7 -10
View File
@@ -83,12 +83,11 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() {
assert!(crowded > whole_row, "{crowded} against {whole_row}"); assert!(crowded > whole_row, "{crowded} against {whole_row}");
} }
/// The same reading through a pad: padding goes around what it pads and /// The same reading through a pad: its inset is the whole box less the
/// does not narrow what a fraction under it is a fraction of, so half of the /// padding, so half of the inset plus the padding is half the box plus one
/// window plus the padding is what the pad takes and where the next child /// padding, not two.
/// starts.
#[test] #[test]
fn a_pad_puts_its_padding_around_a_fraction_of_the_whole_box() { fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() {
let mut h = Harness::new((400, 100)); let mut h = Harness::new((400, 100));
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc); 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); let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc);
@@ -97,9 +96,8 @@ fn a_pad_puts_its_padding_around_a_fraction_of_the_whole_box() {
// placed inside it by its own alignment, which is not what is under test. // 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))); h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0)));
assert_corners!(h, inner, (10, 10), (210, 90)); assert_corners!(h, padded, (0, 0), (210, 100));
assert_corners!(h, padded, (0, 0), (220, 100)); assert_corners!(h, tail, (210, 0), (310, 100));
assert_corners!(h, tail, (220, 0), (320, 100));
} }
#[test] #[test]
@@ -428,8 +426,7 @@ fn a_row_of_equal_shares_fills_it_exactly() {
/// a step of. Kept in step with `snap_floor` in `prelude.wgsl`. /// a step of. Kept in step with `snap_floor` in `prelude.wgsl`.
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) { fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
let active = &h.render.active[&id]; let active = &h.render.active[&id];
let drawn = active.extent.within(&active.frame_abs); let region = h.render.moves.resolve(active.parent_move, active.region);
let region = h.render.moves.resolve(active.parent_move, drawn);
let dim = h.size().axis(axis); let dim = h.size().axis(axis);
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor(); 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()); let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32());
+88 -96
View File
@@ -213,12 +213,9 @@ struct FromHint {
impl Widget for FromHint { impl Widget for FromHint {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
let len = painter.size_hint(&self.inner, Axis::Y).unwrap(); let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px)); let mut region = UiRegion::FULL;
painter.widget_at( region.y.end = region.y.start.offset(len.px);
&self.inner, painter.widget_within(&self.inner, region);
UiRegion::FULL,
[Place::Within(Part::All), Place::Within(Part::From(top))],
);
Size::LEFTOVER Size::LEFTOVER
} }
} }
@@ -308,51 +305,6 @@ 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)); 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 /// 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 /// 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. /// drawing that holds for one length is drawn again whichever box moved.
@@ -797,6 +749,36 @@ fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
.collect() .collect()
} }
#[test]
fn frame_geometry_and_extent_geometry_keep_their_references() {
struct Both(Rc<Cell<usize>>);
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] #[test]
fn changing_an_inherited_extent_keeps_the_original_measurement_offer() { fn changing_an_inherited_extent_keeps_the_original_measurement_offer() {
fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget<Text>, WeakWidget<Rect>) { fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget<Text>, WeakWidget<Rect>) {
@@ -876,14 +858,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
} }
impl Widget for Frame { impl Widget for Frame {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
painter.widget_at( painter.widget_within(&self.child, self.region);
&self.child,
UiRegion::FULL,
[
Place::Within(Part::From(self.region.x)),
Place::Within(Part::From(self.region.y)),
],
);
Size::LEFTOVER Size::LEFTOVER
} }
} }
@@ -891,7 +866,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
impl Widget for Painted { impl Widget for Painted {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
self.0.set(self.0.get() + 1); self.0.set(self.0.get() + 1);
painter.set_mask(UiRegion::FULL); painter.set_mask(DrawRegion::Extent(UiRegion::FULL));
painter.primitive(RectPrimitive::color(Color::BLUE)); painter.primitive(RectPrimitive::color(Color::BLUE));
Size::LEFTOVER Size::LEFTOVER
} }
@@ -940,6 +915,43 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
} }
} }
#[test]
fn a_span_does_not_place_its_measurement_before_assigning_the_childs_slot() {
struct MeasuredBox(Rc<Cell<usize>>);
impl Widget for MeasuredBox {
fn draw(&mut self, painter: &mut Painter) -> Size {
self.0.set(self.0.get() + 1);
painter.px_size();
painter.primitive(RectPrimitive::color(Color::BLUE));
Size::from((100, 50))
}
}
let mut h = Harness::new((400, 200));
let draws = Rc::new(Cell::new(0));
let leaf = MeasuredBox(draws.clone()).add(&mut h.rsc);
h.set_root((leaf,).span(Dir::RIGHT).width(rel(1.0)).height(rel(1.0)));
assert_eq!(draws.get(), 3);
assert_corners!(h, leaf, (0, 75), (100, 125));
assert_eq!(
primitive_bounds(&h, leaf.id()),
vec![h.region(&leaf.id()).unwrap()]
);
h.frame();
assert_eq!(draws.get(), 3);
h.resize((600, 300));
h.frame();
assert_eq!(draws.get(), 6);
assert_corners!(h, leaf, (0, 125), (100, 175));
assert_eq!(
primitive_bounds(&h, leaf.id()),
vec![h.region(&leaf.id()).unwrap()]
);
}
#[test] #[test]
fn glyph_origins_compose_identically_when_drawn_and_when_retained() { fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
struct Glyphs { struct Glyphs {
@@ -954,7 +966,8 @@ 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.23) + Len::px(-7.125), Len::FULL),
UiSpan::new(Len::rel(0.37) + Len::px(3.25), Len::FULL), UiSpan::new(Len::rel(0.37) + Len::px(3.25), Len::FULL),
); );
painter.glyphs(text, origin); painter.glyphs(text, DrawRegion::Frame(origin));
painter.glyphs(text, DrawRegion::Extent(origin));
Size::LEFTOVER Size::LEFTOVER
} }
} }
@@ -968,10 +981,7 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
self.region, self.region,
[ [Some(self.extent.x), Some(self.extent.y)],
Place::Fill(Part::From(self.extent.x)),
Place::Fill(Part::From(self.extent.y)),
],
); );
Size::LEFTOVER Size::LEFTOVER
} }
@@ -1118,7 +1128,7 @@ fn widening_and_restoring_a_contract_does_not_invalidate_its_reader() {
assert_eq!(leaf_draws.get(), settled + 1); assert_eq!(leaf_draws.get(), settled + 1);
} }
#[test] #[test]
fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() { fn padding_and_stack_frames_follow_the_extent_without_drawing_again() {
struct Observed<W> { struct Observed<W> {
widget: W, widget: W,
draws: Rc<Cell<usize>>, draws: Rc<Cell<usize>>,
@@ -1138,10 +1148,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
painter.widget_at( painter.widget_at(
&self.child, &self.child,
UiRegion::FULL, UiRegion::FULL,
[ [Some(self.extent.x), Some(self.extent.y)],
Place::Fill(Part::From(self.extent.x)),
Place::Fill(Part::From(self.extent.y)),
],
); );
Size::LEFTOVER Size::LEFTOVER
} }
@@ -1172,18 +1179,13 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
h.set_root(root); h.set_root(root);
(root, leaf, fixed, draws) (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 mut warm = Harness::new((403, 211));
let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13)); let (root, leaf, fixed, draws) = plant(&mut warm, UiRegion::FULL);
for start in [0.13, -0.17, 0.31] { for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] {
let extent = at(start); 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 before = draws.get(); let before = draws.get();
warm.rsc[root].extent = extent; warm.rsc[root].extent = extent;
warm.frame(); warm.frame();
@@ -1227,11 +1229,11 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
&self.child, &self.child,
UiRegion::FULL, UiRegion::FULL,
[ [
Place::Fill(Part::From(UiSpan::new( Some(UiSpan::new(
Len::px(self.start), Len::px(self.start),
Len::px(self.start + 200.0), Len::px(self.start + 200.0),
))), )),
Place::Fill(Part::From(UiSpan::FULL)), Some(UiSpan::FULL),
], ],
); );
Size::LEFTOVER Size::LEFTOVER
@@ -1265,14 +1267,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
impl Widget for Container { impl Widget for Container {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
painter painter
.widget_at( .widget_within(&self.child, DrawRegion::Extent(self.region))
&self.child,
UiRegion::FULL,
[
Place::Within(Part::From(self.region.x)),
Place::Within(Part::From(self.region.y)),
],
)
.size() .size()
} }
} }
@@ -1288,10 +1283,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
.widget_at( .widget_at(
&self.child, &self.child,
UiRegion::FULL, UiRegion::FULL,
[ [Some(self.extent.x), Some(self.extent.y)],
Place::Fill(Part::From(self.extent.x)),
Place::Fill(Part::From(self.extent.y)),
],
) )
.size(), .size(),
); );
+1 -1
View File
@@ -71,7 +71,7 @@ fn a_clipping_widget_reporting_more_than_its_box_is_caught() {
impl Widget for Clipper { impl Widget for Clipper {
fn draw(&mut self, painter: &mut Painter) -> Size { fn draw(&mut self, painter: &mut Painter) -> Size {
painter.set_mask(UiRegion::FULL); painter.set_mask(painter.region());
painter.widget(&self.0).size() painter.widget(&self.0).size()
} }
} }