use super::*;
use crate::{
items::{AnimationDirection, PropertyAnimation},
lengths::LogicalLength,
};
#[cfg(not(feature = "std"))]
use num_traits::Float;
enum AnimationState {
Delaying,
Animating { current_iteration: u64 },
Done { interation_count: u64 },
}
pub(super) struct PropertyValueAnimationData<T> {
from_value: T,
to_value: T,
details: PropertyAnimation,
start_time: crate::animations::Instant,
state: AnimationState,
}
impl<T: InterpolatedPropertyValue + Clone> PropertyValueAnimationData<T> {
pub fn new(from_value: T, to_value: T, details: PropertyAnimation) -> Self {
let start_time = crate::animations::current_tick();
Self { from_value, to_value, details, start_time, state: AnimationState::Delaying }
}
pub fn compute_interpolated_value(&mut self) -> (T, bool) {
let new_tick = crate::animations::current_tick();
let mut time_progress = new_tick.duration_since(self.start_time).as_millis() as u64;
let reversed = |iteration: u64| -> bool {
match self.details.direction {
AnimationDirection::Normal => false,
AnimationDirection::Reverse => true,
AnimationDirection::Alternate => iteration % 2 == 1,
AnimationDirection::AlternateReverse => iteration % 2 == 0,
}
};
match self.state {
AnimationState::Delaying => {
if self.details.delay <= 0 {
self.state = AnimationState::Animating { current_iteration: 0 };
return self.compute_interpolated_value();
}
let delay = self.details.delay as u64;
if time_progress < delay {
if reversed(0) {
(self.to_value.clone(), false)
} else {
(self.from_value.clone(), false)
}
} else {
self.start_time =
new_tick - core::time::Duration::from_millis(time_progress - delay);
self.state = AnimationState::Animating { current_iteration: 0 };
self.compute_interpolated_value()
}
}
AnimationState::Animating { mut current_iteration } => {
if self.details.duration <= 0 || self.details.iteration_count == 0. {
self.state = AnimationState::Done { interation_count: 0 };
return self.compute_interpolated_value();
}
let duration = self.details.duration as u64;
if time_progress >= duration {
current_iteration += time_progress / duration;
time_progress %= duration;
self.start_time = new_tick - core::time::Duration::from_millis(time_progress);
}
if (self.details.iteration_count < 0.)
|| (((current_iteration * duration) + time_progress) as f64)
< ((self.details.iteration_count as f64) * (duration as f64))
{
self.state = AnimationState::Animating { current_iteration };
let progress = {
let progress =
(time_progress as f32 / self.details.duration as f32).clamp(0., 1.);
if reversed(current_iteration) {
1. - progress
} else {
progress
}
};
let t = crate::animations::easing_curve(&self.details.easing, progress);
let val = self.from_value.interpolate(&self.to_value, t);
(val, false)
} else {
self.state =
AnimationState::Done { interation_count: current_iteration.max(1) - 1 };
self.compute_interpolated_value()
}
}
AnimationState::Done { interation_count } => {
if reversed(interation_count) {
(self.from_value.clone(), true)
} else {
(self.to_value.clone(), true)
}
}
}
}
fn reset(&mut self) {
self.state = AnimationState::Delaying;
self.start_time = crate::animations::current_tick();
}
}
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub(super) enum AnimatedBindingState {
Animating,
NotAnimating,
ShouldStart,
}
pub(super) struct AnimatedBindingCallable<T, A> {
pub(super) original_binding: PropertyHandle,
pub(super) state: Cell<AnimatedBindingState>,
pub(super) animation_data: RefCell<PropertyValueAnimationData<T>>,
pub(super) compute_animation_details: A,
}
pub(super) type AnimationDetail = Option<(PropertyAnimation, crate::animations::Instant)>;
unsafe impl<T: InterpolatedPropertyValue + Clone, A: Fn() -> AnimationDetail> BindingCallable
for AnimatedBindingCallable<T, A>
{
unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
let original_binding = Pin::new_unchecked(&self.original_binding);
original_binding.register_as_dependency_to_current_binding(
#[cfg(slint_debug_property)]
"<AnimatedBindingCallable>",
);
match self.state.get() {
AnimatedBindingState::Animating => {
let (val, finished) = self.animation_data.borrow_mut().compute_interpolated_value();
*(value as *mut T) = val;
if finished {
self.state.set(AnimatedBindingState::NotAnimating)
} else {
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.set_has_active_animations());
}
}
AnimatedBindingState::NotAnimating => {
self.original_binding.update(value);
}
AnimatedBindingState::ShouldStart => {
let value = &mut *(value as *mut T);
self.state.set(AnimatedBindingState::Animating);
let mut animation_data = self.animation_data.borrow_mut();
animation_data.from_value = value.clone();
self.original_binding.update((&mut animation_data.to_value) as *mut T as *mut ());
if let Some((details, start_time)) = (self.compute_animation_details)() {
animation_data.start_time = start_time;
animation_data.details = details;
}
let (val, finished) = animation_data.compute_interpolated_value();
*value = val;
if finished {
self.state.set(AnimatedBindingState::NotAnimating)
} else {
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.set_has_active_animations());
}
}
};
BindingResult::KeepBinding
}
fn mark_dirty(self: Pin<&Self>) {
if self.state.get() == AnimatedBindingState::ShouldStart {
return;
}
let original_dirty = self.original_binding.access(|b| b.unwrap().dirty.get());
if original_dirty {
self.state.set(AnimatedBindingState::ShouldStart);
self.animation_data.borrow_mut().reset();
}
}
}
pub trait InterpolatedPropertyValue: PartialEq + Default + 'static {
#[must_use]
fn interpolate(&self, target_value: &Self, t: f32) -> Self;
}
impl InterpolatedPropertyValue for f32 {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
self + t * (target_value - self)
}
}
impl InterpolatedPropertyValue for i32 {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
self + (t * (target_value - self) as f32).round() as i32
}
}
impl InterpolatedPropertyValue for i64 {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
self + (t * (target_value - self) as f32).round() as Self
}
}
impl InterpolatedPropertyValue for u8 {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).round().clamp(0., 255.)
as u8
}
}
impl InterpolatedPropertyValue for LogicalLength {
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
LogicalLength::new(self.get().interpolate(&target_value.get(), t))
}
}
impl<T: Clone + InterpolatedPropertyValue + 'static> Property<T> {
pub fn set_animated_value(&self, value: T, animation_data: PropertyAnimation) {
let d = RefCell::new(properties_animations::PropertyValueAnimationData::new(
self.get_internal(),
value,
animation_data,
));
unsafe {
self.handle.set_binding(
move |val: *mut ()| {
let (value, finished) = d.borrow_mut().compute_interpolated_value();
*(val as *mut T) = value;
if finished {
BindingResult::RemoveBinding
} else {
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.set_has_active_animations());
BindingResult::KeepBinding
}
},
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
);
}
self.handle.mark_dirty(
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
);
}
pub fn set_animated_binding(
&self,
binding: impl Binding<T> + 'static,
animation_data: PropertyAnimation,
) {
let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> {
original_binding: PropertyHandle {
handle: Cell::new(
(alloc_binding_holder(move |val: *mut ()| unsafe {
let val = &mut *(val as *mut T);
*(val as *mut T) = binding.evaluate(val);
BindingResult::KeepBinding
}) as usize)
| 0b10,
),
},
state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new(
T::default(),
T::default(),
animation_data,
)),
compute_animation_details: || -> properties_animations::AnimationDetail { None },
};
unsafe {
self.handle.set_binding(
binding_callable,
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
)
};
self.handle.mark_dirty(
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
);
}
pub fn set_animated_binding_for_transition(
&self,
binding: impl Binding<T> + 'static,
compute_animation_details: impl Fn() -> (PropertyAnimation, crate::animations::Instant)
+ 'static,
) {
let binding_callable = properties_animations::AnimatedBindingCallable::<T, _> {
original_binding: PropertyHandle {
handle: Cell::new(
(alloc_binding_holder(move |val: *mut ()| unsafe {
let val = &mut *(val as *mut T);
*(val as *mut T) = binding.evaluate(val);
BindingResult::KeepBinding
}) as usize)
| 0b10,
),
},
state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating),
animation_data: RefCell::new(properties_animations::PropertyValueAnimationData::new(
T::default(),
T::default(),
PropertyAnimation::default(),
)),
compute_animation_details: move || Some(compute_animation_details()),
};
unsafe {
self.handle.set_binding(
binding_callable,
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
)
};
self.handle.mark_dirty(
#[cfg(slint_debug_property)]
self.debug_name.borrow().as_str(),
);
}
}
#[cfg(test)]
mod animation_tests {
use super::*;
#[derive(Default)]
struct Component {
width: Property<i32>,
width_times_two: Property<i32>,
feed_property: Property<i32>, }
impl Component {
fn new_test_component() -> Rc<Self> {
let compo = Rc::new(Component::default());
let w = Rc::downgrade(&compo);
compo.width_times_two.set_binding(move || {
let compo = w.upgrade().unwrap();
get_prop_value(&compo.width) * 2
});
compo
}
}
const DURATION: std::time::Duration = std::time::Duration::from_millis(10000);
const DELAY: std::time::Duration = std::time::Duration::from_millis(800);
fn get_prop_value<T: Clone>(prop: &Property<T>) -> T {
unsafe { Pin::new_unchecked(prop).get() }
}
#[test]
fn properties_test_animation_negative_delay_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: -25,
duration: DURATION.as_millis() as _,
iteration_count: 1.,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_animation_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
duration: DURATION.as_millis() as _,
iteration_count: 1.,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: 1.,
duration: DURATION.as_millis() as _,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_fractual_interation_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: 1.5,
duration: DURATION.as_millis() as _,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 4));
assert_eq!(get_prop_value(&compo.width), 125);
assert_eq!(get_prop_value(&compo.width_times_two), 250);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_null_duration_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: 1.0,
duration: 0,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_negative_duration_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: 1.0,
duration: -25,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_no_iteration_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: 0.0,
duration: DURATION.as_millis() as _,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_delayed_animation_negative_iteration_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
iteration_count: -42., duration: DURATION.as_millis() as _,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + 500 * DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| {
driver.update_animations(start_time + DELAY + 50000 * DURATION + DURATION / 2)
});
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
compo.width.handle.access(|binding| assert!(binding.is_some()));
}
#[test]
fn properties_test_animation_direction_triggered_by_set() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
delay: -25,
duration: DURATION.as_millis() as _,
direction: AnimationDirection::AlternateReverse,
iteration_count: 1.,
..PropertyAnimation::default()
};
compo.width.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn properties_test_animation_triggered_by_binding() {
let compo = Component::new_test_component();
let start_time = crate::animations::current_tick();
let animation_details = PropertyAnimation {
duration: DURATION.as_millis() as _,
iteration_count: 1.,
..PropertyAnimation::default()
};
let w = Rc::downgrade(&compo);
compo.width.set_animated_binding(
move || {
let compo = w.upgrade().unwrap();
get_prop_value(&compo.feed_property)
},
animation_details,
);
compo.feed_property.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
compo.feed_property.set(200);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
}
#[test]
fn properties_test_delayed_animation_triggered_by_binding() {
let compo = Component::new_test_component();
let start_time = crate::animations::current_tick();
let animation_details = PropertyAnimation {
delay: DELAY.as_millis() as _,
duration: DURATION.as_millis() as _,
iteration_count: 1.0,
..PropertyAnimation::default()
};
let w = Rc::downgrade(&compo);
compo.width.set_animated_binding(
move || {
let compo = w.upgrade().unwrap();
get_prop_value(&compo.feed_property)
},
animation_details,
);
compo.feed_property.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
compo.feed_property.set(200);
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY / 2));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY));
assert_eq!(get_prop_value(&compo.width), 100);
assert_eq!(get_prop_value(&compo.width_times_two), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
assert_eq!(get_prop_value(&compo.width_times_two), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DELAY + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
assert_eq!(get_prop_value(&compo.width_times_two), 400);
}
#[test]
fn test_loop() {
let compo = Component::new_test_component();
let animation_details = PropertyAnimation {
duration: DURATION.as_millis() as _,
iteration_count: 2.,
..PropertyAnimation::default()
};
compo.width.set(100);
let start_time = crate::animations::current_tick();
compo.width.set_animated_value(200, animation_details);
assert_eq!(get_prop_value(&compo.width), 100);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION * 2));
assert_eq!(get_prop_value(&compo.width), 200);
compo.width.handle.access(|binding| assert!(binding.is_none()));
}
#[test]
fn test_loop_via_binding() {
let compo = Component::new_test_component();
let start_time = crate::animations::current_tick();
let animation_details = PropertyAnimation {
duration: DURATION.as_millis() as _,
iteration_count: 2.,
..PropertyAnimation::default()
};
let w = Rc::downgrade(&compo);
compo.width.set_animated_binding(
move || {
let compo = w.upgrade().unwrap();
get_prop_value(&compo.feed_property)
},
animation_details,
);
compo.feed_property.set(100);
assert_eq!(get_prop_value(&compo.width), 100);
compo.feed_property.set(200);
assert_eq!(get_prop_value(&compo.width), 100);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 100);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 150);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + 2 * DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 200);
let start_time = crate::animations::current_tick();
compo.feed_property.set(300);
assert_eq!(get_prop_value(&compo.width), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 250);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION));
assert_eq!(get_prop_value(&compo.width), 200);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 250);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + 2 * DURATION));
assert_eq!(get_prop_value(&compo.width), 300);
crate::animations::CURRENT_ANIMATION_DRIVER
.with(|driver| driver.update_animations(start_time + 2 * DURATION + DURATION / 2));
assert_eq!(get_prop_value(&compo.width), 300);
}
}