Skip to main content

aura_anim_core/runtime/
event.rs

1//! Runtime animation lifecycle events and stable identifiers.
2
3use super::Motion;
4
5/// Identifies one runtime motion slot lifecycle.
6///
7/// A slot's generation changes when storage is reused, so events from a
8/// removed motion never match a later motion occupying the same slot.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct MotionId {
11    slot: usize,
12    generation: u64,
13}
14
15impl MotionId {
16    pub(super) const fn new(slot: usize, generation: u64) -> Self {
17        Self { slot, generation }
18    }
19
20    /// Returns the runtime slot index.
21    #[must_use]
22    pub const fn slot(self) -> usize {
23        self.slot
24    }
25
26    /// Returns the slot generation captured by this ID.
27    #[must_use]
28    pub const fn generation(self) -> u64 {
29        self.generation
30    }
31}
32
33/// Identifies one concrete playback on a motion.
34///
35/// Replacing or retargeting a motion creates a new playback ID even though the
36/// typed [`Motion`] handle remains unchanged.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct PlaybackId {
39    motion: MotionId,
40    revision: u64,
41}
42
43impl PlaybackId {
44    pub(super) const fn new(motion: MotionId, revision: u64) -> Self {
45        Self { motion, revision }
46    }
47
48    /// Returns the motion owning this playback.
49    #[must_use]
50    pub const fn motion(self) -> MotionId {
51        self.motion
52    }
53
54    /// Returns the playback revision within the motion lifecycle.
55    #[must_use]
56    pub const fn revision(self) -> u64 {
57        self.revision
58    }
59}
60
61/// Explains why an unfinished playback stopped.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
63#[non_exhaustive]
64pub enum InterruptionReason {
65    /// A new animation replaced the playback.
66    Replaced,
67    /// A transition retargeted the playback.
68    Retargeted,
69    /// The owning motion was removed.
70    Removed,
71}
72
73/// Explains why a motion left runtime storage.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75#[non_exhaustive]
76pub enum RemovalReason {
77    /// The application explicitly removed the motion.
78    Explicit,
79    /// The motion used [`super::RetainPolicy::DropWhenSettled`].
80    Settled,
81}
82
83/// The lifecycle change represented by a [`MotionEvent`].
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85#[non_exhaustive]
86pub enum MotionEventKind {
87    /// The playback reached its completed state.
88    Completed,
89    /// The playback was canceled.
90    Canceled,
91    /// The playback stopped because another operation superseded it.
92    Interrupted(InterruptionReason),
93    /// The owning motion was removed from runtime storage.
94    Removed(RemovalReason),
95}
96
97/// A structured runtime lifecycle event.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99pub struct MotionEvent {
100    motion: MotionId,
101    playback: PlaybackId,
102    kind: MotionEventKind,
103}
104
105impl MotionEvent {
106    pub(super) const fn new(playback: PlaybackId, kind: MotionEventKind) -> Self {
107        Self {
108            motion: playback.motion(),
109            playback,
110            kind,
111        }
112    }
113
114    /// Returns the motion associated with this event.
115    #[must_use]
116    pub const fn motion(self) -> MotionId {
117        self.motion
118    }
119
120    /// Returns the concrete playback associated with this event.
121    #[must_use]
122    pub const fn playback(self) -> PlaybackId {
123        self.playback
124    }
125
126    /// Returns the event kind.
127    #[must_use]
128    pub const fn kind(self) -> MotionEventKind {
129        self.kind
130    }
131
132    /// Returns whether this event belongs to `target`.
133    #[must_use]
134    pub fn is_for(self, target: impl MotionEventTarget) -> bool {
135        target.matches(self)
136    }
137
138    /// Returns whether this is a completed event for `target`.
139    #[must_use]
140    pub fn is_completed_for(self, target: impl MotionEventTarget) -> bool {
141        self.kind == MotionEventKind::Completed && target.matches(self)
142    }
143
144    /// Returns whether this is a canceled event for `target`.
145    #[must_use]
146    pub fn is_canceled_for(self, target: impl MotionEventTarget) -> bool {
147        self.kind == MotionEventKind::Canceled && target.matches(self)
148    }
149}
150
151mod private {
152    pub trait Sealed {}
153
154    impl<T> Sealed for super::Motion<T> {}
155    impl Sealed for super::MotionId {}
156    impl Sealed for super::PlaybackId {}
157}
158
159/// A motion or playback that can be matched against a [`MotionEvent`].
160pub trait MotionEventTarget: private::Sealed {
161    /// Returns whether this target matches `event`.
162    #[doc(hidden)]
163    fn matches(self, event: MotionEvent) -> bool;
164}
165
166impl<T> MotionEventTarget for Motion<T> {
167    fn matches(self, event: MotionEvent) -> bool {
168        self.id() == event.motion
169    }
170}
171
172impl MotionEventTarget for MotionId {
173    fn matches(self, event: MotionEvent) -> bool {
174        self == event.motion
175    }
176}
177
178impl MotionEventTarget for PlaybackId {
179    fn matches(self, event: MotionEvent) -> bool {
180        self == event.playback
181    }
182}