i_slint_compiler/passes/
lower_property_to_element.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//! Pass that lowers synthetic properties such as `opacity` and `layer` properties to their corresponding elements.
5//! For example `f := Foo { opacity: <some float>; }` is mapped to `Opacity { opacity <=> f.opacity; f := Foo { ... } }`
6
7use crate::diagnostics::BuildDiagnostics;
8use crate::expression_tree::{BindingExpression, Expression, NamedReference};
9use crate::langtype::Type;
10use crate::object_tree::{self, Component, Element, ElementRc};
11use crate::typeregister::TypeRegister;
12use smol_str::{format_smolstr, SmolStr, ToSmolStr};
13use std::rc::Rc;
14
15/// If any element in `component` declares a binding to any of `property_names`, then a new
16/// element of type `element_name` is created, injected as a parent to the element and bindings
17/// to all properties in property_names and extra_properties are mapped.
18/// Default value for the property extra_properties is queried with the `default_value_for_extra_properties`
19pub(crate) fn lower_property_to_element(
20    component: &Rc<Component>,
21    property_names: impl Iterator<Item = &'static str> + Clone,
22    extra_properties: impl Iterator<Item = &'static str> + Clone,
23    default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Option<Expression>>,
24    element_name: &SmolStr,
25    type_register: &TypeRegister,
26    diag: &mut BuildDiagnostics,
27) {
28    for property_name in property_names.clone() {
29        if let Some(b) = component.root_element.borrow().bindings.get(property_name) {
30            diag.push_warning(
31                format!(
32                    "The {property_name} property cannot be used on the root element, it will not be applied"
33                ),
34                &*b.borrow(),
35            );
36        }
37    }
38
39    object_tree::recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
40        if elem.borrow().base_type.to_smolstr() == *element_name {
41            return;
42        }
43
44        let old_children = {
45            let mut elem = elem.borrow_mut();
46            let new_children = Vec::with_capacity(elem.children.len());
47            std::mem::replace(&mut elem.children, new_children)
48        };
49
50        let has_property_binding = |e: &ElementRc| {
51            property_names.clone().any(|property_name| {
52                e.borrow().base_type.lookup_property(property_name).property_type != Type::Invalid
53                    && (e.borrow().bindings.contains_key(property_name)
54                        || e.borrow()
55                            .property_analysis
56                            .borrow()
57                            .get(property_name)
58                            .is_some_and(|a| a.is_set || a.is_linked))
59            })
60        };
61
62        for mut child in old_children {
63            if child.borrow().repeated.is_some() {
64                let root_elem = child.borrow().base_type.as_component().root_element.clone();
65                if has_property_binding(&root_elem) {
66                    object_tree::inject_element_as_repeated_element(
67                        &child,
68                        create_property_element(
69                            &root_elem,
70                            property_names.clone().chain(extra_properties.clone()),
71                            default_value_for_extra_properties,
72                            element_name,
73                            type_register,
74                        ),
75                    )
76                }
77            } else if has_property_binding(&child) {
78                let new_child = create_property_element(
79                    &child,
80                    property_names.clone().chain(extra_properties.clone()),
81                    default_value_for_extra_properties,
82                    element_name,
83                    type_register,
84                );
85                crate::object_tree::adjust_geometry_for_injected_parent(&new_child, &child);
86                new_child.borrow_mut().children.push(child);
87                child = new_child;
88            }
89
90            elem.borrow_mut().children.push(child);
91        }
92    });
93}
94
95fn create_property_element(
96    child: &ElementRc,
97    properties: impl Iterator<Item = &'static str>,
98    default_value_for_extra_properties: Option<&dyn Fn(&ElementRc, &str) -> Option<Expression>>,
99    element_name: &SmolStr,
100    type_register: &TypeRegister,
101) -> ElementRc {
102    let bindings = properties
103        .map(|property_name| {
104            let mut bind =
105                BindingExpression::new_two_way(NamedReference::new(child, property_name.into()));
106            if let Some(default_value_for_extra_properties) = default_value_for_extra_properties {
107                if !child.borrow().bindings.contains_key(property_name) {
108                    if let Some(e) = default_value_for_extra_properties(child, property_name) {
109                        bind.expression = e;
110                    }
111                }
112            }
113            (property_name.into(), bind.into())
114        })
115        .collect();
116
117    let element = Element {
118        id: format_smolstr!("{}-{}", child.borrow().id, element_name),
119        base_type: type_register.lookup_element(element_name).unwrap(),
120        enclosing_component: child.borrow().enclosing_component.clone(),
121        bindings,
122        ..Default::default()
123    };
124    element.make_rc()
125}
126
127/// Wrapper around lower_property_to_element for the Transform element
128pub fn lower_transform_properties(
129    component: &Rc<Component>,
130    tr: &TypeRegister,
131    diag: &mut BuildDiagnostics,
132) {
133    let transform_origin = crate::typeregister::transform_origin_property();
134
135    lower_property_to_element(
136        component,
137        crate::typeregister::RESERVED_TRANSFORM_PROPERTIES.iter().map(|(prop_name, _)| *prop_name),
138        std::iter::once(transform_origin.0),
139        Some(&|e, prop| {
140            let prop_div_2 = |prop: &str| Expression::BinaryExpression {
141                lhs: Expression::PropertyReference(NamedReference::new(e, prop.into())).into(),
142                op: '/',
143                rhs: Expression::NumberLiteral(2., Default::default()).into(),
144            };
145
146            match prop {
147                "transform-origin" => Some(Expression::Struct {
148                    ty: transform_origin.1.clone(),
149                    values: [
150                        (SmolStr::new_static("x"), prop_div_2("width")),
151                        (SmolStr::new_static("y"), prop_div_2("height")),
152                    ]
153                    .into_iter()
154                    .collect(),
155                }),
156                "transform-scale-x" | "transform-scale-y" => {
157                    if e.borrow().is_binding_set("transform-scale", true) {
158                        Some(Expression::PropertyReference(NamedReference::new(
159                            e,
160                            SmolStr::new_static("transform-scale"),
161                        )))
162                    } else {
163                        Some(Expression::NumberLiteral(1., Default::default()))
164                    }
165                }
166                "transform-scale" => None,
167                "transform-rotation" => Some(Expression::NumberLiteral(0., Default::default())),
168                _ => unreachable!(),
169            }
170        }),
171        &SmolStr::new_static("Transform"),
172        tr,
173        diag,
174    );
175}