1use 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 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 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
86fn 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
94fn 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 return Some(Type::Bool);
117 } else if prop == "close-policy" {
118 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
130pub 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
144pub 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 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}