1use crate::math::Lerp;
11use std::marker::PhantomData;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum Ease {
20 Linear,
21
22 QuadIn,
24 QuadOut,
25 QuadInOut,
26
27 CubicIn,
29 CubicOut,
30 CubicInOut,
31
32 QuartIn,
34 QuartOut,
35 QuartInOut,
36
37 QuintIn,
39 QuintOut,
40 QuintInOut,
41
42 SineIn,
44 SineOut,
45 SineInOut,
46
47 ExpoIn,
49 ExpoOut,
50 ExpoInOut,
51
52 CircIn,
54 CircOut,
55 CircInOut,
56
57 ElasticIn,
59 ElasticOut,
60 ElasticInOut,
61
62 BackIn,
64 BackOut,
65 BackInOut,
66
67 BounceIn,
69 BounceOut,
70 BounceInOut,
71}
72
73impl Ease {
74 pub fn apply(self, t: f32) -> f32 {
76 let t = t.clamp(0.0, 1.0);
77 match self {
78 Ease::Linear => t,
79
80 Ease::QuadIn => t * t,
82 Ease::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
83 Ease::QuadInOut => {
84 if t < 0.5 { 2.0 * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0 }
85 }
86
87 Ease::CubicIn => t * t * t,
89 Ease::CubicOut => 1.0 - (1.0 - t).powi(3),
90 Ease::CubicInOut => {
91 if t < 0.5 { 4.0 * t * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0 }
92 }
93
94 Ease::QuartIn => t * t * t * t,
96 Ease::QuartOut => 1.0 - (1.0 - t).powi(4),
97 Ease::QuartInOut => {
98 if t < 0.5 { 8.0 * t * t * t * t } else { 1.0 - (-2.0 * t + 2.0).powi(4) / 2.0 }
99 }
100
101 Ease::QuintIn => t * t * t * t * t,
103 Ease::QuintOut => 1.0 - (1.0 - t).powi(5),
104 Ease::QuintInOut => {
105 if t < 0.5 { 16.0 * t.powi(5) } else { 1.0 - (-2.0 * t + 2.0).powi(5) / 2.0 }
106 }
107
108 Ease::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
110 Ease::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
111 Ease::SineInOut => -(std::f32::consts::PI * t).cos() / 2.0 + 0.5,
112
113 Ease::ExpoIn => if t == 0.0 { 0.0 } else { 2.0_f32.powf(10.0 * t - 10.0) },
115 Ease::ExpoOut => if t == 1.0 { 1.0 } else { 1.0 - 2.0_f32.powf(-10.0 * t) },
116 Ease::ExpoInOut => {
117 if t == 0.0 { 0.0 }
118 else if t == 1.0 { 1.0 }
119 else if t < 0.5 { 2.0_f32.powf(20.0 * t - 10.0) / 2.0 }
120 else { (2.0 - 2.0_f32.powf(-20.0 * t + 10.0)) / 2.0 }
121 }
122
123 Ease::CircIn => 1.0 - (1.0 - t * t).sqrt(),
125 Ease::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
126 Ease::CircInOut => {
127 if t < 0.5 { (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0 }
128 else { ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0 }
129 }
130
131 Ease::ElasticIn => {
133 if t == 0.0 { 0.0 }
134 else if t == 1.0 { 1.0 }
135 else { -(2.0_f32).powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * (2.0 * std::f32::consts::PI) / 3.0).sin() }
136 }
137 Ease::ElasticOut => {
138 if t == 0.0 { 0.0 }
139 else if t == 1.0 { 1.0 }
140 else { 2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * (2.0 * std::f32::consts::PI) / 3.0).sin() + 1.0 }
141 }
142 Ease::ElasticInOut => {
143 const C4: f32 = 2.0 * std::f32::consts::PI / 3.0;
144 if t == 0.0 { 0.0 }
145 else if t == 1.0 { 1.0 }
146 else if t < 0.5 {
147 -(2.0_f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C4).sin()) / 2.0
148 } else {
149 2.0_f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C4).sin() / 2.0 + 1.0
150 }
151 }
152
153 Ease::BackIn => {
155 const C1: f32 = 1.70158;
156 const C3: f32 = C1 + 1.0;
157 C3 * t * t * t - C1 * t * t
158 }
159 Ease::BackOut => {
160 const C1: f32 = 1.70158;
161 const C3: f32 = C1 + 1.0;
162 1.0 + C3 * (t - 1.0).powi(3) + C1 * (t - 1.0).powi(2)
163 }
164 Ease::BackInOut => {
165 const C1: f32 = 1.70158;
166 const C2: f32 = C1 * 1.525;
167 if t < 0.5 {
168 (2.0 * t).powi(2) * ((C2 + 1.0) * 2.0 * t - C2) / 2.0
169 } else {
170 ((2.0 * t - 2.0).powi(2) * ((C2 + 1.0) * (t * 2.0 - 2.0) + C2) + 2.0) / 2.0
171 }
172 }
173
174 Ease::BounceIn => 1.0 - Ease::BounceOut.apply(1.0 - t),
176 Ease::BounceOut => {
177 const N1: f32 = 7.5625;
178 const D1: f32 = 2.75;
179 if t < 1.0 / D1 {
180 N1 * t * t
181 } else if t < 2.0 / D1 {
182 let t = t - 1.5 / D1;
183 N1 * t * t + 0.75
184 } else if t < 2.5 / D1 {
185 let t = t - 2.25 / D1;
186 N1 * t * t + 0.9375
187 } else {
188 let t = t - 2.625 / D1;
189 N1 * t * t + 0.984375
190 }
191 }
192 Ease::BounceInOut => {
193 if t < 0.5 {
194 (1.0 - Ease::BounceOut.apply(1.0 - 2.0 * t)) / 2.0
195 } else {
196 (1.0 + Ease::BounceOut.apply(2.0 * t - 1.0)) / 2.0
197 }
198 }
199 }
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub enum TweenStatus {
210 Running,
212 Completed,
214 Stopped,
216 Delayed,
218}
219
220#[derive(Debug, Clone)]
233pub struct Tween<T: Lerp + Copy> {
234 current: T,
236 start: T,
238 end: T,
240 duration: f32,
242 elapsed: f32,
244 delay: f32,
246 ease: Ease,
248 status: TweenStatus,
250 ping_pong: bool,
252 forward: bool,
254 loops: u32,
256 loops_done: u32,
258 speed: f32,
260 on_complete_tag: Option<String>,
262 _marker: PhantomData<T>,
263}
264
265impl<T: Lerp + Copy> Tween<T> {
266 pub fn new(start: T, end: T, duration: f32, ease: Ease) -> Self {
268 Self {
269 current: start,
270 start,
271 end,
272 duration,
273 elapsed: 0.0,
274 delay: 0.0,
275 ease,
276 status: TweenStatus::Running,
277 ping_pong: false,
278 forward: true,
279 loops: 1,
280 loops_done: 0,
281 speed: 1.0,
282 on_complete_tag: None,
283 _marker: PhantomData,
284 }
285 }
286
287 pub fn with_delay(mut self, delay: f32) -> Self {
289 self.delay = delay;
290 if delay > 0.0 {
291 self.status = TweenStatus::Delayed;
292 }
293 self
294 }
295
296 pub fn with_ping_pong(mut self) -> Self {
298 self.ping_pong = true;
299 self
300 }
301
302 pub fn with_loops(mut self, loops: u32) -> Self {
304 self.loops = loops;
305 self
306 }
307
308 pub fn with_speed(mut self, speed: f32) -> Self {
310 self.speed = speed;
311 self
312 }
313
314 pub fn with_tag(mut self, tag: &str) -> Self {
316 self.on_complete_tag = Some(tag.to_string());
317 self
318 }
319
320 pub fn value(&self) -> T {
322 self.current
323 }
324
325 pub fn status(&self) -> TweenStatus {
327 self.status
328 }
329
330 pub fn progress(&self) -> f32 {
332 if self.duration <= 0.0 { return 1.0; }
333 (self.elapsed / self.duration).clamp(0.0, 1.0)
334 }
335
336 pub fn reverse(&mut self) {
338 self.forward = !self.forward;
339 }
340
341 pub fn stop(&mut self) {
343 self.status = TweenStatus::Stopped;
344 }
345
346 pub fn restart(&mut self) {
348 self.elapsed = 0.0;
349 self.loops_done = 0;
350 self.forward = true;
351 self.status = if self.delay > 0.0 { TweenStatus::Delayed } else { TweenStatus::Running };
352 }
353
354 pub fn update(&mut self, dt: f32) -> bool {
358 match self.status {
359 TweenStatus::Completed | TweenStatus::Stopped => return false,
360 TweenStatus::Delayed => {
361 self.delay -= dt * self.speed;
362 if self.delay <= 0.0 {
363 self.status = TweenStatus::Running;
364 }
365 return false;
366 }
367 TweenStatus::Running => {}
368 }
369
370 self.elapsed += dt * self.speed;
371 let raw_t = if self.duration > 0.0 {
372 (self.elapsed / self.duration).clamp(0.0, 1.0)
373 } else {
374 1.0
375 };
376
377 if raw_t >= 1.0 {
378 if self.ping_pong {
380 if self.forward {
381 self.forward = false;
382 self.elapsed = 0.0;
383 self.current = self.end;
384 return false;
385 } else {
386 self.forward = true;
388 self.loops_done += 1;
389 }
390 } else {
391 self.current = self.end;
392 self.loops_done += 1;
393 }
394
395 if self.loops_done >= self.loops {
396 self.status = TweenStatus::Completed;
397 self.current = if self.forward { self.end } else { self.start };
398 return true;
399 }
400
401 self.elapsed = 0.0;
402 return false;
403 }
404
405 let eased_t = self.ease.apply(raw_t);
406 if self.forward {
407 self.current = self.start.lerp(self.end, eased_t);
408 } else {
409 self.current = self.end.lerp(self.start, eased_t);
410 }
411
412 false
413 }
414}
415
416#[derive(Default)]
434pub struct TweenManager {
435 tweens: Vec<Box<dyn TweenTrait>>,
436 completed_tags: Vec<String>,
438}
439
440trait TweenTrait: AsAny {
442 fn update_box(&mut self, dt: f32) -> bool;
443 fn is_done(&self) -> bool;
444 fn tag(&self) -> Option<&str>;
445}
446
447impl<T: Lerp + Copy + 'static> TweenTrait for Tween<T> {
448 fn update_box(&mut self, dt: f32) -> bool {
449 self.update(dt)
450 }
451 fn is_done(&self) -> bool {
452 self.status == TweenStatus::Completed || self.status == TweenStatus::Stopped
453 }
454 fn tag(&self) -> Option<&str> {
455 self.on_complete_tag.as_deref()
456 }
457}
458
459impl TweenManager {
460 pub fn new() -> Self {
462 Self::default()
463 }
464
465 pub fn add_tween<T: Lerp + Copy + 'static>(&mut self, tween: Tween<T>) -> usize {
467 let id = self.tweens.len();
468 self.tweens.push(Box::new(tween));
469 id
470 }
471
472 pub fn get_tween<T: Lerp + Copy + 'static>(&self, id: usize) -> Option<&Tween<T>> {
474 self.tweens.get(id)?.as_any().downcast_ref::<Tween<T>>()
475 }
476
477 pub fn get_tween_mut<T: Lerp + Copy + 'static>(&mut self, id: usize) -> Option<&mut Tween<T>> {
479 self.tweens.get_mut(id)?.as_any_mut().downcast_mut::<Tween<T>>()
480 }
481
482 pub fn update(&mut self, dt: f32) -> &[String] {
484 self.completed_tags.clear();
485
486 for tween in &mut self.tweens {
487 if tween.update_box(dt) {
488 if let Some(tag) = tween.tag() {
489 self.completed_tags.push(tag.to_string());
490 }
491 }
492 }
493
494 self.tweens.retain(|t| !t.is_done());
496 &self.completed_tags
497 }
498
499 pub fn stop_all(&mut self) {
501 for _tween in &mut self.tweens {
502 }
505 self.tweens.clear();
506 }
507
508 pub fn count(&self) -> usize {
510 self.tweens.len()
511 }
512
513 pub fn is_empty(&self) -> bool {
515 self.tweens.is_empty()
516 }
517}
518
519trait AsAny {
521 fn as_any(&self) -> &dyn std::any::Any;
522 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
523}
524impl<T: Lerp + Copy + 'static> AsAny for Tween<T> {
525 fn as_any(&self) -> &dyn std::any::Any { self }
526 fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
527}