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 {
34 spacing: f32,
36 bias: f32,
38 },
39}
40
41pub trait AxisAlignment {
44 fn bias(&self) -> f32;
46}
47
48impl AxisAlignment for crate::HorizontalAlignment {
49 fn bias(&self) -> f32 {
50 crate::HorizontalAlignment::bias(self)
51 }
52}
53
54impl AxisAlignment for crate::VerticalAlignment {
55 fn bias(&self) -> f32 {
56 crate::VerticalAlignment::bias(self)
57 }
58}
59
60impl LinearArrangement {
61 pub fn spaced_by(spacing: f32) -> Self {
63 Self::SpacedBy(spacing)
64 }
65
66 pub fn spaced_by_aligned(spacing: f32, alignment: impl AxisAlignment) -> Self {
70 Self::SpacedByAligned {
71 spacing,
72 bias: alignment.bias(),
73 }
74 }
75
76 pub fn is_spaced(&self) -> bool {
79 matches!(self, Self::SpacedBy(_) | Self::SpacedByAligned { .. })
80 }
81
82 pub fn spacing(&self, density: f32) -> f32 {
86 match *self {
87 Self::SpacedBy(spacing) | Self::SpacedByAligned { spacing, .. } => {
88 round_to_px(spacing.max(0.0), density)
89 }
90 _ => 0.0,
91 }
92 }
93
94 fn fill_positions(
98 density: f32,
99 start: f32,
100 gap: f32,
101 sizes: &[f32],
102 out_positions: &mut [f32],
103 ) {
104 debug_assert_eq!(sizes.len(), out_positions.len());
105 let mut cursor = start;
106 for (size, position) in sizes.iter().zip(out_positions.iter_mut()) {
107 *position = round_to_px(cursor, density);
108 cursor += size + gap;
109 }
110 }
111
112 fn spaced_positions(spacing: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
116 let mut occupied = 0.0_f32;
117 for (&size, position) in sizes.iter().zip(out_positions.iter_mut()) {
118 *position = occupied.min(total_size - size);
119 let space_after = spacing.min(total_size - *position - size);
120 occupied = *position + size + space_after;
121 }
122 }
123}
124
125impl Arrangement for LinearArrangement {
126 fn arrange(&self, density: f32, total_size: f32, sizes: &[f32], out_positions: &mut [f32]) {
127 debug_assert_eq!(sizes.len(), out_positions.len());
128 if sizes.is_empty() {
129 return;
130 }
131
132 let remaining = total_size - sizes.iter().sum::<f32>();
133 let count = sizes.len() as f32;
134
135 match *self {
136 LinearArrangement::Start => {
137 Self::fill_positions(density, 0.0, 0.0, sizes, out_positions);
138 }
139 LinearArrangement::End => {
140 Self::fill_positions(density, remaining, 0.0, sizes, out_positions);
141 }
142 LinearArrangement::Center => {
143 Self::fill_positions(density, remaining / 2.0, 0.0, sizes, out_positions);
144 }
145 LinearArrangement::SpaceBetween => {
146 let gap = remaining / (count - 1.0).max(1.0);
147 Self::fill_positions(density, 0.0, gap, sizes, out_positions);
148 }
149 LinearArrangement::SpaceAround => {
150 let gap = remaining / count;
151 Self::fill_positions(density, gap / 2.0, gap, sizes, out_positions);
152 }
153 LinearArrangement::SpaceEvenly => {
154 let gap = remaining / (count + 1.0);
155 Self::fill_positions(density, gap, gap, sizes, out_positions);
156 }
157 LinearArrangement::SpacedBy(_) => {
158 Self::spaced_positions(self.spacing(density), total_size, sizes, out_positions);
159 }
160 LinearArrangement::SpacedByAligned { bias, .. } => {
161 Self::spaced_positions(self.spacing(density), total_size, sizes, out_positions);
162 let (Some(&last), Some(&last_size)) = (out_positions.last(), sizes.last()) else {
163 return;
164 };
165 let occupied = last + last_size;
166 if occupied < total_size {
167 let shift = bias_offset(bias, total_size, occupied, density);
168 for position in out_positions.iter_mut() {
169 *position += shift;
170 }
171 }
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178#[path = "tests/arrangement_tests.rs"]
179mod tests;