1use alloc::{vec, vec::Vec};
8use waterui_core::{AnyView, View};
9
10use crate::{
11 Layout, Point, ProposalSize, Rect, Size, SubView,
12 container::FixedContainer,
13 stack::{Alignment, HorizontalAlignment, VerticalAlignment},
14};
15
16#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
18pub struct FrameLayout {
19 min_width: Option<f32>,
20 ideal_width: Option<f32>,
21 max_width: Option<f32>,
22 min_height: Option<f32>,
23 ideal_height: Option<f32>,
24 max_height: Option<f32>,
25 alignment: Alignment,
26}
27
28impl Layout for FrameLayout {
29 fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size {
30 let proposed_width = self.ideal_width.or(proposal.width);
35 let proposed_height = self.ideal_height.or(proposal.height);
36
37 let child_proposal = ProposalSize {
38 width: proposed_width.map(|w| {
39 w.max(self.min_width.unwrap_or(f32::NEG_INFINITY))
40 .min(self.max_width.unwrap_or(f32::INFINITY))
41 }),
42 height: proposed_height.map(|h| {
43 h.max(self.min_height.unwrap_or(f32::NEG_INFINITY))
44 .min(self.max_height.unwrap_or(f32::INFINITY))
45 }),
46 };
47
48 let child_size = children
50 .first()
51 .map_or(Size::zero(), |c| c.size_that_fits(child_proposal));
52
53 let mut target_width = self.ideal_width.unwrap_or(child_size.width);
55 target_width = target_width
56 .max(self.min_width.unwrap_or(f32::NEG_INFINITY))
57 .min(self.max_width.unwrap_or(f32::INFINITY));
58
59 let mut target_height = self.ideal_height.unwrap_or(child_size.height);
61 target_height = target_height
62 .max(self.min_height.unwrap_or(f32::NEG_INFINITY))
63 .min(self.max_height.unwrap_or(f32::INFINITY));
64
65 Size::new(
68 proposal.width.unwrap_or(target_width),
69 proposal.height.unwrap_or(target_height),
70 )
71 }
72
73 fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect> {
74 if children.is_empty() {
75 return vec![];
76 }
77
78 let proposed_width = self.ideal_width.unwrap_or_else(|| bounds.width());
80 let proposed_height = self.ideal_height.unwrap_or_else(|| bounds.height());
81
82 let child_proposal = ProposalSize {
83 width: Some(
84 proposed_width
85 .max(self.min_width.unwrap_or(0.0))
86 .min(self.max_width.unwrap_or(f32::INFINITY))
87 .min(bounds.width()),
88 ),
89 height: Some(
90 proposed_height
91 .max(self.min_height.unwrap_or(0.0))
92 .min(self.max_height.unwrap_or(f32::INFINITY))
93 .min(bounds.height()),
94 ),
95 };
96
97 let child_size = children
98 .first()
99 .map_or(Size::zero(), |c| c.size_that_fits(child_proposal));
100
101 let child_width = if child_size.width.is_infinite() {
103 bounds.width()
104 } else {
105 child_size.width
106 };
107
108 let child_height = if child_size.height.is_infinite() {
109 bounds.height()
110 } else {
111 child_size.height
112 };
113
114 let final_child_size = Size::new(child_width, child_height);
115
116 let child_x = match self.alignment.horizontal() {
118 HorizontalAlignment::Leading => bounds.x(),
119 HorizontalAlignment::Center => {
120 bounds.x() + (bounds.width() - final_child_size.width) / 2.0
121 }
122 HorizontalAlignment::Trailing => bounds.max_x() - final_child_size.width,
123 };
124
125 let child_y = match self.alignment.vertical() {
126 VerticalAlignment::Top => bounds.y(),
127 VerticalAlignment::Center => {
128 bounds.y() + (bounds.height() - final_child_size.height) / 2.0
129 }
130 VerticalAlignment::Bottom => bounds.max_y() - final_child_size.height,
131 };
132
133 vec![Rect::new(Point::new(child_x, child_y), final_child_size)]
134 }
135}
136
137#[derive(Debug)]
142pub struct Frame {
143 layout: FrameLayout,
144 content: AnyView,
145}
146
147impl Frame {
148 #[must_use]
154 pub fn new(content: impl View) -> Self {
155 Self {
156 layout: FrameLayout::default(),
157 content: AnyView::new(content),
158 }
159 }
160
161 #[must_use]
166 pub const fn alignment(mut self, alignment: Alignment) -> Self {
167 self.layout.alignment = alignment;
168 self
169 }
170
171 #[must_use]
173 pub const fn width(mut self, width: f32) -> Self {
174 self.layout.ideal_width = Some(width);
175 self
176 }
177
178 #[must_use]
180 pub const fn height(mut self, height: f32) -> Self {
181 self.layout.ideal_height = Some(height);
182 self
183 }
184
185 #[must_use]
187 pub const fn min_width(mut self, width: f32) -> Self {
188 self.layout.min_width = Some(width);
189 self
190 }
191
192 #[must_use]
194 pub const fn max_width(mut self, width: f32) -> Self {
195 self.layout.max_width = Some(width);
196 self
197 }
198
199 #[must_use]
201 pub const fn min_height(mut self, height: f32) -> Self {
202 self.layout.min_height = Some(height);
203 self
204 }
205
206 #[must_use]
208 pub const fn max_height(mut self, height: f32) -> Self {
209 self.layout.max_height = Some(height);
210 self
211 }
212}
213
214impl View for Frame {
215 fn body(self, _env: &waterui_core::Environment) -> impl View {
216 FixedContainer::new(self.layout, vec![self.content])
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::StretchAxis;
225
226 struct MockSubView {
227 size: Size,
228 }
229
230 impl SubView for MockSubView {
231 fn size_that_fits(&self, _proposal: ProposalSize) -> Size {
232 self.size
233 }
234 fn stretch_axis(&self) -> StretchAxis {
235 StretchAxis::None
236 }
237 fn priority(&self) -> i32 {
238 0
239 }
240 }
241
242 #[test]
243 fn test_frame_with_ideal_size() {
244 let layout = FrameLayout {
245 ideal_width: Some(100.0),
246 ideal_height: Some(50.0),
247 ..Default::default()
248 };
249
250 let mut child = MockSubView {
251 size: Size::new(30.0, 20.0),
252 };
253 let children: Vec<&dyn SubView> = vec![&mut child];
254
255 let size = layout.size_that_fits(ProposalSize::UNSPECIFIED, &children);
256
257 assert!((size.width - 100.0).abs() < f32::EPSILON);
259 assert!((size.height - 50.0).abs() < f32::EPSILON);
260 }
261
262 #[test]
263 fn test_frame_alignment() {
264 let layout = FrameLayout {
265 alignment: Alignment::BottomTrailing,
266 ..Default::default()
267 };
268
269 let mut child = MockSubView {
270 size: Size::new(30.0, 20.0),
271 };
272 let children: Vec<&dyn SubView> = vec![&mut child];
273
274 let bounds = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
275 let rects = layout.place(bounds, &children);
276
277 assert!((rects[0].x() - 70.0).abs() < f32::EPSILON); assert!((rects[0].y() - 80.0).abs() < f32::EPSILON); }
281}