Skip to main content

aura_anim_iced/timeline/
track.rs

1use crate::{
2    keyframes::{Keyframes, KeyframesBuilder},
3    prelude::PropertyValueKind,
4    property::{PropertyEntry, PropertySnapshot, PropertySpec},
5    timing::{Duration, Easing, Timing},
6};
7
8/// A keyframe track placed in a timeline.
9#[derive(Debug, Clone, PartialEq)]
10pub struct Track {
11    name: Option<String>,
12    keyframes: Keyframes,
13}
14
15/// A builder for creating a single-property timeline track.
16#[derive(Debug, Clone, PartialEq)]
17pub struct PropertyTrackBuilder<K: PropertyValueKind> {
18    property: PropertySpec<K>,
19    builder: KeyframesBuilder,
20}
21
22impl Track {
23    /// Creates a track from keyframes.
24    #[must_use]
25    pub const fn new(keyframes: Keyframes) -> Self {
26        Self {
27            name: None,
28            keyframes,
29        }
30    }
31
32    /// Creates a track with an initial value for `property` at offset `0.0`.
33    #[must_use]
34    pub fn from<K: PropertyValueKind>(
35        property: PropertySpec<K>,
36        value: K::Inner,
37    ) -> PropertyTrackBuilder<K> {
38        PropertyTrackBuilder {
39            property,
40            builder: KeyframesBuilder::new().at(0.0, (property, value)),
41        }
42    }
43
44    /// Sets the track name.
45    #[must_use]
46    pub fn with_name(mut self, name: impl Into<String>) -> Self {
47        self.name = Some(name.into());
48        self
49    }
50
51    /// Returns the track name.
52    #[must_use]
53    pub fn name(&self) -> Option<&str> {
54        self.name.as_deref()
55    }
56
57    /// Returns the track keyframes.
58    #[must_use]
59    pub const fn keyframes(&self) -> &Keyframes {
60        &self.keyframes
61    }
62
63    /// Returns the finite total duration of the track, or `None` for infinite timing.
64    #[must_use]
65    pub fn total_duration(&self) -> Option<Duration> {
66        self.keyframes.timing().total_duration()
67    }
68
69    /// Samples this track at local timeline `offset`.
70    #[must_use]
71    pub fn sample_at(&self, offset: impl Into<Duration>) -> Option<PropertySnapshot> {
72        let mut snapshot = PropertySnapshot::with_capacity(self.keyframes.track_count());
73
74        if self.sample_into(offset, &mut snapshot) {
75            Some(snapshot)
76        } else {
77            None
78        }
79    }
80
81    pub(crate) fn sample_into(
82        &self,
83        offset: impl Into<Duration>,
84        output: &mut PropertySnapshot,
85    ) -> bool {
86        let timing = self
87            .keyframes
88            .timing()
89            .normalize_elapsed(offset.into().as_millis());
90
91        if !timing.has_sample() {
92            output.clear();
93            return false;
94        }
95
96        #[allow(
97            clippy::cast_possible_truncation,
98            reason = "Normalized keyframe offsets are stored as f32 throughout the keyframe module."
99        )]
100        self.keyframes
101            .sample_into(timing.iteration_progress as f32, output)
102    }
103
104    /// Samples the final keyframe state for this track.
105    #[must_use]
106    pub fn completion_snapshot(&self) -> Option<PropertySnapshot> {
107        self.keyframes.sample_completion()
108    }
109}
110
111impl<K: PropertyValueKind> PropertyTrackBuilder<K> {
112    /// Creates an empty builder for `property`.
113    #[must_use]
114    pub fn new(property: PropertySpec<K>) -> Self {
115        Self {
116            property,
117            builder: KeyframesBuilder::default(),
118        }
119    }
120
121    /// Inserts the final value and returns the completed track.
122    #[must_use]
123    pub fn to(self, value: K::Inner) -> Self {
124        let property = self.property;
125        let builder = self.builder.at(1.0, (property, value));
126
127        Self { property, builder }
128    }
129
130    /// Inserts a keyframe at the end of the track with the given property and value.
131    #[must_use]
132    pub fn keyframe_at_end(mut self, property: PropertyEntry) -> Self {
133        self.builder = self.builder.at(1.0, vec![property]);
134        self
135    }
136
137    /// Sets the active duration while preserving the rest of the timing configuration.
138    #[must_use]
139    pub fn duration(mut self, duration: impl Into<Duration>) -> Self {
140        let timing = *self.builder.timing();
141
142        self.builder = self
143            .builder
144            .with_timing(with_duration(timing, duration.into()));
145        self
146    }
147
148    /// Sets the easing curve on the track timing.
149    #[must_use]
150    pub fn easing(mut self, easing: Easing) -> Self {
151        let timing = self.builder.timing().with_easing(easing);
152
153        self.builder = self.builder.with_timing(timing);
154        self
155    }
156
157    /// Finishes the builder and returns a timeline track.
158    #[must_use]
159    pub fn finish(self) -> Track {
160        Track::new(self.builder.finish())
161    }
162}
163
164impl<K: PropertyValueKind> From<PropertyTrackBuilder<K>> for Track {
165    fn from(value: PropertyTrackBuilder<K>) -> Self {
166        value.finish()
167    }
168}
169
170impl From<Keyframes> for Track {
171    fn from(value: Keyframes) -> Self {
172        Self::new(value)
173    }
174}
175
176fn with_duration(timing: Timing, duration: Duration) -> Timing {
177    Timing::new(duration.as_millis())
178        .with_delay(timing.delay())
179        .with_direction(timing.direction())
180        .with_fill_mode(timing.fill_mode())
181        .with_easing(timing.easing())
182        .with_iterations(timing.iterations())
183        .with_playback_rate(timing.playback_rate())
184}