slint/internal/compiler/passes/lower_popups.rs
Tobias Hunger aaeb4a0df5 compiler: Add a DebugHook expression
You can not create this expression manually, but there
is a pass in the compiler that adds it to all set
properties in a compilation run.

All it does is basically associate an id with an expression,
so that we can then in a later step have the interpreter do
something with that information. Apart from that, it tries to
be as transparent as possible.

The LLR lowering removes that expression again, just so we can
be sure it does not end up in the generated live code.
2025-04-09 13:52:29 +02:00

268 lines
10 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! Passe that transform the PopupWindow element into a component
use crate::diagnostics::{BuildDiagnostics, SourceLocation};
use crate::expression_tree::{BindingExpression, Expression, NamedReference};
use crate::langtype::{ElementType, EnumerationValue, Type};
use crate::object_tree::*;
use crate::typeregister::TypeRegister;
use smol_str::{format_smolstr, SmolStr};
use std::rc::{Rc, Weak};
const CLOSE_ON_CLICK: &str = "close-on-click";
const CLOSE_POLICY: &str = "close-policy";
pub fn lower_popups(
component: &Rc<Component>,
type_register: &TypeRegister,
diag: &mut BuildDiagnostics,
) {
let window_type = type_register.lookup_builtin_element("Window").unwrap();
recurse_elem_including_sub_components_no_borrow(
component,
&None,
&mut |elem, parent_element: &Option<ElementRc>| {
if is_popup_window(elem) {
lower_popup_window(elem, parent_element.as_ref(), &window_type, diag);
}
Some(elem.clone())
},
)
}
pub fn is_popup_window(element: &ElementRc) -> bool {
match &element.borrow().base_type {
ElementType::Builtin(base_type) => base_type.name == "PopupWindow",
ElementType::Component(base_type) => base_type.inherits_popup_window.get(),
_ => false,
}
}
fn lower_popup_window(
popup_window_element: &ElementRc,
parent_element: Option<&ElementRc>,
window_type: &ElementType,
diag: &mut BuildDiagnostics,
) {
if let Some(binding) = popup_window_element.borrow().bindings.get(CLOSE_ON_CLICK) {
if popup_window_element.borrow().bindings.contains_key(CLOSE_POLICY) {
diag.push_error(
"close-policy and close-on-click cannot be set at the same time".into(),
&binding.borrow().span,
);
} else {
diag.push_property_deprecation_warning(
CLOSE_ON_CLICK,
CLOSE_POLICY,
&binding.borrow().span,
);
if !matches!(
super::ignore_debug_hooks(&binding.borrow().expression),
Expression::BoolLiteral(_)
) {
report_const_error(CLOSE_ON_CLICK, &binding.borrow().span, diag);
}
}
} else if let Some(binding) = popup_window_element.borrow().bindings.get(CLOSE_POLICY) {
if !matches!(
super::ignore_debug_hooks(&binding.borrow().expression),
Expression::EnumerationValue(_)
) {
report_const_error(CLOSE_POLICY, &binding.borrow().span, diag);
}
}
let parent_component = popup_window_element.borrow().enclosing_component.upgrade().unwrap();
let parent_element = match parent_element {
None => {
if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
popup_window_element.borrow_mut().base_type = window_type.clone();
}
parent_component.inherits_popup_window.set(true);
return;
}
Some(parent_element) => parent_element,
};
if Rc::ptr_eq(&parent_component.root_element, popup_window_element) {
diag.push_error(
"PopupWindow cannot be directly repeated or conditional".into(),
&*popup_window_element.borrow(),
);
return;
}
// Remove the popup_window_element from its parent
let mut parent_element_borrowed = parent_element.borrow_mut();
let index = parent_element_borrowed
.children
.iter()
.position(|child| Rc::ptr_eq(child, popup_window_element))
.expect("PopupWindow must be a child of its parent");
parent_element_borrowed.children.remove(index);
parent_element_borrowed.has_popup_child = true;
drop(parent_element_borrowed);
if let Some((p, idx, _)) = &mut *parent_component.child_insertion_point.borrow_mut() {
if Rc::ptr_eq(p, parent_element) && *idx > index {
*idx -= 1;
}
}
if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) {
popup_window_element.borrow_mut().base_type = window_type.clone();
}
let map_close_on_click_value = |b: &BindingExpression| {
let Expression::BoolLiteral(v) = super::ignore_debug_hooks(&b.expression) else {
assert!(diag.has_errors());
return None;
};
let enum_ty = crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone());
let s = if *v { "close-on-click" } else { "no-auto-close" };
Some(EnumerationValue {
value: enum_ty.values.iter().position(|v| v == s).unwrap(),
enumeration: enum_ty,
})
};
let close_policy =
popup_window_element.borrow_mut().bindings.remove(CLOSE_POLICY).and_then(|b| {
let b = b.into_inner();
if let Expression::EnumerationValue(v) = super::ignore_debug_hooks(&b.expression) {
Some(v.clone())
} else {
assert!(diag.has_errors());
None
}
});
let close_policy = close_policy
.or_else(|| {
popup_window_element
.borrow_mut()
.bindings
.remove(CLOSE_ON_CLICK)
.and_then(|b| map_close_on_click_value(&b.borrow()))
})
.or_else(|| {
// check bases
let mut base = popup_window_element.borrow().base_type.clone();
while let ElementType::Component(b) = base {
let base_policy = b
.root_element
.borrow()
.bindings
.get(CLOSE_POLICY)
.and_then(|b| {
let b = b.borrow();
if let Expression::EnumerationValue(v) = &b.expression {
return Some(v.clone());
}
assert!(diag.has_errors());
None
})
.or_else(|| {
b.root_element
.borrow()
.bindings
.get(CLOSE_ON_CLICK)
.and_then(|b| map_close_on_click_value(&b.borrow()))
});
if let Some(base_policy) = base_policy {
return Some(base_policy);
}
base = b.root_element.borrow().base_type.clone();
}
None
})
.unwrap_or_else(|| EnumerationValue {
value: 0,
enumeration: crate::typeregister::BUILTIN.with(|e| e.enums.PopupClosePolicy.clone()),
});
let popup_comp = Rc::new(Component {
root_element: popup_window_element.clone(),
parent_element: Rc::downgrade(parent_element),
..Component::default()
});
let weak = Rc::downgrade(&popup_comp);
recurse_elem(&popup_comp.root_element, &(), &mut |e, _| {
e.borrow_mut().enclosing_component = weak.clone()
});
// Take a reference to the x/y coordinates, to be read when calling show_popup(), and
// converted to absolute coordinates in the run-time library.
let coord_x = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("x"));
let coord_y = NamedReference::new(&popup_comp.root_element, SmolStr::new_static("y"));
// Meanwhile, set the geometry x/y to zero, because we'll be shown as a top-level and
// children should be rendered starting with a (0, 0) offset.
{
let mut popup_mut = popup_comp.root_element.borrow_mut();
let name = format_smolstr!("popup-{}-dummy", popup_mut.id);
popup_mut.property_declarations.insert(name.clone(), Type::LogicalLength.into());
drop(popup_mut);
let dummy1 = NamedReference::new(&popup_comp.root_element, name.clone());
let dummy2 = NamedReference::new(&popup_comp.root_element, name.clone());
let mut popup_mut = popup_comp.root_element.borrow_mut();
popup_mut.geometry_props.as_mut().unwrap().x = dummy1;
popup_mut.geometry_props.as_mut().unwrap().y = dummy2;
}
// Throw error when accessing the popup from outside
// FIXME:
// - the span is the span of the PopupWindow, that's wrong, we should have the span of the reference
// - There are other object reference than in the NamedReference
// - Maybe this should actually be allowed
visit_all_named_references(&parent_component, &mut |nr| {
let element = &nr.element();
if check_element(element, &weak, diag, popup_window_element) {
// just set it to whatever is a valid NamedReference, otherwise we'll panic later
*nr = coord_x.clone();
}
});
visit_all_expressions(&parent_component, |exp, _| {
exp.visit_recursive_mut(&mut |exp| {
if let Expression::ElementReference(ref element) = exp {
let elem = element.upgrade().unwrap();
if !Rc::ptr_eq(&elem, popup_window_element) {
check_element(&elem, &weak, diag, popup_window_element);
}
}
});
});
super::focus_handling::call_focus_on_init(&popup_comp);
parent_component.popup_windows.borrow_mut().push(PopupWindow {
component: popup_comp,
x: coord_x,
y: coord_y,
close_policy,
parent_element: parent_element.clone(),
});
}
fn report_const_error(prop: &str, span: &Option<SourceLocation>, diag: &mut BuildDiagnostics) {
diag.push_error(format!("The {prop} property only supports constants at the moment"), span);
}
fn check_element(
element: &ElementRc,
popup_comp: &Weak<Component>,
diag: &mut BuildDiagnostics,
popup_window_element: &ElementRc,
) -> bool {
if Weak::ptr_eq(&element.borrow().enclosing_component, popup_comp) {
diag.push_error(
"Cannot access the inside of a PopupWindow from enclosing component".into(),
&*popup_window_element.borrow(),
);
true
} else {
false
}
}