presentar_layout/
flex.rs

1//! Flexbox layout types.
2
3use serde::{Deserialize, Serialize};
4
5/// Direction for flex layout.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
7pub enum FlexDirection {
8    /// Horizontal (left to right)
9    #[default]
10    Row,
11    /// Horizontal (right to left)
12    RowReverse,
13    /// Vertical (top to bottom)
14    Column,
15    /// Vertical (bottom to top)
16    ColumnReverse,
17}
18
19/// Main axis alignment for flex layout.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
21pub enum FlexJustify {
22    /// Pack items at the start
23    #[default]
24    Start,
25    /// Pack items at the end
26    End,
27    /// Center items
28    Center,
29    /// Distribute space evenly between items
30    SpaceBetween,
31    /// Distribute space evenly around items
32    SpaceAround,
33    /// Distribute space evenly, including edges
34    SpaceEvenly,
35}
36
37/// Cross axis alignment for flex layout.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
39pub enum FlexAlign {
40    /// Align to the start
41    Start,
42    /// Align to the end
43    End,
44    /// Center items
45    #[default]
46    Center,
47    /// Stretch to fill
48    Stretch,
49    /// Align to baseline
50    Baseline,
51}
52
53/// Flex item properties.
54#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
55pub struct FlexItem {
56    /// Flex grow factor
57    pub grow: f32,
58    /// Flex shrink factor
59    pub shrink: f32,
60    /// Flex basis (initial size)
61    pub basis: Option<f32>,
62    /// Self alignment override
63    pub align_self: Option<FlexAlign>,
64}
65
66impl FlexItem {
67    /// Create a new flex item with default values.
68    #[must_use]
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Set the grow factor.
74    #[must_use]
75    pub const fn grow(mut self, grow: f32) -> Self {
76        self.grow = grow;
77        self
78    }
79
80    /// Set the shrink factor.
81    #[must_use]
82    pub const fn shrink(mut self, shrink: f32) -> Self {
83        self.shrink = shrink;
84        self
85    }
86
87    /// Set the basis.
88    #[must_use]
89    pub const fn basis(mut self, basis: f32) -> Self {
90        self.basis = Some(basis);
91        self
92    }
93
94    /// Set self alignment.
95    #[must_use]
96    pub const fn align_self(mut self, align: FlexAlign) -> Self {
97        self.align_self = Some(align);
98        self
99    }
100}
101
102/// Distribute available space among flex items.
103#[must_use]
104#[allow(dead_code)]
105pub(crate) fn distribute_flex(items: &[FlexItem], sizes: &[f32], available: f32) -> Vec<f32> {
106    if items.is_empty() {
107        return Vec::new();
108    }
109
110    let total_size: f32 = sizes.iter().sum();
111    let remaining = available - total_size;
112
113    if remaining.abs() < 0.001 {
114        return sizes.to_vec();
115    }
116
117    if remaining > 0.0 {
118        // Grow items
119        let total_grow: f32 = items.iter().map(|i| i.grow).sum();
120        if total_grow > 0.0 {
121            return sizes
122                .iter()
123                .zip(items.iter())
124                .map(|(&size, item)| size + (remaining * item.grow / total_grow))
125                .collect();
126        }
127    } else {
128        // Shrink items
129        let total_shrink: f32 = items.iter().map(|i| i.shrink).sum();
130        if total_shrink > 0.0 {
131            return sizes
132                .iter()
133                .zip(items.iter())
134                .map(|(&size, item)| (size + (remaining * item.shrink / total_shrink)).max(0.0))
135                .collect();
136        }
137    }
138
139    sizes.to_vec()
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_flex_direction_default() {
148        assert_eq!(FlexDirection::default(), FlexDirection::Row);
149    }
150
151    #[test]
152    fn test_flex_justify_default() {
153        assert_eq!(FlexJustify::default(), FlexJustify::Start);
154    }
155
156    #[test]
157    fn test_flex_align_default() {
158        assert_eq!(FlexAlign::default(), FlexAlign::Center);
159    }
160
161    #[test]
162    fn test_flex_item_builder() {
163        let item = FlexItem::new()
164            .grow(1.0)
165            .shrink(0.0)
166            .basis(100.0)
167            .align_self(FlexAlign::Start);
168
169        assert_eq!(item.grow, 1.0);
170        assert_eq!(item.shrink, 0.0);
171        assert_eq!(item.basis, Some(100.0));
172        assert_eq!(item.align_self, Some(FlexAlign::Start));
173    }
174
175    #[test]
176    fn test_distribute_flex_empty() {
177        let result = distribute_flex(&[], &[], 100.0);
178        assert!(result.is_empty());
179    }
180
181    #[test]
182    fn test_distribute_flex_exact_fit() {
183        let items = vec![FlexItem::new(), FlexItem::new()];
184        let sizes = vec![50.0, 50.0];
185        let result = distribute_flex(&items, &sizes, 100.0);
186        assert_eq!(result, vec![50.0, 50.0]);
187    }
188
189    #[test]
190    fn test_distribute_flex_grow() {
191        let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
192        let sizes = vec![25.0, 25.0];
193        let result = distribute_flex(&items, &sizes, 100.0);
194        assert_eq!(result, vec![50.0, 50.0]);
195    }
196
197    #[test]
198    fn test_distribute_flex_grow_uneven() {
199        let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(3.0)];
200        let sizes = vec![0.0, 0.0];
201        let result = distribute_flex(&items, &sizes, 100.0);
202        assert_eq!(result, vec![25.0, 75.0]);
203    }
204
205    #[test]
206    fn test_distribute_flex_shrink() {
207        let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(1.0)];
208        let sizes = vec![75.0, 75.0];
209        let result = distribute_flex(&items, &sizes, 100.0);
210        assert_eq!(result, vec![50.0, 50.0]);
211    }
212
213    // =========================================================================
214    // FlexDirection Tests
215    // =========================================================================
216
217    #[test]
218    fn test_flex_direction_clone() {
219        let dir = FlexDirection::Column;
220        let cloned = dir;
221        assert_eq!(dir, cloned);
222    }
223
224    #[test]
225    fn test_flex_direction_all_variants() {
226        assert_eq!(FlexDirection::Row, FlexDirection::Row);
227        assert_eq!(FlexDirection::RowReverse, FlexDirection::RowReverse);
228        assert_eq!(FlexDirection::Column, FlexDirection::Column);
229        assert_eq!(FlexDirection::ColumnReverse, FlexDirection::ColumnReverse);
230    }
231
232    #[test]
233    fn test_flex_direction_debug() {
234        let dir = FlexDirection::Row;
235        let debug = format!("{:?}", dir);
236        assert!(debug.contains("Row"));
237    }
238
239    // =========================================================================
240    // FlexJustify Tests
241    // =========================================================================
242
243    #[test]
244    fn test_flex_justify_all_variants() {
245        assert_eq!(FlexJustify::Start, FlexJustify::Start);
246        assert_eq!(FlexJustify::End, FlexJustify::End);
247        assert_eq!(FlexJustify::Center, FlexJustify::Center);
248        assert_eq!(FlexJustify::SpaceBetween, FlexJustify::SpaceBetween);
249        assert_eq!(FlexJustify::SpaceAround, FlexJustify::SpaceAround);
250        assert_eq!(FlexJustify::SpaceEvenly, FlexJustify::SpaceEvenly);
251    }
252
253    #[test]
254    fn test_flex_justify_clone() {
255        let justify = FlexJustify::SpaceBetween;
256        let cloned = justify;
257        assert_eq!(justify, cloned);
258    }
259
260    #[test]
261    fn test_flex_justify_debug() {
262        let justify = FlexJustify::Center;
263        let debug = format!("{:?}", justify);
264        assert!(debug.contains("Center"));
265    }
266
267    // =========================================================================
268    // FlexAlign Tests
269    // =========================================================================
270
271    #[test]
272    fn test_flex_align_all_variants() {
273        assert_eq!(FlexAlign::Start, FlexAlign::Start);
274        assert_eq!(FlexAlign::End, FlexAlign::End);
275        assert_eq!(FlexAlign::Center, FlexAlign::Center);
276        assert_eq!(FlexAlign::Stretch, FlexAlign::Stretch);
277        assert_eq!(FlexAlign::Baseline, FlexAlign::Baseline);
278    }
279
280    #[test]
281    fn test_flex_align_clone() {
282        let align = FlexAlign::Stretch;
283        let cloned = align;
284        assert_eq!(align, cloned);
285    }
286
287    #[test]
288    fn test_flex_align_debug() {
289        let align = FlexAlign::Baseline;
290        let debug = format!("{:?}", align);
291        assert!(debug.contains("Baseline"));
292    }
293
294    // =========================================================================
295    // FlexItem Tests
296    // =========================================================================
297
298    #[test]
299    fn test_flex_item_default() {
300        let item = FlexItem::default();
301        assert_eq!(item.grow, 0.0);
302        assert_eq!(item.shrink, 0.0);
303        assert_eq!(item.basis, None);
304        assert_eq!(item.align_self, None);
305    }
306
307    #[test]
308    fn test_flex_item_new() {
309        let item = FlexItem::new();
310        assert_eq!(item.grow, 0.0);
311        assert_eq!(item.shrink, 0.0);
312    }
313
314    #[test]
315    fn test_flex_item_grow_only() {
316        let item = FlexItem::new().grow(2.5);
317        assert_eq!(item.grow, 2.5);
318        assert_eq!(item.shrink, 0.0);
319    }
320
321    #[test]
322    fn test_flex_item_shrink_only() {
323        let item = FlexItem::new().shrink(0.5);
324        assert_eq!(item.shrink, 0.5);
325        assert_eq!(item.grow, 0.0);
326    }
327
328    #[test]
329    fn test_flex_item_basis_only() {
330        let item = FlexItem::new().basis(200.0);
331        assert_eq!(item.basis, Some(200.0));
332    }
333
334    #[test]
335    fn test_flex_item_align_self_only() {
336        let item = FlexItem::new().align_self(FlexAlign::End);
337        assert_eq!(item.align_self, Some(FlexAlign::End));
338    }
339
340    #[test]
341    fn test_flex_item_clone() {
342        let item = FlexItem::new().grow(1.0).shrink(0.5);
343        let cloned = item;
344        assert_eq!(item.grow, cloned.grow);
345        assert_eq!(item.shrink, cloned.shrink);
346    }
347
348    #[test]
349    fn test_flex_item_debug() {
350        let item = FlexItem::new().grow(1.0);
351        let debug = format!("{:?}", item);
352        assert!(debug.contains("FlexItem"));
353    }
354
355    // =========================================================================
356    // distribute_flex Tests
357    // =========================================================================
358
359    #[test]
360    fn test_distribute_flex_no_grow_no_shrink() {
361        let items = vec![FlexItem::new(), FlexItem::new()];
362        let sizes = vec![30.0, 30.0];
363        let result = distribute_flex(&items, &sizes, 100.0);
364        // No grow factor, so sizes remain unchanged
365        assert_eq!(result, vec![30.0, 30.0]);
366    }
367
368    #[test]
369    fn test_distribute_flex_single_item_grow() {
370        let items = vec![FlexItem::new().grow(1.0)];
371        let sizes = vec![50.0];
372        let result = distribute_flex(&items, &sizes, 100.0);
373        assert_eq!(result, vec![100.0]);
374    }
375
376    #[test]
377    fn test_distribute_flex_single_item_shrink() {
378        let items = vec![FlexItem::new().shrink(1.0)];
379        let sizes = vec![150.0];
380        let result = distribute_flex(&items, &sizes, 100.0);
381        assert_eq!(result, vec![100.0]);
382    }
383
384    #[test]
385    fn test_distribute_flex_shrink_uneven() {
386        let items = vec![FlexItem::new().shrink(1.0), FlexItem::new().shrink(3.0)];
387        let sizes = vec![100.0, 100.0];
388        let result = distribute_flex(&items, &sizes, 100.0);
389        // Total: 200, need to shrink by 100
390        // item1: 100 - 100 * 1/4 = 75
391        // item2: 100 - 100 * 3/4 = 25
392        assert_eq!(result, vec![75.0, 25.0]);
393    }
394
395    #[test]
396    fn test_distribute_flex_shrink_to_zero() {
397        let items = vec![FlexItem::new().shrink(1.0)];
398        let sizes = vec![50.0];
399        // Need to shrink more than available
400        let result = distribute_flex(&items, &sizes, 0.0);
401        assert_eq!(result, vec![0.0]); // Can't go below 0
402    }
403
404    #[test]
405    fn test_distribute_flex_mixed_grow() {
406        let items = vec![
407            FlexItem::new().grow(0.0), // Won't grow
408            FlexItem::new().grow(1.0), // Will take all remaining
409        ];
410        let sizes = vec![50.0, 0.0];
411        let result = distribute_flex(&items, &sizes, 100.0);
412        assert_eq!(result, vec![50.0, 50.0]);
413    }
414
415    #[test]
416    fn test_distribute_flex_three_items() {
417        let items = vec![
418            FlexItem::new().grow(1.0),
419            FlexItem::new().grow(2.0),
420            FlexItem::new().grow(1.0),
421        ];
422        let sizes = vec![0.0, 0.0, 0.0];
423        let result = distribute_flex(&items, &sizes, 100.0);
424        assert_eq!(result, vec![25.0, 50.0, 25.0]);
425    }
426
427    #[test]
428    fn test_distribute_flex_near_exact_fit() {
429        let items = vec![FlexItem::new().grow(1.0), FlexItem::new().grow(1.0)];
430        let sizes = vec![49.9995, 50.0005];
431        let result = distribute_flex(&items, &sizes, 100.0);
432        // Should be treated as exact fit (within 0.001 tolerance)
433        assert_eq!(result, vec![49.9995, 50.0005]);
434    }
435}