Skip to main content

aura_anim_iced/
runtime.rs

1//! Runtime storage for active Iced-first animations.
2
3mod clock;
4mod entry;
5mod handle;
6mod policy;
7mod registration;
8mod registry;
9mod source;
10mod target;
11#[cfg(test)]
12mod tests;
13mod tick;
14
15pub use clock::{AnimationClock, SystemClock};
16pub use entry::AnimationPlaybackState;
17pub use handle::AnimationHandle;
18pub use policy::TickPolicy;
19pub use registration::AnimationRegistration;
20pub use target::{AnimationTargetId, TargetedPropertySnapshot};
21pub use tick::AnimationTick;
22
23use crate::runtime::clock::TestClock;
24use crate::runtime::{
25    entry::ActiveAnimation,
26    registry::AnimationRegistry,
27    source::{AnimationSource, PropertyTransitionSource},
28};
29use crate::{
30    keyframes::Keyframes,
31    property::{PropertySnapshot, PropertySpec, PropertyValueKind},
32    timeline::Timeline,
33    timing::{Duration, Timing},
34};
35
36/// Runtime state owned by an Iced application.
37///
38/// # Example
39///
40/// ```
41/// use aura_anim_iced::{
42///     AnimationRuntime, AnimationTargetId, KeyframesBuilder, Timing, property,
43/// };
44///
45/// let mut runtime = AnimationRuntime::new();
46/// let target = AnimationTargetId::new();
47///
48/// let registration = runtime.register_keyframes(
49///     target,
50///     KeyframesBuilder::new()
51///         .with_timing(Timing::new(100.0))
52///         .at(0.0, (property::OPACITY, 0.0))
53///         .at(1.0, (property::OPACITY, 1.0))
54///         .finish(),
55/// );
56///
57/// assert!(runtime.should_subscribe());
58/// ```
59#[derive(Debug, Clone)]
60pub struct AnimationRuntime<C = SystemClock> {
61    registry: AnimationRegistry,
62    clock: C,
63    motion_policy: TickPolicy,
64}
65
66impl AnimationRuntime<SystemClock> {
67    /// Creates an empty runtime using a monotonic system clock.
68    #[must_use]
69    pub fn new() -> Self {
70        Self::with_clock(SystemClock::new())
71    }
72}
73
74impl Default for AnimationRuntime<SystemClock> {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl AnimationRuntime<TestClock> {
81    /// Creates an empty runtime with a deterministic test clock at zero.
82    #[must_use]
83    pub fn testing() -> Self {
84        Self::with_clock(TestClock::new())
85    }
86
87    /// Returns mutable access to the runtime clock.
88    ///
89    /// This is primarily useful for deterministic tests and custom clock
90    /// integrations.
91    pub const fn clock_mut(&mut self) -> &mut TestClock {
92        &mut self.clock
93    }
94}
95
96impl<C: AnimationClock> AnimationRuntime<C> {
97    /// Creates an empty runtime with a custom clock.
98    #[must_use]
99    pub fn with_clock(clock: C) -> Self {
100        Self {
101            registry: AnimationRegistry::new(),
102            clock,
103            motion_policy: TickPolicy::default(),
104        }
105    }
106
107    /// Registers an erased animation source and returns its initial runtime output.
108    pub(crate) fn register_target(
109        &mut self,
110        target: AnimationTargetId,
111        source: impl Into<AnimationSource>,
112    ) -> AnimationRegistration {
113        let handle = AnimationHandle::new();
114        let now = self.clock.now();
115        let source = source.into();
116        let initial_snapshot = source.sample_at(Duration::ZERO);
117        let mut entry = ActiveAnimation::new(handle, target, source, now);
118
119        entry.set_last_snapshot(initial_snapshot.clone());
120
121        if entry.source().total_duration() == Some(Duration::ZERO) {
122            let completion_snapshot = entry.source().completion_snapshot();
123
124            entry.set_last_snapshot(completion_snapshot);
125            entry.mark_completed(now);
126        }
127
128        let registration = AnimationRegistration::from_entry(&entry);
129        self.registry.insert(target, entry);
130
131        #[cfg(feature = "tracing")]
132        tracing::debug!(
133            target: "aura_anim_iced::runtime",
134            handle = registration.handle().id(),
135            target_id = ?target,
136            state = ?registration.state(),
137            completed = registration.completed_at().is_some(),
138            "registered animation"
139        );
140
141        registration
142    }
143
144    /// Registers keyframes and returns their initial runtime output.
145    pub fn register_keyframes(
146        &mut self,
147        target: AnimationTargetId,
148        keyframes: Keyframes,
149    ) -> AnimationRegistration {
150        self.register_target(target, keyframes)
151    }
152
153    pub(crate) fn register_property_transition<K>(
154        &mut self,
155        target: AnimationTargetId,
156        property: PropertySpec<K>,
157        timing: Timing,
158        from: K::Inner,
159        to: K::Inner,
160    ) -> AnimationRegistration
161    where
162        K: PropertyValueKind,
163    {
164        self.register_target(
165            target,
166            PropertyTransitionSource::new(property.raw(), K::wrap(from), K::wrap(to), timing),
167        )
168    }
169
170    /// Registers a timeline and returns its initial runtime output.
171    pub fn register_timeline(
172        &mut self,
173        target: AnimationTargetId,
174        timeline: Timeline,
175    ) -> AnimationRegistration {
176        self.register_target(target, timeline)
177    }
178
179    pub(crate) fn register_timeline_arc(
180        &mut self,
181        target: AnimationTargetId,
182        timeline: Arc<Timeline>,
183    ) -> AnimationRegistration {
184        self.register_target(target, timeline)
185    }
186
187    /// Advances active animations and returns a view-ready aggregated snapshot.
188    pub fn tick(&mut self) -> AnimationTick {
189        let now = self.clock.now();
190
191        tick::tick_registry(&mut self.registry, now)
192    }
193
194    /// Advances active animations into a reusable tick output.
195    pub fn tick_into(&mut self, output: &mut AnimationTick) {
196        let now = self.clock.now();
197
198        tick::tick_registry_into(&mut self.registry, now, output);
199    }
200
201    /// Cancels and removes all active animations registered for `target`.
202    pub fn cancel_target(&mut self, target: AnimationTargetId) {
203        self.registry.cancel_target(target);
204    }
205
206    /// Seeks all active animations registered for `target`.
207    pub fn seek_target(&mut self, target: AnimationTargetId, pos: Duration) {
208        self.registry.seek_target(target, pos, self.clock.now());
209    }
210
211    /// Pauses all active animations registered for `target`.
212    pub fn pause_target(&mut self, target: AnimationTargetId) {
213        self.registry.pause_target(target, self.clock.now());
214    }
215
216    /// Cancels and removes one animation when `handle` belongs to `target`.
217    ///
218    /// Returns `true` when an entry was removed.
219    pub fn cancel(&mut self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
220        self.registry.cancel(target, handle)
221    }
222
223    /// Seeks one animation when `handle` belongs to `target`.
224    ///
225    /// Returns `true` when an entry was found and updated.
226    pub fn seek(
227        &mut self,
228        target: AnimationTargetId,
229        handle: AnimationHandle,
230        pos: Duration,
231    ) -> bool {
232        self.registry.seek(target, handle, pos, self.clock.now())
233    }
234
235    /// Pauses one animation when `handle` belongs to `target`.
236    ///
237    /// Returns `true` when an entry was found and updated.
238    pub fn pause(&mut self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
239        self.registry.pause(target, handle, self.clock.now())
240    }
241
242    pub(crate) fn last_properties(
243        &self,
244        target: AnimationTargetId,
245        handle: AnimationHandle,
246    ) -> Option<&PropertySnapshot> {
247        let entry = self.registry.get_by_handle(handle)?;
248
249        if entry.target() != target {
250            return None;
251        }
252
253        entry.last_snapshot()
254    }
255
256    pub(crate) fn contains(&self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
257        self.registry
258            .get_by_handle(handle)
259            .is_some_and(|entry| entry.target() == target)
260    }
261}
262
263impl<C> AnimationRuntime<C> {
264    /// Returns the runtime clock.
265    #[must_use]
266    pub const fn clock(&self) -> &C {
267        &self.clock
268    }
269
270    /// Returns the current motion policy.
271    #[must_use]
272    pub const fn motion_policy(&self) -> TickPolicy {
273        self.motion_policy
274    }
275
276    /// Replaces the current motion policy.
277    pub const fn set_motion_policy(&mut self, motion_policy: TickPolicy) {
278        self.motion_policy = motion_policy;
279    }
280
281    /// Returns the number of active animation entries.
282    #[must_use]
283    pub fn active_count(&self) -> usize {
284        self.registry.active_count()
285    }
286
287    /// Returns whether the runtime has no active animation entries.
288    #[must_use]
289    pub fn is_idle(&self) -> bool {
290        self.active_count() == 0
291    }
292
293    /// Returns whether the runtime has entries that should receive animation ticks.
294    #[must_use]
295    pub fn should_tick(&self) -> bool {
296        self.registry
297            .entries()
298            .iter()
299            .any(ActiveAnimation::needs_tick)
300    }
301
302    /// Returns whether an Iced subscription should keep producing animation ticks.
303    #[must_use]
304    pub fn should_subscribe(&self) -> bool {
305        self.should_tick()
306    }
307}
308use std::sync::Arc;