1use crate::diagnostics::{BuildDiagnostics, SourceLocation};
7use crate::expression_tree::{BindingExpression, Expression, NamedReference};
8use crate::langtype::{ElementType, EnumerationValue, Type};
9use crate::object_tree::*;
10use crate::typeregister::TypeRegister;
11use smol_str::{SmolStr, format_smolstr};
12use std::rc::{Rc, Weak};
13
14const CLOSE_ON_CLICK: &str = "close-on-click";
15const CLOSE_POLICY: &str = "close-policy";
16
17pub fn lower_popups(
18 component: &Rc<Component>,
19 type_register: &TypeRegister,
20 diag: &mut BuildDiagnostics,
21) {
22 let window_type = type_register.lookup_builtin_element("Window").unwrap();
23
24 recurse_elem_including_sub_components_no_borrow(
25 component,
26 &None,
27 &mut |elem, parent_element: &Option<ElementRc>| {
28 if is_popup_window(elem) {
29 lower_popup_window(elem, parent_element.as_ref(), &window_type, diag);
30 }
31 Some(elem.clone())
32 },
33 )
34}
35
36pub fn is_popup_window(element: &ElementRc) -> bool {
37 match &element.borrow().base_type {
38 ElementType::Builtin(base_type) => base_type.name == "PopupWindow",
39 ElementType::Component(base_type) => base_type.inherits_popup_window.get(),
40 _ => false,
41 }
42}
43
44fn lower_popup_window(
45 popup_window_element: &ElementRc,
46 parent_element: Option<&ElementRc>,
47 window_type: &ElementType,
48 diag: &mut BuildDiagnostics,
49) {
50 if let Some(binding) = popup_window_element.borrow().bindings.get(CLOSE_ON_CLICK) {
51 if popup_window_element.borrow().bindings.contains_key(CLOSE_POLICY) {
52 diag.push_error(
53 "close-policy and close-on-click cannot be set at the same time".into(),
54 &binding.borrow().span,
55 );
56 } else {
57 diag.push_property_deprecation_warning(
58 CLOSE_ON_CLICK,
59 CLOSE_POLICY,
60 &binding.borrow().span,
61 );
62 if !matches!(
63 super::ignore_debug_hooks(&binding.borrow().expression),
64 Expression::BoolLiteral(_)
65 ) {
66 report_const_error(CLOSE_ON_CLICK, &binding.borrow().span, diag);
67 }
68 }
69 } else if let Some(binding) = popup_window_element.borrow().bindings.get(CLOSE_POLICY)
70 && !matches!(
71 super::ignore_debug_hooks(&binding.borrow().expression),
72 Expression::EnumerationValue(_)
73 )
74 {
75 report_const_error(CLOSE_POLICY, &binding.borrow().span, diag);
76 }
77
78 let parent_component = popup_window_element.borrow().enclosing_component.upgrade().unwrap();
79 let parent_element = match parent_element {
80 None => {
81 if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
82 popup_window_element.borrow_mut().base_type = window_type.clone();
83 }
84 parent_component.inherits_popup_window.set(true);
85 return;
86 }
87 Some(parent_element) => parent_element,
88 };
89
90 if Rc::ptr_eq(&parent_component.root_element, popup_window_element) {
91 diag.push_error(
92 "PopupWindow cannot be directly repeated or conditional".into(),
93 &*popup_window_element.borrow(),
94 );
95 return;
96 }
97
98 let mut parent_element_borrowed = parent_element.borrow_mut();
100 let index = parent_element_borrowed
101 .children
102 .iter()
103 .position(|child| Rc::ptr_eq(child, popup_window_element))
104 .expect("PopupWindow must be a child of its parent");
105 parent_element_borrowed.children.remove(index);
106 parent_element_borrowed.has_popup_child = true;
107 drop(parent_element_borrowed);
108 if let Some(parent_cip) = &mut *parent_component.child_insertion_point.borrow_mut()
109 && Rc::ptr_eq(&parent_cip.parent, parent_element)
110 && parent_cip.insertion_index > index
111 {
112 parent_cip.insertion_index -= 1;
113 }
114
115 let map_close_on_click_value = |b: &BindingExpression| {
116 let Expression::BoolLiteral(v) = super::ignore_debug_hooks(&b.expression) else {
117 assert!(diag.has_errors());
118 return None;
119 };
120 let enum_ty = crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone());
121 let s = if *v { "close-on-click" } else { "no-auto-close" };
122 Some(EnumerationValue {
123 value: enum_ty.values.iter().position(|v| v == s).unwrap(),
124 enumeration: enum_ty,
125 })
126 };
127
128 let close_policy =
129 popup_window_element.borrow_mut().bindings.remove(CLOSE_POLICY).and_then(|b| {
130 let b = b.into_inner();
131 if let Expression::EnumerationValue(v) = super::ignore_debug_hooks(&b.expression) {
132 Some(v.clone())
133 } else {
134 assert!(diag.has_errors());
135 None
136 }
137 });
138 let close_policy = close_policy
139 .or_else(|| {
140 popup_window_element
141 .borrow_mut()
142 .bindings
143 .remove(CLOSE_ON_CLICK)
144 .and_then(|b| map_close_on_click_value(&b.borrow()))
145 })
146 .or_else(|| {
147 let mut base = popup_window_element.borrow().base_type.clone();
149 while let ElementType::Component(b) = base {
150 let base_policy = b
151 .root_element
152 .borrow()
153 .bindings
154 .get(CLOSE_POLICY)
155 .and_then(|b| {
156 let b = b.borrow();
157 if let Expression::EnumerationValue(v) = &b.expression {
158 return Some(v.clone());
159 }
160 assert!(diag.has_errors());
161 None
162 })
163 .or_else(|| {
164 b.root_element
165 .borrow()
166 .bindings
167 .get(CLOSE_ON_CLICK)
168 .and_then(|b| map_close_on_click_value(&b.borrow()))
169 });
170 if let Some(base_policy) = base_policy {
171 return Some(base_policy);
172 }
173 base = b.root_element.borrow().base_type.clone();
174 }
175 None
176 })
177 .unwrap_or_else(|| EnumerationValue {
178 value: 0,
179 enumeration: crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone()),
180 });
181
182 let popup_comp = Rc::new(Component {
183 root_element: popup_window_element.clone(),
184 parent_element: Rc::downgrade(parent_element),
185 ..Component::default()
186 });
187
188 let weak = Rc::downgrade(&popup_comp);
189 recurse_elem(&popup_comp.root_element, &(), &mut |e, _| {
190 e.borrow_mut().enclosing_component = weak.clone()
191 });
192
193 let coord_x = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("x"));
196 let coord_y = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("y"));
197
198 {
201 let mut popup_mut = popup_comp.root_element.borrow_mut();
202 let name = format_smolstr!("popup-{}-dummy", popup_mut.id);
203 popup_mut.property_declarations.insert(name.clone(), Type::LogicalLength.into());
204 drop(popup_mut);
205 let dummy1 = NamedReference::new(&popup_comp.root_element, name.clone());
206 let dummy2 = NamedReference::new(&popup_comp.root_element, name.clone());
207 let mut popup_mut = popup_comp.root_element.borrow_mut();
208 popup_mut.geometry_props.as_mut().unwrap().x = dummy1;
209 popup_mut.geometry_props.as_mut().unwrap().y = dummy2;
210 }
211
212 check_no_reference_to_popup(popup_window_element, &parent_component, &weak, &coord_x, diag);
213
214 if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
215 popup_window_element.borrow_mut().base_type = window_type.clone();
216 }
217
218 super::focus_handling::call_focus_on_init(&popup_comp);
219
220 parent_component.popup_windows.borrow_mut().push(PopupWindow {
221 component: popup_comp,
222 x: coord_x,
223 y: coord_y,
224 close_policy,
225 parent_element: parent_element.clone(),
226 });
227}
228
229fn report_const_error(prop: &str, span: &Option<SourceLocation>, diag: &mut BuildDiagnostics) {
230 diag.push_error(format!("The {prop} property only supports constants at the moment"), span);
231}
232
233pub fn check_no_reference_to_popup(
239 popup_window_element: &ElementRc,
240 parent_component: &Rc<Component>,
241 new_weak: &Weak<Component>,
242 random_valid_ref: &NamedReference,
243 diag: &mut BuildDiagnostics,
244) {
245 visit_all_named_references(parent_component, &mut |nr| {
246 let element = &nr.element();
247 if check_element(element, new_weak, diag, popup_window_element, nr.name()) {
248 *nr = random_valid_ref.clone();
250 }
251 });
252 visit_all_expressions(parent_component, |exp, _| {
253 exp.visit_recursive_mut(&mut |exp| {
254 if let Expression::ElementReference(element) = exp {
255 let elem = element.upgrade().unwrap();
256 if !Rc::ptr_eq(&elem, popup_window_element) {
257 check_element(&elem, new_weak, diag, popup_window_element, &"");
258 }
259 }
260 });
261 });
262}
263
264fn check_element(
265 element: &ElementRc,
266 popup_comp: &Weak<Component>,
267 diag: &mut BuildDiagnostics,
268 popup_window_element: &ElementRc,
269 prop_name: &str,
270) -> bool {
271 if Weak::ptr_eq(&element.borrow().enclosing_component, popup_comp) {
272 let element_name = popup_window_element
273 .borrow()
274 .builtin_type()
275 .map(|t| t.name.clone())
276 .unwrap_or_else(|| SmolStr::new_static("PopupWindow"));
277 let id = element.borrow().id.clone();
278 let what = if prop_name.is_empty() {
279 if id.is_empty() { "something".into() } else { format!("element '{id}'") }
280 } else if id.is_empty() {
281 format!("property or callback '{prop_name}'")
282 } else {
283 format!("property or callback '{id}.{prop_name}'")
284 };
285
286 diag.push_error(
287 format!("Cannot access {what} inside of a {element_name} from enclosing component"),
288 &*popup_window_element.borrow(),
289 );
290 true
291 } else {
292 false
293 }
294}