Skip to main content

i_slint_compiler/generator/
rust_live_preview.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use super::accessor_names::{self, AccessorKind};
5use super::rust::{ident, rust_primitive_type};
6use crate::CompilerConfiguration;
7use crate::langtype::{Struct, StructName, Type};
8use crate::llr;
9use crate::object_tree::Document;
10use proc_macro2::TokenStream;
11use quote::{format_ident, quote};
12
13/// Generate the rust code for the given component.
14pub fn generate(
15    doc: &Document,
16    compiler_config: &CompilerConfiguration,
17) -> std::io::Result<TokenStream> {
18    let module_header = super::rust::generate_module_header();
19
20    let type_value_conversions =
21        generate_value_conversions(&doc.used_types.borrow().structs_and_enums);
22
23    let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
24
25    if llr.public_components.is_empty() {
26        return Ok(Default::default());
27    }
28
29    let inner_module =
30        super::rust::generate_types(&doc.used_types.borrow().structs_and_enums, &llr);
31
32    let main_file = doc
33        .node
34        .as_ref()
35        .ok_or_else(|| std::io::Error::other("Cannot determine path of the main file"))?
36        .source_file
37        .path();
38    let main_file = std::path::absolute(main_file).unwrap_or_else(|_| main_file.to_path_buf());
39    let main_file = main_file.to_string_lossy();
40
41    let public_components = llr
42        .public_components
43        .iter()
44        .map(|p| generate_public_component(p, compiler_config, &main_file));
45
46    let globals = llr
47        .globals
48        .iter_enumerated()
49        .filter(|(_, glob)| glob.must_generate())
50        .map(|(_, glob)| generate_global(glob, &llr));
51    let globals_ids = llr.globals.iter().filter(|glob| glob.exported).flat_map(|glob| {
52        std::iter::once(ident(&glob.name)).chain(glob.aliases.iter().map(|x| ident(x)))
53    });
54    let compo_ids = llr.public_components.iter().map(|c| ident(&c.name));
55
56    // The inner module was meant to be internal private, but projects have been reaching into it
57    // so we can't change the name of this module
58    let generated_mod = doc
59        .last_exported_component()
60        .map(|c| format_ident!("slint_generated{}", ident(&c.id)))
61        .unwrap_or_else(|| format_ident!("slint_generated"));
62
63    let (type_reexports, deprecated_type_exports) = super::rust::type_exports(&llr, &generated_mod);
64
65    Ok(quote! {
66        mod #generated_mod {
67            #module_header
68            #inner_module
69            #(#globals)*
70            #(#public_components)*
71            #type_value_conversions
72        }
73        #(#deprecated_type_exports)*
74        #[allow(unused_imports)]
75        pub use #generated_mod::{#(#compo_ids,)* #(#type_reexports,)* #(#globals_ids,)*};
76        #[allow(unused_imports)]
77        pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
78    })
79}
80
81fn generate_public_component(
82    llr: &llr::PublicComponent,
83    compiler_config: &CompilerConfiguration,
84    main_file: &str,
85) -> TokenStream {
86    let public_component_id = ident(&llr.name);
87    let component_name = llr.name.as_str();
88
89    let main_file = if main_file.ends_with("Cargo.toml") {
90        // We couldn't get the actual .rs file from a slint! macro, so use file!() which will expand to the actual file name
91        let current_dir = std::env::current_dir().unwrap_or_default();
92        let current_dir = current_dir.to_string_lossy();
93        quote!(std::path::Path::new(#current_dir).join(file!()))
94    } else {
95        quote!(#main_file)
96    };
97
98    let mut property_and_callback_accessors: Vec<TokenStream> = Vec::new();
99    for (name, p) in &llr.public_properties {
100        let prop_name = name.as_str();
101
102        if let Type::Callback(callback) = &p.ty {
103            let callback_args =
104                callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
105            let return_type = rust_primitive_type(&callback.return_type).unwrap();
106            let args_name =
107                (0..callback.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
108            let caller_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Invoker);
109            property_and_callback_accessors.push(quote!(
110                #[allow(dead_code)]
111                pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
112                    self.0.borrow().invoke(#prop_name, &[#(#args_name.into(),)*])
113                        .try_into().unwrap_or_else(|_| panic!("Invalid return type for callback {}::{}", #component_name, #prop_name))
114                }
115            ));
116            let on_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Handler);
117            property_and_callback_accessors.push(quote!(
118                #[allow(dead_code)]
119                pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) {
120                    let f = ::core::cell::RefCell::new(f);
121                    self.0.borrow().set_callback(#prop_name, sp::Rc::new(move |values| {
122                        let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #component_name, #prop_name) };
123                        (*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #component_name, #prop_name)),)*).into()
124                    }))
125                }
126            ));
127        } else if let Type::Function(function) = &p.ty {
128            let callback_args =
129                function.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
130            let return_type = rust_primitive_type(&function.return_type).unwrap();
131            let args_name =
132                (0..function.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
133            let caller_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Invoker);
134            property_and_callback_accessors.push(quote!(
135                #[allow(dead_code)]
136                pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
137                    self.0.borrow().invoke(#prop_name, &[#(#args_name.into(),)*])
138                        .try_into().unwrap_or_else(|_| panic!("Invalid return type for function {}::{}", #component_name, #prop_name))
139                }
140            ));
141        } else {
142            let rust_property_type = rust_primitive_type(&p.ty).unwrap();
143            let convert_to_value = convert_to_value_fn(&p.ty);
144            let convert_from_value = convert_from_value_fn(&p.ty);
145
146            let getter_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Getter);
147            property_and_callback_accessors.push(quote!(
148                #[allow(dead_code)]
149                pub fn #getter_ident(&self) -> #rust_property_type {
150                    #convert_from_value(self.0.borrow().get_property(#prop_name))
151                        .unwrap_or_else(|_| panic!("Invalid property type for {}::{}", #component_name, #prop_name))
152                }
153            ));
154
155            let setter_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Setter);
156            if !p.read_only() {
157                property_and_callback_accessors.push(quote!(
158                    #[allow(dead_code)]
159                    pub fn #setter_ident(&self, value: #rust_property_type) {
160                        self.0.borrow().set_property(#prop_name, #convert_to_value(value))
161                    }
162                ));
163            } else {
164                property_and_callback_accessors.push(quote!(
165                    #[allow(dead_code)] fn #setter_ident(&self, _read_only_property : ()) { }
166                ));
167            }
168        }
169    }
170
171    let include_paths = compiler_config.include_paths.iter().map(|p| p.to_string_lossy());
172    let mut sorted_library_paths: Vec<_> = compiler_config.library_paths.iter().collect();
173    sorted_library_paths.sort_by(|a, b| a.0.cmp(b.0));
174    let library_paths = sorted_library_paths.into_iter().map(|(n, p)| {
175        let p = p.to_string_lossy();
176        quote!((#n.to_string(), #p.into()))
177    });
178    let translation_domain = compiler_config.translation_domain.iter();
179    let no_default_translation_context = (compiler_config.default_translation_context == crate::DefaultTranslationContext::None)
180        .then(|| quote!(compiler.set_default_translation_context(sp::live_preview::DefaultTranslationContext::None);));
181    let style = compiler_config.style.iter();
182
183    quote!(
184        pub struct #public_component_id(sp::Rc<::core::cell::RefCell<sp::live_preview::LiveReloadingComponent>>, sp::Rc<dyn sp::WindowAdapter>);
185
186        impl #public_component_id {
187            pub fn new() -> sp::Result<Self, slint::PlatformError> {
188                let mut compiler = sp::live_preview::Compiler::default();
189                compiler.set_include_paths([#(#include_paths.into()),*].into_iter().collect());
190                compiler.set_library_paths([#(#library_paths.into()),*].into_iter().collect());
191                #(compiler.set_style(#style.to_string());)*
192                #(compiler.set_translation_domain(#translation_domain.to_string());)*
193                #no_default_translation_context
194                let instance = sp::live_preview::LiveReloadingComponent::new(compiler, #main_file.into(), Some(#component_name.into()))?;
195                let window_adapter = sp::WindowInner::from_pub(slint::ComponentHandle::window(instance.borrow().instance())).window_adapter();
196                sp::Ok(Self(instance, window_adapter))
197            }
198
199            #(#property_and_callback_accessors)*
200        }
201
202        impl slint::StrongHandle for #public_component_id {
203            type WeakInner = sp::Weak<::core::cell::RefCell<sp::live_preview::LiveReloadingComponent>>;
204
205            fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> sp::Option<Self> {
206                let rc = inner.upgrade()?;
207                let window_adapter = sp::WindowInner::from_pub(slint::ComponentHandle::window(rc.borrow().instance())).window_adapter();
208                sp::Some(Self(rc, window_adapter))
209            }
210        }
211
212        impl slint::ComponentHandle for #public_component_id {
213            fn as_weak(&self) -> slint::Weak<Self> {
214                slint::Weak::new(sp::Rc::downgrade(&self.0))
215            }
216
217            fn clone_strong(&self) -> Self {
218                Self(self.0.clone(), self.1.clone())
219            }
220
221            fn run(&self) -> ::core::result::Result<(), slint::PlatformError> {
222                self.show()?;
223                slint::run_event_loop()
224            }
225
226            fn show(&self) -> ::core::result::Result<(), slint::PlatformError> {
227                self.0.borrow().instance().show()
228            }
229
230            fn hide(&self) -> ::core::result::Result<(), slint::PlatformError> {
231                self.0.borrow().instance().hide()
232            }
233
234            fn window(&self) -> &slint::Window {
235                self.1.window()
236            }
237
238            fn global<'a, T: slint::Global<'a, Self>>(&'a self) -> T {
239                T::get(&self)
240            }
241        }
242
243        /// This is needed for the the internal tests  (eg `slint_testing::send_keyboard_string_sequence`)
244        impl<X> ::core::convert::From<#public_component_id> for sp::VRc<sp::ItemTreeVTable, X>
245            where Self : ::core::convert::From<sp::live_preview::ComponentInstance>
246        {
247            fn from(value: #public_component_id) -> Self {
248                Self::from(slint::ComponentHandle::clone_strong(value.0.borrow().instance()))
249            }
250        }
251
252    )
253}
254
255fn generate_global(global: &llr::GlobalComponent, root: &llr::CompilationUnit) -> TokenStream {
256    if !global.exported {
257        return quote!();
258    }
259    let global_name = global.name.as_str();
260    let mut property_and_callback_accessors: Vec<TokenStream> = Vec::new();
261    for (name, p) in &global.public_properties {
262        let prop_name = name.as_str();
263
264        if let Type::Callback(callback) = &p.ty {
265            let callback_args =
266                callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
267            let return_type = rust_primitive_type(&callback.return_type).unwrap();
268            let args_name =
269                (0..callback.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
270            let caller_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Invoker);
271            property_and_callback_accessors.push(quote!(
272                #[allow(dead_code)]
273                pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
274                    self.0.borrow().invoke_global(#global_name, #prop_name, &[#(#args_name.into(),)*])
275                        .try_into().unwrap_or_else(|_| panic!("Invalid return type for callback {}::{}", #global_name, #prop_name))
276                }
277            ));
278            let on_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Handler);
279            property_and_callback_accessors.push(quote!(
280                #[allow(dead_code)]
281                pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) {
282                    let f = ::core::cell::RefCell::new(f);
283                    self.0.borrow().set_global_callback(#global_name, #prop_name, sp::Rc::new(move |values| {
284                        let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #global_name, #prop_name) };
285                        (*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #global_name, #prop_name)),)*).into()
286                    }))
287                }
288            ));
289        } else if let Type::Function(function) = &p.ty {
290            let callback_args =
291                function.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::<Vec<_>>();
292            let return_type = rust_primitive_type(&function.return_type).unwrap();
293            let args_name =
294                (0..function.args.len()).map(|i| format_ident!("arg_{}", i)).collect::<Vec<_>>();
295            let caller_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Invoker);
296            property_and_callback_accessors.push(quote!(
297                #[allow(dead_code)]
298                pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type {
299                    self.0.borrow().invoke_global(#global_name, #prop_name, &[#(#args_name.into(),)*])
300                        .try_into().unwrap_or_else(|_| panic!("Invalid return type for function {}::{}", #global_name, #prop_name))
301                }
302            ));
303        } else {
304            let rust_property_type = rust_primitive_type(&p.ty).unwrap();
305            let convert_to_value = convert_to_value_fn(&p.ty);
306            let convert_from_value = convert_from_value_fn(&p.ty);
307
308            let getter_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Getter);
309            property_and_callback_accessors.push(quote!(
310                #[allow(dead_code)]
311                pub fn #getter_ident(&self) -> #rust_property_type {
312                    #convert_from_value(self.0.borrow().get_global_property(#global_name, #prop_name))
313                        .unwrap_or_else(|_| panic!("Invalid property type for {}::{}", #global_name, #prop_name))
314                }
315            ));
316
317            let setter_ident = accessor_names::rust_accessor_ident(name, AccessorKind::Setter);
318            if !p.read_only() {
319                property_and_callback_accessors.push(quote!(
320                    #[allow(dead_code)]
321                    pub fn #setter_ident(&self, value: #rust_property_type) {
322                        self.0.borrow().set_global_property(#global_name, #prop_name, #convert_to_value(value))
323                    }
324                ));
325            } else {
326                property_and_callback_accessors.push(quote!(
327                    #[allow(dead_code)] fn #setter_ident(&self, _read_only_property : ()) { }
328                ));
329            }
330        }
331    }
332
333    let public_component_id = ident(&global.name);
334    let aliases = global.aliases.iter().map(|name| ident(name));
335    let getters = root.public_components.iter().map(|c| {
336        let root_component_id = ident(&c.name);
337        quote! {
338            impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> {
339                type StaticSelf = #public_component_id<'static>;
340
341                fn get(component: &'a #root_component_id) -> Self {
342                    Self(
343                        sp::Rc::clone(&component.0),
344                        ::core::marker::PhantomData::default(),
345                    )
346                }
347
348                fn as_weak(&self) -> slint::Weak<Self::StaticSelf> {
349                    slint::Weak::new(sp::Rc::downgrade(&self.0))
350                }
351            }
352        }
353    });
354
355    let strong_handle_impl = quote!(
356        impl slint::StrongHandle for #public_component_id<'static> {
357            type WeakInner = sp::Weak<::core::cell::RefCell<sp::live_preview::LiveReloadingComponent>>;
358
359            fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> ::core::option::Option<Self> {
360                let rc = inner.upgrade()?;
361                ::core::option::Option::Some(Self(rc, ::core::marker::PhantomData::default()))
362            }
363        }
364    );
365
366    quote!(
367        #[allow(unused)]
368        pub struct #public_component_id<'a>(
369            sp::Rc<::core::cell::RefCell<sp::live_preview::LiveReloadingComponent>>,
370            ::core::marker::PhantomData<&'a sp::live_preview::LiveReloadingComponent>,
371
372        );
373
374        impl<'a> #public_component_id<'a> {
375            #(#property_and_callback_accessors)*
376        }
377        #(pub type #aliases<'a> = #public_component_id<'a>;)*
378        #(#getters)*
379
380        #strong_handle_impl
381    )
382}
383
384/// returns a function that converts the type to a Value.
385/// Normally, that would simply be `xxx.into()`, but for anonymous struct, we need an explicit conversion
386fn convert_to_value_fn(ty: &Type) -> TokenStream {
387    match ty {
388        Type::Struct(s) if s.name.is_none() => {
389            // anonymous struct is mapped to a tuple
390            let names = s.fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
391            let fields = names.iter().map(|k| ident(k)).collect::<Vec<_>>();
392            quote!((|(#(#fields,)*)| {
393                sp::live_preview::Value::Struct([#((#names.to_string(), sp::live_preview::Value::from(#fields)),)*].into_iter().collect())
394            }))
395        }
396        Type::Array(a) if matches!(a.as_ref(), Type::Struct(s) if s.name.is_none()) => {
397            let conf_fn = convert_to_value_fn(a.as_ref());
398            quote!((|model: sp::ModelRc<_>| -> sp::live_preview::Value {
399                sp::live_preview::Value::Model(sp::ModelRc::new(model.map(#conf_fn)))
400            }))
401        }
402        _ => quote!(::core::convert::From::from),
403    }
404}
405
406/// Returns a function that converts a Value to the type.
407/// Normally, that would simply be `xxx.try_into()`, but for anonymous struct, we need an explicit conversion
408fn convert_from_value_fn(ty: &Type) -> TokenStream {
409    match ty {
410        Type::Struct(s) if s.name.is_none() => {
411            let names = s.fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
412            // anonymous struct is mapped to a tuple
413            quote!((|v: sp::live_preview::Value| -> sp::Result<_, ()> {
414                let sp::live_preview::Value::Struct(s) = v else { return sp::Err(()) };
415                sp::Ok((#(s.get_field(#names).ok_or(())?.clone().try_into().map_err(|_|())?,)*))
416            }))
417        }
418        Type::Array(a) if matches!(a.as_ref(), Type::Struct(s) if s.name.is_none()) => {
419            let conf_fn = convert_from_value_fn(a.as_ref());
420            quote!((|v: sp::live_preview::Value| -> sp::Result<_, ()> {
421                let sp::live_preview::Value::Model(model) = v else { return sp::Err(()) };
422                sp::Ok(sp::ModelRc::new(model.map(|x| #conf_fn(x).unwrap_or_default())))
423            }))
424        }
425        _ => quote!(::core::convert::TryFrom::try_from),
426    }
427}
428
429fn generate_value_conversions(used_types: &[Type]) -> TokenStream {
430    let r = used_types
431        .iter()
432        .filter_map(|ty| match ty {
433            Type::Struct(s) => match s.as_ref() {
434                Struct { fields, name: StructName::User { name, .. }, .. } => {
435                    let ty = ident(name);
436                    let convert_to_value = fields.values().map(convert_to_value_fn);
437                    let convert_from_value = fields.values().map(convert_from_value_fn);
438                    let field_names = fields.keys().map(|k| k.as_str()).collect::<Vec<_>>();
439                    let fields = field_names.iter().map(|k| ident(k)).collect::<Vec<_>>();
440                    Some(quote!{
441                        impl From<#ty> for sp::live_preview::Value {
442                            fn from(_value: #ty) -> Self {
443                                Self::Struct([#((#field_names.to_string(), #convert_to_value(_value.#fields)),)*].into_iter().collect())
444                            }
445                        }
446                        impl TryFrom<sp::live_preview::Value> for #ty {
447                            type Error = ();
448                            fn try_from(v: sp::live_preview::Value) -> sp::Result<Self, ()> {
449                                match v {
450                                    sp::live_preview::Value::Struct(_x) => {
451                                        sp::Ok(Self {
452                                            #(#fields: #convert_from_value(_x.get_field(#field_names).ok_or(())?.clone()).map_err(|_|())?,)*
453                                        })
454                                    }
455                                    _ => sp::Err(()),
456                                }
457                            }
458                        }
459                    })
460                }
461                _ => None,
462            },
463            Type::Enumeration(en) => {
464                let name = en.name.as_str();
465                let ty = ident(&en.name);
466                let vals = en.values.iter().map(|v| ident(&crate::generator::to_pascal_case(v))).collect::<Vec<_>>();
467                let val_names = en.values.iter().map(|v| v.as_str()).collect::<Vec<_>>();
468
469                Some(quote!{
470                    impl From<#ty> for sp::live_preview::Value {
471                        fn from(v: #ty) -> Self {
472                            fn to_string(v: #ty) -> String {
473                                match v {
474                                    #(#ty::#vals => #val_names.to_string(),)*
475                                }
476                            }
477                            Self::EnumerationValue(#name.to_owned(), to_string(v))
478                        }
479                    }
480                    impl TryFrom<sp::live_preview::Value> for #ty {
481                        type Error = ();
482                        fn try_from(v: sp::live_preview::Value) -> sp::Result<Self, ()> {
483                            match v {
484                                sp::live_preview::Value::EnumerationValue(enumeration, value) => {
485                                    if enumeration != #name {
486                                        return sp::Err(());
487                                    }
488                                    fn from_str(value: &str) -> sp::Result<#ty, ()> {
489                                        match value {
490                                            #(#val_names => Ok(#ty::#vals),)*
491                                            _ => sp::Err(()),
492                                        }
493                                    }
494                                    from_str(value.as_str()).map_err(|_| ())
495                                }
496                                _ => sp::Err(()),
497                            }
498                        }
499                    }
500                })
501            },
502            _ => None,
503        });
504    quote!(#(#r)*)
505}