raui_core/
animator.rs

1//! Animation engine
2//!
3//! RAUI widget components can be animated by updating and adding animations using the [`Animator`]
4//! inside of widget lifecycle hooks and by reading the progress of those animations from the
5//! [`AnimatorStates`] provided by the [`WidgetContext`].
6//!
7//! See [`Animator`] and [`AnimatorStates`] for code samples.
8//!
9//! [`WidgetContext`]: crate::widget::context::WidgetContext
10use crate::{MessageData, Scalar, messenger::MessageSender, widget::WidgetId};
11use serde::{Deserialize, Serialize};
12use std::{collections::HashMap, sync::mpsc::Sender};
13
14/// An error that may occur when animating a value
15pub enum AnimationError {
16    /// Could not read animation data
17    CouldNotReadData,
18    /// Could not write animation data
19    CouldNotWriteData,
20}
21
22/// Handle to an animation sending channel used internally to update widget animations values in
23/// lifecycle hooks
24#[derive(Clone)]
25pub(crate) struct AnimationUpdate(Sender<(String, Option<Animation>)>);
26
27impl AnimationUpdate {
28    pub fn new(sender: Sender<(String, Option<Animation>)>) -> Self {
29        Self(sender)
30    }
31
32    pub fn change(&self, name: &str, data: Option<Animation>) -> Result<(), AnimationError> {
33        if self.0.send((name.to_owned(), data)).is_err() {
34            Err(AnimationError::CouldNotWriteData)
35        } else {
36            Ok(())
37        }
38    }
39}
40
41/// Allows manipulating widget animations
42///
43/// An [`Animator`] can be used inside of the [`WidgetMountOrChangeContext`] that is provided when
44/// setting widget lifecycle handlers.
45///
46/// # Animations & Values
47///
48/// The animator can manage any number of different animations identified by a string `anim_id`.
49/// Additionally each animation can have more than one _value_ that is animated and each of these
50/// values has a `value_name` that can be used to get the animated value.
51///
52/// [`WidgetMountOrChangeContext`]: crate::widget::context::WidgetMountOrChangeContext
53pub struct Animator<'a> {
54    states: &'a AnimatorStates,
55    update: AnimationUpdate,
56}
57
58impl<'a> Animator<'a> {
59    /// Create a new [`Animator`]
60    #[inline]
61    pub(crate) fn new(states: &'a AnimatorStates, update: AnimationUpdate) -> Self {
62        Self { states, update }
63    }
64
65    /// Check whether or not the widget has an animation with the given `anim_id`
66    #[inline]
67    pub fn has(&self, anim_id: &str) -> bool {
68        self.states.has(anim_id)
69    }
70
71    /// Change the animation associated to a given `anim_id`
72    #[inline]
73    pub fn change(
74        &self,
75        anim_id: &str,
76        animation: Option<Animation>,
77    ) -> Result<(), AnimationError> {
78        self.update.change(anim_id, animation)
79    }
80
81    /// Get the current progress of the animation of a given value
82    ///
83    /// This will return [`None`] if the value is not currently being animated.
84    #[inline]
85    pub fn value_progress(&self, anim_id: &str, value_name: &str) -> Option<AnimatedValueProgress> {
86        self.states.value_progress(anim_id, value_name)
87    }
88
89    /// Get the current progress factor of the animation of a given value
90    ///
91    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`
92    /// and `1` with `0` meaning just started and `1` meaning finished.
93    ///
94    /// If the value is **not** currently being animated [`None`] will be returned
95    #[inline]
96    pub fn value_progress_factor(&self, anim_id: &str, value_name: &str) -> Option<Scalar> {
97        self.states
98            .value_progress(anim_id, value_name)
99            .map(|x| x.progress_factor)
100    }
101
102    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]
103    #[inline]
104    pub fn value_progress_factor_or(
105        &self,
106        anim_id: &str,
107        value_name: &str,
108        default: Scalar,
109    ) -> Scalar {
110        self.value_progress_factor(anim_id, value_name)
111            .unwrap_or(default)
112    }
113
114    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]
115    #[inline]
116    pub fn value_progress_factor_or_zero(&self, anim_id: &str, value_name: &str) -> Scalar {
117        self.value_progress_factor(anim_id, value_name)
118            .unwrap_or(0.)
119    }
120}
121
122/// The amount of progress made for a value in an animation
123#[derive(Debug, Default, Clone, Copy)]
124pub struct AnimatedValueProgress {
125    /// How far along this animation is from 0 to 1
126    pub progress_factor: Scalar,
127    /// The amount of time this animation has been running
128    pub time: Scalar,
129    /// The amount of time that this animation will run for
130    pub duration: Scalar,
131}
132
133/// The current state of animations in a component
134///
135/// The [`AnimatorStates`] can be accessed from the [`WidgetContext`] to get information about the
136/// current state of all component animations.
137///
138/// # Animations & Values
139///
140/// A component may have any number of different animations identified by a string `anim_id`.
141/// Additionally each animation can have more than one _value_ that is animated and each of these
142/// values has a `value_name` that can be used to get the animated value.
143///
144/// [`WidgetContext`]: crate::widget::context::WidgetContext
145#[derive(Debug, Default, Clone, Serialize, Deserialize)]
146pub struct AnimatorStates(
147    #[serde(default)]
148    #[serde(skip_serializing_if = "HashMap::is_empty")]
149    pub HashMap<String, AnimatorState>,
150);
151
152impl AnimatorStates {
153    /// Initialize a new [`AnimatorStates`] that contains a single animation
154    pub(crate) fn new(anim_id: String, animation: Animation) -> Self {
155        let mut result = HashMap::with_capacity(1);
156        result.insert(anim_id, AnimatorState::new(animation));
157        Self(result)
158    }
159
160    /// Returns whether or not _any_ of the animations for this component are in-progress
161    pub fn in_progress(&self) -> bool {
162        self.0.values().any(|s| s.in_progress())
163    }
164
165    /// Returns `true` if none of this component's animations are currently running
166    #[inline]
167    pub fn is_done(&self) -> bool {
168        !self.in_progress()
169    }
170
171    /// Returns true if the widget has an animation with the given `anim_id`
172    #[inline]
173    pub fn has(&self, anim_id: &str) -> bool {
174        self.0.contains_key(anim_id)
175    }
176
177    /// Get the current progress of the animation of a given value
178    ///
179    /// This will return [`None`] if the value is not currently being animated.
180    #[inline]
181    pub fn value_progress(&self, anim_id: &str, value_name: &str) -> Option<AnimatedValueProgress> {
182        if let Some(state) = self.0.get(anim_id) {
183            state.value_progress(value_name)
184        } else {
185            None
186        }
187    }
188
189    /// Get the current progress factor of the animation of a given value
190    ///
191    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`
192    /// and `1` with `0` meaning just started and `1` meaning finished.
193    ///
194    /// If the value is **not** currently being animated [`None`] will be returned
195    #[inline]
196    pub fn value_progress_factor(&self, anim_id: &str, value_name: &str) -> Option<Scalar> {
197        if let Some(state) = self.0.get(anim_id) {
198            state.value_progress_factor(value_name)
199        } else {
200            None
201        }
202    }
203
204    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]
205    #[inline]
206    pub fn value_progress_factor_or(
207        &self,
208        anim_id: &str,
209        value_name: &str,
210        default: Scalar,
211    ) -> Scalar {
212        self.value_progress_factor(anim_id, value_name)
213            .unwrap_or(default)
214    }
215
216    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]
217    #[inline]
218    pub fn value_progress_factor_or_zero(&self, anim_id: &str, value_name: &str) -> Scalar {
219        self.value_progress_factor(anim_id, value_name)
220            .unwrap_or(0.)
221    }
222
223    /// Update the animation with the given `anim_id`
224    ///
225    /// If `animation` is [`None`] the animation will be removed.
226    pub fn change(&mut self, anim_id: String, animation: Option<Animation>) {
227        if let Some(animation) = animation {
228            self.0.insert(anim_id, AnimatorState::new(animation));
229        } else {
230            self.0.remove(&anim_id);
231        }
232    }
233
234    /// Processes the animations, updating the values of each animation baed on the progressed time
235    pub(crate) fn process(
236        &mut self,
237        delta_time: Scalar,
238        owner: &WidgetId,
239        message_sender: &MessageSender,
240    ) {
241        for state in self.0.values_mut() {
242            state.process(delta_time, owner, message_sender);
243        }
244    }
245}
246
247/// The state of a single animation in a component
248///
249/// This is most often accessed though [`AnimatorStates`] in the [`WidgetContext`].
250///
251/// [`WidgetContext`]: crate::widget::context::WidgetContext
252#[derive(Debug, Default, Clone, Serialize, Deserialize)]
253pub struct AnimatorState {
254    #[serde(default)]
255    #[serde(skip_serializing_if = "HashMap::is_empty")]
256    sheet: HashMap<String, AnimationPhase>,
257    #[serde(default)]
258    #[serde(skip_serializing_if = "Vec::is_empty")]
259    messages: Vec<(Scalar, String)>,
260    #[serde(default)]
261    time: Scalar,
262    #[serde(default)]
263    duration: Scalar,
264    #[serde(default)]
265    looped: bool,
266}
267
268impl AnimatorState {
269    /// Initialize a new [`AnimatorState`] given an animation
270    pub(crate) fn new(animation: Animation) -> Self {
271        let mut sheet = HashMap::new();
272        let mut messages = vec![];
273        let (time, looped) = Self::include_animation(animation, &mut sheet, &mut messages, 0.0);
274        Self {
275            sheet,
276            messages,
277            time: 0.0,
278            duration: time,
279            looped,
280        }
281    }
282
283    /// Returns whether or not the animations is in-progress
284    #[inline]
285    pub fn in_progress(&self) -> bool {
286        self.looped || (self.time <= self.duration && !self.sheet.is_empty())
287    }
288
289    /// Returns `true` if this animation is not in-progress
290    #[inline]
291    pub fn is_done(&self) -> bool {
292        !self.in_progress()
293    }
294
295    /// Get the current progress of the animation of a given value
296    ///
297    /// This will return [`None`] if the value is not currently being animated.
298    #[inline]
299    pub fn value_progress(&self, name: &str) -> Option<AnimatedValueProgress> {
300        self.sheet.get(name).map(|p| AnimatedValueProgress {
301            progress_factor: p.cached_progress,
302            time: p.cached_time,
303            duration: p.duration,
304        })
305    }
306
307    /// Get the current progress factor of the animation of a given value
308    ///
309    /// If the value is currently being animated this will return [`Some`] [`Scalar`] between `0`
310    /// and `1` with `0` meaning just started and `1` meaning finished.
311    ///
312    /// If the value is **not** currently being animated [`None`] will be returned
313    #[inline]
314    pub fn value_progress_factor(&self, name: &str) -> Option<Scalar> {
315        self.sheet.get(name).map(|p| p.cached_progress)
316    }
317
318    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `default` instead of [`None`]
319    #[inline]
320    pub fn value_progress_factor_or(&self, name: &str, default: Scalar) -> Scalar {
321        self.value_progress_factor(name).unwrap_or(default)
322    }
323
324    /// Same as [`value_progress_factor`][Self::value_progress_factor] but returning `0` instead of [`None`]
325    #[inline]
326    pub fn value_progress_factor_or_zero(&self, name: &str) -> Scalar {
327        self.value_progress_factor(name).unwrap_or(0.)
328    }
329
330    /// Processes the animations, updating the values of each animation baed on the progressed time
331    pub(crate) fn process(
332        &mut self,
333        delta_time: Scalar,
334        owner: &WidgetId,
335        message_sender: &MessageSender,
336    ) {
337        if delta_time > 0.0 {
338            if self.looped && self.time > self.duration {
339                self.time = 0.0;
340            }
341            let old_time = self.time;
342            self.time += delta_time;
343            for phase in self.sheet.values_mut() {
344                phase.cached_time = (self.time - phase.start).min(phase.duration).max(0.0);
345                phase.cached_progress = if phase.duration > 0.0 {
346                    phase.cached_time / phase.duration
347                } else {
348                    0.0
349                };
350            }
351            for (time, message) in &self.messages {
352                if *time >= old_time && *time < self.time {
353                    message_sender.write(owner.to_owned(), AnimationMessage(message.to_owned()));
354                }
355            }
356        }
357    }
358
359    // Add an animation to this [`AnimatorState`] recursively
360    fn include_animation(
361        animation: Animation,
362        sheet: &mut HashMap<String, AnimationPhase>,
363        messages: &mut Vec<(Scalar, String)>,
364        mut time: Scalar,
365    ) -> (Scalar, bool) {
366        match animation {
367            Animation::Value(value) => {
368                let duration = value.duration.max(0.0);
369                let phase = AnimationPhase {
370                    start: time,
371                    duration,
372                    cached_time: 0.0,
373                    cached_progress: 0.0,
374                };
375                sheet.insert(value.name, phase);
376                (time + duration, false)
377            }
378            Animation::Sequence(anims) => {
379                for anim in anims {
380                    time = Self::include_animation(anim, sheet, messages, time).0;
381                }
382                (time, false)
383            }
384            Animation::Parallel(anims) => {
385                let mut result = time;
386                for anim in anims {
387                    result = Self::include_animation(anim, sheet, messages, time)
388                        .0
389                        .max(result);
390                }
391                (result, false)
392            }
393            Animation::Looped(anim) => {
394                let looped = sheet.is_empty();
395                time = Self::include_animation(*anim, sheet, messages, time).0;
396                (time, looped)
397            }
398            Animation::TimeShift(v) => ((time - v).max(0.0), false),
399            Animation::Message(message) => {
400                messages.push((time, message));
401                (time, false)
402            }
403        }
404    }
405}
406
407#[derive(Debug, Default, Clone, Serialize, Deserialize)]
408struct AnimationPhase {
409    #[serde(default)]
410    pub start: Scalar,
411    #[serde(default)]
412    pub duration: Scalar,
413    #[serde(default)]
414    pub cached_time: Scalar,
415    #[serde(default)]
416    pub cached_progress: Scalar,
417}
418
419/// Defines a widget animation
420///
421/// [`Animation`]'s can be added to widget component's [`AnimatorStates`] to animate values.
422///
423/// Creating an [`Animation`] doesn't actually animate a specific value, but instead gives you a way
424/// to track the _progress_ of an animated value using the
425/// [`value_progress`][AnimatorStates::value_progress] function. This allows you to use the progress
426/// to calculate how to interpolate the real values when you build your widget.
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub enum Animation {
429    /// A single animated value with a name and a duration
430    Value(AnimatedValue),
431    /// A sequence of animations that will be run in a row
432    Sequence(Vec<Animation>),
433    /// A set of animations that will be run at the same time
434    Parallel(Vec<Animation>),
435    /// An animation that will play in a loop
436    Looped(Box<Animation>),
437    /// TODO: Document `TimeShift`
438    TimeShift(Scalar),
439    /// Send an [`AnimationMessage`]
440    Message(String),
441}
442
443impl Default for Animation {
444    fn default() -> Self {
445        Self::TimeShift(0.0)
446    }
447}
448
449/// A single, animated value with a name and a duration
450#[derive(Debug, Default, Clone, Serialize, Deserialize)]
451pub struct AnimatedValue {
452    /// The name of the animated value
453    ///
454    /// This is used to get the progress of the animation value with the
455    /// [`value_progress`][AnimatorStates::value_progress] function.
456    #[serde(default)]
457    pub name: String,
458    /// The duration of the animation
459    #[serde(default)]
460    pub duration: Scalar,
461}
462
463/// A [`MessageData`][crate::messenger::MessageData] implementation sent by running an
464/// [`Animation::Message`] animation
465#[derive(MessageData, Debug, Default, Clone)]
466#[message_data(crate::messenger::MessageData)]
467pub struct AnimationMessage(pub String);
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472    use std::{str::FromStr, sync::mpsc::channel};
473
474    #[test]
475    fn test_animator() {
476        let animation = Animation::Sequence(vec![
477            Animation::Value(AnimatedValue {
478                name: "fade-in".to_owned(),
479                duration: 0.2,
480            }),
481            Animation::Value(AnimatedValue {
482                name: "delay".to_owned(),
483                duration: 0.6,
484            }),
485            Animation::Value(AnimatedValue {
486                name: "fade-out".to_owned(),
487                duration: 0.2,
488            }),
489            Animation::Message("next".to_owned()),
490        ]);
491        println!("Animation: {animation:#?}");
492        let mut states = AnimatorStates::new("".to_owned(), animation);
493        println!("States 0: {states:#?}");
494        let id = WidgetId::from_str("type:/widget").unwrap();
495        let (sender, receiver) = channel();
496        let sender = MessageSender::new(sender);
497        states.process(0.5, &id, &sender);
498        println!("States 1: {states:#?}");
499        states.process(0.6, &id, &sender);
500        println!("States 2: {states:#?}");
501        println!(
502            "Message: {:#?}",
503            receiver
504                .try_recv()
505                .unwrap()
506                .1
507                .as_any()
508                .downcast_ref::<AnimationMessage>()
509                .unwrap()
510        );
511    }
512}