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