i_slint_core/
animations.rs1#![warn(missing_docs)]
5use alloc::boxed::Box;
8use core::cell::Cell;
9#[cfg(not(feature = "std"))]
10use num_traits::Float;
11
12mod cubic_bezier {
13 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 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 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 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 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 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 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#[repr(C, u32)]
143#[derive(Debug, Clone, Copy, PartialEq, Default)]
144pub enum EasingCurve {
145 #[default]
147 Linear,
148 CubicBezier([f32; 4]),
150 EaseInElastic,
152 EaseOutElastic,
154 EaseInOutElastic,
156 EaseInBounce,
158 EaseOutBounce,
160 EaseInOutBounce,
162 }
164
165#[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 pub fn duration_since(self, earlier: Instant) -> core::time::Duration {
208 self - earlier
209 }
210
211 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 pub fn as_millis(&self) -> u64 {
225 self.0
226 }
227}
228
229pub struct AnimationDriver {
231 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 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 pub fn has_active_animations(&self) -> bool {
261 self.active_animations.get()
262 }
263
264 pub fn set_has_active_animations(&self) {
266 self.active_animations.set(true);
267 }
268 pub fn current_tick(&self) -> Instant {
271 self.global_instant.as_ref().get()
272 }
273}
274
275crate::thread_local!(
276pub static CURRENT_ANIMATION_DRIVER : AnimationDriver = AnimationDriver::default()
279);
280
281pub fn current_tick() -> Instant {
284 CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick())
285}
286
287pub 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
314pub 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
380pub 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}