1use crate::diagnostics::Spanned;
9use crate::expression_tree::{BindingExpression, Expression, Unit};
10use crate::langtype::{ElementType, PropertyLookupMode, 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 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().real_bindings().map(|(name, _)| name) {
43 let nr = NamedReference::new(elem, prop.clone());
44 if let std::collections::hash_map::Entry::Vacant(entry) = 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 entry.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(
66 nr.name().clone(),
67 PropertyDeclaration { property_type: ty, ..PropertyDeclaration::default() },
68 );
69
70 if !must_initialize(&elem.borrow(), nr.name()) {
71 continue;
75 }
76 if let Some(init_expr) = initialize(&elem, nr.name()) {
77 let mut elem_mut = elem.borrow_mut();
78 if let Some(cell) = elem_mut.binding_cell_including_synthetic(nr.name()) {
79 cell.borrow_mut().set_value_expression(init_expr);
83 } else {
84 let span = elem_mut.to_source_location();
85 let mut binding = BindingExpression::new_with_span(init_expr, span);
86 binding.priority = i32::MAX;
87 elem_mut.set_binding(nr.name().clone(), binding);
88 }
89 }
90 }
91}
92
93fn must_initialize(elem: &Element, prop: &str) -> bool {
95 match elem.binding(prop) {
96 None => true,
97 Some(b) => {
98 matches!(b.value_expression(), Expression::Invalid)
99 }
100 }
101}
102
103pub(crate) fn should_materialize(
105 property_declarations: &BTreeMap<SmolStr, PropertyDeclaration>,
106 base_type: &ElementType,
107 prop: &str,
108) -> Option<Type> {
109 if property_declarations.contains_key(prop) {
110 return None;
111 }
112 let has_declared_property = match base_type {
113 ElementType::Component(c) => has_declared_property(&c.root_element.borrow(), prop),
114 ElementType::Builtin(b) => b.native_class.lookup_property(prop).is_some(),
115 ElementType::Native(n) => n.lookup_property(prop).is_some(),
116 ElementType::Global | ElementType::Interface | ElementType::Error => false,
117 };
118
119 if !has_declared_property {
120 let ty = crate::typeregister::reserved_property(Cow::Borrowed(prop)).property_type.clone();
121 if ty != Type::Invalid {
122 return Some(ty);
123 } else if prop == "close-on-click" {
124 return Some(Type::Bool);
126 } else if prop == "close-policy" {
127 return Some(Type::Enumeration(
129 crate::typeregister::BUILTIN.enums.PopupClosePolicy.clone(),
130 ));
131 } else {
132 let ty = base_type
133 .lookup_property(prop, PropertyLookupMode::InternalName)
134 .property_type
135 .clone();
136 return (ty != Type::Invalid).then_some(ty);
137 }
138 }
139 None
140}
141
142pub fn has_declared_property(elem: &Element, prop: &str) -> bool {
145 if elem.property_declarations.contains_key(prop) {
146 return true;
147 }
148 match &elem.base_type {
149 ElementType::Component(c) => has_declared_property(&c.root_element.borrow(), prop),
150 ElementType::Builtin(b) => b.native_class.lookup_property(prop).is_some(),
151 ElementType::Native(n) => n.lookup_property(prop).is_some(),
152 ElementType::Global | ElementType::Interface | ElementType::Error => false,
153 }
154}
155
156pub fn initialize(elem: &ElementRc, name: &str) -> Option<Expression> {
158 let mut base_type = elem.borrow().base_type.clone();
159 loop {
160 base_type = match base_type {
161 ElementType::Component(ref c) => c.root_element.borrow().base_type.clone(),
162 ElementType::Builtin(b) => {
163 match b.properties.get(name).and_then(|prop| prop.default_value.expr(elem)) {
164 Some(expr) => return Some(expr),
165 None => break,
166 }
167 }
168 _ => break,
169 };
170 }
171
172 if elem.borrow().builtin_type().is_some_and(|n| n.name == "Image") {
177 if elem.borrow().effective_layout_info_prop(Orientation::Horizontal).is_none() {
178 match name {
179 "min-width" => return Some(Expression::NumberLiteral(0., Unit::Px)),
180 "max-width" => return Some(Expression::NumberLiteral(f32::MAX as _, Unit::Px)),
181 "horizontal-stretch" => return Some(Expression::NumberLiteral(0., Unit::None)),
182 _ => {}
183 }
184 }
185
186 if elem.borrow().effective_layout_info_prop(Orientation::Vertical).is_none() {
187 match name {
188 "min-height" => return Some(Expression::NumberLiteral(0., Unit::Px)),
189 "max-height" => return Some(Expression::NumberLiteral(f32::MAX as _, Unit::Px)),
190 "vertical-stretch" => return Some(Expression::NumberLiteral(0., Unit::None)),
191 _ => {}
192 }
193 }
194 }
195
196 let expr = match name {
197 "min-height" => layout_constraint_prop(elem, "min", Orientation::Vertical),
198 "min-width" => layout_constraint_prop(elem, "min", Orientation::Horizontal),
199 "max-height" => layout_constraint_prop(elem, "max", Orientation::Vertical),
200 "max-width" => layout_constraint_prop(elem, "max", Orientation::Horizontal),
201 "horizontal-stretch" => layout_constraint_prop(elem, "stretch", Orientation::Horizontal),
202 "vertical-stretch" => layout_constraint_prop(elem, "stretch", Orientation::Vertical),
203 "preferred-height" => layout_constraint_prop(elem, "preferred", Orientation::Vertical),
204 "preferred-width" => layout_constraint_prop(elem, "preferred", Orientation::Horizontal),
205 "opacity" => Expression::NumberLiteral(1., Unit::None),
206 "visible" => Expression::BoolLiteral(true),
207 "rowspan" => Expression::NumberLiteral(1., Unit::None),
208 "colspan" => Expression::NumberLiteral(1., Unit::None),
209 "rotation-origin-x" => size_div_2(elem, "width"),
210 "rotation-origin-y" => size_div_2(elem, "height"),
211 _ => return None,
212 };
213 Some(expr)
214}
215
216fn layout_constraint_prop(elem: &ElementRc, field: &str, orient: Orientation) -> Expression {
217 let expr = match elem.borrow().effective_layout_info_prop(orient) {
218 Some(e) => Expression::PropertyReference(e.clone()),
219 None => crate::layout::implicit_layout_info_call(
220 elem,
221 orient,
222 crate::layout::BuiltinFilter::All,
223 None,
224 )
225 .unwrap(),
226 };
227 Expression::StructFieldAccess { base: expr.into(), name: field.into() }
228}
229
230fn size_div_2(elem: &ElementRc, field: &str) -> Expression {
231 Expression::BinaryExpression {
232 lhs: Expression::PropertyReference(NamedReference::new(elem, field.into())).into(),
233 op: '/',
234 rhs: Expression::NumberLiteral(2., Unit::None).into(),
235 source_location: None,
236 }
237}