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 delay = self.timing.delay().as_millis();
195 let elapsed = self.elapsed.as_millis();
196
197 if elapsed < delay {
198 self.current = self.from.clone();
199 return;
200 }
201
202 let duration = self.timing.duration().as_millis();
203 if duration <= 0.0 {
204 self.finish();
205 return;
206 }
207
208 let active_elapsed = elapsed - delay;
209 let iterations = self.timing.iterations().finite_count();
210
211 if let Some(count) = iterations {
212 let total = duration * f64::from(count);
213 if active_elapsed >= total {
214 self.finish();
215 return;
216 }
217 }
218
219 let iteration = (active_elapsed / duration).floor() as u32;
220 let raw_progress = (active_elapsed % duration) / duration;
221 let progress = self
222 .timing
223 .direction()
224 .sample_progress(iteration, raw_progress);
225 let eased = self.timing.easing().value(progress as f32);
226 self.current = T::interpolate(&self.from, &self.to, eased);
227 }
228}
229
230impl<T: Animatable> Animation<T> for Tween<T> {
231 fn value(&self) -> &T {
232 self.value()
233 }
234
235 fn state(&self) -> AnimationState {
236 self.state()
237 }
238
239 fn duration(&self) -> Option<Duration> {
240 self.timing.total_duration()
241 }
242
243 fn tick(&mut self, delta: Duration) {
244 self.tick(delta);
245 }
246
247 fn advance(&mut self, delta: Duration) -> Duration {
248 if self.state != AnimationState::Running {
249 return delta;
250 }
251
252 let Some(remaining) = self.remaining() else {
253 self.tick(delta);
254 return Duration::ZERO;
255 };
256 let consumed = delta.min(remaining);
257 self.tick(consumed);
258 delta.saturating_sub(consumed)
259 }
260
261 fn pause(&mut self) {
262 self.pause();
263 }
264
265 fn resume(&mut self) {
266 self.resume();
267 }
268
269 fn cancel(&mut self) {
270 self.cancel();
271 }
272
273 fn seek(&mut self, progress: f32) {
274 self.seek(progress);
275 }
276
277 fn finish(&mut self) {
278 self.finish();
279 }
280
281 fn retarget(&mut self, target: &T) -> bool {
282 self.transition_to(target.clone());
283 true
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::Tween;
290 use crate::{
291 Animation, AnimationState,
292 timing::{Direction, Duration, IterationCount, Timing},
293 };
294 use float_cmp::assert_approx_eq;
295
296 #[test]
297 fn new_tween_is_idle_with_matching_endpoints() {
298 let tween = Tween::new(3.0_f32);
299
300 assert_eq!(tween.state(), AnimationState::Idle);
301 assert_approx_eq!(f32, *tween.value(), 3.0);
302 assert_approx_eq!(f32, *tween.from(), 3.0);
303 assert_approx_eq!(f32, *tween.target(), 3.0);
304 assert_approx_eq!(f64, tween.timing().duration().as_millis(), 200.0);
305 }
306
307 #[test]
308 fn transition_uses_current_value_as_new_start() {
309 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(100.0));
310 tween.tick(Duration::from_millis(40.0));
311 tween.transition_to(20.0);
312
313 assert_approx_eq!(f32, *tween.from(), 4.0);
314 assert_approx_eq!(f32, *tween.value(), 4.0);
315 assert_approx_eq!(f32, *tween.target(), 20.0);
316 }
317
318 #[test]
319 fn paused_and_canceled_tweens_do_not_tick() {
320 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(100.0));
321 tween.pause();
322 tween.tick(Duration::from_millis(50.0));
323 assert_approx_eq!(f32, *tween.value(), 0.0);
324
325 tween.resume();
326 tween.tick(Duration::from_millis(25.0));
327 assert_approx_eq!(f32, *tween.value(), 2.5);
328
329 tween.cancel();
330 tween.tick(Duration::from_millis(75.0));
331 assert_eq!(tween.state(), AnimationState::Canceled);
332 assert_approx_eq!(f32, *tween.value(), 2.5);
333 }
334
335 #[test]
336 fn advance_returns_unconsumed_duration() {
337 let mut tween = Tween::between(0.0_f32, 1.0, Timing::new(100.0));
338
339 let overflow = Animation::advance(&mut tween, Duration::from_millis(125.0));
340
341 assert_eq!(overflow, Duration::from_millis(25.0));
342 assert_eq!(tween.state(), AnimationState::Completed);
343 }
344
345 #[test]
346 fn infinite_tween_consumes_all_advanced_time() {
347 let timing = Timing::new(100.0).with_iterations(IterationCount::INFINITE);
348 let mut tween = Tween::between(0.0_f32, 1.0, timing);
349
350 let overflow = Animation::advance(&mut tween, Duration::from_millis(250.0));
351
352 assert_eq!(overflow, Duration::ZERO);
353 assert_eq!(tween.state(), AnimationState::Running);
354 assert_approx_eq!(f32, *tween.value(), 0.5);
355 }
356
357 #[test]
358 fn finish_respects_repeated_direction() {
359 let timing = Timing::new(100.0)
360 .with_iterations(2)
361 .with_direction(Direction::Alternate);
362 let mut tween = Tween::between(0.0_f32, 10.0, timing);
363
364 tween.finish();
365
366 assert_eq!(tween.state(), AnimationState::Completed);
367 assert_approx_eq!(f32, *tween.value(), 0.0);
368 }
369
370 #[test]
371 fn zero_duration_tween_finishes_on_tick() {
372 let mut tween = Tween::between(0.0_f32, 10.0, Timing::new(0.0));
373
374 tween.tick(Duration::ZERO);
375
376 assert_eq!(tween.state(), AnimationState::Completed);
377 assert_approx_eq!(f32, *tween.value(), 10.0);
378 }
379}