Skip to main content

aura_anim_core/
field.rs

1//! Type-safe field descriptors and independent struct field animations.
2
3use crate::{Animatable, Animation, AnimationState, IntoMotionAnimation, timing::Duration};
4
5/// Describes an animatable field on `S`.
6///
7/// `#[derive(Animatable)]` generates one descriptor constant per struct field.
8/// Descriptors can also be created manually with [`Field::new`].
9pub struct Field<S, V> {
10    name: &'static str,
11    get: fn(&S) -> &V,
12    get_mut: fn(&mut S) -> &mut V,
13}
14
15impl<S, V> Copy for Field<S, V> {}
16
17impl<S, V> Clone for Field<S, V> {
18    fn clone(&self) -> Self {
19        *self
20    }
21}
22
23impl<S, V> Field<S, V> {
24    /// Creates a field descriptor from immutable and mutable accessors.
25    #[must_use]
26    pub const fn new(name: &'static str, get: fn(&S) -> &V, get_mut: fn(&mut S) -> &mut V) -> Self {
27        Self { name, get, get_mut }
28    }
29
30    /// Returns the field name used to identify duplicate registrations.
31    #[must_use]
32    pub const fn name(self) -> &'static str {
33        self.name
34    }
35
36    fn get(self, value: &S) -> &V {
37        (self.get)(value)
38    }
39
40    fn set(self, value: &mut S, field_value: V) {
41        *(self.get_mut)(value) = field_value;
42    }
43}
44
45trait FieldAnimationFactory<S>: 'static {
46    fn name(&self) -> &'static str;
47    fn build(self: Box<Self>, initial: &S) -> Box<dyn FieldAnimation<S>>;
48}
49
50struct TypedFieldAnimationFactory<S, V, Source, Kind> {
51    field: Field<S, V>,
52    source: Source,
53    marker: std::marker::PhantomData<fn() -> Kind>,
54}
55
56impl<S, V, Source, Kind> FieldAnimationFactory<S> for TypedFieldAnimationFactory<S, V, Source, Kind>
57where
58    S: Animatable,
59    V: Animatable,
60    Source: IntoMotionAnimation<V, Kind> + 'static,
61    Kind: 'static,
62{
63    fn name(&self) -> &'static str {
64        self.field.name()
65    }
66
67    fn build(self: Box<Self>, initial: &S) -> Box<dyn FieldAnimation<S>> {
68        let Self { field, source, .. } = *self;
69        let animation = source.into_motion_animation(field.get(initial));
70
71        Box::new(TypedFieldAnimation { field, animation })
72    }
73}
74
75trait FieldAnimation<S>: 'static {
76    fn value_into(&self, output: &mut S);
77    fn state(&self) -> AnimationState;
78    fn duration(&self) -> Option<Duration>;
79    fn advance(&mut self, delta: Duration) -> Duration;
80    fn pause(&mut self);
81    fn resume(&mut self);
82    fn cancel(&mut self);
83    fn seek(&mut self, progress: f32);
84    fn finish(&mut self);
85    fn set_rate(&mut self, rate: f64);
86}
87
88struct TypedFieldAnimation<S, V, A> {
89    field: Field<S, V>,
90    animation: A,
91}
92
93impl<S, V, A> FieldAnimation<S> for TypedFieldAnimation<S, V, A>
94where
95    S: Animatable,
96    V: Animatable,
97    A: Animation<V>,
98{
99    fn value_into(&self, output: &mut S) {
100        self.field.set(output, self.animation.value().clone());
101    }
102
103    fn state(&self) -> AnimationState {
104        self.animation.state()
105    }
106
107    fn duration(&self) -> Option<Duration> {
108        self.animation.duration()
109    }
110
111    fn advance(&mut self, delta: Duration) -> Duration {
112        self.animation.advance(delta)
113    }
114
115    fn pause(&mut self) {
116        self.animation.pause();
117    }
118
119    fn resume(&mut self) {
120        self.animation.resume();
121    }
122
123    fn cancel(&mut self) {
124        self.animation.cancel();
125    }
126
127    fn seek(&mut self, progress: f32) {
128        self.animation.seek(progress);
129    }
130
131    fn finish(&mut self) {
132        self.animation.finish();
133    }
134
135    fn set_rate(&mut self, rate: f64) {
136        self.animation.set_rate(rate);
137    }
138}
139
140/// A deferred plan for animating fields of a struct independently.
141///
142/// The animation factory receives the field's currently sampled value when
143/// the plan is played, so interrupted field animations continue without
144/// jumping to an earlier origin.
145pub struct Fields<S: Animatable> {
146    factories: Vec<Box<dyn FieldAnimationFactory<S>>>,
147}
148
149impl<S: Animatable> Fields<S> {
150    /// Creates an empty field animation plan.
151    #[must_use]
152    pub fn new() -> Self {
153        Self {
154            factories: Vec::new(),
155        }
156    }
157
158    /// Adds an independently animated field.
159    ///
160    /// `source` may be an animation, a `|from| ...` factory, or a deferred
161    /// target factory such as [`crate::tween_to`] or [`crate::spring_to`].
162    /// Registering the same field name again replaces the earlier animation.
163    #[must_use]
164    pub fn animate<V, Source, Kind>(mut self, field: Field<S, V>, source: Source) -> Self
165    where
166        V: Animatable,
167        Source: IntoMotionAnimation<V, Kind> + 'static,
168        Kind: 'static,
169    {
170        let factory = Box::new(TypedFieldAnimationFactory {
171            field,
172            source,
173            marker: std::marker::PhantomData::<fn() -> Kind>,
174        });
175        if let Some(existing) = self.factories.iter_mut().find(|f| f.name() == field.name()) {
176            *existing = factory;
177        } else {
178            self.factories.push(factory);
179        }
180        self
181    }
182
183    /// Returns the number of independently animated fields.
184    #[must_use]
185    pub fn len(&self) -> usize {
186        self.factories.len()
187    }
188
189    /// Returns whether no fields have been registered.
190    #[must_use]
191    pub fn is_empty(&self) -> bool {
192        self.factories.is_empty()
193    }
194
195    pub(crate) fn build(self, initial: &S) -> FieldsAnimation<S> {
196        let children: Vec<_> = self
197            .factories
198            .into_iter()
199            .map(|factory| factory.build(initial))
200            .collect();
201
202        let state = if children.is_empty() {
203            AnimationState::Idle
204        } else {
205            AnimationState::Running
206        };
207
208        let mut animation = FieldsAnimation {
209            current: initial.clone(),
210            children,
211            state,
212        };
213        animation.sample();
214        animation
215    }
216}
217
218impl<S: Animatable> Default for Fields<S> {
219    fn default() -> Self {
220        Self::new()
221    }
222}
223
224/// Creates an empty field animation plan.
225#[must_use]
226pub fn fields<S: Animatable>() -> Fields<S> {
227    Fields::new()
228}
229
230/// An animation that runs independent field animations in parallel.
231pub struct FieldsAnimation<S: Animatable> {
232    current: S,
233    children: Vec<Box<dyn FieldAnimation<S>>>,
234    state: AnimationState,
235}
236
237impl<S: Animatable> FieldsAnimation<S> {
238    fn sample(&mut self) {
239        for child in &self.children {
240            child.value_into(&mut self.current);
241        }
242    }
243}
244
245impl<S: Animatable> Animation<S> for FieldsAnimation<S> {
246    fn value(&self) -> &S {
247        &self.current
248    }
249
250    fn state(&self) -> AnimationState {
251        self.state
252    }
253
254    fn duration(&self) -> Option<Duration> {
255        self.children
256            .iter()
257            .map(|child| child.duration())
258            .try_fold(Duration::ZERO, |longest, duration| {
259                duration.map(|duration| longest.max(duration))
260            })
261    }
262
263    fn tick(&mut self, delta: Duration) {
264        self.advance(delta);
265    }
266
267    fn advance(&mut self, delta: Duration) -> Duration {
268        if self.state != AnimationState::Running {
269            return delta;
270        }
271        if self.children.is_empty() {
272            self.state = AnimationState::Completed;
273            return delta;
274        }
275
276        let mut overflow = delta;
277        let mut completed = true;
278        for child in &mut self.children {
279            overflow = overflow.min(child.advance(delta));
280            completed &= child.state() == AnimationState::Completed;
281        }
282        self.sample();
283
284        if completed {
285            self.state = AnimationState::Completed;
286            overflow
287        } else {
288            Duration::ZERO
289        }
290    }
291
292    fn pause(&mut self) {
293        if self.state == AnimationState::Running {
294            for child in &mut self.children {
295                child.pause();
296            }
297            self.state = AnimationState::Paused;
298        }
299    }
300
301    fn resume(&mut self) {
302        if self.state == AnimationState::Paused {
303            for child in &mut self.children {
304                child.resume();
305            }
306            self.state = AnimationState::Running;
307        }
308    }
309
310    fn cancel(&mut self) {
311        if matches!(self.state, AnimationState::Running | AnimationState::Paused) {
312            for child in &mut self.children {
313                child.cancel();
314            }
315            self.state = AnimationState::Canceled;
316        }
317    }
318
319    fn seek(&mut self, progress: f32) {
320        let progress = if progress.is_nan() {
321            0.0
322        } else {
323            progress.clamp(0.0, 1.0)
324        };
325        let duration = self.duration();
326
327        for child in &mut self.children {
328            #[allow(clippy::cast_possible_truncation)]
329            let child_progress = match (duration, child.duration()) {
330                (Some(total), Some(child_duration)) if !child_duration.is_zero() => {
331                    (total.as_secs() * f64::from(progress) / child_duration.as_secs())
332                        .clamp(0.0, 1.0) as f32
333                }
334                (Some(_), Some(_)) => 1.0,
335                _ => progress,
336            };
337            child.seek(child_progress);
338        }
339        self.sample();
340        self.state = if progress >= 1.0 {
341            AnimationState::Completed
342        } else {
343            AnimationState::Running
344        };
345    }
346
347    fn finish(&mut self) {
348        for child in &mut self.children {
349            child.finish();
350        }
351        self.sample();
352        self.state = AnimationState::Completed;
353    }
354
355    fn set_rate(&mut self, rate: f64) {
356        for child in &mut self.children {
357            child.set_rate(rate);
358        }
359    }
360
361    fn into_value(self: Box<Self>) -> S {
362        self.current
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::{Field, fields};
369    use crate::{
370        Animation, AnimationState, Tween,
371        timing::{Duration, Timing},
372    };
373    use float_cmp::assert_approx_eq;
374
375    #[derive(Clone)]
376    struct Position {
377        x: f32,
378        y: f32,
379        fixed: f32,
380    }
381
382    impl crate::Interpolate for Position {
383        fn interpolate_progress(
384            from: &Self,
385            to: &Self,
386            progress: crate::InterpolationProgress,
387        ) -> Self {
388            Self {
389                x: f32::interpolate_progress(&from.x, &to.x, progress),
390                y: f32::interpolate_progress(&from.y, &to.y, progress),
391                fixed: f32::interpolate_progress(&from.fixed, &to.fixed, progress),
392            }
393        }
394    }
395
396    const X: Field<Position, f32> = Field::new("x", |value| &value.x, |value| &mut value.x);
397    const Y: Field<Position, f32> = Field::new("y", |value| &value.y, |value| &mut value.y);
398
399    #[test]
400    fn fields_run_independently_and_preserve_unregistered_values() {
401        let initial = Position {
402            x: 0.0,
403            y: 10.0,
404            fixed: 7.0,
405        };
406        let mut animation = fields()
407            .animate(X, |from| Tween::between(from, 100.0, Timing::new(100.0)))
408            .animate(Y, |from| Tween::between(from, 210.0, Timing::new(200.0)))
409            .build(&initial);
410
411        animation.tick(Duration::from_millis(100.0));
412
413        assert_approx_eq!(f32, animation.value().x, 100.0);
414        assert_approx_eq!(f32, animation.value().y, 110.0);
415        assert_approx_eq!(f32, animation.value().fixed, 7.0);
416        assert_eq!(animation.state(), AnimationState::Running);
417    }
418
419    #[test]
420    fn duplicate_field_registration_keeps_the_last_animation() {
421        let initial = Position {
422            x: 0.0,
423            y: 0.0,
424            fixed: 0.0,
425        };
426        let mut animation = fields()
427            .animate(X, |from| Tween::between(from, 10.0, Timing::new(100.0)))
428            .animate(X, |from| Tween::between(from, 20.0, Timing::new(100.0)))
429            .build(&initial);
430
431        assert_eq!(animation.children.len(), 1);
432        animation.finish();
433        assert_approx_eq!(f32, animation.value().x, 20.0);
434    }
435
436    #[test]
437    fn unregistered_fields_retain_their_original_value() {
438        let initial = Position {
439            x: 0.0,
440            y: 0.0,
441            fixed: 7.0,
442        };
443        let mut animation = fields()
444            .animate(Y, |from| Tween::between(from, 10.0, Timing::new(100.0)))
445            .build(&initial);
446
447        animation.tick(Duration::from_millis(100.0));
448        assert_eq!(animation.state(), AnimationState::Completed);
449        assert_approx_eq!(f32, animation.value().x, 0.0);
450        assert_approx_eq!(f32, animation.value().y, 10.0);
451        assert_approx_eq!(f32, animation.value().fixed, 7.0);
452    }
453}