mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-02 04:48:27 +00:00
Rust codegen that forward to the interpreter (live-reload)
When the SLINT_LIVE_RELOAD env variable is set, generate a component that will forward everything to the interpreter instead of generating everything. Fix running the test driver rust with the SLINT_LIVE_RELOAD ``` SLINT_LIVE_RELOAD=1 cargo test -p test-driver-rust --all-features --features=slint/live-reload ```
This commit is contained in:
parent
ec12f22653
commit
cc9c573c9a
6 changed files with 545 additions and 40 deletions
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
key: x-v3
|
||||
- name: Run tests (not qt)
|
||||
- name: Run tests
|
||||
run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} ${{ matrix.maybe_exclude_bevy }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python -- --skip=_qt::t
|
||||
env:
|
||||
SLINT_CREATE_SCREENSHOTS: 1
|
||||
|
|
@ -102,6 +102,11 @@ jobs:
|
|||
- name: Run tests (qt)
|
||||
run: cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} ${{ matrix.maybe_exclude_bevy }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python --bin test-driver-rust -- _qt --test-threads=1
|
||||
shell: bash
|
||||
- name: live-reload test
|
||||
env:
|
||||
SLINT_LIVE_RELOAD: 1
|
||||
run: cargo test --verbose --features=build-time,slint/live-reload -p test-driver-rust -- --skip=_qt::t
|
||||
shell: bash
|
||||
- name: Archive screenshots after failed tests
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ pub fn compile_with_config(
|
|||
println!("cargo:rerun-if-env-changed=SLINT_ASSET_SECTION");
|
||||
println!("cargo:rerun-if-env-changed=SLINT_EMBED_RESOURCES");
|
||||
println!("cargo:rerun-if-env-changed=SLINT_EMIT_DEBUG_INFO");
|
||||
println!("cargo:rerun-if-env-changed=SLINT_LIVE_RELOAD");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-env=SLINT_INCLUDE_GENERATED={}",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ pub mod cpp;
|
|||
|
||||
#[cfg(feature = "rust")]
|
||||
pub mod rust;
|
||||
#[cfg(feature = "rust")]
|
||||
pub mod rust_live_reload;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum OutputFormat {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ struct RustGeneratorContext {
|
|||
type EvaluationContext<'a> = llr_EvaluationContext<'a, RustGeneratorContext>;
|
||||
type ParentCtx<'a> = llr_ParentCtx<'a, RustGeneratorContext>;
|
||||
|
||||
fn ident(ident: &str) -> proc_macro2::Ident {
|
||||
pub fn ident(ident: &str) -> proc_macro2::Ident {
|
||||
if ident.contains('-') {
|
||||
format_ident!("r#{}", ident.replace('-', "_"))
|
||||
} else {
|
||||
|
|
@ -76,7 +76,7 @@ impl quote::ToTokens for crate::embedded_resources::PixelFormat {
|
|||
}
|
||||
}
|
||||
|
||||
fn rust_primitive_type(ty: &Type) -> Option<proc_macro2::TokenStream> {
|
||||
pub fn rust_primitive_type(ty: &Type) -> Option<proc_macro2::TokenStream> {
|
||||
match ty {
|
||||
Type::Void => Some(quote!(())),
|
||||
Type::Int32 => Some(quote!(i32)),
|
||||
|
|
@ -155,22 +155,12 @@ pub fn generate(
|
|||
doc: &Document,
|
||||
compiler_config: &CompilerConfiguration,
|
||||
) -> std::io::Result<TokenStream> {
|
||||
let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = doc
|
||||
.used_types
|
||||
.borrow()
|
||||
.structs_and_enums
|
||||
.iter()
|
||||
.filter_map(|ty| match ty {
|
||||
Type::Struct(s) => match s.as_ref() {
|
||||
Struct { fields, name: Some(name), node: Some(_), rust_attributes } => {
|
||||
Some((ident(name), generate_struct(name, fields, rust_attributes)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Type::Enumeration(en) => Some((ident(&en.name), generate_enum(en))),
|
||||
_ => None,
|
||||
})
|
||||
.unzip();
|
||||
if std::env::var("SLINT_LIVE_RELOAD").is_ok() {
|
||||
return super::rust_live_reload::generate(doc, compiler_config);
|
||||
}
|
||||
|
||||
let (structs_and_enums_ids, inner_module) =
|
||||
generate_types(&doc.used_types.borrow().structs_and_enums);
|
||||
|
||||
let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?;
|
||||
|
||||
|
|
@ -189,13 +179,6 @@ pub fn generate(
|
|||
let popup_menu =
|
||||
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
|
||||
|
||||
let version_check = format_ident!(
|
||||
"VersionCheck_{}_{}_{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH"),
|
||||
);
|
||||
|
||||
let globals = llr
|
||||
.globals
|
||||
.iter_enumerated()
|
||||
|
|
@ -208,7 +191,7 @@ pub fn generate(
|
|||
let compo_ids = llr.public_components.iter().map(|c| ident(&c.name));
|
||||
|
||||
let resource_symbols = generate_resources(doc);
|
||||
let named_exports = generate_named_exports(doc);
|
||||
let named_exports = generate_named_exports(&doc.exports);
|
||||
// The inner module was meant to be internal private, but projects have been reaching into it
|
||||
// so we can't change the name of this module
|
||||
let generated_mod = doc
|
||||
|
|
@ -222,15 +205,8 @@ pub fn generate(
|
|||
let translations = llr.translations.as_ref().map(|t| (generate_translations(t, &llr)));
|
||||
|
||||
Ok(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
|
||||
mod #generated_mod {
|
||||
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)*
|
||||
#inner_module
|
||||
#(#globals)*
|
||||
#(#sub_compos)*
|
||||
#popup_menu
|
||||
|
|
@ -238,7 +214,6 @@ pub fn generate(
|
|||
#shared_globals
|
||||
#(#resource_symbols)*
|
||||
#translations
|
||||
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
|
||||
}
|
||||
#[allow(unused_imports)]
|
||||
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
|
||||
|
|
@ -247,6 +222,45 @@ pub fn generate(
|
|||
})
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = used_types
|
||||
.iter()
|
||||
.filter_map(|ty| match ty {
|
||||
Type::Struct(s) => match s.as_ref() {
|
||||
Struct { fields, name: Some(name), node: Some(_), rust_attributes } => {
|
||||
Some((ident(name), generate_struct(name, fields, rust_attributes)))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Type::Enumeration(en) => Some((ident(&en.name), generate_enum(en))),
|
||||
_ => None,
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let version_check = format_ident!(
|
||||
"VersionCheck_{}_{}_{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH"),
|
||||
);
|
||||
|
||||
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)*
|
||||
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
|
||||
};
|
||||
|
||||
(structs_and_enums_ids, inner_module)
|
||||
}
|
||||
|
||||
fn generate_public_component(
|
||||
llr: &llr::PublicComponent,
|
||||
unit: &llr::CompilationUnit,
|
||||
|
|
@ -3442,8 +3456,8 @@ fn generate_resources(doc: &Document) -> Vec<TokenStream> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn generate_named_exports(doc: &Document) -> Vec<TokenStream> {
|
||||
doc.exports
|
||||
pub fn generate_named_exports(exports: &crate::object_tree::Exports) -> Vec<TokenStream> {
|
||||
exports
|
||||
.iter()
|
||||
.filter_map(|export| match &export.1 {
|
||||
Either::Left(component) if !component.is_global() => {
|
||||
|
|
|
|||
469
internal/compiler/generator/rust_live_reload.rs
Normal file
469
internal/compiler/generator/rust_live_reload.rs
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
// 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
|
||||
|
||||
use super::rust::{ident, rust_primitive_type};
|
||||
use crate::langtype::{Struct, Type};
|
||||
use crate::llr;
|
||||
use crate::object_tree::Document;
|
||||
use crate::CompilerConfiguration;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
/// Generate the rust code for the given component.
|
||||
pub fn generate(
|
||||
doc: &Document,
|
||||
compiler_config: &CompilerConfiguration,
|
||||
) -> std::io::Result<TokenStream> {
|
||||
let (structs_and_enums_ids, inner_module) =
|
||||
super::rust::generate_types(&doc.used_types.borrow().structs_and_enums);
|
||||
|
||||
let type_value_conversions =
|
||||
generate_value_conversions(&doc.used_types.borrow().structs_and_enums);
|
||||
|
||||
let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?;
|
||||
|
||||
if llr.public_components.is_empty() {
|
||||
return Ok(Default::default());
|
||||
}
|
||||
|
||||
let main_file = doc
|
||||
.node
|
||||
.as_ref()
|
||||
.ok_or_else(|| std::io::Error::other("Cannot determine path of the main file"))?
|
||||
.source_file
|
||||
.path()
|
||||
.to_string_lossy();
|
||||
|
||||
let public_components = llr
|
||||
.public_components
|
||||
.iter()
|
||||
.map(|p| generate_public_component(p, compiler_config, &main_file));
|
||||
|
||||
let globals = llr
|
||||
.globals
|
||||
.iter_enumerated()
|
||||
.filter(|(_, glob)| glob.must_generate())
|
||||
.map(|(_, glob)| generate_global(glob, &llr));
|
||||
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)))
|
||||
});
|
||||
let compo_ids = llr.public_components.iter().map(|c| ident(&c.name));
|
||||
|
||||
let named_exports = super::rust::generate_named_exports(&doc.exports);
|
||||
// The inner module was meant to be internal private, but projects have been reaching into it
|
||||
// so we can't change the name of this module
|
||||
let generated_mod = doc
|
||||
.last_exported_component()
|
||||
.map(|c| format_ident!("slint_generated{}", ident(&c.id)))
|
||||
.unwrap_or_else(|| format_ident!("slint_generated"));
|
||||
|
||||
Ok(quote! {
|
||||
mod #generated_mod {
|
||||
#inner_module
|
||||
#(#globals)*
|
||||
#(#public_components)*
|
||||
#type_value_conversions
|
||||
}
|
||||
#[allow(unused_imports)]
|
||||
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
|
||||
#[allow(unused_imports)]
|
||||
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_public_component(
|
||||
llr: &llr::PublicComponent,
|
||||
compiler_config: &CompilerConfiguration,
|
||||
main_file: &str,
|
||||
) -> TokenStream {
|
||||
let public_component_id = ident(&llr.name);
|
||||
let component_name = llr.name.as_str();
|
||||
|
||||
let mut property_and_callback_accessors: Vec<TokenStream> = vec![];
|
||||
for p in &llr.public_properties {
|
||||
let prop_name = p.name.as_str();
|
||||
let prop_ident = ident(&p.name);
|
||||
|
||||
if let Type::Callback(callback) = &p.ty {
|
||||
let callback_args =
|
||||
callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
|
||||
let return_type = rust_primitive_type(&callback.return_type).unwrap();
|
||||
let args_name =
|
||||
(0..callback.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
|
||||
let caller_ident = format_ident!("invoke_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
|
||||
self.0.invoke(#prop_name, &[#(#args_name.into(),)*])
|
||||
.unwrap_or_else(|e| panic!("Cannot invoke callback {}::{}: {e}", #component_name, #prop_name))
|
||||
.try_into().expect("Invalid return type")
|
||||
}
|
||||
));
|
||||
let on_ident = format_ident!("on_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) {
|
||||
let f = ::core::cell::RefCell::new(f);
|
||||
self.0.set_callback(#prop_name, move |values| {
|
||||
let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #component_name, #prop_name) };
|
||||
(*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #component_name, #prop_name)),)*).into()
|
||||
}).unwrap_or_else(|e| panic!("Cannot set callback {}::{}: {e}", #component_name, #prop_name))
|
||||
}
|
||||
));
|
||||
} else if let Type::Function(function) = &p.ty {
|
||||
let callback_args =
|
||||
function.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
|
||||
let return_type = rust_primitive_type(&function.return_type).unwrap();
|
||||
let args_name =
|
||||
(0..function.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
|
||||
let caller_ident = format_ident!("invoke_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
|
||||
self.0.invoke(#prop_name, &[#(#args_name.into(),)*])
|
||||
.unwrap_or_else(|e| panic!("Cannot invoke callback {}::{}: {e}", #component_name, #prop_name))
|
||||
.try_into().expect("Invalid return type")
|
||||
}
|
||||
));
|
||||
} else {
|
||||
let rust_property_type = rust_primitive_type(&p.ty).unwrap();
|
||||
let convert_to_value = convert_to_value_fn(&p.ty);
|
||||
let convert_from_value = convert_from_value_fn(&p.ty);
|
||||
|
||||
let getter_ident = format_ident!("get_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #getter_ident(&self) -> #rust_property_type {
|
||||
#[allow(unused_imports)]
|
||||
#convert_from_value(
|
||||
self.0.get_property(#prop_name)
|
||||
.unwrap_or_else(|e| panic!("Cannot get property {}::{} - {e}", #component_name, #prop_name))
|
||||
).expect("Invalid property type")
|
||||
}
|
||||
));
|
||||
|
||||
let setter_ident = format_ident!("set_{}", prop_ident);
|
||||
if !p.read_only {
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #setter_ident(&self, value: #rust_property_type) {
|
||||
self.0.set_property(#prop_name, #convert_to_value(value))
|
||||
.unwrap_or_else(|e| panic!("Cannot set property {}::{} - {e}", #component_name, #prop_name));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)] fn #setter_ident(&self, _read_only_property : ()) { }
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let include_paths = compiler_config.include_paths.iter().map(|p| p.to_string_lossy());
|
||||
let library_paths = compiler_config.library_paths.iter().map(|(n, p)| {
|
||||
let p = p.to_string_lossy();
|
||||
quote!((#n.to_string(), #p.into()))
|
||||
});
|
||||
let style = compiler_config.style.iter();
|
||||
|
||||
quote!(
|
||||
pub struct #public_component_id(slint_interpreter::ComponentInstance);
|
||||
|
||||
impl #public_component_id {
|
||||
pub fn new() -> sp::Result<Self, slint::PlatformError> {
|
||||
let mut compiler = slint_interpreter::Compiler::default();
|
||||
compiler.set_include_paths([#(#include_paths.into()),*].into_iter().collect());
|
||||
compiler.set_library_paths([#(#library_paths.into()),*].into_iter().collect());
|
||||
#(compiler.set_style(#style.to_string());)*
|
||||
|
||||
let mut future = ::core::pin::pin!(compiler.build_from_path(#main_file));
|
||||
let mut cx = ::std::task::Context::from_waker(::std::task::Waker::noop());
|
||||
let ::std::task::Poll::Ready(result) = ::std::future::Future::poll(future.as_mut(), &mut cx) else { unreachable!("Compiler returned Pending") };
|
||||
result.print_diagnostics();
|
||||
assert!(!result.has_errors(), "Was not able to compile the file");
|
||||
let definition = result.component(#component_name).expect("Cannot open component");
|
||||
let instance = definition.create()?;
|
||||
sp::Ok(Self(instance))
|
||||
}
|
||||
|
||||
#(#property_and_callback_accessors)*
|
||||
}
|
||||
|
||||
impl slint::ComponentHandle for #public_component_id {
|
||||
type WeakInner = slint_interpreter::Weak<slint_interpreter::ComponentInstance>;
|
||||
fn as_weak(&self) -> slint_interpreter::Weak<Self> {
|
||||
slint::Weak::new(slint_interpreter::ComponentHandle::as_weak(&self.0))
|
||||
}
|
||||
|
||||
fn clone_strong(&self) -> Self {
|
||||
Self(slint::ComponentHandle::clone_strong(&self.0))
|
||||
}
|
||||
|
||||
fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> sp::Option<Self> {
|
||||
sp::Some(Self(inner.upgrade()?))
|
||||
}
|
||||
|
||||
fn run(&self) -> ::core::result::Result<(), slint::PlatformError> {
|
||||
slint::ComponentHandle::run(&self.0)
|
||||
}
|
||||
|
||||
fn show(&self) -> ::core::result::Result<(), slint::PlatformError> {
|
||||
slint::ComponentHandle::show(&self.0)
|
||||
}
|
||||
|
||||
fn hide(&self) -> ::core::result::Result<(), slint::PlatformError> {
|
||||
slint::ComponentHandle::hide(&self.0)
|
||||
}
|
||||
|
||||
fn window(&self) -> &slint::Window {
|
||||
slint::ComponentHandle::window(&self.0)
|
||||
}
|
||||
|
||||
fn global<'a, T: slint::Global<'a, Self>>(&'a self) -> T {
|
||||
T::get(&self)
|
||||
}
|
||||
}
|
||||
|
||||
/// This is needed for the the internal tests (eg `slint_testing::send_keyboard_string_sequence`)
|
||||
impl<X> ::core::convert::From<#public_component_id> for sp::VRc<sp::ItemTreeVTable, X>
|
||||
where Self : ::core::convert::From<sp::live_reload::ComponentInstance>
|
||||
{
|
||||
fn from(value: #public_component_id) -> Self {
|
||||
Self::from(slint::ComponentHandle::clone_strong(value.0.borrow().instance()))
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
fn generate_global(global: &llr::GlobalComponent, root: &llr::CompilationUnit) -> TokenStream {
|
||||
if !global.exported {
|
||||
return quote!();
|
||||
}
|
||||
let global_name = global.name.as_str();
|
||||
let mut property_and_callback_accessors: Vec<TokenStream> = vec![];
|
||||
for p in &global.public_properties {
|
||||
let prop_name = p.name.as_str();
|
||||
let prop_ident = ident(&p.name);
|
||||
|
||||
if let Type::Callback(callback) = &p.ty {
|
||||
let callback_args =
|
||||
callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
|
||||
let return_type = rust_primitive_type(&callback.return_type).unwrap();
|
||||
let args_name =
|
||||
(0..callback.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
|
||||
let caller_ident = format_ident!("invoke_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
|
||||
self.0.invoke_global(#global_name, #prop_name, &[#(#args_name.into(),)*])
|
||||
.unwrap_or_else(|e| panic!("Cannot invoke callback {}::{}: {e}", #global_name, #prop_name))
|
||||
.try_into().expect("Invalid return type")
|
||||
}
|
||||
));
|
||||
let on_ident = format_ident!("on_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) {
|
||||
let f = ::core::cell::RefCell::new(f);
|
||||
self.0.set_global_callback(#global_name, #prop_name, move |values| {
|
||||
let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #global_name, #prop_name) };
|
||||
(*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #global_name, #prop_name)),)*).into()
|
||||
}).unwrap_or_else(|e| panic!("Cannot set callback {}::{}: {e}", #global_name, #prop_name))
|
||||
}
|
||||
));
|
||||
} else if let Type::Function(function) = &p.ty {
|
||||
let callback_args =
|
||||
function.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
|
||||
let return_type = rust_primitive_type(&function.return_type).unwrap();
|
||||
let args_name =
|
||||
(0..function.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
|
||||
let caller_ident = format_ident!("invoke_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
|
||||
self.0.invoke_global(#global_name, #prop_name, &[#(#args_name.into(),)*])
|
||||
.unwrap_or_else(|e| panic!("Cannot invoke callback {}::{}: {e}", #global_name, #prop_name))
|
||||
.try_into().expect("Invalid return type")
|
||||
}
|
||||
));
|
||||
} else {
|
||||
let rust_property_type = rust_primitive_type(&p.ty).unwrap();
|
||||
let convert_to_value = convert_to_value_fn(&p.ty);
|
||||
let convert_from_value = convert_from_value_fn(&p.ty);
|
||||
|
||||
let getter_ident = format_ident!("get_{}", prop_ident);
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #getter_ident(&self) -> #rust_property_type {
|
||||
#convert_from_value(
|
||||
self.0.get_global_property(#global_name, #prop_name)
|
||||
.unwrap_or_else(|e| panic!("Cannot get property {}::{} - {e}", #global_name, #prop_name))
|
||||
).expect("Invalid property type")
|
||||
}
|
||||
));
|
||||
|
||||
let setter_ident = format_ident!("set_{}", prop_ident);
|
||||
if !p.read_only {
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)]
|
||||
pub fn #setter_ident(&self, value: #rust_property_type) {
|
||||
self.0.set_global_property(#global_name, #prop_name, #convert_to_value(value))
|
||||
.unwrap_or_else(|e| panic!("Cannot set property {}::{} - {e}", #global_name, #prop_name));
|
||||
}
|
||||
));
|
||||
} else {
|
||||
property_and_callback_accessors.push(quote!(
|
||||
#[allow(dead_code)] fn #setter_ident(&self, _read_only_property : ()) { }
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let public_component_id = ident(&global.name);
|
||||
let aliases = global.aliases.iter().map(|name| ident(name));
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote!(
|
||||
#[allow(unused)]
|
||||
pub struct #public_component_id<'a>(&'a slint_interpreter::ComponentInstance);
|
||||
|
||||
impl<'a> #public_component_id<'a> {
|
||||
#(#property_and_callback_accessors)*
|
||||
}
|
||||
#(pub type #aliases<'a> = #public_component_id<'a>;)*
|
||||
#(#getters)*
|
||||
)
|
||||
}
|
||||
|
||||
/// returns a function that converts the type to a Value.
|
||||
/// Normally, that would simply be `xxx.into()`, but for anonymous struct, we need an explicit conversion
|
||||
fn convert_to_value_fn(ty: &Type) -> TokenStream {
|
||||
match ty {
|
||||
Type::Struct(s) if s.name.is_none() => {
|
||||
// anonymous struct is mapped to a tuple
|
||||
let names = s.fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
|
||||
let fields = names.iter().map(|k| ident(k)).collect::<Vec<_>>();
|
||||
quote!((|(#(#fields,)*)| {
|
||||
slint_interpreter::Value::Struct([#((#names.to_string(), slint_interpreter::Value::from(#fields)),)*].into_iter().collect())
|
||||
}))
|
||||
}
|
||||
Type::Array(a) if matches!(a.as_ref(), Type::Struct(s) if s.name.is_none()) => {
|
||||
let conf_fn = convert_to_value_fn(a.as_ref());
|
||||
quote!((|model: sp::ModelRc<_>| -> slint_interpreter::Value {
|
||||
slint_interpreter::Value::Model(sp::ModelRc::new(model.map(#conf_fn)))
|
||||
}))
|
||||
}
|
||||
_ => quote!(::core::convert::From::from),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a function that converts a Value to the type.
|
||||
/// Normally, that would simply be `xxx.try_into()`, but for anonymous struct, we need an explicit conversion
|
||||
fn convert_from_value_fn(ty: &Type) -> TokenStream {
|
||||
match ty {
|
||||
Type::Struct(s) if s.name.is_none() => {
|
||||
let names = s.fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
|
||||
// anonymous struct is mapped to a tuple
|
||||
quote!((|v: slint_interpreter::Value| -> sp::Result<_, ()> {
|
||||
let slint_interpreter::Value::Struct(s) = v else { return sp::Err(()) };
|
||||
sp::Ok((#(s.get_field(#names).ok_or(())?.clone().try_into().map_err(|_|())?,)*))
|
||||
}))
|
||||
}
|
||||
Type::Array(a) if matches!(a.as_ref(), Type::Struct(s) if s.name.is_none()) => {
|
||||
let conf_fn = convert_from_value_fn(a.as_ref());
|
||||
quote!((|v: slint_interpreter::Value| -> sp::Result<_, ()> {
|
||||
let slint_interpreter::Value::Model(model) = v else { return sp::Err(()) };
|
||||
sp::Ok(sp::ModelRc::new(model.map(|x| #conf_fn(x).unwrap_or_default())))
|
||||
}))
|
||||
}
|
||||
_ => quote!(::core::convert::TryFrom::try_from),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_value_conversions(used_types: &[Type]) -> TokenStream {
|
||||
let r = used_types
|
||||
.iter()
|
||||
.filter_map(|ty| match ty {
|
||||
Type::Struct(s) => match s.as_ref() {
|
||||
Struct { fields, name: Some(name), node: Some(_), .. } => {
|
||||
let ty = ident(name);
|
||||
let convert_to_value = fields.values().map(convert_to_value_fn);
|
||||
let convert_from_value = fields.values().map(convert_from_value_fn);
|
||||
let field_names = fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
|
||||
let fields = field_names.iter().map(|k| ident(k)).collect::<Vec<_>>();
|
||||
Some(quote!{
|
||||
impl From<#ty> for slint_interpreter::Value {
|
||||
fn from(_value: #ty) -> Self {
|
||||
Self::Struct([#((#field_names.to_string(), #convert_to_value(_value.#fields)),)*].into_iter().collect())
|
||||
}
|
||||
}
|
||||
impl TryFrom<slint_interpreter::Value> for #ty {
|
||||
type Error = ();
|
||||
fn try_from(v: slint_interpreter::Value) -> sp::Result<Self, ()> {
|
||||
match v {
|
||||
slint_interpreter::Value::Struct(_x) => {
|
||||
sp::Ok(Self {
|
||||
#(#fields: #convert_from_value(_x.get_field(#field_names).ok_or(())?.clone()).map_err(|_|())?,)*
|
||||
})
|
||||
}
|
||||
_ => sp::Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
Type::Enumeration(en) => {
|
||||
let name = en.name.as_str();
|
||||
let ty = ident(&en.name);
|
||||
let vals = en.values.iter().map(|v| ident(&crate::generator::to_pascal_case(v))).collect::<Vec<_>>();
|
||||
let val_names = en.values.iter().map(|v| v.as_str()).collect::<Vec<_>>();
|
||||
|
||||
Some(quote!{
|
||||
impl From<#ty> for slint_interpreter::Value {
|
||||
fn from(v: #ty) -> Self {
|
||||
fn to_string(v: #ty) -> String {
|
||||
match v {
|
||||
#(#ty::#vals => #val_names.to_string(),)*
|
||||
}
|
||||
}
|
||||
Self::EnumerationValue(#name.to_owned(), to_string(v))
|
||||
}
|
||||
}
|
||||
impl TryFrom<slint_interpreter::Value> for #ty {
|
||||
type Error = ();
|
||||
fn try_from(v: slint_interpreter::Value) -> sp::Result<Self, ()> {
|
||||
match v {
|
||||
slint_interpreter::Value::EnumerationValue(enumeration, value) => {
|
||||
if enumeration != #name {
|
||||
return sp::Err(());
|
||||
}
|
||||
fn from_str(value: &str) -> sp::Result<#ty, ()> {
|
||||
match value {
|
||||
#(#val_names => Ok(#ty::#vals),)*
|
||||
_ => sp::Err(()),
|
||||
}
|
||||
}
|
||||
from_str(value.as_str()).map_err(|_| ())
|
||||
}
|
||||
_ => sp::Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
_ => None,
|
||||
});
|
||||
quote!(#(#r)*)
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ use std::io::{BufWriter, Write};
|
|||
use std::path::Path;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let live_reload = std::env::var("SLINT_LIVE_RELOAD").is_ok();
|
||||
|
||||
let mut generated_file = BufWriter::new(std::fs::File::create(
|
||||
Path::new(&std::env::var_os("OUT_DIR").unwrap()).join("generated.rs"),
|
||||
)?);
|
||||
|
|
@ -19,8 +21,18 @@ fn main() -> std::io::Result<()> {
|
|||
let source = std::fs::read_to_string(&testcase.absolute_path)?;
|
||||
let ignored = if testcase.is_ignored("rust") {
|
||||
"#[ignore = \"testcase ignored for rust\"]"
|
||||
} else if cfg!(not(feature = "build-time")) && source.contains("//bundle-translations") {
|
||||
} else if (cfg!(not(feature = "build-time")) || live_reload)
|
||||
&& source.contains("//bundle-translations")
|
||||
{
|
||||
"#[ignore = \"translation bundle not working with the macro\"]"
|
||||
} else if live_reload && source.contains("ComponentContainer") {
|
||||
"#[ignore = \"ComponentContainer doesn't work with the interpreter\"]"
|
||||
} else if live_reload && source.contains("#3464") {
|
||||
"#[ignore = \"issue #3464 not fixed with the interpreter\"]"
|
||||
} else if live_reload && module_name.contains("widgets_menubar") {
|
||||
"#[ignore = \"issue #8454 causes crashes\"]"
|
||||
} else if live_reload && module_name.contains("write_to_model") {
|
||||
"#[ignore = \"Interpreted model don't forward to underlying models for anonymous structs\"]"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
|
@ -58,7 +70,9 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
// By default resources are embedded. The WASM example builds provide test coverage for that. This switch
|
||||
// provides test coverage for the non-embedding case, compiling tests without embedding the images.
|
||||
println!("cargo:rustc-env=SLINT_EMBED_RESOURCES=false");
|
||||
if !live_reload {
|
||||
println!("cargo:rustc-env=SLINT_EMBED_RESOURCES=false");
|
||||
}
|
||||
|
||||
//Make sure to use a consistent style
|
||||
println!("cargo:rustc-env=SLINT_STYLE=fluent");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue