1use alloc::{vec, vec::Vec};
4use nami::collection::Collection;
5use waterui_core::{AnyView, View, id::Identifiable, view::TupleViews, views::ForEach};
6
7use crate::{
8 Layout, LazyContainer, PlacedSubview, Point, ProposalSize, Rect, Size, StretchAxis, SubView,
9 SubviewPlacement, ViewDimensions,
10 container::FixedContainer,
11 stack::{
12 Alignment, HorizontalAlignment, VerticalAlignment,
13 distribute::{LineAnchor, container_line, cross_envelope},
14 },
15};
16
17struct ChildMeasurement {
19 dimensions: ViewDimensions,
20}
21
22impl ChildMeasurement {
23 const fn size(&self) -> Size {
24 self.dimensions.size
25 }
26}
27
28fn zstack_horizontal_metrics(
30 measurements: &[ChildMeasurement],
31 alignment: HorizontalAlignment,
32) -> (f32, f32) {
33 cross_envelope(measurements.iter().map(|measurement| {
34 (
35 measurement.size().width,
36 measurement.dimensions.horizontal(alignment),
37 )
38 }))
39}
40
41fn zstack_vertical_metrics(
43 measurements: &[ChildMeasurement],
44 alignment: VerticalAlignment,
45) -> (f32, f32) {
46 cross_envelope(measurements.iter().map(|measurement| {
47 (
48 measurement.size().height,
49 measurement.dimensions.vertical(alignment),
50 )
51 }))
52}
53
54#[derive(Debug, Clone, Default)]
62pub struct ZStackLayout {
63 pub alignment: Alignment,
65}
66
67impl Layout for ZStackLayout {
68 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
77 let mut fills_h = false;
78 let mut fills_v = false;
79 for child in children {
80 match child {
81 StretchAxis::None => {}
82 StretchAxis::Both | StretchAxis::MainAxis => {
83 fills_h = true;
84 fills_v = true;
85 }
86 StretchAxis::Horizontal | StretchAxis::CrossAxis => fills_h = true,
87 StretchAxis::Vertical => fills_v = true,
88 }
89 }
90 match (fills_h, fills_v) {
91 (true, true) => StretchAxis::Both,
92 (true, false) => StretchAxis::Horizontal,
93 (false, true) => StretchAxis::Vertical,
94 (false, false) => StretchAxis::None,
95 }
96 }
97
98 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
99 if children.is_empty() {
100 return Size::zero();
101 }
102
103 let measurements: Vec<ChildMeasurement> = children
105 .iter()
106 .map(|child| ChildMeasurement {
107 dimensions: child.measure(proposal),
108 })
109 .collect();
110
111 let (max_leading, max_trailing) =
116 zstack_horizontal_metrics(&measurements, self.alignment.horizontal());
117 let (max_above, max_below) =
118 zstack_vertical_metrics(&measurements, self.alignment.vertical());
119 let width = if measurements.iter().any(|m| m.size().width.is_infinite()) {
120 f32::INFINITY
121 } else {
122 max_leading + max_trailing
123 };
124 let height = if measurements.iter().any(|m| m.size().height.is_infinite()) {
125 f32::INFINITY
126 } else {
127 max_above + max_below
128 };
129 Size::new(width, height)
130 }
131
132 fn place(
133 &self,
134 bounds: Rect,
135 _proposal: ProposalSize,
136 children: &[&dyn SubView],
137 ) -> Vec<SubviewPlacement> {
138 if children.is_empty() {
139 return vec![];
140 }
141
142 let placement = ProposalSize::new(Some(bounds.width()), Some(bounds.height()));
146 let measurements: Vec<ChildMeasurement> = children
147 .iter()
148 .map(|child| ChildMeasurement {
149 dimensions: child.measure(placement),
150 })
151 .collect();
152
153 let horizontal = self.alignment.horizontal();
154 let vertical = self.alignment.vertical();
155 let (max_leading, max_trailing) = zstack_horizontal_metrics(&measurements, horizontal);
156 let (max_above, max_below) = zstack_vertical_metrics(&measurements, vertical);
157 let line_x = bounds.x()
158 + container_line(
159 bounds.width(),
160 LineAnchor::horizontal(horizontal),
161 max_leading,
162 max_trailing,
163 );
164 let line_y = bounds.y()
165 + container_line(
166 bounds.height(),
167 LineAnchor::vertical(vertical),
168 max_above,
169 max_below,
170 );
171
172 let mut placements = Vec::with_capacity(children.len());
173
174 for measurement in &measurements {
175 let child_width = if measurement.dimensions.size.width.is_infinite() {
179 bounds.width()
180 } else {
181 measurement.dimensions.size.width
182 };
183 let child_height = if measurement.dimensions.size.height.is_infinite() {
184 bounds.height()
185 } else {
186 measurement.dimensions.size.height
187 };
188
189 let child_size = Size::new(child_width, child_height);
190 let mut adjusted_dimensions = measurement.dimensions.clone();
191 adjusted_dimensions.size = child_size;
192 let x = line_x - adjusted_dimensions.horizontal(horizontal);
193 let y = line_y - adjusted_dimensions.vertical(vertical);
194
195 placements.push(SubviewPlacement::new(
196 Rect::new(Point::new(x, y), child_size),
197 placement,
198 ));
199 }
200
201 placements
202 }
203
204 fn explicit_horizontal(
205 &self,
206 alignment: HorizontalAlignment,
207 _bounds: Rect,
208 children: &[PlacedSubview<'_>],
209 ) -> Option<f32> {
210 if alignment == self.alignment.horizontal() {
211 return children
212 .iter()
213 .filter_map(|child| child.explicit_horizontal(alignment))
214 .min_by(f32::total_cmp);
215 }
216 None
217 }
218
219 fn explicit_vertical(
220 &self,
221 alignment: VerticalAlignment,
222 _bounds: Rect,
223 children: &[PlacedSubview<'_>],
224 ) -> Option<f32> {
225 if alignment == VerticalAlignment::LastBaseline {
226 return children
227 .iter()
228 .filter_map(|child| child.explicit_vertical(alignment))
229 .max_by(f32::total_cmp);
230 }
231 if alignment == VerticalAlignment::FirstBaseline || alignment == self.alignment.vertical() {
232 return children
233 .iter()
234 .filter_map(|child| child.explicit_vertical(alignment))
235 .min_by(f32::total_cmp);
236 }
237 None
238 }
239}
240
241#[derive(Debug, Clone)]
271pub struct ZStack<C> {
272 layout: ZStackLayout,
273 contents: C,
274}
275
276impl<C> ZStack<C> {
277 #[must_use]
280 pub fn alignment(mut self, alignment: impl Into<Alignment>) -> Self {
281 self.layout.alignment = alignment.into();
282 self
283 }
284
285 crate::alignment::two_dimensional_alignment_methods!();
286}
287
288crate::stack::impl_stack_for_each!(ZStack, ZStackLayout);
289
290impl<C: TupleViews> ZStack<(C,)> {
291 pub const fn new(alignment: Alignment, contents: C) -> Self {
297 Self {
298 layout: ZStackLayout { alignment },
299 contents: (contents,),
300 }
301 }
302}
303
304impl<V> FromIterator<V> for ZStack<(Vec<AnyView>,)>
305where
306 V: View,
307{
308 fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
309 let contents = iter.into_iter().map(AnyView::new).collect::<Vec<_>>();
310 Self::new(Alignment::default(), contents)
311 }
312}
313
314pub const fn zstack<C: TupleViews>(contents: C) -> ZStack<(C,)> {
318 ZStack::new(Alignment::Center, contents)
319}
320
321impl<C> View for ZStack<(C,)>
322where
323 C: TupleViews + 'static,
324{
325 fn body(self, _env: &waterui_core::Environment) -> impl View {
326 FixedContainer::new(self.layout, self.contents.0)
327 }
328
329 fn stretch_axis(&self) -> StretchAxis {
333 self.layout.stretch_axis(&self.contents.0.stretch_axes())
334 }
335}
336
337impl<C, F, V> View for ZStack<ForEach<C, F, V>>
338where
339 C: Collection + Clone,
340 C::Item: Identifiable,
341 F: 'static + Fn(C::Item) -> V,
342 V: View,
343{
344 fn body(self, _env: &waterui_core::Environment) -> impl View {
345 LazyContainer::new(self.layout, self.contents)
346 }
347
348 fn stretch_axis(&self) -> StretchAxis {
352 self.layout.stretch_axis(&[])
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::StretchAxis;
360
361 struct MockSubView {
362 size: Size,
363 }
364
365 impl SubView for MockSubView {
366 fn measure(&self, _proposal: ProposalSize) -> ViewDimensions {
367 ViewDimensions::new(self.size)
368 }
369 fn stretch_axis(&self) -> StretchAxis {
370 StretchAxis::None
371 }
372 fn priority(&self) -> i32 {
373 0
374 }
375 }
376
377 #[test]
378 fn test_zstack_size_multiple_children() {
379 let layout = ZStackLayout {
380 alignment: Alignment::Center,
381 };
382
383 let mut child1 = MockSubView {
384 size: Size::new(50.0, 30.0),
385 };
386 let mut child2 = MockSubView {
387 size: Size::new(80.0, 40.0),
388 };
389 let mut child3 = MockSubView {
390 size: Size::new(60.0, 60.0),
391 };
392
393 let children: Vec<&dyn SubView> = vec![&mut child1, &mut child2, &mut child3];
394
395 let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
396
397 assert!((size.width - 80.0).abs() < f32::EPSILON);
399 assert!((size.height - 60.0).abs() < f32::EPSILON);
400 }
401
402 #[test]
403 fn test_zstack_placement_center_bounded_proposal() {
404 let layout = ZStackLayout {
405 alignment: Alignment::Center,
406 };
407
408 let mut child1 = MockSubView {
409 size: Size::new(40.0, 20.0),
410 };
411 let mut child2 = MockSubView {
412 size: Size::new(60.0, 40.0),
413 };
414
415 let children: Vec<&dyn SubView> = vec![&mut child1, &mut child2];
416
417 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
418 let proposal = ProposalSize::new(Some(bounds.width()), Some(bounds.height()));
419 let placements = layout.place(bounds, proposal, &children);
420
421 assert!((placements[0].frame.x() - 30.0).abs() < f32::EPSILON); assert!((placements[0].frame.y() - 40.0).abs() < f32::EPSILON); assert!((placements[1].frame.x() - 20.0).abs() < f32::EPSILON); assert!((placements[1].frame.y() - 30.0).abs() < f32::EPSILON); }
429}