Constrain offered boxes with independent widget size bounds
This commit is contained in:
1 parent
4cb6f6882a
commit
2ac0843cb2
18 files changed
+554
-379
No files matched your search
+226
-25
@@ -824,7 +824,7 @@ fn row_under_fractions(kid: Option<LayoutLen>, gap: f32, box_w: f32) -> (Vec<Px>
|
||||
if let Some(len) = kid {
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rule(r.id(), Axis::X, SizeRule::Exact(len));
|
||||
.set_size_rule(r.id(), Axis::X, SizeRule::from(len));
|
||||
}
|
||||
ids.push(r.id());
|
||||
kids.push(r.add_strong(&mut h.rsc));
|
||||
@@ -1005,10 +1005,6 @@ fn a_region_node_root_is_a_region_node() {
|
||||
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(900));
|
||||
}
|
||||
|
||||
/// A bound is a rule about what a widget answers: it holds the length that
|
||||
/// reaches whoever asked and leaves the box alone. Here the content is 400
|
||||
/// wide in a 250 window, so a cap cuts what the row reports and a floor
|
||||
/// raises it, while the rects inside stay where the 250 box put them.
|
||||
#[test]
|
||||
fn a_bound_holds_what_a_widget_answers() {
|
||||
let row = |rule: SizeRule| {
|
||||
@@ -1023,51 +1019,37 @@ fn a_bound_holds_what_a_widget_answers() {
|
||||
h.region(&left).unwrap().size().x,
|
||||
)
|
||||
};
|
||||
let (capped, left) = row(SizeRule::Max(Len::px(300.0)));
|
||||
let (capped, left) = row(SizeRule::max(Len::px(300.0)));
|
||||
assert_eq!(capped, Px::from_int(300), "the cap, not the 400 drawn");
|
||||
assert_eq!(left, Px::from_int(200), "the box the children were given");
|
||||
|
||||
let (floored, _) = row(SizeRule::Min(Len::px(600.0)));
|
||||
let (floored, _) = row(SizeRule::min(Len::px(600.0)));
|
||||
assert_eq!(floored, Px::from_int(600), "the floor, not the 400 drawn");
|
||||
|
||||
let (free, _) = row(SizeRule::Free);
|
||||
let (free, _) = row(SizeRule::FREE);
|
||||
assert_eq!(free, Px::from_int(400), "what it drew");
|
||||
}
|
||||
|
||||
/// A cap on the box is `MaxSize`, which asks its child in the shorter of the
|
||||
/// cap and its own box. That is the box a text wraps at and a scroll takes
|
||||
/// its viewport from, so it cannot be had by holding the answer.
|
||||
#[test]
|
||||
fn a_cap_widget_asks_its_child_in_the_shorter_box() {
|
||||
fn a_cap_attribute_narrows_the_widgets_box() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
// A fraction of its box, so it says what box it was asked in.
|
||||
let fills = rect(Color::RED).width(rel(1.0)).add(&mut h.rsc);
|
||||
let capped = fills.max_width(300).add(&mut h.rsc);
|
||||
assert_eq!(fills.id(), capped.id());
|
||||
h.set_root(capped);
|
||||
|
||||
assert_eq!(h.region(&fills).unwrap().size().x, Px::from_int(300));
|
||||
assert_eq!(
|
||||
h.region(&capped).unwrap().size().x,
|
||||
Px::from_int(300),
|
||||
"as long as its child used"
|
||||
);
|
||||
|
||||
// A child that asked for a share takes the box the cap allows, and the
|
||||
// share itself passes up: whoever divides one is this widget's parent.
|
||||
let mut h = Harness::new((400, 200));
|
||||
let share = rect(Color::RED).add(&mut h.rsc);
|
||||
let capped = share.max_width(300).add(&mut h.rsc);
|
||||
h.set_root(capped);
|
||||
|
||||
assert_eq!(h.region(&share).unwrap().size().x, Px::from_int(300));
|
||||
assert_eq!(h.region(&capped).unwrap().size().x, Px::from_int(400));
|
||||
}
|
||||
|
||||
/// Which of the cap and the box is shorter is a question in pixels, so it is
|
||||
/// asked again wherever the answer can change -- and the widget asking it is
|
||||
/// drawn again whenever its own box is, which is what keeps the two in step.
|
||||
#[test]
|
||||
fn a_cap_widget_is_decided_again_on_either_side_of_the_crossing() {
|
||||
fn a_cap_attribute_is_decided_again_on_either_side_of_the_crossing() {
|
||||
let mut h = Harness::new((400, 200));
|
||||
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||
h.set_root(probe.max_width(300));
|
||||
@@ -1116,3 +1098,222 @@ fn a_cap_holds_an_answer_that_overflowed_its_box() {
|
||||
// row draws 400 of it. Its answer is the cap, and the window centres it.
|
||||
assert_corners!(h, row, (-25, 0), (275, 200));
|
||||
}
|
||||
|
||||
struct Offered {
|
||||
seen: Rc<Cell<PxVec2>>,
|
||||
answer: Size,
|
||||
}
|
||||
|
||||
impl Widget for Offered {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
self.seen.set(painter.px_size());
|
||||
painter.primitive(RectPrimitive::color(Color::RED));
|
||||
self.answer
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounds_constrain_the_offer_without_replacing_an_intrinsic_answer() {
|
||||
for axis in Axis::BOTH {
|
||||
for node in [false, true] {
|
||||
let mut h = Harness::new((400, 400));
|
||||
let seen = Rc::new(Cell::new(PxVec2::ZERO));
|
||||
let probe = h.rsc.widgets_mut().add_strong(Offered {
|
||||
seen: seen.clone(),
|
||||
answer: Size::px(Vec2::new(40.0, 40.0)),
|
||||
});
|
||||
let id = probe.id();
|
||||
h.rsc.widgets_mut().set_region_node(id, node);
|
||||
h.rsc.widgets_mut().set_max_len(id, axis, 100.into());
|
||||
h.state.root = Some(probe);
|
||||
h.frame();
|
||||
assert_eq!(seen.get()[axis], Px::from_int(100));
|
||||
assert_eq!(h.region(&id).unwrap().size()[axis], Px::from_int(40));
|
||||
h.rsc.widgets_mut().set_min_len(id, axis, 60.into());
|
||||
h.frame();
|
||||
assert_eq!(h.region(&id).unwrap().size()[axis], Px::from_int(60));
|
||||
h.resize((50, 50));
|
||||
h.frame();
|
||||
assert_eq!(seen.get()[axis], Px::from_int(60));
|
||||
h.rsc.widgets_mut().set_size_rule(id, axis, SizeRule::FREE);
|
||||
h.frame();
|
||||
assert_eq!(seen.get()[axis], Px::from_int(50));
|
||||
assert_eq!(h.region(&id).unwrap().size()[axis], Px::from_int(40));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cap_attribute_is_the_scroll_viewport() {
|
||||
for node in [false, true] {
|
||||
let mut h = Harness::new((400, 400));
|
||||
let content = rect(Color::RED).height(400).add(&mut h.rsc);
|
||||
let inner = content.add_strong(&mut h.rsc);
|
||||
let scroll = Scroll::new(inner, Axis::Y).max_height(100).add(&mut h.rsc);
|
||||
h.rsc.widgets_mut().set_region_node(scroll, node);
|
||||
h.set_root(scroll);
|
||||
assert_corners!(h, scroll, (0, 150), (400, 250));
|
||||
assert_corners!(h, content, (0, -150), (400, 250));
|
||||
h.rsc.widgets_mut().get_mut(&scroll).unwrap().scroll(1000.0);
|
||||
h.frame();
|
||||
assert_corners!(h, content, (0, 150), (400, 550));
|
||||
h.resize((400, 80));
|
||||
h.frame();
|
||||
assert_corners!(h, scroll, (0, 0), (400, 80));
|
||||
assert_corners!(h, content, (0, 0), (400, 400));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cap_attribute_wraps_text_before_it_answers() {
|
||||
let mut h = Harness::new((400, 500));
|
||||
let text = wtext("one two three four five six seven eight nine ten")
|
||||
.size(16)
|
||||
.wrap(true)
|
||||
.max_width(80)
|
||||
.align(Align::TOP_LEFT)
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(text);
|
||||
let capped = h.region(&text).unwrap().size();
|
||||
assert!(capped.x <= Px::from_int(80));
|
||||
assert!(capped.y > Px::from_int(30));
|
||||
h.rsc
|
||||
.widgets_mut()
|
||||
.set_size_rule(text, Axis::X, SizeRule::FREE);
|
||||
h.frame();
|
||||
let free = h.region(&text).unwrap().size();
|
||||
assert!(free.x > capped.x);
|
||||
assert!(free.y < capped.y);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dimensions_and_bounds_are_independent_attributes_in_either_order() {
|
||||
for bounds_first in [false, true] {
|
||||
let mut h = Harness::new((400, 400));
|
||||
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||
let bounded = if bounds_first {
|
||||
probe
|
||||
.max_width(80)
|
||||
.min_height(60)
|
||||
.width(120)
|
||||
.height(40)
|
||||
.add(&mut h.rsc)
|
||||
} else {
|
||||
probe
|
||||
.width(120)
|
||||
.height(40)
|
||||
.max_width(80)
|
||||
.min_height(60)
|
||||
.add(&mut h.rsc)
|
||||
};
|
||||
assert_eq!(probe.id(), bounded.id());
|
||||
h.set_root(bounded);
|
||||
assert_eq!(
|
||||
h.region(&probe).unwrap().size(),
|
||||
PxVec2::from_f32((80, 60).into())
|
||||
);
|
||||
h.rsc.widgets_mut().set_len(probe, Axis::X, 50);
|
||||
h.rsc.widgets_mut().set_len(probe, Axis::Y, 100);
|
||||
h.frame();
|
||||
assert_eq!(
|
||||
h.region(&probe).unwrap().size(),
|
||||
PxVec2::from_f32((50, 100).into())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_a_share_cap_replaces_it_without_losing_the_share() {
|
||||
let mut h = Harness::new((300, 100));
|
||||
let capped = rect(Color::RED)
|
||||
.max_width(80)
|
||||
.width(leftover(1))
|
||||
.add(&mut h.rsc);
|
||||
let sibling = rect(Color::BLUE).add(&mut h.rsc);
|
||||
h.set_root((capped, sibling).span(Dir::RIGHT));
|
||||
assert_corners!(h, capped, (0, 0), (80, 100));
|
||||
assert_corners!(h, sibling, (80, 0), (300, 100));
|
||||
h.rsc.widgets_mut().set_max_len(capped, Axis::X, 160.into());
|
||||
h.frame();
|
||||
assert_corners!(h, capped, (0, 0), (150, 100));
|
||||
assert_corners!(h, sibling, (150, 0), (300, 100));
|
||||
}
|
||||
|
||||
// Reduced from seed 104 at depth 5: a widget widening its own window
|
||||
// contract must not erase the bound's crossing at a quarter-window of 173.
|
||||
#[test]
|
||||
fn a_widgets_window_contract_cannot_widen_its_bounds_contract() {
|
||||
fn tree(h: &mut Harness) -> WidgetId {
|
||||
let content = rect(Color::RED)
|
||||
.width(137)
|
||||
.min_height(194)
|
||||
.add_strong(&mut h.rsc);
|
||||
let scroll = Scroll::new(content, Axis::X).add_strong(&mut h.rsc);
|
||||
let probe = rect(Color::RED).add_strong(&mut h.rsc);
|
||||
let id = probe.id();
|
||||
let wide = rect(Color::GREEN).add_strong(&mut h.rsc);
|
||||
let narrow = rect(Color::BLUE).add_strong(&mut h.rsc);
|
||||
let branch = iris::random::Branch {
|
||||
probe,
|
||||
wide,
|
||||
narrow,
|
||||
threshold: 459.0,
|
||||
}
|
||||
.min_width(94)
|
||||
.max_width(173)
|
||||
.add_strong(&mut h.rsc);
|
||||
let stack = Stack {
|
||||
children: vec![scroll, branch],
|
||||
size: StackSize::Child(0),
|
||||
}
|
||||
.add_strong(&mut h.rsc);
|
||||
let mut children: Vec<StrongWidget> = (0..3)
|
||||
.map(|_| rect(Color::RED).add_strong(&mut h.rsc).any())
|
||||
.collect();
|
||||
children.push(stack);
|
||||
h.set_root(
|
||||
Span {
|
||||
children,
|
||||
dir: Dir::RIGHT,
|
||||
gap: Px::ZERO,
|
||||
}
|
||||
.height(rel(1)),
|
||||
);
|
||||
id
|
||||
}
|
||||
let mut warm = Harness::new((1920, 1200));
|
||||
let probe = tree(&mut warm);
|
||||
warm.resize((640, 900));
|
||||
warm.frame();
|
||||
assert_corners!(warm, probe, (480, 0), (640, 40));
|
||||
let mut cold = Harness::new((640, 900));
|
||||
let other = tree(&mut cold);
|
||||
assert_eq!(warm.region(&probe), cold.region(&other));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_fixed_declaration_keeps_its_cap_when_the_row_has_no_leftover() {
|
||||
let mut h = Harness::new((100, 100));
|
||||
let fixed = rect(Color::RED).width(200).max_width(100).add(&mut h.rsc);
|
||||
let share = rect(Color::BLUE).add(&mut h.rsc);
|
||||
h.set_root((fixed, share).span(Dir::RIGHT));
|
||||
assert_corners!(h, fixed, (0, 0), (100, 100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_bound_reaches_the_parent_even_when_the_current_answer_is_unchanged() {
|
||||
let mut h = Harness::new((400, 100));
|
||||
let leaf = rect(Color::RED).add(&mut h.rsc);
|
||||
let inner = leaf.add_strong(&mut h.rsc);
|
||||
let root = Scroll::new(inner, Axis::Y)
|
||||
.pad(Padding::uniform(0))
|
||||
.width(154)
|
||||
.height(100)
|
||||
.add(&mut h.rsc);
|
||||
h.set_root(root);
|
||||
assert_eq!(h.region(&leaf).unwrap().size().x, Px::from_int(154));
|
||||
h.rsc.widgets_mut().set_min_len(leaf, Axis::X, 140.into());
|
||||
h.rsc.widgets_mut().set_len(root, Axis::X, 78);
|
||||
h.frame();
|
||||
assert_corners!(h, leaf, (130, 0), (270, 100));
|
||||
}
|
||||
Reference in new issue
Block a user