mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
parent
3ddc3c6ce5
commit
ce34ff87d0
12 changed files with 224 additions and 73 deletions
|
@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file.
|
|||
- `z` property on items
|
||||
- Mouse wheel work on the Flickable and derivatives
|
||||
- One can now omit the type of a two way binding property
|
||||
- One can declare callback aliases
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -224,6 +224,8 @@ fn to_eval_value<'cx>(
|
|||
Type::Enumeration(_) => todo!(),
|
||||
Type::Invalid
|
||||
| Type::Void
|
||||
| Type::InferredProperty
|
||||
| Type::InferredCallback
|
||||
| Type::Builtin(_)
|
||||
| Type::Native(_)
|
||||
| Type::Function { .. }
|
||||
|
|
|
@ -358,6 +358,16 @@ Example := Rectangle {
|
|||
}
|
||||
```
|
||||
|
||||
### Callback aliases
|
||||
|
||||
It is possible to declare callback aliases in a similar way to two-way bindings:
|
||||
|
||||
```60
|
||||
Example := Rectangle {
|
||||
callback clicked <=> area.clicked;
|
||||
area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
|
|
|
@ -659,6 +659,12 @@ fn generate_component(
|
|||
let mut init = vec!["[[maybe_unused]] auto self = this;".into()];
|
||||
|
||||
for (cpp_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
|
||||
let access = if let Some(alias) = &property_decl.is_alias {
|
||||
access_named_reference(alias, component, "this")
|
||||
} else {
|
||||
format!("this->{}", cpp_name)
|
||||
};
|
||||
|
||||
let ty = if let Type::Callback { args, return_type } = &property_decl.property_type {
|
||||
let param_types =
|
||||
args.iter().map(|t| get_cpp_type(t, property_decl, diag)).collect::<Vec<_>>();
|
||||
|
@ -668,7 +674,7 @@ fn generate_component(
|
|||
if property_decl.expose_in_public_api && is_root {
|
||||
let callback_emitter = vec![format!(
|
||||
"return {}.call({});",
|
||||
cpp_name,
|
||||
access,
|
||||
(0..args.len()).map(|i| format!("arg_{}", i)).join(", ")
|
||||
)];
|
||||
component_struct.members.push((
|
||||
|
@ -696,7 +702,7 @@ fn generate_component(
|
|||
signature: "(Functor && callback_handler) const".into(),
|
||||
statements: Some(vec![format!(
|
||||
"{}.set_handler(std::forward<Functor>(callback_handler));",
|
||||
cpp_name
|
||||
access
|
||||
)]),
|
||||
..Default::default()
|
||||
}),
|
||||
|
@ -707,12 +713,6 @@ fn generate_component(
|
|||
let cpp_type = get_cpp_type(&property_decl.property_type, property_decl, diag);
|
||||
|
||||
if property_decl.expose_in_public_api && is_root {
|
||||
let access = if let Some(alias) = &property_decl.is_alias {
|
||||
access_named_reference(alias, component, "this")
|
||||
} else {
|
||||
format!("this->{}", cpp_name)
|
||||
};
|
||||
|
||||
let prop_getter: Vec<String> = vec![format!("return {}.get();", access)];
|
||||
component_struct.members.push((
|
||||
Access::Public,
|
||||
|
|
|
@ -280,8 +280,13 @@ fn generate_component(
|
|||
let mut property_and_callback_accessors: Vec<TokenStream> = vec![];
|
||||
for (prop_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
|
||||
let prop_ident = format_ident!("{}", prop_name);
|
||||
let prop = if let Some(alias) = &property_decl.is_alias {
|
||||
access_named_reference(alias, component, quote!(_self))
|
||||
} else {
|
||||
quote!(#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self))
|
||||
};
|
||||
|
||||
if let Type::Callback { args, return_type } = &property_decl.property_type {
|
||||
declared_callbacks.push(prop_ident.clone());
|
||||
let callback_args = args
|
||||
.iter()
|
||||
.map(|a| get_rust_type(a, &property_decl.type_node(), diag))
|
||||
|
@ -295,16 +300,13 @@ fn generate_component(
|
|||
.map(|i| format_ident!("arg_{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
let caller_ident = format_ident!("invoke_{}", prop_name);
|
||||
property_and_callback_accessors.push(
|
||||
quote!(
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
|
||||
let _self = vtable::VRc::as_pin_ref(&self.0);
|
||||
#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self).call(&(#(#args_name,)*))
|
||||
#prop.call(&(#(#args_name,)*))
|
||||
}
|
||||
)
|
||||
,
|
||||
);
|
||||
));
|
||||
|
||||
let on_ident = format_ident!("on_{}", prop_name);
|
||||
let args_index =
|
||||
|
@ -315,7 +317,7 @@ fn generate_component(
|
|||
pub fn #on_ident(&self, f: impl Fn(#(#callback_args),*) -> #return_type + 'static) {
|
||||
let _self = vtable::VRc::as_pin_ref(&self.0);
|
||||
#[allow(unused)]
|
||||
#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self).set_handler(
|
||||
#prop.set_handler(
|
||||
// FIXME: why do i need to clone here?
|
||||
move |args| f(#(args.#args_index.clone()),*)
|
||||
)
|
||||
|
@ -324,8 +326,11 @@ fn generate_component(
|
|||
,
|
||||
);
|
||||
}
|
||||
if property_decl.is_alias.is_none() {
|
||||
declared_callbacks.push(prop_ident.clone());
|
||||
declared_callbacks_types.push(callback_args);
|
||||
declared_callbacks_ret.push(return_type);
|
||||
}
|
||||
} else {
|
||||
let rust_property_type =
|
||||
get_rust_type(&property_decl.property_type, &property_decl.type_node(), diag);
|
||||
|
@ -333,12 +338,6 @@ fn generate_component(
|
|||
let getter_ident = format_ident!("get_{}", prop_name);
|
||||
let setter_ident = format_ident!("set_{}", prop_name);
|
||||
|
||||
let prop = if let Some(alias) = &property_decl.is_alias {
|
||||
access_named_reference(alias, component, quote!(_self))
|
||||
} else {
|
||||
quote!(#inner_component_id::FIELD_OFFSETS.#prop_ident.apply_pin(_self))
|
||||
};
|
||||
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #getter_ident(&self) -> #rust_property_type {
|
||||
|
|
|
@ -638,7 +638,7 @@ impl Element {
|
|||
let unresolved_name = unwrap_or_continue!(identifier_text(&con_node); diag);
|
||||
let PropertyLookupResult { resolved_name, property_type } =
|
||||
r.lookup_property(&unresolved_name);
|
||||
if let Type::Callback { args, .. } = property_type {
|
||||
if let Type::Callback { args, .. } = &property_type {
|
||||
let num_arg = con_node.DeclaredIdentifier().count();
|
||||
if num_arg > args.len() {
|
||||
diag.push_error(
|
||||
|
@ -651,6 +651,15 @@ impl Element {
|
|||
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
|
||||
);
|
||||
}
|
||||
} else if property_type == Type::InferredCallback {
|
||||
// argument matching will happen later
|
||||
} else {
|
||||
diag.push_error(
|
||||
format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
|
||||
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if r.bindings
|
||||
.insert(
|
||||
resolved_name.into_owned(),
|
||||
|
@ -663,12 +672,6 @@ impl Element {
|
|||
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
diag.push_error(
|
||||
format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
|
||||
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for anim in node.PropertyAnimation() {
|
||||
|
|
|
@ -326,7 +326,6 @@ fn parse_callback_declaration(p: &mut impl Parser) {
|
|||
|
||||
if p.test(SyntaxKind::DoubleArrow) {
|
||||
let mut p = p.start_node(SyntaxKind::TwoWayBinding);
|
||||
p.consume();
|
||||
parse_expression(&mut *p);
|
||||
}
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ impl Expression {
|
|||
Expression::TwoWayBinding(n, None)
|
||||
}
|
||||
Expression::CallbackReference(n) => {
|
||||
if ctx.property_type != Type::InferredCallback {
|
||||
if ctx.property_type != Type::InferredCallback && ty != ctx.property_type {
|
||||
ctx.diag.push_error("Cannot bind to a callback".into(), &node);
|
||||
Expression::Invalid
|
||||
} else {
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
Please contact info@sixtyfps.io for more information.
|
||||
LICENSE END */
|
||||
|
||||
Sub := Rectangle {
|
||||
callback compute(int) -> int;
|
||||
callback compute_alias <=> compute;
|
||||
}
|
||||
|
||||
Xxx := Rectangle {
|
||||
|
||||
foo := Rectangle {
|
||||
|
@ -26,4 +31,12 @@ Xxx := Rectangle {
|
|||
// ^^error{The expression in a two way binding must be a property reference}
|
||||
callback loop2 <=> loop3;
|
||||
|
||||
Sub {
|
||||
compute_alias(a, b, c) => {
|
||||
debug(b); // FIXME: one should actually check that the connection has the right amount of arguments
|
||||
// ^error{Unknown unqualified identifier 'b'}
|
||||
return "hello";
|
||||
// ^error{Cannot convert string to int}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -439,9 +439,38 @@ impl<'id> ComponentDescription<'id> {
|
|||
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
||||
return Err(());
|
||||
}
|
||||
if let Some(alias) = self
|
||||
.original
|
||||
.root_element
|
||||
.borrow()
|
||||
.property_declarations
|
||||
.get(name)
|
||||
.and_then(|d| d.is_alias.as_ref())
|
||||
{
|
||||
generativity::make_guard!(guard);
|
||||
// Safety: we just verified that the component has the right vtable
|
||||
let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
|
||||
generativity::make_guard!(guard);
|
||||
let element = alias.element();
|
||||
let enclosing_component = eval::enclosing_component_for_element(&element, c, guard);
|
||||
let component_type = enclosing_component.component_type;
|
||||
let item_info = &component_type.items[element.borrow().id.as_str()];
|
||||
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||
|
||||
if let Some(callback) = item_info.rtti.callbacks.get(alias.name()) {
|
||||
callback.set_handler(item, handler)
|
||||
} else if let Some(callback_offset) = component_type.custom_callbacks.get(alias.name())
|
||||
{
|
||||
let callback = callback_offset.apply(&*enclosing_component.instance);
|
||||
callback.set_handler(handler)
|
||||
} else {
|
||||
return Err(());
|
||||
}
|
||||
} else {
|
||||
let x = self.custom_callbacks.get(name).ok_or(())?;
|
||||
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
|
||||
sig.set_handler(handler);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -458,9 +487,31 @@ impl<'id> ComponentDescription<'id> {
|
|||
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
||||
return Err(());
|
||||
}
|
||||
let x = self.custom_callbacks.get(name).ok_or(())?;
|
||||
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
|
||||
Ok(sig.call(args))
|
||||
generativity::make_guard!(guard);
|
||||
// Safety: we just verified that the component has the right vtable
|
||||
let c = unsafe { InstanceRef::from_pin_ref(component, guard) };
|
||||
if let Some(alias) = self
|
||||
.original
|
||||
.root_element
|
||||
.borrow()
|
||||
.property_declarations
|
||||
.get(name)
|
||||
.and_then(|d| d.is_alias.as_ref())
|
||||
{
|
||||
Ok(eval::invoke_callback(
|
||||
eval::ComponentInstance::InstanceRef(c),
|
||||
&alias.element(),
|
||||
alias.name(),
|
||||
args,
|
||||
))
|
||||
} else {
|
||||
Ok(eval::invoke_callback(
|
||||
eval::ComponentInstance::InstanceRef(c),
|
||||
&self.original.root_element,
|
||||
name,
|
||||
args,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,30 +193,8 @@ pub fn eval_expression(e: &Expression, local_context: &mut EvalLocalContext) ->
|
|||
}
|
||||
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
|
||||
Expression::CallbackReference(nr) => {
|
||||
let element = nr.element();
|
||||
generativity::make_guard!(guard);
|
||||
match enclosing_component_instance_for_element(&element, local_context.component_instance, guard) {
|
||||
ComponentInstance::InstanceRef(enclosing_component) => {
|
||||
let component_type = enclosing_component.component_type;
|
||||
let item_info = &component_type.items[element.borrow().id.as_str()];
|
||||
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||
let args = arguments.iter().map(|e| eval_expression(e, local_context)).collect::<Vec<_>>();
|
||||
|
||||
if let Some(callback) = item_info.rtti.callbacks.get(nr.name()) {
|
||||
callback.call(item, args.as_slice())
|
||||
} else if let Some(callback_offset) = component_type.custom_callbacks.get(nr.name())
|
||||
{
|
||||
let callback = callback_offset.apply(&*enclosing_component.instance);
|
||||
callback.call(args.as_slice())
|
||||
} else {
|
||||
panic!("unkown callback {}", nr.name())
|
||||
}
|
||||
}
|
||||
ComponentInstance::GlobalComponent(global) => {
|
||||
let args = arguments.iter().map(|e| eval_expression(e, local_context));
|
||||
global.as_ref().invoke_callback(nr.name(), args.collect::<Vec<_>>().as_slice())
|
||||
}
|
||||
}
|
||||
invoke_callback(local_context.component_instance, &nr.element(), nr.name(), &args)
|
||||
}
|
||||
Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor, _) => {
|
||||
match local_context.component_instance {
|
||||
|
@ -720,6 +698,35 @@ pub fn store_property(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn invoke_callback(
|
||||
component_instance: ComponentInstance,
|
||||
element: &ElementRc,
|
||||
callback_name: &str,
|
||||
args: &[Value],
|
||||
) -> Value {
|
||||
generativity::make_guard!(guard);
|
||||
match enclosing_component_instance_for_element(element, component_instance, guard) {
|
||||
ComponentInstance::InstanceRef(enclosing_component) => {
|
||||
let component_type = enclosing_component.component_type;
|
||||
let item_info = &component_type.items[element.borrow().id.as_str()];
|
||||
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||
|
||||
if let Some(callback) = item_info.rtti.callbacks.get(callback_name) {
|
||||
callback.call(item, args)
|
||||
} else if let Some(callback_offset) = component_type.custom_callbacks.get(callback_name)
|
||||
{
|
||||
let callback = callback_offset.apply(&*enclosing_component.instance);
|
||||
callback.call(args)
|
||||
} else {
|
||||
panic!("unkown callback {}", callback_name)
|
||||
}
|
||||
}
|
||||
ComponentInstance::GlobalComponent(global) => {
|
||||
global.as_ref().invoke_callback(callback_name, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn root_component_instance<'a, 'old_id, 'new_id>(
|
||||
component: InstanceRef<'a, 'old_id>,
|
||||
guard: generativity::Guard<'new_id>,
|
||||
|
|
66
tests/cases/callbacks/callback_alias.60
Normal file
66
tests/cases/callbacks/callback_alias.60
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* LICENSE BEGIN
|
||||
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
This file is also available under commercial licensing terms.
|
||||
Please contact info@sixtyfps.io for more information.
|
||||
LICENSE END */
|
||||
|
||||
Foo := Rectangle {
|
||||
callback hallo_alias <=> xxx.hallo;
|
||||
callback clicked <=> are.clicked;
|
||||
xxx := Rectangle {
|
||||
callback hallo(int) -> int;
|
||||
hallo(a) => { return a + 88; }
|
||||
}
|
||||
|
||||
are := TouchArea { }
|
||||
}
|
||||
|
||||
TestCase := Rectangle {
|
||||
|
||||
callback foo1_alias <=> foo1.hallo_alias;
|
||||
callback foo2_alias <=> foo2.hallo_alias;
|
||||
|
||||
callback foo1_clicked <=> foo1.clicked;
|
||||
|
||||
callback call_foo2(int) -> int;
|
||||
call_foo2(a) => { return foo2.hallo_alias(a); }
|
||||
|
||||
foo1 := Foo {
|
||||
hallo_alias(a) => { return a + 22; }
|
||||
}
|
||||
|
||||
foo2 := Foo {
|
||||
clicked => { debug(42) }
|
||||
}
|
||||
|
||||
property <bool> test: foo1_alias(100) == 122 && foo2_alias(100) == 188;
|
||||
}
|
||||
|
||||
/*
|
||||
```rust
|
||||
let instance = TestCase::new();
|
||||
assert_eq!(instance.invoke_foo1_alias(100), 122);
|
||||
assert_eq!(instance.invoke_foo2_alias(100), 188);
|
||||
assert_eq!(instance.invoke_call_foo2(100), 188);
|
||||
```
|
||||
|
||||
```cpp
|
||||
auto handle = TestCase::create();
|
||||
const TestCase &instance = *handle;
|
||||
assert_eq(instance.invoke_foo1_alias(100), 122);
|
||||
assert_eq(instance.invoke_foo2_alias(100), 188);
|
||||
assert_eq(instance.invoke_call_foo2(100), 188);
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
var instance = new sixtyfps.TestCase();
|
||||
assert.equal(instance.foo1_alias(100), 122);
|
||||
assert.equal(instance.foo2_alias(100), 188);
|
||||
assert.equal(instance.call_foo2(100), 188);
|
||||
```
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue