A box in pixels was composed back up the move chain, on a grid fine enough that the walk rounded once, while a widget's offer was threaded down through its ancestors' offers. Two routes to one length, which is what `Holds::through` allowed for -- and the offer's route broke at a region node. `offered_region` fell back to `UiRegion::FULL` there, and `redraw` resolved that against the node's slot entry, which holds the box its parent *placed* the node in. Under a `Scroll` that is as long as the content rather than the viewport, so everything below was re-asked at a width its own answer had produced and the old answer confirmed itself: shrinker seed 220 on `reorder` left a widget 290px out. `ActiveData` now keeps a widget's box as lengths of its parent's box -- `given_len`, and `offer_len` for the box it was first asked about -- and `DrawInfo` carries the pixel lengths, threaded down one `Len::to_px` at a time: the box its parent gave it, then the part of that box its own answer placed the drawing in, which `placed_lens` states once for both `placed_box` and the walk. `Painter::px_size` and `px_len` read that value, and `UiRenderState::asked_px` takes the same steps back up the parent chain where a local redraw starts part-way down the tree. Neither chain has a coordinate frame in it, so neither can break at a region node, and warm and cold reach every length by the same expression. Three things follow. `Holds::through` is the exact preimage of `px + floor(rel * box)` -- two divisions, no allowance, the whole of a box mapping back to itself. A local redraw asks in the box its parent gave it and only where that box is as long as the offer, which retires `redraw`'s third ask and the region-node exception beside it; `draw_inner` places the answer inside that box itself. And symbolic regions are left to the GPU, hit testing and remaps, where `Moves::resolve` is the only walk: `wide.rs`, `Moves::compose`, `Moves::size_of`, `px_of`, `px_region`, `offered_region` and `slot_wide` are gone, 252 lines of `core/` net. `px` is deliberately not stored beside those lengths. A resize every widget's `Holds` admits redraws nothing, so a stored pixel length would be stale on every widget in the tree with nothing on it to say so, and refreshing it costs a walk down every reused subtree on the resize path. Instructions:u, medians of 21 runs, seed 1 at depth 8: | phase | before | after | | | --- | ---: | ---: | ---: | | `cold`, 200 frames | 313.1M | 312.9M | -0.04% | | `resize` | 408.1M | 405.6M | -0.61% | | `many` | 1,924M | 1,756M | -8.75% | | `scroll` | 357.3M | 323.4M | -9.49% | | `repaint` | 363.3M | 315.4M | -13.18% | `cold` and `resize` have all twenty-five work counters identical, so those two rows say the draw path costs the same threaded as composed. The other three do less work: `repaint` goes from 23 draw requests and 13 widget draws a frame to 1 and 1, `scroll` from 20 and 11 to 8 and 2, `many` from 273 and 186 to 207 and 157. Primitive writes are unmoved in every phase. Verified: `view`, `minimal`, `random`, `tabs` and `text` render byte-identical at 1920x1200 against `5b78002`, as does the `tabs` touch replay before and after the gesture, and a live resize of `random` to 1280x800 is identical both to the old head's and to a cold render at that size. The oracle passes 100 seeds in release and 120 in debug -- the debug run is the one that exercises the `Holds` assertion -- and the fifteen shrinker cases pass at 400 seeds of depth 5 and 1000 of depth 6. Seed 220 is `unsettled::a_widget_under_a_region_node_is_asked_in_the_box_that_node_was_offered`, which needs both halves of this to fail: the old chain with the old allowance passes it, and the old chain with the exact preimage does not. `AGREE_STEPS` stays 2. One step passes the 100-seed oracle and fails the 400-seed shrinker on `resize-size` by 0.002 px, so what is left there is the resize path re-expressing a part as a fraction of a box that changed length, not a length reached two ways. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
629 lines
24 KiB
Rust
629 lines
24 KiB
Rust
#[cfg(feature = "layout-diagnostics")]
|
|
use crate::layout_diagnostics::{self as diag, Counter};
|
|
use crate::{
|
|
Axis, Holds, LayoutLen, Len, Px, PxVec2, RegionAlign, RenderedText, Size, StrongWidget,
|
|
TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight,
|
|
WidgetId, Widgets,
|
|
render::{
|
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
|
PrimitiveKind, TexturePrimitive,
|
|
},
|
|
ui::render_state::DrawInfo,
|
|
};
|
|
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
|
|
|
/// makes your surfaces look pretty
|
|
pub struct Painter<'a> {
|
|
pub(super) state: &'a mut UiRenderState,
|
|
pub(super) rsc: &'a mut dyn UiRsc,
|
|
|
|
/// This widget's box, in the coordinates of `move_idx`.
|
|
pub(super) region: UiRegion,
|
|
/// 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.
|
|
pub(super) px: PxVec2,
|
|
pub(super) mask: MaskIdx,
|
|
pub(super) textures: Vec<TextureHandle>,
|
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
|
pub(super) children: Vec<WidgetId>,
|
|
/// The children asked about so far, so the first box 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.
|
|
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
|
|
/// length until it reads one, then that one, unless it says otherwise.
|
|
pub(super) own: [Holds; 2],
|
|
/// What the children it asked about and drew keep it to.
|
|
pub(super) under: [Holds; 2],
|
|
/// The movable region this widget's primitives are positioned through:
|
|
/// its own when opted in, otherwise the nearest ancestor's.
|
|
pub(super) move_idx: MoveIdx,
|
|
pub layer: usize,
|
|
/// The layer this widget was entered on, which its children's layers are
|
|
/// counted from however far `layer` has walked.
|
|
pub(super) own_layer: usize,
|
|
pub(super) depth: usize,
|
|
pub(super) id: WidgetId,
|
|
}
|
|
|
|
impl<'a> Painter<'a> {
|
|
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: UiRegion) {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::PrimitiveWrites);
|
|
let h = self.state.layers.write(
|
|
self.layer,
|
|
PrimitiveInst {
|
|
kind,
|
|
id: self.id,
|
|
primitive,
|
|
region,
|
|
mask_idx: self.mask,
|
|
move_idx: self.move_idx,
|
|
},
|
|
);
|
|
self.push_primitive(h);
|
|
}
|
|
|
|
fn push_primitive(&mut self, h: PrimitiveHandle) {
|
|
if self.mask != MaskIdx::NONE {
|
|
// TODO: I have no clue if this works at all :joy:
|
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
|
}
|
|
self.primitives.push(h);
|
|
}
|
|
|
|
/// Writes a primitive to be rendered
|
|
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
|
let primitive = primitive.into_primitive(self);
|
|
self.primitive_at(primitive, self.region)
|
|
}
|
|
|
|
pub fn primitive_within(&mut self, primitive: impl PrimitiveLike, region: UiRegion) {
|
|
let primitive = primitive.into_primitive(self);
|
|
self.primitive_at(primitive, region.within(&self.region));
|
|
}
|
|
|
|
pub fn set_mask(&mut self, region: UiRegion) {
|
|
assert!(self.mask == MaskIdx::NONE);
|
|
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
|
region,
|
|
move_idx: self.move_idx,
|
|
});
|
|
}
|
|
|
|
/// Draws a widget within this widget's region.
|
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
|
self.widget_within(id, UiRegion::FULL)
|
|
}
|
|
|
|
/// 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.state.undraw_rec(id.id(), self.rsc);
|
|
}
|
|
|
|
/// Draws a widget somewhere within this one. `region` is in this widget's
|
|
/// own coordinates, and the child's declared lengths are still to be
|
|
/// taken from it. Where the child's drawing sits inside what it is given
|
|
/// is the child's alignment, applied where the child is drawn, so a
|
|
/// container positions a child either by handing it a box of exactly its
|
|
/// length or by leaving it room and letting its alignment decide.
|
|
pub fn widget_within<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
) -> DrawResult<'s, 'a, W> {
|
|
self.widget_at(id, region, [false; 2])
|
|
}
|
|
|
|
/// Draws a widget in a box this widget chose from the widget's own
|
|
/// answer along the `decided` axes. On those the answer is not placed
|
|
/// inside the box again: it already is the box, and a fraction the
|
|
/// widget reported of its offer, taken of this box a second time, would
|
|
/// shrink it twice. A container uses this where it hands back exactly
|
|
/// what a child asked for -- a span placing a child at the length it
|
|
/// reported, a scroll giving its content the content's own length.
|
|
pub fn widget_decided<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
decided: [bool; 2],
|
|
) -> DrawResult<'s, 'a, W> {
|
|
self.widget_at(id, region, decided)
|
|
}
|
|
|
|
fn widget_at<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
region: UiRegion,
|
|
decided: [bool; 2],
|
|
) -> DrawResult<'s, 'a, W> {
|
|
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());
|
|
// Composing `FULL` through a box is not quite the identity in f32,
|
|
// so a child with nothing declared keeps the box it would have had.
|
|
let local = match declared.iter().any(Option::is_some) {
|
|
true => declared_box(region, declared, align),
|
|
false => region,
|
|
};
|
|
let within = match local == UiRegion::FULL {
|
|
true => self.region,
|
|
false => local.within(&self.region),
|
|
};
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
if region_node {
|
|
diag::bump(Counter::RegionNodeDraws);
|
|
diag::region_node(id.id(), self.id, within);
|
|
}
|
|
// A child listed twice would be moved twice.
|
|
if !self.children.contains(&id.id()) {
|
|
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 px = given_len.to_px(self.px);
|
|
let offered_px = offer_len.to_px(self.offered_px);
|
|
// Whether this ask is the child's offer question, which is a question
|
|
// about lengths: the same lengths somewhere else is the same question.
|
|
let answers_offer = self.at_offer && px == 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
|
|
// placed: a drawing made again in its placed box holds for that box.
|
|
let (size, holds) = self.state.draw_inner(
|
|
id.id(),
|
|
within,
|
|
DrawInfo {
|
|
layer: self.layer,
|
|
parent: Some(self.id),
|
|
depth: self.depth + 1,
|
|
parent_move: self.move_idx,
|
|
region_node,
|
|
mask: self.mask,
|
|
given_len,
|
|
offer_len,
|
|
px,
|
|
offered_px,
|
|
decided,
|
|
},
|
|
None,
|
|
self.rsc,
|
|
);
|
|
if answers_offer {
|
|
self.state.active.get_mut(&id.id()).unwrap().answer = (size, holds);
|
|
}
|
|
// Whatever the child's answer holds for keeps this one to the boxes
|
|
// that give the child a length inside it.
|
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
|
}
|
|
DrawResult {
|
|
child: id,
|
|
painter: self,
|
|
size: in_parent_frame(size, local, declared),
|
|
}
|
|
}
|
|
|
|
/// 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> {
|
|
let widgets = self.rsc.widgets();
|
|
// A rule is the answer where there is one: it wins over whatever the
|
|
// widget would draw, so it has to win over what the widget says too.
|
|
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
|
|
widgets
|
|
.get_dyn(id.id())
|
|
.and_then(|widget| widget.size_hint(axis))
|
|
});
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::hint_read(id.id(), self.id, axis, hint);
|
|
match hint {
|
|
Some(hint) => {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::HintHits);
|
|
self.depend_on(id);
|
|
Some(hint)
|
|
}
|
|
None => {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::HintMisses);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A child's length in the box it is about to be offered, if it can be
|
|
/// had without drawing it: from its hint, or from a drawing it already
|
|
/// has that holds for that box.
|
|
pub fn known_len<W: ?Sized>(
|
|
&mut self,
|
|
child: &StrongWidget<W>,
|
|
axis: Axis,
|
|
region: UiRegion,
|
|
) -> Option<LayoutLen> {
|
|
let declared = self.declared_lens(child);
|
|
let align = self.rsc.widgets().alignment(child.id());
|
|
let local = declared_box(region, declared, align);
|
|
let first_ask = self.offer(child.id());
|
|
if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) {
|
|
active.offer_len = local.size();
|
|
}
|
|
if let Some(hint) = self.size_hint(child, axis) {
|
|
return Some(hint);
|
|
}
|
|
let px = local.size().to_px(self.px);
|
|
let (size, holds) =
|
|
self.state
|
|
.retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?;
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::RetainedSizeHits);
|
|
self.depend_on(child);
|
|
if first_ask {
|
|
let active = self.state.active.get_mut(&child.id()).unwrap();
|
|
active.answer = (size, holds);
|
|
}
|
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
|
}
|
|
Some(in_parent_frame(size, local, 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.
|
|
fn offer(&mut self, child: WidgetId) -> bool {
|
|
if !self.at_offer || self.offered.contains(&child) {
|
|
return false;
|
|
}
|
|
self.offered.push(child);
|
|
true
|
|
}
|
|
|
|
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
|
if !self.size_deps.contains(&child.id()) {
|
|
self.size_deps.push(child.id());
|
|
}
|
|
}
|
|
|
|
pub fn render_text<'b>(
|
|
&mut self,
|
|
buffer: &'b mut TextBuffer,
|
|
attrs: &TextAttrs,
|
|
width: Option<f32>,
|
|
) -> &'b RenderedText {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::render_text(self.id, self.rsc.widgets().label(self.id), width);
|
|
let ui = self.rsc.ui_mut();
|
|
ui.text.render(buffer, attrs, width)
|
|
}
|
|
|
|
// TODO: merge the text methods into the primitive ones.
|
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
|
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
|
for glyph in text.glyphs.iter() {
|
|
let mut region = origin;
|
|
region.x.end = region.x.start;
|
|
region.y.end = region.y.start;
|
|
let mut region = region.offset(UiVec2::from_px(glyph.offset));
|
|
let size = PxVec2::new(
|
|
Px::from_int(glyph.entry.width as i32),
|
|
Px::from_int(glyph.entry.height as i32),
|
|
);
|
|
region.x.end = region.x.start.offset(size.x);
|
|
region.y.end = region.y.start.offset(size.y);
|
|
self.write(
|
|
kind,
|
|
GlyphPrimitive {
|
|
uv_min: glyph.entry.uv_min,
|
|
uv_max: glyph.entry.uv_max,
|
|
layer: glyph.entry.layer,
|
|
color: text.color,
|
|
flags: glyph.entry.flags(),
|
|
},
|
|
region,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// This widget's box, in the coordinates its own primitives are written
|
|
/// in -- so a region composed `within` it may be drawn directly.
|
|
pub fn region(&self) -> UiRegion {
|
|
self.region
|
|
}
|
|
|
|
/// Where this widget sits in a box longer than the length it takes. A
|
|
/// widget that positions its own content reads it to place that content
|
|
/// the way the box around it would have placed the widget.
|
|
pub fn alignment(&self) -> RegionAlign {
|
|
self.rsc.widgets().alignment(self.id)
|
|
}
|
|
|
|
/// Whether a rule beside this widget gives its length on `axis` outright,
|
|
/// which makes whatever it reports for that axis moot. A rule that only
|
|
/// bounds the length is not one of these: the answer is still the
|
|
/// widget's to give, and something still has to work it out.
|
|
///
|
|
/// The widget under a rule does not otherwise learn of it -- this is for
|
|
/// a container deciding whether reading its children across an axis is
|
|
/// worth anything, since reading one is also what makes its own size
|
|
/// depend on it.
|
|
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
|
self.rsc
|
|
.widgets()
|
|
.size_rules(self.id)
|
|
.axis(axis)
|
|
.exact()
|
|
.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 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 {
|
|
for (own, len) in self.own.iter_mut().zip([self.px.x, self.px.y]) {
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(len);
|
|
}
|
|
}
|
|
self.px
|
|
}
|
|
|
|
/// One axis of this widget's 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 len = self.px.axis(axis);
|
|
let own = &mut self.own[axis as usize];
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(len);
|
|
}
|
|
len
|
|
}
|
|
|
|
/// The lengths of this widget's box on `axis` that what it is drawing
|
|
/// holds for -- the same primitives, in the same fractions and offsets
|
|
/// 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 holds = holds.into();
|
|
debug_assert!(
|
|
holds.contains(self.px.axis(axis)),
|
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
|
self.label(),
|
|
self.id
|
|
);
|
|
self.own[axis as usize] = holds;
|
|
}
|
|
|
|
pub fn text_data(&mut self) -> &mut TextData {
|
|
&mut self.rsc.ui_mut().text
|
|
}
|
|
|
|
pub fn child_layer(&mut self) {
|
|
self.layer = self.state.layers.child(self.layer);
|
|
}
|
|
|
|
/// The layer this widget's `n`th child draws on, addressed rather than
|
|
/// walked to. A container that measures one child by drawing it can ask
|
|
/// on the layer that child will end up on, and then the second ask is a
|
|
/// reuse rather than a second drawing on another layer.
|
|
pub fn child_layer_at(&mut self, n: usize) {
|
|
let mut at = self.state.layers.child(self.own_layer);
|
|
for _ in 0..n {
|
|
at = self.state.layers.next(at);
|
|
}
|
|
self.layer = at;
|
|
}
|
|
|
|
pub fn next_layer(&mut self) {
|
|
self.layer = self.state.layers.next(self.layer);
|
|
}
|
|
|
|
pub fn label(&self) -> &str {
|
|
&self.rsc.widgets().data(self.id).unwrap().label
|
|
}
|
|
|
|
pub fn id(&self) -> &WidgetId {
|
|
&self.id
|
|
}
|
|
}
|
|
|
|
/// A child that has just been drawn. Reading its size records that this
|
|
/// widget's own size depends on it; dropping it without reading draws the
|
|
/// child and leaves the parent independent of what it came to.
|
|
pub struct DrawResult<'p, 'a, W: ?Sized> {
|
|
painter: &'p mut Painter<'a>,
|
|
child: &'p StrongWidget<W>,
|
|
size: Size,
|
|
}
|
|
|
|
impl<W: ?Sized> DrawResult<'_, '_, W> {
|
|
pub fn size(self) -> Size {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
{
|
|
diag::bump(Counter::SizeReads);
|
|
diag::size_read(self.child.id(), self.painter.id, self.size);
|
|
}
|
|
self.painter.depend_on(self.child);
|
|
self.size
|
|
}
|
|
|
|
pub fn len(self, axis: Axis) -> LayoutLen {
|
|
self.size().axis(axis)
|
|
}
|
|
}
|
|
|
|
/// What `Painter::primitive` takes: a primitive, or something that yields one
|
|
/// and does whatever else drawing it needs.
|
|
pub trait PrimitiveLike {
|
|
type Primitive: Primitive;
|
|
fn into_primitive(self, painter: &mut Painter) -> Self::Primitive;
|
|
}
|
|
|
|
impl<P: Primitive> PrimitiveLike for P {
|
|
type Primitive = P;
|
|
fn into_primitive(self, _: &mut Painter) -> P {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl PrimitiveLike for &TextureHandle {
|
|
type Primitive = TexturePrimitive;
|
|
|
|
/// Retains a share of the handle, so the slot the primitive names cannot
|
|
/// be freed and reused while it is still drawn.
|
|
fn into_primitive(self, painter: &mut Painter) -> TexturePrimitive {
|
|
painter.textures.push(self.clone());
|
|
self.into()
|
|
}
|
|
}
|
|
|
|
/// A child's answer as lengths of the box it was asked from. A widget reports
|
|
/// a fraction of the box it was given, and the widget that gave it wants the
|
|
/// same length as a fraction of its own: one composition apart wherever the
|
|
/// offer was not the whole of the parent's extent, as a span's is after a
|
|
/// relative child. A declared axis is already the parent's: it resolved the
|
|
/// rule in its own box, and the rule is what the report says.
|
|
fn in_parent_frame(size: Size, local: UiRegion, declared: [Option<LayoutLen>; 2]) -> Size {
|
|
let mut size = size;
|
|
for (axis, declared) in AXES.into_iter().zip(declared) {
|
|
if declared.is_none() {
|
|
let len = local.axis(axis).len();
|
|
*size.axis_mut(axis) = size.axis(axis).within_len(len);
|
|
}
|
|
}
|
|
size
|
|
}
|
|
|
|
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
|
/// share of what is left over is only a length to the widget dividing one,
|
|
/// so it passes up in the size instead.
|
|
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
|
|
let rules = widgets.size_rules(id);
|
|
let widget = widgets.get_dyn(id);
|
|
AXES.map(|axis| {
|
|
rules.axis(axis).declared().or_else(|| {
|
|
// A hint still narrows the box where no rule does, which is how a
|
|
// widget with a natural pixel size -- an image, a gap -- gets that
|
|
// size rather than the whole offer. That is the offer's business
|
|
// rather than a declaration's, and this falls away once a widget
|
|
// occupies its reported size inside the box it was offered.
|
|
widget
|
|
.and_then(|widget| widget.size_hint(axis))
|
|
.filter(|len| len.leftover == Weight::ZERO)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// Whether what a widget reported along an axis is the whole of the box it
|
|
/// is in rather than a part to be placed inside it. A share fills, because a
|
|
/// share is a length only to whoever divides one, and whoever did is the one
|
|
/// that handed down this box. A declared axis does too: `declared_box`
|
|
/// already placed it, in the parent's box, and the rule's length is what the
|
|
/// widget reports there. And an axis the parent decided from the answer is
|
|
/// the answer already.
|
|
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
|
|
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.
|
|
///
|
|
/// 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(
|
|
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;
|
|
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;
|
|
}
|
|
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
|
|
}
|
|
|
|
/// Takes a widget's declared lengths in the box `region` is given in, since a
|
|
/// fraction of a length means a fraction of that one, and puts what is left
|
|
/// over on the side its alignment says. A caller that already reserved the
|
|
/// space hands back the same length, so this is the identity for it.
|
|
pub(crate) fn declared_box(
|
|
mut region: UiRegion,
|
|
declared: [Option<LayoutLen>; 2],
|
|
align: RegionAlign,
|
|
) -> UiRegion {
|
|
for (axis, len) in AXES.into_iter().zip(declared) {
|
|
let Some(len) = len else { continue };
|
|
let span = region.axis_mut(axis);
|
|
let len = Len::from_parts(len.rel, len.px);
|
|
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
|
span.end = span.start + len;
|
|
}
|
|
region
|
|
}
|