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