mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-17 10:53:07 +00:00
Add support for importing Rust types from another crate Slint compilation (#9329)
To implement an external Slint library, the types and components implemented in the .slint files needs to be exposed through the Rust crate. A simple example example/app-library is added to demonstrate how to use this feature. CC #7060
This commit is contained in:
parent
b23a657c44
commit
0bda0a64eb
30 changed files with 766 additions and 42 deletions
|
|
@ -61,6 +61,9 @@ members = [
|
||||||
'tests/driver/rust',
|
'tests/driver/rust',
|
||||||
'tests/screenshots',
|
'tests/screenshots',
|
||||||
'tests/manual/windowattributes',
|
'tests/manual/windowattributes',
|
||||||
|
'tests/manual/module-builds/blogica',
|
||||||
|
'tests/manual/module-builds/blogicb',
|
||||||
|
'tests/manual/module-builds/app',
|
||||||
'tools/compiler',
|
'tools/compiler',
|
||||||
'tools/docsnapper',
|
'tools/docsnapper',
|
||||||
'tools/figma_import',
|
'tools/figma_import',
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ path = "lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
experimental-module-builds = ["i-slint-compiler/experimental-library-module"]
|
||||||
sdf-fonts = ["i-slint-compiler/sdf-fonts"]
|
sdf-fonts = ["i-slint-compiler/sdf-fonts"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,26 @@ impl CompilerConfiguration {
|
||||||
Self { config }
|
Self { config }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures the compiler to treat the Slint as part of a library.
|
||||||
|
///
|
||||||
|
/// Use this when the components and types of the Slint code need
|
||||||
|
/// to be accessible from other modules.
|
||||||
|
#[cfg(feature = "experimental-module-builds")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_library(self, library_name: &str) -> Self {
|
||||||
|
let mut config = self.config;
|
||||||
|
config.library_name = Some(library_name.to_string());
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specify the Rust module to place the generated code in.
|
||||||
|
#[cfg(feature = "experimental-module-builds")]
|
||||||
|
#[must_use]
|
||||||
|
pub fn rust_module(self, rust_module: &str) -> Self {
|
||||||
|
let mut config = self.config;
|
||||||
|
config.rust_module = Some(rust_module.to_string());
|
||||||
|
Self { config }
|
||||||
|
}
|
||||||
/// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts.
|
/// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts.
|
||||||
///
|
///
|
||||||
/// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`],
|
/// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`],
|
||||||
|
|
@ -429,6 +449,18 @@ pub fn compile_with_config(
|
||||||
.with_extension("rs"),
|
.with_extension("rs"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "experimental-module-builds")]
|
||||||
|
if let Some(library_name) = config.config.library_name.clone() {
|
||||||
|
println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name);
|
||||||
|
println!(
|
||||||
|
"cargo::metadata=SLINT_LIBRARY_PACKAGE={}",
|
||||||
|
std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default()
|
||||||
|
);
|
||||||
|
println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display());
|
||||||
|
if let Some(rust_module) = &config.config.rust_module {
|
||||||
|
println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module);
|
||||||
|
}
|
||||||
|
}
|
||||||
let paths_dependencies =
|
let paths_dependencies =
|
||||||
compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
|
compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ sdf-fonts = ["dep:fdsm", "dep:fdsm-ttf-parser", "dep:nalgebra", "dep:rayon"]
|
||||||
# Translation bundler
|
# Translation bundler
|
||||||
bundle-translations = ["dep:polib"]
|
bundle-translations = ["dep:polib"]
|
||||||
|
|
||||||
|
# Enable expermental library module support
|
||||||
|
experimental-library-module = []
|
||||||
|
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use crate::llr::{
|
||||||
TypeResolutionContext as _,
|
TypeResolutionContext as _,
|
||||||
};
|
};
|
||||||
use crate::object_tree::Document;
|
use crate::object_tree::Document;
|
||||||
|
use crate::typeloader::LibraryInfo;
|
||||||
use crate::CompilerConfiguration;
|
use crate::CompilerConfiguration;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use lyon_path::geom::euclid::approxeq::ApproxEq;
|
use lyon_path::geom::euclid::approxeq::ApproxEq;
|
||||||
|
|
@ -160,6 +161,36 @@ pub fn generate(
|
||||||
return super::rust_live_preview::generate(doc, compiler_config);
|
return super::rust_live_preview::generate(doc, compiler_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let module_header = generate_module_header();
|
||||||
|
let qualified_name_ident = |symbol: &SmolStr, library_info: &LibraryInfo| {
|
||||||
|
let symbol = ident(symbol);
|
||||||
|
let package = ident(&library_info.package);
|
||||||
|
if let Some(module) = &library_info.module {
|
||||||
|
let module = ident(module);
|
||||||
|
quote!(#package :: #module :: #symbol)
|
||||||
|
} else {
|
||||||
|
quote!(#package :: #symbol)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let library_imports = {
|
||||||
|
let doc_used_types = doc.used_types.borrow();
|
||||||
|
doc_used_types
|
||||||
|
.library_types_imports
|
||||||
|
.iter()
|
||||||
|
.map(|(symbol, library_info)| {
|
||||||
|
let ident = qualified_name_ident(symbol, library_info);
|
||||||
|
quote!(pub use #ident;)
|
||||||
|
})
|
||||||
|
.chain(doc_used_types.library_global_imports.iter().map(|(symbol, library_info)| {
|
||||||
|
let ident = qualified_name_ident(symbol, library_info);
|
||||||
|
let inner_symbol_name = smol_str::format_smolstr!("Inner{}", symbol);
|
||||||
|
let inner_ident = qualified_name_ident(&inner_symbol_name, library_info);
|
||||||
|
quote!(pub use #ident, #inner_ident;)
|
||||||
|
}))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
let (structs_and_enums_ids, inner_module) =
|
let (structs_and_enums_ids, inner_module) =
|
||||||
generate_types(&doc.used_types.borrow().structs_and_enums);
|
generate_types(&doc.used_types.borrow().structs_and_enums);
|
||||||
|
|
||||||
|
|
@ -180,12 +211,22 @@ pub fn generate(
|
||||||
let popup_menu =
|
let popup_menu =
|
||||||
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
|
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
|
||||||
|
|
||||||
let globals = llr
|
let mut global_exports = Vec::<TokenStream>::new();
|
||||||
|
if let Some(library_name) = &compiler_config.library_name {
|
||||||
|
// Building as a library, SharedGlobals needs to be exported
|
||||||
|
let ident = format_ident!("{}SharedGlobals", library_name);
|
||||||
|
global_exports.push(quote!(SharedGlobals as #ident));
|
||||||
|
}
|
||||||
|
let globals =
|
||||||
|
llr.globals.iter_enumerated().filter(|(_, glob)| glob.must_generate()).map(
|
||||||
|
|(idx, glob)| generate_global(idx, glob, &llr, compiler_config, &mut global_exports),
|
||||||
|
);
|
||||||
|
let library_globals_getters = llr
|
||||||
.globals
|
.globals
|
||||||
.iter_enumerated()
|
.iter_enumerated()
|
||||||
.filter(|(_, glob)| glob.must_generate())
|
.filter(|(_, glob)| glob.from_library)
|
||||||
.map(|(idx, glob)| generate_global(idx, glob, &llr));
|
.map(|(_idx, glob)| generate_global_getters(glob, &llr));
|
||||||
let shared_globals = generate_shared_globals(&llr, compiler_config);
|
let shared_globals = generate_shared_globals(&doc, &llr, compiler_config);
|
||||||
let globals_ids = llr.globals.iter().filter(|glob| glob.exported).flat_map(|glob| {
|
let globals_ids = llr.globals.iter().filter(|glob| glob.exported).flat_map(|glob| {
|
||||||
std::iter::once(ident(&glob.name)).chain(glob.aliases.iter().map(|x| ident(x)))
|
std::iter::once(ident(&glob.name)).chain(glob.aliases.iter().map(|x| ident(x)))
|
||||||
});
|
});
|
||||||
|
|
@ -207,8 +248,11 @@ pub fn generate(
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
mod #generated_mod {
|
mod #generated_mod {
|
||||||
|
#module_header
|
||||||
|
#(#library_imports)*
|
||||||
#inner_module
|
#inner_module
|
||||||
#(#globals)*
|
#(#globals)*
|
||||||
|
#(#library_globals_getters)*
|
||||||
#(#sub_compos)*
|
#(#sub_compos)*
|
||||||
#popup_menu
|
#popup_menu
|
||||||
#(#public_components)*
|
#(#public_components)*
|
||||||
|
|
@ -217,12 +261,25 @@ pub fn generate(
|
||||||
#translations
|
#translations
|
||||||
}
|
}
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
|
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #(#global_exports,)*};
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
|
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn generate_module_header() -> TokenStream {
|
||||||
|
quote! {
|
||||||
|
#![allow(non_snake_case, non_camel_case_types)]
|
||||||
|
#![allow(unused_braces, unused_parens)]
|
||||||
|
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
|
||||||
|
#![allow(unknown_lints, if_let_rescope, tail_expr_drop_order)] // We don't have fancy Drop
|
||||||
|
|
||||||
|
use slint::private_unstable_api::re_exports as sp;
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use sp::{RepeatedItemTree as _, ModelExt as _, Model as _, Float as _};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate the struct and enums. Return a vector of names to import and a token stream with the inner module
|
/// Generate the struct and enums. Return a vector of names to import and a token stream with the inner module
|
||||||
pub fn generate_types(used_types: &[Type]) -> (Vec<Ident>, TokenStream) {
|
pub fn generate_types(used_types: &[Type]) -> (Vec<Ident>, TokenStream) {
|
||||||
let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = used_types
|
let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = used_types
|
||||||
|
|
@ -247,14 +304,6 @@ pub fn generate_types(used_types: &[Type]) -> (Vec<Ident>, TokenStream) {
|
||||||
);
|
);
|
||||||
|
|
||||||
let inner_module = quote! {
|
let inner_module = quote! {
|
||||||
#![allow(non_snake_case, non_camel_case_types)]
|
|
||||||
#![allow(unused_braces, unused_parens)]
|
|
||||||
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
|
|
||||||
#![allow(unknown_lints, if_let_rescope, tail_expr_drop_order)] // We don't have fancy Drop
|
|
||||||
|
|
||||||
use slint::private_unstable_api::re_exports as sp;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use sp::{RepeatedItemTree as _, ModelExt as _, Model as _, Float as _};
|
|
||||||
#(#structs_and_enum_def)*
|
#(#structs_and_enum_def)*
|
||||||
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
|
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
|
||||||
};
|
};
|
||||||
|
|
@ -361,6 +410,7 @@ fn generate_public_component(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_shared_globals(
|
fn generate_shared_globals(
|
||||||
|
doc: &Document,
|
||||||
llr: &llr::CompilationUnit,
|
llr: &llr::CompilationUnit,
|
||||||
compiler_config: &CompilerConfiguration,
|
compiler_config: &CompilerConfiguration,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
|
|
@ -377,6 +427,15 @@ fn generate_shared_globals(
|
||||||
.map(global_inner_name)
|
.map(global_inner_name)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let from_library_global_names = llr
|
||||||
|
.globals
|
||||||
|
.iter()
|
||||||
|
.filter(|g| g.from_library)
|
||||||
|
.map(|g| format_ident!("global_{}", ident(&g.name)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let from_library_global_types =
|
||||||
|
llr.globals.iter().filter(|g| g.from_library).map(global_inner_name).collect::<Vec<_>>();
|
||||||
let apply_constant_scale_factor = if !compiler_config.const_scale_factor.approx_eq(&1.0) {
|
let apply_constant_scale_factor = if !compiler_config.const_scale_factor.approx_eq(&1.0) {
|
||||||
let factor = compiler_config.const_scale_factor as f32;
|
let factor = compiler_config.const_scale_factor as f32;
|
||||||
Some(
|
Some(
|
||||||
|
|
@ -386,18 +445,59 @@ fn generate_shared_globals(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let library_global_vars = llr
|
||||||
|
.globals
|
||||||
|
.iter()
|
||||||
|
.filter(|g| g.from_library)
|
||||||
|
.map(|g| {
|
||||||
|
let library_info = doc.library_exports.get(g.name.as_str()).unwrap();
|
||||||
|
let shared_gloabls_var_name =
|
||||||
|
format_ident!("library_{}_shared_globals", library_info.name);
|
||||||
|
let global_name = format_ident!("global_{}", ident(&g.name));
|
||||||
|
quote!( #shared_gloabls_var_name.#global_name )
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let pub_token = if compiler_config.library_name.is_some() { quote!(pub) } else { quote!() };
|
||||||
|
|
||||||
|
let (library_shared_globals_names, library_shared_globals_types): (Vec<_>, Vec<_>) = doc
|
||||||
|
.imports
|
||||||
|
.iter()
|
||||||
|
.filter_map(|import| import.library_info.clone())
|
||||||
|
.map(|library_info| {
|
||||||
|
let struct_name = format_ident!("{}SharedGlobals", library_info.name);
|
||||||
|
let shared_gloabls_var_name =
|
||||||
|
format_ident!("library_{}_shared_globals", library_info.name);
|
||||||
|
let shared_globals_type_name = if let Some(module) = library_info.module {
|
||||||
|
let package = ident(&library_info.package);
|
||||||
|
let module = ident(&module);
|
||||||
|
//(quote!(#shared_gloabls_var_name),quote!(let #shared_gloabls_var_name = #package::#module::#shared_globals_type_name::new(root_item_tree_weak.clone());))
|
||||||
|
quote!(#package::#module::#struct_name)
|
||||||
|
} else {
|
||||||
|
let package = ident(&library_info.package);
|
||||||
|
quote!(#package::#struct_name)
|
||||||
|
};
|
||||||
|
(quote!(#shared_gloabls_var_name), shared_globals_type_name)
|
||||||
|
})
|
||||||
|
.unzip();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
struct SharedGlobals {
|
#pub_token struct SharedGlobals {
|
||||||
#(#global_names : ::core::pin::Pin<sp::Rc<#global_types>>,)*
|
#(#pub_token #global_names : ::core::pin::Pin<sp::Rc<#global_types>>,)*
|
||||||
|
#(#pub_token #from_library_global_names : ::core::pin::Pin<sp::Rc<#from_library_global_types>>,)*
|
||||||
window_adapter : sp::OnceCell<sp::WindowAdapterRc>,
|
window_adapter : sp::OnceCell<sp::WindowAdapterRc>,
|
||||||
root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>,
|
root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>,
|
||||||
|
#(#[allow(dead_code)]
|
||||||
|
#library_shared_globals_names : sp::Rc<#library_shared_globals_types>,)*
|
||||||
}
|
}
|
||||||
impl SharedGlobals {
|
impl SharedGlobals {
|
||||||
fn new(root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>) -> sp::Rc<Self> {
|
#pub_token fn new(root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>) -> sp::Rc<Self> {
|
||||||
|
#(let #library_shared_globals_names = #library_shared_globals_types::new(root_item_tree_weak.clone());)*
|
||||||
let _self = sp::Rc::new(Self {
|
let _self = sp::Rc::new(Self {
|
||||||
#(#global_names : #global_types::new(),)*
|
#(#global_names : #global_types::new(),)*
|
||||||
|
#(#from_library_global_names : #library_global_vars.clone(),)*
|
||||||
window_adapter : ::core::default::Default::default(),
|
window_adapter : ::core::default::Default::default(),
|
||||||
root_item_tree_weak,
|
root_item_tree_weak,
|
||||||
|
#(#library_shared_globals_names,)*
|
||||||
});
|
});
|
||||||
#(_self.#global_names.clone().init(&_self);)*
|
#(_self.#global_names.clone().init(&_self);)*
|
||||||
_self
|
_self
|
||||||
|
|
@ -1340,6 +1440,8 @@ fn generate_global(
|
||||||
global_idx: llr::GlobalIdx,
|
global_idx: llr::GlobalIdx,
|
||||||
global: &llr::GlobalComponent,
|
global: &llr::GlobalComponent,
|
||||||
root: &llr::CompilationUnit,
|
root: &llr::CompilationUnit,
|
||||||
|
compiler_config: &CompilerConfiguration,
|
||||||
|
global_exports: &mut Vec<TokenStream>,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let mut declared_property_vars = vec![];
|
let mut declared_property_vars = vec![];
|
||||||
let mut declared_property_types = vec![];
|
let mut declared_property_types = vec![];
|
||||||
|
|
@ -1442,6 +1544,13 @@ fn generate_global(
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let pub_token = if compiler_config.library_name.is_some() {
|
||||||
|
global_exports.push(quote! (#inner_component_id));
|
||||||
|
quote!(pub)
|
||||||
|
} else {
|
||||||
|
quote!()
|
||||||
|
};
|
||||||
|
|
||||||
let public_interface = global.exported.then(|| {
|
let public_interface = global.exported.then(|| {
|
||||||
let property_and_callback_accessors = public_api(
|
let property_and_callback_accessors = public_api(
|
||||||
&global.public_properties,
|
&global.public_properties,
|
||||||
|
|
@ -1450,26 +1559,17 @@ fn generate_global(
|
||||||
&ctx,
|
&ctx,
|
||||||
);
|
);
|
||||||
let aliases = global.aliases.iter().map(|name| ident(name));
|
let aliases = global.aliases.iter().map(|name| ident(name));
|
||||||
let getters = root.public_components.iter().map(|c| {
|
let getters = generate_global_getters(global, root);
|
||||||
let root_component_id = ident(&c.name);
|
|
||||||
quote! {
|
|
||||||
impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> {
|
|
||||||
fn get(component: &'a #root_component_id) -> Self {
|
|
||||||
Self(&component.0.globals.get().unwrap().#global_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct #public_component_id<'a>(&'a ::core::pin::Pin<sp::Rc<#inner_component_id>>);
|
pub struct #public_component_id<'a>(#pub_token &'a ::core::pin::Pin<sp::Rc<#inner_component_id>>);
|
||||||
|
|
||||||
impl<'a> #public_component_id<'a> {
|
impl<'a> #public_component_id<'a> {
|
||||||
#property_and_callback_accessors
|
#property_and_callback_accessors
|
||||||
}
|
}
|
||||||
#(pub type #aliases<'a> = #public_component_id<'a>;)*
|
#(pub type #aliases<'a> = #public_component_id<'a>;)*
|
||||||
#(#getters)*
|
#getters
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1478,10 +1578,10 @@ fn generate_global(
|
||||||
#[const_field_offset(sp::const_field_offset)]
|
#[const_field_offset(sp::const_field_offset)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[pin]
|
#[pin]
|
||||||
struct #inner_component_id {
|
#pub_token struct #inner_component_id {
|
||||||
#(#declared_property_vars: sp::Property<#declared_property_types>,)*
|
#(#pub_token #declared_property_vars: sp::Property<#declared_property_types>,)*
|
||||||
#(#declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
|
#(#pub_token #declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
|
||||||
#(#change_tracker_names : sp::ChangeTracker,)*
|
#(#pub_token #change_tracker_names : sp::ChangeTracker,)*
|
||||||
globals : sp::OnceCell<sp::Weak<SharedGlobals>>,
|
globals : sp::OnceCell<sp::Weak<SharedGlobals>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1504,6 +1604,29 @@ fn generate_global(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_global_getters(
|
||||||
|
global: &llr::GlobalComponent,
|
||||||
|
root: &llr::CompilationUnit,
|
||||||
|
) -> TokenStream {
|
||||||
|
let public_component_id = ident(&global.name);
|
||||||
|
let global_id = format_ident!("global_{}", public_component_id);
|
||||||
|
|
||||||
|
let getters = root.public_components.iter().map(|c| {
|
||||||
|
let root_component_id = ident(&c.name);
|
||||||
|
quote! {
|
||||||
|
impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> {
|
||||||
|
fn get(component: &'a #root_component_id) -> Self {
|
||||||
|
Self(&component.0.globals.get().unwrap().#global_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! (
|
||||||
|
#(#getters)*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_item_tree(
|
fn generate_item_tree(
|
||||||
sub_tree: &llr::ItemTree,
|
sub_tree: &llr::ItemTree,
|
||||||
root: &llr::CompilationUnit,
|
root: &llr::CompilationUnit,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub fn generate(
|
||||||
doc: &Document,
|
doc: &Document,
|
||||||
compiler_config: &CompilerConfiguration,
|
compiler_config: &CompilerConfiguration,
|
||||||
) -> std::io::Result<TokenStream> {
|
) -> std::io::Result<TokenStream> {
|
||||||
|
let module_header = super::rust::generate_module_header();
|
||||||
|
|
||||||
let (structs_and_enums_ids, inner_module) =
|
let (structs_and_enums_ids, inner_module) =
|
||||||
super::rust::generate_types(&doc.used_types.borrow().structs_and_enums);
|
super::rust::generate_types(&doc.used_types.borrow().structs_and_enums);
|
||||||
|
|
||||||
|
|
@ -60,6 +62,7 @@ pub fn generate(
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
mod #generated_mod {
|
mod #generated_mod {
|
||||||
|
#module_header
|
||||||
#inner_module
|
#inner_module
|
||||||
#(#globals)*
|
#(#globals)*
|
||||||
#(#public_components)*
|
#(#public_components)*
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,12 @@ pub struct CompilerConfiguration {
|
||||||
|
|
||||||
#[cfg(feature = "software-renderer")]
|
#[cfg(feature = "software-renderer")]
|
||||||
pub font_cache: FontCache,
|
pub font_cache: FontCache,
|
||||||
|
|
||||||
|
/// The name of the library when compiling as a library.
|
||||||
|
pub library_name: Option<String>,
|
||||||
|
|
||||||
|
/// Specify the Rust module to place the generated code in.
|
||||||
|
pub rust_module: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompilerConfiguration {
|
impl CompilerConfiguration {
|
||||||
|
|
@ -249,6 +255,8 @@ impl CompilerConfiguration {
|
||||||
translation_path_bundle: std::env::var("SLINT_BUNDLE_TRANSLATIONS")
|
translation_path_bundle: std::env::var("SLINT_BUNDLE_TRANSLATIONS")
|
||||||
.ok()
|
.ok()
|
||||||
.map(|x| x.into()),
|
.map(|x| x.into()),
|
||||||
|
library_name: None,
|
||||||
|
rust_module: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ pub struct GlobalComponent {
|
||||||
pub aliases: Vec<SmolStr>,
|
pub aliases: Vec<SmolStr>,
|
||||||
/// True when this is a built-in global that does not need to be generated
|
/// True when this is a built-in global that does not need to be generated
|
||||||
pub is_builtin: bool,
|
pub is_builtin: bool,
|
||||||
|
/// True if this component is imported from an external library
|
||||||
|
pub from_library: bool,
|
||||||
/// Analysis for each properties
|
/// Analysis for each properties
|
||||||
pub prop_analysis: TiVec<PropertyIdx, crate::object_tree::PropertyAnalysis>,
|
pub prop_analysis: TiVec<PropertyIdx, crate::object_tree::PropertyAnalysis>,
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +92,7 @@ pub struct GlobalComponent {
|
||||||
impl GlobalComponent {
|
impl GlobalComponent {
|
||||||
pub fn must_generate(&self) -> bool {
|
pub fn must_generate(&self) -> bool {
|
||||||
!self.is_builtin
|
!self.is_builtin
|
||||||
|
&& !self.from_library
|
||||||
&& (self.exported
|
&& (self.exported
|
||||||
|| !self.functions.is_empty()
|
|| !self.functions.is_empty()
|
||||||
|| self.properties.iter().any(|p| p.use_count.get() > 0))
|
|| self.properties.iter().any(|p| p.use_count.get() > 0))
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,8 @@ fn property_reference_within_sub_component(
|
||||||
fn component_id(component: &Rc<Component>) -> SmolStr {
|
fn component_id(component: &Rc<Component>) -> SmolStr {
|
||||||
if component.is_global() {
|
if component.is_global() {
|
||||||
component.root_element.borrow().id.clone()
|
component.root_element.borrow().id.clone()
|
||||||
|
} else if component.from_library.get() {
|
||||||
|
component.id.clone()
|
||||||
} else if component.id.is_empty() {
|
} else if component.id.is_empty() {
|
||||||
format_smolstr!("Component_{}", component.root_element.borrow().id)
|
format_smolstr!("Component_{}", component.root_element.borrow().id)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -833,6 +835,7 @@ fn lower_global(
|
||||||
exported: !global.exported_global_names.borrow().is_empty(),
|
exported: !global.exported_global_names.borrow().is_empty(),
|
||||||
aliases: global.global_aliases(),
|
aliases: global.global_aliases(),
|
||||||
is_builtin,
|
is_builtin,
|
||||||
|
from_library: global.from_library.get(),
|
||||||
prop_analysis,
|
prop_analysis,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use crate::layout::{LayoutConstraints, Orientation};
|
||||||
use crate::namedreference::NamedReference;
|
use crate::namedreference::NamedReference;
|
||||||
use crate::parser;
|
use crate::parser;
|
||||||
use crate::parser::{syntax_nodes, SyntaxKind, SyntaxNode};
|
use crate::parser::{syntax_nodes, SyntaxKind, SyntaxNode};
|
||||||
use crate::typeloader::{ImportKind, ImportedTypes};
|
use crate::typeloader::{ImportKind, ImportedTypes, LibraryInfo};
|
||||||
use crate::typeregister::TypeRegister;
|
use crate::typeregister::TypeRegister;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use smol_str::{format_smolstr, SmolStr, ToSmolStr};
|
use smol_str::{format_smolstr, SmolStr, ToSmolStr};
|
||||||
|
|
@ -53,6 +53,7 @@ pub struct Document {
|
||||||
pub custom_fonts: Vec<(SmolStr, crate::parser::SyntaxToken)>,
|
pub custom_fonts: Vec<(SmolStr, crate::parser::SyntaxToken)>,
|
||||||
pub exports: Exports,
|
pub exports: Exports,
|
||||||
pub imports: Vec<ImportedTypes>,
|
pub imports: Vec<ImportedTypes>,
|
||||||
|
pub library_exports: HashMap<String, LibraryInfo>,
|
||||||
|
|
||||||
/// Map of resources that should be embedded in the generated code, indexed by their absolute path on
|
/// Map of resources that should be embedded in the generated code, indexed by their absolute path on
|
||||||
/// disk on the build system
|
/// disk on the build system
|
||||||
|
|
@ -265,6 +266,7 @@ impl Document {
|
||||||
custom_fonts,
|
custom_fonts,
|
||||||
imports,
|
imports,
|
||||||
exports,
|
exports,
|
||||||
|
library_exports: Default::default(),
|
||||||
embedded_file_resources: Default::default(),
|
embedded_file_resources: Default::default(),
|
||||||
#[cfg(feature = "bundle-translations")]
|
#[cfg(feature = "bundle-translations")]
|
||||||
translation_builder: None,
|
translation_builder: None,
|
||||||
|
|
@ -339,6 +341,12 @@ pub struct UsedSubTypes {
|
||||||
/// All the sub components use by this components and its children,
|
/// All the sub components use by this components and its children,
|
||||||
/// and the amount of time it is used
|
/// and the amount of time it is used
|
||||||
pub sub_components: Vec<Rc<Component>>,
|
pub sub_components: Vec<Rc<Component>>,
|
||||||
|
/// All types, structs, enums, that orignates from an
|
||||||
|
/// external library
|
||||||
|
pub library_types_imports: Vec<(SmolStr, LibraryInfo)>,
|
||||||
|
/// All global components that originates from an
|
||||||
|
/// external library
|
||||||
|
pub library_global_imports: Vec<(SmolStr, LibraryInfo)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
|
@ -413,6 +421,9 @@ pub struct Component {
|
||||||
/// The list of properties (name and type) declared as private in the component.
|
/// The list of properties (name and type) declared as private in the component.
|
||||||
/// This is used to issue better error in the generated code if the property is used.
|
/// This is used to issue better error in the generated code if the property is used.
|
||||||
pub private_properties: RefCell<Vec<(SmolStr, Type)>>,
|
pub private_properties: RefCell<Vec<(SmolStr, Type)>>,
|
||||||
|
|
||||||
|
/// True if this component is imported from an external library.
|
||||||
|
pub from_library: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component {
|
impl Component {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ mod clip;
|
||||||
mod collect_custom_fonts;
|
mod collect_custom_fonts;
|
||||||
mod collect_globals;
|
mod collect_globals;
|
||||||
mod collect_init_code;
|
mod collect_init_code;
|
||||||
|
mod collect_libraries;
|
||||||
mod collect_structs_and_enums;
|
mod collect_structs_and_enums;
|
||||||
mod collect_subcomponents;
|
mod collect_subcomponents;
|
||||||
mod compile_paths;
|
mod compile_paths;
|
||||||
|
|
@ -99,6 +100,7 @@ pub async fn run_passes(
|
||||||
let raw_type_loader =
|
let raw_type_loader =
|
||||||
keep_raw.then(|| crate::typeloader::snapshot_with_extra_doc(type_loader, doc).unwrap());
|
keep_raw.then(|| crate::typeloader::snapshot_with_extra_doc(type_loader, doc).unwrap());
|
||||||
|
|
||||||
|
collect_libraries::collect_libraries(doc);
|
||||||
collect_subcomponents::collect_subcomponents(doc);
|
collect_subcomponents::collect_subcomponents(doc);
|
||||||
lower_tabwidget::lower_tabwidget(doc, type_loader, diag).await;
|
lower_tabwidget::lower_tabwidget(doc, type_loader, diag).await;
|
||||||
lower_menus::lower_menus(doc, type_loader, diag).await;
|
lower_menus::lower_menus(doc, type_loader, diag).await;
|
||||||
|
|
@ -208,6 +210,7 @@ pub async fn run_passes(
|
||||||
}
|
}
|
||||||
|
|
||||||
binding_analysis::binding_analysis(doc, &type_loader.compiler_config, diag);
|
binding_analysis::binding_analysis(doc, &type_loader.compiler_config, diag);
|
||||||
|
collect_globals::mark_library_globals(doc);
|
||||||
unique_id::assign_unique_id(doc);
|
unique_id::assign_unique_id(doc);
|
||||||
|
|
||||||
doc.visit_all_used_components(|component| {
|
doc.visit_all_used_components(|component| {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub fn check_public_api(
|
||||||
if is_last {
|
if is_last {
|
||||||
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. No code will be generated for it", export.0.name), &export.0.name_ident);
|
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. No code will be generated for it", export.0.name), &export.0.name_ident);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else if config.library_name.is_none () {
|
||||||
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. This is deprecated", export.0.name), &export.0.name_ident);
|
diag.push_warning(format!("Exported component '{}' doesn't inherit Window. This is deprecated", export.0.name), &export.0.name_ident);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
//! This pass fills the root component used_types.globals
|
//! This pass fills the root component used_types.globals
|
||||||
|
|
||||||
use by_address::ByAddress;
|
use by_address::ByAddress;
|
||||||
|
use smol_str::format_smolstr;
|
||||||
|
|
||||||
use crate::diagnostics::BuildDiagnostics;
|
use crate::diagnostics::BuildDiagnostics;
|
||||||
use crate::expression_tree::NamedReference;
|
use crate::expression_tree::NamedReference;
|
||||||
|
|
@ -31,6 +32,19 @@ pub fn collect_globals(doc: &Document, _diag: &mut BuildDiagnostics) {
|
||||||
doc.used_types.borrow_mut().globals = sorted_globals;
|
doc.used_types.borrow_mut().globals = sorted_globals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mark_library_globals(doc: &Document) {
|
||||||
|
let mut used_types = doc.used_types.borrow_mut();
|
||||||
|
used_types.globals.clone().iter().for_each(|component| {
|
||||||
|
if let Some(library_info) = doc.library_exports.get(component.id.as_str()) {
|
||||||
|
component.from_library.set(true);
|
||||||
|
used_types.library_types_imports.push((component.id.clone(), library_info.clone()));
|
||||||
|
used_types
|
||||||
|
.library_types_imports
|
||||||
|
.push((format_smolstr!("Inner{}", component.id.clone()), library_info.clone()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_in_component(
|
fn collect_in_component(
|
||||||
component: &Rc<Component>,
|
component: &Rc<Component>,
|
||||||
global_set: &mut HashSet<ByAddress<Rc<Component>>>,
|
global_set: &mut HashSet<ByAddress<Rc<Component>>>,
|
||||||
|
|
|
||||||
15
internal/compiler/passes/collect_libraries.rs
Normal file
15
internal/compiler/passes/collect_libraries.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||||
|
|
||||||
|
//! This pass fills the root component library_imports
|
||||||
|
use crate::object_tree::Document;
|
||||||
|
|
||||||
|
pub fn collect_libraries(doc: &mut Document) {
|
||||||
|
doc.imports.iter().for_each(|import| {
|
||||||
|
if let Some(library_info) = &import.library_info {
|
||||||
|
library_info.exports.iter().for_each(|export_name| {
|
||||||
|
doc.library_exports.insert(export_name.to_string(), library_info.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -23,11 +23,18 @@ pub fn collect_structs_and_enums(doc: &Document) {
|
||||||
doc.visit_all_used_components(|component| collect_types_in_component(component, &mut hash));
|
doc.visit_all_used_components(|component| collect_types_in_component(component, &mut hash));
|
||||||
|
|
||||||
let mut used_types = doc.used_types.borrow_mut();
|
let mut used_types = doc.used_types.borrow_mut();
|
||||||
let used_struct_and_enums = &mut used_types.structs_and_enums;
|
used_types.structs_and_enums = Vec::with_capacity(hash.len());
|
||||||
*used_struct_and_enums = Vec::with_capacity(hash.len());
|
|
||||||
while let Some(next) = hash.iter().next() {
|
while let Some(next) = hash.iter().next() {
|
||||||
// Here, using BTreeMap::pop_first would be great when it is stable
|
|
||||||
let key = next.0.clone();
|
let key = next.0.clone();
|
||||||
|
if let Some(library_info) = doc.library_exports.get(key.as_str()) {
|
||||||
|
// This is a type imported from an external library, just skip it for code generation
|
||||||
|
hash.remove(&key);
|
||||||
|
used_types.library_types_imports.push((key, library_info.clone()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here, using BTreeMap::pop_first would be great when it is stable
|
||||||
|
let used_struct_and_enums = &mut used_types.structs_and_enums;
|
||||||
sort_types(&mut hash, used_struct_and_enums, &key);
|
sort_types(&mut hash, used_struct_and_enums, &key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -447,6 +447,7 @@ fn duplicate_sub_component(
|
||||||
used: component_to_duplicate.used.clone(),
|
used: component_to_duplicate.used.clone(),
|
||||||
private_properties: Default::default(),
|
private_properties: Default::default(),
|
||||||
inherits_popup_window: core::cell::Cell::new(false),
|
inherits_popup_window: core::cell::Cell::new(false),
|
||||||
|
from_library: core::cell::Cell::new(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_component = Rc::new(new_component);
|
let new_component = Rc::new(new_component);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ fn rename_globals(doc: &Document, mut count: u32) {
|
||||||
root.id.clone_from(&g.id);
|
root.id.clone_from(&g.id);
|
||||||
} else if let Some(s) = g.exported_global_names.borrow().first() {
|
} else if let Some(s) = g.exported_global_names.borrow().first() {
|
||||||
root.id = s.to_smolstr();
|
root.id = s.to_smolstr();
|
||||||
|
} else if g.from_library.get() {
|
||||||
|
root.id = format_smolstr!("{}", g.id);
|
||||||
} else {
|
} else {
|
||||||
root.id = format_smolstr!("{}-{}", g.id, count);
|
root.id = format_smolstr!("{}-{}", g.id, count);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,23 @@ pub enum ImportKind {
|
||||||
ModuleReexport(syntax_nodes::ExportsList),
|
ModuleReexport(syntax_nodes::ExportsList),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LibraryInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub package: String,
|
||||||
|
pub module: Option<String>,
|
||||||
|
pub exports: Vec<ExportedName>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ImportedTypes {
|
pub struct ImportedTypes {
|
||||||
pub import_uri_token: SyntaxToken,
|
pub import_uri_token: SyntaxToken,
|
||||||
pub import_kind: ImportKind,
|
pub import_kind: ImportKind,
|
||||||
pub file: String,
|
pub file: String,
|
||||||
|
|
||||||
|
/// `import {Foo, Bar} from "@Foo"` where Foo is an external
|
||||||
|
/// library located in another crate
|
||||||
|
pub library_info: Option<LibraryInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -290,6 +302,7 @@ impl Snapshotter {
|
||||||
custom_fonts: document.custom_fonts.clone(),
|
custom_fonts: document.custom_fonts.clone(),
|
||||||
imports: document.imports.clone(),
|
imports: document.imports.clone(),
|
||||||
exports,
|
exports,
|
||||||
|
library_exports: document.library_exports.clone(),
|
||||||
embedded_file_resources: document.embedded_file_resources.clone(),
|
embedded_file_resources: document.embedded_file_resources.clone(),
|
||||||
#[cfg(feature = "bundle-translations")]
|
#[cfg(feature = "bundle-translations")]
|
||||||
translation_builder: document.translation_builder.clone(),
|
translation_builder: document.translation_builder.clone(),
|
||||||
|
|
@ -369,6 +382,7 @@ impl Snapshotter {
|
||||||
private_properties: RefCell::new(component.private_properties.borrow().clone()),
|
private_properties: RefCell::new(component.private_properties.borrow().clone()),
|
||||||
root_constraints,
|
root_constraints,
|
||||||
root_element,
|
root_element,
|
||||||
|
from_library: core::cell::Cell::new(false),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.keep_alive.push((component.clone(), result.clone()));
|
self.keep_alive.push((component.clone(), result.clone()));
|
||||||
|
|
@ -623,7 +637,15 @@ impl Snapshotter {
|
||||||
Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
|
Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
object_tree::UsedSubTypes { globals, structs_and_enums, sub_components }
|
let library_types_imports = used_types.library_types_imports.clone();
|
||||||
|
let library_global_imports = used_types.library_global_imports.clone();
|
||||||
|
object_tree::UsedSubTypes {
|
||||||
|
globals,
|
||||||
|
structs_and_enums,
|
||||||
|
sub_components,
|
||||||
|
library_types_imports,
|
||||||
|
library_global_imports,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snapshot_popup_window(
|
fn snapshot_popup_window(
|
||||||
|
|
@ -988,11 +1010,56 @@ impl TypeLoader {
|
||||||
imports.push(import);
|
imports.push(import);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies_futures.push(Box::pin(async move {
|
dependencies_futures.push(Box::pin(async move {
|
||||||
let file = import.file.as_str();
|
#[cfg(feature = "experimental-library-module")]
|
||||||
|
let import_file = import.file.clone();
|
||||||
|
#[cfg(feature = "experimental-library-module")]
|
||||||
|
if let Some(maybe_library_import) = import_file.strip_prefix('@') {
|
||||||
|
if let Some(library_name) = std::env::var(format!(
|
||||||
|
"DEP_{}_SLINT_LIBRARY_NAME",
|
||||||
|
maybe_library_import.to_uppercase()
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
{
|
||||||
|
if library_name == maybe_library_import {
|
||||||
|
|
||||||
|
let library_slint_source = std::env::var(format!(
|
||||||
|
"DEP_{}_SLINT_LIBRARY_SOURCE",
|
||||||
|
maybe_library_import.to_uppercase()
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
import.file = library_slint_source;
|
||||||
|
|
||||||
|
if let Some(library_package) = std::env::var(format!(
|
||||||
|
"DEP_{}_SLINT_LIBRARY_PACKAGE",
|
||||||
|
maybe_library_import.to_uppercase()
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
{
|
||||||
|
import.library_info = Some(LibraryInfo {
|
||||||
|
name: library_name,
|
||||||
|
package: library_package,
|
||||||
|
module: std::env::var(format!("DEP_{}_SLINT_LIBRARY_MODULE",
|
||||||
|
maybe_library_import.to_uppercase()
|
||||||
|
)).ok(),
|
||||||
|
exports: Vec::new(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// This should never happen
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
let state: &mut BorrowedTypeLoader<'a> = &mut *state;
|
||||||
|
state.diag.push_error(format!("DEP_{}_SLINT_LIBRARY_PACKAGE is missing for external library import", maybe_library_import.to_uppercase()).into(), &import.import_uri_token.parent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let doc_path = Self::ensure_document_loaded(
|
let doc_path = Self::ensure_document_loaded(
|
||||||
state,
|
state,
|
||||||
file,
|
import.file.as_str(),
|
||||||
Some(import.import_uri_token.clone().into()),
|
Some(import.import_uri_token.clone().into()),
|
||||||
import_stack.clone(),
|
import_stack.clone(),
|
||||||
)
|
)
|
||||||
|
|
@ -1008,7 +1075,7 @@ impl TypeLoader {
|
||||||
let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
|
let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
|
||||||
let Some(doc_path) = doc_path else { return false };
|
let Some(doc_path) = doc_path else { return false };
|
||||||
let mut state = state.borrow_mut();
|
let mut state = state.borrow_mut();
|
||||||
let state = &mut *state;
|
let state: &mut BorrowedTypeLoader<'a> = &mut *state;
|
||||||
let Some(doc) = state.tl.get_document(&doc_path) else {
|
let Some(doc) = state.tl.get_document(&doc_path) else {
|
||||||
panic!("Just loaded document not available")
|
panic!("Just loaded document not available")
|
||||||
};
|
};
|
||||||
|
|
@ -1017,6 +1084,13 @@ impl TypeLoader {
|
||||||
let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
|
let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
|
||||||
if imported_types.peek().is_some() {
|
if imported_types.peek().is_some() {
|
||||||
Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
|
Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
|
||||||
|
|
||||||
|
#[cfg(feature = "experimental-library-module")]
|
||||||
|
if import.library_info.is_some() {
|
||||||
|
import.library_info.as_mut().unwrap().exports = doc.exports.iter().map(|(exported_name, _compo_or_type)| {
|
||||||
|
exported_name.clone()
|
||||||
|
}).collect();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
|
state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
|
||||||
}
|
}
|
||||||
|
|
@ -1549,6 +1623,7 @@ impl TypeLoader {
|
||||||
doc.ImportSpecifier()
|
doc.ImportSpecifier()
|
||||||
.map(|import| {
|
.map(|import| {
|
||||||
let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
|
let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
|
||||||
|
|
||||||
let kind = import
|
let kind = import
|
||||||
.ImportIdentifierList()
|
.ImportIdentifierList()
|
||||||
.map(ImportKind::ImportList)
|
.map(ImportKind::ImportList)
|
||||||
|
|
@ -1587,6 +1662,7 @@ impl TypeLoader {
|
||||||
import_uri_token: import_uri,
|
import_uri_token: import_uri,
|
||||||
import_kind: type_specifier,
|
import_kind: type_specifier,
|
||||||
file: path_to_import,
|
file: path_to_import,
|
||||||
|
library_info: None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
tests/manual/module-builds/app/Cargo.toml
Normal file
20
tests/manual/module-builds/app/Cargo.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "bapp"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Slint Developers <info@slint.dev>"]
|
||||||
|
publish = false
|
||||||
|
license = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
slint = { path = "../../../../api/rs/slint" }
|
||||||
|
blogica = { path = "../blogica" }
|
||||||
|
blogicb = { path = "../blogicb" }
|
||||||
|
rand = "0.8.5"
|
||||||
|
random_word = { version = "0.5.2", features = ["en"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
slint-build = { path = "../../../../api/rs/build", features = ["experimental-module-builds"] }
|
||||||
6
tests/manual/module-builds/app/build.rs
Normal file
6
tests/manual/module-builds/app/build.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
slint_build::compile("ui/app-window.slint").expect("Slint build failed");
|
||||||
|
}
|
||||||
53
tests/manual/module-builds/app/src/main.rs
Normal file
53
tests/manual/module-builds/app/src/main.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use blogica;
|
||||||
|
use blogicb;
|
||||||
|
use random_word;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
slint::include_modules!();
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let ui = AppWindow::new()?;
|
||||||
|
|
||||||
|
let blociga_api = ui.global::<blogica::backend::BLogicAAPI>();
|
||||||
|
blogica::backend::init(&blociga_api);
|
||||||
|
|
||||||
|
let blogicb_api = ui.global::<blogicb::BLogicBAPI>();
|
||||||
|
blogicb::init(&blogicb_api);
|
||||||
|
|
||||||
|
ui.on_update_blogic_data({
|
||||||
|
let ui_handle = ui.as_weak();
|
||||||
|
move || {
|
||||||
|
let ui = ui_handle.upgrade().unwrap();
|
||||||
|
let blogica_api = ui.global::<blogica::backend::BLogicAAPI>();
|
||||||
|
let mut bdata = blogica::backend::BData::default();
|
||||||
|
|
||||||
|
bdata.colors = slint::ModelRc::new(slint::VecModel::from(
|
||||||
|
(1..6)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| {
|
||||||
|
let red = rand::random::<u8>();
|
||||||
|
let green = rand::random::<u8>();
|
||||||
|
let blue = rand::random::<u8>();
|
||||||
|
slint::Color::from_rgb_u8(red, green, blue)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
));
|
||||||
|
|
||||||
|
bdata.codes = slint::ModelRc::new(slint::VecModel::from(
|
||||||
|
(1..6)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| slint::SharedString::from(random_word::get(random_word::Lang::En)))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
));
|
||||||
|
|
||||||
|
blogica_api.invoke_update(bdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
32
tests/manual/module-builds/app/ui/app-window.slint
Normal file
32
tests/manual/module-builds/app/ui/app-window.slint
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { Button, VerticalBox } from "std-widgets.slint";
|
||||||
|
import { BLogicA, BLogicAAPI } from "@BLogicA";
|
||||||
|
import { BLogicB, BLogicBAPI } from "@BLogicB";
|
||||||
|
|
||||||
|
export component AppWindow inherits Window {
|
||||||
|
callback update-blogic-data();
|
||||||
|
|
||||||
|
VerticalBox {
|
||||||
|
BLogicA {}
|
||||||
|
BLogicB {}
|
||||||
|
Button {
|
||||||
|
text: "Crank me up!";
|
||||||
|
clicked => {
|
||||||
|
BLogicBAPI.crank-it({ magic-number: 42, cranks: [ "delta", "alfta", "sorta", "coso", "tokyo", "denia" ]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text:BLogicBAPI.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1s;
|
||||||
|
running: true;
|
||||||
|
triggered => {
|
||||||
|
root.update-blogic-data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/manual/module-builds/blogica/Cargo.toml
Normal file
16
tests/manual/module-builds/blogica/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "blogica"
|
||||||
|
links = "blogica"
|
||||||
|
authors = ["Slint Developers <info@slint.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
slint = { path = "../../../../api/rs/slint" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
slint-build = { path = "../../../../api/rs/build", features = ["experimental-module-builds"] }
|
||||||
10
tests/manual/module-builds/blogica/build.rs
Normal file
10
tests/manual/module-builds/blogica/build.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
fn main() -> Result<(), slint_build::CompileError> {
|
||||||
|
let config =
|
||||||
|
slint_build::CompilerConfiguration::new().as_library("BLogicA").rust_module("backend");
|
||||||
|
slint_build::compile_with_config("ui/blogica.slint", config)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
17
tests/manual/module-builds/blogica/src/lib.rs
Normal file
17
tests/manual/module-builds/blogica/src/lib.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pub mod backend {
|
||||||
|
use slint::SharedString;
|
||||||
|
|
||||||
|
slint::include_modules!();
|
||||||
|
|
||||||
|
pub fn init(blogica_api: &BLogicAAPI) {
|
||||||
|
blogica_api.set_code1(SharedString::from("Important thing"));
|
||||||
|
blogica_api.set_code2(SharedString::from("Another important thing"));
|
||||||
|
blogica_api.set_code3(SharedString::from("Yet another important thing"));
|
||||||
|
blogica_api.set_code4(SharedString::from("One more important thing"));
|
||||||
|
|
||||||
|
blogica_api.set_initialized(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
95
tests/manual/module-builds/blogica/ui/blogica.slint
Normal file
95
tests/manual/module-builds/blogica/ui/blogica.slint
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
export struct BData {
|
||||||
|
colors: [color],
|
||||||
|
codes: [string]
|
||||||
|
}
|
||||||
|
|
||||||
|
export global BLogicAAPI {
|
||||||
|
in property <bool> initialized: false;
|
||||||
|
|
||||||
|
out property <color> color1: #0e3151;
|
||||||
|
out property <color> color2: #107013;
|
||||||
|
out property <color> color3: #8a1624;
|
||||||
|
out property <color> color4: #e4d213;
|
||||||
|
|
||||||
|
in-out property <string> code1: "Important thing";
|
||||||
|
in-out property <string> code2: "Also important thing";
|
||||||
|
in-out property <string> code3: "May be an important thingy";
|
||||||
|
in-out property <string> code4: "Not a important thing";
|
||||||
|
|
||||||
|
public function update(bdata:BData) {
|
||||||
|
if (bdata.colors.length >= 4) {
|
||||||
|
self.color1 = bdata.colors[0];
|
||||||
|
self.color2 = bdata.colors[1];
|
||||||
|
self.color3 = bdata.colors[2];
|
||||||
|
self.color4 = bdata.colors[3];
|
||||||
|
}
|
||||||
|
if (bdata.codes.length >= 4) {
|
||||||
|
self.code1 = bdata.codes[0];
|
||||||
|
self.code2 = bdata.codes[1];
|
||||||
|
self.code3 = bdata.codes[2];
|
||||||
|
self.code4 = bdata.codes[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component BLogicA {
|
||||||
|
private property <bool> api-initialized <=> BLogicAAPI.initialized;
|
||||||
|
width: 600px; height: 200px;
|
||||||
|
Rectangle {
|
||||||
|
x: 0px; y:0px;
|
||||||
|
width: 50%; height: 50%;
|
||||||
|
background: BLogicAAPI.color1;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicAAPI.code1;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
x: root.width / 2; y:0px;
|
||||||
|
width: 50%; height: 50%;
|
||||||
|
background: BLogicAAPI.color2;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicAAPI.code2;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
x: 0px; y:root.height / 2;
|
||||||
|
width: 50%; height: 50%;
|
||||||
|
background: BLogicAAPI.color3;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicAAPI.code3;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
x: root.width / 2; y: root.height / 2;
|
||||||
|
width: 50%; height: 50%;
|
||||||
|
background: BLogicAAPI.color4;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicAAPI.code4;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed api-initialized => {
|
||||||
|
if (self.api-initialized) {
|
||||||
|
debug("BLogicAAPI initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/manual/module-builds/blogicb/Cargo.toml
Normal file
16
tests/manual/module-builds/blogicb/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "blogicb"
|
||||||
|
links = "blogicb"
|
||||||
|
authors = ["Slint Developers <info@slint.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
license = "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
slint = { path = "../../../../api/rs/slint" }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
slint-build = { path = "../../../../api/rs/build", features = ["experimental-module-builds"] }
|
||||||
9
tests/manual/module-builds/blogicb/build.rs
Normal file
9
tests/manual/module-builds/blogicb/build.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
fn main() -> Result<(), slint_build::CompileError> {
|
||||||
|
let config = slint_build::CompilerConfiguration::new().as_library("BLogicB");
|
||||||
|
slint_build::compile_with_config("ui/blogicb.slint", config)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
20
tests/manual/module-builds/blogicb/src/lib.rs
Normal file
20
tests/manual/module-builds/blogicb/src/lib.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use slint::SharedString;
|
||||||
|
|
||||||
|
slint::include_modules!();
|
||||||
|
|
||||||
|
pub fn init(blogicb_api: &BLogicBAPI) {
|
||||||
|
blogicb_api.set_crank1(SharedString::from("1"));
|
||||||
|
blogicb_api.set_crank2(SharedString::from("2"));
|
||||||
|
blogicb_api.set_crank3(SharedString::from("3"));
|
||||||
|
blogicb_api.set_crank4(SharedString::from("5"));
|
||||||
|
blogicb_api.set_crank5(SharedString::from("7"));
|
||||||
|
blogicb_api.set_crank6(SharedString::from("11"));
|
||||||
|
|
||||||
|
// TODO: if BLogicBAPI can be a shared reference, so we can connect callbacks here
|
||||||
|
// and pass / move the reference to the closures
|
||||||
|
|
||||||
|
blogicb_api.set_initialized(true);
|
||||||
|
}
|
||||||
123
tests/manual/module-builds/blogicb/ui/blogicb.slint
Normal file
123
tests/manual/module-builds/blogicb/ui/blogicb.slint
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
export struct CrankData {
|
||||||
|
magic-number: int,
|
||||||
|
cranks: [string]
|
||||||
|
}
|
||||||
|
|
||||||
|
export global BLogicBAPI {
|
||||||
|
in property <bool> initialized: false;
|
||||||
|
|
||||||
|
in property <color> color1: #6c839a;
|
||||||
|
in property <color> color2: #4b5c72;
|
||||||
|
in property <color> color3: #185151;
|
||||||
|
in property <color> color4: #464969;
|
||||||
|
in property <color> color5: #1b1143;
|
||||||
|
in property <color> color6: #203158;
|
||||||
|
|
||||||
|
in-out property <string> crank1: "1";
|
||||||
|
in-out property <string> crank2: "2";
|
||||||
|
in-out property <string> crank3: "3";
|
||||||
|
in-out property <string> crank4: "5";
|
||||||
|
in-out property <string> crank5: "7";
|
||||||
|
in-out property <string> crank6: "11";
|
||||||
|
|
||||||
|
out property <string> status;
|
||||||
|
|
||||||
|
public function crank-it(crank-data:CrankData) {
|
||||||
|
if (crank-data.magic-number == 42) {
|
||||||
|
self.status = "The answer to life, the universe and everything";
|
||||||
|
} else {
|
||||||
|
self.status = "Just a number";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crank-data.cranks.length >= 6) {
|
||||||
|
self.crank1 = crank-data.cranks[0];
|
||||||
|
self.crank2 = crank-data.cranks[1];
|
||||||
|
self.crank3 = crank-data.cranks[2];
|
||||||
|
self.crank4 = crank-data.cranks[3];
|
||||||
|
self.crank5 = crank-data.cranks[4];
|
||||||
|
self.crank6 = crank-data.cranks[5];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export component BLogicB {
|
||||||
|
private property <bool> api-initialized <=> BLogicBAPI.initialized;
|
||||||
|
width: 600px; height: 240px;
|
||||||
|
GridLayout {
|
||||||
|
Row {
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color1;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank1;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color2;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank2;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color3;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank3;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color4;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank4;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color5;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank5;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
background: BLogicBAPI.color6;
|
||||||
|
Text {
|
||||||
|
text <=> BLogicBAPI.crank6;
|
||||||
|
color: white;
|
||||||
|
font-size: 24px;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changed api-initialized => {
|
||||||
|
if (self.api-initialized) {
|
||||||
|
debug("BLogicBAPI initialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue