diff --git a/api/cpp/include/slint_properties.h b/api/cpp/include/slint_properties.h index 6af24a8cec1..ace359797b6 100644 --- a/api/cpp/include/slint_properties.h +++ b/api/cpp/include/slint_properties.h @@ -23,41 +23,37 @@ using cbindgen_private::StateInfo; inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, int *), void *user_data, void (*drop_user_data)(void *), - const cbindgen_private::PropertyAnimation *animation_data, - cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t *)) + cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { - cbindgen_private::slint_property_set_animated_binding_int( - handle, binding, user_data, drop_user_data, animation_data, transition_data); + cbindgen_private::slint_property_set_animated_binding_int(handle, binding, user_data, + drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, float *), void *user_data, void (*drop_user_data)(void *), - const cbindgen_private::PropertyAnimation *animation_data, - cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t *)) + cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { - cbindgen_private::slint_property_set_animated_binding_float( - handle, binding, user_data, drop_user_data, animation_data, transition_data); + cbindgen_private::slint_property_set_animated_binding_float(handle, binding, user_data, + drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, Color *), void *user_data, void (*drop_user_data)(void *), - const cbindgen_private::PropertyAnimation *animation_data, - cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t *)) + cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { - cbindgen_private::slint_property_set_animated_binding_color( - handle, binding, user_data, drop_user_data, animation_data, transition_data); + cbindgen_private::slint_property_set_animated_binding_color(handle, binding, user_data, + drop_user_data, transition_data); } inline void slint_property_set_animated_binding_helper( const cbindgen_private::PropertyHandleOpaque *handle, void (*binding)(void *, Brush *), void *user_data, void (*drop_user_data)(void *), - const cbindgen_private::PropertyAnimation *animation_data, - cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t *)) + cbindgen_private::PropertyAnimation (*transition_data)(void *, uint64_t **)) { - cbindgen_private::slint_property_set_animated_binding_brush( - handle, binding, user_data, drop_user_data, animation_data, transition_data); + cbindgen_private::slint_property_set_animated_binding_brush(handle, binding, user_data, + drop_user_data, transition_data); } template @@ -106,21 +102,9 @@ struct Property inline void set_animated_value(const T &value, const cbindgen_private::PropertyAnimation &animation_data) const; - template - inline void - set_animated_binding(F binding, const cbindgen_private::PropertyAnimation &animation_data) const - { - private_api::slint_property_set_animated_binding_helper( - &inner, - [](void *user_data, T *value) { - *reinterpret_cast(value) = (*reinterpret_cast(user_data))(); - }, - new F(binding), [](void *user_data) { delete reinterpret_cast(user_data); }, - &animation_data, nullptr); - } template - inline void set_animated_binding_for_transition(F binding, Trans animation) const + inline void set_animated_binding(F binding, Trans animation) const { struct UserData { @@ -134,8 +118,8 @@ struct Property reinterpret_cast(user_data)->binding(); }, new UserData { binding, animation }, - [](void *user_data) { delete reinterpret_cast(user_data); }, nullptr, - [](void *user_data, uint64_t *instant) { + [](void *user_data) { delete reinterpret_cast(user_data); }, + [](void *user_data, uint64_t **instant) { return reinterpret_cast(user_data)->animation(instant); }); } diff --git a/api/rs/slint/private_unstable_api.rs b/api/rs/slint/private_unstable_api.rs index ab135146453..a864776fe02 100644 --- a/api/rs/slint/private_unstable_api.rs +++ b/api/rs/slint/private_unstable_api.rs @@ -67,33 +67,18 @@ pub fn set_property_binding< pub fn set_animated_property_binding< T: Clone + i_slint_core::properties::InterpolatedPropertyValue + 'static, StrongRef: StrongItemTreeRef + 'static, ->( - property: Pin<&Property>, - component_strong: &StrongRef, - binding: fn(StrongRef) -> T, - animation_data: PropertyAnimation, -) { - let weak = component_strong.to_weak(); - property.set_animated_binding( - move || binding(::from_weak(&weak).unwrap()), - animation_data, - ) -} - -pub fn set_animated_property_binding_for_transition< - T: Clone + i_slint_core::properties::InterpolatedPropertyValue + 'static, - StrongRef: StrongItemTreeRef + 'static, >( property: Pin<&Property>, component_strong: &StrongRef, binding: fn(StrongRef) -> T, compute_animation_details: fn( StrongRef, - ) -> (PropertyAnimation, i_slint_core::animations::Instant), + ) + -> (PropertyAnimation, Option), ) { let weak_1 = component_strong.to_weak(); let weak_2 = weak_1.clone(); - property.set_animated_binding_for_transition( + property.set_animated_binding( move || binding(::from_weak(&weak_1).unwrap()), move || { compute_animation_details(::from_weak(&weak_2).unwrap()) @@ -186,7 +171,7 @@ pub mod re_exports { pub use i_slint_core::accessibility::{ AccessibilityAction, AccessibleStringProperty, SupportedAccessibilityAction, }; - pub use i_slint_core::animations::{EasingCurve, animation_tick}; + pub use i_slint_core::animations::{EasingCurve, animation_tick, current_tick}; pub use i_slint_core::api::{LogicalPosition, StyledText}; pub use i_slint_core::callbacks::Callback; pub use i_slint_core::date_time::*; diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 6cff1745cb1..262b407647f 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -669,19 +669,26 @@ fn handle_property_init( match &binding_expression.animation { Some(llr::Animation::Static(anim)) => { let anim = compile_expression(anim, ctx); - format!("{prop_access}.set_animated_binding({binding_code}, {anim});") + // Note: The start_time defaults to the current tick, so doesn't need to be + // udpated here. + format!("{prop_access}.set_animated_binding({binding_code}, + [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{ + [[maybe_unused]] auto self = this; + auto anim = {anim}; + *start_time = nullptr; + return anim; + }});", + ) } - Some(llr::Animation::Transition ( - anim - )) => { - let anim = compile_expression(anim, ctx); + Some(llr::Animation::Transition(animation)) => { + let animation = compile_expression(animation, ctx); format!( - "{prop_access}.set_animated_binding_for_transition({binding_code}, - [this](uint64_t *start_time) -> slint::cbindgen_private::PropertyAnimation {{ + "{prop_access}.set_animated_binding({binding_code}, + [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{ [[maybe_unused]] auto self = this; - auto [anim, time] = {anim}; - *start_time = time; - return anim; + auto [animation, change_time] = {animation}; + **start_time = change_time; + return animation; }});", ) } diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 11dc9a5b6ca..94c05533e66 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -633,16 +633,21 @@ fn handle_property_init( let anim = compile_expression(anim, ctx); quote! { { #init_self_pin_ref - slint::private_unstable_api::set_animated_property_binding(#rust_property, &self_rc, #binding_tokens, #anim); + slint::private_unstable_api::set_animated_property_binding( + #rust_property, &self_rc, #binding_tokens, move |self_rc| { + #init_self_pin_ref + (#anim, None) + }); } } } - Some(llr::Animation::Transition(anim)) => { - let anim = compile_expression(anim, ctx); + Some(llr::Animation::Transition(animation)) => { + let animation = compile_expression(animation, ctx); quote! { - slint::private_unstable_api::set_animated_property_binding_for_transition( + slint::private_unstable_api::set_animated_property_binding( #rust_property, &self_rc, #binding_tokens, move |self_rc| { #init_self_pin_ref - #anim + let (animation, change_time) = #animation; + (animation, Some(change_time)) } ); } diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index 62ebb4ce9f5..fe200ff58d4 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -159,7 +159,7 @@ fn visit_binding_expression(binding: &BindingExpression, ctx: &EvaluationContext binding.expression.borrow().visit_property_references(ctx, &mut visit_property); match &binding.animation { Some(Animation::Static(e) | Animation::Transition(e)) => { - e.visit_property_references(ctx, &mut visit_property) + e.visit_property_references(ctx, &mut visit_property); } None => (), } diff --git a/internal/compiler/llr/optim_passes/remove_unused.rs b/internal/compiler/llr/optim_passes/remove_unused.rs index 4e7423b1ae4..0e4231531ef 100644 --- a/internal/compiler/llr/optim_passes/remove_unused.rs +++ b/internal/compiler/llr/optim_passes/remove_unused.rs @@ -501,9 +501,8 @@ mod visitor { ) { visit_expression(expression.get_mut(), scope, state, visitor); match animation { - Some(Animation::Static(anim)) => visit_expression(anim, scope, state, visitor), - Some(Animation::Transition(anim)) => { - visit_expression(anim, scope, state, visitor); + Some(Animation::Static(anim) | Animation::Transition(anim)) => { + visit_expression(anim, scope, state, visitor) } None => (), } diff --git a/internal/core/properties/ffi.rs b/internal/core/properties/ffi.rs index fd1d5ab1c2b..fb99dd2e5c0 100644 --- a/internal/core/properties/ffi.rs +++ b/internal/core/properties/ffi.rs @@ -261,10 +261,10 @@ unsafe fn c_set_animated_binding( binding: extern "C" fn(*mut c_void, *mut T), user_data: *mut c_void, drop_user_data: Option, - animation_data: Option<&PropertyAnimation>, - transition_data: Option< - extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation, - >, + transition_data: extern "C" fn( + user_data: *mut c_void, + start_instant: &mut *mut u64, + ) -> PropertyAnimation, ) { unsafe { let binding = core::mem::transmute::< @@ -286,27 +286,31 @@ unsafe fn c_set_animated_binding( let animation_data = RefCell::new(properties_animations::PropertyValueAnimationData::new( T::default(), T::default(), - animation_data.cloned().unwrap_or_default(), + PropertyAnimation::default(), )); - if let Some(transition_data) = transition_data { - handle.0.set_binding(properties_animations::AnimatedBindingCallable:: { - original_binding, - state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating), - animation_data, - compute_animation_details: move || -> properties_animations::AnimationDetail { - let mut start_instant = 0; - let anim = transition_data(user_data, &mut start_instant); - Some((anim, crate::animations::Instant(start_instant))) - }, - }); - } else { - handle.0.set_binding(properties_animations::AnimatedBindingCallable:: { - original_binding, - state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating), - animation_data, - compute_animation_details: || -> properties_animations::AnimationDetail { None }, - }); - } + + handle.0.set_binding(properties_animations::AnimatedBindingCallable:: { + original_binding, + state: Cell::new(properties_animations::AnimatedBindingState::NotAnimating), + animation_data, + compute_animation_details: move || -> properties_animations::AnimationDetail { + // The transition_data function receives a *mut *mut u64 pointer for the + // timestamp. + // If the function sets the pointer to nullptr, it doesn't provide a start_time. + // Otherwise, we assume it has written a value to the start_instant. + // This basically models a `&mut Option`, which is then converted to an + // `Option` + let mut start_instant = 0u64; + let mut start_instant_ref = &mut start_instant as *mut u64; + let anim = transition_data(user_data, &mut start_instant_ref); + let start_instant = if start_instant_ref.is_null() { + None + } else { + Some(crate::animations::Instant(start_instant)) + }; + (anim, start_instant) + }, + }); handle.0.mark_dirty(); } } @@ -318,20 +322,13 @@ pub unsafe extern "C" fn slint_property_set_animated_binding_int( binding: extern "C" fn(*mut c_void, *mut core::ffi::c_int), user_data: *mut c_void, drop_user_data: Option, - animation_data: Option<&PropertyAnimation>, - transition_data: Option< - extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation, - >, + transition_data: extern "C" fn( + user_data: *mut c_void, + start_instant: &mut *mut u64, + ) -> PropertyAnimation, ) { unsafe { - c_set_animated_binding( - handle, - binding, - user_data, - drop_user_data, - animation_data, - transition_data, - ); + c_set_animated_binding(handle, binding, user_data, drop_user_data, transition_data); } } @@ -342,20 +339,13 @@ pub unsafe extern "C" fn slint_property_set_animated_binding_float( binding: extern "C" fn(*mut c_void, *mut f32), user_data: *mut c_void, drop_user_data: Option, - animation_data: Option<&PropertyAnimation>, - transition_data: Option< - extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation, - >, + transition_data: extern "C" fn( + user_data: *mut c_void, + start_instant: &mut *mut u64, + ) -> PropertyAnimation, ) { unsafe { - c_set_animated_binding( - handle, - binding, - user_data, - drop_user_data, - animation_data, - transition_data, - ); + c_set_animated_binding(handle, binding, user_data, drop_user_data, transition_data); } } @@ -366,20 +356,13 @@ pub unsafe extern "C" fn slint_property_set_animated_binding_color( binding: extern "C" fn(*mut c_void, *mut Color), user_data: *mut c_void, drop_user_data: Option, - animation_data: Option<&PropertyAnimation>, - transition_data: Option< - extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation, - >, + transition_data: extern "C" fn( + user_data: *mut c_void, + start_instant: &mut *mut u64, + ) -> PropertyAnimation, ) { unsafe { - c_set_animated_binding( - handle, - binding, - user_data, - drop_user_data, - animation_data, - transition_data, - ); + c_set_animated_binding(handle, binding, user_data, drop_user_data, transition_data); } } @@ -390,20 +373,13 @@ pub unsafe extern "C" fn slint_property_set_animated_binding_brush( binding: extern "C" fn(*mut c_void, *mut Brush), user_data: *mut c_void, drop_user_data: Option, - animation_data: Option<&PropertyAnimation>, - transition_data: Option< - extern "C" fn(user_data: *mut c_void, start_instant: &mut u64) -> PropertyAnimation, - >, + transition_data: extern "C" fn( + user_data: *mut c_void, + start_instant: &mut *mut u64, + ) -> PropertyAnimation, ) { unsafe { - c_set_animated_binding( - handle, - binding, - user_data, - drop_user_data, - animation_data, - transition_data, - ); + c_set_animated_binding(handle, binding, user_data, drop_user_data, transition_data); } } diff --git a/internal/core/properties/properties_animations.rs b/internal/core/properties/properties_animations.rs index 6f04008f0c1..7e3ea323f12 100644 --- a/internal/core/properties/properties_animations.rs +++ b/internal/core/properties/properties_animations.rs @@ -140,7 +140,7 @@ pub(super) struct AnimatedBindingCallable { pub(super) compute_animation_details: A, } -pub(super) type AnimationDetail = Option<(PropertyAnimation, crate::animations::Instant)>; +pub(super) type AnimationDetail = (PropertyAnimation, Option); unsafe impl AnimationDetail> BindingCallable for AnimatedBindingCallable @@ -171,12 +171,14 @@ unsafe impl AnimationDetail> Bi let mut animation_data = self.animation_data.borrow_mut(); // animation_data.details.iteration_count = 1.; animation_data.from_value = value.clone(); - // Safety: `animation_data.to_value` is a valid mutable reference - unsafe { self.original_binding.update((&mut animation_data.to_value) as *mut T) }; - if let Some((details, start_time)) = (self.compute_animation_details)() { + let (details, start_time) = (self.compute_animation_details)(); + if let Some(start_time) = start_time { animation_data.start_time = start_time; - animation_data.details = details; } + animation_data.details = details; + + // Safety: `animation_data.to_value` is a valid mutable reference + unsafe { self.original_binding.update((&mut animation_data.to_value) as *mut T) }; let (val, finished) = animation_data.compute_interpolated_value(); *value = val; if finished { @@ -281,52 +283,12 @@ impl Property { ); } - /// Set a binding to this property. - /// + /// Set a binding to this property, providing a callback for the animation and an optional + /// start_time (relevant for state transitions). pub fn set_animated_binding( &self, binding: impl Binding + 'static, - animation_data: PropertyAnimation, - ) { - let binding_callable = properties_animations::AnimatedBindingCallable:: { - original_binding: PropertyHandle { - handle: Cell::new( - (alloc_binding_holder(move |val: &mut T| { - *val = 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 }, - }; - - // Safety: the `AnimatedBindingCallable`'s type match the property type - 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(), - ); - } - - /// Set a binding to this property, providing a callback for the transition animation - /// - pub fn set_animated_binding_for_transition( - &self, - binding: impl Binding + 'static, - compute_animation_details: impl Fn() -> (PropertyAnimation, crate::animations::Instant) + compute_animation_details: impl Fn() -> (PropertyAnimation, Option) + 'static, ) { let binding_callable = properties_animations::AnimatedBindingCallable:: { @@ -345,7 +307,7 @@ impl Property { T::default(), PropertyAnimation::default(), )), - compute_animation_details: move || Some(compute_animation_details()), + compute_animation_details, }; // Safety: the `AnimatedBindingCallable`'s type match the property type @@ -835,7 +797,7 @@ mod animation_tests { let compo = w.upgrade().unwrap(); get_prop_value(&compo.feed_property) }, - animation_details, + move || (animation_details.clone(), None), ); compo.feed_property.set(100); @@ -876,7 +838,7 @@ mod animation_tests { let compo = w.upgrade().unwrap(); get_prop_value(&compo.feed_property) }, - animation_details, + move || (animation_details.clone(), None), ); compo.feed_property.set(100); @@ -973,7 +935,7 @@ mod animation_tests { let compo = w.upgrade().unwrap(); get_prop_value(&compo.feed_property) }, - animation_details, + move || (animation_details.clone(), None), ); compo.feed_property.set(100); diff --git a/internal/core/rtti.rs b/internal/core/rtti.rs index b9db495a0c3..8d7cbdd9e62 100644 --- a/internal/core/rtti.rs +++ b/internal/core/rtti.rs @@ -67,7 +67,7 @@ pub enum AnimatedBindingKind { /// No animation is on the binding NotAnimated, /// Single animation - Animation(PropertyAnimation), + Animation(Box PropertyAnimation>), /// Transition Transition(Box (PropertyAnimation, crate::animations::Instant)>), } @@ -77,7 +77,7 @@ impl AnimatedBindingKind { pub fn as_animation(self) -> Option { match self { AnimatedBindingKind::NotAnimated => None, - AnimatedBindingKind::Animation(a) => Some(a), + AnimatedBindingKind::Animation(a) => Some(a()), AnimatedBindingKind::Transition(_) => None, } } @@ -307,19 +307,22 @@ where .map_err(|_| ()) .expect("binding was of the wrong type") }, - animation, + move || (animation(), None), ); Ok(()) } AnimatedBindingKind::Transition(tr) => { - p.set_animated_binding_for_transition( + p.set_animated_binding( move || { binding() .try_into() .map_err(|_| ()) .expect("binding was of the wrong type") }, - tr, + move || { + let (animation, start_time) = tr(); + (animation, Some(start_time)) + }, ); Ok(()) } diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index 9d1ad70de8e..c5ebd515fd4 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -1406,10 +1406,28 @@ pub fn animation_for_property( ) -> AnimatedBindingKind { match animation { Some(i_slint_compiler::object_tree::PropertyAnimation::Static(anim_elem)) => { - AnimatedBindingKind::Animation(eval::new_struct_with_bindings( - &anim_elem.borrow().bindings, - &mut eval::EvalLocalContext::from_component_instance(component), - )) + AnimatedBindingKind::Animation(Box::new({ + let component_ptr = component.as_ptr(); + let vtable = NonNull::from(&component.description.ct).cast(); + let anim_elem = Rc::clone(&anim_elem); + move || -> PropertyAnimation { + generativity::make_guard!(guard); + let component = unsafe { + InstanceRef::from_pin_ref( + Pin::new_unchecked(vtable::VRef::from_raw( + vtable, + NonNull::new_unchecked(component_ptr as *mut u8), + )), + guard, + ) + }; + + eval::new_struct_with_bindings( + &anim_elem.borrow().bindings, + &mut eval::EvalLocalContext::from_component_instance(component), + ) + } + })) } Some(i_slint_compiler::object_tree::PropertyAnimation::Transition { animations, diff --git a/tests/cases/properties/animation_bindings_reactive.slint b/tests/cases/properties/animation_bindings_reactive.slint new file mode 100644 index 00000000000..6af0808c3e7 --- /dev/null +++ b/tests/cases/properties/animation_bindings_reactive.slint @@ -0,0 +1,131 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Modified example from issue #348 which allows for better testing from the backend. +export component TestCase inherits Window { + in-out property dur: 1s; + + // Animations currently follow two different code paths, test that both work. + // 1. bindings - if the property has a binding, it uses set_animated_binding + out property aaa_binding: is-pressed ? 1.0 : 0.0; + animate aaa_binding { duration: dur; } + out property zzz_binding: is-pressed ? 1.0 : 0.0; + animate zzz_binding { duration: dur; } + + // 2. value - if a value is assigned, it uses set_animated_value + in-out property value: 0.0; + animate value { duration: dur; } + + in-out property is-pressed: touch.pressed; + + VerticalLayout { + Text { + text: aaa_binding; + } + + Text { + text: zzz_binding; + } + } + + touch := TouchArea { + clicked => { + dur = dur / 2; + if value == 0.0 { + value = 1.0; + } else { + value = 0.0; + } + } + } +} + + +/* +```rust +let instance = TestCase::new().unwrap(); +fn assert_almost_eq(value: f32, expected: f32) { + assert!(expected - 0.05 <= value && value <= expected + 0.05, "Expected {value} to be close to {expected} (+-0.05)"); +} + +// Initial: 0.0 +assert_eq!(instance.get_aaa_binding(), 0.0); +assert_eq!(instance.get_zzz_binding(), 0.0); +assert_eq!(instance.get_value(), 0.0); + +// initial duration is 1s; +instance.set_is_pressed(true); +instance.set_value(1.0); + +slint_testing::mock_elapsed_time(500); +// should be around 0.5 +assert_almost_eq(instance.get_aaa_binding(), 0.5); +assert_almost_eq(instance.get_zzz_binding(), 0.5); +assert_almost_eq(instance.get_value(), 0.5); + +slint_testing::mock_elapsed_time(500); +assert_eq!(instance.get_aaa_binding(), 1.0); +assert_eq!(instance.get_zzz_binding(), 1.0); +assert_eq!(instance.get_value(), 1.0); + +instance.set_dur(500); +instance.set_is_pressed(false); +instance.set_value(0.0); + +slint_testing::mock_elapsed_time(250); +// should be around 0.5 +assert_almost_eq(instance.get_aaa_binding(), 0.5); +assert_almost_eq(instance.get_zzz_binding(), 0.5); +assert_almost_eq(instance.get_value(), 0.5); + +slint_testing::mock_elapsed_time(250); +assert_eq!(instance.get_aaa_binding(), 0.0); +assert_eq!(instance.get_zzz_binding(), 0.0); +assert_eq!(instance.get_value(), 0.0); +``` + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +// Initial: 0.0 +assert_eq(instance.get_aaa_binding(), 0.0); +assert_eq(instance.get_zzz_binding(), 0.0); +assert_eq(instance.get_value(), 0.0); + +// initial duration is 1s; +instance.set_is_pressed(true); +instance.set_value(1.0); + +slint_testing::mock_elapsed_time(500); +// should be around 0.5 +assert(instance.get_aaa_binding() > 0.45); +assert(instance.get_aaa_binding() < 0.55); +assert(instance.get_zzz_binding() > 0.45); +assert(instance.get_zzz_binding() < 0.55); +assert(instance.get_value() > 0.45); +assert(instance.get_value() < 0.55); + +slint_testing::mock_elapsed_time(500); +assert_eq(instance.get_aaa_binding(), 1.0); +assert_eq(instance.get_zzz_binding(), 1.0); +assert_eq(instance.get_value(), 1.0); + +instance.set_dur(500); +instance.set_is_pressed(false); +instance.set_value(0.0); + +slint_testing::mock_elapsed_time(250); +// should be around 0.5 +assert(instance.get_aaa_binding() > 0.45); +assert(instance.get_aaa_binding() < 0.55); +assert(instance.get_zzz_binding() > 0.45); +assert(instance.get_zzz_binding() < 0.55); +assert(instance.get_value() > 0.45); +assert(instance.get_value() < 0.55); + +slint_testing::mock_elapsed_time(250); +assert_eq(instance.get_aaa_binding(), 0.0); +assert_eq(instance.get_zzz_binding(), 0.0); +assert_eq(instance.get_value(), 0.0); +``` +*/