1use super::{Tween, TweenState, Lerp};
7use super::easing::Easing;
8use glam::{Vec2, Vec3, Vec4};
9
10pub struct SequenceStep<T: Lerp + std::fmt::Debug> {
14 pub tween: TweenState<T>,
15 pub overlap: f32,
17 pub(crate) start_t: f32,
19}
20
21pub struct TweenSequence<T: Lerp + Clone + std::fmt::Debug> {
28 pub steps: Vec<SequenceStep<T>>,
29 pub elapsed: f32,
30 pub looping: bool,
31 pub duration: f32,
32 pub done: bool,
33 default_val: T,
34}
35
36impl<T: Lerp + Clone + std::fmt::Debug> TweenSequence<T> {
37 pub fn new(steps: Vec<(Tween<T>, f32)>, default_val: T, looping: bool) -> Self {
39 let mut seq_steps: Vec<SequenceStep<T>> = Vec::with_capacity(steps.len());
40 let mut cursor = 0.0_f32;
41 for (tween, overlap) in steps {
42 let start_t = cursor - overlap;
43 cursor = start_t + tween.duration;
44 seq_steps.push(SequenceStep {
45 tween: TweenState::new(tween),
46 overlap,
47 start_t,
48 });
49 }
50 let duration = cursor;
51 Self { steps: seq_steps, elapsed: 0.0, looping, duration, done: false, default_val }
52 }
53
54 pub fn tick(&mut self, dt: f32) -> T {
56 self.elapsed += dt;
57 if self.looping && self.elapsed >= self.duration {
58 self.elapsed -= self.duration;
59 }
60 self.done = !self.looping && self.elapsed >= self.duration;
61 self.current_value()
62 }
63
64 pub fn current_value(&self) -> T {
66 let t = if self.looping {
67 self.elapsed % self.duration.max(f32::EPSILON)
68 } else {
69 self.elapsed.min(self.duration)
70 };
71
72 let mut active: Option<usize> = None;
74 for (i, step) in self.steps.iter().enumerate() {
75 if t >= step.start_t {
76 active = Some(i);
77 }
78 }
79
80 if let Some(idx) = active {
81 let step = &self.steps[idx];
82 let local_t = (t - step.start_t).max(0.0);
83 step.tween.tween.sample(local_t)
84 } else {
85 self.default_val.clone()
86 }
87 }
88
89 pub fn reset(&mut self) {
90 self.elapsed = 0.0;
91 self.done = false;
92 }
93
94 pub fn progress(&self) -> f32 {
95 (self.elapsed / self.duration.max(f32::EPSILON)).clamp(0.0, 1.0)
96 }
97}
98
99pub struct SequenceBuilder<T: Lerp + Clone + std::fmt::Debug> {
115 steps: Vec<(Tween<T>, f32)>,
116 default_val: T,
117 looping: bool,
118 next_overlap: f32,
119}
120
121impl<T: Lerp + Clone + std::fmt::Debug> SequenceBuilder<T> {
122 pub fn new(default_val: T) -> Self {
123 Self { steps: Vec::new(), default_val, looping: false, next_overlap: 0.0 }
124 }
125
126 pub fn then(mut self, from: T, to: T, duration: f32, easing: Easing) -> Self {
128 let overlap = self.next_overlap;
129 self.next_overlap = 0.0;
130 self.steps.push((Tween::new(from, to, duration, easing), overlap));
131 self
132 }
133
134 pub fn overlap(mut self, seconds: f32) -> Self {
136 self.next_overlap = seconds;
137 self
138 }
139
140 pub fn wait(mut self, seconds: f32) -> Self {
142 self.next_overlap = -seconds;
143 self
144 }
145
146 pub fn looping(mut self, looping: bool) -> Self {
147 self.looping = looping;
148 self
149 }
150
151 pub fn build(self) -> TweenSequence<T> {
152 TweenSequence::new(self.steps, self.default_val, self.looping)
153 }
154}
155
156pub struct TweenTimeline {
163 pub tracks: std::collections::HashMap<String, TweenSequence<f32>>,
164 pub elapsed: f32,
165 pub looping: bool,
166 duration: f32,
167 pub done: bool,
168}
169
170impl TweenTimeline {
171 pub fn new(looping: bool) -> Self {
172 Self {
173 tracks: std::collections::HashMap::new(),
174 elapsed: 0.0,
175 looping,
176 duration: 0.0,
177 done: false,
178 }
179 }
180
181 pub fn add_track(&mut self, name: impl Into<String>, seq: TweenSequence<f32>) {
183 self.duration = self.duration.max(seq.duration);
184 self.tracks.insert(name.into(), seq);
185 }
186
187 pub fn tick(&mut self, dt: f32) {
189 self.elapsed += dt;
190 if self.looping && self.elapsed >= self.duration {
191 self.elapsed -= self.duration;
192 for track in self.tracks.values_mut() { track.reset(); }
193 }
194 self.done = !self.looping && self.elapsed >= self.duration;
195 for track in self.tracks.values_mut() {
196 track.tick(dt);
197 }
198 }
199
200 pub fn get(&self, name: &str) -> f32 {
202 self.tracks.get(name).map(|t| t.current_value()).unwrap_or(0.0)
203 }
204
205 pub fn reset(&mut self) {
207 self.elapsed = 0.0;
208 self.done = false;
209 for track in self.tracks.values_mut() { track.reset(); }
210 }
211
212 pub fn progress(&self) -> f32 {
213 (self.elapsed / self.duration.max(f32::EPSILON)).clamp(0.0, 1.0)
214 }
215}
216
217impl TweenTimeline {
220 pub fn damage_flash(intensity: f32) -> Self {
222 let mut tl = Self::new(false);
223
224 let flash_seq = SequenceBuilder::new(0.0f32)
225 .then(intensity, 0.0, 0.3, Easing::EaseOutExpo)
226 .build();
227 tl.add_track("flash", flash_seq);
228
229 let scale_seq = SequenceBuilder::new(1.0f32)
230 .then(1.0 + intensity * 0.1, 1.0, 0.25, Easing::EaseOutBack)
231 .build();
232 tl.add_track("scale", scale_seq);
233
234 tl
235 }
236
237 pub fn level_up() -> Self {
239 let mut tl = Self::new(false);
240
241 let flash = SequenceBuilder::new(0.0f32)
242 .then(1.5, 1.0, 0.15, Easing::EaseOutExpo)
243 .then(1.0, 1.0, 1.5, Easing::Linear)
244 .then(1.0, 0.0, 0.5, Easing::EaseInQuad)
245 .build();
246 tl.add_track("brightness", flash);
247
248 let hue_shift = SequenceBuilder::new(0.0f32)
249 .then(0.0, 360.0, 2.0, Easing::Linear)
250 .build();
251 tl.add_track("hue_shift", hue_shift);
252
253 let bloom = SequenceBuilder::new(1.0f32)
254 .then(3.0, 1.0, 0.8, Easing::EaseOutCubic)
255 .build();
256 tl.add_track("bloom", bloom);
257
258 tl
259 }
260
261 pub fn boss_entrance() -> Self {
263 let mut tl = Self::new(false);
264
265 let vignette = SequenceBuilder::new(0.15f32)
266 .then(0.15, 0.9, 0.8, Easing::EaseInCubic)
267 .then(0.9, 0.9, 1.2, Easing::Linear)
268 .then(0.9, 0.2, 0.6, Easing::EaseOutExpo)
269 .build();
270 tl.add_track("vignette", vignette);
271
272 let chromatic = SequenceBuilder::new(0.002f32)
273 .then(0.002, 0.02, 0.8, Easing::EaseInExpo)
274 .then(0.02, 0.001, 0.4, Easing::EaseOutCubic)
275 .build();
276 tl.add_track("chromatic", chromatic);
277
278 let saturation = SequenceBuilder::new(1.0f32)
279 .then(1.0, 0.0, 0.8, Easing::EaseInCubic)
280 .then(0.0, 1.2, 0.6, Easing::EaseOutBack)
281 .build();
282 tl.add_track("saturation", saturation);
283
284 tl
285 }
286
287 pub fn death_sequence() -> Self {
289 let mut tl = Self::new(false);
290
291 let saturation = SequenceBuilder::new(1.0f32)
292 .then(1.0, 0.0, 2.5, Easing::EaseInCubic)
293 .build();
294 tl.add_track("saturation", saturation);
295
296 let brightness = SequenceBuilder::new(0.0f32)
297 .then(0.0, -0.8, 3.0, Easing::EaseInQuart)
298 .build();
299 tl.add_track("brightness", brightness);
300
301 let vignette = SequenceBuilder::new(0.15f32)
302 .then(0.15, 1.0, 3.0, Easing::EaseInCubic)
303 .build();
304 tl.add_track("vignette", vignette);
305
306 let chromatic = SequenceBuilder::new(0.002f32)
307 .then(0.002, 0.015, 1.5, Easing::EaseInQuad)
308 .build();
309 tl.add_track("chromatic", chromatic);
310
311 tl
312 }
313
314 pub fn heal_pulse(amount_fraction: f32) -> Self {
316 let mut tl = Self::new(false);
317
318 let green = SequenceBuilder::new(1.0f32)
319 .then(1.0, 1.0 + amount_fraction * 0.4, 0.15, Easing::EaseOutExpo)
320 .then(1.0 + amount_fraction * 0.4, 1.0, 0.4, Easing::EaseInQuad)
321 .build();
322 tl.add_track("green_tint", green);
323
324 let bloom = SequenceBuilder::new(1.0f32)
325 .then(1.0, 1.8, 0.15, Easing::EaseOutExpo)
326 .then(1.8, 1.0, 0.5, Easing::EaseOutCubic)
327 .build();
328 tl.add_track("bloom", bloom);
329
330 tl
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn sequence_basic() {
340 let mut seq = SequenceBuilder::new(0.0f32)
341 .then(0.0, 1.0, 1.0, Easing::Linear)
342 .then(1.0, 2.0, 1.0, Easing::Linear)
343 .build();
344 let v0 = seq.tick(0.0);
345 assert!((v0 - 0.0).abs() < 1e-4);
346 seq.elapsed = 0.5;
347 let v_mid = seq.current_value();
348 assert!((v_mid - 0.5).abs() < 1e-4, "expected 0.5 got {v_mid}");
349 seq.elapsed = 1.5;
350 let v2 = seq.current_value();
351 assert!((v2 - 1.5).abs() < 1e-4, "expected 1.5 got {v2}");
352 }
353
354 #[test]
355 fn timeline_damage_flash() {
356 let mut tl = TweenTimeline::damage_flash(1.0);
357 let flash_start = tl.get("flash");
358 assert!((flash_start - 1.0).abs() < 0.01, "flash starts at intensity");
359 tl.tick(0.3);
360 let flash_end = tl.get("flash");
361 assert!(flash_end < 0.2, "flash should decay quickly");
362 }
363
364 #[test]
365 fn builder_wait() {
366 let seq = SequenceBuilder::new(0.0f32)
367 .then(0.0, 1.0, 0.5, Easing::Linear)
368 .wait(0.5)
369 .then(1.0, 2.0, 0.5, Easing::Linear)
370 .build();
371 assert!((seq.steps[1].start_t - 1.0).abs() < 1e-4, "second step at t=1.0");
373 }
374}