allui/layout/
zstack.rs

1//! ZStack - Overlay stack layout.
2
3use gpui::{div, App, IntoElement, ParentElement, RenderOnce, Styled, Window};
4
5use crate::alignment::Alignment;
6use crate::modifier::Modifier;
7
8/// A view that overlays its children, aligning them in both axes.
9///
10/// By default, ZStack centers its children (matching SwiftUI).
11///
12/// # Example
13///
14/// ```rust,ignore
15/// ZStack::new()
16///     .alignment(Alignment::bottom_trailing())
17///     .child(Image::new("background"))
18///     .child(Text::new("Badge"))
19/// ```
20#[derive(IntoElement)]
21pub struct ZStack {
22    alignment: Alignment,
23    children: Vec<gpui::AnyElement>,
24}
25
26impl ZStack {
27    /// Create a new overlay stack.
28    pub fn new() -> Self {
29        Self {
30            alignment: Alignment::center(), // SwiftUI default
31            children: Vec::new(),
32        }
33    }
34
35    /// Set the alignment of children within the stack.
36    pub fn alignment(mut self, alignment: Alignment) -> Self {
37        self.alignment = alignment;
38        self
39    }
40
41    impl_child_methods!();
42}
43
44impl Default for ZStack {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl Modifier for ZStack {}
51
52impl RenderOnce for ZStack {
53    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
54        // Strategy: Use relative container + absolute positioned children
55        // - Container is relative, sizes to its largest child via a "sizer" approach
56        // - First child determines size (rendered normally)
57        // - All children rendered absolutely on top with alignment
58        //
59        // For proper "size to largest", we render the first child twice:
60        // once for sizing (invisible), and all children absolutely for display.
61        //
62        // Actually simpler: just use absolute positioning with inset-0 and flex
63        // to align within. The container needs an explicit size or to get size
64        // from somewhere.
65        //
66        // The REAL issue: When ZStack is wrapped in frame_size(200, 200), the
67        // Frame modifier creates a container. The ZStack's container with
68        // size_full() should fill that. Then absolute children with inset_0
69        // fill the ZStack container, and flex aligns within.
70
71        let alignment = self.alignment;
72
73        let positioned_children: Vec<_> = self
74            .children
75            .into_iter()
76            .map(|child| {
77                // Absolute positioning fills the container via inset_0
78                // Flex + alignment positions the child within
79                let wrapper = div().absolute().inset_0().flex();
80                let wrapper = alignment.horizontal.apply_as_justify(wrapper);
81                let wrapper = alignment.vertical.apply_as_items(wrapper);
82
83                wrapper.child(child)
84            })
85            .collect();
86
87        // Container needs relative for absolute children to position against
88        // size_full so it fills any frame wrapper
89        div().relative().size_full().children(positioned_children)
90    }
91}