Replace placement calls with region nodes
This commit is contained in:
1 parent
f437495309
commit
71c9c39523
23 files changed
+374
-170
No files matched your search
+134
-49
@@ -17,8 +17,7 @@ pub(super) struct DrawInfo {
|
||||
pub parent: Option<WidgetId>,
|
||||
pub depth: usize,
|
||||
pub parent_move: MoveIdx,
|
||||
/// Whether the widget gets a slot of its own to be placed through.
|
||||
pub slotted: bool,
|
||||
pub region_node: bool,
|
||||
pub mask: MaskIdx,
|
||||
/// The box it was first asked about in, as a part of its parent's, and
|
||||
/// that box in pixels.
|
||||
@@ -88,7 +87,7 @@ impl UiRenderState {
|
||||
parent: None,
|
||||
depth: 1,
|
||||
parent_move: self.root_move,
|
||||
slotted: false,
|
||||
region_node: false,
|
||||
mask: MaskIdx::NONE,
|
||||
offer: UiRegion::FULL,
|
||||
offered_px: self.output_size,
|
||||
@@ -169,7 +168,7 @@ impl UiRenderState {
|
||||
info.parent,
|
||||
region,
|
||||
self.px_of(info.parent_move, region),
|
||||
info.slotted,
|
||||
info.region_node,
|
||||
);
|
||||
}
|
||||
if self.active.contains_key(&id) {
|
||||
@@ -181,14 +180,17 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
// draw widget
|
||||
let (move_idx, local) = match info.slotted {
|
||||
// Its box becomes its slot's, so it draws in the slot's own
|
||||
// coordinates and the box it was given is one entry to rewrite.
|
||||
true => (self.move_slot(id, info.parent_move, region), UiRegion::FULL),
|
||||
false => {
|
||||
self.drop_slot(id);
|
||||
(info.parent_move, region)
|
||||
}
|
||||
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.
|
||||
true => (
|
||||
self.move_slot(id, info.parent_move, region),
|
||||
UiRegion::FULL,
|
||||
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, region, self.slots.remove(&id)),
|
||||
};
|
||||
let (old_children, old_answer) = match old {
|
||||
Some(old) => (old.children, Some(old.answer)),
|
||||
@@ -257,8 +259,8 @@ impl UiRenderState {
|
||||
let holds = [own[0].and(under[0]), own[1].and(under[1])];
|
||||
debug_assert!(
|
||||
holds[0].contains(px.x) && holds[1].contains(px.y),
|
||||
"'{}' ({id:?}) drew something that does not hold for its own box",
|
||||
rsc.widgets().label(id)
|
||||
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
||||
rsc.widgets().label(id),
|
||||
);
|
||||
|
||||
for c in &old_children {
|
||||
@@ -266,6 +268,9 @@ impl UiRenderState {
|
||||
self.undraw_rec(*c, rsc);
|
||||
}
|
||||
}
|
||||
if let Some(idx) = retired_move {
|
||||
self.moves.remove(idx);
|
||||
}
|
||||
// What it asked about and did not draw is still something it asked,
|
||||
// and a change there has to reach it. Asking answered whatever mark
|
||||
// it had: a hint is read live, and a drawing is not kept past one.
|
||||
@@ -278,7 +283,7 @@ impl UiRenderState {
|
||||
parent: Some(id),
|
||||
depth: info.depth + 1,
|
||||
parent_move: move_idx,
|
||||
slotted: false,
|
||||
region_node: false,
|
||||
mask,
|
||||
offer: UiRegion::FULL,
|
||||
offered_px: px,
|
||||
@@ -317,9 +322,8 @@ impl UiRenderState {
|
||||
size
|
||||
}
|
||||
|
||||
/// The slot a widget's box is held in, made on its first placed draw and
|
||||
/// kept until it stops being drawn -- a redraw replaces its `ActiveData`
|
||||
/// while descendants go on naming the slot.
|
||||
/// Keeps a region node's entry across redraws because descendants retain
|
||||
/// its index.
|
||||
fn move_slot(&mut self, id: WidgetId, parent: MoveIdx, region: UiRegion) -> MoveIdx {
|
||||
if let Some(&idx) = self.slots.get(&id) {
|
||||
self.moves.set_parent(idx, parent);
|
||||
@@ -331,9 +335,7 @@ impl UiRenderState {
|
||||
idx
|
||||
}
|
||||
|
||||
/// Gives up a slot a widget no longer needs, because it is drawn somewhere
|
||||
/// that does not place it. Its descendants name it, so this is only
|
||||
/// reached where they are about to be drawn again.
|
||||
/// Removes a region node only after its descendants stop naming it.
|
||||
fn drop_slot(&mut self, id: WidgetId) {
|
||||
if let Some(idx) = self.slots.remove(&id) {
|
||||
self.moves.remove(idx);
|
||||
@@ -417,6 +419,10 @@ impl UiRenderState {
|
||||
diag::reuse(id, ReuseOutcome::Undrawn);
|
||||
return None;
|
||||
}
|
||||
let has_region_node = active.move_idx != active.parent_move;
|
||||
if has_region_node != info.region_node {
|
||||
return None;
|
||||
}
|
||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||
// longer sits in, and its slot names the wrong parent.
|
||||
if active.parent_move != info.parent_move {
|
||||
@@ -439,21 +445,15 @@ impl UiRenderState {
|
||||
return None;
|
||||
}
|
||||
let moved = active.region != region;
|
||||
// Only a placed widget can be given a different region without
|
||||
// drawing again: it has an entry of its own to say where it went,
|
||||
// where an unslotted one shares its parent's and has nothing to
|
||||
// write.
|
||||
if moved && active.move_idx == info.parent_move {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::ReuseUnslotted);
|
||||
diag::reuse(id, ReuseOutcome::Unslotted);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let (size, slot) = (active.size, active.move_idx);
|
||||
let (size, old_region, slot, mask) =
|
||||
(active.size, active.region, active.move_idx, info.mask);
|
||||
if moved {
|
||||
self.moves.set(slot, region);
|
||||
if has_region_node {
|
||||
self.moves.set(slot, region);
|
||||
} else {
|
||||
let remap = RegionRemap::new(old_region, region)?;
|
||||
self.remap_subtree(id, remap, info.parent_move, mask, rsc);
|
||||
}
|
||||
}
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.region = region;
|
||||
@@ -461,14 +461,19 @@ impl UiRenderState {
|
||||
active.depth = info.depth;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
match moved {
|
||||
true => diag::bump(Counter::ReuseMoved),
|
||||
false => diag::bump(Counter::ReuseExact),
|
||||
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 {
|
||||
ReuseOutcome::Moved
|
||||
if has_region_node {
|
||||
ReuseOutcome::Moved
|
||||
} else {
|
||||
ReuseOutcome::Remapped
|
||||
}
|
||||
} else {
|
||||
ReuseOutcome::Exact
|
||||
},
|
||||
@@ -477,6 +482,42 @@ impl UiRenderState {
|
||||
Some(size)
|
||||
}
|
||||
|
||||
/// Re-expresses an ordinary retained subtree in a new parent region.
|
||||
/// An independently movable descendant needs only its own region changed;
|
||||
/// its contents stay in that region's coordinate space.
|
||||
fn remap_subtree(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
remap: RegionRemap,
|
||||
parent_move: MoveIdx,
|
||||
inherited_mask: MaskIdx,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
if active.move_idx != parent_move {
|
||||
let region = remap.apply(active.region);
|
||||
active.region = region;
|
||||
self.moves.set(active.move_idx, region);
|
||||
return;
|
||||
}
|
||||
for handle in &active.primitives {
|
||||
let region = self.layers[handle.layer].region_mut(handle);
|
||||
*region = remap.apply(*region);
|
||||
}
|
||||
active.region = remap.apply(active.region);
|
||||
let mask = active.mask;
|
||||
let children = active.children.len();
|
||||
if mask != inherited_mask && mask != MaskIdx::NONE {
|
||||
let mask = rsc.ui_mut().masks.get_mut(mask);
|
||||
debug_assert_eq!(mask.move_idx, parent_move);
|
||||
mask.region = remap.apply(mask.region);
|
||||
}
|
||||
for index in 0..children {
|
||||
let child = self.active[&id].children[index];
|
||||
self.remap_subtree(child, remap, parent_move, mask, rsc);
|
||||
}
|
||||
}
|
||||
|
||||
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||
return true;
|
||||
@@ -716,15 +757,15 @@ impl UiRenderState {
|
||||
return;
|
||||
}
|
||||
let region = active.region;
|
||||
let slotted = active.move_idx != active.parent_move;
|
||||
let region_node = active.parent.is_some() && rsc.widgets().is_region_node(id);
|
||||
let offered_px = self.offered_px(id);
|
||||
let at_offer = same_px(self.px_of(active.parent_move, region), offered_px);
|
||||
// Asked again where its parent asked: a box decided from its own
|
||||
// answer gives that answer back whatever the content now says. Only
|
||||
// a placed widget can be drawn in a box other than the one it has,
|
||||
// so one that was not is a question for its parent.
|
||||
// a region node can be redrawn away from its current box without
|
||||
// first involving the parent that chose that box.
|
||||
if let Some(parent) = active.parent
|
||||
&& !slotted
|
||||
&& !region_node
|
||||
&& !at_offer
|
||||
{
|
||||
rsc.widgets_mut().needs_redraw.insert(id);
|
||||
@@ -737,7 +778,7 @@ impl UiRenderState {
|
||||
parent: active.parent,
|
||||
depth: active.depth,
|
||||
parent_move: active.parent_move,
|
||||
slotted,
|
||||
region_node,
|
||||
mask: active.mask,
|
||||
offer: active.offer,
|
||||
offered_px,
|
||||
@@ -762,8 +803,8 @@ impl UiRenderState {
|
||||
return;
|
||||
};
|
||||
if answer != was_answer {
|
||||
// Left where it was asked: the parent lays out again, and places
|
||||
// it.
|
||||
// Left where it was asked: the parent lays out again and chooses
|
||||
// its final box.
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::SizeChanges);
|
||||
@@ -775,9 +816,8 @@ impl UiRenderState {
|
||||
if at_offer {
|
||||
return;
|
||||
}
|
||||
// Then where its parent placed it, which is the box that answer
|
||||
// decided. Kept as it is if it holds there; otherwise what it comes
|
||||
// to there is the parent's business too.
|
||||
// Then in the final box its parent chose from that answer. It is kept
|
||||
// if it holds there; otherwise its result is the parent's business.
|
||||
self.draw_inner(id, region, info, None, rsc);
|
||||
let active = &self.active[&id];
|
||||
if (active.size, active.holds) != was {
|
||||
@@ -790,6 +830,51 @@ fn same_px(a: Vec2, b: Vec2) -> bool {
|
||||
Holds::at(a.x).contains(b.x) && Holds::at(a.y).contains(b.y)
|
||||
}
|
||||
|
||||
/// A retained region rewritten from one parent box into another. A fixed
|
||||
/// source extent can be translated but cannot recover fractions for a resize.
|
||||
#[derive(Clone, Copy)]
|
||||
struct RegionRemap {
|
||||
from: UiRegion,
|
||||
to: UiRegion,
|
||||
}
|
||||
|
||||
impl RegionRemap {
|
||||
fn new(from: UiRegion, to: UiRegion) -> Option<Self> {
|
||||
AXES.into_iter()
|
||||
.all(|axis| {
|
||||
let from = from.axis(axis);
|
||||
from.start.rel != from.end.rel || from.len() == to.axis(axis).len()
|
||||
})
|
||||
.then_some(Self { from, to })
|
||||
}
|
||||
|
||||
fn apply(self, region: UiRegion) -> UiRegion {
|
||||
UiRegion {
|
||||
x: self.apply_span(region.x, self.from.x, self.to.x),
|
||||
y: self.apply_span(region.y, self.from.y, self.to.y),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_span(self, span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan {
|
||||
UiSpan {
|
||||
start: self.apply_scalar(span.start, from, to),
|
||||
end: self.apply_scalar(span.end, from, to),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_scalar(self, scalar: UiScalar, from: UiSpan, to: UiSpan) -> UiScalar {
|
||||
let extent = from.end.rel - from.start.rel;
|
||||
if extent == 0.0 {
|
||||
return scalar + to.start - from.start;
|
||||
}
|
||||
let fraction = (scalar.rel - from.start.rel) / extent;
|
||||
let from_px = from.start.px + fraction * (from.end.px - from.start.px);
|
||||
let to_rel = to.start.rel + fraction * (to.end.rel - to.start.rel);
|
||||
let to_px = to.start.px + fraction * (to.end.px - to.start.px);
|
||||
UiScalar::new(to_rel, scalar.px - from_px + to_px)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiRenderState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
||||
Reference in new issue
Block a user