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}