Skip to main content

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