Implement property animations for C++

This commit is contained in:
Simon Hausmann 2020-06-25 13:32:50 +02:00
parent f50a705e00
commit e6b386ab53
4 changed files with 241 additions and 32 deletions

View file

@ -1,5 +1,12 @@
#pragma once
#include <string_view>
namespace sixtyfps {
namespace internal {
struct PropertyAnimation;
}
}
#include "sixtyfps_properties_internal.h"
namespace sixtyfps {
@ -41,8 +48,56 @@ struct Property
new F(binding), [](void *user_data) { delete reinterpret_cast<F *>(user_data); });
}
inline void set_animated_value(const T &value,
const internal::PropertyAnimation &animation_data);
template<typename F>
inline void set_animated_binding(F binding, const internal::PropertyAnimation &animation_data);
private:
internal::PropertyHandleOpaque inner;
mutable T value{};
};
template<>
void Property<int32_t>::set_animated_value(const int32_t &value,
const internal::PropertyAnimation &animation_data)
{
internal::sixtyfps_property_set_animated_value_int(&inner, value, &animation_data);
}
template<>
void Property<float>::set_animated_value(const float &value,
const internal::PropertyAnimation &animation_data)
{
internal::sixtyfps_property_set_animated_value_float(&inner, value, &animation_data);
}
template<>
template<typename F>
void Property<int32_t>::set_animated_binding(F binding,
const internal::PropertyAnimation &animation_data)
{
internal::sixtyfps_property_set_animated_binding_int(
&inner,
[](void *user_data, const internal::EvaluationContext *context, int32_t *value) {
*reinterpret_cast<int32_t *>(value) = (*reinterpret_cast<F *>(user_data))(context);
},
new F(binding), [](void *user_data) { delete reinterpret_cast<F *>(user_data); },
&animation_data);
}
template<>
template<typename F>
void Property<float>::set_animated_binding(F binding,
const internal::PropertyAnimation &animation_data)
{
internal::sixtyfps_property_set_animated_binding_float(
&inner,
[](void *user_data, const internal::EvaluationContext *context, float *value) {
*reinterpret_cast<float *>(value) = (*reinterpret_cast<F *>(user_data))(context);
},
new F(binding), [](void *user_data) { delete reinterpret_cast<F *>(user_data); },
&animation_data);
}
}

View file

@ -153,6 +153,68 @@ impl CppType for Type {
}
}
fn property_animation_code(
component: &Rc<Component>,
element: &Element,
property_name: &str,
) -> Option<String> {
if let Some(animation) = element.property_animations.get(property_name) {
let bindings: Vec<String> = animation
.borrow()
.bindings
.iter()
.map(|(prop, initializer)| {
let initializer = compile_expression(initializer, component);
format!("animation.{} = {};", prop, initializer)
})
.collect();
Some(format!(
r#"[](){{
sixtyfps::internal::PropertyAnimation animation{{}};
{}
return animation;
}}()"#,
bindings.join("\n")
))
} else {
None
}
}
fn property_set_value_code(
component: &Rc<Component>,
element: &Element,
property_name: &str,
value_expr: String,
) -> String {
if let Some(animation_code) = property_animation_code(component, element, property_name) {
format!(
"set_animated_value({value}, {animation})",
value = value_expr,
animation = animation_code
)
} else {
format!("set({})", value_expr)
}
}
fn property_set_binding_code(
component: &Rc<Component>,
element: &Element,
property_name: &str,
binding_expr: String,
) -> String {
if let Some(animation_code) = property_animation_code(component, element, property_name) {
format!(
"set_animated_binding({binding}, {animation})",
binding = binding_expr,
animation = animation_code
)
} else {
format!("set_binding({})", binding_expr)
}
}
fn handle_item(item: &Element, main_struct: &mut Struct, init: &mut Vec<String>) {
main_struct.members.push(Declaration::Var(Var {
ty: format!("sixtyfps::{}", item.base_type.as_builtin().class_name),
@ -187,26 +249,34 @@ fn handle_item(item: &Element, main_struct: &mut Struct, init: &mut Vec<String>)
format!("{id}.", id = id.clone())
};
let init = compile_expression(i, &item.enclosing_component.upgrade().unwrap());
let component = &item.enclosing_component.upgrade().unwrap();
let init = compile_expression(i, component);
if i.is_constant() {
let setter = property_set_value_code(&component, item, s, init);
format!(
"{accessor_prefix}{cpp_prop}.set({init});",
"{accessor_prefix}{cpp_prop}.{setter};",
accessor_prefix = accessor_prefix,
cpp_prop = s,
init = init
setter = setter
)
} else {
format!(
"{accessor_prefix}{cpp_prop}.set_binding(
[]([[maybe_unused]] const sixtyfps::EvaluationContext *context) {{
let binding_code = format!(
"[]([[maybe_unused]] const sixtyfps::EvaluationContext *context) {{
[[maybe_unused]] auto self = reinterpret_cast<const {ty}*>(context->component.instance);
return {init};
}}
);",
accessor_prefix = accessor_prefix,
cpp_prop = s,
}}",
ty = main_struct.name,
init = init
);
let binding_setter = property_set_binding_code(component, item, s, binding_code);
format!(
"{accessor_prefix}{cpp_prop}.{binding_setter};",
accessor_prefix = accessor_prefix,
cpp_prop = s,
binding_setter = binding_setter,
)
}
}

View file

@ -399,6 +399,34 @@ pub unsafe extern "C" fn sixtyfps_property_set_changed(out: *const PropertyHandl
inner.borrow_mut().binding = None;
}
fn make_c_function_binding<T: 'static>(
binding: extern "C" fn(*mut c_void, &EvaluationContext, *mut T),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
) -> Rc<dyn Binding<T>> {
struct CFunctionBinding<T> {
binding_function: extern "C" fn(*mut c_void, &EvaluationContext, *mut T),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
}
impl<T> Drop for CFunctionBinding<T> {
fn drop(&mut self) {
if let Some(x) = self.drop_user_data {
x(self.user_data)
}
}
}
impl<T> Binding<T> for CFunctionBinding<T> {
fn evaluate(self: Rc<Self>, value_ptr: &mut T, context: &EvaluationContext) {
(self.binding_function)(self.user_data, context, value_ptr);
}
}
Rc::new(CFunctionBinding { binding_function: binding, user_data, drop_user_data })
}
/// Set a binding
/// The binding has signature fn(user_data, context, pointer_to_value)
///
@ -416,28 +444,7 @@ pub unsafe extern "C" fn sixtyfps_property_set_binding(
) {
let inner = &*(out as *const PropertyHandle<()>);
struct CFunctionBinding {
binding_function: extern "C" fn(*mut c_void, &EvaluationContext, *mut c_void),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
}
impl Drop for CFunctionBinding {
fn drop(&mut self) {
if let Some(x) = self.drop_user_data {
x(self.user_data)
}
}
}
impl Binding<()> for CFunctionBinding {
fn evaluate(self: Rc<Self>, value_ptr: &mut (), context: &EvaluationContext) {
(self.binding_function)(self.user_data, context, value_ptr);
}
}
let binding =
Rc::new(CFunctionBinding { binding_function: binding, user_data, drop_user_data });
let binding = make_c_function_binding(binding, user_data, drop_user_data);
inner.borrow_mut().binding = Some(binding);
inner.clone().mark_dirty(DirtyReason::ValueOrDependencyHasChanged);
@ -626,6 +633,82 @@ impl<T: InterpolatedPropertyValue> Drop for PropertyAnimationBinding<T> {
}
}
/// Internal function to set up a property animation to the specified target value for an integer property.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_property_set_animated_value_int(
out: *const PropertyHandleOpaque,
value: i32,
animation_data: &crate::abi::primitives::PropertyAnimation,
) {
let inner = &*(out as *const PropertyHandle<i32>);
let animation = Rc::new(RefCell::new(PropertyAnimationBinding::new_with_value(
value,
animation_data,
inner.clone(),
)));
PropertyImpl::set_binding(inner.clone(), Some(animation.clone()));
}
/// Internal function to set up a property animation to the specified target value for a float property.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_property_set_animated_value_float(
out: *const PropertyHandleOpaque,
value: f32,
animation_data: &crate::abi::primitives::PropertyAnimation,
) {
let inner = &*(out as *const PropertyHandle<f32>);
let animation = Rc::new(RefCell::new(PropertyAnimationBinding::new_with_value(
value,
animation_data,
inner.clone(),
)));
PropertyImpl::set_binding(inner.clone(), Some(animation.clone()));
}
/// Internal function to set up a property animation between values produced by the specified binding for an integer property.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_property_set_animated_binding_int(
out: *const PropertyHandleOpaque,
binding: extern "C" fn(*mut c_void, &EvaluationContext, *mut i32),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
animation_data: &crate::abi::primitives::PropertyAnimation,
) {
let inner = &*(out as *const PropertyHandle<i32>);
let binding = make_c_function_binding(binding, user_data, drop_user_data);
let animation = Rc::new(RefCell::new(PropertyAnimationBinding::new_with_binding(
binding,
animation_data,
inner.clone(),
)));
PropertyImpl::set_binding(inner.clone(), Some(animation.clone()));
}
/// Internal function to set up a property animation between values produced by the specified binding for a float property.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_property_set_animated_binding_float(
out: *const PropertyHandleOpaque,
binding: extern "C" fn(*mut c_void, &EvaluationContext, *mut f32),
user_data: *mut c_void,
drop_user_data: Option<extern "C" fn(*mut c_void)>,
animation_data: &crate::abi::primitives::PropertyAnimation,
) {
let inner = &*(out as *const PropertyHandle<f32>);
let binding = make_c_function_binding(binding, user_data, drop_user_data);
let animation = Rc::new(RefCell::new(PropertyAnimationBinding::new_with_binding(
binding,
animation_data,
inner.clone(),
)));
PropertyImpl::set_binding(inner.clone(), Some(animation.clone()));
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -12,6 +12,7 @@ fn main() {
"ComponentVTable",
"Slice",
"ComponentWindowOpaque",
"PropertyAnimation",
]
.iter()
.map(|x| x.to_string())