Settle growing layout branches in one frame
This commit is contained in:
1 parent
f95835593f
commit
46e6edfd0b
3 files changed
+90
-4
No files matched your search
@@ -1207,6 +1207,16 @@ impl UiRenderState {
|
|||||||
|
|
||||||
/// redraws a widget that's currently active (drawn)
|
/// redraws a widget that's currently active (drawn)
|
||||||
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
||||||
|
self.redraw_and_settle(id, rsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure a changed branch toward the root, then revisit each widget
|
||||||
|
/// whose reported size changed on the way back down. The upward pass gives
|
||||||
|
/// every parent the new child size; the downward pass is what lets those
|
||||||
|
/// children draw inside the final boxes their parents chose. Without it a
|
||||||
|
/// newly grown subtree can retain the provisional (even inverted) region
|
||||||
|
/// it was measured in until an unrelated later update redraws it.
|
||||||
|
fn redraw_and_settle(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
// An ancestor is drawing this widget right now, and that draw is
|
// An ancestor is drawing this widget right now, and that draw is
|
||||||
// about to write fresh primitives for it. Drawing it a second time
|
// about to write fresh primitives for it. Drawing it a second time
|
||||||
@@ -1248,11 +1258,15 @@ impl UiRenderState {
|
|||||||
// relay out too. Checked after the real draw, not before it --
|
// relay out too. Checked after the real draw, not before it --
|
||||||
// there is no query left that answers "what size would this be"
|
// there is no query left that answers "what size would this be"
|
||||||
// without actually drawing (LAYOUT.md section 5).
|
// without actually drawing (LAYOUT.md section 5).
|
||||||
if let Some(pid) = parent {
|
let changed = self.active.get(&id).map(|a| a.size) != Some(old_size);
|
||||||
let new_size = self.active.get(&id).map(|a| a.size);
|
if changed {
|
||||||
if new_size != Some(old_size) {
|
if let Some(pid) = parent {
|
||||||
self.redraw(pid, rsc);
|
self.redraw_and_settle(pid, rsc);
|
||||||
}
|
}
|
||||||
|
// The parent pass above has now placed this widget in its final
|
||||||
|
// region. Draw it once more there; unchanged descendants still
|
||||||
|
// take draw_inner's retained fast path.
|
||||||
|
self.redraw_and_settle(id, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1172,3 +1172,67 @@ fn a_span_of_padded_children_inside_a_span_draws_each_where_its_box_is() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Growing an already-drawn row first measures its new child against the
|
||||||
|
/// row's old height. That provisional box can end before it starts when the
|
||||||
|
/// old trailing edge is above the new child's cursor. The size must bubble to
|
||||||
|
/// `LazySpan` and the corrected allocation must travel back down before this
|
||||||
|
/// update is presented; a later stream event is not a layout pass.
|
||||||
|
#[test]
|
||||||
|
fn a_new_child_in_a_growing_lazy_row_uses_its_final_box_immediately() {
|
||||||
|
const FIRST: f32 = 30.0;
|
||||||
|
const SECOND: f32 = 70.0;
|
||||||
|
const GAP: f32 = 8.0;
|
||||||
|
|
||||||
|
let mut rsc = TestRsc {
|
||||||
|
ui: UiData::default(),
|
||||||
|
};
|
||||||
|
let first = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
||||||
|
let first_id = first.id();
|
||||||
|
let first = rsc.ui.widgets.add_strong(Sized {
|
||||||
|
inner: first.any(),
|
||||||
|
x: None,
|
||||||
|
y: Some(Len::abs(FIRST)),
|
||||||
|
});
|
||||||
|
let mut contents = Span::empty(Dir::DOWN).gap(Len::abs(GAP));
|
||||||
|
contents.push(first.any());
|
||||||
|
let contents = rsc.ui.widgets.add_strong(contents);
|
||||||
|
let contents_w = contents.weak();
|
||||||
|
let row = rsc.ui.widgets.add_strong(Sized {
|
||||||
|
inner: contents.any(),
|
||||||
|
x: Some(Len::rest(1.0)),
|
||||||
|
y: None,
|
||||||
|
});
|
||||||
|
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
|
||||||
|
list.push_back(LazyItem::new(0, row.any()));
|
||||||
|
let root = rsc.ui.widgets.add_strong(list).any();
|
||||||
|
|
||||||
|
let mut render = UiRenderState::new();
|
||||||
|
render.resize((200.0, 200.0));
|
||||||
|
render.update(&root, &mut rsc);
|
||||||
|
|
||||||
|
let second = rsc.ui.widgets.add_strong(Rect::new(UiColor::WHITE));
|
||||||
|
let second_id = second.id();
|
||||||
|
let second = rsc.ui.widgets.add_strong(Sized {
|
||||||
|
inner: second.any(),
|
||||||
|
x: None,
|
||||||
|
y: Some(Len::abs(SECOND)),
|
||||||
|
});
|
||||||
|
rsc.ui
|
||||||
|
.widgets
|
||||||
|
.get_mut(&contents_w)
|
||||||
|
.unwrap()
|
||||||
|
.push(second.any());
|
||||||
|
render.update(&root, &mut rsc);
|
||||||
|
|
||||||
|
let first = render.primitive_corners(render.first_primitive(first_id).unwrap(), &rsc);
|
||||||
|
let second = render.primitive_corners(render.first_primitive(second_id).unwrap(), &rsc);
|
||||||
|
assert!(
|
||||||
|
(second.top_left.y - (first.bot_right.y + GAP)).abs() < 0.01,
|
||||||
|
"the new child should start after the old child and its gap: first={first:?} second={second:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
(second.bot_right.y - second.top_left.y - SECOND).abs() < 0.01,
|
||||||
|
"the new child retained its provisional box: {second:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -31,4 +31,12 @@ impl Widget for Sized {
|
|||||||
Axis::Y => self.y,
|
Axis::Y => self.y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn requires_exact_region(&self) -> bool {
|
||||||
|
// `Sized` may be measured in a provisional box and then placed in
|
||||||
|
// the content-sized box it reported. Its own region can be corrected
|
||||||
|
// by a move, but its child consumed the original box and must see the
|
||||||
|
// final one too.
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user