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}