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