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 | |
7 | use alloc::boxed::Box; |
8 | use core::cell::Cell; |
9 | #[cfg (not(feature = "std" ))] |
10 | use num_traits::Float; |
11 | |
12 | mod 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)] |
144 | pub 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)] |
168 | pub struct Instant(pub u64); |
169 | |
170 | impl 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 | |
177 | impl 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 | |
184 | impl 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 | |
191 | impl 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 | |
197 | impl 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 | |
203 | impl 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 |
230 | pub 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 | |
236 | impl Default for AnimationDriver { |
237 | fn default() -> Self { |
238 | AnimationDriver { |
239 | active_animations: Cell::default(), |
240 | global_instant: Box::pin(crate::Property::new_named( |
241 | value:Instant::default(), |
242 | _name:"i_slint_core::AnimationDriver::global_instant" , |
243 | )), |
244 | } |
245 | } |
246 | } |
247 | |
248 | impl 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 | |
275 | crate::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. |
278 | pub 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 |
283 | pub fn current_tick() -> Instant { |
284 | CURRENT_ANIMATION_DRIVER.with(|driver: &AnimationDriver| driver.current_tick()) |
285 | } |
286 | |
287 | /// Same as [`current_tick`], but also register that one should be running animation |
288 | /// on next frame |
289 | pub fn animation_tick() -> u64 { |
290 | CURRENT_ANIMATION_DRIVER.with(|driver: &AnimationDriver| { |
291 | driver.set_has_active_animations(); |
292 | driver.current_tick().0 |
293 | }) |
294 | } |
295 | |
296 | fn 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: f32 = value - (1.5 / D1); |
304 | N1 * value * value + 0.75 |
305 | } else if value < 2.5 / D1 { |
306 | let value: f32 = value - (2.25 / D1); |
307 | N1 * value * value + 0.9375 |
308 | } else { |
309 | let value: f32 = 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 |
315 | pub 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] |
382 | fn 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 |
412 | pub fn update_animations() { |
413 | CURRENT_ANIMATION_DRIVER.with(|driver: &AnimationDriver| { |
414 | #[allow (unused_mut)] |
415 | let mut duration: u64 = Instant::duration_since_start().as_millis() as u64; |
416 | #[cfg (feature = "std" )] |
417 | if let Ok(val: String) = std::env::var(key:"SLINT_SLOW_ANIMATIONS" ) { |
418 | let factor: u64 = val.parse().unwrap_or(default:2); |
419 | duration /= factor; |
420 | }; |
421 | driver.update_animations(new_tick:Instant(duration)) |
422 | }); |
423 | } |
424 | |