#[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);
for (i, &child_id) in children.iter().enumerate() {
let hints = self.hints[i];
if hints.fill_width && hints.fill_height {
continue;
}
let Some(child_size) = ctx.layout_child(child_id, constraints) else {
continue;
};
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 = constraints.max_width;
}
if !led_height {
stack.height = constraints.max_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);
}
for &child_id in children.iter() {
ctx.tree().set_origin(child_id, origin.0, origin.1);
}
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 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 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"
);
}
}