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
@@ -28,7 +28,7 @@ pub(crate) enum Counter {
|
||||
Updates,
|
||||
DrawRequests,
|
||||
WidgetDraws,
|
||||
PlaceCalls,
|
||||
RegionNodeDraws,
|
||||
SizeReads,
|
||||
HintHits,
|
||||
HintMisses,
|
||||
@@ -38,7 +38,7 @@ pub(crate) enum Counter {
|
||||
ReuseMoved,
|
||||
ReuseDirty,
|
||||
ReuseWrongParent,
|
||||
ReuseUnslotted,
|
||||
ReuseRemapped,
|
||||
ReuseOutside,
|
||||
QueuePops,
|
||||
DepthReads,
|
||||
@@ -60,7 +60,7 @@ impl Counter {
|
||||
"updates",
|
||||
"draw requests",
|
||||
"widget draws",
|
||||
"place calls",
|
||||
"region-node draws",
|
||||
"draw-result size reads",
|
||||
"hint hits",
|
||||
"hint misses",
|
||||
@@ -70,7 +70,7 @@ impl Counter {
|
||||
"reuse moved",
|
||||
"reuse: dirty",
|
||||
"reuse: wrong parent",
|
||||
"reuse: unslotted",
|
||||
"reuse remapped",
|
||||
"reuse: outside what it holds for",
|
||||
"redraw queue pops",
|
||||
"depth reads",
|
||||
@@ -242,7 +242,7 @@ pub enum ReuseOutcome {
|
||||
Moved,
|
||||
Dirty,
|
||||
WrongParent,
|
||||
Unslotted,
|
||||
Remapped,
|
||||
Outside,
|
||||
Undrawn,
|
||||
}
|
||||
@@ -256,7 +256,7 @@ pub enum TraceEvent {
|
||||
parent: Option<WidgetId>,
|
||||
region: UiRegion,
|
||||
pixel_size: Vec2,
|
||||
slotted: bool,
|
||||
region_node: bool,
|
||||
},
|
||||
Reuse {
|
||||
id: WidgetId,
|
||||
@@ -266,7 +266,7 @@ pub enum TraceEvent {
|
||||
id: WidgetId,
|
||||
size: Size,
|
||||
},
|
||||
Placed {
|
||||
RegionNode {
|
||||
id: WidgetId,
|
||||
parent: WidgetId,
|
||||
region: UiRegion,
|
||||
@@ -352,7 +352,7 @@ pub(crate) fn draw_request(
|
||||
parent: Option<WidgetId>,
|
||||
region: UiRegion,
|
||||
pixel_size: Vec2,
|
||||
slotted: bool,
|
||||
region_node: bool,
|
||||
) {
|
||||
trace(
|
||||
id,
|
||||
@@ -361,7 +361,7 @@ pub(crate) fn draw_request(
|
||||
parent,
|
||||
region,
|
||||
pixel_size,
|
||||
slotted,
|
||||
region_node,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -374,8 +374,8 @@ pub(crate) fn size_reported(id: WidgetId, size: Size) {
|
||||
trace(id, TraceEvent::SizeReported { id, size });
|
||||
}
|
||||
|
||||
pub(crate) fn placed(id: WidgetId, parent: WidgetId, region: UiRegion) {
|
||||
trace(id, TraceEvent::Placed { id, parent, region });
|
||||
pub(crate) fn region_node(id: WidgetId, parent: WidgetId, region: UiRegion) {
|
||||
trace(id, TraceEvent::RegionNode { id, parent, region });
|
||||
}
|
||||
|
||||
pub(crate) fn size_read(id: WidgetId, reader: WidgetId, size: Size) {
|
||||
|
||||
@@ -32,14 +32,14 @@ pub struct ActiveData {
|
||||
pub children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub size_deps: Vec<WidgetId>,
|
||||
/// The slot its primitives are positioned through: its own if its parent
|
||||
/// placed it, otherwise the nearest ancestor that has one.
|
||||
/// The movable region its primitives are positioned through: its own when
|
||||
/// opted in, otherwise the nearest ancestor's.
|
||||
pub move_idx: MoveIdx,
|
||||
/// The declared lengths whoever drew this widget resolved into its box.
|
||||
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||
/// and comparing them is what says so.
|
||||
pub declared: [Option<Len>; 2],
|
||||
/// The slot `region` is given in, which is whatever its parent drew in.
|
||||
/// The movable region whose coordinates `region` uses.
|
||||
pub parent_move: MoveIdx,
|
||||
pub mask: MaskIdx,
|
||||
pub layer: LayerId,
|
||||
|
||||
+30
-13
@@ -25,11 +25,27 @@ impl Holds {
|
||||
};
|
||||
|
||||
pub const fn at(len: f32) -> Self {
|
||||
Self { lo: len, hi: len }
|
||||
Self::tolerant(len, len)
|
||||
}
|
||||
|
||||
const fn tolerant(lo: f32, hi: f32) -> Self {
|
||||
Self {
|
||||
lo: lo - HOLDS_EPSILON_PX,
|
||||
hi: hi + HOLDS_EPSILON_PX,
|
||||
}
|
||||
}
|
||||
|
||||
/// A range whose endpoints are exact, for a widget decision with a hard
|
||||
/// boundary rather than an accumulated coordinate-rounding difference.
|
||||
pub fn exact(range: RangeInclusive<f32>) -> Self {
|
||||
Self {
|
||||
lo: *range.start(),
|
||||
hi: *range.end(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, len: f32) -> bool {
|
||||
len >= self.lo - HOLDS_EPSILON_PX && len <= self.hi + HOLDS_EPSILON_PX
|
||||
len >= self.lo && len <= self.hi
|
||||
}
|
||||
|
||||
pub fn and(self, other: Self) -> Self {
|
||||
@@ -57,10 +73,7 @@ impl Holds {
|
||||
|
||||
impl From<RangeInclusive<f32>> for Holds {
|
||||
fn from(range: RangeInclusive<f32>) -> Self {
|
||||
Self {
|
||||
lo: *range.start(),
|
||||
hi: *range.end(),
|
||||
}
|
||||
Self::tolerant(*range.start(), *range.end())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,12 +83,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn through_reverses_a_range_for_a_negative_fraction() {
|
||||
assert_eq!(
|
||||
Holds { lo: 20.0, hi: 40.0 }.through(UiScalar::new(-0.5, 10.0)),
|
||||
Holds {
|
||||
lo: -60.0,
|
||||
hi: -20.0
|
||||
}
|
||||
);
|
||||
let holds = Holds::from(20.0..=40.0).through(UiScalar::new(-0.5, 10.0));
|
||||
assert!((holds.lo - -60.1).abs() < 0.001);
|
||||
assert!((holds.hi - -19.9).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_exact_open_boundary_does_not_admit_the_boundary() {
|
||||
let boundary = 10.0_f32;
|
||||
let above = Holds::exact(boundary.next_up()..=f32::INFINITY);
|
||||
assert!(!above.contains(boundary));
|
||||
assert!(above.contains(boundary.next_up()));
|
||||
}
|
||||
}
|
||||
+11
-28
@@ -10,8 +10,6 @@ use crate::{
|
||||
ui::render_state::DrawInfo,
|
||||
util::Vec2,
|
||||
};
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||
|
||||
/// makes your surfaces look pretty
|
||||
@@ -40,8 +38,8 @@ pub struct Painter<'a> {
|
||||
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.
|
||||
/// The movable region this widget's primitives are positioned through:
|
||||
/// its own when opted in, otherwise the nearest ancestor's.
|
||||
pub(super) move_idx: MoveIdx,
|
||||
pub layer: usize,
|
||||
pub(super) depth: usize,
|
||||
@@ -101,7 +99,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> {
|
||||
self.widget_at(id, UiRegion::FULL, false)
|
||||
self.widget_at(id, UiRegion::FULL)
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
@@ -110,7 +108,7 @@ impl<'a> Painter<'a> {
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
self.widget_at(id, region, false)
|
||||
self.widget_at(id, region)
|
||||
}
|
||||
|
||||
/// What a widget declares its lengths to be, which whoever draws it
|
||||
@@ -125,22 +123,6 @@ impl<'a> Painter<'a> {
|
||||
AXES.map(|axis| declared_len(widget, axis))
|
||||
}
|
||||
|
||||
/// Draws a child this widget decides the box of, and may decide again
|
||||
/// once it knows what the child came to. The child gets a slot of its
|
||||
/// own, so placing it a second time writes one entry however much it
|
||||
/// drew -- moved or resized alike, since everything under the slot is
|
||||
/// held as a fraction of its box. A child drawn any other way has no slot
|
||||
/// and can only be given a different box by drawing again.
|
||||
pub fn place<'s, W: ?Sized>(
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::PlaceCalls);
|
||||
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.
|
||||
@@ -155,8 +137,8 @@ impl<'a> Painter<'a> {
|
||||
&'s mut self,
|
||||
id: &'s StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
slotted: bool,
|
||||
) -> DrawResult<'s, 'a, W> {
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
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.
|
||||
@@ -169,8 +151,9 @@ impl<'a> Painter<'a> {
|
||||
false => local.within(&self.region),
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
if slotted {
|
||||
diag::placed(id.id(), self.id, within);
|
||||
if region_node {
|
||||
diag::bump(Counter::RegionNodeDraws);
|
||||
diag::region_node(id.id(), self.id, within);
|
||||
}
|
||||
// A child listed twice would be moved twice.
|
||||
if !self.children.contains(&id.id()) {
|
||||
@@ -190,7 +173,7 @@ impl<'a> Painter<'a> {
|
||||
parent: Some(self.id),
|
||||
depth: self.depth + 1,
|
||||
parent_move: self.move_idx,
|
||||
slotted,
|
||||
region_node,
|
||||
mask: self.mask,
|
||||
offer,
|
||||
offered_px: self.px_within_offer(offer),
|
||||
@@ -371,8 +354,8 @@ impl<'a> Painter<'a> {
|
||||
/// 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);
|
||||
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||
let holds = holds.into();
|
||||
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",
|
||||
|
||||
+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()
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
||||
self.data[i]
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
pub(crate) fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
&mut self.data[id.idx()]
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,11 @@ impl<T, I: IdNum> TrackedArena<T, I> {
|
||||
self.refs[i.idx()] += 1;
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||
self.changed = true;
|
||||
self.inner.get_mut(id)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: Id<I>) -> T
|
||||
where
|
||||
T: Copy,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::Widget;
|
||||
pub struct WidgetData {
|
||||
pub widget: Box<dyn Widget>,
|
||||
pub label: String,
|
||||
pub(super) region_node: bool,
|
||||
/// dynamic borrow checking
|
||||
pub borrowed: bool,
|
||||
}
|
||||
@@ -16,6 +17,7 @@ impl WidgetData {
|
||||
Self {
|
||||
widget: Box::new(widget),
|
||||
label,
|
||||
region_node: false,
|
||||
borrowed: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,24 @@ impl Widgets {
|
||||
self.data_mut(id.id()).unwrap().label = label;
|
||||
}
|
||||
|
||||
/// Whether this widget owns a movable retained region.
|
||||
pub fn is_region_node(&self, id: impl IdLike) -> bool {
|
||||
self.data(id).unwrap().region_node
|
||||
}
|
||||
|
||||
/// Chooses whether this widget's retained drawing has one movable region
|
||||
/// of its own. Changing the boundary redraws the subtree once so every
|
||||
/// primitive names the right coordinate space.
|
||||
pub fn set_region_node(&mut self, id: impl IdLike, region_node: bool) {
|
||||
let id = id.id();
|
||||
let data = self.data_mut(id).unwrap();
|
||||
if data.region_node == region_node {
|
||||
return;
|
||||
}
|
||||
data.region_node = region_node;
|
||||
self.needs_redraw.insert(id);
|
||||
}
|
||||
|
||||
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
|
||||
self.vec.get_mut(id.id())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user