diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index 48af3bada..54c93851c 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -28,6 +28,7 @@ using internal::ItemTreeNode; using ComponentRef = VRef; using ItemVisitorRefMut = VRefMut; using internal::WindowProperties; +using internal::EasingCurve; struct ComponentWindow { diff --git a/api/sixtyfps-node/native/lib.rs b/api/sixtyfps-node/native/lib.rs index 17edefcee..0b96c7874 100644 --- a/api/sixtyfps-node/native/lib.rs +++ b/api/sixtyfps-node/native/lib.rs @@ -166,6 +166,7 @@ fn to_js_value<'cx>( } Value::Color(c) => JsNumber::new(cx, c.as_argb_encoded()).as_value(cx), Value::PathElements(_) => todo!(), + Value::EasingCurve(_) => todo!(), }) } diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index 6d733b5f0..623c16c2d 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -2,7 +2,7 @@ */ use crate::diagnostics::{BuildDiagnostics, CompilerDiagnostic, Spanned}; -use crate::expression_tree::{Expression, NamedReference, OperatorClass, Path}; +use crate::expression_tree::{EasingCurve, Expression, NamedReference, OperatorClass, Path}; use crate::object_tree::{Component, ElementRc}; use crate::typeregister::Type; use proc_macro2::TokenStream; @@ -709,7 +709,12 @@ fn compile_expression(e: &Expression, component: &Rc) -> TokenStream let name = quote::format_ident!("{}", name); quote!(#name) } - Expression::EasingCurve(_) => quote!(todo!("EasingCurve not yet implemented in rust.rs")), + Expression::EasingCurve(EasingCurve::Linear) => { + quote!(sixtyfps::re_exports::EasingCurve::Linear) + } + Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => { + quote!(sixtyfps::re_exports::EasingCurve::CubicBezier([#a, #b, #c, #d])) + } } } diff --git a/sixtyfps_runtime/corelib/abi/datastructures.rs b/sixtyfps_runtime/corelib/abi/datastructures.rs index a617d4f6c..a92f56012 100644 --- a/sixtyfps_runtime/corelib/abi/datastructures.rs +++ b/sixtyfps_runtime/corelib/abi/datastructures.rs @@ -647,6 +647,23 @@ pub struct MouseEvent { pub what: MouseEventType, } +/// The representation of an easing curve, for animations +#[repr(C, u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum EasingCurve { + /// The linear curve + Linear, + /// A Cubic bezier curve, with its 4 parameter + CubicBezier([f32; 4]), + //Custom(Box f32>), +} + +impl Default for EasingCurve { + fn default() -> Self { + Self::Linear + } +} + #[repr(C)] #[derive(Default)] /// WindowProperties is used to pass the references to properties of the instantiated diff --git a/sixtyfps_runtime/corelib/abi/primitives.rs b/sixtyfps_runtime/corelib/abi/primitives.rs index 8e66beb46..79f9a2003 100644 --- a/sixtyfps_runtime/corelib/abi/primitives.rs +++ b/sixtyfps_runtime/corelib/abi/primitives.rs @@ -365,4 +365,6 @@ pub struct PropertyAnimation { pub duration: i32, #[rtti_field] pub loop_count: i32, + #[rtti_field] + pub easing: crate::abi::datastructures::EasingCurve, } diff --git a/sixtyfps_runtime/corelib/abi/properties.rs b/sixtyfps_runtime/corelib/abi/properties.rs index a1b81a498..8b0880007 100644 --- a/sixtyfps_runtime/corelib/abi/properties.rs +++ b/sixtyfps_runtime/corelib/abi/properties.rs @@ -622,7 +622,8 @@ impl PropertyValueAnimationData { } let progress = time_progress as f32 / self.details.duration as f32; assert!(progress <= 1.); - let val = self.from_value.interpolate(self.to_value, progress); + let t = crate::animations::easing_curve(&self.details.easing, progress); + let val = self.from_value.interpolate(self.to_value, t); (val, false) } } @@ -997,8 +998,10 @@ mod animation_tests { fn properties_test_animation_triggered_by_set() { let compo = Component::new_test_component(); - let animation_details = - PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 0 }; + let animation_details = PropertyAnimation { + duration: DURATION.as_millis() as _, + ..PropertyAnimation::default() + }; compo.width.set(100); assert_eq!(get_prop_value(&compo.width), 100); @@ -1036,8 +1039,10 @@ mod animation_tests { let start_time = crate::animations::CURRENT_ANIMATION_DRIVER.with(|driver| driver.current_tick()); - let animation_details = - PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 0 }; + let animation_details = PropertyAnimation { + duration: DURATION.as_millis() as _, + ..PropertyAnimation::default() + }; let w = Rc::downgrade(&compo); compo.width.set_animated_binding( @@ -1073,8 +1078,11 @@ mod animation_tests { fn test_loop() { let compo = Component::new_test_component(); - let animation_details = - PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 2 }; + let animation_details = PropertyAnimation { + duration: DURATION.as_millis() as _, + loop_count: 2, + ..PropertyAnimation::default() + }; compo.width.set(100); @@ -1116,8 +1124,11 @@ mod animation_tests { fn test_loop_overshoot() { let compo = Component::new_test_component(); - let animation_details = - PropertyAnimation { duration: DURATION.as_millis() as _, loop_count: 2 }; + let animation_details = PropertyAnimation { + duration: DURATION.as_millis() as _, + loop_count: 2, + ..PropertyAnimation::default() + }; compo.width.set(100); diff --git a/sixtyfps_runtime/corelib/animations.rs b/sixtyfps_runtime/corelib/animations.rs index 8a2172459..d835de012 100644 --- a/sixtyfps_runtime/corelib/animations.rs +++ b/sixtyfps_runtime/corelib/animations.rs @@ -1,5 +1,6 @@ #![warn(missing_docs)] +use crate::abi::datastructures::EasingCurve; use std::cell::Cell; /// The AnimationDriver @@ -44,3 +45,27 @@ impl AnimationDriver { } thread_local!(pub(crate) static CURRENT_ANIMATION_DRIVER : AnimationDriver = AnimationDriver::default()); + +/// map a value betwen 0 and 1 to another value between 0 and 1 according to the curve +pub fn easing_curve(curve: &EasingCurve, value: f32) -> f32 { + match curve { + EasingCurve::Linear => value, + EasingCurve::CubicBezier([a, b, c, d]) => { + if !(0.0..=1.0).contains(a) && !(0.0..=1.0).contains(c) { + return value; + }; + let curve = lyon::algorithms::geom::cubic_bezier::CubicBezierSegment { + from: (0., 0.).into(), + ctrl1: (*a, *b).into(), + ctrl2: (*c, *d).into(), + to: (1., 1.).into(), + }; + let curve = curve.assume_monotonic(); + curve.y(curve.solve_t_for_x( + value, + 0.0..1.0, + lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE, + )) + } + } +} diff --git a/sixtyfps_runtime/corelib/build.rs b/sixtyfps_runtime/corelib/build.rs index 8a744067d..f7331f1c8 100644 --- a/sixtyfps_runtime/corelib/build.rs +++ b/sixtyfps_runtime/corelib/build.rs @@ -15,6 +15,7 @@ fn main() { "Slice", "ComponentWindowOpaque", "PropertyAnimation", + "EasingCurve", ] .iter() .map(|x| x.to_string()) @@ -142,6 +143,11 @@ fn main() { "CachedRenderingData".to_owned(), " constexpr CachedRenderingData() : cache_index{}, cache_ok{} {}".to_owned(), ); + config.export.body.insert( + "EasingCurve".to_owned(), + " constexpr EasingCurve() : tag(Tag::Linear), cubic_bezier{{0,0,1,1}} {} + constexpr explicit EasingCurve(EasingCurve::Tag tag, float a, float b, float c, float d) : tag(tag), cubic_bezier{{a,b,c,d}} {}".into() + ); cbindgen::Builder::new() .with_config(config) .with_src(crate_dir.join("abi/datastructures.rs")) diff --git a/sixtyfps_runtime/corelib/rtti.rs b/sixtyfps_runtime/corelib/rtti.rs index dc4fe5768..0a22f2a6d 100644 --- a/sixtyfps_runtime/corelib/rtti.rs +++ b/sixtyfps_runtime/corelib/rtti.rs @@ -23,7 +23,8 @@ declare_ValueType![ crate::SharedString, crate::Resource, crate::Color, - crate::PathData + crate::PathData, + crate::abi::datastructures::EasingCurve ]; pub trait PropertyInfo { diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index 9684b63fe..4e6dc419b 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -1,7 +1,8 @@ use core::convert::{TryFrom, TryInto}; use core::pin::Pin; use sixtyfps_compilerlib::expression_tree::{ - Expression, ExpressionSpanned, NamedReference, Path as ExprPath, PathElement as ExprPathElement, + EasingCurve, Expression, ExpressionSpanned, NamedReference, Path as ExprPath, + PathElement as ExprPathElement, }; use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type}; use sixtyfps_corelib as corelib; @@ -69,6 +70,8 @@ pub enum Value { Color(Color), /// The elements of a path PathElements(PathData), + /// An easing curve + EasingCurve(corelib::abi::datastructures::EasingCurve), } impl Default for Value { @@ -114,6 +117,7 @@ declare_value_conversion!(Resource => [Resource] ); declare_value_conversion!(Object => [HashMap] ); declare_value_conversion!(Color => [Color] ); declare_value_conversion!(PathElements => [PathData]); +declare_value_conversion!(EasingCurve => [corelib::abi::datastructures::EasingCurve]); /// The local variable needed for binding evaluation #[derive(Default)] @@ -348,7 +352,12 @@ pub fn eval_expression( Expression::ReadLocalVariable { name, .. } => { local_context.local_variables.get(name).unwrap().clone() } - Expression::EasingCurve(_) => todo!("EasingCurve not yet implemented"), + Expression::EasingCurve(curve) => Value::EasingCurve(match curve { + EasingCurve::Linear => corelib::abi::datastructures::EasingCurve::Linear, + EasingCurve::CubicBezier(a, b, c, d) => { + corelib::abi::datastructures::EasingCurve::CubicBezier([*a, *b, *c, *d]) + } + }), } } diff --git a/sixtyfps_widgets/sixtyfps_widgets.60 b/sixtyfps_widgets/sixtyfps_widgets.60 index d5531e0e5..16712814d 100644 --- a/sixtyfps_widgets/sixtyfps_widgets.60 +++ b/sixtyfps_widgets/sixtyfps_widgets.60 @@ -44,7 +44,7 @@ export CheckBox := Rectangle { y: 4lx; x: root.checked ? 4lx : indicator.width - bubble.width - 4lx; color: root.checked ? #aea : #eaa; - animate x, color { duration: 200ms; } + animate x, color { duration: 200ms; easing: ease;} } }