i_slint_compiler/passes/
lower_tabwidget.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 tabwidget
5
6//! This pass lowers the TabWidget to create the tabbar.
7//!
8//! Must be done before inlining and many other passes because the lowered code must
9//! be further inlined as it may expand to a native widget that needs inlining
10
11use crate::diagnostics::BuildDiagnostics;
12use crate::expression_tree::{BindingExpression, Expression, MinMaxOp, NamedReference, Unit};
13use crate::langtype::{ElementType, Type};
14use crate::object_tree::*;
15use smol_str::{format_smolstr, SmolStr};
16use std::cell::RefCell;
17
18pub async fn lower_tabwidget(
19    doc: &Document,
20    type_loader: &mut crate::typeloader::TypeLoader,
21    diag: &mut BuildDiagnostics,
22) {
23    // Ignore import errors
24    let mut build_diags_to_ignore = BuildDiagnostics::default();
25    let tabwidget_impl = type_loader
26        .import_component("std-widgets.slint", "TabWidgetImpl", &mut build_diags_to_ignore)
27        .await
28        .expect("can't load TabWidgetImpl from std-widgets.slint");
29    let tab_impl = type_loader
30        .import_component("std-widgets.slint", "TabImpl", &mut build_diags_to_ignore)
31        .await
32        .expect("can't load TabImpl from std-widgets.slint");
33    let tabbar_impl = type_loader
34        .import_component("std-widgets.slint", "TabBarImpl", &mut build_diags_to_ignore)
35        .await
36        .expect("can't load TabBarImpl from std-widgets.slint");
37    let empty_type = type_loader.global_type_registry.borrow().empty_type();
38
39    doc.visit_all_used_components(|component| {
40        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
41            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "TabWidget") {
42                process_tabwidget(
43                    elem,
44                    ElementType::Component(tabwidget_impl.clone()),
45                    ElementType::Component(tab_impl.clone()),
46                    ElementType::Component(tabbar_impl.clone()),
47                    &empty_type,
48                    diag,
49                );
50            }
51        })
52    });
53}
54
55fn process_tabwidget(
56    elem: &ElementRc,
57    tabwidget_impl: ElementType,
58    tab_impl: ElementType,
59    tabbar_impl: ElementType,
60    empty_type: &ElementType,
61    diag: &mut BuildDiagnostics,
62) {
63    if matches!(&elem.borrow_mut().base_type, ElementType::Builtin(_)) {
64        // That's the TabWidget re-exported from the style, it doesn't need to be processed
65        return;
66    }
67
68    elem.borrow_mut().base_type = tabwidget_impl;
69    let mut children = std::mem::take(&mut elem.borrow_mut().children);
70    let num_tabs = children.len();
71    let mut tabs = Vec::new();
72    for child in &mut children {
73        if child.borrow().repeated.is_some() {
74            diag.push_error(
75                "dynamic tabs ('if' or 'for') are currently not supported".into(),
76                &*child.borrow(),
77            );
78            continue;
79        }
80        if child.borrow().base_type.to_string() != "Tab" {
81            assert!(diag.has_errors());
82            continue;
83        }
84        let index = tabs.len();
85        child.borrow_mut().base_type = empty_type.clone();
86        child
87            .borrow_mut()
88            .property_declarations
89            .insert(SmolStr::new_static("title"), Type::String.into());
90        set_geometry_prop(elem, child, "x", diag);
91        set_geometry_prop(elem, child, "y", diag);
92        set_geometry_prop(elem, child, "width", diag);
93        set_geometry_prop(elem, child, "height", diag);
94        let condition = Expression::BinaryExpression {
95            lhs: Expression::PropertyReference(NamedReference::new(
96                elem,
97                SmolStr::new_static("current-index"),
98            ))
99            .into(),
100            rhs: Expression::NumberLiteral(index as _, Unit::None).into(),
101            op: '=',
102        };
103        let old = child
104            .borrow_mut()
105            .bindings
106            .insert(SmolStr::new_static("visible"), RefCell::new(condition.into()));
107        if let Some(old) = old {
108            diag.push_error(
109                "The property 'visible' cannot be set for Tabs inside a TabWidget".to_owned(),
110                &old.into_inner(),
111            );
112        }
113        let role = crate::typeregister::BUILTIN
114            .with(|e| e.enums.AccessibleRole.clone())
115            .try_value_from_string("tab-panel")
116            .unwrap();
117        let old = child.borrow_mut().bindings.insert(
118            SmolStr::new_static("accessible-role"),
119            RefCell::new(Expression::EnumerationValue(role).into()),
120        );
121        if let Some(old) = old {
122            diag.push_error(
123                "The property 'accessible-role' cannot be set for Tabs inside a TabWidget"
124                    .to_owned(),
125                &old.into_inner(),
126            );
127        }
128        let title_ref = RefCell::new(
129            Expression::PropertyReference(NamedReference::new(child, "title".into())).into(),
130        );
131        let old = child.borrow_mut().bindings.insert("accessible-label".into(), title_ref);
132        if let Some(old) = old {
133            diag.push_error(
134                "The property 'accessible-label' cannot be set for Tabs inside a TabWidget"
135                    .to_owned(),
136                &old.into_inner(),
137            );
138        }
139
140        let mut tab = Element {
141            id: format_smolstr!("{}-tab{}", elem.borrow().id, index),
142            base_type: tab_impl.clone(),
143            enclosing_component: elem.borrow().enclosing_component.clone(),
144            ..Default::default()
145        };
146        tab.bindings.insert(
147            SmolStr::new_static("title"),
148            BindingExpression::new_two_way(NamedReference::new(
149                child,
150                SmolStr::new_static("title"),
151            ))
152            .into(),
153        );
154        tab.bindings.insert(
155            SmolStr::new_static("current"),
156            BindingExpression::new_two_way(NamedReference::new(
157                elem,
158                SmolStr::new_static("current-index"),
159            ))
160            .into(),
161        );
162        tab.bindings.insert(
163            SmolStr::new_static("current-focused"),
164            BindingExpression::new_two_way(NamedReference::new(
165                elem,
166                SmolStr::new_static("current-focused"),
167            ))
168            .into(),
169        );
170        tab.bindings.insert(
171            SmolStr::new_static("tab-index"),
172            RefCell::new(Expression::NumberLiteral(index as _, Unit::None).into()),
173        );
174        tab.bindings.insert(
175            SmolStr::new_static("num-tabs"),
176            RefCell::new(Expression::NumberLiteral(num_tabs as _, Unit::None).into()),
177        );
178        tabs.push(Element::make_rc(tab));
179    }
180
181    let tabbar = Element {
182        id: format_smolstr!("{}-tabbar", elem.borrow().id),
183        base_type: tabbar_impl,
184        enclosing_component: elem.borrow().enclosing_component.clone(),
185        children: tabs,
186        ..Default::default()
187    };
188    let tabbar = Element::make_rc(tabbar);
189    set_tabbar_geometry_prop(elem, &tabbar, "x");
190    set_tabbar_geometry_prop(elem, &tabbar, "y");
191    set_tabbar_geometry_prop(elem, &tabbar, "width");
192    set_tabbar_geometry_prop(elem, &tabbar, "height");
193    tabbar.borrow_mut().bindings.insert(
194        SmolStr::new_static("num-tabs"),
195        RefCell::new(Expression::NumberLiteral(num_tabs as _, Unit::None).into()),
196    );
197    tabbar.borrow_mut().bindings.insert(
198        SmolStr::new_static("current"),
199        BindingExpression::new_two_way(NamedReference::new(
200            elem,
201            SmolStr::new_static("current-index"),
202        ))
203        .into(),
204    );
205    elem.borrow_mut().bindings.insert(
206        SmolStr::new_static("current-focused"),
207        BindingExpression::new_two_way(NamedReference::new(
208            &tabbar,
209            SmolStr::new_static("current-focused"),
210        ))
211        .into(),
212    );
213    elem.borrow_mut().bindings.insert(
214        SmolStr::new_static("tabbar-preferred-width"),
215        BindingExpression::new_two_way(NamedReference::new(
216            &tabbar,
217            SmolStr::new_static("preferred-width"),
218        ))
219        .into(),
220    );
221    elem.borrow_mut().bindings.insert(
222        SmolStr::new_static("tabbar-preferred-height"),
223        BindingExpression::new_two_way(NamedReference::new(
224            &tabbar,
225            SmolStr::new_static("preferred-height"),
226        ))
227        .into(),
228    );
229
230    if let Some(expr) = children
231        .iter()
232        .map(|x| {
233            Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-width")))
234        })
235        .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
236    {
237        elem.borrow_mut().bindings.insert("content-min-width".into(), RefCell::new(expr.into()));
238    };
239    if let Some(expr) = children
240        .iter()
241        .map(|x| {
242            Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-height")))
243        })
244        .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
245    {
246        elem.borrow_mut().bindings.insert("content-min-height".into(), RefCell::new(expr.into()));
247    };
248
249    elem.borrow_mut().children = std::iter::once(tabbar).chain(children).collect();
250}
251
252fn set_geometry_prop(
253    tab_widget: &ElementRc,
254    content: &ElementRc,
255    prop: &str,
256    diag: &mut BuildDiagnostics,
257) {
258    let old = content.borrow_mut().bindings.insert(
259        prop.into(),
260        RefCell::new(
261            Expression::PropertyReference(NamedReference::new(
262                tab_widget,
263                format_smolstr!("content-{}", prop),
264            ))
265            .into(),
266        ),
267    );
268    if let Some(old) = old.map(RefCell::into_inner) {
269        diag.push_error(
270            format!("The property '{prop}' cannot be set for Tabs inside a TabWidget"),
271            &old,
272        );
273    }
274}
275
276fn set_tabbar_geometry_prop(tab_widget: &ElementRc, tabbar: &ElementRc, prop: &str) {
277    tabbar.borrow_mut().bindings.insert(
278        prop.into(),
279        RefCell::new(
280            Expression::PropertyReference(NamedReference::new(
281                tab_widget,
282                format_smolstr!("tabbar-{}", prop),
283            ))
284            .into(),
285        ),
286    );
287}