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::PropertyAnimation;
using cbindgen_private::StateInfo;
using cbindgen_private::Slice;
using cbindgen_private::TextHorizontalAlignment;
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::{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;

View file

@ -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 <length> x;
property <length> y;

View file

@ -144,7 +144,8 @@ fn handle_property_binding(
init: &mut Vec<TokenStream>,
) {
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 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,
};
init.push(quote!(
#rust_property.#setter;
));
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<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)
///
/// 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::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);

View file

@ -15,19 +15,38 @@ use crate::langtype::Type;
use crate::object_tree::*;
use std::rc::Rc;
pub fn lower_states(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
recurse_elem(&component.root_element, &(), &mut |elem, _| lower_state_in_element(elem, diag));
pub fn lower_states(
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() {
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());
}

View file

@ -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<Instant> for Instant {
type Output = core::time::Duration;

View file

@ -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<F> {
@ -1187,7 +1187,7 @@ struct StateInfoBinding<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 {
// 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<F: Fn() -> u32> crate::properties::BindingCallable for StateInfoBinding<F>
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<F: Fn() -> u32> crate::properties::BindingCallable for StateInfoBinding<F>
}
/// 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 };
// Safety: The StateInfoBinding is a BindingCallable for type StateInfo
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]>>());
continue;
}
Type::Object { name: Some(name), .. } if name.ends_with("::StateInfo") => {
property_info::<sixtyfps_corelib::properties::StateInfo>()
}
Type::Object { .. } => property_info::<eval::Value>(),
Type::Array(_) => property_info::<eval::Value>(),
Type::Percent => property_info::<f32>(),
@ -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<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(
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,

View file

@ -159,28 +159,34 @@ declare_value_conversion!(Color => [Color] );
declare_value_conversion!(PathElements => [PathData]);
declare_value_conversion!(EasingCurve => [corelib::animations::EasingCurve]);
impl TryFrom<corelib::model::StandardListViewItem> for Value {
macro_rules! declare_value_struct_conversion {
(struct $name:path { $($field:ident),* $(,)? }) => {
impl TryFrom<$name> for Value {
type Error = ();
fn try_from(
corelib::model::StandardListViewItem { text }: corelib::model::StandardListViewItem,
) -> Result<Self, ()> {
fn try_from($name { $($field),* }: $name) -> Result<Self, ()> {
let mut hm = HashMap::new();
hm.insert("text".into(), text.try_into()?);
$(hm.insert(stringify!($field).into(), $field.try_into()?);)*
Ok(Value::Object(hm))
}
}
impl TryInto<corelib::model::StandardListViewItem> for Value {
}
impl TryInto<$name> for Value {
type Error = ();
fn try_into(self) -> Result<corelib::model::StandardListViewItem, ()> {
fn try_into(self) -> Result<$name, ()> {
match self {
Self::Object(x) => Ok(corelib::model::StandardListViewItem {
text: x.get("text").ok_or(())?.clone().try_into()?,
}),
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<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)]
enum ComponentInstance<'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();
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"))