i_slint_compiler/passes/
materialize_fake_properties.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 creates properties that are used but are otherwise not real.
5//!
6//! Must be run after lower_layout and default_geometry passes
7
8use crate::diagnostics::Spanned;
9use crate::expression_tree::{BindingExpression, Expression, Unit};
10use crate::langtype::{ElementType, Type};
11use crate::layout::Orientation;
12use crate::namedreference::NamedReference;
13use crate::object_tree::*;
14use smol_str::SmolStr;
15use std::borrow::Cow;
16use std::collections::BTreeMap;
17use std::rc::Rc;
18
19pub fn materialize_fake_properties(component: &Rc<Component>) {
20    let mut to_materialize = std::collections::HashMap::new();
21
22    visit_all_named_references(component, &mut |nr| {
23        let elem = nr.element();
24        let elem = elem.borrow();
25        if !to_materialize.contains_key(nr) {
26            if let Some(ty) =
27                should_materialize(&elem.property_declarations, &elem.base_type, nr.name())
28            {
29                to_materialize.insert(nr.clone(), ty);
30            }
31        }
32    });
33
34    recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
35        for prop in elem.borrow().bindings.keys() {
36            let nr = NamedReference::new(elem, prop.clone());
37            if let std::collections::hash_map::Entry::Vacant(e) = to_materialize.entry(nr) {
38                let elem = elem.borrow();
39                if let Some(ty) =
40                    should_materialize(&elem.property_declarations, &elem.base_type, prop)
41                {
42                    e.insert(ty);
43                }
44            }
45        }
46    });
47
48    for (nr, ty) in to_materialize {
49        let elem = nr.element();
50
51        elem.borrow_mut().property_declarations.insert(
52            nr.name().clone(),
53            PropertyDeclaration { property_type: ty, ..PropertyDeclaration::default() },
54        );
55
56        if !must_initialize(&elem.borrow(), nr.name()) {
57            // One must check again if one really need to be initialized, because when
58            // we checked the first time, the element's binding were temporarily moved
59            // by visit_all_named_references_in_element
60            continue;
61        }
62        if let Some(init_expr) = initialize(&elem, nr.name()) {
63            let mut elem_mut = elem.borrow_mut();
64            let span = elem_mut.to_source_location();
65            match elem_mut.bindings.entry(nr.name().clone()) {
66                std::collections::btree_map::Entry::Vacant(e) => {
67                    let mut binding = BindingExpression::new_with_span(init_expr, span);
68                    binding.priority = i32::MAX;
69                    e.insert(binding.into());
70                }
71                std::collections::btree_map::Entry::Occupied(mut e) => {
72                    e.get_mut().get_mut().expression = init_expr;
73                }
74            }
75        }
76    }
77}
78
79// One must initialize if there is an actual expression for that binding
80fn must_initialize(elem: &Element, prop: &str) -> bool {
81    match elem.bindings.get(prop) {
82        None => true,
83        Some(b) => matches!(b.borrow().expression, Expression::Invalid),
84    }
85}
86
87/// Returns a type if the property needs to be materialized.
88fn should_materialize(
89    property_declarations: &BTreeMap<SmolStr, PropertyDeclaration>,
90    base_type: &ElementType,
91    prop: &str,
92) -> Option<Type> {
93    if property_declarations.contains_key(prop) {
94        return None;
95    }
96    let has_declared_property = match base_type {
97        ElementType::Component(c) => has_declared_property(&c.root_element.borrow(), prop),
98        ElementType::Builtin(b) => b.native_class.lookup_property(prop).is_some(),
99        ElementType::Native(n) => n.lookup_property(prop).is_some(),
100        ElementType::Global | ElementType::Error => false,
101    };
102
103    if !has_declared_property {
104        let ty = crate::typeregister::reserved_property(Cow::Borrowed(prop)).property_type.clone();
105        if ty != Type::Invalid {
106            return Some(ty);
107        } else if prop == "close-on-click" {
108            // PopupWindow::close-on-click
109            return Some(Type::Bool);
110        } else if prop == "close-policy" {
111            // PopupWindow::close-policy
112            return Some(Type::Enumeration(
113                crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone()),
114            ));
115        } else {
116            let ty = base_type.lookup_property(prop).property_type.clone();
117            return (ty != Type::Invalid).then_some(ty);
118        }
119    }
120    None
121}
122
123/// Returns true if the property is declared in this element or parent
124/// (as opposed to being implicitly declared)
125pub fn has_declared_property(elem: &Element, prop: &str) -> bool {
126    if elem.property_declarations.contains_key(prop) {
127        return true;
128    }
129    match &elem.base_type {
130        ElementType::Component(c) => has_declared_property(&c.root_element.borrow(), prop),
131        ElementType::Builtin(b) => b.native_class.lookup_property(prop).is_some(),
132        ElementType::Native(n) => n.lookup_property(prop).is_some(),
133        ElementType::Global | ElementType::Error => false,
134    }
135}
136
137/// Initialize a sensible default binding for the now materialized property
138pub fn initialize(elem: &ElementRc, name: &str) -> Option<Expression> {
139    let mut base_type = elem.borrow().base_type.clone();
140    loop {
141        base_type = match base_type {
142            ElementType::Component(ref c) => c.root_element.borrow().base_type.clone(),
143            ElementType::Builtin(b) => {
144                match b.properties.get(name).and_then(|prop| prop.default_value.expr(elem)) {
145                    Some(expr) => return Some(expr),
146                    None => break,
147                }
148            }
149            _ => break,
150        };
151    }
152
153    // Hardcode properties for images, because this is a very common call, and this allows
154    // later optimization steps to eliminate these properties.
155    // Note that Rectangles and Empties are similarly optimized in layout_constraint_prop, and
156    // we rely on struct field access simplification for those.
157    if elem.borrow().builtin_type().map_or(false, |n| n.name == "Image") {
158        if elem.borrow().layout_info_prop(Orientation::Horizontal).is_none() {
159            match name {
160                "min-width" => return Some(Expression::NumberLiteral(0., Unit::Px)),
161                "max-width" => return Some(Expression::NumberLiteral(f32::MAX as _, Unit::Px)),
162                "horizontal-stretch" => return Some(Expression::NumberLiteral(0., Unit::None)),
163                _ => {}
164            }
165        }
166
167        if elem.borrow().layout_info_prop(Orientation::Vertical).is_none() {
168            match name {
169                "min-height" => return Some(Expression::NumberLiteral(0., Unit::Px)),
170                "max-height" => return Some(Expression::NumberLiteral(f32::MAX as _, Unit::Px)),
171                "vertical-stretch" => return Some(Expression::NumberLiteral(0., Unit::None)),
172                _ => {}
173            }
174        }
175    }
176
177    let expr = match name {
178        "min-height" => layout_constraint_prop(elem, "min", Orientation::Vertical),
179        "min-width" => layout_constraint_prop(elem, "min", Orientation::Horizontal),
180        "max-height" => layout_constraint_prop(elem, "max", Orientation::Vertical),
181        "max-width" => layout_constraint_prop(elem, "max", Orientation::Horizontal),
182        "horizontal-stretch" => layout_constraint_prop(elem, "stretch", Orientation::Horizontal),
183        "vertical-stretch" => layout_constraint_prop(elem, "stretch", Orientation::Vertical),
184        "preferred-height" => layout_constraint_prop(elem, "preferred", Orientation::Vertical),
185        "preferred-width" => layout_constraint_prop(elem, "preferred", Orientation::Horizontal),
186        "opacity" => Expression::NumberLiteral(1., Unit::None),
187        "visible" => Expression::BoolLiteral(true),
188        "rowspan" => Expression::NumberLiteral(1., Unit::None),
189        "colspan" => Expression::NumberLiteral(1., Unit::None),
190        "rotation-origin-x" => size_div_2(elem, "width"),
191        "rotation-origin-y" => size_div_2(elem, "height"),
192        _ => return None,
193    };
194    Some(expr)
195}
196
197fn layout_constraint_prop(elem: &ElementRc, field: &str, orient: Orientation) -> Expression {
198    let expr = match elem.borrow().layout_info_prop(orient) {
199        Some(e) => Expression::PropertyReference(e.clone()),
200        None => crate::layout::implicit_layout_info_call(elem, orient),
201    };
202    Expression::StructFieldAccess { base: expr.into(), name: field.into() }
203}
204
205fn size_div_2(elem: &ElementRc, field: &str) -> Expression {
206    Expression::BinaryExpression {
207        lhs: Expression::PropertyReference(NamedReference::new(elem, field.into())).into(),
208        op: '/',
209        rhs: Expression::NumberLiteral(2., Unit::None).into(),
210    }
211}