`Stack` and `Pad` forced the near edge on every child. That override exists so a container that reports a child's size and then hands it the box derived from that report does not place its content twice -- and it is owed only where the box really is the child's own answer. `Stack` gives every child the box its sizing child defines. That box is `box_of(child.size())`, so the sizing child has no room in it and needs the override; every other child is handed a box that owes nothing to it, and where it sits in one bigger than itself is its own business. With the override it could not be aligned at all. `Pad` reports its inner's size plus the padding, so where its box is that answer the inset box is exactly the inner and alignment has nowhere to move it. Where the box is bigger -- a share of a row, a rule over the pad -- the slack belongs to the inner, and the override pinned it to a corner. The `tabs` example is the visible case both ways: its counters asked for `Align::RIGHT` inside a stack and sat at the left, and `text`'s narrow panel filled a row it had asked to sit at the top of. Both match canonical `main` again. Neither was noticed when `d3b0ebf` made alignment a property, and the handoff's claim that `tabs` then "differs only in the widget count it prints about itself" was wrong -- it was checked at `8220a78` and not re-checked after the next commit. Checked: fmt, clippy, 81 suite tests, 17 core unit tests, the release oracle at 100 seeds and at 1000 seeds of depth 6, and all fifteen shrinker cases at 400 seeds of depth 5. `tabs`, `text` and `random` change exactly where a child now honours its own alignment; `view` and `minimal` are unchanged. `tabs` is still not `main`'s render: `.sized((100, 100)).center().width( leftover(2))` on one widget no longer means a square centred in a two-share box, because one widget carries one length per axis and `.width` overwrites what `.sized` set. That one is an API question, not a bug, and is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
531 lines
19 KiB
Rust
531 lines
19 KiB
Rust
//! Where a frame puts things, with no window to put them in.
|
|
|
|
use iris::harness::{Harness, assert_corners};
|
|
use iris::prelude::*;
|
|
|
|
/// A fixed 100 wide, and the rest of the 400 to its neighbour.
|
|
fn two_rects(h: &mut Harness) -> (WidgetId, WidgetId) {
|
|
let left = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
let right = rect(Color::BLUE).add(&mut h.rsc);
|
|
h.set_root((left, right).span(Dir::RIGHT));
|
|
(left.id(), right.id())
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_gives_each_child_the_width_it_asked_for() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (left, right) = two_rects(&mut h);
|
|
|
|
assert_corners!(h, left, (0, 0), (100, 200));
|
|
assert_corners!(h, right, (100, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_ruled_across_itself_does_not_measure_its_children_there() {
|
|
let mut h = Harness::new((400, 200));
|
|
let child = rect(Color::RED).height(40).add(&mut h.rsc);
|
|
let span = (child,).span(Dir::RIGHT).height(rel(1.0)).add(&mut h.rsc);
|
|
h.set_root(span);
|
|
|
|
assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::rel(1.0));
|
|
}
|
|
|
|
#[test]
|
|
fn a_span_reports_its_tallest_fixed_child() {
|
|
let mut h = Harness::new((400, 200));
|
|
let short = rect(Color::RED).height(40).add(&mut h.rsc);
|
|
let tall = rect(Color::BLUE).height(70).add(&mut h.rsc);
|
|
let span = (short, tall).span(Dir::RIGHT).add(&mut h.rsc);
|
|
h.set_root(span);
|
|
|
|
assert_eq!(h.render.active[&span.id()].size.y, LayoutLen::px(70.0));
|
|
}
|
|
|
|
#[test]
|
|
fn resizing_relays_out_against_the_new_output() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (left, right) = two_rects(&mut h);
|
|
|
|
h.resize((800, 100));
|
|
assert!(h.needs_redraw());
|
|
h.frame();
|
|
|
|
assert_corners!(h, left, (0, 0), (100, 100));
|
|
assert_corners!(h, right, (100, 0), (800, 100));
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_widget_takes_a_share_of_a_span() {
|
|
let mut h = Harness::new((400, 200));
|
|
let gap = ().add(&mut h.rsc);
|
|
let right = rect(Color::BLUE).width(100).add(&mut h.rsc);
|
|
h.set_root((gap, right).span(Dir::RIGHT));
|
|
|
|
assert_corners!(h, gap, (0, 0), (300, 200));
|
|
assert_corners!(h, right, (300, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_child_drawn_twice_moves_once() {
|
|
let mut h = Harness::new((400, 200));
|
|
// The span measures a child and then places it; listing it twice would
|
|
// move it twice. The span's own fixed total is shorter than the window,
|
|
// so the span is centred in it and everything under it carries that.
|
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
|
let centered = inner.center().width(200).add(&mut h.rsc);
|
|
let left = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((left, centered).span(Dir::RIGHT));
|
|
assert_corners!(h, inner, (150, 0), (350, 200));
|
|
|
|
h.set_len(left, Axis::X, 150);
|
|
h.frame();
|
|
|
|
assert_corners!(h, inner, (175, 0), (375, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn alignment_accepts_an_arbitrary_fraction_and_changes_at_runtime() {
|
|
let mut h = Harness::new((400, 200));
|
|
let fixed = rect(Color::BLUE).sized((100, 100)).add(&mut h.rsc);
|
|
h.rsc
|
|
.widgets_mut()
|
|
.set_alignment(fixed, Axis::X, AxisAlign::new(0.25));
|
|
h.rsc
|
|
.widgets_mut()
|
|
.set_alignment(fixed, Axis::Y, AxisAlign::NEG);
|
|
h.set_root(fixed);
|
|
assert_corners!(h, fixed, (75, 0), (175, 100));
|
|
|
|
h.rsc
|
|
.widgets_mut()
|
|
.set_alignment(fixed, Axis::X, AxisAlign::new(0.75));
|
|
h.frame();
|
|
assert_corners!(h, fixed, (225, 0), (325, 100));
|
|
}
|
|
|
|
#[test]
|
|
fn a_resize_lands_where_a_cold_start_would() {
|
|
let build = |h: &mut Harness| {
|
|
let para = wtext(
|
|
"Wrapping shapes one source into as many lines as its container leaves room \
|
|
for, so the height of a paragraph is an answer rather than a setting.",
|
|
)
|
|
.size(20)
|
|
.wrap(true)
|
|
.pad(16)
|
|
.add(&mut h.rsc);
|
|
let below = rect(Color::RED).add(&mut h.rsc);
|
|
let root = (para, below).span(Dir::DOWN).pad(12);
|
|
h.set_root(root);
|
|
(para, below)
|
|
};
|
|
|
|
let mut cold = Harness::new((900, 1200));
|
|
let (cold_para, cold_below) = build(&mut cold);
|
|
|
|
let mut resized = Harness::new((1920, 1200));
|
|
let (para, below) = build(&mut resized);
|
|
resized.resize((900, 1200));
|
|
resized.frame();
|
|
|
|
assert_eq!(resized.region(¶), cold.region(&cold_para), "paragraph");
|
|
assert_eq!(resized.region(&below), cold.region(&cold_below), "below");
|
|
}
|
|
|
|
#[test]
|
|
fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
|
let mut h = Harness::new((400, 400));
|
|
// The panel fills a stack sized by its sibling, so it is first asked in
|
|
// the whole box and then given the shorter one. Reusing it in that fixed
|
|
// box afterwards would leave it whatever height it happened to have.
|
|
let panel = rect(Color::BLUE).add(&mut h.rsc);
|
|
let leaf = rect(Color::RED).height(100).add(&mut h.rsc);
|
|
let stack = (panel, leaf)
|
|
.stack()
|
|
.size(StackSize::Child(1))
|
|
.add(&mut h.rsc);
|
|
h.set_root(stack.align(Align::TOP));
|
|
assert_corners!(h, panel, (0, 0), (400, 100));
|
|
|
|
h.set_len(leaf, Axis::Y, 250);
|
|
h.frame();
|
|
|
|
assert_corners!(h, panel, (0, 0), (400, 250));
|
|
}
|
|
|
|
#[test]
|
|
fn a_moved_subtree_takes_its_children_with_it() {
|
|
let mut h = Harness::new((400, 400));
|
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
|
let row = inner.pad(10).height(40).region_node().add(&mut h.rsc);
|
|
// 80 of fixed rows in a 400 window, so the span takes 80 and sits in the
|
|
// middle of what it was given.
|
|
h.set_root((first, row).span(Dir::DOWN));
|
|
assert_corners!(h, inner, (10, 210), (390, 230));
|
|
|
|
h.set_len(first, Axis::Y, 80);
|
|
h.frame();
|
|
|
|
// The row opted into one movable region, so its descendants follow one
|
|
// entry rather than having their primitive regions rewritten.
|
|
assert_corners!(h, inner, (10, 230), (390, 250));
|
|
}
|
|
|
|
#[test]
|
|
fn a_fixed_length_child_keeps_it_when_the_box_around_it_grows() {
|
|
let mut h = Harness::new((400, 200));
|
|
let fixed = rect(Color::BLUE).width(50).add(&mut h.rsc);
|
|
let leftover = rect(Color::GREEN).add(&mut h.rsc);
|
|
let panel = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc);
|
|
// Changing the bar's width is the only thing that changes the box the
|
|
// panel and everything under it was drawn for.
|
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((bar, panel).span(Dir::RIGHT));
|
|
assert_corners!(h, fixed, (100, 0), (150, 200));
|
|
assert_corners!(h, leftover, (150, 0), (400, 200));
|
|
|
|
h.set_len(bar, Axis::X, 200);
|
|
h.frame();
|
|
|
|
// The panel's box is 100 shorter, so the fixed child is the same 50 wide
|
|
// against its new start and the one taking what is left absorbs the change.
|
|
assert_corners!(h, fixed, (200, 0), (250, 200));
|
|
assert_corners!(h, leftover, (250, 0), (400, 200));
|
|
}
|
|
|
|
#[test]
|
|
fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
|
let mut h = Harness::new((400, 200));
|
|
// The row is 40 tall whatever happens, which used to make its drawing
|
|
// impossible to take out of: recovering a fraction of a box needs a
|
|
// relative extent, and it has none on that axis.
|
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
|
let row = inner.pad(10).height(40).add(&mut h.rsc);
|
|
let filler = rect(Color::GREEN).add(&mut h.rsc);
|
|
// This column is an item in a row, so it takes the width left for it
|
|
// rather than asking for a full row-width in addition to the bar.
|
|
let column = (row, filler).span(Dir::DOWN).add(&mut h.rsc);
|
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((bar, column).span(Dir::RIGHT));
|
|
assert_corners!(h, inner, (110, 10), (390, 30));
|
|
|
|
h.set_len(bar, Axis::X, 200);
|
|
h.frame();
|
|
|
|
assert_corners!(h, inner, (210, 10), (390, 30));
|
|
}
|
|
|
|
#[test]
|
|
fn only_a_region_node_lengthens_the_chain_and_it_can_be_removed() {
|
|
let mut h = Harness::new((400, 200));
|
|
let leaf = rect(Color::BLUE).add(&mut h.rsc);
|
|
let buried = leaf.pad(4).pad(4).pad(4).pad(4).add(&mut h.rsc);
|
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
h.set_root((bar, buried).span(Dir::RIGHT));
|
|
|
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
|
assert_eq!(h.render.moves.depth(move_idx), 1, "only the root region");
|
|
|
|
h.rsc.widgets_mut().set_region_node(buried, true);
|
|
h.frame();
|
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
|
assert_eq!(
|
|
h.render.moves.depth(move_idx),
|
|
2,
|
|
"the opted-in widget's region and the root region"
|
|
);
|
|
|
|
h.rsc.widgets_mut().set_region_node(buried, false);
|
|
h.frame();
|
|
let move_idx = h.render.active[&leaf.id()].parent_move;
|
|
assert_eq!(h.render.moves.depth(move_idx), 1);
|
|
}
|
|
|
|
/// A span that sizes from its children passes their `leftover` weight up
|
|
/// than collapsing it to one share, so nesting divides the same space instead
|
|
/// of re-dividing a share of it.
|
|
#[test]
|
|
fn nested_spans_divide_the_space_once_however_deep_the_nesting_is() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (a, b, c, d) = (
|
|
rect(Color::RED).add(&mut h.rsc),
|
|
rect(Color::BLUE).add(&mut h.rsc),
|
|
rect(Color::GREEN).add(&mut h.rsc),
|
|
rect(Color::WHITE).add(&mut h.rsc),
|
|
);
|
|
let left = (a, b).span(Dir::RIGHT).add(&mut h.rsc);
|
|
let right = (c, d).span(Dir::RIGHT).add(&mut h.rsc);
|
|
h.set_root((left, right).span(Dir::RIGHT));
|
|
|
|
for (i, id) in [a, b, c, d].into_iter().enumerate() {
|
|
let x = i as f32 * 100.0;
|
|
assert_corners!(h, id, (x, 0), (x + 100.0, 200));
|
|
}
|
|
}
|
|
|
|
/// The same space, unevenly nested: weights carried up mean a share is a
|
|
/// share of the whole, not of whatever branch a widget happens to sit in.
|
|
///
|
|
/// Each edge lands on the even division or one step below it, since a share
|
|
/// is a fraction of the room and a truncating multiply gives up what that
|
|
/// fraction does not divide. What stays exact is that each share starts
|
|
/// where the last one ended and the row ends at its own edge.
|
|
#[test]
|
|
fn an_uneven_nesting_still_gives_every_share_the_same_length() {
|
|
let mut h = Harness::new((400, 200));
|
|
let (a, b, c, d) = (
|
|
rect(Color::RED).add(&mut h.rsc),
|
|
rect(Color::BLUE).add(&mut h.rsc),
|
|
rect(Color::GREEN).add(&mut h.rsc),
|
|
rect(Color::WHITE).add(&mut h.rsc),
|
|
);
|
|
let one = (a,).span(Dir::RIGHT).add(&mut h.rsc);
|
|
let three = (b, c, d).span(Dir::RIGHT).add(&mut h.rsc);
|
|
h.set_root((one, three).span(Dir::RIGHT));
|
|
|
|
let mut start = Px::ZERO;
|
|
for (i, id) in [a, b, c, d].into_iter().enumerate() {
|
|
let got = h.region(&id).expect("widget drew nothing");
|
|
let even = Px::from_int((i as i32 + 1) * 100);
|
|
assert_eq!(got.top_left, PxVec2::new(start, Px::ZERO), "share {i}");
|
|
assert_eq!(got.bot_right.y, Px::from_int(200), "share {i}");
|
|
assert!(
|
|
got.bot_right.x == even || got.bot_right.x == even.next_down(),
|
|
"share {i} ends at {:?}, not {even:?}",
|
|
got.bot_right.x
|
|
);
|
|
start = got.bot_right.x;
|
|
}
|
|
assert_eq!(
|
|
start,
|
|
Px::from_int(400),
|
|
"the row stopped short of its edge"
|
|
);
|
|
}
|
|
|
|
/// However many ways a row is divided, the shares add up to the row: each
|
|
/// one is the fixed parts before it plus a share of the room, rather than a
|
|
/// step from where the last one ended, so the roundings do not accumulate
|
|
/// along it. Chained, two hundred of them ended a step short of the edge.
|
|
#[test]
|
|
fn a_row_of_equal_shares_fills_it_exactly() {
|
|
for n in [2usize, 3, 7, 64, 200] {
|
|
let mut h = Harness::new((1000, 100));
|
|
let mut ids = Vec::new();
|
|
let mut kids: Vec<StrongWidget> = Vec::new();
|
|
for _ in 0..n {
|
|
let kid = rect(Color::RED).add(&mut h.rsc);
|
|
ids.push(kid.id());
|
|
kids.push(kid.add_strong(&mut h.rsc));
|
|
}
|
|
let span = Span {
|
|
children: kids,
|
|
dir: Dir::RIGHT,
|
|
gap: Px::ZERO,
|
|
}
|
|
.add(&mut h.rsc);
|
|
h.set_root(span);
|
|
h.frame();
|
|
|
|
for (i, id) in ids.iter().enumerate() {
|
|
let at = h.region(id).expect("a share drew nothing").top_left.x;
|
|
let want = Px::from_f32(1000.0 * (i as f32) / (n as f32));
|
|
assert!(
|
|
(at - want).abs() <= Px::STEP,
|
|
"{n} shares: the {i}th starts at {at:?}, not {want:?}"
|
|
);
|
|
}
|
|
let end = h.region(ids.last().unwrap()).unwrap().bot_right.x;
|
|
assert_eq!(end, Px::from_int(1000), "{n} shares do not reach the edge");
|
|
}
|
|
}
|
|
|
|
/// Where the shader puts an edge: the two parts of a scalar are floored
|
|
/// apart, so a fraction and a pixel offset snap independently, and each is
|
|
/// taken to the boundary it composes to within half a step of. Kept in step
|
|
/// with `snap_floor` in `prelude.wgsl`.
|
|
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
|
let active = &h.render.active[&id];
|
|
let region = h.render.moves.resolve(active.parent_move, active.region);
|
|
let dim = h.size().axis(axis);
|
|
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
|
|
let edge = |s: Len| snap(s.rel.to_f32() * dim) + snap(s.px.to_f32());
|
|
let span = region.axis(axis);
|
|
(edge(span.start), edge(span.end))
|
|
}
|
|
|
|
fn hairline(h: &mut Harness, marks: &mut Vec<WidgetId>) -> StrongWidget {
|
|
let mark = rect(Color::RED).width(1).add_strong(&mut h.rsc);
|
|
marks.push(mark.id());
|
|
mark
|
|
}
|
|
|
|
fn share(h: &mut Harness, inner: StrongWidget, ratio: f32) -> StrongWidget {
|
|
h.set_len(&inner, Axis::X, LayoutLen::leftover(ratio));
|
|
inner
|
|
}
|
|
|
|
/// Shares in weights no binary fraction lands on, a padding on one branch
|
|
/// and not the other, so an edge falls near an integer as often as it can.
|
|
fn hairlines(h: &mut Harness, depth: usize, marks: &mut Vec<WidgetId>) -> StrongWidget {
|
|
let mut span = Span::empty(Dir::RIGHT);
|
|
if depth == 0 {
|
|
let left = rect(Color::BLUE).add_strong(&mut h.rsc);
|
|
let left = share(h, left, 3.0);
|
|
span.push(left);
|
|
let mark = hairline(h, marks);
|
|
span.push(mark);
|
|
let right = rect(Color::BLUE).add_strong(&mut h.rsc);
|
|
let right = share(h, right, 7.0);
|
|
span.push(right);
|
|
return span.add_strong(&mut h.rsc);
|
|
}
|
|
let first = hairlines(h, depth - 1, marks);
|
|
let first = share(h, first, 3.0);
|
|
span.push(first);
|
|
let second = hairlines(h, depth - 1, marks);
|
|
let second = Pad {
|
|
padding: Padding {
|
|
left: Px::from_int(3),
|
|
right: Px::from_int(7),
|
|
top: Px::ZERO,
|
|
bottom: Px::ZERO,
|
|
},
|
|
inner: second,
|
|
}
|
|
.add_strong(&mut h.rsc);
|
|
let second = share(h, second, 5.0);
|
|
span.push(second);
|
|
span.add_strong(&mut h.rsc)
|
|
}
|
|
|
|
/// A one-pixel line is a pixel wherever it is drawn. Both edges of a fixed
|
|
/// length share their box's fraction, so composing the chain moves them
|
|
/// together and the shader's `floor` cannot round the pixel between them
|
|
/// away -- only shift it. A separator that disappeared at one window size
|
|
/// would be a defect no size comparison catches.
|
|
#[test]
|
|
fn a_one_pixel_line_keeps_its_pixel_through_a_chain() {
|
|
let mut h = Harness::new((1920, 1200));
|
|
let mut marks = Vec::new();
|
|
let root = hairlines(&mut h, 4, &mut marks);
|
|
h.state.set_root(root);
|
|
h.frame();
|
|
assert_eq!(marks.len(), 16);
|
|
|
|
for size in [(1920, 1200), (1919, 1201), (997, 1003), (1367, 733)] {
|
|
h.resize(size);
|
|
h.frame();
|
|
for mark in &marks {
|
|
let (start, end) = drawn_edges(&h, *mark, Axis::X);
|
|
assert_eq!(end - start, 1.0, "at {size:?}, mark {mark:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A span short of room takes it from its shares, which go to nothing and
|
|
/// then to nothing wider; the fixed lengths between them keep their pixels.
|
|
/// Collapsing those to make room would delete a separator the caller asked
|
|
/// for, which is worse than overflowing.
|
|
#[test]
|
|
fn a_span_out_of_room_shrinks_its_shares_and_not_its_fixed_lengths() {
|
|
let mut h = Harness::new((400, 20));
|
|
let mut marks = Vec::new();
|
|
let mut span = Span::empty(Dir::RIGHT);
|
|
for _ in 0..3 {
|
|
let share_of = rect(Color::BLUE).add_strong(&mut h.rsc);
|
|
let share_of = share(&mut h, share_of, 1.0);
|
|
span.push(share_of);
|
|
let mark = hairline(&mut h, &mut marks);
|
|
span.push(mark);
|
|
}
|
|
let root = span.add_strong(&mut h.rsc);
|
|
h.state.set_root(root);
|
|
h.frame();
|
|
|
|
for width in [400, 10, 3, 1] {
|
|
h.resize((width, 20));
|
|
h.frame();
|
|
for mark in &marks {
|
|
let (start, end) = drawn_edges(&h, *mark, Axis::X);
|
|
assert_eq!(end - start, 1.0, "at {width} wide, mark {mark:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
|
let mut h = Harness::new((100, 20));
|
|
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
let leftover = rect(Color::BLUE).add(&mut h.rsc);
|
|
h.set_root((fixed, leftover).span(Dir::RIGHT));
|
|
|
|
assert_corners!(h, fixed, (0, 0), (100, 20));
|
|
assert_eq!(h.region(&leftover), None);
|
|
|
|
// An undrawn child remains a dependency of the span, so making room for
|
|
// it draws it without rebuilding the tree.
|
|
h.set_len(fixed, Axis::X, 60);
|
|
h.frame();
|
|
assert_corners!(h, leftover, (60, 0), (100, 20));
|
|
|
|
let mut h = Harness::new((100, 20));
|
|
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
|
let mixed = rect(Color::BLUE)
|
|
.width(LayoutLen::px(20) + LayoutLen::LEFTOVER)
|
|
.add(&mut h.rsc);
|
|
h.set_root((fixed, mixed).span(Dir::RIGHT));
|
|
|
|
// Pixels and fractions still overflow; only a child whose entire length
|
|
// is leftover is omitted.
|
|
assert_corners!(h, mixed, (100, 0), (120, 20));
|
|
}
|
|
|
|
#[test]
|
|
fn leftover_children_disappear_at_the_exact_fixed_content_boundary() {
|
|
let mut h = Harness::new((100, 100));
|
|
let first = rect(Color::RED).height(90).add(&mut h.rsc);
|
|
let a = rect(Color::GREEN).add(&mut h.rsc);
|
|
let b = rect(Color::BLUE).add(&mut h.rsc);
|
|
let inner = (a, b).span(Dir::DOWN).gap(4).add(&mut h.rsc);
|
|
h.set_root((first, inner).span(Dir::DOWN));
|
|
assert!(h.region(&a).is_some());
|
|
assert!(h.region(&b).is_some());
|
|
|
|
h.set_len(first, Axis::Y, 96.0);
|
|
h.frame();
|
|
|
|
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));
|
|
}
|