Compare commits
4
Commits
08c9d5aa32
...
e166e005dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e166e005dc | ||
|
|
38eba543f6 | ||
|
|
2bc6bdfc77 | ||
|
|
d8ae9c3bdd |
No files matched your search
+24
-20
@@ -55,23 +55,26 @@ impl Holds {
|
|||||||
if rel == 0 {
|
if rel == 0 {
|
||||||
return Self::ANY;
|
return Self::ANY;
|
||||||
}
|
}
|
||||||
// In half steps. The box a length was composed down the chain from
|
// In half steps, and no more than the arithmetic needs: too wide a
|
||||||
// and the box the same length is measured against the window in are
|
// range admits reusing a drawing where it does not hold, and too
|
||||||
// two routes to one number, each rounding where the other does not,
|
// narrow a one leaves out the box a drawing was made in, which the
|
||||||
// and each rounding drops a whole step since `Fixed::mul` truncates:
|
// `Holds` assertion in `draw_at` catches. `ROUTES` is at that floor
|
||||||
// two steps either side. The multiply on the way in drops a step of
|
// -- two half steps fires it -- and tightening both ends moved not
|
||||||
// its own, and only downward, so it is one more step at the top and
|
// one of the rig's work counters, so the slack is not buying reuse.
|
||||||
// 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
|
// `ROUTES` covers a box composed down the chain against the same box
|
||||||
// is exact and taking the pixels off again is too. Allowing for it
|
// measured against the window: two routes to one number, each
|
||||||
// there anyway compounded, a step a level down a chain of widgets
|
// rounding where the other does not. `way_in` covers the multiply
|
||||||
// each taking the whole of its parent, which is the unsound
|
// this inverts, which drops a step and only ever downward, so it
|
||||||
// direction: a range wider than what a drawing holds for admits
|
// belongs at the top of the range alone -- and the whole of a box has
|
||||||
// reusing it where it does not hold.
|
// 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
|
// Shifted by half of what a `Rel` counts in, to divide by the
|
||||||
// fraction: exact until the division takes it back to the grid.
|
// 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 px = len.px.raw() as i64;
|
||||||
let half_rel = REL_SHIFT - 1;
|
let half_rel = REL_SHIFT - 1;
|
||||||
let way_in = match rel == Rel::ONE.raw() as i64 {
|
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
|
/// 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
|
/// 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
|
/// 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]
|
#[test]
|
||||||
fn the_whole_of_a_box_widens_by_the_routes_alone() {
|
fn the_whole_of_a_box_widens_by_the_routes_alone() {
|
||||||
let at = Px::from_int(956);
|
let at = Px::from_int(956);
|
||||||
let two_steps = |len: Px| Holds {
|
let one_step = |len: Px| Holds {
|
||||||
lo: len - Px::from_raw(2),
|
lo: len - Px::STEP,
|
||||||
hi: len + Px::from_raw(2),
|
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));
|
let less_eight = Len::from_parts(Rel::ONE, Px::from_int(-8));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Holds::at(at).through(less_eight),
|
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 {
|
if at_offer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Then in the final box its parent chose from that answer. It is kept
|
// Then in the final box its parent chose from that answer. That box
|
||||||
// if it holds there; otherwise its result is the parent's business.
|
// is already placed, so the near edge goes with it: applying the
|
||||||
self.draw_inner(id, region, info, None, rsc);
|
// 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];
|
let active = &self.active[&id];
|
||||||
if (active.size, active.holds) != was {
|
if (active.size, active.holds) != was {
|
||||||
rsc.widgets_mut().needs_redraw.insert(parent);
|
rsc.widgets_mut().needs_redraw.insert(parent);
|
||||||
|
|||||||
@@ -7,8 +7,14 @@ pub struct Pad {
|
|||||||
|
|
||||||
impl Widget for Pad {
|
impl Widget for Pad {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
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
|
let inner = painter
|
||||||
.widget_aligned(&self.inner, self.padding.region(), RegionAlign::NEAR)
|
.widget_within(&self.inner, self.padding.region())
|
||||||
.size();
|
.size();
|
||||||
Size {
|
Size {
|
||||||
x: LayoutLen {
|
x: LayoutLen {
|
||||||
|
|||||||
@@ -29,7 +29,15 @@ impl Widget for Stack {
|
|||||||
let region = painter.box_of(size);
|
let region = painter.box_of(size);
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
painter.child_layer_at(i);
|
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
|
size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,3 +499,124 @@ fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
|||||||
assert!(h.region(&a).is_none());
|
assert!(h.region(&a).is_none());
|
||||||
assert!(h.region(&b).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)
|
env("IRIS_GENERATED_DEPTH", 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The seeds the ordinary tests take. Eight that have never failed and one,
|
/// The seeds the ordinary tests take. Seven that have never failed; 86,
|
||||||
/// 86, that a `Scroll` fixed point once settled differently on.
|
/// which a `Scroll` fixed point once settled differently on; and 20, which
|
||||||
const SEEDS: [u64; 9] = [1, 2, 3, 5, 8, 10, 13, 86, 98];
|
/// 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) {
|
fn check(seed: u64, depth: usize, case: Case) {
|
||||||
let grown = plan(seed, depth, &Edits::default());
|
let grown = plan(seed, depth, &Edits::default());
|
||||||
|
|||||||
Reference in new issue
Block a user