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:
Simon Hausmann 2022-11-15 14:24:38 +01:00 committed by Simon Hausmann
parent 9baf6f2bde
commit 907b58161c
20 changed files with 366 additions and 46 deletions

View file

@ -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

View file

@ -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();

View file

@ -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.

View file

@ -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);

View file

@ -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")
} }

View file

@ -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())

View file

@ -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 {

View file

@ -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);

View file

@ -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)],

View 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);
}
});
}

View file

@ -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,

View file

@ -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);
} }
} }

View file

@ -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(),

View file

@ -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);
} }

View file

@ -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'}
} }

View 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'}
}

View file

@ -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

View file

@ -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

View file

@ -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),

View 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");
```
*/