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