1#![warn(missing_docs)]
5use alloc::boxed::Box;
8use core::cell::Cell;
9#[cfg(not(feature = "std"))]
10use num_traits::Float;
11
12pub(crate) mod simulations;
13
14mod cubic_bezier {
15 type S = f32;
18 use euclid::default::Point2D as Point;
19 #[allow(unused)]
20 use num_traits::Float;
21 trait Scalar {
22 const ONE: f32 = 1.;
23 const THREE: f32 = 3.;
24 const HALF: f32 = 0.5;
25 const SIX: f32 = 6.;
26 const NINE: f32 = 9.;
27 fn value(v: f32) -> f32 {
28 v
29 }
30 }
31 impl Scalar for f32 {}
32 pub struct CubicBezierSegment {
33 pub from: Point<S>,
34 pub ctrl1: Point<S>,
35 pub ctrl2: Point<S>,
36 pub to: Point<S>,
37 }
38
39 impl CubicBezierSegment {
40 pub fn x(&self, t: S) -> S {
42 let t2 = t * t;
43 let t3 = t2 * t;
44 let one_t = S::ONE - t;
45 let one_t2 = one_t * one_t;
46 let one_t3 = one_t2 * one_t;
47
48 self.from.x * one_t3
49 + self.ctrl1.x * S::THREE * one_t2 * t
50 + self.ctrl2.x * S::THREE * one_t * t2
51 + self.to.x * t3
52 }
53
54 pub fn y(&self, t: S) -> S {
56 let t2 = t * t;
57 let t3 = t2 * t;
58 let one_t = S::ONE - t;
59 let one_t2 = one_t * one_t;
60 let one_t3 = one_t2 * one_t;
61
62 self.from.y * one_t3
63 + self.ctrl1.y * S::THREE * one_t2 * t
64 + self.ctrl2.y * S::THREE * one_t * t2
65 + self.to.y * t3
66 }
67
68 #[inline]
69 fn derivative_coefficients(&self, t: S) -> (S, S, S, S) {
70 let t2 = t * t;
71 (
72 -S::THREE * t2 + S::SIX * t - S::THREE,
73 S::NINE * t2 - S::value(12.0) * t + S::THREE,
74 -S::NINE * t2 + S::SIX * t,
75 S::THREE * t2,
76 )
77 }
78
79 pub fn dx(&self, t: S) -> S {
81 let (c0, c1, c2, c3) = self.derivative_coefficients(t);
82 self.from.x * c0 + self.ctrl1.x * c1 + self.ctrl2.x * c2 + self.to.x * c3
83 }
84 }
85
86 impl CubicBezierSegment {
87 pub fn solve_t_for_x(&self, x: S, t_range: core::ops::Range<S>, tolerance: S) -> S {
89 debug_assert!(t_range.start <= t_range.end);
90 let from = self.x(t_range.start);
91 let to = self.x(t_range.end);
92 if x <= from {
93 return t_range.start;
94 }
95 if x >= to {
96 return t_range.end;
97 }
98
99 let mut t = (x - from) / (to - from);
101 let mut degenerate = false;
102 for _ in 0..8 {
103 let x2 = self.x(t);
104 let dx = self.dx(t);
105
106 if dx <= S::EPSILON {
107 degenerate = true;
108 break;
109 }
110
111 let step = (x2 - x) / dx;
112 t -= step;
113
114 if S::abs(step) <= tolerance {
115 return t.max(t_range.start).min(t_range.end);
116 }
117 }
118
119 if !degenerate {
120 return t.max(t_range.start).min(t_range.end);
121 }
122
123 let mut min = t_range.start;
125 let mut max = t_range.end;
126 let mut t = S::HALF;
127
128 while min < max {
129 let x2 = self.x(t);
130
131 if S::abs(x2 - x) < tolerance {
132 return t;
133 }
134
135 if x > x2 {
136 min = t;
137 } else {
138 max = t;
139 }
140
141 t = (max - min) * S::HALF + min;
142 }
143
144 t
145 }
146 }
147}
148
149#[repr(C, u32)]
151#[derive(Debug, Clone, Copy, PartialEq, Default)]
152pub enum EasingCurve {
153 #[default]
155 Linear,
156 CubicBezier([f32; 4]),
158 EaseInElastic,
160 EaseOutElastic,
162 EaseInOutElastic,
164 EaseInBounce,
166 EaseOutBounce,
168 EaseInOutBounce,
170 Spring(f32),
173 }
175
176#[repr(transparent)]
178#[derive(Copy, Clone, Debug, Default, PartialEq, Ord, PartialOrd, Eq)]
179pub struct Instant(pub u64);
180
181impl core::ops::Sub<Instant> for Instant {
182 type Output = core::time::Duration;
183 fn sub(self, other: Self) -> core::time::Duration {
184 core::time::Duration::from_millis(self.0 - other.0)
185 }
186}
187
188impl core::ops::Sub<core::time::Duration> for Instant {
189 type Output = Instant;
190 fn sub(self, other: core::time::Duration) -> Instant {
191 Self(self.0 - other.as_millis() as u64)
192 }
193}
194
195impl core::ops::Add<core::time::Duration> for Instant {
196 type Output = Instant;
197 fn add(self, other: core::time::Duration) -> Instant {
198 Self(self.0 + other.as_millis() as u64)
199 }
200}
201
202impl core::ops::AddAssign<core::time::Duration> for Instant {
203 fn add_assign(&mut self, other: core::time::Duration) {
204 self.0 += other.as_millis() as u64;
205 }
206}
207
208impl core::ops::SubAssign<core::time::Duration> for Instant {
209 fn sub_assign(&mut self, other: core::time::Duration) {
210 self.0 -= other.as_millis() as u64;
211 }
212}
213
214impl Instant {
215 pub fn duration_since(self, earlier: Instant) -> core::time::Duration {
219 self - earlier
220 }
221
222 pub fn now(ctx: &crate::SlintContext) -> Self {
229 Self(ctx.platform().duration_since_start().as_millis() as u64)
230 }
231
232 pub fn as_millis(&self) -> u64 {
234 self.0
235 }
236}
237
238pub struct AnimationDriver {
240 active_animations: Cell<bool>,
242 global_instant: core::pin::Pin<Box<crate::Property<Instant>>>,
243}
244
245impl Default for AnimationDriver {
246 fn default() -> Self {
247 AnimationDriver {
248 active_animations: Cell::default(),
249 global_instant: Box::pin(crate::Property::new_named(
250 Instant::default(),
251 "i_slint_core::AnimationDriver::global_instant",
252 )),
253 }
254 }
255}
256
257impl AnimationDriver {
258 pub fn update_animations(&self, new_tick: Instant) {
261 let current_tick = self.global_instant.as_ref().get_untracked();
262 assert!(current_tick <= new_tick, "The platform's clock is not monotonic!");
263 if current_tick != new_tick {
264 self.active_animations.set(false);
265 self.global_instant.as_ref().set(new_tick);
266 }
267 }
268
269 pub fn has_active_animations(&self) -> bool {
272 self.active_animations.get()
273 }
274
275 pub fn set_has_active_animations(&self) {
277 self.active_animations.set(true);
278 }
279 pub fn current_tick(&self) -> Instant {
282 self.global_instant.as_ref().get()
283 }
284}
285
286crate::thread_local!(
287pub static CURRENT_ANIMATION_DRIVER : AnimationDriver = AnimationDriver::default()
290);
291
292pub fn current_tick() -> Instant {
295 CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick())
296}
297
298pub fn animation_tick() -> u64 {
301 CURRENT_ANIMATION_DRIVER.with(|driver| {
302 driver.set_has_active_animations();
303 driver.current_tick().0
304 })
305}
306
307fn ease_out_bounce_curve(value: f32) -> f32 {
308 const N1: f32 = 7.5625;
309 const D1: f32 = 2.75;
310
311 if value < 1.0 / D1 {
312 N1 * value * value
313 } else if value < 2.0 / D1 {
314 let value = value - (1.5 / D1);
315 N1 * value * value + 0.75
316 } else if value < 2.5 / D1 {
317 let value = value - (2.25 / D1);
318 N1 * value * value + 0.9375
319 } else {
320 let value = value - (2.625 / D1);
321 N1 * value * value + 0.984375
322 }
323}
324
325const SPRING_SETTLE_POSITION_EPSILON: f32 = 0.001;
329const SPRING_SETTLE_VELOCITY_EPSILON: f32 = 0.05;
330
331pub fn spring_settle_progress(
333 regime: &simulations::spring::SpringRegime,
334 elapsed_secs: f32,
335) -> (f32, bool) {
336 let (rel_pos, rel_vel) = regime.evaluate(elapsed_secs);
337 let settled = rel_pos.abs() < SPRING_SETTLE_POSITION_EPSILON
338 && rel_vel.abs() < SPRING_SETTLE_VELOCITY_EPSILON;
339 (1.0 + rel_pos, settled)
340}
341
342const SPRING_SETTLE_ZETA: f32 = 1.0 - 0.87;
350
351pub fn spring_settle_within(
353 regime: &simulations::spring::SpringRegime,
354 elapsed_secs: f32,
355 w_n: f32,
356) -> simulations::spring::SpringRegime {
357 let (rel_pos, rel_vel) = regime.evaluate(elapsed_secs);
358 let zeta = regime.zeta().max(SPRING_SETTLE_ZETA);
359 simulations::spring::SpringRegime::new(rel_pos, rel_vel, w_n, zeta)
360}
361
362pub fn easing_curve(curve: &EasingCurve, value: f32) -> f32 {
364 match curve {
365 EasingCurve::Linear => value,
366 EasingCurve::CubicBezier([a, b, c, d]) => {
367 if !(0.0..=1.0).contains(a) && !(0.0..=1.0).contains(c) {
368 return value;
369 };
370 let curve = cubic_bezier::CubicBezierSegment {
371 from: (0., 0.).into(),
372 ctrl1: (*a, *b).into(),
373 ctrl2: (*c, *d).into(),
374 to: (1., 1.).into(),
375 };
376 curve.y(curve.solve_t_for_x(value, 0.0..1.0, 0.01))
377 }
378 EasingCurve::EaseInElastic => {
379 const C4: f32 = 2.0 * core::f32::consts::PI / 3.0;
380
381 if value == 0.0 {
382 0.0
383 } else if value == 1.0 {
384 1.0
385 } else {
386 -f32::powf(2.0, 10.0 * value - 10.0) * f32::sin((value * 10.0 - 10.75) * C4)
387 }
388 }
389 EasingCurve::EaseOutElastic => {
390 let c4 = (2.0 * core::f32::consts::PI) / 3.0;
391
392 if value == 0.0 {
393 0.0
394 } else if value == 1.0 {
395 1.0
396 } else {
397 2.0f32.powf(-10.0 * value) * ((value * 10.0 - 0.75) * c4).sin() + 1.0
398 }
399 }
400 EasingCurve::EaseInOutElastic => {
401 const C5: f32 = 2.0 * core::f32::consts::PI / 4.5;
402
403 if value == 0.0 {
404 0.0
405 } else if value == 1.0 {
406 1.0
407 } else if value < 0.5 {
408 -(f32::powf(2.0, 20.0 * value - 10.0) * f32::sin((20.0 * value - 11.125) * C5))
409 / 2.0
410 } else {
411 (f32::powf(2.0, -20.0 * value + 10.0) * f32::sin((20.0 * value - 11.125) * C5))
412 / 2.0
413 + 1.0
414 }
415 }
416 EasingCurve::EaseInBounce => 1.0 - ease_out_bounce_curve(1.0 - value),
417 EasingCurve::EaseOutBounce => ease_out_bounce_curve(value),
418 EasingCurve::EaseInOutBounce => {
419 if value < 0.5 {
420 (1.0 - ease_out_bounce_curve(1.0 - 2.0 * value)) / 2.0
421 } else {
422 (1.0 + ease_out_bounce_curve(2.0 * value - 1.0)) / 2.0
423 }
424 }
425 EasingCurve::Spring(_) => {
426 panic!("Springs are handled separately");
427 }
428 }
429}
430
431pub fn update_animations(now: Instant) {
469 CURRENT_ANIMATION_DRIVER.with(|driver| {
470 #[allow(unused_mut)]
471 let mut duration = now.0;
472 #[cfg(feature = "std")]
473 if let Ok(val) = std::env::var("SLINT_SLOW_ANIMATIONS") {
474 let factor = val.parse().unwrap_or(2).max(1);
475 duration /= factor;
476 };
477 driver.update_animations(Instant(duration))
478 });
479}