Skip to main content

cranpose_ui_layout/
arrangement.rs

1//! Arrangement strategies for distributing children along an axis
2
3use crate::round_to_px;
4
5/// Trait implemented by arrangement strategies that distribute children on an axis.
6pub trait Arrangement {
7    /// Computes the position for each child given the available space and
8    /// their sizes, on the device pixel grid of `density`, where Compose
9    /// places children.
10    fn arrange(&self, density: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]);
11}
12
13/// Arrangement strategy matching Jetpack Compose's linear arrangements.
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum LinearArrangement {
16    /// Place children consecutively starting from the leading edge.
17    Start,
18    /// Place children so the last child touches the trailing edge.
19    End,
20    /// Place children so they are centered as a block.
21    Center,
22    /// Distribute the remaining space evenly between children.
23    SpaceBetween,
24    /// Distribute the remaining space before, after, and between children.
25    SpaceAround,
26    /// Distribute the remaining space before the first child, between children, and after the last child.
27    SpaceEvenly,
28    /// Insert a fixed amount of space between children.
29    SpacedBy(f32),
30}
31
32impl LinearArrangement {
33    /// Creates an arrangement that inserts a fixed spacing between children.
34    pub fn spaced_by(spacing: f32) -> Self {
35        Self::SpacedBy(spacing)
36    }
37
38    /// The space `SpacedBy` puts between children, on the device pixel grid
39    /// of `density` as Compose's `roundToPx` puts it; none for the others.
40    pub fn spacing(&self, density: f32) -> f32 {
41        match *self {
42            Self::SpacedBy(spacing) => round_to_px(spacing.max(0.0), density),
43            _ => 0.0,
44        }
45    }
46
47    /// Compose's `placeLeftOrTop` and its `placeCenter`, `placeSpace*` and
48    /// `placeRightOrBottom` kin: each child `gap` after the one before, the
49    /// first at `start`, every position rounded to a device pixel.
50    fn fill_positions(
51        density: f32,
52        start: f32,
53        gap: f32,
54        sizes: &[f32],
55        out_positions: &mut [f32],
56    ) {
57        debug_assert_eq!(sizes.len(), out_positions.len());
58        let mut cursor = start;
59        for (size, position) in sizes.iter().zip(out_positions.iter_mut()) {
60            *position = round_to_px(cursor, density);
61            cursor += size + gap;
62        }
63    }
64
65    /// Compose's `SpacedAligned` without an alignment: each child after the
66    /// one before and `spacing` more, but never past the end of
67    /// `total_size`, and the spacing after it only as wide as what is left.
68    fn spaced_positions(spacing: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
69        let mut occupied = 0.0_f32;
70        for (&size, position) in sizes.iter().zip(out_positions.iter_mut()) {
71            *position = occupied.min(total_size - size);
72            let space_after = spacing.min(total_size - *position - size);
73            occupied = *position + size + space_after;
74        }
75    }
76}
77
78impl Arrangement for LinearArrangement {
79    fn arrange(&self, density: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
80        debug_assert_eq!(sizes.len(), out_positions.len());
81        if sizes.is_empty() {
82            return;
83        }
84
85        let remaining = total_size - sizes.iter().sum::<f32>();
86        let count = sizes.len() as f32;
87
88        match *self {
89            LinearArrangement::Start => {
90                Self::fill_positions(density, 0.0, 0.0, sizes, out_positions);
91            }
92            LinearArrangement::End => {
93                Self::fill_positions(density, remaining, 0.0, sizes, out_positions);
94            }
95            LinearArrangement::Center => {
96                Self::fill_positions(density, remaining / 2.0, 0.0, sizes, out_positions);
97            }
98            LinearArrangement::SpaceBetween => {
99                let gap = remaining / (count - 1.0).max(1.0);
100                Self::fill_positions(density, 0.0, gap, sizes, out_positions);
101            }
102            LinearArrangement::SpaceAround => {
103                let gap = remaining / count;
104                Self::fill_positions(density, gap / 2.0, gap, sizes, out_positions);
105            }
106            LinearArrangement::SpaceEvenly => {
107                let gap = remaining / (count + 1.0);
108                Self::fill_positions(density, gap, gap, sizes, out_positions);
109            }
110            LinearArrangement::SpacedBy(_) => {
111                Self::spaced_positions(self.spacing(density), total_size, sizes, out_positions);
112            }
113        }
114    }
115}
116
117#[cfg(test)]
118#[path = "tests/arrangement_tests.rs"]
119mod tests;