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