Skip to main content

i_slint_compiler/passes/
repeater_component.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/*!
5Make sure that the Repeated expression are just components without any children
6 */
7
8use crate::expression_tree::{Expression, NamedReference};
9use crate::langtype::ElementType;
10use crate::object_tree::*;
11use smol_str::SmolStr;
12use std::cell::RefCell;
13use std::rc::Rc;
14
15pub fn process_repeater_components(component: &Rc<Component>) {
16    create_repeater_components(component);
17    adjust_references(component);
18}
19
20fn create_repeater_components(component: &Rc<Component>) {
21    recurse_elem(&component.root_element, &(), &mut |elem, _| {
22        let is_listview = match &elem.borrow().repeated {
23            Some(r) => r.is_listview.clone(),
24            None => return,
25        };
26        let parent_element = Rc::downgrade(elem);
27        let mut elem = elem.borrow_mut();
28
29        if matches!(&elem.base_type, ElementType::Component(c) if c.parent_element.upgrade().is_some())
30        {
31            debug_assert!(std::rc::Weak::ptr_eq(
32                &parent_element,
33                &elem.base_type.as_component().parent_element
34            ));
35            // Already processed (can happen if a component is both used and exported root)
36            return;
37        }
38
39        let comp = Rc::new(Component {
40            root_element: Rc::new(RefCell::new(Element {
41                id: elem.id.clone(),
42                base_type: std::mem::take(&mut elem.base_type),
43                bindings: std::mem::take(&mut elem.bindings),
44                change_callbacks: std::mem::take(&mut elem.change_callbacks),
45                property_analysis: std::mem::take(&mut elem.property_analysis),
46                children: std::mem::take(&mut elem.children),
47                property_declarations: std::mem::take(&mut elem.property_declarations),
48                named_references: Default::default(),
49                repeated: None,
50                is_component_placeholder: false,
51                debug: elem.debug.clone(),
52                enclosing_component: Default::default(),
53                states: std::mem::take(&mut elem.states),
54                transitions: std::mem::take(&mut elem.transitions),
55                child_of_layout: elem.child_of_layout || is_listview.is_some(),
56                layout_info_prop: elem.layout_info_prop.take(),
57                default_fill_parent: elem.default_fill_parent,
58                accessibility_props: std::mem::take(&mut elem.accessibility_props),
59                geometry_props: elem.geometry_props.clone(),
60                is_flickable_viewport: elem.is_flickable_viewport,
61                has_popup_child: elem.has_popup_child,
62                item_index: Default::default(), // Not determined yet
63                item_index_of_first_children: Default::default(),
64                is_legacy_syntax: elem.is_legacy_syntax,
65                inline_depth: 0,
66                grid_layout_cell: elem.grid_layout_cell.clone(),
67            })),
68            parent_element,
69            ..Component::default()
70        });
71
72        if let Some(listview) = is_listview {
73            if !comp.root_element.borrow().is_binding_set("height", false) {
74                let preferred = Expression::PropertyReference(NamedReference::new(
75                    &comp.root_element,
76                    SmolStr::new_static("preferred-height"),
77                ));
78                comp.root_element
79                    .borrow_mut()
80                    .bindings
81                    .insert("height".into(), RefCell::new(preferred.into()));
82            }
83            if !comp.root_element.borrow().is_binding_set("width", false) {
84                comp.root_element.borrow_mut().bindings.insert(
85                    "width".into(),
86                    RefCell::new(Expression::PropertyReference(listview.listview_width).into()),
87                );
88            }
89        }
90
91        let weak = Rc::downgrade(&comp);
92        recurse_elem(&comp.root_element, &(), &mut |e, _| {
93            e.borrow_mut().enclosing_component = weak.clone()
94        });
95
96        // Move all the menus that belong to the newly created component
97        // Could use Vec::extract_if if MSRV >= 1.87
98        component.menu_item_tree.borrow_mut().retain(|x| {
99            if x.parent_element
100                .upgrade()
101                .unwrap()
102                .try_borrow() // borrow fails if `x.parent_element` == `elem`
103                .ok()
104                .is_none_or(|parent| std::rc::Weak::ptr_eq(&parent.enclosing_component, &weak))
105            {
106                comp.menu_item_tree.borrow_mut().push(x.clone());
107                false
108            } else {
109                true
110            }
111        });
112
113        create_repeater_components(&comp);
114        elem.base_type = ElementType::Component(comp);
115    });
116
117    for p in component.popup_windows.borrow().iter() {
118        create_repeater_components(&p.component);
119    }
120    for c in component.menu_item_tree.borrow().iter() {
121        create_repeater_components(c);
122    }
123}
124
125/// Make sure that references to properties within the repeated element actually point to the reference
126/// to the root of the newly created component
127pub fn adjust_references(comp: &Rc<Component>) {
128    visit_all_named_references(comp, &mut |nr| {
129        if nr.name() == "$model" {
130            return;
131        }
132        let e = nr.element();
133        if e.borrow().repeated.is_some()
134            && let ElementType::Component(c) = e.borrow().base_type.clone()
135        {
136            *nr = NamedReference::new(&c.root_element, nr.name().clone())
137        };
138    });
139    // Transform any references to the repeated element to refer to the root of each instance.
140    visit_all_expressions(comp, |expr, _| {
141        expr.visit_recursive_mut(&mut |expr| {
142            if let Expression::ElementReference(element_ref) = expr
143                && let Some(repeater_element) =
144                    element_ref.upgrade().filter(|e| e.borrow().repeated.is_some())
145            {
146                let inner_element =
147                    repeater_element.borrow().base_type.as_component().root_element.clone();
148                *element_ref = Rc::downgrade(&inner_element);
149            }
150        })
151    });
152}