Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e166e005dc | ||
|
|
38eba543f6 | ||
|
|
2bc6bdfc77 | ||
|
|
d8ae9c3bdd |
No files matched your search
+24
-20
@@ -55,23 +55,26 @@ impl Holds {
|
||||
if rel == 0 {
|
||||
return Self::ANY;
|
||||
}
|
||||
// In half steps. The box a length was composed down the chain from
|
||||
// and the box the same length is measured against the window in are
|
||||
// two routes to one number, each rounding where the other does not,
|
||||
// and each rounding drops a whole step since `Fixed::mul` truncates:
|
||||
// two steps either side. The multiply on the way in drops a step of
|
||||
// its own, and only downward, so it is one more step at the top and
|
||||
// nothing at the bottom -- and the whole of a box has no multiply in
|
||||
// it, however many pixels were added to it, since multiplying by one
|
||||
// is exact and taking the pixels off again is too. Allowing for it
|
||||
// there anyway compounded, a step a level down a chain of widgets
|
||||
// each taking the whole of its parent, which is the unsound
|
||||
// direction: a range wider than what a drawing holds for admits
|
||||
// reusing it where it does not hold.
|
||||
// In half steps, and no more than the arithmetic needs: too wide a
|
||||
// range admits reusing a drawing where it does not hold, and too
|
||||
// narrow a one leaves out the box a drawing was made in, which the
|
||||
// `Holds` assertion in `draw_at` catches. `ROUTES` is at that floor
|
||||
// -- two half steps fires it -- and tightening both ends moved not
|
||||
// one of the rig's work counters, so the slack is not buying reuse.
|
||||
//
|
||||
// `ROUTES` covers a box composed down the chain against the same box
|
||||
// measured against the window: two routes to one number, each
|
||||
// rounding where the other does not. `way_in` covers the multiply
|
||||
// this inverts, which drops a step and only ever downward, so it
|
||||
// belongs at the top of the range alone -- and the whole of a box has
|
||||
// no multiply in it, however many pixels were added to it, since
|
||||
// multiplying by one is exact and taking the pixels off again is too.
|
||||
// Allowing for it there anyway compounded, a step a level down a
|
||||
// chain of widgets each taking the whole of its parent.
|
||||
//
|
||||
// Shifted by half of what a `Rel` counts in, to divide by the
|
||||
// fraction: exact until the division takes it back to the grid.
|
||||
const ROUTES: i64 = 4;
|
||||
const ROUTES: i64 = 3;
|
||||
let px = len.px.raw() as i64;
|
||||
let half_rel = REL_SHIFT - 1;
|
||||
let way_in = match rel == Rel::ONE.raw() as i64 {
|
||||
@@ -136,19 +139,20 @@ mod tests {
|
||||
/// taken off it, brings no multiply of its own: only the two routes to
|
||||
/// the same length are left to allow for, and not a rounding that did
|
||||
/// not happen. Widening for it as well grew the interval a level at a
|
||||
/// time down a chain of them.
|
||||
/// time down a chain of them. Three half steps come back as one whole
|
||||
/// one, since dividing by a whole box is dividing by one.
|
||||
#[test]
|
||||
fn the_whole_of_a_box_widens_by_the_routes_alone() {
|
||||
let at = Px::from_int(956);
|
||||
let two_steps = |len: Px| Holds {
|
||||
lo: len - Px::from_raw(2),
|
||||
hi: len + Px::from_raw(2),
|
||||
let one_step = |len: Px| Holds {
|
||||
lo: len - Px::STEP,
|
||||
hi: len + Px::STEP,
|
||||
};
|
||||
assert_eq!(Holds::at(at).through(Len::FULL), two_steps(at));
|
||||
assert_eq!(Holds::at(at).through(Len::FULL), one_step(at));
|
||||
let less_eight = Len::from_parts(Rel::ONE, Px::from_int(-8));
|
||||
assert_eq!(
|
||||
Holds::at(at).through(less_eight),
|
||||
two_steps(at + Px::from_int(8))
|
||||
one_step(at + Px::from_int(8))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1013,9 +1013,16 @@ impl UiRenderState {
|
||||
if at_offer {
|
||||
return;
|
||||
}
|
||||
// Then in the final box its parent chose from that answer. It is kept
|
||||
// if it holds there; otherwise its result is the parent's business.
|
||||
self.draw_inner(id, region, info, None, rsc);
|
||||
// Then in the final box its parent chose from that answer. That box
|
||||
// is already placed, so the near edge goes with it: applying the
|
||||
// widget's own alignment to it again would place its content twice,
|
||||
// the way it did for a region node under a `Stack` once the stack
|
||||
// stopped overriding every child's alignment.
|
||||
let placed_info = DrawInfo {
|
||||
align: Some(RegionAlign::NEAR),
|
||||
..info
|
||||
};
|
||||
self.draw_inner(id, region, placed_info, None, rsc);
|
||||
let active = &self.active[&id];
|
||||
if (active.size, active.holds) != was {
|
||||
rsc.widgets_mut().needs_redraw.insert(parent);
|
||||
|
||||
@@ -7,8 +7,14 @@ pub struct Pad {
|
||||
|
||||
impl Widget for Pad {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
// The inner's own alignment, not the near edge. This reports the
|
||||
// inner's size plus the padding, so where the box is that answer the
|
||||
// inset box is exactly the inner and alignment has no room to move
|
||||
// it; where the box is bigger -- a share of a row, a rule over this
|
||||
// widget -- the slack is the inner's to sit in, and forcing the near
|
||||
// edge pinned it to a corner it had not asked for.
|
||||
let inner = painter
|
||||
.widget_aligned(&self.inner, self.padding.region(), RegionAlign::NEAR)
|
||||
.widget_within(&self.inner, self.padding.region())
|
||||
.size();
|
||||
Size {
|
||||
x: LayoutLen {
|
||||
|
||||
@@ -29,7 +29,15 @@ impl Widget for Stack {
|
||||
let region = painter.box_of(size);
|
||||
for (i, child) in self.children.iter().enumerate() {
|
||||
painter.child_layer_at(i);
|
||||
painter.widget_aligned(child, region, RegionAlign::NEAR);
|
||||
// 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_aligned(child, region, RegionAlign::NEAR),
|
||||
false => painter.widget_within(child, region),
|
||||
};
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
@@ -499,3 +499,124 @@ fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
||||
assert!(h.region(&a).is_none());
|
||||
assert!(h.region(&b).is_none());
|
||||
}
|
||||
|
||||
/// **A stack child smaller than the stack sits where its own alignment
|
||||
/// says.** `Stack` gives every child the box its sizing child defines and
|
||||
/// used to force the near edge on all of them; that override is owed only to
|
||||
/// the sizing child, which has already placed its own content in the box the
|
||||
/// stack derived from its answer. Every other child is handed a box that owes
|
||||
/// nothing to it, so where it sits in one bigger than itself is its own
|
||||
/// business -- and with the override it could not be aligned at all, which is
|
||||
/// what moved the `tabs` example's counters to the wrong corner.
|
||||
#[test]
|
||||
fn a_stack_child_smaller_than_the_stack_keeps_its_own_alignment() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let big = rect(Color::BLUE).add(&mut h.rsc);
|
||||
let small = rect(Color::RED).sized((50, 50)).add(&mut h.rsc);
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_alignment(small.id(), Axis::X, AxisAlign::POS);
|
||||
let (a, b) = (big.add_strong(&mut h.rsc), small.add_strong(&mut h.rsc));
|
||||
let children: Vec<StrongWidget> = vec![a, b];
|
||||
h.set_root(Stack {
|
||||
children,
|
||||
size: StackSize::Default,
|
||||
});
|
||||
|
||||
assert_corners!(h, big, (0, 0), (400, 200));
|
||||
// The far edge on X because it asked for it, the middle on Y because
|
||||
// that is the default.
|
||||
assert_corners!(h, small, (350, 75), (400, 125));
|
||||
}
|
||||
/// Five children of one span, buried under three containers that are each a
|
||||
/// fraction of their parent so no length reaches the window without being
|
||||
/// composed and rounded on the way. Returns each child's drawn width and
|
||||
/// each gap between them, in pixels.
|
||||
fn row_under_fractions(kid: Option<LayoutLen>, gap: f32, box_w: f32) -> (Vec<Px>, Vec<Px>) {
|
||||
let mut h = Harness::new((box_w, 400.0));
|
||||
let mut ids = Vec::new();
|
||||
let mut kids: Vec<StrongWidget> = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let r = rect(Color::RED).add(&mut h.rsc);
|
||||
if let Some(len) = kid {
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rule(r.id(), Axis::X, SizeRule::Exact(len));
|
||||
}
|
||||
ids.push(r.id());
|
||||
kids.push(r.add_strong(&mut h.rsc));
|
||||
}
|
||||
let span = Span {
|
||||
children: kids,
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::from_f32(gap),
|
||||
}
|
||||
.add(&mut h.rsc);
|
||||
let a = (span.width(rel(0.9)),).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
let b = (a.width(rel(0.8)),).span(Dir::RIGHT).add(&mut h.rsc);
|
||||
h.set_root((b.width(rel(0.7)),).span(Dir::RIGHT));
|
||||
let boxes: Vec<_> = ids
|
||||
.iter()
|
||||
.map(|id| h.region(id).expect("a child drew nothing"))
|
||||
.collect();
|
||||
(
|
||||
boxes.iter().map(|b| b.bot_right.x - b.top_left.x).collect(),
|
||||
boxes
|
||||
.windows(2)
|
||||
.map(|p| p[1].top_left.x - p[0].bot_right.x)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// **A length given in pixels is that many pixels, wherever it ends up.** A
|
||||
/// gap and a declared width compose additively -- `Len::within` adds a part's
|
||||
/// own pixels rather than scaling them, and both ends of a gap carry the same
|
||||
/// fraction, so the multiply that rounds is the same on each -- which is why
|
||||
/// nesting the row inside fractions of fractions cannot move them. Swept over
|
||||
/// 2,100 box widths when this was written and exact at every one; five here,
|
||||
/// including widths that divide badly by five.
|
||||
#[test]
|
||||
fn a_length_in_pixels_is_that_many_pixels_however_it_is_nested() {
|
||||
for box_w in [300.0, 1000.0, 1001.0, 1003.0, 1920.0] {
|
||||
let want = Px::from_int(7);
|
||||
let (_, gaps) = row_under_fractions(None, 7.0, box_w);
|
||||
assert!(
|
||||
gaps.iter().all(|g| *g == want),
|
||||
"box {box_w}: gaps between leftover children are {gaps:?}"
|
||||
);
|
||||
let (widths, gaps) = row_under_fractions(Some(LayoutLen::px(100.0)), 7.0, box_w);
|
||||
assert!(
|
||||
gaps.iter().all(|g| *g == want),
|
||||
"box {box_w}: gaps between fixed children are {gaps:?}"
|
||||
);
|
||||
assert!(
|
||||
widths.iter().all(|w| *w == Px::from_int(100)),
|
||||
"box {box_w}: declared widths came out {widths:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Children asking for the same share of a row are not the same length**,
|
||||
/// and this pins by how much rather than claiming they are equal. A position
|
||||
/// is the quantity that gets rounded, so the row fills exactly and no two
|
||||
/// children leave a seam; what that costs is a step or two between lengths
|
||||
/// that were asked for identically. Exact composition would shrink the
|
||||
/// spread, not remove it: five equal lengths cannot fill a row whose step
|
||||
/// count is not a multiple of five.
|
||||
#[test]
|
||||
fn equal_shares_differ_by_at_most_two_steps_and_fill_the_row() {
|
||||
for kid in [None, Some(LayoutLen::rel(0.2))] {
|
||||
for box_w in [300.0, 1000.0, 1001.0, 1003.0, 1920.0] {
|
||||
let (widths, gaps) = row_under_fractions(kid, 0.0, box_w);
|
||||
let spread = *widths.iter().max().unwrap() - *widths.iter().min().unwrap();
|
||||
assert!(
|
||||
spread <= Px::from_raw(2),
|
||||
"box {box_w}, {kid:?}: widths {widths:?} spread {spread:?}"
|
||||
);
|
||||
assert!(
|
||||
gaps.iter().all(|g| *g == Px::ZERO),
|
||||
"box {box_w}, {kid:?}: children left seams {gaps:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -25,9 +25,11 @@ fn depth() -> usize {
|
||||
env("IRIS_GENERATED_DEPTH", 4)
|
||||
}
|
||||
|
||||
/// The seeds the ordinary tests take. Eight that have never failed and one,
|
||||
/// 86, that a `Scroll` fixed point once settled differently on.
|
||||
const SEEDS: [u64; 9] = [1, 2, 3, 5, 8, 10, 13, 86, 98];
|
||||
/// The seeds the ordinary tests take. Seven that have never failed; 86,
|
||||
/// which a `Scroll` fixed point once settled differently on; and 20, which
|
||||
/// caught a locally redrawn widget being placed twice in the box its parent
|
||||
/// had already placed it in.
|
||||
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
|
||||
|
||||
fn check(seed: u64, depth: usize, case: Case) {
|
||||
let grown = plan(seed, depth, &Edits::default());
|
||||
|
||||
Reference in new issue
Block a user