Skip to main content

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::{SmolStr, format_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    // First check if any TabWidget is used - avoid loading std-widgets.slint if not needed
24    let mut has_tabwidget = false;
25    doc.visit_all_used_components(|component| {
26        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
27            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "TabWidget") {
28                has_tabwidget = true;
29            }
30        })
31    });
32
33    if !has_tabwidget {
34        return;
35    }
36
37    // Ignore import errors
38    let mut build_diags_to_ignore = BuildDiagnostics::default();
39    let tabwidget_impl = type_loader
40        .import_component("std-widgets-impl.slint", "TabWidgetImpl", &mut build_diags_to_ignore)
41        .await
42        .expect("can't load TabWidgetImpl from std-widgets-impl.slint");
43    let tab_impl = type_loader
44        .import_component("std-widgets-impl.slint", "TabImpl", &mut build_diags_to_ignore)
45        .await
46        .expect("can't load TabImpl from std-widgets-impl.slint");
47    let tabbar_horizontal_impl = type_loader
48        .import_component(
49            "std-widgets-impl.slint",
50            "TabBarHorizontalImpl",
51            &mut build_diags_to_ignore,
52        )
53        .await
54        .expect("can't load TabBarHorizontalImpl from std-widgets-impl.slint");
55    let tabbar_vertical_impl = type_loader
56        .import_component(
57            "std-widgets-impl.slint",
58            "TabBarVerticalImpl",
59            &mut build_diags_to_ignore,
60        )
61        .await
62        .expect("can't load TabBarVerticalImpl from std-widgets-impl.slint");
63    let empty_type = type_loader.global_type_registry.borrow().empty_type();
64
65    doc.visit_all_used_components(|component| {
66        recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
67            if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "TabWidget") {
68                process_tabwidget(
69                    elem,
70                    ElementType::Component(tabwidget_impl.clone()),
71                    ElementType::Component(tab_impl.clone()),
72                    ElementType::Component(tabbar_horizontal_impl.clone()),
73                    ElementType::Component(tabbar_vertical_impl.clone()),
74                    &empty_type,
75                    diag,
76                );
77            }
78        })
79    });
80}
81
82fn process_tabwidget(
83    elem: &ElementRc,
84    tabwidget_impl: ElementType,
85    tab_impl: ElementType,
86    tabbar_horizontal_impl: ElementType,
87    tabbar_vertical_impl: ElementType,
88    empty_type: &ElementType,
89    diag: &mut BuildDiagnostics,
90) {
91    if matches!(&elem.borrow_mut().base_type, ElementType::Builtin(_)) {
92        // That's the TabWidget re-exported from the style, it doesn't need to be processed
93        return;
94    }
95
96    elem.borrow_mut().base_type = tabwidget_impl;
97    let mut children = std::mem::take(&mut elem.borrow_mut().children);
98    let num_tabs = children.len();
99    let mut tabs = Vec::new();
100    for child in &mut children {
101        if child.borrow().repeated.is_some() {
102            diag.push_error(
103                "dynamic tabs ('if' or 'for') are currently not supported".into(),
104                &*child.borrow(),
105            );
106            continue;
107        }
108        if child.borrow().base_type.to_string() != "Tab" {
109            assert!(diag.has_errors());
110            continue;
111        }
112        let index = tabs.len();
113        child.borrow_mut().base_type = empty_type.clone();
114        child
115            .borrow_mut()
116            .property_declarations
117            .insert(SmolStr::new_static("title"), Type::String.into());
118        set_geometry_prop(elem, child, "x", diag);
119        set_geometry_prop(elem, child, "y", diag);
120        set_geometry_prop(elem, child, "width", diag);
121        set_geometry_prop(elem, child, "height", diag);
122        let condition = Expression::BinaryExpression {
123            lhs: Expression::PropertyReference(NamedReference::new(
124                elem,
125                SmolStr::new_static("current-index"),
126            ))
127            .into(),
128            rhs: Expression::NumberLiteral(index as _, Unit::None).into(),
129            op: '=',
130        };
131        let old = child
132            .borrow_mut()
133            .bindings
134            .insert(SmolStr::new_static("visible"), RefCell::new(condition.into()));
135        if let Some(old) = old {
136            diag.push_error(
137                "The property 'visible' cannot be set for Tabs inside a TabWidget".to_owned(),
138                &old.into_inner(),
139            );
140        }
141        let role = crate::typeregister::BUILTIN
142            .with(|e| e.enums.AccessibleRole.clone())
143            .try_value_from_string("tab-panel")
144            .unwrap();
145        let old = child.borrow_mut().bindings.insert(
146            SmolStr::new_static("accessible-role"),
147            RefCell::new(Expression::EnumerationValue(role).into()),
148        );
149        if let Some(old) = old {
150            diag.push_error(
151                "The property 'accessible-role' cannot be set for Tabs inside a TabWidget"
152                    .to_owned(),
153                &old.into_inner(),
154            );
155        }
156        let title_ref = RefCell::new(
157            Expression::PropertyReference(NamedReference::new(child, "title".into())).into(),
158        );
159        let old = child.borrow_mut().bindings.insert("accessible-label".into(), title_ref);
160        if let Some(old) = old {
161            diag.push_error(
162                "The property 'accessible-label' cannot be set for Tabs inside a TabWidget"
163                    .to_owned(),
164                &old.into_inner(),
165            );
166        }
167
168        let mut tab = Element {
169            id: format_smolstr!("{}-tab{}", elem.borrow().id, index),
170            base_type: tab_impl.clone(),
171            enclosing_component: elem.borrow().enclosing_component.clone(),
172            ..Default::default()
173        };
174        tab.bindings.insert(
175            SmolStr::new_static("title"),
176            BindingExpression::new_two_way(
177                NamedReference::new(child, SmolStr::new_static("title")).into(),
178            )
179            .into(),
180        );
181        tab.bindings.insert(
182            SmolStr::new_static("current"),
183            BindingExpression::new_two_way(
184                NamedReference::new(elem, SmolStr::new_static("current-index")).into(),
185            )
186            .into(),
187        );
188        tab.bindings.insert(
189            SmolStr::new_static("current-focused"),
190            BindingExpression::new_two_way(
191                NamedReference::new(elem, SmolStr::new_static("current-focused")).into(),
192            )
193            .into(),
194        );
195        tab.bindings.insert(
196            SmolStr::new_static("tab-index"),
197            RefCell::new(Expression::NumberLiteral(index as _, Unit::None).into()),
198        );
199        tab.bindings.insert(
200            SmolStr::new_static("num-tabs"),
201            RefCell::new(Expression::NumberLiteral(num_tabs as _, Unit::None).into()),
202        );
203        tabs.push(Element::make_rc(tab));
204    }
205
206    let mut tabbar_impl = tabbar_horizontal_impl;
207    if let Some(orientation) = elem.borrow().bindings.get("orientation") {
208        if let Expression::EnumerationValue(val) =
209            super::ignore_debug_hooks(&orientation.borrow().expression)
210        {
211            if val.value == 1 {
212                tabbar_impl = tabbar_vertical_impl;
213            }
214        } else {
215            diag.push_error(
216                "The orientation property only supports constants at the moment".into(),
217                &orientation.borrow().span,
218            );
219        }
220    }
221    let tabbar = Element {
222        id: format_smolstr!("{}-tabbar", elem.borrow().id),
223        base_type: tabbar_impl,
224        enclosing_component: elem.borrow().enclosing_component.clone(),
225        children: tabs,
226        ..Default::default()
227    };
228    let tabbar = Element::make_rc(tabbar);
229    set_tabbar_geometry_prop(elem, &tabbar, "x");
230    set_tabbar_geometry_prop(elem, &tabbar, "y");
231    set_tabbar_geometry_prop(elem, &tabbar, "width");
232    set_tabbar_geometry_prop(elem, &tabbar, "height");
233    tabbar.borrow_mut().bindings.insert(
234        SmolStr::new_static("num-tabs"),
235        RefCell::new(Expression::NumberLiteral(num_tabs as _, Unit::None).into()),
236    );
237    tabbar.borrow_mut().bindings.insert(
238        SmolStr::new_static("current"),
239        BindingExpression::new_two_way(
240            NamedReference::new(elem, SmolStr::new_static("current-index")).into(),
241        )
242        .into(),
243    );
244    elem.borrow_mut().bindings.insert(
245        SmolStr::new_static("current-focused"),
246        BindingExpression::new_two_way(
247            NamedReference::new(&tabbar, SmolStr::new_static("current-focused")).into(),
248        )
249        .into(),
250    );
251    elem.borrow_mut().bindings.insert(
252        SmolStr::new_static("tabbar-preferred-width"),
253        BindingExpression::new_two_way(
254            NamedReference::new(&tabbar, SmolStr::new_static("preferred-width")).into(),
255        )
256        .into(),
257    );
258    elem.borrow_mut().bindings.insert(
259        SmolStr::new_static("tabbar-preferred-height"),
260        BindingExpression::new_two_way(
261            NamedReference::new(&tabbar, SmolStr::new_static("preferred-height")).into(),
262        )
263        .into(),
264    );
265
266    if let Some(expr) = children
267        .iter()
268        .map(|x| {
269            Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-width")))
270        })
271        .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
272    {
273        elem.borrow_mut().bindings.insert("content-min-width".into(), RefCell::new(expr.into()));
274    };
275    if let Some(expr) = children
276        .iter()
277        .map(|x| {
278            Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-height")))
279        })
280        .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
281    {
282        elem.borrow_mut().bindings.insert("content-min-height".into(), RefCell::new(expr.into()));
283    };
284
285    elem.borrow_mut().children = std::iter::once(tabbar).chain(children).collect();
286}
287
288fn set_geometry_prop(
289    tab_widget: &ElementRc,
290    content: &ElementRc,
291    prop: &str,
292    diag: &mut BuildDiagnostics,
293) {
294    let old = content.borrow_mut().bindings.insert(
295        prop.into(),
296        RefCell::new(
297            Expression::PropertyReference(NamedReference::new(
298                tab_widget,
299                format_smolstr!("content-{}", prop),
300            ))
301            .into(),
302        ),
303    );
304    if let Some(old) = old.map(RefCell::into_inner) {
305        diag.push_error(
306            format!("The property '{prop}' cannot be set for Tabs inside a TabWidget"),
307            &old,
308        );
309    }
310}
311
312fn set_tabbar_geometry_prop(tab_widget: &ElementRc, tabbar: &ElementRc, prop: &str) {
313    tabbar.borrow_mut().bindings.insert(
314        prop.into(),
315        RefCell::new(
316            Expression::PropertyReference(NamedReference::new(
317                tab_widget,
318                format_smolstr!("tabbar-{}", prop),
319            ))
320            .into(),
321        ),
322    );
323}