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