Settle dirty layout from the leaves upward
This commit is contained in:
1 parent
a640c6cce2
commit
84f589e364
5 files changed
+69
-68
No files matched your search
@@ -22,8 +22,6 @@ pub struct Painter<'a> {
|
|||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub(super) size_deps: Vec<WidgetId>,
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
pub(super) reads_output: bool,
|
pub(super) reads_output: bool,
|
||||||
/// Whether this widget is drawing in the same pixel-sized box as before.
|
|
||||||
pub(super) same_box: bool,
|
|
||||||
/// The slot this widget's primitives are positioned through: its own if
|
/// The slot this widget's primitives are positioned through: its own if
|
||||||
/// its parent placed it, otherwise the nearest ancestor that has one.
|
/// its parent placed it, otherwise the nearest ancestor that has one.
|
||||||
pub(super) move_idx: MoveIdx,
|
pub(super) move_idx: MoveIdx,
|
||||||
@@ -146,21 +144,6 @@ impl<'a> Painter<'a> {
|
|||||||
Some(hint)
|
Some(hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A clean child's retained size, when this widget's own constraints are
|
|
||||||
/// unchanged. This is the answer from its last real draw, not a guess.
|
|
||||||
pub fn retained_size<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> Option<Size> {
|
|
||||||
if !self.same_box || self.rsc.widgets().needs_redraw.contains(&id.id()) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let active = self.state.active.get(&id.id())?;
|
|
||||||
if active.parent != Some(self.id) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let size = active.size;
|
|
||||||
self.depend_on_size(id);
|
|
||||||
Some(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn depend_on_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
fn depend_on_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||||
if !self.size_deps.contains(&child.id()) {
|
if !self.size_deps.contains(&child.id()) {
|
||||||
self.size_deps.push(child.id());
|
self.size_deps.push(child.id());
|
||||||
|
|||||||
+37
-15
@@ -74,10 +74,10 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.resized = false;
|
|
||||||
if rsc.widgets().has_updates() {
|
if rsc.widgets().has_updates() {
|
||||||
self.redraw_updates(rsc);
|
self.redraw_updates(rsc);
|
||||||
}
|
}
|
||||||
|
self.resized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) {
|
fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) {
|
||||||
@@ -109,19 +109,18 @@ impl UiRenderState {
|
|||||||
parent_move: MoveIdx,
|
parent_move: MoveIdx,
|
||||||
slotted: bool,
|
slotted: bool,
|
||||||
mask: MaskIdx,
|
mask: MaskIdx,
|
||||||
old_active: Option<ActiveData>,
|
old_children: Option<Vec<WidgetId>>,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) -> Size {
|
) -> Size {
|
||||||
let mut old_active = old_active;
|
let mut old_children = old_children.unwrap_or_default();
|
||||||
if self.active.contains_key(&id) {
|
if self.active.contains_key(&id) {
|
||||||
if let Some(size) = self.try_reuse(id, region, parent_move, rsc) {
|
if let Some(size) = self.try_reuse(id, region, parent_move, rsc) {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
// if not, then maintain resize and track old children to remove unneeded
|
// if not, then maintain resize and track old children to remove unneeded
|
||||||
old_active = self.remove(id, false, rsc);
|
let active = self.remove(id, false, rsc).unwrap();
|
||||||
|
old_children = active.children;
|
||||||
}
|
}
|
||||||
let previous_px = old_active.as_ref().map(|active| active.px);
|
|
||||||
let old_children = old_active.map(|active| active.children).unwrap_or_default();
|
|
||||||
|
|
||||||
// draw widget
|
// draw widget
|
||||||
let (move_idx, local) = match slotted {
|
let (move_idx, local) = match slotted {
|
||||||
@@ -134,7 +133,6 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let px = self.px_of(move_idx, local);
|
let px = self.px_of(move_idx, local);
|
||||||
let same_box = previous_px == Some(px);
|
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
self.draw_started.insert(id);
|
self.draw_started.insert(id);
|
||||||
|
|
||||||
@@ -149,7 +147,6 @@ impl UiRenderState {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
size_deps: Vec::new(),
|
||||||
reads_output: false,
|
reads_output: false,
|
||||||
same_box,
|
|
||||||
move_idx,
|
move_idx,
|
||||||
rsc,
|
rsc,
|
||||||
};
|
};
|
||||||
@@ -168,7 +165,6 @@ impl UiRenderState {
|
|||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
reads_output,
|
reads_output,
|
||||||
same_box: _,
|
|
||||||
move_idx,
|
move_idx,
|
||||||
layer,
|
layer,
|
||||||
id,
|
id,
|
||||||
@@ -401,12 +397,31 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
||||||
while let Some(&id) = rsc.widgets().needs_redraw.iter().next() {
|
// A reader's answer is only valid after every dirty size it reads has
|
||||||
|
// settled. Equal-depth widgets are independent, so their order does
|
||||||
|
// not matter.
|
||||||
|
while let Some(id) = rsc
|
||||||
|
.widgets()
|
||||||
|
.needs_redraw
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.max_by_key(|&id| self.depth(id))
|
||||||
|
{
|
||||||
self.redraw(id, rsc);
|
self.redraw(id, rsc);
|
||||||
}
|
}
|
||||||
rsc.free();
|
rsc.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn depth(&self, id: WidgetId) -> usize {
|
||||||
|
let mut depth = 0;
|
||||||
|
let mut at = Some(id);
|
||||||
|
while let Some(id) = at {
|
||||||
|
at = self.active.get(&id).and_then(|active| active.parent);
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
depth
|
||||||
|
}
|
||||||
|
|
||||||
pub fn root_changed<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
pub fn root_changed<'a>(&self, root: impl Into<Option<&'a StrongWidget>>) -> bool {
|
||||||
root.into().map(|r| r.id()) != self.old_root
|
root.into().map(|r| r.id()) != self.old_root
|
||||||
}
|
}
|
||||||
@@ -462,7 +477,9 @@ impl UiRenderState {
|
|||||||
.active
|
.active
|
||||||
.get(&id)
|
.get(&id)
|
||||||
.is_some_and(|active| self.px_of(active.parent_move, active.region) != active.px);
|
.is_some_and(|active| self.px_of(active.parent_move, active.region) != active.px);
|
||||||
if box_changed && let Some(top) = self.mark_readers(id, rsc) {
|
if (self.resized || box_changed)
|
||||||
|
&& let Some(top) = self.mark_readers(id, rsc)
|
||||||
|
{
|
||||||
self.redraw(top, rsc);
|
self.redraw(top, rsc);
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
return;
|
return;
|
||||||
@@ -486,15 +503,20 @@ impl UiRenderState {
|
|||||||
active.parent_move,
|
active.parent_move,
|
||||||
active.move_idx != active.parent_move,
|
active.move_idx != active.parent_move,
|
||||||
active.mask,
|
active.mask,
|
||||||
Some(active),
|
Some(active.children),
|
||||||
rsc,
|
rsc,
|
||||||
);
|
);
|
||||||
|
|
||||||
if size != was
|
if size != was
|
||||||
&& let Some(top) = self.mark_readers(id, rsc)
|
&& let Some(parent) = self.active.get(&id).and_then(|active| active.parent)
|
||||||
|
&& self
|
||||||
|
.active
|
||||||
|
.get(&parent)
|
||||||
|
.is_some_and(|active| active.size_deps.contains(&id))
|
||||||
{
|
{
|
||||||
self.redraw(top, rsc);
|
// Propagate one dependency edge at a time. If drawing the reader
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
// does not change its own size, nothing above it can observe this.
|
||||||
|
rsc.widgets_mut().needs_redraw.insert(parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
-1
@@ -148,6 +148,22 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn align(&mut self) -> Align {
|
||||||
|
let mut axis = || match self.rng.below(4) {
|
||||||
|
0 => None,
|
||||||
|
1 => Some(AxisAlign::Neg),
|
||||||
|
2 => Some(AxisAlign::Center),
|
||||||
|
_ => Some(AxisAlign::Pos),
|
||||||
|
};
|
||||||
|
let (mut x, y) = (axis(), axis());
|
||||||
|
// Aligning on neither axis is just another transparent wrapper and
|
||||||
|
// would leave this branch unexercised.
|
||||||
|
if x.is_none() && y.is_none() {
|
||||||
|
x = Some(AxisAlign::Center);
|
||||||
|
}
|
||||||
|
Align { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
/// A declared size over half the tree, kept where a test can change it.
|
/// A declared size over half the tree, kept where a test can change it.
|
||||||
fn sized(&mut self, inner: StrongWidget) -> StrongWidget {
|
fn sized(&mut self, inner: StrongWidget) -> StrongWidget {
|
||||||
if !self.rng.chance() {
|
if !self.rng.chance() {
|
||||||
@@ -171,7 +187,8 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
|||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
return self.leaf();
|
return self.leaf();
|
||||||
}
|
}
|
||||||
if self.rng.below(6) == 0 {
|
let positioned = self.rng.below(6);
|
||||||
|
if positioned == 0 {
|
||||||
// Scrolling reads the pixel length of its box, which nothing
|
// Scrolling reads the pixel length of its box, which nothing
|
||||||
// else here does, and gives its child a box longer than its own.
|
// else here does, and gives its child a box longer than its own.
|
||||||
let inner = self.node(depth - 1);
|
let inner = self.node(depth - 1);
|
||||||
@@ -182,6 +199,17 @@ impl<Rsc: UiRsc + 'static> Grow<'_, Rsc> {
|
|||||||
self.tree.ids.push(id.id());
|
self.tree.ids.push(id.id());
|
||||||
return id.add_strong(self.rsc);
|
return id.add_strong(self.rsc);
|
||||||
}
|
}
|
||||||
|
if positioned == 1 {
|
||||||
|
let inner = self.node(depth - 1);
|
||||||
|
let inner = self.sized(inner);
|
||||||
|
let id = Aligned {
|
||||||
|
inner,
|
||||||
|
align: self.align(),
|
||||||
|
}
|
||||||
|
.add_strong(self.rsc);
|
||||||
|
self.tree.ids.push(id.id());
|
||||||
|
return id;
|
||||||
|
}
|
||||||
if self.rng.below(4) == 0 {
|
if self.rng.below(4) == 0 {
|
||||||
let inner = self.node(depth - 1);
|
let inner = self.node(depth - 1);
|
||||||
let inner = self.sized(inner);
|
let inner = self.sized(inner);
|
||||||
|
|||||||
@@ -13,13 +13,9 @@ impl Widget for Scroll {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let output_len = painter.output_size().axis(self.axis);
|
let output_len = painter.output_size().axis(self.axis);
|
||||||
let container_len = UiScalar::abs(painter.px_size().axis(self.axis));
|
let container_len = UiScalar::abs(painter.px_size().axis(self.axis));
|
||||||
// Its last measured size stays valid while neither the child nor this
|
// Draw in the whole container to learn the content's length, then
|
||||||
// container's constraints changed. Otherwise draw it in the whole
|
// place it at the scrolled offset.
|
||||||
// container to learn its length, then place it at the scrolled offset.
|
let child = painter.place(&self.inner, UiRegion::FULL).size();
|
||||||
let child = match painter.retained_size(&self.inner) {
|
|
||||||
Some(size) => size,
|
|
||||||
None => painter.place(&self.inner, UiRegion::FULL).size(),
|
|
||||||
};
|
|
||||||
let content_len = child
|
let content_len = child
|
||||||
.axis(self.axis)
|
.axis(self.axis)
|
||||||
.apply_rest()
|
.apply_rest()
|
||||||
|
|||||||
@@ -131,34 +131,6 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
|||||||
assert_eq!(draws.get(), settled + 1);
|
assert_eq!(draws.get(), settled + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scrolling_reuses_the_clean_contents_size() {
|
|
||||||
let mut h = Harness::new((400, 200));
|
|
||||||
let (inner, draws) = counted(&mut h, Size::from((400, 600)), OnResize::Translate);
|
|
||||||
let scroll = Scroll::new(inner.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc);
|
|
||||||
h.set_root(scroll);
|
|
||||||
let settled = draws.get();
|
|
||||||
|
|
||||||
h.rsc[scroll].scroll(40.0);
|
|
||||||
h.frame();
|
|
||||||
|
|
||||||
assert_eq!(draws.get(), settled);
|
|
||||||
assert_corners!(h, inner, (0, -360), (400, 240));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn scrolling_remeasures_changed_contents() {
|
|
||||||
let mut h = Harness::new((400, 200));
|
|
||||||
let (inner, _) = counted(&mut h, Size::from((400, 600)), OnResize::Translate);
|
|
||||||
let scroll = Scroll::new(inner.add_strong(&mut h.rsc), Axis::Y).add(&mut h.rsc);
|
|
||||||
h.set_root(scroll);
|
|
||||||
|
|
||||||
h.rsc[inner].size = Size::from((400, 800));
|
|
||||||
h.frame();
|
|
||||||
|
|
||||||
assert_corners!(h, inner, (0, -600), (400, 200));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_placed_child_survives_the_next_frame() {
|
fn a_placed_child_survives_the_next_frame() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
|
|||||||
Reference in new issue
Block a user