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        if self.global_instant.as_ref().get_untracked() != new_tick {
253            self.active_animations.set(false);
254            self.global_instant.as_ref().set(new_tick);
255        }
256    }
257
258    /// Returns true if there are any active or ready animations. This is used by the windowing system to determine
259    /// if a new animation frame is required or not. Returns false otherwise.
260    pub fn has_active_animations(&self) -> bool {
261        self.active_animations.get()
262    }
263
264    /// Tell the driver that there are active animations
265    pub fn set_has_active_animations(&self) {
266        self.active_animations.set(true);
267    }
268    /// The current instant that is to be used for animation
269    /// using this function register the current binding as a dependency
270    pub fn current_tick(&self) -> Instant {
271        self.global_instant.as_ref().get()
272    }
273}
274
275crate::thread_local!(
276/// This is the default instance of the animation driver that's used to advance all property animations
277/// at the same time.
278pub static CURRENT_ANIMATION_DRIVER : AnimationDriver = AnimationDriver::default()
279);
280
281/// The current instant that is to be used for animation
282/// using this function register the current binding as a dependency
283pub fn current_tick() -> Instant {
284    CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick())
285}
286
287/// Same as [`current_tick`], but also register that one should be running animation
288/// on next frame
289pub fn animation_tick() -> u64 {
290    CURRENT_ANIMATION_DRIVER.with(|driver| {
291        driver.set_has_active_animations();
292        driver.current_tick().0
293    })
294}
295
296fn ease_out_bounce_curve(value: f32) -> f32 {
297    const N1: f32 = 7.5625;
298    const D1: f32 = 2.75;
299
300    if value < 1.0 / D1 {
301        N1 * value * value
302    } else if value < 2.0 / D1 {
303        let value = value - (1.5 / D1);
304        N1 * value * value + 0.75
305    } else if value < 2.5 / D1 {
306        let value = value - (2.25 / D1);
307        N1 * value * value + 0.9375
308    } else {
309        let value = value - (2.625 / D1);
310        N1 * value * value + 0.984375
311    }
312}
313
314/// map a value between 0 and 1 to another value between 0 and 1 according to the curve
315pub fn easing_curve(curve: &EasingCurve, value: f32) -> f32 {
316    match curve {
317        EasingCurve::Linear => value,
318        EasingCurve::CubicBezier([a, b, c, d]) => {
319            if !(0.0..=1.0).contains(a) && !(0.0..=1.0).contains(c) {
320                return value;
321            };
322            let curve = cubic_bezier::CubicBezierSegment {
323                from: (0., 0.).into(),
324                ctrl1: (*a, *b).into(),
325                ctrl2: (*c, *d).into(),
326                to: (1., 1.).into(),
327            };
328            curve.y(curve.solve_t_for_x(value, 0.0..1.0, 0.01))
329        }
330        EasingCurve::EaseInElastic => {
331            const C4: f32 = 2.0 * core::f32::consts::PI / 3.0;
332
333            if value == 0.0 {
334                0.0
335            } else if value == 1.0 {
336                1.0
337            } else {
338                -f32::powf(2.0, 10.0 * value - 10.0) * f32::sin((value * 10.0 - 10.75) * C4)
339            }
340        }
341        EasingCurve::EaseOutElastic => {
342            let c4 = (2.0 * core::f32::consts::PI) / 3.0;
343
344            if value == 0.0 {
345                0.0
346            } else if value == 1.0 {
347                1.0
348            } else {
349                2.0f32.powf(-10.0 * value) * ((value * 10.0 - 0.75) * c4).sin() + 1.0
350            }
351        }
352        EasingCurve::EaseInOutElastic => {
353            const C5: f32 = 2.0 * core::f32::consts::PI / 4.5;
354
355            if value == 0.0 {
356                0.0
357            } else if value == 1.0 {
358                1.0
359            } else if value < 0.5 {
360                -(f32::powf(2.0, 20.0 * value - 10.0) * f32::sin((20.0 * value - 11.125) * C5))
361                    / 2.0
362            } else {
363                (f32::powf(2.0, -20.0 * value + 10.0) * f32::sin((20.0 * value - 11.125) * C5))
364                    / 2.0
365                    + 1.0
366            }
367        }
368        EasingCurve::EaseInBounce => 1.0 - ease_out_bounce_curve(1.0 - value),
369        EasingCurve::EaseOutBounce => ease_out_bounce_curve(value),
370        EasingCurve::EaseInOutBounce => {
371            if value < 0.5 {
372                (1.0 - ease_out_bounce_curve(1.0 - 2.0 * value)) / 2.0
373            } else {
374                (1.0 + ease_out_bounce_curve(2.0 * value - 1.0)) / 2.0
375            }
376        }
377    }
378}
379
380/*
381#[test]
382fn easing_test() {
383    fn test_curve(name: &str, curve: &EasingCurve) {
384        let mut img = image::ImageBuffer::new(500, 500);
385        let white = image::Rgba([255 as u8, 255 as u8, 255 as u8, 255 as u8]);
386
387        for x in 0..img.width() {
388            let t = (x as f32) / (img.width() as f32);
389            let y = easing_curve(curve, t);
390            let y = (y * (img.height() as f32)) as u32;
391            let y = y.min(img.height() - 1);
392            *img.get_pixel_mut(x, img.height() - 1 - y) = white;
393        }
394
395        img.save(
396            std::path::PathBuf::from(std::env::var_os("HOME").unwrap())
397                .join(format!("{}.png", name)),
398        )
399        .unwrap();
400    }
401
402    test_curve("linear", &EasingCurve::Linear);
403    test_curve("linear2", &EasingCurve::CubicBezier([0.0, 0.0, 1.0, 1.0]));
404    test_curve("ease", &EasingCurve::CubicBezier([0.25, 0.1, 0.25, 1.0]));
405    test_curve("ease_in", &EasingCurve::CubicBezier([0.42, 0.0, 1.0, 1.0]));
406    test_curve("ease_in_out", &EasingCurve::CubicBezier([0.42, 0.0, 0.58, 1.0]));
407    test_curve("ease_out", &EasingCurve::CubicBezier([0.0, 0.0, 0.58, 1.0]));
408}
409*/
410
411/// Update the global animation time to the current time
412pub fn update_animations() {
413    CURRENT_ANIMATION_DRIVER.with(|driver| {
414        #[allow(unused_mut)]
415        let mut duration = Instant::duration_since_start().as_millis() as u64;
416        #[cfg(feature = "std")]
417        if let Ok(val) = std::env::var("SLINT_SLOW_ANIMATIONS") {
418            let factor = val.parse().unwrap_or(2);
419            duration /= factor;
420        };
421        driver.update_animations(Instant(duration))
422    });
423}