Make the frame a length of the window and the box a region
There is one coordinate unit, the window. Every box in the tree is a region in window units and a widget's frame is a length in the same units, which is only what fractions resolve against, so the box need not be the frame and padding can take from both without either becoming the other. A region node's entry is a translation -- a rel 1 region anchored where its box starts -- rather than a box, so nothing composes a frame back up a chain and a node that moves is one entry write. Padding is then an inset of both: its pixels come off the frame, so rel(1.0) under it fills the padded widget rather than overflowing it, and off the box, so what is drawn sits inside. A length a container decides for a child's frame is a length of the window like everything else here -- a row's slot, padding's frame less its pixels, or the box a stack's sizing child decided, which arrives as Part::Sized -- because a slot of a row is not a fraction of anything the row can name, the same reason a node entry is a translation. A declaration is a fraction of whichever of those reached it, and is the only one that also places the box. Frame validity is a pin beside the box's, not a range: a range of window pixels cannot say which frame an answer is a fraction of, since two frames are different lengths at the same window size. A widget pins its frame by reading it or by being answered with it under a fractional rule, and the pin composes up wherever a length of this frame is what reached the child. Also here, because the diagnosis needed them: the shrinker reports the shrunk tree's own divergence with each level's frame, ask, box and size warm against cold, and there is a size-resize case -- a change and then a resize, the order that shows an answer kept as a fraction of the wrong length, which every other case compares at the window it was made at. Three defects the reports found, each pinned: a rule changed over two pads relocated the column under them instead of dividing it again (seed 59, depth 5, resize-size), a share inside padding had the padding taken off twice, and a root resolved its own rule twice. fmt and clippy clean with and without layout-diagnostics, 121 suite, 20 core, 11 generated, the 400-seed depth-5 shrinker over all sixteen cases. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ef87ebfcf
commit
1512d8418b
15 files changed
+680
-444
No files matched your search
+172
-204
@@ -1,10 +1,10 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||
use crate::ui::painter::{declared_lens, frame_and_extent, narrowed_by, part_of, placed_extent};
|
||||
use crate::ui::painter::{declared_lens, frame_and_extent, placed_extent};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
|
||||
Moves, Painter, Part, PixelRegion, Place, PxVec2, Size, StrongWidget, UiRegion, UiRsc, Weight,
|
||||
WidgetId, Widgets,
|
||||
Moves, Painter, Part, PixelRegion, Place, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc,
|
||||
UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||
util::{HashMap, Vec2},
|
||||
};
|
||||
|
||||
@@ -20,28 +20,23 @@ pub(super) struct DrawInfo {
|
||||
pub parent_move: MoveIdx,
|
||||
pub region_node: bool,
|
||||
pub mask: MaskIdx,
|
||||
/// The frame in the parent widget's frame coordinates, before
|
||||
/// composition. Its length is the same on every ask of the widget.
|
||||
pub frame: UiRegion,
|
||||
/// That frame composed into `parent_move`'s coordinates, which is what
|
||||
/// the widget's own drawing is written within.
|
||||
pub frame_abs: UiRegion,
|
||||
/// The box the widget is asked in, in the frame's own coordinates: the
|
||||
/// part of the parent's own box that `place` names, before the widget's
|
||||
/// answer is placed inside it.
|
||||
/// What a fraction declared or reported under this widget is a fraction
|
||||
/// of, as a length of the window.
|
||||
pub frame: UiVec2,
|
||||
/// The box the widget is asked in, in its parent region node's
|
||||
/// coordinates.
|
||||
pub part: UiRegion,
|
||||
/// Where the widget is put, and where it was asked, as parts of the
|
||||
/// parent's box. See [`Place`]. The two are one ask's place until the
|
||||
/// parent places the answer somewhere else.
|
||||
pub place: [Place; 2],
|
||||
pub offer_place: [Place; 2],
|
||||
/// The length the frame was narrowed to on each axis, as a length of
|
||||
/// the parent's frame, where anything narrowed it.
|
||||
/// A frame the parent decided for it on each axis, as a length of the
|
||||
/// window, which the widget's own declaration is a fraction of.
|
||||
pub narrow: [Option<Len>; 2],
|
||||
/// Whether the parent already asked about this widget in this draw.
|
||||
pub re_asked: bool,
|
||||
/// The frame in pixels: one multiply from the parent's own, which is
|
||||
/// where every pixel length in layout comes from.
|
||||
/// The frame in pixels, resolved once against the window.
|
||||
pub px: PxVec2,
|
||||
}
|
||||
|
||||
@@ -58,10 +53,8 @@ impl DrawInfo {
|
||||
pub(super) struct Placing {
|
||||
pub id: WidgetId,
|
||||
pub extent: UiRegion,
|
||||
/// The widget's frame in the coordinates its children compose within:
|
||||
/// `FULL` where it is a region node, since its box is that node.
|
||||
pub local: UiRegion,
|
||||
pub px: PxVec2,
|
||||
pub frame: UiVec2,
|
||||
pub window: PxVec2,
|
||||
pub depth: usize,
|
||||
pub move_idx: MoveIdx,
|
||||
pub mask: MaskIdx,
|
||||
@@ -126,23 +119,20 @@ impl UiRenderState {
|
||||
self.resized = true;
|
||||
let Some(root) = self.old_root else { return };
|
||||
let stands = self.active.get(&root).is_some_and(|active| {
|
||||
let px = active.frame.size().to_px(size);
|
||||
// Nothing above the root chose anything, so the box it was first
|
||||
// asked about is the whole of its frame.
|
||||
active.answers_at(px, active.offer_part) && active.holds.contains(px, active.offer_part)
|
||||
active.answers_at(size, active.offer_part)
|
||||
&& active.holds.contains(size, active.frame, active.offer_part)
|
||||
});
|
||||
if !stands {
|
||||
widgets.needs_redraw.insert(root);
|
||||
}
|
||||
}
|
||||
|
||||
/// The root is asked about in the output: the window is where a fraction
|
||||
/// becomes pixels rather than a box of its own, so the root's box is the
|
||||
/// first length threaded down. Its own rules narrow that box, and where
|
||||
/// they do the narrowed box is also the offer -- nothing above it chose
|
||||
/// anything else.
|
||||
fn root_info(&self, region: UiRegion) -> DrawInfo {
|
||||
let px = region.size().to_px(self.output_size);
|
||||
/// The root is asked about in the output. Its own rules narrow both its
|
||||
/// frame and box; nothing above it chose a different one.
|
||||
fn root_info(&self, frame: UiVec2, extent: UiRegion) -> DrawInfo {
|
||||
let px = frame.to_px(self.output_size);
|
||||
DrawInfo {
|
||||
layer: 0,
|
||||
parent: None,
|
||||
@@ -150,9 +140,8 @@ impl UiRenderState {
|
||||
parent_move: MoveIdx::NONE,
|
||||
region_node: false,
|
||||
mask: MaskIdx::NONE,
|
||||
frame: region,
|
||||
frame_abs: region,
|
||||
part: UiRegion::FULL,
|
||||
frame,
|
||||
part: extent,
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
narrow: [None; 2],
|
||||
@@ -203,17 +192,24 @@ impl UiRenderState {
|
||||
let _layout = diag::timer(TimerKind::FullLayout);
|
||||
self.clear(rsc);
|
||||
if let Some(id) = root {
|
||||
let region = Self::root_region(id.id(), rsc.widgets());
|
||||
let info = self.root_info(region);
|
||||
let (frame, extent) = Self::root_layout(id.id(), rsc.widgets());
|
||||
let info = self.root_info(frame, extent);
|
||||
self.draw_inner(id.id(), info, None, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
/// The root's frame: the window, narrowed by the root's own rules. Its
|
||||
/// extent is that frame, since nothing above it chose anything else.
|
||||
fn root_region(id: WidgetId, widgets: &Widgets) -> UiRegion {
|
||||
let narrow = narrowed_by(declared_lens(widgets, id));
|
||||
frame_and_extent(UiRegion::FULL, narrow, widgets.alignment(id)).0
|
||||
/// The root's frame and box: the window, taken in by the root's own
|
||||
/// rules. Nothing above it narrowed anything or chose where it goes, so
|
||||
/// its declaration is the whole of what decides either.
|
||||
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
|
||||
frame_and_extent(
|
||||
UiRegion::FULL,
|
||||
UiVec2::FULL_SIZE,
|
||||
[Place::Within(Part::All); 2],
|
||||
[None; 2],
|
||||
declared_lens(widgets, id),
|
||||
widgets.alignment(id),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn draw_inner(
|
||||
@@ -223,11 +219,11 @@ impl UiRenderState {
|
||||
mut old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds, LayoutHolds) {
|
||||
let (frame, part) = (info.frame_abs, info.part);
|
||||
let part = info.part;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::DrawRequests);
|
||||
diag::draw_request(id, info.parent, frame, info.px, info.region_node);
|
||||
diag::draw_request(id, info.parent, part, info.px, info.region_node);
|
||||
}
|
||||
let align = rsc.widgets().alignment(id);
|
||||
let declared = declared_lens(rsc.widgets(), id);
|
||||
@@ -245,8 +241,7 @@ impl UiRenderState {
|
||||
.flatten()
|
||||
.and_then(|answer| {
|
||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
||||
self.try_reuse(id, frame, part, extent, info, rsc)
|
||||
.map(|()| answer)
|
||||
self.try_reuse(id, part, extent, info, rsc).map(|()| answer)
|
||||
});
|
||||
let answer = reused.unwrap_or_else(|| {
|
||||
if old.is_none() {
|
||||
@@ -258,7 +253,7 @@ impl UiRenderState {
|
||||
// open.
|
||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
||||
if extent != part {
|
||||
self.reposition(id, frame, extent, info, rsc);
|
||||
self.relocate(id, extent, info, rsc);
|
||||
}
|
||||
answer
|
||||
});
|
||||
@@ -268,7 +263,6 @@ impl UiRenderState {
|
||||
// Whoever asked owns how the boxes were reached: the frame it stated,
|
||||
// and what of its own box it asked in. A local redraw asks the same
|
||||
// question again from these.
|
||||
active.frame_abs = frame;
|
||||
active.frame = info.frame;
|
||||
active.narrow = info.narrow;
|
||||
active.re_asked = info.re_asked;
|
||||
@@ -301,29 +295,30 @@ impl UiRenderState {
|
||||
old: Option<ActiveData>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> (Size, LayoutHolds) {
|
||||
let frame = info.frame_abs;
|
||||
let (move_idx, local, retired_move) = match info.region_node {
|
||||
// Its box becomes its movable region, so it draws in that
|
||||
// region's coordinates and its box is one entry to rewrite.
|
||||
let frame = info.frame;
|
||||
let (move_idx, extent, retired_move) = match info.region_node {
|
||||
// A node entry is only a translation. Its local box keeps the
|
||||
// same window-unit length as the box in its parent's node.
|
||||
true => (
|
||||
self.move_slot(id, info.parent_move, frame),
|
||||
UiRegion::FULL,
|
||||
self.move_slot(id, info.parent_move, translation(extent)),
|
||||
local_region(extent),
|
||||
None,
|
||||
),
|
||||
// Keep the old entry alive until every descendant has migrated.
|
||||
// Reusing its index sooner could make an old parent look current.
|
||||
false => (info.parent_move, frame, self.slots.remove(&id)),
|
||||
false => (info.parent_move, extent, self.slots.remove(&id)),
|
||||
};
|
||||
let old_children = old.map_or_else(Vec::new, |old| old.children);
|
||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||
let px = info.px;
|
||||
|
||||
let window = self.output_size;
|
||||
let mut painter = Painter {
|
||||
state: self,
|
||||
frame: local,
|
||||
frame,
|
||||
extent,
|
||||
extent_len: [None; 2],
|
||||
px,
|
||||
window,
|
||||
mask: info.mask,
|
||||
layer: info.layer,
|
||||
own_layer: info.layer,
|
||||
@@ -334,6 +329,7 @@ impl UiRenderState {
|
||||
children: Vec::new(),
|
||||
size_deps: Vec::new(),
|
||||
frame_own: [Holds::ANY; 2],
|
||||
frame_own_len: [None; 2],
|
||||
under: Vec::new(),
|
||||
extent_own: [Holds::ANY; 2],
|
||||
answer_under: LayoutHolds::ANY,
|
||||
@@ -358,7 +354,7 @@ impl UiRenderState {
|
||||
rsc: _,
|
||||
frame: _,
|
||||
extent: _,
|
||||
px: _,
|
||||
window: _,
|
||||
mask,
|
||||
textures,
|
||||
primitives,
|
||||
@@ -369,6 +365,7 @@ impl UiRenderState {
|
||||
children,
|
||||
size_deps,
|
||||
frame_own,
|
||||
frame_own_len,
|
||||
under,
|
||||
move_idx,
|
||||
layer,
|
||||
@@ -384,11 +381,23 @@ impl UiRenderState {
|
||||
);
|
||||
// A rule wins on the axis it names, and the draw answers the rest.
|
||||
// Applied here so it is one place rather than every widget that could
|
||||
// carry one, and so the widget under a rule never learns of it.
|
||||
// carry one, and so the widget under a rule never learns of it. The
|
||||
// frame is the answer where the rule gave a length outright: it was
|
||||
// resolved into the frame when the child was asked, and resolving it
|
||||
// again here would take the fraction of a fraction.
|
||||
let rules = rsc.widgets().size_rules(id);
|
||||
let ruled = |axis: Axis, reported: LayoutLen| match rules.axis(axis).exact() {
|
||||
None => reported,
|
||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||
rel: info.frame.axis(axis).rel,
|
||||
px: info.frame.axis(axis).px,
|
||||
leftover: Weight::ZERO,
|
||||
},
|
||||
Some(len) => len.within_len(info.frame.axis(axis)),
|
||||
};
|
||||
let size = Size {
|
||||
x: rules.x.apply(size.x),
|
||||
y: rules.y.apply(size.y),
|
||||
x: ruled(Axis::X, size.x),
|
||||
y: ruled(Axis::Y, size.y),
|
||||
};
|
||||
// A widget that clipped its contents to its box drew nothing outside
|
||||
// it, so reporting more than the box asks to be placed at a length it
|
||||
@@ -396,7 +405,10 @@ impl UiRenderState {
|
||||
// Overflowing is otherwise ordinary: a text too tall for the box it
|
||||
// was offered reports the height it needs.
|
||||
debug_assert!(
|
||||
mask == info.mask || AXES.into_iter().all(|axis| within_box(size, px, axis)),
|
||||
mask == info.mask
|
||||
|| AXES
|
||||
.into_iter()
|
||||
.all(|axis| within_box(size, extent, self.output_size, axis)),
|
||||
"'{}' ({id:?}) clips to {px:?} and reports {size}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
@@ -408,8 +420,23 @@ impl UiRenderState {
|
||||
if let Some(idx) = retired_move {
|
||||
self.moves.remove(idx);
|
||||
}
|
||||
// A rule that is a fraction of the frame is answered with the
|
||||
// frame's own length, so the answer is that frame's and not just
|
||||
// that many pixels of this window -- the same pin a widget that read
|
||||
// its frame took for its drawing.
|
||||
let frame_len = AXES.map(|axis| {
|
||||
let fraction = rules
|
||||
.axis(axis)
|
||||
.exact()
|
||||
.is_some_and(|len| len.rel != Rel::ZERO);
|
||||
match fraction {
|
||||
true => Some(info.frame.axis(axis)),
|
||||
false => frame_own_len[axis as usize],
|
||||
}
|
||||
});
|
||||
let own_holds = LayoutHolds {
|
||||
frame: frame_own,
|
||||
frame_len,
|
||||
extent: extent_own,
|
||||
extent_len,
|
||||
};
|
||||
@@ -418,7 +445,7 @@ impl UiRenderState {
|
||||
.into_iter()
|
||||
.fold(answer_holds, |holds, (_, child)| holds.and(child));
|
||||
debug_assert!(
|
||||
holds.contains(px, extent),
|
||||
holds.contains(self.output_size, info.frame, extent),
|
||||
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
@@ -436,8 +463,7 @@ impl UiRenderState {
|
||||
parent_move: move_idx,
|
||||
region_node: false,
|
||||
mask,
|
||||
frame: UiRegion::FULL,
|
||||
frame_abs: UiRegion::FULL,
|
||||
frame: UiVec2::FULL_SIZE,
|
||||
part: UiRegion::FULL,
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
@@ -453,7 +479,6 @@ impl UiRenderState {
|
||||
|
||||
let active = ActiveData {
|
||||
id,
|
||||
frame_abs: frame,
|
||||
extent,
|
||||
frame: info.frame,
|
||||
narrow: info.narrow,
|
||||
@@ -525,26 +550,10 @@ impl UiRenderState {
|
||||
return None;
|
||||
}
|
||||
let answer = active.answer?;
|
||||
answer.1.contains(info.px, part).then_some(answer)
|
||||
}
|
||||
|
||||
/// The pixel lengths of a widget's frame, which is what a local redraw
|
||||
/// needs to ask the question its parent asked.
|
||||
///
|
||||
/// It is threaded down from the window a length of a box at a time, and
|
||||
/// this takes the same steps back up: a widget's frame is a length of its
|
||||
/// parent's frame, and that chain has no coordinate frame in it, so a
|
||||
/// region node cannot break it -- and it lands on the number a cold
|
||||
/// layout computes rather than near it.
|
||||
fn asked_px(&self, id: WidgetId) -> PxVec2 {
|
||||
let active = &self.active[&id];
|
||||
// Nothing above the root: the window is where a fraction becomes
|
||||
// pixels, which is also the whole of the frame the root is given.
|
||||
let parent_px = match active.parent.and_then(|p| self.active.get(&p)) {
|
||||
Some(parent) => self.asked_px(parent.id),
|
||||
None => self.output_size,
|
||||
};
|
||||
active.frame.size().to_px(parent_px)
|
||||
answer
|
||||
.1
|
||||
.contains(self.output_size, info.frame, part)
|
||||
.then_some(answer)
|
||||
}
|
||||
|
||||
/// Keeps the retained drawing if its contract holds for `part`, the box
|
||||
@@ -552,7 +561,6 @@ impl UiRenderState {
|
||||
fn try_reuse(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
frame: UiRegion,
|
||||
part: UiRegion,
|
||||
extent: UiRegion,
|
||||
info: DrawInfo,
|
||||
@@ -603,10 +611,10 @@ impl UiRenderState {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
// In pixels, because the frame is a fraction of its parent's and
|
||||
// that may be what changed -- an unchanged fraction of a box half the
|
||||
// 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
|
||||
// size is half the widget.
|
||||
if !active.holds.contains(info.px, part) {
|
||||
if !active.holds.contains(self.output_size, info.frame, part) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
// Which of the three said no, so a frame that redraws more
|
||||
@@ -618,10 +626,14 @@ impl UiRenderState {
|
||||
if holds.extent_len[n].is_some_and(|pinned| pinned != part.axis(axis).len()) {
|
||||
diag::bump(Counter::OutsidePinnedLen);
|
||||
}
|
||||
if !holds.frame[n].contains(info.px.axis(axis)) {
|
||||
if !holds.frame[n].contains(self.output_size.axis(axis))
|
||||
|| holds.frame_len[n].is_some_and(|pinned| pinned != info.frame.axis(axis))
|
||||
{
|
||||
diag::bump(Counter::OutsideFrame);
|
||||
}
|
||||
if !holds.extent[n].contains(part.axis(axis).len().to_px(info.px.axis(axis))) {
|
||||
if !holds.extent[n]
|
||||
.contains(part.axis(axis).len().to_px(self.output_size.axis(axis)))
|
||||
{
|
||||
diag::bump(Counter::OutsideExtent);
|
||||
}
|
||||
}
|
||||
@@ -630,21 +642,14 @@ impl UiRenderState {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
self.relocate(id, frame, extent, info, rsc);
|
||||
self.relocate(id, extent, info, rsc);
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Puts a retained drawing where its parent now has it, without drawing:
|
||||
/// a moved frame recomposes the subtree or rewrites its node, and a box
|
||||
/// of another length re-expresses everything inside it.
|
||||
fn relocate(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
frame: UiRegion,
|
||||
extent: UiRegion,
|
||||
info: DrawInfo,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
/// a widget with a node of its own writes that node's translation, and
|
||||
/// one without re-expresses its own drawing and everything inside it.
|
||||
fn relocate(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
let active = &self.active[&id];
|
||||
debug_assert!(
|
||||
!rsc.widgets().needs_redraw.contains(&id),
|
||||
@@ -652,22 +657,20 @@ impl UiRenderState {
|
||||
rsc.widgets().label(id)
|
||||
);
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
let extent_moved = active.extent != extent;
|
||||
let moved = active.frame_abs != frame;
|
||||
let local = match has_region_node {
|
||||
true => local_region(extent),
|
||||
false => extent,
|
||||
};
|
||||
let moved = active.extent != local;
|
||||
let slot = active.move_idx;
|
||||
if moved {
|
||||
if has_region_node {
|
||||
self.moves.set(slot, frame);
|
||||
} else {
|
||||
self.recompose_subtree(id, frame, info.parent_move, rsc);
|
||||
}
|
||||
if has_region_node {
|
||||
self.moves.set(slot, translation(extent));
|
||||
}
|
||||
if extent_moved {
|
||||
self.reposition(id, frame, extent, info, rsc);
|
||||
if moved {
|
||||
self.reposition(id, local, info, rsc);
|
||||
}
|
||||
self.redepth(id, info.depth);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.frame_abs = frame;
|
||||
active.frame = info.frame;
|
||||
active.place = info.place;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
@@ -709,7 +712,7 @@ impl UiRenderState {
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = &self.active[&child];
|
||||
let (frame, part) = Self::re_ask(active, at.extent, place);
|
||||
let (frame, part) = Self::ask_again(active, at, place);
|
||||
let extent = placed_extent(
|
||||
part,
|
||||
active.measured().unwrap_or(active.size),
|
||||
@@ -725,63 +728,50 @@ impl UiRenderState {
|
||||
region_node: active.move_idx != active.parent_move,
|
||||
mask: at.mask,
|
||||
frame,
|
||||
frame_abs: frame.within(&at.local),
|
||||
part,
|
||||
place,
|
||||
offer_place: active.offer_place,
|
||||
narrow: active.narrow,
|
||||
re_asked: active.re_asked,
|
||||
px: frame.size().to_px(at.px),
|
||||
px: frame.to_px(at.window),
|
||||
};
|
||||
self.relocate(child, info.frame_abs, extent, info, rsc);
|
||||
self.relocate(child, extent, info, rsc);
|
||||
}
|
||||
|
||||
/// The frame and the box a widget is given at `place` of its parent's
|
||||
/// box, from what it already has. A frame's length is the same on every
|
||||
/// ask, so a narrowed frame is put back where it sits in the part rather
|
||||
/// than resolved a second time.
|
||||
fn re_ask(
|
||||
active: &ActiveData,
|
||||
parent_box: UiRegion,
|
||||
place: [Place; 2],
|
||||
) -> (UiRegion, UiRegion) {
|
||||
frame_and_extent(part_of(parent_box, place), active.narrow, active.own_align)
|
||||
/// The frame and the box a widget already drawn is given at `place` of
|
||||
/// the box its parent is being taken as. What narrowed its frame and what
|
||||
/// it declared are its own record's, so both are resolved against that
|
||||
/// parent's frame again exactly as the first ask resolved them.
|
||||
fn ask_again(active: &ActiveData, at: &Placing, place: [Place; 2]) -> (UiVec2, UiRegion) {
|
||||
frame_and_extent(
|
||||
at.extent,
|
||||
at.frame,
|
||||
place,
|
||||
active.narrow,
|
||||
active.declared,
|
||||
active.own_align,
|
||||
)
|
||||
}
|
||||
|
||||
/// Re-places everything inside a widget whose own box moved. Every child
|
||||
/// is placed as a part of that box, so each one's new box is its retained
|
||||
/// part re-added to the new start -- and a child whose own box then did
|
||||
/// not change is not touched at all.
|
||||
fn reposition(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
frame: UiRegion,
|
||||
extent: UiRegion,
|
||||
info: DrawInfo,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
fn reposition(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.frame_abs = frame;
|
||||
active.extent = extent;
|
||||
let local = if info.region_node {
|
||||
UiRegion::FULL
|
||||
} else {
|
||||
frame
|
||||
};
|
||||
for primitive in &active.primitives {
|
||||
let handle = &primitive.handle;
|
||||
*self.layers[handle.layer].region_mut(handle) =
|
||||
primitive.region.within(&extent).within(&local);
|
||||
*self.layers[handle.layer].region_mut(handle) = primitive.region.within(&extent);
|
||||
}
|
||||
if let Some(mask_region) = active.mask_region {
|
||||
rsc.ui_mut().masks.get_mut(active.mask).region =
|
||||
mask_region.within(&extent).within(&local);
|
||||
rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.within(&extent);
|
||||
}
|
||||
let at = Placing {
|
||||
id,
|
||||
extent,
|
||||
local,
|
||||
px: info.px,
|
||||
frame: info.frame,
|
||||
window: self.output_size,
|
||||
depth: info.depth,
|
||||
move_idx: active.move_idx,
|
||||
mask: active.mask,
|
||||
@@ -811,38 +801,6 @@ impl UiRenderState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replays the original local compositions, including their rounding order.
|
||||
/// A region node terminates the walk because its contents name its slot.
|
||||
fn recompose_subtree(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
frame: UiRegion,
|
||||
parent_move: MoveIdx,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.frame_abs = frame;
|
||||
if active.move_idx != parent_move {
|
||||
self.moves.set(active.move_idx, frame);
|
||||
return;
|
||||
}
|
||||
let extent = active.extent;
|
||||
for primitive in &active.primitives {
|
||||
let handle = &primitive.handle;
|
||||
*self.layers[handle.layer].region_mut(handle) =
|
||||
primitive.region.within(&extent).within(&frame);
|
||||
}
|
||||
if let Some(local) = active.mask_region {
|
||||
rsc.ui_mut().masks.get_mut(active.mask).region = local.within(&extent).within(&frame);
|
||||
}
|
||||
let children = active.children.len();
|
||||
for index in 0..children {
|
||||
let child = self.active[&id].children[index];
|
||||
let local = self.active[&child].frame;
|
||||
self.recompose_subtree(child, local.within(&frame), parent_move, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||
return true;
|
||||
@@ -913,9 +871,8 @@ impl UiRenderState {
|
||||
id,
|
||||
ActiveData {
|
||||
id,
|
||||
frame_abs: UiRegion::FULL,
|
||||
extent: UiRegion::FULL,
|
||||
frame: UiRegion::FULL,
|
||||
frame: UiVec2::FULL_SIZE,
|
||||
narrow: [None; 2],
|
||||
place: [Place::Within(Part::All); 2],
|
||||
offer_place: [Place::Within(Part::All); 2],
|
||||
@@ -1102,9 +1059,8 @@ impl UiRenderState {
|
||||
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
||||
let active = self.active.get(&id.id())?;
|
||||
active.drawn.then(|| {
|
||||
let placed = active.extent.within(&active.frame_abs);
|
||||
self.moves
|
||||
.resolve(active.parent_move, placed)
|
||||
.resolve(active.move_idx, active.extent)
|
||||
.to_px(self.output_size)
|
||||
})
|
||||
}
|
||||
@@ -1146,10 +1102,10 @@ impl UiRenderState {
|
||||
// box is its own to work out again against the output. Every other
|
||||
// widget was given one.
|
||||
let Some(parent) = active.parent else {
|
||||
let region = Self::root_region(id, rsc.widgets());
|
||||
let (frame, extent) = Self::root_layout(id, rsc.widgets());
|
||||
let info = DrawInfo {
|
||||
mask: active.parent_mask,
|
||||
..self.root_info(region)
|
||||
..self.root_info(frame, extent)
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
@@ -1157,7 +1113,6 @@ impl UiRenderState {
|
||||
self.draw_inner(id, info, old, rsc);
|
||||
return true;
|
||||
};
|
||||
let px = self.asked_px(id);
|
||||
let (was_answer, was_holds, was_place) = (active.answer, active.holds, active.place);
|
||||
// The question its parent asked, asked again: the same place of the
|
||||
// box the parent was asked in, which is the box the parent's own
|
||||
@@ -1165,13 +1120,7 @@ impl UiRenderState {
|
||||
// parent's answer put its own drawing is not a question anybody
|
||||
// asked, and nothing is asked in it here either.
|
||||
let asked = self.placing_of(parent, self.active[&parent].offer_part);
|
||||
let (frame, part) = Self::re_ask(active, asked.extent, active.offer_place);
|
||||
debug_assert_eq!(
|
||||
frame.size(),
|
||||
active.frame.size(),
|
||||
"'{}' ({id:?}) asked again in a frame of another length",
|
||||
rsc.widgets().label(id)
|
||||
);
|
||||
let (frame, part) = Self::ask_again(active, &asked, active.offer_place);
|
||||
let info = DrawInfo {
|
||||
layer: active.layer,
|
||||
parent: active.parent,
|
||||
@@ -1180,13 +1129,12 @@ impl UiRenderState {
|
||||
region_node: rsc.widgets().is_region_node(id),
|
||||
mask: active.parent_mask,
|
||||
frame,
|
||||
frame_abs: frame.within(&asked.local),
|
||||
part,
|
||||
place: active.offer_place,
|
||||
offer_place: active.offer_place,
|
||||
narrow: active.narrow,
|
||||
re_asked: false,
|
||||
px,
|
||||
px: frame.to_px(self.output_size),
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
@@ -1202,7 +1150,9 @@ impl UiRenderState {
|
||||
{
|
||||
active.answer = was_answer;
|
||||
}
|
||||
if active.holds.covers(was_holds) && was_holds.contains(px, active.extent) {
|
||||
if active.holds.covers(was_holds)
|
||||
&& was_holds.contains(self.output_size, active.frame, active.extent)
|
||||
{
|
||||
active.holds = was_holds;
|
||||
}
|
||||
if active.answer != was_answer || active.holds != was_holds {
|
||||
@@ -1232,11 +1182,8 @@ impl UiRenderState {
|
||||
Placing {
|
||||
id,
|
||||
extent,
|
||||
local: match active.move_idx != active.parent_move {
|
||||
true => UiRegion::FULL,
|
||||
false => active.frame_abs,
|
||||
},
|
||||
px: self.asked_px(id),
|
||||
frame: active.frame,
|
||||
window: self.output_size,
|
||||
depth: active.depth,
|
||||
move_idx: active.move_idx,
|
||||
mask: active.mask,
|
||||
@@ -1245,12 +1192,33 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
/// Whether what a widget reports along `axis` is inside the box it drew in.
|
||||
/// 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(size: Size, px: PxVec2, axis: Axis) -> bool {
|
||||
/// 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(size: Size, extent: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||
let len = size.axis(axis);
|
||||
let box_len = px.axis(axis);
|
||||
len.leftover != Weight::ZERO || box_len.mul(len.rel) + len.px <= box_len
|
||||
let window = window.axis(axis);
|
||||
len.leftover != Weight::ZERO
|
||||
|| Len::from_parts(len.rel, len.px).to_px(window) <= extent.axis(axis).len().to_px(window)
|
||||
}
|
||||
|
||||
/// A box in a fresh region node keeps its window-unit length and starts at
|
||||
/// that node's origin.
|
||||
fn local_region(region: UiRegion) -> UiRegion {
|
||||
let size = region.size();
|
||||
UiRegion::new(
|
||||
UiSpan::new(Len::ZERO, size.x),
|
||||
UiSpan::new(Len::ZERO, size.y),
|
||||
)
|
||||
}
|
||||
|
||||
/// A region node changes only the origin. A full relative span anchored at
|
||||
/// the box start composes as that translation in both the CPU and shader.
|
||||
fn translation(region: UiRegion) -> UiRegion {
|
||||
UiRegion {
|
||||
x: UiSpan::new(region.x.start, region.x.start + Len::FULL),
|
||||
y: UiSpan::new(region.y.start, region.y.start + Len::FULL),
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiRenderState {
|
||||
|
||||
Reference in new issue
Block a user