#[cfg(test)]
use crate::tree::Tree;
use crate::tree::{LayoutCtx, WidgetId};
use crate::widgets::LayoutHints;
use super::{Constraints, Layout, Size};
#[derive(Default)]
pub struct ZStack {
hints: Vec<LayoutHints>,
}
impl ZStack {
pub fn new() -> Self {
Self::default()
}
}
impl Layout for ZStack {
fn layout(
&mut self,
ctx: &mut LayoutCtx,
children: &[WidgetId],
constraints: Constraints,
origin: (f32, f32),
) -> Size {
self.hints.clear();
self.hints.extend(children.iter().map(|&child_id| {
ctx.tree_ref()
.with_widget(child_id, |w| w.layout_hints())
.unwrap_or_default()
}));
let mut stack = Size::zero();
let (mut led_width, mut led_height) = (false, false);
let unled = |max: f32, leads: fn(&LayoutHints) -> bool| {
!max.is_finite() && !self.hints.iter().any(|h| !h.hidden && leads(h))
};
let measure_followers = unled(constraints.max_width, |h| !h.fill_width)
|| unled(constraints.max_height, |h| !h.fill_height);
let mut largest = Size::zero();
for (i, &child_id) in children.iter().enumerate() {
let hints = self.hints[i];
if hints.fill_width && hints.fill_height && !measure_followers {
continue;
}
let Some(child_size) = ctx.layout_child(child_id, constraints) else {
continue;
};
if hints.hidden {
continue;
}
largest.width = largest.width.max(child_size.width);
largest.height = largest.height.max(child_size.height);
if !hints.fill_width {
stack.width = stack.width.max(child_size.width);
led_width = true;
}
if !hints.fill_height {
stack.height = stack.height.max(child_size.height);
led_height = true;
}
}
if !led_width {
stack.width = if constraints.max_width.is_finite() {
constraints.max_width
} else {
largest.width
};
}
if !led_height {
stack.height = if constraints.max_height.is_finite() {
constraints.max_height
} else {
largest.height
};
}
let size = constraints.constrain(stack);
for (i, &child_id) in children.iter().enumerate() {
let hints = self.hints[i];
if !hints.fill_width && !hints.fill_height {
continue;
}
let leads_an_axis = !hints.fill_width || !hints.fill_height;
let followed_size_changed = (hints.fill_width && size.width != constraints.max_width)
|| (hints.fill_height && size.height != constraints.max_height);
if leads_an_axis && !followed_size_changed {
continue;
}
let child_constraints = Constraints {
min_width: if hints.fill_width {
size.width
} else {
constraints.min_width.min(size.width)
},
min_height: if hints.fill_height {
size.height
} else {
constraints.min_height.min(size.height)
},
max_width: size.width,
max_height: size.height,
};
ctx.layout_child(child_id, child_constraints);
}
let mut least = Size::zero();
for &child_id in children.iter() {
ctx.tree().set_origin(child_id, origin.0, origin.1);
let child = ctx.tree_ref().minimum(child_id).unwrap_or_default();
least.width = least.width.max(child.width);
least.height = least.height.max(child.height);
}
ctx.set_minimum(least);
size
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::{Flex, fill};
use crate::widgets::container;
fn layout_root(
widget: crate::widgets::Container,
constraints: Constraints,
) -> (Tree, WidgetId) {
let mut tree = Tree::new();
let id = tree.register(Box::new(widget));
tree.with_widget_mut(id, |w, id, tree| w.register_children(tree, id));
tree.layout_widget(id, constraints);
(tree, id)
}
fn child_size(tree: &Tree, root: WidgetId, index: usize) -> Size {
let child = tree.get_children(root)[index];
tree.cached_size(child).expect("child was laid out")
}
#[test]
fn a_stack_records_its_largest_minimum() {
let offered = Constraints::new(0.0, 0.0, 400.0, 400.0);
let (tree, root) = layout_root(
container()
.layout(ZStack::new())
.child(container().width(30.0).height(50.0))
.child(container().width(50.0).height(20.0)),
offered,
);
assert_eq!(tree.minimum(root), Some(Size::new(50.0, 50.0)));
}
#[test]
fn filling_child_follows_its_sibling() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().width(80).height(20));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, 400.0));
assert_eq!(tree.cached_size(id), Some(Size::new(80.0, 20.0)));
assert_eq!(
child_size(&tree, id, 0),
Size::new(80.0, 20.0),
"the follower must take the leader's size, not the 400x400 offered"
);
assert_eq!(tree.get_origin(tree.get_children(id)[0]), Some((0.0, 0.0)));
assert_eq!(tree.get_origin(tree.get_children(id)[1]), Some((0.0, 0.0)));
}
#[test]
fn axes_are_decided_independently() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().width(60).height(fill()));
let (tree, id) = layout_root(root, Constraints::new(0.0, 34.0, 400.0, 34.0));
assert_eq!(tree.cached_size(id), Some(Size::new(60.0, 34.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(60.0, 34.0));
assert_eq!(child_size(&tree, id, 1), Size::new(60.0, 34.0));
}
#[test]
fn all_followers_fall_back_to_the_available_space() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().width(fill()).height(fill()));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, 200.0));
assert_eq!(tree.cached_size(id), Some(Size::new(400.0, 200.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(400.0, 200.0));
assert_eq!(child_size(&tree, id, 1), Size::new(400.0, 200.0));
}
#[test]
fn a_hidden_child_takes_no_part_in_the_size() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().visible(false).width(10).height(10));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, 200.0));
assert_eq!(tree.cached_size(id), Some(Size::new(400.0, 200.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(400.0, 200.0));
}
#[test]
fn a_hidden_child_does_not_lead_an_unbounded_axis() {
let root = container()
.layout(ZStack::new())
.child(
container()
.width(fill())
.height(fill())
.child(container().width(20).height(30)),
)
.child(container().visible(false).width(10).height(10));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, f32::INFINITY));
assert_eq!(tree.cached_size(id), Some(Size::new(400.0, 30.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(400.0, 30.0));
}
#[test]
fn an_overlay_shown_and_hidden_again_leads_only_while_shown() {
let shown = crate::reactive::create_signal(false);
let mut tree = Tree::new();
let root = tree.register(Box::new(
container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().visible(shown).width(10).height(10)),
));
tree.with_widget_mut(root, |w, id, t| w.register_children(t, id));
let frame = |tree: &mut Tree| {
crate::jobs::pump_and_layout(tree, root, Constraints::new(0.0, 0.0, 400.0, 200.0))
.expect("the root is registered")
};
assert_eq!(frame(&mut tree), Size::new(400.0, 200.0));
shown.set(true);
assert_eq!(frame(&mut tree), Size::new(10.0, 10.0));
shown.set(false);
assert_eq!(frame(&mut tree), Size::new(400.0, 200.0));
}
#[test]
fn a_visible_empty_child_still_leads() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().width(0).height(0));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, 200.0));
assert_eq!(tree.cached_size(id), Some(Size::new(0.0, 0.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(0.0, 0.0));
}
#[test]
fn follower_can_align_inside_the_stack() {
let root = container()
.layout(ZStack::new())
.child(container().width(50).height(50))
.child(
container()
.width(fill())
.height(fill())
.layout(Flex::row().main_alignment(crate::layout::MainAlignment::End))
.child(container().width(4).height(4)),
);
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, 400.0));
assert_eq!(tree.cached_size(id), Some(Size::new(50.0, 50.0)));
let follower = tree.get_children(id)[1];
let badge = tree.get_children(follower)[0];
assert_eq!(
tree.get_origin(badge),
Some((46.0, 0.0)),
"the badge should sit at the right edge of the 50px leader"
);
}
#[test]
fn all_followers_on_an_unbounded_axis_take_their_childrens_size() {
let root = container()
.layout(ZStack::new())
.child(
container()
.width(fill())
.height(fill())
.child(container().width(20).height(30)),
)
.child(
container()
.width(fill())
.height(fill())
.child(container().width(10).height(40)),
);
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, f32::INFINITY));
assert_eq!(tree.cached_size(id), Some(Size::new(400.0, 40.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(400.0, 40.0));
assert_eq!(child_size(&tree, id, 1), Size::new(400.0, 40.0));
}
#[test]
fn a_follower_on_an_unbounded_axis_still_follows_its_leader() {
let root = container()
.layout(ZStack::new())
.child(container().width(fill()).height(fill()))
.child(container().width(80).height(20));
let (tree, id) = layout_root(root, Constraints::new(0.0, 0.0, 400.0, f32::INFINITY));
assert_eq!(tree.cached_size(id), Some(Size::new(80.0, 20.0)));
assert_eq!(child_size(&tree, id, 0), Size::new(80.0, 20.0));
}
}