Implement easing curve in the runtime

This commit is contained in:
Olivier Goffart 2020-07-29 15:20:28 +02:00
parent feec73674f
commit 46a011683f
11 changed files with 93 additions and 15 deletions

View file

@ -28,6 +28,7 @@ using internal::ItemTreeNode;
using ComponentRef = VRef<ComponentVTable>;
using ItemVisitorRefMut = VRefMut<internal::ItemVisitorVTable>;
using internal::WindowProperties;
using internal::EasingCurve;
struct ComponentWindow
{

View file

@ -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!(),
})
}

View file

@ -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<Component>) -> 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]))
}
}
}

View file

@ -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<dyn Fn(f32) -> 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

View file

@ -365,4 +365,6 @@ pub struct PropertyAnimation {
pub duration: i32,
#[rtti_field]
pub loop_count: i32,
#[rtti_field]
pub easing: crate::abi::datastructures::EasingCurve,
}

View file

@ -622,7 +622,8 @@ impl<T: InterpolatedPropertyValue> PropertyValueAnimationData<T> {
}
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);

View file

@ -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,
))
}
}
}

View file

@ -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"))

View file

@ -23,7 +23,8 @@ declare_ValueType![
crate::SharedString,
crate::Resource,
crate::Color,
crate::PathData
crate::PathData,
crate::abi::datastructures::EasingCurve
];
pub trait PropertyInfo<Item, Value> {

View file

@ -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<String, Value>] );
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])
}
}),
}
}

View file

@ -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;}
}
}