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