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
43impl Debug for ScrollDriver {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 f.debug_struct("ScrollDriver")
46 .field("min", &self.min)
47 .field("max", &self.max)
48 .field("position", &self.position)
49 .field("animations", &self.animations.len())
50 .finish()
51 }
52}
53
54impl ScrollDriver {
55 pub fn new(min: f32, max: f32) -> Self {
60 Self {
61 min,
62 max: max.max(min + f32::EPSILON),
63 position: min,
64 animations: std::vec::Vec::new(),
65 }
66 }
67
68 pub fn add<A: Update + Send + 'static>(&mut self, animation: A) {
70 self.animations.push(Box::new(animation));
71 }
72
73 pub fn set_position(&mut self, pos: f32) {
82 let clamped = pos.clamp(self.min, self.max);
83 let range = self.max - self.min;
84 if range <= 0.0 {
85 return;
86 }
87
88 let delta = (clamped - self.position).abs() / range;
89 self.position = clamped;
90
91 if delta > 0.0 {
92 for animation in self.animations.iter_mut() {
93 animation.update(delta);
94 }
95 }
96 }
97
98 pub fn clear_completed(&mut self) {
103 self.animations.retain_mut(|a| a.update(0.0));
105 }
106
107 pub fn position(&self) -> f32 {
109 self.position
110 }
111
112 pub fn progress(&self) -> f32 {
114 let range = self.max - self.min;
115 if range <= 0.0 {
116 return 0.0;
117 }
118 ((self.position - self.min) / range).clamp(0.0, 1.0)
119 }
120
121 pub fn min(&self) -> f32 {
123 self.min
124 }
125
126 pub fn max(&self) -> f32 {
128 self.max
129 }
130
131 pub fn animation_count(&self) -> usize {
133 self.animations.len()
134 }
135}
136
137#[derive(Clone, Debug)]
156pub struct ScrollClock {
157 last: f32,
158 pending: f32,
159 min: f32,
160 max: f32,
161}
162
163impl Default for ScrollClock {
164 fn default() -> Self {
165 Self::new(0.0, 1000.0)
166 }
167}
168
169impl ScrollClock {
170 pub fn new(min: f32, max: f32) -> Self {
172 Self {
173 last: min,
174 pending: 0.0,
175 min,
176 max: max.max(min + f32::EPSILON),
177 }
178 }
179
180 pub fn set_scroll(&mut self, pos: f32) {
185 let clamped = pos.clamp(self.min, self.max);
186 let range = self.max - self.min;
187 if range > 0.0 {
188 self.pending += (clamped - self.last).abs() / range;
189 }
190 self.last = clamped;
191 }
192
193 pub fn scroll_position(&self) -> f32 {
195 self.last
196 }
197
198 pub fn progress(&self) -> f32 {
200 let range = self.max - self.min;
201 if range <= 0.0 {
202 return 0.0;
203 }
204 ((self.last - self.min) / range).clamp(0.0, 1.0)
205 }
206}
207
208impl Clock for ScrollClock {
209 fn delta(&mut self) -> f32 {
211 let dt = self.pending;
212 self.pending = 0.0;
213 dt
214 }
215}
216
217#[cfg(test)]
220mod tests {
221 use super::*;
222 use animato_core::{Easing, Update};
223 use animato_tween::Tween;
224
225 #[test]
226 fn scroll_driver_progress_tracks_position() {
227 let mut driver = ScrollDriver::new(0.0, 100.0);
228 assert_eq!(driver.progress(), 0.0);
229 driver.set_position(50.0);
230 assert!((driver.progress() - 0.5).abs() < 0.001);
231 driver.set_position(100.0);
232 assert!((driver.progress() - 1.0).abs() < 0.001);
233 }
234
235 #[test]
236 fn scroll_driver_clamps_position() {
237 let mut driver = ScrollDriver::new(0.0, 100.0);
238 driver.set_position(-50.0);
239 assert_eq!(driver.position(), 0.0);
240 driver.set_position(200.0);
241 assert_eq!(driver.position(), 100.0);
242 }
243
244 #[test]
245 fn scroll_driver_ticks_animations_proportionally() {
246 let mut driver = ScrollDriver::new(0.0, 1000.0);
247 driver.add(
249 Tween::new(0.0_f32, 100.0)
250 .duration(1.0)
251 .easing(Easing::Linear)
252 .build(),
253 );
254 driver.set_position(500.0);
256 assert_eq!(driver.animation_count(), 1);
257 }
258
259 #[test]
260 fn scroll_driver_zero_delta_does_not_tick() {
261 struct _PanicOnUpdate;
262 impl Update for _PanicOnUpdate {
263 fn update(&mut self, _dt: f32) -> bool {
264 panic!("should not be called")
265 }
266 }
267 let mut driver = ScrollDriver::new(0.0, 100.0);
268 driver.set_position(0.0); }
272
273 #[test]
274 fn scroll_clock_delta_is_normalised() {
275 let mut clock = ScrollClock::new(0.0, 1000.0);
276 clock.set_scroll(250.0);
277 let dt = clock.delta();
278 assert!((dt - 0.25).abs() < 0.001);
279 }
280
281 #[test]
282 fn scroll_clock_delta_consumed_after_read() {
283 let mut clock = ScrollClock::new(0.0, 100.0);
284 clock.set_scroll(30.0);
285 let _ = clock.delta();
286 assert_eq!(clock.delta(), 0.0);
287 }
288
289 #[test]
290 fn scroll_clock_accumulates_multiple_moves() {
291 let mut clock = ScrollClock::new(0.0, 100.0);
292 clock.set_scroll(10.0); clock.set_scroll(20.0); clock.set_scroll(30.0); let dt = clock.delta();
296 assert!((dt - 0.3).abs() < 0.001);
297 }
298
299 #[test]
300 fn scroll_clock_progress() {
301 let mut clock = ScrollClock::new(0.0, 200.0);
302 clock.set_scroll(100.0);
303 let _ = clock.delta();
304 assert!((clock.progress() - 0.5).abs() < 0.001);
305 }
306}