Lay out in a frame that passes through and a box placed in it
A widget is asked in two boxes rather than one. Its frame is what a fraction it declares or reports is a fraction of, and it passes through a span, a stack and a scroll unchanged, so `rel(0.5)` is half the same area however many containers sit between: a frame is narrowed only by what is decided above the widget -- a declared length, the root. Its extent is where the drawing goes, given as a `Place` per axis: a part of the parent's own box, measured in frame lengths from where that box starts, which the child either fills or has its answer placed inside. What that buys is that nothing under a container depends on where the container sits. A container reads `extent_len` for the length it divides and nothing about the start, so moving it re-places its children by re-adding that start and draws nobody again; and a fraction is resolved once, against the frame, rather than once per box it is composed through -- a stack sized by a child that reports `rel(0.5)` no longer takes half of half. `Place` replaces `DrawRegion`, `ExtentPlacement`, `widget_within`, `measure_len`, `region()`, `placement()` and `box_of`. Primitives and masks are written in the widget's own box's coordinates alone, so the drawing has one reference rather than two. The placement pin goes with them: reading the extent's length pins that length symbolically, and pins compose only where a child's box is its parent's own. Placing an answer waits for the end of the parent's draw or for the next ask of that child in it, so a span child is one drawing and one move rather than two moves. `Pad` is transparent: its padding goes around what it pads and its child keeps the outer frame, which is where `Outset` was going anyway. A fraction under a pad is now a fraction of the frame rather than of the inset box. Checked: fmt, clippy with -D warnings, 109 suite tests and 20 core tests in debug, the 11 generated cases, and the shrinker at 400 trees of depth 5 over all fifteen cases -- which still finds seed 108 under `reorder`, where a wrapping text measured in one box and drawn in another settles differently warm than cold. `redraw` therefore keeps the baseline's deferral for a box that is not as long as the one the widget was measured in; the plan's step 6 is not done, and the next commit message or the handoff says why.
This commit is contained in:
1 parent
34cafb6edc
commit
1956be3f3d
19 files changed
+989
-943
No files matched your search
+255
-362
@@ -1,9 +1,9 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, DrawRegion, ExtentPlacement, Holds, LayoutHolds, LayoutLen, Len, Px, PxVec2, RegionAlign,
|
||||
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
Axis, Holds, LayoutHolds, LayoutLen, Len, Place, Px, PxVec2, RegionAlign, RenderedText,
|
||||
RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||
TexturePrimitive,
|
||||
@@ -17,49 +17,47 @@ pub struct Painter<'a> {
|
||||
pub(super) state: &'a mut UiRenderState,
|
||||
pub(super) rsc: &'a mut dyn UiRsc,
|
||||
|
||||
/// The box its parent gave it, in the coordinates of `move_idx`: what a
|
||||
/// fraction of this widget's area is a fraction of, and what every region
|
||||
/// it writes composes within. The same box on the ask that measures and
|
||||
/// the ask that places, which is what keeps a fraction under it from
|
||||
/// being resolved twice.
|
||||
pub(super) region: UiRegion,
|
||||
/// Where this widget's drawing sits inside that box, in the box's own
|
||||
/// coordinates: `FULL` while its answer is not yet known, and the box
|
||||
/// its answer or its parent chose once one of them has.
|
||||
pub(super) placement: UiRegion,
|
||||
/// Whether this draw read its placement, which makes the drawing one
|
||||
/// that holds for that placement alone -- the way reading a length in
|
||||
/// pixels makes it hold for that length.
|
||||
pub(super) reads_placement: bool,
|
||||
/// That box in pixels, which its children's are a length of: threaded
|
||||
/// down from the box this widget was given rather than composed back up
|
||||
/// the chain, so every length in layout is one multiply from its
|
||||
/// parent's and [`Holds::through`] inverts exactly that.
|
||||
/// What a fraction this widget declares or reports is a fraction of, in
|
||||
/// the coordinates of `move_idx`: forwarded from its parent unchanged
|
||||
/// through a span, a stack or a scroll, and narrowed only by what was
|
||||
/// decided above it -- a declared length, an inset, the root. Its length
|
||||
/// is the same on every ask of the widget, which is what keeps a fraction
|
||||
/// under it from being resolved twice.
|
||||
pub(super) frame: UiRegion,
|
||||
/// Where this widget's drawing goes, in the frame's own coordinates.
|
||||
/// Everything it writes is in these coordinates, and its children are
|
||||
/// placed as parts of it.
|
||||
pub(super) extent: UiRegion,
|
||||
/// The extent's symbolic length where this draw read it, which makes the
|
||||
/// drawing one that holds for that length alone -- the way reading a
|
||||
/// length in pixels makes it hold for that number of pixels.
|
||||
pub(super) extent_len: [Option<Len>; 2],
|
||||
/// The frame in pixels, which its children's frames are a length of:
|
||||
/// threaded down rather than composed back up the chain, so every length
|
||||
/// in layout is one multiply from its parent's and [`Holds::through`]
|
||||
/// inverts exactly that.
|
||||
pub(super) px: PxVec2,
|
||||
pub(super) mask: MaskIdx,
|
||||
pub(super) textures: Vec<TextureHandle>,
|
||||
pub(super) primitives: Vec<RetainedPrimitive>,
|
||||
pub(super) mask_region: Option<DrawRegion>,
|
||||
pub(super) extent_children: Vec<(WidgetId, ExtentPlacement)>,
|
||||
pub(super) extent_own: [Holds; 2],
|
||||
pub(super) mask_region: Option<UiRegion>,
|
||||
/// Only children whose answers were read constrain this widget's answer.
|
||||
pub(super) answer_under: LayoutHolds,
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
/// The children asked about so far, so the first box each was asked in
|
||||
/// The children asked about so far, so the first place each was asked in
|
||||
/// is the one recorded as its offer.
|
||||
pub(super) offered: Vec<WidgetId>,
|
||||
/// The lengths of the box this widget was first asked about in, in
|
||||
/// pixels. Its children's offers are a fraction of it.
|
||||
pub(super) offered_px: PxVec2,
|
||||
/// Whether this draw is in a box of those lengths, which makes the
|
||||
/// questions it asks the ones a cold layout asks and their answers the
|
||||
/// ones to keep.
|
||||
/// Whether this draw is at the place its parent first asked about, which
|
||||
/// makes the questions it asks the ones a cold layout asks and their
|
||||
/// answers the ones to keep.
|
||||
pub(super) at_offer: bool,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub(super) size_deps: Vec<WidgetId>,
|
||||
/// What this draw itself read of its box in pixels, per axis: every
|
||||
/// What this draw itself read of its frame in pixels, per axis: every
|
||||
/// length until it reads one, then that one, unless it says otherwise.
|
||||
pub(super) own: [Holds; 2],
|
||||
/// The same for its extent.
|
||||
pub(super) extent_own: [Holds; 2],
|
||||
/// Dependencies of every child drawing, including unmeasured overlays.
|
||||
pub(super) under: LayoutHolds,
|
||||
/// The movable region this widget's primitives are positioned through:
|
||||
@@ -74,26 +72,29 @@ pub struct Painter<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Painter<'a> {
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: DrawRegion) {
|
||||
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||
let kind = self.rsc.ui_mut().primitives.kind::<P>();
|
||||
self.write(kind, primitive, region);
|
||||
}
|
||||
|
||||
/// Takes the kind, for a caller writing many of one primitive.
|
||||
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: DrawRegion) {
|
||||
self.write_resolved(
|
||||
kind,
|
||||
primitive,
|
||||
region,
|
||||
region.resolve(self.region, self.placement),
|
||||
);
|
||||
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
|
||||
self.write_resolved(kind, primitive, region, self.resolve(region));
|
||||
}
|
||||
|
||||
/// A box in this widget's extent coordinates, composed into the
|
||||
/// coordinates its move slot is in: through the extent, then through the
|
||||
/// frame the extent is a part of. The same two steps a recomposition
|
||||
/// replays, so a moved drawing lands where a cold one does.
|
||||
fn resolve(&self, region: UiRegion) -> UiRegion {
|
||||
region.within(&self.extent).within(&self.frame)
|
||||
}
|
||||
|
||||
fn write_resolved<P: Primitive>(
|
||||
&mut self,
|
||||
kind: PrimitiveKind<P>,
|
||||
primitive: P,
|
||||
region: DrawRegion,
|
||||
region: UiRegion,
|
||||
resolved: UiRegion,
|
||||
) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -122,135 +123,70 @@ impl<'a> Painter<'a> {
|
||||
|
||||
/// Writes a primitive over the whole of this widget's own box.
|
||||
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
||||
let at = DrawRegion::Extent(UiRegion::FULL);
|
||||
let primitive = primitive.into_primitive(self);
|
||||
self.primitive_at(primitive, at)
|
||||
self.primitive_at(primitive, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Writes in the frame by default. `DrawRegion::Extent` keeps the local
|
||||
/// geometry attached to this widget's box without reading its placement.
|
||||
pub fn primitive_within(
|
||||
&mut self,
|
||||
primitive: impl PrimitiveLike,
|
||||
region: impl Into<DrawRegion>,
|
||||
) {
|
||||
/// Writes a primitive in a part of this widget's own box, in that box's
|
||||
/// coordinates.
|
||||
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
||||
let primitive = primitive.into_primitive(self);
|
||||
self.primitive_at(primitive, region.into());
|
||||
self.primitive_at(primitive, region);
|
||||
}
|
||||
|
||||
/// Sets a mask in the selected frame or extent coordinates.
|
||||
pub fn set_mask(&mut self, region: impl Into<DrawRegion>) {
|
||||
let region = region.into();
|
||||
/// Sets a mask, in this widget's own box's coordinates.
|
||||
pub fn set_mask(&mut self, region: UiRegion) {
|
||||
self.mask_region = Some(region);
|
||||
assert!(self.mask == MaskIdx::NONE);
|
||||
let resolved = self.resolve(region);
|
||||
let move_idx = self.move_idx;
|
||||
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
||||
region: region.resolve(self.region, self.placement),
|
||||
move_idx: self.move_idx,
|
||||
region: resolved,
|
||||
move_idx,
|
||||
});
|
||||
}
|
||||
|
||||
/// Draws a widget in the whole of this widget's own box: it gets the
|
||||
/// same region -- the same area for its fractions to be of -- and is put
|
||||
/// where this widget was put. What a container that is only a wrapper
|
||||
/// around one child wants, since its box is the child's.
|
||||
/// Draws a widget in the whole of this widget's own box, with the frame
|
||||
/// forwarded unchanged: what a container that is only a wrapper around
|
||||
/// one child wants, and what every transparent container passes for the
|
||||
/// frame.
|
||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||
let own = self.placement;
|
||||
self.widget_at_inner(
|
||||
id,
|
||||
UiRegion::FULL,
|
||||
[Some(own.x), Some(own.y)],
|
||||
Some(ExtentPlacement::Inherit),
|
||||
false,
|
||||
)
|
||||
self.widget_at(id, UiRegion::FULL, [Place::Within(None); 2])
|
||||
}
|
||||
|
||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||
/// it resolves into its box. Reading them depends on nothing -- the box
|
||||
/// that comes of them is kept on the child, and `redraw` compares it
|
||||
/// there.
|
||||
fn declared_lens<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.
|
||||
/// Draws a child, saying what its fractions are of and where its drawing
|
||||
/// goes.
|
||||
///
|
||||
/// `DrawRegion::Extent` gives a part of where this widget's drawing sits
|
||||
/// instead, for a container whose children belong inside that rather than
|
||||
/// inside the box it was offered. The part is what is kept, so moving the
|
||||
/// extent re-places the child rather than drawing this widget again.
|
||||
pub fn widget_within<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<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.
|
||||
/// `frame` is that reference, in this widget's own frame coordinates:
|
||||
/// [`UiRegion::FULL`] forwards this widget's frame, which is what a
|
||||
/// container that only divides room passes, so a fraction under it means
|
||||
/// the same wherever it sits and however deeply it is nested. Narrowing
|
||||
/// it is for what is decided from above -- an inset's margins -- and a
|
||||
/// declared length narrows it here.
|
||||
///
|
||||
/// `region` is the child's own area: what a fraction it declares or
|
||||
/// reports is a fraction of, and the coordinates the regions it writes
|
||||
/// compose within. It is the same box on the ask that measures and the
|
||||
/// ask that places, which is what stops a fraction under it being
|
||||
/// resolved twice.
|
||||
///
|
||||
/// `placement` is what of that region the child's drawing takes, per
|
||||
/// axis, wherever this widget is choosing. `None` leaves the axis to the
|
||||
/// child's own answer and alignment, which is what
|
||||
/// [`Self::widget_within`] passes. A span passes the whole row as the
|
||||
/// region, so `rel(0.5)` is half the row wherever the child sits in it,
|
||||
/// and places the child by passing the slot along its axis.
|
||||
/// `place` is where the drawing goes, per axis, as a part of this
|
||||
/// widget's extent: see [`Place`]. A narrowed frame is its own extent,
|
||||
/// since the narrowing is what said where the drawing goes.
|
||||
pub fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
placement: [Option<UiSpan>; 2],
|
||||
frame: UiRegion,
|
||||
place: [Place; 2],
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at_inner(id, region, placement, None, false)
|
||||
}
|
||||
|
||||
fn widget_at_inner<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<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 declared = self.declared_lens(id);
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
let (local, placement) = ask_box(region, declared, align, placement);
|
||||
let narrow = AXES.map(|axis| {
|
||||
let n = axis as usize;
|
||||
// A rule's fraction is a fraction of the frame the child was
|
||||
// given, which is the one length the rule can mean.
|
||||
declared[n]
|
||||
.map(|len| Len::from_parts(len.rel, len.px).within_len(frame.axis(axis).len()))
|
||||
});
|
||||
let (local, extent) = frame_and_extent(frame, part_of(self.extent, place), narrow, align);
|
||||
let within = match local == UiRegion::FULL {
|
||||
true => self.region,
|
||||
false => local.within(&self.region),
|
||||
true => self.frame,
|
||||
false => local.within(&self.frame),
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if region_node {
|
||||
@@ -262,31 +198,20 @@ impl<'a> Painter<'a> {
|
||||
self.children.push(id.id());
|
||||
}
|
||||
let first_ask = self.offer(id.id());
|
||||
let given_len = local.size();
|
||||
let offer_len = match first_ask {
|
||||
true => given_len,
|
||||
false => self
|
||||
.state
|
||||
.active
|
||||
.get(&id.id())
|
||||
.map_or(given_len, |a| a.offer_len),
|
||||
};
|
||||
let offer_placement = if first_ask {
|
||||
placement
|
||||
let offer_place = if first_ask {
|
||||
place
|
||||
} else {
|
||||
self.state
|
||||
.active
|
||||
.get(&id.id())
|
||||
.map_or(placement, |a| a.offer_placement)
|
||||
.map_or(place, |a| a.offer_place)
|
||||
};
|
||||
let px = given_len.to_px(self.px);
|
||||
let offered_px = offer_len.to_px(self.offered_px);
|
||||
// The answer and what it holds for, both about the box asked in. The
|
||||
// child's record may say something else once its drawing has been
|
||||
let px = local.size().to_px(self.px);
|
||||
// The answer and what it holds for, both about the place asked in.
|
||||
// The child's record may say something else once its drawing has been
|
||||
// placed: a drawing made again in its placed box holds for that box.
|
||||
let (size, answer_holds, holds) = self.state.draw_inner(
|
||||
id.id(),
|
||||
within,
|
||||
DrawInfo {
|
||||
layer: self.layer,
|
||||
parent: Some(self.id),
|
||||
@@ -294,76 +219,46 @@ impl<'a> Painter<'a> {
|
||||
parent_move: self.move_idx,
|
||||
region_node,
|
||||
mask: self.mask,
|
||||
given_region: local,
|
||||
offer_len,
|
||||
offer_placement,
|
||||
frame: local,
|
||||
frame_abs: within,
|
||||
part: extent,
|
||||
place,
|
||||
offer_place,
|
||||
// The question its parent measured it by, asked again: the
|
||||
// same box of the same widget, however this draw came about.
|
||||
offer: place == offer_place,
|
||||
px,
|
||||
offered_px,
|
||||
placement,
|
||||
},
|
||||
None,
|
||||
measuring,
|
||||
false,
|
||||
self.rsc,
|
||||
);
|
||||
let in_parent = |holds: LayoutHolds| {
|
||||
let mut result = LayoutHolds::ANY;
|
||||
for axis in AXES {
|
||||
let n = axis as usize;
|
||||
let chosen = placement[n].unwrap_or(UiSpan::FULL).len();
|
||||
match extent {
|
||||
// Its box is this widget's own, so what its drawing holds
|
||||
// for is what this widget's extent holds for.
|
||||
Some(ExtentPlacement::Inherit) if declared[n].is_none() => {
|
||||
result.frame[n] = holds.frame[n].through(local.axis(axis).len());
|
||||
result.extent[n] = holds.extent[n];
|
||||
if holds.placement.is_some() {
|
||||
result.placement = Some(self.placement);
|
||||
}
|
||||
}
|
||||
// Its box is a part of this widget's extent, so what it
|
||||
// holds for is a range on that extent and none of it a
|
||||
// range on the frame. Only the part's length reaches it,
|
||||
// which is what lets the extent move without a redraw.
|
||||
Some(ExtentPlacement::Within(part)) if declared[n].is_none() => {
|
||||
result.extent[n] = holds.frame[n]
|
||||
.and(holds.extent[n].through(chosen))
|
||||
.through(part.axis(axis).len());
|
||||
}
|
||||
// Its box is a length of this widget's frame: an
|
||||
// ordinary ask, or a declared length, which is that
|
||||
// length wherever the box it sits in came from.
|
||||
_ => {
|
||||
result.frame[n] = holds.frame[n].through(local.axis(axis).len()).and(
|
||||
holds.extent[n]
|
||||
.through(chosen)
|
||||
.through(local.axis(axis).len()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
};
|
||||
self.under = self.under.and(in_parent(holds));
|
||||
let mut answer_holds = in_parent(answer_holds);
|
||||
// What it reports is a fraction of the box it was given, which is a
|
||||
// part of this widget's extent -- so the same fraction is a different
|
||||
// length once that extent is, and pixels are not. The answer only:
|
||||
// the drawing this holds is re-placed rather than made again.
|
||||
if matches!(extent, Some(ExtentPlacement::Within(_)))
|
||||
&& AXES.into_iter().any(|axis| {
|
||||
declared[axis as usize].is_none() && size.axis(axis).rel != crate::Rel::ZERO
|
||||
})
|
||||
{
|
||||
answer_holds.placement = Some(self.placement);
|
||||
}
|
||||
let compose = |holds| in_parent(holds, local, extent, place, declared);
|
||||
self.under = self.under.and(compose(holds));
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size: in_parent_frame(size, local.size(), declared),
|
||||
answer_holds,
|
||||
answer_holds: compose(answer_holds),
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes back a child that was drawn only to find out how long it is.
|
||||
/// Its drawing is dropped and it is not one of this widget's children
|
||||
/// this frame; what it answered is still something this widget asked.
|
||||
pub fn undraw<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.
|
||||
/// Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||
@@ -392,57 +287,11 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Measures a child's length from its hint, a retained answer, or `draw`.
|
||||
/// A fresh draw evaluates the offer without placing its answer. The caller
|
||||
/// must later place or undraw the child.
|
||||
pub fn measure_len<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_placement = placement;
|
||||
}
|
||||
let placement = UiRegion {
|
||||
x: placement[0].unwrap_or(UiSpan::FULL),
|
||||
y: placement[1].unwrap_or(UiSpan::FULL),
|
||||
};
|
||||
let holds = holds.in_frame(placement);
|
||||
for (axis, under) in AXES.into_iter().zip(self.answer_under.frame.iter_mut()) {
|
||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||
}
|
||||
in_parent_frame(size, local.size(), declared).axis(axis)
|
||||
}
|
||||
|
||||
/// Whether this is the first box a child is asked about in during a draw
|
||||
/// that is itself in the box it was asked in -- the question a cold
|
||||
/// layout asks, whose answer is the one to keep.
|
||||
/// that is itself the one its parent measured -- the question a cold
|
||||
/// layout asks, whose answer is the one to keep. A drawing made again in
|
||||
/// a box chosen from an answer asks about that box instead, and what it
|
||||
/// hears back is not a measurement of anything.
|
||||
fn offer(&mut self, child: WidgetId) -> bool {
|
||||
if !self.at_offer || self.offered.contains(&child) {
|
||||
return false;
|
||||
@@ -471,11 +320,10 @@ impl<'a> Painter<'a> {
|
||||
|
||||
/// Writes glyphs in the selected frame or extent coordinates.
|
||||
// TODO: merge the text methods into the primitive ones.
|
||||
pub fn glyphs(&mut self, text: &RenderedText, origin: impl Into<DrawRegion>) {
|
||||
let origin = origin.into();
|
||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||
// Glyph offsets and sizes are pixels, which compose additively.
|
||||
// Only the shared origin needs the frame/extent composition.
|
||||
let resolved = origin.resolve(self.region, self.placement);
|
||||
// Only the shared origin needs composing through the extent.
|
||||
let resolved = self.resolve(origin);
|
||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||
for glyph in text.glyphs.iter() {
|
||||
let place = |mut region: UiRegion| {
|
||||
@@ -499,30 +347,20 @@ impl<'a> Painter<'a> {
|
||||
color: text.color,
|
||||
flags: glyph.entry.flags(),
|
||||
},
|
||||
origin.map(place),
|
||||
place(origin),
|
||||
place(resolved),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The box this widget's parent gave it, in the coordinates its own
|
||||
/// primitives are written in -- so a region composed `within` it may be
|
||||
/// drawn directly. Its own box is [`Self::placement`] of this one.
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.region
|
||||
}
|
||||
|
||||
/// Where this widget's drawing goes inside the box it was given, in that
|
||||
/// box's coordinates: what its own answer took of it, or what its parent
|
||||
/// chose for it. `FULL` on the ask that measures, since nothing has been
|
||||
/// placed yet.
|
||||
///
|
||||
/// Reading it is what says the drawing depends on it, so a widget that
|
||||
/// positions its own content reads it and is drawn again once its box is
|
||||
/// known, and one that fills whatever it is given never is.
|
||||
pub fn placement(&mut self) -> UiRegion {
|
||||
self.reads_placement = true;
|
||||
self.placement
|
||||
/// The symbolic length of this widget's own box, in the lengths of its
|
||||
/// frame that it places its children in. Reading it pins the drawing to
|
||||
/// that length -- and to nothing about where the box starts, which is
|
||||
/// what lets a container move without being drawn again.
|
||||
pub fn extent_len(&mut self) -> UiVec2 {
|
||||
let len = self.extent.size();
|
||||
self.extent_len = [Some(len.x), Some(len.y)];
|
||||
len
|
||||
}
|
||||
|
||||
/// Where this widget sits in a box longer than the length it takes. A
|
||||
@@ -550,14 +388,6 @@ impl<'a> Painter<'a> {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// The part of this widget's box that something of `size` takes, at the
|
||||
/// near edge. A container that reports one child's size gives every child
|
||||
/// this, so what it draws is inside what it says it occupies.
|
||||
pub fn box_of(&self, size: Size) -> UiRegion {
|
||||
let lens = placed_lens(size, [None; 2], [false; 2]);
|
||||
placed_box(UiRegion::FULL, lens, RegionAlign::NEAR)
|
||||
}
|
||||
|
||||
/// This widget's own box in pixels. Reading it makes the drawing one
|
||||
/// that holds for this box only, until `holds` says how far it goes.
|
||||
pub fn px_size(&mut self) -> PxVec2 {
|
||||
@@ -567,7 +397,7 @@ impl<'a> Painter<'a> {
|
||||
/// One axis of this widget's own box in pixels. Prefer this to
|
||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||
pub fn px_len(&mut self, axis: Axis) -> Px {
|
||||
let part = self.placement.axis(axis).len();
|
||||
let part = self.extent.axis(axis).len();
|
||||
let len = part.to_px(self.px.axis(axis));
|
||||
let own = &mut self.extent_own[axis as usize];
|
||||
if *own == Holds::ANY {
|
||||
@@ -581,7 +411,7 @@ impl<'a> Painter<'a> {
|
||||
/// of the box, and the same reported size. A widget that read its length
|
||||
/// in pixels holds for that one alone until it says otherwise.
|
||||
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let part = self.placement.axis(axis).len();
|
||||
let part = self.extent.axis(axis).len();
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(part.to_px(self.px.axis(axis))),
|
||||
@@ -592,10 +422,10 @@ impl<'a> Painter<'a> {
|
||||
self.extent_own[axis as usize] = holds;
|
||||
}
|
||||
|
||||
/// One axis of the box this widget's parent gave it, in pixels -- what a
|
||||
/// fraction of its area resolves against, and so what a container divides
|
||||
/// among its children. Its own box is a part of this one.
|
||||
pub fn region_px_len(&mut self, axis: Axis) -> Px {
|
||||
/// One axis of this widget's frame in pixels -- what a fraction of its
|
||||
/// area resolves against, and so what a container divides among its
|
||||
/// children. Its own box is a part of this one.
|
||||
pub fn frame_px_len(&mut self, axis: Axis) -> Px {
|
||||
let len = self.px.axis(axis);
|
||||
let own = &mut self.own[axis as usize];
|
||||
if *own == Holds::ANY {
|
||||
@@ -604,14 +434,14 @@ impl<'a> Painter<'a> {
|
||||
len
|
||||
}
|
||||
|
||||
/// [`Self::holds`] stated about the region rather than about this
|
||||
/// widget's own box, for a container whose drawing turns on the box it
|
||||
/// was given rather than on the part of it it took.
|
||||
pub fn region_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
/// [`Self::holds`] stated about the frame rather than about this
|
||||
/// widget's own box, for a container whose drawing turns on what its
|
||||
/// fractions are of rather than on the part of it it took.
|
||||
pub fn frame_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let holds = holds.into();
|
||||
debug_assert!(
|
||||
holds.contains(self.px.axis(axis)),
|
||||
"'{}' ({:?}) says its drawing holds for lengths that leave out its region",
|
||||
"'{}' ({:?}) says its drawing holds for lengths that leave out its frame",
|
||||
self.label(),
|
||||
self.id
|
||||
);
|
||||
@@ -703,6 +533,52 @@ impl PrimitiveLike for &TextureHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a child depends on, said about the boxes the widget that drew it
|
||||
/// has rather than the ones the child was given.
|
||||
///
|
||||
/// `frame` is the child's frame in this widget's frame coordinates and
|
||||
/// `extent` the box it was given, in the child's own frame coordinates. Both
|
||||
/// reach it as one length, so what it holds for maps back through that
|
||||
/// length exactly -- and where the box it was given is this widget's own,
|
||||
/// what it says about that box is what this widget can say about its own.
|
||||
pub(crate) fn in_parent(
|
||||
holds: LayoutHolds,
|
||||
frame: UiRegion,
|
||||
extent: UiRegion,
|
||||
place: [Place; 2],
|
||||
declared: [Option<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].span(), declared[n]) {
|
||||
// Its box is this widget's own, so what it holds for is a range
|
||||
// on this widget's own box -- which is what lets that box move
|
||||
// without a redraw. A length it pinned is this widget's length,
|
||||
// and pins the same way: where the part is a span instead, that
|
||||
// span is this widget's own to decide and nothing has to be
|
||||
// said.
|
||||
(None, None) => {
|
||||
result.extent[n] = holds.extent[n];
|
||||
result.extent_len[n] = holds.extent_len[n];
|
||||
}
|
||||
// Its box is a part of this widget's frame: a length of the
|
||||
// frame is all that reaches it, so what it holds for is a range
|
||||
// on the frame and none of it on this widget's own box.
|
||||
_ => {
|
||||
result.frame[n] = result.frame[n].and(
|
||||
holds.extent[n]
|
||||
.through(extent.axis(axis).len())
|
||||
.through(frame_len),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// A child's answer as lengths of the parent's own region. A widget reports
|
||||
/// a fraction of its own region, and `of` is that region as a length of this
|
||||
/// one. Pixels come through untouched, being that many pixels wherever they
|
||||
@@ -749,68 +625,85 @@ pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: b
|
||||
reported.leftover != Weight::ZERO || declared.is_some() || decided
|
||||
}
|
||||
|
||||
/// What of the box it was given a widget's drawing occupies, as lengths of
|
||||
/// that box: the size it reported wherever that is a part to be placed, and
|
||||
/// the whole of the box wherever the answer fills it.
|
||||
/// Where a widget's drawing goes inside the part its parent gave it: what
|
||||
/// it reported, on the side of the part its alignment says, and the whole
|
||||
/// part wherever the answer fills it.
|
||||
///
|
||||
/// A reported fraction is a fraction of the box the widget drew in, where a
|
||||
/// declared one is a fraction of the box its parent handed down -- a span
|
||||
/// reporting `rel(1.0)` means all of what it was given, whatever that was a
|
||||
/// fraction of. So this is a length of the box rather than a length composed
|
||||
/// into it, and a box in pixels is this step from the given box's pixels.
|
||||
pub(crate) fn placed_lens(
|
||||
/// The length it reported is a length of its frame, and the part is one too,
|
||||
/// so this takes one from the other rather than composing it into the part.
|
||||
/// That is what makes a fraction the same fraction wherever the part it is
|
||||
/// placed in sits and however long it is -- the fraction is resolved once,
|
||||
/// here, against the frame it was reported of.
|
||||
pub(crate) fn placed_extent(
|
||||
part: UiRegion,
|
||||
size: Size,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
decided: [bool; 2],
|
||||
) -> UiVec2 {
|
||||
let mut lens = UiVec2::FULL_SIZE;
|
||||
for (axis, (declared, decided)) in AXES.into_iter().zip(declared.into_iter().zip(decided)) {
|
||||
let reported = size.axis(axis);
|
||||
if !fills(reported, declared, decided) {
|
||||
*lens.axis_mut(axis) = Len::from_parts(reported.rel, reported.px);
|
||||
}
|
||||
}
|
||||
lens
|
||||
}
|
||||
|
||||
/// Where that drawing sits: those lengths taken of the box the widget was
|
||||
/// asked in, on the side of it that the widget's alignment says.
|
||||
pub(crate) fn placed_box(region: UiRegion, lens: UiVec2, align: RegionAlign) -> UiRegion {
|
||||
let mut placed = region;
|
||||
fill: [bool; 2],
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = part;
|
||||
for axis in AXES {
|
||||
// The whole of the box is already where it sits, and the arithmetic
|
||||
// below is the identity for it.
|
||||
if lens.axis(axis) == Len::FULL {
|
||||
let n = axis as usize;
|
||||
let reported = size.axis(axis);
|
||||
if fills(reported, declared[n], fill[n]) {
|
||||
continue;
|
||||
}
|
||||
let len = Len::from_parts(reported.rel, reported.px);
|
||||
let span = placed.axis_mut(axis);
|
||||
let len = lens.axis(axis).within_len(span.len());
|
||||
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
||||
span.end = span.start + len;
|
||||
}
|
||||
placed
|
||||
}
|
||||
|
||||
/// A declared axis gets a frame of that length, aligned within the parent's
|
||||
/// slot (or the offer). Undeclared axes keep the offered frame and chosen
|
||||
/// placement, so their reported fractions retain that reference.
|
||||
pub(crate) fn ask_box(
|
||||
mut region: UiRegion,
|
||||
declared: [Option<LayoutLen>; 2],
|
||||
align: RegionAlign,
|
||||
placement: [Option<UiSpan>; 2],
|
||||
) -> (UiRegion, [Option<UiSpan>; 2]) {
|
||||
let mut placed = [None; 2];
|
||||
for (axis, (len, chosen)) in AXES.into_iter().zip(declared.into_iter().zip(placement)) {
|
||||
let Some(len) = len else {
|
||||
placed[axis as usize] = chosen;
|
||||
continue;
|
||||
};
|
||||
let span = region.axis_mut(axis);
|
||||
let len = Len::from_parts(len.rel, len.px);
|
||||
let slot = chosen.unwrap_or(*span);
|
||||
span.start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
||||
span.end = span.start + len;
|
||||
/// The part of a widget's extent a `place` names, in the coordinates its
|
||||
/// extent is in: a span is measured in frame lengths from where the extent
|
||||
/// starts, so nothing under it depends on where that is, and an extent that
|
||||
/// moved re-places every child by re-adding its start.
|
||||
pub(crate) fn part_of(extent: UiRegion, place: [Place; 2]) -> UiRegion {
|
||||
let mut part = extent;
|
||||
for axis in AXES {
|
||||
if let Some(span) = place[axis as usize].span() {
|
||||
let start = extent.axis(axis).start;
|
||||
*part.axis_mut(axis) = UiSpan::new(start + span.start, start + span.end);
|
||||
}
|
||||
}
|
||||
(region, placed)
|
||||
part
|
||||
}
|
||||
|
||||
/// The frame a child is asked in and the box its drawing goes in, both in
|
||||
/// the coordinates of the widget asking.
|
||||
///
|
||||
/// `frame` is what the caller said the child's fractions are of, and `part`
|
||||
/// what of the caller's own box the drawing takes. `narrow` is the length a
|
||||
/// declared rule gives the frame, which makes the frame the box the drawing
|
||||
/// goes in -- a rule is what decided where it goes, and there is nothing
|
||||
/// left to place inside it. A caller that narrowed the frame itself said the
|
||||
/// same thing.
|
||||
///
|
||||
/// The length is the caller's to supply so that a widget asked again gets
|
||||
/// the frame it already has rather than a second resolution of its rule.
|
||||
pub(crate) fn frame_and_extent(
|
||||
mut frame: UiRegion,
|
||||
part: UiRegion,
|
||||
narrow: [Option<Len>; 2],
|
||||
align: RegionAlign,
|
||||
) -> (UiRegion, UiRegion) {
|
||||
let mut extent = part;
|
||||
for (axis, narrow) in AXES.into_iter().zip(narrow) {
|
||||
let span = frame.axis_mut(axis);
|
||||
let narrowed = match narrow {
|
||||
Some(len) => {
|
||||
let slot = part.axis(axis);
|
||||
let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
||||
*span = UiSpan::new(start, start + len);
|
||||
true
|
||||
}
|
||||
None => *span != UiSpan::FULL,
|
||||
};
|
||||
if narrowed {
|
||||
*extent.axis_mut(axis) = UiSpan::FULL;
|
||||
}
|
||||
}
|
||||
(frame, extent)
|
||||
}
|
||||
Reference in new issue
Block a user