Skip to main content

i_slint_compiler/generator/
cpp.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/*! module for the C++ code generator
5*/
6
7// cSpell:ignore cmath constexpr cstdlib decltype intptr itertools nullptr prepended struc subcomponent uintptr vals compl consteval constinit glyphset glyphsets reflexpr
8
9use crate::fileaccess;
10use std::collections::HashSet;
11use std::fmt::Write;
12use std::sync::OnceLock;
13
14use smol_str::{SmolStr, StrExt, format_smolstr};
15
16/// The configuration for the C++ code generator
17#[derive(Clone, Debug, Default, PartialEq)]
18pub struct Config {
19    pub namespace: Option<String>,
20    pub cpp_files: Vec<std::path::PathBuf>,
21    pub header_include: String,
22}
23
24// Check if word is one of C++ keywords
25fn is_cpp_keyword(word: &str) -> bool {
26    static CPP_KEYWORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
27    let keywords = CPP_KEYWORDS.get_or_init(|| {
28        #[rustfmt::skip]
29        let keywords: HashSet<&str> = HashSet::from([
30            "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
31            "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch",
32            "char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const",
33            "consteval", "constexpr", "constinit", "const_cast", "continue", "co_await",
34            "co_return", "co_yield", "decltype", "default", "delete", "do", "double",
35            "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float",
36            "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace",
37            "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private",
38            "protected", "public", "reflexpr", "register", "reinterpret_cast", "requires",
39            "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast",
40            "struct", "switch", "synchronized", "template", "this", "thread_local", "throw",
41            "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using",
42            "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq",
43        ]);
44        keywords
45    });
46    keywords.contains(word)
47}
48
49pub fn ident(ident: &str) -> SmolStr {
50    let mut new_ident = SmolStr::from(ident);
51    if ident.contains('-') {
52        new_ident = ident.replace_smolstr("-", "_");
53    }
54    if is_cpp_keyword(new_ident.as_str()) {
55        new_ident = format_smolstr!("{}_", new_ident);
56    }
57    new_ident
58}
59
60pub fn concatenate_ident(ident: &str) -> SmolStr {
61    if ident.contains('-') { ident.replace_smolstr("-", "_") } else { ident.into() }
62}
63
64/// Given a property reference to a native item (eg, the property name is empty)
65/// return tokens to the `ItemRc`
66fn access_item_rc(pr: &llr::MemberReference, ctx: &EvaluationContext) -> String {
67    let mut component_access = "self->".into();
68
69    let llr::MemberReference::Relative { parent_level, local_reference } = pr else {
70        unreachable!()
71    };
72    let llr::LocalMemberIndex::Native { item_index, prop_name: _ } = &local_reference.reference
73    else {
74        unreachable!()
75    };
76
77    for _ in 0..*parent_level {
78        component_access = format!("{component_access}parent.lock().value()->");
79    }
80
81    let (sub_compo_path, sub_component) = follow_sub_component_path(
82        ctx.compilation_unit,
83        ctx.parent_sub_component_idx(*parent_level).unwrap(),
84        &local_reference.sub_component_path,
85    );
86    if !local_reference.sub_component_path.is_empty() {
87        component_access += &sub_compo_path;
88    }
89    let component_rc = format!("{component_access}self_weak.lock()->into_dyn()");
90    let item_index_in_tree = sub_component.items[*item_index].index_in_tree;
91    let item_index = if item_index_in_tree == 0 {
92        format!("{component_access}tree_index")
93    } else {
94        format!("{component_access}tree_index_of_first_child + {item_index_in_tree} - 1")
95    };
96
97    format!("{}, {}", &component_rc, item_index)
98}
99
100/// This module contains some data structure that helps represent a C++ code.
101/// It is then rendered into an actual C++ text using the Display trait
102pub mod cpp_ast {
103
104    use std::cell::Cell;
105    use std::fmt::{Display, Error, Formatter};
106
107    use smol_str::{SmolStr, format_smolstr};
108
109    thread_local!(static INDENTATION : Cell<u32> = const { Cell::new(0) });
110    fn indent(f: &mut Formatter<'_>) -> Result<(), Error> {
111        INDENTATION.with(|i| {
112            for _ in 0..(i.get()) {
113                write!(f, "    ")?;
114            }
115            Ok(())
116        })
117    }
118
119    ///A full C++ file
120    #[derive(Default, Debug)]
121    pub struct File {
122        pub is_cpp_file: bool,
123        pub includes: Vec<SmolStr>,
124        pub after_includes: String,
125        pub namespace: Option<String>,
126        pub declarations: Vec<Declaration>,
127        pub resources: Vec<Declaration>,
128        pub definitions: Vec<Declaration>,
129    }
130
131    impl File {
132        #[allow(clippy::manual_checked_ops)]
133        pub fn split_off_cpp_files(&mut self, header_file_name: String, count: usize) -> Vec<File> {
134            let mut cpp_files = Vec::with_capacity(count);
135            if count > 0 {
136                let mut definitions = Vec::new();
137
138                let mut i = 0;
139                while i < self.definitions.len() {
140                    if matches!(
141                        &self.definitions[i],
142                        Declaration::Function(Function { template_parameters: Some(..), .. })
143                            | Declaration::TypeAlias(..)
144                    ) {
145                        i += 1;
146                        continue;
147                    }
148
149                    definitions.push(self.definitions.remove(i));
150                }
151
152                let mut cpp_resources = self
153                    .resources
154                    .iter_mut()
155                    .filter_map(|header_resource| match header_resource {
156                        Declaration::Var(var) => {
157                            var.is_extern = true;
158                            Some(Declaration::Var(Var {
159                                ty: var.ty.clone(),
160                                name: var.name.clone(),
161                                array_size: var.array_size,
162                                init: std::mem::take(&mut var.init),
163                                is_extern: false,
164                                ..Default::default()
165                            }))
166                        }
167                        _ => None,
168                    })
169                    .collect::<Vec<_>>();
170
171                let cpp_includes = vec![format_smolstr!("\"{header_file_name}\"")];
172
173                let def_chunk_size = definitions.len() / count;
174                let res_chunk_size = cpp_resources.len() / count;
175                cpp_files.extend((0..count - 1).map(|_| File {
176                    is_cpp_file: true,
177                    includes: cpp_includes.clone(),
178                    after_includes: String::new(),
179                    namespace: self.namespace.clone(),
180                    declarations: Default::default(),
181                    resources: cpp_resources.drain(0..res_chunk_size).collect(),
182                    definitions: definitions.drain(0..def_chunk_size).collect(),
183                }));
184
185                cpp_files.push(File {
186                    is_cpp_file: true,
187                    includes: cpp_includes,
188                    after_includes: String::new(),
189                    namespace: self.namespace.clone(),
190                    declarations: Default::default(),
191                    resources: cpp_resources,
192                    definitions,
193                });
194
195                cpp_files.resize_with(count, Default::default);
196            }
197
198            // Any definition in the header file is inline.
199            self.definitions.iter_mut().for_each(|def| match def {
200                Declaration::Function(f) => f.is_inline = true,
201                Declaration::Var(v) => v.is_inline = true,
202                _ => {}
203            });
204
205            cpp_files
206        }
207    }
208
209    impl Display for File {
210        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
211            writeln!(f, "// This file is auto-generated")?;
212            if !self.is_cpp_file {
213                writeln!(f, "#pragma once")?;
214            }
215            for i in &self.includes {
216                writeln!(f, "#include {i}")?;
217            }
218            if let Some(namespace) = &self.namespace {
219                writeln!(f, "namespace {namespace} {{")?;
220                INDENTATION.with(|x| x.set(x.get() + 1));
221            }
222
223            write!(f, "{}", self.after_includes)?;
224            for d in self.declarations.iter().chain(self.resources.iter()) {
225                write!(f, "\n{d}")?;
226            }
227            for d in &self.definitions {
228                write!(f, "\n{d}")?;
229            }
230            if let Some(namespace) = &self.namespace {
231                writeln!(f, "}} // namespace {namespace}")?;
232                INDENTATION.with(|x| x.set(x.get() - 1));
233            }
234
235            Ok(())
236        }
237    }
238
239    /// Declarations  (top level, or within a struct)
240    #[derive(Debug, derive_more::Display)]
241    pub enum Declaration {
242        Struct(Struct),
243        Function(Function),
244        Var(Var),
245        TypeAlias(TypeAlias),
246        Enum(Enum),
247    }
248
249    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
250    pub enum Access {
251        Public,
252        Private,
253        /*Protected,*/
254    }
255
256    #[derive(Default, Debug)]
257    pub struct Struct {
258        pub name: SmolStr,
259        pub members: Vec<(Access, Declaration)>,
260        pub friends: Vec<SmolStr>,
261    }
262
263    impl Display for Struct {
264        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
265            indent(f)?;
266            if self.members.is_empty() && self.friends.is_empty() {
267                writeln!(f, "class {};", self.name)
268            } else {
269                writeln!(f, "class {} {{", self.name)?;
270                INDENTATION.with(|x| x.set(x.get() + 1));
271                let mut access = Access::Private;
272                for m in &self.members {
273                    if m.0 != access {
274                        access = m.0;
275                        indent(f)?;
276                        match access {
277                            Access::Public => writeln!(f, "public:")?,
278                            Access::Private => writeln!(f, "private:")?,
279                        }
280                    }
281                    write!(f, "{}", m.1)?;
282                }
283                for friend in &self.friends {
284                    indent(f)?;
285                    writeln!(f, "friend class {friend};")?;
286                }
287                INDENTATION.with(|x| x.set(x.get() - 1));
288                indent(f)?;
289                writeln!(f, "}};")
290            }
291        }
292    }
293
294    impl Struct {
295        pub fn extract_definitions(&mut self) -> impl Iterator<Item = Declaration> + '_ {
296            let struct_name = self.name.clone();
297            self.members.iter_mut().filter_map(move |x| match &mut x.1 {
298                Declaration::Function(f) if f.statements.is_some() => {
299                    Some(Declaration::Function(Function {
300                        name: format_smolstr!("{}::{}", struct_name, f.name),
301                        signature: f.signature.clone(),
302                        is_constructor_or_destructor: f.is_constructor_or_destructor,
303                        is_static: false,
304                        is_friend: false,
305                        statements: f.statements.take(),
306                        template_parameters: f.template_parameters.clone(),
307                        constructor_member_initializers: f.constructor_member_initializers.clone(),
308                        ..Default::default()
309                    }))
310                }
311                _ => None,
312            })
313        }
314    }
315
316    #[derive(Default, Debug)]
317    pub struct Enum {
318        pub name: SmolStr,
319        pub values: Vec<SmolStr>,
320    }
321
322    impl Display for Enum {
323        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
324            indent(f)?;
325            writeln!(f, "enum class {} {{", self.name)?;
326            INDENTATION.with(|x| x.set(x.get() + 1));
327            for value in &self.values {
328                write!(f, "{value},")?;
329            }
330            INDENTATION.with(|x| x.set(x.get() - 1));
331            indent(f)?;
332            writeln!(f, "}};")
333        }
334    }
335
336    /// Function or method
337    #[derive(Default, Debug)]
338    pub struct Function {
339        pub name: SmolStr,
340        /// "(...) -> ..."
341        pub signature: String,
342        /// The function does not have return type
343        pub is_constructor_or_destructor: bool,
344        pub is_static: bool,
345        pub is_friend: bool,
346        pub is_inline: bool,
347        /// The list of statement instead the function.  When None,  this is just a function
348        /// declaration without the definition
349        pub statements: Option<Vec<String>>,
350        /// What's inside template<...> if any
351        pub template_parameters: Option<String>,
352        /// Explicit initializers, such as FooClass::FooClass() : someMember(42) {}
353        pub constructor_member_initializers: Vec<String>,
354    }
355
356    impl Display for Function {
357        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
358            indent(f)?;
359            if let Some(tpl) = &self.template_parameters {
360                write!(f, "template<{tpl}> ")?;
361            }
362            if self.is_static {
363                write!(f, "static ")?;
364            }
365            if self.is_friend {
366                write!(f, "friend ")?;
367            }
368            if self.is_inline {
369                write!(f, "inline ")?;
370            }
371            if !self.is_constructor_or_destructor {
372                write!(f, "auto ")?;
373            }
374            write!(f, "{} {}", self.name, self.signature)?;
375            if let Some(st) = &self.statements {
376                if !self.constructor_member_initializers.is_empty() {
377                    writeln!(f, "\n : {}", self.constructor_member_initializers.join(","))?;
378                }
379                writeln!(f, "{{")?;
380                for s in st {
381                    indent(f)?;
382                    writeln!(f, "    {s}")?;
383                }
384                indent(f)?;
385                writeln!(f, "}}")
386            } else {
387                writeln!(f, ";")
388            }
389        }
390    }
391
392    /// A variable or a member declaration.
393    #[derive(Default, Debug)]
394    pub struct Var {
395        pub is_inline: bool,
396        pub is_extern: bool,
397        pub ty: SmolStr,
398        pub name: SmolStr,
399        pub array_size: Option<usize>,
400        pub init: Option<String>,
401    }
402
403    impl Display for Var {
404        fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
405            indent(f)?;
406            if self.is_extern {
407                write!(f, "extern ")?;
408            }
409            if self.is_inline {
410                write!(f, "inline ")?;
411            }
412            write!(f, "{} {}", self.ty, self.name)?;
413            if let Some(size) = self.array_size {
414                write!(f, "[{size}]")?;
415            }
416            if let Some(i) = &self.init {
417                write!(f, " = {i}")?;
418            }
419            writeln!(f, ";")
420        }
421    }
422
423    #[derive(Default, Debug)]
424    pub struct TypeAlias {
425        pub new_name: SmolStr,
426        pub old_name: SmolStr,
427    }
428
429    impl Display for TypeAlias {
430        fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
431            indent(f)?;
432            writeln!(f, "using {} = {};", self.new_name, self.old_name)
433        }
434    }
435
436    pub trait CppType {
437        fn cpp_type(&self) -> Option<SmolStr>;
438    }
439
440    pub fn escape_string(str: &str) -> String {
441        let mut result = String::with_capacity(str.len());
442        for x in str.chars() {
443            match x {
444                '\n' => result.push_str("\\n"),
445                '\\' => result.push_str("\\\\"),
446                '\"' => result.push_str("\\\""),
447                '\t' => result.push_str("\\t"),
448                '\r' => result.push_str("\\r"),
449                _ if !x.is_ascii() || (x as u32) < 32 => {
450                    use std::fmt::Write;
451                    write!(result, "\\U{:0>8x}", x as u32).unwrap();
452                }
453                _ => result.push(x),
454            }
455        }
456        result
457    }
458}
459
460use crate::CompilerConfiguration;
461use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp};
462use crate::langtype::{
463    BuiltinStruct, Enumeration, EnumerationValue, NativeClass, StructName, Type,
464};
465use crate::layout::Orientation;
466use crate::llr::{
467    self, EvaluationContext as llr_EvaluationContext, EvaluationScope, ParentScope,
468    TypeResolutionContext as _,
469};
470use crate::object_tree::Document;
471use cpp_ast::*;
472use itertools::{Either, Itertools};
473use std::cell::Cell;
474use std::collections::{BTreeMap, BTreeSet};
475
476const SHARED_GLOBAL_CLASS: &str = "SharedGlobals";
477
478#[derive(Default)]
479struct ConditionalIncludes {
480    iostream: Cell<bool>,
481    cstdlib: Cell<bool>,
482    cmath: Cell<bool>,
483}
484
485#[derive(Clone)]
486struct CppGeneratorContext<'a> {
487    global_access: String,
488    conditional_includes: &'a ConditionalIncludes,
489}
490
491type EvaluationContext<'a> = llr_EvaluationContext<'a, CppGeneratorContext<'a>>;
492
493impl CppType for StructName {
494    fn cpp_type(&self) -> Option<SmolStr> {
495        match self {
496            StructName::None => None,
497            StructName::User { name, .. } => Some(ident(name)),
498            StructName::Builtin(builtin) => builtin.cpp_type(),
499        }
500    }
501}
502
503impl CppType for BuiltinStruct {
504    fn cpp_type(&self) -> Option<SmolStr> {
505        let name: &'static str = self.into();
506        match self {
507            Self::Color | Self::LogicalPosition | Self::LogicalSize => {
508                Some(format_smolstr!("slint::{}", name))
509            }
510            Self::PathMoveTo
511            | Self::PathLineTo
512            | Self::PathArcTo
513            | Self::PathCubicTo
514            | Self::PathQuadraticTo
515            | Self::PathClose => Some(format_smolstr!("slint::private_api::{}", name)),
516            s if s.is_public() => Some(format_smolstr!("slint::language::{}", name)),
517            _ => Some(format_smolstr!("slint::cbindgen_private::{}", name)),
518        }
519    }
520}
521
522impl CppType for Type {
523    fn cpp_type(&self) -> Option<SmolStr> {
524        match self {
525            Type::Void => Some("void".into()),
526            Type::Float32 => Some("float".into()),
527            Type::Int32 => Some("int".into()),
528            Type::String => Some("slint::SharedString".into()),
529            Type::Keys => Some("slint::Keys".into()),
530            Type::Color => Some("slint::Color".into()),
531            Type::Duration => Some("std::int64_t".into()),
532            Type::Angle => Some("float".into()),
533            Type::PhysicalLength => Some("float".into()),
534            Type::LogicalLength => Some("float".into()),
535            Type::Rem => Some("float".into()),
536            Type::Percent => Some("float".into()),
537            Type::Bool => Some("bool".into()),
538            Type::Struct(s) => s.name.cpp_type().or_else(|| {
539                let elem = s.fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
540
541                Some(format_smolstr!("std::tuple<{}>", elem.join(", ")))
542            }),
543            Type::Array(i) => {
544                Some(format_smolstr!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?))
545            }
546            Type::Image => Some("slint::Image".into()),
547            Type::DataTransfer => Some("slint::DataTransfer".into()),
548            Type::Enumeration(enumeration) => {
549                if enumeration.node.is_some() {
550                    Some(ident(&enumeration.name))
551                } else {
552                    Some(format_smolstr!("slint::cbindgen_private::{}", ident(&enumeration.name)))
553                }
554            }
555            Type::Brush => Some("slint::Brush".into()),
556            Type::LayoutCache => Some("slint::SharedVector<float>".into()),
557            Type::ArrayOfU16 => Some("slint::SharedVector<uint16_t>".into()),
558            Type::Easing => Some("slint::cbindgen_private::EasingCurve".into()),
559            Type::StyledText => Some("slint::StyledText".into()),
560            _ => None,
561        }
562    }
563}
564
565fn to_cpp_orientation(o: Orientation) -> &'static str {
566    match o {
567        Orientation::Horizontal => "slint::cbindgen_private::Orientation::Horizontal",
568        Orientation::Vertical => "slint::cbindgen_private::Orientation::Vertical",
569    }
570}
571
572/// If the expression is surrounded with parentheses, remove these parentheses
573fn remove_parentheses(expr: &str) -> &str {
574    if expr.starts_with('(') && expr.ends_with(')') {
575        let mut level = 0;
576        // check that the opening and closing parentheses are on the same level
577        for byte in &expr.as_bytes()[1..expr.len() - 1] {
578            match byte {
579                b')' if level == 0 => return expr,
580                b')' => level -= 1,
581                b'(' => level += 1,
582                _ => (),
583            }
584        }
585        &expr[1..expr.len() - 1]
586    } else {
587        expr
588    }
589}
590
591#[test]
592fn remove_parentheses_test() {
593    assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)");
594    assert_eq!(remove_parentheses("(foo).bar"), "(foo).bar");
595    assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)");
596    assert_eq!(remove_parentheses("(foo)(bar)"), "(foo)(bar)");
597    assert_eq!(remove_parentheses("(foo).get()"), "(foo).get()");
598    assert_eq!(remove_parentheses("((foo).get())"), "(foo).get()");
599    assert_eq!(remove_parentheses("(((()())()))"), "((()())())");
600    assert_eq!(remove_parentheses("((()())())"), "(()())()");
601    assert_eq!(remove_parentheses("(()())()"), "(()())()");
602    assert_eq!(remove_parentheses("()())("), "()())(");
603}
604
605fn property_set_value_code(
606    property: &llr::MemberReference,
607    value_expr: &str,
608    ctx: &EvaluationContext,
609) -> String {
610    let prop = access_member(property, ctx);
611    if let Some((animation, map)) = &ctx.property_info(property).animation {
612        let mut animation = (*animation).clone();
613        map.map_expression(&mut animation);
614        let animation_code = compile_expression(&animation, ctx);
615        return prop
616            .then(|prop| format!("{prop}.set_animated_value({value_expr}, {animation_code})"));
617    }
618    prop.then(|prop| format!("{prop}.set({value_expr})"))
619}
620
621/// Walk `field_access` on `root_ty`, prepending each access to `base` to
622/// produce a C++ expression (e.g. `base.foo.bar`), and return the leaf type.
623fn lower_field_access_chain(
624    mut base: String,
625    root_ty: &Type,
626    field_access: &[SmolStr],
627) -> (String, Type) {
628    let mut ty = root_ty.clone();
629    for f in field_access {
630        let Type::Struct(s) = &ty else { panic!("Field of two way binding on a non-struct type") };
631        base = struct_field_access(base, s, f);
632        ty = s.fields.get(f).unwrap().clone();
633    }
634    (base, ty)
635}
636
637/// Emit a `link_two_way_to_model_data` call wiring `p1` to a row of the
638/// model described by `info`, optionally through a struct `field_access`.
639fn generate_model_two_way_binding(
640    ctx: &EvaluationContext,
641    info: &llr::ResolvedModelTwoWayBinding,
642    p1: &str,
643    field_access: &[SmolStr],
644) -> String {
645    let body_sc = &ctx.compilation_unit.sub_components[info.body_sub_component];
646    let data_prop_name = field_name(&body_sc.properties[info.data_prop].name);
647    let index_prop_name = field_name(&body_sc.properties[info.index_prop].name);
648    let repeater_index = usize::from(info.repeater_index);
649
650    // Determine the C++ class name of `self` so we can cast back from
651    // the type-erased VRc obtained by locking the weak pointer.
652    let self_type = ident(
653        &ctx.current_sub_component()
654            .expect("model two-way bindings only exist on sub-components")
655            .name,
656    );
657
658    // Walk the parent chain in a single expression so the intermediate
659    // `lock().value()` temporaries live until we assign to `body_rc`.
660    let (body_setup, body) = if info.parent_level == 0 {
661        (String::new(), "self")
662    } else {
663        let chain: String = (0..info.parent_level).map(|_| "->parent.lock().value()").collect();
664        (format!("auto body_rc = self{chain}; "), "body_rc")
665    };
666
667    let (getter_expr, ty) = lower_field_access_chain(
668        format!("{body}->{data_prop_name}.get()"),
669        info.data_prop_ty,
670        field_access,
671    );
672    let (setter_lvalue, _) =
673        lower_field_access_chain("data".into(), info.data_prop_ty, field_access);
674    let cpp_ty = ty.cpp_type().unwrap();
675
676    // Capture a weak pointer instead of a raw `self` so the getter and
677    // setter stay safe when the repeater instance is destroyed while a
678    // forwarded binding on a shared common property still references it.
679    format!(
680        "slint::private_api::Property<{cpp_ty}>::link_two_way_to_model_data(&{p1}, \
681         [weak = self->self_weak]() -> std::optional<{cpp_ty}> {{ \
682            auto rc = weak.lock(); \
683            if (!rc) return std::nullopt; \
684            auto self = reinterpret_cast<const {self_type}*>((*rc).borrow().instance); \
685            {body_setup}return {getter_expr}; \
686         }}, \
687         [weak = self->self_weak](const {cpp_ty} &value) {{ \
688            auto rc = weak.lock(); \
689            if (!rc) return; \
690            auto self = reinterpret_cast<const {self_type}*>((*rc).borrow().instance); \
691            {body_setup}\
692            if (auto parent_opt = {body}->parent.lock()) {{ \
693                auto data = {body}->{data_prop_name}.get(); \
694                {setter_lvalue} = value; \
695                (*parent_opt)->repeater_{repeater_index}.model_set_row_data(\
696                    static_cast<size_t>({body}->{index_prop_name}.get()), data); \
697            }} \
698         }});"
699    )
700}
701
702fn handle_property_init(
703    prop: &llr::MemberReference,
704    binding_expression: &llr::BindingExpression,
705    init: &mut Vec<String>,
706    ctx: &EvaluationContext,
707) {
708    let prop_access = access_member(prop, ctx).unwrap();
709    let prop_type = ctx.property_ty(prop);
710    if let Type::Callback(callback) = &prop_type {
711        let mut ctx2 = ctx.clone();
712        ctx2.argument_types = &callback.args;
713
714        let mut params = callback.args.iter().enumerate().map(|(i, ty)| {
715            format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i)
716        });
717
718        init.push(format!(
719            "{prop_access}.set_handler(
720                [this]({params}) {{
721                    [[maybe_unused]] auto self = this;
722                    {code};
723                }});",
724            prop_access = prop_access,
725            params = params.join(", "),
726            code = return_compile_expression(
727                &binding_expression.expression.borrow(),
728                &ctx2,
729                Some(&callback.return_type)
730            )
731        ));
732    } else {
733        let init_expr = compile_expression(&binding_expression.expression.borrow(), ctx);
734
735        init.push(if binding_expression.is_constant && !binding_expression.is_state_info {
736            format!("{prop_access}.set({init_expr});")
737        } else {
738            let binding_code = format!(
739                "[this]() {{
740                            [[maybe_unused]] auto self = this;
741                            return {init_expr};
742                        }}"
743            );
744
745            if binding_expression.is_state_info {
746                format!("slint::private_api::set_state_binding({prop_access}, {binding_code});")
747            } else {
748                match &binding_expression.animation {
749                    Some(llr::Animation::Static(anim)) => {
750                        let anim = compile_expression(anim, ctx);
751                        // Note: The start_time defaults to the current tick, so doesn't need to be
752                        // updated here.
753                        format!("{prop_access}.set_animated_binding({binding_code},
754                                [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{
755                                    [[maybe_unused]] auto self = this;
756                                    auto anim = {anim};
757                                    *start_time = nullptr;
758                                    return anim;
759                                }});",
760                                )
761                    }
762                    Some(llr::Animation::Transition(animation)) => {
763                        let animation = compile_expression(animation, ctx);
764                        format!(
765                            "{prop_access}.set_animated_binding({binding_code},
766                            [this](uint64_t **start_time) -> slint::cbindgen_private::PropertyAnimation {{
767                                [[maybe_unused]] auto self = this;
768                                auto [animation, change_time] = {animation};
769                                **start_time = change_time;
770                                return animation;
771                            }});",
772                        )
773                    }
774                    None => format!("{prop_access}.set_binding({binding_code});"),
775                }
776            }
777        });
778    }
779}
780
781/// Returns the text of the C++ code produced by the given root component
782pub fn generate(
783    doc: &Document,
784    config: Config,
785    compiler_config: &CompilerConfiguration,
786) -> std::io::Result<impl std::fmt::Display> {
787    if std::env::var("SLINT_LIVE_PREVIEW").is_ok() {
788        return super::cpp_live_preview::generate(doc, config, compiler_config);
789    }
790
791    let mut file = generate_types(&doc.used_types.borrow().structs_and_enums, &config);
792
793    for (resource_id, er) in doc.embedded_file_resources.borrow().iter_enumerated() {
794        embed_resource(er, resource_id, &mut file.resources);
795    }
796
797    let llr = llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
798
799    #[cfg(feature = "bundle-translations")]
800    if let Some(translations) = &llr.translations {
801        generate_translation(translations, &llr, &mut file.resources);
802    }
803
804    // Forward-declare the root so that sub-components can access singletons, the window, etc.
805    file.declarations.extend(
806        llr.public_components
807            .iter()
808            .map(|c| Declaration::Struct(Struct { name: ident(&c.name), ..Default::default() })),
809    );
810
811    // forward-declare the global struct
812    file.declarations.push(Declaration::Struct(Struct {
813        name: SmolStr::new_static(SHARED_GLOBAL_CLASS),
814        ..Default::default()
815    }));
816
817    // Forward-declare sub components.
818    file.declarations.extend(llr.used_sub_components.iter().map(|sub_compo| {
819        Declaration::Struct(Struct {
820            name: ident(&llr.sub_components[*sub_compo].name),
821            ..Default::default()
822        })
823    }));
824
825    let conditional_includes = ConditionalIncludes::default();
826
827    for sub_compo in &llr.used_sub_components {
828        let sub_compo_id = ident(&llr.sub_components[*sub_compo].name);
829        let mut sub_compo_struct = Struct { name: sub_compo_id.clone(), ..Default::default() };
830        generate_sub_component(
831            &mut sub_compo_struct,
832            *sub_compo,
833            &llr,
834            None,
835            Access::Public,
836            &mut file,
837            &conditional_includes,
838        );
839        file.definitions.extend(sub_compo_struct.extract_definitions().collect::<Vec<_>>());
840        file.declarations.push(Declaration::Struct(sub_compo_struct));
841    }
842
843    let mut globals_struct =
844        Struct { name: SmolStr::new_static(SHARED_GLOBAL_CLASS), ..Default::default() };
845
846    // The window need to be the first member so it is destroyed last
847    globals_struct.members.push((
848        // FIXME: many of the different component bindings need to access this
849        Access::Public,
850        Declaration::Var(Var {
851            ty: "std::optional<slint::Window>".into(),
852            name: "m_window".into(),
853            ..Default::default()
854        }),
855    ));
856
857    globals_struct.members.push((
858        Access::Public,
859        Declaration::Var(Var {
860            ty: "slint::cbindgen_private::ItemTreeWeak".into(),
861            name: "root_weak".into(),
862            ..Default::default()
863        }),
864    ));
865
866    let mut window_creation_code = vec![
867        format!("auto self = const_cast<{SHARED_GLOBAL_CLASS} *>(this);"),
868        "if (!self->m_window.has_value()) {".into(),
869        "   auto &window = self->m_window.emplace(slint::private_api::WindowAdapterRc());".into(),
870    ];
871
872    if let Some(scale_factor) = compiler_config.const_scale_factor {
873        window_creation_code
874            .push(format!("window.window_handle().set_const_scale_factor({scale_factor});"));
875    }
876
877    window_creation_code.extend([
878        "   window.window_handle().set_component(self->root_weak);".into(),
879        "}".into(),
880        "return *self->m_window;".into(),
881    ]);
882
883    globals_struct.members.push((
884        Access::Public,
885        Declaration::Function(Function {
886            name: "window".into(),
887            signature: "() const -> slint::Window&".into(),
888            statements: Some(window_creation_code),
889            ..Default::default()
890        }),
891    ));
892
893    let mut init_global = Vec::new();
894    let mut clone_constructor_global_inits = Vec::new();
895
896    for (idx, glob) in llr.globals.iter_enumerated() {
897        if !glob.must_generate() {
898            continue;
899        }
900        let name = format_smolstr!("global_{}", concatenate_ident(&glob.name));
901        let ty = if glob.is_builtin {
902            generate_global_builtin(&mut file, &conditional_includes, idx, glob, &llr);
903            format_smolstr!("slint::cbindgen_private::{}", glob.name)
904        } else {
905            init_global.push(format!("{name}->init();"));
906            generate_global(&mut file, &conditional_includes, idx, glob, &llr);
907            ident(&glob.name)
908        };
909
910        file.definitions.extend(glob.aliases.iter().map(|name| {
911            Declaration::TypeAlias(TypeAlias { old_name: ident(&glob.name), new_name: ident(name) })
912        }));
913
914        clone_constructor_global_inits.push(format!("{name}(source.{name})"));
915
916        globals_struct.members.push((
917            Access::Public,
918            Declaration::Var(Var {
919                ty: format_smolstr!("std::shared_ptr<{}>", ty),
920                name,
921                init: Some(format!("std::make_shared<{ty}>(this)")),
922                ..Default::default()
923            }),
924        ));
925    }
926
927    // The globals are not initialized in the constructor: a global's init may evaluate a
928    // binding (e.g. `Palette.color-scheme`) that resolves the root through `root_weak` and the
929    // root component's `globals` pointer. Those are only set after the SharedGlobals member has
930    // been constructed, so the init is deferred to `init_globals()`, called from the root
931    // component's `create()` once `globals` and `root_weak` are in place.
932    globals_struct.members.push((
933        Access::Public,
934        Declaration::Function(Function {
935            name: globals_struct.name.clone(),
936            is_constructor_or_destructor: true,
937            signature: "()".into(),
938            statements: Some(vec![]),
939            ..Default::default()
940        }),
941    ));
942    globals_struct.members.push((
943        Access::Public,
944        Declaration::Function(Function {
945            name: "init_globals".into(),
946            signature: "() -> void".into(),
947            statements: Some(init_global),
948            ..Default::default()
949        }),
950    ));
951
952    // Build initializer-list string for the clone_with_window_adapter constructor
953    {
954        let global_inits = std::iter::once("root_weak(source.root_weak)".to_string())
955            .chain(clone_constructor_global_inits)
956            .collect::<Vec<_>>()
957            .join(", ");
958        let init_list =
959            if global_inits.is_empty() { String::new() } else { format!(" : {global_inits}") };
960
961        // A private constructor for cloning with a different window adapter
962        globals_struct.members.push((
963                Access::Private,
964                Declaration::Function(Function {
965                    name: globals_struct.name.clone(),
966                    is_constructor_or_destructor: true,
967                    signature: format!(
968                        "(const {SHARED_GLOBAL_CLASS}& source, const slint::private_api::WindowAdapterRc& adapter){init_list}"
969                    ),
970                    statements: Some(vec!["m_window.emplace(adapter);".into()]),
971                    ..Default::default()
972                }),
973            ));
974
975        globals_struct.members.push((
976                Access::Public,
977                Declaration::Function(Function {
978                    name: "clone_with_window_adapter".into(),
979                    signature: format!("(const slint::private_api::WindowAdapterRc& adapter) const -> {SHARED_GLOBAL_CLASS}*"),
980                    statements: Some(vec![format!(
981                        "return new {SHARED_GLOBAL_CLASS}(*this, adapter);"
982                    )]),
983                    ..Default::default()
984                }),
985            ));
986    }
987
988    file.declarations.push(Declaration::Struct(globals_struct));
989
990    if let Some(popup_menu) = &llr.popup_menu {
991        let component_id = ident(&llr.sub_components[popup_menu.item_tree.root].name);
992        let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
993        generate_item_tree(
994            &mut popup_struct,
995            &popup_menu.item_tree,
996            &llr,
997            None,
998            true,
999            component_id,
1000            Access::Public,
1001            &mut file,
1002            &conditional_includes,
1003        );
1004        file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
1005        file.declarations.push(Declaration::Struct(popup_struct));
1006    };
1007
1008    for p in &llr.public_components {
1009        generate_public_component(&mut file, &conditional_includes, p, &llr);
1010    }
1011
1012    generate_type_aliases(&mut file, doc);
1013
1014    if conditional_includes.iostream.get() {
1015        file.includes.push("<iostream>".into());
1016    }
1017
1018    if conditional_includes.cstdlib.get() {
1019        file.includes.push("<cstdlib>".into());
1020    }
1021
1022    if conditional_includes.cmath.get() {
1023        file.includes.push("<cmath>".into());
1024    }
1025
1026    let cpp_files = file.split_off_cpp_files(config.header_include, config.cpp_files.len());
1027
1028    for (cpp_file_name, cpp_file) in config.cpp_files.iter().zip(cpp_files) {
1029        // Important: Write without unnecessary mtime modification to avoid
1030        // build systems to always detect the generated file as modified.
1031        fileaccess::write_file_if_changed(cpp_file_name, cpp_file.to_string().as_bytes())?;
1032    }
1033
1034    Ok(file)
1035}
1036
1037pub fn generate_types(used_types: &[Type], config: &Config) -> File {
1038    let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
1039
1040    file.includes.push("<array>".into());
1041    file.includes.push("<limits>".into());
1042    file.includes.push("<slint.h>".into());
1043
1044    file.after_includes = format!(
1045        "static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \
1046        \"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \
1047        SLINT_VERSION_STRING \". The version numbers must match exactly.\");",
1048        x = env!("CARGO_PKG_VERSION_MAJOR"),
1049        y = env!("CARGO_PKG_VERSION_MINOR"),
1050        z = env!("CARGO_PKG_VERSION_PATCH")
1051    );
1052
1053    for ty in used_types {
1054        match ty {
1055            Type::Struct(s) if s.node().is_some() => {
1056                generate_struct(&mut file, &s.name, &s.fields);
1057            }
1058            Type::Enumeration(en) => {
1059                generate_enum(&mut file, en);
1060            }
1061            _ => (),
1062        }
1063    }
1064
1065    file
1066}
1067
1068fn expand_data_to_cpp_u8_array(data: &[u8]) -> String {
1069    let mut init = "{ ".to_string();
1070
1071    for (index, byte) in data.iter().enumerate() {
1072        if index > 0 {
1073            init.push(',');
1074        }
1075        write!(&mut init, "0x{byte:x}").unwrap();
1076        if index % 16 == 0 {
1077            init.push('\n');
1078        }
1079    }
1080
1081    init.push('}');
1082    init
1083}
1084
1085fn embed_resource(
1086    resource: &crate::embedded_resources::EmbeddedResources,
1087    resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
1088    declarations: &mut Vec<Declaration>,
1089) {
1090    match &resource.kind {
1091        crate::embedded_resources::EmbeddedResourcesKind::ListOnly => {}
1092        crate::embedded_resources::EmbeddedResourcesKind::FileData => {
1093            let resource_file = crate::fileaccess::load_file(std::path::Path::new(
1094                resource.path.as_deref().unwrap(),
1095            ))
1096            .unwrap(); // embedding pass ensured that the file exists
1097            let data = resource_file.read();
1098
1099            declarations.push(Declaration::Var(Var {
1100                ty: "const uint8_t".into(),
1101                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
1102                array_size: Some(data.len()),
1103                init: Some(expand_data_to_cpp_u8_array(data.as_ref())),
1104                ..Default::default()
1105            }));
1106        }
1107        crate::embedded_resources::EmbeddedResourcesKind::DataUriPayload(data, _) => {
1108            declarations.push(Declaration::Var(Var {
1109                ty: "const uint8_t".into(),
1110                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
1111                array_size: Some(data.len()),
1112                init: Some(expand_data_to_cpp_u8_array(data)),
1113                ..Default::default()
1114            }));
1115        }
1116        #[cfg(feature = "software-renderer")]
1117        crate::embedded_resources::EmbeddedResourcesKind::TextureData(
1118            crate::embedded_resources::Texture {
1119                data,
1120                format,
1121                rect,
1122                total_size: crate::embedded_resources::Size { width, height },
1123                original_size:
1124                    crate::embedded_resources::Size { width: unscaled_width, height: unscaled_height },
1125            },
1126        ) => {
1127            let (r_x, r_y, r_w, r_h) = (rect.x(), rect.y(), rect.width(), rect.height());
1128            let color = if let crate::embedded_resources::PixelFormat::AlphaMap([r, g, b]) = format
1129            {
1130                format!("slint::Color::from_rgb_uint8({r}, {g}, {b})")
1131            } else {
1132                "slint::Color{}".to_string()
1133            };
1134            let count = data.len();
1135            let data = data.iter().map(ToString::to_string).join(", ");
1136            let data_name = format_smolstr!("slint_embedded_resource_{}_data", resource_id);
1137            declarations.push(Declaration::Var(Var {
1138                ty: "const uint8_t".into(),
1139                name: data_name.clone(),
1140                array_size: Some(count),
1141                init: Some(format!("{{ {data} }}")),
1142                ..Default::default()
1143            }));
1144            let texture_name = format_smolstr!("slint_embedded_resource_{}_texture", resource_id);
1145            declarations.push(Declaration::Var(Var {
1146                ty: "const slint::cbindgen_private::types::StaticTexture".into(),
1147                name: texture_name.clone(),
1148                array_size: None,
1149                init: Some(format!(
1150                    "{{
1151                            .rect = {{ {r_x}, {r_y}, {r_w}, {r_h} }},
1152                            .format = slint::cbindgen_private::types::TexturePixelFormat::{format},
1153                            .color = {color},
1154                            .index = 0,
1155                            }}"
1156                )),
1157                ..Default::default()
1158            }));
1159            let init = format!(
1160                "slint::cbindgen_private::types::StaticTextures {{
1161                        .size = {{ {width}, {height} }},
1162                        .original_size = {{ {unscaled_width}, {unscaled_height} }},
1163                        .data = slint::private_api::make_slice({data_name} , {count} ),
1164                        .textures = slint::private_api::make_slice(&{texture_name}, 1)
1165                    }}"
1166            );
1167            declarations.push(Declaration::Var(Var {
1168                ty: "const slint::cbindgen_private::types::StaticTextures".into(),
1169                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
1170                array_size: None,
1171                init: Some(init),
1172                ..Default::default()
1173            }))
1174        }
1175        #[cfg(feature = "software-renderer")]
1176        crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
1177            crate::embedded_resources::BitmapFont {
1178                family_name,
1179                character_map,
1180                units_per_em,
1181                ascent,
1182                descent,
1183                x_height,
1184                cap_height,
1185                glyphs,
1186                weight,
1187                italic,
1188                sdf,
1189            },
1190        ) => {
1191            let family_name_var =
1192                format_smolstr!("slint_embedded_resource_{}_family_name", resource_id);
1193            let family_name_size = family_name.len();
1194            declarations.push(Declaration::Var(Var {
1195                ty: "const uint8_t".into(),
1196                name: family_name_var.clone(),
1197                array_size: Some(family_name_size),
1198                init: Some(format!(
1199                    "{{ {} }}",
1200                    family_name.as_bytes().iter().map(ToString::to_string).join(", ")
1201                )),
1202                ..Default::default()
1203            }));
1204
1205            let charmap_var = format_smolstr!("slint_embedded_resource_{}_charmap", resource_id);
1206            let charmap_size = character_map.len();
1207            declarations.push(Declaration::Var(Var {
1208                ty: "const slint::cbindgen_private::CharacterMapEntry".into(),
1209                name: charmap_var.clone(),
1210                array_size: Some(charmap_size),
1211                init: Some(format!(
1212                    "{{ {} }}",
1213                    character_map
1214                        .iter()
1215                        .map(|entry| format!(
1216                            "{{ .code_point = {}, .glyph_index = {} }}",
1217                            entry.code_point as u32, entry.glyph_index
1218                        ))
1219                        .join(", ")
1220                )),
1221                ..Default::default()
1222            }));
1223
1224            for (glyphset_index, glyphset) in glyphs.iter().enumerate() {
1225                for (glyph_index, glyph) in glyphset.glyph_data.iter().enumerate() {
1226                    declarations.push(Declaration::Var(Var {
1227                        ty: "const uint8_t".into(),
1228                        name: format_smolstr!(
1229                            "slint_embedded_resource_{}_gs_{}_gd_{}",
1230                            resource_id,
1231                            glyphset_index,
1232                            glyph_index
1233                        ),
1234                        array_size: Some(glyph.data.len()),
1235                        init: Some(format!(
1236                            "{{ {} }}",
1237                            glyph.data.iter().map(ToString::to_string).join(", ")
1238                        )),
1239                        ..Default::default()
1240                    }));
1241                }
1242
1243                declarations.push(Declaration::Var(Var{
1244                    ty: "const slint::cbindgen_private::BitmapGlyph".into(),
1245                    name: format_smolstr!("slint_embedded_resource_{}_glyphset_{}", resource_id, glyphset_index),
1246                    array_size: Some(glyphset.glyph_data.len()),
1247                    init: Some(format!("{{ {} }}", glyphset.glyph_data.iter().enumerate().map(|(glyph_index, glyph)| {
1248                        format!("{{ .x = {}, .y = {}, .width = {}, .height = {}, .x_advance = {}, .data = slint::private_api::make_slice({}, {}) }}",
1249                        glyph.x, glyph.y, glyph.width, glyph.height, glyph.x_advance,
1250                        format_args!("slint_embedded_resource_{}_gs_{}_gd_{}", resource_id, glyphset_index, glyph_index),
1251                        glyph.data.len()
1252                    )
1253                    }).join(", \n"))),
1254                    ..Default::default()
1255                }));
1256            }
1257
1258            let glyphsets_var =
1259                format_smolstr!("slint_embedded_resource_{}_glyphsets", resource_id);
1260            let glyphsets_size = glyphs.len();
1261            declarations.push(Declaration::Var(Var {
1262                ty: "const slint::cbindgen_private::BitmapGlyphs".into(),
1263                name: glyphsets_var.clone(),
1264                array_size: Some(glyphsets_size),
1265                init: Some(format!(
1266                    "{{ {} }}",
1267                    glyphs
1268                        .iter()
1269                        .enumerate()
1270                        .map(|(glyphset_index, glyphset)| format!(
1271                            "{{ .pixel_size = {}, .glyph_data = slint::private_api::make_slice({}, {}) }}",
1272                            glyphset.pixel_size, format_args!("slint_embedded_resource_{}_glyphset_{}", resource_id, glyphset_index), glyphset.glyph_data.len()
1273                        ))
1274                        .join(", \n")
1275                )),
1276                ..Default::default()
1277            }));
1278
1279            let init = format!(
1280                "slint::cbindgen_private::BitmapFont {{
1281                        .family_name = slint::private_api::make_slice({family_name_var} , {family_name_size}),
1282                        .character_map = slint::private_api::make_slice({charmap_var}, {charmap_size}),
1283                        .units_per_em = {units_per_em},
1284                        .ascent = {ascent},
1285                        .descent = {descent},
1286                        .x_height = {x_height},
1287                        .cap_height = {cap_height},
1288                        .glyphs = slint::private_api::make_slice({glyphsets_var}, {glyphsets_size}),
1289                        .weight = {weight},
1290                        .italic = {italic},
1291                        .sdf = {sdf},
1292                }}"
1293            );
1294
1295            declarations.push(Declaration::Var(Var {
1296                ty: "const slint::cbindgen_private::BitmapFont".into(),
1297                name: format_smolstr!("slint_embedded_resource_{}", resource_id),
1298                array_size: None,
1299                init: Some(init),
1300                ..Default::default()
1301            }))
1302        }
1303    }
1304}
1305
1306fn generate_struct(file: &mut File, name: &StructName, fields: &BTreeMap<SmolStr, Type>) {
1307    let StructName::User { name: user_name, node } = name else {
1308        panic!("internal error: Cannot generate anonymous struct");
1309    };
1310    let name = ident(user_name);
1311    let mut members = node
1312        .ObjectTypeMember()
1313        .map(|n| crate::parser::identifier_text(&n).unwrap())
1314        .map(|name| {
1315            (
1316                Access::Public,
1317                Declaration::Var(Var {
1318                    ty: fields.get(&name).unwrap().cpp_type().unwrap(),
1319                    name: ident(&name),
1320                    ..Default::default()
1321                }),
1322            )
1323        })
1324        .collect::<Vec<_>>();
1325
1326    members.push((
1327        Access::Public,
1328        Declaration::Function(Function {
1329            name: "operator==".into(),
1330            signature: format!("(const class {name} &a, const class {name} &b) -> bool = default"),
1331            is_friend: true,
1332            statements: None,
1333            ..Function::default()
1334        }),
1335    ));
1336
1337    file.declarations.push(Declaration::Struct(Struct { name, members, ..Default::default() }))
1338}
1339
1340fn generate_enum(file: &mut File, en: &std::rc::Rc<Enumeration>) {
1341    file.declarations.push(Declaration::Enum(Enum {
1342        name: ident(&en.name),
1343        values: (0..en.values.len())
1344            .map(|value| {
1345                ident(&EnumerationValue { value, enumeration: en.clone() }.to_pascal_case())
1346            })
1347            .collect(),
1348    }))
1349}
1350
1351/// Generate the component in `file`.
1352///
1353/// `sub_components`, if Some, will be filled with all the sub component which needs to be added as friends
1354fn generate_public_component(
1355    file: &mut File,
1356    conditional_includes: &ConditionalIncludes,
1357    component: &llr::PublicComponent,
1358    unit: &llr::CompilationUnit,
1359) {
1360    let component_id = ident(&component.name);
1361
1362    let mut component_struct = Struct { name: component_id.clone(), ..Default::default() };
1363
1364    // need to be the first member, because it contains the window which is to be destroyed last
1365    component_struct.members.push((
1366        Access::Private,
1367        Declaration::Var(Var {
1368            ty: SmolStr::new_static(SHARED_GLOBAL_CLASS),
1369            name: "m_globals".into(),
1370            ..Default::default()
1371        }),
1372    ));
1373
1374    for glob in unit.globals.iter().filter(|glob| glob.must_generate() && !glob.is_builtin) {
1375        component_struct.friends.push(ident(&glob.name));
1376    }
1377
1378    let mut global_accessor_function_body = Vec::new();
1379    let mut builtin_globals = Vec::new();
1380    for glob in unit.globals.iter().filter(|glob| glob.exported && glob.must_generate()) {
1381        let accessor_statement = if glob.is_builtin {
1382            builtin_globals.push(format!("std::is_same_v<T, {}>", ident(&glob.name)));
1383            format!(
1384                "{0}if constexpr(std::is_same_v<T, {1}>) {{ return {1}(m_globals.global_{1}); }}",
1385                if global_accessor_function_body.is_empty() { "" } else { "else " },
1386                concatenate_ident(&glob.name),
1387            )
1388        } else {
1389            format!(
1390                "{0}if constexpr(std::is_same_v<T, {1}>) {{ return *m_globals.global_{1}.get(); }}",
1391                if global_accessor_function_body.is_empty() { "" } else { "else " },
1392                concatenate_ident(&glob.name),
1393            )
1394        };
1395        global_accessor_function_body.push(accessor_statement);
1396    }
1397    if !global_accessor_function_body.is_empty() {
1398        global_accessor_function_body.push(
1399            "else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(),
1400        );
1401
1402        component_struct.members.push((
1403            Access::Public,
1404            Declaration::Function(Function {
1405                name: "global".into(),
1406                signature: if builtin_globals.is_empty() {
1407                    "() const -> const T&".into()
1408                } else {
1409                    format!(
1410                        "() const -> std::conditional_t<{} , T, const T&>",
1411                        builtin_globals.iter().join(" || ")
1412                    )
1413                },
1414                statements: Some(global_accessor_function_body),
1415                template_parameters: Some("typename T".into()),
1416                ..Default::default()
1417            }),
1418        ));
1419    }
1420
1421    let ctx = EvaluationContext {
1422        compilation_unit: unit,
1423        current_scope: EvaluationScope::SubComponent(component.item_tree.root, None),
1424        generator_state: CppGeneratorContext {
1425            global_access: "(&this->m_globals)".to_string(),
1426            conditional_includes,
1427        },
1428        argument_types: &[],
1429    };
1430
1431    let old_declarations = file.declarations.len();
1432
1433    generate_item_tree(
1434        &mut component_struct,
1435        &component.item_tree,
1436        unit,
1437        None,
1438        false,
1439        component_id,
1440        Access::Private, // Hide properties and other fields from the C++ API
1441        file,
1442        conditional_includes,
1443    );
1444
1445    // Give generated sub-components, etc. access to our fields
1446
1447    for new_decl in file.declarations.iter().skip(old_declarations) {
1448        if let Declaration::Struct(struc @ Struct { .. }) = new_decl {
1449            component_struct.friends.push(struc.name.clone());
1450        };
1451    }
1452
1453    generate_public_api_for_properties(
1454        &mut component_struct.members,
1455        &component.public_properties,
1456        &component.private_properties,
1457        &ctx,
1458    );
1459
1460    // Window-rooted components route `show`/`hide` through the underlying
1461    // window adapter, expose `window()`, and have a `run()` that drives the
1462    // event loop. SystemTrayIcon-rooted components instead toggle the `visible`
1463    // property on the tray native item, expose no `window()`, and skip
1464    // `run()` entirely (a tray icon doesn't drive the event loop).
1465    let (show_body, hide_body) = match component.top_level_type {
1466        llr::TopLevelComponentType::Window => {
1467            ("m_globals.window().show();".to_string(), "m_globals.window().hide();".to_string())
1468        }
1469        llr::TopLevelComponentType::SystemTrayIcon => {
1470            let root_sub = &unit.sub_components[component.item_tree.root];
1471            let tray_item = &root_sub.items[llr::ItemInstanceIdx::from(0usize)];
1472            debug_assert_eq!(
1473                tray_item.ty.class_name.as_str(),
1474                "SystemTrayIcon",
1475                "TopLevelComponentType::SystemTrayIcon expects the root item to be a SystemTrayIcon"
1476            );
1477            let tray_field = field_name(&tray_item.name);
1478            (
1479                format!("{tray_field}.visible.set(true);"),
1480                format!("{tray_field}.visible.set(false);"),
1481            )
1482        }
1483    };
1484
1485    component_struct.members.push((
1486        Access::Public,
1487        Declaration::Function(Function {
1488            name: "show".into(),
1489            signature: "() -> void".into(),
1490            statements: Some(vec![show_body]),
1491            ..Default::default()
1492        }),
1493    ));
1494
1495    component_struct.members.push((
1496        Access::Public,
1497        Declaration::Function(Function {
1498            name: "hide".into(),
1499            signature: "() -> void".into(),
1500            statements: Some(vec![hide_body]),
1501            ..Default::default()
1502        }),
1503    ));
1504
1505    match component.top_level_type {
1506        llr::TopLevelComponentType::Window => {
1507            component_struct.members.push((
1508                Access::Public,
1509                Declaration::Function(Function {
1510                    name: "window".into(),
1511                    signature: "() const -> slint::Window&".into(),
1512                    statements: Some(vec!["return m_globals.window();".into()]),
1513                    ..Default::default()
1514                }),
1515            ));
1516            component_struct.members.push((
1517                Access::Public,
1518                Declaration::Function(Function {
1519                    name: "run".into(),
1520                    signature: "() -> void".into(),
1521                    statements: Some(vec![
1522                        "show();".into(),
1523                        "slint::run_event_loop();".into(),
1524                        "hide();".into(),
1525                    ]),
1526                    ..Default::default()
1527                }),
1528            ));
1529        }
1530        llr::TopLevelComponentType::SystemTrayIcon => {}
1531    }
1532
1533    component_struct.friends.push("slint::private_api::WindowAdapterRc".into());
1534
1535    add_friends(&mut component_struct.friends, unit, component.item_tree.root, true);
1536
1537    fn add_friends(
1538        friends: &mut Vec<SmolStr>,
1539        unit: &llr::CompilationUnit,
1540        c: llr::SubComponentIdx,
1541        is_root: bool,
1542    ) {
1543        let sc = &unit.sub_components[c];
1544        if !is_root {
1545            friends.push(ident(&sc.name));
1546        }
1547        for repeater in &sc.repeated {
1548            add_friends(friends, unit, repeater.sub_tree.root, false)
1549        }
1550        for popup in &sc.popup_windows {
1551            add_friends(friends, unit, popup.item_tree.root, false)
1552        }
1553        for menu in &sc.menu_item_trees {
1554            add_friends(friends, unit, menu.root, false)
1555        }
1556    }
1557
1558    file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
1559    file.declarations.push(Declaration::Struct(component_struct));
1560}
1561
1562fn generate_item_tree(
1563    target_struct: &mut Struct,
1564    sub_tree: &llr::ItemTree,
1565    root: &llr::CompilationUnit,
1566    parent_ctx: Option<&ParentScope>,
1567    is_popup: bool,
1568    item_tree_class_name: SmolStr,
1569    field_access: Access,
1570    file: &mut File,
1571    conditional_includes: &ConditionalIncludes,
1572) {
1573    let needs_window_adapter = root.needs_window_adapter();
1574    // True only for the root tree of a SystemTrayIcon-rooted public component.
1575    // Repeaters / popup_menu / popup-window trees stay on the windowed code
1576    // path even when they live inside a tray-only unit (popup menus are
1577    // window-shaped, and there's no SystemTrayIcon-rooted repeater root anyway).
1578    let is_system_tray_root = parent_ctx.is_none()
1579        && !is_popup
1580        && root.public_components.iter().any(|p| {
1581            p.item_tree.root == sub_tree.root
1582                && p.top_level_type == llr::TopLevelComponentType::SystemTrayIcon
1583        });
1584
1585    target_struct.friends.push(format_smolstr!(
1586        "vtable::VRc<slint::private_api::ItemTreeVTable, {}>",
1587        item_tree_class_name
1588    ));
1589
1590    generate_sub_component(
1591        target_struct,
1592        sub_tree.root,
1593        root,
1594        parent_ctx,
1595        field_access,
1596        file,
1597        conditional_includes,
1598    );
1599
1600    let mut item_tree_array: Vec<String> = Default::default();
1601    let mut item_array: Vec<String> = Default::default();
1602
1603    sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| {
1604        let parent_index = parent_index as u32;
1605
1606        match node.item_index {
1607            Either::Right(mut repeater_index) => {
1608                assert_eq!(node.children.len(), 0);
1609                let mut sub_component = &root.sub_components[sub_tree.root];
1610                for i in &node.sub_component_path {
1611                    repeater_index += sub_component.sub_components[*i].repeater_offset;
1612                    sub_component = &root.sub_components[sub_component.sub_components[*i].ty];
1613                }
1614                item_tree_array.push(format!(
1615                    "slint::private_api::make_dyn_node({repeater_index}, {parent_index})"
1616                ));
1617            }
1618            Either::Left(item_index) => {
1619                let mut compo_offset = String::new();
1620                let mut sub_component = &root.sub_components[sub_tree.root];
1621                for i in &node.sub_component_path {
1622                    let next_sub_component_name =
1623                        field_name(&sub_component.sub_components[*i].name);
1624                    write!(
1625                        compo_offset,
1626                        "offsetof({}, {}) + ",
1627                        ident(&sub_component.name),
1628                        next_sub_component_name
1629                    )
1630                    .unwrap();
1631                    sub_component = &root.sub_components[sub_component.sub_components[*i].ty];
1632                }
1633
1634                let item = &sub_component.items[item_index];
1635                let children_count = node.children.len() as u32;
1636                let children_index = children_offset as u32;
1637                let item_array_index = item_array.len() as u32;
1638
1639                item_tree_array.push(format!(
1640                    "slint::private_api::make_item_node({}, {}, {}, {}, {})",
1641                    children_count,
1642                    children_index,
1643                    parent_index,
1644                    item_array_index,
1645                    node.is_accessible
1646                ));
1647                item_array.push(format!(
1648                    "{{ {}, {} offsetof({}, {}) }}",
1649                    item.ty.cpp_vtable_getter,
1650                    compo_offset,
1651                    &ident(&sub_component.name),
1652                    field_name(&item.name),
1653                ));
1654            }
1655        }
1656    });
1657
1658    let mut visit_children_statements = vec![
1659        "static const auto dyn_visit = [] (const void *base,  [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor, [[maybe_unused]] uint32_t dyn_index) -> uint64_t {".to_owned(),
1660        format!("    [[maybe_unused]] auto self = reinterpret_cast<const {}*>(base);", item_tree_class_name)];
1661    let mut subtree_range_statement = vec!["    std::abort();".into()];
1662    let mut subtree_component_statement = vec!["    std::abort();".into()];
1663
1664    if target_struct.members.iter().any(|(_, declaration)| {
1665        matches!(&declaration, Declaration::Function(func @ Function { .. }) if func.name == "visit_dynamic_children")
1666    }) {
1667        visit_children_statements
1668            .push("    return self->visit_dynamic_children(dyn_index, order, visitor);".into());
1669        subtree_range_statement = vec![
1670                format!("auto self = reinterpret_cast<const {}*>(component.instance);", item_tree_class_name),
1671                "return self->subtree_range(dyn_index);".to_owned(),
1672        ];
1673        subtree_component_statement = vec![
1674                format!("auto self = reinterpret_cast<const {}*>(component.instance);", item_tree_class_name),
1675                "self->subtree_component(dyn_index, subtree_index, result);".to_owned(),
1676        ];
1677    } else {
1678        visit_children_statements.push("    std::abort();".into());
1679     }
1680
1681    visit_children_statements.extend([
1682        "};".into(),
1683        format!("auto self_rc = reinterpret_cast<const {item_tree_class_name}*>(component.instance)->self_weak.lock()->into_dyn();"),
1684        "return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component) , index, order, visitor, dyn_visit);".to_owned(),
1685    ]);
1686
1687    target_struct.members.push((
1688        Access::Private,
1689        Declaration::Function(Function {
1690            name: "visit_children".into(),
1691            signature: "(slint::private_api::ItemTreeRef component, intptr_t index, slint::private_api::TraversalOrder order, slint::private_api::ItemVisitorRefMut visitor) -> uint64_t".into(),
1692            is_static: true,
1693            statements: Some(visit_children_statements),
1694            ..Default::default()
1695        }),
1696    ));
1697
1698    target_struct.members.push((
1699        Access::Private,
1700        Declaration::Function(Function {
1701            name: "get_item_ref".into(),
1702            signature: "(slint::private_api::ItemTreeRef component, uint32_t index) -> slint::private_api::ItemRef".into(),
1703            is_static: true,
1704            statements: Some(vec![
1705                "return slint::private_api::get_item_ref(component, get_item_tree(component), item_array(), index);".to_owned(),
1706            ]),
1707            ..Default::default()
1708        }),
1709    ));
1710
1711    target_struct.members.push((
1712        Access::Private,
1713        Declaration::Function(Function {
1714            name: "get_subtree_range".into(),
1715            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index) -> slint::private_api::IndexRange".into(),
1716            is_static: true,
1717            statements: Some(subtree_range_statement),
1718            ..Default::default()
1719        }),
1720    ));
1721
1722    target_struct.members.push((
1723        Access::Private,
1724        Declaration::Function(Function {
1725            name: "get_subtree".into(),
1726            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) -> void".into(),
1727            is_static: true,
1728            statements: Some(subtree_component_statement),
1729            ..Default::default()
1730        }),
1731    ));
1732
1733    target_struct.members.push((
1734        Access::Private,
1735        Declaration::Function(Function {
1736            name: "get_item_tree".into(),
1737            signature: "(slint::private_api::ItemTreeRef) -> slint::cbindgen_private::Slice<slint::private_api::ItemTreeNode>".into(),
1738            is_static: true,
1739            statements: Some(vec![
1740                "return item_tree();".to_owned(),
1741            ]),
1742            ..Default::default()
1743        }),
1744    ));
1745
1746    let parent_item_from_parent_component = parent_ctx.as_ref()
1747        .map(|parent| {
1748            parent.repeater_index.map_or_else(|| {
1749                // No repeater index, this could be a PopupWindow
1750                vec![
1751                    format!("auto self = reinterpret_cast<const {item_tree_class_name}*>(component.instance);"),
1752                    format!("auto parent = self->parent.lock().value();"),
1753                    // TODO: store popup index in ctx and set it here instead of 0?
1754                    format!("*result = {{ parent->self_weak, 0 }};"),
1755                    ]
1756                }, |idx| {
1757                let current_sub_component = &root.sub_components[parent.sub_component];
1758                let parent_index = current_sub_component.repeated[idx].index_in_tree;
1759                vec![
1760                    format!("auto self = reinterpret_cast<const {item_tree_class_name}*>(component.instance);"),
1761                    format!("auto parent = self->parent.lock().value();"),
1762                    format!("*result = {{ parent->self_weak, parent->tree_index_of_first_child + {} }};", parent_index - 1),
1763                ]
1764            })
1765        })
1766        .unwrap_or_default();
1767    target_struct.members.push((
1768        Access::Private,
1769        Declaration::Function(Function {
1770            name: "parent_node".into(),
1771            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] slint::private_api::ItemWeak *result) -> void".into(),
1772            is_static: true,
1773            statements: Some(parent_item_from_parent_component,),
1774            ..Default::default()
1775        }),
1776    ));
1777
1778    target_struct.members.push((
1779        Access::Private,
1780        Declaration::Function(Function {
1781            name: "embed_component".into(),
1782            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] const slint::private_api::ItemTreeWeak *parent_component, [[maybe_unused]] const uint32_t parent_index) -> bool".into(),
1783            is_static: true,
1784            statements: Some(vec!["return false; /* todo! */".into()]),
1785            ..Default::default()
1786        }),
1787    ));
1788
1789    // Statements will be overridden for repeated components!
1790    target_struct.members.push((
1791        Access::Private,
1792        Declaration::Function(Function {
1793            name: "subtree_index".into(),
1794            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component) -> uintptr_t"
1795                .into(),
1796            is_static: true,
1797            statements: Some(vec!["return std::numeric_limits<uintptr_t>::max();".into()]),
1798            ..Default::default()
1799        }),
1800    ));
1801
1802    target_struct.members.push((
1803        Access::Private,
1804        Declaration::Function(Function {
1805            name: "item_tree".into(),
1806            signature: "() -> slint::cbindgen_private::Slice<slint::private_api::ItemTreeNode>"
1807                .into(),
1808            is_static: true,
1809            statements: Some(vec![
1810                "static const slint::private_api::ItemTreeNode children[] {".to_owned(),
1811                format!("    {} }};", item_tree_array.join(", \n")),
1812                "return slint::private_api::make_slice(std::span(children));".to_owned(),
1813            ]),
1814            ..Default::default()
1815        }),
1816    ));
1817
1818    target_struct.members.push((
1819        Access::Private,
1820        Declaration::Function(Function {
1821            name: "item_array".into(),
1822            signature: "() -> const slint::private_api::ItemArray".into(),
1823            is_static: true,
1824            statements: Some(vec![
1825                "static const slint::private_api::ItemArrayEntry items[] {".to_owned(),
1826                format!("    {} }};", item_array.join(", \n")),
1827                "return slint::private_api::make_slice(std::span(items));".to_owned(),
1828            ]),
1829            ..Default::default()
1830        }),
1831    ));
1832
1833    target_struct.members.push((
1834        Access::Private,
1835        Declaration::Function(Function {
1836            name: "layout_info".into(),
1837            signature:
1838                "([[maybe_unused]] slint::private_api::ItemTreeRef component, slint::cbindgen_private::Orientation o) -> slint::cbindgen_private::LayoutInfo"
1839                    .into(),
1840            is_static: true,
1841            statements: Some(vec![format!(
1842                "return reinterpret_cast<const {}*>(component.instance)->layout_info(o);",
1843                item_tree_class_name
1844            )]),
1845            ..Default::default()
1846        }),
1847    ));
1848
1849    target_struct.members.push((
1850        Access::Private,
1851        Declaration::Function(Function {
1852            name: "ensure_instantiated".into(),
1853            signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component) -> bool"
1854                .into(),
1855            is_static: true,
1856            statements: Some(vec![format!(
1857                "return reinterpret_cast<const {}*>(component.instance)->ensure_instantiated();",
1858                item_tree_class_name
1859            )]),
1860            ..Default::default()
1861        }),
1862    ));
1863
1864    target_struct.members.push((
1865        Access::Private,
1866        Declaration::Function(Function {
1867            name: "item_geometry".into(),
1868            signature:
1869                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::LogicalRect"
1870                    .into(),
1871            is_static: true,
1872            statements: Some(vec![format!(
1873                "return reinterpret_cast<const {}*>(component.instance)->item_geometry(index);",
1874                item_tree_class_name
1875            ), ]),
1876            ..Default::default()
1877        }),
1878    ));
1879
1880    target_struct.members.push((
1881        Access::Private,
1882        Declaration::Function(Function {
1883            name: "accessible_role".into(),
1884            signature:
1885                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::AccessibleRole"
1886                    .into(),
1887            is_static: true,
1888            statements: Some(vec![format!(
1889                "return reinterpret_cast<const {}*>(component.instance)->accessible_role(index);",
1890                item_tree_class_name
1891            )]),
1892            ..Default::default()
1893        }),
1894    ));
1895
1896    target_struct.members.push((
1897        Access::Private,
1898        Declaration::Function(Function {
1899            name: "accessible_string_property".into(),
1900            signature:
1901                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, slint::cbindgen_private::AccessibleStringProperty what, slint::SharedString *result) -> bool"
1902                    .into(),
1903            is_static: true,
1904            statements: Some(vec![format!(
1905                "if (auto r = reinterpret_cast<const {}*>(component.instance)->accessible_string_property(index, what)) {{ *result = *r; return true; }} else {{ return false; }}",
1906                item_tree_class_name
1907            )]),
1908            ..Default::default()
1909        }),
1910    ));
1911
1912    target_struct.members.push((
1913        Access::Private,
1914        Declaration::Function(Function {
1915            name: "accessibility_action".into(),
1916            signature:
1917                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, const slint::cbindgen_private::AccessibilityAction *action) -> void"
1918                    .into(),
1919            is_static: true,
1920            statements: Some(vec![format!(
1921                "reinterpret_cast<const {}*>(component.instance)->accessibility_action(index, *action);",
1922                item_tree_class_name
1923            )]),
1924            ..Default::default()
1925        }),
1926    ));
1927
1928    target_struct.members.push((
1929        Access::Private,
1930        Declaration::Function(Function {
1931            name: "supported_accessibility_actions".into(),
1932            signature:
1933                "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> uint32_t"
1934                    .into(),
1935            is_static: true,
1936            statements: Some(vec![format!(
1937                "return reinterpret_cast<const {}*>(component.instance)->supported_accessibility_actions(index);",
1938                item_tree_class_name
1939            )]),
1940            ..Default::default()
1941        }),
1942    ));
1943
1944    target_struct.members.push((
1945        Access::Private,
1946        Declaration::Function(Function {
1947            name: "element_infos".into(),
1948            signature:
1949                "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t index, [[maybe_unused]] slint::SharedString *result) -> bool"
1950                    .into(),
1951            is_static: true,
1952            statements: Some(if root.has_debug_info {
1953                vec![
1954                    format!("if (auto infos = reinterpret_cast<const {}*>(component.instance)->element_infos(index)) {{ *result = *infos; }};",
1955                    item_tree_class_name),
1956                    "return true;".into()
1957                ]
1958            } else {
1959                vec!["return false;".into()]
1960            }),
1961            ..Default::default()
1962        }),
1963    ));
1964
1965    let window_adapter_vtable_statements = if needs_window_adapter {
1966        vec![format!(
1967            "*reinterpret_cast<slint::private_api::WindowAdapterRc*>(result) = reinterpret_cast<const {item_tree_class_name}*>(component.instance)->globals->window().window_handle();"
1968        )]
1969    } else {
1970        // Tray-only units have no `WindowAdapter`. The runtime initializes
1971        // `*result` to None before calling, so leaving it untouched reports
1972        // "no adapter" — and crucially `do_create=true` no longer silently
1973        // materializes a hidden window adapter.
1974        vec![]
1975    };
1976    target_struct.members.push((
1977        Access::Private,
1978        Declaration::Function(Function {
1979            name: "window_adapter".into(),
1980            signature:
1981                "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] bool do_create, [[maybe_unused]] slint::cbindgen_private::Option<slint::private_api::WindowAdapterRc>* result) -> void"
1982                    .into(),
1983            is_static: true,
1984            statements: Some(window_adapter_vtable_statements),
1985            ..Default::default()
1986        }),
1987    ));
1988
1989    target_struct.members.push((
1990        Access::Public,
1991        Declaration::Var(Var {
1992            ty: "static const slint::private_api::ItemTreeVTable".into(),
1993            name: "static_vtable".into(),
1994            ..Default::default()
1995        }),
1996    ));
1997
1998    file.definitions.push(Declaration::Var(Var {
1999        ty: "const slint::private_api::ItemTreeVTable".into(),
2000        name: format_smolstr!("{}::static_vtable", item_tree_class_name),
2001        init: Some(format!(
2002            "{{ visit_children, get_item_ref, get_subtree_range, get_subtree, \
2003                get_item_tree, parent_node, embed_component, subtree_index, layout_info, \
2004                ensure_instantiated, \
2005                item_geometry, accessible_role, accessible_string_property, accessibility_action, \
2006                supported_accessibility_actions, element_infos, window_adapter, \
2007                slint::private_api::drop_in_place<{item_tree_class_name}>, slint::private_api::dealloc }}"
2008        )),
2009        ..Default::default()
2010    }));
2011
2012    let mut create_parameters = Vec::new();
2013    let mut init_parent_parameters = "";
2014
2015    if let Some(parent) = &parent_ctx {
2016        let parent_type =
2017            format!("class {} const *", ident(&root.sub_components[parent.sub_component].name));
2018        create_parameters.push(format!("{parent_type} parent"));
2019
2020        init_parent_parameters = ", parent";
2021    }
2022
2023    let mut create_code = vec![
2024        format!(
2025            "auto self_rc = vtable::VRc<slint::private_api::ItemTreeVTable, {0}>::make();",
2026            target_struct.name
2027        ),
2028        format!("auto self = const_cast<{0} *>(&*self_rc);", target_struct.name),
2029        "self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(),
2030    ];
2031
2032    if is_popup {
2033        create_code.push("self->globals = globals;".into());
2034        create_parameters.push("const SharedGlobals *globals".into());
2035    } else if parent_ctx.is_none() {
2036        create_code.push("slint::cbindgen_private::slint_ensure_backend();".into());
2037
2038        #[cfg(feature = "bundle-translations")]
2039        if let Some(translations) = &root.translations {
2040            let lang_len = translations.languages.len();
2041            create_code.push(format!(
2042                "std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
2043                translations
2044                    .languages
2045                    .iter()
2046                    .map(|(l, _)| format!("slint::private_api::string_to_slice({l:?})"))
2047                    .join(", ")
2048            ));
2049            create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages(slint::private_api::make_slice(std::span(languages)), \
2050                                                                                                     slint::private_api::make_slice(reinterpret_cast<uint32_t *>(slint_translation_bundle_decimal_separators), {}));",
2051                                                                                                     translations.languages.len()));
2052        }
2053
2054        create_code.push("self->globals = &self->m_globals;".into());
2055        create_code.push("self->m_globals.root_weak = self->self_weak;".into());
2056        // Now that `globals` and `root_weak` are set, the globals can be initialized: their
2057        // init may resolve the root window adapter through these (see `init_globals`).
2058        create_code.push("self->m_globals.init_globals();".into());
2059    }
2060
2061    let global_access =
2062        if !is_popup && parent_ctx.is_some() { "parent->globals" } else { "self->globals" };
2063    create_code.extend([
2064        format!(
2065            "slint::private_api::register_item_tree(&self_rc.into_dyn(), {global_access}->m_window);",
2066        ),
2067        format!("self->init({global_access}, self->self_weak, 0, 1 {init_parent_parameters});"),
2068    ]);
2069
2070    // Repeaters run their user_init() code from Repeater::ensure_updated() after update() initialized model_data/index.
2071    // And in PopupWindow this is also called by the runtime
2072    if parent_ctx.is_none() && !is_popup {
2073        if !is_system_tray_root {
2074            // Ensure that the window exists before user_init, consistent with the
2075            // Rust codegen order.
2076            create_code.push(format!("auto &window = {global_access}->window();"));
2077            create_code.push("self->user_init();".to_string());
2078            create_code.push("self->m_globals.window();".to_string());
2079            create_code.push(
2080                "slint::cbindgen_private::slint_windowrc_ensure_tree_instantiated(\
2081                 reinterpret_cast<const slint::cbindgen_private::WindowAdapterRcOpaque*>\
2082                 (&window.window_handle()));"
2083                    .to_string(),
2084            );
2085        } else {
2086            create_code.push("self->user_init();".to_string());
2087        }
2088    }
2089
2090    create_code
2091        .push(format!("return slint::ComponentHandle<{0}>{{ self_rc }};", target_struct.name));
2092
2093    target_struct.members.push((
2094        Access::Public,
2095        Declaration::Function(Function {
2096            name: "create".into(),
2097            signature: format!(
2098                "({}) -> slint::ComponentHandle<{}>",
2099                create_parameters.join(","),
2100                target_struct.name
2101            ),
2102            statements: Some(create_code),
2103            is_static: true,
2104            ..Default::default()
2105        }),
2106    ));
2107
2108    let destructor = vec![format!(
2109        "if (auto &window = globals->m_window) window->window_handle().unregister_item_tree(this, item_array());"
2110    )];
2111
2112    target_struct.members.push((
2113        Access::Public,
2114        Declaration::Function(Function {
2115            name: format_smolstr!("~{}", target_struct.name),
2116            signature: "()".to_owned(),
2117            is_constructor_or_destructor: true,
2118            statements: Some(destructor),
2119            ..Default::default()
2120        }),
2121    ));
2122}
2123
2124fn generate_sub_component(
2125    target_struct: &mut Struct,
2126    component: llr::SubComponentIdx,
2127    root: &llr::CompilationUnit,
2128    parent_ctx: Option<&ParentScope>,
2129    field_access: Access,
2130    file: &mut File,
2131    conditional_includes: &ConditionalIncludes,
2132) {
2133    let globals_type_ptr = "const class SharedGlobals*";
2134
2135    let mut init_parameters = vec![
2136        format!("{} globals", globals_type_ptr),
2137        "slint::cbindgen_private::ItemTreeWeak enclosing_component".into(),
2138        "uint32_t tree_index".into(),
2139        "uint32_t tree_index_of_first_child".into(),
2140    ];
2141
2142    let mut init: Vec<String> =
2143        vec!["auto self = this;".into(), "self->self_weak = enclosing_component;".into()];
2144
2145    target_struct.members.push((
2146        Access::Public,
2147        Declaration::Var(Var {
2148            ty: "slint::cbindgen_private::ItemTreeWeak".into(),
2149            name: "self_weak".into(),
2150            ..Default::default()
2151        }),
2152    ));
2153
2154    target_struct.members.push((
2155        field_access,
2156        Declaration::Var(Var {
2157            ty: globals_type_ptr.into(),
2158            name: "globals".into(),
2159            ..Default::default()
2160        }),
2161    ));
2162    init.push("self->globals = globals;".into());
2163
2164    target_struct.members.push((
2165        field_access,
2166        Declaration::Var(Var {
2167            ty: "uint32_t".into(),
2168            name: "tree_index_of_first_child".into(),
2169            ..Default::default()
2170        }),
2171    ));
2172    init.push("this->tree_index_of_first_child = tree_index_of_first_child;".into());
2173
2174    target_struct.members.push((
2175        field_access,
2176        Declaration::Var(Var {
2177            ty: "uint32_t".into(),
2178            name: "tree_index".into(),
2179            ..Default::default()
2180        }),
2181    ));
2182    init.push("self->tree_index = tree_index;".into());
2183
2184    if let Some(parent_ctx) = &parent_ctx {
2185        let parent_type = ident(&root.sub_components[parent_ctx.sub_component].name);
2186        init_parameters.push(format!("class {parent_type} const *parent"));
2187
2188        target_struct.members.push((
2189            field_access,
2190            Declaration::Var(Var {
2191                ty: format_smolstr!(
2192                    "vtable::VWeakMapped<slint::private_api::ItemTreeVTable, class {parent_type} const>"
2193                )
2194                .clone(),
2195                name: "parent".into(),
2196                ..Default::default()
2197            }),
2198        ));
2199        init.push(format!("self->parent = vtable::VRcMapped<slint::private_api::ItemTreeVTable, const {parent_type}>(parent->self_weak.lock().value(), parent);"));
2200    }
2201
2202    let ctx = EvaluationContext::new_sub_component(
2203        root,
2204        component,
2205        CppGeneratorContext { global_access: "self->globals".into(), conditional_includes },
2206        parent_ctx,
2207    );
2208
2209    let component = &root.sub_components[component];
2210
2211    let parent_ctx = ParentScope::new(&ctx, None);
2212
2213    component.popup_windows.iter().for_each(|popup| {
2214        let component_id = ident(&root.sub_components[popup.item_tree.root].name);
2215        let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
2216        generate_item_tree(
2217            &mut popup_struct,
2218            &popup.item_tree,
2219            root,
2220            Some(&parent_ctx),
2221            true,
2222            component_id,
2223            Access::Public,
2224            file,
2225            conditional_includes,
2226        );
2227        file.definitions.extend(popup_struct.extract_definitions());
2228        file.declarations.push(Declaration::Struct(popup_struct));
2229    });
2230    for menu in &component.menu_item_trees {
2231        let component_id = ident(&root.sub_components[menu.root].name);
2232        let mut menu_struct = Struct { name: component_id.clone(), ..Default::default() };
2233        generate_item_tree(
2234            &mut menu_struct,
2235            menu,
2236            root,
2237            Some(&parent_ctx),
2238            false,
2239            component_id,
2240            Access::Public,
2241            file,
2242            conditional_includes,
2243        );
2244        file.definitions.extend(menu_struct.extract_definitions());
2245        file.declarations.push(Declaration::Struct(menu_struct));
2246    }
2247
2248    for property in component.properties.iter() {
2249        let cpp_name = field_name(&property.name);
2250        let ty =
2251            format_smolstr!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap());
2252        target_struct.members.push((
2253            field_access,
2254            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
2255        ));
2256    }
2257    for callback in component.callbacks.iter() {
2258        let cpp_name = field_name(&callback.name);
2259        let param_types = callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
2260        let ty = format_smolstr!(
2261            "slint::private_api::Callback<{}({})>",
2262            callback.ret_ty.cpp_type().unwrap(),
2263            param_types.join(", ")
2264        );
2265        target_struct.members.push((
2266            field_access,
2267            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
2268        ));
2269        if callback.needs_tracker {
2270            let tracker_name = callback_tracker_name(&callback.name);
2271            target_struct.members.push((
2272                field_access,
2273                Declaration::Var(Var {
2274                    ty: "slint::private_api::Property<uint8_t>".into(),
2275                    name: tracker_name,
2276                    ..Default::default()
2277                }),
2278            ));
2279        }
2280    }
2281
2282    for (i, _) in component.change_callbacks.iter().enumerate() {
2283        target_struct.members.push((
2284            field_access,
2285            Declaration::Var(Var {
2286                ty: "slint::private_api::ChangeTracker".into(),
2287                name: format_smolstr!("change_tracker{}", i),
2288                ..Default::default()
2289            }),
2290        ));
2291    }
2292
2293    let mut user_init = vec!["[[maybe_unused]] auto self = this;".into()];
2294
2295    let mut children_visitor_cases = Vec::new();
2296    let mut subtrees_ranges_cases = Vec::new();
2297    let mut subtrees_components_cases = Vec::new();
2298    let mut ensure_instantiated_stmts: Vec<String> = Vec::new();
2299
2300    // The pre-init code (custom font registration) runs before the property initialization.
2301    init.extend(component.pre_init_code.iter().map(|e| {
2302        let mut expr_str = compile_expression(&e.borrow(), &ctx);
2303        expr_str.push(';');
2304        expr_str
2305    }));
2306
2307    for sub in &component.sub_components {
2308        let sub_field = field_name(&sub.name);
2309        let sub_sc = &root.sub_components[sub.ty];
2310        let local_tree_index: u32 = sub.index_in_tree as _;
2311        let local_index_of_first_child: u32 = sub.index_of_first_child_in_tree as _;
2312
2313        // For children of sub-components, the item index generated by the generate_item_indices pass
2314        // starts at 1 (0 is the root element).
2315        let global_index = if local_tree_index == 0 {
2316            "tree_index".into()
2317        } else {
2318            format!("tree_index_of_first_child + {local_tree_index} - 1")
2319        };
2320        let global_children = if local_index_of_first_child == 0 {
2321            "0".into()
2322        } else {
2323            format!("tree_index_of_first_child + {local_index_of_first_child} - 1")
2324        };
2325
2326        init.push(format!(
2327            "this->{sub_field}.init(globals, self_weak.into_dyn(), {global_index}, {global_children});"
2328        ));
2329        user_init.push(format!("this->{sub_field}.user_init();"));
2330
2331        let sub_component_repeater_count = sub_sc.repeater_count(root);
2332        if sub_component_repeater_count > 0 {
2333            let mut case_code = String::new();
2334            let repeater_offset = sub.repeater_offset;
2335
2336            for local_repeater_index in 0..sub_component_repeater_count {
2337                write!(case_code, "case {}: ", repeater_offset + local_repeater_index).unwrap();
2338            }
2339
2340            children_visitor_cases.push(format!(
2341                "\n        {case_code} {{
2342                        return self->{sub_field}.visit_dynamic_children(dyn_index - {repeater_offset}, order, visitor);
2343                    }}",
2344            ));
2345            subtrees_ranges_cases.push(format!(
2346                "\n        {case_code} {{
2347                        return self->{sub_field}.subtree_range(dyn_index - {repeater_offset});
2348                    }}",
2349            ));
2350            subtrees_components_cases.push(format!(
2351                "\n        {case_code} {{
2352                        self->{sub_field}.subtree_component(dyn_index - {repeater_offset}, subtree_index, result);
2353                        return;
2354                    }}",
2355            ));
2356            ensure_instantiated_stmts
2357                .push(format!("_changed |= self->{sub_field}.ensure_instantiated();"));
2358        }
2359
2360        target_struct.members.push((
2361            field_access,
2362            Declaration::Var(Var {
2363                ty: ident(&sub_sc.name),
2364                name: sub_field,
2365                ..Default::default()
2366            }),
2367        ));
2368    }
2369
2370    for (i, _) in component.popup_windows.iter().enumerate() {
2371        target_struct.members.push((
2372            field_access,
2373            Declaration::Var(Var {
2374                ty: ident("mutable uint32_t"),
2375                name: format_smolstr!("popup_id_{}", i),
2376                ..Default::default()
2377            }),
2378        ));
2379    }
2380
2381    for twb in &component.two_way_bindings {
2382        let p1 = access_local_member(&twb.prop1, &ctx);
2383        if let Some(info) = twb.resolve_model(&ctx) {
2384            init.push(generate_model_two_way_binding(&ctx, &info, &p1, &twb.field_access));
2385        } else if twb.field_access.is_empty() {
2386            let ty = ctx.relative_property_ty(&twb.prop1, 0).cpp_type().unwrap();
2387            init.push(
2388                access_member(&twb.prop2, &ctx).then(|p2| {
2389                    format!("slint::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2})",)
2390                }) + ";",
2391            );
2392        } else {
2393            let prop2_ty = ctx.property_ty(&twb.prop2);
2394            let cpp_ty = prop2_ty.cpp_type().unwrap();
2395            let (access, _) = lower_field_access_chain("x".into(), prop2_ty, &twb.field_access);
2396            init.push(
2397                access_member(&twb.prop2, &ctx).then(|p2|
2398                    format!("slint::private_api::Property<{cpp_ty}>::link_two_way_with_map(&{p2}, &{p1}, [](const auto &x){{ return {access}; }}, [](auto &x, const auto &v){{ {access} = v; }})")
2399                ) + ";",
2400            );
2401        }
2402    }
2403
2404    let mut properties_init_code = Vec::new();
2405    for (prop, expression) in &component.property_init {
2406        handle_property_init(prop, expression, &mut properties_init_code, &ctx)
2407    }
2408    for prop in &component.const_properties {
2409        let p = access_local_member(prop, &ctx);
2410        properties_init_code.push(format!("{p}.set_constant();"));
2411    }
2412
2413    // Create all member components for the header
2414    for item in &component.items {
2415        target_struct.members.push((
2416            field_access,
2417            Declaration::Var(Var {
2418                ty: format_smolstr!("slint::cbindgen_private::{}", ident(&item.ty.class_name)),
2419                name: field_name(&item.name),
2420                init: Some("{}".to_owned()),
2421                ..Default::default()
2422            }),
2423        ));
2424    }
2425
2426    for (idx, repeated) in component.repeated.iter_enumerated() {
2427        let sc = &root.sub_components[repeated.sub_tree.root];
2428        let data_type = repeated.data_prop.map(|data_prop| sc.properties[data_prop].ty.clone());
2429
2430        generate_repeated_component(
2431            repeated,
2432            root,
2433            ParentScope::new(&ctx, Some(idx)),
2434            data_type.as_ref(),
2435            file,
2436            conditional_includes,
2437        );
2438
2439        let idx = usize::from(idx);
2440        let repeater_id = format_smolstr!("repeater_{}", idx);
2441
2442        let model = compile_expression(&repeated.model.borrow(), &ctx);
2443
2444        // FIXME: optimize  if repeated.model.is_constant()
2445        properties_init_code.push(format!(
2446            "self->{repeater_id}.set_model_binding([self] {{ (void)self; return {model}; }});",
2447        ));
2448
2449        if let Some(listview) = &repeated.listview {
2450            let vp_y = access_member(&listview.viewport_y, &ctx).unwrap();
2451            let vp_h = access_member(&listview.viewport_height, &ctx).unwrap();
2452            let lv_h = access_member(&listview.listview_height, &ctx).unwrap();
2453            let vp_w = access_member(&listview.viewport_width, &ctx).unwrap();
2454            let lv_w = access_member(&listview.listview_width, &ctx).unwrap();
2455
2456            children_visitor_cases.push(format!(
2457                "\n        case {idx}: {{
2458                self->{repeater_id}.track_changes_listview(&{vp_w}, &{vp_h}, &{vp_y}, {lv_w}.get(), &{lv_h});
2459                return self->{repeater_id}.visit(order, visitor);
2460            }}",
2461            ));
2462            ensure_instantiated_stmts.push(format!(
2463                "_changed |= self->{repeater_id}.ensure_updated_listview(self, &{vp_w}, &{vp_h}, &{vp_y}, {lv_w}.get(), {lv_h}.get());"
2464            ));
2465        } else {
2466            children_visitor_cases.push(format!(
2467                "\n        case {idx}: {{
2468                return self->{repeater_id}.visit(order, visitor);
2469            }}",
2470            ));
2471            ensure_instantiated_stmts
2472                .push(format!("_changed |= self->{repeater_id}.ensure_updated(self);"));
2473        }
2474        subtrees_ranges_cases.push(format!(
2475            "\n        case {idx}: {{
2476                self->{repeater_id}.track_instance_changes();
2477                return self->{repeater_id}.index_range();
2478            }}",
2479        ));
2480        subtrees_components_cases.push(format!(
2481            "\n        case {idx}: {{
2482                *result = self->{repeater_id}.instance_at(subtree_index);
2483                return;
2484            }}",
2485        ));
2486
2487        let rep_type = match data_type {
2488            Some(data_type) => {
2489                format_smolstr!(
2490                    "slint::private_api::Repeater<class {}, {}>",
2491                    ident(&sc.name),
2492                    data_type.cpp_type().unwrap()
2493                )
2494            }
2495            None => format_smolstr!("slint::private_api::Conditional<class {}>", ident(&sc.name)),
2496        };
2497        target_struct.members.push((
2498            field_access,
2499            Declaration::Var(Var { ty: rep_type, name: repeater_id, ..Default::default() }),
2500        ));
2501    }
2502
2503    init.extend(properties_init_code);
2504
2505    user_init.extend(component.init_code.iter().map(|e| {
2506        let mut expr_str = compile_expression(&e.borrow(), &ctx);
2507        expr_str.push(';');
2508        expr_str
2509    }));
2510
2511    user_init.extend(component.change_callbacks.iter().enumerate().map(|(idx, (p, e))| {
2512        let code = compile_expression(&e.borrow(), &ctx);
2513        let prop = compile_expression(&llr::Expression::PropertyReference(p.clone()), &ctx);
2514        format!("self->change_tracker{idx}.init(self, [](auto self) {{ return {prop}; }}, []([[maybe_unused]] auto self, auto) {{ {code}; }});")
2515    }));
2516
2517    if !component.timers.is_empty() {
2518        let mut update_timers = vec!["auto self = this;".into()];
2519        for (i, tmr) in component.timers.iter().enumerate() {
2520            user_init.push("self->update_timers();".to_string());
2521            let name = format_smolstr!("timer{}", i);
2522            let running = compile_expression(&tmr.running.borrow(), &ctx);
2523            let interval = compile_expression(&tmr.interval.borrow(), &ctx);
2524            let callback = compile_expression(&tmr.triggered.borrow(), &ctx);
2525            update_timers.push(format!("if ({running}) {{"));
2526            update_timers
2527                .push(format!("   auto interval = std::chrono::milliseconds({interval});"));
2528            update_timers.push(format!(
2529                "   if (!self->{name}.running() || self->{name}.interval() != interval)"
2530            ));
2531            update_timers.push(format!("       self->{name}.start(slint::TimerMode::Repeated, interval, [self] {{ {callback}; }});"));
2532            update_timers.push(format!("}} else {{ self->{name}.stop(); }}"));
2533            target_struct.members.push((
2534                field_access,
2535                Declaration::Var(Var { ty: "slint::Timer".into(), name, ..Default::default() }),
2536            ));
2537        }
2538        target_struct.members.push((
2539            field_access,
2540            Declaration::Function(Function {
2541                name: "update_timers".into(),
2542                signature: "() -> void".into(),
2543                statements: Some(update_timers),
2544                ..Default::default()
2545            }),
2546        ));
2547    }
2548
2549    target_struct.members.extend(
2550        generate_functions(component.functions.as_ref(), &ctx).map(|x| (Access::Public, x)),
2551    );
2552
2553    target_struct.members.push((
2554        field_access,
2555        Declaration::Function(Function {
2556            name: "init".into(),
2557            signature: format!("({}) -> void", init_parameters.join(",")),
2558            statements: Some(init),
2559            ..Default::default()
2560        }),
2561    ));
2562
2563    target_struct.members.push((
2564        field_access,
2565        Declaration::Function(Function {
2566            name: "user_init".into(),
2567            signature: "() -> void".into(),
2568            statements: Some(user_init),
2569            ..Default::default()
2570        }),
2571    ));
2572
2573    target_struct.members.push((
2574        field_access,
2575        Declaration::Function(Function {
2576            name: "layout_info".into(),
2577            signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::LayoutInfo"
2578                .into(),
2579            statements: Some(vec![
2580                "[[maybe_unused]] auto self = this;".into(),
2581                format!(
2582                    "return o == slint::cbindgen_private::Orientation::Horizontal ? {} : {};",
2583                    compile_expression(&component.layout_info_h.borrow(), &ctx),
2584                    compile_expression(&component.layout_info_v.borrow(), &ctx)
2585                ),
2586            ]),
2587            ..Default::default()
2588        }),
2589    ));
2590
2591    let mut dispatch_item_function =
2592        |name: &str, signature: &str, forward_args: &str, code: Vec<String>| {
2593            let mut code = ["[[maybe_unused]] auto self = this;".into()]
2594                .into_iter()
2595                .chain(code)
2596                .collect::<Vec<_>>();
2597
2598            let mut else_ = "";
2599            for sub in &component.sub_components {
2600                let sub_sc = &ctx.compilation_unit.sub_components[sub.ty];
2601                let sub_items_count = sub_sc.child_item_count(ctx.compilation_unit);
2602                code.push(format!("{else_}if (index == {}) {{", sub.index_in_tree,));
2603                code.push(format!(
2604                    "    return self->{}.{name}(0{forward_args});",
2605                    field_name(&sub.name)
2606                ));
2607                if sub_items_count > 1 {
2608                    code.push(format!(
2609                        "}} else if (index >= {} && index < {}) {{",
2610                        sub.index_of_first_child_in_tree,
2611                        sub.index_of_first_child_in_tree + sub_items_count - 1
2612                            + sub_sc.repeater_count(ctx.compilation_unit)
2613                    ));
2614                    code.push(format!(
2615                        "    return self->{}.{name}(index - {}{forward_args});",
2616                        field_name(&sub.name),
2617                        sub.index_of_first_child_in_tree - 1
2618                    ));
2619                }
2620                else_ = "} else ";
2621            }
2622            let ret =
2623                if signature.contains("->") && !signature.contains("-> void") { "{}" } else { "" };
2624            code.push(format!("{else_}return {ret};"));
2625            target_struct.members.push((
2626                field_access,
2627                Declaration::Function(Function {
2628                    name: name.into(),
2629                    signature: signature.into(),
2630                    statements: Some(code),
2631                    ..Default::default()
2632                }),
2633            ));
2634        };
2635
2636    let mut item_geometry_cases = vec!["switch (index) {".to_string()];
2637    item_geometry_cases.extend(
2638        component
2639            .geometries
2640            .iter()
2641            .enumerate()
2642            .filter_map(|(i, x)| x.as_ref().map(|x| (i, x)))
2643            .map(|(index, expr)| {
2644                format!(
2645                    "    case {index}: return slint::private_api::convert_anonymous_rect({});",
2646                    compile_expression(&expr.borrow(), &ctx)
2647                )
2648            }),
2649    );
2650    item_geometry_cases.push("}".into());
2651
2652    dispatch_item_function(
2653        "item_geometry",
2654        "(uint32_t index) const -> slint::cbindgen_private::Rect",
2655        "",
2656        item_geometry_cases,
2657    );
2658
2659    let mut accessible_role_cases = vec!["switch (index) {".into()];
2660    let mut accessible_string_cases = vec!["switch ((index << 8) | uintptr_t(what)) {".into()];
2661    let mut accessibility_action_cases =
2662        vec!["switch ((index << 8) | uintptr_t(action.tag)) {".into()];
2663    let mut supported_accessibility_actions = BTreeMap::<u32, BTreeSet<_>>::new();
2664    for ((index, what), expr) in &component.accessible_prop {
2665        let e = compile_expression(&expr.borrow(), &ctx);
2666        if what == "Role" {
2667            accessible_role_cases.push(format!("    case {index}: return {e};"));
2668        } else if let Some(what) = what.strip_prefix("Action") {
2669            let has_args = matches!(&*expr.borrow(), llr::Expression::CallBackCall { arguments, .. } if !arguments.is_empty());
2670
2671            accessibility_action_cases.push(if has_args {
2672                let member = ident(&crate::generator::to_kebab_case(what));
2673                format!("    case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibilityAction::Tag::{what}): {{ auto arg_0 = action.{member}._0; return {e}; }}")
2674            } else {
2675                format!("    case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibilityAction::Tag::{what}): return {e};")
2676            });
2677            supported_accessibility_actions
2678                .entry(*index)
2679                .or_default()
2680                .insert(format!("slint::cbindgen_private::SupportedAccessibilityAction_{what}"));
2681        } else {
2682            accessible_string_cases.push(format!("    case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibleStringProperty::{what}): return {e};"));
2683        }
2684    }
2685    accessible_role_cases.push("}".into());
2686    accessible_string_cases.push("}".into());
2687    accessibility_action_cases.push("}".into());
2688
2689    let mut supported_accessibility_actions_cases = vec!["switch (index) {".into()];
2690    supported_accessibility_actions_cases.extend(supported_accessibility_actions.into_iter().map(
2691        |(index, values)| format!("    case {index}: return {};", values.into_iter().join("|")),
2692    ));
2693    supported_accessibility_actions_cases.push("}".into());
2694
2695    dispatch_item_function(
2696        "accessible_role",
2697        "(uint32_t index) const -> slint::cbindgen_private::AccessibleRole",
2698        "",
2699        accessible_role_cases,
2700    );
2701    dispatch_item_function(
2702        "accessible_string_property",
2703        "(uint32_t index, slint::cbindgen_private::AccessibleStringProperty what) const -> std::optional<slint::SharedString>",
2704        ", what",
2705        accessible_string_cases,
2706    );
2707
2708    dispatch_item_function(
2709        "accessibility_action",
2710        "(uint32_t index, const slint::cbindgen_private::AccessibilityAction &action) const -> void",
2711        ", action",
2712        accessibility_action_cases,
2713    );
2714
2715    dispatch_item_function(
2716        "supported_accessibility_actions",
2717        "(uint32_t index) const -> uint32_t",
2718        "",
2719        supported_accessibility_actions_cases,
2720    );
2721
2722    let mut element_infos_cases = vec!["switch (index) {".to_string()];
2723    element_infos_cases.extend(
2724        component
2725            .element_infos
2726            .iter()
2727            .map(|(index, ids)| format!("    case {index}: return \"{ids}\";")),
2728    );
2729    element_infos_cases.push("}".into());
2730
2731    dispatch_item_function(
2732        "element_infos",
2733        "(uint32_t index) const -> std::optional<slint::SharedString>",
2734        "",
2735        element_infos_cases,
2736    );
2737
2738    {
2739        let mut stmts = vec![
2740            "[[maybe_unused]] auto self = this;".to_owned(),
2741            "bool _changed = false;".to_owned(),
2742        ];
2743        stmts.extend(ensure_instantiated_stmts);
2744        stmts.push("return _changed;".to_owned());
2745        target_struct.members.push((
2746            field_access,
2747            Declaration::Function(Function {
2748                name: "ensure_instantiated".into(),
2749                signature: "() const -> bool".into(),
2750                statements: Some(stmts),
2751                ..Default::default()
2752            }),
2753        ));
2754    }
2755
2756    if !children_visitor_cases.is_empty() {
2757        target_struct.members.push((
2758            field_access,
2759            Declaration::Function(Function {
2760                name: "visit_dynamic_children".into(),
2761                signature: "(uint32_t dyn_index, [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor) const -> uint64_t".into(),
2762                statements: Some(vec![
2763                    "    auto self = this;".to_owned(),
2764                    format!("    switch(dyn_index) {{ {} }};", children_visitor_cases.join("")),
2765                    "    std::abort();".to_owned(),
2766                ]),
2767                ..Default::default()
2768            }),
2769        ));
2770        target_struct.members.push((
2771            field_access,
2772            Declaration::Function(Function {
2773                name: "subtree_range".into(),
2774                signature: "(uintptr_t dyn_index) const -> slint::private_api::IndexRange".into(),
2775                statements: Some(vec![
2776                    "[[maybe_unused]] auto self = this;".to_owned(),
2777                    format!("    switch(dyn_index) {{ {} }};", subtrees_ranges_cases.join("")),
2778                    "    std::abort();".to_owned(),
2779                ]),
2780                ..Default::default()
2781            }),
2782        ));
2783        target_struct.members.push((
2784            field_access,
2785            Declaration::Function(Function {
2786                name: "subtree_component".into(),
2787                signature: "(uintptr_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) const -> void".into(),
2788                statements: Some(vec![
2789                    "[[maybe_unused]] auto self = this;".to_owned(),
2790                    format!("    switch(dyn_index) {{ {} }};", subtrees_components_cases.join("")),
2791                    "    std::abort();".to_owned(),
2792                ]),
2793                ..Default::default()
2794            }),
2795        ));
2796    }
2797}
2798
2799/// Generates the `layout_item_info` member function for a repeated component struct.
2800/// Dispatches by `child_index` to per-child layout info queries, supporting static children
2801/// and inner repeaters within a row child template.
2802fn generate_layout_item_info_decl(
2803    root_sc: &llr::SubComponent,
2804    ctx: &EvaluationContext,
2805) -> Declaration {
2806    const SIGNATURE: &str = "(slint::cbindgen_private::Orientation o, [[maybe_unused]] std::optional<size_t> child_index) const -> slint::cbindgen_private::LayoutItemInfo";
2807
2808    if root_sc.row_child_templates.is_none()
2809        || (root_sc.grid_layout_children.is_empty()
2810            && !llr::has_inner_repeaters(&root_sc.row_child_templates))
2811    {
2812        return Declaration::Function(Function {
2813            name: "layout_item_info".into(),
2814            signature: SIGNATURE.to_owned(),
2815            statements: Some(vec!["return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, o) };".into()]),
2816            ..Function::default()
2817        });
2818    }
2819
2820    let templates = root_sc.row_child_templates.as_ref().unwrap();
2821    let n = templates.len();
2822
2823    // Generate a sequential scan through all templates in declaration order.
2824    // Count up from 0; for Static entries check count == index, for Repeated entries
2825    // check whether index falls within [count, count + inner_len).
2826    let mut body = String::from(
2827        "[[maybe_unused]] auto self = this;\n\
2828         if (child_index.has_value()) {\n\
2829             size_t index = *child_index;\n\
2830             size_t count = 0;\n",
2831    );
2832    for (i, entry) in templates.iter().enumerate() {
2833        let is_last = i + 1 == n;
2834        match entry {
2835            llr::RowChildTemplateInfo::Static { child_index } => {
2836                let child = &root_sc.grid_layout_children[*child_index];
2837                let layout_info_h_code = compile_expression(&child.layout_info_h.borrow(), ctx);
2838                let layout_info_v_code = compile_expression(&child.layout_info_v.borrow(), ctx);
2839                let advance = if is_last { String::new() } else { "count += 1;\n".to_owned() };
2840                write!(
2841                    body,
2842                    "if (count == index) {{\n\
2843                         return {{ (o == slint::cbindgen_private::Orientation::Horizontal) ? ({layout_info_h_code}) : ({layout_info_v_code}) }};\n\
2844                     }}\n\
2845                     {advance}",
2846                )
2847                .unwrap();
2848            }
2849            llr::RowChildTemplateInfo::Repeated { repeater_index } => {
2850                let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
2851                let advance =
2852                    if is_last { String::new() } else { "count += inner_len;\n".to_owned() };
2853                write!(
2854                    body,
2855                    "{{\n\
2856                     self->{inner_rep_id}.track_instance_changes();\n\
2857                     size_t inner_len = {inner_rep_id}.len();\n\
2858                     if (index >= count && index - count < inner_len) {{\n\
2859                         if (auto vrc = {inner_rep_id}.instance_at(index - count).lock()) {{\n\
2860                             auto vref = vrc->borrow();\n\
2861                             return {{ vref.vtable->layout_info(vref, o) }};\n\
2862                         }}\n\
2863                     }}\n\
2864                     {advance}}}\n",
2865                )
2866                .unwrap();
2867            }
2868        }
2869    }
2870    body.push_str(
2871        // Phantom cell: return "unconstrained" info (matches Rust's LayoutInfo::default()).
2872        // field order: max, max_percent, min, min_percent, preferred, stretch
2873        "return { slint::cbindgen_private::LayoutInfo{ std::numeric_limits<float>::max(), 100.f, 0, 0, 0, 0 } };\n\
2874         }\n\
2875         return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, o) };",
2876    );
2877    Declaration::Function(Function {
2878        name: "layout_item_info".into(),
2879        signature: SIGNATURE.to_owned(),
2880        statements: Some(vec![body]),
2881        ..Function::default()
2882    })
2883}
2884
2885fn generate_flexbox_layout_item_info_decl(
2886    root_sc: &llr::SubComponent,
2887    ctx: &EvaluationContext,
2888) -> Declaration {
2889    const SIGNATURE: &str = "(slint::cbindgen_private::Orientation o, [[maybe_unused]] std::optional<size_t> child_index) const -> slint::cbindgen_private::FlexboxLayoutItemInfo";
2890
2891    let body = if let Some(expr) = &root_sc.flexbox_layout_item_info_for_repeated {
2892        let compiled = compile_expression(&expr.borrow(), ctx);
2893        format!(
2894            "[[maybe_unused]] auto self = this; \
2895             auto info = {compiled}; \
2896             info.constraint = layout_item_info(o, child_index).constraint; \
2897             return info;"
2898        )
2899    } else {
2900        "auto base = layout_item_info(o, child_index); \
2901         return { base.constraint, 0.0f, 0.0f, -1.0f, slint::cbindgen_private::FlexboxLayoutAlignSelf::Auto, 0 };"
2902            .to_owned()
2903    };
2904
2905    Declaration::Function(Function {
2906        name: "flexbox_layout_item_info".into(),
2907        signature: SIGNATURE.to_owned(),
2908        statements: Some(vec![body]),
2909        ..Function::default()
2910    })
2911}
2912
2913/// Generates the `grid_layout_input_for_repeated` member function for a repeated component struct,
2914/// or returns `None` if the sub-component doesn't participate in a grid layout as a repeated row.
2915fn generate_grid_layout_input_decl(
2916    root_sc: &llr::SubComponent,
2917    ctx: &EvaluationContext,
2918) -> Option<Declaration> {
2919    let expr = root_sc.grid_layout_input_for_repeated.as_ref()?;
2920    let compiled_expr = compile_expression(&expr.borrow(), ctx);
2921    // Ensure the expression is terminated as a statement (CodeBlock with 1 item doesn't add semicolon)
2922    let statement =
2923        if compiled_expr.is_empty() || compiled_expr.ends_with(';') || compiled_expr.ends_with('}')
2924        {
2925            compiled_expr
2926        } else {
2927            format!("{compiled_expr};")
2928        };
2929
2930    // Generate fill code for all template children in declaration order
2931    let fn_body: Vec<String> = if llr::has_inner_repeaters(&root_sc.row_child_templates) {
2932        let templates = root_sc.row_child_templates.as_ref().unwrap();
2933        let static_count = llr::static_child_count(templates);
2934        let auto_val = i_slint_common::ROW_COL_AUTO;
2935        // When static children are present: fill them via the compiled expression into a temp
2936        // array, then interleave with inner-repeater cells in declaration order.
2937        // When there are no static children: skip the array/index variables entirely to avoid
2938        // unused-variable warnings when compiling the generated C++ with -Werror.
2939        let mut fill_code = if static_count > 0 {
2940            format!(
2941                "std::array<slint::cbindgen_private::GridLayoutInputData, {static_count}> statics{{}};\n\
2942                 {{\n\
2943                     // Intentionally shadows the outer `result` so the compiled statement fills `statics`.\n\
2944                     auto result = std::span<slint::cbindgen_private::GridLayoutInputData>{{statics.data(), statics.size()}};\n\
2945                     {statement}\n\
2946                 }}\n\
2947                 size_t static_idx = 0;\n\
2948                 size_t write_idx = 0;\n"
2949            )
2950        } else {
2951            String::from("size_t write_idx = 0;\n")
2952        };
2953        for entry in templates {
2954            match entry {
2955                llr::RowChildTemplateInfo::Static { .. } => {
2956                    write!(
2957                        fill_code,
2958                        "if (write_idx < result.size()) {{\n\
2959                             auto data = statics[static_idx];\n\
2960                             data.new_row = (write_idx == 0) && new_row;\n\
2961                             result[write_idx] = data;\n\
2962                         }}\n\
2963                         ++write_idx; ++static_idx;\n"
2964                    )
2965                    .unwrap();
2966                }
2967                llr::RowChildTemplateInfo::Repeated { repeater_index } => {
2968                    let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
2969                    // Let the inner cell report its own col/row/colspan/rowspan.
2970                    write!(
2971                        fill_code,
2972                        "this->{inner_rep_id}.track_instance_changes();\n\
2973                         {inner_rep_id}.for_each([&](const auto &sub_comp) {{\n\
2974                             if (write_idx < result.size()) {{\n\
2975                                 sub_comp->grid_layout_input_for_repeated((write_idx == 0) && new_row, result.subspan(write_idx, 1));\n\
2976                             }}\n\
2977                             ++write_idx;\n\
2978                         }});\n"
2979                    )
2980                    .unwrap();
2981                }
2982            }
2983        }
2984        // Padding loop: fill remaining slots with sentinel values. C++ zero-initializes
2985        // result (col=0, row=0), so we need to specify auto explicitly.
2986        write!(
2987            fill_code,
2988            "while (write_idx < result.size()) {{\n\
2989                 result[write_idx] = slint::cbindgen_private::GridLayoutInputData {{ false, {auto_val:.1}f, {auto_val:.1}f, 1.0f, 1.0f }};\n\
2990                 ++write_idx;\n\
2991             }}\n"
2992        )
2993        .unwrap();
2994        vec!["[[maybe_unused]] auto self = this;".into(), fill_code]
2995    } else {
2996        vec!["[[maybe_unused]] auto self = this;".into(), statement]
2997    };
2998
2999    Some(Declaration::Function(Function {
3000        name: "grid_layout_input_for_repeated".into(),
3001        signature: "([[maybe_unused]] bool new_row, [[maybe_unused]] std::span<slint::cbindgen_private::GridLayoutInputData> result) const -> void"
3002            .to_owned(),
3003        statements: Some(fn_body),
3004        ..Function::default()
3005    }))
3006}
3007
3008fn generate_repeated_component(
3009    repeated: &llr::RepeatedElement,
3010    unit: &llr::CompilationUnit,
3011    parent_ctx: ParentScope,
3012    model_data_type: Option<&Type>,
3013    file: &mut File,
3014    conditional_includes: &ConditionalIncludes,
3015) {
3016    let root_sc = &unit.sub_components[repeated.sub_tree.root];
3017    let repeater_id = ident(&root_sc.name);
3018    let mut repeater_struct = Struct { name: repeater_id.clone(), ..Default::default() };
3019    generate_item_tree(
3020        &mut repeater_struct,
3021        &repeated.sub_tree,
3022        unit,
3023        Some(&parent_ctx),
3024        false,
3025        repeater_id.clone(),
3026        Access::Public,
3027        file,
3028        conditional_includes,
3029    );
3030
3031    let ctx = EvaluationContext {
3032        compilation_unit: unit,
3033        current_scope: EvaluationScope::SubComponent(repeated.sub_tree.root, Some(&parent_ctx)),
3034        generator_state: CppGeneratorContext {
3035            global_access: "self->globals".into(),
3036            conditional_includes,
3037        },
3038        argument_types: &[],
3039    };
3040
3041    let access_prop = |idx: &llr::PropertyIdx| {
3042        access_member(
3043            &llr::LocalMemberReference { sub_component_path: Vec::new(), reference: (*idx).into() }
3044                .into(),
3045            &ctx,
3046        )
3047        .unwrap()
3048    };
3049    let index_prop = repeated.index_prop.iter().map(access_prop);
3050    let data_prop = repeated.data_prop.iter().map(access_prop);
3051
3052    if let Some(model_data_type) = model_data_type {
3053        let mut update_statements = vec!["[[maybe_unused]] auto self = this;".into()];
3054        update_statements.extend(index_prop.map(|prop| format!("{prop}.set(i);")));
3055        update_statements.extend(data_prop.map(|prop| format!("{prop}.set(data);")));
3056
3057        repeater_struct.members.push((
3058            Access::Public, // Because Repeater accesses it
3059            Declaration::Function(Function {
3060                name: "update_data".into(),
3061                signature: format!(
3062                    "([[maybe_unused]] int i, [[maybe_unused]] const {} &data) const -> void",
3063                    model_data_type.cpp_type().unwrap()
3064                ),
3065                statements: Some(update_statements),
3066                ..Function::default()
3067            }),
3068        ));
3069    }
3070
3071    repeater_struct.members.push((
3072        Access::Public, // Because Repeater accesses it
3073        Declaration::Function(Function {
3074            name: "init".into(),
3075            signature: "() -> void".into(),
3076            statements: Some(vec!["user_init();".into()]),
3077            ..Function::default()
3078        }),
3079    ));
3080
3081    if let Some(listview) = &repeated.listview {
3082        let p_y = access_member(&listview.prop_y, &ctx).unwrap();
3083        let p_height = access_member(&listview.prop_height, &ctx).unwrap();
3084
3085        repeater_struct.members.push((
3086            Access::Public, // Because Repeater accesses it
3087            Declaration::Function(Function {
3088                name: "listview_layout".into(),
3089                signature: "(float *offset_y) const -> float".to_owned(),
3090                statements: Some(vec![
3091                    "[[maybe_unused]] auto self = this;".into(),
3092                    format!("{}.set(*offset_y);", p_y),
3093                    format!("*offset_y += {}.get();", p_height),
3094                    "return layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, slint::cbindgen_private::Orientation::Horizontal).min;".into(),
3095                ]),
3096                ..Function::default()
3097            }),
3098        ));
3099    } else {
3100        repeater_struct.members.push((
3101            Access::Public, // Because Repeater accesses it
3102            generate_layout_item_info_decl(root_sc, &ctx),
3103        ));
3104        repeater_struct
3105            .members
3106            .push((Access::Public, generate_flexbox_layout_item_info_decl(root_sc, &ctx)));
3107        if let Some(decl) = generate_grid_layout_input_decl(root_sc, &ctx) {
3108            repeater_struct.members.push((Access::Public, decl));
3109        }
3110    }
3111
3112    if let Some(index_prop) = repeated.index_prop {
3113        // Override default subtree_index function implementation
3114        let subtree_index_func = repeater_struct
3115            .members
3116            .iter_mut()
3117            .find(|(_, d)| matches!(d, Declaration::Function(f) if f.name == "subtree_index"));
3118
3119        if let Declaration::Function(f) = &mut subtree_index_func.unwrap().1 {
3120            let index = access_prop(&index_prop);
3121            f.statements = Some(vec![
3122                format!(
3123                    "auto self = reinterpret_cast<const {}*>(component.instance);",
3124                    repeater_id
3125                ),
3126                format!("return {index}.get();"),
3127            ]);
3128        }
3129    }
3130
3131    file.definitions.extend(repeater_struct.extract_definitions().collect::<Vec<_>>());
3132    file.declarations.push(Declaration::Struct(repeater_struct));
3133}
3134
3135fn generate_global(
3136    file: &mut File,
3137    conditional_includes: &ConditionalIncludes,
3138    global_idx: llr::GlobalIdx,
3139    global: &llr::GlobalComponent,
3140    root: &llr::CompilationUnit,
3141) {
3142    let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };
3143
3144    for property in global.properties.iter() {
3145        let cpp_name = field_name(&property.name);
3146        let ty =
3147            format_smolstr!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap());
3148        global_struct.members.push((
3149            // FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the
3150            // fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API
3151            // when the global is exported and exposed in the public component.
3152            Access::Public,
3153            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
3154        ));
3155    }
3156    for callback in global.callbacks.iter().filter(|p| p.use_count.get() > 0) {
3157        let cpp_name = field_name(&callback.name);
3158        let param_types = callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
3159        let ty = format_smolstr!(
3160            "slint::private_api::Callback<{}({})>",
3161            callback.ret_ty.cpp_type().unwrap(),
3162            param_types.join(", ")
3163        );
3164        global_struct.members.push((
3165            // FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the
3166            // fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API
3167            // when the global is exported and exposed in the public component.
3168            Access::Public,
3169            Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
3170        ));
3171        if callback.needs_tracker {
3172            let tracker_name = callback_tracker_name(&callback.name);
3173            global_struct.members.push((
3174                Access::Public,
3175                Declaration::Var(Var {
3176                    ty: "slint::private_api::Property<uint8_t>".into(),
3177                    name: tracker_name,
3178                    ..Default::default()
3179                }),
3180            ));
3181        }
3182    }
3183
3184    let mut init = vec!["(void)this->globals;".into()];
3185    let ctx = EvaluationContext::new_global(
3186        root,
3187        global_idx,
3188        CppGeneratorContext { global_access: "this->globals".into(), conditional_includes },
3189    );
3190
3191    for (property_index, expression) in &global.init_values {
3192        handle_property_init(
3193            &llr::LocalMemberReference::from(property_index.clone()).into(),
3194            expression,
3195            &mut init,
3196            &ctx,
3197        )
3198    }
3199
3200    for (i, _) in global.change_callbacks.iter() {
3201        global_struct.members.push((
3202            Access::Private,
3203            Declaration::Var(Var {
3204                ty: "slint::private_api::ChangeTracker".into(),
3205                name: format_smolstr!("change_tracker{}", usize::from(*i)),
3206                ..Default::default()
3207            }),
3208        ));
3209    }
3210
3211    init.extend(global.change_callbacks.iter().map(|(p, e)| {
3212        let code = compile_expression(&e.borrow(), &ctx);
3213        let prop = access_member(&llr::LocalMemberReference::from(*p).into(), &ctx);
3214        prop.then(|prop| {
3215            format!("this->change_tracker{}.init(this, [this]([[maybe_unused]] auto self) {{ return {prop}.get(); }}, [this]([[maybe_unused]] auto self, auto) {{ {code}; }});", usize::from(*p))
3216        })
3217    }));
3218
3219    global_struct.members.push((
3220        Access::Public,
3221        Declaration::Function(Function {
3222            name: ident(&global.name),
3223            signature: "(const class SharedGlobals *globals)".into(),
3224            is_constructor_or_destructor: true,
3225            statements: Some(Vec::new()),
3226            constructor_member_initializers: vec!["globals(globals)".into()],
3227            ..Default::default()
3228        }),
3229    ));
3230    global_struct.members.push((
3231        Access::Private,
3232        Declaration::Function(Function {
3233            name: ident("init"),
3234            signature: "() -> void".into(),
3235            statements: Some(init),
3236            ..Default::default()
3237        }),
3238    ));
3239    global_struct.members.push((
3240        Access::Private,
3241        Declaration::Var(Var {
3242            ty: "const class SharedGlobals*".into(),
3243            name: "globals".into(),
3244            ..Default::default()
3245        }),
3246    ));
3247    global_struct.friends.push(SmolStr::new_static(SHARED_GLOBAL_CLASS));
3248
3249    generate_public_api_for_properties(
3250        &mut global_struct.members,
3251        &global.public_properties,
3252        &global.private_properties,
3253        &ctx,
3254    );
3255    global_struct
3256        .members
3257        .extend(generate_functions(global.functions.as_ref(), &ctx).map(|x| (Access::Public, x)));
3258
3259    file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
3260    file.declarations.push(Declaration::Struct(global_struct));
3261}
3262
3263fn generate_global_builtin(
3264    file: &mut File,
3265    conditional_includes: &ConditionalIncludes,
3266    global_idx: llr::GlobalIdx,
3267    global: &llr::GlobalComponent,
3268    root: &llr::CompilationUnit,
3269) {
3270    let mut global_struct = Struct { name: ident(&global.name), ..Default::default() };
3271    let ctx = EvaluationContext::new_global(
3272        root,
3273        global_idx,
3274        CppGeneratorContext {
3275            global_access: "\n#error binding in builtin global\n".into(),
3276            conditional_includes,
3277        },
3278    );
3279
3280    global_struct.members.push((
3281        Access::Public,
3282        Declaration::Function(Function {
3283            name: ident(&global.name),
3284            signature: format!(
3285                "(std::shared_ptr<slint::cbindgen_private::{}> builtin)",
3286                ident(&global.name)
3287            ),
3288            is_constructor_or_destructor: true,
3289            statements: Some(Vec::new()),
3290            constructor_member_initializers: vec!["builtin(std::move(builtin))".into()],
3291            ..Default::default()
3292        }),
3293    ));
3294    global_struct.members.push((
3295        Access::Private,
3296        Declaration::Var(Var {
3297            ty: format_smolstr!(
3298                "std::shared_ptr<slint::cbindgen_private::{}>",
3299                ident(&global.name)
3300            ),
3301            name: "builtin".into(),
3302            ..Default::default()
3303        }),
3304    ));
3305    global_struct.friends.push(SmolStr::new_static(SHARED_GLOBAL_CLASS));
3306
3307    generate_public_api_for_properties(
3308        &mut global_struct.members,
3309        &global.public_properties,
3310        &global.private_properties,
3311        &ctx,
3312    );
3313    file.definitions.extend(global_struct.extract_definitions().collect::<Vec<_>>());
3314    file.declarations.push(Declaration::Struct(global_struct));
3315}
3316
3317fn generate_functions<'a>(
3318    functions: &'a [llr::Function],
3319    ctx: &'a EvaluationContext<'_>,
3320) -> impl Iterator<Item = Declaration> + 'a {
3321    functions.iter().map(|f| {
3322        let mut ctx2 = ctx.clone();
3323        ctx2.argument_types = &f.args;
3324        let ret = if f.ret_ty != Type::Void { "return " } else { "" };
3325        let body = vec![
3326            "[[maybe_unused]] auto self = this;".into(),
3327            format!("{ret}{};", compile_expression(&f.code, &ctx2)),
3328        ];
3329        Declaration::Function(Function {
3330            name: concatenate_ident(&format_smolstr!("fn_{}", f.name)),
3331            signature: format!(
3332                "({}) const -> {}",
3333                f.args
3334                    .iter()
3335                    .enumerate()
3336                    .map(|(i, ty)| format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap(), i))
3337                    .join(", "),
3338                f.ret_ty.cpp_type().unwrap()
3339            ),
3340            statements: Some(body),
3341            ..Default::default()
3342        })
3343    })
3344}
3345
3346fn generate_public_api_for_properties(
3347    declarations: &mut Vec<(Access, Declaration)>,
3348    public_properties: &llr::PublicProperties,
3349    private_properties: &llr::PrivateProperties,
3350    ctx: &EvaluationContext,
3351) {
3352    for p in public_properties {
3353        let prop_ident = concatenate_ident(&p.name);
3354
3355        let access = access_member(&p.prop, ctx).unwrap();
3356
3357        if let Type::Callback(callback) = &p.ty {
3358            let param_types =
3359                callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
3360            let callback_emitter = vec![
3361                "slint::private_api::assert_main_thread();".into(),
3362                "[[maybe_unused]] auto self = this;".into(),
3363                format!(
3364                    "return {}.call({});",
3365                    access,
3366                    (0..callback.args.len()).map(|i| format!("arg_{i}")).join(", ")
3367                ),
3368            ];
3369            declarations.push((
3370                Access::Public,
3371                Declaration::Function(Function {
3372                    name: format_smolstr!("invoke_{prop_ident}"),
3373                    signature: format!(
3374                        "({}) const -> {}",
3375                        param_types
3376                            .iter()
3377                            .enumerate()
3378                            .map(|(i, ty)| format!("{ty} arg_{i}"))
3379                            .join(", "),
3380                        callback.return_type.cpp_type().unwrap()
3381                    ),
3382                    statements: Some(callback_emitter),
3383                    ..Default::default()
3384                }),
3385            ));
3386            let tracker = access_callback_tracker_cpp(&p.prop, ctx);
3387            let mut on_stmts = vec![
3388                "slint::private_api::assert_main_thread();".into(),
3389                "[[maybe_unused]] auto self = this;".into(),
3390                format!("{}.set_handler(std::forward<Functor>(callback_handler));", access),
3391            ];
3392            if let Some(t) = &tracker {
3393                on_stmts.push(format!("{t}.mark_dirty();"));
3394            }
3395            declarations.push((
3396                Access::Public,
3397                Declaration::Function(Function {
3398                    name: format_smolstr!("on_{}", concatenate_ident(&p.name)),
3399                    template_parameters: Some(format!(
3400                        "std::invocable<{}> Functor",
3401                        param_types.join(", "),
3402                    )),
3403                    signature: "(Functor && callback_handler) const".into(),
3404                    statements: Some(on_stmts),
3405                    ..Default::default()
3406                }),
3407            ));
3408        } else if let Type::Function(function) = &p.ty {
3409            let param_types =
3410                function.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
3411            let ret = function.return_type.cpp_type().unwrap();
3412            let call_code = vec![
3413                "[[maybe_unused]] auto self = this;".into(),
3414                format!(
3415                    "{}{access}({});",
3416                    if function.return_type == Type::Void { "" } else { "return " },
3417                    (0..function.args.len()).map(|i| format!("arg_{i}")).join(", ")
3418                ),
3419            ];
3420            declarations.push((
3421                Access::Public,
3422                Declaration::Function(Function {
3423                    name: format_smolstr!("invoke_{}", concatenate_ident(&p.name)),
3424                    signature: format!(
3425                        "({}) const -> {ret}",
3426                        param_types
3427                            .iter()
3428                            .enumerate()
3429                            .map(|(i, ty)| format!("{ty} arg_{i}"))
3430                            .join(", "),
3431                    ),
3432                    statements: Some(call_code),
3433                    ..Default::default()
3434                }),
3435            ));
3436        } else {
3437            let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties");
3438            let prop_getter: Vec<String> = vec![
3439                "slint::private_api::assert_main_thread();".into(),
3440                "[[maybe_unused]] auto self = this;".into(),
3441                format!("return {}.get();", access),
3442            ];
3443            declarations.push((
3444                Access::Public,
3445                Declaration::Function(Function {
3446                    name: format_smolstr!("get_{}", &prop_ident),
3447                    signature: format!("() const -> {}", &cpp_property_type),
3448                    statements: Some(prop_getter),
3449                    ..Default::default()
3450                }),
3451            ));
3452
3453            if !p.read_only {
3454                let prop_setter: Vec<String> = vec![
3455                    "slint::private_api::assert_main_thread();".into(),
3456                    "[[maybe_unused]] auto self = this;".into(),
3457                    property_set_value_code(&p.prop, "value", ctx) + ";",
3458                ];
3459                declarations.push((
3460                    Access::Public,
3461                    Declaration::Function(Function {
3462                        name: format_smolstr!("set_{}", &prop_ident),
3463                        signature: format!("(const {} &value) const -> void", &cpp_property_type),
3464                        statements: Some(prop_setter),
3465                        ..Default::default()
3466                    }),
3467                ));
3468            } else {
3469                declarations.push((
3470                    Access::Private,
3471                    Declaration::Function(Function {
3472                        name: format_smolstr!("set_{}", &prop_ident),
3473                        signature: format!(
3474                            "(const {cpp_property_type} &) const = SLINT_DELETED_FUNCTION(\"property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter\")", p.name
3475                        ),
3476                        ..Default::default()
3477                    }),
3478                ));
3479            }
3480        }
3481    }
3482
3483    for (name, ty) in private_properties {
3484        let prop_ident = concatenate_ident(name);
3485
3486        if let Type::Function(function) = &ty {
3487            let param_types = function.args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
3488            declarations.push((
3489                Access::Private,
3490                Declaration::Function(Function {
3491                    name: format_smolstr!("invoke_{prop_ident}"),
3492                    signature: format!(
3493                        "({param_types}) const = SLINT_DELETED_FUNCTION(\"the function '{name}' is declared as private. Declare it as 'public'\")",
3494                    ),
3495                    ..Default::default()
3496                }),
3497            ));
3498        } else {
3499            declarations.push((
3500                Access::Private,
3501                Declaration::Function(Function {
3502                    name: format_smolstr!("get_{prop_ident}"),
3503                    signature: format!(
3504                        "() const = SLINT_DELETED_FUNCTION(\"the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public\")",
3505                    ),
3506                    ..Default::default()
3507                }),
3508            ));
3509            declarations.push((
3510                Access::Private,
3511                Declaration::Function(Function {
3512                    name: format_smolstr!("set_{}", &prop_ident),
3513                    signature: format!(
3514                        "(const auto &) const = SLINT_DELETED_FUNCTION(\"property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public\")",
3515                    ),
3516                    ..Default::default()
3517                }),
3518            ));
3519        }
3520    }
3521}
3522
3523fn follow_sub_component_path<'a>(
3524    compilation_unit: &'a llr::CompilationUnit,
3525    root: llr::SubComponentIdx,
3526    sub_component_path: &[llr::SubComponentInstanceIdx],
3527) -> (String, &'a llr::SubComponent) {
3528    let mut compo_path = String::new();
3529    let mut sub_component = &compilation_unit.sub_components[root];
3530    for i in sub_component_path {
3531        let sub_component_name = field_name(&sub_component.sub_components[*i].name);
3532        write!(compo_path, "{sub_component_name}.").unwrap();
3533        sub_component = &compilation_unit.sub_components[sub_component.sub_components[*i].ty];
3534    }
3535    (compo_path, sub_component)
3536}
3537
3538fn access_window_field(ctx: &EvaluationContext) -> String {
3539    format!("{}->window().window_handle()", ctx.generator_state.global_access)
3540}
3541
3542/// Returns the code that can access the given property (but without the set or get)
3543fn access_member(reference: &llr::MemberReference, ctx: &EvaluationContext) -> MemberAccess {
3544    match reference {
3545        llr::MemberReference::Relative { parent_level, local_reference } => {
3546            let mut path = MemberAccess::Direct("self".to_string());
3547            for _ in 0..*parent_level {
3548                path = path.and_then(|x| format!("{x}->parent.lock()"));
3549            }
3550            if let Some(sub_component) = ctx.parent_sub_component_idx(*parent_level) {
3551                let (compo_path, sub_component) = follow_sub_component_path(
3552                    ctx.compilation_unit,
3553                    sub_component,
3554                    &local_reference.sub_component_path,
3555                );
3556                match &local_reference.reference {
3557                    llr::LocalMemberIndex::Property(property_index) => {
3558                        let property_name =
3559                            field_name(&sub_component.properties[*property_index].name);
3560                        path.with_member(format!("->{compo_path}{property_name}"))
3561                    }
3562                    llr::LocalMemberIndex::Callback(callback_index) => {
3563                        let callback_name =
3564                            field_name(&sub_component.callbacks[*callback_index].name);
3565                        path.with_member(format!("->{compo_path}{callback_name}"))
3566                    }
3567                    llr::LocalMemberIndex::Function(function_index) => {
3568                        let function_name = ident(&sub_component.functions[*function_index].name);
3569                        path.with_member(format!("->{compo_path}fn_{function_name}"))
3570                    }
3571                    llr::LocalMemberIndex::Native { item_index, prop_name } => {
3572                        let item_name = field_name(&sub_component.items[*item_index].name);
3573                        if prop_name.is_empty()
3574                            || matches!(
3575                                sub_component.items[*item_index].ty.lookup_property(prop_name),
3576                                Some(Type::Function { .. })
3577                            )
3578                        {
3579                            // then this is actually a reference to the element itself
3580                            // (or a call to a builtin member function)
3581                            path.with_member(format!("->{compo_path}{item_name}"))
3582                        } else {
3583                            let property_name = ident(prop_name);
3584                            path.with_member(format!("->{compo_path}{item_name}.{property_name}"))
3585                        }
3586                    }
3587                }
3588            } else if let Some(current_global) = ctx.current_global() {
3589                match &local_reference.reference {
3590                    llr::LocalMemberIndex::Property(property_index) => {
3591                        let property_name =
3592                            field_name(&current_global.properties[*property_index].name);
3593                        MemberAccess::Direct(format!("this->{property_name}"))
3594                    }
3595                    llr::LocalMemberIndex::Function(function_index) => {
3596                        let function_name = ident(&current_global.functions[*function_index].name);
3597                        MemberAccess::Direct(format!("this->fn_{function_name}"))
3598                    }
3599                    llr::LocalMemberIndex::Callback(callback_index) => {
3600                        let callback_name =
3601                            field_name(&current_global.callbacks[*callback_index].name);
3602                        MemberAccess::Direct(format!("this->{callback_name}"))
3603                    }
3604                    _ => unreachable!(),
3605                }
3606            } else {
3607                unreachable!()
3608            }
3609        }
3610        llr::MemberReference::Global { global_index, member } => {
3611            let global = &ctx.compilation_unit.globals[*global_index];
3612            // Builtin globals are structs from the C++ runtime library whose fields keep
3613            // the declared names
3614            let field = |name| if global.is_builtin { ident(name) } else { field_name(name) };
3615            let name = match member {
3616                llr::LocalMemberIndex::Property(property_index) => {
3617                    field(&global.properties[*property_index].name)
3618                }
3619                llr::LocalMemberIndex::Callback(callback_index) => {
3620                    field(&global.callbacks[*callback_index].name)
3621                }
3622                llr::LocalMemberIndex::Function(function_index) => {
3623                    ident(&format!("fn_{}", &global.functions[*function_index].name))
3624                }
3625                _ => unreachable!(),
3626            };
3627            if matches!(ctx.current_scope, EvaluationScope::Global(i) if i == *global_index) {
3628                if global.is_builtin {
3629                    MemberAccess::Direct(format!("builtin->{name}"))
3630                } else {
3631                    MemberAccess::Direct(format!("this->{name}"))
3632                }
3633            } else {
3634                let global_access = &ctx.generator_state.global_access;
3635                let global_id = format!("global_{}", concatenate_ident(&global.name));
3636                MemberAccess::Direct(format!("{global_access}->{global_id}->{name}"))
3637            }
3638        }
3639    }
3640}
3641
3642fn access_local_member(reference: &llr::LocalMemberReference, ctx: &EvaluationContext) -> String {
3643    access_member(&reference.clone().into(), ctx).unwrap()
3644}
3645
3646/// Returns the C++ field name for the change-tracker property of a callback.
3647fn callback_tracker_name(callback_name: &str) -> SmolStr {
3648    format_smolstr!("callback_tracker_{}", callback_name.replace('-', "_"))
3649}
3650
3651/// Returns the name of the C++ field holding a property, callback, item, or sub-component
3652/// instance.
3653/// The prefix keeps the field apart from generated member functions
3654/// (e.g. a callback named `set-foo` and the `set_foo` setter of a property `foo`)
3655/// and from reserved members such as `repeater_0` or `self_weak`.
3656fn field_name(name: &str) -> SmolStr {
3657    format_smolstr!("field_{}", concatenate_ident(name))
3658}
3659
3660/// Returns the C++ code to access the change-tracker `Property<uint8_t>` for an exported callback.
3661/// Returns `None` if the callback doesn't have a tracker.
3662fn access_callback_tracker_cpp(
3663    reference: &llr::MemberReference,
3664    ctx: &EvaluationContext,
3665) -> Option<String> {
3666    fn in_global(
3667        g: &llr::GlobalComponent,
3668        callback_idx: &llr::CallbackIdx,
3669        self_: &str,
3670    ) -> Option<String> {
3671        if !g.callbacks[*callback_idx].needs_tracker {
3672            return None;
3673        }
3674        let tracker_name = callback_tracker_name(&g.callbacks[*callback_idx].name);
3675        Some(format!("{self_}{tracker_name}"))
3676    }
3677
3678    match reference {
3679        llr::MemberReference::Global {
3680            global_index,
3681            member: llr::LocalMemberIndex::Callback(callback_idx),
3682        } => {
3683            let global = &ctx.compilation_unit.globals[*global_index];
3684            if matches!(ctx.current_scope, EvaluationScope::Global(i) if i == *global_index) {
3685                in_global(global, callback_idx, "this->")
3686            } else {
3687                let global_access = &ctx.generator_state.global_access;
3688                let global_id = format!("global_{}", concatenate_ident(&global.name));
3689                in_global(global, callback_idx, &format!("{global_access}->{global_id}->"))
3690            }
3691        }
3692        llr::MemberReference::Relative { parent_level: 0, local_reference } => {
3693            if let llr::LocalMemberIndex::Callback(callback_idx) = &local_reference.reference {
3694                if let Some(current_global) = ctx.current_global() {
3695                    return in_global(current_global, callback_idx, "this->");
3696                }
3697                if local_reference.sub_component_path.is_empty()
3698                    && let Some(sc_idx) = ctx.parent_sub_component_idx(0)
3699                {
3700                    let sc = &ctx.compilation_unit.sub_components[sc_idx];
3701                    if sc.callbacks[*callback_idx].needs_tracker {
3702                        let tracker_name = callback_tracker_name(&sc.callbacks[*callback_idx].name);
3703                        return Some(format!("self->{tracker_name}"));
3704                    }
3705                }
3706            }
3707            None
3708        }
3709        _ => None,
3710    }
3711}
3712
3713/// Helper to access a member property/callback of a component.
3714///
3715/// Because the parent can be deleted (issue #3464), this might be an option when accessing the parent
3716#[derive(Clone)]
3717enum MemberAccess {
3718    /// The string is just an expression
3719    Direct(String),
3720    /// The string is a an expression to an `std::optional`
3721    Option(String),
3722    /// The first string is an expression to an `std::optional`,
3723    /// the second is a string to be appended after dereferencing the optional
3724    /// like so: `<1>.transform([](auto &&x) { return x<2>; })`
3725    OptionWithMember(String, String),
3726}
3727
3728impl MemberAccess {
3729    /// Used for code that is meant to return `()`
3730    fn then(&self, f: impl FnOnce(&str) -> String) -> String {
3731        match self {
3732            MemberAccess::Direct(t) => f(t),
3733            MemberAccess::Option(t) => {
3734                format!("slint::private_api::optional_then({t}, [&](auto&&x) {{ {}; }})", f("x"))
3735            }
3736            MemberAccess::OptionWithMember(t, m) => {
3737                format!(
3738                    "slint::private_api::optional_then({t}, [&](auto&&x) {{ {}; }})",
3739                    f(&format!("x{}", m))
3740                )
3741            }
3742        }
3743    }
3744
3745    fn map_or_default(&self, f: impl FnOnce(&str) -> String) -> String {
3746        match self {
3747            MemberAccess::Direct(t) => f(t),
3748            MemberAccess::Option(t) => {
3749                format!(
3750                    "slint::private_api::optional_or_default(slint::private_api::optional_transform({t}, [&](auto&&x) {{ return {}; }}))",
3751                    f("x")
3752                )
3753            }
3754            MemberAccess::OptionWithMember(t, m) => {
3755                format!(
3756                    "slint::private_api::optional_or_default(slint::private_api::optional_transform({t}, [&](auto&&x) {{ return {}; }}))",
3757                    f(&format!("x{}", m))
3758                )
3759            }
3760        }
3761    }
3762
3763    fn and_then(&self, f: impl Fn(&str) -> String) -> MemberAccess {
3764        match self {
3765            MemberAccess::Direct(t) => MemberAccess::Option(f(t)),
3766            MemberAccess::Option(t) => MemberAccess::Option(format!(
3767                "slint::private_api::optional_and_then({t}, [&](auto&&x) {{ return {}; }})",
3768                f("x")
3769            )),
3770            MemberAccess::OptionWithMember(t, m) => MemberAccess::Option(format!(
3771                "slint::private_api::optional_and_then({t}, [&](auto&&x) {{ return {}; }})",
3772                f(&format!("x{}", m))
3773            )),
3774        }
3775    }
3776
3777    fn get_property(self) -> String {
3778        self.map_or_default(|x| format!("{x}.get()"))
3779    }
3780
3781    /// To be used when we know that the reference was local
3782    #[track_caller]
3783    fn unwrap(self) -> String {
3784        match self {
3785            MemberAccess::Direct(t) => t,
3786            _ => panic!("not a local property?"),
3787        }
3788    }
3789
3790    fn with_member(self, member: String) -> MemberAccess {
3791        match self {
3792            MemberAccess::Direct(t) => MemberAccess::Direct(format!("{t}{member}")),
3793            MemberAccess::Option(t) => MemberAccess::OptionWithMember(t, member),
3794            MemberAccess::OptionWithMember(t, m) => {
3795                MemberAccess::OptionWithMember(t, format!("{m}{member}"))
3796            }
3797        }
3798    }
3799}
3800
3801/// Returns the NativeClass for a PropertyReference::InNativeItem
3802/// (or a InParent of InNativeItem )
3803/// As well as the property name
3804fn native_prop_info<'a, 'b>(
3805    item_ref: &'b llr::MemberReference,
3806    ctx: &'a EvaluationContext,
3807) -> (&'a NativeClass, &'b str) {
3808    let llr::MemberReference::Relative { parent_level, local_reference } = item_ref else {
3809        unreachable!()
3810    };
3811    let llr::LocalMemberIndex::Native { item_index, prop_name } = &local_reference.reference else {
3812        unreachable!()
3813    };
3814
3815    let (_, sub_component) = follow_sub_component_path(
3816        ctx.compilation_unit,
3817        ctx.parent_sub_component_idx(*parent_level).unwrap(),
3818        &local_reference.sub_component_path,
3819    );
3820    (&sub_component.items[*item_index].ty, prop_name)
3821}
3822
3823fn shared_string_literal(string: &str) -> String {
3824    format!(r#"slint::SharedString(u8"{}")"#, escape_string(string))
3825}
3826
3827fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String {
3828    use llr::Expression;
3829    match expr {
3830        Expression::StringLiteral(s) => shared_string_literal(s),
3831        Expression::NumberLiteral(num) => {
3832            if num.is_nan() {
3833                "std::numeric_limits<double>::quiet_NaN()".to_string()
3834            } else if num.is_infinite() {
3835                if *num > 0. {
3836                    "std::numeric_limits<double>::infinity()".to_string()
3837                } else {
3838                    "-std::numeric_limits<double>::infinity()".to_string()
3839                }
3840            } else if num.abs() > 1_000_000_000. {
3841                // If the numbers are too big, decimal notation will give too many digit
3842                format!("{num:+e}")
3843            } else {
3844                num.to_string()
3845            }
3846        }
3847        Expression::BoolLiteral(b) => b.to_string(),
3848        Expression::KeysLiteral(ks) => {
3849            format!(
3850                "[&](const slint::SharedString &key, bool alt, bool control, bool shift, bool meta, bool ignoreShift, bool ignoreAlt) {{
3851                    slint::Keys out;
3852                    slint::private_api::make_keys(out, key, alt, control, shift, meta, ignoreShift, ignoreAlt);
3853                    return out;
3854                }}({}, {}, {}, {}, {}, {}, {})",
3855                shared_string_literal(&ks.key),
3856                ks.modifiers.alt,
3857                ks.modifiers.control,
3858                ks.modifiers.shift,
3859                ks.modifiers.meta,
3860                ks.ignore_shift,
3861                ks.ignore_alt,
3862            )
3863        }
3864        Expression::PropertyReference(nr) => access_member(nr, ctx).get_property(),
3865        Expression::BuiltinFunctionCall { function, arguments } => {
3866            compile_builtin_function_call(function.clone(), arguments, ctx)
3867        }
3868        Expression::CallBackCall { callback, arguments } => {
3869            let f = access_member(callback, ctx);
3870            let tracker_get = access_callback_tracker_cpp(callback, ctx)
3871                .map(|t| format!("(void){t}.get(), "))
3872                .unwrap_or_default();
3873            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
3874            if expr.ty(ctx) == Type::Void {
3875                f.then(|f| format!("{tracker_get}{f}.call({})", a.join(",")))
3876            } else {
3877                f.map_or_default(|f| format!("({tracker_get}{f}.call({}))", a.join(",")))
3878            }
3879        }
3880        Expression::FunctionCall { function, arguments } => {
3881            let f = access_member(function, ctx);
3882            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
3883            if expr.ty(ctx) == Type::Void {
3884                f.then(|f| format!("{}({})", f, a.join(",")))
3885            } else {
3886                f.map_or_default(|f| format!("{}({})", f, a.join(",")))
3887            }
3888        }
3889        Expression::ItemMemberFunctionCall { function } => {
3890            let item = access_member(function, ctx);
3891            let item_rc = access_item_rc(function, ctx);
3892            let window = access_window_field(ctx);
3893            let (native, name) = native_prop_info(function, ctx);
3894            let function_name = format!(
3895                "slint_{}_{}",
3896                native.class_name.to_lowercase(),
3897                ident(name).to_lowercase()
3898            );
3899            if expr.ty(ctx) == Type::Void {
3900                item.then(|item| {
3901                    format!("{function_name}(&{item}, &{window}.handle(), &{item_rc})")
3902                })
3903            } else {
3904                item.map_or_default(|item| {
3905                    format!("{function_name}(&{item}, &{window}.handle(), &{item_rc})")
3906                })
3907            }
3908        }
3909        Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => {
3910            let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
3911            format!("slint::private_api::{}({})", ident(function), a.join(","))
3912        }
3913        Expression::FunctionParameterReference { index, .. } => format!("arg_{index}"),
3914        Expression::StoreLocalVariable { name, value } => {
3915            format!("[[maybe_unused]] auto {} = {};", ident(name), compile_expression(value, ctx))
3916        }
3917        Expression::ReadLocalVariable { name, .. } => ident(name).to_string(),
3918        Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
3919            Type::Struct(s) => struct_field_access(compile_expression(base, ctx), &s, name),
3920            _ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
3921        },
3922        Expression::ArrayIndex { array, index } => {
3923            format!(
3924                "slint::private_api::access_array_index({}, {})",
3925                compile_expression(array, ctx),
3926                compile_expression(index, ctx)
3927            )
3928        }
3929        Expression::Cast { from, to } => {
3930            let f = compile_expression(from, ctx);
3931            match (from.ty(ctx), to) {
3932                (Type::Float32, Type::Int32) => {
3933                    format!("slint::private_api::saturating_float_to_int({f})")
3934                }
3935                (from, Type::String) if from.as_unit_product().is_some() => {
3936                    format!("slint::SharedString::from_number({f})")
3937                }
3938                (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => {
3939                    format!(
3940                        "std::make_shared<slint::private_api::UIntModel>(std::max<int>(0, {f}))"
3941                    )
3942                }
3943                (Type::Array(_), Type::Model) => f,
3944                (Type::Float32, Type::Color) => {
3945                    format!("slint::Color::from_argb_encoded({f})")
3946                }
3947                (Type::Color, Type::Brush) => {
3948                    format!("slint::Brush({f})")
3949                }
3950                (Type::Brush, Type::Color) => {
3951                    format!("{f}.color()")
3952                }
3953                (Type::Struct(lhs), Type::Struct(rhs)) => {
3954                    debug_assert_eq!(
3955                        lhs.fields, rhs.fields,
3956                        "cast of struct with deferent fields should be handled before llr"
3957                    );
3958                    match (&lhs.name, &rhs.name) {
3959                        (StructName::None, targetstruct) if targetstruct.is_some() => {
3960                            // Convert from an anonymous struct to a named one
3961                            format!(
3962                                "[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})",
3963                                struct_name = to.cpp_type().unwrap(),
3964                                fields = lhs
3965                                    .fields
3966                                    .keys()
3967                                    .enumerate()
3968                                    .map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i))
3969                                    .join(""),
3970                                obj = f,
3971                            )
3972                        }
3973                        (sourcestruct, StructName::None) if sourcestruct.is_some() => {
3974                            // Convert from a named struct to an anonymous one
3975                            format!(
3976                                "[&](const auto &o){{ return std::make_tuple({}); }}({f})",
3977                                rhs.fields.keys().map(|n| format!("o.{}", ident(n))).join(", ")
3978                            )
3979                        }
3980                        _ => f,
3981                    }
3982                }
3983                (Type::Array(..), Type::PathData)
3984                    if matches!(
3985                        from.as_ref(),
3986                        Expression::Array { element_ty: Type::Struct { .. }, .. }
3987                    ) =>
3988                {
3989                    let path_elements = match from.as_ref() {
3990                        Expression::Array { element_ty: _, values, output: _ } => {
3991                            values.iter().map(|path_elem_expr| {
3992                                let (field_count, qualified_elem_type_name) =
3993                                    match path_elem_expr.ty(ctx) {
3994                                        Type::Struct(s) if s.name.is_some() => {
3995                                            (s.fields.len(), s.name.cpp_type().unwrap().clone())
3996                                        }
3997                                        _ => unreachable!(),
3998                                    };
3999                                // Turn slint::private_api::PathLineTo into `LineTo`
4000                                let elem_type_name = qualified_elem_type_name
4001                                    .split("::")
4002                                    .last()
4003                                    .unwrap()
4004                                    .strip_prefix("Path")
4005                                    .unwrap();
4006                                let elem_init = if field_count > 0 {
4007                                    compile_expression(path_elem_expr, ctx)
4008                                } else {
4009                                    String::new()
4010                                };
4011                                format!(
4012                                    "slint::private_api::PathElement::{elem_type_name}({elem_init})"
4013                                )
4014                            })
4015                        }
4016                        _ => {
4017                            unreachable!()
4018                        }
4019                    }
4020                    .collect::<Vec<_>>();
4021                    if !path_elements.is_empty() {
4022                        format!(
4023                            r#"[&](){{
4024                                slint::private_api::PathElement elements[{}] = {{
4025                                    {}
4026                                }};
4027                                return slint::private_api::PathData(&elements[0], std::size(elements));
4028                            }}()"#,
4029                            path_elements.len(),
4030                            path_elements.join(",")
4031                        )
4032                    } else {
4033                        "slint::private_api::PathData()".into()
4034                    }
4035                }
4036                (Type::Struct { .. }, Type::PathData)
4037                    if matches!(from.as_ref(), Expression::Struct { .. }) =>
4038                {
4039                    let (events, points) = match from.as_ref() {
4040                        Expression::Struct { ty: _, values } => (
4041                            compile_expression(&values["events"], ctx),
4042                            compile_expression(&values["points"], ctx),
4043                        ),
4044                        _ => {
4045                            unreachable!()
4046                        }
4047                    };
4048                    format!(
4049                        r#"[&](auto events, auto points){{
4050                            return slint::private_api::PathData(events.ptr, events.len, points.ptr, points.len);
4051                        }}({events}, {points})"#
4052                    )
4053                }
4054                (Type::Enumeration(e), Type::String) => {
4055                    let mut cases = e.values.iter().enumerate().map(|(idx, v)| {
4056                        let c = compile_expression(
4057                            &Expression::EnumerationValue(EnumerationValue {
4058                                value: idx,
4059                                enumeration: e.clone(),
4060                            }),
4061                            ctx,
4062                        );
4063                        format!("case {c}: return {v:?};")
4064                    });
4065                    format!(
4066                        "[&]() -> slint::SharedString {{ switch ({f}) {{ {} default: return {{}}; }} }}()",
4067                        cases.join(" ")
4068                    )
4069                }
4070                _ => f,
4071            }
4072        }
4073        Expression::CodeBlock(sub) => match sub.len() {
4074            0 => String::new(),
4075            1 => compile_expression(&sub[0], ctx),
4076            len => {
4077                let mut x = sub.iter().enumerate().map(|(i, e)| {
4078                    if i == len - 1 {
4079                        return_compile_expression(e, ctx, None) + ";"
4080                    } else {
4081                        compile_expression(e, ctx)
4082                    }
4083                });
4084                format!("[&]{{ {} }}()", x.join(";"))
4085            }
4086        },
4087        Expression::PropertyAssignment { property, value } => {
4088            let value = compile_expression(value, ctx);
4089            property_set_value_code(property, &value, ctx)
4090        }
4091        Expression::ModelDataAssignment { level, value } => {
4092            let value = compile_expression(value, ctx);
4093            let mut path = "self".to_string();
4094            let EvaluationScope::SubComponent(mut sc, mut par) = ctx.current_scope else {
4095                unreachable!()
4096            };
4097            let mut repeater_index = None;
4098            for _ in 0..=*level {
4099                let x = par.unwrap();
4100                par = x.parent;
4101                repeater_index = x.repeater_index;
4102                sc = x.sub_component;
4103                write!(path, "->parent.lock().value()").unwrap();
4104            }
4105            let repeater_index = repeater_index.unwrap();
4106            let local_reference = ctx.compilation_unit.sub_components[sc].repeated[repeater_index]
4107                .index_prop
4108                .unwrap()
4109                .into();
4110            let index_prop =
4111                llr::MemberReference::Relative { parent_level: *level, local_reference };
4112            let index_access = access_member(&index_prop, ctx).get_property();
4113            write!(path, "->repeater_{}", usize::from(repeater_index)).unwrap();
4114            format!("{path}.model_set_row_data({index_access}, {value})")
4115        }
4116        Expression::ArrayIndexAssignment { array, index, value } => {
4117            debug_assert!(matches!(array.ty(ctx), Type::Array(_)));
4118            let base_e = compile_expression(array, ctx);
4119            let index_e = compile_expression(index, ctx);
4120            let value_e = compile_expression(value, ctx);
4121            format!(
4122                "[&](auto index, const auto &base) {{ if (index >= 0. && std::size_t(index) < base->row_count()) base->set_row_data(index, {value_e}); }}({index_e}, {base_e})"
4123            )
4124        }
4125        Expression::SliceIndexAssignment { slice_name, index, value } => {
4126            let value_e = compile_expression(value, ctx);
4127            format!("{slice_name}[{index}] = {value_e}")
4128        }
4129        Expression::BinaryExpression { lhs, rhs, op } => {
4130            let lhs_str = compile_expression(lhs, ctx);
4131            let rhs_str = compile_expression(rhs, ctx);
4132
4133            let lhs_ty = lhs.ty(ctx);
4134
4135            if lhs_ty.as_unit_product().is_some() && (*op == '=' || *op == '!') {
4136                let op = if *op == '=' { "<" } else { ">=" };
4137                format!(
4138                    "(std::abs(float({lhs_str} - {rhs_str})) {op} std::numeric_limits<float>::epsilon())"
4139                )
4140            } else {
4141                let mut buffer = [0; 3];
4142                format!(
4143                    "({lhs_str} {op} {rhs_str})",
4144                    op = match op {
4145                        '=' => "==",
4146                        '!' => "!=",
4147                        '≤' => "<=",
4148                        '≥' => ">=",
4149                        '&' => "&&",
4150                        '|' => "||",
4151                        '/' => "/(float)",
4152                        '-' => "-(float)", // conversion to float to avoid overflow between unsigned
4153                        _ => op.encode_utf8(&mut buffer),
4154                    },
4155                )
4156            }
4157        }
4158        Expression::UnaryOp { sub, op } => {
4159            format!("({op} {sub})", sub = compile_expression(sub, ctx), op = op,)
4160        }
4161        Expression::ImageReference { resource_ref, nine_slice } => {
4162            let image = match resource_ref {
4163                crate::expression_tree::ImageReference::None => r#"slint::Image()"#.to_string(),
4164                crate::expression_tree::ImageReference::AbsolutePath(path) => format!(
4165                    r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#,
4166                    escape_string(path.as_str())
4167                ),
4168                crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
4169                    let symbol = format!("slint_embedded_resource_{resource_id}");
4170                    format!(
4171                        r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#,
4172                        escape_string(extension)
4173                    )
4174                }
4175                crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => {
4176                    format!(
4177                        "slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})"
4178                    )
4179                }
4180            };
4181            match &nine_slice {
4182                Some([a, b, c, d]) => {
4183                    format!(
4184                        "([&] {{ auto image = {image}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()"
4185                    )
4186                }
4187                None => image,
4188            }
4189        }
4190        Expression::Condition { condition, true_expr, false_expr } => {
4191            let ty = expr.ty(ctx);
4192            let cond_code = compile_expression(condition, ctx);
4193            let cond_code = remove_parentheses(&cond_code);
4194            let true_code = compile_expression(true_expr, ctx);
4195            let false_code = compile_expression(false_expr, ctx);
4196            if ty == Type::Void {
4197                format!("if ({cond_code}) {{ {true_code}; }} else {{ {false_code}; }}")
4198            } else {
4199                format!("({cond_code} ? {true_code} : {false_code})")
4200            }
4201        }
4202        Expression::Array { element_ty, values, output } => {
4203            let ty = element_ty.cpp_type().unwrap();
4204            let mut val = values
4205                .iter()
4206                .map(|e| format!("{ty} ( {expr} )", expr = compile_expression(e, ctx), ty = ty));
4207            match output {
4208                llr::ArrayOutput::Model => format!(
4209                    "std::make_shared<slint::private_api::ArrayModel<{count},{ty}>>({val})",
4210                    count = values.len(),
4211                    ty = ty,
4212                    val = val.join(", ")
4213                ),
4214                llr::ArrayOutput::Slice => format!(
4215                    "slint::private_api::make_slice<{ty}>(std::array<{ty}, {count}>{{ {val} }}.data(), {count})",
4216                    count = values.len(),
4217                    ty = ty,
4218                    val = val.join(", ")
4219                ),
4220                llr::ArrayOutput::Vector => {
4221                    format!("std::vector<{ty}>{{ {val} }}", ty = ty, val = val.join(", "))
4222                }
4223            }
4224        }
4225        Expression::Struct { ty, values } => {
4226            if ty.name.is_none() {
4227                let mut elem = ty.fields.iter().map(|(k, t)| {
4228                    values
4229                        .get(k)
4230                        .map(|e| compile_expression(e, ctx))
4231                        .map(|e| {
4232                            // explicit conversion to avoid warning C4244 (possible loss of data) with MSVC
4233                            if t.as_unit_product().is_some() {
4234                                format!("{}({e})", t.cpp_type().unwrap())
4235                            } else {
4236                                e
4237                            }
4238                        })
4239                        .unwrap_or_else(|| "(Error: missing member in object)".to_owned())
4240                });
4241                format!("std::make_tuple({})", elem.join(", "))
4242            } else {
4243                format!(
4244                    "[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})",
4245                    args = (0..values.len()).map(|i| format!("const auto &a_{i}")).join(", "),
4246                    ty = Type::Struct(ty.clone()).cpp_type().unwrap(),
4247                    fields = values
4248                        .keys()
4249                        .enumerate()
4250                        .map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i))
4251                        .join(""),
4252                    vals = values.values().map(|e| compile_expression(e, ctx)).join(", "),
4253                )
4254            }
4255        }
4256        Expression::EasingCurve(EasingCurve::Linear) => {
4257            "slint::cbindgen_private::EasingCurve()".into()
4258        }
4259        Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!(
4260            "slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {a}, {b}, {c}, {d})"
4261        ),
4262        // The other curves have no parameters and their C++ Tag matches the variant name.
4263        Expression::EasingCurve(e) => {
4264            format!("slint::cbindgen_private::EasingCurve::Tag::{e:?}")
4265        }
4266        Expression::LinearGradient { angle, stops } => {
4267            let angle = compile_expression(angle, ctx);
4268            let mut stops_it = stops.iter().map(|(color, stop)| {
4269                let color = compile_expression(color, ctx);
4270                let position = compile_expression(stop, ctx);
4271                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
4272            });
4273            format!(
4274                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::LinearGradientBrush({}, stops, {})); }}()",
4275                stops_it.join(", "),
4276                angle,
4277                stops.len()
4278            )
4279        }
4280        Expression::RadialGradient { center, radius, stops } => {
4281            let mut stops_it = stops.iter().map(|(color, stop)| {
4282                let color = compile_expression(color, ctx);
4283                let position = compile_expression(stop, ctx);
4284                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
4285            });
4286            let center_setup = match (center, radius) {
4287                (Some((cx, cy)), Some(r)) => {
4288                    let cx = compile_expression(cx, ctx);
4289                    let cy = compile_expression(cy, ctx);
4290                    let r = compile_expression(r, ctx);
4291                    format!(
4292                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, float({cx}), float({cy}), float({r})));",
4293                        stops_count = stops.len()
4294                    )
4295                }
4296                (Some((cx, cy)), None) => {
4297                    let cx = compile_expression(cx, ctx);
4298                    let cy = compile_expression(cy, ctx);
4299                    format!(
4300                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, float({cx}), float({cy}), -1.0f));",
4301                        stops_count = stops.len()
4302                    )
4303                }
4304                (None, Some(r)) => {
4305                    let r = compile_expression(r, ctx);
4306                    format!(
4307                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {stops_count}, std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::quiet_NaN(), float({r})));",
4308                        stops_count = stops.len()
4309                    )
4310                }
4311                (None, None) => {
4312                    format!(
4313                        "return slint::Brush(slint::private_api::RadialGradientBrush(stops, {}));",
4314                        stops.len()
4315                    )
4316                }
4317            };
4318            format!(
4319                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; {} }}()",
4320                stops_it.join(", "),
4321                center_setup
4322            )
4323        }
4324        Expression::ConicGradient { from_angle, center, stops } => {
4325            let from_angle = compile_expression(from_angle, ctx);
4326            let mut stops_it = stops.iter().map(|(color, stop)| {
4327                let color = compile_expression(color, ctx);
4328                let position = compile_expression(stop, ctx);
4329                format!("slint::private_api::GradientStop{{ {color}, float({position}), }}")
4330            });
4331            let center_setup = if let Some((cx, cy)) = center {
4332                let cx = compile_expression(cx, ctx);
4333                let cy = compile_expression(cy, ctx);
4334                format!(
4335                    "return slint::Brush(slint::private_api::ConicGradientBrush(float({from_angle}), stops, {stops_count}, float({cx}), float({cy})));",
4336                    stops_count = stops.len()
4337                )
4338            } else {
4339                format!(
4340                    "return slint::Brush(slint::private_api::ConicGradientBrush(float({from_angle}), stops, {}));",
4341                    stops.len()
4342                )
4343            };
4344            format!(
4345                "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; {} }}()",
4346                stops_it.join(", "),
4347                center_setup
4348            )
4349        }
4350        Expression::EnumerationValue(value) => {
4351            let prefix =
4352                if value.enumeration.node.is_some() { "" } else { "slint::cbindgen_private::" };
4353            format!(
4354                "{prefix}{}::{}",
4355                ident(&value.enumeration.name),
4356                ident(&value.to_pascal_case()),
4357            )
4358        }
4359        Expression::LayoutCacheAccess {
4360            layout_cache_prop,
4361            index,
4362            repeater_index,
4363            entries_per_item,
4364        } => {
4365            let cache = access_member(layout_cache_prop, ctx);
4366            cache.map_or_default(|cache| {
4367                if let Some(ri) = repeater_index {
4368                    format!(
4369                        "slint::private_api::layout_cache_access({}.get(), {}, {}, {})",
4370                        cache,
4371                        index,
4372                        compile_expression(ri, ctx),
4373                        entries_per_item
4374                    )
4375                } else {
4376                    format!("{cache}.get()[{index}]")
4377                }
4378            })
4379        }
4380        Expression::GridRepeaterCacheAccess {
4381            layout_cache_prop,
4382            index,
4383            repeater_index,
4384            stride,
4385            child_offset,
4386            inner_repeater_index,
4387            entries_per_item,
4388        } => {
4389            let cache = access_member(layout_cache_prop, ctx);
4390            cache.map_or_default(|cache| {
4391                let stride_val = compile_expression(stride, ctx);
4392                let col_offset = if let Some(inner_ri) = inner_repeater_index {
4393                    format!(
4394                        "{} + {} * {}",
4395                        child_offset,
4396                        compile_expression(inner_ri, ctx),
4397                        entries_per_item
4398                    )
4399                } else {
4400                    child_offset.to_string()
4401                };
4402                format!(
4403                    "slint::private_api::layout_cache_grid_repeater_access({}.get(), {}, {}, {}, {})",
4404                    cache,
4405                    index,
4406                    compile_expression(repeater_index, ctx),
4407                    stride_val,
4408                    col_offset
4409                )
4410            })
4411        }
4412        Expression::WithLayoutItemInfo {
4413            cells_variable,
4414            repeater_indices_var_name,
4415            repeater_steps_var_name,
4416            elements,
4417            orientation,
4418            sub_expression,
4419        } => generate_with_layout_item_info(
4420            cells_variable,
4421            repeater_indices_var_name.as_ref().map(SmolStr::as_str),
4422            repeater_steps_var_name.as_ref().map(SmolStr::as_str),
4423            elements.as_ref(),
4424            *orientation,
4425            sub_expression,
4426            ctx,
4427        ),
4428        Expression::WithFlexboxLayoutItemInfo {
4429            cells_h_variable,
4430            cells_v_variable,
4431            repeater_indices_var_name,
4432            elements,
4433            sub_expression,
4434        } => generate_with_flexbox_layout_item_info(
4435            cells_h_variable,
4436            cells_v_variable,
4437            repeater_indices_var_name.as_ref().map(SmolStr::as_str),
4438            elements.as_ref(),
4439            sub_expression,
4440            ctx,
4441        ),
4442        Expression::SolveFlexboxLayoutWithMeasure {
4443            data,
4444            repeater_indices,
4445            measure_cells,
4446            default_cells,
4447        } => {
4448            let data = compile_expression(data, ctx);
4449            let repeater_indices = compile_expression(repeater_indices, ctx);
4450            // cbindgen does not expose `LayoutInfo::preferred_bounded()`, so
4451            // inline it: preferred_bounded = max(min(preferred, max), min).
4452            let mut v_cases = String::new();
4453            let mut h_cases = String::new();
4454            for (i, item) in measure_cells.iter().enumerate() {
4455                if let Either::Left((h_info, v_info)) = item {
4456                    let v = compile_expression(v_info, ctx);
4457                    let h = compile_expression(h_info, ctx);
4458                    v_cases.push_str(&format!(
4459                        "case {i}: {{ auto li = {v}; nh = std::max(std::min(li.preferred, li.max), li.min); break; }}\n"
4460                    ));
4461                    h_cases.push_str(&format!(
4462                        "case {i}: {{ auto li = {h}; nw = std::max(std::min(li.preferred, li.max), li.min); break; }}\n"
4463                    ));
4464                }
4465            }
4466            // Preferred (default-constraint) size per cell, returned when taffy
4467            // asks for a dimension without a known cross-axis size.
4468            let mut pref_w = String::new();
4469            let mut pref_h = String::new();
4470            for item in default_cells {
4471                if let Either::Left((h_info, v_info)) = item {
4472                    let h = compile_expression(h_info, ctx);
4473                    let v = compile_expression(v_info, ctx);
4474                    pref_w.push_str(&format!(
4475                        "[&]{{ auto li = {h}; return std::max(std::min(li.preferred, li.max), li.min); }}(),\n"
4476                    ));
4477                    pref_h.push_str(&format!(
4478                        "[&]{{ auto li = {v}; return std::max(std::min(li.preferred, li.max), li.min); }}(),\n"
4479                    ));
4480                }
4481            }
4482            format!(
4483                "slint::private_api::solve_flexbox_layout_with_measure({data}, {repeater_indices}, \
4484                 [&](uintptr_t index, std::optional<float> known_w, std::optional<float> known_h) \
4485                 -> std::pair<float, float> {{\n\
4486                    const float pref_w[] = {{ {pref_w} }};\n\
4487                    const float pref_h[] = {{ {pref_h} }};\n\
4488                    const size_t cell_count = sizeof(pref_w) / sizeof(float);\n\
4489                    float w = known_w.value_or(index < cell_count ? pref_w[index] : 0.0f);\n\
4490                    float h = known_h.value_or(index < cell_count ? pref_h[index] : 0.0f);\n\
4491                    if (known_w.has_value() && !known_h.has_value()) {{\n\
4492                        [[maybe_unused]] float measure_known_w = w;\n\
4493                        float nh = h;\n\
4494                        switch (index) {{\n{v_cases}default: break;\n}}\n\
4495                        return {{ w, nh }};\n\
4496                    }}\n\
4497                    if (known_h.has_value() && !known_w.has_value()) {{\n\
4498                        [[maybe_unused]] float measure_known_h = h;\n\
4499                        float nw = w;\n\
4500                        switch (index) {{\n{h_cases}default: break;\n}}\n\
4501                        return {{ nw, h }};\n\
4502                    }}\n\
4503                    return {{ w, h }};\n\
4504                 }})"
4505            )
4506        }
4507        Expression::WithGridInputData {
4508            cells_variable,
4509            repeater_indices_var_name,
4510            repeater_steps_var_name,
4511            elements,
4512            sub_expression,
4513        } => generate_with_grid_input_data(
4514            cells_variable,
4515            repeater_indices_var_name,
4516            repeater_steps_var_name,
4517            elements.as_ref(),
4518            sub_expression,
4519            ctx,
4520        ),
4521        Expression::MinMax { ty, op, lhs, rhs } => {
4522            let ident = match op {
4523                MinMaxOp::Min => "min",
4524                MinMaxOp::Max => "max",
4525            };
4526            let lhs_code = compile_expression(lhs, ctx);
4527            let rhs_code = compile_expression(rhs, ctx);
4528            format!(
4529                r#"std::{ident}<{ty}>({lhs_code}, {rhs_code})"#,
4530                ty = ty.cpp_type().unwrap_or_default(),
4531                ident = ident,
4532                lhs_code = lhs_code,
4533                rhs_code = rhs_code
4534            )
4535        }
4536        Expression::EmptyComponentFactory => panic!("component-factory not yet supported in C++"),
4537        Expression::EmptyDataTransfer => "slint::DataTransfer()".into(),
4538        Expression::TranslationReference { format_args, string_index, plural } => {
4539            let args = compile_expression(format_args, ctx);
4540            match plural {
4541                Some(plural) => {
4542                    let plural = compile_expression(plural, ctx);
4543                    format!(
4544                        "slint::private_api::translate_from_bundle_with_plural(slint_translation_bundle_plural_{string_index}_str, slint_translation_bundle_plural_{string_index}_idx,  slint_translated_plural_rules, {args}, {plural})"
4545                    )
4546                }
4547                None => format!(
4548                    "slint::private_api::translate_from_bundle(slint_translation_bundle_{string_index}, {args})"
4549                ),
4550            }
4551        }
4552    }
4553}
4554
4555fn struct_field_access(base: String, s: &crate::langtype::Struct, name: &str) -> String {
4556    if s.name.is_none() {
4557        let index = s
4558            .fields
4559            .keys()
4560            .position(|k| k == name)
4561            .expect("Expression::ObjectAccess: Cannot find a key in an object");
4562        format!("std::get<{}>({})", index, base)
4563    } else {
4564        format!("{}.{}", base, ident(name))
4565    }
4566}
4567
4568fn compile_builtin_function_call(
4569    function: BuiltinFunction,
4570    arguments: &[llr::Expression],
4571    ctx: &EvaluationContext,
4572) -> String {
4573    let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
4574    let pi_180 = std::f64::consts::PI / 180.0;
4575
4576    match function {
4577        BuiltinFunction::GetWindowScaleFactor => {
4578            format!("{}.scale_factor()", access_window_field(ctx))
4579        }
4580        BuiltinFunction::GetWindowDefaultFontSize => {
4581            "slint::private_api::get_resolved_default_font_size(*this)".to_string()
4582        }
4583        BuiltinFunction::AnimationTick => "slint::cbindgen_private::slint_animation_tick()".into(),
4584        BuiltinFunction::Debug => {
4585            ctx.generator_state.conditional_includes.iostream.set(true);
4586            format!("slint::private_api::debug({});", a.join(","))
4587        }
4588        BuiltinFunction::DecimalSeparator => "slint::private_api::decimal_separator()".into(),
4589        BuiltinFunction::Mod => {
4590            ctx.generator_state.conditional_includes.cmath.set(true);
4591            format!("([](float a, float b) {{ auto r = std::fmod(a, b); return r >= 0 ? r : r + std::abs(b); }})({},{})", a.next().unwrap(), a.next().unwrap())
4592        }
4593        BuiltinFunction::Round => {
4594            ctx.generator_state.conditional_includes.cmath.set(true);
4595            format!("std::round({})", a.next().unwrap())
4596        }
4597        BuiltinFunction::Ceil => {
4598            ctx.generator_state.conditional_includes.cmath.set(true);
4599            format!("std::ceil({})", a.next().unwrap())
4600        }
4601        BuiltinFunction::Floor => {
4602            ctx.generator_state.conditional_includes.cmath.set(true);
4603            format!("std::floor({})", a.next().unwrap())
4604        }
4605        BuiltinFunction::Sqrt => {
4606            ctx.generator_state.conditional_includes.cmath.set(true);
4607            format!("std::sqrt({})", a.next().unwrap())
4608        }
4609        BuiltinFunction::Abs => {
4610            ctx.generator_state.conditional_includes.cmath.set(true);
4611            format!("std::abs({})", a.next().unwrap())
4612        }
4613        BuiltinFunction::Log => {
4614            ctx.generator_state.conditional_includes.cmath.set(true);
4615            format!("std::log({}) / std::log({})", a.next().unwrap(), a.next().unwrap())
4616        }
4617        BuiltinFunction::Ln => {
4618            ctx.generator_state.conditional_includes.cmath.set(true);
4619            format!("std::log({})", a.next().unwrap())
4620        }
4621        BuiltinFunction::Pow => {
4622            ctx.generator_state.conditional_includes.cmath.set(true);
4623            format!("std::pow(({}), ({}))", a.next().unwrap(), a.next().unwrap())
4624        }
4625        BuiltinFunction::Exp => {
4626            ctx.generator_state.conditional_includes.cmath.set(true);
4627            format!("std::exp({})", a.next().unwrap())
4628        }
4629        BuiltinFunction::Sin => {
4630            ctx.generator_state.conditional_includes.cmath.set(true);
4631            format!("std::sin(({}) * {})", a.next().unwrap(), pi_180)
4632        }
4633        BuiltinFunction::Cos => {
4634            ctx.generator_state.conditional_includes.cmath.set(true);
4635            format!("std::cos(({}) * {})", a.next().unwrap(), pi_180)
4636        }
4637        BuiltinFunction::Tan => {
4638            ctx.generator_state.conditional_includes.cmath.set(true);
4639            format!("std::tan(({}) * {})", a.next().unwrap(), pi_180)
4640        }
4641        BuiltinFunction::ASin => {
4642            ctx.generator_state.conditional_includes.cmath.set(true);
4643            format!("std::asin({}) / {}", a.next().unwrap(), pi_180)
4644        }
4645        BuiltinFunction::ACos => {
4646            ctx.generator_state.conditional_includes.cmath.set(true);
4647            format!("std::acos({}) / {}", a.next().unwrap(), pi_180)
4648        }
4649        BuiltinFunction::ATan => {
4650            ctx.generator_state.conditional_includes.cmath.set(true);
4651            format!("std::atan({}) / {}", a.next().unwrap(), pi_180)
4652        }
4653        BuiltinFunction::ATan2 => {
4654            ctx.generator_state.conditional_includes.cmath.set(true);
4655            format!("std::atan2({}, {}) / {}", a.next().unwrap(), a.next().unwrap(), pi_180)
4656        }
4657        BuiltinFunction::ToFixed => {
4658            format!("[](double n, int d) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_fixed(&out, n, std::max(d, 0)); return out; }}({}, {})",
4659                a.next().unwrap(), a.next().unwrap(),
4660            )
4661        }
4662        BuiltinFunction::ToPrecision => {
4663            format!("[](double n, int p) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_precision(&out, n, std::max(p, 0)); return out; }}({}, {})",
4664                a.next().unwrap(), a.next().unwrap(),
4665            )
4666        }
4667        BuiltinFunction::ToStringUnlocalized => {
4668            format!("[](double n) {{ slint::SharedString out; slint::cbindgen_private::slint_shared_string_from_number_unlocalized(&out, n); return out; }}({})",
4669                a.next().unwrap(),
4670            )
4671        }
4672        BuiltinFunction::SetFocusItem => {
4673            if let [llr::Expression::PropertyReference(pr)] = arguments {
4674                let window = access_window_field(ctx);
4675                let focus_item = access_item_rc(pr, ctx);
4676                format!("{window}.set_focus_item({focus_item}, true, slint::cbindgen_private::FocusReason::Programmatic);")
4677            } else {
4678                panic!("internal error: invalid args to SetFocusItem {arguments:?}")
4679            }
4680        }
4681        BuiltinFunction::ClearFocusItem => {
4682            if let [llr::Expression::PropertyReference(pr)] = arguments {
4683                let window = access_window_field(ctx);
4684                let focus_item = access_item_rc(pr, ctx);
4685                format!("{window}.set_focus_item({focus_item}, false, slint::cbindgen_private::FocusReason::Programmatic);")
4686            } else {
4687                panic!("internal error: invalid args to ClearFocusItem {arguments:?}")
4688            }
4689        }
4690        /* std::from_chars is unfortunately not yet implemented in all stdlib compiler we support.
4691         * And std::strtod depends on the locale. Use slint_string_to_float implemented in Rust
4692        BuiltinFunction::StringIsFloat => {
4693            "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
4694                .into()
4695        }
4696        BuiltinFunction::StringToFloat => {
4697            "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a) ? v : 0; }"
4698                .into()
4699        }*/
4700        BuiltinFunction::StringIsFloat => {
4701            ctx.generator_state.conditional_includes.cstdlib.set(true);
4702            format!("[](const auto &a){{ float res = 0; return slint::cbindgen_private::slint_string_to_float(&a, &res); }}({})", a.next().unwrap())
4703        }
4704        BuiltinFunction::StringToFloat => {
4705            ctx.generator_state.conditional_includes.cstdlib.set(true);
4706            format!("[](const auto &a){{ float res = 0; slint::cbindgen_private::slint_string_to_float(&a, &res); return res; }}({})", a.next().unwrap())
4707        }
4708        BuiltinFunction::StringIsEmpty => {
4709            format!("{}.empty()", a.next().unwrap())
4710        }
4711        BuiltinFunction::StringCharacterCount => {
4712            format!("[](const auto &a){{ return slint::cbindgen_private::slint_string_character_count(&a); }}({})", a.next().unwrap())
4713        }
4714        BuiltinFunction::StringToLowercase => {
4715            format!("{}.to_lowercase()", a.next().unwrap())
4716        }
4717        BuiltinFunction::StringToUppercase => {
4718            format!("{}.to_uppercase()", a.next().unwrap())
4719        }
4720        BuiltinFunction::KeysToString => {
4721            format!("{}.to_string()", a.next().unwrap())
4722        }
4723        BuiltinFunction::ColorRgbaStruct => {
4724            format!("{}.to_argb_uint()", a.next().unwrap())
4725        }
4726        BuiltinFunction::ColorHsvaStruct => {
4727            format!("{}.to_hsva()", a.next().unwrap())
4728        }
4729        BuiltinFunction::ColorOklchStruct => {
4730            format!("{}.to_oklch()", a.next().unwrap())
4731        }
4732        BuiltinFunction::ColorBrighter => {
4733            format!("{}.brighter({})", a.next().unwrap(), a.next().unwrap())
4734        }
4735        BuiltinFunction::ColorDarker => {
4736            format!("{}.darker({})", a.next().unwrap(), a.next().unwrap())
4737        }
4738        BuiltinFunction::ColorTransparentize => {
4739            format!("{}.transparentize({})", a.next().unwrap(), a.next().unwrap())
4740        }
4741        BuiltinFunction::ColorMix => {
4742            format!("{}.mix({}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap())
4743        }
4744        BuiltinFunction::ColorWithAlpha => {
4745            format!("{}.with_alpha({})", a.next().unwrap(), a.next().unwrap())
4746        }
4747        BuiltinFunction::ImageSize => {
4748            format!("{}.size()", a.next().unwrap())
4749        }
4750        BuiltinFunction::ArrayLength => {
4751            format!("slint::private_api::model_length({})", a.next().unwrap())
4752        }
4753        BuiltinFunction::Rgb => {
4754            format!("slint::Color::from_argb_uint8(std::clamp(static_cast<float>({a}) * 255., 0., 255.), std::clamp(static_cast<int>({r}), 0, 255), std::clamp(static_cast<int>({g}), 0, 255), std::clamp(static_cast<int>({b}), 0, 255))",
4755                r = a.next().unwrap(),
4756                g = a.next().unwrap(),
4757                b = a.next().unwrap(),
4758                a = a.next().unwrap(),
4759            )
4760        }
4761        BuiltinFunction::Hsv => {
4762            format!("slint::Color::from_hsva(static_cast<float>({h}), std::clamp(static_cast<float>({s}), 0.f, 1.f), std::clamp(static_cast<float>({v}), 0.f, 1.f), std::clamp(static_cast<float>({a}), 0.f, 1.f))",
4763                h = a.next().unwrap(),
4764                s = a.next().unwrap(),
4765                v = a.next().unwrap(),
4766                a = a.next().unwrap(),
4767            )
4768        }
4769        BuiltinFunction::Oklch => {
4770            format!("slint::Color::from_oklch(std::clamp(static_cast<float>({l}), 0.f, 1.f), std::max(static_cast<float>({c}), 0.f), static_cast<float>({h}), std::clamp(static_cast<float>({alpha}), 0.f, 1.f))",
4771                l = a.next().unwrap(),
4772                c = a.next().unwrap(),
4773                h = a.next().unwrap(),
4774                alpha = a.next().unwrap(),
4775            )
4776        }
4777        BuiltinFunction::ColorScheme => {
4778            // Route through the runtime helper so a `Palette.color-scheme` binding
4779            // inside a SystemTrayIcon-rooted component naturally resolves against
4780            // the tray's scheme without going through any window adapter.
4781            format!(
4782                "[&]{{ auto _root = (*{0}->root_weak.lock()).into_dyn(); return slint::cbindgen_private::slint_context_color_scheme(&_root); }}()",
4783                ctx.generator_state.global_access
4784            )
4785        }
4786        BuiltinFunction::AccentColor => {
4787            format!(
4788                "[&]{{ auto _root = (*{0}->root_weak.lock()).into_dyn(); slint::Color col; slint::cbindgen_private::slint_context_accent_color(&_root, &col); return col; }}()",
4789                ctx.generator_state.global_access
4790            )
4791        }
4792        BuiltinFunction::SupportsNativeMenuBar => {
4793            format!("{}.supports_native_menu_bar()", access_window_field(ctx))
4794        }
4795        BuiltinFunction::SetupMenuBar => {
4796            let window = access_window_field(ctx);
4797            let [llr::Expression::PropertyReference(entries_r), llr::Expression::PropertyReference(sub_menu_r), llr::Expression::PropertyReference(activated_r), llr::Expression::NumberLiteral(tree_index), llr::Expression::BoolLiteral(no_native), condition, visible, ..] = arguments
4798            else {
4799                panic!("internal error: incorrect argument count to SetupMenuBar")
4800            };
4801
4802            let current_sub_component = ctx.current_sub_component().unwrap();
4803            let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
4804            let access_entries = access_member(entries_r, ctx).unwrap();
4805            let access_sub_menu = access_member(sub_menu_r, ctx).unwrap();
4806            let access_activated = access_member(activated_r, ctx).unwrap();
4807            if *no_native {
4808                format!(r"{{
4809                    auto item_tree = {item_tree_id}::create(self);
4810                    auto item_tree_dyn = item_tree.into_dyn();
4811                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn);
4812                    slint::private_api::slint_windowrc_setup_menu_bar_shortcuts(&{window}.handle(), &menu_wrapper);
4813                    slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
4814                }}")
4815            } else {
4816                let compile_prop = |prop_expr: &llr::Expression| {
4817                    let binding = compile_expression(prop_expr, ctx);
4818                    format!(r"[](auto menu_tree) {{
4819                                auto self_mapped = reinterpret_cast<const {item_tree_id} *>(menu_tree->operator->())->parent.lock();
4820                                [[maybe_unused]] auto self = &**self_mapped;
4821                                return {binding};
4822                            }}")
4823                };
4824
4825                let condition = compile_prop(condition);
4826                let visible = compile_prop(visible);
4827
4828                format!(r"{{
4829                    auto item_tree = {item_tree_id}::create(self);
4830                    auto item_tree_dyn = item_tree.into_dyn();
4831                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn, {condition}, {visible});
4832                    slint::private_api::slint_windowrc_setup_menu_bar_shortcuts(&{window}.handle(), &menu_wrapper);
4833                    if ({window}.supports_native_menu_bar()) {{
4834                        slint::cbindgen_private::slint_windowrc_setup_native_menu_bar(&{window}.handle(), &menu_wrapper);
4835                    }} else {{
4836                        slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
4837                    }}
4838                }}")
4839            }
4840        }
4841        BuiltinFunction::SetupSystemTrayIcon => {
4842            let [
4843                llr::Expression::PropertyReference(system_tray_ref),
4844                llr::Expression::NumberLiteral(tree_index),
4845                rest @ ..,
4846            ] = arguments
4847            else {
4848                panic!("internal error: incorrect arguments to SetupSystemTrayIcon")
4849            };
4850
4851            let current_sub_component = ctx.current_sub_component().unwrap();
4852            let item_tree_id = ident(
4853                &ctx.compilation_unit.sub_components
4854                    [current_sub_component.menu_item_trees[*tree_index as usize].root]
4855                    .name,
4856            );
4857            let system_tray = access_member(system_tray_ref, ctx).unwrap();
4858            let system_tray_rc = access_item_rc(system_tray_ref, ctx);
4859
4860            // `if cond : Menu { ... }` is lowered to a condition lambda passed
4861            // alongside the menu wrapper. `create_menu_wrapper` already accepts
4862            // the optional condition pointer.
4863            let condition = if let [condition] = rest {
4864                let condition = compile_expression(condition, ctx);
4865                format!(
4866                    r"[](auto menu_tree) {{
4867                        auto self_mapped = reinterpret_cast<const {item_tree_id} *>(menu_tree->operator->())->parent.lock();
4868                        [[maybe_unused]] auto self = &**self_mapped;
4869                        return {condition};
4870                    }}"
4871                )
4872            } else {
4873                "nullptr".to_string()
4874            };
4875
4876            format!(
4877                r"{{
4878                    auto item_tree = {item_tree_id}::create(self);
4879                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree.into_dyn(), {condition});
4880                    slint::cbindgen_private::ItemRc item_rc{{ {system_tray_rc} }};
4881                    slint::cbindgen_private::slint_system_tray_icon_set_menu(&{system_tray}, &item_rc, &menu_wrapper);
4882                }}"
4883            )
4884        }
4885        BuiltinFunction::Use24HourFormat => {
4886            "slint::cbindgen_private::slint_date_time_use_24_hour_format()".to_string()
4887        }
4888        BuiltinFunction::MonthDayCount => {
4889            format!("slint::cbindgen_private::slint_date_time_month_day_count({}, {})", a.next().unwrap(), a.next().unwrap())
4890        }
4891        BuiltinFunction::MonthOffset => {
4892            format!("slint::cbindgen_private::slint_date_time_month_offset({}, {})", a.next().unwrap(), a.next().unwrap())
4893        }
4894        BuiltinFunction::FormatDate => {
4895            format!("[](const auto &format, int d, int m, int y) {{ slint::SharedString out; slint::cbindgen_private::slint_date_time_format_date(&format, d, m, y, &out); return out; }}({}, {}, {}, {})",
4896                a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()
4897            )
4898        }
4899        BuiltinFunction::DateNow => {
4900            "[] { int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_date_now(&d, &m, &y); return std::make_shared<slint::private_api::ArrayModel<3,int32_t>>(d, m, y); }()".into()
4901        }
4902        BuiltinFunction::ValidDate => {
4903            format!(
4904                "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; return slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); }}({}, {})",
4905                a.next().unwrap(), a.next().unwrap()
4906            )
4907        }
4908        BuiltinFunction::ParseDate => {
4909            format!(
4910                "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); return std::make_shared<slint::private_api::ArrayModel<3,int32_t>>(d, m, y); }}({}, {})",
4911                a.next().unwrap(), a.next().unwrap()
4912            )
4913        }
4914        BuiltinFunction::SetTextInputFocused => {
4915            format!("{}.set_text_input_focused({})", access_window_field(ctx), a.next().unwrap())
4916        }
4917        BuiltinFunction::TextInputFocused => {
4918            format!("{}.text_input_focused()", access_window_field(ctx))
4919        }
4920        BuiltinFunction::ShowPopupWindow => {
4921            // A trailing argument may carry the synthesized `is-open` property reference, resolved in
4922            // this call's own frame (see lower_show_popup_window).
4923            if let [llr::Expression::NumberLiteral(popup_index), close_policy, llr::Expression::PropertyReference(parent_ref), is_open_args @ ..] =
4924                arguments
4925            {
4926                let mut component_access = MemberAccess::Direct("self".into());
4927                let llr::MemberReference::Relative { parent_level, .. } = parent_ref else {unreachable!()};
4928                for _ in 0..*parent_level {
4929                    component_access = component_access.and_then(|x| format!("{x}->parent.lock()"));
4930                }
4931
4932                let window = access_window_field(ctx);
4933                let current_sub_component = &ctx.compilation_unit.sub_components[ctx.parent_sub_component_idx(*parent_level).unwrap()];
4934                let popup = &current_sub_component.popup_windows[*popup_index as usize];
4935                let popup_window_id =
4936                    ident(&ctx.compilation_unit.sub_components[popup.item_tree.root].name);
4937                let parent_component = access_item_rc(parent_ref, ctx);
4938                let parent_ctx = ParentScope::new(ctx, None);
4939                let popup_ctx = EvaluationContext::new_sub_component(
4940                    ctx.compilation_unit,
4941                    popup.item_tree.root,
4942                    CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
4943                    Some(&parent_ctx),
4944                );
4945                let position = compile_expression(&popup.position.borrow(), &popup_ctx);
4946                let close_policy = compile_expression(close_policy, ctx);
4947                let window_kind = if popup.is_tooltip { "slint::cbindgen_private::WindowKind::ToolTip" } else { "slint::cbindgen_private::WindowKind::Popup" };
4948                // Keep the parent's `is-open` property in sync. The setter is passed directly into
4949                // `show_popup`, so there is no extra registration call and no second popup lookup. The
4950                // `false` is delivered when the popup is dropped, which may be long after this frame, so
4951                // we cannot capture a raw `self`. We capture a weak *mapped* handle to the current
4952                // component instance -- the C++ equivalent of Rust's `self_weak`, which for a
4953                // sub-component points at that sub-component itself rather than at the enclosing item
4954                // tree (`self->self_weak`). Popups without `is-open` get a no-op setter.
4955                let is_open_setter = match is_open_args.first() {
4956                    Some(llr::Expression::PropertyReference(is_open_ref)) => {
4957                        let self_ty = ident(&ctx.current_sub_component().expect("ShowPopupWindow is invoked on a sub-component").name);
4958                        let set_is_open = access_member(is_open_ref, ctx).then(|p| format!("{p}.set(is_open)"));
4959                        format!(
4960                            "[weak = vtable::VWeakMapped<slint::private_api::ItemTreeVTable, const {self_ty}>( \
4961                                    vtable::VRcMapped<slint::private_api::ItemTreeVTable, const {self_ty}>(self->self_weak.lock().value(), self))] \
4962                             (bool is_open) {{ \
4963                                auto rc = weak.lock(); \
4964                                if (!rc) return; \
4965                                [[maybe_unused]] auto self = &**rc; \
4966                                {set_is_open}; \
4967                            }}"
4968                        )
4969                    }
4970                    _ => "[](bool) {}".to_string(),
4971                };
4972                component_access.then(|component_access| format!(
4973                    // Use a block statement to create own globals and popup instance
4974                    "{window}.close_popup({component_access}->popup_id_{popup_index}); \
4975                    {component_access}->popup_id_{popup_index} =  \
4976                        {window}.template show_popup<{popup_window_id}>(&*({component_access}),  \
4977                                                                        [=](auto self) {{ return {position}; }},  \
4978                                                                        {close_policy},  \
4979                                                                        {{ {parent_component} }},  \
4980                                                                        {window_kind},  \
4981                                                                        {is_open_setter})"
4982                ))
4983            } else {
4984                panic!("internal error: invalid args to ShowPopupWindow {arguments:?}")
4985            }
4986        }
4987        BuiltinFunction::ClosePopupWindow => {
4988            if let [llr::Expression::NumberLiteral(popup_index), llr::Expression::PropertyReference(parent_ref)] = arguments {
4989                let mut component_access = MemberAccess::Direct("self".into());
4990                let llr::MemberReference::Relative { parent_level, .. } = parent_ref else {unreachable!()};
4991                for _ in 0..*parent_level {
4992                    component_access = component_access.and_then(|x| format!("{x}->parent.lock()"));
4993                }
4994
4995                component_access.then(|component_access| format!("{component_access}->globals->window().window_handle().close_popup({component_access}->popup_id_{popup_index})"))
4996            } else {
4997                panic!("internal error: invalid args to ClosePopupWindow {arguments:?}")
4998            }
4999        }
5000
5001        BuiltinFunction::ShowPopupMenu | BuiltinFunction::ShowPopupMenuInternal => {
5002            let [llr::Expression::PropertyReference(context_menu_ref), entries, position] = arguments
5003            else {
5004                panic!("internal error: invalid args to ShowPopupMenu {arguments:?}")
5005            };
5006
5007            let context_menu = access_member(context_menu_ref, ctx);
5008            let context_menu_rc = access_item_rc(context_menu_ref, ctx);
5009            let position = compile_expression(position, ctx);
5010            let popup = ctx
5011                .compilation_unit
5012                .popup_menu
5013                .as_ref()
5014                .expect("there should be a popup menu if we want to show it");
5015            let popup_id = ident(&ctx.compilation_unit.sub_components[popup.item_tree.root].name);
5016            let window = access_window_field(ctx);
5017
5018            let popup_ctx = EvaluationContext::new_sub_component(
5019                ctx.compilation_unit,
5020                popup.item_tree.root,
5021                CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
5022                None,
5023            );
5024            let access_entries = access_member(&popup.entries, &popup_ctx).unwrap();
5025            let access_sub_menu = access_member(&popup.sub_menu, &popup_ctx).unwrap();
5026            let access_activated = access_member(&popup.activated, &popup_ctx).unwrap();
5027            let access_close = access_member(&popup.close, &popup_ctx).unwrap();
5028
5029            let close_popup = context_menu.then(|context_menu| {
5030                format!("{window}.close_popup({context_menu}.popup_id)")
5031            });
5032            let set_id = context_menu
5033                .then(|context_menu| format!("{context_menu}.popup_id = id"));
5034
5035            if let llr::Expression::NumberLiteral(tree_index) = entries {
5036                // We have an MenuItem tree
5037                let current_sub_component = ctx.current_sub_component().unwrap();
5038                let item_tree_id = ident(&ctx.compilation_unit.sub_components[current_sub_component.menu_item_trees[*tree_index as usize].root].name);
5039                format!(r"{{
5040                    auto item_tree = {item_tree_id}::create(self);
5041                    auto item_tree_dyn = item_tree.into_dyn();
5042                    auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn);
5043                    {close_popup};
5044                    auto id = {window}.template show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self, &menu_wrapper](auto popup_menu) {{
5045                        auto parent_weak = self->self_weak;
5046                        auto self_ = self;
5047                        auto self = popup_menu;
5048                        slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
5049                        {access_close}.set_handler([parent_weak,self = self_] {{ if(auto lock = parent_weak.lock()) {{ {close_popup}; }} }});
5050                    }}, menu_wrapper);
5051                    {set_id};
5052                }}", globals = ctx.generator_state.global_access)
5053            } else {
5054                // ShowPopupMenuInternal
5055                let forward_callback = |access, cb, default| {
5056                    let call = context_menu.map_or_default(|context_menu| format!("{context_menu}.{cb}.call(entry)"));
5057                    format!("{access}.set_handler(
5058                        [parent_weak,self = self_](const auto &entry) {{
5059                            if(auto lock = parent_weak.lock()) {{
5060                                return {call};
5061                            }} else {{
5062                                return {default};
5063                            }}
5064                        }});")
5065                };
5066                let fw_sub_menu = forward_callback(access_sub_menu, "sub_menu", "std::shared_ptr<slint::Model<slint::cbindgen_private::MenuEntry>>()");
5067                let fw_activated = forward_callback(access_activated, "activated", "");
5068                let entries = compile_expression(entries, ctx);
5069                format!(r"
5070                    {close_popup};
5071                    auto id = {window}.template show_popup_menu<{popup_id}>({globals}, {position}, {{ {context_menu_rc} }}, [self](auto popup_menu) {{
5072                        auto parent_weak = self->self_weak;
5073                        auto self_ = self;
5074                        auto entries = {entries};
5075                        auto self = popup_menu;
5076                        {access_entries}.set(std::move(entries));
5077                        {fw_sub_menu}
5078                        {fw_activated}
5079                        {access_close}.set_handler([parent_weak,self = self_] {{ if(auto lock = parent_weak.lock()) {{ {close_popup}; }} }});
5080                    }});
5081                    {set_id};
5082                ", globals = ctx.generator_state.global_access)
5083            }
5084        }
5085        BuiltinFunction::SetSelectionOffsets => {
5086            if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
5087                let item = access_member(pr, ctx);
5088                let item_rc = access_item_rc(pr, ctx);
5089                let window = access_window_field(ctx);
5090                let start = compile_expression(from, ctx);
5091                let end = compile_expression(to, ctx);
5092                item.then(|item| {
5093                    format!("slint_textinput_set_selection_offsets(&{item}, &{window}.handle(), &{item_rc}, static_cast<int>({start}), static_cast<int>({end}))")
5094                })
5095            } else {
5096                panic!("internal error: invalid args to set-selection-offsets {arguments:?}")
5097            }
5098        }
5099        BuiltinFunction::ItemFontMetrics => {
5100            if let [llr::Expression::PropertyReference(pr)] = arguments {
5101                let item_rc = access_item_rc(pr, ctx);
5102                let window = access_window_field(ctx);
5103                format!("slint_cpp_text_item_fontmetrics(&{window}.handle(), &{item_rc})")
5104            } else {
5105                panic!("internal error: invalid args to ItemFontMetrics {arguments:?}")
5106            }
5107        }
5108        BuiltinFunction::ItemAbsolutePosition => {
5109            if let [llr::Expression::PropertyReference(pr)] = arguments {
5110                let item_rc = access_item_rc(pr, ctx);
5111                format!("slint::LogicalPosition(slint::cbindgen_private::slint_item_absolute_position(&{item_rc}))")
5112            } else {
5113                panic!("internal error: invalid args to ItemAbsolutePosition {arguments:?}")
5114            }
5115        }
5116        BuiltinFunction::RegisterCustomFontByPath => {
5117            if let [llr::Expression::StringLiteral(path)] = arguments {
5118                let window = access_window_field(ctx);
5119                format!("{window}.register_font_from_path(\"{}\");", escape_string(path))
5120            } else {
5121                panic!(
5122                    "internal error: argument to RegisterCustomFontByPath must be a string literal"
5123                )
5124            }
5125        }
5126        BuiltinFunction::RegisterCustomFontByMemory => {
5127            if let [llr::Expression::NumberLiteral(resource_id)] = &arguments {
5128                let window = access_window_field(ctx);
5129                let resource_id: usize = *resource_id as _;
5130                let symbol = format!("slint_embedded_resource_{resource_id}");
5131                format!("{window}.register_font_from_data({symbol}, std::size({symbol}));")
5132            } else {
5133                panic!("internal error: invalid args to RegisterCustomFontByMemory {arguments:?}")
5134            }
5135        }
5136        BuiltinFunction::RegisterBitmapFont => {
5137            if let [llr::Expression::NumberLiteral(resource_id)] = &arguments {
5138                let window = access_window_field(ctx);
5139                let resource_id: usize = *resource_id as _;
5140                let symbol = format!("slint_embedded_resource_{resource_id}");
5141                format!("{window}.register_bitmap_font({symbol});")
5142            } else {
5143                panic!("internal error: invalid args to RegisterBitmapFont {arguments:?}")
5144            }
5145        }
5146        BuiltinFunction::ImplicitLayoutInfo(orient) => {
5147            if let [llr::Expression::PropertyReference(pr), constraint_expr] = arguments {
5148                let native = native_prop_info(pr, ctx).0;
5149                let item_rc = access_item_rc(pr, ctx);
5150                let constraint = compile_expression(constraint_expr, ctx);
5151                access_member(pr, ctx).then(|item|
5152                format!(
5153                    "slint::private_api::item_layout_info({vt}, const_cast<slint::cbindgen_private::{ty}*>(&{item}), {o}, {constraint}, &{window}, {item_rc})",
5154                    vt = native.cpp_vtable_getter,
5155                    ty = native.class_name,
5156                    o = to_cpp_orientation(orient),
5157                    window = access_window_field(ctx),
5158                ))
5159            } else {
5160                panic!("internal error: invalid args to ImplicitLayoutInfo {arguments:?}")
5161            }
5162        }
5163        BuiltinFunction::Translate => {
5164            format!("slint::private_api::translate({})", a.join(","))
5165        }
5166        BuiltinFunction::UpdateTimers => {
5167            "self->update_timers()".into()
5168        }
5169        BuiltinFunction::DetectOperatingSystem => {
5170            "slint::cbindgen_private::slint_detect_operating_system()".to_string()
5171        }
5172        // start and stop are unreachable because they are lowered to simple assignment of running
5173        BuiltinFunction::StartTimer => unreachable!(),
5174        BuiltinFunction::StopTimer => unreachable!(),
5175        BuiltinFunction::RestartTimer => {
5176            if let [llr::Expression::NumberLiteral(timer_index)] = arguments {
5177                format!("const_cast<slint::Timer&>(self->timer{}).restart()", timer_index)
5178            } else {
5179                panic!("internal error: invalid args to RestartTimer {arguments:?}")
5180            }
5181        }
5182        BuiltinFunction::OpenUrl => {
5183            let url = a.next().unwrap();
5184            let window = access_window_field(ctx);
5185            format!("slint::private_api::open_url({url}, {window})")
5186        }
5187        BuiltinFunction::MacosBringAllWindowsToFront => {
5188            "slint::private_api::macos_bring_all_windows_to_front()".to_owned()
5189        }
5190        BuiltinFunction::ParseMarkdown => {
5191            let format_string = a.next().unwrap();
5192            let args = a.next().unwrap();
5193            format!("slint::private_api::parse_markdown({}, {})", format_string, args)
5194        }
5195        BuiltinFunction::StringToStyledText => {
5196            let string = a.next().unwrap();
5197            format!("slint::private_api::string_to_styled_text({})", string)
5198        }
5199        BuiltinFunction::ColorToStyledText => {
5200            let color = a.next().unwrap();
5201            format!("slint::private_api::color_to_styled_text({})", color)
5202        }
5203    }
5204}
5205
5206/// Builds the C++ snippet that, for each inner repeater in `templates`, calls
5207/// `ensure_updated` on the sub-component and updates `max_total`.
5208fn build_inner_ensure_code(templates: &[llr::RowChildTemplateInfo], static_count: usize) -> String {
5209    templates
5210        .iter()
5211        .filter_map(|e| match e {
5212            llr::RowChildTemplateInfo::Repeated { repeater_index } => {
5213                let inner_rep_id = format!("repeater_{}", usize::from(*repeater_index));
5214                Some(format!(
5215                    "sub_comp->{inner_rep_id}.track_instance_changes();\n\
5216                     max_total = std::max(max_total, {static_count} + sub_comp->{inner_rep_id}.len());\n"
5217                ))
5218            }
5219            _ => None,
5220        })
5221        .collect()
5222}
5223
5224fn generate_repeater_loop_code(
5225    repeater_index: llr::RepeatedElementIdx,
5226    row_child_templates: &Option<Vec<llr::RowChildTemplateInfo>>,
5227    repeater_steps_var_name: &Option<SmolStr>,
5228    repeater_idx: usize,
5229    dynamic_stride_var_name: &str,
5230    dynamic_loop_code: impl FnOnce(String, usize, String, String) -> String,
5231    static_loop_code: impl FnOnce(String, usize, bool, String) -> String,
5232) -> String {
5233    let repeater_id = format!("repeater_{}", usize::from(repeater_index));
5234    if llr::has_inner_repeaters(row_child_templates) {
5235        let templates = row_child_templates.as_ref().unwrap();
5236        let static_count = llr::static_child_count(templates);
5237        let inner_ensure = build_inner_ensure_code(templates, static_count);
5238        let rs_init = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
5239            format!("{rs}_array[{repeater_idx}] = {dynamic_stride_var_name};")
5240        });
5241        dynamic_loop_code(repeater_id, static_count, inner_ensure, rs_init)
5242    } else {
5243        let step = row_child_templates.as_deref().map_or(1, |t| t.len());
5244        let rs_init = repeater_steps_var_name
5245            .as_ref()
5246            .map_or(String::new(), |rs| format!("{rs}_array[{repeater_idx}] = {step};"));
5247        static_loop_code(repeater_id, step, row_child_templates.is_none(), rs_init)
5248    }
5249}
5250
5251fn generate_with_layout_item_info(
5252    cells_variable: &str,
5253    repeated_indices_var_name: Option<&str>,
5254    repeater_steps_var_name: Option<&str>,
5255    elements: &[Either<llr::Expression, llr::LayoutRepeatedElement>],
5256    orientation: Orientation,
5257    sub_expression: &llr::Expression,
5258    ctx: &llr_EvaluationContext<CppGeneratorContext>,
5259) -> String {
5260    let repeated_indices_var_name = repeated_indices_var_name.map(ident);
5261    let repeater_steps_var_name = repeater_steps_var_name.map(ident);
5262    let mut push_code =
5263        "std::vector<slint::cbindgen_private::LayoutItemInfo> cells_vector;".to_owned();
5264    let mut repeater_idx = 0usize;
5265
5266    for item in elements {
5267        match item {
5268            Either::Left(value) => {
5269                write!(
5270                    push_code,
5271                    "cells_vector.push_back({{ {} }});",
5272                    compile_expression(value, ctx)
5273                )
5274                .unwrap();
5275            }
5276            Either::Right(repeater) => {
5277                let repeater_index = usize::from(repeater.repeater_index);
5278                write!(push_code, "self->repeater_{repeater_index}.track_instance_changes();")
5279                    .unwrap();
5280
5281                if let Some(ri) = &repeated_indices_var_name {
5282                    write!(
5283                        push_code,
5284                        "{ri}_array[{c}] = cells_vector.size();",
5285                        c = repeater_idx * 2
5286                    )
5287                    .unwrap();
5288                    write!(
5289                        push_code,
5290                        "{ri}_array[{c}] = self->repeater_{repeater_index}.len();",
5291                        c = repeater_idx * 2 + 1,
5292                    )
5293                    .unwrap();
5294                }
5295                let repeater_loop_code = generate_repeater_loop_code(
5296                    repeater.repeater_index,
5297                    &repeater.row_child_templates,
5298                    &repeater_steps_var_name,
5299                    repeater_idx,
5300                    "max_total",
5301                    |repeater_id, static_count, inner_ensure, rs_init| {
5302                        // for_each only visits instantiated slots; pad the cells up to len()
5303                        // afterwards so the cell count matches the repeater length recorded in
5304                        // the repeater_indices array (not-yet-instantiated rows get placeholders).
5305                        format!(
5306                            "{{
5307                                size_t max_total = {static_count};
5308                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
5309                                    {inner_ensure}
5310                                }});
5311                                {rs_init}
5312                                auto start_offset = cells_vector.size();
5313                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
5314                                    for (size_t child_idx = 0; child_idx < max_total; ++child_idx) {{
5315                                        cells_vector.push_back(sub_comp->layout_item_info({o}, child_idx));
5316                                    }}
5317                                }});
5318                                cells_vector.resize(start_offset + self->{repeater_id}.len() * max_total);
5319                            }}",
5320                            o = to_cpp_orientation(orientation),
5321                        )
5322                    },
5323                    |repeater_id, step, is_column_repeater, rs_init| {
5324                        if step == 0 {
5325                            rs_init
5326                        } else if step == 1 && is_column_repeater {
5327                            // Column-repeater: each sub-component IS a cell; nullopt returns its own layout_info
5328                            format!(
5329                                "{rs_init}{{
5330                                    auto start_offset = cells_vector.size();
5331                                    self->{repeater_id}.for_each([&](const auto &sub_comp){{ cells_vector.push_back(sub_comp->layout_item_info({o}, std::nullopt)); }});
5332                                    cells_vector.resize(start_offset + self->{repeater_id}.len());
5333                                }}",
5334                                o = to_cpp_orientation(orientation),
5335                            )
5336                        } else {
5337                            format!(
5338                                "{rs_init}{{
5339                                    auto start_offset = cells_vector.size();
5340                                    self->{repeater_id}.for_each([&](const auto &sub_comp){{
5341                                        for (size_t child_idx = 0; child_idx < {step}; ++child_idx) {{
5342                                            cells_vector.push_back(sub_comp->layout_item_info({o}, child_idx));
5343                                        }}
5344                                    }});
5345                                    cells_vector.resize(start_offset + self->{repeater_id}.len() * {step});
5346                                }}",
5347                                o = to_cpp_orientation(orientation),
5348                            )
5349                        }
5350                    },
5351                );
5352                push_code.push_str(&repeater_loop_code);
5353                repeater_idx += 1;
5354            }
5355        }
5356    }
5357
5358    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
5359        write!(
5360            push_code,
5361            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
5362        )
5363        .unwrap();
5364        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
5365    });
5366    let rs = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
5367        write!(
5368            push_code,
5369            "slint::cbindgen_private::Slice<int> {rs} = slint::private_api::make_slice(std::span({rs}_array));"
5370        )
5371        .unwrap();
5372        format!("std::array<int, {}> {rs}_array;", repeater_idx)
5373    });
5374    format!(
5375        "[&]{{ {ri} {rs} {push_code} slint::cbindgen_private::Slice<slint::cbindgen_private::LayoutItemInfo>{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()",
5376        ident(cells_variable),
5377        compile_expression(sub_expression, ctx)
5378    )
5379}
5380
5381fn generate_with_flexbox_layout_item_info(
5382    cells_h_variable: &str,
5383    cells_v_variable: &str,
5384    repeated_indices_var_name: Option<&str>,
5385    elements: &[Either<(llr::Expression, llr::Expression), llr::LayoutRepeatedElement>],
5386    sub_expression: &llr::Expression,
5387    ctx: &llr_EvaluationContext<CppGeneratorContext>,
5388) -> String {
5389    let repeated_indices_var_name = repeated_indices_var_name.map(ident);
5390    let mut push_code =
5391        "std::vector<slint::cbindgen_private::FlexboxLayoutItemInfo> cells_vector_h; std::vector<slint::cbindgen_private::FlexboxLayoutItemInfo> cells_vector_v;".to_owned();
5392    let mut repeater_idx = 0usize;
5393
5394    for item in elements {
5395        match item {
5396            Either::Left((value_h, value_v)) => {
5397                write!(
5398                    push_code,
5399                    "cells_vector_h.push_back({{ {} }}); cells_vector_v.push_back({{ {} }});",
5400                    compile_expression(value_h, ctx),
5401                    compile_expression(value_v, ctx)
5402                )
5403                .unwrap();
5404            }
5405            Either::Right(repeater) => {
5406                let repeater_index = usize::from(repeater.repeater_index);
5407                write!(push_code, "self->repeater_{repeater_index}.track_instance_changes();")
5408                    .unwrap();
5409
5410                if let Some(ri) = &repeated_indices_var_name {
5411                    write!(
5412                        push_code,
5413                        "{ri}_array[{c}] = cells_vector_h.size();",
5414                        c = repeater_idx * 2
5415                    )
5416                    .unwrap();
5417                    write!(
5418                        push_code,
5419                        "{ri}_array[{c}] = self->repeater_{repeater_index}.len();",
5420                        c = repeater_idx * 2 + 1,
5421                    )
5422                    .unwrap();
5423                }
5424                repeater_idx += 1;
5425                // for_each only visits instantiated slots; pad the cells up to len() afterwards so
5426                // the cell count matches the repeater length recorded in the repeater_indices array
5427                // (not-yet-instantiated rows get placeholders).
5428                write!(
5429                    push_code,
5430                    "{{ \
5431                     auto start_offset = cells_vector_h.size(); \
5432                     self->repeater_{repeater_index}.for_each([&](const auto &sub_comp){{ \
5433                     cells_vector_h.push_back(sub_comp->flexbox_layout_item_info(slint::cbindgen_private::Orientation::Horizontal, std::nullopt)); \
5434                     cells_vector_v.push_back(sub_comp->flexbox_layout_item_info(slint::cbindgen_private::Orientation::Vertical, std::nullopt)); }}); \
5435                     auto repeater_len = self->repeater_{repeater_index}.len(); \
5436                     cells_vector_h.resize(start_offset + repeater_len); \
5437                     cells_vector_v.resize(start_offset + repeater_len); }}"
5438                )
5439                .unwrap();
5440            }
5441        }
5442    }
5443
5444    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
5445        write!(
5446            push_code,
5447            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
5448        )
5449        .unwrap();
5450        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
5451    });
5452    format!(
5453        "[&]{{ {ri} {push_code} [[maybe_unused]] slint::cbindgen_private::Slice<slint::cbindgen_private::FlexboxLayoutItemInfo>{cells_h} = slint::private_api::make_slice(std::span(cells_vector_h)); [[maybe_unused]] slint::cbindgen_private::Slice<slint::cbindgen_private::FlexboxLayoutItemInfo>{cells_v} = slint::private_api::make_slice(std::span(cells_vector_v)); return {}; }}()",
5454        compile_expression(sub_expression, ctx),
5455        cells_h = ident(cells_h_variable),
5456        cells_v = ident(cells_v_variable),
5457    )
5458}
5459
5460fn generate_with_grid_input_data(
5461    cells_variable: &str,
5462    repeated_indices_var_name: &SmolStr,
5463    repeater_steps_var_name: &SmolStr,
5464    elements: &[Either<llr::Expression, llr::GridLayoutRepeatedElement>],
5465    sub_expression: &llr::Expression,
5466    ctx: &llr_EvaluationContext<CppGeneratorContext>,
5467) -> String {
5468    let repeated_indices_var_name = Some(ident(repeated_indices_var_name));
5469    let repeater_steps_var_name = Some(ident(repeater_steps_var_name));
5470    let mut push_code =
5471        "std::vector<slint::cbindgen_private::GridLayoutInputData> cells_vector;".to_owned();
5472    let mut repeater_idx = 0usize;
5473    let mut has_new_row_bool = false;
5474
5475    for item in elements {
5476        match item {
5477            Either::Left(value) => {
5478                write!(
5479                    push_code,
5480                    "cells_vector.push_back({{ {} }});",
5481                    compile_expression(value, ctx)
5482                )
5483                .unwrap();
5484            }
5485            Either::Right(repeater) => {
5486                let repeater_id = format!("repeater_{}", usize::from(repeater.repeater_index));
5487                write!(push_code, "self->{repeater_id}.track_instance_changes();").unwrap();
5488
5489                if let Some(ri) = &repeated_indices_var_name {
5490                    write!(push_code, "{ri}_array[{}] = cells_vector.size();", repeater_idx * 2)
5491                        .unwrap();
5492                    write!(
5493                        push_code,
5494                        "{ri}_array[{c}] = self->{repeater_id}.len();",
5495                        c = repeater_idx * 2 + 1,
5496                    )
5497                    .unwrap();
5498                }
5499                let maybe_bool = if has_new_row_bool { "" } else { "bool " };
5500                let repeater_loop_code = generate_repeater_loop_code(
5501                    repeater.repeater_index,
5502                    &repeater.row_child_templates,
5503                    &repeater_steps_var_name,
5504                    repeater_idx,
5505                    "total_item_count",
5506                    |repeater_id, static_count, inner_ensure, rs_init| {
5507                        format!(
5508                            "{maybe_bool} new_row = {new_row};
5509                            {{
5510                                size_t max_total = {static_count};
5511                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
5512                                    {inner_ensure}
5513                                }});
5514                                size_t total_item_count = max_total;
5515                                {rs_init}
5516                                auto start_offset = cells_vector.size();
5517                                cells_vector.resize(start_offset + self->{repeater_id}.len() * total_item_count);
5518                                std::size_t i = 0;
5519                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
5520                                    auto offset = start_offset + i * total_item_count;
5521                                    sub_comp->grid_layout_input_for_repeated(new_row, std::span(cells_vector).subspan(offset, total_item_count));
5522                                    ++i;
5523                                }});
5524                            }}",
5525                            new_row = repeater.new_row,
5526                        )
5527                    },
5528                    |repeater_id, step, is_column_repeater, rs_init| {
5529                        let reset_new_row =
5530                            if is_column_repeater { "new_row = false;" } else { "" };
5531                        format!(
5532                            "{rs_init}{maybe_bool} new_row = {new_row};
5533                            {{
5534                                auto start_offset = cells_vector.size();
5535                                cells_vector.resize(start_offset + self->{repeater_id}.len() * {step});
5536                                std::size_t i = 0;
5537                                self->{repeater_id}.for_each([&](const auto &sub_comp) {{
5538                                    auto offset = start_offset + i * {step};
5539                                    sub_comp->grid_layout_input_for_repeated(new_row, std::span(cells_vector).subspan(offset, {step}));
5540                                    {reset_new_row}
5541                                    ++i;
5542                                }});
5543                            }}",
5544                            new_row = repeater.new_row,
5545                        )
5546                    },
5547                );
5548                push_code.push_str(&repeater_loop_code);
5549                repeater_idx += 1;
5550                has_new_row_bool = true;
5551            }
5552        }
5553    }
5554
5555    let ri = repeated_indices_var_name.as_ref().map_or(String::new(), |ri| {
5556        write!(
5557            push_code,
5558            "slint::cbindgen_private::Slice<int> {ri} = slint::private_api::make_slice(std::span({ri}_array));"
5559        )
5560        .unwrap();
5561        format!("std::array<int, {}> {ri}_array;", 2 * repeater_idx)
5562    });
5563    let rs = repeater_steps_var_name.as_ref().map_or(String::new(), |rs| {
5564        write!(
5565            push_code,
5566            "slint::cbindgen_private::Slice<int> {rs} = slint::private_api::make_slice(std::span({rs}_array));"
5567        )
5568        .unwrap();
5569        format!("std::array<int, {}> {rs}_array;", repeater_idx)
5570    });
5571    format!(
5572        "[&]{{ {ri} {rs} {push_code} slint::cbindgen_private::Slice<slint::cbindgen_private::GridLayoutInputData>{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()",
5573        ident(cells_variable),
5574        compile_expression(sub_expression, ctx)
5575    )
5576}
5577
5578/// Like compile expression, but prepended with `return` if not void.
5579/// ret_type is the expecting type that should be returned with that return statement
5580fn return_compile_expression(
5581    expr: &llr::Expression,
5582    ctx: &EvaluationContext,
5583    ret_type: Option<&Type>,
5584) -> String {
5585    let e = compile_expression(expr, ctx);
5586    if ret_type == Some(&Type::Void) || ret_type == Some(&Type::Invalid) {
5587        e
5588    } else {
5589        let ty = expr.ty(ctx);
5590        if ty == Type::Invalid && ret_type.is_some() {
5591            // e is unreachable so it probably throws. But we still need to return something to avoid a warning
5592            format!("{e}; return {{}}")
5593        } else if ty == Type::Invalid || ty == Type::Void {
5594            e
5595        } else {
5596            format!("return {e}")
5597        }
5598    }
5599}
5600
5601pub fn generate_type_aliases(file: &mut File, doc: &Document) {
5602    let type_aliases = doc
5603        .exports
5604        .iter()
5605        .filter_map(|export| match &export.1 {
5606            Either::Left(component) if !component.is_global() => {
5607                Some((&export.0.name, component.id.clone()))
5608            }
5609            Either::Right(ty) => match &ty {
5610                Type::Struct(s) if s.node().is_some() => {
5611                    Some((&export.0.name, s.name.cpp_type().unwrap()))
5612                }
5613                Type::Enumeration(en) => Some((&export.0.name, en.name.clone())),
5614                _ => None,
5615            },
5616            _ => None,
5617        })
5618        .filter(|(export_name, type_name)| *export_name != type_name)
5619        .map(|(export_name, type_name)| {
5620            Declaration::TypeAlias(TypeAlias {
5621                old_name: ident(&type_name),
5622                new_name: ident(export_name),
5623            })
5624        });
5625
5626    file.declarations.extend(type_aliases);
5627}
5628
5629#[cfg(feature = "bundle-translations")]
5630fn generate_translation(
5631    translations: &crate::translations::Translations,
5632    compilation_unit: &llr::CompilationUnit,
5633    declarations: &mut Vec<Declaration>,
5634) {
5635    for (idx, m) in translations.strings.iter().enumerate() {
5636        declarations.push(Declaration::Var(Var {
5637            ty: "const char8_t* const".into(),
5638            name: format_smolstr!("slint_translation_bundle_{idx}"),
5639            array_size: Some(m.len()),
5640            init: Some(format!(
5641                "{{ {} }}",
5642                m.iter()
5643                    .map(|s| match s {
5644                        Some(s) => format_smolstr!("u8\"{}\"", escape_string(s.as_str())),
5645                        None => "nullptr".into(),
5646                    })
5647                    .join(", ")
5648            )),
5649            ..Default::default()
5650        }));
5651    }
5652    declarations.push(Declaration::Var(Var {
5653        ty: "uint32_t".into(),
5654        name: "slint_translation_bundle_decimal_separators".into(),
5655        array_size: Some(translations.languages.len()),
5656        init: Some(format!(
5657            "{{ {} }}",
5658            translations
5659                .languages
5660                .iter()
5661                .map(|(_, s)| format_smolstr!("{}", *s as u32),)
5662                .join(", ")
5663        )),
5664        ..Default::default()
5665    }));
5666    for (idx, ms) in translations.plurals.iter().enumerate() {
5667        let all_strs = ms.iter().flatten().flatten();
5668        let all_strs_len = all_strs.clone().count();
5669        declarations.push(Declaration::Var(Var {
5670            ty: "const char8_t* const".into(),
5671            name: format_smolstr!("slint_translation_bundle_plural_{}_str", idx),
5672            array_size: Some(all_strs_len),
5673            init: Some(format!(
5674                "{{ {} }}",
5675                all_strs.map(|s| format_smolstr!("u8\"{}\"", escape_string(s.as_str()))).join(", ")
5676            )),
5677            ..Default::default()
5678        }));
5679
5680        let mut count = 0;
5681        declarations.push(Declaration::Var(Var {
5682            ty: "const uint32_t".into(),
5683            name: format_smolstr!("slint_translation_bundle_plural_{}_idx", idx),
5684            array_size: Some(ms.len()),
5685            init: Some(format!(
5686                "{{ {} }}",
5687                ms.iter()
5688                    .map(|x| {
5689                        count += x.as_ref().map_or(0, |x| x.len());
5690                        count
5691                    })
5692                    .join(", ")
5693            )),
5694            ..Default::default()
5695        }));
5696    }
5697
5698    if !translations.plurals.is_empty() {
5699        let ctx = EvaluationContext {
5700            compilation_unit,
5701            current_scope: EvaluationScope::Global(0.into()),
5702            generator_state: CppGeneratorContext {
5703                global_access: "\n#error \"language rule can't access state\";".into(),
5704                conditional_includes: &Default::default(),
5705            },
5706            argument_types: &[Type::Int32],
5707        };
5708
5709        declarations.push(Declaration::Var(Var {
5710            ty: format_smolstr!(
5711                "const std::array<uintptr_t (*const)(int32_t), {}>",
5712                translations.plural_rules.len()
5713            ),
5714            name: "slint_translated_plural_rules".into(),
5715            init: Some(format!(
5716                "{{ {} }}",
5717                translations
5718                    .plural_rules
5719                    .iter()
5720                    .map(|s| match s {
5721                        Some(s) => {
5722                            format!(
5723                                "[]([[maybe_unused]] int32_t arg_0) -> uintptr_t {{ return {}; }}",
5724                                compile_expression(s, &ctx)
5725                            )
5726                        }
5727                        None => "nullptr".into(),
5728                    })
5729                    .join(", ")
5730            )),
5731            ..Default::default()
5732        }));
5733    }
5734}