diff --git a/api/rs/build/lib.rs b/api/rs/build/lib.rs index 5180714ccf2..f6e54dd1aa8 100644 --- a/api/rs/build/lib.rs +++ b/api/rs/build/lib.rs @@ -162,6 +162,14 @@ impl CompilerConfiguration { }; Self { config } } + + /// The generated component will take an argument of the type in its new() function. + /// The component will aldo have a have a `user_data()` function that returns a reference to it + #[must_use] + pub fn with_user_data_type(mut self, type_name: &str) -> CompilerConfiguration { + self.config.user_data_type = Some(type_name.to_string()); + self + } } /// Error returned by the `compile` function @@ -374,8 +382,11 @@ pub fn compile_with_config( let syntax_node = syntax_node.expect("diags contained no compilation errors"); // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future - let (doc, diag) = - spin_on::spin_on(i_slint_compiler::compile_syntax_node(syntax_node, diag, compiler_config)); + let (doc, diag) = spin_on::spin_on(i_slint_compiler::compile_syntax_node( + syntax_node, + diag, + compiler_config.clone(), + )); if diag.has_error() { let vec = diag.to_string_vec(); @@ -393,7 +404,7 @@ pub fn compile_with_config( let file = std::fs::File::create(&output_file_path).map_err(CompileError::SaveError)?; let mut code_formatter = CodeFormatter::new(BufWriter::new(file)); - let generated = i_slint_compiler::generator::rust::generate(&doc); + let generated = i_slint_compiler::generator::rust::generate(&doc, &compiler_config); for x in &diag.all_loaded_files { if x.is_absolute() { diff --git a/api/rs/macros/lib.rs b/api/rs/macros/lib.rs index f29f68e74d6..31e5facf4f3 100644 --- a/api/rs/macros/lib.rs +++ b/api/rs/macros/lib.rs @@ -381,13 +381,13 @@ pub fn slint(stream: TokenStream) -> TokenStream { compiler_config.include_paths = include_paths; compiler_config.library_paths = library_paths; let (root_component, diag) = - spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); + spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config.clone())); //println!("{:#?}", tree); if diag.has_error() { return diag.report_macro_diagnostic(&tokens); } - let mut result = generator::rust::generate(&root_component); + let mut result = generator::rust::generate(&root_component, &compiler_config); // Make sure to recompile if any of the external files changes let reload = diag diff --git a/examples/printerdemo/rust/build.rs b/examples/printerdemo/rust/build.rs index d6fb5ac4061..0c93357bc86 100644 --- a/examples/printerdemo/rust/build.rs +++ b/examples/printerdemo/rust/build.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT fn main() { - slint_build::compile("../ui/printerdemo.slint").unwrap(); - slint_build::print_rustc_flags().unwrap(); + let config = + slint_build::CompilerConfiguration::new().with_user_data_type("crate::PrinterQueueData"); + slint_build::compile_with_config("../ui/printerdemo.slint", config).unwrap(); } diff --git a/examples/printerdemo/rust/main.rs b/examples/printerdemo/rust/main.rs index 6b5bf08bca7..e0022b5ac95 100644 --- a/examples/printerdemo/rust/main.rs +++ b/examples/printerdemo/rust/main.rs @@ -17,7 +17,7 @@ fn current_time() -> slint::SharedString { return "".into(); } -struct PrinterQueueData { +pub struct PrinterQueueData { data: Rc>, print_progress_timer: slint::Timer, } @@ -43,7 +43,11 @@ pub fn main() { #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); - let main_window = MainWindow::new().unwrap(); + let printer_queue = PrinterQueueData { + data: Rc::new(slint::VecModel::default()), + print_progress_timer: Default::default(), + }; + let main_window = MainWindow::new(printer_queue).unwrap(); main_window.set_ink_levels( [ InkLevel { color: slint::Color::from_rgb_u8(0, 255, 255), level: 0.40 }, @@ -56,45 +60,44 @@ pub fn main() { let default_queue: Vec = main_window.global::().get_printer_queue().iter().collect(); - let printer_queue = Rc::new(PrinterQueueData { - data: Rc::new(slint::VecModel::from(default_queue)), - print_progress_timer: Default::default(), - }); - main_window.global::().set_printer_queue(printer_queue.data.clone().into()); + main_window.user_data().data.set_vec(default_queue); + main_window + .global::() + .set_printer_queue(main_window.user_data().data.clone().into()); main_window.on_quit(move || { #[cfg(not(target_arch = "wasm32"))] std::process::exit(0); }); - let printer_queue_copy = printer_queue.clone(); + let weak = main_window.as_weak(); main_window.global::().on_start_job(move |title| { - printer_queue_copy.push_job(title); + weak.unwrap().user_data().push_job(title); }); - let printer_queue_copy = printer_queue.clone(); + let weak = main_window.as_weak(); main_window.global::().on_cancel_job(move |idx| { - printer_queue_copy.data.remove(idx as usize); + weak.unwrap().user_data().data.remove(idx as usize); }); - let printer_queue_weak = Rc::downgrade(&printer_queue); - printer_queue.print_progress_timer.start( + let weak = main_window.as_weak(); + main_window.user_data().print_progress_timer.start( slint::TimerMode::Repeated, std::time::Duration::from_secs(1), move || { - if let Some(printer_queue) = printer_queue_weak.upgrade() { - if printer_queue.data.row_count() > 0 { - let mut top_item = printer_queue.data.row_data(0).unwrap(); + if let Some(main_window) = weak.upgrade() { + if main_window.user_data().data.row_count() > 0 { + let mut top_item = main_window.user_data().data.row_data(0).unwrap(); top_item.progress += 1; top_item.status = JobStatus::Waiting; if top_item.progress > 100 { - printer_queue.data.remove(0); - if printer_queue.data.row_count() == 0 { + main_window.user_data().data.remove(0); + if main_window.user_data().data.row_count() == 0 { return; } - top_item = printer_queue.data.row_data(0).unwrap(); + top_item = main_window.user_data().data.row_data(0).unwrap(); } - printer_queue.data.set_row_data(0, top_item); + main_window.user_data().data.set_row_data(0, top_item); } else { // FIXME: stop this timer? } diff --git a/examples/slide_puzzle/build.rs b/examples/slide_puzzle/build.rs index 9b1bb7c6ec0..b23cb31b8b3 100644 --- a/examples/slide_puzzle/build.rs +++ b/examples/slide_puzzle/build.rs @@ -2,5 +2,7 @@ // SPDX-License-Identifier: MIT fn main() { - slint_build::compile("slide_puzzle.slint").unwrap(); + let config = slint_build::CompilerConfiguration::new() + .with_user_data_type("std::cell::RefCell"); + slint_build::compile_with_config("slide_puzzle.slint", config).unwrap(); } diff --git a/examples/slide_puzzle/main.rs b/examples/slide_puzzle/main.rs index 719b98c06ed..a5dc0933a03 100644 --- a/examples/slide_puzzle/main.rs +++ b/examples/slide_puzzle/main.rs @@ -37,9 +37,9 @@ fn shuffle() -> Vec { vec } -struct AppState { +// This is pub because otherwise there is a warning: "type `AppState` is more private than the item `MainWindow::new`" +pub struct AppState { pieces: Rc>, - main_window: slint::Weak, /// An array of 16 values which represent a 4x4 matrix containing the piece number in that /// position. -1 is no piece. positions: Vec, @@ -60,22 +60,22 @@ impl AppState { } } - fn randomize(&mut self) { + fn randomize(&mut self, main_window: &MainWindow) { self.positions = shuffle(); for (i, p) in self.positions.iter().enumerate() { self.set_pieces_pos(*p, i as _); } - self.main_window.unwrap().set_moves(0); - self.apply_tiles_left(); + main_window.set_moves(0); + self.apply_tiles_left(main_window); } - fn apply_tiles_left(&mut self) { + fn apply_tiles_left(&mut self, main_window: &MainWindow) { let left = 15 - self.positions.iter().enumerate().filter(|(i, x)| *i as i8 == **x).count(); - self.main_window.unwrap().set_tiles_left(left as _); + main_window.set_tiles_left(left as _); self.finished = left == 0; } - fn piece_clicked(&mut self, p: i8) -> bool { + fn piece_clicked(&mut self, p: i8, main_window: &MainWindow) -> bool { let piece = self.pieces.row_data(p as usize).unwrap_or_default(); assert_eq!(self.positions[(piece.pos_x * 4 + piece.pos_y) as usize], p); @@ -94,10 +94,8 @@ impl AppState { ); return false; }; - self.apply_tiles_left(); - if let Some(x) = self.main_window.upgrade() { - x.set_moves(x.get_moves() + 1); - } + self.apply_tiles_left(main_window); + main_window.set_moves(main_window.get_moves() + 1); true } @@ -110,7 +108,7 @@ impl AppState { } } - fn random_move(&mut self) { + fn random_move(&mut self, main_window: &MainWindow) { let mut rng = rand::thread_rng(); let hole = self.positions.iter().position(|x| *x == -1).unwrap() as i8; let mut p; @@ -123,7 +121,7 @@ impl AppState { } } let p = self.positions[p as usize]; - self.piece_clicked(p); + self.piece_clicked(p, main_window); } /// Advance the kick animation @@ -171,63 +169,64 @@ pub fn main() { #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); - let main_window = MainWindow::new().unwrap(); - - let state = Rc::new(RefCell::new(AppState { + let state = RefCell::new(AppState { pieces: Rc::new(slint::VecModel::::from(vec![Piece::default(); 15])), - main_window: main_window.as_weak(), positions: vec![], auto_play_timer: Default::default(), kick_animation_timer: Default::default(), speed_for_kick_animation: Default::default(), finished: false, - })); - state.borrow_mut().randomize(); - main_window.set_pieces(state.borrow().pieces.clone().into()); + }); + let main_window = MainWindow::new(state).unwrap(); + main_window.user_data().borrow_mut().randomize(&main_window); + main_window.set_pieces(main_window.user_data().borrow().pieces.clone().into()); - let state_copy = state.clone(); + let weak = main_window.as_weak(); main_window.on_piece_clicked(move |p| { - state_copy.borrow().auto_play_timer.stop(); - state_copy.borrow().main_window.unwrap().set_auto_play(false); - if state_copy.borrow().finished { + let main_window = weak.unwrap(); + main_window.user_data().borrow().auto_play_timer.stop(); + main_window.set_auto_play(false); + if main_window.user_data().borrow().finished { return; } - if !state_copy.borrow_mut().piece_clicked(p as i8) { - let state_weak = Rc::downgrade(&state_copy); - state_copy.borrow().kick_animation_timer.start( + if !main_window.user_data().borrow_mut().piece_clicked(p as i8, &main_window) { + let weak = weak.clone(); + main_window.user_data().borrow().kick_animation_timer.start( slint::TimerMode::Repeated, std::time::Duration::from_millis(16), move || { - if let Some(state) = state_weak.upgrade() { - state.borrow_mut().kick_animation(); + if let Some(main_window) = weak.upgrade() { + main_window.user_data().borrow_mut().kick_animation(); } }, ); } }); - let state_copy = state.clone(); + let weak = main_window.as_weak(); main_window.on_reset(move || { - state_copy.borrow().auto_play_timer.stop(); - state_copy.borrow().main_window.unwrap().set_auto_play(false); - state_copy.borrow_mut().randomize(); + let main_window = weak.unwrap(); + main_window.user_data().borrow().auto_play_timer.stop(); + main_window.set_auto_play(false); + main_window.user_data().borrow_mut().randomize(&main_window); }); - let state_copy = state; + let weak = main_window.as_weak(); main_window.on_enable_auto_mode(move |enabled| { + let main_window = weak.unwrap(); if enabled { - let state_weak = Rc::downgrade(&state_copy); - state_copy.borrow().auto_play_timer.start( + let weak = weak.clone(); + main_window.user_data().borrow().auto_play_timer.start( slint::TimerMode::Repeated, std::time::Duration::from_millis(200), move || { - if let Some(state) = state_weak.upgrade() { - state.borrow_mut().random_move(); + if let Some(main_window) = weak.upgrade() { + main_window.user_data().borrow_mut().random_move(&main_window); } }, ); } else { - state_copy.borrow().auto_play_timer.stop(); + main_window.user_data().borrow().auto_play_timer.stop(); } }); main_window.run().unwrap(); diff --git a/internal/compiler/generator.rs b/internal/compiler/generator.rs index 943575f7904..b8f236e8392 100644 --- a/internal/compiler/generator.rs +++ b/internal/compiler/generator.rs @@ -16,6 +16,7 @@ use crate::expression_tree::{BindingExpression, Expression}; use crate::langtype::ElementType; use crate::namedreference::NamedReference; use crate::object_tree::{Component, Document, ElementRc}; +use crate::CompilerConfiguration; #[cfg(feature = "cpp")] mod cpp; @@ -63,6 +64,7 @@ pub fn generate( format: OutputFormat, destination: &mut impl std::io::Write, doc: &Document, + config: &CompilerConfiguration, ) -> std::io::Result<()> { #![allow(unused_variables)] #![allow(unreachable_code)] @@ -80,7 +82,7 @@ pub fn generate( } #[cfg(feature = "rust")] OutputFormat::Rust => { - let output = rust::generate(doc); + let output = rust::generate(doc, config); write!(destination, "{}", output)?; } OutputFormat::Interpreter => { diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 2a192a9d8c4..5080ef14dc5 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -20,6 +20,7 @@ use crate::llr::{ TypeResolutionContext as _, }; use crate::object_tree::Document; +use crate::CompilerConfiguration; use itertools::Either; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; @@ -138,7 +139,7 @@ fn set_primitive_property_value(ty: &Type, value_expression: TokenStream) -> Tok } /// Generate the rust code for the given component. -pub fn generate(doc: &Document) -> TokenStream { +pub fn generate(doc: &Document, config: &CompilerConfiguration) -> TokenStream { let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = doc .root_component .used_types @@ -170,7 +171,16 @@ pub fn generate(doc: &Document) -> TokenStream { .map(|sub_compo| generate_sub_component(sub_compo, &llr, None, quote!(), None, false)) .collect::>(); - let compo = generate_public_component(&llr); + let user_data_type = config.user_data_type.as_ref().map(|t| { + TokenStream::from_str(t).unwrap_or_else(|e| { + // FIXME: ideally the conpiler config already contains the TokenStream for rust, so that error can be reported before, + // or can have a span (in the case of the slint! macro) + let error = format!("Could not parse user_type from compiler config: '{t}': {e}"); + quote!(compile_error!(#error)) + }) + }); + + let compo = generate_public_component(&llr, user_data_type.as_ref()); let compo_id = public_component_id(&llr.item_tree.root); let compo_module = format_ident!("slint_generated{}", compo_id); let version_check = format_ident!( @@ -221,7 +231,10 @@ pub fn generate(doc: &Document) -> TokenStream { } } -fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream { +fn generate_public_component( + llr: &llr::PublicComponent, + user_data_type: Option<&TokenStream>, +) -> TokenStream { let public_component_id = public_component_id(&llr.item_tree.root); let inner_component_id = inner_component_id(&llr.item_tree.root); let global_container_id = format_ident!("Globals_{}", public_component_id); @@ -235,6 +248,7 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream { window_adapter_: sp::OnceCell, ), None, + user_data_type, ); let ctx = EvaluationContext { @@ -257,18 +271,24 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream { llr.globals.iter().map(|g| format_ident!("global_{}", ident(&g.name))).collect::>(); let global_types = llr.globals.iter().map(global_inner_name).collect::>(); + // FIXME: use Option::as_slice whtn MSRV is Rust 1.75 + let user_data_type = user_data_type.into_iter().collect::>(); + quote!( #component pub struct #public_component_id(sp::VRc); impl #public_component_id { - pub fn new() -> core::result::Result { - let inner = #inner_component_id::new()?; + pub fn new(#(user_data: #user_data_type)*) -> core::result::Result { + let inner = #inner_component_id::new(#(user_data as #user_data_type)*)?; #(inner.globals.#global_names.clone().init(&inner);)* #inner_component_id::user_init(sp::VRc::map(inner.clone(), |x| x)); core::result::Result::Ok(Self(inner)) } + #(pub fn user_data(&self) -> &#user_data_type { self.0.user_data.as_ref().unwrap() })* + + #property_and_callback_accessors } @@ -606,7 +626,9 @@ fn generate_sub_component( let mut extra_components = component .popup_windows .iter() - .map(|c| generate_item_tree(c, root, Some(ParentCtx::new(&ctx, None)), quote!(), None)) + .map(|c| { + generate_item_tree(c, root, Some(ParentCtx::new(&ctx, None)), quote!(), None, None) + }) .collect::>(); let mut declared_property_vars = vec![]; @@ -1245,9 +1267,17 @@ fn generate_item_tree( sub_tree: &llr::ItemTree, root: &llr::PublicComponent, parent_ctx: Option, - extra_fields: TokenStream, + mut extra_fields: TokenStream, index_property: Option, + user_data_type: Option<&TokenStream>, ) -> TokenStream { + if let Some(user_data_type) = user_data_type { + extra_fields = quote!( + user_data: Option<#user_data_type>, + #extra_fields + ); + } + let sub_comp = generate_sub_component( &sub_tree.root, root, @@ -1395,14 +1425,19 @@ fn generate_item_tree( let item_tree_array_len = item_tree_array.len(); let item_array_len = item_array.len(); + // FIXME: use Option::as_slice when MSRV is Rust 1.75 + let user_data_type = user_data_type.into_iter().collect::>(); + quote!( #sub_comp impl #inner_component_id { - pub fn new(#(parent: #parent_component_type)*) -> core::result::Result, slint::PlatformError> { + pub fn new(#(user_data: #user_data_type,)* #(parent: #parent_component_type,)*) -> core::result::Result, slint::PlatformError> { #![allow(unused)] slint::private_unstable_api::ensure_backend()?; let mut _self = Self::default(); + // Ideally, user_data shouldn't be an Option, but otherwise, Self wouldn't implement default and the whole thing would be complicated + #(_self.user_data = Some(user_data as #user_data_type);)* #(_self.parent = parent.clone() as #parent_component_type;)* let self_rc = sp::VRc::new(_self); let self_dyn_rc = sp::VRc::into_dyn(self_rc.clone()); @@ -1540,6 +1575,7 @@ fn generate_repeated_component( Some(parent_ctx), quote!(), repeated.index_prop, + None, ); let ctx = EvaluationContext { diff --git a/internal/compiler/lib.rs b/internal/compiler/lib.rs index 81481f8f149..331baa060fd 100644 --- a/internal/compiler/lib.rs +++ b/internal/compiler/lib.rs @@ -92,6 +92,9 @@ pub struct CompilerConfiguration { /// The domain used as one of the parameter to the translate function pub translation_domain: Option, + + /// The type of the user data in the rust generated component + pub user_data_type: Option, } impl CompilerConfiguration { @@ -152,6 +155,7 @@ impl CompilerConfiguration { accessibility: true, enable_component_containers, translation_domain: None, + user_data_type: None, } } } diff --git a/tests/driver/cpp/cppdriver.rs b/tests/driver/cpp/cppdriver.rs index cb711702621..1af754c3fd1 100644 --- a/tests/driver/cpp/cppdriver.rs +++ b/tests/driver/cpp/cppdriver.rs @@ -22,7 +22,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box> compiler_config.include_paths = include_paths; compiler_config.library_paths = library_paths; let (root_component, diag) = - spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); + spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config.clone())); if diag.has_error() { let vec = diag.to_string_vec(); @@ -31,7 +31,12 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box> let mut generated_cpp: Vec = Vec::new(); - generator::generate(generator::OutputFormat::Cpp, &mut generated_cpp, &root_component)?; + generator::generate( + generator::OutputFormat::Cpp, + &mut generated_cpp, + &root_component, + &compiler_config, + )?; if diag.has_error() { let vec = diag.to_string_vec(); diff --git a/tests/screenshots/build.rs b/tests/screenshots/build.rs index b0e6426915f..1af28fdb567 100644 --- a/tests/screenshots/build.rs +++ b/tests/screenshots/build.rs @@ -154,7 +154,7 @@ fn generate_source( compiler_config.enable_component_containers = true; compiler_config.style = Some("fluent".to_string()); let (root_component, diag) = - spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); + spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config.clone())); if diag.has_error() { diag.print_warnings_and_exit_on_error(); @@ -166,6 +166,6 @@ fn generate_source( diag.print(); } - generator::generate(generator::OutputFormat::Rust, output, &root_component)?; + generator::generate(generator::OutputFormat::Rust, output, &root_component, &compiler_config)?; Ok(()) } diff --git a/tools/compiler/main.rs b/tools/compiler/main.rs index aa1e36b9598..6dee508accc 100644 --- a/tools/compiler/main.rs +++ b/tools/compiler/main.rs @@ -97,17 +97,19 @@ fn main() -> std::io::Result<()> { compiler_config.style = Some(style); } let syntax_node = syntax_node.expect("diags contained no compilation errors"); - let (doc, diag) = spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); + let (doc, diag) = + spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config.clone())); let diag = diag.check_and_exit_on_error(); if args.output == std::path::Path::new("-") { - generator::generate(args.format, &mut std::io::stdout(), &doc)?; + generator::generate(args.format, &mut std::io::stdout(), &doc, &compiler_config)?; } else { generator::generate( args.format, &mut BufWriter::new(std::fs::File::create(&args.output)?), &doc, + &compiler_config, )?; }