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
4use 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![
336                        "using slint::private_api::live_preview::into_slint_value;".into(),
337                        format!(
338                            "live_preview.set_callback(\"{prefix}{prop_name}\", [callback_handler]([[maybe_unused]] auto args) {{ {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        // Array of anonymous struct
472        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}