zng_var/
animation.rs

1//! Var animation types and functions.
2
3use std::{any::Any, fmt, sync::Arc, time::Duration};
4
5use parking_lot::Mutex;
6use smallbox::SmallBox;
7use zng_app_context::context_local;
8use zng_handle::{Handle, HandleOwner, WeakHandle};
9use zng_time::{DInstant, Deadline};
10use zng_unit::Factor;
11
12use crate::{
13    Var, VarHandle, VarHandlerOwner, VarValue,
14    animation::easing::{EasingStep, EasingTime},
15};
16
17pub mod easing;
18pub use zng_var_proc_macros::Transitionable;
19
20/// View on an app loop timer.
21pub trait AnimationTimer {
22    /// Returns `true` if the `deadline` has elapsed, `false` if the `deadline` was
23    /// registered for future waking.
24    fn elapsed(&mut self, deadline: Deadline) -> bool;
25
26    /// Register the future `deadline` for waking.
27    fn register(&mut self, deadline: Deadline);
28
29    /// Frame timestamp.
30    fn now(&self) -> DInstant;
31}
32
33/// Animations controller.
34///
35/// See [`VARS.with_animation_controller`] for more details.
36///
37/// [`VARS.with_animation_controller`]: crate::VARS::with_animation_controller
38pub trait AnimationController: Send + Sync + Any {
39    /// Called for each `animation` that starts in the controller context.
40    ///
41    /// Note that this handler itself is not called inside the controller context.
42    fn on_start(&self, animation: &Animation) {
43        let _ = animation;
44    }
45
46    /// Called for each `animation` that ends in the controller context.
47    ///
48    /// Note that this handler itself is not called inside the controller context.
49    fn on_stop(&self, animation: &Animation) {
50        let _ = animation;
51    }
52}
53
54impl AnimationController for () {}
55
56/// An [`AnimationController`] that forces animations to run even if animations are not enabled.
57pub struct ForceAnimationController;
58impl AnimationController for ForceAnimationController {
59    fn on_start(&self, animation: &Animation) {
60        animation.force_enable();
61    }
62}
63
64context_local! {
65    pub(crate) static VARS_ANIMATION_CTRL_CTX: Box<dyn AnimationController> = {
66        let r: Box<dyn AnimationController> = Box::new(());
67        r
68    };
69}
70
71/// Represents an animation in its closure.
72///
73/// See the [`VARS.animate`] method for more details.
74///
75/// [`VARS.animate`]: crate::VARS::animate
76#[derive(Clone)]
77pub struct Animation(Arc<Mutex<AnimationData>>);
78struct AnimationData {
79    start_time: DInstant,
80    restart_count: usize,
81    stop: bool,
82    sleep: Option<Deadline>,
83    animations_enabled: bool,
84    force_enabled: bool,
85    now: DInstant,
86    time_scale: Factor,
87}
88
89impl Animation {
90    pub(super) fn new(animations_enabled: bool, now: DInstant, time_scale: Factor) -> Self {
91        Animation(Arc::new(Mutex::new(AnimationData {
92            start_time: now,
93            restart_count: 0,
94            stop: false,
95            now,
96            sleep: None,
97            animations_enabled,
98            force_enabled: false,
99            time_scale,
100        })))
101    }
102
103    /// The instant this animation (re)started.
104    pub fn start_time(&self) -> DInstant {
105        self.0.lock().start_time
106    }
107
108    /// The instant the current animation update started.
109    ///
110    /// Use this value instead of [`INSTANT.now`], animations update sequentially, but should behave as if
111    /// they are updating exactly in parallel, using this timestamp ensures that.
112    ///
113    /// [`INSTANT.now`]: zng_time::INSTANT::now
114    pub fn now(&self) -> DInstant {
115        self.0.lock().now
116    }
117
118    /// Global time scale for animations.
119    pub fn time_scale(&self) -> Factor {
120        self.0.lock().time_scale
121    }
122
123    pub(crate) fn reset_state(&self, enabled: bool, now: DInstant, time_scale: Factor) {
124        let mut m = self.0.lock();
125        if !m.force_enabled {
126            m.animations_enabled = enabled;
127        }
128        m.now = now;
129        m.time_scale = time_scale;
130        m.sleep = None;
131    }
132
133    pub(crate) fn reset_sleep(&self) {
134        self.0.lock().sleep = None;
135    }
136
137    /// Set the duration to the next animation update. The animation will *sleep* until `duration` elapses.
138    ///
139    /// The animation awakes in the next [`VARS.frame_duration`] after the `duration` elapses. The minimum
140    /// possible `duration` is the frame duration, shorter durations behave the same as if not set.
141    ///
142    /// [`VARS.frame_duration`]: crate::VARS::frame_duration
143    pub fn sleep(&self, duration: Duration) {
144        let mut me = self.0.lock();
145        me.sleep = Some(Deadline(me.now + duration));
146    }
147
148    pub(crate) fn sleep_deadline(&self) -> Option<Deadline> {
149        self.0.lock().sleep
150    }
151
152    /// Returns a value that indicates if animations are enabled in the operating system.
153    ///
154    /// If `false` all animations must be skipped to the end, users with photo-sensitive epilepsy disable animations system wide.
155    pub fn animations_enabled(&self) -> bool {
156        self.0.lock().animations_enabled
157    }
158
159    /// Set [`animations_enabled`] to `true`.
160    ///
161    /// This should only be used for animations that are component of an app feature, cosmetic animations must not force enable.
162    ///
163    /// [`animations_enabled`]: crate::VARS::animations_enabled
164    pub fn force_enable(&self) {
165        let mut me = self.0.lock();
166        me.force_enabled = true;
167        me.animations_enabled = true;
168    }
169
170    /// Compute the time elapsed from [`start_time`] to [`now`].
171    ///
172    /// [`start_time`]: Self::start_time
173    /// [`now`]: Self::now
174    pub fn elapsed_dur(&self) -> Duration {
175        let me = self.0.lock();
176        me.now - me.start_time
177    }
178
179    /// Compute the elapsed [`EasingTime`], in the span of the total `duration`, if [`animations_enabled`].
180    ///
181    /// If animations are disabled, returns [`EasingTime::end`], the returned time is scaled.
182    ///
183    /// [`animations_enabled`]: Self::animations_enabled
184    pub fn elapsed(&self, duration: Duration) -> EasingTime {
185        let me = self.0.lock();
186        if me.animations_enabled {
187            EasingTime::elapsed(duration, me.now - me.start_time, me.time_scale)
188        } else {
189            EasingTime::end()
190        }
191    }
192
193    /// Compute the elapsed [`EasingTime`], if the time [`is_end`] requests animation stop.
194    ///
195    /// [`is_end`]: EasingTime::is_end
196    pub fn elapsed_stop(&self, duration: Duration) -> EasingTime {
197        let t = self.elapsed(duration);
198        if t.is_end() {
199            self.stop()
200        }
201        t
202    }
203
204    /// Compute the elapsed [`EasingTime`], if the time [`is_end`] restarts the animation.
205    ///
206    /// [`is_end`]: EasingTime::is_end
207    pub fn elapsed_restart(&self, duration: Duration) -> EasingTime {
208        let t = self.elapsed(duration);
209        if t.is_end() {
210            self.restart()
211        }
212        t
213    }
214
215    /// Compute the elapsed [`EasingTime`], if the time [`is_end`] restarts the animation, repeats until has
216    /// restarted `max_restarts` inclusive, then stops the animation.
217    ///
218    /// [`is_end`]: EasingTime::is_end
219    pub fn elapsed_restart_stop(&self, duration: Duration, max_restarts: usize) -> EasingTime {
220        let t = self.elapsed(duration);
221        if t.is_end() {
222            if self.restart_count() < max_restarts {
223                self.restart();
224            } else {
225                self.stop();
226            }
227        }
228        t
229    }
230
231    /// Drop the animation after applying the current update.
232    pub fn stop(&self) {
233        self.0.lock().stop = true;
234    }
235
236    /// If the animation will be dropped after applying the update.
237    pub fn stop_requested(&self) -> bool {
238        self.0.lock().stop
239    }
240
241    /// Set the animation start time to now.
242    pub fn restart(&self) {
243        let now = self.0.lock().now;
244        self.set_start_time(now);
245        let mut me = self.0.lock();
246        me.restart_count += 1;
247    }
248
249    /// Number of times the animation restarted.
250    pub fn restart_count(&self) -> usize {
251        self.0.lock().restart_count
252    }
253
254    /// Change the start time to an arbitrary value.
255    ///
256    /// Note that this does not affect the restart count.
257    pub fn set_start_time(&self, instant: DInstant) {
258        self.0.lock().start_time = instant;
259    }
260
261    /// Change the start to an instant that computes the `elapsed` for the `duration` at the moment
262    /// this method is called.
263    ///
264    /// Note that this does not affect the restart count.
265    pub fn set_elapsed(&self, elapsed: EasingTime, duration: Duration) {
266        let now = self.0.lock().now;
267        self.set_start_time(now.checked_sub(duration * elapsed.fct()).unwrap());
268    }
269}
270
271/// Represents the current *modify* operation when it is applying.
272#[derive(Clone)]
273pub struct ModifyInfo {
274    pub(crate) handle: Option<WeakAnimationHandle>,
275    pub(crate) importance: usize,
276}
277impl ModifyInfo {
278    /// Initial value, is always of lowest importance.
279    pub fn never() -> Self {
280        ModifyInfo {
281            handle: None,
282            importance: 0,
283        }
284    }
285
286    /// Indicates the *override* importance of the operation, when two animations target
287    /// a variable only the newer one must apply, and all running animations are *overridden* by
288    /// a later modify/set operation.
289    ///
290    /// Variables ignore modify requests from lower importance closures.
291    pub fn importance(&self) -> usize {
292        self.importance
293    }
294
295    /// Indicates if the *modify* request was made from inside an animation, if `true` the [`importance`]
296    /// is for that animation, even if the modify request is from the current frame.
297    ///
298    /// You can clone this info to track this animation, when it stops or is dropped this returns `false`. Note
299    /// that sleeping animations still count as animating.
300    ///
301    /// [`importance`]: Self::importance
302    pub fn is_animating(&self) -> bool {
303        self.handle.as_ref().map(|h| h.upgrade().is_some()).unwrap_or(false)
304    }
305
306    /// Returns `true` if `self` and `other` have the same animation or are both not animating.
307    pub fn animation_eq(&self, other: &Self) -> bool {
308        self.handle == other.handle
309    }
310
311    /// Register a `handler` to be called once when the current animation stops.
312    ///
313    /// [`importance`]: Self::importance
314    pub fn hook_animation_stop(&self, handler: AnimationStopFn) -> VarHandle {
315        if let Some(h) = &self.handle
316            && let Some(h) = h.upgrade()
317        {
318            return h.hook_animation_stop(handler);
319        }
320        VarHandle::dummy()
321    }
322}
323impl fmt::Debug for ModifyInfo {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        f.debug_struct("ModifyInfo")
326            .field("is_animating()", &self.is_animating())
327            .field("importance()", &self.importance)
328            .finish()
329    }
330}
331
332pub(crate) type AnimationStopFn = SmallBox<dyn FnMut() + Send + 'static, smallbox::space::S4>;
333
334#[derive(Default)]
335pub(super) struct AnimationHandleData {
336    on_drop: Mutex<Vec<(AnimationStopFn, VarHandlerOwner)>>,
337}
338impl Drop for AnimationHandleData {
339    fn drop(&mut self) {
340        for (mut f, h) in self.on_drop.get_mut().drain(..) {
341            if h.is_alive() {
342                f()
343            }
344        }
345    }
346}
347/// Represents a running animation.
348///
349/// Drop all clones of this handle to stop the animation, or call [`perm`] to drop the handle
350/// but keep the animation alive until it is stopped from the inside.
351///
352/// [`perm`]: AnimationHandle::perm
353#[derive(Clone, PartialEq, Eq, Hash, Debug)]
354#[repr(transparent)]
355#[must_use = "the animation stops if the handle is dropped"]
356pub struct AnimationHandle(Handle<AnimationHandleData>);
357impl Default for AnimationHandle {
358    /// `dummy`.
359    fn default() -> Self {
360        Self::dummy()
361    }
362}
363impl AnimationHandle {
364    pub(super) fn new() -> (HandleOwner<AnimationHandleData>, Self) {
365        let (owner, handle) = Handle::new(AnimationHandleData::default());
366        (owner, AnimationHandle(handle))
367    }
368
369    /// Create dummy handle that is always in the *stopped* state.
370    ///
371    /// Note that `Option<AnimationHandle>` takes up the same space as `AnimationHandle` and avoids an allocation.
372    pub fn dummy() -> Self {
373        AnimationHandle(Handle::dummy(AnimationHandleData::default()))
374    }
375
376    /// Drops the handle but does **not** stop.
377    ///
378    /// The animation stays in memory for the duration of the app or until another handle calls [`stop`](Self::stop).
379    pub fn perm(self) {
380        self.0.perm();
381    }
382
383    /// If another handle has called [`perm`](Self::perm).
384    ///
385    /// If `true` the animation will stay active until the app exits, unless [`stop`](Self::stop) is called.
386    pub fn is_permanent(&self) -> bool {
387        self.0.is_permanent()
388    }
389
390    /// Drops the handle and forces the animation to drop.
391    pub fn stop(self) {
392        self.0.force_drop();
393    }
394
395    /// If another handle has called [`stop`](Self::stop).
396    ///
397    /// The animation is already dropped or will be dropped in the next app update, this is irreversible.
398    pub fn is_stopped(&self) -> bool {
399        self.0.is_dropped()
400    }
401
402    /// Create a weak handle.
403    pub fn downgrade(&self) -> WeakAnimationHandle {
404        WeakAnimationHandle(self.0.downgrade())
405    }
406
407    /// Register a `handler` to be called once when the animation stops.
408    ///
409    /// Returns the `handler` if the animation has already stopped.
410    ///
411    /// [`importance`]: ModifyInfo::importance
412    pub fn hook_animation_stop(&self, handler: AnimationStopFn) -> VarHandle {
413        if !self.is_stopped() {
414            let (owner, handle) = VarHandle::new();
415            self.0.data().on_drop.lock().push((handler, owner));
416            handle
417        } else {
418            VarHandle::dummy()
419        }
420    }
421}
422
423/// Weak [`AnimationHandle`].
424#[derive(Clone, PartialEq, Eq, Hash, Default, Debug)]
425pub struct WeakAnimationHandle(pub(super) WeakHandle<AnimationHandleData>);
426impl WeakAnimationHandle {
427    /// New weak handle that does not upgrade.
428    pub fn new() -> Self {
429        Self(WeakHandle::new())
430    }
431
432    /// Get the animation handle if it is still animating.
433    pub fn upgrade(&self) -> Option<AnimationHandle> {
434        self.0.upgrade().map(AnimationHandle)
435    }
436}
437
438/// Represents a type that can be animated between two values.
439///
440/// This trait is auto-implemented for all [`Copy`] types that can add, subtract and multiply by [`Factor`], [`Clone`]
441/// only types must implement this trait manually.
442///
443/// [`Factor`]: zng_unit::Factor
444pub trait Transitionable: VarValue {
445    /// Sample the linear interpolation from `self` -> `to` by `step`.  
446    fn lerp(self, to: &Self, step: EasingStep) -> Self;
447}
448
449/// Represents a simple transition between two values.
450#[non_exhaustive]
451pub struct Transition<T> {
452    /// Value sampled at the `0.fct()` step.
453    pub from: T,
454    ///
455    /// Value sampled at the `1.fct()` step.
456    pub to: T,
457}
458impl<T> Transition<T>
459where
460    T: Transitionable,
461{
462    /// New transition.
463    pub fn new(from: T, to: T) -> Self {
464        Self { from, to }
465    }
466
467    /// Compute the transition value at the `step`.
468    pub fn sample(&self, step: EasingStep) -> T {
469        self.from.clone().lerp(&self.to, step)
470    }
471}
472
473/// Represents a transition across multiple keyed values that can be sampled using [`EasingStep`].
474#[derive(Clone, Debug)]
475pub struct TransitionKeyed<T> {
476    keys: Vec<(Factor, T)>,
477}
478impl<T> TransitionKeyed<T>
479where
480    T: Transitionable,
481{
482    /// New transition.
483    ///
484    /// Returns `None` if `keys` is empty.
485    pub fn new(mut keys: Vec<(Factor, T)>) -> Option<Self> {
486        if keys.is_empty() {
487            return None;
488        }
489
490        // correct backtracking keyframes.
491        for i in 1..keys.len() {
492            if keys[i].0 < keys[i - 1].0 {
493                keys[i].0 = keys[i - 1].0;
494            }
495        }
496
497        Some(TransitionKeyed { keys })
498    }
499
500    /// Keyed values.
501    pub fn keys(&self) -> &[(Factor, T)] {
502        &self.keys
503    }
504
505    /// Compute the transition value at the `step`.
506    pub fn sample(&self, step: EasingStep) -> T {
507        if let Some(i) = self.keys.iter().position(|(f, _)| *f > step) {
508            if i == 0 {
509                // step before first
510                self.keys[0].1.clone()
511            } else {
512                let (from_step, from_value) = self.keys[i - 1].clone();
513                if from_step == step {
514                    // step exact key
515                    from_value
516                } else {
517                    // linear interpolate between steps
518
519                    let (_, to_value) = &self.keys[i];
520                    let step = step - from_step;
521
522                    from_value.lerp(to_value, step)
523                }
524            }
525        } else {
526            // step is after last
527            self.keys[self.keys.len() - 1].1.clone()
528        }
529    }
530}
531
532/// Represents the editable final value of a [`Var::chase`] animation.
533pub struct ChaseAnimation<T: VarValue + Transitionable> {
534    pub(super) target: T,
535    pub(super) var: Var<T>,
536    pub(super) handle: AnimationHandle,
537}
538impl<T> fmt::Debug for ChaseAnimation<T>
539where
540    T: VarValue + Transitionable,
541{
542    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543        f.debug_struct("ChaseAnimation")
544            .field("target", &self.target)
545            .finish_non_exhaustive()
546    }
547}
548impl<T> ChaseAnimation<T>
549where
550    T: VarValue + Transitionable,
551{
552    /// Current animation target.
553    pub fn target(&self) -> &T {
554        &self.target
555    }
556
557    /// Modify the chase target, replaces the animation with a new one from the current value to the modified target.
558    pub fn modify(&mut self, modify: impl FnOnce(&mut T), duration: Duration, easing: impl Fn(EasingTime) -> EasingStep + Send + 'static) {
559        if self.handle.is_stopped() {
560            // re-sync target
561            self.target = self.var.get();
562        }
563        modify(&mut self.target);
564        self.handle = self.var.ease(self.target.clone(), duration, easing);
565    }
566
567    /// Replace the chase target, replaces the animation with a new one from the current value to the modified target.
568    pub fn set(&mut self, value: impl Into<T>, duration: Duration, easing: impl Fn(EasingTime) -> EasingStep + Send + 'static) {
569        self.target = value.into();
570        self.handle = self.var.ease(self.target.clone(), duration, easing);
571    }
572}
573
574/// Spherical linear interpolation sampler.
575///
576/// Animates rotations over the shortest change between angles by modulo wrapping.
577/// A transition from 358º to 1º goes directly to 361º (modulo normalized to 1º).
578///
579/// Types that support this use the [`is_slerp_enabled`] function inside [`Transitionable::lerp`] to change
580/// mode, types that don't support this use the normal linear interpolation. All angle and transform units
581/// implement this.
582///
583/// Samplers can be set in animations using the `Var::easing_with` method.
584pub fn slerp_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
585    slerp_enabled(true, || t.sample(step))
586}
587
588/// Gets if slerp mode is enabled in the context.
589///
590/// See [`slerp_sampler`] for more details.
591pub fn is_slerp_enabled() -> bool {
592    SLERP_ENABLED.get_clone()
593}
594
595/// Calls `f` with [`is_slerp_enabled`] set to `enabled`.
596///
597/// See [`slerp_sampler`] for a way to enable in animations.
598pub fn slerp_enabled<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
599    SLERP_ENABLED.with_context(&mut Some(Arc::new(enabled)), f)
600}
601
602context_local! {
603    static SLERP_ENABLED: bool = false;
604}
605
606/// API for app implementers to replace the transitionable implementation for foreign types.
607#[expect(non_camel_case_types)]
608pub struct TRANSITIONABLE_APP;