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().binding(CLOSE_ON_CLICK) {
52 if popup_window_element.borrow().binding(CLOSE_POLICY).is_some() {
53 diag.push_error(
54 "close-policy and close-on-click cannot be set at the same time".into(),
55 &binding.span,
56 );
57 } else {
58 diag.push_property_deprecation_warning(CLOSE_ON_CLICK, CLOSE_POLICY, &binding.span);
59 if !matches!(binding.value_expression(), Expression::BoolLiteral(_)) {
60 report_const_error(CLOSE_ON_CLICK, &binding.span, diag);
61 }
62 }
63 } else if let Some(binding) = popup_window_element.borrow().binding(CLOSE_POLICY)
64 && !matches!(binding.value_expression(), Expression::EnumerationValue(_))
65 {
66 report_const_error(CLOSE_POLICY, &binding.span, diag);
67 }
68
69 let parent_component = popup_window_element.borrow().enclosing_component.upgrade().unwrap();
70 let parent_element = match parent_element {
71 None => {
72 if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
73 popup_window_element.borrow_mut().base_type = window_type.clone();
74 }
75 parent_component.inherits_popup_window.set(true);
76 return;
77 }
78 Some(parent_element) => parent_element,
79 };
80
81 if Rc::ptr_eq(&parent_component.root_element, popup_window_element) {
82 diag.push_error(
83 "PopupWindow cannot be directly repeated or conditional".into(),
84 &*popup_window_element.borrow(),
85 );
86 return;
87 }
88
89 let mut parent_element_borrowed = parent_element.borrow_mut();
91 let index = parent_element_borrowed
92 .children
93 .iter()
94 .position(|child| Rc::ptr_eq(child, popup_window_element))
95 .expect("PopupWindow must be a child of its parent");
96 parent_element_borrowed.children.remove(index);
97 parent_element_borrowed.has_popup_child = true;
98 drop(parent_element_borrowed);
99 for parent_cip in parent_component.child_insertion_points.borrow_mut().values_mut() {
100 if Rc::ptr_eq(&parent_cip.parent, parent_element) && parent_cip.insertion_index > index {
101 parent_cip.insertion_index -= 1;
102 }
103 }
104
105 let map_close_on_click_value = |b: &BindingExpression| {
106 let Expression::BoolLiteral(v) = b.expression.ignore_debug_hooks() else {
107 assert!(diag.has_errors());
108 return None;
109 };
110 let enum_ty = crate::typeregister::BUILTIN.enums.PopupClosePolicy.clone();
111 let s = if *v { "close-on-click" } else { "no-auto-close" };
112 Some(EnumerationValue {
113 value: enum_ty.values.iter().position(|v| v == s).unwrap(),
114 enumeration: enum_ty,
115 })
116 };
117
118 let close_policy = popup_window_element.borrow_mut().take_binding(CLOSE_POLICY).and_then(|b| {
119 if let Expression::EnumerationValue(v) = b.expression.ignore_debug_hooks() {
120 Some(v.clone())
121 } else {
122 assert!(diag.has_errors());
123 None
124 }
125 });
126 let close_policy = close_policy
127 .or_else(|| {
128 popup_window_element
129 .borrow_mut()
130 .take_binding(CLOSE_ON_CLICK)
131 .and_then(|b| map_close_on_click_value(&b))
132 })
133 .or_else(|| {
134 let mut base = popup_window_element.borrow().base_type.clone();
136 while let ElementType::Component(b) = base {
137 let base_policy = b
138 .root_element
139 .borrow()
140 .binding(CLOSE_POLICY)
141 .and_then(|b| {
142 if let Expression::EnumerationValue(v) = b.value_expression() {
143 return Some(v.clone());
144 }
145 assert!(diag.has_errors());
146 None
147 })
148 .or_else(|| {
149 b.root_element
150 .borrow()
151 .binding(CLOSE_ON_CLICK)
152 .and_then(|b| map_close_on_click_value(&b))
153 });
154 if let Some(base_policy) = base_policy {
155 return Some(base_policy);
156 }
157 base = b.root_element.borrow().base_type.clone();
158 }
159 None
160 })
161 .unwrap_or_else(|| EnumerationValue {
162 value: 0,
163 enumeration: crate::typeregister::BUILTIN.enums.PopupClosePolicy.clone(),
164 });
165
166 let popup_comp = Rc::new(Component {
167 root_element: popup_window_element.clone(),
168 parent_element: RefCell::new(Rc::downgrade(parent_element)),
169 ..Component::default()
170 });
171
172 let weak = Rc::downgrade(&popup_comp);
173 recurse_elem(&popup_comp.root_element, &(), &mut |e, _| {
174 e.borrow_mut().enclosing_component = weak.clone()
175 });
176
177 let is_open = {
183 let mut referenced = false;
184 visit_all_named_references(&parent_component, &mut |nr| {
185 if Rc::ptr_eq(&nr.element(), popup_window_element) && nr.name() == "is-open" {
186 referenced = true;
187 }
188 });
189 if referenced {
190 let is_open_ref = crate::layout::create_new_prop(
192 &parent_component.root_element,
193 format_smolstr!("popup-{}-is-open", popup_window_element.borrow().id),
194 Type::Bool,
195 );
196 is_open_ref.mark_as_set();
199 let target = is_open_ref.clone();
200 visit_all_named_references(&parent_component, &mut |nr| {
201 if Rc::ptr_eq(&nr.element(), popup_window_element) && nr.name() == "is-open" {
202 *nr = target.clone();
203 }
204 });
205 Some(is_open_ref)
206 } else {
207 None
208 }
209 };
210
211 let coord_x = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("x"));
214 let coord_y = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("y"));
215
216 {
219 let mut popup_mut = popup_comp.root_element.borrow_mut();
220 let name = format_smolstr!("popup-{}-dummy", popup_mut.id);
221 popup_mut.property_declarations.insert(name.clone(), Type::LogicalLength.into());
222 drop(popup_mut);
223 let dummy1 = NamedReference::new(&popup_comp.root_element, name.clone());
224 let dummy2 = NamedReference::new(&popup_comp.root_element, name.clone());
225 let mut popup_mut = popup_comp.root_element.borrow_mut();
226 popup_mut.geometry_props.as_mut().unwrap().x = dummy1;
227 popup_mut.geometry_props.as_mut().unwrap().y = dummy2;
228 }
229
230 check_no_reference_to_popup(popup_window_element, &parent_component, &weak, &coord_x, diag);
231
232 if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
233 popup_window_element.borrow_mut().base_type = window_type.clone();
234 }
235
236 super::focus_handling::call_focus_on_init(&popup_comp);
237
238 parent_component.popup_windows.borrow_mut().push(PopupWindow {
239 component: popup_comp,
240 x: coord_x,
241 y: coord_y,
242 close_policy,
243 parent_element: parent_element.clone(),
244 is_tooltip: popup_window_element.borrow().is_tooltip,
245 is_open,
246 });
247}
248
249fn report_const_error(prop: &str, span: &Option<SourceLocation>, diag: &mut BuildDiagnostics) {
250 diag.push_error(format!("The {prop} property only supports constants at the moment"), span);
251}
252
253pub fn check_no_reference_to_popup(
259 popup_window_element: &ElementRc,
260 parent_component: &Rc<Component>,
261 new_weak: &Weak<Component>,
262 random_valid_ref: &NamedReference,
263 diag: &mut BuildDiagnostics,
264) {
265 visit_all_named_references(parent_component, &mut |nr| {
266 let element = &nr.element();
267 if check_element(element, new_weak, diag, popup_window_element, nr.name()) {
268 *nr = random_valid_ref.clone();
270 }
271 });
272 visit_all_expressions(parent_component, |exp, _| {
273 exp.visit_recursive_mut(&mut |exp| {
274 if let Expression::ElementReference(element) = exp {
275 let elem = element.upgrade().unwrap();
276 if !Rc::ptr_eq(&elem, popup_window_element) {
277 check_element(&elem, new_weak, diag, popup_window_element, "");
278 }
279 }
280 });
281 });
282}
283
284fn check_element(
285 element: &ElementRc,
286 popup_comp: &Weak<Component>,
287 diag: &mut BuildDiagnostics,
288 popup_window_element: &ElementRc,
289 prop_name: &str,
290) -> bool {
291 if Weak::ptr_eq(&element.borrow().enclosing_component, popup_comp) {
292 let element_name = popup_window_element
293 .borrow()
294 .builtin_type()
295 .map(|t| t.name.clone())
296 .unwrap_or_else(|| SmolStr::new_static("PopupWindow"));
297 let id = element.borrow().id.clone();
298 let what = if prop_name.is_empty() {
299 if id.is_empty() { "something".into() } else { format!("element '{id}'") }
300 } else if id.is_empty() {
301 format!("property or callback '{prop_name}'")
302 } else {
303 format!("property or callback '{id}.{prop_name}'")
304 };
305
306 diag.push_error(
307 format!("Cannot access {what} inside of a {element_name} from enclosing component"),
308 &*popup_window_element.borrow(),
309 );
310 true
311 } else {
312 false
313 }
314}