waterui_layout/modifiers/
background.rs1use core::fmt;
21
22use alloc::vec::Vec;
23use waterui_core::View;
24
25use crate::{
26 HorizontalAlignment, Layout, PlacedSubview, ProposalSize, Rect, Size, StretchAxis, SubView,
27 VerticalAlignment, container::FixedContainer,
28};
29
30#[derive(Debug, Clone, Copy, Default)]
35pub struct BackgroundLayout;
36
37impl Layout for BackgroundLayout {
38 fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
42 children.get(1).copied().unwrap_or_default()
43 }
44
45 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
46 children
50 .get(1)
51 .expect("BackgroundLayout requires a content child at index 1")
52 .measure(proposal)
53 .size
54 }
55
56 fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect> {
57 assert!(
58 children.len() >= 2,
59 "BackgroundLayout requires at least 2 children: background and content"
60 );
61
62 let mut placements = Vec::with_capacity(children.len());
63
64 placements.push(Rect::new(
66 bounds.origin(),
67 Size::new(bounds.width(), bounds.height()),
68 ));
69
70 placements.push(Rect::new(
72 bounds.origin(),
73 Size::new(bounds.width(), bounds.height()),
74 ));
75
76 placements
77 }
78
79 fn explicit_horizontal(
80 &self,
81 alignment: HorizontalAlignment,
82 _bounds: Rect,
83 children: &[PlacedSubview<'_>],
84 ) -> Option<f32> {
85 children
86 .get(1)
87 .and_then(|child| child.explicit_horizontal(alignment))
88 }
89
90 fn explicit_vertical(
91 &self,
92 alignment: VerticalAlignment,
93 _bounds: Rect,
94 children: &[PlacedSubview<'_>],
95 ) -> Option<f32> {
96 children
97 .get(1)
98 .and_then(|child| child.explicit_vertical(alignment))
99 }
100}
101
102pub struct BackgroundView<Content, Bg> {
107 content: Content,
108 background: Bg,
109}
110
111impl<Content, Bg> BackgroundView<Content, Bg> {
112 #[must_use]
114 pub const fn new(content: Content, background: Bg) -> Self {
115 Self {
116 content,
117 background,
118 }
119 }
120}
121
122impl<Content, Bg> fmt::Debug for BackgroundView<Content, Bg> {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 f.debug_struct("BackgroundView").finish_non_exhaustive()
125 }
126}
127
128impl<Content, Bg> View for BackgroundView<Content, Bg>
129where
130 Content: View + 'static,
131 Bg: View + 'static,
132{
133 fn body(self, _env: &waterui_core::Environment) -> impl View {
134 let Self {
135 content,
136 background,
137 } = self;
138
139 FixedContainer::new(BackgroundLayout, (background, content))
141 }
142}
143
144#[must_use]
146pub const fn background<Content, Bg>(
147 content: Content,
148 background: Bg,
149) -> BackgroundView<Content, Bg> {
150 BackgroundView::new(content, background)
151}
152
153#[cfg(test)]
154#[allow(clippy::float_cmp)]
155mod tests {
156 use waterui_core::layout::Point;
157 use waterui_core::layout::ViewDimensions;
158
159 use super::*;
160 use alloc::vec;
161
162 struct MockSubView {
163 size: Size,
164 }
165
166 impl SubView for MockSubView {
167 fn measure(&self, _proposal: ProposalSize) -> ViewDimensions {
168 ViewDimensions::new(self.size)
169 }
170 fn stretch_axis(&self) -> StretchAxis {
171 StretchAxis::None
172 }
173 fn priority(&self) -> i32 {
174 0
175 }
176 }
177
178 #[test]
179 fn test_background_size_from_content() {
180 let layout = BackgroundLayout;
181
182 let mut bg = MockSubView {
183 size: Size::new(200.0, 200.0),
184 };
185 let mut content = MockSubView {
186 size: Size::new(100.0, 50.0),
187 };
188
189 let children: Vec<&dyn SubView> = vec![&mut bg, &mut content];
190 let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
191
192 assert_eq!(size.width, 100.0);
194 assert_eq!(size.height, 50.0);
195 }
196
197 #[test]
198 fn test_background_fills_bounds() {
199 let layout = BackgroundLayout;
200
201 let mut bg = MockSubView {
202 size: Size::new(50.0, 50.0),
203 };
204 let mut content = MockSubView {
205 size: Size::new(100.0, 100.0),
206 };
207
208 let children: Vec<&dyn SubView> = vec![&mut bg, &mut content];
209 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
210 let rects = layout.place(bounds, &children);
211
212 assert_eq!(rects[0].width(), 100.0);
214 assert_eq!(rects[0].height(), 100.0);
215 assert_eq!(rects[1].width(), 100.0);
216 assert_eq!(rects[1].height(), 100.0);
217 }
218
219 #[test]
220 fn test_background_layout_reports_its_content_stretch_axis() {
221 assert_eq!(
224 BackgroundLayout.stretch_axis(&[StretchAxis::Both, StretchAxis::Horizontal]),
225 StretchAxis::Horizontal
226 );
227 }
228}