Take a span child's length from its hint, and ask it once in its slot
A share child was drawn in the measuring room and again in its slot, and one record holding two questions made every local change under it defer to the span. Where a rule or a hint gives the length along the span, the first ask answers nothing the rule does not, so the child is asked once, in its slot; the widgets that always report the whole of their box now say so. A hint with a fraction resolves against the frame and pins it. The dump rig prints every cold layout so a change to it shows in a diff.
This commit is contained in:
1 parent
a888717ee9
commit
f6242aa33c
7 files changed
+120
-33
No files matched your search
+19
-7
@@ -259,17 +259,24 @@ impl<'a> Painter<'a> {
|
|||||||
declared_lens(self.rsc.widgets(), id.id())
|
declared_lens(self.rsc.widgets(), id.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a child says its length is without being drawn, if it can say.
|
/// What a child says its length is without being drawn, if it can say,
|
||||||
/// Asking counts as reading its size.
|
/// as the length its draw would report: a fraction in it is resolved
|
||||||
|
/// against this widget's frame, which is the frame a child asked with
|
||||||
|
/// nothing narrowed gets. Asking counts as reading its size.
|
||||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||||
let widgets = self.rsc.widgets();
|
let widgets = self.rsc.widgets();
|
||||||
// A rule is the answer where there is one: it wins over whatever the
|
// A rule is the answer where there is one: it wins over whatever the
|
||||||
// widget would draw, so it has to win over what the widget says too.
|
// widget would draw, so it has to win over what the widget says too.
|
||||||
let hint = widgets.size_rules(id.id()).axis(axis).exact().or_else(|| {
|
let hint = widgets
|
||||||
widgets
|
.size_rules(id.id())
|
||||||
.get_dyn(id.id())
|
.axis(axis)
|
||||||
.and_then(|widget| widget.size_hint(axis))
|
.exact()
|
||||||
});
|
.or_else(|| {
|
||||||
|
widgets
|
||||||
|
.get_dyn(id.id())
|
||||||
|
.and_then(|widget| widget.size_hint(axis))
|
||||||
|
})
|
||||||
|
.map(|hint| hint.within_len(self.frame.axis(axis)));
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::hint_read(id.id(), self.id, axis, hint);
|
diag::hint_read(id.id(), self.id, axis, hint);
|
||||||
match hint {
|
match hint {
|
||||||
@@ -277,6 +284,11 @@ impl<'a> Painter<'a> {
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::HintHits);
|
diag::bump(Counter::HintHits);
|
||||||
self.depend_on(id);
|
self.depend_on(id);
|
||||||
|
// A fraction was just resolved against this frame, so what
|
||||||
|
// this draw does with it is a function of the frame's length.
|
||||||
|
if hint.rel != Rel::ZERO {
|
||||||
|
self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
|
||||||
|
}
|
||||||
Some(hint)
|
Some(hint)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ impl Widget for Branch {
|
|||||||
};
|
};
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||||
|
Some(LayoutLen::LEFTOVER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Spanned {
|
pub struct Spanned {
|
||||||
|
|||||||
@@ -15,4 +15,8 @@ impl Widget for Masked {
|
|||||||
// draw, and the framework would place the drawing it clipped away.
|
// draw, and the framework would place the drawing it clipped away.
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||||
|
Some(LayoutLen::LEFTOVER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -72,6 +72,10 @@ impl Widget for Scroll {
|
|||||||
// is.
|
// is.
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||||
|
Some(LayoutLen::LEFTOVER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scroll {
|
impl Scroll {
|
||||||
|
|||||||
+38
-26
@@ -22,26 +22,38 @@ impl Widget for Span {
|
|||||||
// whole of the row: a span is what contains its children there, and
|
// whole of the row: a span is what contains its children there, and
|
||||||
// nothing divides that axis.
|
// nothing divides that axis.
|
||||||
let across = Place::Within(Part::All);
|
let across = Place::Within(Part::All);
|
||||||
// A length for every child before their final slots are chosen. The
|
// A length for every child before their final slots are chosen: from
|
||||||
// frame passes through unchanged, so `rel(0.5)` is half the area this
|
// a hint where one says, and from drawing otherwise. The frame passes
|
||||||
// span was given whatever else is in it and wherever this child sits
|
// through unchanged, so `rel(0.5)` is half the area this span was
|
||||||
// among them; what it is asked in is the room left from the cursor,
|
// given whatever else is in it and wherever this child sits among
|
||||||
// because a text has to wrap at the width actually there. This is
|
// them; what a drawn child is asked in is the room left from the
|
||||||
// the one ask a fixed child gets: its slot is its answer, and the
|
// cursor, because a text has to wrap at the width actually there.
|
||||||
// drawing is moved there once the shares are known.
|
// This is the one ask a drawn fixed child gets: its slot is its
|
||||||
|
// answer, and the drawing is moved there once the shares are known.
|
||||||
|
// A hinted child is asked once, in its slot.
|
||||||
let mut cursor = Len::rel_min();
|
let mut cursor = Len::rel_min();
|
||||||
let mut sizes = Vec::with_capacity(self.children.len());
|
let mut lens = Vec::with_capacity(self.children.len());
|
||||||
|
let mut measured = Vec::with_capacity(self.children.len());
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
let room = Place::Within(Part::From(along(cursor, far)));
|
let size = match painter.size_hint(child, axis) {
|
||||||
let size = painter
|
Some(len) => {
|
||||||
.widget_at(child, [None; 2], axis.pair(room, across))
|
measured.push(None);
|
||||||
.size();
|
len
|
||||||
let len = size.axis(axis);
|
}
|
||||||
|
None => {
|
||||||
|
let room = Place::Within(Part::From(along(cursor, far)));
|
||||||
|
let size = painter
|
||||||
|
.widget_at(child, [None; 2], axis.pair(room, across))
|
||||||
|
.size();
|
||||||
|
measured.push(Some(size));
|
||||||
|
size.axis(axis)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let len = size;
|
||||||
cursor.px += len.px + self.gap;
|
cursor.px += len.px + self.gap;
|
||||||
cursor.rel += len.rel;
|
cursor.rel += len.rel;
|
||||||
sizes.push(size);
|
lens.push(len);
|
||||||
}
|
}
|
||||||
let lens: Vec<LayoutLen> = sizes.iter().map(|size| size.axis(axis)).collect();
|
|
||||||
|
|
||||||
let gaps = self
|
let gaps = self
|
||||||
.gap
|
.gap
|
||||||
@@ -92,8 +104,8 @@ impl Widget for Span {
|
|||||||
let mut taken = Weight::ZERO;
|
let mut taken = Weight::ZERO;
|
||||||
let mut start = Len::rel_min();
|
let mut start = Len::rel_min();
|
||||||
let mut ortho = LayoutLen::ZERO;
|
let mut ortho = LayoutLen::ZERO;
|
||||||
for (child, size) in self.children.iter().zip(&sizes) {
|
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
|
||||||
let len = size.axis(axis);
|
let len = *len;
|
||||||
// A child asking for nothing but a part of what is left over,
|
// A child asking for nothing but a part of what is left over,
|
||||||
// when nothing is, is not drawn at all. One that also asked for
|
// when nothing is, is not drawn at all. One that also asked for
|
||||||
// pixels or a fraction keeps those and overflows.
|
// pixels or a fraction keeps those and overflows.
|
||||||
@@ -115,20 +127,20 @@ impl Widget for Span {
|
|||||||
// answer inside again. A share is decided here and nowhere
|
// answer inside again. A share is decided here and nowhere
|
||||||
// else: its slot narrows its frame, and the child is asked in
|
// else: its slot narrows its frame, and the child is asked in
|
||||||
// it, since a text wraps at the width it is actually given. A
|
// it, since a text wraps at the width it is actually given. A
|
||||||
// fixed child's slot is its own answer, so its drawing is put
|
// fixed child's slot is its own answer, so a drawing made in the
|
||||||
// there as it is.
|
// room is put there as it is, and one not made yet is made here.
|
||||||
let slot = along(from, start);
|
let slot = along(from, start);
|
||||||
let place = axis.pair(Place::Fill(Part::From(slot)), across);
|
let place = axis.pair(Place::Fill(Part::From(slot)), across);
|
||||||
let used = match len.leftover > Weight::ZERO && shares {
|
let mut narrow = [None; 2];
|
||||||
true => {
|
if len.leftover > Weight::ZERO && shares {
|
||||||
let mut narrow = [None; 2];
|
narrow[axis as usize] = Some(slot.len());
|
||||||
narrow[axis as usize] = Some(slot.len());
|
}
|
||||||
painter.widget_at(child, narrow, place).len(!axis)
|
let used = match (measured, narrow[axis as usize]) {
|
||||||
}
|
(Some(size), None) => {
|
||||||
false => {
|
|
||||||
painter.place_at(child, place);
|
painter.place_at(child, place);
|
||||||
size.axis(!axis)
|
size.axis(!axis)
|
||||||
}
|
}
|
||||||
|
_ => painter.widget_at(child, narrow, place).len(!axis),
|
||||||
};
|
};
|
||||||
if shrinks {
|
if shrinks {
|
||||||
// Choosing between a fixed and a relative length from the
|
// Choosing between a fixed and a relative length from the
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ impl Widget for Stack {
|
|||||||
}
|
}
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Without a sizing child a stack is whatever box it is given, which it
|
||||||
|
/// can say without drawing anything.
|
||||||
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||||
|
match self.size {
|
||||||
|
StackSize::Default => Some(LayoutLen::LEFTOVER),
|
||||||
|
StackSize::Child(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//! Prints where a cold layout puts every widget of many grown trees, so two
|
||||||
|
//! commits can be compared on cold layout alone. The warm/cold oracle cannot
|
||||||
|
//! see a change that moves cold layout, since both of its sides move; this
|
||||||
|
//! can, by diffing its output across the change:
|
||||||
|
//!
|
||||||
|
//! IRIS_DUMP_SEEDS=400 IRIS_DUMP_DEPTH=5 cargo test --release \
|
||||||
|
//! --test layout_dump -- --ignored --nocapture > /tmp/before.txt
|
||||||
|
//!
|
||||||
|
//! then the same after, and `diff` the two. A line is one widget: the seed,
|
||||||
|
//! its index in creation order, and its box in window pixels, or `-` where
|
||||||
|
//! it is not drawn.
|
||||||
|
|
||||||
|
use iris::harness::Harness;
|
||||||
|
use iris::random::{Edits, grow};
|
||||||
|
|
||||||
|
fn env<T: std::str::FromStr>(name: &str, fallback: T) -> T {
|
||||||
|
std::env::var(name)
|
||||||
|
.ok()
|
||||||
|
.and_then(|value| value.parse().ok())
|
||||||
|
.unwrap_or(fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "a dump to diff across commits, not a check"]
|
||||||
|
fn every_cold_layout_is_printed() {
|
||||||
|
let seeds = env("IRIS_DUMP_SEEDS", 400_u64);
|
||||||
|
let depth = env("IRIS_DUMP_DEPTH", 5_usize);
|
||||||
|
let mut out = String::new();
|
||||||
|
for seed in 1..=seeds {
|
||||||
|
let mut harness = Harness::new((1920.0, 1200.0));
|
||||||
|
let (root, tree) = grow(&mut harness.rsc, seed, depth, &Edits::default());
|
||||||
|
harness.state.root = Some(root);
|
||||||
|
harness.frame();
|
||||||
|
for (index, id) in tree.ids.iter().enumerate() {
|
||||||
|
match harness.region(id) {
|
||||||
|
Some(region) => out.push_str(&format!("{seed} {index} {region:?}\n")),
|
||||||
|
None => out.push_str(&format!("{seed} {index} -\n")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print!("{out}");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user