mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-03 05:12:55 +00:00
Add support for invoking an init callback on component and element construction
This enables imperative code to be run. To be used sparingly :-)
This commit is contained in:
parent
9baf6f2bde
commit
907b58161c
20 changed files with 366 additions and 46 deletions
|
|
@ -14,6 +14,7 @@ All notable changes to this project are documented in this file.
|
||||||
old name continues to work.
|
old name continues to work.
|
||||||
- Disallow overrides or duplicated declarations of callbacks. Previously they were silently overwritten,
|
- Disallow overrides or duplicated declarations of callbacks. Previously they were silently overwritten,
|
||||||
now an error is produced.
|
now an error is produced.
|
||||||
|
- The name "init" is now a reserved name in callbacks and properties.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
|
@ -22,6 +23,8 @@ All notable changes to this project are documented in this file.
|
||||||
- Added `From<char>` for `SharedString` in Rust.
|
- Added `From<char>` for `SharedString` in Rust.
|
||||||
- Added `KeyPressed` and `KeyReleased` variants to `slint::WindowEvent` in Rust, along
|
- Added `KeyPressed` and `KeyReleased` variants to `slint::WindowEvent` in Rust, along
|
||||||
with `slint::Key`, for use by custom platform backends.
|
with `slint::Key`, for use by custom platform backends.
|
||||||
|
- Added suppport for the implicitly declared `init` callback that can be used to run code when
|
||||||
|
an element or component is instantiated.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1312,12 +1312,17 @@ public:
|
||||||
inner->data.resize(count);
|
inner->data.resize(count);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
auto &c = inner->data[i];
|
auto &c = inner->data[i];
|
||||||
|
bool created = false;
|
||||||
if (!c.ptr) {
|
if (!c.ptr) {
|
||||||
c.ptr = C::create(parent);
|
c.ptr = C::create(parent);
|
||||||
|
created = true;
|
||||||
}
|
}
|
||||||
if (c.state == RepeaterInner::State::Dirty) {
|
if (c.state == RepeaterInner::State::Dirty) {
|
||||||
(*c.ptr)->update_data(i, *m->row_data(i));
|
(*c.ptr)->update_data(i, *m->row_data(i));
|
||||||
}
|
}
|
||||||
|
if (created) {
|
||||||
|
(*c.ptr)->init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inner->data.clear();
|
inner->data.clear();
|
||||||
|
|
|
||||||
|
|
@ -1079,6 +1079,26 @@ Example := Window {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Builtin callbacks
|
||||||
|
|
||||||
|
Every component and element implicitly declares an `init` callback. You can assign a code block to it that will be run when the
|
||||||
|
element or component is instantiated. The recommended way is to avoid using this callback, unless you need it. For example in
|
||||||
|
order to notify some native code.
|
||||||
|
|
||||||
|
```slint,no-preview
|
||||||
|
global SystemService := {
|
||||||
|
// This callback can be implemented in native code using the Slint API
|
||||||
|
callback ensure_service_running();
|
||||||
|
}
|
||||||
|
|
||||||
|
MySystemButton := Rectangle {
|
||||||
|
init => {
|
||||||
|
SystemService.ensure_service_running();
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `Math` namespace
|
### `Math` namespace
|
||||||
|
|
||||||
These functions are available both in the global scope and in the `Math` namespace.
|
These functions are available both in the global scope and in the `Math` namespace.
|
||||||
|
|
|
||||||
|
|
@ -1110,9 +1110,17 @@ fn generate_item_tree(
|
||||||
root_access
|
root_access
|
||||||
),
|
),
|
||||||
format!("self->init({}, self->self_weak, 0, 1 {});", root_access, init_parent_parameters),
|
format!("self->init({}, self->self_weak, 0, 1 {});", root_access, init_parent_parameters),
|
||||||
format!("return slint::ComponentHandle<{0}>{{ self_rc }};", target_struct.name),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Repeaters run their user_init() code from Repeater::ensure_updated() after update() initialized model_data/index.
|
||||||
|
// So always call user_init(), unless this component is a repeated.
|
||||||
|
if parent_ctx.map_or(true, |parent_ctx| parent_ctx.repeater_index.is_none()) {
|
||||||
|
create_code.push(format!("self->user_init();"));
|
||||||
|
}
|
||||||
|
|
||||||
|
create_code
|
||||||
|
.push(format!("return slint::ComponentHandle<{0}>{{ self_rc }};", target_struct.name));
|
||||||
|
|
||||||
target_struct.members.push((
|
target_struct.members.push((
|
||||||
Access::Public,
|
Access::Public,
|
||||||
Declaration::Function(Function {
|
Declaration::Function(Function {
|
||||||
|
|
@ -1259,6 +1267,8 @@ fn generate_sub_component(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut user_init = vec!["[[maybe_unused]] auto self = this;".into()];
|
||||||
|
|
||||||
let mut children_visitor_cases = Vec::new();
|
let mut children_visitor_cases = Vec::new();
|
||||||
let mut subtrees_ranges_cases = Vec::new();
|
let mut subtrees_ranges_cases = Vec::new();
|
||||||
let mut subtrees_components_cases = Vec::new();
|
let mut subtrees_components_cases = Vec::new();
|
||||||
|
|
@ -1285,6 +1295,7 @@ fn generate_sub_component(
|
||||||
"this->{}.init(root, self_weak.into_dyn(), {}, {});",
|
"this->{}.init(root, self_weak.into_dyn(), {}, {});",
|
||||||
field_name, global_index, global_children
|
field_name, global_index, global_children
|
||||||
));
|
));
|
||||||
|
user_init.push(format!("this->{}.user_init();", field_name));
|
||||||
|
|
||||||
let sub_component_repeater_count = sub.ty.repeater_count();
|
let sub_component_repeater_count = sub.ty.repeater_count();
|
||||||
if sub_component_repeater_count > 0 {
|
if sub_component_repeater_count > 0 {
|
||||||
|
|
@ -1453,7 +1464,12 @@ fn generate_sub_component(
|
||||||
}
|
}
|
||||||
|
|
||||||
init.extend(properties_init_code);
|
init.extend(properties_init_code);
|
||||||
init.extend(component.init_code.iter().map(|e| compile_expression(&e.borrow(), &ctx)));
|
|
||||||
|
user_init.extend(component.init_code.iter().map(|e| {
|
||||||
|
let mut expr_str = compile_expression(&e.borrow(), &ctx);
|
||||||
|
expr_str.push(';');
|
||||||
|
expr_str
|
||||||
|
}));
|
||||||
|
|
||||||
target_struct.members.push((
|
target_struct.members.push((
|
||||||
field_access,
|
field_access,
|
||||||
|
|
@ -1465,6 +1481,16 @@ fn generate_sub_component(
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
target_struct.members.push((
|
||||||
|
field_access,
|
||||||
|
Declaration::Function(Function {
|
||||||
|
name: "user_init".to_owned(),
|
||||||
|
signature: "() -> void".into(),
|
||||||
|
statements: Some(user_init),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
target_struct.members.push((
|
target_struct.members.push((
|
||||||
field_access,
|
field_access,
|
||||||
Declaration::Function(Function {
|
Declaration::Function(Function {
|
||||||
|
|
@ -1646,6 +1672,16 @@ fn generate_repeated_component(
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
repeater_struct.members.push((
|
||||||
|
Access::Public, // Because Repeater accesses it
|
||||||
|
Declaration::Function(Function {
|
||||||
|
name: "init".into(),
|
||||||
|
signature: "() -> void".into(),
|
||||||
|
statements: Some(vec!["user_init();".into()]),
|
||||||
|
..Function::default()
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
if let Some(listview) = &repeated.listview {
|
if let Some(listview) = &repeated.listview {
|
||||||
let p_y = access_member(&listview.prop_y, &ctx);
|
let p_y = access_member(&listview.prop_y, &ctx);
|
||||||
let p_height = access_member(&listview.prop_height, &ctx);
|
let p_height = access_member(&listview.prop_height, &ctx);
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let inner = #inner_component_id::new();
|
let inner = #inner_component_id::new();
|
||||||
#(inner.globals.#global_names.clone().init(&inner);)*
|
#(inner.globals.#global_names.clone().init(&inner);)*
|
||||||
|
#inner_component_id::user_init(slint::private_unstable_api::re_exports::VRc::map(inner.clone(), |x| x));
|
||||||
Self(inner)
|
Self(inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -761,6 +762,8 @@ fn generate_sub_component(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut user_init_code: Vec<TokenStream> = Vec::new();
|
||||||
|
|
||||||
let mut sub_component_names: Vec<Ident> = vec![];
|
let mut sub_component_names: Vec<Ident> = vec![];
|
||||||
let mut sub_component_types: Vec<Ident> = vec![];
|
let mut sub_component_types: Vec<Ident> = vec![];
|
||||||
|
|
||||||
|
|
@ -791,6 +794,9 @@ fn generate_sub_component(
|
||||||
&#root_ref_tokens,
|
&#root_ref_tokens,
|
||||||
#global_index, #global_children
|
#global_index, #global_children
|
||||||
);));
|
);));
|
||||||
|
user_init_code.push(quote!(#sub_component_id::user_init(
|
||||||
|
VRcMapped::map(self_rc.clone(), |x| #sub_compo_field.apply_pin(x)),
|
||||||
|
);));
|
||||||
|
|
||||||
let sub_component_repeater_count = sub.ty.repeater_count();
|
let sub_component_repeater_count = sub.ty.repeater_count();
|
||||||
if sub_component_repeater_count > 0 {
|
if sub_component_repeater_count > 0 {
|
||||||
|
|
@ -870,7 +876,10 @@ fn generate_sub_component(
|
||||||
quote!(slint::private_unstable_api::re_exports::VWeakMapped::<slint::private_unstable_api::re_exports::ComponentVTable, #parent_component_id>)
|
quote!(slint::private_unstable_api::re_exports::VWeakMapped::<slint::private_unstable_api::re_exports::ComponentVTable, #parent_component_id>)
|
||||||
});
|
});
|
||||||
|
|
||||||
init.extend(component.init_code.iter().map(|e| compile_expression(&e.borrow(), &ctx)));
|
user_init_code.extend(component.init_code.iter().map(|e| {
|
||||||
|
let code = compile_expression(&e.borrow(), &ctx);
|
||||||
|
quote!(#code;)
|
||||||
|
}));
|
||||||
|
|
||||||
let layout_info_h = compile_expression(&component.layout_info_h.borrow(), &ctx);
|
let layout_info_h = compile_expression(&component.layout_info_h.borrow(), &ctx);
|
||||||
let layout_info_v = compile_expression(&component.layout_info_v.borrow(), &ctx);
|
let layout_info_v = compile_expression(&component.layout_info_v.borrow(), &ctx);
|
||||||
|
|
@ -927,6 +936,11 @@ fn generate_sub_component(
|
||||||
#(#init)*
|
#(#init)*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn user_init(self_rc: slint::private_unstable_api::re_exports::VRcMapped<slint::private_unstable_api::re_exports::ComponentVTable, Self>) {
|
||||||
|
let _self = self_rc.as_pin_ref();
|
||||||
|
#(#user_init_code)*
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_dynamic_children(
|
fn visit_dynamic_children(
|
||||||
self: ::core::pin::Pin<&Self>,
|
self: ::core::pin::Pin<&Self>,
|
||||||
dyn_index: usize,
|
dyn_index: usize,
|
||||||
|
|
@ -1152,7 +1166,21 @@ fn generate_item_tree(
|
||||||
} else {
|
} else {
|
||||||
quote!(&self_rc)
|
quote!(&self_rc)
|
||||||
};
|
};
|
||||||
let (create_window_adapter, init_window) = if parent_ctx.is_none() {
|
|
||||||
|
let (create_window_adapter, init_window, maybe_user_init) = if let Some(parent_ctx) = parent_ctx
|
||||||
|
{
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
if parent_ctx.repeater_index.is_some() {
|
||||||
|
None // Repeaters run their user_init() code from RepeatedComponent::init() after update() initialized model_data/index.
|
||||||
|
} else {
|
||||||
|
Some(quote! {
|
||||||
|
Self::user_init(slint::private_unstable_api::re_exports::VRc::map(self_rc.clone(), |x| x));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
(
|
(
|
||||||
Some(
|
Some(
|
||||||
quote!(let window_adapter = slint::private_unstable_api::create_window_adapter();),
|
quote!(let window_adapter = slint::private_unstable_api::create_window_adapter();),
|
||||||
|
|
@ -1161,9 +1189,8 @@ fn generate_item_tree(
|
||||||
_self.window_adapter.set(window_adapter);
|
_self.window_adapter.set(window_adapter);
|
||||||
slint::private_unstable_api::re_exports::WindowInner::from_pub(_self.window_adapter.get().unwrap().window()).set_component(&VRc::into_dyn(self_rc.clone()));
|
slint::private_unstable_api::re_exports::WindowInner::from_pub(_self.window_adapter.get().unwrap().window()).set_component(&VRc::into_dyn(self_rc.clone()));
|
||||||
}),
|
}),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent_item_expression = parent_ctx.and_then(|parent| {
|
let parent_item_expression = parent_ctx.and_then(|parent| {
|
||||||
|
|
@ -1248,6 +1275,7 @@ fn generate_item_tree(
|
||||||
#init_window
|
#init_window
|
||||||
slint::private_unstable_api::re_exports::register_component(_self, Self::item_array(), #root_token.window_adapter.get().unwrap());
|
slint::private_unstable_api::re_exports::register_component(_self, Self::item_array(), #root_token.window_adapter.get().unwrap());
|
||||||
Self::init(slint::private_unstable_api::re_exports::VRc::map(self_rc.clone(), |x| x), #root_token, 0, 1);
|
Self::init(slint::private_unstable_api::re_exports::VRc::map(self_rc.clone(), |x| x), #root_token, 0, 1);
|
||||||
|
#maybe_user_init
|
||||||
self_rc
|
self_rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1441,6 +1469,12 @@ fn generate_repeated_component(
|
||||||
#(#index_prop.set(_index as _);)*
|
#(#index_prop.set(_index as _);)*
|
||||||
#(#set_data_expr)*
|
#(#set_data_expr)*
|
||||||
}
|
}
|
||||||
|
fn init(&self) {
|
||||||
|
let self_rc = self.self_weak.get().unwrap().upgrade().unwrap();
|
||||||
|
#inner_component_id::user_init(
|
||||||
|
VRcMapped::map(self_rc.clone(), |x| x),
|
||||||
|
);
|
||||||
|
}
|
||||||
#extra_fn
|
#extra_fn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -2038,7 +2072,7 @@ fn compile_builtin_function_call(
|
||||||
let window_tokens = access_window_adapter_field(ctx);
|
let window_tokens = access_window_adapter_field(ctx);
|
||||||
let focus_item = access_item_rc(pr, ctx);
|
let focus_item = access_item_rc(pr, ctx);
|
||||||
quote!(
|
quote!(
|
||||||
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item);
|
slint::private_unstable_api::re_exports::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
||||||
|
|
@ -2070,7 +2104,7 @@ fn compile_builtin_function_call(
|
||||||
&VRc::into_dyn(#popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).into()),
|
&VRc::into_dyn(#popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).into()),
|
||||||
Point::new(#x as slint::private_unstable_api::re_exports::Coord, #y as slint::private_unstable_api::re_exports::Coord),
|
Point::new(#x as slint::private_unstable_api::re_exports::Coord, #y as slint::private_unstable_api::re_exports::Coord),
|
||||||
#parent_component
|
#parent_component
|
||||||
);
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||||
|
|
@ -2090,7 +2124,7 @@ fn compile_builtin_function_call(
|
||||||
BuiltinFunction::RegisterCustomFontByPath => {
|
BuiltinFunction::RegisterCustomFontByPath => {
|
||||||
if let [Expression::StringLiteral(path)] = arguments {
|
if let [Expression::StringLiteral(path)] = arguments {
|
||||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||||
quote!(#window_adapter_tokens.renderer().register_font_from_path(&std::path::PathBuf::from(#path));)
|
quote!(#window_adapter_tokens.renderer().register_font_from_path(&std::path::PathBuf::from(#path)).unwrap())
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to RegisterCustomFontByPath {:?}", arguments)
|
panic!("internal error: invalid args to RegisterCustomFontByPath {:?}", arguments)
|
||||||
}
|
}
|
||||||
|
|
@ -2100,7 +2134,7 @@ fn compile_builtin_function_call(
|
||||||
let resource_id: usize = *resource_id as _;
|
let resource_id: usize = *resource_id as _;
|
||||||
let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
||||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||||
quote!(#window_adapter_tokens.renderer().register_font_from_memory(#symbol.into());)
|
quote!(#window_adapter_tokens.renderer().register_font_from_memory(#symbol.into()).unwrap())
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to RegisterCustomFontByMemory {:?}", arguments)
|
panic!("internal error: invalid args to RegisterCustomFontByMemory {:?}", arguments)
|
||||||
}
|
}
|
||||||
|
|
@ -2110,7 +2144,7 @@ fn compile_builtin_function_call(
|
||||||
let resource_id: usize = *resource_id as _;
|
let resource_id: usize = *resource_id as _;
|
||||||
let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
||||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||||
quote!(#window_adapter_tokens.renderer().register_bitmap_font(&#symbol);)
|
quote!(#window_adapter_tokens.renderer().register_bitmap_font(&#symbol))
|
||||||
} else {
|
} else {
|
||||||
panic!("internal error: invalid args to RegisterBitmapFont must be a number")
|
panic!("internal error: invalid args to RegisterBitmapFont must be a number")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,7 +368,7 @@ fn lower_sub_component(
|
||||||
});
|
});
|
||||||
|
|
||||||
sub_component.init_code = component
|
sub_component.init_code = component
|
||||||
.setup_code
|
.init_code
|
||||||
.borrow()
|
.borrow()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| super::lower_expression::lower_expression(e, &ctx).into())
|
.map(|e| super::lower_expression::lower_expression(e, &ctx).into())
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,13 @@ pub struct Component {
|
||||||
/// the element pointer to by this field.
|
/// the element pointer to by this field.
|
||||||
pub child_insertion_point: RefCell<Option<ChildrenInsertionPoint>>,
|
pub child_insertion_point: RefCell<Option<ChildrenInsertionPoint>>,
|
||||||
|
|
||||||
/// Code to be inserted into the constructor
|
/// Code inserted from inlined components, ordered by offset of the place where it was inlined from. This way
|
||||||
pub setup_code: RefCell<Vec<Expression>>,
|
/// we can preserve the order across multiple inlining passes.
|
||||||
|
pub inlined_init_code: RefCell<BTreeMap<usize, Expression>>,
|
||||||
|
|
||||||
|
/// Code to be inserted into the constructor, such as font registration, focus setting or
|
||||||
|
/// init callback code collected from elements.
|
||||||
|
pub init_code: RefCell<Vec<Expression>>,
|
||||||
|
|
||||||
/// The list of used extra types used (recursively) by this root component.
|
/// The list of used extra types used (recursively) by this root component.
|
||||||
/// (This only make sense on the root component)
|
/// (This only make sense on the root component)
|
||||||
|
|
@ -709,6 +714,17 @@ impl Element {
|
||||||
node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
|
node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
|
||||||
node.States().for_each(|n| error_on(&n, "states"));
|
node.States().for_each(|n| error_on(&n, "states"));
|
||||||
node.Transitions().for_each(|n| error_on(&n, "transitions"));
|
node.Transitions().for_each(|n| error_on(&n, "transitions"));
|
||||||
|
node.CallbackDeclaration().for_each(|cb| {
|
||||||
|
if parser::identifier_text(&cb.DeclaredIdentifier()).map_or(false, |s| s == "init")
|
||||||
|
{
|
||||||
|
error_on(&cb, "an 'init' callback")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
node.CallbackConnection().for_each(|cb| {
|
||||||
|
if parser::identifier_text(&cb).map_or(false, |s| s == "init") {
|
||||||
|
error_on(&cb, "an 'init' callback")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ElementType::Global
|
ElementType::Global
|
||||||
} else if parent_type != ElementType::Error {
|
} else if parent_type != ElementType::Error {
|
||||||
|
|
@ -1783,33 +1799,39 @@ pub fn visit_element_expressions(
|
||||||
elem.borrow_mut().transitions = transitions;
|
elem.borrow_mut().transitions = transitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn visit_named_references_in_expression(
|
||||||
|
expr: &mut Expression,
|
||||||
|
vis: &mut impl FnMut(&mut NamedReference),
|
||||||
|
) {
|
||||||
|
expr.visit_mut(|sub| visit_named_references_in_expression(sub, vis));
|
||||||
|
match expr {
|
||||||
|
Expression::PropertyReference(r) | Expression::CallbackReference(r) => vis(r),
|
||||||
|
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
|
||||||
|
Expression::SolveLayout(l, _) => l.visit_named_references(vis),
|
||||||
|
Expression::ComputeLayoutInfo(l, _) => l.visit_named_references(vis),
|
||||||
|
// This is not really a named reference, but the result is the same, it need to be updated
|
||||||
|
// FIXME: this should probably be lowered into a PropertyReference
|
||||||
|
Expression::RepeaterModelReference { element }
|
||||||
|
| Expression::RepeaterIndexReference { element } => {
|
||||||
|
// FIXME: this is questionable
|
||||||
|
let mut nc = NamedReference::new(&element.upgrade().unwrap(), "$model");
|
||||||
|
vis(&mut nc);
|
||||||
|
debug_assert!(nc.element().borrow().repeated.is_some());
|
||||||
|
*element = Rc::downgrade(&nc.element());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Visit all the named reference in an element
|
/// Visit all the named reference in an element
|
||||||
/// But does not recurse in sub-elements. (unlike [`visit_all_named_references`] which recurse)
|
/// But does not recurse in sub-elements. (unlike [`visit_all_named_references`] which recurse)
|
||||||
pub fn visit_all_named_references_in_element(
|
pub fn visit_all_named_references_in_element(
|
||||||
elem: &ElementRc,
|
elem: &ElementRc,
|
||||||
mut vis: impl FnMut(&mut NamedReference),
|
mut vis: impl FnMut(&mut NamedReference),
|
||||||
) {
|
) {
|
||||||
fn recurse_expression(expr: &mut Expression, vis: &mut impl FnMut(&mut NamedReference)) {
|
visit_element_expressions(elem, |expr, _, _| {
|
||||||
expr.visit_mut(|sub| recurse_expression(sub, vis));
|
visit_named_references_in_expression(expr, &mut vis)
|
||||||
match expr {
|
});
|
||||||
Expression::PropertyReference(r) | Expression::CallbackReference(r) => vis(r),
|
|
||||||
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
|
|
||||||
Expression::SolveLayout(l, _) => l.visit_named_references(vis),
|
|
||||||
Expression::ComputeLayoutInfo(l, _) => l.visit_named_references(vis),
|
|
||||||
// This is not really a named reference, but the result is the same, it need to be updated
|
|
||||||
// FIXME: this should probably be lowered into a PropertyReference
|
|
||||||
Expression::RepeaterModelReference { element }
|
|
||||||
| Expression::RepeaterIndexReference { element } => {
|
|
||||||
// FIXME: this is questionable
|
|
||||||
let mut nc = NamedReference::new(&element.upgrade().unwrap(), "$model");
|
|
||||||
vis(&mut nc);
|
|
||||||
debug_assert!(nc.element().borrow().repeated.is_some());
|
|
||||||
*element = Rc::downgrade(&nc.element());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visit_element_expressions(elem, |expr, _, _| recurse_expression(expr, &mut vis));
|
|
||||||
let mut states = std::mem::take(&mut elem.borrow_mut().states);
|
let mut states = std::mem::take(&mut elem.borrow_mut().states);
|
||||||
for s in &mut states {
|
for s in &mut states {
|
||||||
for (r, _, _) in &mut s.property_changes {
|
for (r, _, _) in &mut s.property_changes {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod check_rotation;
|
||||||
mod clip;
|
mod clip;
|
||||||
mod collect_custom_fonts;
|
mod collect_custom_fonts;
|
||||||
mod collect_globals;
|
mod collect_globals;
|
||||||
|
mod collect_init_code;
|
||||||
mod collect_structs;
|
mod collect_structs;
|
||||||
mod collect_subcomponents;
|
mod collect_subcomponents;
|
||||||
mod compile_paths;
|
mod compile_paths;
|
||||||
|
|
@ -173,6 +174,7 @@ pub async fn run_passes(
|
||||||
if compiler_config.accessibility {
|
if compiler_config.accessibility {
|
||||||
lower_accessibility::lower_accessibility_properties(component, diag);
|
lower_accessibility::lower_accessibility_properties(component, diag);
|
||||||
}
|
}
|
||||||
|
collect_init_code::collect_init_code(component);
|
||||||
materialize_fake_properties::materialize_fake_properties(component);
|
materialize_fake_properties::materialize_fake_properties(component);
|
||||||
}
|
}
|
||||||
collect_globals::collect_globals(doc, diag);
|
collect_globals::collect_globals(doc, diag);
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ pub fn collect_custom_fonts<'a>(
|
||||||
Box::new(|font_path| Expression::StringLiteral(font_path.clone()))
|
Box::new(|font_path| Expression::StringLiteral(font_path.clone()))
|
||||||
};
|
};
|
||||||
|
|
||||||
root_component.setup_code.borrow_mut().extend(all_fonts.into_iter().map(|font_path| {
|
root_component.init_code.borrow_mut().extend(all_fonts.into_iter().map(|font_path| {
|
||||||
Expression::FunctionCall {
|
Expression::FunctionCall {
|
||||||
function: Box::new(registration_function.clone()),
|
function: Box::new(registration_function.clone()),
|
||||||
arguments: vec![prepare_font_registration_argument(font_path)],
|
arguments: vec![prepare_font_registration_argument(font_path)],
|
||||||
|
|
|
||||||
25
internal/compiler/passes/collect_init_code.rs
Normal file
25
internal/compiler/passes/collect_init_code.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
//! Passe that collects the code from init callbacks from elements and moves it into the component's init_code.
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::langtype::ElementType;
|
||||||
|
use crate::object_tree::{recurse_elem, Component};
|
||||||
|
|
||||||
|
pub fn collect_init_code(component: &Rc<Component>) {
|
||||||
|
recurse_elem(&component.root_element, &(), &mut |elem, _| {
|
||||||
|
if elem.borrow().repeated.is_some() {
|
||||||
|
if let ElementType::Component(base) = &elem.borrow().base_type {
|
||||||
|
if base.parent_element.upgrade().is_some() {
|
||||||
|
collect_init_code(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(init_callback) = elem.borrow_mut().bindings.remove("init") {
|
||||||
|
component.init_code.borrow_mut().push(init_callback.into_inner().expression);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -170,7 +170,7 @@ pub fn embed_glyphs<'a>(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
component.setup_code.borrow_mut().push(Expression::FunctionCall {
|
component.init_code.borrow_mut().push(Expression::FunctionCall {
|
||||||
function: Box::new(Expression::BuiltinFunctionReference(
|
function: Box::new(Expression::BuiltinFunctionReference(
|
||||||
BuiltinFunction::RegisterBitmapFont,
|
BuiltinFunction::RegisterBitmapFont,
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ pub fn determine_initial_focus_item(component: &Rc<Component>, diag: &mut BuildD
|
||||||
source_location: None,
|
source_location: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
component.setup_code.borrow_mut().push(setup_code);
|
component.init_code.borrow_mut().push(setup_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,13 @@ pub fn inline(doc: &Document, inline_selection: InlineSelection) {
|
||||||
.for_each(|p| inline_components_recursively(&p.component, inline_selection))
|
.for_each(|p| inline_components_recursively(&p.component, inline_selection))
|
||||||
}
|
}
|
||||||
inline_components_recursively(&doc.root_component, inline_selection);
|
inline_components_recursively(&doc.root_component, inline_selection);
|
||||||
|
|
||||||
|
if matches!(inline_selection, InlineSelection::InlineAllComponents) {
|
||||||
|
doc.root_component
|
||||||
|
.init_code
|
||||||
|
.borrow_mut()
|
||||||
|
.splice(0..0, doc.root_component.inlined_init_code.borrow().values().cloned());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_tuple<U: Clone, V: Clone>((u, v): (&U, &V)) -> (U, V) {
|
fn clone_tuple<U: Clone, V: Clone>((u, v): (&U, &V)) -> (U, V) {
|
||||||
|
|
@ -165,6 +172,27 @@ fn inline_element(
|
||||||
|
|
||||||
core::mem::drop(elem_mut);
|
core::mem::drop(elem_mut);
|
||||||
|
|
||||||
|
let inlined_init_code = inlined_component
|
||||||
|
.inlined_init_code
|
||||||
|
.borrow()
|
||||||
|
.values()
|
||||||
|
.cloned()
|
||||||
|
.chain(inlined_component.init_code.borrow().iter().map(|setup_code_expr| {
|
||||||
|
// Fix up any property references from within already collected init code.
|
||||||
|
let mut new_setup_code = setup_code_expr.clone();
|
||||||
|
visit_named_references_in_expression(&mut new_setup_code, &mut |nr| {
|
||||||
|
fixup_reference(nr, &mapping)
|
||||||
|
});
|
||||||
|
fixup_element_references(&mut new_setup_code, &mapping);
|
||||||
|
new_setup_code
|
||||||
|
}))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
root_component
|
||||||
|
.inlined_init_code
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(elem.borrow().span().offset, Expression::CodeBlock(inlined_init_code));
|
||||||
|
|
||||||
// Now fixup all binding and reference
|
// Now fixup all binding and reference
|
||||||
for e in mapping.values() {
|
for e in mapping.values() {
|
||||||
visit_all_named_references_in_element(e, |nr| fixup_reference(nr, &mapping));
|
visit_all_named_references_in_element(e, |nr| fixup_reference(nr, &mapping));
|
||||||
|
|
@ -266,7 +294,8 @@ fn duplicate_sub_component(
|
||||||
embedded_file_resources: component_to_duplicate.embedded_file_resources.clone(),
|
embedded_file_resources: component_to_duplicate.embedded_file_resources.clone(),
|
||||||
root_constraints: component_to_duplicate.root_constraints.clone(),
|
root_constraints: component_to_duplicate.root_constraints.clone(),
|
||||||
child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
|
child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
|
||||||
setup_code: component_to_duplicate.setup_code.clone(),
|
inlined_init_code: component_to_duplicate.inlined_init_code.clone(),
|
||||||
|
init_code: component_to_duplicate.init_code.clone(),
|
||||||
used_types: Default::default(),
|
used_types: Default::default(),
|
||||||
popup_windows: Default::default(),
|
popup_windows: Default::default(),
|
||||||
exported_global_names: component_to_duplicate.exported_global_names.clone(),
|
exported_global_names: component_to_duplicate.exported_global_names.clone(),
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ fn do_move_declarations(component: &Rc<Component>) {
|
||||||
fixup_reference(&mut p.y);
|
fixup_reference(&mut p.y);
|
||||||
visit_all_named_references(&p.component, &mut fixup_reference)
|
visit_all_named_references(&p.component, &mut fixup_reference)
|
||||||
});
|
});
|
||||||
|
component.init_code.borrow_mut().iter_mut().for_each(|expr| {
|
||||||
|
visit_named_references_in_expression(expr, &mut fixup_reference);
|
||||||
|
});
|
||||||
for pd in decl.property_declarations.values_mut() {
|
for pd in decl.property_declarations.values_mut() {
|
||||||
pd.is_alias.as_mut().map(fixup_reference);
|
pd.is_alias.as_mut().map(fixup_reference);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,4 +49,7 @@ SubElements := Rectangle {
|
||||||
property <bool> pressed;
|
property <bool> pressed;
|
||||||
callback pressed;
|
callback pressed;
|
||||||
// ^error{Cannot declare callback 'pressed' when a property with the same name exists}
|
// ^error{Cannot declare callback 'pressed' when a property with the same name exists}
|
||||||
|
|
||||||
|
callback init;
|
||||||
|
// ^error{Cannot override callback 'init'}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
internal/compiler/tests/syntax/callbacks/init.slint
Normal file
14
internal/compiler/tests/syntax/callbacks/init.slint
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
global Singleton := {
|
||||||
|
callback init;
|
||||||
|
// ^error{A global component cannot have an 'init' callback}
|
||||||
|
init => { debug("nope"); }
|
||||||
|
// ^error{A global component cannot have an 'init' callback}
|
||||||
|
}
|
||||||
|
|
||||||
|
Test := Rectangle {
|
||||||
|
callback init;
|
||||||
|
// ^error{Cannot override callback 'init'}
|
||||||
|
}
|
||||||
|
|
@ -129,6 +129,7 @@ pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type)> {
|
||||||
Type::Enumeration(BUILTIN_ENUMS.with(|e| e.AccessibleRole.clone())),
|
Type::Enumeration(BUILTIN_ENUMS.with(|e| e.AccessibleRole.clone())),
|
||||||
),
|
),
|
||||||
]))
|
]))
|
||||||
|
.chain(std::iter::once(("init", Type::Callback { return_type: None, args: vec![] })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// lookup reserved property injected in every item
|
/// lookup reserved property injected in every item
|
||||||
|
|
|
||||||
|
|
@ -604,6 +604,10 @@ pub trait RepeatedComponent:
|
||||||
/// Update this component at the given index and the given data
|
/// Update this component at the given index and the given data
|
||||||
fn update(&self, index: usize, data: Self::Data);
|
fn update(&self, index: usize, data: Self::Data);
|
||||||
|
|
||||||
|
/// Called once after the component has been instantiated and update()
|
||||||
|
/// was called once.
|
||||||
|
fn init(&self) {}
|
||||||
|
|
||||||
/// Layout this item in the listview
|
/// Layout this item in the listview
|
||||||
///
|
///
|
||||||
/// offset_y is the `y` position where this item should be placed.
|
/// offset_y is the `y` position where this item should be placed.
|
||||||
|
|
@ -799,19 +803,25 @@ impl<C: RepeatedComponent + 'static> Repeater<C> {
|
||||||
let mut inner = self.0.inner.borrow_mut();
|
let mut inner = self.0.inner.borrow_mut();
|
||||||
inner.components.resize_with(count, || (RepeatedComponentState::Dirty, None));
|
inner.components.resize_with(count, || (RepeatedComponentState::Dirty, None));
|
||||||
let offset = inner.offset;
|
let offset = inner.offset;
|
||||||
let mut created = false;
|
let mut any_items_created = false;
|
||||||
for (i, c) in inner.components.iter_mut().enumerate() {
|
for (i, c) in inner.components.iter_mut().enumerate() {
|
||||||
if c.0 == RepeatedComponentState::Dirty {
|
if c.0 == RepeatedComponentState::Dirty {
|
||||||
if c.1.is_none() {
|
let created = if c.1.is_none() {
|
||||||
created = true;
|
any_items_created = true;
|
||||||
c.1 = Some(init());
|
c.1 = Some(init());
|
||||||
}
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset).unwrap());
|
c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset).unwrap());
|
||||||
|
if created {
|
||||||
|
c.1.as_ref().unwrap().init();
|
||||||
|
}
|
||||||
c.0 = RepeatedComponentState::Clean;
|
c.0 = RepeatedComponentState::Clean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.data().is_dirty.set(false);
|
self.data().is_dirty.set(false);
|
||||||
created
|
any_items_created
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `Self::ensuer_updated` but for a ListView
|
/// Same as `Self::ensuer_updated` but for a ListView
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,10 @@ impl RepeatedComponent for ErasedComponentBox {
|
||||||
s.component_type.set_property(s.borrow(), "model_data", data).unwrap();
|
s.component_type.set_property(s.borrow(), "model_data", data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init(&self) {
|
||||||
|
self.run_setup_code();
|
||||||
|
}
|
||||||
|
|
||||||
fn listview_layout(
|
fn listview_layout(
|
||||||
self: Pin<&Self>,
|
self: Pin<&Self>,
|
||||||
offset_y: &mut LogicalLength,
|
offset_y: &mut LogicalLength,
|
||||||
|
|
@ -666,7 +670,6 @@ fn ensure_repeater_updated<'id>(
|
||||||
window_adapter,
|
window_adapter,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
instance.run_setup_code();
|
|
||||||
instance
|
instance
|
||||||
};
|
};
|
||||||
if let Some(lv) = &rep_in_comp
|
if let Some(lv) = &rep_in_comp
|
||||||
|
|
@ -1455,7 +1458,7 @@ impl ErasedComponentBox {
|
||||||
generativity::make_guard!(guard);
|
generativity::make_guard!(guard);
|
||||||
let compo_box = self.unerase(guard);
|
let compo_box = self.unerase(guard);
|
||||||
let instance_ref = compo_box.borrow_instance();
|
let instance_ref = compo_box.borrow_instance();
|
||||||
for extra_init_code in self.0.component_type.original.setup_code.borrow().iter() {
|
for extra_init_code in self.0.component_type.original.init_code.borrow().iter() {
|
||||||
eval::eval_expression(
|
eval::eval_expression(
|
||||||
extra_init_code,
|
extra_init_code,
|
||||||
&mut eval::EvalLocalContext::from_component_instance(instance_ref),
|
&mut eval::EvalLocalContext::from_component_instance(instance_ref),
|
||||||
|
|
|
||||||
110
tests/cases/callbacks/init.slint
Normal file
110
tests/cases/callbacks/init.slint
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
// Verify that the init callback is invoked in the correct order
|
||||||
|
|
||||||
|
export global InitOrder := {
|
||||||
|
property <string> observed-order: "start";
|
||||||
|
}
|
||||||
|
|
||||||
|
Sub1 := Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|sub1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sub2 := Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|sub2";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SubSub := Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|subsub";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sub3 := Rectangle {
|
||||||
|
property <string> some-value: "should-not-show-up";
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += some-value;
|
||||||
|
}
|
||||||
|
SubSub {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Container := Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|container";
|
||||||
|
}
|
||||||
|
@children
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase := Rectangle {
|
||||||
|
width: 300phx;
|
||||||
|
height: 300phx;
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|root";
|
||||||
|
}
|
||||||
|
Sub1 {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|sub1-use-site";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sub2 {
|
||||||
|
}
|
||||||
|
Sub3 {
|
||||||
|
some-value: "|sub3";
|
||||||
|
}
|
||||||
|
|
||||||
|
Container {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|element";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 2: Rectangle {
|
||||||
|
init => {
|
||||||
|
InitOrder.observed-order += "|repeater";
|
||||||
|
InitOrder.observed-order += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property <string> test_global_prop_value: InitOrder.observed-order;
|
||||||
|
|
||||||
|
property <string> expected_static_order: "start|sub1|sub2|subsub|sub3|root|sub1-use-site|container|element";
|
||||||
|
|
||||||
|
property <bool> test: InitOrder.observed-order == expected_static_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
|
||||||
|
assert!(instance.get_test());
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq!(instance.global::<InitOrder>().get_observed_order(), instance.get_expected_static_order() + "|repeater0|repeater1");
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto handle = TestCase::create();
|
||||||
|
const TestCase &instance = *handle;
|
||||||
|
assert(instance.get_test());
|
||||||
|
slint_testing::send_mouse_click(&instance, 5., 5.);
|
||||||
|
assert_eq(instance.global<InitOrder>().get_observed_order(), instance.get_expected_static_order() + "|repeater0|repeater1");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new slint.TestCase({});
|
||||||
|
assert(instance.test);
|
||||||
|
instance.send_mouse_click(5., 5.);
|
||||||
|
assert.equal(instance.test_global_prop_value, instance.expected_static_order + "|repeater0|repeater1");
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
Loading…
Add table
Add a link
Reference in a new issue