The eleventh sweep, over the built-in bounds work in2ac0843. `Painter::size_hint` refused to answer for a bounded widget by returning above the diagnostics, so that read was neither a hint hit nor a miss and `hint_read` recorded nothing. It is a miss now, with the reason on it. The two `for axis in Axis::BOTH` loops that `draw_widget` grew, both writing `own_holds`, are one loop, and the comment about combining the ask's holds no longer sits between a comment and the code it describes. `Declared::from_axes` lost its only caller with `Widgets::declared_lens`; `Bounds::from_axes` and `SizeRule::declared` never had one. The scenario shrinker printed a rule with derived `Debug`, which is 130 characters an axis in a line that carries every ancestor, in the one function whose job is output a tree can be rebuilt from. It prints its parts again. `bounds_cost` invented three environment-reading spellings where four copies of one `env` helper already existed; there is now one, in `tests/rig`, and the four copies are gone. It also verified 128 regions inside its measured loop, which the other rigs deliberately do before theirs; that measured 0.65% of the total, and none of it is layout. The 250-window row with a 300 cap was built by two tests, and the one that still explained itself tested less; they are one. The half of `a_cap_attribute_narrows_the_widgets_box` that the wrapper's removal left without its deciding assertion is the allocator's path instead, which nothing at the root covered. Format, workspace clippy under -D warnings with and without layout-diagnostics, 206 ordinary and 210 diagnostic tests, 400 depth-5 trees warm against cold in 64.19s, and the cold dump byte-identical to2ac0843across all 34,986 boxes.
1063 lines
43 KiB
Rust
1063 lines
43 KiB
Rust
#[cfg(feature = "layout-diagnostics")]
|
|
use crate::layout_diagnostics::{self as diag, Counter};
|
|
use crate::{
|
|
Axis, Bound, Bounds, Declared, DrawScratch, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc,
|
|
PlaceFit, Px, PxVec2, RegionAlign, Rel, RenderedText, RequestArena, RequestedLen,
|
|
RetainedPrimitive, Size, SizeRequests, StrongWidget, TextAttrs, TextBuffer, TextureHandle,
|
|
UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
|
render::{
|
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
|
TexturePrimitive,
|
|
},
|
|
ui::{
|
|
place::{PlaceSpan, RelBase},
|
|
render_state::{DrawInfo, Placing},
|
|
},
|
|
};
|
|
|
|
/// 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 rel base, per axis: a length of the window, and what a
|
|
/// fraction it or anything under it declares or reports is a fraction
|
|
/// of. A length rather than a box, so padding can take from both the
|
|
/// rel base and the box without either becoming the other.
|
|
pub(super) rel_base: UiVec2,
|
|
/// The box this widget was asked in, in its region node's coordinates:
|
|
/// what it draws in, and what its children's places are parts of.
|
|
pub(super) region: UiRegion,
|
|
/// The window in pixels. Frames and boxes become pixels against this one
|
|
/// unit, regardless of region-node boundaries.
|
|
pub(super) window: PxVec2,
|
|
pub(super) mask: MaskIdx,
|
|
pub(super) textures: Vec<TextureHandle>,
|
|
pub(super) primitives: Vec<RetainedPrimitive>,
|
|
pub(super) mask_region: Option<UiRegion>,
|
|
/// The previous drawing's owned mask, available for this draw to reclaim.
|
|
pub(super) mask_slot: Option<MaskIdx>,
|
|
/// Only children whose answers were read constrain this widget's answer.
|
|
pub(super) answer_under: LayoutHolds,
|
|
pub(super) children: Vec<WidgetId>,
|
|
/// The children whose size this widget read while drawing.
|
|
pub(super) size_deps: Vec<WidgetId>,
|
|
pub(super) request_deps: Vec<WidgetId>,
|
|
pub(super) scratch: DrawScratch,
|
|
/// What this draw itself reads, as against what its children's drawings
|
|
/// hold for: every window and every length of its own region until it
|
|
/// reads one, then that one unless it says otherwise, and the rel base or
|
|
/// region length it read symbolically, each of which makes the drawing
|
|
/// hold for that length alone.
|
|
pub(super) own: LayoutHolds,
|
|
/// What each child's drawing depends on. Asking a child again replaces
|
|
/// its drawing, so it replaces this too rather than narrowing it.
|
|
pub(super) under: Vec<(WidgetId, LayoutHolds)>,
|
|
/// 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> {
|
|
/// Reuses this widget's allocation buffers across draws. A child drawn
|
|
/// part way through gets a painter of its own, with buffers of its own,
|
|
/// so nothing it does while this one is mid-row can reach these.
|
|
pub fn with_requests<T>(
|
|
&mut self,
|
|
f: impl FnOnce(&mut Self, &mut Vec<RequestedLen>, &mut Vec<Px>) -> T,
|
|
) -> T {
|
|
let mut requests = std::mem::take(&mut self.scratch.requests);
|
|
let mut lengths = std::mem::take(&mut self.scratch.lengths);
|
|
requests.clear();
|
|
lengths.clear();
|
|
let result = f(self, &mut requests, &mut lengths);
|
|
self.scratch.requests = requests;
|
|
self.scratch.lengths = lengths;
|
|
result
|
|
}
|
|
|
|
/// Discovers a composable request without painting a provisional box.
|
|
pub fn size_request<W: ?Sized>(
|
|
&mut self,
|
|
child: &StrongWidget<W>,
|
|
axis: Axis,
|
|
) -> Option<RequestedLen> {
|
|
if let Some(len) = self.size_hint(child, axis) {
|
|
self.request_deps.push(child.id());
|
|
return Some(len.into());
|
|
}
|
|
let start = self.request_deps.len();
|
|
let mut requests = SizeRequests {
|
|
arena: &mut self.state.requests,
|
|
measured: None,
|
|
widgets: self.rsc.widgets(),
|
|
dependencies: &mut self.request_deps,
|
|
rel_base: self.rel_base[axis],
|
|
};
|
|
// Intrinsic fixed content must keep its offered box for wrapping;
|
|
// only a declaration or a share chooses the box it is drawn in.
|
|
let request = requests.widget(child, axis).filter(|request| {
|
|
request.has_leftover()
|
|
|| self.rsc.widgets().size_rules(child.id())[axis]
|
|
.request
|
|
.is_some()
|
|
});
|
|
if request.is_some() {
|
|
self.rel_base(axis);
|
|
} else {
|
|
// A discarded request contributes no dependency: the measured
|
|
// draw below records the size and box it actually used instead.
|
|
self.request_deps.truncate(start);
|
|
}
|
|
request
|
|
}
|
|
|
|
/// Completes discovery after a child was measured. Only this call may use
|
|
/// drawn answers: before the ask they could belong to an obsolete box.
|
|
pub fn measured_request<W: ?Sized>(
|
|
&mut self,
|
|
child: &StrongWidget<W>,
|
|
axis: Axis,
|
|
len: LayoutLen,
|
|
) -> RequestedLen {
|
|
let start = self.request_deps.len();
|
|
let bound = self.rsc.widgets().size_rules(child.id())[axis].bound;
|
|
let mut requests = SizeRequests {
|
|
arena: &mut self.state.requests,
|
|
measured: Some(&self.state.active),
|
|
widgets: self.rsc.widgets(),
|
|
dependencies: &mut self.request_deps,
|
|
rel_base: self.rel_base[axis],
|
|
};
|
|
if let Some(request) = requests.widget(child, axis)
|
|
&& request.linear().is_none()
|
|
&& request.has_leftover()
|
|
{
|
|
self.rel_base(axis);
|
|
return request;
|
|
}
|
|
let shares = len.leftover > Weight::ZERO;
|
|
let request = match shares {
|
|
true => requests.bounded(len.into(), bound),
|
|
false => len.into(),
|
|
};
|
|
self.request_deps.truncate(start);
|
|
// Only a bound that is a fraction was read against the rel base; one
|
|
// in pixels binds at the same length under any of them.
|
|
if shares && bound.has_fraction() {
|
|
self.rel_base(axis);
|
|
}
|
|
request
|
|
}
|
|
|
|
/// Divides `room` between `requests`, one length per request in
|
|
/// `output`. A deferred comparison is decided here, against this window:
|
|
/// which side of a crossing the solution falls is a question in pixels,
|
|
/// so the drawing holds only for the window that answered it.
|
|
pub fn allocate(
|
|
&mut self,
|
|
requests: &[RequestedLen],
|
|
room: Len,
|
|
axis: Axis,
|
|
output: &mut Vec<Px>,
|
|
) {
|
|
let window = self.window[axis];
|
|
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
|
|
output.clear();
|
|
output.extend(
|
|
self.state
|
|
.requests
|
|
.allocate(requests, room.to_px(window), window),
|
|
)
|
|
}
|
|
|
|
/// The least a request can come to, which is what it takes of the row
|
|
/// before anything is divided. A comparison is read at no share at all.
|
|
pub fn minimum_request(&mut self, request: &RequestedLen, axis: Axis) -> Len {
|
|
match request.linear() {
|
|
Some(len) => len.without_leftover(),
|
|
None => {
|
|
let window = self.window[axis];
|
|
self.own[axis].window = self.own[axis].window.and(Holds::at(window));
|
|
Len::from_parts(Rel::ZERO, self.state.requests.minimum(*request, window))
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
self.write_resolved(kind, primitive, region, self.resolve(region));
|
|
}
|
|
|
|
/// A box in this widget's region, composed into its region node's
|
|
/// coordinates.
|
|
fn resolve(&self, region: UiRegion) -> UiRegion {
|
|
region.within(&self.region)
|
|
}
|
|
|
|
fn write_resolved<P: Primitive>(
|
|
&mut self,
|
|
kind: PrimitiveKind<P>,
|
|
primitive: P,
|
|
region: UiRegion,
|
|
resolved: UiRegion,
|
|
) {
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
diag::bump(Counter::PrimitiveWrites);
|
|
let h = self.state.layers.write(
|
|
self.layer,
|
|
PrimitiveInst {
|
|
kind,
|
|
id: self.id,
|
|
primitive,
|
|
region: resolved,
|
|
mask_idx: self.mask,
|
|
move_idx: self.move_idx,
|
|
},
|
|
);
|
|
self.push_primitive(RetainedPrimitive { handle: h, region });
|
|
}
|
|
|
|
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
|
if self.mask != MaskIdx::NONE {
|
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
|
}
|
|
self.primitives.push(h);
|
|
}
|
|
|
|
/// Writes a primitive over the whole of this widget's own box.
|
|
pub fn primitive(&mut self, primitive: impl PrimitiveLike) {
|
|
let primitive = primitive.into_primitive(self);
|
|
self.primitive_at(primitive, UiRegion::FULL)
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// 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;
|
|
let mask = Mask {
|
|
region: resolved,
|
|
move_idx,
|
|
};
|
|
let masks = &mut self.rsc.ui_mut().masks;
|
|
self.mask = match self.mask_slot.take() {
|
|
Some(idx) => {
|
|
*masks.get_mut(idx) = mask;
|
|
idx
|
|
}
|
|
None => {
|
|
let idx = masks.push(mask);
|
|
// The owner keeps the slot alive even with no primitives.
|
|
masks.push_ref(idx);
|
|
idx
|
|
}
|
|
};
|
|
}
|
|
|
|
/// Draws a widget in the whole of this widget's own box, with the rel
|
|
/// base forwarded unchanged: what a container that is only a wrapper
|
|
/// around one child wants.
|
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
|
self.widget_at(id, UiRegion::FULL)
|
|
}
|
|
|
|
/// Resolves what the place says about the child's rel base into a length,
|
|
/// where that is this widget's own narrowed the way the region is. An
|
|
/// axis the region leaves whole is not read at all, so a wrapper that
|
|
/// only moves its child does not pin its drawing to a rel base.
|
|
fn resolve_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
|
for axis in Axis::BOTH {
|
|
let at = &mut place[axis];
|
|
let (RelBase::WithRegion, PlaceSpan::Within(span)) = (at.rel_base, at.span) else {
|
|
continue;
|
|
};
|
|
let len = span.len();
|
|
at.rel_base = match len == Len::FULL {
|
|
true => RelBase::Inherit,
|
|
false => RelBase::Len(len.within_len(self.rel_base(axis))),
|
|
};
|
|
}
|
|
place
|
|
}
|
|
|
|
/// Asks a child, saying what its fractions are of and where it is asked.
|
|
///
|
|
/// `place` says where the child goes and what its fractions are of:
|
|
/// see [`PlaceDesc`]. A `UiRegion` converts into the common case, which
|
|
/// is a box of this widget's own with the answer placed inside it.
|
|
///
|
|
/// The child draws once, in the region that comes of it, and its answer
|
|
/// is placed inside that region by re-expressing the drawing. Nothing is
|
|
/// drawn again in a box an answer chose; a container that puts the
|
|
/// answer somewhere else says so with [`Self::place_at`].
|
|
pub fn widget_at<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
place: impl Into<PlaceDesc>,
|
|
) -> DrawResult<'s, 'a, W> {
|
|
let offer = self.resolve_rel_base(place.into());
|
|
let Ask {
|
|
rel_base,
|
|
region,
|
|
place,
|
|
declared,
|
|
bounds,
|
|
holds: ask_holds,
|
|
inputs,
|
|
} = self.placing().ask(
|
|
self.rsc.widgets(),
|
|
&mut self.state.requests,
|
|
self.window,
|
|
id.id(),
|
|
offer,
|
|
);
|
|
self.own = self.own.and(inputs);
|
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
if region_node {
|
|
diag::bump(Counter::RegionNodeDraws);
|
|
diag::region_node(id.id(), self.id, region);
|
|
}
|
|
// A child listed twice would be moved twice.
|
|
let re_asked = self.children.contains(&id.id());
|
|
if !re_asked {
|
|
self.children.push(id.id());
|
|
}
|
|
let drawn = self.state.draw_inner(
|
|
id.id(),
|
|
DrawInfo {
|
|
layer: self.layer,
|
|
parent: Some(self.id),
|
|
depth: self.depth + 1,
|
|
parent_move: self.move_idx,
|
|
region_node,
|
|
mask: self.mask,
|
|
rel_base,
|
|
region,
|
|
placed: place,
|
|
asked: offer,
|
|
declared,
|
|
bounds,
|
|
ask_holds,
|
|
re_asked,
|
|
},
|
|
None,
|
|
self.rsc,
|
|
);
|
|
let holds = self.in_parent(drawn.drawing_holds, region, place, declared);
|
|
let answer_holds = self.in_parent(drawn.answer.holds, region, place, declared);
|
|
match self.under.iter_mut().find(|(child, _)| *child == id.id()) {
|
|
Some((_, kept)) => *kept = holds,
|
|
None => self.under.push((id.id(), holds)),
|
|
}
|
|
DrawResult {
|
|
child: id,
|
|
painter: self,
|
|
size: drawn.answer.size,
|
|
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.under.retain(|(child, _)| *child != id.id());
|
|
self.state.undraw_rec(id.id(), self.rsc);
|
|
}
|
|
|
|
/// Puts a child in `place` of this widget's box, where that box is the
|
|
/// answer the child already gave: the drawing is re-expressed there
|
|
/// rather than made again -- what a row does once it knows every slot,
|
|
/// having measured each child from its cursor.
|
|
///
|
|
/// A child this draw has not asked about, and one whose rel base this
|
|
/// narrows, is asked here instead: there is no answer to re-express, or
|
|
/// the question has changed. So a container that places every child the
|
|
/// same way says it once, and which of the two happens is this widget's
|
|
/// business rather than the caller's.
|
|
pub fn place_at<'s, W: ?Sized>(
|
|
&'s mut self,
|
|
id: &'s StrongWidget<W>,
|
|
place: impl Into<PlaceDesc>,
|
|
) -> DrawResult<'s, 'a, W> {
|
|
let place = self.resolve_rel_base(place.into());
|
|
let states_rel_base = Axis::BOTH
|
|
.iter()
|
|
.any(|&axis| matches!(place[axis].rel_base, RelBase::Len(_)));
|
|
if states_rel_base || !self.children.contains(&id.id()) {
|
|
return self.widget_at(id, place);
|
|
}
|
|
let at = self.placing();
|
|
self.state.place_in(id.id(), &at, place, self.rsc);
|
|
let active = &self.state.active[&id.id()];
|
|
let size = active.measured().unwrap_or(active.size);
|
|
DrawResult {
|
|
child: id,
|
|
painter: self,
|
|
size,
|
|
// Read where it was asked; moving it is not a second answer.
|
|
answer_holds: LayoutHolds::ANY,
|
|
}
|
|
}
|
|
|
|
/// This widget as the thing its children are placed within.
|
|
fn placing(&self) -> Placing {
|
|
Placing {
|
|
id: Some(self.id),
|
|
region: self.region,
|
|
rel_base: self.rel_base,
|
|
depth: self.depth,
|
|
move_idx: self.move_idx,
|
|
mask: self.mask,
|
|
}
|
|
}
|
|
|
|
/// What a child says its length is without being drawn, if it can say,
|
|
/// as the length its draw would report: a fraction in it is resolved
|
|
/// against this widget's rel base, which is the rel base a child asked with
|
|
/// nothing narrowed gets. Asking counts as reading its size.
|
|
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
|
// A bound is composed into a request rather than applied to a hint,
|
|
// so a bounded widget cannot say its length without being asked: what
|
|
// it comes to is a comparison only the ask or the allocator makes.
|
|
// A miss rather than no read at all, so the counters see it.
|
|
let bounded = self.rsc.widgets().size_rules(id.id())[axis].bound != Bound::ANY;
|
|
let hint = (!bounded)
|
|
.then(|| self.rsc.widgets().exact_len(id.id(), axis))
|
|
.flatten();
|
|
let rel_base = self.rel_base[axis];
|
|
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
|
#[cfg(feature = "layout-diagnostics")]
|
|
{
|
|
diag::hint_read(id.id(), self.id, axis, resolved);
|
|
diag::bump(match resolved {
|
|
Some(_) => Counter::HintHits,
|
|
None => Counter::HintMisses,
|
|
});
|
|
}
|
|
if let Some(hint) = hint {
|
|
self.depend_on(id);
|
|
// Resolving a fraction against this rel base makes this draw a
|
|
// function of the rel base's length. The fraction to ask about is
|
|
// the child's own: resolved against a rel base of pixels, none is
|
|
// left to see it by.
|
|
if hint.rel != Rel::ZERO {
|
|
self.own[axis].rel_base = Some(rel_base);
|
|
}
|
|
}
|
|
resolved
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
/// Writes glyphs in the selected rel base or region coordinates.
|
|
// TODO: merge the text methods into the primitive ones.
|
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
|
// Glyph offsets and sizes are pixels, which compose additively.
|
|
// Only the shared origin needs composing through the region.
|
|
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| {
|
|
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);
|
|
region
|
|
};
|
|
self.write_resolved(
|
|
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(),
|
|
},
|
|
place(origin),
|
|
place(resolved),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// The symbolic length of this widget's own box along one axis, in the
|
|
/// lengths of its rel base 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. One axis at a time, because a container that divides one axis
|
|
/// holds for any length of the other.
|
|
pub fn region_len(&mut self, axis: Axis) -> Len {
|
|
let len = self.region[axis].len();
|
|
self.own[axis].region_len = Some(len);
|
|
len
|
|
}
|
|
|
|
/// This widget's rel base along one axis: what a fraction it or anything
|
|
/// under it declares or reports is a fraction of. A container reads it
|
|
/// to hand a length of it down -- padding, which takes its pixels off.
|
|
/// Reading it pins the drawing to that rel base, the way
|
|
/// [`Self::region_len`] pins it to the box.
|
|
pub fn rel_base(&mut self, axis: Axis) -> Len {
|
|
let len = self.rel_base[axis];
|
|
self.own[axis].rel_base = Some(len);
|
|
len
|
|
}
|
|
|
|
/// 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]
|
|
.request
|
|
.is_some()
|
|
}
|
|
|
|
/// 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 {
|
|
PxVec2::new(self.px_len(Axis::X), self.px_len(Axis::Y))
|
|
}
|
|
|
|
/// 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 len = self.region[axis].len();
|
|
let px = len.to_px(self.window[axis]);
|
|
let own = &mut self.own[axis].region;
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(px);
|
|
}
|
|
px
|
|
}
|
|
|
|
/// The lengths of this widget's own 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 len = self.region[axis].len();
|
|
let holds = holds.into();
|
|
debug_assert!(
|
|
holds.contains(len.to_px(self.window[axis])),
|
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
|
self.label(),
|
|
self.id
|
|
);
|
|
self.own[axis].region = holds;
|
|
}
|
|
|
|
/// A window length in pixels, which is what every length in layout is
|
|
/// measured in. Reading one pins the drawing to this window wherever the
|
|
/// length is a fraction of it; one that is only pixels is that many
|
|
/// pixels in any window and pins nothing.
|
|
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
|
|
let window = self.window[axis];
|
|
if len.rel != Rel::ZERO {
|
|
let own = &mut self.own[axis].window;
|
|
if *own == Holds::ANY {
|
|
*own = Holds::at(window);
|
|
}
|
|
}
|
|
len.to_px(window)
|
|
}
|
|
|
|
/// [`Len::longer_than`], asked on this widget's behalf: the windows the
|
|
/// comparison comes out the same way on are windows its drawing holds
|
|
/// for, and nowhere else does it. What a container has left for the
|
|
/// shares it divides is the one thing that asks.
|
|
///
|
|
/// Narrowed rather than stated, because whatever else this widget read
|
|
/// about the window is a reason its drawing holds where it does too.
|
|
pub fn longer_than(&mut self, len: Len, than: Len, axis: Axis) -> bool {
|
|
let window = self.window[axis];
|
|
let (longer, holds) = len.longer_than(than, window);
|
|
debug_assert!(
|
|
holds.contains(window),
|
|
"'{}' ({:?}) compared two lengths and kept a range without this window",
|
|
self.label(),
|
|
self.id
|
|
);
|
|
self.own[axis].window = self.own[axis].window.and(holds);
|
|
longer
|
|
}
|
|
|
|
/// The windows this drawing holds for, stated rather than taken: a
|
|
/// container that branched on a length in pixels says which side of the
|
|
/// boundary it was on, which is wider than the one window reading that
|
|
/// length pins, and replaces it.
|
|
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
|
let holds = holds.into();
|
|
debug_assert!(
|
|
holds.contains(self.window[axis]),
|
|
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
|
|
self.label(),
|
|
self.id
|
|
);
|
|
self.own[axis].window = holds;
|
|
}
|
|
|
|
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,
|
|
answer_holds: LayoutHolds,
|
|
}
|
|
|
|
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.painter.answer_under = self.painter.answer_under.and(self.answer_holds);
|
|
self.size
|
|
}
|
|
|
|
pub fn len(self, axis: Axis) -> LayoutLen {
|
|
self.size()[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()
|
|
}
|
|
}
|
|
|
|
impl Painter<'_> {
|
|
/// Moves what a child depends on into this widget's own terms, taking
|
|
/// only what the child was asked with.
|
|
///
|
|
/// Window ranges are already about the one unit and combine directly.
|
|
/// A rel base pin becomes this widget's own rel base wherever a length of it
|
|
/// is what reached the child; where only pixels did, no length of this
|
|
/// rel base can change the child's and the pin stops here.
|
|
///
|
|
/// A child's validity maps back through the part of this widget's box,
|
|
/// where the box the child was asked in is that part; a declared length
|
|
/// places the box inside the part instead, and then only that length
|
|
/// reaches the child. A narrowed rel base is not one of these: it decides
|
|
/// what fractions under the child mean and leaves the box the part it
|
|
/// was given.
|
|
fn in_parent(
|
|
&self,
|
|
holds: LayoutHolds,
|
|
region: UiRegion,
|
|
place: PlaceDesc,
|
|
declared: Declared,
|
|
) -> LayoutHolds {
|
|
let mut result = LayoutHolds::ANY;
|
|
for axis in Axis::BOTH {
|
|
let declared = declared[axis];
|
|
let holds = holds[axis];
|
|
let at = place[axis];
|
|
let result = &mut result[axis];
|
|
// Every read became pixels against the window, so a range on
|
|
// it is already in this widget's terms.
|
|
result.window = holds.window;
|
|
// A length this widget named -- a resolved share, a box a sibling
|
|
// decided, a box it sized outright, which is its own base -- is
|
|
// not a length of this widget's rel base, so a pin on it stops
|
|
// here. So does a declaration in pixels: no length of either base
|
|
// is in it to see.
|
|
let reaches = !matches!(at.rel_base, RelBase::Len(_))
|
|
&& declared.is_none_or(|len| len.rel != Rel::ZERO);
|
|
result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
|
|
match (at.span, declared) {
|
|
// 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. A length it pinned
|
|
// is this widget's length less the part's pixels where the
|
|
// part is the whole of the box less pixels, which is the one
|
|
// shape that inverts exactly; any other part pins this
|
|
// widget's own length.
|
|
(PlaceSpan::Within(span), None) => {
|
|
let part_len = span.len();
|
|
result.region = holds.region.through(part_len);
|
|
result.region_len = holds.region_len.map(|pinned| match part_len.rel {
|
|
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
|
|
_ => self.region[axis].len(),
|
|
});
|
|
}
|
|
// Its box is a length this widget decided, from its own
|
|
// rel base or from a sibling's answer: no length of this
|
|
// widget's box reaches it, so what it holds for is a range
|
|
// on the window and none of it on that box.
|
|
_ => {
|
|
result.window = result.window.and(holds.region.through(region[axis].len()));
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
}
|
|
|
|
impl Widgets {
|
|
/// What says a widget's length on one axis without drawing it, if anything
|
|
/// does. 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 -- a
|
|
/// share included, since a share is a length only to whoever divides one,
|
|
/// and that is the parent rather than this widget.
|
|
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
|
|
// A request is a length the rule gives, and the hint below must not
|
|
// narrow the box in its place: what the request comes to is not known
|
|
// until the parent allocates, and it is the parent's answer, not this
|
|
// widget's.
|
|
let rule = &self.size_rules(id)[axis];
|
|
if rule.deferred().is_some() {
|
|
return None;
|
|
}
|
|
rule.exact().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.
|
|
self.get_dyn(id)?.size_hint(axis)
|
|
})
|
|
}
|
|
}
|
|
|
|
/// One ask of a widget: the box it draws in, what its fractions are of, and
|
|
/// what deciding those read.
|
|
pub(super) struct Ask {
|
|
pub rel_base: UiVec2,
|
|
pub region: UiRegion,
|
|
/// The place the ask came to, which a rule of the widget's own can take
|
|
/// past the box its parent offered.
|
|
pub place: PlaceDesc,
|
|
/// What the widget's box is on each axis where something says so
|
|
/// outright: its rule, its hint, or a bound the offer fell outside.
|
|
/// An intrinsic answer can still occupy less than a capped box.
|
|
pub declared: Declared,
|
|
/// Its bounds, resolved against the rel base its rules were resolved
|
|
/// against, for the answer to be held to where the box was not.
|
|
pub bounds: Bounds,
|
|
/// What the ask itself holds for, kept on the widget asked about: a rule
|
|
/// compared against the offer in pixels holds only for the windows on its
|
|
/// side of the crossing, and that range reaches whoever asked through the
|
|
/// drawing it is part of. Kept on the widget asked about rather than on
|
|
/// the asker because the root has no asker.
|
|
pub holds: LayoutHolds,
|
|
/// Inputs read against the parent before declarations choose a new base.
|
|
/// These belong to the asker; the widget's own holds describe its output box.
|
|
pub inputs: LayoutHolds,
|
|
}
|
|
|
|
impl Placing {
|
|
/// Asks about a widget at `place` of this box, with the widget's own
|
|
/// rules applied to what the place offers it. `place` is resolved: what
|
|
/// a rel base of the asker's is a fraction of, the asker worked out.
|
|
///
|
|
/// Every ask is this one, the root's included -- there the box is the
|
|
/// window and nothing above narrowed it, which is what [`Self::WINDOW`]
|
|
/// says.
|
|
pub(super) fn ask(
|
|
&self,
|
|
widgets: &Widgets,
|
|
requests: &mut RequestArena,
|
|
window: PxVec2,
|
|
id: WidgetId,
|
|
mut place: PlaceDesc,
|
|
) -> Ask {
|
|
let align = widgets.alignment(id);
|
|
let rules = widgets.size_rules(id);
|
|
let mut holds = LayoutHolds::ANY;
|
|
let mut inputs = LayoutHolds::ANY;
|
|
let mut declared = Declared::NONE;
|
|
let mut bounds = Bounds::ANY;
|
|
for axis in Axis::BOTH {
|
|
let base = place.base(axis, self.rel_base);
|
|
let stated = widgets.exact_len(id, axis);
|
|
if let Some(len) = stated.and_then(|len| len.declared()) {
|
|
if len.rel != Rel::ZERO {
|
|
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
|
}
|
|
declared[axis] = Some(len.within_len(base));
|
|
}
|
|
if let Some(request) = rules[axis].deferred() {
|
|
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
|
inputs[axis].region_len = Some(self.region[axis].len());
|
|
inputs[axis].window = Holds::at(window[axis]);
|
|
let offer = place.of(self.region, align)[axis].len();
|
|
let px = if place[axis].fit == PlaceFit::Allocated {
|
|
offer.to_px(window[axis])
|
|
} else {
|
|
let request = requests.import(request, base);
|
|
let request = requests.bounded(request, rules[axis].bound.within_len(base));
|
|
holds[axis].window = Holds::at(window[axis]);
|
|
requests
|
|
.allocate(&[request], offer.to_px(window[axis]), window[axis])
|
|
.next()
|
|
.unwrap()
|
|
};
|
|
let len = Len::from_parts(Rel::ZERO, px);
|
|
place[axis].rel_base = RelBase::Len(len);
|
|
declared[axis] = Some(len);
|
|
holds[axis].rel_base = Some(len);
|
|
continue;
|
|
}
|
|
// A share fills what the pixels and fraction beside it leave of
|
|
// the box and overflows where they are longer, which is the rule
|
|
// a span follows with one child. Only the overflow is a box of
|
|
// the widget's own: a share that fits is the box it was given,
|
|
// which is what this place already says.
|
|
let (share, kept) = self.share_past_the_offer(stated, window[axis], place, align, axis);
|
|
holds[axis].window = holds[axis].window.and(kept);
|
|
if let Some(len) = share {
|
|
place[axis] = len.as_desc().fills();
|
|
}
|
|
let bound = rules[axis].bound;
|
|
if bound != Bound::ANY {
|
|
if bound.has_fraction() {
|
|
inputs[axis].rel_base = Some(self.rel_base[axis]);
|
|
}
|
|
// A solved slot may narrow the widget's rel base, but the
|
|
// allocator evaluated its bounds against this parent's base.
|
|
let bound_base = if place[axis].fit == PlaceFit::Allocated {
|
|
self.rel_base[axis]
|
|
} else {
|
|
base
|
|
};
|
|
bounds[axis] = bound.within_len(bound_base);
|
|
let offer =
|
|
declared[axis].unwrap_or_else(|| place.of(self.region, align)[axis].len());
|
|
let (held, kept) = bounds[axis].outside(offer, window[axis]);
|
|
holds[axis].window = holds[axis].window.and(kept);
|
|
// The comparison reads the incoming box, before a bound
|
|
// replaces it. Placement keeps that decision; only an ask
|
|
// may compare a new offer.
|
|
if declared[axis].is_none() {
|
|
inputs[axis].region_len = Some(self.region[axis].len());
|
|
}
|
|
if let Some(len) = held {
|
|
declared[axis] = Some(len);
|
|
}
|
|
}
|
|
}
|
|
let (rel_base, region) =
|
|
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
|
Ask {
|
|
rel_base,
|
|
region,
|
|
place,
|
|
declared,
|
|
bounds,
|
|
holds,
|
|
inputs,
|
|
}
|
|
}
|
|
|
|
/// The box a widget's own share asks for where that is longer than the
|
|
/// box `place` gives it, and nothing where the share fits -- with the
|
|
/// windows that answer holds for, which is a range either way.
|
|
///
|
|
/// A share is a length only to whoever divides one, and nothing divides a
|
|
/// box handed to one child: what is left of it after the pixels and the
|
|
/// fraction beside the share is what the share takes, so the length comes
|
|
/// to the whole box until those are longer than it and to them once they
|
|
/// are. Only that second case is a box its parent did not give, and the
|
|
/// crossing between them is a question in pixels.
|
|
fn share_past_the_offer(
|
|
&self,
|
|
stated: Option<LayoutLen>,
|
|
window: Px,
|
|
place: PlaceDesc,
|
|
align: RegionAlign,
|
|
axis: Axis,
|
|
) -> (Option<Len>, Holds) {
|
|
// A place that is the widget's placement outright is a box its parent
|
|
// decided, and a parent that divides one has already given the share
|
|
// whatever it was owed. Only an offer -- a box with the answer still
|
|
// to be placed inside it -- is a box a share reads.
|
|
if place[axis].fit.fills() {
|
|
return (None, Holds::ANY);
|
|
}
|
|
// A share with nothing beside it is the box whatever the box is, so
|
|
// there is no comparison to make and no range to keep for one.
|
|
let Some(stated) = stated else {
|
|
return (None, Holds::ANY);
|
|
};
|
|
if stated.leftover == Weight::ZERO || stated.is_only_leftover() {
|
|
return (None, Holds::ANY);
|
|
}
|
|
let fixed = stated
|
|
.without_leftover()
|
|
.within_len(place.base(axis, self.rel_base));
|
|
let offer = place.of(self.region, align)[axis].len();
|
|
let (longer, holds) = fixed.longer_than(offer, window);
|
|
(longer.then_some(fixed), holds)
|
|
}
|
|
}
|
|
|
|
impl LayoutLen {
|
|
/// 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. An axis the parent decided
|
|
/// from the answer is the answer already.
|
|
pub(super) fn fills(&self, decided: bool) -> bool {
|
|
self.leftover != Weight::ZERO || decided
|
|
}
|
|
}
|
|
|
|
impl PlaceDesc {
|
|
/// 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.
|
|
///
|
|
/// The length it reported is a length of its rel base, 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 rel base it was reported of.
|
|
pub(super) fn placement(self, region: UiRegion, size: Size, align: RegionAlign) -> UiRegion {
|
|
let mut placed = region;
|
|
for axis in Axis::BOTH {
|
|
let reported = size[axis];
|
|
if reported.fills(self[axis].fit.fills()) {
|
|
continue;
|
|
}
|
|
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
|
}
|
|
placed
|
|
}
|
|
|
|
/// The rel base length and the box a child is asked in, in the coordinates the
|
|
/// widget asking draws in.
|
|
///
|
|
/// `own` is that widget's own box, and `place` what of it the child is
|
|
/// given, including any rel base it states -- a row's slot, or padding's rel
|
|
/// base less its pixels. The ask has already resolved declarations into
|
|
/// window lengths, so placement only aligns them inside the given box.
|
|
pub(super) fn rel_base_and_region(
|
|
self,
|
|
own: UiRegion,
|
|
parent_rel_base: UiVec2,
|
|
declared: Declared,
|
|
align: RegionAlign,
|
|
) -> (UiVec2, UiRegion) {
|
|
let given = self.of(own, align);
|
|
let mut rel_base = parent_rel_base;
|
|
let mut region = given;
|
|
for axis in Axis::BOTH {
|
|
let base = self.base(axis, parent_rel_base);
|
|
let len = declared[axis].unwrap_or(base);
|
|
rel_base[axis] = len;
|
|
if declared[axis].is_some() {
|
|
region[axis] = given[axis].place(len, align[axis]);
|
|
}
|
|
}
|
|
(rel_base, region)
|
|
}
|
|
}
|