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