i_slint_compiler/generator/
rust_live_preview.rs1use 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
13pub 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 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 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 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
384fn convert_to_value_fn(ty: &Type) -> TokenStream {
387 match ty {
388 Type::Struct(s) if s.name.is_none() => {
389 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
406fn 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 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}