Replace placement calls with region nodes
This commit is contained in:
1 parent
f437495309
commit
71c9c39523
23 files changed
+371
-167
No files matched your search
@@ -28,7 +28,7 @@ pub(crate) enum Counter {
|
|||||||
Updates,
|
Updates,
|
||||||
DrawRequests,
|
DrawRequests,
|
||||||
WidgetDraws,
|
WidgetDraws,
|
||||||
PlaceCalls,
|
RegionNodeDraws,
|
||||||
SizeReads,
|
SizeReads,
|
||||||
HintHits,
|
HintHits,
|
||||||
HintMisses,
|
HintMisses,
|
||||||
@@ -38,7 +38,7 @@ pub(crate) enum Counter {
|
|||||||
ReuseMoved,
|
ReuseMoved,
|
||||||
ReuseDirty,
|
ReuseDirty,
|
||||||
ReuseWrongParent,
|
ReuseWrongParent,
|
||||||
ReuseUnslotted,
|
ReuseRemapped,
|
||||||
ReuseOutside,
|
ReuseOutside,
|
||||||
QueuePops,
|
QueuePops,
|
||||||
DepthReads,
|
DepthReads,
|
||||||
@@ -60,7 +60,7 @@ impl Counter {
|
|||||||
"updates",
|
"updates",
|
||||||
"draw requests",
|
"draw requests",
|
||||||
"widget draws",
|
"widget draws",
|
||||||
"place calls",
|
"region-node draws",
|
||||||
"draw-result size reads",
|
"draw-result size reads",
|
||||||
"hint hits",
|
"hint hits",
|
||||||
"hint misses",
|
"hint misses",
|
||||||
@@ -70,7 +70,7 @@ impl Counter {
|
|||||||
"reuse moved",
|
"reuse moved",
|
||||||
"reuse: dirty",
|
"reuse: dirty",
|
||||||
"reuse: wrong parent",
|
"reuse: wrong parent",
|
||||||
"reuse: unslotted",
|
"reuse remapped",
|
||||||
"reuse: outside what it holds for",
|
"reuse: outside what it holds for",
|
||||||
"redraw queue pops",
|
"redraw queue pops",
|
||||||
"depth reads",
|
"depth reads",
|
||||||
@@ -242,7 +242,7 @@ pub enum ReuseOutcome {
|
|||||||
Moved,
|
Moved,
|
||||||
Dirty,
|
Dirty,
|
||||||
WrongParent,
|
WrongParent,
|
||||||
Unslotted,
|
Remapped,
|
||||||
Outside,
|
Outside,
|
||||||
Undrawn,
|
Undrawn,
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ pub enum TraceEvent {
|
|||||||
parent: Option<WidgetId>,
|
parent: Option<WidgetId>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
pixel_size: Vec2,
|
pixel_size: Vec2,
|
||||||
slotted: bool,
|
region_node: bool,
|
||||||
},
|
},
|
||||||
Reuse {
|
Reuse {
|
||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
@@ -266,7 +266,7 @@ pub enum TraceEvent {
|
|||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
size: Size,
|
size: Size,
|
||||||
},
|
},
|
||||||
Placed {
|
RegionNode {
|
||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
parent: WidgetId,
|
parent: WidgetId,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
@@ -352,7 +352,7 @@ pub(crate) fn draw_request(
|
|||||||
parent: Option<WidgetId>,
|
parent: Option<WidgetId>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
pixel_size: Vec2,
|
pixel_size: Vec2,
|
||||||
slotted: bool,
|
region_node: bool,
|
||||||
) {
|
) {
|
||||||
trace(
|
trace(
|
||||||
id,
|
id,
|
||||||
@@ -361,7 +361,7 @@ pub(crate) fn draw_request(
|
|||||||
parent,
|
parent,
|
||||||
region,
|
region,
|
||||||
pixel_size,
|
pixel_size,
|
||||||
slotted,
|
region_node,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -374,8 +374,8 @@ pub(crate) fn size_reported(id: WidgetId, size: Size) {
|
|||||||
trace(id, TraceEvent::SizeReported { id, size });
|
trace(id, TraceEvent::SizeReported { id, size });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn placed(id: WidgetId, parent: WidgetId, region: UiRegion) {
|
pub(crate) fn region_node(id: WidgetId, parent: WidgetId, region: UiRegion) {
|
||||||
trace(id, TraceEvent::Placed { id, parent, region });
|
trace(id, TraceEvent::RegionNode { id, parent, region });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn size_read(id: WidgetId, reader: WidgetId, size: Size) {
|
pub(crate) fn size_read(id: WidgetId, reader: WidgetId, size: Size) {
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ pub struct ActiveData {
|
|||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub size_deps: Vec<WidgetId>,
|
pub size_deps: Vec<WidgetId>,
|
||||||
/// The slot its primitives are positioned through: its own if its parent
|
/// The movable region its primitives are positioned through: its own when
|
||||||
/// placed it, otherwise the nearest ancestor that has one.
|
/// opted in, otherwise the nearest ancestor's.
|
||||||
pub move_idx: MoveIdx,
|
pub move_idx: MoveIdx,
|
||||||
/// The declared lengths whoever drew this widget resolved into its box.
|
/// 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,
|
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||||
/// and comparing them is what says so.
|
/// and comparing them is what says so.
|
||||||
pub declared: [Option<Len>; 2],
|
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 parent_move: MoveIdx,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
|
|||||||
+29
-12
@@ -25,11 +25,27 @@ impl Holds {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const fn at(len: f32) -> Self {
|
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 {
|
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 {
|
pub fn and(self, other: Self) -> Self {
|
||||||
@@ -57,10 +73,7 @@ impl Holds {
|
|||||||
|
|
||||||
impl From<RangeInclusive<f32>> for Holds {
|
impl From<RangeInclusive<f32>> for Holds {
|
||||||
fn from(range: RangeInclusive<f32>) -> Self {
|
fn from(range: RangeInclusive<f32>) -> Self {
|
||||||
Self {
|
Self::tolerant(*range.start(), *range.end())
|
||||||
lo: *range.start(),
|
|
||||||
hi: *range.end(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +83,16 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn through_reverses_a_range_for_a_negative_fraction() {
|
fn through_reverses_a_range_for_a_negative_fraction() {
|
||||||
assert_eq!(
|
let holds = Holds::from(20.0..=40.0).through(UiScalar::new(-0.5, 10.0));
|
||||||
Holds { lo: 20.0, hi: 40.0 }.through(UiScalar::new(-0.5, 10.0)),
|
assert!((holds.lo - -60.1).abs() < 0.001);
|
||||||
Holds {
|
assert!((holds.hi - -19.9).abs() < 0.001);
|
||||||
lo: -60.0,
|
|
||||||
hi: -20.0
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
#[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,
|
ui::render_state::DrawInfo,
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
use std::ops::RangeInclusive;
|
|
||||||
|
|
||||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||||
|
|
||||||
/// makes your surfaces look pretty
|
/// makes your surfaces look pretty
|
||||||
@@ -40,8 +38,8 @@ pub struct Painter<'a> {
|
|||||||
pub(super) own: [Holds; 2],
|
pub(super) own: [Holds; 2],
|
||||||
/// What the children it asked about and drew keep it to.
|
/// What the children it asked about and drew keep it to.
|
||||||
pub(super) under: [Holds; 2],
|
pub(super) under: [Holds; 2],
|
||||||
/// The slot this widget's primitives are positioned through: its own if
|
/// The movable region this widget's primitives are positioned through:
|
||||||
/// its parent placed it, otherwise the nearest ancestor that has one.
|
/// its own when opted in, otherwise the nearest ancestor's.
|
||||||
pub(super) move_idx: MoveIdx,
|
pub(super) move_idx: MoveIdx,
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub(super) depth: usize,
|
pub(super) depth: usize,
|
||||||
@@ -101,7 +99,7 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// 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> {
|
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.
|
/// Draws a widget somewhere within this one.
|
||||||
@@ -110,7 +108,7 @@ impl<'a> Painter<'a> {
|
|||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> 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
|
/// 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))
|
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.
|
/// 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
|
/// 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.
|
/// this frame; what it answered is still something this widget asked.
|
||||||
@@ -155,8 +137,8 @@ impl<'a> Painter<'a> {
|
|||||||
&'s mut self,
|
&'s mut self,
|
||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
slotted: bool,
|
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||||
let declared = self.declared_lens(id);
|
let declared = self.declared_lens(id);
|
||||||
// Composing `FULL` through a box is not quite the identity in f32,
|
// 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.
|
// 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),
|
false => local.within(&self.region),
|
||||||
};
|
};
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
if slotted {
|
if region_node {
|
||||||
diag::placed(id.id(), self.id, within);
|
diag::bump(Counter::RegionNodeDraws);
|
||||||
|
diag::region_node(id.id(), self.id, within);
|
||||||
}
|
}
|
||||||
// A child listed twice would be moved twice.
|
// A child listed twice would be moved twice.
|
||||||
if !self.children.contains(&id.id()) {
|
if !self.children.contains(&id.id()) {
|
||||||
@@ -190,7 +173,7 @@ impl<'a> Painter<'a> {
|
|||||||
parent: Some(self.id),
|
parent: Some(self.id),
|
||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
parent_move: self.move_idx,
|
parent_move: self.move_idx,
|
||||||
slotted,
|
region_node,
|
||||||
mask: self.mask,
|
mask: self.mask,
|
||||||
offer,
|
offer,
|
||||||
offered_px: self.px_within_offer(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
|
/// holds for -- the same primitives, in the same fractions and offsets
|
||||||
/// of the box, and the same reported size. A widget that read its
|
/// 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.
|
/// length in pixels holds for that one alone until it says otherwise.
|
||||||
pub fn holds(&mut self, axis: Axis, range: RangeInclusive<f32>) {
|
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||||
let holds = Holds::from(range);
|
let holds = holds.into();
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds.contains(self.state.px_of(self.move_idx, self.region).axis(axis)),
|
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",
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
||||||
|
|||||||
+132
-47
@@ -17,8 +17,7 @@ pub(super) struct DrawInfo {
|
|||||||
pub parent: Option<WidgetId>,
|
pub parent: Option<WidgetId>,
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub parent_move: MoveIdx,
|
pub parent_move: MoveIdx,
|
||||||
/// Whether the widget gets a slot of its own to be placed through.
|
pub region_node: bool,
|
||||||
pub slotted: bool,
|
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
/// The box it was first asked about in, as a part of its parent's, and
|
/// The box it was first asked about in, as a part of its parent's, and
|
||||||
/// that box in pixels.
|
/// that box in pixels.
|
||||||
@@ -88,7 +87,7 @@ impl UiRenderState {
|
|||||||
parent: None,
|
parent: None,
|
||||||
depth: 1,
|
depth: 1,
|
||||||
parent_move: self.root_move,
|
parent_move: self.root_move,
|
||||||
slotted: false,
|
region_node: false,
|
||||||
mask: MaskIdx::NONE,
|
mask: MaskIdx::NONE,
|
||||||
offer: UiRegion::FULL,
|
offer: UiRegion::FULL,
|
||||||
offered_px: self.output_size,
|
offered_px: self.output_size,
|
||||||
@@ -169,7 +168,7 @@ impl UiRenderState {
|
|||||||
info.parent,
|
info.parent,
|
||||||
region,
|
region,
|
||||||
self.px_of(info.parent_move, region),
|
self.px_of(info.parent_move, region),
|
||||||
info.slotted,
|
info.region_node,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.active.contains_key(&id) {
|
if self.active.contains_key(&id) {
|
||||||
@@ -181,14 +180,17 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw widget
|
// draw widget
|
||||||
let (move_idx, local) = match info.slotted {
|
let (move_idx, local, retired_move) = match info.region_node {
|
||||||
// Its box becomes its slot's, so it draws in the slot's own
|
// Its box becomes its movable region, so it draws in that
|
||||||
// coordinates and the box it was given is one entry to rewrite.
|
// region's coordinates and its box is one entry to rewrite.
|
||||||
true => (self.move_slot(id, info.parent_move, region), UiRegion::FULL),
|
true => (
|
||||||
false => {
|
self.move_slot(id, info.parent_move, region),
|
||||||
self.drop_slot(id);
|
UiRegion::FULL,
|
||||||
(info.parent_move, region)
|
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 {
|
let (old_children, old_answer) = match old {
|
||||||
Some(old) => (old.children, Some(old.answer)),
|
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])];
|
let holds = [own[0].and(under[0]), own[1].and(under[1])];
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds[0].contains(px.x) && holds[1].contains(px.y),
|
holds[0].contains(px.x) && holds[1].contains(px.y),
|
||||||
"'{}' ({id:?}) drew something that does not hold for its own box",
|
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
||||||
rsc.widgets().label(id)
|
rsc.widgets().label(id),
|
||||||
);
|
);
|
||||||
|
|
||||||
for c in &old_children {
|
for c in &old_children {
|
||||||
@@ -266,6 +268,9 @@ impl UiRenderState {
|
|||||||
self.undraw_rec(*c, rsc);
|
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,
|
// 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
|
// 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.
|
// it had: a hint is read live, and a drawing is not kept past one.
|
||||||
@@ -278,7 +283,7 @@ impl UiRenderState {
|
|||||||
parent: Some(id),
|
parent: Some(id),
|
||||||
depth: info.depth + 1,
|
depth: info.depth + 1,
|
||||||
parent_move: move_idx,
|
parent_move: move_idx,
|
||||||
slotted: false,
|
region_node: false,
|
||||||
mask,
|
mask,
|
||||||
offer: UiRegion::FULL,
|
offer: UiRegion::FULL,
|
||||||
offered_px: px,
|
offered_px: px,
|
||||||
@@ -317,9 +322,8 @@ impl UiRenderState {
|
|||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The slot a widget's box is held in, made on its first placed draw and
|
/// Keeps a region node's entry across redraws because descendants retain
|
||||||
/// kept until it stops being drawn -- a redraw replaces its `ActiveData`
|
/// its index.
|
||||||
/// while descendants go on naming the slot.
|
|
||||||
fn move_slot(&mut self, id: WidgetId, parent: MoveIdx, region: UiRegion) -> MoveIdx {
|
fn move_slot(&mut self, id: WidgetId, parent: MoveIdx, region: UiRegion) -> MoveIdx {
|
||||||
if let Some(&idx) = self.slots.get(&id) {
|
if let Some(&idx) = self.slots.get(&id) {
|
||||||
self.moves.set_parent(idx, parent);
|
self.moves.set_parent(idx, parent);
|
||||||
@@ -331,9 +335,7 @@ impl UiRenderState {
|
|||||||
idx
|
idx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives up a slot a widget no longer needs, because it is drawn somewhere
|
/// Removes a region node only after its descendants stop naming it.
|
||||||
/// that does not place it. Its descendants name it, so this is only
|
|
||||||
/// reached where they are about to be drawn again.
|
|
||||||
fn drop_slot(&mut self, id: WidgetId) {
|
fn drop_slot(&mut self, id: WidgetId) {
|
||||||
if let Some(idx) = self.slots.remove(&id) {
|
if let Some(idx) = self.slots.remove(&id) {
|
||||||
self.moves.remove(idx);
|
self.moves.remove(idx);
|
||||||
@@ -417,6 +419,10 @@ impl UiRenderState {
|
|||||||
diag::reuse(id, ReuseOutcome::Undrawn);
|
diag::reuse(id, ReuseOutcome::Undrawn);
|
||||||
return None;
|
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
|
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||||
// longer sits in, and its slot names the wrong parent.
|
// longer sits in, and its slot names the wrong parent.
|
||||||
if active.parent_move != info.parent_move {
|
if active.parent_move != info.parent_move {
|
||||||
@@ -439,21 +445,15 @@ impl UiRenderState {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let moved = active.region != region;
|
let moved = active.region != region;
|
||||||
// Only a placed widget can be given a different region without
|
let (size, old_region, slot, mask) =
|
||||||
// drawing again: it has an entry of its own to say where it went,
|
(active.size, active.region, active.move_idx, info.mask);
|
||||||
// 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);
|
|
||||||
if moved {
|
if moved {
|
||||||
|
if has_region_node {
|
||||||
self.moves.set(slot, region);
|
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();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.region = region;
|
active.region = region;
|
||||||
@@ -461,14 +461,19 @@ impl UiRenderState {
|
|||||||
active.depth = info.depth;
|
active.depth = info.depth;
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
match moved {
|
match (moved, has_region_node) {
|
||||||
true => diag::bump(Counter::ReuseMoved),
|
(true, true) => diag::bump(Counter::ReuseMoved),
|
||||||
false => diag::bump(Counter::ReuseExact),
|
(true, false) => diag::bump(Counter::ReuseRemapped),
|
||||||
|
(false, _) => diag::bump(Counter::ReuseExact),
|
||||||
}
|
}
|
||||||
diag::reuse(
|
diag::reuse(
|
||||||
id,
|
id,
|
||||||
if moved {
|
if moved {
|
||||||
|
if has_region_node {
|
||||||
ReuseOutcome::Moved
|
ReuseOutcome::Moved
|
||||||
|
} else {
|
||||||
|
ReuseOutcome::Remapped
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ReuseOutcome::Exact
|
ReuseOutcome::Exact
|
||||||
},
|
},
|
||||||
@@ -477,6 +482,42 @@ impl UiRenderState {
|
|||||||
Some(size)
|
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 {
|
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||||
return true;
|
return true;
|
||||||
@@ -716,15 +757,15 @@ impl UiRenderState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let region = active.region;
|
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 offered_px = self.offered_px(id);
|
||||||
let at_offer = same_px(self.px_of(active.parent_move, region), offered_px);
|
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
|
// Asked again where its parent asked: a box decided from its own
|
||||||
// answer gives that answer back whatever the content now says. Only
|
// 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,
|
// a region node can be redrawn away from its current box without
|
||||||
// so one that was not is a question for its parent.
|
// first involving the parent that chose that box.
|
||||||
if let Some(parent) = active.parent
|
if let Some(parent) = active.parent
|
||||||
&& !slotted
|
&& !region_node
|
||||||
&& !at_offer
|
&& !at_offer
|
||||||
{
|
{
|
||||||
rsc.widgets_mut().needs_redraw.insert(id);
|
rsc.widgets_mut().needs_redraw.insert(id);
|
||||||
@@ -737,7 +778,7 @@ impl UiRenderState {
|
|||||||
parent: active.parent,
|
parent: active.parent,
|
||||||
depth: active.depth,
|
depth: active.depth,
|
||||||
parent_move: active.parent_move,
|
parent_move: active.parent_move,
|
||||||
slotted,
|
region_node,
|
||||||
mask: active.mask,
|
mask: active.mask,
|
||||||
offer: active.offer,
|
offer: active.offer,
|
||||||
offered_px,
|
offered_px,
|
||||||
@@ -762,8 +803,8 @@ impl UiRenderState {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if answer != was_answer {
|
if answer != was_answer {
|
||||||
// Left where it was asked: the parent lays out again, and places
|
// Left where it was asked: the parent lays out again and chooses
|
||||||
// it.
|
// its final box.
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
diag::bump(Counter::SizeChanges);
|
diag::bump(Counter::SizeChanges);
|
||||||
@@ -775,9 +816,8 @@ impl UiRenderState {
|
|||||||
if at_offer {
|
if at_offer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Then where its parent placed it, which is the box that answer
|
// Then in the final box its parent chose from that answer. It is kept
|
||||||
// decided. Kept as it is if it holds there; otherwise what it comes
|
// if it holds there; otherwise its result is the parent's business.
|
||||||
// to there is the parent's business too.
|
|
||||||
self.draw_inner(id, region, info, None, rsc);
|
self.draw_inner(id, region, info, None, rsc);
|
||||||
let active = &self.active[&id];
|
let active = &self.active[&id];
|
||||||
if (active.size, active.holds) != was {
|
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)
|
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 {
|
impl Default for UiRenderState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
|||||||
self.data[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()]
|
&mut self.data[id.idx()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,11 @@ impl<T, I: IdNum> TrackedArena<T, I> {
|
|||||||
self.refs[i.idx()] += 1;
|
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
|
pub fn remove(&mut self, id: Id<I>) -> T
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::Widget;
|
|||||||
pub struct WidgetData {
|
pub struct WidgetData {
|
||||||
pub widget: Box<dyn Widget>,
|
pub widget: Box<dyn Widget>,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
pub(super) region_node: bool,
|
||||||
/// dynamic borrow checking
|
/// dynamic borrow checking
|
||||||
pub borrowed: bool,
|
pub borrowed: bool,
|
||||||
}
|
}
|
||||||
@@ -16,6 +17,7 @@ impl WidgetData {
|
|||||||
Self {
|
Self {
|
||||||
widget: Box::new(widget),
|
widget: Box::new(widget),
|
||||||
label,
|
label,
|
||||||
|
region_node: false,
|
||||||
borrowed: false,
|
borrowed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,24 @@ impl Widgets {
|
|||||||
self.data_mut(id.id()).unwrap().label = label;
|
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> {
|
pub fn data_mut(&mut self, id: impl IdLike) -> Option<&mut WidgetData> {
|
||||||
self.vec.get_mut(id.id())
|
self.vec.get_mut(id.id())
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -100,14 +100,14 @@ impl Widget for Branch {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let mut top = UiRegion::FULL;
|
let mut top = UiRegion::FULL;
|
||||||
top.y.end = top.y.start.offset(40.0);
|
top.y.end = top.y.start.offset(40.0);
|
||||||
let measured = painter.place(&self.probe, top).len(Axis::X);
|
let measured = painter.widget_within(&self.probe, top).len(Axis::X);
|
||||||
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
|
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
|
||||||
|
|
||||||
let mut below = UiRegion::FULL;
|
let mut below = UiRegion::FULL;
|
||||||
below.y.start = below.y.start.offset(40.0);
|
below.y.start = below.y.start.offset(40.0);
|
||||||
match px > self.threshold {
|
match px > self.threshold {
|
||||||
true => painter.place(&self.wide, below),
|
true => painter.widget_within(&self.wide, below),
|
||||||
false => painter.place(&self.narrow, below),
|
false => painter.widget_within(&self.narrow, below),
|
||||||
};
|
};
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,15 @@ impl Widget for Aligned {
|
|||||||
// Drawn where it may be too big only when the aligned axes are not
|
// Drawn where it may be too big only when the aligned axes are not
|
||||||
// already known, then given its aligned box once its size is known.
|
// already known, then given its aligned box once its size is known.
|
||||||
let had_size = known.is_some();
|
let had_size = known.is_some();
|
||||||
let size = known.unwrap_or_else(|| painter.place(&self.inner, UiRegion::FULL).size());
|
let size =
|
||||||
|
known.unwrap_or_else(|| painter.widget_within(&self.inner, UiRegion::FULL).size());
|
||||||
let region = match self.align.tuple() {
|
let region = match self.align.tuple() {
|
||||||
(Some(x), Some(y)) => size.to_uivec2().align(RegionAlign { x, y }),
|
(Some(x), Some(y)) => size.to_uivec2().align(RegionAlign { x, y }),
|
||||||
(Some(x), None) => UiRegion::new(size.x.apply_leftover().align(x), UiSpan::FULL),
|
(Some(x), None) => UiRegion::new(size.x.apply_leftover().align(x), UiSpan::FULL),
|
||||||
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_leftover().align(y)),
|
(None, Some(y)) => UiRegion::new(UiSpan::FULL, size.y.apply_leftover().align(y)),
|
||||||
(None, None) => UiRegion::FULL,
|
(None, None) => UiRegion::FULL,
|
||||||
};
|
};
|
||||||
let placed = painter.place(&self.inner, region).size();
|
let placed = painter.widget_within(&self.inner, region).size();
|
||||||
if had_size { placed } else { size }
|
if had_size { placed } else { size }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,12 +13,12 @@ impl Widget for Scroll {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let container_len = painter.px_len(self.axis);
|
let container_len = painter.px_len(self.axis);
|
||||||
// Draw in the whole container only when its scrolling-axis length is
|
// Draw in the whole container only when its scrolling-axis length is
|
||||||
// not already known, then place it at the scrolled offset.
|
// not already known, then draw it at the scrolled offset.
|
||||||
let (answer_len, measured) = match painter.known_len(&self.inner, self.axis, UiRegion::FULL)
|
let (answer_len, measured) = match painter.known_len(&self.inner, self.axis, UiRegion::FULL)
|
||||||
{
|
{
|
||||||
Some(len) => (len, None),
|
Some(len) => (len, None),
|
||||||
None => {
|
None => {
|
||||||
let size = painter.place(&self.inner, UiRegion::FULL).size();
|
let size = painter.widget_within(&self.inner, UiRegion::FULL).size();
|
||||||
(size.axis(self.axis), Some(size))
|
(size.axis(self.axis), Some(size))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -43,7 +43,7 @@ impl Widget for Scroll {
|
|||||||
|
|
||||||
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
||||||
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
||||||
let placed = painter.place(&self.inner, region).size();
|
let placed = painter.widget_within(&self.inner, region).size();
|
||||||
measured.unwrap_or_else(|| Size::from_axis(self.axis, answer_len, placed.axis(!self.axis)))
|
measured.unwrap_or_else(|| Size::from_axis(self.axis, answer_len, placed.axis(!self.axis)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -21,8 +21,8 @@ pub struct Span {
|
|||||||
impl Widget for Span {
|
impl Widget for Span {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let axis = self.dir.axis;
|
let axis = self.dir.axis;
|
||||||
// A length for every child before any is placed: from its own hint
|
// A length for every child before their final boxes are chosen: from
|
||||||
// where it has one, and from drawing it where it does not.
|
// a hint where one exists, and from drawing otherwise.
|
||||||
let mut cursor = UiScalar::rel_min();
|
let mut cursor = UiScalar::rel_min();
|
||||||
let mut lens = Vec::with_capacity(self.children.len());
|
let mut lens = Vec::with_capacity(self.children.len());
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
@@ -33,7 +33,7 @@ impl Widget for Span {
|
|||||||
let region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
let region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||||
let len = match painter.known_len(child, axis, region) {
|
let len = match painter.known_len(child, axis, region) {
|
||||||
Some(len) => len,
|
Some(len) => len,
|
||||||
None => painter.place(child, region).len(axis),
|
None => painter.widget_within(child, region).len(axis),
|
||||||
};
|
};
|
||||||
cursor.px += len.px + self.gap;
|
cursor.px += len.px + self.gap;
|
||||||
cursor.rel += len.rel;
|
cursor.rel += len.rel;
|
||||||
@@ -47,24 +47,28 @@ impl Widget for Span {
|
|||||||
// beside 300 px is full at 600 and overfull at 400. The answer is
|
// beside 300 px is full at 600 and overfull at 400. The answer is
|
||||||
// the same on either side of the length the fixed parts alone fill.
|
// the same on either side of the length the fixed parts alone fill.
|
||||||
let fixed = 1.0 - total.rel;
|
let fixed = 1.0 - total.rel;
|
||||||
let shares = total.leftover > 0.0 && fixed * painter.px_len(axis) > total.px;
|
let mut shares = false;
|
||||||
if total.leftover > 0.0 {
|
if total.leftover > 0.0 {
|
||||||
let range = if fixed > 0.0 {
|
let current = painter.px_len(axis);
|
||||||
|
let holds = if fixed > 0.0 {
|
||||||
let full = total.px / fixed;
|
let full = total.px / fixed;
|
||||||
|
shares = current > full;
|
||||||
match shares {
|
match shares {
|
||||||
true => full..=f32::INFINITY,
|
true => Holds::exact(full.next_up()..=f32::INFINITY),
|
||||||
false => f32::NEG_INFINITY..=full,
|
false => Holds::exact(f32::NEG_INFINITY..=full),
|
||||||
}
|
}
|
||||||
} else if fixed < 0.0 {
|
} else if fixed < 0.0 {
|
||||||
let full = total.px / fixed;
|
let full = total.px / fixed;
|
||||||
|
shares = current < full;
|
||||||
match shares {
|
match shares {
|
||||||
true => f32::NEG_INFINITY..=full,
|
true => Holds::exact(f32::NEG_INFINITY..=full.next_down()),
|
||||||
false => full..=f32::INFINITY,
|
false => Holds::exact(full..=f32::INFINITY),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f32::NEG_INFINITY..=f32::INFINITY
|
shares = total.px < 0.0;
|
||||||
|
Holds::ANY
|
||||||
};
|
};
|
||||||
painter.holds(axis, range);
|
painter.holds(axis, holds);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut start = UiScalar::rel_min();
|
let mut start = UiScalar::rel_min();
|
||||||
@@ -93,7 +97,7 @@ impl Widget for Span {
|
|||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
region.flip(axis);
|
region.flip(axis);
|
||||||
}
|
}
|
||||||
let placed = painter.place(child, region);
|
let placed = painter.widget_within(child, region);
|
||||||
if self.ortho == OrthoSize::Children {
|
if self.ortho == OrthoSize::Children {
|
||||||
let used = placed.len(!axis);
|
let used = placed.len(!axis);
|
||||||
// Choosing between a fixed and a relative length from the
|
// Choosing between a fixed and a relative length from the
|
||||||
|
|||||||
+11
-1
@@ -31,6 +31,14 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn region_node(self) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||||
|
|state| {
|
||||||
|
let id = self.add(state);
|
||||||
|
state.ui_mut().widgets.set_region_node(id, true);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
move |state| SetSize {
|
move |state| SetSize {
|
||||||
@@ -85,7 +93,9 @@ widget_trait! {
|
|||||||
|
|
||||||
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
|
fn scrollable(self) -> impl WidgetIdFn<Rsc, Scroll> where Rsc: HasEvents {
|
||||||
move |state| {
|
move |state| {
|
||||||
Scroll::new(self.add_strong(state), Axis::Y)
|
let inner = self.add(state);
|
||||||
|
state.ui_mut().widgets.set_region_node(inner, true);
|
||||||
|
Scroll::new(inner.upgrade(state), Axis::Y)
|
||||||
.on(CursorSense::Scroll, |ctx, rsc| {
|
.on(CursorSense::Scroll, |ctx, rsc| {
|
||||||
let delta = ctx.data.scroll_delta.y * 50.0;
|
let delta = ctx.data.scroll_delta.y * 50.0;
|
||||||
ctx.widget(rsc).scroll(delta);
|
ctx.widget(rsc).scroll(delta);
|
||||||
|
|||||||
+2
-3
@@ -1,6 +1,5 @@
|
|||||||
//! What the vertex shader's move-chain walk costs, against how deep the chain
|
//! What the vertex shader's move-chain walk costs, against how many nested
|
||||||
//! is. Every active widget owns a slot, so the depth a primitive resolves
|
//! region nodes a primitive resolves through.
|
||||||
//! through is its depth in the widget tree.
|
|
||||||
//!
|
//!
|
||||||
//! cargo test --release --test chain_cost -- --ignored --nocapture
|
//! cargo test --release --test chain_cost -- --ignored --nocapture
|
||||||
//!
|
//!
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ impl Widget for BranchesOnMeasurement {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let mut top = UiRegion::FULL;
|
let mut top = UiRegion::FULL;
|
||||||
top.y.end = top.y.start.offset(40.0);
|
top.y.end = top.y.start.offset(40.0);
|
||||||
let measured = painter.place(&self.probe, top).len(Axis::X);
|
let measured = painter.widget_within(&self.probe, top).len(Axis::X);
|
||||||
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
|
let px = measured.apply_leftover().to_px(painter.px_len(Axis::X));
|
||||||
|
|
||||||
let mut below = UiRegion::FULL;
|
let mut below = UiRegion::FULL;
|
||||||
below.y.start = below.y.start.offset(40.0);
|
below.y.start = below.y.start.offset(40.0);
|
||||||
match px > self.threshold {
|
match px > self.threshold {
|
||||||
true => painter.place(&self.wide, below),
|
true => painter.widget_within(&self.wide, below),
|
||||||
false => painter.place(&self.narrow, below),
|
false => painter.widget_within(&self.narrow, below),
|
||||||
};
|
};
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-9
@@ -1,10 +1,9 @@
|
|||||||
//! Random trees, checked against building the same tree cold.
|
//! Random trees, checked against building the same tree cold.
|
||||||
//!
|
//!
|
||||||
//! A frame reaches its layout by keeping most of the last one: slots
|
//! A frame reaches its layout by keeping most of the last one: movable regions
|
||||||
//! rewritten, some widgets drawn again, the rest untouched. The property here
|
//! or primitive boxes rewritten, some widgets drawn again, the rest untouched.
|
||||||
//! is that what comes out is the tree a cold start would have produced, so
|
//! The result must be the tree a cold start would have produced, so anything
|
||||||
//! anything the retained path carried over that it should not have shows up
|
//! wrongly retained shows up as a difference in somebody's box.
|
||||||
//! as a difference in somebody's box.
|
|
||||||
//!
|
//!
|
||||||
//! `iris::random` grows the tree and `examples/random.rs` draws one. A seed is
|
//! `iris::random` grows the tree and `examples/random.rs` draws one. A seed is
|
||||||
//! the whole reproduction; `a_long_run_of_seeds_agrees` is the ignored sweep
|
//! the whole reproduction; `a_long_run_of_seeds_agrees` is the ignored sweep
|
||||||
@@ -220,8 +219,8 @@ fn describe(id: WidgetId, h: &Harness) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Every widget in one tree against the matching widget in the other. A
|
/// Every widget in one tree against the matching widget in the other. A
|
||||||
/// mismatch prints the widget's ancestry, marking the ones that own a slot,
|
/// mismatch prints the widget's ancestry, marking region nodes, since where
|
||||||
/// since where two trees disagree is rarely where the cause is.
|
/// two trees disagree is rarely where the cause is.
|
||||||
fn assert_same(seed: u64, what: &str, warm: (&Harness, &Tree), cold: (&Harness, &Tree)) {
|
fn assert_same(seed: u64, what: &str, warm: (&Harness, &Tree), cold: (&Harness, &Tree)) {
|
||||||
let ((wh, wt), (ch, ct)) = (warm, cold);
|
let ((wh, wt), (ch, ct)) = (warm, cold);
|
||||||
assert_eq!(wt.ids.len(), ct.ids.len(), "seed {seed}: different trees");
|
assert_eq!(wt.ids.len(), ct.ids.len(), "seed {seed}: different trees");
|
||||||
@@ -243,11 +242,11 @@ fn assert_same(seed: u64, what: &str, warm: (&Harness, &Tree), cold: (&Harness,
|
|||||||
let mut at = Some(w);
|
let mut at = Some(w);
|
||||||
while let Some(id) = at {
|
while let Some(id) = at {
|
||||||
let active = &wh.render.active[&id];
|
let active = &wh.render.active[&id];
|
||||||
let slot = match active.move_idx == active.parent_move {
|
let node = match active.move_idx == active.parent_move {
|
||||||
true => "",
|
true => "",
|
||||||
false => "*",
|
false => "*",
|
||||||
};
|
};
|
||||||
chain.push(format!("{}{slot}", describe(id, wh)));
|
chain.push(format!("{}{node}", describe(id, wh)));
|
||||||
at = active.parent;
|
at = active.parent;
|
||||||
}
|
}
|
||||||
println!(
|
println!(
|
||||||
|
|||||||
+37
-11
@@ -119,8 +119,8 @@ fn a_resize_lands_where_a_cold_start_would() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
||||||
let mut h = Harness::new((400, 400));
|
let mut h = Harness::new((400, 400));
|
||||||
// The panel fills a stack sized by its sibling, so it is drawn in the
|
// The panel fills a stack sized by its sibling, so it is first asked in
|
||||||
// whole box and then placed in the shorter one. Reusing it in that fixed
|
// the whole box and then given the shorter one. Reusing it in that fixed
|
||||||
// box afterwards would leave it whatever height it happened to have.
|
// box afterwards would leave it whatever height it happened to have.
|
||||||
let panel = rect(Color::BLUE).add(&mut h.rsc);
|
let panel = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
let leaf = rect(Color::RED).height(100).add(&mut h.rsc);
|
let leaf = rect(Color::RED).height(100).add(&mut h.rsc);
|
||||||
@@ -142,15 +142,15 @@ fn a_moved_subtree_takes_its_children_with_it() {
|
|||||||
let mut h = Harness::new((400, 400));
|
let mut h = Harness::new((400, 400));
|
||||||
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
let row = inner.pad(10).height(40).add(&mut h.rsc);
|
let row = inner.pad(10).height(40).region_node().add(&mut h.rsc);
|
||||||
h.set_root((first, row).span(Dir::DOWN));
|
h.set_root((first, row).span(Dir::DOWN));
|
||||||
assert_corners!(h, inner, (10, 50), (390, 70));
|
assert_corners!(h, inner, (10, 50), (390, 70));
|
||||||
|
|
||||||
h.rsc[first].y = Some(Len::px(80));
|
h.rsc[first].y = Some(Len::px(80));
|
||||||
h.frame();
|
h.frame();
|
||||||
|
|
||||||
// The row is the same shape somewhere else, so one slot moved it and
|
// The row opted into one movable region, so its descendants follow one
|
||||||
// `inner`'s own region was never rewritten.
|
// entry rather than having their primitive regions rewritten.
|
||||||
assert_corners!(h, inner, (10, 90), (390, 110));
|
assert_corners!(h, inner, (10, 90), (390, 110));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,21 +202,29 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn only_a_container_that_places_its_children_lengthens_the_chain() {
|
fn only_a_region_node_lengthens_the_chain_and_it_can_be_removed() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let leaf = rect(Color::BLUE).add(&mut h.rsc);
|
let leaf = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
// Four widgets between the span and the leaf, none of which places what
|
|
||||||
// it draws, so all of them share the span's slot.
|
|
||||||
let buried = leaf.pad(4).pad(4).pad(4).pad(4).add(&mut h.rsc);
|
let buried = leaf.pad(4).pad(4).pad(4).pad(4).add(&mut h.rsc);
|
||||||
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
h.set_root((bar, buried).span(Dir::RIGHT));
|
h.set_root((bar, buried).span(Dir::RIGHT));
|
||||||
|
|
||||||
let slot = h.render.active[&leaf.id()].parent_move;
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||||
|
assert_eq!(h.render.moves.depth(move_idx), 1, "only the root region");
|
||||||
|
|
||||||
|
h.rsc.widgets_mut().set_region_node(buried, true);
|
||||||
|
h.frame();
|
||||||
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
h.render.moves.depth(slot),
|
h.render.moves.depth(move_idx),
|
||||||
2,
|
2,
|
||||||
"the span above the leaf, and the root the window is held in"
|
"the opted-in widget's region and the root region"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
h.rsc.widgets_mut().set_region_node(buried, false);
|
||||||
|
h.frame();
|
||||||
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
||||||
|
assert_eq!(h.render.moves.depth(move_idx), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A span that sizes from its children passes their `leftover` weight up
|
/// A span that sizes from its children passes their `leftover` weight up
|
||||||
@@ -412,3 +420,21 @@ fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
|||||||
// is leftover is omitted.
|
// is leftover is omitted.
|
||||||
assert_corners!(h, mixed, (100, 0), (120, 20));
|
assert_corners!(h, mixed, (100, 0), (120, 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
||||||
|
let mut h = Harness::new((100, 100));
|
||||||
|
let first = rect(Color::RED).height(90).add(&mut h.rsc);
|
||||||
|
let a = rect(Color::GREEN).add(&mut h.rsc);
|
||||||
|
let b = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let inner = (a, b).span(Dir::DOWN).gap(4).add(&mut h.rsc);
|
||||||
|
h.set_root((first, inner).span(Dir::DOWN));
|
||||||
|
assert!(h.region(&a).is_some());
|
||||||
|
assert!(h.region(&b).is_some());
|
||||||
|
|
||||||
|
h.rsc[first].y = Some(Len::px(96.0));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert!(h.region(&a).is_none());
|
||||||
|
assert!(h.region(&b).is_none());
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ fn a_selected_widget_retains_its_layout_events() {
|
|||||||
diagnostics::clear_traced_widgets();
|
diagnostics::clear_traced_widgets();
|
||||||
let _ = diagnostics::take();
|
let _ = diagnostics::take();
|
||||||
let mut harness = Harness::new((400, 200));
|
let mut harness = Harness::new((400, 200));
|
||||||
let leaf = rect(Color::RED).add(&mut harness.rsc);
|
let leaf = rect(Color::RED).region_node().add(&mut harness.rsc);
|
||||||
let other = rect(Color::BLUE).add(&mut harness.rsc);
|
let other = rect(Color::BLUE).add(&mut harness.rsc);
|
||||||
let root = (leaf, other).span(Dir::RIGHT).add(&mut harness.rsc);
|
let root = (leaf, other).span(Dir::RIGHT).add(&mut harness.rsc);
|
||||||
harness.set_root(root);
|
harness.set_root(root);
|
||||||
@@ -46,7 +46,7 @@ fn a_selected_widget_retains_its_layout_events() {
|
|||||||
report
|
report
|
||||||
.traces()
|
.traces()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|event| matches!(event, TraceEvent::Placed { id, .. } if *id == leaf.id()))
|
.any(|event| matches!(event, TraceEvent::RegionNode { id, .. } if *id == leaf.id()))
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
report
|
report
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! What re-placing a subtree costs per frame, as a load for a counter rather
|
//! What remapping a subtree costs per frame, as a load for a counter rather
|
||||||
//! than a check. A span of 200 fixed-height rows, five primitives each, with
|
//! than a check. A span of 200 fixed-height rows, five primitives each, with
|
||||||
//! the row above them changing height every frame, so every row below is
|
//! the row above them changing height every frame, so every row below is
|
||||||
//! offered a box the same shape somewhere else.
|
//! offered a box the same shape somewhere else.
|
||||||
@@ -6,9 +6,7 @@
|
|||||||
//! cargo test --release --test replace_cost -- --ignored
|
//! cargo test --release --test replace_cost -- --ignored
|
||||||
//! perf stat -e instructions:u target/release/.../replace_cost-* --ignored
|
//! perf stat -e instructions:u target/release/.../replace_cost-* --ignored
|
||||||
//!
|
//!
|
||||||
//! Wall time is the wrong number here; see `draw_cost.rs`. Measured on
|
//! Wall time is the wrong number here; see `draw_cost.rs`.
|
||||||
//! 2026-09-14 at 1.98M instructions per frame, against 2.38M for rewriting
|
|
||||||
//! each row's regions instead and 7.13M for redrawing them.
|
|
||||||
|
|
||||||
use iris::harness::Harness;
|
use iris::harness::Harness;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
@@ -18,7 +16,7 @@ const FRAMES: usize = 200;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "measurement, not a check"]
|
#[ignore = "measurement, not a check"]
|
||||||
fn replacing_rows_every_frame() {
|
fn remapping_rows_every_frame() {
|
||||||
let mut h = Harness::new((1920, 1200));
|
let mut h = Harness::new((1920, 1200));
|
||||||
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||||
let mut span = Span::empty(Dir::DOWN);
|
let mut span = Span::empty(Dir::DOWN);
|
||||||
|
|||||||
+28
-6
@@ -111,6 +111,28 @@ fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
|||||||
assert_corners!(h, second, (150, 0), (400, 200));
|
assert_corners!(h, second, (150, 0), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn moving_an_ordinary_subtree_remaps_its_mask() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (first, _) = counted(&mut h, Size::from((100, 200)), false);
|
||||||
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let masked = inner.masked().add(&mut h.rsc);
|
||||||
|
h.set_root((first, masked).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
h.rsc[first].size = Size::from((150, 200));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
let active = &h.render.active[&masked.id()];
|
||||||
|
assert_eq!(
|
||||||
|
h.rsc.ui().masks[active.mask.idx()].region,
|
||||||
|
UiRegion::new(
|
||||||
|
UiSpan::new(UiScalar::px(150.0), UiScalar::rel_max()),
|
||||||
|
UiSpan::FULL,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_corners!(h, inner, (150, 0), (400, 200));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
@@ -121,7 +143,7 @@ fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
|
|
||||||
// The preceding fixed child makes the remaining box this child's real
|
// The preceding fixed child makes the remaining box this child's real
|
||||||
// box, so measuring it also draws it in its final place.
|
// box, so measuring it also draws it in its final box.
|
||||||
assert_eq!(draws.get(), settled + 1);
|
assert_eq!(draws.get(), settled + 1);
|
||||||
assert_corners!(h, second, (150, 0), (400, 200));
|
assert_corners!(h, second, (150, 0), (400, 200));
|
||||||
}
|
}
|
||||||
@@ -132,7 +154,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
|||||||
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
|
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
|
||||||
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
|
||||||
// The span takes one child's length from its hint and has to draw the
|
// The span takes one child's length from its hint and has to draw the
|
||||||
// other to find out, so only the second is drawn before it is placed.
|
// other to find out, so only the second is drawn before its final box.
|
||||||
let hinted = told.width(100).add(&mut h.rsc);
|
let hinted = told.width(100).add(&mut h.rsc);
|
||||||
h.set_root((hinted, asked).span(Dir::RIGHT));
|
h.set_root((hinted, asked).span(Dir::RIGHT));
|
||||||
|
|
||||||
@@ -140,7 +162,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
asked_draws.get(),
|
asked_draws.get(),
|
||||||
2,
|
2,
|
||||||
"drawn to be measured, then again to be placed"
|
"drawn to be measured, then again in its final box"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,10 +195,10 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_placed_child_survives_the_next_frame() {
|
fn a_span_child_survives_the_next_frame() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
// Both children declare a length, so the span places them from their hints
|
// Both children declare a length, so the span chooses their boxes from
|
||||||
// rather than drawing them to find out.
|
// hints rather than drawing them to find out.
|
||||||
let top = rect(Color::RED).height(80).add(&mut h.rsc);
|
let top = rect(Color::RED).height(80).add(&mut h.rsc);
|
||||||
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
||||||
h.set_root((top, bottom).span(Dir::DOWN));
|
h.set_root((top, bottom).span(Dir::DOWN));
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
use iris::harness::{Harness, assert_corners};
|
use iris::harness::{Harness, assert_corners};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scrollable_enables_a_region_node_but_raw_scroll_does_not() {
|
||||||
|
let mut h = Harness::new((100, 100));
|
||||||
|
let default_child = ().add(&mut h.rsc);
|
||||||
|
let _default = default_child.scrollable().add(&mut h.rsc);
|
||||||
|
assert!(h.rsc.widgets().is_region_node(default_child));
|
||||||
|
h.rsc.widgets_mut().set_region_node(default_child, false);
|
||||||
|
assert!(!h.rsc.widgets().is_region_node(default_child));
|
||||||
|
|
||||||
|
let raw_child = ().add(&mut h.rsc);
|
||||||
|
let _raw = Scroll::new(raw_child.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc);
|
||||||
|
assert!(!h.rsc.widgets().is_region_node(raw_child));
|
||||||
|
|
||||||
|
let explicit = ().region_node().add(&mut h.rsc);
|
||||||
|
assert!(h.rsc.widgets().is_region_node(explicit));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_scrollable_child_can_drop_its_region_node() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let top = rect(Color::RED).height(200).add(&mut h.rsc);
|
||||||
|
let bottom = rect(Color::BLUE).height(200).add(&mut h.rsc);
|
||||||
|
let content = (top, bottom).span(Dir::DOWN).add(&mut h.rsc);
|
||||||
|
h.set_root(content.scrollable());
|
||||||
|
h.rsc.widgets_mut().set_region_node(content, false);
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
h.move_to((200, 100));
|
||||||
|
h.scroll((0, 1));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert!(!h.rsc.widgets().is_region_node(content));
|
||||||
|
assert_corners!(h, top, (0, -150), (400, 50));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_wheel_scrolls_the_content_and_stops_at_its_end() {
|
fn a_wheel_scrolls_the_content_and_stops_at_its_end() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
|
|||||||
@@ -62,8 +62,8 @@ fn dump(label: &str, report: &diag::Report, text: WidgetId) {
|
|||||||
TraceEvent::SizeRead { id, reader, size } if *id == text => {
|
TraceEvent::SizeRead { id, reader, size } if *id == text => {
|
||||||
println!(" size read by {reader:?}: {size}")
|
println!(" size read by {reader:?}: {size}")
|
||||||
}
|
}
|
||||||
TraceEvent::Placed { id, parent, region } if *id == text => {
|
TraceEvent::RegionNode { id, parent, region } if *id == text => {
|
||||||
println!(" placed by {parent:?} at {region:?}")
|
println!(" region node under {parent:?} at {region:?}")
|
||||||
}
|
}
|
||||||
TraceEvent::Reuse { id, outcome } if *id == text => println!(" reuse: {outcome:?}"),
|
TraceEvent::Reuse { id, outcome } if *id == text => println!(" reuse: {outcome:?}"),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
+3
-3
@@ -216,8 +216,8 @@ fn swapping_two_children_lands_where_growing_them_that_way_does() {
|
|||||||
|
|
||||||
/// Eight widgets, shrunk from 80. The scroll decides how wide to make its
|
/// Eight widgets, shrunk from 80. The scroll decides how wide to make its
|
||||||
/// content from what the content says, and hands that box down through a
|
/// content from what the content says, and hands that box down through a
|
||||||
/// pass-through; the span under it was placed once, in that box, so nothing
|
/// pass-through; the span under it was given that box once, so nothing at its
|
||||||
/// at its own edge says the box was its own answer.
|
/// own edge says the box was its own answer.
|
||||||
fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget<Span>; 2]) {
|
fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget<Span>; 2]) {
|
||||||
let words = "Wrapping shapes one source into as many lines as the box leaves room for,";
|
let words = "Wrapping shapes one source into as many lines as the box leaves room for,";
|
||||||
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
||||||
@@ -277,7 +277,7 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_span_placed_once_in_a_box_its_answer_decided() {
|
fn a_span_given_the_box_its_answer_decided_matches_a_cold_layout() {
|
||||||
let mut warm = Harness::new((640, 900));
|
let mut warm = Harness::new((640, 900));
|
||||||
let (ids, spans) = plant_scrolled(&mut warm, false);
|
let (ids, spans) = plant_scrolled(&mut warm, false);
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|||||||
Reference in new issue
Block a user