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 | use super::*; |
5 | use crate::{ |
6 | items::{AnimationDirection, PropertyAnimation}, |
7 | lengths::LogicalLength, |
8 | }; |
9 | #[cfg (not(feature = "std" ))] |
10 | use num_traits::Float; |
11 | |
12 | enum AnimationState { |
13 | Delaying, |
14 | Animating { current_iteration: u64 }, |
15 | Done { iteration_count: u64 }, |
16 | } |
17 | |
18 | pub(super) struct PropertyValueAnimationData<T> { |
19 | from_value: T, |
20 | to_value: T, |
21 | details: PropertyAnimation, |
22 | start_time: crate::animations::Instant, |
23 | state: AnimationState, |
24 | } |
25 | |
26 | impl<T: InterpolatedPropertyValue + Clone> PropertyValueAnimationData<T> { |
27 | pub fn new(from_value: T, to_value: T, details: PropertyAnimation) -> Self { |
28 | let start_time = crate::animations::current_tick(); |
29 | |
30 | Self { from_value, to_value, details, start_time, state: AnimationState::Delaying } |
31 | } |
32 | |
33 | pub fn compute_interpolated_value(&mut self) -> (T, bool) { |
34 | let new_tick = crate::animations::current_tick(); |
35 | let mut time_progress = new_tick.duration_since(self.start_time).as_millis() as u64; |
36 | let reversed = |iteration: u64| -> bool { |
37 | match self.details.direction { |
38 | AnimationDirection::Normal => false, |
39 | AnimationDirection::Reverse => true, |
40 | AnimationDirection::Alternate => iteration % 2 == 1, |
41 | AnimationDirection::AlternateReverse => iteration % 2 == 0, |
42 | } |
43 | }; |
44 | |
45 | match self.state { |
46 | AnimationState::Delaying => { |
47 | if self.details.delay <= 0 { |
48 | self.state = AnimationState::Animating { current_iteration: 0 }; |
49 | return self.compute_interpolated_value(); |
50 | } |
51 | |
52 | let delay = self.details.delay as u64; |
53 | |
54 | if time_progress < delay { |
55 | if reversed(0) { |
56 | (self.to_value.clone(), false) |
57 | } else { |
58 | (self.from_value.clone(), false) |
59 | } |
60 | } else { |
61 | self.start_time = |
62 | new_tick - core::time::Duration::from_millis(time_progress - delay); |
63 | |
64 | // Decide on next state: |
65 | self.state = AnimationState::Animating { current_iteration: 0 }; |
66 | self.compute_interpolated_value() |
67 | } |
68 | } |
69 | AnimationState::Animating { mut current_iteration } => { |
70 | if self.details.duration <= 0 || self.details.iteration_count == 0. { |
71 | self.state = AnimationState::Done { iteration_count: 0 }; |
72 | return self.compute_interpolated_value(); |
73 | } |
74 | |
75 | let duration = self.details.duration as u64; |
76 | if time_progress >= duration { |
77 | // wrap around |
78 | current_iteration += time_progress / duration; |
79 | time_progress %= duration; |
80 | self.start_time = new_tick - core::time::Duration::from_millis(time_progress); |
81 | } |
82 | |
83 | if (self.details.iteration_count < 0.) |
84 | || (((current_iteration * duration) + time_progress) as f64) |
85 | < ((self.details.iteration_count as f64) * (duration as f64)) |
86 | { |
87 | self.state = AnimationState::Animating { current_iteration }; |
88 | |
89 | let progress = { |
90 | let progress = |
91 | (time_progress as f32 / self.details.duration as f32).clamp(0., 1.); |
92 | if reversed(current_iteration) { |
93 | 1. - progress |
94 | } else { |
95 | progress |
96 | } |
97 | }; |
98 | let t = crate::animations::easing_curve(&self.details.easing, progress); |
99 | let val = self.from_value.interpolate(&self.to_value, t); |
100 | |
101 | (val, false) |
102 | } else { |
103 | self.state = |
104 | AnimationState::Done { iteration_count: current_iteration.max(1) - 1 }; |
105 | self.compute_interpolated_value() |
106 | } |
107 | } |
108 | AnimationState::Done { iteration_count } => { |
109 | if reversed(iteration_count) { |
110 | (self.from_value.clone(), true) |
111 | } else { |
112 | (self.to_value.clone(), true) |
113 | } |
114 | } |
115 | } |
116 | } |
117 | |
118 | fn reset(&mut self) { |
119 | self.state = AnimationState::Delaying; |
120 | self.start_time = crate::animations::current_tick(); |
121 | } |
122 | } |
123 | |
124 | #[derive (Clone, Copy, Eq, PartialEq, Debug)] |
125 | pub(super) enum AnimatedBindingState { |
126 | Animating, |
127 | NotAnimating, |
128 | ShouldStart, |
129 | } |
130 | |
131 | pub(super) struct AnimatedBindingCallable<T, A> { |
132 | pub(super) original_binding: PropertyHandle, |
133 | pub(super) state: Cell<AnimatedBindingState>, |
134 | pub(super) animation_data: RefCell<PropertyValueAnimationData<T>>, |
135 | pub(super) compute_animation_details: A, |
136 | } |
137 | |
138 | pub(super) type AnimationDetail = Option<(PropertyAnimation, crate::animations::Instant)>; |
139 | |
140 | unsafe impl<T: InterpolatedPropertyValue + Clone, A: Fn() -> AnimationDetail> BindingCallable |
141 | for AnimatedBindingCallable<T, A> |
142 | { |
143 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
144 | let original_binding = Pin::new_unchecked(&self.original_binding); |
145 | original_binding.register_as_dependency_to_current_binding( |
146 | #[cfg (slint_debug_property)] |
147 | "<AnimatedBindingCallable>" , |
148 | ); |
149 | match self.state.get() { |
150 | AnimatedBindingState::Animating => { |
151 | let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value(); |
152 | *(value as *mut T) = val; |
153 | if finished { |
154 | self.state.set(AnimatedBindingState::NotAnimating) |
155 | } else { |
156 | crate::animations::CURRENT_ANIMATION_DRIVER |
157 | .with(|driver| driver.set_has_active_animations()); |
158 | } |
159 | } |
160 | AnimatedBindingState::NotAnimating => { |
161 | self.original_binding.update(value); |
162 | } |
163 | AnimatedBindingState::ShouldStart => { |
164 | let value = &mut *(value as *mut T); |
165 | self.state.set(AnimatedBindingState::Animating); |
166 | let mut animation_data = self.animation_data.borrow_mut(); |
167 | // animation_data.details.iteration_count = 1.; |
168 | animation_data.from_value = value.clone(); |
169 | self.original_binding.update((&mut animation_data.to_value) as *mut T as *mut ()); |
170 | if let Some((details, start_time)) = (self.compute_animation_details)() { |
171 | animation_data.start_time = start_time; |
172 | animation_data.details = details; |
173 | } |
174 | let (val, finished) = animation_data.compute_interpolated_value(); |
175 | *value = val; |
176 | if finished { |
177 | self.state.set(AnimatedBindingState::NotAnimating) |
178 | } else { |
179 | crate::animations::CURRENT_ANIMATION_DRIVER |
180 | .with(|driver| driver.set_has_active_animations()); |
181 | } |
182 | } |
183 | }; |
184 | BindingResult::KeepBinding |
185 | } |
186 | fn mark_dirty(self: Pin<&Self>) { |
187 | if self.state.get() == AnimatedBindingState::ShouldStart { |
188 | return; |
189 | } |
190 | let original_dirty = self.original_binding.access(|b| b.unwrap().dirty.get()); |
191 | if original_dirty { |
192 | self.state.set(AnimatedBindingState::ShouldStart); |
193 | self.animation_data.borrow_mut().reset(); |
194 | } |
195 | } |
196 | } |
197 | |
198 | /// InterpolatedPropertyValue is a trait used to enable properties to be used with |
199 | /// animations that interpolate values. The basic requirement is the ability to apply |
200 | /// a progress that's typically between 0 and 1 to a range. |
201 | pub trait InterpolatedPropertyValue: PartialEq + Default + 'static { |
202 | /// Returns the interpolated value between self and target_value according to the |
203 | /// progress parameter t that's usually between 0 and 1. With certain animation |
204 | /// easing curves it may over- or undershoot though. |
205 | #[must_use ] |
206 | fn interpolate(&self, target_value: &Self, t: f32) -> Self; |
207 | } |
208 | |
209 | impl InterpolatedPropertyValue for f32 { |
210 | fn interpolate(&self, target_value: &Self, t: f32) -> Self { |
211 | self + t * (target_value - self) |
212 | } |
213 | } |
214 | |
215 | impl InterpolatedPropertyValue for i32 { |
216 | fn interpolate(&self, target_value: &Self, t: f32) -> Self { |
217 | self + (t * (target_value - self) as f32).round() as i32 |
218 | } |
219 | } |
220 | |
221 | impl InterpolatedPropertyValue for i64 { |
222 | fn interpolate(&self, target_value: &Self, t: f32) -> Self { |
223 | self + (t * (target_value - self) as f32).round() as Self |
224 | } |
225 | } |
226 | |
227 | impl InterpolatedPropertyValue for u8 { |
228 | fn interpolate(&self, target_value: &Self, t: f32) -> Self { |
229 | ((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).round().clamp(min:0., max:255.) |
230 | as u8 |
231 | } |
232 | } |
233 | |
234 | impl InterpolatedPropertyValue for LogicalLength { |
235 | fn interpolate(&self, target_value: &Self, t: f32) -> Self { |
236 | LogicalLength::new(self.get().interpolate(&target_value.get(), t)) |
237 | } |
238 | } |
239 | |
240 | impl<T: Clone + InterpolatedPropertyValue + 'static> Property<T> { |
241 | /// Change the value of this property, by animating (interpolating) from the current property's value |
242 | /// to the specified parameter value. The animation is done according to the parameters described by |
243 | /// the PropertyAnimation object. |
244 | /// |
245 | /// If other properties have binding depending of this property, these properties will |
246 | /// be marked as dirty. |
247 | pub fn set_animated_value(&self, value: T, animation_data: PropertyAnimation) { |
248 | // FIXME if the current value is a dirty binding, we must run it, but we do not have the context |
249 | let d = RefCell::new(properties_animations::PropertyValueAnimationData::new( |
250 | self.get_internal(), |
251 | value, |
252 | animation_data, |
253 | )); |
254 | // Safety: the BindingCallable will cast its argument to T |
255 | unsafe { |
256 | self.handle.set_binding( |
257 | move |val: *mut ()| { |
258 | let (value, finished) = d.borrow_mut().compute_interpolated_value(); |
259 | *(val as *mut T) = value; |
260 | if finished { |
261 | BindingResult::RemoveBinding |
262 | } else { |
263 | crate::animations::CURRENT_ANIMATION_DRIVER |
264 | .with(|driver| driver.set_has_active_animations()); |
265 | BindingResult::KeepBinding |
266 | } |
267 | }, |
268 | #[cfg (slint_debug_property)] |
269 | self.debug_name.borrow().as_str(), |
270 | ); |
271 | } |
272 | self.handle.mark_dirty( |
273 | #[cfg (slint_debug_property)] |
274 | self.debug_name.borrow().as_str(), |
275 | ); |
276 | } |
277 | |
278 | /// Set a binding to this property. |
279 | /// |
280 | pub fn set_animated_binding( |
281 | &self, |
282 | binding: impl Binding<T> + 'static, |
283 | animation_data: PropertyAnimation, |
284 | ) { |
285 | let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> { |
286 | original_binding: PropertyHandle { |
287 | handle: Cell::new( |
288 | (alloc_binding_holder(move |val: *mut ()| unsafe { |
289 | let val = &mut *(val as *mut T); |
290 | *(val as *mut T) = binding.evaluate(val); |
291 | BindingResult::KeepBinding |
292 | }) as usize) |
293 | | 0b10, |
294 | ), |
295 | }, |
296 | state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating), |
297 | animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new( |
298 | T::default(), |
299 | T::default(), |
300 | animation_data, |
301 | )), |
302 | compute_animation_details: || -> properties_animations::AnimationDetail { None }, |
303 | }; |
304 | |
305 | // Safety: the `AnimatedBindingCallable`'s type match the property type |
306 | unsafe { |
307 | self.handle.set_binding( |
308 | binding_callable, |
309 | #[cfg (slint_debug_property)] |
310 | self.debug_name.borrow().as_str(), |
311 | ) |
312 | }; |
313 | self.handle.mark_dirty( |
314 | #[cfg (slint_debug_property)] |
315 | self.debug_name.borrow().as_str(), |
316 | ); |
317 | } |
318 | |
319 | /// Set a binding to this property, providing a callback for the transition animation |
320 | /// |
321 | pub fn set_animated_binding_for_transition( |
322 | &self, |
323 | binding: impl Binding<T> + 'static, |
324 | compute_animation_details: impl Fn() -> (PropertyAnimation, crate::animations::Instant) |
325 | + 'static, |
326 | ) { |
327 | let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> { |
328 | original_binding: PropertyHandle { |
329 | handle: Cell::new( |
330 | (alloc_binding_holder(move |val: *mut ()| unsafe { |
331 | let val = &mut *(val as *mut T); |
332 | *(val as *mut T) = binding.evaluate(val); |
333 | BindingResult::KeepBinding |
334 | }) as usize) |
335 | | 0b10, |
336 | ), |
337 | }, |
338 | state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating), |
339 | animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new( |
340 | T::default(), |
341 | T::default(), |
342 | PropertyAnimation::default(), |
343 | )), |
344 | compute_animation_details: move || Some(compute_animation_details()), |
345 | }; |
346 | |
347 | // Safety: the `AnimatedBindingCallable`'s type match the property type |
348 | unsafe { |
349 | self.handle.set_binding( |
350 | binding_callable, |
351 | #[cfg (slint_debug_property)] |
352 | self.debug_name.borrow().as_str(), |
353 | ) |
354 | }; |
355 | self.handle.mark_dirty( |
356 | #[cfg (slint_debug_property)] |
357 | self.debug_name.borrow().as_str(), |
358 | ); |
359 | } |
360 | } |
361 | |
362 | #[cfg (test)] |
363 | mod animation_tests { |
364 | use super::*; |
365 | |
366 | #[derive (Default)] |
367 | struct Component { |
368 | width: Property<i32>, |
369 | width_times_two: Property<i32>, |
370 | feed_property: Property<i32>, // used by binding to feed values into width |
371 | } |
372 | |
373 | impl Component { |
374 | fn new_test_component() -> Rc<Self> { |
375 | let compo = Rc::new(Component::default()); |
376 | let w = Rc::downgrade(&compo); |
377 | compo.width_times_two.set_binding(move || { |
378 | let compo = w.upgrade().unwrap(); |
379 | get_prop_value(&compo.width) * 2 |
380 | }); |
381 | |
382 | compo |
383 | } |
384 | } |
385 | |
386 | const DURATION: std::time::Duration = std::time::Duration::from_millis(10000); |
387 | const DELAY: std::time::Duration = std::time::Duration::from_millis(800); |
388 | |
389 | // Helper just for testing |
390 | fn get_prop_value<T: Clone>(prop: &Property<T>) -> T { |
391 | unsafe { Pin::new_unchecked(prop).get() } |
392 | } |
393 | |
394 | #[test ] |
395 | fn properties_test_animation_negative_delay_triggered_by_set() { |
396 | let compo = Component::new_test_component(); |
397 | |
398 | let animation_details = PropertyAnimation { |
399 | delay: -25, |
400 | duration: DURATION.as_millis() as _, |
401 | iteration_count: 1., |
402 | ..PropertyAnimation::default() |
403 | }; |
404 | |
405 | compo.width.set(100); |
406 | assert_eq!(get_prop_value(&compo.width), 100); |
407 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
408 | |
409 | let start_time = crate::animations::current_tick(); |
410 | |
411 | compo.width.set_animated_value(200, animation_details); |
412 | assert_eq!(get_prop_value(&compo.width), 100); |
413 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
414 | |
415 | crate::animations::CURRENT_ANIMATION_DRIVER |
416 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
417 | assert_eq!(get_prop_value(&compo.width), 150); |
418 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
419 | |
420 | crate::animations::CURRENT_ANIMATION_DRIVER |
421 | .with(|driver| driver.update_animations(start_time + DURATION)); |
422 | assert_eq!(get_prop_value(&compo.width), 200); |
423 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
424 | |
425 | // Overshoot: Always to_value. |
426 | crate::animations::CURRENT_ANIMATION_DRIVER |
427 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
428 | assert_eq!(get_prop_value(&compo.width), 200); |
429 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
430 | |
431 | // the binding should be removed |
432 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
433 | } |
434 | |
435 | #[test ] |
436 | fn properties_test_animation_triggered_by_set() { |
437 | let compo = Component::new_test_component(); |
438 | |
439 | let animation_details = PropertyAnimation { |
440 | duration: DURATION.as_millis() as _, |
441 | iteration_count: 1., |
442 | ..PropertyAnimation::default() |
443 | }; |
444 | |
445 | compo.width.set(100); |
446 | assert_eq!(get_prop_value(&compo.width), 100); |
447 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
448 | |
449 | let start_time = crate::animations::current_tick(); |
450 | |
451 | compo.width.set_animated_value(200, animation_details); |
452 | assert_eq!(get_prop_value(&compo.width), 100); |
453 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
454 | |
455 | crate::animations::CURRENT_ANIMATION_DRIVER |
456 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
457 | assert_eq!(get_prop_value(&compo.width), 150); |
458 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
459 | |
460 | crate::animations::CURRENT_ANIMATION_DRIVER |
461 | .with(|driver| driver.update_animations(start_time + DURATION)); |
462 | assert_eq!(get_prop_value(&compo.width), 200); |
463 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
464 | |
465 | // Overshoot: Always to_value. |
466 | crate::animations::CURRENT_ANIMATION_DRIVER |
467 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
468 | assert_eq!(get_prop_value(&compo.width), 200); |
469 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
470 | |
471 | // the binding should be removed |
472 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
473 | } |
474 | |
475 | #[test ] |
476 | fn properties_test_delayed_animation_triggered_by_set() { |
477 | let compo = Component::new_test_component(); |
478 | |
479 | let animation_details = PropertyAnimation { |
480 | delay: DELAY.as_millis() as _, |
481 | iteration_count: 1., |
482 | duration: DURATION.as_millis() as _, |
483 | ..PropertyAnimation::default() |
484 | }; |
485 | |
486 | compo.width.set(100); |
487 | assert_eq!(get_prop_value(&compo.width), 100); |
488 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
489 | |
490 | let start_time = crate::animations::current_tick(); |
491 | |
492 | compo.width.set_animated_value(200, animation_details); |
493 | assert_eq!(get_prop_value(&compo.width), 100); |
494 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
495 | |
496 | // In delay: |
497 | crate::animations::CURRENT_ANIMATION_DRIVER |
498 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
499 | assert_eq!(get_prop_value(&compo.width), 100); |
500 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
501 | |
502 | // In animation: |
503 | crate::animations::CURRENT_ANIMATION_DRIVER |
504 | .with(|driver| driver.update_animations(start_time + DELAY)); |
505 | assert_eq!(get_prop_value(&compo.width), 100); |
506 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
507 | |
508 | crate::animations::CURRENT_ANIMATION_DRIVER |
509 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2)); |
510 | assert_eq!(get_prop_value(&compo.width), 150); |
511 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
512 | |
513 | crate::animations::CURRENT_ANIMATION_DRIVER |
514 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION)); |
515 | assert_eq!(get_prop_value(&compo.width), 200); |
516 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
517 | |
518 | // Overshoot: Always to_value. |
519 | crate::animations::CURRENT_ANIMATION_DRIVER |
520 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
521 | assert_eq!(get_prop_value(&compo.width), 200); |
522 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
523 | |
524 | // the binding should be removed |
525 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
526 | } |
527 | |
528 | #[test ] |
529 | fn properties_test_delayed_animation_fractual_iteration_triggered_by_set() { |
530 | let compo = Component::new_test_component(); |
531 | |
532 | let animation_details = PropertyAnimation { |
533 | delay: DELAY.as_millis() as _, |
534 | iteration_count: 1.5, |
535 | duration: DURATION.as_millis() as _, |
536 | ..PropertyAnimation::default() |
537 | }; |
538 | |
539 | compo.width.set(100); |
540 | assert_eq!(get_prop_value(&compo.width), 100); |
541 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
542 | |
543 | let start_time = crate::animations::current_tick(); |
544 | |
545 | compo.width.set_animated_value(200, animation_details); |
546 | assert_eq!(get_prop_value(&compo.width), 100); |
547 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
548 | |
549 | // In delay: |
550 | crate::animations::CURRENT_ANIMATION_DRIVER |
551 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
552 | assert_eq!(get_prop_value(&compo.width), 100); |
553 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
554 | |
555 | // In animation: |
556 | crate::animations::CURRENT_ANIMATION_DRIVER |
557 | .with(|driver| driver.update_animations(start_time + DELAY)); |
558 | assert_eq!(get_prop_value(&compo.width), 100); |
559 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
560 | |
561 | crate::animations::CURRENT_ANIMATION_DRIVER |
562 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2)); |
563 | assert_eq!(get_prop_value(&compo.width), 150); |
564 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
565 | |
566 | crate::animations::CURRENT_ANIMATION_DRIVER |
567 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION)); |
568 | assert_eq!(get_prop_value(&compo.width), 100); |
569 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
570 | |
571 | // (fractual) end of animation |
572 | crate::animations::CURRENT_ANIMATION_DRIVER |
573 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 4)); |
574 | assert_eq!(get_prop_value(&compo.width), 125); |
575 | assert_eq!(get_prop_value(&compo.width_times_two), 250); |
576 | |
577 | // End of animation: |
578 | crate::animations::CURRENT_ANIMATION_DRIVER |
579 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
580 | assert_eq!(get_prop_value(&compo.width), 200); |
581 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
582 | |
583 | // the binding should be removed |
584 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
585 | } |
586 | #[test ] |
587 | fn properties_test_delayed_animation_null_duration_triggered_by_set() { |
588 | let compo = Component::new_test_component(); |
589 | |
590 | let animation_details = PropertyAnimation { |
591 | delay: DELAY.as_millis() as _, |
592 | iteration_count: 1.0, |
593 | duration: 0, |
594 | ..PropertyAnimation::default() |
595 | }; |
596 | |
597 | compo.width.set(100); |
598 | assert_eq!(get_prop_value(&compo.width), 100); |
599 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
600 | |
601 | let start_time = crate::animations::current_tick(); |
602 | |
603 | compo.width.set_animated_value(200, animation_details); |
604 | assert_eq!(get_prop_value(&compo.width), 100); |
605 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
606 | |
607 | // In delay: |
608 | crate::animations::CURRENT_ANIMATION_DRIVER |
609 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
610 | assert_eq!(get_prop_value(&compo.width), 100); |
611 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
612 | |
613 | // No animation: |
614 | crate::animations::CURRENT_ANIMATION_DRIVER |
615 | .with(|driver| driver.update_animations(start_time + DELAY)); |
616 | assert_eq!(get_prop_value(&compo.width), 200); |
617 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
618 | |
619 | // Overshoot: Always to_value. |
620 | crate::animations::CURRENT_ANIMATION_DRIVER |
621 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
622 | assert_eq!(get_prop_value(&compo.width), 200); |
623 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
624 | |
625 | // the binding should be removed |
626 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
627 | } |
628 | |
629 | #[test ] |
630 | fn properties_test_delayed_animation_negative_duration_triggered_by_set() { |
631 | let compo = Component::new_test_component(); |
632 | |
633 | let animation_details = PropertyAnimation { |
634 | delay: DELAY.as_millis() as _, |
635 | iteration_count: 1.0, |
636 | duration: -25, |
637 | ..PropertyAnimation::default() |
638 | }; |
639 | |
640 | compo.width.set(100); |
641 | assert_eq!(get_prop_value(&compo.width), 100); |
642 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
643 | |
644 | let start_time = crate::animations::current_tick(); |
645 | |
646 | compo.width.set_animated_value(200, animation_details); |
647 | assert_eq!(get_prop_value(&compo.width), 100); |
648 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
649 | |
650 | // In delay: |
651 | crate::animations::CURRENT_ANIMATION_DRIVER |
652 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
653 | assert_eq!(get_prop_value(&compo.width), 100); |
654 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
655 | |
656 | // No animation: |
657 | crate::animations::CURRENT_ANIMATION_DRIVER |
658 | .with(|driver| driver.update_animations(start_time + DELAY)); |
659 | assert_eq!(get_prop_value(&compo.width), 200); |
660 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
661 | |
662 | // Overshoot: Always to_value. |
663 | crate::animations::CURRENT_ANIMATION_DRIVER |
664 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
665 | assert_eq!(get_prop_value(&compo.width), 200); |
666 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
667 | |
668 | // the binding should be removed |
669 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
670 | } |
671 | |
672 | #[test ] |
673 | fn properties_test_delayed_animation_no_iteration_triggered_by_set() { |
674 | let compo = Component::new_test_component(); |
675 | |
676 | let animation_details = PropertyAnimation { |
677 | delay: DELAY.as_millis() as _, |
678 | iteration_count: 0.0, |
679 | duration: DURATION.as_millis() as _, |
680 | ..PropertyAnimation::default() |
681 | }; |
682 | |
683 | compo.width.set(100); |
684 | assert_eq!(get_prop_value(&compo.width), 100); |
685 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
686 | |
687 | let start_time = crate::animations::current_tick(); |
688 | |
689 | compo.width.set_animated_value(200, animation_details); |
690 | assert_eq!(get_prop_value(&compo.width), 100); |
691 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
692 | |
693 | // In delay: |
694 | crate::animations::CURRENT_ANIMATION_DRIVER |
695 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
696 | assert_eq!(get_prop_value(&compo.width), 100); |
697 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
698 | |
699 | // No animation: |
700 | crate::animations::CURRENT_ANIMATION_DRIVER |
701 | .with(|driver| driver.update_animations(start_time + DELAY)); |
702 | assert_eq!(get_prop_value(&compo.width), 200); |
703 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
704 | |
705 | // Overshoot: Always to_value. |
706 | crate::animations::CURRENT_ANIMATION_DRIVER |
707 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
708 | assert_eq!(get_prop_value(&compo.width), 200); |
709 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
710 | |
711 | // the binding should be removed |
712 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
713 | } |
714 | |
715 | #[test ] |
716 | fn properties_test_delayed_animation_negative_iteration_triggered_by_set() { |
717 | let compo = Component::new_test_component(); |
718 | |
719 | let animation_details = PropertyAnimation { |
720 | delay: DELAY.as_millis() as _, |
721 | iteration_count: -42., // loop forever! |
722 | duration: DURATION.as_millis() as _, |
723 | ..PropertyAnimation::default() |
724 | }; |
725 | |
726 | compo.width.set(100); |
727 | assert_eq!(get_prop_value(&compo.width), 100); |
728 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
729 | |
730 | let start_time = crate::animations::current_tick(); |
731 | |
732 | compo.width.set_animated_value(200, animation_details); |
733 | assert_eq!(get_prop_value(&compo.width), 100); |
734 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
735 | |
736 | // In delay: |
737 | crate::animations::CURRENT_ANIMATION_DRIVER |
738 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
739 | assert_eq!(get_prop_value(&compo.width), 100); |
740 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
741 | |
742 | // In animation: |
743 | crate::animations::CURRENT_ANIMATION_DRIVER |
744 | .with(|driver| driver.update_animations(start_time + DELAY)); |
745 | assert_eq!(get_prop_value(&compo.width), 100); |
746 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
747 | |
748 | crate::animations::CURRENT_ANIMATION_DRIVER |
749 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2)); |
750 | assert_eq!(get_prop_value(&compo.width), 150); |
751 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
752 | |
753 | crate::animations::CURRENT_ANIMATION_DRIVER |
754 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION)); |
755 | assert_eq!(get_prop_value(&compo.width), 100); |
756 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
757 | |
758 | // In animation (again): |
759 | crate::animations::CURRENT_ANIMATION_DRIVER |
760 | .with(|driver| driver.update_animations(start_time + DELAY + 500 * DURATION)); |
761 | assert_eq!(get_prop_value(&compo.width), 100); |
762 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
763 | |
764 | crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| { |
765 | driver.update_animations(start_time + DELAY + 50000 * DURATION + DURATION / 2) |
766 | }); |
767 | assert_eq!(get_prop_value(&compo.width), 150); |
768 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
769 | |
770 | // the binding should not be removed as it is still animating! |
771 | compo.width.handle.access(|binding| assert!(binding.is_some())); |
772 | } |
773 | |
774 | #[test ] |
775 | fn properties_test_animation_direction_triggered_by_set() { |
776 | let compo = Component::new_test_component(); |
777 | |
778 | let animation_details = PropertyAnimation { |
779 | delay: -25, |
780 | duration: DURATION.as_millis() as _, |
781 | direction: AnimationDirection::AlternateReverse, |
782 | iteration_count: 1., |
783 | ..PropertyAnimation::default() |
784 | }; |
785 | |
786 | compo.width.set(100); |
787 | assert_eq!(get_prop_value(&compo.width), 100); |
788 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
789 | |
790 | let start_time = crate::animations::current_tick(); |
791 | |
792 | compo.width.set_animated_value(200, animation_details); |
793 | assert_eq!(get_prop_value(&compo.width), 200); |
794 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
795 | |
796 | crate::animations::CURRENT_ANIMATION_DRIVER |
797 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
798 | assert_eq!(get_prop_value(&compo.width), 150); |
799 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
800 | |
801 | crate::animations::CURRENT_ANIMATION_DRIVER |
802 | .with(|driver| driver.update_animations(start_time + DURATION)); |
803 | assert_eq!(get_prop_value(&compo.width), 100); |
804 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
805 | |
806 | // Overshoot: Always from_value. |
807 | crate::animations::CURRENT_ANIMATION_DRIVER |
808 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
809 | assert_eq!(get_prop_value(&compo.width), 100); |
810 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
811 | |
812 | // the binding should be removed |
813 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
814 | } |
815 | |
816 | #[test ] |
817 | fn properties_test_animation_triggered_by_binding() { |
818 | let compo = Component::new_test_component(); |
819 | |
820 | let start_time = crate::animations::current_tick(); |
821 | |
822 | let animation_details = PropertyAnimation { |
823 | duration: DURATION.as_millis() as _, |
824 | iteration_count: 1., |
825 | ..PropertyAnimation::default() |
826 | }; |
827 | |
828 | let w = Rc::downgrade(&compo); |
829 | compo.width.set_animated_binding( |
830 | move || { |
831 | let compo = w.upgrade().unwrap(); |
832 | get_prop_value(&compo.feed_property) |
833 | }, |
834 | animation_details, |
835 | ); |
836 | |
837 | compo.feed_property.set(100); |
838 | assert_eq!(get_prop_value(&compo.width), 100); |
839 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
840 | |
841 | compo.feed_property.set(200); |
842 | assert_eq!(get_prop_value(&compo.width), 100); |
843 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
844 | |
845 | crate::animations::CURRENT_ANIMATION_DRIVER |
846 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
847 | assert_eq!(get_prop_value(&compo.width), 150); |
848 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
849 | |
850 | crate::animations::CURRENT_ANIMATION_DRIVER |
851 | .with(|driver| driver.update_animations(start_time + DURATION)); |
852 | assert_eq!(get_prop_value(&compo.width), 200); |
853 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
854 | } |
855 | |
856 | #[test ] |
857 | fn properties_test_delayed_animation_triggered_by_binding() { |
858 | let compo = Component::new_test_component(); |
859 | |
860 | let start_time = crate::animations::current_tick(); |
861 | |
862 | let animation_details = PropertyAnimation { |
863 | delay: DELAY.as_millis() as _, |
864 | duration: DURATION.as_millis() as _, |
865 | iteration_count: 1.0, |
866 | ..PropertyAnimation::default() |
867 | }; |
868 | |
869 | let w = Rc::downgrade(&compo); |
870 | compo.width.set_animated_binding( |
871 | move || { |
872 | let compo = w.upgrade().unwrap(); |
873 | get_prop_value(&compo.feed_property) |
874 | }, |
875 | animation_details, |
876 | ); |
877 | |
878 | compo.feed_property.set(100); |
879 | assert_eq!(get_prop_value(&compo.width), 100); |
880 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
881 | |
882 | compo.feed_property.set(200); |
883 | assert_eq!(get_prop_value(&compo.width), 100); |
884 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
885 | |
886 | // In delay: |
887 | crate::animations::CURRENT_ANIMATION_DRIVER |
888 | .with(|driver| driver.update_animations(start_time + DELAY / 2)); |
889 | assert_eq!(get_prop_value(&compo.width), 100); |
890 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
891 | |
892 | // In animation: |
893 | crate::animations::CURRENT_ANIMATION_DRIVER |
894 | .with(|driver| driver.update_animations(start_time + DELAY)); |
895 | assert_eq!(get_prop_value(&compo.width), 100); |
896 | assert_eq!(get_prop_value(&compo.width_times_two), 200); |
897 | |
898 | crate::animations::CURRENT_ANIMATION_DRIVER |
899 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2)); |
900 | assert_eq!(get_prop_value(&compo.width), 150); |
901 | assert_eq!(get_prop_value(&compo.width_times_two), 300); |
902 | |
903 | crate::animations::CURRENT_ANIMATION_DRIVER |
904 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION)); |
905 | assert_eq!(get_prop_value(&compo.width), 200); |
906 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
907 | |
908 | // Overshoot: Always to_value. |
909 | crate::animations::CURRENT_ANIMATION_DRIVER |
910 | .with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2)); |
911 | assert_eq!(get_prop_value(&compo.width), 200); |
912 | assert_eq!(get_prop_value(&compo.width_times_two), 400); |
913 | } |
914 | |
915 | #[test ] |
916 | fn test_loop() { |
917 | let compo = Component::new_test_component(); |
918 | |
919 | let animation_details = PropertyAnimation { |
920 | duration: DURATION.as_millis() as _, |
921 | iteration_count: 2., |
922 | ..PropertyAnimation::default() |
923 | }; |
924 | |
925 | compo.width.set(100); |
926 | |
927 | let start_time = crate::animations::current_tick(); |
928 | |
929 | compo.width.set_animated_value(200, animation_details); |
930 | assert_eq!(get_prop_value(&compo.width), 100); |
931 | |
932 | crate::animations::CURRENT_ANIMATION_DRIVER |
933 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
934 | assert_eq!(get_prop_value(&compo.width), 150); |
935 | |
936 | crate::animations::CURRENT_ANIMATION_DRIVER |
937 | .with(|driver| driver.update_animations(start_time + DURATION)); |
938 | assert_eq!(get_prop_value(&compo.width), 100); |
939 | |
940 | crate::animations::CURRENT_ANIMATION_DRIVER |
941 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
942 | assert_eq!(get_prop_value(&compo.width), 150); |
943 | |
944 | crate::animations::CURRENT_ANIMATION_DRIVER |
945 | .with(|driver| driver.update_animations(start_time + DURATION * 2)); |
946 | assert_eq!(get_prop_value(&compo.width), 200); |
947 | |
948 | // the binding should be removed |
949 | compo.width.handle.access(|binding| assert!(binding.is_none())); |
950 | } |
951 | |
952 | #[test ] |
953 | fn test_loop_via_binding() { |
954 | // Loop twice, restart the animation and still loop twice. |
955 | |
956 | let compo = Component::new_test_component(); |
957 | |
958 | let start_time = crate::animations::current_tick(); |
959 | |
960 | let animation_details = PropertyAnimation { |
961 | duration: DURATION.as_millis() as _, |
962 | iteration_count: 2., |
963 | ..PropertyAnimation::default() |
964 | }; |
965 | |
966 | let w = Rc::downgrade(&compo); |
967 | compo.width.set_animated_binding( |
968 | move || { |
969 | let compo = w.upgrade().unwrap(); |
970 | get_prop_value(&compo.feed_property) |
971 | }, |
972 | animation_details, |
973 | ); |
974 | |
975 | compo.feed_property.set(100); |
976 | assert_eq!(get_prop_value(&compo.width), 100); |
977 | |
978 | compo.feed_property.set(200); |
979 | assert_eq!(get_prop_value(&compo.width), 100); |
980 | |
981 | crate::animations::CURRENT_ANIMATION_DRIVER |
982 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
983 | |
984 | assert_eq!(get_prop_value(&compo.width), 150); |
985 | |
986 | crate::animations::CURRENT_ANIMATION_DRIVER |
987 | .with(|driver| driver.update_animations(start_time + DURATION)); |
988 | |
989 | assert_eq!(get_prop_value(&compo.width), 100); |
990 | |
991 | crate::animations::CURRENT_ANIMATION_DRIVER |
992 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
993 | |
994 | assert_eq!(get_prop_value(&compo.width), 150); |
995 | |
996 | crate::animations::CURRENT_ANIMATION_DRIVER |
997 | .with(|driver| driver.update_animations(start_time + 2 * DURATION)); |
998 | |
999 | assert_eq!(get_prop_value(&compo.width), 200); |
1000 | |
1001 | // Overshoot a bit: |
1002 | crate::animations::CURRENT_ANIMATION_DRIVER |
1003 | .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2)); |
1004 | |
1005 | assert_eq!(get_prop_value(&compo.width), 200); |
1006 | |
1007 | // Restart the animation by setting a new value. |
1008 | |
1009 | let start_time = crate::animations::current_tick(); |
1010 | |
1011 | compo.feed_property.set(300); |
1012 | assert_eq!(get_prop_value(&compo.width), 200); |
1013 | |
1014 | crate::animations::CURRENT_ANIMATION_DRIVER |
1015 | .with(|driver| driver.update_animations(start_time + DURATION / 2)); |
1016 | |
1017 | assert_eq!(get_prop_value(&compo.width), 250); |
1018 | |
1019 | crate::animations::CURRENT_ANIMATION_DRIVER |
1020 | .with(|driver| driver.update_animations(start_time + DURATION)); |
1021 | |
1022 | assert_eq!(get_prop_value(&compo.width), 200); |
1023 | |
1024 | crate::animations::CURRENT_ANIMATION_DRIVER |
1025 | .with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2)); |
1026 | |
1027 | assert_eq!(get_prop_value(&compo.width), 250); |
1028 | |
1029 | crate::animations::CURRENT_ANIMATION_DRIVER |
1030 | .with(|driver| driver.update_animations(start_time + 2 * DURATION)); |
1031 | |
1032 | assert_eq!(get_prop_value(&compo.width), 300); |
1033 | |
1034 | crate::animations::CURRENT_ANIMATION_DRIVER |
1035 | .with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2)); |
1036 | |
1037 | assert_eq!(get_prop_value(&compo.width), 300); |
1038 | } |
1039 | } |
1040 | |