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