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.
|
||||
- Disallow overrides or duplicated declarations of callbacks. Previously they were silently overwritten,
|
||||
now an error is produced.
|
||||
- The name "init" is now a reserved name in callbacks and properties.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -22,6 +23,8 @@ All notable changes to this project are documented in this file.
|
|||
- Added `From<char>` for `SharedString` in Rust.
|
||||
- Added `KeyPressed` and `KeyReleased` variants to `slint::WindowEvent` in Rust, along
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -1312,12 +1312,17 @@ public:
|
|||
inner->data.resize(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto &c = inner->data[i];
|
||||
bool created = false;
|
||||
if (!c.ptr) {
|
||||
c.ptr = C::create(parent);
|
||||
created = true;
|
||||
}
|
||||
if (c.state == RepeaterInner::State::Dirty) {
|
||||
(*c.ptr)->update_data(i, *m->row_data(i));
|
||||
}
|
||||
if (created) {
|
||||
(*c.ptr)->init();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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
|
||||
|
||||
These functions are available both in the global scope and in the `Math` namespace.
|
||||
|
|
|
|||
|
|
@ -1110,9 +1110,17 @@ fn generate_item_tree(
|
|||
root_access
|
||||
),
|
||||
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((
|
||||
Access::Public,
|
||||
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 subtrees_ranges_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(), {}, {});",
|
||||
field_name, global_index, global_children
|
||||
));
|
||||
user_init.push(format!("this->{}.user_init();", field_name));
|
||||
|
||||
let sub_component_repeater_count = sub.ty.repeater_count();
|
||||
if sub_component_repeater_count > 0 {
|
||||
|
|
@ -1453,7 +1464,12 @@ fn generate_sub_component(
|
|||
}
|
||||
|
||||
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((
|
||||
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((
|
||||
field_access,
|
||||
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 {
|
||||
let p_y = access_member(&listview.prop_y, &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 {
|
||||
let inner = #inner_component_id::new();
|
||||
#(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)
|
||||
}
|
||||
|
||||
|
|
@ -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_types: Vec<Ident> = vec![];
|
||||
|
||||
|
|
@ -791,6 +794,9 @@ fn generate_sub_component(
|
|||
&#root_ref_tokens,
|
||||
#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();
|
||||
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>)
|
||||
});
|
||||
|
||||
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_v = compile_expression(&component.layout_info_v.borrow(), &ctx);
|
||||
|
|
@ -927,6 +936,11 @@ fn generate_sub_component(
|
|||
#(#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(
|
||||
self: ::core::pin::Pin<&Self>,
|
||||
dyn_index: usize,
|
||||
|
|
@ -1152,7 +1166,21 @@ fn generate_item_tree(
|
|||
} else {
|
||||
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(
|
||||
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);
|
||||
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| {
|
||||
|
|
@ -1248,6 +1275,7 @@ fn generate_item_tree(
|
|||
#init_window
|
||||
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);
|
||||
#maybe_user_init
|
||||
self_rc
|
||||
}
|
||||
|
||||
|
|
@ -1441,6 +1469,12 @@ fn generate_repeated_component(
|
|||
#(#index_prop.set(_index as _);)*
|
||||
#(#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
|
||||
}
|
||||
)
|
||||
|
|
@ -2038,7 +2072,7 @@ fn compile_builtin_function_call(
|
|||
let window_tokens = access_window_adapter_field(ctx);
|
||||
let focus_item = access_item_rc(pr, ctx);
|
||||
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 {
|
||||
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()),
|
||||
Point::new(#x as slint::private_unstable_api::re_exports::Coord, #y as slint::private_unstable_api::re_exports::Coord),
|
||||
#parent_component
|
||||
);
|
||||
)
|
||||
)
|
||||
} else {
|
||||
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
||||
|
|
@ -2090,7 +2124,7 @@ fn compile_builtin_function_call(
|
|||
BuiltinFunction::RegisterCustomFontByPath => {
|
||||
if let [Expression::StringLiteral(path)] = arguments {
|
||||
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 {
|
||||
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 symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
||||
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 {
|
||||
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 symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);
|
||||
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 {
|
||||
panic!("internal error: invalid args to RegisterBitmapFont must be a number")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ fn lower_sub_component(
|
|||
});
|
||||
|
||||
sub_component.init_code = component
|
||||
.setup_code
|
||||
.init_code
|
||||
.borrow()
|
||||
.iter()
|
||||
.map(|e| super::lower_expression::lower_expression(e, &ctx).into())
|
||||
|
|
|
|||
|
|
@ -222,8 +222,13 @@ pub struct Component {
|
|||
/// the element pointer to by this field.
|
||||
pub child_insertion_point: RefCell<Option<ChildrenInsertionPoint>>,
|
||||
|
||||
/// Code to be inserted into the constructor
|
||||
pub setup_code: RefCell<Vec<Expression>>,
|
||||
/// Code inserted from inlined components, ordered by offset of the place where it was inlined from. This way
|
||||
/// 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.
|
||||
/// (This only make sense on the root component)
|
||||
|
|
@ -709,6 +714,17 @@ impl Element {
|
|||
node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
|
||||
node.States().for_each(|n| error_on(&n, "states"));
|
||||
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
|
||||
} else if parent_type != ElementType::Error {
|
||||
|
|
@ -1783,33 +1799,39 @@ pub fn visit_element_expressions(
|
|||
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
|
||||
/// But does not recurse in sub-elements. (unlike [`visit_all_named_references`] which recurse)
|
||||
pub fn visit_all_named_references_in_element(
|
||||
elem: &ElementRc,
|
||||
mut vis: impl FnMut(&mut NamedReference),
|
||||
) {
|
||||
fn recurse_expression(expr: &mut Expression, vis: &mut impl FnMut(&mut NamedReference)) {
|
||||
expr.visit_mut(|sub| recurse_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_element_expressions(elem, |expr, _, _| recurse_expression(expr, &mut vis));
|
||||
visit_element_expressions(elem, |expr, _, _| {
|
||||
visit_named_references_in_expression(expr, &mut vis)
|
||||
});
|
||||
let mut states = std::mem::take(&mut elem.borrow_mut().states);
|
||||
for s in &mut states {
|
||||
for (r, _, _) in &mut s.property_changes {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ mod check_rotation;
|
|||
mod clip;
|
||||
mod collect_custom_fonts;
|
||||
mod collect_globals;
|
||||
mod collect_init_code;
|
||||
mod collect_structs;
|
||||
mod collect_subcomponents;
|
||||
mod compile_paths;
|
||||
|
|
@ -173,6 +174,7 @@ pub async fn run_passes(
|
|||
if compiler_config.accessibility {
|
||||
lower_accessibility::lower_accessibility_properties(component, diag);
|
||||
}
|
||||
collect_init_code::collect_init_code(component);
|
||||
materialize_fake_properties::materialize_fake_properties(component);
|
||||
}
|
||||
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()))
|
||||
};
|
||||
|
||||
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 {
|
||||
function: Box::new(registration_function.clone()),
|
||||
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(
|
||||
BuiltinFunction::RegisterBitmapFont,
|
||||
None,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ pub fn determine_initial_focus_item(component: &Rc<Component>, diag: &mut BuildD
|
|||
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))
|
||||
}
|
||||
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) {
|
||||
|
|
@ -165,6 +172,27 @@ fn inline_element(
|
|||
|
||||
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
|
||||
for e in mapping.values() {
|
||||
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(),
|
||||
root_constraints: component_to_duplicate.root_constraints.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(),
|
||||
popup_windows: Default::default(),
|
||||
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);
|
||||
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() {
|
||||
pd.is_alias.as_mut().map(fixup_reference);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,4 +49,7 @@ SubElements := Rectangle {
|
|||
property <bool> pressed;
|
||||
callback pressed;
|
||||
// ^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())),
|
||||
),
|
||||
]))
|
||||
.chain(std::iter::once(("init", Type::Callback { return_type: None, args: vec![] })))
|
||||
}
|
||||
|
||||
/// 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
|
||||
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
|
||||
///
|
||||
/// 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();
|
||||
inner.components.resize_with(count, || (RepeatedComponentState::Dirty, None));
|
||||
let offset = inner.offset;
|
||||
let mut created = false;
|
||||
let mut any_items_created = false;
|
||||
for (i, c) in inner.components.iter_mut().enumerate() {
|
||||
if c.0 == RepeatedComponentState::Dirty {
|
||||
if c.1.is_none() {
|
||||
created = true;
|
||||
let created = if c.1.is_none() {
|
||||
any_items_created = true;
|
||||
c.1 = Some(init());
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
self.data().is_dirty.set(false);
|
||||
created
|
||||
any_items_created
|
||||
}
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
fn init(&self) {
|
||||
self.run_setup_code();
|
||||
}
|
||||
|
||||
fn listview_layout(
|
||||
self: Pin<&Self>,
|
||||
offset_y: &mut LogicalLength,
|
||||
|
|
@ -666,7 +670,6 @@ fn ensure_repeater_updated<'id>(
|
|||
window_adapter,
|
||||
Default::default(),
|
||||
);
|
||||
instance.run_setup_code();
|
||||
instance
|
||||
};
|
||||
if let Some(lv) = &rep_in_comp
|
||||
|
|
@ -1455,7 +1458,7 @@ impl ErasedComponentBox {
|
|||
generativity::make_guard!(guard);
|
||||
let compo_box = self.unerase(guard);
|
||||
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(
|
||||
extra_init_code,
|
||||
&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