Skip to main content

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