Track retained layout validity explicitly
This commit is contained in:
1 parent
691e3eb23c
commit
29c7881c8a
25 files changed
+836
-795
No files matched your search
+142
-128
@@ -1,14 +1,18 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter};
|
||||
use crate::{
|
||||
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
||||
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||
Axis, Holds, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||
render::{
|
||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||
PrimitiveKind, TexturePrimitive,
|
||||
},
|
||||
ui::render_state::DrawInfo,
|
||||
util::Vec2,
|
||||
};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a> {
|
||||
@@ -21,14 +25,21 @@ pub struct Painter<'a> {
|
||||
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
|
||||
/// about is the one recorded as its offer.
|
||||
/// 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 box this widget was first asked about in, in pixels.
|
||||
pub(super) offered_px: Vec2,
|
||||
/// Whether this draw is in that box, 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>,
|
||||
/// Offered pixel axes which can affect the size this draw reports.
|
||||
pub(super) size_box_inputs: [bool; 2],
|
||||
pub(super) size_output_inputs: [bool; 2],
|
||||
/// 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 slot this widget's primitives are positioned through: its own if
|
||||
/// its parent placed it, otherwise the nearest ancestor that has one.
|
||||
pub(super) move_idx: MoveIdx,
|
||||
@@ -90,14 +101,7 @@ impl<'a> Painter<'a> {
|
||||
|
||||
/// 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> {
|
||||
let declared = self.declared_lens(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 region = match declared.iter().any(Option::is_some) {
|
||||
true => declared_box(UiRegion::FULL, declared).within(&self.region),
|
||||
false => self.region,
|
||||
};
|
||||
self.widget_at(id, region, false, declared)
|
||||
self.widget_at(id, UiRegion::FULL, false)
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
@@ -106,9 +110,7 @@ impl<'a> Painter<'a> {
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let declared = self.declared_lens(id);
|
||||
let region = declared_box(region, declared).within(&self.region);
|
||||
self.widget_at(id, region, false, declared)
|
||||
self.widget_at(id, region, false)
|
||||
}
|
||||
|
||||
/// What a widget declares its lengths to be, which whoever draws it
|
||||
@@ -120,7 +122,7 @@ impl<'a> Painter<'a> {
|
||||
let Some(widget) = self.rsc.widgets().get_dyn(id.id()) else {
|
||||
return [None; 2];
|
||||
};
|
||||
[Axis::X, Axis::Y].map(|axis| declared_len(widget, axis))
|
||||
AXES.map(|axis| declared_len(widget, axis))
|
||||
}
|
||||
|
||||
/// Draws a child this widget decides the box of, and may decide again
|
||||
@@ -136,39 +138,75 @@ impl<'a> Painter<'a> {
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::PlaceCalls);
|
||||
let declared = self.declared_lens(id);
|
||||
let region = declared_box(region, declared).within(&self.region);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::placed(id.id(), self.id, region);
|
||||
self.widget_at(id, region, true, declared)
|
||||
self.widget_at(id, region, true)
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// `region` in this widget's own coordinates, and with the child's
|
||||
/// declared lengths still to be taken.
|
||||
fn widget_at<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
slotted: bool,
|
||||
declared: [Option<Len>; 2],
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let declared = self.declared_lens(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),
|
||||
false => region,
|
||||
};
|
||||
let within = match local == UiRegion::FULL {
|
||||
true => self.region,
|
||||
false => local.within(&self.region),
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if slotted {
|
||||
diag::placed(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 offer = match first_ask {
|
||||
true => local,
|
||||
false => self.state.active.get(&id.id()).map_or(local, |a| a.offer),
|
||||
};
|
||||
let answers_offer = self.at_offer && local == offer;
|
||||
let size = self.state.draw_inner(
|
||||
self.layer,
|
||||
id.id(),
|
||||
region,
|
||||
Some(self.id),
|
||||
self.depth + 1,
|
||||
self.move_idx,
|
||||
slotted,
|
||||
self.mask,
|
||||
within,
|
||||
DrawInfo {
|
||||
layer: self.layer,
|
||||
parent: Some(self.id),
|
||||
depth: self.depth + 1,
|
||||
parent_move: self.move_idx,
|
||||
slotted,
|
||||
mask: self.mask,
|
||||
offer,
|
||||
offered_px: self.px_within_offer(offer),
|
||||
},
|
||||
None,
|
||||
self.rsc,
|
||||
);
|
||||
self.offer(id.id(), region);
|
||||
if let Some(active) = self.state.active.get_mut(&id.id()) {
|
||||
active.declared = declared;
|
||||
let active = self.state.active.get_mut(&id.id()).unwrap();
|
||||
active.declared = declared;
|
||||
if answers_offer {
|
||||
active.answer = (active.size, active.holds);
|
||||
}
|
||||
// Whatever the child's drawing 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(active.holds[axis as usize].through(local.axis(axis).len()));
|
||||
}
|
||||
DrawResult {
|
||||
child: id,
|
||||
@@ -191,7 +229,7 @@ impl<'a> Painter<'a> {
|
||||
Some(hint) => {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::HintHits);
|
||||
self.depend_on_hint(id);
|
||||
self.depend_on(id);
|
||||
Some(hint)
|
||||
}
|
||||
None => {
|
||||
@@ -202,87 +240,66 @@ impl<'a> Painter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A retained child length valid under the region it is about to be
|
||||
/// offered. Unlike a hint, this is contextual: it is kept only when none
|
||||
/// of the offered pixel axes which produced it changed.
|
||||
/// 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<Len> {
|
||||
let region = region.within(&self.region);
|
||||
self.offer(child.id(), region);
|
||||
let declared = self.declared_lens(child);
|
||||
let local = declared_box(region, declared);
|
||||
let within = local.within(&self.region);
|
||||
let first_ask = self.offer(child.id());
|
||||
if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) {
|
||||
active.offer = local;
|
||||
}
|
||||
if let Some(hint) = self.size_hint(child, axis) {
|
||||
return Some(hint);
|
||||
}
|
||||
self.retained_size(child, region)
|
||||
.map(|size| size.axis(axis))
|
||||
}
|
||||
|
||||
/// `region` in this widget's own coordinates.
|
||||
fn retained_size<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> Option<Size> {
|
||||
let (size, box_inputs, output_inputs) =
|
||||
let px = self.state.px_of(self.move_idx, within);
|
||||
let (size, holds) =
|
||||
self.state
|
||||
.retained_size(child.id(), region, self.move_idx, self.rsc.widgets())?;
|
||||
.retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::RetainedSizeHits);
|
||||
self.depend_on_size_inputs(child, box_inputs, output_inputs);
|
||||
Some(size)
|
||||
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(size.axis(axis))
|
||||
}
|
||||
|
||||
/// Records the box a child was first asked about in this draw. Any later
|
||||
/// box this draw gives it was decided knowing its answer, so a size the
|
||||
/// child measures there is not an answer to this widget's question.
|
||||
fn offer(&mut self, child: WidgetId, region: UiRegion) {
|
||||
if self.offered.contains(&child) {
|
||||
return;
|
||||
/// 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);
|
||||
let px = self.state.px_of(self.move_idx, region);
|
||||
if let Some(active) = self.state.active.get_mut(&child) {
|
||||
active.offered_px = px;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Depends on a length the child gave without being drawn. A hint is
|
||||
/// context-free, so this depends on the child but on no pixel axis.
|
||||
fn depend_on_hint<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||
self.depend_on_size_inputs(child, [false; 2], [false; 2]);
|
||||
/// The pixel size of a part of the box this widget was asked in.
|
||||
fn px_within_offer(&self, local: UiRegion) -> Vec2 {
|
||||
let size = local.size();
|
||||
Vec2::new(
|
||||
size.x.to_px(self.offered_px.x),
|
||||
size.y.to_px(self.offered_px.y),
|
||||
)
|
||||
}
|
||||
|
||||
/// Depends on a size the child produced by drawing, which carries
|
||||
/// whatever the child read to produce it.
|
||||
fn depend_on_drawn_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||
let (box_inputs, output_inputs) = self
|
||||
.state
|
||||
.active
|
||||
.get(&child.id())
|
||||
.map_or(([false; 2], [false; 2]), |active| {
|
||||
(active.size_box_inputs, active.size_output_inputs)
|
||||
});
|
||||
self.depend_on_size_inputs(child, box_inputs, output_inputs);
|
||||
}
|
||||
|
||||
fn depend_on_size_inputs<W: ?Sized>(
|
||||
&mut self,
|
||||
child: &StrongWidget<W>,
|
||||
box_inputs: [bool; 2],
|
||||
output_inputs: [bool; 2],
|
||||
) {
|
||||
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||
if !self.size_deps.contains(&child.id()) {
|
||||
self.size_deps.push(child.id());
|
||||
}
|
||||
for (own, child) in self.size_box_inputs.iter_mut().zip(box_inputs) {
|
||||
*own |= child;
|
||||
}
|
||||
for (own, child) in self.size_output_inputs.iter_mut().zip(output_inputs) {
|
||||
*own |= child;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_text<'b>(
|
||||
@@ -327,45 +344,42 @@ impl<'a> Painter<'a> {
|
||||
self.region
|
||||
}
|
||||
|
||||
/// The output's size in pixels. A widget that reads it draws again when
|
||||
/// the output changes, since nothing else can put that right.
|
||||
pub fn output_size(&mut self) -> Vec2 {
|
||||
self.size_output_inputs = [true; 2];
|
||||
self.state.output_size
|
||||
}
|
||||
|
||||
/// One axis of the output in pixels. Prefer this to [`Self::output_size`]
|
||||
/// when the other axis cannot affect the size this widget reports.
|
||||
pub fn output_len(&mut self, axis: Axis) -> f32 {
|
||||
self.size_output_inputs[axis as usize] = true;
|
||||
self.state.output_size.axis(axis)
|
||||
}
|
||||
|
||||
/// This widget's box in pixels. Resolved against the output's size and
|
||||
/// the boxes it sits within, so a widget that reads it draws again when
|
||||
/// the output changes.
|
||||
/// 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) -> Vec2 {
|
||||
self.size_box_inputs = [true; 2];
|
||||
let region = self.state.moves.resolve(self.move_idx, self.region);
|
||||
region.size().to_px(self.state.output_size)
|
||||
let px = self.state.px_of(self.move_idx, self.region);
|
||||
for (own, len) in self.own.iter_mut().zip([px.x, px.y]) {
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(len);
|
||||
}
|
||||
}
|
||||
px
|
||||
}
|
||||
|
||||
/// One axis of this widget's box in pixels. Prefer this to
|
||||
/// [`Self::px_size`] when the other axis cannot affect the reported size.
|
||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||
pub fn px_len(&mut self, axis: Axis) -> f32 {
|
||||
self.size_box_inputs[axis as usize] = true;
|
||||
self.px_len_for_draw(axis)
|
||||
let len = self.state.px_of(self.move_idx, self.region).axis(axis);
|
||||
let own = &mut self.own[axis as usize];
|
||||
if *own == Holds::ANY {
|
||||
*own = Holds::at(len);
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
/// One axis of this widget's box in pixels, for a draw whose reported
|
||||
/// size does not follow from it -- a clamp or a position. Nothing records
|
||||
/// the read, so a size that does depend on it would go stale.
|
||||
pub fn px_len_for_draw(&self, axis: Axis) -> f32 {
|
||||
let region = self.state.moves.resolve(self.move_idx, self.region);
|
||||
region
|
||||
.size()
|
||||
.axis(axis)
|
||||
.to_px(self.state.output_size.axis(axis))
|
||||
/// 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, range: RangeInclusive<f32>) {
|
||||
let holds = Holds::from(range);
|
||||
debug_assert!(
|
||||
holds.contains(self.state.px_of(self.move_idx, self.region).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 {
|
||||
@@ -405,7 +419,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
|
||||
diag::bump(Counter::SizeReads);
|
||||
diag::size_read(self.child.id(), self.painter.id, self.size);
|
||||
}
|
||||
self.painter.depend_on_drawn_size(self.child);
|
||||
self.painter.depend_on(self.child);
|
||||
self.size
|
||||
}
|
||||
|
||||
@@ -451,7 +465,7 @@ pub(crate) fn declared_len(widget: &dyn Widget, axis: Axis) -> Option<Len> {
|
||||
/// reserved the space hands back the same length, so this is the identity
|
||||
/// for it.
|
||||
fn declared_box(mut region: UiRegion, declared: [Option<Len>; 2]) -> UiRegion {
|
||||
for (axis, len) in [Axis::X, Axis::Y].into_iter().zip(declared) {
|
||||
for (axis, len) in AXES.into_iter().zip(declared) {
|
||||
let Some(len) = len else { continue };
|
||||
let span = region.axis_mut(axis);
|
||||
span.end = span.start + UiScalar::new(len.rel, len.px);
|
||||
|
||||
Reference in new issue
Block a user