i_slint_compiler/passes/
inlining.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//! Inline each object_tree::Component within the main Component
5
6use crate::diagnostics::{BuildDiagnostics, Spanned};
7use crate::expression_tree::{BindingExpression, Expression, NamedReference};
8use crate::langtype::{ElementType, Type};
9use crate::object_tree::*;
10use by_address::ByAddress;
11use smol_str::SmolStr;
12use std::cell::RefCell;
13use std::collections::{HashMap, HashSet};
14use std::rc::Rc;
15
16#[derive(Copy, Clone, Eq, PartialEq)]
17pub enum InlineSelection {
18    InlineAllComponents,
19    InlineOnlyRequiredComponents,
20}
21
22pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut BuildDiagnostics) {
23    fn inline_components_recursively(
24        component: &Rc<Component>,
25        roots: &HashSet<ByAddress<Rc<Component>>>,
26        inline_selection: InlineSelection,
27        diag: &mut BuildDiagnostics,
28    ) {
29        recurse_elem_no_borrow(&component.root_element, &(), &mut |elem, _| {
30            let base = elem.borrow().base_type.clone();
31            if let ElementType::Component(c) = base {
32                // First, make sure that the component itself is properly inlined
33                inline_components_recursively(&c, roots, inline_selection, diag);
34
35                if c.parent_element.upgrade().is_some() {
36                    // We should not inline a repeated element
37                    return;
38                }
39
40                // Inline this component.
41                if match inline_selection {
42                    InlineSelection::InlineAllComponents => true,
43                    InlineSelection::InlineOnlyRequiredComponents => {
44                        component_requires_inlining(&c)
45                            || element_require_inlining(elem)
46                            // We always inline the root in case the element that instantiate this component needs full inlining,
47                            // except when the root is a repeater component, which are never inlined.
48                            || component.parent_element.upgrade().is_none() && Rc::ptr_eq(elem, &component.root_element)
49                            // We always inline other roots as a component can't be both a sub component and a root
50                            || roots.contains(&ByAddress(c.clone()))
51                    }
52                } {
53                    inline_element(elem, &c, component, diag);
54                }
55            }
56        });
57        component.popup_windows.borrow().iter().for_each(|p| {
58            inline_components_recursively(&p.component, roots, inline_selection, diag)
59        })
60    }
61    let mut roots = HashSet::new();
62    if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
63        for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
64            roots.insert(ByAddress(component.clone()));
65        }
66    }
67    for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
68        inline_components_recursively(&component, &roots, inline_selection, diag);
69        let mut init_code = component.init_code.borrow_mut();
70        let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
71        init_code.constructor_code.splice(0..0, inlined_init_code.into_values());
72    }
73}
74
75fn element_key(e: ElementRc) -> ByAddress<ElementRc> {
76    ByAddress(e)
77}
78
79type Mapping = HashMap<ByAddress<ElementRc>, ElementRc>;
80
81fn inline_element(
82    elem: &ElementRc,
83    inlined_component: &Rc<Component>,
84    root_component: &Rc<Component>,
85    diag: &mut BuildDiagnostics,
86) {
87    // inlined_component must be the base type of this element
88    debug_assert_eq!(elem.borrow().base_type, ElementType::Component(inlined_component.clone()));
89    debug_assert!(
90        inlined_component.root_element.borrow().repeated.is_none(),
91        "root element of a component cannot be repeated"
92    );
93    debug_assert!(inlined_component.parent_element.upgrade().is_none());
94
95    let mut elem_mut = elem.borrow_mut();
96    let priority_delta = 1 + elem_mut.inline_depth;
97    elem_mut.base_type = inlined_component.root_element.borrow().base_type.clone();
98    elem_mut.property_declarations.extend(
99        inlined_component.root_element.borrow().property_declarations.iter().map(|(name, decl)| {
100            let mut decl = decl.clone();
101            decl.expose_in_public_api = false;
102            (name.clone(), decl)
103        }),
104    );
105
106    for (p, a) in inlined_component.root_element.borrow().property_analysis.borrow().iter() {
107        elem_mut.property_analysis.borrow_mut().entry(p.clone()).or_default().merge_with_base(a);
108    }
109
110    // states and transitions must be lowered before inlining
111    debug_assert!(inlined_component.root_element.borrow().states.is_empty());
112    debug_assert!(inlined_component.root_element.borrow().transitions.is_empty());
113
114    // Map the old element to the new
115    let mut mapping = HashMap::new();
116    mapping.insert(element_key(inlined_component.root_element.clone()), elem.clone());
117
118    let mut new_children = Vec::with_capacity(
119        elem_mut.children.len() + inlined_component.root_element.borrow().children.len(),
120    );
121    new_children.extend(
122        inlined_component.root_element.borrow().children.iter().map(|x| {
123            duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
124        }),
125    );
126
127    let mut move_children_into_popup = None;
128
129    match inlined_component.child_insertion_point.borrow().as_ref() {
130        Some(inlined_cip) => {
131            let children = std::mem::take(&mut elem_mut.children);
132            let old_count = children.len();
133            if let Some(insertion_element) = mapping.get(&element_key(inlined_cip.parent.clone())) {
134                if old_count > 0 {
135                    if !Rc::ptr_eq(elem, insertion_element) {
136                        debug_assert!(std::rc::Weak::ptr_eq(
137                            &insertion_element.borrow().enclosing_component,
138                            &elem_mut.enclosing_component,
139                        ));
140                        insertion_element.borrow_mut().children.splice(
141                            inlined_cip.insertion_index..inlined_cip.insertion_index,
142                            children,
143                        );
144                    } else {
145                        new_children.splice(
146                            inlined_cip.insertion_index..inlined_cip.insertion_index,
147                            children,
148                        );
149                    }
150                }
151                let mut cip = root_component.child_insertion_point.borrow_mut();
152                if let Some(cip) = cip.as_mut() {
153                    if Rc::ptr_eq(&cip.parent, elem) {
154                        *cip = ChildrenInsertionPoint {
155                            parent: insertion_element.clone(),
156                            insertion_index: inlined_cip.insertion_index + cip.insertion_index,
157                            node: inlined_cip.node.clone(),
158                        };
159                    }
160                } else if Rc::ptr_eq(elem, &root_component.root_element) {
161                    *cip = Some(ChildrenInsertionPoint {
162                        parent: insertion_element.clone(),
163                        insertion_index: inlined_cip.insertion_index + old_count,
164                        node: inlined_cip.node.clone(),
165                    });
166                };
167            } else if old_count > 0 {
168                // @children was into a PopupWindow
169                debug_assert!(inlined_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
170                    &p.component,
171                    &inlined_cip.parent.borrow().enclosing_component.upgrade().unwrap()
172                )));
173                move_children_into_popup = Some(children);
174            };
175        }
176        _ => {
177            new_children.append(&mut elem_mut.children);
178        }
179    }
180
181    elem_mut.children = new_children;
182    elem_mut.debug.extend_from_slice(&inlined_component.root_element.borrow().debug);
183
184    if let ElementType::Component(c) = &mut elem_mut.base_type {
185        if c.parent_element.upgrade().is_some() {
186            debug_assert!(Rc::ptr_eq(elem, &c.parent_element.upgrade().unwrap()));
187            *c = duplicate_sub_component(c, elem, &mut mapping, priority_delta);
188        }
189    };
190
191    root_component.optimized_elements.borrow_mut().extend(
192        inlined_component.optimized_elements.borrow().iter().map(|x| {
193            duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
194        }),
195    );
196    root_component.popup_windows.borrow_mut().extend(
197        inlined_component
198            .popup_windows
199            .borrow()
200            .iter()
201            .map(|p| duplicate_popup(p, &mut mapping, priority_delta)),
202    );
203
204    root_component.menu_item_tree.borrow_mut().extend(
205        inlined_component
206            .menu_item_tree
207            .borrow()
208            .iter()
209            .map(|it| duplicate_sub_component(it, elem, &mut mapping, priority_delta)),
210    );
211
212    root_component.timers.borrow_mut().extend(inlined_component.timers.borrow().iter().map(|t| {
213        let inlined_element = mapping.get(&element_key(t.element.upgrade().unwrap())).unwrap();
214
215        Timer { element: Rc::downgrade(inlined_element), ..t.clone() }
216    }));
217
218    let mut moved_into_popup = HashSet::new();
219    if let Some(children) = move_children_into_popup {
220        let child_insertion_point = inlined_component.child_insertion_point.borrow();
221        let inlined_cip = child_insertion_point.as_ref().unwrap();
222
223        let insertion_element = mapping.get(&element_key(inlined_cip.parent.clone())).unwrap();
224        debug_assert!(!std::rc::Weak::ptr_eq(
225            &insertion_element.borrow().enclosing_component,
226            &elem_mut.enclosing_component,
227        ));
228        debug_assert!(root_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
229            &p.component,
230            &insertion_element.borrow().enclosing_component.upgrade().unwrap()
231        )));
232        for c in &children {
233            recurse_elem(c, &(), &mut |e, _| {
234                e.borrow_mut().enclosing_component =
235                    insertion_element.borrow().enclosing_component.clone();
236                moved_into_popup.insert(element_key(e.clone()));
237            });
238        }
239        insertion_element
240            .borrow_mut()
241            .children
242            .splice(inlined_cip.insertion_index..inlined_cip.insertion_index, children);
243        let mut cip = root_component.child_insertion_point.borrow_mut();
244        if let Some(cip) = cip.as_mut() {
245            if Rc::ptr_eq(&cip.parent, elem) {
246                *cip = ChildrenInsertionPoint {
247                    parent: insertion_element.clone(),
248                    insertion_index: inlined_cip.insertion_index + cip.insertion_index,
249                    node: inlined_cip.node.clone(),
250                };
251            }
252        } else {
253            *cip = Some(ChildrenInsertionPoint {
254                parent: insertion_element.clone(),
255                insertion_index: inlined_cip.insertion_index,
256                node: inlined_cip.node.clone(),
257            });
258        };
259    }
260
261    for (k, val) in inlined_component.root_element.borrow().bindings.iter() {
262        match elem_mut.bindings.entry(k.clone()) {
263            std::collections::btree_map::Entry::Vacant(entry) => {
264                let priority = &mut entry.insert(val.clone()).get_mut().priority;
265                *priority = priority.saturating_add(priority_delta);
266            }
267            std::collections::btree_map::Entry::Occupied(mut entry) => {
268                let entry = entry.get_mut().get_mut();
269                if entry.merge_with(&val.borrow()) {
270                    entry.priority = entry.priority.saturating_add(priority_delta);
271                }
272            }
273        }
274    }
275    for (k, val) in inlined_component.root_element.borrow().change_callbacks.iter() {
276        match elem_mut.change_callbacks.entry(k.clone()) {
277            std::collections::btree_map::Entry::Vacant(entry) => {
278                entry.insert(val.clone());
279            }
280            std::collections::btree_map::Entry::Occupied(mut entry) => {
281                entry.get_mut().get_mut().splice(0..0, val.borrow().iter().cloned());
282            }
283        }
284    }
285
286    if let Some(orig) = &inlined_component.root_element.borrow().layout_info_prop {
287        if let Some(_new) = &mut elem_mut.layout_info_prop {
288            todo!("Merge layout infos");
289        } else {
290            elem_mut.layout_info_prop = Some(orig.clone());
291        }
292    }
293
294    core::mem::drop(elem_mut);
295
296    let fixup_init_expression = |mut init_code: Expression| {
297        // Fix up any property references from within already collected init code.
298        visit_named_references_in_expression(&mut init_code, &mut |nr| {
299            fixup_reference(nr, &mapping)
300        });
301        fixup_element_references(&mut init_code, &mapping);
302        init_code
303    };
304    let inlined_init_code = inlined_component
305        .init_code
306        .borrow()
307        .inlined_init_code
308        .values()
309        .cloned()
310        .chain(inlined_component.init_code.borrow().constructor_code.iter().cloned())
311        .map(fixup_init_expression)
312        .collect();
313
314    root_component
315        .init_code
316        .borrow_mut()
317        .inlined_init_code
318        .insert(elem.borrow().span().offset, Expression::CodeBlock(inlined_init_code));
319
320    // Now fixup all binding and reference
321    for e in mapping.values() {
322        visit_all_named_references_in_element(e, |nr| fixup_reference(nr, &mapping));
323        visit_element_expressions(e, |expr, _, _| fixup_element_references(expr, &mapping));
324    }
325    for p in root_component.popup_windows.borrow_mut().iter_mut() {
326        fixup_reference(&mut p.x, &mapping);
327        fixup_reference(&mut p.y, &mapping);
328    }
329    for t in root_component.timers.borrow_mut().iter_mut() {
330        fixup_reference(&mut t.interval, &mapping);
331        fixup_reference(&mut t.running, &mapping);
332        fixup_reference(&mut t.triggered, &mapping);
333    }
334    // If some element were moved into PopupWindow, we need to report error if they are used outside of the popup window.
335    if !moved_into_popup.is_empty() {
336        recurse_elem_no_borrow(&root_component.root_element.clone(), &(), &mut |e, _| {
337            if !moved_into_popup.contains(&element_key(e.clone())) {
338                visit_all_named_references_in_element(e, |nr| {
339                    if moved_into_popup.contains(&element_key(nr.element())) {
340                        diag.push_error(format!("Access to property '{nr:?}' which is inlined into a PopupWindow via @children is forbidden"), &*e.borrow());
341                    }
342                });
343            }
344        });
345    }
346}
347
348// Duplicate the element elem and all its children. And fill the mapping to point from the old to the new
349fn duplicate_element_with_mapping(
350    element: &ElementRc,
351    mapping: &mut Mapping,
352    root_component: &Rc<Component>,
353    priority_delta: i32,
354) -> ElementRc {
355    let elem = element.borrow();
356    let new = Rc::new(RefCell::new(Element {
357        base_type: elem.base_type.clone(),
358        id: elem.id.clone(),
359        property_declarations: elem.property_declarations.clone(),
360        // We will do the fixup of the references in bindings later
361        bindings: elem
362            .bindings
363            .iter()
364            .map(|b| duplicate_binding(b, mapping, root_component, priority_delta))
365            .collect(),
366        change_callbacks: elem.change_callbacks.clone(),
367        property_analysis: elem.property_analysis.clone(),
368        children: elem
369            .children
370            .iter()
371            .map(|x| duplicate_element_with_mapping(x, mapping, root_component, priority_delta))
372            .collect(),
373        repeated: elem.repeated.clone(),
374        is_component_placeholder: elem.is_component_placeholder,
375        debug: elem.debug.clone(),
376        enclosing_component: Rc::downgrade(root_component),
377        states: elem.states.clone(),
378        transitions: elem
379            .transitions
380            .iter()
381            .map(|t| duplicate_transition(t, mapping, root_component, priority_delta))
382            .collect(),
383        child_of_layout: elem.child_of_layout,
384        layout_info_prop: elem.layout_info_prop.clone(),
385        default_fill_parent: elem.default_fill_parent,
386        accessibility_props: elem.accessibility_props.clone(),
387        geometry_props: elem.geometry_props.clone(),
388        named_references: Default::default(),
389        item_index: Default::default(), // Not determined yet
390        item_index_of_first_children: Default::default(),
391        is_flickable_viewport: elem.is_flickable_viewport,
392        has_popup_child: elem.has_popup_child,
393        is_legacy_syntax: elem.is_legacy_syntax,
394        inline_depth: elem.inline_depth + 1,
395    }));
396    mapping.insert(element_key(element.clone()), new.clone());
397    if let ElementType::Component(c) = &mut new.borrow_mut().base_type {
398        if c.parent_element.upgrade().is_some() {
399            debug_assert!(Rc::ptr_eq(element, &c.parent_element.upgrade().unwrap()));
400            *c = duplicate_sub_component(c, &new, mapping, priority_delta);
401        }
402    };
403
404    new
405}
406
407/// Duplicate Component for repeated element or popup window that have a parent_element
408fn duplicate_sub_component(
409    component_to_duplicate: &Rc<Component>,
410    new_parent: &ElementRc,
411    mapping: &mut Mapping,
412    priority_delta: i32,
413) -> Rc<Component> {
414    debug_assert!(component_to_duplicate.parent_element.upgrade().is_some());
415    let new_component = Component {
416        node: component_to_duplicate.node.clone(),
417        id: component_to_duplicate.id.clone(),
418        root_element: duplicate_element_with_mapping(
419            &component_to_duplicate.root_element,
420            mapping,
421            component_to_duplicate, // that's the wrong one, but we fixup further
422            priority_delta,
423        ),
424        parent_element: Rc::downgrade(new_parent),
425        optimized_elements: RefCell::new(
426            component_to_duplicate
427                .optimized_elements
428                .borrow()
429                .iter()
430                .map(|e| {
431                    duplicate_element_with_mapping(
432                        e,
433                        mapping,
434                        component_to_duplicate,
435                        priority_delta,
436                    )
437                })
438                .collect(),
439        ),
440        root_constraints: component_to_duplicate.root_constraints.clone(),
441        child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
442        init_code: component_to_duplicate.init_code.clone(),
443        popup_windows: Default::default(),
444        timers: component_to_duplicate.timers.clone(),
445        menu_item_tree: Default::default(),
446        exported_global_names: component_to_duplicate.exported_global_names.clone(),
447        used: component_to_duplicate.used.clone(),
448        private_properties: Default::default(),
449        inherits_popup_window: core::cell::Cell::new(false),
450        from_library: core::cell::Cell::new(false),
451    };
452
453    let new_component = Rc::new(new_component);
454    let weak = Rc::downgrade(&new_component);
455    recurse_elem(&new_component.root_element, &(), &mut |e, _| {
456        e.borrow_mut().enclosing_component = weak.clone()
457    });
458    for o in new_component.optimized_elements.borrow().iter() {
459        o.borrow_mut().enclosing_component = weak.clone()
460    }
461    *new_component.popup_windows.borrow_mut() = component_to_duplicate
462        .popup_windows
463        .borrow()
464        .iter()
465        .map(|p| duplicate_popup(p, mapping, priority_delta))
466        .collect();
467    for p in new_component.popup_windows.borrow_mut().iter_mut() {
468        fixup_reference(&mut p.x, mapping);
469        fixup_reference(&mut p.y, mapping);
470    }
471    for t in new_component.timers.borrow_mut().iter_mut() {
472        fixup_reference(&mut t.interval, mapping);
473        fixup_reference(&mut t.running, mapping);
474        fixup_reference(&mut t.triggered, mapping);
475    }
476    *new_component.menu_item_tree.borrow_mut() = component_to_duplicate
477        .menu_item_tree
478        .borrow()
479        .iter()
480        .map(|it| {
481            let new_parent =
482                mapping.get(&element_key(it.parent_element.upgrade().unwrap())).unwrap().clone();
483            duplicate_sub_component(it, &new_parent, mapping, priority_delta)
484        })
485        .collect();
486    new_component
487        .root_constraints
488        .borrow_mut()
489        .visit_named_references(&mut |nr| fixup_reference(nr, mapping));
490    new_component
491}
492
493fn duplicate_popup(p: &PopupWindow, mapping: &mut Mapping, priority_delta: i32) -> PopupWindow {
494    let parent = mapping
495        .get(&element_key(p.component.parent_element.upgrade().expect("must have a parent")))
496        .expect("Parent must be in the mapping")
497        .clone();
498    PopupWindow {
499        x: p.x.clone(),
500        y: p.y.clone(),
501        close_policy: p.close_policy.clone(),
502        component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
503        parent_element: mapping
504            .get(&element_key(p.parent_element.clone()))
505            .expect("Parent element must be in the mapping")
506            .clone(),
507    }
508}
509
510/// Clone and increase the priority of a binding
511/// and duplicate its animation
512fn duplicate_binding(
513    (k, b): (&SmolStr, &RefCell<BindingExpression>),
514    mapping: &mut Mapping,
515    root_component: &Rc<Component>,
516    priority_delta: i32,
517) -> (SmolStr, RefCell<BindingExpression>) {
518    let b = b.borrow();
519    let b = BindingExpression {
520        expression: b.expression.clone(),
521        span: b.span.clone(),
522        priority: b.priority.saturating_add(priority_delta),
523        animation: b
524            .animation
525            .as_ref()
526            .map(|pa| duplicate_property_animation(pa, mapping, root_component, priority_delta)),
527        analysis: b.analysis.clone(),
528        two_way_bindings: b.two_way_bindings.clone(),
529    };
530    (k.clone(), b.into())
531}
532
533fn duplicate_property_animation(
534    v: &PropertyAnimation,
535    mapping: &mut Mapping,
536    root_component: &Rc<Component>,
537    priority_delta: i32,
538) -> PropertyAnimation {
539    match v {
540        PropertyAnimation::Static(a) => PropertyAnimation::Static(duplicate_element_with_mapping(
541            a,
542            mapping,
543            root_component,
544            priority_delta,
545        )),
546        PropertyAnimation::Transition { state_ref, animations } => PropertyAnimation::Transition {
547            state_ref: state_ref.clone(),
548            animations: animations
549                .iter()
550                .map(|a| TransitionPropertyAnimation {
551                    state_id: a.state_id,
552                    direction: a.direction,
553                    animation: duplicate_element_with_mapping(
554                        &a.animation,
555                        mapping,
556                        root_component,
557                        priority_delta,
558                    ),
559                })
560                .collect(),
561        },
562    }
563}
564
565fn fixup_reference(nr: &mut NamedReference, mapping: &Mapping) {
566    if let Some(e) = mapping.get(&element_key(nr.element())) {
567        *nr = NamedReference::new(e, nr.name().clone());
568    }
569}
570
571fn fixup_element_references(expr: &mut Expression, mapping: &Mapping) {
572    let fx = |element: &mut std::rc::Weak<RefCell<Element>>| {
573        if let Some(e) = element.upgrade().and_then(|e| mapping.get(&element_key(e))) {
574            *element = Rc::downgrade(e);
575        }
576    };
577    let fxe = |element: &mut ElementRc| {
578        if let Some(e) = mapping.get(&element_key(element.clone())) {
579            *element = e.clone();
580        }
581    };
582    match expr {
583        Expression::ElementReference(element) => fx(element),
584        Expression::SolveLayout(l, _) | Expression::ComputeLayoutInfo(l, _) => match l {
585            crate::layout::Layout::GridLayout(l) => {
586                for e in &mut l.elems {
587                    fxe(&mut e.item.element);
588                }
589            }
590            crate::layout::Layout::BoxLayout(l) => {
591                for e in &mut l.elems {
592                    fxe(&mut e.element);
593                }
594            }
595        },
596        Expression::RepeaterModelReference { element }
597        | Expression::RepeaterIndexReference { element } => fx(element),
598        _ => expr.visit_mut(|e| fixup_element_references(e, mapping)),
599    }
600}
601
602fn duplicate_transition(
603    t: &Transition,
604    mapping: &mut HashMap<ByAddress<ElementRc>, Rc<RefCell<Element>>>,
605    root_component: &Rc<Component>,
606    priority_delta: i32,
607) -> Transition {
608    Transition {
609        direction: t.direction,
610        state_id: t.state_id.clone(),
611        property_animations: t
612            .property_animations
613            .iter()
614            .map(|(r, loc, anim)| {
615                (
616                    r.clone(),
617                    loc.clone(),
618                    duplicate_element_with_mapping(anim, mapping, root_component, priority_delta),
619                )
620            })
621            .collect(),
622        node: t.node.clone(),
623    }
624}
625
626// Some components need to be inlined to avoid increased complexity in handling them
627// in the code generators and subsequent passes.
628fn component_requires_inlining(component: &Rc<Component>) -> bool {
629    let root_element = &component.root_element;
630    if super::flickable::is_flickable_element(root_element) {
631        return true;
632    }
633
634    for (prop, binding) in &root_element.borrow().bindings {
635        let binding = binding.borrow();
636        // The passes that dp the drop shadow or the opacity currently won't allow this property
637        // on the top level of a component. This could be changed in the future.
638        if prop.starts_with("drop-shadow-")
639            || prop == "opacity"
640            || prop == "cache-rendering-hint"
641            || prop == "visible"
642        {
643            return true;
644        }
645        if (prop == "height" || prop == "width") && binding.expression.ty() == Type::Percent {
646            // percentage size in the root element might not make sense anyway.
647            return true;
648        }
649        if binding.animation.is_some() {
650            let lookup_result = root_element.borrow().lookup_property(prop);
651            if !lookup_result.is_valid()
652                || !lookup_result.is_local_to_component
653                || !matches!(
654                    lookup_result.property_visibility,
655                    PropertyVisibility::Private | PropertyVisibility::Output
656                )
657            {
658                // If there is an animation, we currently inline so that if this property
659                // is set with a binding, it is merged
660                return true;
661            }
662        }
663    }
664
665    false
666}
667
668fn element_require_inlining(elem: &ElementRc) -> bool {
669    if !elem.borrow().children.is_empty() {
670        // the generators assume that the children list is complete, which sub-components may break
671        return true;
672    }
673
674    // Popup windows need to be inlined for root.close() to work properly.
675    if super::lower_popups::is_popup_window(elem) {
676        return true;
677    }
678
679    for (prop, binding) in &elem.borrow().bindings {
680        if prop == "clip" {
681            // otherwise the children of the clipped items won't get moved as child of the Clip element
682            return true;
683        }
684
685        if prop == "padding"
686            || prop == "spacing"
687            || prop.starts_with("padding-")
688            || prop.starts_with("spacing-")
689            || prop == "alignment"
690        {
691            if let ElementType::Component(base) = &elem.borrow().base_type {
692                if crate::layout::is_layout(&base.root_element.borrow().base_type) {
693                    if !base.root_element.borrow().is_binding_set(prop, false) {
694                        // The layout pass need to know that this property is set
695                        return true;
696                    }
697                }
698            }
699        }
700
701        let binding = binding.borrow();
702        if binding.animation.is_some() && matches!(binding.expression, Expression::Invalid) {
703            // If there is an animation but no binding, we must merge the binding with its animation.
704            return true;
705        }
706    }
707
708    false
709}