1use core::fmt;
9
10use alloc::{vec, vec::Vec};
11use waterui_core::View;
12
13use crate::{
14 Layout, Point, ProposalSize, Rect, Size, StretchAxis, SubView, container::FixedContainer,
15 stack::Alignment,
16};
17
18struct ChildMeasurement {
20 size: Size,
21}
22
23#[derive(Debug, Clone, Default)]
26pub struct OverlayLayout {
27 alignment: Alignment,
28}
29
30impl OverlayLayout {
31 #[must_use]
33 pub const fn alignment(mut self, alignment: Alignment) -> Self {
34 self.alignment = alignment;
35 self
36 }
37
38 #[must_use]
40 pub const fn alignment_ref(&self) -> Alignment {
41 self.alignment
42 }
43
44 fn aligned_origin(&self, bounds: &Rect, size: Size) -> Point {
45 match self.alignment {
46 Alignment::TopLeading => Point::new(bounds.x(), bounds.y()),
47 Alignment::Top => {
48 Point::new(bounds.x() + (bounds.width() - size.width) / 2.0, bounds.y())
49 }
50 Alignment::TopTrailing => Point::new(bounds.max_x() - size.width, bounds.y()),
51 Alignment::Leading => Point::new(
52 bounds.x(),
53 bounds.y() + (bounds.height() - size.height) / 2.0,
54 ),
55 Alignment::Center => Point::new(
56 bounds.x() + (bounds.width() - size.width) / 2.0,
57 bounds.y() + (bounds.height() - size.height) / 2.0,
58 ),
59 Alignment::Trailing => Point::new(
60 bounds.max_x() - size.width,
61 bounds.y() + (bounds.height() - size.height) / 2.0,
62 ),
63 Alignment::BottomLeading => Point::new(bounds.x(), bounds.max_y() - size.height),
64 Alignment::Bottom => Point::new(
65 bounds.x() + (bounds.width() - size.width) / 2.0,
66 bounds.max_y() - size.height,
67 ),
68 Alignment::BottomTrailing => {
69 Point::new(bounds.max_x() - size.width, bounds.max_y() - size.height)
70 }
71 }
72 }
73}
74
75impl Layout for OverlayLayout {
76 fn stretch_axis(&self) -> StretchAxis {
79 StretchAxis::Both
80 }
81
82 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
83 let base_size = children
86 .first()
87 .map_or(Size::zero(), |c| c.size_that_fits(proposal));
88
89 let base_width = if base_size.width.is_finite() && base_size.width > 0.0 {
90 base_size.width
91 } else {
92 proposal.width.unwrap_or(0.0)
93 };
94
95 let base_height = if base_size.height.is_finite() && base_size.height > 0.0 {
96 base_size.height
97 } else {
98 proposal.height.unwrap_or(0.0)
99 };
100
101 let width = proposal.width.unwrap_or(base_width);
102 let height = proposal.height.unwrap_or(base_height);
103
104 Size::new(width.max(0.0), height.max(0.0))
105 }
106
107 fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect> {
108 if children.is_empty() {
109 return vec![];
110 }
111
112 let child_proposal = ProposalSize::new(Some(bounds.width()), Some(bounds.height()));
114
115 let measurements: Vec<ChildMeasurement> = children
116 .iter()
117 .map(|child| ChildMeasurement {
118 size: child.size_that_fits(child_proposal),
119 })
120 .collect();
121
122 let mut placements = Vec::with_capacity(children.len());
123
124 if let Some(base) = measurements.first() {
126 let base_width = if base.size.width.is_infinite() {
127 bounds.width()
128 } else {
129 base.size.width
130 };
131 let base_height = if base.size.height.is_infinite() {
132 bounds.height()
133 } else {
134 base.size.height
135 };
136 placements.push(Rect::new(
137 bounds.origin(),
138 Size::new(base_width, base_height),
139 ));
140 }
141
142 for measurement in measurements.iter().skip(1) {
144 let width = if measurement.size.width.is_infinite() {
145 bounds.width()
146 } else {
147 measurement.size.width.min(bounds.width()).max(0.0)
148 };
149 let height = if measurement.size.height.is_infinite() {
150 bounds.height()
151 } else {
152 measurement.size.height.min(bounds.height()).max(0.0)
153 };
154 let size = Size::new(width, height);
155 let origin = self.aligned_origin(&bounds, size);
156 placements.push(Rect::new(origin, size));
157 }
158
159 placements
160 }
161}
162
163pub struct Overlay<Base, Layer> {
166 layout: OverlayLayout,
167 base: Base,
168 layer: Layer,
169}
170
171impl<Base, Layer> Overlay<Base, Layer> {
172 #[must_use]
174 pub const fn new(base: Base, layer: Layer) -> Self {
175 Self {
176 layout: OverlayLayout {
177 alignment: Alignment::Center,
178 },
179 base,
180 layer,
181 }
182 }
183
184 #[must_use]
186 pub const fn alignment(mut self, alignment: Alignment) -> Self {
187 self.layout.alignment = alignment;
188 self
189 }
190}
191
192impl<Base, Layer> fmt::Debug for Overlay<Base, Layer> {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.debug_struct("Overlay")
195 .field("layout", &self.layout)
196 .finish_non_exhaustive()
197 }
198}
199
200impl<Base, Layer> View for Overlay<Base, Layer>
201where
202 Base: View + 'static,
203 Layer: View + 'static,
204{
205 fn body(self, _env: &waterui_core::Environment) -> impl View {
206 FixedContainer::new(self.layout, (self.base, self.layer))
207 }
208}
209
210#[must_use]
212pub const fn overlay<Base, Layer>(base: Base, layer: Layer) -> Overlay<Base, Layer> {
213 Overlay::new(base, layer)
214}
215
216#[cfg(test)]
217#[allow(clippy::float_cmp)]
218mod tests {
219 use super::*;
220 use crate::StretchAxis;
221
222 struct MockSubView {
223 size: Size,
224 }
225
226 impl SubView for MockSubView {
227 fn size_that_fits(&self, _proposal: ProposalSize) -> Size {
228 self.size
229 }
230 fn stretch_axis(&self) -> StretchAxis {
231 StretchAxis::None
232 }
233 fn priority(&self) -> i32 {
234 0
235 }
236 }
237
238 #[test]
239 fn test_overlay_size_from_base() {
240 let layout = OverlayLayout::default();
241
242 let mut base = MockSubView {
243 size: Size::new(100.0, 50.0),
244 };
245 let mut overlay_child = MockSubView {
246 size: Size::new(20.0, 20.0),
247 };
248
249 let children: Vec<&dyn SubView> = vec![&mut base, &mut overlay_child];
250
251 let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
252
253 assert_eq!(size.width, 100.0);
255 assert_eq!(size.height, 50.0);
256 }
257
258 #[test]
259 fn test_overlay_placement_center() {
260 let layout = OverlayLayout {
261 alignment: Alignment::Center,
262 };
263
264 let mut base = MockSubView {
265 size: Size::new(100.0, 100.0),
266 };
267 let mut overlay_child = MockSubView {
268 size: Size::new(20.0, 20.0),
269 };
270
271 let children: Vec<&dyn SubView> = vec![&mut base, &mut overlay_child];
272
273 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
274 let rects = layout.place(bounds, &children);
275
276 assert_eq!(rects[0].width(), 100.0);
278 assert_eq!(rects[0].height(), 100.0);
279
280 assert_eq!(rects[1].x(), 40.0); assert_eq!(rects[1].y(), 40.0); }
284}