Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e44dea34b4 | ||
|
|
a0693acc56 | ||
|
|
25e456e0b5 | ||
|
|
53b00c68e9 |
No files matched your search
@@ -74,4 +74,12 @@ impl ActiveData {
|
||||
pub fn holds_at(&self, px: crate::PxVec2) -> bool {
|
||||
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
||||
}
|
||||
|
||||
/// Whether what it answered still stands for a box of these pixel
|
||||
/// lengths -- the box it was asked in, where `holds` is about the box its
|
||||
/// answer then chose.
|
||||
pub fn answers_at(&self, px: crate::PxVec2) -> bool {
|
||||
let (_, holds) = self.answer;
|
||||
holds[0].contains(px.x) && holds[1].contains(px.y)
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -168,20 +168,10 @@ impl<'a> Painter<'a> {
|
||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||
let declared = self.declared_lens(id);
|
||||
let align = self.rsc.widgets().alignment(id.id());
|
||||
// A rule this box was already chosen from is not resolved into it a
|
||||
// second time. The box is that rule's length already, so resolving
|
||||
// it again takes the fraction twice -- a widget declaring half of a
|
||||
// stack, in the stack its own answer made half a row, is a quarter
|
||||
// of the row. Pixels survive it, being the same length wherever they
|
||||
// are taken from, which is why only a share ever shrank.
|
||||
let resolve = AXES.map(|axis| match decided[axis as usize] {
|
||||
true => None,
|
||||
false => declared[axis as usize],
|
||||
});
|
||||
// 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.
|
||||
let local = match resolve.iter().any(Option::is_some) {
|
||||
true => declared_box(region, resolve, align),
|
||||
let local = match declared.iter().any(Option::is_some) {
|
||||
true => declared_box(region, declared, align),
|
||||
false => region,
|
||||
};
|
||||
let within = match local == UiRegion::FULL {
|
||||
@@ -403,6 +393,14 @@ impl<'a> Painter<'a> {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// The part of this widget's box that something of `size` takes, at the
|
||||
/// near edge. A container that reports one child's size gives every child
|
||||
/// this, so what it draws is inside what it says it occupies.
|
||||
pub fn box_of(&self, size: Size) -> UiRegion {
|
||||
let lens = placed_lens(size, [None; 2], [false; 2]);
|
||||
placed_box(UiRegion::FULL, lens, RegionAlign::NEAR)
|
||||
}
|
||||
|
||||
/// This widget's box in pixels. Reading it makes the drawing one that
|
||||
/// holds for this box only, until `holds` says how far it goes.
|
||||
pub fn px_size(&mut self) -> PxVec2 {
|
||||
|
||||
+56
-40
@@ -43,7 +43,9 @@ pub struct UiRenderState {
|
||||
|
||||
old_root: Option<WidgetId>,
|
||||
/// Whether the output has changed since the last update. A frame is
|
||||
/// owed for that whether or not anything has to be drawn again.
|
||||
/// owed for that whether or not anything has to be drawn again: every
|
||||
/// fraction becomes pixels against the output, in the shader's uniform
|
||||
/// as well as here.
|
||||
resized: bool,
|
||||
/// A widget's move slot, which outlives any one `ActiveData`: a redraw
|
||||
/// replaces that while its children go on pointing at the slot.
|
||||
@@ -83,13 +85,28 @@ impl UiRenderState {
|
||||
/// size is applied where a fraction becomes pixels -- here in `to_px`,
|
||||
/// and in the shader by its uniform. A resize therefore rewrites no
|
||||
/// retained entry at all.
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||
///
|
||||
/// The root is the only widget a resize marks, and only where the new
|
||||
/// output falls outside what its answer holds for: that range is the
|
||||
/// intersection of everything under it, so admitting the new output says
|
||||
/// the whole tree still stands. Where it does not, the ordinary walk
|
||||
/// draws the root, and each widget's own range decides how far down the
|
||||
/// new length reaches.
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>, widgets: &mut Widgets) {
|
||||
let size = PxVec2::from_f32(size.into());
|
||||
if size == self.output_size {
|
||||
return;
|
||||
}
|
||||
self.output_size = size;
|
||||
self.resized = true;
|
||||
let Some(root) = self.old_root else { return };
|
||||
let stands = self
|
||||
.active
|
||||
.get(&root)
|
||||
.is_some_and(|active| active.answers_at(active.given_len.to_px(size)));
|
||||
if !stands {
|
||||
widgets.needs_redraw.insert(root);
|
||||
}
|
||||
}
|
||||
|
||||
/// The root is asked about in the output: the window is where a fraction
|
||||
@@ -143,17 +160,6 @@ impl UiRenderState {
|
||||
if self.root_changed(root) {
|
||||
self.redraw_all(root, rsc);
|
||||
self.old_root = root.map(|r| r.id());
|
||||
} else if let Some(root) = root
|
||||
&& self.resized
|
||||
{
|
||||
// The output is the root's box, so a resize is that box changing
|
||||
// length, found the way every other box change is found. Before
|
||||
// anything dirty settles, so that whatever a new output draws
|
||||
// again is drawn once, in the box it will have.
|
||||
let region = Self::root_region(root.id(), rsc.widgets());
|
||||
let info = self.root_info(region);
|
||||
let answer = self.draw_inner(root.id(), region, info, None, rsc);
|
||||
self.active.get_mut(&root.id()).unwrap().answer = answer;
|
||||
}
|
||||
self.resized = false;
|
||||
if rsc.widgets().has_updates() {
|
||||
@@ -196,14 +202,10 @@ impl UiRenderState {
|
||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||
}
|
||||
let align = rsc.widgets().alignment(id);
|
||||
// Nothing this widget has is an answer while something it measured
|
||||
// is dirty: settling that changes what it would report, and a widget
|
||||
// settled inside its parent's draw tells nobody -- the comparison
|
||||
// that marks a reader is in `redraw`, which is not what asked here.
|
||||
// Both retained routes are an answer, so the question is asked once
|
||||
// rather than by each of them.
|
||||
let stale =
|
||||
rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets());
|
||||
// Nothing this widget measured can be dirty while it draws: layout is
|
||||
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||
// its own parent, and a deferred one leaves that parent marked.
|
||||
let stale = rsc.widgets().needs_redraw.contains(&id);
|
||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||
let retained = match replace_answer || stale {
|
||||
true => None,
|
||||
@@ -251,7 +253,18 @@ impl UiRenderState {
|
||||
active.answer = settled;
|
||||
active.decided = info.decided;
|
||||
active.own_align = align;
|
||||
active.depth = info.depth;
|
||||
// A subtree can be reused whole under a different parent -- same box,
|
||||
// same layer, same region node -- and nothing in the drawing says it
|
||||
// changed hands. Two things read who its parent is: a deferral, which
|
||||
// marks whoever has it to draw, and the old parent's list of children,
|
||||
// which its next draw undraws whatever is missing from.
|
||||
let old_parent = std::mem::replace(&mut active.parent, info.parent);
|
||||
if old_parent != info.parent
|
||||
&& let Some(old_parent) = old_parent
|
||||
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
||||
{
|
||||
old_parent.children.retain(|child| *child != id);
|
||||
}
|
||||
settled
|
||||
}
|
||||
|
||||
@@ -484,7 +497,7 @@ impl UiRenderState {
|
||||
parent_move: MoveIdx,
|
||||
widgets: &Widgets,
|
||||
) -> Option<(Size, [Holds; 2])> {
|
||||
if widgets.needs_redraw.contains(&id) || self.dirty_size_under(id, widgets) {
|
||||
if widgets.needs_redraw.contains(&id) {
|
||||
return None;
|
||||
}
|
||||
let active = self.active.get(&id)?;
|
||||
@@ -509,22 +522,7 @@ impl UiRenderState {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let (size, holds) = active.answer;
|
||||
(holds[0].contains(info.px.x) && holds[1].contains(info.px.y)).then_some((size, holds))
|
||||
}
|
||||
|
||||
/// Whether anything whose size this widget's own size was read from is
|
||||
/// dirty, which makes what it would answer not yet known. It also keeps
|
||||
/// a reader that asks first from laying out twice, which is all it was
|
||||
/// here for while a changed size was thought to reach its reader in any
|
||||
/// order; it does not, where the change settles inside the reader's own
|
||||
/// draw.
|
||||
fn dirty_size_under(&self, id: WidgetId, widgets: &Widgets) -> bool {
|
||||
self.active.get(&id).is_some_and(|active| {
|
||||
active.size_deps.iter().any(|child| {
|
||||
widgets.needs_redraw.contains(child) || self.dirty_size_under(*child, widgets)
|
||||
})
|
||||
})
|
||||
active.answers_at(info.px).then_some(active.answer)
|
||||
}
|
||||
|
||||
/// The pixel lengths of the box a widget was given and of the box it was
|
||||
@@ -638,12 +636,12 @@ impl UiRenderState {
|
||||
self.remap_subtree(id, &remap, info.parent_move, rsc);
|
||||
}
|
||||
}
|
||||
self.redepth(id, info.depth);
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.region = region;
|
||||
active.given = region;
|
||||
active.given_len = info.given_len;
|
||||
active.offer_len = info.offer_len;
|
||||
active.depth = info.depth;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
match (moved, has_region_node) {
|
||||
@@ -667,6 +665,24 @@ impl UiRenderState {
|
||||
Some(answer)
|
||||
}
|
||||
|
||||
/// A reused subtree keeps its shape, so every widget in it moves by the
|
||||
/// same amount -- and where the top of it did not move, none of it did,
|
||||
/// which is what makes this free in the ordinary case.
|
||||
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
||||
let Some(active) = self.active.get_mut(&id) else {
|
||||
return;
|
||||
};
|
||||
if active.depth == depth {
|
||||
return;
|
||||
}
|
||||
active.depth = depth;
|
||||
let children = active.children.len();
|
||||
for index in 0..children {
|
||||
let child = self.active[&id].children[index];
|
||||
self.redepth(child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
@@ -26,7 +26,6 @@ impl DefaultAppState for State {
|
||||
.wrap(true)
|
||||
.text_align(Align::LEFT)
|
||||
.pad(16)
|
||||
.width(rel(1.0))
|
||||
.background(panel());
|
||||
|
||||
// Each one takes the whole width, because `text_align` puts the
|
||||
|
||||
+1
-1
@@ -251,7 +251,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
||||
ui_state.renderer.draw();
|
||||
}
|
||||
WindowEvent::Resized(size) => {
|
||||
render.resize((size.width, size.height));
|
||||
render.resize((size.width, size.height), rsc.widgets_mut());
|
||||
ui_state.renderer.resize(size)
|
||||
}
|
||||
WindowEvent::KeyboardInput { event, .. } => {
|
||||
|
||||
+3
-3
@@ -144,9 +144,9 @@ impl Harness {
|
||||
// bound that comes with `SyncSender` is far past anything a test
|
||||
// leaves unread.
|
||||
let (send, updates) = sync_channel(1024);
|
||||
let rsc = DefaultRsc::init(Arc::new(Queue(send)));
|
||||
let mut rsc = DefaultRsc::init(Arc::new(Queue(send)));
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize(size);
|
||||
render.resize(size, rsc.widgets_mut());
|
||||
Self {
|
||||
rsc,
|
||||
render,
|
||||
@@ -161,7 +161,7 @@ impl Harness {
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||
self.render.resize(size);
|
||||
self.render.resize(size, self.rsc.widgets_mut());
|
||||
}
|
||||
|
||||
/// Changes a length rule after the fact, the way `.width()` sets one.
|
||||
|
||||
+16
-34
@@ -46,43 +46,26 @@ impl Widget for Span {
|
||||
|sum, len| sum + *len,
|
||||
);
|
||||
|
||||
// What is left for the shares to divide: the box less everything
|
||||
// fixed, as a length of the box rather than a number of pixels.
|
||||
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||
// beside 300 px is full at 600 and overfull at 400. The room to
|
||||
// divide is `len * fixed - total.px`, and the length where it runs
|
||||
// out is exactly the box a parent sizing itself from this answer
|
||||
// hands back -- which is why this used to need a margin either side
|
||||
// of the boundary, and why it does not now: that box and this sum are
|
||||
// whole counts of the same step, and both routes to it land on the
|
||||
// same count. What the generated oracle checks is the consequence,
|
||||
// since which children exist at all turns on this.
|
||||
let fixed = Rel::ONE - total.rel;
|
||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||
// itself, and answered back through the same expression, so the
|
||||
// boundary is the drawing's own and not a second way of finding it:
|
||||
// the three cases a rounded division needed -- the fixed parts
|
||||
// growing slower than the box, faster, or exactly with it -- are the
|
||||
// sign of `room.rel`, which `through` already reads. What the
|
||||
// generated oracle checks is the consequence, since which children
|
||||
// exist at all turns on this.
|
||||
let mut shares = false;
|
||||
if total.leftover > Weight::ZERO {
|
||||
let current = painter.px_len(axis);
|
||||
let holds = if fixed > Rel::ZERO {
|
||||
// The box length the fixed parts alone fill.
|
||||
let full = total.px.div(fixed);
|
||||
shares = current > full;
|
||||
match shares {
|
||||
true => Holds::from(full.next_up()..=Px::MAX),
|
||||
false => Holds::from(Px::MIN..=full),
|
||||
}
|
||||
} else if fixed < Rel::ZERO {
|
||||
// The relative parts grow faster than the box does, so here
|
||||
// a shorter box is the one that leaves room.
|
||||
let full = total.px.div(fixed);
|
||||
shares = current < full;
|
||||
match shares {
|
||||
true => Holds::from(Px::MIN..=full.next_down()),
|
||||
false => Holds::from(full..=Px::MAX),
|
||||
}
|
||||
} else {
|
||||
// The relative parts take exactly the box, whatever it is, so
|
||||
// the only room is what negative pixels leave.
|
||||
shares = total.px < Px::ZERO;
|
||||
Holds::ANY
|
||||
shares = room.to_px(painter.px_len(axis)) > Px::ZERO;
|
||||
let holds = match shares {
|
||||
true => Holds::from(Px::STEP..=Px::MAX),
|
||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
||||
};
|
||||
painter.holds(axis, holds);
|
||||
painter.holds(axis, holds.through(room));
|
||||
}
|
||||
|
||||
// Across itself a span is as long as its longest child -- unless a
|
||||
@@ -99,7 +82,6 @@ impl Widget for Span {
|
||||
// row.
|
||||
let mut fixed = Len::rel_min();
|
||||
let mut taken = Weight::ZERO;
|
||||
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
||||
let mut start = Len::rel_min();
|
||||
let mut ortho = LayoutLen::ZERO;
|
||||
for (child, len) in self.children.iter().zip(&lens) {
|
||||
|
||||
@@ -13,42 +13,31 @@ impl Widget for Stack {
|
||||
StackSize::Default => None,
|
||||
StackSize::Child(i) => Some(i),
|
||||
};
|
||||
// Every child gets the whole of this stack's box, the sizing one
|
||||
// included, and the stack is then handed a box of the length that
|
||||
// child asked for. Not the part of the box that length takes: the
|
||||
// stack's own box becomes that length, and taking the fraction of it
|
||||
// again is the fraction twice -- a child asking for half of a stack
|
||||
// that is already half a row would have a quarter of the row.
|
||||
//
|
||||
// It cannot be told apart by asking whether this box is the answer
|
||||
// yet, either. A drawing has to be a function of the box alone, since
|
||||
// moving the stack into the box it asked for reuses the drawing by
|
||||
// scaling it, and a drawing made a fraction of one box is right in
|
||||
// any other. So: fractions of this box throughout, and the move is
|
||||
// the whole of the difference.
|
||||
let region = UiRegion::FULL;
|
||||
// Whichever child sizes the stack is asked here and not again below,
|
||||
// on the layer it ends up on: a retained drawing belongs to the layer
|
||||
// it was made on, so measuring it anywhere else costs a second
|
||||
// drawing of it. Its box is its own answer, so the answer is not
|
||||
// placed inside it again.
|
||||
// Whichever child sizes the stack decides the box every child gets.
|
||||
// The stack reports that size, so a child given a longer box would
|
||||
// draw outside what the stack says it occupies.
|
||||
let size = match sizing.and_then(|i| self.children.get(i).map(|c| (i, c))) {
|
||||
// On the layer that child ends up on, so the ask below is a reuse
|
||||
// rather than a second drawing of it somewhere else: a retained
|
||||
// drawing belongs to the layer it was made on.
|
||||
Some((i, child)) => {
|
||||
painter.child_layer_at(i);
|
||||
painter
|
||||
.widget_at(child, region, region.size(), [true; 2])
|
||||
.size()
|
||||
painter.widget(child).size()
|
||||
}
|
||||
None => Size::LEFTOVER,
|
||||
};
|
||||
let region = painter.box_of(size);
|
||||
for (i, child) in self.children.iter().enumerate() {
|
||||
if sizing == Some(i) {
|
||||
continue;
|
||||
}
|
||||
painter.child_layer_at(i);
|
||||
// A box that owes nothing to this child's own answer: where it
|
||||
// sits in one bigger than itself is its own business.
|
||||
painter.widget_within(child, region);
|
||||
// The sizing child placed its own content in the box its answer
|
||||
// decided, and this box was derived from that answer, so applying
|
||||
// its alignment again here would place it twice. Every other
|
||||
// child is handed a box that owes nothing to its own answer, and
|
||||
// where it sits in one bigger than itself is its own business.
|
||||
match sizing == Some(i) {
|
||||
true => painter.widget_at(child, region, region.size(), [true; 2]),
|
||||
false => painter.widget_within(child, region),
|
||||
};
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
@@ -82,31 +82,6 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() {
|
||||
assert!(crowded > whole_row, "{crowded} against {whole_row}");
|
||||
}
|
||||
|
||||
/// A stack takes its size from one child and gives every child that size, so
|
||||
/// a child asking for half of it is asking for half of what it is itself the
|
||||
/// size of. Once the stack has been placed at the length it reported that
|
||||
/// length is the box, and taking the fraction of it again takes it twice:
|
||||
/// half a row became a quarter, and a further stack around it a further half.
|
||||
/// Nothing pinned it because a pixel is the same length wherever it is taken
|
||||
/// from, so only a share ever shrank -- and warm and cold shrink alike, so no
|
||||
/// oracle saw it either.
|
||||
#[test]
|
||||
fn a_stack_sized_by_a_child_does_not_take_that_childs_fraction_twice() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let half = rect(Color::RED).width(rel(0.5)).add(&mut h.rsc);
|
||||
let behind = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let stack = Stack {
|
||||
children: vec![behind.add_strong(&mut h.rsc), half.add_strong(&mut h.rsc)],
|
||||
size: StackSize::Child(1),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.set_root((stack,).span(Dir::RIGHT).width(rel(1.0)));
|
||||
|
||||
assert_corners!(h, stack, (0, 0), (200, 200));
|
||||
assert_corners!(h, half, (0, 0), (200, 200));
|
||||
assert_corners!(h, behind, (0, 0), (200, 200));
|
||||
}
|
||||
|
||||
/// The same reading through a pad: its inset is the whole box less the
|
||||
/// padding, so half of the inset plus the padding is half the box plus one
|
||||
/// padding, not two.
|
||||
|
||||
@@ -628,3 +628,112 @@ fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() {
|
||||
h.frame();
|
||||
assert_corners!(h, inner, (100, 0), (400, 200));
|
||||
}
|
||||
|
||||
/// The two spans a subtree changes hands between, and the branch that is not
|
||||
/// in the tree yet -- kept alive by the test until it is.
|
||||
struct Handover {
|
||||
leaf: WidgetId,
|
||||
first: WeakWidget<Span>,
|
||||
second: WeakWidget<Span>,
|
||||
root: WeakWidget<Span>,
|
||||
spare: StrongWidget,
|
||||
}
|
||||
|
||||
/// A subtree that changes hands while its box does not move, so nothing about
|
||||
/// reusing its drawing says it changed parents. `deeper` puts a span between
|
||||
/// the root and `second`, so it changes depth by changing hands as well.
|
||||
fn plant_handover(h: &mut Harness, moved: bool, deeper: bool, width: f32) -> Handover {
|
||||
let leaf = rect(Color::RED).add(&mut h.rsc);
|
||||
let sized = leaf.width(width).add(&mut h.rsc);
|
||||
let holder = (sized,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
let first = Span {
|
||||
children: match moved {
|
||||
true => Vec::new(),
|
||||
false => vec![holder.add_strong(&mut h.rsc)],
|
||||
},
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let second = Span {
|
||||
children: match moved {
|
||||
true => vec![holder.add_strong(&mut h.rsc)],
|
||||
false => Vec::new(),
|
||||
},
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let branch = match deeper {
|
||||
true => (second,).span(Dir::RIGHT).add_strong(&mut h.rsc),
|
||||
false => second.add_strong(&mut h.rsc),
|
||||
};
|
||||
let (in_tree, spare) = match moved {
|
||||
true => (branch, first.add_strong(&mut h.rsc)),
|
||||
false => (first.add_strong(&mut h.rsc), branch),
|
||||
};
|
||||
let root = Span {
|
||||
children: vec![in_tree],
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||
Handover {
|
||||
leaf: sized.id(),
|
||||
first,
|
||||
second,
|
||||
root,
|
||||
spare,
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the subtree and swaps the branch it sits in for the one it left.
|
||||
fn hand_over(h: &mut Harness, tree: Handover) -> WidgetId {
|
||||
let holder = h.rsc[tree.first].children.remove(0);
|
||||
h.rsc[tree.second].children.push(holder);
|
||||
h.rsc[tree.root].children.clear();
|
||||
h.rsc[tree.root].children.push(tree.spare);
|
||||
h.frame();
|
||||
tree.leaf
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_subtree_that_changed_parents_is_not_undrawn_by_the_one_it_left() {
|
||||
let mut warm = Harness::new((400, 200));
|
||||
let tree = plant_handover(&mut warm, false, false, 40.0);
|
||||
warm.frame();
|
||||
let leaf = hand_over(&mut warm, tree);
|
||||
|
||||
let mut cold = Harness::new((400, 200));
|
||||
let grown = plant_handover(&mut cold, true, false, 40.0);
|
||||
cold.frame();
|
||||
|
||||
assert_eq!(
|
||||
warm.region(&leaf),
|
||||
cold.region(&grown.leaf),
|
||||
"the span it left still listed it and undrew it"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_subtree_that_changed_parents_settles_at_the_depth_it_moved_to() {
|
||||
let mut warm = Harness::new((400, 200));
|
||||
let tree = plant_handover(&mut warm, false, true, 40.0);
|
||||
warm.frame();
|
||||
let leaf = hand_over(&mut warm, tree);
|
||||
// After it has changed hands, so what has to reach the new parent is a
|
||||
// change made under the subtree it now holds.
|
||||
warm.set_len(leaf, Axis::X, LayoutLen::px(90.0));
|
||||
warm.frame();
|
||||
|
||||
let mut cold = Harness::new((400, 200));
|
||||
let grown = plant_handover(&mut cold, true, true, 90.0);
|
||||
cold.frame();
|
||||
|
||||
assert_eq!(
|
||||
warm.region(&leaf),
|
||||
cold.region(&grown.leaf),
|
||||
"the span it moved to is the one the change has to reach"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user