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