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::collections::HashSet;
17use std::rc::Rc;
18
19pub async fn lower_tabwidget(
20 doc: &Document,
21 type_loader: &mut crate::typeloader::TypeLoader,
22 diag: &mut BuildDiagnostics,
23) {
24 let mut seen = HashSet::new();
29 let mut tab_widgets = Vec::new();
30 doc.visit_all_used_components(|component| {
31 recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
32 if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "TabWidget")
33 && seen.insert(Rc::as_ptr(elem))
34 {
35 tab_widgets.push(elem.clone());
36 }
37 })
38 });
39
40 if tab_widgets.is_empty() {
41 return;
42 }
43
44 let tab_widgets: Vec<_> = tab_widgets
54 .into_iter()
55 .filter(|elem| {
56 if !base_chain_declares_tabs(&elem.borrow().base_type, &seen) {
57 return true;
58 }
59 if !elem.borrow().children.is_empty() {
60 diag.push_error(
61 "Cannot add Tab elements when instantiating a TabWidget subclass that already declares its own Tab; declare them on the subclass instead".to_owned(),
62 &*elem.borrow(),
63 );
64 }
65 if let Some(orientation) = elem.borrow().binding("orientation") {
66 diag.push_error(
67 "Cannot set orientation when instantiating a TabWidget subclass that already declares its own Tab; declare it on the subclass instead".to_owned(),
68 &orientation.span,
69 );
70 }
71 false
72 })
73 .collect();
74
75 let mut build_diags_to_ignore = BuildDiagnostics::default();
77 let tabwidget_impl = type_loader
78 .import_component("std-widgets-impl.slint", "TabWidgetImpl", &mut build_diags_to_ignore)
79 .await
80 .expect("can't load TabWidgetImpl from std-widgets-impl.slint");
81 let tab_impl = type_loader
82 .import_component("std-widgets-impl.slint", "TabImpl", &mut build_diags_to_ignore)
83 .await
84 .expect("can't load TabImpl from std-widgets-impl.slint");
85 let tabbar_horizontal_impl = type_loader
86 .import_component(
87 "std-widgets-impl.slint",
88 "TabBarHorizontalImpl",
89 &mut build_diags_to_ignore,
90 )
91 .await
92 .expect("can't load TabBarHorizontalImpl from std-widgets-impl.slint");
93 let tabbar_vertical_impl = type_loader
94 .import_component(
95 "std-widgets-impl.slint",
96 "TabBarVerticalImpl",
97 &mut build_diags_to_ignore,
98 )
99 .await
100 .expect("can't load TabBarVerticalImpl from std-widgets-impl.slint");
101 let empty_type = type_loader.global_type_registry.borrow().empty_type();
102
103 for elem in &tab_widgets {
104 process_tabwidget(
105 elem,
106 ElementType::Component(tabwidget_impl.clone()),
107 ElementType::Component(tab_impl.clone()),
108 ElementType::Component(tabbar_horizontal_impl.clone()),
109 ElementType::Component(tabbar_vertical_impl.clone()),
110 &empty_type,
111 diag,
112 );
113 }
114}
115
116fn base_chain_declares_tabs(
122 base_type: &ElementType,
123 tab_widget_roots: &HashSet<*const std::cell::RefCell<Element>>,
124) -> bool {
125 let mut base_type = base_type.clone();
126 loop {
127 let ElementType::Component(c) = &base_type else { return false };
128 if !tab_widget_roots.contains(&Rc::as_ptr(&c.root_element)) {
129 return false;
130 }
131 if !c.root_element.borrow().children.is_empty() {
132 return true;
133 }
134 let next = c.root_element.borrow().base_type.clone();
135 base_type = next;
136 }
137}
138
139fn process_tabwidget(
140 elem: &ElementRc,
141 tabwidget_impl: ElementType,
142 tab_impl: ElementType,
143 tabbar_horizontal_impl: ElementType,
144 tabbar_vertical_impl: ElementType,
145 empty_type: &ElementType,
146 diag: &mut BuildDiagnostics,
147) {
148 elem.borrow_mut().base_type = tabwidget_impl;
149 let mut children = std::mem::take(&mut elem.borrow_mut().children);
150 let num_tabs = children.len();
151 let mut tabs = Vec::new();
152 for child in &mut children {
153 if child.borrow().repeated.is_some() {
154 diag.push_error(
155 "dynamic tabs ('if' or 'for') are currently not supported".into(),
156 &*child.borrow(),
157 );
158 continue;
159 }
160 if child.borrow().base_type.to_string() != "Tab" {
161 assert!(diag.has_errors());
162 continue;
163 }
164 let index = tabs.len();
165 child.borrow_mut().base_type = empty_type.clone();
166 child
167 .borrow_mut()
168 .property_declarations
169 .insert(SmolStr::new_static("title"), Type::String.into());
170 set_geometry_prop(elem, child, "x", diag);
171 set_geometry_prop(elem, child, "y", diag);
172 set_geometry_prop(elem, child, "width", diag);
173 set_geometry_prop(elem, child, "height", diag);
174 let condition = Expression::BinaryExpression {
175 lhs: Expression::PropertyReference(NamedReference::new(
176 elem,
177 SmolStr::new_static("current-index"),
178 ))
179 .into(),
180 rhs: Expression::NumberLiteral(index as _, Unit::None).into(),
181 op: '=',
182 source_location: None,
183 };
184 let old = child.borrow_mut().set_binding(SmolStr::new_static("visible"), condition.into());
185 if let Some(old) = old {
186 diag.push_error(
187 "The property 'visible' cannot be set for Tabs inside a TabWidget".to_owned(),
188 &old,
189 );
190 }
191 let role = crate::typeregister::BUILTIN
192 .enums
193 .AccessibleRole
194 .clone()
195 .try_value_from_string("tab-panel")
196 .unwrap();
197 let old = child.borrow_mut().set_binding(
198 SmolStr::new_static("accessible-role"),
199 Expression::EnumerationValue(role).into(),
200 );
201 if let Some(old) = old {
202 diag.push_error(
203 "The property 'accessible-role' cannot be set for Tabs inside a TabWidget"
204 .to_owned(),
205 &old,
206 );
207 }
208 let title_ref =
209 Expression::PropertyReference(NamedReference::new(child, "title".into())).into();
210 let old = child.borrow_mut().set_binding("accessible-label".into(), title_ref);
211 if let Some(old) = old {
212 diag.push_error(
213 "The property 'accessible-label' cannot be set for Tabs inside a TabWidget"
214 .to_owned(),
215 &old,
216 );
217 }
218
219 let mut tab = Element {
220 id: format_smolstr!("{}-tab{}", elem.borrow().id, index),
221 base_type: tab_impl.clone(),
222 enclosing_component: elem.borrow().enclosing_component.clone(),
223 ..Default::default()
224 };
225 tab.set_binding(
226 SmolStr::new_static("title"),
227 BindingExpression::new_two_way(
228 NamedReference::new(child, SmolStr::new_static("title")).into(),
229 ),
230 );
231 tab.set_binding(
232 SmolStr::new_static("current"),
233 BindingExpression::new_two_way(
234 NamedReference::new(elem, SmolStr::new_static("current-index")).into(),
235 ),
236 );
237 tab.set_binding(
238 SmolStr::new_static("current-focused"),
239 BindingExpression::new_two_way(
240 NamedReference::new(elem, SmolStr::new_static("current-focused")).into(),
241 ),
242 );
243 tab.set_binding(
244 SmolStr::new_static("tab-index"),
245 Expression::NumberLiteral(index as _, Unit::None).into(),
246 );
247 tab.set_binding(
248 SmolStr::new_static("num-tabs"),
249 Expression::NumberLiteral(num_tabs as _, Unit::None).into(),
250 );
251 tabs.push(Element::make_rc(tab));
252 }
253
254 let mut tabbar_impl = tabbar_horizontal_impl;
255 if let Some(orientation) = elem.borrow().binding("orientation") {
256 if let Expression::EnumerationValue(val) = orientation.value_expression() {
257 if val.value == 1 {
258 tabbar_impl = tabbar_vertical_impl;
259 }
260 } else {
261 diag.push_error(
262 "The orientation property only supports constants at the moment".into(),
263 &orientation.span,
264 );
265 }
266 }
267 let tabbar = Element {
268 id: format_smolstr!("{}-tabbar", elem.borrow().id),
269 base_type: tabbar_impl,
270 enclosing_component: elem.borrow().enclosing_component.clone(),
271 children: tabs,
272 ..Default::default()
273 };
274 let tabbar = Element::make_rc(tabbar);
275 set_tabbar_geometry_prop(elem, &tabbar, "x");
276 set_tabbar_geometry_prop(elem, &tabbar, "y");
277 set_tabbar_geometry_prop(elem, &tabbar, "width");
278 set_tabbar_geometry_prop(elem, &tabbar, "height");
279 tabbar.borrow_mut().set_binding(
280 SmolStr::new_static("num-tabs"),
281 Expression::NumberLiteral(num_tabs as _, Unit::None).into(),
282 );
283 tabbar.borrow_mut().set_binding(
284 SmolStr::new_static("current"),
285 BindingExpression::new_two_way(
286 NamedReference::new(elem, SmolStr::new_static("current-index")).into(),
287 ),
288 );
289 elem.borrow_mut().set_binding(
290 SmolStr::new_static("current-focused"),
291 BindingExpression::new_two_way(
292 NamedReference::new(&tabbar, SmolStr::new_static("current-focused")).into(),
293 ),
294 );
295 elem.borrow_mut().set_binding(
296 SmolStr::new_static("tabbar-preferred-width"),
297 BindingExpression::new_two_way(
298 NamedReference::new(&tabbar, SmolStr::new_static("preferred-width")).into(),
299 ),
300 );
301 elem.borrow_mut().set_binding(
302 SmolStr::new_static("tabbar-preferred-height"),
303 BindingExpression::new_two_way(
304 NamedReference::new(&tabbar, SmolStr::new_static("preferred-height")).into(),
305 ),
306 );
307
308 if let Some(expr) = children
309 .iter()
310 .map(|x| {
311 Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-width")))
312 })
313 .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
314 {
315 elem.borrow_mut().set_binding("content-min-width".into(), expr.into());
316 };
317 if let Some(expr) = children
318 .iter()
319 .map(|x| {
320 Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static("min-height")))
321 })
322 .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max))
323 {
324 elem.borrow_mut().set_binding("content-min-height".into(), expr.into());
325 };
326
327 elem.borrow_mut().children = std::iter::once(tabbar).chain(children).collect();
328}
329
330fn set_geometry_prop(
331 tab_widget: &ElementRc,
332 content: &ElementRc,
333 prop: &str,
334 diag: &mut BuildDiagnostics,
335) {
336 let old = content.borrow_mut().set_binding(
337 prop.into(),
338 Expression::PropertyReference(NamedReference::new(
339 tab_widget,
340 format_smolstr!("content-{}", prop),
341 ))
342 .into(),
343 );
344 if let Some(old) = old {
345 diag.push_error(
346 format!("The property '{prop}' cannot be set for Tabs inside a TabWidget"),
347 &old,
348 );
349 }
350}
351
352fn set_tabbar_geometry_prop(tab_widget: &ElementRc, tabbar: &ElementRc, prop: &str) {
353 tabbar.borrow_mut().set_binding(
354 prop.into(),
355 Expression::PropertyReference(NamedReference::new(
356 tab_widget,
357 format_smolstr!("tabbar-{}", prop),
358 ))
359 .into(),
360 );
361}