Skip to main content

cranpose_ui_layout/
arrangement.rs

1//! Arrangement strategies for distributing children along an axis
2
3/// Trait implemented by arrangement strategies that distribute children on an axis.
4pub trait Arrangement {
5    /// Computes the position for each child given the available space and their sizes.
6    fn arrange(&self, total_size: f32, sizes: &[f32], out_positions: &mut [f32]);
7}
8
9/// Arrangement strategy matching Jetpack Compose's linear arrangements.
10#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum LinearArrangement {
12    /// Place children consecutively starting from the leading edge.
13    Start,
14    /// Place children so the last child touches the trailing edge.
15    End,
16    /// Place children so they are centered as a block.
17    Center,
18    /// Distribute the remaining space evenly between children.
19    SpaceBetween,
20    /// Distribute the remaining space before, after, and between children.
21    SpaceAround,
22    /// Distribute the remaining space before the first child, between children, and after the last child.
23    SpaceEvenly,
24    /// Insert a fixed amount of space between children.
25    SpacedBy(f32),
26}
27
28impl LinearArrangement {
29    /// Creates an arrangement that inserts a fixed spacing between children.
30    pub fn spaced_by(spacing: f32) -> Self {
31        Self::SpacedBy(spacing)
32    }
33
34    fn total_children_size(sizes: &[f32]) -> f32 {
35        sizes.iter().copied().sum()
36    }
37
38    fn fill_positions(start: f32, gap: f32, sizes: &[f32], out_positions: &mut [f32]) {
39        debug_assert_eq!(sizes.len(), out_positions.len());
40        let mut cursor = start;
41        for (index, (size, position)) in sizes.iter().zip(out_positions.iter_mut()).enumerate() {
42            *position = cursor;
43            cursor += size;
44            if index + 1 < sizes.len() {
45                cursor += gap;
46            }
47        }
48    }
49}
50
51impl Arrangement for LinearArrangement {
52    fn arrange(&self, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
53        debug_assert_eq!(sizes.len(), out_positions.len());
54        if sizes.is_empty() {
55            return;
56        }
57
58        let children_total = Self::total_children_size(sizes);
59        let remaining = total_size - children_total;
60
61        match *self {
62            LinearArrangement::Start => Self::fill_positions(0.0, 0.0, sizes, out_positions),
63            LinearArrangement::End => {
64                let start = remaining;
65                Self::fill_positions(start, 0.0, sizes, out_positions);
66            }
67            LinearArrangement::Center => {
68                let start = remaining / 2.0;
69                Self::fill_positions(start, 0.0, sizes, out_positions);
70            }
71            LinearArrangement::SpaceBetween => {
72                let gap = if sizes.len() <= 1 {
73                    0.0
74                } else {
75                    remaining / (sizes.len() as f32 - 1.0)
76                };
77                Self::fill_positions(0.0, gap, sizes, out_positions);
78            }
79            LinearArrangement::SpaceAround => {
80                let gap = remaining / sizes.len() as f32;
81                let start = gap / 2.0;
82                Self::fill_positions(start, gap, sizes, out_positions);
83            }
84            LinearArrangement::SpaceEvenly => {
85                let gap = remaining / (sizes.len() as f32 + 1.0);
86                let start = gap;
87                Self::fill_positions(start, gap, sizes, out_positions);
88            }
89            LinearArrangement::SpacedBy(spacing) => {
90                Self::fill_positions(0.0, spacing, sizes, out_positions);
91            }
92        }
93    }
94}
95
96#[cfg(test)]
97#[path = "tests/arrangement_tests.rs"]
98mod tests;