More work on the state binding for transition:

When there is a transition, turn the state property into a property of StateInfo

Not yet implemented for C++
This commit is contained in:
Olivier Goffart 2020-11-19 12:42:34 +01:00
parent 05924da620
commit cfa9413861
11 changed files with 156 additions and 71 deletions

View file

@ -64,6 +64,7 @@ using ItemVisitorRefMut = vtable::VRefMut<cbindgen_private::ItemVisitorVTable>;
} }
using cbindgen_private::EasingCurve; using cbindgen_private::EasingCurve;
using cbindgen_private::PropertyAnimation; using cbindgen_private::PropertyAnimation;
using cbindgen_private::StateInfo;
using cbindgen_private::Slice; using cbindgen_private::Slice;
using cbindgen_private::TextHorizontalAlignment; using cbindgen_private::TextHorizontalAlignment;
using cbindgen_private::TextVerticalAlignment; using cbindgen_private::TextVerticalAlignment;

View file

@ -159,6 +159,11 @@ pub use sixtyfps_corelib::string::SharedString;
pub use sixtyfps_corelib::timers::{Timer, TimerMode}; pub use sixtyfps_corelib::timers::{Timer, TimerMode};
pub use sixtyfps_corelib::{ARGBColor, Color}; pub use sixtyfps_corelib::{ARGBColor, Color};
// FIXME: this should not be in this namespace
// but the name is `sixtyfps::StateInfo` in builtin.60
#[doc(hidden)]
pub use sixtyfps_corelib::properties::StateInfo;
/// internal re_exports used by the macro generated /// internal re_exports used by the macro generated
#[doc(hidden)] #[doc(hidden)]
pub mod re_exports { pub mod re_exports {
@ -187,7 +192,7 @@ pub mod re_exports {
pub use sixtyfps_corelib::items::*; pub use sixtyfps_corelib::items::*;
pub use sixtyfps_corelib::layout::*; pub use sixtyfps_corelib::layout::*;
pub use sixtyfps_corelib::model::*; pub use sixtyfps_corelib::model::*;
pub use sixtyfps_corelib::properties::{Property, PropertyTracker}; pub use sixtyfps_corelib::properties::{set_state_binding, Property, PropertyTracker};
pub use sixtyfps_corelib::signals::Signal; pub use sixtyfps_corelib::signals::Signal;
pub use sixtyfps_corelib::slice::Slice; pub use sixtyfps_corelib::slice::Slice;
pub use sixtyfps_corelib::Color; pub use sixtyfps_corelib::Color;

View file

@ -203,6 +203,13 @@ export struct StandardListViewItem := {
text: string text: string
} }
export struct StateInfo := {
//-name:sixtyfps::StateInfo
current_state: int,
previous_state: int,
//change_time: duration,
}
export NativeButton := _ { export NativeButton := _ {
property <length> x; property <length> x;
property <length> y; property <length> y;

View file

@ -144,7 +144,8 @@ fn handle_property_binding(
init: &mut Vec<TokenStream>, init: &mut Vec<TokenStream>,
) { ) {
let rust_property = access_member(item_rc, prop_name, component, quote!(_self), false); let rust_property = access_member(item_rc, prop_name, component, quote!(_self), false);
if matches!(item_rc.borrow().lookup_property(prop_name), Type::Signal{..}) { let prop_type = item_rc.borrow().lookup_property(prop_name);
if matches!(prop_type, Type::Signal{..}) {
let tokens_for_expression = compile_expression(binding_expression, &component); let tokens_for_expression = compile_expression(binding_expression, &component);
init.push(quote!( init.push(quote!(
#rust_property.set_handler({ #rust_property.set_handler({
@ -172,26 +173,30 @@ fn handle_property_binding(
} }
} else { } else {
let tokens_for_expression = compile_expression(binding_expression, &component); let tokens_for_expression = compile_expression(binding_expression, &component);
let setter = if binding_expression.is_constant() { init.push(if binding_expression.is_constant() {
quote!(set((#tokens_for_expression) as _)) quote! { #rust_property.set((#tokens_for_expression) as _); }
} else { } else {
property_set_binding_tokens( let binding_tokens = quote!({
component, let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned);
&item_rc, move || {
prop_name, let self_pinned = self_weak.upgrade().unwrap();
quote!({ let _self = self_pinned.as_pin_ref();
let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned); (#tokens_for_expression) as _
move || { }
let self_pinned = self_weak.upgrade().unwrap(); });
let _self = self_pinned.as_pin_ref();
(#tokens_for_expression) as _ let is_state_info = match prop_type {
} Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => true,
}), _ => false,
) };
}; if is_state_info {
init.push(quote!( quote! { sixtyfps::re_exports::set_state_binding(#rust_property, #binding_tokens); }
#rust_property.#setter; } else if let Some(anim) = property_animation_tokens(component, &item_rc, prop_name) {
)); quote! { #rust_property.set_animated_binding(#binding_tokens, #anim); }
} else {
quote! { #rust_property.set_binding(#binding_tokens); }
}
});
} }
} }
@ -899,19 +904,6 @@ fn property_set_value_tokens(
} }
} }
fn property_set_binding_tokens(
component: &Rc<Component>,
element: &ElementRc,
property_name: &str,
binding_tokens: TokenStream,
) -> TokenStream {
if let Some(animation_tokens) = property_animation_tokens(component, element, property_name) {
quote!(set_animated_binding(#binding_tokens, #animation_tokens))
} else {
quote!(set_binding(#binding_tokens))
}
}
/// Returns the code that can access the given property or signal (but without the set or get) /// Returns the code that can access the given property or signal (but without the set or get)
/// ///
/// to be used like: /// to be used like:

View file

@ -156,7 +156,7 @@ pub async fn run_passes<'a>(
passes::materialize_fake_properties::materialize_fake_properties(&doc.root_component); passes::materialize_fake_properties::materialize_fake_properties(&doc.root_component);
passes::collect_resources::collect_resources(&doc.root_component); passes::collect_resources::collect_resources(&doc.root_component);
doc.root_component.embed_file_resources.set(compiler_config.embed_resources); doc.root_component.embed_file_resources.set(compiler_config.embed_resources);
passes::lower_states::lower_states(&doc.root_component, diag); passes::lower_states::lower_states(&doc.root_component, &doc.local_registry, diag);
passes::repeater_component::process_repeater_components(&doc.root_component); passes::repeater_component::process_repeater_components(&doc.root_component);
passes::lower_layout::lower_layouts(&doc.root_component, &mut type_loader, diag).await; passes::lower_layout::lower_layouts(&doc.root_component, &mut type_loader, diag).await;
passes::deduplicate_property_read::deduplicate_property_read(&doc.root_component); passes::deduplicate_property_read::deduplicate_property_read(&doc.root_component);

View file

@ -15,19 +15,38 @@ use crate::langtype::Type;
use crate::object_tree::*; use crate::object_tree::*;
use std::rc::Rc; use std::rc::Rc;
pub fn lower_states(component: &Rc<Component>, diag: &mut BuildDiagnostics) { pub fn lower_states(
recurse_elem(&component.root_element, &(), &mut |elem, _| lower_state_in_element(elem, diag)); component: &Rc<Component>,
tr: &crate::typeregister::TypeRegister,
diag: &mut BuildDiagnostics,
) {
let state_info_type = tr.lookup("StateInfo");
assert!(matches!(state_info_type, Type::Object{ name: Some(_), .. }));
recurse_elem(&component.root_element, &(), &mut |elem, _| {
lower_state_in_element(elem, &state_info_type, diag)
});
} }
fn lower_state_in_element(root_element: &ElementRc, _diag: &mut BuildDiagnostics) { fn lower_state_in_element(
root_element: &ElementRc,
state_info_type: &Type,
_diag: &mut BuildDiagnostics,
) {
if root_element.borrow().states.is_empty() { if root_element.borrow().states.is_empty() {
return; return;
} }
let has_transitions = !root_element.borrow().transitions.is_empty();
let state_property = compute_state_property_name(root_element); let state_property = compute_state_property_name(root_element);
let state_property_ref = Expression::PropertyReference(NamedReference { let mut state_property_ref = Expression::PropertyReference(NamedReference {
element: Rc::downgrade(root_element), element: Rc::downgrade(root_element),
name: state_property.clone(), name: state_property.clone(),
}); });
if has_transitions {
state_property_ref = Expression::ObjectAccess {
base: Box::new(state_property_ref),
name: "current_state".into(),
};
}
let mut state_value = Expression::NumberLiteral(0., Unit::None); let mut state_value = Expression::NumberLiteral(0., Unit::None);
let states = std::mem::take(&mut root_element.borrow_mut().states); let states = std::mem::take(&mut root_element.borrow_mut().states);
for (idx, state) in states.into_iter().enumerate().rev() { for (idx, state) in states.into_iter().enumerate().rev() {
@ -58,7 +77,10 @@ fn lower_state_in_element(root_element: &ElementRc, _diag: &mut BuildDiagnostics
} }
root_element.borrow_mut().property_declarations.insert( root_element.borrow_mut().property_declarations.insert(
state_property.clone(), state_property.clone(),
PropertyDeclaration { property_type: Type::Int32, ..PropertyDeclaration::default() }, PropertyDeclaration {
property_type: if has_transitions { state_info_type.clone() } else { Type::Int32 },
..PropertyDeclaration::default()
},
); );
root_element.borrow_mut().bindings.insert(state_property.clone(), state_value.into()); root_element.borrow_mut().bindings.insert(state_property.clone(), state_value.into());
} }

View file

@ -31,7 +31,7 @@ impl Default for EasingCurve {
/// Represent an instant, in miliseconds since the AnimationDriver's initial_instant /// Represent an instant, in miliseconds since the AnimationDriver's initial_instant
#[repr(transparent)] #[repr(transparent)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Ord, PartialOrd, Eq)] #[derive(Copy, Clone, Debug, Default, PartialEq, Ord, PartialOrd, Eq)]
pub struct Instant(u64); pub struct Instant(pub u64);
impl core::ops::Sub<Instant> for Instant { impl core::ops::Sub<Instant> for Instant {
type Output = core::time::Duration; type Output = core::time::Duration;

View file

@ -1175,11 +1175,11 @@ mod animation_tests {
#[derive(Clone, Default, Debug, PartialEq)] #[derive(Clone, Default, Debug, PartialEq)]
pub struct StateInfo { pub struct StateInfo {
/// The current state value /// The current state value
current_state: u32, pub current_state: i32,
/// The previous state /// The previous state
previous_state: u32, pub previous_state: i32,
/// The instant in which the state changed last /// The instant in which the state changed last
change_time: crate::animations::Instant, pub change_time: crate::animations::Instant,
} }
struct StateInfoBinding<F> { struct StateInfoBinding<F> {
@ -1187,7 +1187,7 @@ struct StateInfoBinding<F> {
binding: F, binding: F,
} }
impl<F: Fn() -> u32> crate::properties::BindingCallable for StateInfoBinding<F> { impl<F: Fn() -> i32> crate::properties::BindingCallable for StateInfoBinding<F> {
unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
// Safety: We should ony set this binding on a property of type StateInfo // Safety: We should ony set this binding on a property of type StateInfo
let value = &mut *(value as *mut StateInfo); let value = &mut *(value as *mut StateInfo);
@ -1196,6 +1196,7 @@ impl<F: Fn() -> u32> crate::properties::BindingCallable for StateInfoBinding<F>
if new_state != value.current_state { if new_state != value.current_state {
value.previous_state = value.current_state; value.previous_state = value.current_state;
value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick); value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
value.current_state = new_state;
} }
BindingResult::KeepBinding BindingResult::KeepBinding
} }
@ -1208,7 +1209,7 @@ impl<F: Fn() -> u32> crate::properties::BindingCallable for StateInfoBinding<F>
} }
/// Sets a binding that returns a state to a StateInfo property /// Sets a binding that returns a state to a StateInfo property
pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> u32 + 'static) { pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding }; let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding };
// Safety: The StateInfoBinding is a BindingCallable for type StateInfo // Safety: The StateInfoBinding is a BindingCallable for type StateInfo
unsafe { property.handle.set_binding(bind_callable) } unsafe { property.handle.set_binding(bind_callable) }

View file

@ -643,6 +643,9 @@ fn generate_component<'id>(
.insert(name.clone(), builder.add_field_type::<Signal<[eval::Value]>>()); .insert(name.clone(), builder.add_field_type::<Signal<[eval::Value]>>());
continue; continue;
} }
Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => {
property_info::<sixtyfps_corelib::properties::StateInfo>()
}
Type::Object { .. } => property_info::<eval::Value>(), Type::Object { .. } => property_info::<eval::Value>(),
Type::Array(_) => property_info::<eval::Value>(), Type::Array(_) => property_info::<eval::Value>(),
Type::Percent => property_info::<f32>(), Type::Percent => property_info::<f32>(),
@ -888,6 +891,39 @@ pub fn instantiate<'id>(
offset, prop: prop_info, .. offset, prop: prop_info, ..
}) = component_type.custom_properties.get(prop.as_str()) }) = component_type.custom_properties.get(prop.as_str())
{ {
let c = Pin::new_unchecked(vtable::VRef::from_raw(
NonNull::from(&component_type.ct).cast(),
component_box.instance.as_ptr().cast(),
));
let is_state_info = match ty {
Type::Object { name: Some(name), .. }
if name.ends_with("::StateInfo") =>
{
true
}
_ => false,
};
if is_state_info {
let prop = Pin::new_unchecked(
&*(instance_ref.as_ptr().add(*offset)
as *const Property<sixtyfps_corelib::properties::StateInfo>),
);
let e = expr.expression.clone();
sixtyfps_corelib::properties::set_state_binding(prop, move || {
generativity::make_guard!(guard);
eval::eval_expression(
&e,
&mut eval::EvalLocalContext::from_component_instance(
InstanceRef::from_pin_ref(c, guard),
),
)
.try_into()
.unwrap()
});
continue;
}
let maybe_animation = animation_for_property( let maybe_animation = animation_for_property(
instance_ref, instance_ref,
&component_type.original.root_element.borrow().property_animations, &component_type.original.root_element.borrow().property_animations,
@ -912,12 +948,6 @@ pub fn instantiate<'id>(
prop_info.set(item, v, None).unwrap(); prop_info.set(item, v, None).unwrap();
} else { } else {
let e = e.clone(); let e = e.clone();
let component_type = component_type.clone();
let instance = component_box.instance.as_ptr();
let c = Pin::new_unchecked(vtable::VRef::from_raw(
NonNull::from(&component_type.ct).cast(),
instance.cast(),
));
prop_info prop_info
.set_binding( .set_binding(
item, item,

View file

@ -159,28 +159,34 @@ declare_value_conversion!(Color => [Color] );
declare_value_conversion!(PathElements => [PathData]); declare_value_conversion!(PathElements => [PathData]);
declare_value_conversion!(EasingCurve => [corelib::animations::EasingCurve]); declare_value_conversion!(EasingCurve => [corelib::animations::EasingCurve]);
impl TryFrom<corelib::model::StandardListViewItem> for Value { macro_rules! declare_value_struct_conversion {
type Error = (); (struct $name:path { $($field:ident),* $(,)? }) => {
fn try_from( impl TryFrom<$name> for Value {
corelib::model::StandardListViewItem { text }: corelib::model::StandardListViewItem, type Error = ();
) -> Result<Self, ()> { fn try_from($name { $($field),* }: $name) -> Result<Self, ()> {
let mut hm = HashMap::new(); let mut hm = HashMap::new();
hm.insert("text".into(), text.try_into()?); $(hm.insert(stringify!($field).into(), $field.try_into()?);)*
Ok(Value::Object(hm)) Ok(Value::Object(hm))
} }
}
impl TryInto<corelib::model::StandardListViewItem> for Value {
type Error = ();
fn try_into(self) -> Result<corelib::model::StandardListViewItem, ()> {
match self {
Self::Object(x) => Ok(corelib::model::StandardListViewItem {
text: x.get("text").ok_or(())?.clone().try_into()?,
}),
_ => Err(()),
} }
} impl TryInto<$name> for Value {
type Error = ();
fn try_into(self) -> Result<$name, ()> {
match self {
Self::Object(x) => {
type Ty = $name;
Ok(Ty { $($field: x.get(stringify!($field)).ok_or(())?.clone().try_into()?),* })
}
_ => Err(()),
}
}
}
};
} }
declare_value_struct_conversion!(struct corelib::model::StandardListViewItem { text });
declare_value_struct_conversion!(struct corelib::properties::StateInfo { current_state, previous_state, change_time });
macro_rules! declare_value_enum_conversion { macro_rules! declare_value_enum_conversion {
($ty:ty, $n:ident) => { ($ty:ty, $n:ident) => {
impl TryFrom<$ty> for Value { impl TryFrom<$ty> for Value {
@ -212,6 +218,22 @@ declare_value_enum_conversion!(corelib::items::TextHorizontalAlignment, TextHori
declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVerticalAlignment); declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVerticalAlignment);
declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment); declare_value_enum_conversion!(corelib::layout::LayoutAlignment, LayoutAlignment);
impl TryFrom<corelib::animations::Instant> for Value {
type Error = ();
fn try_from(value: corelib::animations::Instant) -> Result<Self, ()> {
Ok(Value::Number(value.0 as _))
}
}
impl TryInto<corelib::animations::Instant> for Value {
type Error = ();
fn try_into(self) -> Result<corelib::animations::Instant, ()> {
match self {
Value::Number(x) => Ok(corelib::animations::Instant(x as _)),
_ => Err(()),
}
}
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
enum ComponentInstance<'a, 'id> { enum ComponentInstance<'a, 'id> {
InstanceRef(InstanceRef<'a, 'id>), InstanceRef(InstanceRef<'a, 'id>),

View file

@ -110,6 +110,11 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> {
let mut properties_config = config.clone(); let mut properties_config = config.clone();
properties_config.export.exclude.clear(); properties_config.export.exclude.clear();
properties_config.export.include.push("StateInfo".into());
properties_config
.export
.pre_body
.insert("StateInfo".to_owned(), " using Instant = uint64_t;".into());
cbindgen::Builder::new() cbindgen::Builder::new()
.with_config(properties_config) .with_config(properties_config)
.with_src(crate_dir.join("properties.rs")) .with_src(crate_dir.join("properties.rs"))