mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
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:
parent
05924da620
commit
cfa9413861
11 changed files with 156 additions and 71 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -159,27 +159,33 @@ 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) => {
|
||||
|
@ -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>),
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue