mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 13:51:13 +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 => {
|
||||
Object.defineProperty(ret, x, {
|
||||
get() { return function () { comp.emit_signal(x); } },
|
||||
get() { return function () { comp.emit_signal(x, [...arguments]); } },
|
||||
enumerable: true,
|
||||
})
|
||||
});
|
||||
|
|
|
@ -104,19 +104,20 @@ fn create<'cx>(
|
|||
.set_signal_handler(
|
||||
component.borrow(),
|
||||
prop_name.as_str(),
|
||||
Box::new(move |()| {
|
||||
GLOBAL_CONTEXT.with(|cx_fn| {
|
||||
Box::new(move |args| {
|
||||
let args = args.iter().cloned().collect::<Vec<_>>();
|
||||
GLOBAL_CONTEXT.with(move |cx_fn| {
|
||||
cx_fn(&move |cx, presistent_context| {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|a| to_js_value(a.clone(), cx).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
presistent_context
|
||||
.get(cx, fun_idx)
|
||||
.unwrap()
|
||||
.downcast::<JsFunction>()
|
||||
.unwrap()
|
||||
.call::<_, _, JsValue, _>(
|
||||
cx,
|
||||
JsUndefined::new(),
|
||||
std::iter::empty(),
|
||||
)
|
||||
.call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
|
||||
.unwrap();
|
||||
})
|
||||
})
|
||||
|
@ -302,13 +303,33 @@ declare_types! {
|
|||
}
|
||||
method emit_signal(mut cx) {
|
||||
let signal_name = cx.argument::<JsString>(0)?.value();
|
||||
let arguments = cx.argument::<JsArray>(1)?.to_vec(&mut cx)?;
|
||||
let this = cx.this();
|
||||
let lock = cx.lock();
|
||||
let x = this.borrow(&lock).0.clone();
|
||||
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(), || {
|
||||
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())
|
||||
})?;
|
||||
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
|
||||
///
|
||||
#[derive(Default)]
|
||||
#[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
|
||||
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.
|
||||
pub fn emit(&self, a: &Arg) {
|
||||
if let Some(h) = self.handler.take() {
|
||||
|
|
|
@ -185,7 +185,7 @@ pub struct ComponentDescription<'id> {
|
|||
item_tree: Vec<ItemTreeNode<crate::dynamic_type::Instance<'id>>>,
|
||||
pub(crate) items: HashMap<String, ItemWithinComponent>,
|
||||
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>>,
|
||||
/// Map the Element::id of the repeater to the index in the `repeater` vec
|
||||
pub repeater_names: HashMap<String, usize>,
|
||||
|
@ -471,7 +471,8 @@ fn generate_component<'id>(
|
|||
Type::Resource => property_info::<Resource>(),
|
||||
Type::Bool => property_info::<bool>(),
|
||||
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;
|
||||
}
|
||||
Type::Object(_) => property_info::<eval::Value>(),
|
||||
|
@ -611,18 +612,6 @@ pub fn instantiate<'id>(
|
|||
for (prop, expr) in &elem.bindings {
|
||||
let ty = elem.lookup_property(prop.as_str());
|
||||
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 component_type = component_type.clone();
|
||||
let instance = component_box.instance.as_ptr();
|
||||
|
@ -630,7 +619,11 @@ pub fn instantiate<'id>(
|
|||
NonNull::from(&component_type.ct).cast(),
|
||||
instance.cast(),
|
||||
));
|
||||
signal.set_handler(move |_| {
|
||||
if let Some(signal_offset) =
|
||||
item_within_component.rtti.signals.get(prop.as_str())
|
||||
{
|
||||
let signal = &*(item.as_ptr().add(*signal_offset) as *const Signal<()>);
|
||||
signal.set_handler(move |_: &()| {
|
||||
generativity::make_guard!(guard);
|
||||
eval::eval_expression(
|
||||
&expr,
|
||||
|
@ -638,6 +631,24 @@ pub fn instantiate<'id>(
|
|||
&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 {
|
||||
if let Some(prop_rtti) =
|
||||
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::{
|
||||
graphics::PathElement, items::ItemRef, items::PropertyAnimation, Color, PathData, Resource,
|
||||
SharedArray, SharedString,
|
||||
SharedArray, SharedString, Signal,
|
||||
};
|
||||
use std::{collections::HashMap, rc::Rc};
|
||||
|
||||
|
@ -164,7 +164,14 @@ declare_value_enum_conversion!(corelib::items::TextVerticalAlignment, TextVertic
|
|||
#[derive(Default)]
|
||||
pub struct EvalLocalContext {
|
||||
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
|
||||
|
@ -197,7 +204,7 @@ pub fn eval_expression(
|
|||
"model_data",
|
||||
),
|
||||
Expression::FunctionParameterReference { index, .. } => {
|
||||
local_context.function_parameters[*index].clone()
|
||||
local_context.function_arguments[*index].clone()
|
||||
}
|
||||
Expression::ObjectAccess { base, name } => {
|
||||
if let Value::Object(mut o) = eval_expression(base, component, local_context) {
|
||||
|
@ -224,7 +231,8 @@ pub fn eval_expression(
|
|||
}
|
||||
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 {
|
||||
let element = element.upgrade().unwrap();
|
||||
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 = unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||
let signal = item_info
|
||||
.rtti
|
||||
.signals
|
||||
.get(name.as_str())
|
||||
.map(|o| unsafe { &*(item.as_ptr().add(*o) as *const corelib::Signal<()>) })
|
||||
.or_else(|| {
|
||||
component_type
|
||||
.custom_signals
|
||||
.get(name.as_str())
|
||||
.map(|o| o.apply(&*enclosing_component.instance))
|
||||
})
|
||||
.unwrap_or_else(|| panic!("unkown signal {}", name));
|
||||
|
||||
if let Some(signal_offset) = item_info.rtti.signals.get(name.as_str()) {
|
||||
let signal =
|
||||
unsafe { &*(item.as_ptr().add(*signal_offset) as *const Signal<()>) };
|
||||
signal.emit(&());
|
||||
} else if let Some(signal_offset) = component_type.custom_signals.get(name.as_str())
|
||||
{
|
||||
let signal = signal_offset.apply(&*enclosing_component.instance);
|
||||
signal.emit(a.collect::<Vec<_>>().as_slice())
|
||||
} else {
|
||||
panic!("unkown signal {}", name)
|
||||
}
|
||||
|
||||
Value::Void
|
||||
} else if let Expression::BuiltinFunctionReference(funcref) = &**function {
|
||||
match funcref {
|
||||
|
@ -254,7 +262,7 @@ pub fn eval_expression(
|
|||
Value::Number(window_ref(component).unwrap().scale_factor() as _)
|
||||
}
|
||||
BuiltinFunction::Debug => {
|
||||
println!("FIXME: the debug statement in the interpreter should print its argument");
|
||||
println!("{:?}", a);
|
||||
Value::Void
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ impl<'id> dynamic_component::ComponentDescription<'id> {
|
|||
&self,
|
||||
component: Pin<ComponentRef>,
|
||||
name: &str,
|
||||
handler: Box<dyn Fn(&())>,
|
||||
handler: Box<dyn Fn(&[Value])>,
|
||||
) -> Result<(), ()> {
|
||||
if !core::ptr::eq((&self.ct) as *const _, component.get_vtable() as *const _) {
|
||||
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,
|
||||
/// 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 _) {
|
||||
return Err(());
|
||||
}
|
||||
let x = self.custom_signals.get(name).ok_or(())?;
|
||||
let sig = x.apply(unsafe { &*(component.as_ptr() as *const dynamic_type::Instance) });
|
||||
sig.emit(&());
|
||||
sig.emit(args);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,5 +58,37 @@ assert_eq!(instance.get_signal_emission_count(), 88);
|
|||
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