aura-anim-iced 0.2.1

Iced-first animation primitives.
Documentation
//! Runtime storage for active Iced-first animations.

mod clock;
mod entry;
mod handle;
mod policy;
mod registration;
mod registry;
mod source;
mod target;
#[cfg(test)]
mod tests;
mod tick;

pub use clock::{AnimationClock, SystemClock};
pub use entry::AnimationPlaybackState;
pub use handle::AnimationHandle;
pub use policy::TickPolicy;
pub use registration::AnimationRegistration;
pub use target::{AnimationTargetId, TargetedPropertySnapshot};
pub use tick::AnimationTick;

use crate::runtime::clock::TestClock;
use crate::runtime::{
    entry::ActiveAnimation,
    registry::AnimationRegistry,
    source::{AnimationSource, PropertyTransitionSource},
};
use crate::{
    keyframes::Keyframes,
    property::{PropertySnapshot, PropertySpec, PropertyValueKind},
    timeline::Timeline,
    timing::{Duration, Timing},
};

/// Runtime state owned by an Iced application.
///
/// # Example
///
/// ```
/// use aura_anim_iced::{
///     AnimationRuntime, AnimationTargetId, KeyframesBuilder, Timing, property,
/// };
///
/// let mut runtime = AnimationRuntime::new();
/// let target = AnimationTargetId::new();
///
/// let registration = runtime.register_keyframes(
///     target,
///     KeyframesBuilder::new()
///         .with_timing(Timing::new(100.0))
///         .at(0.0, (property::OPACITY, 0.0))
///         .at(1.0, (property::OPACITY, 1.0))
///         .finish(),
/// );
///
/// assert!(runtime.should_subscribe());
/// ```
#[derive(Debug, Clone)]
pub struct AnimationRuntime<C = SystemClock> {
    registry: AnimationRegistry,
    clock: C,
    motion_policy: TickPolicy,
}

impl AnimationRuntime<SystemClock> {
    /// Creates an empty runtime using a monotonic system clock.
    #[must_use]
    pub fn new() -> Self {
        Self::with_clock(SystemClock::new())
    }
}

impl Default for AnimationRuntime<SystemClock> {
    fn default() -> Self {
        Self::new()
    }
}

impl AnimationRuntime<TestClock> {
    /// Creates an empty runtime with a deterministic test clock at zero.
    #[must_use]
    pub fn testing() -> Self {
        Self::with_clock(TestClock::new())
    }

    /// Returns mutable access to the runtime clock.
    ///
    /// This is primarily useful for deterministic tests and custom clock
    /// integrations.
    pub const fn clock_mut(&mut self) -> &mut TestClock {
        &mut self.clock
    }
}

impl<C: AnimationClock> AnimationRuntime<C> {
    /// Creates an empty runtime with a custom clock.
    #[must_use]
    pub fn with_clock(clock: C) -> Self {
        Self {
            registry: AnimationRegistry::new(),
            clock,
            motion_policy: TickPolicy::default(),
        }
    }

    /// Registers an erased animation source and returns its initial runtime output.
    pub(crate) fn register_target(
        &mut self,
        target: AnimationTargetId,
        source: impl Into<AnimationSource>,
    ) -> AnimationRegistration {
        let handle = AnimationHandle::new();
        let now = self.clock.now();
        let source = source.into();
        let initial_snapshot = source.sample_at(Duration::ZERO);
        let mut entry = ActiveAnimation::new(handle, target, source, now);

        entry.set_last_snapshot(initial_snapshot.clone());

        if entry.source().total_duration() == Some(Duration::ZERO) {
            let completion_snapshot = entry.source().completion_snapshot();

            entry.set_last_snapshot(completion_snapshot);
            entry.mark_completed(now);
        }

        let registration = AnimationRegistration::from_entry(&entry);
        self.registry.insert(target, entry);

        #[cfg(feature = "tracing")]
        tracing::debug!(
            target: "aura_anim_iced::runtime",
            handle = registration.handle().id(),
            target_id = ?target,
            state = ?registration.state(),
            completed = registration.completed_at().is_some(),
            "registered animation"
        );

        registration
    }

    /// Registers keyframes and returns their initial runtime output.
    pub fn register_keyframes(
        &mut self,
        target: AnimationTargetId,
        keyframes: Keyframes,
    ) -> AnimationRegistration {
        self.register_target(target, keyframes)
    }

    pub(crate) fn register_property_transition<K>(
        &mut self,
        target: AnimationTargetId,
        property: PropertySpec<K>,
        timing: Timing,
        from: K::Inner,
        to: K::Inner,
    ) -> AnimationRegistration
    where
        K: PropertyValueKind,
    {
        self.register_target(
            target,
            PropertyTransitionSource::new(property.raw(), K::wrap(from), K::wrap(to), timing),
        )
    }

    /// Registers a timeline and returns its initial runtime output.
    pub fn register_timeline(
        &mut self,
        target: AnimationTargetId,
        timeline: Timeline,
    ) -> AnimationRegistration {
        self.register_target(target, timeline)
    }

    pub(crate) fn register_timeline_arc(
        &mut self,
        target: AnimationTargetId,
        timeline: Arc<Timeline>,
    ) -> AnimationRegistration {
        self.register_target(target, timeline)
    }

    /// Advances active animations and returns a view-ready aggregated snapshot.
    pub fn tick(&mut self) -> AnimationTick {
        let now = self.clock.now();

        tick::tick_registry(&mut self.registry, now)
    }

    /// Advances active animations into a reusable tick output.
    pub fn tick_into(&mut self, output: &mut AnimationTick) {
        let now = self.clock.now();

        tick::tick_registry_into(&mut self.registry, now, output);
    }

    /// Cancels and removes all active animations registered for `target`.
    pub fn cancel_target(&mut self, target: AnimationTargetId) {
        self.registry.cancel_target(target);
    }

    /// Seeks all active animations registered for `target`.
    pub fn seek_target(&mut self, target: AnimationTargetId, pos: Duration) {
        self.registry.seek_target(target, pos, self.clock.now());
    }

    /// Pauses all active animations registered for `target`.
    pub fn pause_target(&mut self, target: AnimationTargetId) {
        self.registry.pause_target(target, self.clock.now());
    }

    /// Cancels and removes one animation when `handle` belongs to `target`.
    ///
    /// Returns `true` when an entry was removed.
    pub fn cancel(&mut self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
        self.registry.cancel(target, handle)
    }

    /// Seeks one animation when `handle` belongs to `target`.
    ///
    /// Returns `true` when an entry was found and updated.
    pub fn seek(
        &mut self,
        target: AnimationTargetId,
        handle: AnimationHandle,
        pos: Duration,
    ) -> bool {
        self.registry.seek(target, handle, pos, self.clock.now())
    }

    /// Pauses one animation when `handle` belongs to `target`.
    ///
    /// Returns `true` when an entry was found and updated.
    pub fn pause(&mut self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
        self.registry.pause(target, handle, self.clock.now())
    }

    pub(crate) fn last_properties(
        &self,
        target: AnimationTargetId,
        handle: AnimationHandle,
    ) -> Option<&PropertySnapshot> {
        let entry = self.registry.get_by_handle(handle)?;

        if entry.target() != target {
            return None;
        }

        entry.last_snapshot()
    }

    pub(crate) fn contains(&self, target: AnimationTargetId, handle: AnimationHandle) -> bool {
        self.registry
            .get_by_handle(handle)
            .is_some_and(|entry| entry.target() == target)
    }
}

impl<C> AnimationRuntime<C> {
    /// Returns the runtime clock.
    #[must_use]
    pub const fn clock(&self) -> &C {
        &self.clock
    }

    /// Returns the current motion policy.
    #[must_use]
    pub const fn motion_policy(&self) -> TickPolicy {
        self.motion_policy
    }

    /// Replaces the current motion policy.
    pub const fn set_motion_policy(&mut self, motion_policy: TickPolicy) {
        self.motion_policy = motion_policy;
    }

    /// Returns the number of active animation entries.
    #[must_use]
    pub fn active_count(&self) -> usize {
        self.registry.active_count()
    }

    /// Returns whether the runtime has no active animation entries.
    #[must_use]
    pub fn is_idle(&self) -> bool {
        self.active_count() == 0
    }

    /// Returns whether the runtime has entries that should receive animation ticks.
    #[must_use]
    pub fn should_tick(&self) -> bool {
        self.registry
            .entries()
            .iter()
            .any(ActiveAnimation::needs_tick)
    }

    /// Returns whether an Iced subscription should keep producing animation ticks.
    #[must_use]
    pub fn should_subscribe(&self) -> bool {
        self.should_tick()
    }
}
use std::sync::Arc;