Name the values layout carries, and say what a span's slot is
`along` said nothing about what it did. It is `Span::slot` now: the
stretch of the row between two distances from where the span starts
laying out, as a span of its own box, with the mirror for a negative
direction in one place. `far` is `row`, which is what the comment above
it already called it, and `shares` is `has_room` beside the
`any_leftover` it was folded into. `reached` now guards on the leftover
weight it divides by rather than on the numerator that happened to be
zero with it.
The pairs layout returns are named rather than positional: `Answer`
{size, holds} and `Drawn` {answer, drawing_holds} replace
`(Size, LayoutHolds)` and a three-tuple with two `LayoutHolds` in it,
which was the one shape the cold dump exists to catch. `try_reuse`
answers `bool` rather than `Option<()>`, and the four hand-written
copies of `move_idx != parent_move` are `ActiveData::is_region_node`.
`AXES` was declared in three modules; it is `Axis::BOTH`. `rel_min`,
`rel_max` and the unused `select_len` are gone -- `ZERO` and `FULL`
already said those. Three doc comments sat on `impl` blocks instead of
the single method inside them. `reposition` and `redepth` walked their
children by index, looking the parent up again per child; both take the
list and put it back. `Scroll`'s `fixed` and `fixed_len` are
`answer_px` and `answer_is_px`, which says which one is the length.
fmt, workspace clippy under `-D warnings` with and without
`layout-diagnostics`, and the workspace tests are clean. The cold dump
over 400 depth-5 trees is byte-identical to `6c84b6f`: 34,492 boxes,
no seed moved.
This commit is contained in:
1 parent
6c84b6f2cb
commit
3da1c71870
9 files changed
+164
-147
No files matched your search
@@ -9,6 +9,9 @@ pub enum Axis {
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
/// Both of them, for the layout code that asks the same question of each.
|
||||
pub const BOTH: [Self; 2] = [Self::X, Self::Y];
|
||||
|
||||
/// A per-axis pair with `aligned` on this axis and `ortho` on the other,
|
||||
/// which is what `from_axis` does for a vector.
|
||||
pub fn pair<T>(self, aligned: T, ortho: T) -> [T; 2] {
|
||||
|
||||
@@ -163,14 +163,6 @@ impl Len {
|
||||
Self::from_parts(Rel::ZERO, Px::from_f32(px))
|
||||
}
|
||||
|
||||
pub const fn rel_min() -> Self {
|
||||
Self::ZERO
|
||||
}
|
||||
|
||||
pub const fn rel_max() -> Self {
|
||||
Self::FULL
|
||||
}
|
||||
|
||||
pub const fn max(&self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel.max(other.rel),
|
||||
@@ -213,10 +205,6 @@ impl Len {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn select_len(&self, len: Len) -> Self {
|
||||
len.within_len(*self)
|
||||
}
|
||||
|
||||
pub const fn flip(&mut self) {
|
||||
self.rel = Rel::ONE.sub(self.rel);
|
||||
self.px = self.px.neg();
|
||||
|
||||
+16
-2
@@ -27,7 +27,7 @@ pub struct ActiveData {
|
||||
pub region: UiRegion,
|
||||
/// The measured answer and its dependencies. A hint-only dependency or
|
||||
/// a widget first encountered during placement has no measurement yet.
|
||||
pub answer: Option<(Size, LayoutHolds)>,
|
||||
pub answer: Option<Answer>,
|
||||
/// Asked more than once in its parent's last draw -- measured in one box
|
||||
/// and then asked in the one the parent decided. The parent's layout
|
||||
/// rests on the first answer and its drawing on the last, so only the
|
||||
@@ -82,6 +82,20 @@ impl ActiveData {
|
||||
/// all. Not `size`, which is what its last drawing reported: a drawing
|
||||
/// re-expressed in the box that answer chose is not a second answer.
|
||||
pub fn measured(&self) -> Option<Size> {
|
||||
self.answer.map(|(size, _)| size)
|
||||
self.answer.map(|answer| answer.size)
|
||||
}
|
||||
|
||||
/// Whether it owns a region node rather than sharing the one it was drawn
|
||||
/// under, which is what its two move indices being different says.
|
||||
pub fn is_region_node(&self) -> bool {
|
||||
self.move_idx != self.parent_move
|
||||
}
|
||||
}
|
||||
|
||||
/// What a widget answered when it was asked: the size it reported, and the
|
||||
/// boxes and windows that answer holds for.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Answer {
|
||||
pub size: Size,
|
||||
pub holds: LayoutHolds,
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::util::impl_axis_index;
|
||||
use crate::{Axis, Holds, Len, Px, PxVec2, UiRegion, UiVec2};
|
||||
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// What one evaluation of a widget depends on along one axis: the window
|
||||
/// lengths its reads hold for, the pixel lengths of its own box, and the
|
||||
/// symbolic lengths of that box and of its rel base where either one is what
|
||||
@@ -101,7 +99,8 @@ impl LayoutHolds {
|
||||
}
|
||||
|
||||
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||
AXES.into_iter()
|
||||
Axis::BOTH
|
||||
.into_iter()
|
||||
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
||||
}
|
||||
}
|
||||
|
||||
+21
-22
@@ -10,7 +10,6 @@ use crate::{
|
||||
},
|
||||
ui::render_state::{DrawInfo, Placing},
|
||||
};
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
pub struct Painter<'a> {
|
||||
@@ -155,8 +154,8 @@ impl<'a> Painter<'a> {
|
||||
/// 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 state_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||
for axis in AXES {
|
||||
fn resolve_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||
for axis in Axis::BOTH {
|
||||
if let Some(span) = place[axis].narrows_rel_base() {
|
||||
let len = span.len();
|
||||
let stated = (len != Len::FULL).then(|| len.within_len(self.rel_base(axis)));
|
||||
@@ -181,7 +180,7 @@ impl<'a> Painter<'a> {
|
||||
id: &'s StrongWidget<W>,
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.state_rel_base(place.into());
|
||||
let place = self.resolve_rel_base(place.into());
|
||||
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());
|
||||
@@ -198,7 +197,7 @@ impl<'a> Painter<'a> {
|
||||
self.children.push(id.id());
|
||||
}
|
||||
let px = rel_base.to_px(self.window);
|
||||
let (size, answer_holds, holds) = self.state.draw_inner(
|
||||
let drawn = self.state.draw_inner(
|
||||
id.id(),
|
||||
DrawInfo {
|
||||
layer: self.layer,
|
||||
@@ -217,8 +216,8 @@ impl<'a> Painter<'a> {
|
||||
None,
|
||||
self.rsc,
|
||||
);
|
||||
let holds = self.in_parent(holds, region, place, declared);
|
||||
let answer_holds = self.in_parent(answer_holds, region, place, declared);
|
||||
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)),
|
||||
@@ -226,7 +225,7 @@ impl<'a> Painter<'a> {
|
||||
DrawResult {
|
||||
child: id,
|
||||
painter: self,
|
||||
size,
|
||||
size: drawn.answer.size,
|
||||
answer_holds,
|
||||
}
|
||||
}
|
||||
@@ -255,8 +254,8 @@ impl<'a> Painter<'a> {
|
||||
id: &'s StrongWidget<W>,
|
||||
place: impl Into<PlaceDesc>,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let place = self.state_rel_base(place.into());
|
||||
let states_rel_base = AXES
|
||||
let place = self.resolve_rel_base(place.into());
|
||||
let states_rel_base = Axis::BOTH
|
||||
.iter()
|
||||
.any(|&axis| place[axis].stated_rel_base().is_some());
|
||||
if states_rel_base || !self.children.contains(&id.id()) {
|
||||
@@ -579,10 +578,10 @@ impl PrimitiveLike for &TextureHandle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves what a child depends on into this widget's own terms: this
|
||||
/// method's `impl` block is where a `Painter`'s own boxes are, so it takes
|
||||
/// only what the child was asked with.
|
||||
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
|
||||
@@ -602,7 +601,7 @@ impl Painter<'_> {
|
||||
declared: Declared,
|
||||
) -> LayoutHolds {
|
||||
let mut result = LayoutHolds::ANY;
|
||||
for axis in AXES {
|
||||
for axis in Axis::BOTH {
|
||||
let declared = declared[axis];
|
||||
let holds = holds[axis];
|
||||
let result = &mut result[axis];
|
||||
@@ -677,16 +676,16 @@ impl LayoutLen {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
impl PlaceDesc {
|
||||
/// 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(crate) fn placement(
|
||||
self,
|
||||
region: UiRegion,
|
||||
@@ -695,7 +694,7 @@ impl PlaceDesc {
|
||||
align: RegionAlign,
|
||||
) -> UiRegion {
|
||||
let mut placed = region;
|
||||
for axis in AXES {
|
||||
for axis in Axis::BOTH {
|
||||
let reported = size[axis];
|
||||
if reported.fills(declared[axis], self[axis].does_fill()) {
|
||||
continue;
|
||||
@@ -725,7 +724,7 @@ impl PlaceDesc {
|
||||
let given = self.of(own, align);
|
||||
let mut rel_base = parent_rel_base;
|
||||
let mut region = given;
|
||||
for axis in AXES {
|
||||
for axis in Axis::BOTH {
|
||||
let base = self[axis]
|
||||
.stated_rel_base()
|
||||
.unwrap_or_else(|| parent_rel_base[axis]);
|
||||
|
||||
+82
-78
@@ -1,14 +1,12 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::{
|
||||
ActiveData, Axis, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
|
||||
Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc,
|
||||
UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
ActiveData, Answer, Axis, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx,
|
||||
MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion,
|
||||
UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// Where a widget is drawn: what its parent decides about the draw besides
|
||||
/// the boxes themselves.
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -36,6 +34,14 @@ pub(super) struct DrawInfo {
|
||||
pub px: PxVec2,
|
||||
}
|
||||
|
||||
/// What one draw of a widget came to: the answer it gave, and the boxes and
|
||||
/// windows the drawing that gave it holds for. The two are separate ranges --
|
||||
/// a drawing can be invalid where its answer still stands.
|
||||
pub(super) struct Drawn {
|
||||
pub answer: Answer,
|
||||
pub drawing_holds: LayoutHolds,
|
||||
}
|
||||
|
||||
/// What a widget's children are placed in: its own box, the coordinates its
|
||||
/// drawing is in, and what else one ask of a child is decided from.
|
||||
pub(super) struct Placing {
|
||||
@@ -113,7 +119,7 @@ impl UiRenderState {
|
||||
// it will ask either again.
|
||||
let answer = active
|
||||
.answer
|
||||
.is_some_and(|(_, holds)| holds.contains(size, active.rel_base, active.region));
|
||||
.is_some_and(|answer| answer.holds.contains(size, active.rel_base, active.region));
|
||||
answer && active.holds.contains(size, active.rel_base, active.region)
|
||||
});
|
||||
if !stands {
|
||||
@@ -207,7 +213,7 @@ impl UiRenderState {
|
||||
info: DrawInfo,
|
||||
mut old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds, LayoutHolds) {
|
||||
) -> Drawn {
|
||||
let old_parent = old
|
||||
.as_ref()
|
||||
.or_else(|| self.active.get(&id))
|
||||
@@ -233,9 +239,9 @@ impl UiRenderState {
|
||||
.then(|| self.retained_answer(id, region, info))
|
||||
.flatten()
|
||||
.and_then(|answer| {
|
||||
let placed = info.placed.placement(region, answer.0, declared, align);
|
||||
let placed = info.placed.placement(region, answer.size, declared, align);
|
||||
self.try_reuse(id, region, placed, info, rsc)
|
||||
.map(|()| answer)
|
||||
.then_some(answer)
|
||||
});
|
||||
let answer = reused.unwrap_or_else(|| {
|
||||
if old.is_none() {
|
||||
@@ -245,7 +251,7 @@ impl UiRenderState {
|
||||
// Where the drawing goes: the part its parent gave it, with the
|
||||
// answer placed inside that part on any axis the parent left
|
||||
// open.
|
||||
let placed = info.placed.placement(region, answer.0, declared, align);
|
||||
let placed = info.placed.placement(region, answer.size, declared, align);
|
||||
if placed != region {
|
||||
self.relocate(id, placed, info, rsc);
|
||||
}
|
||||
@@ -273,7 +279,10 @@ impl UiRenderState {
|
||||
{
|
||||
old_parent.children.retain(|child| *child != id);
|
||||
}
|
||||
(answer.0, answer.1, drawing_holds)
|
||||
Drawn {
|
||||
answer,
|
||||
drawing_holds,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls a widget's `draw` and keeps what it drew in `region`.
|
||||
@@ -284,7 +293,7 @@ impl UiRenderState {
|
||||
info: DrawInfo,
|
||||
old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds) {
|
||||
) -> Answer {
|
||||
let rel_base = info.rel_base;
|
||||
let (move_idx, region, retired_move) = match info.region_node {
|
||||
// A node entry is only a translation. Its local box keeps the
|
||||
@@ -395,9 +404,11 @@ impl UiRenderState {
|
||||
// was offered reports the height it needs.
|
||||
debug_assert!(
|
||||
mask == info.mask
|
||||
|| AXES
|
||||
.into_iter()
|
||||
.all(|axis| size.within_box(region, self.output_size, axis)),
|
||||
|| Axis::BOTH.into_iter().all(|axis| size.within_box(
|
||||
region,
|
||||
self.output_size,
|
||||
axis
|
||||
)),
|
||||
"'{}' ({id:?}) clips to {px:?} and reports {size}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
@@ -417,7 +428,7 @@ impl UiRenderState {
|
||||
// that many pixels of this window -- the same pin a widget that read
|
||||
// its rel base took for its drawing.
|
||||
let mut own_holds = own;
|
||||
for axis in AXES {
|
||||
for axis in Axis::BOTH {
|
||||
let fraction = rules[axis].exact().is_some_and(|len| len.rel != Rel::ZERO);
|
||||
if fraction {
|
||||
own_holds[axis].rel_base = Some(info.rel_base[axis]);
|
||||
@@ -489,7 +500,10 @@ impl UiRenderState {
|
||||
};
|
||||
rsc.on_draw(&active);
|
||||
self.active.insert(id, active);
|
||||
(size, answer_holds)
|
||||
Answer {
|
||||
size,
|
||||
holds: answer_holds,
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps a region node's entry across redraws because descendants retain
|
||||
@@ -516,23 +530,17 @@ impl UiRenderState {
|
||||
/// drawing ended up. Alignment is exactly that case: the first box is the
|
||||
/// question and the smaller placed box holds the drawing. Whether the
|
||||
/// answer is stale at all is its caller's question, asked once there.
|
||||
fn retained_answer(
|
||||
&self,
|
||||
id: WidgetId,
|
||||
region: UiRegion,
|
||||
info: DrawInfo,
|
||||
) -> Option<(Size, LayoutHolds)> {
|
||||
fn retained_answer(&self, id: WidgetId, region: UiRegion, info: DrawInfo) -> Option<Answer> {
|
||||
let active = self.active.get(&id)?;
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
if !active.drawn
|
||||
|| has_region_node != info.region_node
|
||||
|| active.is_region_node() != info.region_node
|
||||
|| active.parent_move != info.parent_move
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let answer = active.answer?;
|
||||
answer
|
||||
.1
|
||||
.holds
|
||||
.contains(self.output_size, info.rel_base, region)
|
||||
.then_some(answer)
|
||||
}
|
||||
@@ -546,7 +554,7 @@ impl UiRenderState {
|
||||
placed: UiRegion,
|
||||
info: DrawInfo,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> Option<()> {
|
||||
) -> bool {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseAttempts);
|
||||
if rsc.widgets().needs_redraw.contains(&id) {
|
||||
@@ -555,19 +563,20 @@ impl UiRenderState {
|
||||
diag::bump(Counter::ReuseDirty);
|
||||
diag::reuse(id, ReuseOutcome::Dirty);
|
||||
}
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
let active = self.active.get(&id)?;
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return false;
|
||||
};
|
||||
if !active.drawn {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::reuse(id, ReuseOutcome::Undrawn);
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
if has_region_node != info.region_node {
|
||||
if active.is_region_node() != info.region_node {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseWrongNode);
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
// Drawn on another layer: the drawing sits in that layer's list and
|
||||
// paints at its moment, which no amount of geometry says. A container
|
||||
@@ -580,10 +589,10 @@ impl UiRenderState {
|
||||
diag::bump(Counter::ReuseWrongLayer);
|
||||
diag::reuse(id, ReuseOutcome::WrongLayer);
|
||||
}
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
if active.parent_mask != info.mask {
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||
// longer sits in, and its slot names the wrong parent.
|
||||
@@ -593,7 +602,7 @@ impl UiRenderState {
|
||||
diag::bump(Counter::ReuseWrongParent);
|
||||
diag::reuse(id, ReuseOutcome::WrongParent);
|
||||
}
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
// In pixels, because the box is a fraction of the window and that
|
||||
// may be what changed -- an unchanged fraction of a window half the
|
||||
@@ -607,7 +616,7 @@ impl UiRenderState {
|
||||
// Which of the three said no, so a rel base that redraws more
|
||||
// than it should says where to look. They overlap: a drawing
|
||||
// can be outside two of them at once.
|
||||
for axis in AXES {
|
||||
for axis in Axis::BOTH {
|
||||
let holds = active.holds[axis];
|
||||
let len = region[axis].len();
|
||||
let window = self.output_size[axis];
|
||||
@@ -628,10 +637,10 @@ impl UiRenderState {
|
||||
diag::bump(Counter::ReuseOutside);
|
||||
diag::reuse(id, ReuseOutcome::Outside);
|
||||
}
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
self.relocate(id, placed, info, rsc);
|
||||
Some(())
|
||||
true
|
||||
}
|
||||
|
||||
/// Puts a retained drawing where its parent now has it, without drawing:
|
||||
@@ -644,14 +653,14 @@ impl UiRenderState {
|
||||
"'{}' ({id:?}) placed while marked to draw",
|
||||
rsc.widgets().label(id)
|
||||
);
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
let local = match has_region_node {
|
||||
let is_region_node = active.is_region_node();
|
||||
let local = match is_region_node {
|
||||
true => placed.at_origin(),
|
||||
false => placed,
|
||||
};
|
||||
let moved = active.placement != local;
|
||||
let slot = active.move_idx;
|
||||
if has_region_node {
|
||||
if is_region_node {
|
||||
self.moves.set(slot, placed.as_translation());
|
||||
}
|
||||
if moved {
|
||||
@@ -663,23 +672,13 @@ impl UiRenderState {
|
||||
active.placed = info.placed;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
match (moved, has_region_node) {
|
||||
(true, true) => diag::bump(Counter::ReuseMoved),
|
||||
(true, false) => diag::bump(Counter::ReuseRemapped),
|
||||
(false, _) => diag::bump(Counter::ReuseExact),
|
||||
}
|
||||
diag::reuse(
|
||||
id,
|
||||
if moved {
|
||||
if has_region_node {
|
||||
ReuseOutcome::Moved
|
||||
} else {
|
||||
ReuseOutcome::Remapped
|
||||
}
|
||||
} else {
|
||||
ReuseOutcome::Exact
|
||||
},
|
||||
);
|
||||
let (counter, outcome) = match (moved, is_region_node) {
|
||||
(true, true) => (Counter::ReuseMoved, ReuseOutcome::Moved),
|
||||
(true, false) => (Counter::ReuseRemapped, ReuseOutcome::Remapped),
|
||||
(false, _) => (Counter::ReuseExact, ReuseOutcome::Exact),
|
||||
};
|
||||
diag::bump(counter);
|
||||
diag::reuse(id, outcome);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,7 +711,7 @@ impl UiRenderState {
|
||||
parent: Some(at.id),
|
||||
depth: at.depth + 1,
|
||||
parent_move: at.move_idx,
|
||||
region_node: active.move_idx != active.parent_move,
|
||||
region_node: active.is_region_node(),
|
||||
mask: at.mask,
|
||||
rel_base,
|
||||
region,
|
||||
@@ -755,16 +754,18 @@ impl UiRenderState {
|
||||
move_idx: active.move_idx,
|
||||
mask: active.mask,
|
||||
};
|
||||
let children = active.children.len();
|
||||
for index in 0..children {
|
||||
let child = self.active[&id].children[index];
|
||||
// Taken out and put back so that placing a child can borrow the state
|
||||
// it needs; nothing on that path reads this widget's own child list.
|
||||
let children = std::mem::take(&mut self.active.get_mut(&id).unwrap().children);
|
||||
for &child in &children {
|
||||
self.place_child(child, &at, rsc);
|
||||
}
|
||||
self.active.get_mut(&id).unwrap().children = children;
|
||||
}
|
||||
|
||||
/// A reused subtree keeps its shape, so every widget in it moves by the
|
||||
/// same amount -- and where the top of it did not move, none of it did,
|
||||
/// which is what makes this free in the ordinary case.
|
||||
/// A reused subtree keeps its shape, so each widget in it keeps its depth
|
||||
/// under the top -- and where the top's own depth did not change, none of
|
||||
/// them did, which is what makes this free in the ordinary case.
|
||||
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
||||
let Some(active) = self.active.get_mut(&id) else {
|
||||
return;
|
||||
@@ -773,18 +774,21 @@ impl UiRenderState {
|
||||
return;
|
||||
}
|
||||
active.depth = depth;
|
||||
let children = active.children.len();
|
||||
for index in 0..children {
|
||||
let child = self.active[&id].children[index];
|
||||
// Taken out and put back so the walk can borrow the state it needs;
|
||||
// it only ever goes further down, so it reads no list but its own.
|
||||
let children = std::mem::take(&mut active.children);
|
||||
for &child in &children {
|
||||
self.redepth(child, depth + 1);
|
||||
}
|
||||
self.active.get_mut(&id).unwrap().children = children;
|
||||
}
|
||||
|
||||
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||
return true;
|
||||
};
|
||||
AXES.into_iter()
|
||||
Axis::BOTH
|
||||
.into_iter()
|
||||
.all(|axis| widget.size_hint(axis).is_none_or(|hint| hint == size[axis]))
|
||||
}
|
||||
|
||||
@@ -1118,13 +1122,13 @@ impl UiRenderState {
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
|
||||
let old = self.remove(id, false, rsc);
|
||||
let answer = self.draw_inner(id, info, old, rsc);
|
||||
let drawn = self.draw_inner(id, info, old, rsc);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
// A wider contract does not invalidate the guarantee the parent kept.
|
||||
// Retain that guarantee so widening and narrowing back do not churn it.
|
||||
if let Some((size, holds)) = was_answer
|
||||
&& answer.0 == size
|
||||
&& answer.1.covers(holds)
|
||||
if let Some(was) = was_answer
|
||||
&& drawn.answer.size == was.size
|
||||
&& drawn.answer.holds.covers(was.holds)
|
||||
{
|
||||
active.answer = was_answer;
|
||||
}
|
||||
@@ -1169,11 +1173,11 @@ impl UiRenderState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether what a widget reports along `axis` is inside the box it drew in.
|
||||
/// Both are lengths of the window, so the comparison is in its pixels. A
|
||||
/// share is a length only to whoever divides one, so it is not a claim about
|
||||
/// this box and cannot exceed it.
|
||||
impl Size {
|
||||
/// Whether what a widget reports along `axis` is inside the box it drew
|
||||
/// in. Both are lengths of the window, so the comparison is in its
|
||||
/// pixels. A share is a length only to whoever divides one, so it is not
|
||||
/// a claim about this box and cannot exceed it.
|
||||
fn within_box(self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
let len = self[axis];
|
||||
let window = window[axis];
|
||||
|
||||
@@ -16,9 +16,9 @@ impl Widget for Scroll {
|
||||
let answer_len = painter
|
||||
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
|
||||
.len(self.axis);
|
||||
let fixed = painter.to_px(answer_len.without_leftover(), self.axis);
|
||||
let answer_px = painter.to_px(answer_len.without_leftover(), self.axis);
|
||||
self.container_len = container_len;
|
||||
self.content_len = fixed.max(container_len);
|
||||
self.content_len = answer_px.max(container_len);
|
||||
|
||||
if self.snap_end {
|
||||
self.amt = self.content_len - self.container_len;
|
||||
@@ -31,10 +31,10 @@ impl Widget for Scroll {
|
||||
// the drawing holds for that length alone. One scrolled part way sits
|
||||
// where it is until the box shrinks past what is left of it. Kept to
|
||||
// the end, it moves with every length.
|
||||
let fixed_len = answer_len.is_px();
|
||||
if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
||||
painter.holds(self.axis, fixed..=Px::MAX);
|
||||
} else if fixed_len && !self.snap_end {
|
||||
let answer_is_px = answer_len.is_px();
|
||||
if answer_is_px && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
||||
painter.holds(self.axis, answer_px..=Px::MAX);
|
||||
} else if answer_is_px && !self.snap_end {
|
||||
let left = self.content_len - self.amt;
|
||||
painter.holds(self.axis, Px::MIN..=left);
|
||||
}
|
||||
|
||||
+33
-23
@@ -10,21 +10,18 @@ pub struct Span {
|
||||
impl Widget for Span {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let axis = self.dir.axis;
|
||||
// The row: this span's own box, as a length of the rel base its children
|
||||
// are laid out against. Its start is nothing's business -- a slot is
|
||||
// a length from it -- so what this reads is the length alone.
|
||||
let far = painter.region_len(axis);
|
||||
let along = |from: Len, to: Len| match self.dir.sign {
|
||||
Sign::Pos => UiSpan::new(from, to),
|
||||
Sign::Neg => UiSpan::new(far - to, far - from),
|
||||
};
|
||||
// The row this span lays its children out along, as a length of the
|
||||
// rel base they are laid out against. Where it starts is nothing's
|
||||
// business -- a slot is a length from there -- so what this reads is
|
||||
// the length alone.
|
||||
let row = painter.region_len(axis);
|
||||
// A length for every child before their final slots are chosen: from
|
||||
// a hint where one says, and from drawing otherwise. The rel base passes
|
||||
// through unchanged, so `rel(0.5)` is half the area this span was
|
||||
// given whatever else is in it and wherever this child sits among
|
||||
// them; what a drawn child is asked in is the room left from the
|
||||
// cursor, because a text has to wrap at the width actually there.
|
||||
let mut cursor = Len::rel_min();
|
||||
let mut cursor = Len::ZERO;
|
||||
let mut lens = Vec::with_capacity(self.children.len());
|
||||
for child in &self.children {
|
||||
let len = match painter.size_hint(child, axis) {
|
||||
@@ -33,7 +30,7 @@ impl Widget for Span {
|
||||
// Across itself the child sits where its own alignment
|
||||
// says, in the whole of the row: a span is what contains
|
||||
// its children there, and nothing divides that axis.
|
||||
let room = along(cursor, far).shifted_desc().on_axis(axis);
|
||||
let room = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
|
||||
painter.widget_at(child, room).len(axis)
|
||||
}
|
||||
};
|
||||
@@ -55,7 +52,7 @@ impl Widget for Span {
|
||||
|
||||
// What is left for the shares to divide: the row less everything
|
||||
// fixed, as a length of the rel base rather than a number of pixels.
|
||||
let room = far - total.without_leftover();
|
||||
let room = row - total.without_leftover();
|
||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||
// itself, and answered back through the same expression, so the
|
||||
@@ -65,10 +62,10 @@ impl Widget for Span {
|
||||
// sign of `room.rel`, which `through` already reads. What the
|
||||
// generated oracle checks is the consequence, since which children
|
||||
// exist at all turns on this.
|
||||
let mut shares = false;
|
||||
if total.leftover > Weight::ZERO {
|
||||
shares = painter.to_px(room, axis) > Px::ZERO;
|
||||
let holds = match shares {
|
||||
let any_leftover = total.leftover > Weight::ZERO;
|
||||
let has_room = any_leftover && painter.to_px(room, axis) > Px::ZERO;
|
||||
if any_leftover {
|
||||
let holds = match has_room {
|
||||
true => Holds::from(Px::STEP..=Px::MAX),
|
||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
||||
};
|
||||
@@ -87,24 +84,26 @@ impl Widget for Span {
|
||||
// than stepped from the last child: the share of the room is
|
||||
// rounded, and taking each end from the one before it would carry
|
||||
// every rounding along the row.
|
||||
let mut fixed = Len::rel_min();
|
||||
let mut fixed = Len::ZERO;
|
||||
let mut taken = Weight::ZERO;
|
||||
let mut ortho = LayoutLen::ZERO;
|
||||
let reached = |fixed: Len, taken: Weight| match taken == Weight::ZERO {
|
||||
true => fixed,
|
||||
false => fixed + room.scale(Rel::ratio(taken, total.leftover)),
|
||||
// Nothing divides the room where no child asked for any of it, and a
|
||||
// ratio of a whole of nothing has no answer.
|
||||
let reached = |fixed: Len, taken: Weight| match any_leftover {
|
||||
false => fixed,
|
||||
true => fixed + room.scale(Rel::ratio(taken, total.leftover)),
|
||||
};
|
||||
for (child, &len) in self.children.iter().zip(&lens) {
|
||||
// A child asking for nothing but a part of what is left over,
|
||||
// when nothing is, is not drawn at all. One that also asked for
|
||||
// pixels or a fraction keeps those and overflows.
|
||||
if len.is_only_leftover() && !shares {
|
||||
if len.is_only_leftover() && !has_room {
|
||||
painter.undraw(child);
|
||||
fixed.px += self.gap;
|
||||
continue;
|
||||
}
|
||||
let from = reached(fixed, taken);
|
||||
if len.leftover > Weight::ZERO && shares {
|
||||
if len.leftover > Weight::ZERO && has_room {
|
||||
taken += len.leftover;
|
||||
}
|
||||
fixed += len.without_leftover();
|
||||
@@ -116,9 +115,9 @@ impl Widget for Span {
|
||||
// it, since a text wraps at the width it is actually given. A
|
||||
// fixed child's slot is its own answer, so a drawing made in the
|
||||
// room is put there as it is, and one not made yet is made here.
|
||||
let slot = along(from, to);
|
||||
let slot = self.slot(row, from, to);
|
||||
let mut place = slot.shifted_desc().fills().on_axis(axis);
|
||||
if len.leftover > Weight::ZERO && shares {
|
||||
if len.leftover > Weight::ZERO && has_room {
|
||||
place = place.rel_base(axis, slot.len());
|
||||
}
|
||||
let used = painter.place_at(child, place).len(!axis);
|
||||
@@ -152,6 +151,17 @@ impl Widget for Span {
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// The stretch of the row between two distances from where this span
|
||||
/// starts laying children out, as a span of its own box. A negative
|
||||
/// direction lays out from the far end, so the same two distances mirror
|
||||
/// in a row `row` long.
|
||||
fn slot(&self, row: Len, from: Len, to: Len) -> UiSpan {
|
||||
match self.dir.sign {
|
||||
Sign::Pos => from.to(to),
|
||||
Sign::Neg => (row - to).to(row - from),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty(dir: Dir) -> Self {
|
||||
Self {
|
||||
children: Vec::new(),
|
||||
|
||||
@@ -125,7 +125,7 @@ fn moving_an_ordinary_subtree_remaps_its_mask() {
|
||||
let active = &h.render.active[&masked.id()];
|
||||
assert_eq!(
|
||||
h.rsc.ui().masks[active.mask.idx()].region,
|
||||
UiRegion::new(UiSpan::new(Len::px(150.0), Len::rel_max()), UiSpan::FULL,)
|
||||
UiRegion::new(UiSpan::new(Len::px(150.0), Len::FULL), UiSpan::FULL,)
|
||||
);
|
||||
assert_corners!(h, inner, (150, 0), (400, 200));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user