aura_anim_core/keyframes/
animation.rs1use std::cmp::Ordering;
2
3use lilt::Easing;
4
5use crate::{
6 keyframes::Keyframe,
7 timing::{Delay, Direction, Duration, IterationCount, Timing},
8 traits::{Animatable, Animation, AnimationState},
9};
10
11#[derive(Debug, Clone)]
29pub struct Keyframes<T: Animatable> {
30 frames: Vec<Keyframe<T>>,
31 current: T,
32 elapsed: Duration,
33 timing: Timing,
34 state: AnimationState,
35}
36
37impl<T: Animatable> Keyframes<T> {
38 #[must_use]
40 pub fn new(initial: T) -> Self {
41 Self {
42 frames: vec![Keyframe::new(0.0, initial.clone())],
43 current: initial,
44 elapsed: Duration::ZERO,
45 timing: Timing::default(),
46 state: AnimationState::Running,
47 }
48 }
49
50 #[must_use]
52 pub fn push(self, time_ms: f64, value: T) -> Self {
53 self.push_eased(time_ms, value, Easing::Linear)
54 }
55
56 #[must_use]
58 pub fn push_eased(self, time_ms: f64, value: T, easing: Easing) -> Self {
59 let frame = Keyframe::new(time_ms.max(0.0), value).with_easing(easing);
60 self.push_frame(frame)
61 }
62
63 #[must_use]
65 pub fn push_frame(mut self, frame: Keyframe<T>) -> Self {
66 match self.frames.binary_search_by(|existing| {
67 existing
68 .time()
69 .partial_cmp(&frame.time())
70 .unwrap_or(Ordering::Equal)
71 }) {
72 Ok(index) => self.frames[index] = frame,
73 Err(index) => self.frames.insert(index, frame),
74 }
75 self.timing = self.timing.with_duration(self.duration());
76 self
77 }
78
79 #[must_use]
81 pub fn with_delay(mut self, delay: Delay) -> Self {
82 self.timing = self.timing.with_delay(delay);
83 self
84 }
85
86 #[must_use]
88 pub fn with_iterations(mut self, iterations: impl Into<IterationCount>) -> Self {
89 self.timing = self.timing.with_iterations(iterations);
90 self
91 }
92
93 #[must_use]
95 pub fn with_direction(mut self, direction: Direction) -> Self {
96 self.timing = self.timing.with_direction(direction);
97 self
98 }
99
100 #[must_use]
102 fn duration(&self) -> Duration {
103 Duration::from_millis(self.frames.last().map_or(0.0, Keyframe::time))
104 }
105
106 fn sample(&mut self) {
107 let delay = self.timing.delay().as_millis();
108 let elapsed = self.elapsed.as_millis();
109 if elapsed < delay {
110 self.current = self.frames[0].value().clone();
111 return;
112 }
113
114 let duration = self.duration().as_millis();
115 if duration <= 0.0 {
116 self.finish();
117 return;
118 }
119
120 let active_elapsed = elapsed - delay;
121 if let Some(count) = self.timing.iterations().finite_count() {
122 let total = duration * f64::from(count);
123 if active_elapsed >= total {
124 self.finish();
125 return;
126 }
127 }
128
129 #[allow(clippy::cast_possible_truncation)]
130 #[allow(clippy::cast_sign_loss)]
131 let iteration = (active_elapsed / duration).floor() as u32;
132
133 let raw_progress = (active_elapsed % duration) / duration;
134 let progress = self
135 .timing
136 .direction()
137 .sample_progress(iteration, raw_progress);
138 self.current = self.value_at(duration * progress);
139 }
140
141 fn value_at(&self, time_ms: f64) -> T {
142 if time_ms <= self.frames[0].time() {
143 return self.frames[0].value().clone();
144 }
145
146 let upper = self.frames.partition_point(|frame| frame.time() <= time_ms);
147 if upper >= self.frames.len() {
148 return self.frames[self.frames.len() - 1].value().clone();
149 }
150
151 let from = &self.frames[upper - 1];
152 let to = &self.frames[upper];
153 let span = (to.time() - from.time()).max(f64::EPSILON);
154 let progress = ((time_ms - from.time()) / span).clamp(0.0, 1.0);
155
156 #[allow(clippy::cast_possible_truncation)]
157 let eased = from.easing().value(progress as f32);
158
159 T::interpolate(from.value(), to.value(), eased)
160 }
161}
162
163impl<T: Animatable> Animation<T> for Keyframes<T> {
164 fn value(&self) -> &T {
165 &self.current
166 }
167
168 fn state(&self) -> AnimationState {
169 self.state
170 }
171
172 fn duration(&self) -> Option<Duration> {
173 self.timing.total_duration()
174 }
175
176 fn tick(&mut self, delta: Duration) {
177 if self.state != AnimationState::Running {
178 return;
179 }
180 self.elapsed += delta;
181 self.sample();
182 }
183
184 fn advance(&mut self, delta: Duration) -> Duration {
185 if self.state != AnimationState::Running {
186 return delta;
187 }
188
189 let Some(total) = self.timing.total_duration() else {
190 self.tick(delta);
191 return Duration::ZERO;
192 };
193 let remaining = total.saturating_sub(self.elapsed);
194 let consumed = delta.min(remaining);
195 self.tick(consumed);
196 delta.saturating_sub(consumed)
197 }
198
199 fn pause(&mut self) {
200 if self.state == AnimationState::Running {
201 self.state = AnimationState::Paused;
202 }
203 }
204
205 fn resume(&mut self) {
206 if self.state == AnimationState::Paused {
207 self.state = AnimationState::Running;
208 }
209 }
210
211 fn cancel(&mut self) {
212 if matches!(self.state, AnimationState::Running | AnimationState::Paused) {
213 self.state = AnimationState::Canceled;
214 }
215 }
216
217 fn seek(&mut self, progress: f32) {
218 let progress = if progress.is_nan() {
219 0.0
220 } else {
221 progress.clamp(0.0, 1.0)
222 };
223 self.state = AnimationState::Running;
224 let total = self
225 .timing
226 .total_duration()
227 .unwrap_or(self.duration())
228 .as_millis();
229 self.elapsed = Duration::from_millis(total * f64::from(progress));
230 self.sample();
231 }
232
233 fn finish(&mut self) {
234 let progress = self
235 .timing
236 .iterations()
237 .finite_count()
238 .map_or(self.timing.direction().sample_progress(1, 1.0), |count| {
239 self.timing.direction().end_progress(count)
240 });
241 self.current = self.value_at(self.duration().as_millis() * progress);
242 self.state = AnimationState::Completed;
243 }
244}