cranpose_ui_layout/
arrangement.rs1use crate::{alignment::bias_offset, round_to_px};
4
5pub trait Arrangement {
7 fn arrange(&self, density: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]);
11}
12
13#[derive(Clone, Copy, Debug, PartialEq)]
15pub enum LinearArrangement {
16 Start,
18 End,
20 Center,
22 SpaceBetween,
24 SpaceAround,
26 SpaceEvenly,
28 SpacedBy(f32),
30 SpacedByAligned { spacing: f32, bias: f32 },
34}
35
36pub trait AxisAlignment {
39 fn bias(&self) -> f32;
41}
42
43impl AxisAlignment for crate::HorizontalAlignment {
44 fn bias(&self) -> f32 {
45 crate::HorizontalAlignment::bias(self)
46 }
47}
48
49impl AxisAlignment for crate::VerticalAlignment {
50 fn bias(&self) -> f32 {
51 crate::VerticalAlignment::bias(self)
52 }
53}
54
55impl LinearArrangement {
56 pub fn spaced_by(spacing: f32) -> Self {
58 Self::SpacedBy(spacing)
59 }
60
61 pub fn spaced_by_aligned(spacing: f32, alignment: impl AxisAlignment) -> Self {
65 Self::SpacedByAligned {
66 spacing,
67 bias: alignment.bias(),
68 }
69 }
70
71 pub fn is_spaced(&self) -> bool {
74 matches!(self, Self::SpacedBy(_) | Self::SpacedByAligned { .. })
75 }
76
77 pub fn spacing(&self, density: f32) -> f32 {
81 match *self {
82 Self::SpacedBy(spacing) | Self::SpacedByAligned { spacing, .. } => {
83 round_to_px(spacing.max(0.0), density)
84 }
85 _ => 0.0,
86 }
87 }
88
89 fn fill_positions(
93 density: f32,
94 start: f32,
95 gap: f32,
96 sizes: &[f32],
97 out_positions: &mut [f32],
98 ) {
99 debug_assert_eq!(sizes.len(), out_positions.len());
100 let mut cursor = start;
101 for (size, position) in sizes.iter().zip(out_positions.iter_mut()) {
102 *position = round_to_px(cursor, density);
103 cursor += size + gap;
104 }
105 }
106
107 fn spaced_positions(spacing: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
111 let mut occupied = 0.0_f32;
112 for (&size, position) in sizes.iter().zip(out_positions.iter_mut()) {
113 *position = occupied.min(total_size - size);
114 let space_after = spacing.min(total_size - *position - size);
115 occupied = *position + size + space_after;
116 }
117 }
118}
119
120impl Arrangement for LinearArrangement {
121 fn arrange(&self, density: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
122 debug_assert_eq!(sizes.len(), out_positions.len());
123 if sizes.is_empty() {
124 return;
125 }
126
127 let remaining = total_size - sizes.iter().sum::<f32>();
128 let count = sizes.len() as f32;
129
130 match *self {
131 LinearArrangement::Start => {
132 Self::fill_positions(density, 0.0, 0.0, sizes, out_positions);
133 }
134 LinearArrangement::End => {
135 Self::fill_positions(density, remaining, 0.0, sizes, out_positions);
136 }
137 LinearArrangement::Center => {
138 Self::fill_positions(density, remaining / 2.0, 0.0, sizes, out_positions);
139 }
140 LinearArrangement::SpaceBetween => {
141 let gap = remaining / (count - 1.0).max(1.0);
142 Self::fill_positions(density, 0.0, gap, sizes, out_positions);
143 }
144 LinearArrangement::SpaceAround => {
145 let gap = remaining / count;
146 Self::fill_positions(density, gap / 2.0, gap, sizes, out_positions);
147 }
148 LinearArrangement::SpaceEvenly => {
149 let gap = remaining / (count + 1.0);
150 Self::fill_positions(density, gap, gap, sizes, out_positions);
151 }
152 LinearArrangement::SpacedBy(_) => {
153 Self::spaced_positions(self.spacing(density), total_size, sizes, out_positions);
154 }
155 LinearArrangement::SpacedByAligned { bias, .. } => {
156 Self::spaced_positions(self.spacing(density), total_size, sizes, out_positions);
157 let (Some(&last), Some(&last_size)) = (out_positions.last(), sizes.last()) else {
158 return;
159 };
160 let occupied = last + last_size;
161 if occupied < total_size {
162 let shift = bias_offset(bias, total_size, occupied, density);
163 for position in out_positions.iter_mut() {
164 *position += shift;
165 }
166 }
167 }
168 }
169 }
170}
171
172#[cfg(test)]
173#[path = "tests/arrangement_tests.rs"]
174mod tests;