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