i_slint_compiler/generator/
rust_live_preview.rs1use 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
12pub 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 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 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 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
381fn convert_to_value_fn(ty: &Type) -> TokenStream {
384 match ty {
385 Type::Struct(s) if s.name.is_none() => {
386 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
403fn 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 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}