1#[cfg(any(feature = "std", feature = "alloc"))]
4use alloc::boxed::Box;
5use core::fmt;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum GridOrigin {
11 TopLeft,
13 TopRight,
15 BottomLeft,
17 BottomRight,
19 Center,
21 Top,
23 Bottom,
25 Left,
27 Right,
29}
30
31pub enum StaggerPattern {
33 Grid {
35 cols: usize,
37 rows: usize,
39 origin: GridOrigin,
41 step: f32,
43 },
44 Random {
46 seed: u32,
48 min_delay: f32,
50 max_delay: f32,
52 },
53 CenterOut {
55 count: usize,
57 step: f32,
59 },
60 EdgesIn {
62 count: usize,
64 step: f32,
66 },
67 #[cfg(any(feature = "std", feature = "alloc"))]
69 Custom(
70 Box<dyn Fn(usize, usize) -> f32 + Send + Sync + 'static>,
72 ),
73}
74
75impl fmt::Debug for StaggerPattern {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 Self::Grid {
79 cols,
80 rows,
81 origin,
82 step,
83 } => f
84 .debug_struct("Grid")
85 .field("cols", cols)
86 .field("rows", rows)
87 .field("origin", origin)
88 .field("step", step)
89 .finish(),
90 Self::Random {
91 seed,
92 min_delay,
93 max_delay,
94 } => f
95 .debug_struct("Random")
96 .field("seed", seed)
97 .field("min_delay", min_delay)
98 .field("max_delay", max_delay)
99 .finish(),
100 Self::CenterOut { count, step } => f
101 .debug_struct("CenterOut")
102 .field("count", count)
103 .field("step", step)
104 .finish(),
105 Self::EdgesIn { count, step } => f
106 .debug_struct("EdgesIn")
107 .field("count", count)
108 .field("step", step)
109 .finish(),
110 #[cfg(any(feature = "std", feature = "alloc"))]
111 Self::Custom(_) => f.debug_tuple("Custom").field(&"<function>").finish(),
112 }
113 }
114}
115
116impl StaggerPattern {
117 pub fn delay(&self, index: usize, _total: usize) -> f32 {
119 match self {
120 Self::Grid {
121 cols,
122 rows,
123 origin,
124 step,
125 } => grid_delay(*cols, *rows, *origin, *step, index),
126 Self::Random {
127 seed,
128 min_delay,
129 max_delay,
130 } => random_delay(*seed, *min_delay, *max_delay, index),
131 Self::CenterOut { count, step } => {
132 center_distance(index.min(count.saturating_sub(1)), *count) as f32
133 * finite_or(*step, 0.0).max(0.0)
134 }
135 Self::EdgesIn { count, step } => {
136 let count = *count;
137 if count == 0 {
138 return 0.0;
139 }
140 let index = index.min(count - 1);
141 index.min(count - 1 - index) as f32 * finite_or(*step, 0.0).max(0.0)
142 }
143 #[cfg(any(feature = "std", feature = "alloc"))]
144 Self::Custom(callback) => finite_or(callback(index, _total), 0.0).max(0.0),
145 }
146 }
147}
148
149fn grid_delay(cols: usize, rows: usize, origin: GridOrigin, step: f32, index: usize) -> f32 {
150 let cols = cols.max(1);
151 let rows = rows.max(1);
152 let index = index.min(cols.saturating_mul(rows).saturating_sub(1));
153 let x = index % cols;
154 let y = index / cols;
155 let distance = match origin {
156 GridOrigin::TopLeft => x + y,
157 GridOrigin::TopRight => (cols - 1 - x) + y,
158 GridOrigin::BottomLeft => x + (rows - 1 - y),
159 GridOrigin::BottomRight => (cols - 1 - x) + (rows - 1 - y),
160 GridOrigin::Center => {
161 let left = x.abs_diff((cols - 1) / 2).min(x.abs_diff(cols / 2));
162 let top = y.abs_diff((rows - 1) / 2).min(y.abs_diff(rows / 2));
163 left + top
164 }
165 GridOrigin::Top => y,
166 GridOrigin::Bottom => rows - 1 - y,
167 GridOrigin::Left => x,
168 GridOrigin::Right => cols - 1 - x,
169 };
170 distance as f32 * finite_or(step, 0.0).max(0.0)
171}
172
173fn center_distance(index: usize, count: usize) -> usize {
174 if count <= 1 {
175 return 0;
176 }
177 index
178 .abs_diff((count - 1) / 2)
179 .min(index.abs_diff(count / 2))
180}
181
182fn random_delay(seed: u32, min_delay: f32, max_delay: f32, index: usize) -> f32 {
183 let min = finite_or(min_delay, 0.0).max(0.0);
184 let max = finite_or(max_delay, min).max(min);
185 let mut x = seed ^ (index as u32).wrapping_mul(0x9E37_79B9);
186 x ^= x >> 16;
187 x = x.wrapping_mul(0x7FEB_352D);
188 x ^= x >> 15;
189 x = x.wrapping_mul(0x846C_A68B);
190 x ^= x >> 16;
191 min + (max - min) * (x as f32 / u32::MAX as f32)
192}
193
194fn finite_or(value: f32, fallback: f32) -> f32 {
195 if value.is_finite() { value } else { fallback }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn grid_center_is_symmetric() {
204 let pattern = StaggerPattern::Grid {
205 cols: 3,
206 rows: 3,
207 origin: GridOrigin::Center,
208 step: 0.1,
209 };
210 assert_eq!(pattern.delay(4, 9), 0.0);
211 assert_eq!(pattern.delay(0, 9), pattern.delay(8, 9));
212 assert_eq!(pattern.delay(2, 9), pattern.delay(6, 9));
213 }
214
215 #[test]
216 fn random_is_deterministic_and_bounded() {
217 let pattern = StaggerPattern::Random {
218 seed: 9,
219 min_delay: 0.2,
220 max_delay: 0.5,
221 };
222 let delay = pattern.delay(3, 10);
223 assert_eq!(delay, pattern.delay(3, 10));
224 assert!((0.2..=0.5).contains(&delay));
225 }
226
227 #[test]
228 fn center_out_and_edges_in_order() {
229 let center = StaggerPattern::CenterOut {
230 count: 5,
231 step: 0.1,
232 };
233 assert_eq!(center.delay(2, 5), 0.0);
234 assert_eq!(center.delay(0, 5), 0.2);
235
236 let edges = StaggerPattern::EdgesIn {
237 count: 5,
238 step: 0.1,
239 };
240 assert_eq!(edges.delay(0, 5), 0.0);
241 assert_eq!(edges.delay(4, 5), 0.0);
242 assert_eq!(edges.delay(2, 5), 0.2);
243 }
244}