1use super::Easing;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum StaggerFrom {
23 #[default]
24 First,
25 Last,
26 Center,
27 Index(usize),
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum StaggerAxis {
34 X,
35 Y,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct Stagger {
41 pub step: f32,
43 pub start: f32,
45 pub from: StaggerFrom,
46 pub grid: Option<(usize, usize)>,
48 pub axis: Option<StaggerAxis>,
50 pub ease: Option<Easing>,
53 pub reversed: bool,
54}
55
56impl Stagger {
57 pub fn new(step: f32) -> Self {
59 Stagger {
60 step,
61 start: 0.0,
62 from: StaggerFrom::First,
63 grid: None,
64 axis: None,
65 ease: None,
66 reversed: false,
67 }
68 }
69
70 pub fn start(mut self, ms: f32) -> Self {
71 self.start = ms;
72 self
73 }
74
75 pub fn from(mut self, from: StaggerFrom) -> Self {
76 self.from = from;
77 self
78 }
79
80 pub fn grid(mut self, columns: usize, rows: usize) -> Self {
82 self.grid = Some((columns.max(1), rows.max(1)));
83 self
84 }
85
86 pub fn axis(mut self, axis: StaggerAxis) -> Self {
87 self.axis = Some(axis);
88 self
89 }
90
91 pub fn ease(mut self, easing: Easing) -> Self {
92 self.ease = Some(easing);
93 self
94 }
95
96 pub fn reversed(mut self, reversed: bool) -> Self {
97 self.reversed = reversed;
98 self
99 }
100
101 pub fn at(&self, index: usize, total: usize) -> f32 {
103 self.start + self.weight(index, total) * self.furthest(total) * self.step
104 }
105
106 pub fn value(&self, index: usize, total: usize, from: f32, to: f32) -> f32 {
110 from + (to - from) * self.weight(index, total)
111 }
112
113 pub fn span(&self, total: usize) -> f32 {
115 self.start + self.furthest(total) * self.step
116 }
117
118 fn weight(&self, index: usize, total: usize) -> f32 {
120 let furthest = self.furthest(total);
121 if furthest <= 0.0 {
122 return 0.0;
123 }
124 let mut t = (self.distance(index, total) / furthest).clamp(0.0, 1.0);
125 if self.reversed {
126 t = 1.0 - t;
127 }
128 match self.ease {
129 Some(easing) => easing.apply(t),
130 None => t,
131 }
132 }
133
134 fn distance(&self, index: usize, total: usize) -> f32 {
136 match self.grid {
137 Some((columns, _)) => {
138 let (x, y) = ((index % columns) as f32, (index / columns) as f32);
139 let (ox, oy) = self.grid_origin(total);
140 match self.axis {
141 Some(StaggerAxis::X) => (x - ox).abs(),
142 Some(StaggerAxis::Y) => (y - oy).abs(),
143 None => ((x - ox).powi(2) + (y - oy).powi(2)).sqrt(),
144 }
145 }
146 None => {
147 let last = total.saturating_sub(1) as f32;
148 let origin = match self.from {
149 StaggerFrom::First => 0.0,
150 StaggerFrom::Last => last,
151 StaggerFrom::Center => last / 2.0,
152 StaggerFrom::Index(i) => i as f32,
153 };
154 (index as f32 - origin).abs()
155 }
156 }
157 }
158
159 fn grid_origin(&self, total: usize) -> (f32, f32) {
160 let (columns, rows) = self.grid.unwrap_or((1, 1));
161 let rows = rows.max(total.div_ceil(columns));
162 let (last_x, last_y) = ((columns - 1) as f32, (rows.saturating_sub(1)) as f32);
163 match self.from {
164 StaggerFrom::First => (0.0, 0.0),
165 StaggerFrom::Last => (last_x, last_y),
166 StaggerFrom::Center => (last_x / 2.0, last_y / 2.0),
167 StaggerFrom::Index(i) => ((i % columns) as f32, (i / columns) as f32),
168 }
169 }
170
171 fn furthest(&self, total: usize) -> f32 {
174 (0..total)
175 .map(|i| self.distance(i, total))
176 .fold(0.0_f32, f32::max)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::anim::ease::Curve;
184
185 #[test]
186 fn a_plain_stagger_steps_one_by_one() {
187 let stagger = Stagger::new(50.0);
188 assert_eq!(stagger.at(0, 4), 0.0);
189 assert_eq!(stagger.at(1, 4), 50.0);
190 assert_eq!(stagger.at(3, 4), 150.0);
191 assert_eq!(stagger.span(4), 150.0);
192 }
193
194 #[test]
195 fn start_shifts_everyone() {
196 let stagger = Stagger::new(50.0).start(100.0);
197 assert_eq!(stagger.at(0, 4), 100.0);
198 assert_eq!(stagger.at(2, 4), 200.0);
199 }
200
201 #[test]
202 fn from_last_reverses_the_order() {
203 let stagger = Stagger::new(50.0).from(StaggerFrom::Last);
204 assert_eq!(stagger.at(3, 4), 0.0);
205 assert_eq!(stagger.at(0, 4), 150.0);
206 }
207
208 #[test]
209 fn from_center_ripples_outward() {
210 let stagger = Stagger::new(50.0).from(StaggerFrom::Center);
211 assert_eq!(stagger.at(2, 5), 0.0);
213 assert_eq!(stagger.at(0, 5), stagger.at(4, 5));
214 assert!(stagger.at(1, 5) < stagger.at(0, 5));
215 }
216
217 #[test]
218 fn a_named_index_leads() {
219 let stagger = Stagger::new(10.0).from(StaggerFrom::Index(2));
220 assert_eq!(stagger.at(2, 5), 0.0);
221 assert_eq!(stagger.at(4, 5), 20.0);
222 }
223
224 #[test]
225 fn a_grid_measures_in_two_dimensions() {
226 let stagger = Stagger::new(100.0).grid(3, 2);
227 assert_eq!(stagger.at(0, 6), 0.0);
229 let far = stagger.at(5, 6);
230 assert!((far - 100.0 * 5.0_f32.sqrt()).abs() < 1e-3, "{far}");
231 assert!((stagger.at(3, 6) - 100.0).abs() < 1e-3);
233 }
234
235 #[test]
236 fn an_axis_flattens_the_grid_to_one_direction() {
237 let stagger = Stagger::new(100.0).grid(3, 2).axis(StaggerAxis::Y);
238 assert_eq!(stagger.at(0, 6), stagger.at(2, 6), "same row, same delay");
239 assert!(stagger.at(3, 6) > stagger.at(0, 6));
240 }
241
242 #[test]
243 fn reversed_flips_the_weights() {
244 let plain = Stagger::new(50.0);
245 let flipped = Stagger::new(50.0).reversed(true);
246 assert_eq!(flipped.at(0, 4), plain.at(3, 4));
247 assert_eq!(flipped.at(3, 4), plain.at(0, 4));
248 }
249
250 #[test]
251 fn easing_reshapes_the_spacing_without_moving_the_ends() {
252 let eased = Stagger::new(50.0).ease(Easing::In(Curve::Quad));
253 assert_eq!(eased.at(0, 5), 0.0);
254 assert_eq!(eased.at(4, 5), 200.0);
255 assert!(eased.at(1, 5) < 50.0);
257 }
258
259 #[test]
260 fn values_spread_across_a_range() {
261 let stagger = Stagger::new(0.0);
262 assert_eq!(stagger.value(0, 5, -100.0, 100.0), -100.0);
263 assert_eq!(stagger.value(4, 5, -100.0, 100.0), 100.0);
264 assert_eq!(stagger.value(2, 5, -100.0, 100.0), 0.0);
265 }
266
267 #[test]
268 fn a_single_element_never_waits() {
269 let stagger = Stagger::new(50.0).from(StaggerFrom::Center);
270 assert_eq!(stagger.at(0, 1), 0.0);
271 assert_eq!(stagger.span(1), 0.0);
272 assert_eq!(stagger.at(0, 0), 0.0);
273 }
274}