diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index 4756a2caf..8fce1dff9 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -64,6 +64,7 @@ using ItemVisitorRefMut = vtable::VRefMut; } using cbindgen_private::EasingCurve; using cbindgen_private::PropertyAnimation; +using cbindgen_private::StateInfo; using cbindgen_private::Slice; using cbindgen_private::TextHorizontalAlignment; using cbindgen_private::TextVerticalAlignment; diff --git a/api/sixtyfps-rs/lib.rs b/api/sixtyfps-rs/lib.rs index 9d994e85c..b45cbd61b 100644 --- a/api/sixtyfps-rs/lib.rs +++ b/api/sixtyfps-rs/lib.rs @@ -159,6 +159,11 @@ pub use sixtyfps_corelib::string::SharedString; pub use sixtyfps_corelib::timers::{Timer, TimerMode}; 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 #[doc(hidden)] pub mod re_exports { @@ -187,7 +192,7 @@ pub mod re_exports { pub use sixtyfps_corelib::items::*; pub use sixtyfps_corelib::layout::*; 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::slice::Slice; pub use sixtyfps_corelib::Color; diff --git a/sixtyfps_compiler/builtins.60 b/sixtyfps_compiler/builtins.60 index 5c4a34aa7..02d9bcc5b 100644 --- a/sixtyfps_compiler/builtins.60 +++ b/sixtyfps_compiler/builtins.60 @@ -203,6 +203,13 @@ export struct StandardListViewItem := { text: string } +export struct StateInfo := { + //-name:sixtyfps::StateInfo + current_state: int, + previous_state: int, + //change_time: duration, +} + export NativeButton := _ { property x; property y; diff --git a/sixtyfps_compiler/generator/rust.rs b/sixtyfps_compiler/generator/rust.rs index b00080786..33c5cdfd3 100644 --- a/sixtyfps_compiler/generator/rust.rs +++ b/sixtyfps_compiler/generator/rust.rs @@ -144,7 +144,8 @@ fn handle_property_binding( init: &mut Vec, ) { 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); init.push(quote!( #rust_property.set_handler({ @@ -172,26 +173,30 @@ fn handle_property_binding( } } else { let tokens_for_expression = compile_expression(binding_expression, &component); - let setter = if binding_expression.is_constant() { - quote!(set((#tokens_for_expression) as _)) + init.push(if binding_expression.is_constant() { + quote! { #rust_property.set((#tokens_for_expression) as _); } } else { - property_set_binding_tokens( - component, - &item_rc, - prop_name, - quote!({ - let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned); - move || { - let self_pinned = self_weak.upgrade().unwrap(); - let _self = self_pinned.as_pin_ref(); - (#tokens_for_expression) as _ - } - }), - ) - }; - init.push(quote!( - #rust_property.#setter; - )); + let binding_tokens = quote!({ + let self_weak = sixtyfps::re_exports::VRc::downgrade(&self_pinned); + 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 { + quote! { sixtyfps::re_exports::set_state_binding(#rust_property, #binding_tokens); } + } 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, - 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) /// /// to be used like: diff --git a/sixtyfps_compiler/lib.rs b/sixtyfps_compiler/lib.rs index e9f06f973..c74804794 100644 --- a/sixtyfps_compiler/lib.rs +++ b/sixtyfps_compiler/lib.rs @@ -156,7 +156,7 @@ pub async fn run_passes<'a>( passes::materialize_fake_properties::materialize_fake_properties(&doc.root_component); passes::collect_resources::collect_resources(&doc.root_component); 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::lower_layout::lower_layouts(&doc.root_component, &mut type_loader, diag).await; passes::deduplicate_property_read::deduplicate_property_read(&doc.root_component); diff --git a/sixtyfps_compiler/passes/lower_states.rs b/sixtyfps_compiler/passes/lower_states.rs index 6e72b31a8..a246c9a1d 100644 --- a/sixtyfps_compiler/passes/lower_states.rs +++ b/sixtyfps_compiler/passes/lower_states.rs @@ -15,19 +15,38 @@ use crate::langtype::Type; use crate::object_tree::*; use std::rc::Rc; -pub fn lower_states(component: &Rc, diag: &mut BuildDiagnostics) { - recurse_elem(&component.root_element, &(), &mut |elem, _| lower_state_in_element(elem, diag)); +pub fn lower_states( + component: &Rc, + 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() { return; } + let has_transitions = !root_element.borrow().transitions.is_empty(); 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), 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 states = std::mem::take(&mut root_element.borrow_mut().states); 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( 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()); } diff --git a/sixtyfps_runtime/corelib/animations.rs b/sixtyfps_runtime/corelib/animations.rs index a7a40b63c..fe4cebc75 100644 --- a/sixtyfps_runtime/corelib/animations.rs +++ b/sixtyfps_runtime/corelib/animations.rs @@ -31,7 +31,7 @@ impl Default for EasingCurve { /// Represent an instant, in miliseconds since the AnimationDriver's initial_instant #[repr(transparent)] #[derive(Copy, Clone, Debug, Default, PartialEq, Ord, PartialOrd, Eq)] -pub struct Instant(u64); +pub struct Instant(pub u64); impl core::ops::Sub for Instant { type Output = core::time::Duration; diff --git a/sixtyfps_runtime/corelib/properties.rs b/sixtyfps_runtime/corelib/properties.rs index ae15878d5..e35f57147 100644 --- a/sixtyfps_runtime/corelib/properties.rs +++ b/sixtyfps_runtime/corelib/properties.rs @@ -1175,11 +1175,11 @@ mod animation_tests { #[derive(Clone, Default, Debug, PartialEq)] pub struct StateInfo { /// The current state value - current_state: u32, + pub current_state: i32, /// The previous state - previous_state: u32, + pub previous_state: i32, /// The instant in which the state changed last - change_time: crate::animations::Instant, + pub change_time: crate::animations::Instant, } struct StateInfoBinding { @@ -1187,7 +1187,7 @@ struct StateInfoBinding { binding: F, } -impl u32> crate::properties::BindingCallable for StateInfoBinding { +impl i32> crate::properties::BindingCallable for StateInfoBinding { unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { // Safety: We should ony set this binding on a property of type StateInfo let value = &mut *(value as *mut StateInfo); @@ -1196,6 +1196,7 @@ impl u32> crate::properties::BindingCallable for StateInfoBinding if new_state != value.current_state { value.previous_state = value.current_state; value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick); + value.current_state = new_state; } BindingResult::KeepBinding } @@ -1208,7 +1209,7 @@ impl u32> crate::properties::BindingCallable for StateInfoBinding } /// Sets a binding that returns a state to a StateInfo property -pub fn set_state_binding(property: Pin<&Property>, binding: impl Fn() -> u32 + 'static) { +pub fn set_state_binding(property: Pin<&Property>, binding: impl Fn() -> i32 + 'static) { let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding }; // Safety: The StateInfoBinding is a BindingCallable for type StateInfo unsafe { property.handle.set_binding(bind_callable) } diff --git a/sixtyfps_runtime/interpreter/dynamic_component.rs b/sixtyfps_runtime/interpreter/dynamic_component.rs index 618d650cb..844db0913 100644 --- a/sixtyfps_runtime/interpreter/dynamic_component.rs +++ b/sixtyfps_runtime/interpreter/dynamic_component.rs @@ -643,6 +643,9 @@ fn generate_component<'id>( .insert(name.clone(), builder.add_field_type::>()); continue; } + Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => { + property_info::() + } Type::Object { .. } => property_info::(), Type::Array(_) => property_info::(), Type::Percent => property_info::(), @@ -888,6 +891,39 @@ pub fn instantiate<'id>( offset, prop: prop_info, .. }) = 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), + ); + 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( instance_ref, &component_type.original.root_element.borrow().property_animations, @@ -912,12 +948,6 @@ pub fn instantiate<'id>( prop_info.set(item, v, None).unwrap(); } else { 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 .set_binding( item, diff --git a/sixtyfps_runtime/interpreter/eval.rs b/sixtyfps_runtime/interpreter/eval.rs index 66f2eb5f2..1fecb62f6 100644 --- a/sixtyfps_runtime/interpreter/eval.rs +++ b/sixtyfps_runtime/interpreter/eval.rs @@ -159,28 +159,34 @@ declare_value_conversion!(Color => [Color] ); declare_value_conversion!(PathElements => [PathData]); declare_value_conversion!(EasingCurve => [corelib::animations::EasingCurve]); -impl TryFrom for Value { - type Error = (); - fn try_from( - corelib::model::StandardListViewItem { text }: corelib::model::StandardListViewItem, - ) -> Result { - let mut hm = HashMap::new(); - hm.insert("text".into(), text.try_into()?); - Ok(Value::Object(hm)) - } -} -impl TryInto for Value { - type Error = (); - fn try_into(self) -> Result { - match self { - Self::Object(x) => Ok(corelib::model::StandardListViewItem { - text: x.get("text").ok_or(())?.clone().try_into()?, - }), - _ => Err(()), +macro_rules! declare_value_struct_conversion { + (struct $name:path { $($field:ident),* $(,)? }) => { + impl TryFrom<$name> for Value { + type Error = (); + fn try_from($name { $($field),* }: $name) -> Result { + let mut hm = HashMap::new(); + $(hm.insert(stringify!($field).into(), $field.try_into()?);)* + Ok(Value::Object(hm)) + } } - } + 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 { ($ty:ty, $n:ident) => { 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::layout::LayoutAlignment, LayoutAlignment); +impl TryFrom for Value { + type Error = (); + fn try_from(value: corelib::animations::Instant) -> Result { + Ok(Value::Number(value.0 as _)) + } +} +impl TryInto for Value { + type Error = (); + fn try_into(self) -> Result { + match self { + Value::Number(x) => Ok(corelib::animations::Instant(x as _)), + _ => Err(()), + } + } +} + #[derive(Copy, Clone)] enum ComponentInstance<'a, 'id> { InstanceRef(InstanceRef<'a, 'id>), diff --git a/tests/driver_lib/cbindgen.rs b/tests/driver_lib/cbindgen.rs index 5d5345252..3c6e6f02d 100644 --- a/tests/driver_lib/cbindgen.rs +++ b/tests/driver_lib/cbindgen.rs @@ -110,6 +110,11 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> { let mut properties_config = config.clone(); 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() .with_config(properties_config) .with_src(crate_dir.join("properties.rs"))