Skip to main content

i_slint_compiler/passes/
windows.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Make sure that the top level element of the component is always a Window
5
6use crate::diagnostics::BuildDiagnostics;
7use crate::expression_tree::{BindingExpression, Expression};
8use crate::langtype::{ElementType, Type};
9use crate::namedreference::NamedReference;
10use crate::object_tree::{Component, Element};
11use crate::typeregister::TypeRegister;
12use smol_str::SmolStr;
13use std::cell::RefCell;
14use std::collections::HashSet;
15use std::rc::Rc;
16
17pub fn ensure_window(
18    component: &Rc<Component>,
19    type_register: &TypeRegister,
20    style_metrics: &Rc<Component>,
21    diag: &mut BuildDiagnostics,
22) {
23    if component.inherits_popup_window.get() {
24        diag.push_error(
25            "PopupWindow cannot be the top level".into(),
26            &*component.root_element.borrow(),
27        );
28    }
29
30    if inherits_window(component) {
31        return; // already a window, nothing to do
32    }
33
34    let window_type = type_register.lookup_builtin_element("Window").unwrap();
35
36    let win_elem = component.root_element.clone();
37
38    // the old_root becomes the Window
39    let mut win_elem_mut = win_elem.borrow_mut();
40    let new_root = Element {
41        id: std::mem::replace(&mut win_elem_mut.id, "root_window".into()),
42        base_type: std::mem::replace(&mut win_elem_mut.base_type, window_type),
43        bindings: Default::default(),
44        change_callbacks: Default::default(),
45        is_component_placeholder: false,
46        property_analysis: Default::default(),
47        children: std::mem::take(&mut win_elem_mut.children),
48        enclosing_component: win_elem_mut.enclosing_component.clone(),
49        property_declarations: Default::default(),
50        named_references: Default::default(),
51        repeated: Default::default(),
52        states: Default::default(),
53        transitions: Default::default(),
54        child_of_layout: false,
55        has_popup_child: false,
56        layout_info_prop: Default::default(),
57        default_fill_parent: Default::default(),
58        accessibility_props: Default::default(),
59        geometry_props: Default::default(),
60        is_flickable_viewport: false,
61        item_index: Default::default(),
62        item_index_of_first_children: Default::default(),
63        grid_layout_cell: None,
64        debug: std::mem::take(&mut win_elem_mut.debug),
65
66        inline_depth: 0,
67        is_legacy_syntax: false,
68    };
69    let new_root = new_root.make_rc();
70    win_elem_mut.children.push(new_root.clone());
71    drop(win_elem_mut);
72
73    let make_two_way = |name: &'static str| {
74        new_root.borrow_mut().bindings.insert(
75            name.into(),
76            RefCell::new(BindingExpression::new_two_way(
77                NamedReference::new(&win_elem, SmolStr::new_static(name)).into(),
78            )),
79        );
80    };
81    make_two_way("width");
82    make_two_way("height");
83
84    let mut must_update = HashSet::new();
85
86    let mut base_props: HashSet<SmolStr> =
87        new_root.borrow().base_type.property_list().into_iter().map(|x| x.0).collect();
88    base_props.extend(win_elem.borrow().bindings.keys().cloned());
89    for prop in base_props {
90        if prop == "width" || prop == "height" {
91            continue;
92        }
93
94        if win_elem.borrow().property_declarations.contains_key(&prop) {
95            continue;
96        }
97
98        must_update.insert(NamedReference::new(&win_elem, prop.clone()));
99
100        if let Some(b) = win_elem.borrow_mut().bindings.remove(&prop) {
101            new_root.borrow_mut().bindings.insert(prop.clone(), b);
102        }
103        if let Some(a) = win_elem.borrow().property_analysis.borrow_mut().remove(&prop) {
104            new_root.borrow().property_analysis.borrow_mut().insert(prop.clone(), a);
105        }
106    }
107
108    crate::object_tree::visit_all_named_references(component, &mut |nr| {
109        if must_update.contains(nr) {
110            *nr = NamedReference::new(&new_root, nr.name().clone());
111        }
112    });
113
114    // Fix up any ElementReferences for builtin member function calls, to not refer to the WindowItem,
115    // as we swapped out the base_type.
116    let fixup_element_reference = |expr: &mut Expression| {
117        if let Expression::FunctionCall { arguments, .. } = expr {
118            for arg in arguments.iter_mut() {
119                if matches!(arg, Expression::ElementReference(elr) if elr.upgrade().is_some_and(|elemrc| Rc::ptr_eq(&elemrc, &win_elem)))
120                {
121                    *arg = Expression::ElementReference(Rc::downgrade(&new_root))
122                }
123            }
124        }
125    };
126
127    crate::object_tree::visit_all_expressions(component, |expr, _| {
128        expr.visit_recursive_mut(&mut |expr| fixup_element_reference(expr));
129        fixup_element_reference(expr)
130    });
131
132    component.root_element.borrow_mut().set_binding_if_not_set("background".into(), || {
133        Expression::Cast {
134            from: Expression::PropertyReference(NamedReference::new(
135                &style_metrics.root_element,
136                SmolStr::new_static("window-background"),
137            ))
138            .into(),
139            to: Type::Brush,
140        }
141    });
142}
143
144pub fn inherits_window(component: &Rc<Component>) -> bool {
145    component.root_element.borrow().builtin_type().is_none_or(|b| {
146        matches!(b.name.as_str(), "Window" | "Dialog" | "WindowItem" | "PopupWindow")
147    })
148}
149
150// Note: This pass must run before lower_popups, as that introduces additional Window elements.
151pub fn warn_about_child_windows(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) {
152    for component in &doc.inner_components {
153        crate::object_tree::recurse_elem_including_sub_components(
154            component,
155            &(),
156            &mut |elem, _| {
157                // The root element of a component can be a window, but sub-elements should not be!
158                if Rc::ptr_eq(&component.root_element, elem) {
159                    return;
160                }
161
162                let elem = elem.borrow();
163
164                let Some(builtin) = elem.builtin_type() else {
165                    return;
166                };
167                if matches!(builtin.name.as_str(), "Window" | "WindowItem") {
168                    let inheritance_hint =
169                        if let ElementType::Component(component) = &elem.base_type {
170                            format!("\n(Note: {} inherits Window)", component.id)
171                        } else {
172                            "".to_owned()
173                        };
174                    diag.push_warning(
175                        format!(
176                            "Window elements as children do not create separate windows (this may change in the future)\n\
177                            Consider using a PopupWindow instead\
178                            {inheritance_hint}"
179                        ),
180                        &*elem,
181                    );
182                }
183            },
184        );
185    }
186}