i_slint_core/
animations.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![warn(missing_docs)]
5//! The animation system
6
7use alloc::boxed::Box;
8use core::cell::Cell;
9#[cfg(not(feature = "std"))]
10use num_traits::Float;
11
12mod cubic_bezier {
13    //! This is a copy from lyon_algorithms::geom::cubic_bezier implementation
14    //! (from lyon_algorithms 0.17)
15    type S = f32;
16    use euclid::default::Point2D as Point;
17    #[allow(unused)]
18    use num_traits::Float;
19    trait Scalar {
20        const ONE: f32 = 1.;
21        const THREE: f32 = 3.;
22        const HALF: f32 = 0.5;
23        const SIX: f32 = 6.;
24        const NINE: f32 = 9.;
25        fn value(v: f32) -> f32 {
26            v
27        }
28    }
29    impl Scalar for f32 {}
30    pub struct CubicBezierSegment {
31        pub from: Point<S>,
32        pub ctrl1: Point<S>,
33        pub ctrl2: Point<S>,
34        pub to: Point<S>,
35    }
36
37    impl CubicBezierSegment {
38        /// Sample the x coordinate of the curve at t (expecting t between 0 and 1).
39        pub fn x(&self, t: S) -> S {
40            let t2 = t * t;
41            let t3 = t2 * t;
42            let one_t = S::ONE - t;
43            let one_t2 = one_t * one_t;
44            let one_t3 = one_t2 * one_t;
45
46            self.from.x * one_t3
47                + self.ctrl1.x * S::THREE * one_t2 * t
48                + self.ctrl2.x * S::THREE * one_t * t2
49                + self.to.x * t3
50        }
51
52        /// Sample the y coordinate of the curve at t (expecting t between 0 and 1).
53        pub fn y(&self, t: S) -> S {
54            let t2 = t * t;
55            let t3 = t2 * t;
56            let one_t = S::ONE - t;
57            let one_t2 = one_t * one_t;
58            let one_t3 = one_t2 * one_t;
59
60            self.from.y * one_t3
61                + self.ctrl1.y * S::THREE * one_t2 * t
62                + self.ctrl2.y * S::THREE * one_t * t2
63                + self.to.y * t3
64        }
65
66        #[inline]
67        fn derivative_coefficients(&self, t: S) -> (S, S, S, S) {
68            let t2 = t * t;
69            (
70                -S::THREE * t2 + S::SIX * t - S::THREE,
71                S::NINE * t2 - S::value(12.0) * t + S::THREE,
72                -S::NINE * t2 + S::SIX * t,
73                S::THREE * t2,
74            )
75        }
76
77        /// Sample the x coordinate of the curve's derivative at t (expecting t between 0 and 1).
78        pub fn dx(&self, t: S) -> S {
79            let (c0, c1, c2, c3) = self.derivative_coefficients(t);
80            self.from.x * c0 + self.ctrl1.x * c1 + self.ctrl2.x * c2 + self.to.x * c3
81        }
82    }
83
84    impl CubicBezierSegment {
85        // This is actually in the Monotonic<CubicBezierSegment<S>> impl
86        pub fn solve_t_for_x(&self, x: S, t_range: core::ops::Range<S>, tolerance: S) -> S {
87            debug_assert!(t_range.start <= t_range.end);
88            let from = self.x(t_range.start);
89            let to = self.x(t_range.end);
90            if x <= from {
91                return t_range.start;
92            }
93            if x >= to {
94                return t_range.end;
95            }
96
97            // Newton's method.
98            let mut t = x - from / (to - from);
99            for _ in 0..8 {
100                let x2 = self.x(t);
101
102                if S::abs(x2 - x) <= tolerance {
103                    return t;
104                }
105
106                let dx = self.dx(t);
107
108                if dx <= S::EPSILON {
109                    break;
110                }
111
112                t -= (x2 - x) / dx;
113            }
114
115            // Fall back to binary search.
116            let mut min = t_range.start;
117            let mut max = t_range.end;
118            let mut t = S::HALF;
119
120            while min < max {
121                let x2 = self.x(t);
122
123                if S::abs(x2 - x) < tolerance {
124                    return t;
125                }
126
127                if x > x2 {
128                    min = t;
129                } else {
130                    max = t;
131                }
132
133                t = (max - min) * S::HALF + min;
134            }
135
136            t
137        }
138    }
139}
140
141/// The representation of an easing curve, for animations
142#[repr(C, u32)]
143#[derive(Debug, Clone, Copy, PartialEq, Default)]
144pub enum EasingCurve {
145    /// The linear curve
146    #[default]
147    Linear,
148    /// A Cubic bezier curve, with its 4 parameters
149    CubicBezier([f32; 4]),
150    /// Easing curve as defined at: <https://easings.net/#easeInElastic>
151    EaseInElastic,
152    /// Easing curve as defined at: <https://easings.net/#easeOutElastic>
153    EaseOutElastic,
154    /// Easing curve as defined at: <https://easings.net/#easeInOutElastic>
155    EaseInOutElastic,
156    /// Easing curve as defined at: <https://easings.net/#easeInBounce>
157    EaseInBounce,
158    /// Easing curve as defined at: <https://easings.net/#easeOutBounce>
159    EaseOutBounce,
160    /// Easing curve as defined at: <https://easings.net/#easeInOutBounce>
161    EaseInOutBounce,
162    // Custom(Box<dyn Fn(f32) -> f32>),
163}
164
165/// Represent an instant, in milliseconds since the AnimationDriver's initial_instant
166#[repr(transparent)]
167#[derive(Copy, Clone, Debug, Default, PartialEq, Ord, PartialOrd, Eq)]
168pub struct Instant(pub u64);
169
170impl core::ops::Sub<Instant> for Instant {
171    type Output = core::time::Duration;
172    fn sub(self, other: Self) -> core::time::Duration {
173        core::time::Duration::from_millis(self.0 - other.0)
174    }
175}
176
177impl core::ops::Sub<core::time::Duration> for Instant {
178    type Output = Instant;
179    fn sub(self, other: core::time::Duration) -> Instant {
180        Self(self.0 - other.as_millis() as u64)
181    }
182}
183
184impl core::ops::Add<core::time::Duration> for Instant {
185    type Output = Instant;
186    fn add(self, other: core::time::Duration) -> Instant {
187        Self(self.0 + other.as_millis() as u64)
188    }
189}
190
191impl core::ops::AddAssign<core::time::Duration> for Instant {
192    fn add_assign(&mut self, other: core::time::Duration) {
193        self.0 += other.as_millis() as u64;
194    }
195}
196
197impl core::ops::SubAssign<core::time::Duration> for Instant {
198    fn sub_assign(&mut self, other: core::time::Duration) {
199        self.0 -= other.as_millis() as u64;
200    }
201}
202
203impl Instant {
204    /// Returns the amount of time elapsed since an other instant.
205    ///
206    /// Equivalent to `self - earlier`
207    pub fn duration_since(self, earlier: Instant) -> core::time::Duration {
208        self - earlier
209    }
210
211    /// Wrapper around [`std::time::Instant::now()`] that delegates to the backend
212    /// and allows working in no_std environments.
213    pub fn now() -> Self {
214        Self(Self::duration_since_start().as_millis() as u64)
215    }
216
217    fn duration_since_start() -> core::time::Duration {
218        crate::context::GLOBAL_CONTEXT
219            .with(|p| p.get().map(|p| p.platform().duration_since_start()))
220            .unwrap_or_default()
221    }
222
223    /// Return the number of milliseconds this `Instant` is after the backend has started
224    pub fn as_millis(&self) -> u64 {
225        self.0
226    }
227}
228
229/// The AnimationDriver
230pub struct AnimationDriver {
231    /// Indicate whether there are any active animations that require a future call to update_animations.
232    active_animations: Cell<bool>,
233    global_instant: core::pin::Pin<Box<crate::Property<Instant>>>,
234}
235
236impl Default for AnimationDriver {
237    fn default() -> Self {
238        AnimationDriver {
239            active_animations: Cell::default(),
240            global_instant: Box::pin(crate::Property::new_named(
241                Instant::default(),
242                "i_slint_core::AnimationDriver::global_instant",
243            )),
244        }
245    }
246}
247
248impl AnimationDriver {
249    /// Iterates through all animations based on the new time tick and updates their state. This should be called by
250    /// the windowing system driver for every frame.
251    pub fn update_animations(&self, new_tick: Instant) {
252        let current_tick = self.global_instant.as_ref().get_untracked();
253        assert!(current_tick <= new_tick, "The platform's clock is not monotonic!");
254        if current_tick != new_tick {
255            self.active_animations.set(false);
256            self.global_instant.as_ref().set(new_tick);
257        }
258    }
259
260    /// Returns true if there are any active or ready animations. This is used by the windowing system to determine
261    /// if a new animation frame is required or not. Returns false otherwise.
262    pub fn has_active_animations(&self) -> bool {
263        self.active_animations.get()
264    }
265
266    /// Tell the driver that there are active animations
267    pub fn set_has_active_animations(&self) {
268        self.active_animations.set(true);
269    }
270    /// The current instant that is to be used for animation
271    /// using this function register the current binding as a dependency
272    pub fn current_tick(&self) -> Instant {
273        self.global_instant.as_ref().get()
274    }
275}
276
277crate::thread_local!(
278/// This is the default instance of the animation driver that's used to advance all property animations
279/// at the same time.
280pub static CURRENT_ANIMATION_DRIVER : AnimationDriver = AnimationDriver::default()
281);
282
283/// The current instant that is to be used for animation
284/// using this function register the current binding as a dependency
285pub fn current_tick() -> Instant {
286    CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick())
287}
288
289/// Same as [`current_tick`], but also register that one should be running animation
290/// on next frame
291pub fn animation_tick() -> u64 {
292    CURRENT_ANIMATION_DRIVER.with(|driver| {
293        driver.set_has_active_animations();
294        driver.current_tick().0
295    })
296}
297
298fn ease_out_bounce_curve(value: f32) -> f32 {
299    const N1: f32 = 7.5625;
300    const D1: f32 = 2.75;
301
302    if value < 1.0 / D1 {
303        N1 * value * value
304    } else if value < 2.0 / D1 {
305        let value = value - (1.5 / D1);
306        N1 * value * value + 0.75
307    } else if value < 2.5 / D1 {
308        let value = value - (2.25 / D1);
309        N1 * value * value + 0.9375
310    } else {
311        let value = value - (2.625 / D1);
312        N1 * value * value + 0.984375
313    }
314}
315
316/// map a value between 0 and 1 to another value between 0 and 1 according to the curve
317pub fn easing_curve(curve: &EasingCurve, value: f32) -> f32 {
318    match curve {
319        EasingCurve::Linear => value,
320        EasingCurve::CubicBezier([a, b, c, d]) => {
321            if !(0.0..=1.0).contains(a) && !(0.0..=1.0).contains(c) {
322                return value;
323            };
324            let curve = cubic_bezier::CubicBezierSegment {
325                from: (0., 0.).into(),
326                ctrl1: (*a, *b).into(),
327                ctrl2: (*c, *d).into(),
328                to: (1., 1.).into(),
329            };
330            curve.y(curve.solve_t_for_x(value, 0.0..1.0, 0.01))
331        }
332        EasingCurve::EaseInElastic => {
333            const C4: f32 = 2.0 * core::f32::consts::PI / 3.0;
334
335            if value == 0.0 {
336                0.0
337            } else if value == 1.0 {
338                1.0
339            } else {
340                -f32::powf(2.0, 10.0 * value - 10.0) * f32::sin((value * 10.0 - 10.75) * C4)
341            }
342        }
343        EasingCurve::EaseOutElastic => {
344            let c4 = (2.0 * core::f32::consts::PI) / 3.0;
345
346            if value == 0.0 {
347                0.0
348            } else if value == 1.0 {
349                1.0
350            } else {
351                2.0f32.powf(-10.0 * value) * ((value * 10.0 - 0.75) * c4).sin() + 1.0
352            }
353        }
354        EasingCurve::EaseInOutElastic => {
355            const C5: f32 = 2.0 * core::f32::consts::PI / 4.5;
356
357            if value == 0.0 {
358                0.0
359            } else if value == 1.0 {
360                1.0
361            } else if value < 0.5 {
362                -(f32::powf(2.0, 20.0 * value - 10.0) * f32::sin((20.0 * value - 11.125) * C5))
363                    / 2.0
364            } else {
365                (f32::powf(2.0, -20.0 * value + 10.0) * f32::sin((20.0 * value - 11.125) * C5))
366                    / 2.0
367                    + 1.0
368            }
369        }
370        EasingCurve::EaseInBounce => 1.0 - ease_out_bounce_curve(1.0 - value),
371        EasingCurve::EaseOutBounce => ease_out_bounce_curve(value),
372        EasingCurve::EaseInOutBounce => {
373            if value < 0.5 {
374                (1.0 - ease_out_bounce_curve(1.0 - 2.0 * value)) / 2.0
375            } else {
376                (1.0 + ease_out_bounce_curve(2.0 * value - 1.0)) / 2.0
377            }
378        }
379    }
380}
381
382/*
383#[test]
384fn easing_test() {
385    fn test_curve(name: &str, curve: &EasingCurve) {
386        let mut img = image::ImageBuffer::new(500, 500);
387        let white = image::Rgba([255 as u8, 255 as u8, 255 as u8, 255 as u8]);
388
389        for x in 0..img.width() {
390            let t = (x as f32) / (img.width() as f32);
391            let y = easing_curve(curve, t);
392            let y = (y * (img.height() as f32)) as u32;
393            let y = y.min(img.height() - 1);
394            *img.get_pixel_mut(x, img.height() - 1 - y) = white;
395        }
396
397        img.save(
398            std::path::PathBuf::from(std::env::var_os("HOME").unwrap())
399                .join(format!("{}.png", name)),
400        )
401        .unwrap();
402    }
403
404    test_curve("linear", &EasingCurve::Linear);
405    test_curve("linear2", &EasingCurve::CubicBezier([0.0, 0.0, 1.0, 1.0]));
406    test_curve("ease", &EasingCurve::CubicBezier([0.25, 0.1, 0.25, 1.0]));
407    test_curve("ease_in", &EasingCurve::CubicBezier([0.42, 0.0, 1.0, 1.0]));
408    test_curve("ease_in_out", &EasingCurve::CubicBezier([0.42, 0.0, 0.58, 1.0]));
409    test_curve("ease_out", &EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]));
410}
411*/
412
413/// Update the global animation time to the current time
414pub fn update_animations() {
415    CURRENT_ANIMATION_DRIVER.with(|driver| {
416        #[allow(unused_mut)]
417        let mut duration = Instant::duration_since_start().as_millis() as u64;
418        #[cfg(feature = "std")]
419        if let Ok(val) = std::env::var("SLINT_SLOW_ANIMATIONS") {
420            let factor = val.parse().unwrap_or(2);
421            duration /= factor;
422        };
423        driver.update_animations(Instant(duration))
424    });
425}