Skip to main content

i_slint_compiler/generator/
cpp_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
4// cSpell: ignore Wunused
5
6use 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        // Only exported globals are reachable through the `global<T>()`
44        // accessor; the rust_live_preview generator applies the same filter.
45        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            // A global without public properties generates no accessor that
254            // reads the member; clang's -Wunused-private-field rejects it
255            // under -Werror.
256            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![
344                        "using slint::private_api::live_preview::into_slint_value;".into(),
345                        format!(
346                            "live_preview.set_callback(\"{prefix}{prop_name}\", [callback_handler]([[maybe_unused]] auto args) {{ {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        // Array of anonymous struct
478        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}