Skip to main content

i_slint_compiler/passes/
move_declarations.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//! This pass moves all declaration of properties or callback to the root
5
6use crate::expression_tree::{Expression, NamedReference};
7use crate::langtype::ElementType;
8use crate::object_tree::*;
9use core::cell::RefCell;
10use smol_str::{SmolStr, format_smolstr};
11use std::collections::{BTreeMap, HashMap};
12use std::rc::Rc;
13
14struct Declarations {
15    property_declarations: BTreeMap<SmolStr, PropertyDeclaration>,
16}
17impl Declarations {
18    fn take_from_element(e: &mut Element) -> Self {
19        Declarations { property_declarations: core::mem::take(&mut e.property_declarations) }
20    }
21}
22
23pub fn move_declarations(component: &Rc<Component>) {
24    simplify_optimized_items_recursive(component);
25    do_move_declarations(component);
26}
27
28fn do_move_declarations(component: &Rc<Component>) {
29    let mut decl = Declarations::take_from_element(&mut component.root_element.borrow_mut());
30    component.popup_windows.borrow().iter().for_each(|f| do_move_declarations(&f.component));
31    component.menu_item_tree.borrow().iter().for_each(do_move_declarations);
32
33    let mut new_root_bindings = HashMap::new();
34    let mut new_root_change_callbacks = HashMap::new();
35    let mut new_root_property_analysis = HashMap::new();
36
37    let move_bindings_and_animations = &mut |elem: &ElementRc| {
38        visit_all_named_references_in_element(elem, fixup_reference);
39
40        if elem.borrow().repeated.is_some() {
41            if let ElementType::Component(base) = &elem.borrow().base_type {
42                do_move_declarations(base);
43            } else {
44                panic!(
45                    "Repeated element should have a component as base because of the repeater_component.rs pass"
46                )
47            }
48            debug_assert!(
49                elem.borrow().property_declarations.is_empty() && elem.borrow().children.is_empty(),
50                "Repeated element should be empty because of the repeater_component.rs pass"
51            );
52            return;
53        }
54
55        // take the bindings so we do not keep the borrow_mut of the element
56        let bindings = core::mem::take(&mut elem.borrow_mut().bindings);
57        let mut new_bindings = BindingsMap::default();
58        for (k, e) in bindings {
59            let will_be_moved = elem.borrow().property_declarations.contains_key(&k);
60            if will_be_moved {
61                new_root_bindings.insert(map_name(elem, &k), e);
62            } else {
63                new_bindings.insert(k, e);
64            }
65        }
66        elem.borrow_mut().bindings = new_bindings;
67
68        let property_analysis = elem.borrow().property_analysis.take();
69        let mut new_property_analysis = HashMap::with_capacity(property_analysis.len());
70        for (prop, a) in property_analysis {
71            let will_be_moved = elem.borrow().property_declarations.contains_key(&prop);
72            if will_be_moved {
73                new_root_property_analysis.insert(map_name(elem, &prop), a);
74            } else {
75                new_property_analysis.insert(prop, a);
76            }
77        }
78        *elem.borrow().property_analysis.borrow_mut() = new_property_analysis;
79
80        // Also move the changed callback
81        let change_callbacks = core::mem::take(&mut elem.borrow_mut().change_callbacks);
82        let mut new_change_callbacks = BTreeMap::<SmolStr, RefCell<Vec<Expression>>>::default();
83        for (k, e) in change_callbacks {
84            let will_be_moved = elem.borrow().property_declarations.contains_key(&k);
85            if will_be_moved {
86                new_root_change_callbacks.insert(map_name(elem, &k), e);
87            } else {
88                new_change_callbacks.insert(k, e);
89            }
90        }
91        elem.borrow_mut().change_callbacks = new_change_callbacks;
92    };
93
94    component.optimized_elements.borrow().iter().for_each(&mut *move_bindings_and_animations);
95    recurse_elem(&component.root_element, &(), &mut |e, _| move_bindings_and_animations(e));
96
97    component.root_constraints.borrow_mut().visit_named_references(&mut fixup_reference);
98    component.popup_windows.borrow_mut().iter_mut().for_each(|p| {
99        fixup_reference(&mut p.x);
100        fixup_reference(&mut p.y);
101        visit_all_named_references(&p.component, &mut fixup_reference)
102    });
103    component.timers.borrow_mut().iter_mut().for_each(|t| {
104        fixup_reference(&mut t.interval);
105        fixup_reference(&mut t.running);
106        fixup_reference(&mut t.triggered);
107    });
108    component.menu_item_tree.borrow_mut().iter_mut().for_each(|c| {
109        visit_all_named_references(c, &mut fixup_reference);
110    });
111    component.init_code.borrow_mut().iter_mut().for_each(|expr| {
112        visit_named_references_in_expression(expr, &mut fixup_reference);
113    });
114    for pd in decl.property_declarations.values_mut() {
115        pd.is_alias.as_mut().map(fixup_reference);
116    }
117
118    let move_properties = &mut |elem: &ElementRc| {
119        let elem_decl = Declarations::take_from_element(&mut elem.borrow_mut());
120        decl.property_declarations.extend(
121            elem_decl.property_declarations.into_iter().map(|(p, d)| (map_name(elem, &p), d)),
122        );
123    };
124
125    recurse_elem(&component.root_element, &(), &mut |elem, _| move_properties(elem));
126
127    component.optimized_elements.borrow().iter().for_each(move_properties);
128
129    {
130        let mut r = component.root_element.borrow_mut();
131        r.property_declarations = decl.property_declarations;
132        r.bindings.extend(new_root_bindings);
133        r.property_analysis.borrow_mut().extend(new_root_property_analysis);
134        r.change_callbacks.extend(new_root_change_callbacks);
135    }
136}
137
138fn fixup_reference(nr: &mut NamedReference) {
139    let e = nr.element();
140    let component = e.borrow().enclosing_component.upgrade().unwrap();
141    if !Rc::ptr_eq(&e, &component.root_element)
142        && e.borrow().property_declarations.contains_key(nr.name())
143    {
144        *nr = NamedReference::new(&component.root_element, map_name(&e, nr.name()));
145    }
146}
147
148fn map_name(e: &ElementRc, s: &SmolStr) -> SmolStr {
149    format_smolstr!("{}-{}", e.borrow().id, s)
150}
151
152fn simplify_optimized_items_recursive(component: &Rc<Component>) {
153    simplify_optimized_items(component.optimized_elements.borrow().as_slice());
154    component
155        .popup_windows
156        .borrow()
157        .iter()
158        .for_each(|f| simplify_optimized_items_recursive(&f.component));
159    recurse_elem(&component.root_element, &(), &mut |elem, _| {
160        if elem.borrow().repeated.is_some()
161            && let ElementType::Component(base) = &elem.borrow().base_type
162        {
163            simplify_optimized_items_recursive(base);
164        }
165    });
166}
167
168/// Optimized items are not used for the fact that they are items, but their properties
169/// might still be used.  So we must pretend all the properties are declared in the
170/// item itself so the move_declaration pass can move the declaration in the component root
171fn simplify_optimized_items(items: &[ElementRc]) {
172    for elem in items {
173        recurse_elem(elem, &(), &mut |elem, _| {
174            let base = core::mem::take(&mut elem.borrow_mut().base_type);
175            if let ElementType::Builtin(c) = base {
176                // This assume that all properties of builtin items are fine with the default value
177                elem.borrow_mut().property_declarations.extend(c.properties.iter().map(
178                    |(k, v)| {
179                        (
180                            k.clone(),
181                            PropertyDeclaration {
182                                property_type: v.ty.clone(),
183                                ..Default::default()
184                            },
185                        )
186                    },
187                ));
188            } else {
189                unreachable!("Only builtin items should be optimized")
190            }
191        })
192    }
193}