1use std::fmt::Debug;
10
11use crate::clock::Clock;
12use animato_core::Update;
13
14#[derive(Default)]
36pub struct ScrollDriver {
37 min: f32,
38 max: f32,
39 position: f32,
40 animations: std::vec::Vec<Box<dyn Update + Send>>,
41}
42
43fn normalized_max(min: f32, max: f32) -> f32 {
44 if max > min {
45 max
46 } else {
47 let increment = f32::EPSILON * min.abs().max(1.0);
48 let adjusted = min + increment;
49 if adjusted > min { adjusted } else { min + 1.0 }
50 }
51}
52
53impl Debug for ScrollDriver {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("ScrollDriver")
56 .field("min", &self.min)
57 .field("max", &self.max)
58 .field("position", &self.position)
59 .field("animations", &self.animations.len())
60 .finish()
61 }
62}
63
64impl ScrollDriver {
65 pub fn new(min: f32, max: f32) -> Self {
70 Self {
71 min,
72 max: normalized_max(min, max),
73 position: min,
74 animations: std::vec::Vec::new(),
75 }
76 }
77
78 pub fn add<A: Update + Send + 'static>(&mut self, animation: A) {
80 self.animations.push(Box::new(animation));
81 }
82
83 pub fn set_position(&mut self, pos: f32) {
92 let clamped = pos.clamp(self.min, self.max);
93 let range = self.max - self.min;
94 if range <= 0.0 {
95 return;
96 }
97
98 let delta = (clamped - self.position).abs() / range;
99 self.position = clamped;
100
101 if delta > 0.0 {
102 for animation in self.animations.iter_mut() {
103 animation.update(delta);
104 }
105 }
106 }
107
108 pub fn clear_completed(&mut self) {
113 self.animations.retain_mut(|a| a.update(0.0));
115 }
116
117 pub fn position(&self) -> f32 {
119 self.position
120 }
121
122 pub fn progress(&self) -> f32 {
124 let range = self.max - self.min;
125 if range <= 0.0 {
126 return 0.0;
127 }
128 ((self.position - self.min) / range).clamp(0.0, 1.0)
129 }
130
131 pub fn min(&self) -> f32 {
133 self.min
134 }
135
136 pub fn max(&self) -> f32 {
138 self.max
139 }
140
141 pub fn animation_count(&self) -> usize {
143 self.animations.len()
144 }
145}
146
147#[derive(Clone, Debug)]
166pub struct ScrollClock {
167 last: f32,
168 pending: f32,
169 min: f32,
170 max: f32,
171}
172
173impl Default for ScrollClock {
174 fn default() -> Self {
175 Self::new(0.0, 1000.0)
176 }
177}
178
179impl ScrollClock {
180 pub fn new(min: f32, max: f32) -> Self {
182 Self {
183 last: min,
184 pending: 0.0,
185 min,
186 max: normalized_max(min, max),
187 }
188 }
189
190 pub fn set_scroll(&mut self, pos: f32) {
195 let clamped = pos.clamp(self.min, self.max);
196 let range = self.max - self.min;
197 if range > 0.0 {
198 self.pending += (clamped - self.last).abs() / range;
199 }
200 self.last = clamped;
201 }
202
203 pub fn scroll_position(&self) -> f32 {
205 self.last
206 }
207
208 pub fn progress(&self) -> f32 {
210 let range = self.max - self.min;
211 if range <= 0.0 {
212 return 0.0;
213 }
214 ((self.last - self.min) / range).clamp(0.0, 1.0)
215 }
216}
217
218impl Clock for ScrollClock {
219 fn delta(&mut self) -> f32 {
221 let dt = self.pending;
222 self.pending = 0.0;
223 dt
224 }
225}
226
227#[cfg(test)]
230mod tests {
231 use super::*;
232 use animato_core::{Easing, Update};
233 use animato_tween::Tween;
234
235 #[test]
236 fn scroll_driver_progress_tracks_position() {
237 let mut driver = ScrollDriver::new(0.0, 100.0);
238 assert_eq!(driver.progress(), 0.0);
239 driver.set_position(50.0);
240 assert!((driver.progress() - 0.5).abs() < 0.001);
241 driver.set_position(100.0);
242 assert!((driver.progress() - 1.0).abs() < 0.001);
243 }
244
245 #[test]
246 fn scroll_driver_clamps_position() {
247 let mut driver = ScrollDriver::new(0.0, 100.0);
248 driver.set_position(-50.0);
249 assert_eq!(driver.position(), 0.0);
250 driver.set_position(200.0);
251 assert_eq!(driver.position(), 100.0);
252 }
253
254 #[test]
255 fn scroll_driver_ticks_animations_proportionally() {
256 let mut driver = ScrollDriver::new(0.0, 1000.0);
257 driver.add(
259 Tween::new(0.0_f32, 100.0)
260 .duration(1.0)
261 .easing(Easing::Linear)
262 .build(),
263 );
264 driver.set_position(500.0);
266 assert_eq!(driver.animation_count(), 1);
267 }
268
269 #[test]
270 fn scroll_driver_zero_delta_does_not_tick() {
271 struct _PanicOnUpdate;
272 impl Update for _PanicOnUpdate {
273 fn update(&mut self, _dt: f32) -> bool {
274 panic!("should not be called")
275 }
276 }
277 let mut driver = ScrollDriver::new(0.0, 100.0);
278 driver.set_position(0.0); }
282
283 #[test]
284 fn scroll_clock_delta_is_normalised() {
285 let mut clock = ScrollClock::new(0.0, 1000.0);
286 clock.set_scroll(250.0);
287 let dt = clock.delta();
288 assert!((dt - 0.25).abs() < 0.001);
289 }
290
291 #[test]
292 fn scroll_clock_delta_consumed_after_read() {
293 let mut clock = ScrollClock::new(0.0, 100.0);
294 clock.set_scroll(30.0);
295 let _ = clock.delta();
296 assert_eq!(clock.delta(), 0.0);
297 }
298
299 #[test]
300 fn scroll_clock_accumulates_multiple_moves() {
301 let mut clock = ScrollClock::new(0.0, 100.0);
302 clock.set_scroll(10.0); clock.set_scroll(20.0); clock.set_scroll(30.0); let dt = clock.delta();
306 assert!((dt - 0.3).abs() < 0.001);
307 }
308
309 #[test]
310 fn scroll_clock_progress() {
311 let mut clock = ScrollClock::new(0.0, 200.0);
312 clock.set_scroll(100.0);
313 let _ = clock.delta();
314 assert!((clock.progress() - 0.5).abs() < 0.001);
315 }
316
317 #[test]
318 fn scroll_driver_debug_default_and_degenerate_range() {
319 let mut driver = ScrollDriver::new(10.0, 5.0);
320
321 assert_eq!(driver.min(), 10.0);
322 assert!(driver.max() > driver.min());
323 assert_eq!(driver.position(), 10.0);
324 assert_eq!(driver.progress(), 0.0);
325 assert!(format!("{driver:?}").contains("ScrollDriver"));
326
327 driver.set_position(20.0);
328 assert_eq!(driver.position(), driver.max());
329 assert_eq!(driver.progress(), 1.0);
330
331 let default_driver = ScrollDriver::default();
332 assert_eq!(default_driver.animation_count(), 0);
333 }
334
335 #[test]
336 fn clear_completed_removes_finished_animations() {
337 struct Done;
338 impl Update for Done {
339 fn update(&mut self, _dt: f32) -> bool {
340 false
341 }
342 }
343
344 let mut driver = ScrollDriver::new(0.0, 100.0);
345 driver.add(Done);
346
347 assert_eq!(driver.animation_count(), 1);
348 driver.clear_completed();
349 assert_eq!(driver.animation_count(), 0);
350 }
351
352 #[test]
353 fn scroll_clock_default_clamps_and_reports_position() {
354 let mut clock = ScrollClock::default();
355
356 clock.set_scroll(2000.0);
357
358 assert_eq!(clock.scroll_position(), 1000.0);
359 assert_eq!(clock.progress(), 1.0);
360 assert_eq!(clock.delta(), 1.0);
361 }
362}