slint/internal/compiler/passes/lower_popups.rs
Olivier Goffart f8f61dc2b7 Fix a bunch more issue with PopupWindow
* Make sure that the compiler don't panic if the parent of a PopupWindow
   is optimized (by not optiizing such element)

 * Ensure that we can call popup.show() from within a deeper repeater

 * Ensure that the parent element of the popup is the right one in case of
   repeater (and not the node in the parent component)

This partially revert ad5991f8fa and
6c7a7aed0e because we must do the lower_popup
adter the repeater pass, because otherwise the parent element of the
created component for the PopupWindow might be wrong and it is not easy to
adjust (we would have to make Component::parent_element a RefCell or duplicate
it again.

Fixes #1132
2022-04-01 14:06:38 +02:00

134 lines
4.7 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! Passe that transform the PopupWindow element into a component
use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::{Expression, NamedReference};
use crate::langtype::Type;
use crate::object_tree::*;
use crate::typeregister::TypeRegister;
use std::cell::RefCell;
use std::rc::Rc;
pub fn lower_popups(
component: &Rc<Component>,
type_register: &TypeRegister,
diag: &mut BuildDiagnostics,
) {
let window_type = type_register.lookup_element("Window").unwrap();
recurse_elem_including_sub_components_no_borrow(
component,
&None,
&mut |elem, parent_element: &Option<ElementRc>| {
let is_popup = elem.borrow().base_type.to_string() == "PopupWindow";
if is_popup {
lower_popup_window(elem, parent_element.as_ref(), &window_type, diag);
}
Some(elem.clone())
},
)
}
fn lower_popup_window(
popup_window_element: &ElementRc,
parent_element: Option<&ElementRc>,
window_type: &Type,
diag: &mut BuildDiagnostics,
) {
let parent_element = match parent_element {
None => {
diag.push_error(
"PopupWindow cannot be the top level".into(),
&*popup_window_element.borrow(),
);
return;
}
Some(parent_element) => parent_element,
};
let parent_component = popup_window_element.borrow().enclosing_component.upgrade().unwrap();
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 old_size = parent_element.borrow().children.len();
parent_element.borrow_mut().children.retain(|child| !Rc::ptr_eq(child, popup_window_element));
debug_assert_eq!(
parent_element.borrow().children.len() + 1,
old_size,
"Exactly one child must be removed (the popup itself)"
);
parent_element.borrow_mut().has_popup_child = true;
popup_window_element.borrow_mut().base_type = window_type.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()
});
// Generate a x and y property, relative to the window coordinate
// FIXME: this is a hack that doesn't always work, perhaps should we store an item ref or something
let coord_x = create_coordinate(&popup_comp, parent_element, "x");
let coord_y = create_coordinate(&popup_comp, parent_element, "y");
// 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| {
if std::rc::Weak::ptr_eq(&nr.element().borrow().enclosing_component, &weak) {
diag.push_error(
"Cannot access the inside of a PopupWindow from enclosing component".into(),
&*popup_window_element.borrow(),
);
// just set it to whatever is a valid NamedReference, otherwise we'll panic later
*nr = coord_x.clone();
}
});
parent_component.popup_windows.borrow_mut().push(PopupWindow {
component: popup_comp,
x: coord_x,
y: coord_y,
parent_element: parent_element.clone(),
});
}
fn create_coordinate(
popup_comp: &Rc<Component>,
parent_element: &ElementRc,
coord: &str,
) -> NamedReference {
let expression = popup_comp
.root_element
.borrow()
.bindings
.get(coord)
.map(|e| e.borrow().expression.clone())
.unwrap_or(Expression::NumberLiteral(0., crate::expression_tree::Unit::Phx));
let property_name = format!("{}-popup-{}", popup_comp.root_element.borrow().id, coord);
parent_element
.borrow_mut()
.property_declarations
.insert(property_name.clone(), Type::LogicalLength.into());
parent_element
.borrow_mut()
.bindings
.insert(property_name.clone(), RefCell::new(expression.into()));
NamedReference::new(parent_element, &property_name)
}