mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-11-03 21:24:17 +00:00 
			
		
		
		
	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
 | 
						|
    }
 | 
						|
}
 |