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, Weak};
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 |original_elem_rc, _| {
22        let is_listview = match &original_elem_rc.borrow().repeated {
23            Some(r) => r.is_listview.clone(),
24            None => return,
25        };
26        let original_elem_as_weak = Rc::downgrade(original_elem_rc);
27        let mut original_elem = original_elem_rc.borrow_mut();
28
29        if matches!(&original_elem.base_type, ElementType::Component(c) if c.parent_element().is_some())
30        {
31            debug_assert!(std::rc::Weak::ptr_eq(
32                &original_elem_as_weak,
33                &*original_elem.base_type.as_component().parent_element.borrow()
34            ));
35            // Already processed (can happen if a component is both used and exported root)
36            return;
37        }
38
39        let repeated_component = Rc::new(Component {
40            root_element: Rc::new(RefCell::new(Element {
41                id: original_elem.id.clone(),
42                base_type: std::mem::take(&mut original_elem.base_type),
43                bindings: std::mem::take(&mut original_elem.bindings),
44                change_callbacks: std::mem::take(&mut original_elem.change_callbacks),
45                property_analysis: std::mem::take(&mut original_elem.property_analysis),
46                children: std::mem::take(&mut original_elem.children),
47                property_declarations: std::mem::take(&mut original_elem.property_declarations),
48                named_references: Default::default(),
49                repeated: None,
50                is_component_placeholder: false,
51                debug: original_elem.debug.clone(),
52                enclosing_component: Default::default(),
53                states: std::mem::take(&mut original_elem.states),
54                transitions: std::mem::take(&mut original_elem.transitions),
55                child_of_layout: original_elem.child_of_layout || is_listview.is_some(),
56                layout_info_prop: original_elem.layout_info_prop.take(),
57                layout_info_v_with_constraint: original_elem.layout_info_v_with_constraint.take(),
58                layout_info_h_with_constraint: original_elem.layout_info_h_with_constraint.take(),
59                default_fill_parent: original_elem.default_fill_parent,
60                accessibility_props: std::mem::take(&mut original_elem.accessibility_props),
61                geometry_props: original_elem.geometry_props.clone(),
62                is_flickable_viewport: original_elem.is_flickable_viewport,
63                has_popup_child: original_elem.has_popup_child,
64                is_tooltip: original_elem.is_tooltip,
65                item_index: Default::default(), // Not determined yet
66                item_index_of_first_children: Default::default(),
67                is_legacy_syntax: original_elem.is_legacy_syntax,
68                inline_depth: 0,
69                grid_layout_cell: original_elem.grid_layout_cell.clone(),
70            })),
71            parent_element: RefCell::new(Weak::clone(&original_elem_as_weak)),
72            ..Component::default()
73        });
74
75        if let Some(listview) = is_listview {
76            if !repeated_component.root_element.borrow().is_binding_set("height", false) {
77                let preferred = Expression::PropertyReference(NamedReference::new(
78                    &repeated_component.root_element,
79                    SmolStr::new_static("preferred-height"),
80                ));
81                repeated_component
82                    .root_element
83                    .borrow_mut()
84                    .bindings
85                    .insert("height".into(), RefCell::new(preferred.into()));
86            }
87            if !repeated_component.root_element.borrow().is_binding_set("width", false) {
88                repeated_component.root_element.borrow_mut().bindings.insert(
89                    "width".into(),
90                    RefCell::new(Expression::PropertyReference(listview.listview_width).into()),
91                );
92            }
93        }
94
95        let repeated_component_weak = Rc::downgrade(&repeated_component);
96        recurse_elem(&repeated_component.root_element, &(), &mut |e, _| {
97            e.borrow_mut().enclosing_component = repeated_component_weak.clone()
98        });
99        // Remove the mutable borrow from the RefCell, so that we can later compare it with the parent_element of the menu items
100        drop(original_elem);
101
102        // Move all the menus that belong to the newly created component
103        repeated_component.menu_item_tree.borrow_mut().extend(
104            component.menu_item_tree.borrow_mut().extract_if(.., |menu_item| {
105                let mut parent_elem = menu_item.parent_element.borrow_mut();
106
107                // When parent_element IS the element being split, update the parent_elem
108                // to point to the new sub-component's root.
109                if Weak::ptr_eq(&parent_elem, &original_elem_as_weak) {
110                    *parent_elem = Rc::downgrade(&repeated_component.root_element);
111                }
112
113                let enclosing_component =
114                    parent_elem.upgrade().unwrap().borrow().enclosing_component.clone();
115                Weak::ptr_eq(&enclosing_component, &repeated_component_weak)
116            }),
117        );
118
119        create_repeater_components(&repeated_component);
120        original_elem_rc.borrow_mut().base_type = ElementType::Component(repeated_component);
121    });
122
123    for p in component.popup_windows.borrow().iter() {
124        create_repeater_components(&p.component);
125    }
126    for c in component.menu_item_tree.borrow().iter() {
127        create_repeater_components(c);
128    }
129}
130
131/// Make sure that references to properties within the repeated element actually point to the reference
132/// to the root of the newly created component
133pub fn adjust_references(component: &Rc<Component>) {
134    visit_all_named_references(component, &mut |reference| {
135        if reference.name() == "$model" {
136            return;
137        }
138        let referred_element = reference.element();
139        if referred_element.borrow().repeated.is_some()
140            && let ElementType::Component(created_component) =
141                referred_element.borrow().base_type.clone()
142        {
143            *reference =
144                NamedReference::new(&created_component.root_element, reference.name().clone())
145        };
146    });
147    // Transform any references to the repeated element to refer to the root of each instance.
148    visit_all_expressions(component, |expr, _| {
149        expr.visit_recursive_mut(&mut |expr| {
150            if let Expression::ElementReference(element_ref) = expr
151                && let Some(repeater_element) =
152                    element_ref.upgrade().filter(|e| e.borrow().repeated.is_some())
153            {
154                let inner_element =
155                    repeater_element.borrow().base_type.as_component().root_element.clone();
156                *element_ref = Rc::downgrade(&inner_element);
157            }
158        })
159    });
160}