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