1use super::cpp::{Config, concatenate_ident, cpp_ast::*, ident};
5use crate::CompilerConfiguration;
6use crate::langtype::{EnumerationValue, StructName, Type};
7use crate::llr;
8use crate::object_tree::Document;
9use itertools::Itertools as _;
10use smol_str::format_smolstr;
11use std::io::BufWriter;
12
13pub fn generate(
14 doc: &Document,
15 config: Config,
16 compiler_config: &CompilerConfiguration,
17) -> std::io::Result<File> {
18 let mut file = super::cpp::generate_types(&doc.used_types.borrow().structs_and_enums, &config);
19
20 file.includes.push("<slint_live_preview.h>".into());
21
22 generate_value_conversions(&mut file, &doc.used_types.borrow().structs_and_enums);
23
24 let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
25
26 let main_file = doc
27 .node
28 .as_ref()
29 .ok_or_else(|| std::io::Error::other("Cannot determine path of the main file"))?
30 .source_file
31 .path()
32 .to_string_lossy();
33
34 for p in &llr.public_components {
35 generate_public_component(&mut file, p, &llr, compiler_config, &main_file);
36 }
37
38 for glob in &llr.globals {
39 if glob.must_generate() {
40 generate_global(&mut file, glob);
41 file.definitions.extend(glob.aliases.iter().map(|name| {
42 Declaration::TypeAlias(TypeAlias {
43 old_name: ident(&glob.name),
44 new_name: ident(name),
45 })
46 }));
47 };
48 }
49
50 super::cpp::generate_type_aliases(&mut file, doc);
51
52 let cpp_files = file.split_off_cpp_files(config.header_include, config.cpp_files.len());
53 for (cpp_file_name, cpp_file) in config.cpp_files.iter().zip(cpp_files) {
54 use std::io::Write;
55 let mut cpp_writer = BufWriter::new(std::fs::File::create(&cpp_file_name)?);
56 write!(&mut cpp_writer, "{cpp_file}")?;
57 cpp_writer.flush()?;
58 }
59
60 Ok(file)
61}
62
63fn generate_public_component(
64 file: &mut File,
65 component: &llr::PublicComponent,
66 unit: &llr::CompilationUnit,
67 compiler_config: &CompilerConfiguration,
68 main_file: &str,
69) {
70 let component_id = ident(&component.name);
71
72 let mut component_struct = Struct { name: component_id.clone(), ..Default::default() };
73
74 component_struct.members.push((
75 Access::Private,
76 Declaration::Var(Var {
77 ty: "slint::private_api::live_preview::LiveReloadingComponent".into(),
78 name: "live_preview".into(),
79 ..Default::default()
80 }),
81 ));
82
83 let mut global_accessor_function_body = Vec::new();
84 for glob in unit.globals.iter().filter(|glob| glob.exported && glob.must_generate()) {
85 let accessor_statement = format!(
86 "{0}if constexpr(std::is_same_v<T, {1}>) {{ return T(live_preview); }}",
87 if global_accessor_function_body.is_empty() { "" } else { "else " },
88 concatenate_ident(&glob.name),
89 );
90 global_accessor_function_body.push(accessor_statement);
91 }
92 if !global_accessor_function_body.is_empty() {
93 global_accessor_function_body.push(
94 "else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(),
95 );
96
97 component_struct.members.push((
98 Access::Public,
99 Declaration::Function(Function {
100 name: "global".into(),
101 signature: "() const -> T".into(),
102 statements: Some(global_accessor_function_body),
103 template_parameters: Some("typename T".into()),
104 ..Default::default()
105 }),
106 ));
107 }
108
109 generate_public_api_for_properties(
110 "",
111 &mut component_struct.members,
112 &component.public_properties,
113 &component.private_properties,
114 );
115
116 component_struct.members.push((
117 Access::Public,
118 Declaration::Var(Var {
119 ty: "static const slint::private_api::ItemTreeVTable".into(),
120 name: "static_vtable".into(),
121 ..Default::default()
122 }),
123 ));
124
125 file.definitions.push(Declaration::Var(Var {
126 ty: "const slint::private_api::ItemTreeVTable".into(),
127 name: format_smolstr!("{component_id}::static_vtable"),
128 init: Some(format!(
129 "{{ nullptr, nullptr, nullptr, nullptr, \
130 nullptr, nullptr, nullptr, nullptr, nullptr, \
131 nullptr, nullptr, nullptr, nullptr, \
132 nullptr, nullptr, nullptr, \
133 slint::private_api::drop_in_place<{component_id}>, slint::private_api::dealloc }}"
134 )),
135 ..Default::default()
136 }));
137
138 let create_code = vec![
139 format!(
140 "slint::SharedVector<slint::SharedString> include_paths{{ {} }};",
141 compiler_config
142 .include_paths
143 .iter()
144 .map(|p| format!("\"{}\"", escape_string(&p.to_string_lossy())))
145 .join(", ")
146 ),
147 format!(
148 "slint::SharedVector<slint::SharedString> library_paths{{ {} }};",
149 compiler_config
150 .library_paths
151 .iter()
152 .map(|(l, p)| format!("\"{l}={}\"", p.to_string_lossy()))
153 .join(", ")
154 ),
155 format!(
156 "auto live_preview = slint::private_api::live_preview::LiveReloadingComponent({main_file:?}, {:?}, include_paths, library_paths, {:?}, {:?}, {});",
157 component.name,
158 compiler_config.style.as_ref().unwrap_or(&String::new()),
159 compiler_config.translation_domain.as_ref().unwrap_or(&String::new()),
160 compiler_config.default_translation_context == crate::DefaultTranslationContext::None,
161 ),
162 format!(
163 "auto self_rc = vtable::VRc<slint::private_api::ItemTreeVTable, {component_id}>::make(std::move(live_preview));"
164 ),
165 format!("return slint::ComponentHandle<{component_id}>(self_rc);"),
166 ];
167
168 component_struct.members.push((
169 Access::Public,
170 Declaration::Function(Function {
171 name: "create".into(),
172 signature: format!("() -> slint::ComponentHandle<{component_id}>"),
173 statements: Some(create_code),
174 is_static: true,
175 ..Default::default()
176 }),
177 ));
178
179 component_struct.members.push((
180 Access::Public,
181 Declaration::Function(Function {
182 is_constructor_or_destructor: true,
183 name: ident(&component_struct.name),
184 signature: "(slint::private_api::live_preview::LiveReloadingComponent live_preview)"
185 .into(),
186 constructor_member_initializers: vec!["live_preview(std::move(live_preview))".into()],
187 statements: Some(Vec::new()),
188 ..Default::default()
189 }),
190 ));
191
192 component_struct.members.push((
193 Access::Public,
194 Declaration::Function(Function {
195 name: "show".into(),
196 signature: "() -> void".into(),
197 statements: Some(vec!["window().show();".into()]),
198 ..Default::default()
199 }),
200 ));
201
202 component_struct.members.push((
203 Access::Public,
204 Declaration::Function(Function {
205 name: "hide".into(),
206 signature: "() -> void".into(),
207 statements: Some(vec!["window().hide();".into()]),
208 ..Default::default()
209 }),
210 ));
211
212 component_struct.members.push((
213 Access::Public,
214 Declaration::Function(Function {
215 name: "window".into(),
216 signature: "() const -> slint::Window&".into(),
217 statements: Some(vec!["return live_preview.window();".into()]),
218 ..Default::default()
219 }),
220 ));
221
222 component_struct.members.push((
223 Access::Public,
224 Declaration::Function(Function {
225 name: "run".into(),
226 signature: "() -> void".into(),
227 statements: Some(vec![
228 "show();".into(),
229 "slint::run_event_loop();".into(),
230 "hide();".into(),
231 ]),
232 ..Default::default()
233 }),
234 ));
235
236 file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
237 file.declarations.push(Declaration::Struct(component_struct));
238}
239
240fn generate_global(file: &mut File, global: &llr::GlobalComponent) {
241 let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };
242
243 global_struct.members.push((
244 Access::Private,
245 Declaration::Var(Var {
246 ty: "const slint::private_api::live_preview::LiveReloadingComponent&".into(),
247 name: "live_preview".into(),
248 ..Default::default()
249 }),
250 ));
251
252 global_struct.members.push((
253 Access::Public,
254 Declaration::Function(Function {
255 is_constructor_or_destructor: true,
256 name: ident(&global.name),
257 signature:
258 "(const slint::private_api::live_preview::LiveReloadingComponent &live_preview)"
259 .into(),
260 constructor_member_initializers: vec!["live_preview(live_preview)".into()],
261 statements: Some(Vec::new()),
262 ..Default::default()
263 }),
264 ));
265
266 generate_public_api_for_properties(
267 &format!("{}.", global.name),
268 &mut global_struct.members,
269 &global.public_properties,
270 &global.private_properties,
271 );
272
273 file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
274 file.declarations.push(Declaration::Struct(global_struct));
275}
276
277fn generate_public_api_for_properties(
278 prefix: &str,
279 declarations: &mut Vec<(Access, Declaration)>,
280 public_properties: &llr::PublicProperties,
281 private_properties: &llr::PrivateProperties,
282) {
283 for p in public_properties {
284 let prop_name = &p.name;
285 let prop_ident = concatenate_ident(prop_name);
286
287 if let Type::Callback(callback) = &p.ty {
288 let ret = callback.return_type.cpp_type().unwrap();
289 let param_types =
290 callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
291 let callback_emitter = vec![format!(
292 "return {}(live_preview.invoke(\"{prefix}{prop_name}\" {}));",
293 convert_from_value_fn(&callback.return_type),
294 (0..callback.args.len()).map(|i| format!(", arg_{i}")).join(""),
295 )];
296 declarations.push((
297 Access::Public,
298 Declaration::Function(Function {
299 name: format_smolstr!("invoke_{prop_ident}"),
300 signature: format!(
301 "({}) const -> {ret}",
302 param_types
303 .iter()
304 .enumerate()
305 .map(|(i, ty)| format!("{ty} arg_{i}"))
306 .join(", "),
307 ),
308 statements: Some(callback_emitter),
309 ..Default::default()
310 }),
311 ));
312 let args = callback
313 .args
314 .iter()
315 .enumerate()
316 .map(|(i, t)| format!("{}(args[{i}])", convert_from_value_fn(t)))
317 .join(", ");
318 let return_statement = if callback.return_type == Type::Void {
319 format!("callback_handler({args}); return slint::interpreter::Value();",)
320 } else {
321 format!(
322 "return {}(callback_handler({args}));",
323 convert_to_value_fn(&callback.return_type),
324 )
325 };
326 declarations.push((
327 Access::Public,
328 Declaration::Function(Function {
329 name: format_smolstr!("on_{}", concatenate_ident(&p.name)),
330 template_parameters: Some(format!(
331 "std::invocable<{}> Functor",
332 param_types.join(", "),
333 )),
334 signature: "(Functor && callback_handler) const".into(),
335 statements: Some(vec {{ {return_statement} }});",
339 ),
340 ]),
341 ..Default::default()
342 }),
343 ));
344 } else if let Type::Function(function) = &p.ty {
345 let param_types =
346 function.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
347 let ret = function.return_type.cpp_type().unwrap();
348 let call_code = vec![format!(
349 "return {}(live_preview.invoke(\"{prefix}{prop_name}\"{}));",
350 convert_from_value_fn(&function.return_type),
351 (0..function.args.len()).map(|i| format!(", arg_{i}")).join("")
352 )];
353 declarations.push((
354 Access::Public,
355 Declaration::Function(Function {
356 name: format_smolstr!("invoke_{}", concatenate_ident(&p.name)),
357 signature: format!(
358 "({}) const -> {ret}",
359 param_types
360 .iter()
361 .enumerate()
362 .map(|(i, ty)| format!("{ty} arg_{i}"))
363 .join(", "),
364 ),
365 statements: Some(call_code),
366 ..Default::default()
367 }),
368 ));
369 } else {
370 let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties");
371 let prop_getter: Vec<String> = vec![format!(
372 "return {}(live_preview.get_property(\"{prefix}{prop_name}\"));",
373 convert_from_value_fn(&p.ty)
374 )];
375 declarations.push((
376 Access::Public,
377 Declaration::Function(Function {
378 name: format_smolstr!("get_{}", &prop_ident),
379 signature: format!("() const -> {cpp_property_type}"),
380 statements: Some(prop_getter),
381 ..Default::default()
382 }),
383 ));
384
385 if !p.read_only {
386 let prop_setter: Vec<String> = vec![
387 "using slint::private_api::live_preview::into_slint_value;".into(),
388 format!(
389 "live_preview.set_property(\"{prefix}{prop_name}\", {}(value));",
390 convert_to_value_fn(&p.ty)
391 ),
392 ];
393 declarations.push((
394 Access::Public,
395 Declaration::Function(Function {
396 name: format_smolstr!("set_{}", &prop_ident),
397 signature: format!("(const {} &value) const -> void", cpp_property_type),
398 statements: Some(prop_setter),
399 ..Default::default()
400 }),
401 ));
402 } else {
403 declarations.push((
404 Access::Private,
405 Declaration::Function(Function {
406 name: format_smolstr!("set_{}", &prop_ident),
407 signature: format!(
408 "(const {cpp_property_type} &) const = delete /* property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter */", p.name
409 ),
410 ..Default::default()
411 }),
412 ));
413 }
414 }
415 }
416
417 for (name, ty) in private_properties {
418 let prop_ident = concatenate_ident(name);
419
420 if let Type::Function(function) = &ty {
421 let param_types = function.args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
422 declarations.push((
423 Access::Private,
424 Declaration::Function(Function {
425 name: format_smolstr!("invoke_{prop_ident}"),
426 signature: format!(
427 "({param_types}) const = delete /* the function '{name}' is declared as private. Declare it as 'public' */",
428 ),
429 ..Default::default()
430 }),
431 ));
432 } else {
433 declarations.push((
434 Access::Private,
435 Declaration::Function(Function {
436 name: format_smolstr!("get_{prop_ident}"),
437 signature: format!(
438 "() const = delete /* the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public */",
439 ),
440 ..Default::default()
441 }),
442 ));
443 declarations.push((
444 Access::Private,
445 Declaration::Function(Function {
446 name: format_smolstr!("set_{}", &prop_ident),
447 signature: format!(
448 "(const auto &) const = delete /* property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public */",
449 ),
450 ..Default::default()
451 }),
452 ));
453 }
454 }
455}
456
457fn convert_to_value_fn(ty: &Type) -> String {
458 match ty {
459 Type::Struct(s) if s.name.is_none() => {
460 let mut init = s.fields.iter().enumerate().map(|(i, (name, ty))| {
461 format!(
462 "s.set_field(\"{name}\", {}(std::get<{i}>(tuple))); ",
463 convert_to_value_fn(ty)
464 )
465 });
466 format!(
467 "([](const auto &tuple) {{ slint::interpreter::Struct s; {}return slint::interpreter::Value(s); }})",
468 init.join("")
469 )
470 }
471 Type::Array(a) if matches!(a.as_ref(), Type::Struct(s) if s.name.is_none()) => {
473 let conf_fn = convert_to_value_fn(&a);
474 let aty = a.cpp_type().unwrap();
475 format!(
476 "([](const auto &model) {{ return slint::interpreter::Value(std::make_shared<slint::MapModel<{aty}, slint::interpreter::Value>>(model, {conf_fn})); }})"
477 )
478 }
479 _ => "into_slint_value".into(),
480 }
481}
482
483fn convert_from_value_fn(ty: &Type) -> String {
484 match ty {
485 Type::Struct(s) if s.name.is_none() => {
486 let mut init = s.fields.iter().map(|(name, ty)| {
487 format!("slint::private_api::live_preview::from_slint_value<{}>(s.get_field(\"{name}\").value())", ty.cpp_type().unwrap())
488 });
489 format!(
490 "([](const slint::interpreter::Value &v) {{ auto s = v.to_struct().value(); return std::make_tuple({}); }})",
491 init.join(", ")
492 )
493 }
494 _ => format!(
495 "slint::private_api::live_preview::from_slint_value<{}>",
496 ty.cpp_type().unwrap_or_default()
497 ),
498 }
499}
500
501fn generate_value_conversions(file: &mut File, structs_and_enums: &[Type]) {
502 for ty in structs_and_enums {
503 match ty {
504 Type::Struct(s) if s.node().is_some() => {
505 let StructName::User { name: struct_name, .. } = &s.name else {
506 return;
507 };
508 let name = ident(&struct_name);
509 let mut to_statements = vec![
510 "using slint::private_api::live_preview::into_slint_value;".into(),
511 "slint::interpreter::Struct s;".into(),
512 ];
513 let mut from_statements = vec![
514 "using slint::private_api::live_preview::from_slint_value;".into(),
515 "slint::interpreter::Struct s = val.to_struct().value();".into(),
516 format!("{name} self;"),
517 ];
518 for (f, t) in &s.fields {
519 to_statements.push(format!(
520 "s.set_field(\"{f}\", into_slint_value(self.{}));",
521 ident(f)
522 ));
523 from_statements.push(format!(
524 "self.{} = slint::private_api::live_preview::from_slint_value<{}>(s.get_field(\"{f}\").value());",
525 ident(f),
526 t.cpp_type().unwrap()
527 ));
528 }
529 to_statements.push("return s;".into());
530 from_statements.push("return self;".into());
531 file.declarations.push(Declaration::Function(Function {
532 name: "into_slint_value".into(),
533 signature: format!(
534 "([[maybe_unused]] const {name} &self) -> slint::interpreter::Value"
535 ),
536 statements: Some(to_statements),
537 is_inline: true,
538 ..Function::default()
539 }));
540 file.declarations.push(Declaration::Function(Function {
541 name: "from_slint_value".into(),
542 signature: format!(
543 "(const slint::interpreter::Value &val, const {name} *) -> {name}"
544 ),
545 statements: Some(from_statements),
546 is_inline: true,
547 ..Function::default()
548 }));
549 }
550 Type::Enumeration(e) => {
551 let mut from_statements = vec![
552 "auto value_str = slint::private_api::live_preview::LiveReloadingComponent::get_enum_value(val);".to_string(),
553 ];
554 let mut to_statements = vec!["switch (self) {".to_string()];
555 let name = ident(&e.name);
556
557 for value in 0..e.values.len() {
558 let value = EnumerationValue { value, enumeration: e.clone() };
559 let variant_name = ident(&value.to_pascal_case());
560
561 from_statements.push(format!(
562 "if (value_str == \"{value}\") return {name}::{variant_name};"
563 ));
564 to_statements.push(format!("case {name}::{variant_name}: return slint::private_api::live_preview::LiveReloadingComponent::value_from_enum(\"{}\", \"{value}\");", e.name));
565 }
566 from_statements.push("return {};".to_string());
567 to_statements.push("}".to_string());
568 to_statements.push("return {};".to_string());
569
570 file.declarations.push(Declaration::Function(Function {
571 name: "into_slint_value".into(),
572 signature: format!(
573 "([[maybe_unused]] const {name} &self) -> slint::interpreter::Value"
574 ),
575 statements: Some(to_statements),
576 is_inline: true,
577 ..Function::default()
578 }));
579 file.declarations.push(Declaration::Function(Function {
580 name: "from_slint_value".into(),
581 signature: format!(
582 "(const slint::interpreter::Value &val, const {name} *) -> {name}"
583 ),
584 statements: Some(from_statements),
585 is_inline: true,
586 ..Function::default()
587 }));
588 }
589 _ => (),
590 }
591 }
592}