1use 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 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 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 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}