Skip to main content

aura_anim_core/
presence.rs

1//! Animated visibility lifecycle management.
2
3use crate::{
4    runtime::{Motion, MotionError, MotionEvent, MotionRuntime, PlaybackId},
5    timing::Timing,
6    traits::{Animatable, IntoMotionAnimation},
7};
8
9/// Coordinates mounting and visibility around enter and exit animations.
10///
11/// # Examples
12///
13/// ```
14/// use aura_anim_core::{MotionRuntime, Presence, timing::Timing};
15/// use std::time::Duration;
16///
17/// let mut runtime = MotionRuntime::new();
18/// let mut presence = Presence::new(&mut runtime, 0.0_f32, 1.0, Timing::new(100.0));
19///
20/// presence.show(&mut runtime).unwrap();
21/// assert!(presence.is_mounted());
22/// runtime.tick(Duration::from_millis(100));
23/// assert_eq!(*presence.value(&runtime).unwrap(), 1.0);
24///
25/// presence.hide(&mut runtime).unwrap();
26/// runtime.tick(Duration::from_millis(100));
27/// presence.sync(&runtime).unwrap();
28/// assert!(!presence.is_mounted());
29/// ```
30pub struct Presence<T: Animatable> {
31    motion: Motion<T>,
32    visible: T,
33    hidden: T,
34    mounted: bool,
35    shown: bool,
36    exit_playback: Option<PlaybackId>,
37}
38
39impl<T: Animatable> Presence<T> {
40    /// Creates a hidden, unmounted presence controller.
41    #[must_use]
42    pub fn new(runtime: &mut MotionRuntime, hidden: T, visible: T, timing: Timing) -> Self {
43        Self {
44            motion: runtime.motion_with(hidden.clone(), timing),
45            visible,
46            hidden,
47            mounted: false,
48            shown: false,
49            exit_playback: None,
50        }
51    }
52
53    /// Returns the runtime motion controlled by this presence value.
54    pub fn motion(&self) -> Motion<T> {
55        self.motion
56    }
57
58    /// Returns the current animated value.
59    pub fn value<'a>(&self, runtime: &'a MotionRuntime) -> Result<&'a T, MotionError> {
60        self.motion.value_ref(runtime)
61    }
62
63    /// Returns whether the associated content should be mounted.
64    #[must_use]
65    pub const fn is_mounted(&self) -> bool {
66        self.mounted
67    }
68
69    /// Returns whether the presence target is visible.
70    #[must_use]
71    pub const fn is_visible(&self) -> bool {
72        self.shown
73    }
74
75    /// Updates the target visibility when it differs from the current target.
76    ///
77    /// Returns `Ok(false)` without starting a new playback when the requested
78    /// visibility is already current.
79    pub fn set_visible(
80        &mut self,
81        visible: bool,
82        runtime: &mut MotionRuntime,
83    ) -> Result<bool, MotionError> {
84        if self.shown == visible {
85            #[cfg(feature = "tracing")]
86            tracing::trace!(
87                target: "aura_anim::presence",
88                value_type = std::any::type_name::<T>(),
89                mounted = self.mounted,
90                shown = self.shown,
91                "presence visibility is unchanged"
92            );
93            return Ok(false);
94        }
95
96        if visible {
97            self.show(runtime)?;
98        } else {
99            self.hide(runtime)?;
100        }
101        Ok(true)
102    }
103
104    /// Toggles the target visibility using the default transition.
105    pub fn toggle(&mut self, runtime: &mut MotionRuntime) -> Result<bool, MotionError> {
106        self.set_visible(!self.shown, runtime)
107    }
108
109    /// Mounts the content and transitions toward the visible value.
110    pub fn show(&mut self, runtime: &mut MotionRuntime) -> Result<(), MotionError> {
111        self.motion
112            .transition_to_tracked(self.visible.clone(), runtime)?;
113        self.mounted = true;
114        self.shown = true;
115        self.exit_playback = None;
116        #[cfg(feature = "tracing")]
117        tracing::debug!(
118            target: "aura_anim::presence",
119            value_type = std::any::type_name::<T>(),
120            mounted = self.mounted,
121            shown = self.shown,
122            "showing presence"
123        );
124        Ok(())
125    }
126
127    /// Transitions toward the hidden value.
128    pub fn hide(&mut self, runtime: &mut MotionRuntime) -> Result<(), MotionError> {
129        let playback = self
130            .motion
131            .transition_to_tracked(self.hidden.clone(), runtime)?;
132        self.shown = false;
133        self.exit_playback = Some(playback);
134        #[cfg(feature = "tracing")]
135        tracing::debug!(
136            target: "aura_anim::presence",
137            value_type = std::any::type_name::<T>(),
138            mounted = self.mounted,
139            shown = self.shown,
140            "hiding presence"
141        );
142        Ok(())
143    }
144
145    /// Mounts the content and plays a custom enter animation source.
146    pub fn show_with<P, Kind>(
147        &mut self,
148        animation: P,
149        runtime: &mut MotionRuntime,
150    ) -> Result<(), MotionError>
151    where
152        P: IntoMotionAnimation<T, Kind>,
153    {
154        self.motion.play_tracked(animation, runtime)?;
155        self.mounted = true;
156        self.shown = true;
157        self.exit_playback = None;
158        #[cfg(feature = "tracing")]
159        tracing::debug!(
160            target: "aura_anim::presence",
161            value_type = std::any::type_name::<T>(),
162            mounted = self.mounted,
163            shown = self.shown,
164            "showing presence with custom animation"
165        );
166        Ok(())
167    }
168
169    /// Plays a custom exit animation source.
170    pub fn hide_with<P, Kind>(
171        &mut self,
172        animation: P,
173        runtime: &mut MotionRuntime,
174    ) -> Result<(), MotionError>
175    where
176        P: IntoMotionAnimation<T, Kind>,
177    {
178        let playback = self.motion.play_tracked(animation, runtime)?;
179        self.shown = false;
180        self.exit_playback = Some(playback);
181        #[cfg(feature = "tracing")]
182        tracing::debug!(
183            target: "aura_anim::presence",
184            value_type = std::any::type_name::<T>(),
185            mounted = self.mounted,
186            shown = self.shown,
187            "hiding presence with custom animation"
188        );
189        Ok(())
190    }
191
192    /// Applies one runtime event and returns whether mounted state changed.
193    ///
194    /// Only completion of the current exit playback unmounts content. Events
195    /// from interrupted or superseded exits are ignored.
196    pub fn handle_event(&mut self, event: &MotionEvent) -> bool {
197        let Some(exit_playback) = self.exit_playback else {
198            return false;
199        };
200        if self.shown || !event.is_completed_for(exit_playback) {
201            return false;
202        }
203
204        let changed = self.mounted;
205        self.mounted = false;
206        self.exit_playback = None;
207        #[cfg(feature = "tracing")]
208        tracing::debug!(
209            target: "aura_anim::presence",
210            value_type = std::any::type_name::<T>(),
211            "unmounted completed hidden presence from runtime event"
212        );
213        changed
214    }
215
216    /// Unmounts hidden content after its animation completes.
217    pub fn sync(&mut self, runtime: &MotionRuntime) -> Result<(), MotionError> {
218        if !self.shown && self.motion.is_completed(runtime)? {
219            self.mounted = false;
220            self.exit_playback = None;
221            #[cfg(feature = "tracing")]
222            tracing::debug!(
223                target: "aura_anim::presence",
224                value_type = std::any::type_name::<T>(),
225                "unmounted completed hidden presence"
226            );
227        }
228        Ok(())
229    }
230}