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::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        is_injected_wrapper_element: 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        shadowing_members: Default::default(),
52        named_references: Default::default(),
53        repeated: Default::default(),
54        states: Default::default(),
55        transitions: Default::default(),
56        match_elements: Default::default(),
57        child_of_layout: false,
58        child_of_flexbox: false,
59        parent_box_layout_orientation: None,
60        has_popup_child: false,
61        layout_info_prop: Default::default(),
62        layout_info_v_with_constraint: Default::default(),
63        layout_info_h_at_own_height: Default::default(),
64        height_is_literal: Default::default(),
65        default_fill_parent: Default::default(),
66        accessibility_props: Default::default(),
67        geometry_props: Default::default(),
68        is_flickable_content: false,
69        is_tooltip: false,
70        z_order: None,
71        item_index: Default::default(),
72        item_index_of_first_children: Default::default(),
73        grid_layout_cell: None,
74        debug: std::mem::take(&mut win_elem_mut.debug),
75
76        inline_depth: 0,
77        is_legacy_syntax: false,
78        slot_target: None,
79        forwarded_slots: Vec::new(),
80    };
81    let new_root = new_root.make_rc();
82    win_elem_mut.children.push(new_root.clone());
83    drop(win_elem_mut);
84
85    let make_two_way = |name: &'static str| {
86        new_root.borrow_mut().set_binding(
87            name.into(),
88            BindingExpression::new_two_way(
89                NamedReference::new(&win_elem, SmolStr::new_static(name)).into(),
90            ),
91        );
92    };
93    make_two_way("width");
94    make_two_way("height");
95
96    let mut must_update = HashSet::new();
97
98    let mut base_props: HashSet<SmolStr> =
99        new_root.borrow().base_type.property_list().into_iter().map(|x| x.0).collect();
100    base_props.extend(win_elem.borrow().real_bindings().map(|(name, _)| name.clone()));
101    for prop in base_props {
102        if prop == "width" || prop == "height" {
103            continue;
104        }
105
106        if win_elem.borrow().property_declarations.contains_key(&prop) {
107            continue;
108        }
109
110        must_update.insert(NamedReference::new(&win_elem, prop.clone()));
111
112        if let Some(b) = win_elem.borrow_mut().take_binding_including_synthetic(&prop) {
113            new_root.borrow_mut().set_binding(prop.clone(), b);
114        }
115        if let Some(a) = win_elem.borrow().property_analysis.borrow_mut().remove(&prop) {
116            new_root.borrow().property_analysis.borrow_mut().insert(prop.clone(), a);
117        }
118    }
119
120    crate::object_tree::visit_all_named_references(component, &mut |nr| {
121        if must_update.contains(nr) {
122            *nr = NamedReference::new(&new_root, nr.name().clone());
123        }
124    });
125
126    // Fix up any ElementReferences for builtin member function calls, to not refer to the WindowItem,
127    // as we swapped out the base_type.
128    let fixup_element_reference = |expr: &mut Expression| {
129        if let Expression::FunctionCall { arguments, .. } = expr {
130            for arg in arguments.iter_mut() {
131                if matches!(arg, Expression::ElementReference(elr) if elr.upgrade().is_some_and(|elemrc| Rc::ptr_eq(&elemrc, &win_elem)))
132                {
133                    *arg = Expression::ElementReference(Rc::downgrade(&new_root))
134                }
135            }
136        }
137    };
138
139    crate::object_tree::visit_all_expressions(component, |expr, _| {
140        expr.visit_recursive_mut(&mut |expr| fixup_element_reference(expr));
141        fixup_element_reference(expr)
142    });
143
144    component.root_element.borrow_mut().set_binding_if_not_set("background".into(), || {
145        Expression::Cast {
146            from: Expression::PropertyReference(NamedReference::new(
147                &style_metrics.root_element,
148                SmolStr::new_static("window-background"),
149            ))
150            .into(),
151            to: Type::Brush,
152        }
153    });
154
155    // The element only became a window here, so it missed the defaults that `Element::from_node`
156    // gives a window the source writes, such as the title
157    crate::object_tree::apply_default_type_properties(&mut component.root_element.borrow_mut());
158}
159
160pub fn inherits_window(component: &Rc<Component>) -> bool {
161    component.root_element.borrow().builtin_type().is_none_or(|b| {
162        matches!(
163            b.name.as_str(),
164            "Window" | "Dialog" | "WindowItem" | "PopupWindow" | "SystemTrayIcon"
165        )
166    })
167}
168
169/// The alpha channel of a color literal, if `expr` is one. A binding whose
170/// value is only known at run time, such as a reference to a property the
171/// application sets, has none.
172#[cfg(feature = "slint-sc")]
173fn literal_alpha(expr: &Expression) -> Option<u8> {
174    match expr.ignore_debug_hooks() {
175        Expression::Cast { from, to: Type::Color | Type::Brush } => literal_alpha(from),
176        // A color literal is a number carrying its channels, alpha highest
177        Expression::NumberLiteral(value, _) => Some((*value as u32 >> 24) as u8),
178        _ => None,
179    }
180}
181
182/// The window background must be a color literal whose alpha channel is 0xff,
183/// so that rendering writes every pixel of the frame buffer. See the
184/// `sls.paint.window-opaque` requirement.
185///
186/// Run this after inlining: a background inherited from a base component only
187/// reaches the root element there, and it's the root the generator compiles.
188#[cfg(feature = "slint-sc")]
189pub fn check_sc_window_background(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
190    // A root that isn't a window was already rejected by check_public_api
191    if !inherits_window(component) {
192        return;
193    }
194    let root = component.root_element.borrow();
195    // Without a binding the background is the opaque black default
196    let Some(binding) = root.binding_cell_including_synthetic("background") else { return };
197    let binding = binding.borrow();
198    match literal_alpha(&binding.expression) {
199        Some(0xff) => {}
200        Some(_) => diag.slint_sc_error("A Window background that isn't fully opaque is", &*binding),
201        None => diag.slint_sc_error("A Window background that isn't a color literal is", &*binding),
202    }
203}
204
205// Note: This pass must run before lower_popups, as that introduces additional Window elements.
206pub fn warn_about_child_windows(doc: &crate::object_tree::Document, diag: &mut BuildDiagnostics) {
207    for component in &doc.inner_components {
208        crate::object_tree::recurse_elem_including_sub_components(
209            component,
210            &(),
211            &mut |elem, _| {
212                // The root element of a component can be a window, but sub-elements should not be!
213                if Rc::ptr_eq(&component.root_element, elem) {
214                    return;
215                }
216
217                let elem = elem.borrow();
218
219                let Some(builtin) = elem.builtin_type() else {
220                    return;
221                };
222                let inheritance_hint = if let ElementType::Component(component) = &elem.base_type {
223                    format!("\n(Note: {} inherits {})", component.id, builtin.name)
224                } else {
225                    "".to_owned()
226                };
227                if matches!(builtin.name.as_str(), "Window" | "WindowItem") {
228                    // The SC generator renders Window only as the root, so
229                    // the compatibility warning is a hard error there.
230                    #[cfg(feature = "slint-sc")]
231                    if diag.slint_sc {
232                        diag.push_error(
233                            format!(
234                                "Instantiating Window as an element is not supported in Slint SC\
235                                {inheritance_hint}"
236                            ),
237                            &*elem,
238                        );
239                        return;
240                    }
241                    diag.push_warning(
242                        format!(
243                            "Window elements as children do not create separate windows (this may change in the future)\n\
244                            Consider using a PopupWindow instead\
245                            {inheritance_hint}"
246                        ),
247                        &*elem,
248                    );
249                } else if builtin.name.as_str() == "SystemTrayIcon" {
250                    // Unlike Window children (which become inert sub-windows), a
251                    // SystemTrayIcon inside another element has no meaningful lowering:
252                    // the platform tray APIs only know how to bind to a top-level
253                    // SystemTrayIcon-rooted component. Reject at compile time.
254                    diag.push_error(
255                        format!(
256                            "SystemTrayIcon must be the root of an exported component, not a child element\
257                            {inheritance_hint}"
258                        ),
259                        &*elem,
260                    );
261                }
262            },
263        );
264    }
265}