1mod constraint;
7mod flex;
8mod grid;
9mod responsive;
10mod scroll;
11
12pub use constraint::{ConstraintLayout, ConstraintRule, MinMax};
13pub use flex::{Align, CrossAlign, FlexItem, FlexLayout, FlexWrap, JustifyContent};
14pub use grid::{GridItem, GridLayout, GridSpan};
15pub use responsive::{Breakpoint, ResponsiveLayout};
16pub use scroll::{ScrollContainer, ScrollDirection};
17
18use crate::core::Rect;
19
20#[derive(Debug, Clone, Copy)]
22pub struct SizeConstraints {
23 pub min_width: f32,
24 pub min_height: f32,
25 pub max_width: f32,
26 pub max_height: f32,
27}
28
29impl SizeConstraints {
30 pub fn unconstrained() -> Self {
32 Self {
33 min_width: 0.0,
34 min_height: 0.0,
35 max_width: f32::INFINITY,
36 max_height: f32::INFINITY,
37 }
38 }
39
40 pub fn fixed(width: f32, height: f32) -> Self {
42 Self {
43 min_width: width,
44 min_height: height,
45 max_width: width,
46 max_height: height,
47 }
48 }
49
50 pub fn from_rect(area: Rect) -> Self {
52 Self {
53 min_width: 0.0,
54 min_height: 0.0,
55 max_width: area.width,
56 max_height: area.height,
57 }
58 }
59
60 pub fn clamp(&self, width: f32, height: f32) -> (f32, f32) {
62 (
63 width.clamp(self.min_width, self.max_width),
64 height.clamp(self.min_height, self.max_height),
65 )
66 }
67}
68
69impl Default for SizeConstraints {
70 fn default() -> Self {
71 Self::unconstrained()
72 }
73}
74
75#[derive(Debug, Clone, Copy, Default)]
77pub struct DesiredSize {
78 pub width: f32,
79 pub height: f32,
80}
81
82impl DesiredSize {
83 pub fn new(width: f32, height: f32) -> Self {
84 Self { width, height }
85 }
86
87 pub fn zero() -> Self {
88 Self {
89 width: 0.0,
90 height: 0.0,
91 }
92 }
93}
94
95pub trait Layout {
100 fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize;
102
103 fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect>;
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn size_constraints_unconstrained() {
113 let c = SizeConstraints::unconstrained();
114 assert_eq!(c.min_width, 0.0);
115 assert!(c.max_width.is_infinite());
116 }
117
118 #[test]
119 fn size_constraints_fixed() {
120 let c = SizeConstraints::fixed(100.0, 50.0);
121 assert_eq!(c.min_width, 100.0);
122 assert_eq!(c.max_width, 100.0);
123 assert_eq!(c.min_height, 50.0);
124 assert_eq!(c.max_height, 50.0);
125 }
126
127 #[test]
128 fn size_constraints_clamp() {
129 let c = SizeConstraints {
130 min_width: 10.0,
131 min_height: 10.0,
132 max_width: 100.0,
133 max_height: 100.0,
134 };
135 assert_eq!(c.clamp(5.0, 200.0), (10.0, 100.0));
136 assert_eq!(c.clamp(50.0, 50.0), (50.0, 50.0));
137 }
138
139 #[test]
140 fn desired_size_zero() {
141 let s = DesiredSize::zero();
142 assert_eq!(s.width, 0.0);
143 assert_eq!(s.height, 0.0);
144 }
145
146 #[test]
147 fn size_constraints_from_rect() {
148 let r = Rect::new(10.0, 20.0, 300.0, 400.0);
149 let c = SizeConstraints::from_rect(r);
150 assert_eq!(c.max_width, 300.0);
151 assert_eq!(c.max_height, 400.0);
152 }
153
154 #[test]
157 fn flex_row_even_distribution() {
158 let flex = FlexLayout::row();
159 let children = vec![
160 DesiredSize::new(50.0, 30.0),
161 DesiredSize::new(50.0, 30.0),
162 DesiredSize::new(50.0, 30.0),
163 ];
164 let area = Rect::new(0.0, 0.0, 300.0, 100.0);
165 let rects = flex.arrange(&children, area);
166 assert_eq!(rects.len(), 3);
167 assert!((rects[0].x - 0.0).abs() < 0.01);
168 assert!((rects[1].x - 50.0).abs() < 0.01);
169 assert!((rects[2].x - 100.0).abs() < 0.01);
170 }
171
172 #[test]
173 fn flex_column_stacks_vertically() {
174 let flex = FlexLayout::column();
175 let children = vec![DesiredSize::new(100.0, 40.0), DesiredSize::new(100.0, 60.0)];
176 let area = Rect::new(0.0, 0.0, 200.0, 200.0);
177 let rects = flex.arrange(&children, area);
178 assert_eq!(rects.len(), 2);
179 assert!((rects[0].y - 0.0).abs() < 0.01);
180 assert!((rects[1].y - 40.0).abs() < 0.01);
181 }
182
183 #[test]
184 fn flex_spacing() {
185 let flex = FlexLayout::row().spacing(10.0);
186 let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
187 let area = Rect::new(0.0, 0.0, 200.0, 100.0);
188 let rects = flex.arrange(&children, area);
189 assert!((rects[1].x - 60.0).abs() < 0.01); }
191
192 #[test]
193 fn flex_measure_row() {
194 let flex = FlexLayout::row().spacing(5.0);
195 let children = vec![DesiredSize::new(40.0, 20.0), DesiredSize::new(60.0, 30.0)];
196 let size = flex.measure(&children, SizeConstraints::unconstrained());
197 assert!((size.width - 105.0).abs() < 0.01); assert!((size.height - 30.0).abs() < 0.01); }
200
201 #[test]
202 fn flex_wrap_wraps_to_next_line() {
203 let flex = FlexLayout::row().wrap(FlexWrap::Wrap);
204 let children = vec![
205 DesiredSize::new(60.0, 30.0),
206 DesiredSize::new(60.0, 30.0),
207 DesiredSize::new(60.0, 30.0),
208 ];
209 let area = Rect::new(0.0, 0.0, 130.0, 200.0);
210 let rects = flex.arrange(&children, area);
211 assert!((rects[2].y - 30.0).abs() < 0.01);
213 }
214
215 #[test]
218 fn grid_basic_layout() {
219 let grid = GridLayout::new(2, 2);
220 let children = vec![
221 DesiredSize::new(50.0, 50.0),
222 DesiredSize::new(50.0, 50.0),
223 DesiredSize::new(50.0, 50.0),
224 DesiredSize::new(50.0, 50.0),
225 ];
226 let area = Rect::new(0.0, 0.0, 200.0, 200.0);
227 let rects = grid.arrange(&children, area);
228 assert_eq!(rects.len(), 4);
229 assert!((rects[0].width - 100.0).abs() < 0.01);
230 assert!((rects[0].height - 100.0).abs() < 0.01);
231 }
232
233 #[test]
234 fn grid_measure() {
235 let grid = GridLayout::new(3, 2).gap(4.0);
236 let children = vec![
237 DesiredSize::new(30.0, 20.0),
238 DesiredSize::new(30.0, 20.0),
239 DesiredSize::new(30.0, 20.0),
240 DesiredSize::new(30.0, 20.0),
241 DesiredSize::new(30.0, 20.0),
242 DesiredSize::new(30.0, 20.0),
243 ];
244 let size = grid.measure(&children, SizeConstraints::unconstrained());
245 assert!((size.width - 98.0).abs() < 0.01); assert!((size.height - 44.0).abs() < 0.01); }
248
249 #[test]
250 fn grid_with_spans() {
251 let grid = GridLayout::new(3, 2)
252 .item(GridItem::new(0).col_span(2))
253 .item(GridItem::new(1));
254 let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
255 let area = Rect::new(0.0, 0.0, 300.0, 200.0);
256 let rects = grid.arrange(&children, area);
257 assert!((rects[0].width - 200.0).abs() < 0.01);
259 }
260
261 #[test]
264 fn constraint_layout_min_max() {
265 let layout =
266 ConstraintLayout::new().rule(ConstraintRule::new(0).width(MinMax::new(50.0, 200.0)));
267 let children = vec![DesiredSize::new(300.0, 40.0)];
268 let area = Rect::new(0.0, 0.0, 400.0, 400.0);
269 let rects = layout.arrange(&children, area);
270 assert!((rects[0].width - 200.0).abs() < 0.01); }
272
273 #[test]
274 fn constraint_layout_unconstrained_children() {
275 let layout = ConstraintLayout::new();
276 let children = vec![DesiredSize::new(80.0, 60.0)];
277 let area = Rect::new(10.0, 20.0, 400.0, 300.0);
278 let rects = layout.arrange(&children, area);
279 assert!((rects[0].x - 10.0).abs() < 0.01);
280 assert!((rects[0].y - 20.0).abs() < 0.01);
281 assert!((rects[0].width - 80.0).abs() < 0.01);
282 }
283
284 #[test]
287 fn scroll_container_clips_content() {
288 let scroll = ScrollContainer::new(ScrollDirection::Vertical);
289 let children = vec![DesiredSize::new(100.0, 500.0)];
290 let area = Rect::new(0.0, 0.0, 100.0, 200.0);
291 let rects = scroll.arrange(&children, area);
292 assert!((rects[0].height - 500.0).abs() < 0.01); }
294
295 #[test]
296 fn scroll_with_offset() {
297 let scroll = ScrollContainer::new(ScrollDirection::Vertical).offset(50.0);
298 let children = vec![DesiredSize::new(100.0, 500.0)];
299 let area = Rect::new(0.0, 0.0, 100.0, 200.0);
300 let rects = scroll.arrange(&children, area);
301 assert!((rects[0].y - (-50.0)).abs() < 0.01); }
303
304 #[test]
305 fn scroll_measure_passes_through() {
306 let scroll = ScrollContainer::new(ScrollDirection::Both);
307 let children = vec![DesiredSize::new(800.0, 600.0)];
308 let size = scroll.measure(&children, SizeConstraints::unconstrained());
309 assert!((size.width - 800.0).abs() < 0.01);
310 assert!((size.height - 600.0).abs() < 0.01);
311 }
312
313 #[test]
316 fn responsive_selects_correct_breakpoint() {
317 let layout = ResponsiveLayout::new()
318 .breakpoint(Breakpoint::new(0.0, FlexLayout::column()))
319 .breakpoint(Breakpoint::new(600.0, FlexLayout::row()));
320 let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
321 let wide = Rect::new(0.0, 0.0, 800.0, 400.0);
323 let rects = layout.arrange(&children, wide);
324 assert!((rects[1].x - 50.0).abs() < 0.01); let narrow = Rect::new(0.0, 0.0, 400.0, 400.0);
328 let rects = layout.arrange(&children, narrow);
329 assert!((rects[1].y - 30.0).abs() < 0.01); }
331
332 #[test]
333 fn responsive_measure_uses_widest() {
334 let layout = ResponsiveLayout::new().breakpoint(Breakpoint::new(0.0, FlexLayout::column()));
335 let children = vec![DesiredSize::new(50.0, 30.0)];
336 let size = layout.measure(&children, SizeConstraints::unconstrained());
337 assert!((size.width - 50.0).abs() < 0.01);
338 }
339}