Skip to main content

i_slint_compiler/llr/
lower_to_item_tree.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use by_address::ByAddress;
5
6use super::lower_expression::{ExpressionLoweringCtx, ExpressionLoweringCtxInner};
7use crate::CompilerConfiguration;
8use crate::expression_tree::Expression as tree_Expression;
9use crate::langtype::{BuiltinStruct, ElementType, Struct, StructName, Type};
10use crate::llr::item_tree::*;
11use crate::namedreference::NamedReference;
12use crate::object_tree::{self, Component, ElementRc, PropertyAnalysis, PropertyVisibility};
13use smol_str::{SmolStr, format_smolstr};
14use std::collections::{BTreeMap, HashMap};
15use std::rc::Rc;
16use typed_index_collections::TiVec;
17
18pub fn lower_to_item_tree(
19    document: &crate::object_tree::Document,
20    compiler_config: &CompilerConfiguration,
21) -> CompilationUnit {
22    let mut state = LoweringState::default();
23
24    #[cfg(feature = "bundle-translations")]
25    {
26        state.translation_builder = document.translation_builder.clone();
27    }
28
29    let mut globals = TiVec::new();
30    for g in &document.used_types.borrow().globals {
31        let count = globals.next_key();
32        globals.push(lower_global(g, count, &mut state));
33    }
34    for (g, l) in document.used_types.borrow().globals.iter().zip(&mut globals) {
35        lower_global_expressions(g, &mut state, l);
36    }
37
38    for c in &document.used_types.borrow().sub_components {
39        let sc = lower_sub_component(c, &mut state, None, compiler_config);
40        let idx = state.push_sub_component(sc);
41        state.sub_component_mapping.insert(ByAddress(c.clone()), idx);
42    }
43
44    let public_components = document
45        .exported_roots()
46        .map(|component| {
47            let top_level_type = if component.inherits_system_tray_icon() {
48                TopLevelComponentType::SystemTrayIcon
49            } else {
50                TopLevelComponentType::Window
51            };
52            let mut sc = lower_sub_component(&component, &mut state, None, compiler_config);
53            let public_properties = public_properties(&component, &sc.mapping, &state);
54            sc.sub_component.name = component.id.clone();
55            let item_tree = ItemTree {
56                tree: make_tree(&state, &component.root_element, &sc, &[]),
57                root: state.push_sub_component(sc),
58            };
59            // For C++ codegen, the root component must have the same name as the public component
60            PublicComponent {
61                item_tree,
62                public_properties,
63                private_properties: component.private_properties.borrow().clone(),
64                name: component.id.clone(),
65                top_level_type,
66            }
67        })
68        .collect();
69
70    let popup_menu = document.popup_menu_impl.as_ref().map(|c| {
71        let sc = lower_sub_component(c, &mut state, None, compiler_config);
72        let sub_menu = sc.mapping.map_property_reference(
73            &NamedReference::new(&c.root_element, SmolStr::new_static("sub-menu")),
74            &state,
75        );
76        let activated = sc.mapping.map_property_reference(
77            &NamedReference::new(&c.root_element, SmolStr::new_static("activated")),
78            &state,
79        );
80        let close = sc.mapping.map_property_reference(
81            &NamedReference::new(&c.root_element, SmolStr::new_static("close-popup")),
82            &state,
83        );
84        let entries = sc.mapping.map_property_reference(
85            &NamedReference::new(&c.root_element, SmolStr::new_static("entries")),
86            &state,
87        );
88        let item_tree = ItemTree {
89            tree: make_tree(&state, &c.root_element, &sc, &[]),
90            root: state.push_sub_component(sc),
91        };
92        PopupMenu { item_tree, sub_menu, activated, close, entries }
93    });
94
95    let mut root = CompilationUnit {
96        public_components,
97        globals,
98        sub_components: state.sub_components.into_iter().map(|sc| sc.sub_component).collect(),
99        used_sub_components: document
100            .used_types
101            .borrow()
102            .sub_components
103            .iter()
104            .map(|tree_sub_compo| state.sub_component_mapping[&ByAddress(tree_sub_compo.clone())])
105            .collect(),
106        has_debug_info: compiler_config.debug_info,
107        popup_menu,
108        #[cfg(feature = "bundle-translations")]
109        translations: state.translation_builder.map(|x| x.result()),
110    };
111    super::optim_passes::run_passes(&mut root);
112    root
113}
114
115#[derive(Debug, Clone)]
116pub enum LoweredElement {
117    SubComponent { sub_component_index: SubComponentInstanceIdx },
118    NativeItem { item_index: ItemInstanceIdx },
119    Repeated { repeated_index: RepeatedElementIdx },
120    ComponentPlaceholder { repeated_index: u32 },
121}
122
123#[derive(Default, Debug, Clone)]
124pub struct LoweredSubComponentMapping {
125    pub element_mapping: HashMap<ByAddress<ElementRc>, LoweredElement>,
126    pub property_mapping: HashMap<NamedReference, MemberReference>,
127    pub repeater_count: u32,
128    pub container_count: u32,
129}
130
131impl LoweredSubComponentMapping {
132    pub fn map_property_reference(
133        &self,
134        from: &NamedReference,
135        state: &LoweringState,
136    ) -> MemberReference {
137        if let Some(x) = self.property_mapping.get(from) {
138            return x.clone();
139        }
140        if let Some(x) = state.global_properties.get(from) {
141            return x.clone();
142        }
143        let element = from.element();
144        if let Some(alias) = element
145            .borrow()
146            .property_declarations
147            .get(from.name())
148            .and_then(|x| x.is_alias.as_ref())
149        {
150            return self.map_property_reference(alias, state);
151        }
152        match self.element_mapping.get(&element.clone().into()).unwrap() {
153            LoweredElement::SubComponent { sub_component_index } => {
154                if let ElementType::Component(base) = &element.borrow().base_type {
155                    let mut prop_ref = state.map_property_reference(&NamedReference::new(
156                        &base.root_element,
157                        from.name().clone(),
158                    ));
159                    if let MemberReference::Relative { parent_level, local_reference } =
160                        &mut prop_ref
161                    {
162                        assert_eq!(*parent_level, 0, "the sub-component had no parents");
163                        local_reference.sub_component_path.insert(0, *sub_component_index);
164                    }
165                    return prop_ref;
166                }
167                unreachable!()
168            }
169            LoweredElement::NativeItem { item_index } => MemberReference::Relative {
170                parent_level: 0,
171                local_reference: LocalMemberReference {
172                    sub_component_path: Vec::new(),
173                    reference: LocalMemberIndex::Native {
174                        item_index: *item_index,
175                        prop_name: from.name().clone(),
176                    },
177                },
178            },
179            LoweredElement::Repeated { .. } => {
180                panic!(
181                    "Trying to map property {from:?} on a repeated element {} of type {:?}",
182                    element.borrow().id,
183                    element.borrow().base_type
184                );
185            }
186            LoweredElement::ComponentPlaceholder { .. } => unreachable!(),
187        }
188    }
189}
190
191pub struct LoweredSubComponent {
192    sub_component: SubComponent,
193    mapping: LoweredSubComponentMapping,
194}
195
196#[derive(Default)]
197pub struct LoweringState {
198    global_properties: HashMap<NamedReference, MemberReference>,
199    sub_components: TiVec<SubComponentIdx, LoweredSubComponent>,
200    sub_component_mapping: HashMap<ByAddress<Rc<Component>>, SubComponentIdx>,
201    #[cfg(feature = "bundle-translations")]
202    pub translation_builder: Option<crate::translations::TranslationsBuilder>,
203}
204
205impl LoweringState {
206    pub fn map_property_reference(&self, from: &NamedReference) -> MemberReference {
207        if let Some(x) = self.global_properties.get(from) {
208            return x.clone();
209        }
210
211        let element = from.element();
212        let sc = self.sub_component(&element.borrow().enclosing_component.upgrade().unwrap());
213        sc.mapping.map_property_reference(from, self)
214    }
215
216    fn sub_component<'a>(&'a self, component: &Rc<Component>) -> &'a LoweredSubComponent {
217        &self.sub_components[self.sub_component_idx(component)]
218    }
219
220    /// Returns the `row_child_templates` from an already-lowered sub-component.
221    /// Used by the parent's layout expression lowering to read template info
222    /// from a repeated Row that was lowered earlier.
223    pub fn row_child_templates(
224        &self,
225        component: &Rc<Component>,
226    ) -> Option<Vec<super::RowChildTemplateInfo>> {
227        self.sub_components[self.sub_component_idx(component)]
228            .sub_component
229            .row_child_templates
230            .clone()
231    }
232
233    fn sub_component_idx(&self, component: &Rc<Component>) -> SubComponentIdx {
234        *self.sub_component_mapping.get(&ByAddress(component.clone())).unwrap_or_else(|| {
235            debug_assert!(
236                false,
237                "no entry found for key: component id='{}', available keys: {:?}",
238                component.id,
239                self.sub_component_mapping.keys().map(|k| k.0.id.clone()).collect::<Vec<_>>()
240            );
241            unreachable!(
242                "component must be registered before querying sub_component_idx: '{}'",
243                component.id
244            )
245        })
246    }
247
248    fn push_sub_component(&mut self, sc: LoweredSubComponent) -> SubComponentIdx {
249        self.sub_components.push_and_get_key(sc)
250    }
251}
252
253fn component_id(component: &Rc<Component>) -> SmolStr {
254    if component.is_global() {
255        component.root_element.borrow().id.clone()
256    } else if component.from_library.get() {
257        component.id.clone()
258    } else if component.id.is_empty() {
259        format_smolstr!("Component_{}", component.root_element.borrow().id)
260    } else {
261        format_smolstr!("{}_{}", component.id, component.root_element.borrow().id)
262    }
263}
264
265fn lower_sub_component(
266    component: &Rc<Component>,
267    state: &mut LoweringState,
268    parent_context: Option<&ExpressionLoweringCtxInner>,
269    compiler_config: &CompilerConfiguration,
270) -> LoweredSubComponent {
271    let mut sub_component = SubComponent {
272        name: component_id(component),
273        properties: Default::default(),
274        callbacks: Default::default(),
275        functions: Default::default(),
276        items: Default::default(),
277        repeated: Default::default(),
278        component_containers: Default::default(),
279        popup_windows: Default::default(),
280        menu_item_trees: Vec::new(),
281        timers: Default::default(),
282        sub_components: Default::default(),
283        property_init: Default::default(),
284        change_callbacks: Default::default(),
285        animations: Default::default(),
286        two_way_bindings: Default::default(),
287        const_properties: Default::default(),
288        pre_init_code: Default::default(),
289        init_code: Default::default(),
290        geometries: Default::default(),
291        // just initialize to dummy expression right now and it will be set later
292        layout_info_h: super::Expression::BoolLiteral(false).into(),
293        layout_info_v: super::Expression::BoolLiteral(false).into(),
294        child_of_layout: component.root_element.borrow().child_of_layout,
295        grid_layout_input_for_repeated: None,
296        flexbox_layout_item_info_for_repeated: None,
297        is_repeated_row: component
298            .root_element
299            .borrow()
300            .grid_layout_cell
301            .as_ref()
302            .is_some_and(|c| c.borrow().child_items.is_some()),
303        grid_layout_children: Default::default(),
304        row_child_templates: None,
305        accessible_prop: Default::default(),
306        element_infos: Default::default(),
307        prop_analysis: Default::default(),
308    };
309    let mut mapping = LoweredSubComponentMapping::default();
310    let mut repeated = TiVec::new();
311    let mut accessible_prop = Vec::new();
312    let mut change_callbacks = Vec::new();
313
314    if let Some(parent) = component.parent_element() {
315        // Add properties for the model data and index
316        if parent.borrow().repeated.as_ref().is_some_and(|x| !x.is_conditional_element) {
317            sub_component.properties.push(Property {
318                name: "model_data".into(),
319                ty: crate::expression_tree::Expression::RepeaterModelReference {
320                    element: component.parent_element.borrow().clone(),
321                }
322                .ty(),
323                ..Property::default()
324            });
325            sub_component.properties.push(Property {
326                name: "model_index".into(),
327                ty: Type::Int32,
328                ..Property::default()
329            });
330        }
331    };
332
333    let s: Option<ElementRc> = None;
334    let mut repeater_offset = 0;
335    crate::object_tree::recurse_elem(&component.root_element, &s, &mut |element, parent| {
336        let elem = element.borrow();
337        for (p, x) in &elem.property_declarations {
338            if x.is_alias.is_some() {
339                continue;
340            }
341            let reference = if let Type::Function(function) = &x.property_type {
342                // TODO: Function could wrap the Rc<langtype::Function>
343                //       instead of cloning the return type and args?
344                let index = sub_component.functions.push_and_get_key(Function {
345                    name: p.clone(),
346                    ret_ty: function.return_type.clone(),
347                    args: function.args.clone(),
348                    // will be replaced later
349                    code: super::Expression::CodeBlock(Vec::new()),
350                });
351                index.into()
352            } else if let Type::Callback(callback) = &x.property_type {
353                let index = sub_component.callbacks.push_and_get_key(Callback {
354                    name: format_smolstr!("{}_{}", elem.id, p),
355                    ret_ty: callback.return_type.clone(),
356                    args: callback.args.clone(),
357                    ty: Type::Callback(callback.clone()),
358                    use_count: 0.into(),
359                    needs_tracker: x.expose_in_public_api,
360                });
361                index.into()
362            } else {
363                let index = sub_component.properties.push_and_get_key(Property {
364                    name: format_smolstr!("{}_{}", elem.id, p),
365                    ty: x.property_type.clone(),
366                    ..Property::default()
367                });
368                index.into()
369            };
370            mapping.property_mapping.insert(
371                NamedReference::new(element, p.clone()),
372                MemberReference::Relative {
373                    parent_level: 0,
374                    local_reference: LocalMemberReference {
375                        sub_component_path: Vec::new(),
376                        reference,
377                    },
378                },
379            );
380        }
381        if elem.repeated.is_some() {
382            let parent = if elem.is_component_placeholder { parent.clone() } else { None };
383
384            mapping.element_mapping.insert(
385                element.clone().into(),
386                LoweredElement::Repeated {
387                    repeated_index: repeated.push_and_get_key((element.clone(), parent)),
388                },
389            );
390            mapping.repeater_count += 1;
391            return None;
392        }
393        match &elem.base_type {
394            ElementType::Component(comp) => {
395                let ty = state.sub_component_idx(comp);
396                let sub_component_index =
397                    sub_component.sub_components.push_and_get_key(SubComponentInstance {
398                        ty,
399                        name: elem.id.clone(),
400                        index_in_tree: *elem.item_index.get().unwrap(),
401                        index_of_first_child_in_tree: *elem
402                            .item_index_of_first_children
403                            .get()
404                            .unwrap(),
405                        repeater_offset,
406                    });
407                mapping.element_mapping.insert(
408                    element.clone().into(),
409                    LoweredElement::SubComponent { sub_component_index },
410                );
411                repeater_offset += comp.repeater_count();
412            }
413
414            ElementType::Native(n) => {
415                let item_index = sub_component.items.push_and_get_key(Item {
416                    ty: n.clone(),
417                    name: elem.id.clone(),
418                    index_in_tree: *elem.item_index.get().unwrap(),
419                });
420                mapping
421                    .element_mapping
422                    .insert(element.clone().into(), LoweredElement::NativeItem { item_index });
423            }
424            _ => unreachable!(),
425        };
426        for (key, nr) in &elem.accessibility_props.0 {
427            // TODO: we also want to split by type (role/string/...)
428            let enum_value =
429                crate::generator::to_pascal_case(key.strip_prefix("accessible-").unwrap());
430            accessible_prop.push((*elem.item_index.get().unwrap(), enum_value, nr.clone()));
431        }
432
433        for (prop, expr) in &elem.change_callbacks {
434            change_callbacks
435                .push((NamedReference::new(element, prop.clone()), expr.borrow().clone()));
436        }
437
438        if compiler_config.debug_info {
439            let element_infos = elem.element_infos();
440            if !element_infos.is_empty() {
441                sub_component.element_infos.insert(*elem.item_index.get().unwrap(), element_infos);
442            }
443        }
444
445        Some(element.clone())
446    });
447
448    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: parent_context, component };
449    let mut ctx = ExpressionLoweringCtx { inner, state };
450
451    // Lower repeated components first, so their sub-components (e.g. Row) are available
452    // when lowering layout expressions that need to read row_child_templates.
453    sub_component.repeated = repeated
454        .into_iter()
455        .map(|(elem, parent)| {
456            lower_repeated_component(&elem, parent, &sub_component, &mut ctx, compiler_config)
457        })
458        .collect();
459    for s in &mut sub_component.sub_components {
460        s.repeater_offset +=
461            (sub_component.repeated.len() + sub_component.component_containers.len()) as u32;
462    }
463
464    crate::generator::handle_property_bindings_init(component, |e, p, binding| {
465        let nr = NamedReference::new(e, p.clone());
466        let prop = ctx.map_property_reference(&nr);
467
468        if let Type::Function { .. } = nr.ty() {
469            let MemberReference::Relative { parent_level, local_reference } = prop else {
470                unreachable!()
471            };
472            assert!(parent_level == 0);
473            assert!(local_reference.sub_component_path.is_empty());
474            let LocalMemberIndex::Function(function_index) = local_reference.reference else {
475                unreachable!()
476            };
477
478            sub_component.functions[function_index].code =
479                super::lower_expression::lower_expression(&binding.expression, &mut ctx);
480
481            return;
482        }
483
484        for tw in &binding.two_way_bindings {
485            sub_component.two_way_bindings.push(match tw {
486                crate::expression_tree::TwoWayBinding::Property { property, field_access } => {
487                    TwoWayBinding {
488                        prop1: prop.local(),
489                        prop2: ctx.map_property_reference(property),
490                        field_access: field_access.clone(),
491                        is_model: None,
492                    }
493                }
494                crate::expression_tree::TwoWayBinding::ModelData {
495                    repeated_element,
496                    field_access,
497                } => TwoWayBinding {
498                    prop1: prop.local(),
499                    prop2: super::lower_expression::repeater_special_property(
500                        repeated_element,
501                        component,
502                        PropertyIdx::REPEATER_DATA,
503                    ),
504                    field_access: field_access.clone(),
505                    is_model: Some(PropertyIdx::REPEATER_INDEX),
506                },
507            });
508        }
509        if !matches!(binding.expression, tree_Expression::Invalid) {
510            let expression =
511                super::lower_expression::lower_expression(&binding.expression, &mut ctx).into();
512
513            let is_constant = binding.analysis.as_ref().is_some_and(|a| a.is_const);
514            let animation = binding
515                .animation
516                .as_ref()
517                .filter(|_| !is_constant)
518                .map(|a| super::lower_expression::lower_animation(a, &mut ctx));
519
520            sub_component.prop_analysis.insert(
521                prop.clone(),
522                PropAnalysis {
523                    property_init: Some(sub_component.property_init.len()),
524                    analysis: get_property_analysis(e, p),
525                },
526            );
527
528            let is_state_info = matches!(
529                e.borrow().lookup_property(p).property_type,
530                Type::Struct(s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo))
531            );
532
533            sub_component.property_init.push((
534                prop.clone(),
535                BindingExpression {
536                    expression,
537                    animation,
538                    is_constant,
539                    is_state_info,
540                    use_count: 0.into(),
541                },
542            ));
543        }
544
545        if e.borrow()
546            .property_analysis
547            .borrow()
548            .get(p)
549            .is_none_or(|a| a.is_set || a.is_set_externally)
550            && let Some(anim) = binding.animation.as_ref()
551        {
552            match super::lower_expression::lower_animation(anim, &mut ctx) {
553                Animation::Static(anim) => {
554                    sub_component.animations.insert(prop.local(), anim);
555                }
556                Animation::Transition(_) => {
557                    // Cannot set a property with a transition anyway
558                }
559            }
560        }
561    });
562
563    sub_component.popup_windows = component
564        .popup_windows
565        .borrow()
566        .iter()
567        .map(|popup| lower_popup_component(popup, &mut ctx, compiler_config))
568        .collect();
569
570    sub_component.menu_item_trees = component
571        .menu_item_tree
572        .borrow()
573        .iter()
574        .map(|c| {
575            let sc = lower_sub_component(c, ctx.state, Some(&ctx.inner), compiler_config);
576            ItemTree {
577                tree: make_tree(ctx.state, &c.root_element, &sc, &[]),
578                root: ctx.state.push_sub_component(sc),
579            }
580        })
581        .collect();
582
583    sub_component.timers = component.timers.borrow().iter().map(|t| lower_timer(t, &ctx)).collect();
584
585    crate::generator::for_each_const_properties(component, |elem, n| {
586        let x = ctx.map_property_reference(&NamedReference::new(elem, n.clone()));
587        // ensure that all const properties have analysis
588        sub_component.prop_analysis.entry(x.clone()).or_insert_with(|| PropAnalysis {
589            property_init: None,
590            analysis: get_property_analysis(elem, n),
591        });
592        sub_component.const_properties.push(x.local());
593    });
594
595    sub_component.pre_init_code = component
596        .init_code
597        .borrow()
598        .font_registration_code
599        .iter()
600        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
601        .collect();
602
603    sub_component.init_code = component
604        .init_code
605        .borrow()
606        .iter_without_font_registration()
607        .map(|e| super::lower_expression::lower_expression(e, &mut ctx).into())
608        .collect();
609
610    sub_component.layout_info_h = super::lower_layout_expression::get_layout_info(
611        &component.root_element,
612        &mut ctx,
613        &component.root_constraints.borrow(),
614        crate::layout::Orientation::Horizontal,
615        None,
616    )
617    .into();
618    // Measure the root's height for its preferred width, not an unbounded one, so a
619    // height-for-width Image doesn't report infinite height (mirrors the interpreter).
620    let v_cross_constraint = component
621        .root_element
622        .borrow()
623        .layout_info_v_with_constraint
624        .is_some()
625        .then(|| {
626            super::lower_layout_expression::default_cross_axis_constraint(&component.root_element)
627        })
628        .flatten();
629    sub_component.layout_info_v = super::lower_layout_expression::get_layout_info(
630        &component.root_element,
631        &mut ctx,
632        &component.root_constraints.borrow(),
633        crate::layout::Orientation::Vertical,
634        v_cross_constraint,
635    )
636    .into();
637    // For repeated elements in a FlexboxLayout, generate code to read flex properties
638    if sub_component.child_of_layout {
639        let root_elem = &component.root_element;
640        let has_flex_binding =
641            ["flex-grow", "flex-shrink", "flex-basis", "flex-align-self", "flex-order"]
642                .iter()
643                .any(|name| crate::layout::binding_reference(root_elem, name).is_some());
644        if has_flex_binding {
645            sub_component.flexbox_layout_item_info_for_repeated = Some(
646                super::lower_layout_expression::get_flexbox_layout_item_info_for_repeated(
647                    &mut ctx, root_elem,
648                )
649                .into(),
650            );
651        }
652    }
653
654    if let Some(grid_layout_cell) = component.root_element.borrow().grid_layout_cell.as_ref() {
655        let grid_cell_ref = grid_layout_cell.borrow();
656        sub_component.grid_layout_input_for_repeated = Some(
657            super::lower_layout_expression::get_grid_layout_input_for_repeated(
658                &mut ctx,
659                &grid_cell_ref,
660            )
661            .into(),
662        );
663
664        // Store constraints for children of the Row
665        if let Some(children_constraints) = grid_cell_ref.child_items.as_ref() {
666            let mut row_child_templates = Vec::new();
667            for child_template in children_constraints.iter() {
668                match child_template {
669                    crate::layout::RowChildTemplate::Static(layout_item) => {
670                        let layout_info_h = super::lower_layout_expression::get_layout_info(
671                            &layout_item.element,
672                            &mut ctx,
673                            &layout_item.constraints,
674                            crate::layout::Orientation::Horizontal,
675                            None,
676                        );
677                        let layout_info_v = super::lower_layout_expression::get_layout_info(
678                            &layout_item.element,
679                            &mut ctx,
680                            &layout_item.constraints,
681                            crate::layout::Orientation::Vertical,
682                            None,
683                        );
684                        let child_index = sub_component.grid_layout_children.push_and_get_key(
685                            super::GridLayoutChildLayoutInfo {
686                                layout_info_h: layout_info_h.into(),
687                                layout_info_v: layout_info_v.into(),
688                            },
689                        );
690                        row_child_templates
691                            .push(super::RowChildTemplateInfo::Static { child_index });
692                    }
693                    crate::layout::RowChildTemplate::Repeated { repeated_element, .. } => {
694                        // Inner repeater: layout_info is computed at runtime per instance.
695                        if let Some(super::lower_to_item_tree::LoweredElement::Repeated {
696                            repeated_index,
697                        }) = mapping.element_mapping.get(&repeated_element.clone().into())
698                        {
699                            row_child_templates.push(super::RowChildTemplateInfo::Repeated {
700                                repeater_index: *repeated_index,
701                            });
702                        }
703                    }
704                }
705            }
706            // Always set row_child_templates (even if empty) to mark this as a Row sub-component.
707            // An empty row_child_templates (Some([])) means 0 cells per sub-component — correct for empty Rows.
708            // Leaving it as None would incorrectly treat it as a column-repeater (1 cell per sub-component).
709            sub_component.row_child_templates = Some(row_child_templates);
710        }
711    }
712
713    sub_component.accessible_prop = accessible_prop
714        .into_iter()
715        .map(|(idx, key, nr)| {
716            let prop = ctx.map_property_reference(&nr);
717            let expr = match nr.ty() {
718                Type::Bool => super::Expression::Condition {
719                    condition: super::Expression::PropertyReference(prop).into(),
720                    true_expr: super::Expression::StringLiteral("true".into()).into(),
721                    false_expr: super::Expression::StringLiteral("false".into()).into(),
722                },
723                Type::Int32 | Type::Float32 => super::Expression::Cast {
724                    from: super::Expression::PropertyReference(prop).into(),
725                    to: Type::String,
726                },
727                Type::String => super::Expression::PropertyReference(prop),
728                Type::Enumeration(ref e) if e.name == "AccessibleRole" => {
729                    super::Expression::PropertyReference(prop)
730                }
731                Type::Enumeration(_) => super::Expression::Cast {
732                    from: super::Expression::PropertyReference(prop).into(),
733                    to: Type::String,
734                },
735                Type::Callback(callback) => super::Expression::CallBackCall {
736                    callback: prop,
737                    arguments: (0..callback.args.len())
738                        .map(|index| super::Expression::FunctionParameterReference { index })
739                        .collect(),
740                },
741                _ => panic!("Invalid type for accessible property"),
742            };
743
744            ((idx, key), expr.into())
745        })
746        .collect();
747
748    sub_component.change_callbacks = change_callbacks
749        .into_iter()
750        .map(|(nr, exprs)| {
751            let prop = ctx.map_property_reference(&nr);
752            let expr = super::lower_expression::lower_expression(
753                &tree_Expression::CodeBlock(exprs),
754                &mut ctx,
755            );
756            (prop, expr.into())
757        })
758        .collect();
759
760    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |element, _| {
761        let elem = element.borrow();
762        if elem.repeated.is_some() {
763            return;
764        };
765        let Some(geom) = &elem.geometry_props else { return };
766        let item_index = *elem.item_index.get().unwrap() as usize;
767        if item_index >= sub_component.geometries.len() {
768            sub_component.geometries.resize(item_index + 1, Default::default());
769        }
770        sub_component.geometries[item_index] = Some(lower_geometry(geom, &ctx).into());
771    });
772
773    LoweredSubComponent { sub_component, mapping }
774}
775
776fn lower_geometry(
777    geom: &crate::object_tree::GeometryProps,
778    ctx: &ExpressionLoweringCtx<'_>,
779) -> super::Expression {
780    let mut fields = BTreeMap::default();
781    let mut values = BTreeMap::default();
782    for (f, v) in [("x", &geom.x), ("y", &geom.y), ("width", &geom.width), ("height", &geom.height)]
783    {
784        fields.insert(f.into(), Type::LogicalLength);
785        values
786            .insert(f.into(), super::Expression::PropertyReference(ctx.map_property_reference(v)));
787    }
788    super::Expression::Struct { ty: Rc::new(Struct { fields, name: StructName::None }), values }
789}
790
791fn get_property_analysis(elem: &ElementRc, p: &str) -> crate::object_tree::PropertyAnalysis {
792    let mut a = elem.borrow().property_analysis.borrow().get(p).cloned().unwrap_or_default();
793    let mut elem = elem.clone();
794    loop {
795        if let Some(d) = elem.borrow().property_declarations.get(p) {
796            if let Some(nr) = &d.is_alias {
797                a.merge(&get_property_analysis(&nr.element(), nr.name()));
798            }
799            return a;
800        }
801        let base = elem.borrow().base_type.clone();
802        match base {
803            ElementType::Native(n) if n.properties.get(p).is_some_and(|p| p.is_native_output()) => {
804                a.is_set = true;
805            }
806            ElementType::Component(c) => {
807                elem = c.root_element.clone();
808                if let Some(a2) = elem.borrow().property_analysis.borrow().get(p) {
809                    a.merge_with_base(a2);
810                }
811                continue;
812            }
813            _ => (),
814        };
815        return a;
816    }
817}
818
819fn lower_repeated_component(
820    elem: &ElementRc,
821    parent_component_container: Option<ElementRc>,
822    sub_component: &SubComponent,
823    ctx: &mut ExpressionLoweringCtx,
824    compiler_config: &CompilerConfiguration,
825) -> RepeatedElement {
826    let e = elem.borrow();
827    let component = e.base_type.as_component().clone();
828    let repeated = e.repeated.as_ref().unwrap();
829
830    let sc = lower_sub_component(&component, ctx.state, Some(&ctx.inner), compiler_config);
831
832    let listview = repeated.is_listview.as_ref().map(|lv| {
833        let geom = component.root_element.borrow().geometry_props.clone().unwrap();
834        ListViewInfo {
835            viewport_y: ctx.map_property_reference(&lv.viewport_y),
836            viewport_height: ctx.map_property_reference(&lv.viewport_height),
837            viewport_width: ctx.map_property_reference(&lv.viewport_width),
838            listview_height: ctx.map_property_reference(&lv.listview_height),
839            listview_width: ctx.map_property_reference(&lv.listview_width),
840            prop_y: sc.mapping.map_property_reference(&geom.y, ctx.state),
841            prop_height: sc.mapping.map_property_reference(&geom.height, ctx.state),
842        }
843    });
844
845    let parent_index = parent_component_container.map(|p| *p.borrow().item_index.get().unwrap());
846    let container_item_index =
847        parent_index.and_then(|pii| sub_component.items.position(|i| i.index_in_tree == pii));
848
849    let tree = make_tree(ctx.state, &component.root_element, &sc, &[]);
850    let root = ctx.state.push_sub_component(sc);
851    // Register the repeated component in the mapping so it can be looked up
852    ctx.state.sub_component_mapping.insert(ByAddress(component.clone()), root);
853
854    RepeatedElement {
855        model: super::lower_expression::lower_expression(&repeated.model, ctx).into(),
856        sub_tree: ItemTree { tree, root },
857        index_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_INDEX),
858        data_prop: (!repeated.is_conditional_element).then_some(PropertyIdx::REPEATER_DATA),
859        index_in_tree: *e.item_index.get().unwrap(),
860        listview,
861        container_item_index,
862    }
863}
864
865fn lower_popup_component(
866    popup: &object_tree::PopupWindow,
867    ctx: &mut ExpressionLoweringCtx,
868    compiler_config: &CompilerConfiguration,
869) -> PopupWindow {
870    let sc = lower_sub_component(&popup.component, ctx.state, Some(&ctx.inner), compiler_config);
871    use super::Expression::PropertyReference as PR;
872    let position = super::lower_expression::make_struct(
873        BuiltinStruct::LogicalPosition,
874        [
875            ("x", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.x, ctx.state))),
876            ("y", Type::LogicalLength, PR(sc.mapping.map_property_reference(&popup.y, ctx.state))),
877        ],
878    );
879
880    let item_tree = ItemTree {
881        tree: make_tree(ctx.state, &popup.component.root_element, &sc, &[]),
882        root: ctx.state.push_sub_component(sc),
883    };
884    PopupWindow { item_tree, position: position.into(), is_tooltip: popup.is_tooltip }
885}
886
887fn lower_timer(timer: &object_tree::Timer, ctx: &ExpressionLoweringCtx) -> Timer {
888    Timer {
889        interval: super::Expression::PropertyReference(ctx.map_property_reference(&timer.interval))
890            .into(),
891        running: super::Expression::PropertyReference(ctx.map_property_reference(&timer.running))
892            .into(),
893        // TODO: this calls a callback instead of inlining the callback code directly
894        triggered: super::Expression::CallBackCall {
895            callback: ctx.map_property_reference(&timer.triggered),
896            arguments: Vec::new(),
897        }
898        .into(),
899    }
900}
901
902/// Lower the globals (but not their expressions as we first need to lower all the global to get proper mapping in the state)
903fn lower_global(
904    global: &Rc<Component>,
905    global_index: GlobalIdx,
906    state: &mut LoweringState,
907) -> GlobalComponent {
908    let mut properties = TiVec::new();
909    let mut callbacks = TiVec::new();
910    let mut const_properties = TiVec::new();
911    let mut prop_analysis = TiVec::new();
912    let mut functions = TiVec::new();
913
914    for (p, x) in &global.root_element.borrow().property_declarations {
915        if x.is_alias.is_some() {
916            continue;
917        }
918        let nr = NamedReference::new(&global.root_element, p.clone());
919
920        if let Type::Function(function) = &x.property_type {
921            // TODO: wrap the Rc<langtype::Function> instead of cloning
922            let function_index: FunctionIdx = functions.push_and_get_key(Function {
923                name: p.clone(),
924                ret_ty: function.return_type.clone(),
925                args: function.args.clone(),
926                // will be replaced later
927                code: super::Expression::CodeBlock(Vec::new()),
928            });
929            state.global_properties.insert(
930                nr.clone(),
931                MemberReference::Global { global_index, member: function_index.into() },
932            );
933            continue;
934        } else if let Type::Callback(cb) = &x.property_type {
935            let callback_index: CallbackIdx = callbacks.push_and_get_key(Callback {
936                name: p.clone(),
937                ret_ty: cb.return_type.clone(),
938                args: cb.args.clone(),
939                ty: x.property_type.clone(),
940                use_count: 0.into(),
941                needs_tracker: x.expose_in_public_api,
942            });
943            state.global_properties.insert(
944                nr.clone(),
945                MemberReference::Global { global_index, member: callback_index.into() },
946            );
947            continue;
948        }
949
950        let property_index: PropertyIdx = properties.push_and_get_key(Property {
951            name: p.clone(),
952            ty: x.property_type.clone(),
953            ..Property::default()
954        });
955
956        const_properties.push(nr.is_constant());
957
958        prop_analysis.push(
959            global
960                .root_element
961                .borrow()
962                .property_analysis
963                .borrow()
964                .get(p)
965                .cloned()
966                .unwrap_or_default(),
967        );
968        state.global_properties.insert(
969            nr.clone(),
970            MemberReference::Global { global_index, member: property_index.into() },
971        );
972    }
973
974    let is_builtin = if let Some(builtin) = global.root_element.borrow().native_class() {
975        // We just generate the property so we know how to address them
976        for (p, x) in &builtin.properties {
977            let property_index = properties.push_and_get_key(Property {
978                name: p.clone(),
979                ty: x.ty.clone(),
980                ..Property::default()
981            });
982            let nr = NamedReference::new(&global.root_element, p.clone());
983            state.global_properties.insert(
984                nr,
985                MemberReference::Global { global_index, member: property_index.into() },
986            );
987            prop_analysis.push(PropertyAnalysis {
988                // Assume that a builtin global property can always be set from the builtin code
989                is_set_externally: true,
990                ..global
991                    .root_element
992                    .borrow()
993                    .property_analysis
994                    .borrow()
995                    .get(p)
996                    .cloned()
997                    .unwrap_or_default()
998            });
999        }
1000        true
1001    } else {
1002        false
1003    };
1004
1005    GlobalComponent {
1006        name: global.root_element.borrow().id.clone(),
1007        init_values: BTreeMap::new(),
1008        properties,
1009        callbacks,
1010        functions,
1011        change_callbacks: BTreeMap::new(),
1012        const_properties,
1013        public_properties: Default::default(),
1014        private_properties: global.private_properties.borrow().clone(),
1015        exported: !global.exported_global_names.borrow().is_empty(),
1016        aliases: global.global_aliases(),
1017        is_builtin,
1018        from_library: global.from_library.get(),
1019        prop_analysis,
1020    }
1021}
1022
1023fn lower_global_expressions(
1024    global: &Rc<Component>,
1025    state: &mut LoweringState,
1026    lowered: &mut GlobalComponent,
1027) {
1028    // Note that this mapping doesn't contain anything useful, everything is in the state
1029    let mapping = LoweredSubComponentMapping::default();
1030    let inner = ExpressionLoweringCtxInner { mapping: &mapping, parent: None, component: global };
1031    let mut ctx = ExpressionLoweringCtx { inner, state };
1032
1033    for (prop, binding) in &global.root_element.borrow().bindings {
1034        assert!(binding.borrow().two_way_bindings.is_empty());
1035        assert!(binding.borrow().animation.is_none());
1036        let expression =
1037            super::lower_expression::lower_expression(&binding.borrow().expression, &mut ctx);
1038
1039        let nr = NamedReference::new(&global.root_element, prop.clone());
1040        let member_index = match &ctx.state.global_properties[&nr] {
1041            MemberReference::Global {
1042                member: LocalMemberIndex::Function(function_index), ..
1043            } => {
1044                lowered.functions[*function_index].code = expression;
1045                continue;
1046            }
1047            MemberReference::Global { member, .. } => member.clone(),
1048            _ => unreachable!(),
1049        };
1050        let is_constant = binding.borrow().analysis.as_ref().is_some_and(|a| a.is_const);
1051        lowered.init_values.insert(
1052            member_index,
1053            BindingExpression {
1054                expression: expression.into(),
1055                animation: None,
1056                is_constant,
1057                is_state_info: false,
1058                use_count: 0.into(),
1059            },
1060        );
1061    }
1062
1063    for (prop, expr) in &global.root_element.borrow().change_callbacks {
1064        let nr = NamedReference::new(&global.root_element, prop.clone());
1065        let MemberReference::Global { member: LocalMemberIndex::Property(property_index), .. } =
1066            ctx.state.global_properties[&nr]
1067        else {
1068            unreachable!()
1069        };
1070        let expression = super::lower_expression::lower_expression(
1071            &tree_Expression::CodeBlock(expr.borrow().clone()),
1072            &mut ctx,
1073        );
1074        lowered.change_callbacks.insert(property_index, expression.into());
1075    }
1076
1077    if let Some(builtin) = global.root_element.borrow().native_class() {
1078        if lowered.exported {
1079            lowered.public_properties = builtin
1080                .properties
1081                .iter()
1082                .map(|(p, c)| {
1083                    let property_reference = mapping.map_property_reference(
1084                        &NamedReference::new(&global.root_element, p.clone()),
1085                        state,
1086                    );
1087                    PublicProperty {
1088                        name: p.clone(),
1089                        ty: c.ty.clone(),
1090                        prop: property_reference,
1091                        read_only: c.property_visibility == PropertyVisibility::Output,
1092                    }
1093                })
1094                .collect()
1095        }
1096    } else {
1097        lowered.public_properties = public_properties(global, &mapping, state);
1098    }
1099}
1100
1101fn make_tree(
1102    state: &LoweringState,
1103    element: &ElementRc,
1104    component: &LoweredSubComponent,
1105    sub_component_path: &[SubComponentInstanceIdx],
1106) -> TreeNode {
1107    let e = element.borrow();
1108    let children = e.children.iter().map(|c| make_tree(state, c, component, sub_component_path));
1109    let repeater_count = component.mapping.repeater_count;
1110    match component.mapping.element_mapping.get(&ByAddress(element.clone())).unwrap() {
1111        LoweredElement::SubComponent { sub_component_index } => {
1112            let sub_component = e.sub_component().unwrap();
1113            let new_sub_component_path = sub_component_path
1114                .iter()
1115                .copied()
1116                .chain(std::iter::once(*sub_component_index))
1117                .collect::<Vec<_>>();
1118            let mut tree_node = make_tree(
1119                state,
1120                &sub_component.root_element,
1121                state.sub_component(sub_component),
1122                &new_sub_component_path,
1123            );
1124            tree_node.children.extend(children);
1125            tree_node.is_accessible |= !e.accessibility_props.0.is_empty();
1126            tree_node
1127        }
1128        LoweredElement::NativeItem { item_index } => TreeNode {
1129            is_accessible: !e.accessibility_props.0.is_empty(),
1130            sub_component_path: sub_component_path.into(),
1131            item_index: itertools::Either::Left(*item_index),
1132            children: children.collect(),
1133        },
1134        LoweredElement::Repeated { repeated_index } => TreeNode {
1135            is_accessible: false,
1136            sub_component_path: sub_component_path.into(),
1137            item_index: itertools::Either::Right(usize::from(*repeated_index) as u32),
1138            children: Vec::new(),
1139        },
1140        LoweredElement::ComponentPlaceholder { repeated_index } => TreeNode {
1141            is_accessible: false,
1142            sub_component_path: sub_component_path.into(),
1143            item_index: itertools::Either::Right(*repeated_index + repeater_count),
1144            children: Vec::new(),
1145        },
1146    }
1147}
1148
1149fn public_properties(
1150    component: &Component,
1151    mapping: &LoweredSubComponentMapping,
1152    state: &LoweringState,
1153) -> PublicProperties {
1154    component
1155        .root_element
1156        .borrow()
1157        .property_declarations
1158        .iter()
1159        .filter(|(_, c)| c.expose_in_public_api)
1160        .map(|(p, c)| {
1161            let property_reference = mapping.map_property_reference(
1162                &NamedReference::new(&component.root_element, p.clone()),
1163                state,
1164            );
1165            PublicProperty {
1166                name: p.clone(),
1167                ty: c.property_type.clone(),
1168                prop: property_reference,
1169                read_only: c.visibility == PropertyVisibility::Output,
1170            }
1171        })
1172        .collect()
1173}