1use crate::{
4 timing::{Duration, Timing},
5 traits::{Animatable, Animation, AnimationState},
6};
7
8pub type TweenState = AnimationState;
10
11#[derive(Debug, Clone)]
29pub struct Tween<T: Animatable> {
30 from: T,
31 to: T,
32 current: T,
33 elapsed: Duration,
34 timing: Timing,
35 state: AnimationState,
36}
37
38impl<T: Animatable> Tween<T> {
39 #[must_use]
41 pub fn new(value: T) -> Self {
42 Self::with_timing(value, Timing::new(200.0))
43 }
44
45 #[must_use]
47 pub fn with_timing(value: T, timing: Timing) -> Self {
48 Self {
49 from: value.clone(),
50 to: value.clone(),
51 current: value,
52 elapsed: Duration::ZERO,
53 timing,
54 state: AnimationState::Idle,
55 }
56 }
57
58 #[must_use]
60 pub fn between(from: T, to: T, timing: Timing) -> Self {
61 let mut tween = Self::with_timing(from, timing);
62 tween.transition_to(to);
63 tween
64 }
65
66 #[must_use]
68 pub fn value(&self) -> &T {
69 &self.current
70 }
71
72 #[must_use]
74 pub fn from(&self) -> &T {
75 &self.from
76 }
77
78 #[must_use]
80 pub fn target(&self) -> &T {
81 &self.to
82 }
83
84 #[must_use]
86 pub const fn timing(&self) -> Timing {
87 self.timing
88 }
89
90 #[must_use]
92 pub const fn state(&self) -> AnimationState {
93 self.state
94 }
95
96 #[must_use]
98 pub fn is_active(&self) -> bool {
99 self.state == AnimationState::Running
100 }
101
102 #[must_use]
104 pub fn is_completed(&self) -> bool {
105 self.state == AnimationState::Completed
106 }
107
108 pub fn transition_to(&mut self, target: T) {
110 self.from = self.current.clone();
111 self.to = target;
112 self.elapsed = Duration::ZERO;
113 self.state = AnimationState::Running;
114 self.sample();
115 }
116
117 pub fn tick(&mut self, delta: impl Into<Duration>) {
119 if self.state != AnimationState::Running {
120 return;
121 }
122
123 self.elapsed += delta.into();
124 self.sample();
125 }
126
127 fn remaining(&self) -> Option<Duration> {
128 let total = self.timing.total_duration()?;
129 Some(total.saturating_sub(self.elapsed))
130 }
131
132 pub fn pause(&mut self) {
134 if self.state == AnimationState::Running {
135 self.state = AnimationState::Paused;
136 }
137 }
138
139 pub fn resume(&mut self) {
141 if self.state == AnimationState::Paused {
142 self.state = AnimationState::Running;
143 }
144 }
145
146 pub fn cancel(&mut self) {
148 if matches!(
149 self.state,
150 AnimationState::Running | AnimationState::Paused | AnimationState::Idle
151 ) {
152 self.state = AnimationState::Canceled;
153 }
154 }
155
156 pub fn seek(&mut self, progress: f32) {
158 let progress = if progress.is_nan() {
159 0.0
160 } else {
161 progress.clamp(0.0, 1.0)
162 };
163 self.state = AnimationState::Running;
164 let total = self.timing.total_duration().unwrap_or_else(|| {
165 Duration::from_millis(
166 self.timing.delay().as_millis() + self.timing.duration().as_millis(),
167 )
168 });
169 self.elapsed = Duration::from_millis(total.as_millis() * f64::from(progress));
170 self.sample();
171 }
172
173 #[allow(clippy::cast_possible_truncation)]
175 pub fn finish(&mut self) {
176 let progress = self
177 .timing
178 .iterations()
179 .finite_count()
180 .map_or(self.timing.direction().sample_progress(1, 1.0), |count| {
181 self.timing.direction().end_progress(count)
182 });
183 self.current = T::interpolate(
184 &self.from,
185 &self.to,
186 self.timing.easing().value(progress as f32),
187 );
188 self.state = AnimationState::Completed;
189 }
190
191 #[allow(clippy::cast_sign_loss)]
192 #[allow(clippy::cast_possible_truncation)]
193 fn sample(&mut self) {
194 let Some(active_elapsed) = self.elapsed.checked_sub_delay(self.timing.delay()) else {
195 self.current = self.from.clone();
196 return;
197 };
198
199 let duration = self.timing.duration();
200 if duration.is_zero() {
201 self.finish();
202 return;
203 }
204
205 if self
206 .timing
207 .active_duration()
208 .is_some_and(|total| active_elapsed >= total)
209 {
210 self.finish();
211 return;
212 }
213
214 let iteration_progress = active_elapsed.as_secs() / duration.as_secs();
215 let iteration = iteration_progress.floor() as u32;
216 let raw_progress = iteration_progress.fract();
217 let progress = self
218 .timing
219 .direction()
220 .sample_progress(iteration, raw_progress);
221 let eased = self.timing.easing().value(progress as f32);
222 self.current = T::interpolate(&self.from, &self.to, eased);
223 }
224}
225
226impl<T: Animatable> Animation<T> for Tween<T> {
227 fn value(&self) -> &T {
228 self.value()
229 }
230
231 fn state(&self) -> AnimationState {
232 self.state()
233 }
234
235 fn duration(&self) -> Option<Duration> {
236 self.timing.total_duration()
237 }
238
239 fn tick(&mut self, delta: Duration) {
240 self.tick(delta);
241 }
242
243 fn advance(&mut self, delta: Duration) -> Duration {
244 if self.state != AnimationState::Running {
245 return delta;
246 }
247
248 let Some(remaining) = self.remaining() else {
249 self.tick(delta);
250 return Duration::ZERO;
251 };
252 let consumed = delta.min(remaining);
253 self.tick(consumed);
254 delta.saturating_sub(consumed)
255 }
256
257 fn pause(&mut self) {
258 self.pause();
259 }
260
261 fn resume(&mut self) {
262 self.resume();
263 }
264
265 fn cancel(&mut self) {
266 self.cancel();
267 }
268
269 fn seek(&mut self, progress: f32) {
270 self.seek(progress);
271 }
272
273 fn finish(&mut self) {
274 self.finish();
275 }
276
277 fn retarget(&mut self, target: &T) -> bool {
278 self.transition_to(target.clone());
279 true
280 }
281
282 fn set_rate(&mut self, rate: f64) {
283 self.timing = self.timing.with_rate(rate);
284 }
285
286 fn into_value(self: Box<Self>) -> T {
287 self.current
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::Tween;
294 use crate::{
295 Animation, AnimationState,
296 timing::{Delay, Direction, Duration, IterationCount, Timing},
297 };
298 use float_cmp::assert_approx_eq;
299
300 #[test]
301 fn new_tween_is_idle_with_matching_endpoints() {
302 let tween = Tween::new(3.0_f32);
303
304 assert_eq!(tween.state(), AnimationState::Idle);
305 assert_approx_eq!(f32, *tween.value(), 3.0);
306 assert_approx_eq!(f32, *tween.from(), 3.0);
307 assert_approx_eq!(f32, *tween.target(), 3.0);
308 assert_approx_eq!(f64, tween.timing().duration().as_millis(), 200.0);
309 }
310
311 #[test]
312 fn transition_uses_current_value_as_new_start() {
313 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(100.0));
314 tween.tick(Duration::from_millis(40.0));
315 tween.transition_to(20.0);
316
317 assert_approx_eq!(f32, *tween.from(), 4.0);
318 assert_approx_eq!(f32, *tween.value(), 4.0);
319 assert_approx_eq!(f32, *tween.target(), 20.0);
320 }
321
322 #[test]
323 fn rate_changes_tween_playback_duration() {
324 let mut faster = Tween::between(0.0_f32, 10.0, Timing::new(100.0)).rate(2.0);
325 let mut slower = Tween::between(0.0_f32, 10.0, Timing::new(100.0)).rate(0.5);
326
327 faster.tick(Duration::from_millis(50.0));
328 slower.tick(Duration::from_millis(50.0));
329
330 assert_eq!(faster.state(), AnimationState::Completed);
331 assert_approx_eq!(f32, *faster.value(), 10.0);
332 assert_eq!(slower.state(), AnimationState::Running);
333 assert_approx_eq!(f32, *slower.value(), 2.5);
334 }
335
336 #[test]
337 fn paused_and_canceled_tweens_do_not_tick() {
338 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(100.0));
339 tween.pause();
340 tween.tick(Duration::from_millis(50.0));
341 assert_approx_eq!(f32, *tween.value(), 0.0);
342
343 tween.resume();
344 tween.tick(Duration::from_millis(25.0));
345 assert_approx_eq!(f32, *tween.value(), 2.5);
346
347 tween.cancel();
348 tween.tick(Duration::from_millis(75.0));
349 assert_eq!(tween.state(), AnimationState::Canceled);
350 assert_approx_eq!(f32, *tween.value(), 2.5);
351 }
352
353 #[test]
354 fn advance_returns_unconsumed_duration() {
355 let mut tween = Tween::between(0.0_f32, 1.0, Timing::new(100.0));
356
357 let overflow = Animation::advance(&mut tween, Duration::from_millis(125.0));
358
359 assert_eq!(overflow, Duration::from_millis(25.0));
360 assert_eq!(tween.state(), AnimationState::Completed);
361 }
362
363 #[test]
364 fn infinite_tween_consumes_all_advanced_time() {
365 let timing = Timing::new(100.0).with_iterations(IterationCount::INFINITE);
366 let mut tween = Tween::between(0.0_f32, 1.0, timing);
367
368 let overflow = Animation::advance(&mut tween, Duration::from_millis(250.0));
369
370 assert_eq!(overflow, Duration::ZERO);
371 assert_eq!(tween.state(), AnimationState::Running);
372 assert_approx_eq!(f32, *tween.value(), 0.5);
373 }
374
375 #[test]
376 fn delay_and_submillisecond_progress_preserve_duration_precision() {
377 let timing = Timing::new(0.5).with_delay(Delay::from_millis(0.25));
378 let mut tween = Tween::between(0.0_f32, 10.0, timing);
379
380 tween.tick(std::time::Duration::from_micros(250));
381 assert_approx_eq!(f32, *tween.value(), 0.0);
382
383 tween.tick(std::time::Duration::from_micros(125));
384 assert_approx_eq!(f32, *tween.value(), 2.5);
385 }
386
387 #[test]
388 fn repeated_tween_samples_progress_within_alternate_iteration() {
389 let timing = Timing::new(100.0)
390 .with_iterations(2)
391 .with_direction(Direction::Alternate);
392 let mut tween = Tween::between(0.0_f32, 10.0, timing);
393
394 tween.tick(Duration::from_millis(150.0));
395
396 assert_eq!(tween.state(), AnimationState::Running);
397 assert_approx_eq!(f32, *tween.value(), 5.0);
398 }
399
400 #[test]
401 fn finish_respects_repeated_direction() {
402 let timing = Timing::new(100.0)
403 .with_iterations(2)
404 .with_direction(Direction::Alternate);
405 let mut tween = Tween::between(0.0_f32, 10.0, timing);
406
407 tween.finish();
408
409 assert_eq!(tween.state(), AnimationState::Completed);
410 assert_approx_eq!(f32, *tween.value(), 0.0);
411 }
412
413 #[test]
414 fn zero_duration_tween_finishes_on_tick() {
415 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(0.0));
416
417 tween.tick(Duration::ZERO);
418
419 assert_eq!(tween.state(), AnimationState::Completed);
420 assert_approx_eq!(f32, *tween.value(), 10.0);
421 }
422}