i_slint_compiler/passes/
lower_popups.rs1use 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::{format_smolstr, 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 if !matches!(
71 super::ignore_debug_hooks(&binding.borrow().expression),
72 Expression::EnumerationValue(_)
73 ) {
74 report_const_error(CLOSE_POLICY, &binding.borrow().span, diag);
75 }
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 if Rc::ptr_eq(&parent_cip.parent, parent_element) && parent_cip.insertion_index > index {
110 parent_cip.insertion_index -= 1;
111 }
112 }
113
114 if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
115 popup_window_element.borrow_mut().base_type = window_type.clone();
116 }
117
118 let map_close_on_click_value = |b: &BindingExpression| {
119 let Expression::BoolLiteral(v) = super::ignore_debug_hooks(&b.expression) else {
120 assert!(diag.has_errors());
121 return None;
122 };
123 let enum_ty = crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone());
124 let s = if *v { "close-on-click" } else { "no-auto-close" };
125 Some(EnumerationValue {
126 value: enum_ty.values.iter().position(|v| v == s).unwrap(),
127 enumeration: enum_ty,
128 })
129 };
130
131 let close_policy =
132 popup_window_element.borrow_mut().bindings.remove(CLOSE_POLICY).and_then(|b| {
133 let b = b.into_inner();
134 if let Expression::EnumerationValue(v) = super::ignore_debug_hooks(&b.expression) {
135 Some(v.clone())
136 } else {
137 assert!(diag.has_errors());
138 None
139 }
140 });
141 let close_policy = close_policy
142 .or_else(|| {
143 popup_window_element
144 .borrow_mut()
145 .bindings
146 .remove(CLOSE_ON_CLICK)
147 .and_then(|b| map_close_on_click_value(&b.borrow()))
148 })
149 .or_else(|| {
150 let mut base = popup_window_element.borrow().base_type.clone();
152 while let ElementType::Component(b) = base {
153 let base_policy = b
154 .root_element
155 .borrow()
156 .bindings
157 .get(CLOSE_POLICY)
158 .and_then(|b| {
159 let b = b.borrow();
160 if let Expression::EnumerationValue(v) = &b.expression {
161 return Some(v.clone());
162 }
163 assert!(diag.has_errors());
164 None
165 })
166 .or_else(|| {
167 b.root_element
168 .borrow()
169 .bindings
170 .get(CLOSE_ON_CLICK)
171 .and_then(|b| map_close_on_click_value(&b.borrow()))
172 });
173 if let Some(base_policy) = base_policy {
174 return Some(base_policy);
175 }
176 base = b.root_element.borrow().base_type.clone();
177 }
178 None
179 })
180 .unwrap_or_else(|| EnumerationValue {
181 value: 0,
182 enumeration: crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone()),
183 });
184
185 let popup_comp = Rc::new(Component {
186 root_element: popup_window_element.clone(),
187 parent_element: Rc::downgrade(parent_element),
188 ..Component::default()
189 });
190
191 let weak = Rc::downgrade(&popup_comp);
192 recurse_elem(&popup_comp.root_element, &(), &mut |e, _| {
193 e.borrow_mut().enclosing_component = weak.clone()
194 });
195
196 let coord_x = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("x"));
199 let coord_y = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("y"));
200
201 {
204 let mut popup_mut = popup_comp.root_element.borrow_mut();
205 let name = format_smolstr!("popup-{}-dummy", popup_mut.id);
206 popup_mut.property_declarations.insert(name.clone(), Type::LogicalLength.into());
207 drop(popup_mut);
208 let dummy1 = NamedReference::new(&popup_comp.root_element, name.clone());
209 let dummy2 = NamedReference::new(&popup_comp.root_element, name.clone());
210 let mut popup_mut = popup_comp.root_element.borrow_mut();
211 popup_mut.geometry_props.as_mut().unwrap().x = dummy1;
212 popup_mut.geometry_props.as_mut().unwrap().y = dummy2;
213 }
214
215 visit_all_named_references(&parent_component, &mut |nr| {
221 let element = &nr.element();
222 if check_element(element, &weak, diag, popup_window_element) {
223 *nr = coord_x.clone();
225 }
226 });
227 visit_all_expressions(&parent_component, |exp, _| {
228 exp.visit_recursive_mut(&mut |exp| {
229 if let Expression::ElementReference(ref element) = exp {
230 let elem = element.upgrade().unwrap();
231 if !Rc::ptr_eq(&elem, popup_window_element) {
232 check_element(&elem, &weak, diag, popup_window_element);
233 }
234 }
235 });
236 });
237
238 super::focus_handling::call_focus_on_init(&popup_comp);
239
240 parent_component.popup_windows.borrow_mut().push(PopupWindow {
241 component: popup_comp,
242 x: coord_x,
243 y: coord_y,
244 close_policy,
245 parent_element: parent_element.clone(),
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
253fn check_element(
254 element: &ElementRc,
255 popup_comp: &Weak<Component>,
256 diag: &mut BuildDiagnostics,
257 popup_window_element: &ElementRc,
258) -> bool {
259 if Weak::ptr_eq(&element.borrow().enclosing_component, popup_comp) {
260 diag.push_error(
261 "Cannot access the inside of a PopupWindow from enclosing component".into(),
262 &*popup_window_element.borrow(),
263 );
264 true
265 } else {
266 false
267 }
268}