mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 06:11:16 +00:00
Support for signal with arguments in the interpreter
This commit is contained in:
parent
2ffef9b31a
commit
42aa91e3eb
7 changed files with 137 additions and 55 deletions
|
@ -38,7 +38,7 @@ require.extensions['.60'] =
|
||||||
});
|
});
|
||||||
c.signals().forEach(x => {
|
c.signals().forEach(x => {
|
||||||
Object.defineProperty(ret, x, {
|
Object.defineProperty(ret, x, {
|
||||||
get() { return function () { comp.emit_signal(x); } },
|
get() { return function () { comp.emit_signal(x, [...arguments]); } },
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -104,19 +104,20 @@ fn create<'cx>(
|
||||||
.set_signal_handler(
|
.set_signal_handler(
|
||||||
component.borrow(),
|
component.borrow(),
|
||||||
prop_name.as_str(),
|
prop_name.as_str(),
|
||||||
Box::new(move |()| {
|
Box::new(move |args| {
|
||||||
GLOBAL_CONTEXT.with(|cx_fn| {
|
let args = args.iter().cloned().collect::<Vec<_>>();
|
||||||
|
GLOBAL_CONTEXT.with(move |cx_fn| {
|
||||||
cx_fn(&move |cx, presistent_context| {
|
cx_fn(&move |cx, presistent_context| {
|
||||||
|
let args = args
|
||||||
|
.iter()
|
||||||
|
.map(|a| to_js_value(a.clone(), cx).unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
presistent_context
|
presistent_context
|
||||||
.get(cx, fun_idx)
|
.get(cx, fun_idx)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.downcast::<JsFunction>()
|
.downcast::<JsFunction>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.call::<_, _, JsValue, _>(
|
.call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
|
||||||
cx,
|
|
||||||
JsUndefined::new(),
|
|
||||||
std::iter::empty(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -302,13 +303,33 @@ declare_types! {
|
||||||
}
|
}
|
||||||
method emit_signal(mut cx) {
|
method emit_signal(mut cx) {
|
||||||
let signal_name = cx.argument::<JsString>(0)?.value();
|
let signal_name = cx.argument::<JsString>(0)?.value();
|
||||||
|
let arguments = cx.argument::<JsArray>(1)?.to_vec(&mut cx)?;
|
||||||
let this = cx.this();
|
let this = cx.this();
|
||||||
let lock = cx.lock();
|
let lock = cx.lock();
|
||||||
let x = this.borrow(&lock).0.clone();
|
let x = this.borrow(&lock).0.clone();
|
||||||
let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
let component = x.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||||
|
let ty = component.description().properties().get(&signal_name)
|
||||||
|
.ok_or(())
|
||||||
|
.or_else(|()| {
|
||||||
|
cx.throw_error(format!("Signal {} not found in the component", signal_name))
|
||||||
|
})?
|
||||||
|
.clone();
|
||||||
|
let args = if let Type::Signal {args} = ty {
|
||||||
|
let count = args.len();
|
||||||
|
let args = arguments.into_iter().zip(args.into_iter()).map(|(a, ty)| to_eval_value(a, ty, &mut cx)).collect::<Result<Vec<_>, _>>()?;
|
||||||
|
if args.len() != count {
|
||||||
|
cx.throw_error(format!("{} expect {} arguments, but {} where provided", signal_name, count, args.len()))?;
|
||||||
|
}
|
||||||
|
args
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cx.throw_error(format!("{} is not a signal", signal_name))?;
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
run_scoped(&mut cx,this.downcast().unwrap(), || {
|
run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||||
component.description()
|
component.description()
|
||||||
.emit_signal(component.borrow(), signal_name.as_str())
|
.emit_signal(component.borrow(), signal_name.as_str(), args.as_slice())
|
||||||
.map_err(|()| "Cannot emit signal".to_string())
|
.map_err(|()| "Cannot emit signal".to_string())
|
||||||
})?;
|
})?;
|
||||||
Ok(JsUndefined::new().as_value(&mut cx))
|
Ok(JsUndefined::new().as_value(&mut cx))
|
||||||
|
|
|
@ -22,14 +22,19 @@ use core::cell::Cell;
|
||||||
///
|
///
|
||||||
/// The Arg represents the argument. It should always be a tuple
|
/// The Arg represents the argument. It should always be a tuple
|
||||||
///
|
///
|
||||||
#[derive(Default)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Signal<Arg> {
|
pub struct Signal<Arg: ?Sized> {
|
||||||
/// FIXME: Box<dyn> is a fat object and we probaly want to put an erased type in there
|
/// FIXME: Box<dyn> is a fat object and we probaly want to put an erased type in there
|
||||||
handler: Cell<Option<Box<dyn Fn(&Arg)>>>,
|
handler: Cell<Option<Box<dyn Fn(&Arg)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Arg> Signal<Arg> {
|
impl<Arg: ?Sized> Default for Signal<Arg> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { handler: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Arg: ?Sized> Signal<Arg> {
|
||||||
/// Emit the signal with the given argument.
|
/// Emit the signal with the given argument.
|
||||||
pub fn emit(&self, a: &Arg) {
|
pub fn emit(&self, a: &Arg) {
|
||||||
if let Some(h) = self.handler.take() {
|
if let Some(h) = self.handler.take() {
|
||||||
|
|
|
@ -185,7 +185,7 @@ pub struct ComponentDescription<'id> {
|
||||||
item_tree: Vec<ItemTreeNode<crate::dynamic_type::Instance<'id>>>,
|
item_tree: Vec<ItemTreeNode<crate::dynamic_type::Instance<'id>>>,
|
||||||
pub(crate) items: HashMap<String, ItemWithinComponent>,
|
pub(crate) items: HashMap<String, ItemWithinComponent>,
|
||||||
pub(crate) custom_properties: HashMap<String, PropertiesWithinComponent>,
|
pub(crate) custom_properties: HashMap<String, PropertiesWithinComponent>,
|
||||||
pub(crate) custom_signals: HashMap<String, FieldOffset<Instance<'id>, Signal<()>>>,
|
pub(crate) custom_signals: HashMap<String, FieldOffset<Instance<'id>, Signal<[eval::Value]>>>,
|
||||||
repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
|
repeater: Vec<ErasedRepeaterWithinComponent<'id>>,
|
||||||
/// Map the Element::id of the repeater to the index in the `repeater` vec
|
/// Map the Element::id of the repeater to the index in the `repeater` vec
|
||||||
pub repeater_names: HashMap<String, usize>,
|
pub repeater_names: HashMap<String, usize>,
|
||||||
|
@ -471,7 +471,8 @@ fn generate_component<'id>(
|
||||||
Type::Resource => property_info::<Resource>(),
|
Type::Resource => property_info::<Resource>(),
|
||||||
Type::Bool => property_info::<bool>(),
|
Type::Bool => property_info::<bool>(),
|
||||||
Type::Signal { .. } => {
|
Type::Signal { .. } => {
|
||||||
custom_signals.insert(name.clone(), builder.add_field_type::<Signal<()>>());
|
custom_signals
|
||||||
|
.insert(name.clone(), builder.add_field_type::<Signal<[eval::Value]>>());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Type::Object(_) => property_info::<eval::Value>(),
|
Type::Object(_) => property_info::<eval::Value>(),
|
||||||
|
@ -611,18 +612,6 @@ pub fn instantiate<'id>(
|
||||||
for (prop, expr) in &elem.bindings {
|
for (prop, expr) in &elem.bindings {
|
||||||
let ty = elem.lookup_property(prop.as_str());
|
let ty = elem.lookup_property(prop.as_str());
|
||||||
if let Type::Signal { .. } = ty {
|
if let Type::Signal { .. } = ty {
|
||||||
let signal = item_within_component
|
|
||||||
.rtti
|
|
||||||
.signals
|
|
||||||
.get(prop.as_str())
|
|
||||||
.map(|o| &*(item.as_ptr().add(*o) as *const Signal<()>))
|
|
||||||
.or_else(|| {
|
|
||||||
component_type
|
|
||||||
.custom_signals
|
|
||||||
.get(prop.as_str())
|
|
||||||
.map(|o| o.apply(instance_ref.as_ref()))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| panic!("unkown signal {}", prop));
|
|
||||||
let expr = expr.clone();
|
let expr = expr.clone();
|
||||||
let component_type = component_type.clone();
|
let component_type = component_type.clone();
|
||||||
let instance = component_box.instance.as_ptr();
|
let instance = component_box.instance.as_ptr();
|
||||||
|
@ -630,14 +619,36 @@ pub fn instantiate<'id>(
|
||||||
NonNull::from(&component_type.ct).cast(),
|
NonNull::from(&component_type.ct).cast(),
|
||||||
instance.cast(),
|
instance.cast(),
|
||||||
));
|
));
|
||||||
signal.set_handler(move |_| {
|
if let Some(signal_offset) =
|
||||||
generativity::make_guard!(guard);
|
item_within_component.rtti.signals.get(prop.as_str())
|
||||||
eval::eval_expression(
|
{
|
||||||
&expr,
|
let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>);
|
||||||
InstanceRef::from_pin_ref(c, guard),
|
signal.set_handler(move |_: &()| {
|
||||||
&mut Default::default(),
|
generativity::make_guard!(guard);
|
||||||
);
|
eval::eval_expression(
|
||||||
})
|
&expr,
|
||||||
|
InstanceRef::from_pin_ref(c, guard),
|
||||||
|
&mut Default::default(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
} else if let Some(signal_offset) =
|
||||||
|
component_type.custom_signals.get(prop.as_str())
|
||||||
|
{
|
||||||
|
let signal = signal_offset.apply(instance_ref.as_ref());
|
||||||
|
signal.set_handler(move |args| {
|
||||||
|
generativity::make_guard!(guard);
|
||||||
|
let mut local_context = eval::EvalLocalContext::from_function_arguments(
|
||||||
|
args.iter().cloned().collect(),
|
||||||
|
);
|
||||||
|
eval::eval_expression(
|
||||||
|
&expr,
|
||||||
|
InstanceRef::from_pin_ref(c, guard),
|
||||||
|
&mut local_context,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
panic!("unkown signal {}", prop)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(prop_rtti) =
|
if let Some(prop_rtti) =
|
||||||
item_within_component.rtti.properties.get(prop.as_str())
|
item_within_component.rtti.properties.get(prop.as_str())
|
||||||
|
|
|
@ -18,7 +18,7 @@ use sixtyfps_compilerlib::{object_tree::ElementRc, typeregister::Type};
|
||||||
use sixtyfps_corelib as corelib;
|
use sixtyfps_corelib as corelib;
|
||||||
use sixtyfps_corelib::{
|
use sixtyfps_corelib::{
|
||||||
graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource,
|
graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource,
|
||||||
SharedArray, SharedString,
|
SharedArray, SharedString, Signal,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
@ -164,7 +164,14 @@ declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVertic
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EvalLocalContext {
|
pub struct EvalLocalContext {
|
||||||
local_variables: HashMap<String, Value>,
|
local_variables: HashMap<String, Value>,
|
||||||
function_parameters: Vec<Value>,
|
function_arguments: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EvalLocalContext {
|
||||||
|
/// Create a context for a function and passing the arguments
|
||||||
|
pub fn from_function_arguments(function_arguments: Vec<Value>) -> Self {
|
||||||
|
Self { function_arguments, ..Default::default() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate an expression and return a Value as the result of this expression
|
/// Evaluate an expression and return a Value as the result of this expression
|
||||||
|
@ -197,7 +204,7 @@ pub fn eval_expression(
|
||||||
"model_data",
|
"model_data",
|
||||||
),
|
),
|
||||||
Expression::FunctionParameterReference { index, .. } => {
|
Expression::FunctionParameterReference { index, .. } => {
|
||||||
local_context.function_parameters[*index].clone()
|
local_context.function_arguments[*index].clone()
|
||||||
}
|
}
|
||||||
Expression::ObjectAccess { base, name } => {
|
Expression::ObjectAccess { base, name } => {
|
||||||
if let Value::Object(mut o) = eval_expression(base, component, local_context) {
|
if let Value::Object(mut o) = eval_expression(base, component, local_context) {
|
||||||
|
@ -224,7 +231,8 @@ pub fn eval_expression(
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
Expression::FunctionCall { function, .. } => {
|
Expression::FunctionCall { function, arguments } => {
|
||||||
|
let a = arguments.iter().map(|e| eval_expression(e, component, local_context));
|
||||||
if let Expression::SignalReference(NamedReference { element, name }) = &**function {
|
if let Expression::SignalReference(NamedReference { element, name }) = &**function {
|
||||||
let element = element.upgrade().unwrap();
|
let element = element.upgrade().unwrap();
|
||||||
generativity::make_guard!(guard);
|
generativity::make_guard!(guard);
|
||||||
|
@ -234,19 +242,19 @@ pub fn eval_expression(
|
||||||
|
|
||||||
let item_info = &component_type.items[element.borrow().id.as_str()];
|
let item_info = &component_type.items[element.borrow().id.as_str()];
|
||||||
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
let item = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||||
let signal = item_info
|
|
||||||
.rtti
|
if let Some(signal_offset) = item_info.rtti.signals.get(name.as_str()) {
|
||||||
.signals
|
let signal =
|
||||||
.get(name.as_str())
|
unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) };
|
||||||
.map(|o| unsafe { &*(item.as_ptr().add(*o) as *const corelib::Signal<()>) })
|
signal.emit(&());
|
||||||
.or_else(|| {
|
} else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str())
|
||||||
component_type
|
{
|
||||||
.custom_signals
|
let signal = signal_offset.apply(&*enclosing_component.instance);
|
||||||
.get(name.as_str())
|
signal.emit(a.collect::<Vec<_>>().as_slice())
|
||||||
.map(|o| o.apply(&*enclosing_component.instance))
|
} else {
|
||||||
})
|
panic!("unkown signal {}", name)
|
||||||
.unwrap_or_else(|| panic!("unkown signal {}", name));
|
}
|
||||||
signal.emit(&());
|
|
||||||
Value::Void
|
Value::Void
|
||||||
} else if let Expression::BuiltinFunctionReference(funcref) = &**function {
|
} else if let Expression::BuiltinFunctionReference(funcref) = &**function {
|
||||||
match funcref {
|
match funcref {
|
||||||
|
@ -254,7 +262,7 @@ pub fn eval_expression(
|
||||||
Value::Number(window_ref(component).unwrap().scale_factor() as _)
|
Value::Number(window_ref(component).unwrap().scale_factor() as _)
|
||||||
}
|
}
|
||||||
BuiltinFunction::Debug => {
|
BuiltinFunction::Debug => {
|
||||||
println!("FIXME: the debug statement in the interpreter should print its argument");
|
println!("{:?}", a);
|
||||||
Value::Void
|
Value::Void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
|
||||||
&self,
|
&self,
|
||||||
component: Pin<ComponentRef>,
|
component: Pin<ComponentRef>,
|
||||||
name: &str,
|
name: &str,
|
||||||
handler: Box<dyn Fn(&())>,
|
handler: Box<dyn Fn(&[Value])>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
||||||
return Err(());
|
return Err(());
|
||||||
|
@ -141,13 +141,18 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
|
||||||
///
|
///
|
||||||
/// Returns an error if the component is not an instance corresponding to this ComponentDescription,
|
/// Returns an error if the component is not an instance corresponding to this ComponentDescription,
|
||||||
/// or if the signal with this name does not exist in this component
|
/// or if the signal with this name does not exist in this component
|
||||||
pub fn emit_signal(&self, component: ComponentRefPin, name: &str) -> Result<(), ()> {
|
pub fn emit_signal(
|
||||||
|
&self,
|
||||||
|
component: ComponentRefPin,
|
||||||
|
name: &str,
|
||||||
|
args: &[Value],
|
||||||
|
) -> Result<(), ()> {
|
||||||
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
let x = self.custom_signals.get(name).ok_or(())?;
|
let x = self.custom_signals.get(name).ok_or(())?;
|
||||||
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
|
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
|
||||||
sig.emit(&());
|
sig.emit(args);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,5 +58,37 @@ assert_eq!(instance.get_signal_emission_count(), 88);
|
||||||
assert_eq!(*signal_3_emited.borrow(), (55, "hello".into()));
|
assert_eq!(*signal_3_emited.borrow(), (55, "hello".into()));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
var signal_3_emited = 0;
|
||||||
|
var signal_3_string_value;
|
||||||
|
var signal_3_int_value;
|
||||||
|
var instance = new sixtyfps.TestCase({
|
||||||
|
test_signal3: function(a, b) {
|
||||||
|
signal_3_emited++;
|
||||||
|
signal_3_string_value = b;
|
||||||
|
signal_3_int_value = a;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instance.signal_emission_count = 0;
|
||||||
|
assert.equal(instance.signal_emission_count, 0);
|
||||||
|
instance.test_signal(42);
|
||||||
|
assert.equal(instance.signal_emission_count, 1);
|
||||||
|
instance.test_signal2("hello");
|
||||||
|
assert.equal(instance.signal_emission_count, 88);
|
||||||
|
assert.equal(signal_3_emited, 1);
|
||||||
|
assert.equal(signal_3_string_value, "hello");
|
||||||
|
assert.equal(signal_3_int_value, 55);
|
||||||
|
instance.signal_emission_count = 0;
|
||||||
|
// Calling a signal with a wrong number of arg
|
||||||
|
try {
|
||||||
|
instance.test_signal();
|
||||||
|
assert(false);
|
||||||
|
} catch(e) {
|
||||||
|
assert.equal(e.toString(), "Error: test_signal expect 1 arguments, but 0 where provided");
|
||||||
|
}
|
||||||
|
assert.equal(instance.signal_emission_count, 0);
|
||||||
|
```
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue