mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-31 20:08:35 +00:00 
			
		
		
		
	 25c1085b50
			
		
	
	
		25c1085b50
		
	
	
	
	
		
			
			I expect I will need to add something here to handle indices on children and then a tuple might get unwieldy.
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			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(parent_cip) = &mut *parent_component.child_insertion_point.borrow_mut() {
 | |
|         if Rc::ptr_eq(&parent_cip.parent, parent_element) && parent_cip.insertion_index > index {
 | |
|             parent_cip.insertion_index -= 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
 | |
|     }
 | |
| }
 |