1use lyon_path::geom::euclid::approxeq::ApproxEq;
7
8use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
9use crate::expression_tree::*;
10use crate::langtype::ElementType;
11use crate::langtype::Type;
12use crate::layout::*;
13use crate::object_tree::*;
14use crate::typeloader::TypeLoader;
15use crate::typeregister::{TypeRegister, layout_info_type};
16use smol_str::{SmolStr, format_smolstr};
17use std::cell::RefCell;
18use std::collections::HashSet;
19use std::rc::Rc;
20
21pub fn lower_layouts(
22 component: &Rc<Component>,
23 type_loader: &mut TypeLoader,
24 style_metrics: &Rc<Component>,
25 diag: &mut BuildDiagnostics,
26) {
27 recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
29 if check_preferred_size_100(elem, "preferred-width", diag) {
30 elem.borrow_mut().default_fill_parent.0 = true;
31 }
32 if check_preferred_size_100(elem, "preferred-height", diag) {
33 elem.borrow_mut().default_fill_parent.1 = true;
34 }
35 let base = elem.borrow().sub_component().cloned();
36 if let Some(base) = base {
37 let base = base.root_element.borrow();
38 let mut elem_mut = elem.borrow_mut();
39 elem_mut.default_fill_parent.0 |= base.default_fill_parent.0;
40 elem_mut.default_fill_parent.1 |= base.default_fill_parent.1;
41 }
42 });
43
44 *component.root_constraints.borrow_mut() =
45 LayoutConstraints::new(&component.root_element, diag, DiagnosticLevel::Error);
46
47 recurse_elem_including_sub_components(
48 component,
49 &Option::default(),
50 &mut |elem, parent_layout_type| {
51 let component = elem.borrow().enclosing_component.upgrade().unwrap();
52
53 lower_element_layout(
54 &component,
55 elem,
56 &type_loader.global_type_registry.borrow(),
57 style_metrics,
58 parent_layout_type,
59 diag,
60 )
61 },
62 );
63}
64
65fn check_preferred_size_100(elem: &ElementRc, prop: &str, diag: &mut BuildDiagnostics) -> bool {
66 let ret = if let Some(p) = elem.borrow().bindings.get(prop) {
67 if p.borrow().expression.ty() == Type::Percent {
68 if !matches!(p.borrow().expression.ignore_debug_hooks(), Expression::NumberLiteral(val, _) if *val == 100.)
69 {
70 diag.push_error(
71 format!("{prop} must either be a length, or the literal '100%'"),
72 &*p.borrow(),
73 );
74 }
75 true
76 } else {
77 false
78 }
79 } else {
80 false
81 };
82 if ret {
83 elem.borrow_mut().bindings.remove(prop).unwrap();
84 return true;
85 }
86 false
87}
88
89fn lower_element_layout(
92 component: &Rc<Component>,
93 elem: &ElementRc,
94 type_register: &TypeRegister,
95 style_metrics: &Rc<Component>,
96 parent_layout_type: &Option<SmolStr>,
97 diag: &mut BuildDiagnostics,
98) -> Option<SmolStr> {
99 let layout_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type {
100 Some(base_type.name.clone())
101 } else {
102 None
103 };
104
105 check_no_layout_properties(elem, &layout_type, parent_layout_type, diag);
106
107 match layout_type.as_ref()?.as_str() {
108 "Row" => return layout_type,
109 "GridLayout" => lower_grid_layout(component, elem, diag, type_register),
110 "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
111 "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
112 "Dialog" => {
113 lower_dialog_layout(elem, style_metrics, diag);
114 return layout_type;
116 }
117 _ => return None,
118 };
119
120 let mut elem = elem.borrow_mut();
121 let elem = &mut *elem;
122 let prev_base = std::mem::replace(&mut elem.base_type, type_register.empty_type());
123 elem.default_fill_parent = (true, true);
124 for (p, ty) in prev_base.property_list() {
127 if !elem.base_type.lookup_property(&p).is_valid()
128 && !elem.property_declarations.contains_key(&p)
129 {
130 elem.property_declarations.insert(p, ty.into());
131 }
132 }
133
134 layout_type
135}
136
137#[derive(Debug, PartialEq, Eq)]
139enum RowColExpressionType {
140 Auto, Literal,
142 RuntimeExpression,
143}
144impl RowColExpressionType {
145 fn from_option_expr(
146 expr: &Option<Expression>,
147 is_number_literal: bool,
148 ) -> RowColExpressionType {
149 match expr {
150 None => RowColExpressionType::Auto,
151 Some(_) if is_number_literal => RowColExpressionType::Literal,
152 Some(_) => RowColExpressionType::RuntimeExpression,
153 }
154 }
155}
156
157fn lower_grid_layout(
158 component: &Rc<Component>,
159 grid_layout_element: &ElementRc,
160 diag: &mut BuildDiagnostics,
161 type_register: &TypeRegister,
162) {
163 let mut grid = GridLayout {
164 elems: Default::default(),
165 geometry: LayoutGeometry::new(grid_layout_element),
166 dialog_button_roles: None,
167 uses_auto: false,
168 };
169
170 let layout_organized_data_prop = create_new_prop(
171 grid_layout_element,
172 SmolStr::new_static("layout-organized-data"),
173 Type::ArrayOfU16,
174 );
175 let layout_cache_prop_h = create_new_prop(
176 grid_layout_element,
177 SmolStr::new_static("layout-cache-h"),
178 Type::LayoutCache,
179 );
180 let layout_cache_prop_v = create_new_prop(
181 grid_layout_element,
182 SmolStr::new_static("layout-cache-v"),
183 Type::LayoutCache,
184 );
185 let layout_info_prop_h = create_new_prop(
186 grid_layout_element,
187 SmolStr::new_static("layoutinfo-h"),
188 layout_info_type().into(),
189 );
190 let layout_info_prop_v = create_new_prop(
191 grid_layout_element,
192 SmolStr::new_static("layoutinfo-v"),
193 layout_info_type().into(),
194 );
195
196 let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
197 let mut collected_children = Vec::new();
198 let mut new_row = false; let mut numbering_type: Option<RowColExpressionType> = None;
200 let mut num_cached_items: usize = 0;
201 for layout_child in layout_children {
202 let is_repeated_row = {
203 if layout_child.borrow().repeated.is_some()
204 && let ElementType::Component(comp) = &layout_child.borrow().base_type
205 {
206 match &comp.root_element.borrow().base_type {
207 ElementType::Builtin(b) => b.name == "Row",
208 _ => false,
209 }
210 } else {
211 false
212 }
213 };
214 if is_repeated_row {
215 grid.add_repeated_row(
216 &layout_child,
217 &layout_cache_prop_h,
218 &layout_cache_prop_v,
219 &layout_organized_data_prop,
220 diag,
221 &mut num_cached_items,
222 );
223 collected_children.push(layout_child);
224 new_row = true;
225 } else if layout_child.borrow().base_type.type_name() == Some("Row") {
226 new_row = true;
227 let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
228 for row_child in row_children {
229 if let Some(binding) = row_child.borrow_mut().bindings.get("row") {
230 diag.push_warning(
231 "The 'row' property cannot be used for elements inside a Row. This was accepted by previous versions of Slint, but may become an error in the future".to_string(),
232 &*binding.borrow(),
233 );
234 }
235 grid.add_element(
236 &row_child,
237 new_row,
238 &layout_cache_prop_h,
239 &layout_cache_prop_v,
240 &layout_organized_data_prop,
241 &mut numbering_type,
242 diag,
243 &mut num_cached_items,
244 );
245 collected_children.push(row_child);
246 new_row = false;
247 }
248 new_row = true; if layout_child.borrow().has_popup_child {
250 layout_child.borrow_mut().base_type = type_register.empty_type();
252 collected_children.push(layout_child);
253 } else {
254 component.optimized_elements.borrow_mut().push(layout_child);
255 }
256 } else {
257 grid.add_element(
258 &layout_child,
259 new_row,
260 &layout_cache_prop_h,
261 &layout_cache_prop_v,
262 &layout_organized_data_prop,
263 &mut numbering_type,
264 diag,
265 &mut num_cached_items,
266 );
267 collected_children.push(layout_child);
268 new_row = false;
269 }
270 }
271 grid_layout_element.borrow_mut().children = collected_children;
272 grid.uses_auto = numbering_type == Some(RowColExpressionType::Auto);
273 let span = grid_layout_element.borrow().to_source_location();
274
275 layout_organized_data_prop.element().borrow_mut().bindings.insert(
276 layout_organized_data_prop.name().clone(),
277 BindingExpression::new_with_span(
278 Expression::OrganizeGridLayout(grid.clone()),
279 span.clone(),
280 )
281 .into(),
282 );
283 layout_cache_prop_h.element().borrow_mut().bindings.insert(
284 layout_cache_prop_h.name().clone(),
285 BindingExpression::new_with_span(
286 Expression::SolveGridLayout {
287 layout_organized_data_prop: layout_organized_data_prop.clone(),
288 layout: grid.clone(),
289 orientation: Orientation::Horizontal,
290 },
291 span.clone(),
292 )
293 .into(),
294 );
295 layout_cache_prop_v.element().borrow_mut().bindings.insert(
296 layout_cache_prop_v.name().clone(),
297 BindingExpression::new_with_span(
298 Expression::SolveGridLayout {
299 layout_organized_data_prop: layout_organized_data_prop.clone(),
300 layout: grid.clone(),
301 orientation: Orientation::Vertical,
302 },
303 span.clone(),
304 )
305 .into(),
306 );
307 layout_info_prop_h.element().borrow_mut().bindings.insert(
308 layout_info_prop_h.name().clone(),
309 BindingExpression::new_with_span(
310 Expression::ComputeGridLayoutInfo {
311 layout_organized_data_prop: layout_organized_data_prop.clone(),
312 layout: grid.clone(),
313 orientation: Orientation::Horizontal,
314 },
315 span.clone(),
316 )
317 .into(),
318 );
319 layout_info_prop_v.element().borrow_mut().bindings.insert(
320 layout_info_prop_v.name().clone(),
321 BindingExpression::new_with_span(
322 Expression::ComputeGridLayoutInfo {
323 layout_organized_data_prop: layout_organized_data_prop.clone(),
324 layout: grid.clone(),
325 orientation: Orientation::Vertical,
326 },
327 span,
328 )
329 .into(),
330 );
331 grid_layout_element.borrow_mut().layout_info_prop =
332 Some((layout_info_prop_h, layout_info_prop_v));
333 for d in grid_layout_element.borrow_mut().debug.iter_mut() {
334 d.layout = Some(Layout::GridLayout(grid.clone()));
335 }
336}
337
338impl GridLayout {
339 fn add_element(
340 &mut self,
341 item_element: &ElementRc,
342 new_row: bool,
343 layout_cache_prop_h: &NamedReference,
344 layout_cache_prop_v: &NamedReference,
345 organized_data_prop: &NamedReference,
346 numbering_type: &mut Option<RowColExpressionType>,
347 diag: &mut BuildDiagnostics,
348 num_cached_items: &mut usize,
349 ) {
350 {
352 let mut check_expr = |name: &str| {
353 let mut is_number_literal = false;
354 let expr = item_element.borrow_mut().bindings.get(name).map(|e| {
355 let expr = &e.borrow().expression;
356 is_number_literal =
357 check_number_literal_is_positive_integer(expr, name, &*e.borrow(), diag);
358 expr.clone()
359 });
360 (expr, is_number_literal)
361 };
362
363 let (row_expr, row_is_number_literal) = check_expr("row");
364 let (col_expr, col_is_number_literal) = check_expr("col");
365 check_expr("rowspan");
366 check_expr("colspan");
367
368 let mut check_numbering_consistency =
369 |expr_type: RowColExpressionType, prop_name: &str| {
370 if !matches!(expr_type, RowColExpressionType::Literal) {
371 if let Some(current_numbering_type) = numbering_type {
372 if *current_numbering_type != expr_type {
373 let element_ref = item_element.borrow();
374 let span: &dyn Spanned =
375 if let Some(binding) = element_ref.bindings.get(prop_name) {
376 &*binding.borrow()
377 } else {
378 &*element_ref
379 };
380 diag.push_error(
381 format!("Cannot mix auto-numbering and runtime expressions for the '{prop_name}' property"),
382 span,
383 );
384 }
385 } else {
386 *numbering_type = Some(expr_type);
388 }
389 }
390 };
391
392 let row_expr_type =
393 RowColExpressionType::from_option_expr(&row_expr, row_is_number_literal);
394 check_numbering_consistency(row_expr_type, "row");
395
396 let col_expr_type =
397 RowColExpressionType::from_option_expr(&col_expr, col_is_number_literal);
398 check_numbering_consistency(col_expr_type, "col");
399 }
400
401 let propref = |name: &'static str| -> Option<RowColExpr> {
402 let nr = crate::layout::binding_reference(item_element, name).map(|nr| {
403 let e = nr.element();
405 let mut nr = nr.clone();
406 if e.borrow().repeated.is_some()
407 && let crate::langtype::ElementType::Component(c) = e.borrow().base_type.clone()
408 {
409 nr = NamedReference::new(&c.root_element, nr.name().clone())
410 };
411 nr
412 });
413 nr.map(RowColExpr::Named)
414 };
415
416 let row_expr = propref("row");
417 let col_expr = propref("col");
418 let rowspan_expr = propref("rowspan");
419 let colspan_expr = propref("colspan");
420
421 self.add_element_with_coord_as_expr(
422 item_element,
423 new_row,
424 (&row_expr, &col_expr),
425 (&rowspan_expr, &colspan_expr),
426 layout_cache_prop_h,
427 layout_cache_prop_v,
428 organized_data_prop,
429 diag,
430 num_cached_items,
431 );
432 }
433
434 fn add_element_with_coord(
435 &mut self,
436 item_element: &ElementRc,
437 (row, col): (u16, u16),
438 (rowspan, colspan): (u16, u16),
439 layout_cache_prop_h: &NamedReference,
440 layout_cache_prop_v: &NamedReference,
441 organized_data_prop: &NamedReference,
442 diag: &mut BuildDiagnostics,
443 num_cached_items: &mut usize,
444 ) {
445 self.add_element_with_coord_as_expr(
446 item_element,
447 false, (&Some(RowColExpr::Literal(row)), &Some(RowColExpr::Literal(col))),
449 (&Some(RowColExpr::Literal(rowspan)), &Some(RowColExpr::Literal(colspan))),
450 layout_cache_prop_h,
451 layout_cache_prop_v,
452 organized_data_prop,
453 diag,
454 num_cached_items,
455 )
456 }
457
458 fn add_repeated_row(
459 &mut self,
460 item_element: &ElementRc,
461 layout_cache_prop_h: &NamedReference,
462 layout_cache_prop_v: &NamedReference,
463 organized_data_prop: &NamedReference,
464 diag: &mut BuildDiagnostics,
465 num_cached_items: &mut usize,
466 ) {
467 let layout_item = create_layout_item(item_element, diag);
468 if let ElementType::Component(comp) = &item_element.borrow().base_type {
469 let repeated_children_count = comp.root_element.borrow().children.len();
470 let mut children_layout_items = Vec::new();
471 for child in &comp.root_element.borrow().children {
472 if child.borrow().repeated.is_some() {
473 diag.push_error(
474 "'if' or 'for' expressions are not currently supported within repeated Row elements (https://github.com/slint-ui/slint/issues/10670)".into(),
475 &*child.borrow(),
476 );
477 };
478
479 let sub_item = create_layout_item(child, diag);
480
481 let propref = |name: &'static str, elem: &ElementRc| -> Option<RowColExpr> {
483 let nr = crate::layout::binding_reference(elem, name).map(|nr| {
484 let e = nr.element();
485 let mut nr = nr.clone();
486 if e.borrow().repeated.is_some()
487 && let crate::langtype::ElementType::Component(c) =
488 e.borrow().base_type.clone()
489 {
490 nr = NamedReference::new(&c.root_element, nr.name().clone())
491 };
492 nr
493 });
494 nr.map(RowColExpr::Named)
495 };
496 let colspan_expr = propref("colspan", child);
497 let rowspan_expr = propref("rowspan", child);
498 let child_grid_cell = Rc::new(RefCell::new(GridLayoutCell {
499 new_row: false,
500 col_expr: RowColExpr::Auto,
501 row_expr: RowColExpr::Auto,
502 colspan_expr: colspan_expr.unwrap_or(RowColExpr::Literal(1)),
503 rowspan_expr: rowspan_expr.unwrap_or(RowColExpr::Literal(1)),
504 child_items: None,
505 }));
506 child.borrow_mut().grid_layout_cell = Some(child_grid_cell);
507
508 set_properties_from_cache(
510 &sub_item.elem,
511 &sub_item.item.constraints,
512 layout_cache_prop_h,
513 layout_cache_prop_v,
514 organized_data_prop,
515 *num_cached_items,
516 &layout_item.repeater_index,
517 repeated_children_count,
518 (&None::<RowColExpr>, &None::<RowColExpr>),
519 diag,
520 );
521 children_layout_items.push(sub_item.item);
522
523 *num_cached_items += 1;
524 }
525 let grid_layout_cell = Rc::new(RefCell::new(GridLayoutCell {
527 new_row: true,
528 col_expr: RowColExpr::Auto,
529 row_expr: RowColExpr::Auto,
530 colspan_expr: RowColExpr::Literal(1),
531 rowspan_expr: RowColExpr::Literal(1),
532 child_items: Some(children_layout_items),
533 }));
534 let grid_layout_element = GridLayoutElement {
535 cell: grid_layout_cell.clone(),
536 item: layout_item.item.clone(),
537 };
538 comp.root_element.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
539 self.elems.push(grid_layout_element);
540 }
541 }
542
543 fn add_element_with_coord_as_expr(
544 &mut self,
545 item_element: &ElementRc,
546 new_row: bool,
547 (row_expr, col_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
548 (rowspan_expr, colspan_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
549 layout_cache_prop_h: &NamedReference,
550 layout_cache_prop_v: &NamedReference,
551 organized_data_prop: &NamedReference,
552 diag: &mut BuildDiagnostics,
553 num_cached_items: &mut usize,
554 ) {
555 let layout_item = create_layout_item(item_element, diag);
556
557 set_properties_from_cache(
558 &layout_item.elem,
559 &layout_item.item.constraints,
560 layout_cache_prop_h,
561 layout_cache_prop_v,
562 organized_data_prop,
563 *num_cached_items,
564 &layout_item.repeater_index,
565 1,
566 (row_expr, col_expr),
567 diag,
568 );
569
570 let expr_or_default = |expr: &Option<RowColExpr>, default: RowColExpr| -> RowColExpr {
571 match expr {
572 Some(RowColExpr::Literal(v)) => RowColExpr::Literal(*v),
573 Some(RowColExpr::Named(nr)) => RowColExpr::Named(nr.clone()),
574 Some(RowColExpr::Auto) => RowColExpr::Auto,
575 None => default,
576 }
577 };
578
579 let grid_layout_cell = Rc::new(RefCell::new(GridLayoutCell {
580 new_row,
581 col_expr: expr_or_default(col_expr, RowColExpr::Auto),
582 row_expr: expr_or_default(row_expr, RowColExpr::Auto),
583 colspan_expr: expr_or_default(colspan_expr, RowColExpr::Literal(1)),
584 rowspan_expr: expr_or_default(rowspan_expr, RowColExpr::Literal(1)),
585 child_items: None,
586 }));
587 let grid_layout_element =
588 GridLayoutElement { cell: grid_layout_cell.clone(), item: layout_item.item.clone() };
589 layout_item.elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
590 self.elems.push(grid_layout_element);
591 *num_cached_items += 1;
592 }
593}
594
595fn lower_box_layout(
596 layout_element: &ElementRc,
597 diag: &mut BuildDiagnostics,
598 orientation: Orientation,
599) {
600 let mut layout = BoxLayout {
601 orientation,
602 elems: Default::default(),
603 geometry: LayoutGeometry::new(layout_element),
604 };
605
606 let layout_cache_prop =
607 create_new_prop(layout_element, SmolStr::new_static("layout-cache"), Type::LayoutCache);
608 let layout_info_prop_v = create_new_prop(
609 layout_element,
610 SmolStr::new_static("layoutinfo-v"),
611 layout_info_type().into(),
612 );
613 let layout_info_prop_h = create_new_prop(
614 layout_element,
615 SmolStr::new_static("layoutinfo-h"),
616 layout_info_type().into(),
617 );
618
619 let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
620
621 let (begin_padding, end_padding) = match orientation {
622 Orientation::Horizontal => (&layout.geometry.padding.top, &layout.geometry.padding.bottom),
623 Orientation::Vertical => (&layout.geometry.padding.left, &layout.geometry.padding.right),
624 };
625 let (pos, size, pad, ortho) = match orientation {
626 Orientation::Horizontal => ("x", "width", "y", "height"),
627 Orientation::Vertical => ("y", "height", "x", "width"),
628 };
629 let pad_expr = begin_padding.clone().map(Expression::PropertyReference);
630 let mut size_expr = Expression::PropertyReference(NamedReference::new(
631 layout_element,
632 SmolStr::new_static(ortho),
633 ));
634 if let Some(p) = begin_padding {
635 size_expr = Expression::BinaryExpression {
636 lhs: Box::new(std::mem::take(&mut size_expr)),
637 rhs: Box::new(Expression::PropertyReference(p.clone())),
638 op: '-',
639 }
640 }
641 if let Some(p) = end_padding {
642 size_expr = Expression::BinaryExpression {
643 lhs: Box::new(std::mem::take(&mut size_expr)),
644 rhs: Box::new(Expression::PropertyReference(p.clone())),
645 op: '-',
646 }
647 }
648
649 for layout_child in &layout_children {
650 let item = create_layout_item(layout_child, diag);
651 let index = layout.elems.len() * 2;
652 let rep_idx = &item.repeater_index;
653 let (fixed_size, fixed_ortho) = match orientation {
654 Orientation::Horizontal => {
655 (item.item.constraints.fixed_width, item.item.constraints.fixed_height)
656 }
657 Orientation::Vertical => {
658 (item.item.constraints.fixed_height, item.item.constraints.fixed_width)
659 }
660 };
661 let actual_elem = &item.elem;
662 set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, 2, diag);
664 if !fixed_size {
665 set_prop_from_cache(actual_elem, size, &layout_cache_prop, index + 1, rep_idx, 2, diag);
666 }
667 if let Some(pad_expr) = pad_expr.clone() {
668 actual_elem.borrow_mut().bindings.insert(pad.into(), RefCell::new(pad_expr.into()));
669 }
670 if !fixed_ortho {
671 actual_elem
672 .borrow_mut()
673 .bindings
674 .insert(ortho.into(), RefCell::new(size_expr.clone().into()));
675 }
676 layout.elems.push(item.item);
677 }
678 layout_element.borrow_mut().children = layout_children;
679 let span = layout_element.borrow().to_source_location();
680 layout_cache_prop.element().borrow_mut().bindings.insert(
681 layout_cache_prop.name().clone(),
682 BindingExpression::new_with_span(
683 Expression::SolveLayout(Layout::BoxLayout(layout.clone()), orientation),
684 span.clone(),
685 )
686 .into(),
687 );
688 layout_info_prop_h.element().borrow_mut().bindings.insert(
689 layout_info_prop_h.name().clone(),
690 BindingExpression::new_with_span(
691 Expression::ComputeLayoutInfo(
692 Layout::BoxLayout(layout.clone()),
693 Orientation::Horizontal,
694 ),
695 span.clone(),
696 )
697 .into(),
698 );
699 layout_info_prop_v.element().borrow_mut().bindings.insert(
700 layout_info_prop_v.name().clone(),
701 BindingExpression::new_with_span(
702 Expression::ComputeLayoutInfo(Layout::BoxLayout(layout.clone()), Orientation::Vertical),
703 span,
704 )
705 .into(),
706 );
707 layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
708 for d in layout_element.borrow_mut().debug.iter_mut() {
709 d.layout = Some(Layout::BoxLayout(layout.clone()));
710 }
711}
712
713fn lower_dialog_layout(
714 dialog_element: &ElementRc,
715 style_metrics: &Rc<Component>,
716 diag: &mut BuildDiagnostics,
717) {
718 let mut grid = GridLayout {
719 elems: Default::default(),
720 geometry: LayoutGeometry::new(dialog_element),
721 dialog_button_roles: None,
722 uses_auto: true,
723 };
724 let metrics = &style_metrics.root_element;
725 grid.geometry
726 .padding
727 .bottom
728 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
729 grid.geometry
730 .padding
731 .top
732 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
733 grid.geometry
734 .padding
735 .left
736 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
737 grid.geometry
738 .padding
739 .right
740 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
741 grid.geometry
742 .spacing
743 .horizontal
744 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
745 grid.geometry
746 .spacing
747 .vertical
748 .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
749
750 let layout_organized_data_prop = create_new_prop(
751 dialog_element,
752 SmolStr::new_static("layout-organized-data"),
753 Type::ArrayOfU16,
754 );
755 let layout_cache_prop_h =
756 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-h"), Type::LayoutCache);
757 let layout_cache_prop_v =
758 create_new_prop(dialog_element, SmolStr::new_static("layout-cache-v"), Type::LayoutCache);
759 let layout_info_prop_h = create_new_prop(
760 dialog_element,
761 SmolStr::new_static("layoutinfo-h"),
762 layout_info_type().into(),
763 );
764 let layout_info_prop_v = create_new_prop(
765 dialog_element,
766 SmolStr::new_static("layoutinfo-v"),
767 layout_info_type().into(),
768 );
769
770 let mut main_widget = None;
771 let mut button_roles = Vec::new();
772 let mut seen_buttons = HashSet::new();
773 let mut num_cached_items: usize = 0;
774 let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
775 for layout_child in &layout_children {
776 let dialog_button_role_binding =
777 layout_child.borrow_mut().bindings.remove("dialog-button-role");
778 let is_button = if let Some(role_binding) = dialog_button_role_binding {
779 let role_binding = role_binding.into_inner();
780 if let Expression::EnumerationValue(val) =
781 super::ignore_debug_hooks(&role_binding.expression)
782 {
783 let en = &val.enumeration;
784 debug_assert_eq!(en.name, "DialogButtonRole");
785 button_roles.push(en.values[val.value].clone());
786 if val.value == 0 {
787 diag.push_error(
788 "The `dialog-button-role` cannot be set explicitly to none".into(),
789 &role_binding,
790 );
791 }
792 } else {
793 diag.push_error(
794 "The `dialog-button-role` property must be known at compile-time".into(),
795 &role_binding,
796 );
797 }
798 true
799 } else if matches!(&layout_child.borrow().lookup_property("kind").property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
800 {
801 match layout_child.borrow().bindings.get("kind") {
803 None => diag.push_error(
804 "The `kind` property of the StandardButton in a Dialog must be set".into(),
805 &*layout_child.borrow(),
806 ),
807 Some(binding) => {
808 let binding = &*binding.borrow();
809 if let Expression::EnumerationValue(val) =
810 super::ignore_debug_hooks(&binding.expression)
811 {
812 let en = &val.enumeration;
813 debug_assert_eq!(en.name, "StandardButtonKind");
814 let kind = &en.values[val.value];
815 let role = match kind.as_str() {
816 "ok" => "accept",
817 "cancel" => "reject",
818 "apply" => "apply",
819 "close" => "reject",
820 "reset" => "reset",
821 "help" => "help",
822 "yes" => "accept",
823 "no" => "reject",
824 "abort" => "reject",
825 "retry" => "accept",
826 "ignore" => "accept",
827 _ => unreachable!(),
828 };
829 button_roles.push(role.into());
830 if !seen_buttons.insert(val.value) {
831 diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
832 } else if Rc::ptr_eq(
833 dialog_element,
834 &dialog_element
835 .borrow()
836 .enclosing_component
837 .upgrade()
838 .unwrap()
839 .root_element,
840 ) {
841 let clicked_ty =
842 layout_child.borrow().lookup_property("clicked").property_type;
843 if matches!(&clicked_ty, Type::Callback { .. })
844 && layout_child.borrow().bindings.get("clicked").is_none_or(|c| {
845 matches!(c.borrow().expression, Expression::Invalid)
846 })
847 {
848 dialog_element
849 .borrow_mut()
850 .property_declarations
851 .entry(format_smolstr!("{}-clicked", kind))
852 .or_insert_with(|| PropertyDeclaration {
853 property_type: clicked_ty,
854 node: None,
855 expose_in_public_api: true,
856 is_alias: Some(NamedReference::new(
857 layout_child,
858 SmolStr::new_static("clicked"),
859 )),
860 visibility: PropertyVisibility::InOut,
861 pure: None,
862 });
863 }
864 }
865 } else {
866 diag.push_error(
867 "The `kind` property of the StandardButton in a Dialog must be known at compile-time"
868 .into(),
869 binding,
870 );
871 }
872 }
873 }
874 true
875 } else {
876 false
877 };
878
879 if is_button {
880 grid.add_element_with_coord(
881 layout_child,
882 (1, button_roles.len() as u16),
883 (1, 1),
884 &layout_cache_prop_h,
885 &layout_cache_prop_v,
886 &layout_organized_data_prop,
887 diag,
888 &mut num_cached_items,
889 );
890 } else if main_widget.is_some() {
891 diag.push_error(
892 "A Dialog can have only one child element that is not a StandardButton".into(),
893 &*layout_child.borrow(),
894 );
895 } else {
896 main_widget = Some(layout_child.clone())
897 }
898 }
899 dialog_element.borrow_mut().children = layout_children;
900
901 if let Some(main_widget) = main_widget {
902 grid.add_element_with_coord(
903 &main_widget,
904 (0, 0),
905 (1, button_roles.len() as u16 + 1),
906 &layout_cache_prop_h,
907 &layout_cache_prop_v,
908 &layout_organized_data_prop,
909 diag,
910 &mut num_cached_items,
911 );
912 } else {
913 diag.push_error(
914 "A Dialog must have a single child element that is not StandardButton".into(),
915 &*dialog_element.borrow(),
916 );
917 }
918 grid.dialog_button_roles = Some(button_roles);
919
920 let span = dialog_element.borrow().to_source_location();
921 layout_organized_data_prop.element().borrow_mut().bindings.insert(
922 layout_organized_data_prop.name().clone(),
923 BindingExpression::new_with_span(
924 Expression::OrganizeGridLayout(grid.clone()),
925 span.clone(),
926 )
927 .into(),
928 );
929 layout_cache_prop_h.element().borrow_mut().bindings.insert(
930 layout_cache_prop_h.name().clone(),
931 BindingExpression::new_with_span(
932 Expression::SolveGridLayout {
933 layout_organized_data_prop: layout_organized_data_prop.clone(),
934 layout: grid.clone(),
935 orientation: Orientation::Horizontal,
936 },
937 span.clone(),
938 )
939 .into(),
940 );
941 layout_cache_prop_v.element().borrow_mut().bindings.insert(
942 layout_cache_prop_v.name().clone(),
943 BindingExpression::new_with_span(
944 Expression::SolveGridLayout {
945 layout_organized_data_prop: layout_organized_data_prop.clone(),
946 layout: grid.clone(),
947 orientation: Orientation::Vertical,
948 },
949 span.clone(),
950 )
951 .into(),
952 );
953 layout_info_prop_h.element().borrow_mut().bindings.insert(
954 layout_info_prop_h.name().clone(),
955 BindingExpression::new_with_span(
956 Expression::ComputeGridLayoutInfo {
957 layout_organized_data_prop: layout_organized_data_prop.clone(),
958 layout: grid.clone(),
959 orientation: Orientation::Horizontal,
960 },
961 span.clone(),
962 )
963 .into(),
964 );
965 layout_info_prop_v.element().borrow_mut().bindings.insert(
966 layout_info_prop_v.name().clone(),
967 BindingExpression::new_with_span(
968 Expression::ComputeGridLayoutInfo {
969 layout_organized_data_prop: layout_organized_data_prop.clone(),
970 layout: grid.clone(),
971 orientation: Orientation::Vertical,
972 },
973 span,
974 )
975 .into(),
976 );
977 dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
978 for d in dialog_element.borrow_mut().debug.iter_mut() {
979 d.layout = Some(Layout::GridLayout(grid.clone()));
980 }
981}
982
983struct CreateLayoutItemResult {
984 item: LayoutItem,
985 elem: ElementRc,
986 repeater_index: Option<Expression>,
987}
988
989fn create_layout_item(
991 item_element: &ElementRc,
992 diag: &mut BuildDiagnostics,
993) -> CreateLayoutItemResult {
994 let fix_explicit_percent = |prop: &str, item: &ElementRc| {
995 if !item.borrow().bindings.get(prop).is_some_and(|b| b.borrow().ty() == Type::Percent) {
996 return;
997 }
998 let min_name = format_smolstr!("min-{}", prop);
999 let max_name = format_smolstr!("max-{}", prop);
1000 let mut min_ref = BindingExpression::from(Expression::PropertyReference(
1001 NamedReference::new(item, min_name.clone()),
1002 ));
1003 let mut item = item.borrow_mut();
1004 let b = item.bindings.remove(prop).unwrap().into_inner();
1005 min_ref.span = b.span.clone();
1006 min_ref.priority = b.priority;
1007 item.bindings.insert(max_name.clone(), min_ref.into());
1008 item.bindings.insert(min_name.clone(), b.into());
1009 item.property_declarations.insert(
1010 min_name,
1011 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
1012 );
1013 item.property_declarations.insert(
1014 max_name,
1015 PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
1016 );
1017 };
1018 fix_explicit_percent("width", item_element);
1019 fix_explicit_percent("height", item_element);
1020
1021 item_element.borrow_mut().child_of_layout = true;
1022 let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
1023 let rep_comp = item_element.borrow().base_type.as_component().clone();
1024 fix_explicit_percent("width", &rep_comp.root_element);
1025 fix_explicit_percent("height", &rep_comp.root_element);
1026
1027 *rep_comp.root_constraints.borrow_mut() =
1028 LayoutConstraints::new(&rep_comp.root_element, diag, DiagnosticLevel::Error);
1029 rep_comp.root_element.borrow_mut().child_of_layout = true;
1030 (
1031 Some(if r.is_conditional_element {
1032 Expression::NumberLiteral(0., Unit::None)
1033 } else {
1034 Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
1035 }),
1036 rep_comp.root_element.clone(),
1037 )
1038 } else {
1039 (None, item_element.clone())
1040 };
1041
1042 let constraints = LayoutConstraints::new(&actual_elem, diag, DiagnosticLevel::Error);
1043 CreateLayoutItemResult {
1044 item: LayoutItem { element: item_element.clone(), constraints },
1045 elem: actual_elem,
1046 repeater_index,
1047 }
1048}
1049
1050fn set_prop_from_cache(
1051 elem: &ElementRc,
1052 prop: &str,
1053 layout_cache_prop: &NamedReference,
1054 index: usize,
1055 repeater_index: &Option<Expression>,
1056 entries_per_item: usize,
1057 diag: &mut BuildDiagnostics,
1058) {
1059 let old = elem.borrow_mut().bindings.insert(
1060 prop.into(),
1061 BindingExpression::new_with_span(
1062 Expression::LayoutCacheAccess {
1063 layout_cache_prop: layout_cache_prop.clone(),
1064 index,
1065 repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
1066 entries_per_item,
1067 },
1068 layout_cache_prop.element().borrow().to_source_location(),
1069 )
1070 .into(),
1071 );
1072 if let Some(old) = old.map(RefCell::into_inner) {
1073 diag.push_error(
1074 format!("The property '{prop}' cannot be set for elements placed in this layout, because the layout is already setting it"),
1075 &old,
1076 );
1077 }
1078}
1079
1080fn set_properties_from_cache(
1082 elem: &ElementRc,
1083 constraints: &LayoutConstraints,
1084 layout_cache_prop_h: &NamedReference,
1085 layout_cache_prop_v: &NamedReference,
1086 organized_data_prop: &NamedReference,
1087 num_cached_items: usize,
1088 rep_idx: &Option<Expression>,
1089 repeated_children_count: usize,
1090 (row_expr, col_expr): (&Option<RowColExpr>, &Option<RowColExpr>),
1091 diag: &mut BuildDiagnostics,
1092) {
1093 let cache_idx = num_cached_items * 2;
1094 let nr = 2 * repeated_children_count; set_prop_from_cache(elem, "x", layout_cache_prop_h, cache_idx, rep_idx, nr, diag);
1096 if !constraints.fixed_width {
1097 set_prop_from_cache(elem, "width", layout_cache_prop_h, cache_idx + 1, rep_idx, nr, diag);
1098 }
1099 set_prop_from_cache(elem, "y", layout_cache_prop_v, cache_idx, rep_idx, nr, diag);
1100 if !constraints.fixed_height {
1101 set_prop_from_cache(elem, "height", layout_cache_prop_v, cache_idx + 1, rep_idx, nr, diag);
1102 }
1103
1104 let org_index = num_cached_items * 4;
1105 let org_nr = 4 * repeated_children_count; if col_expr.is_none() {
1107 set_prop_from_cache(elem, "col", organized_data_prop, org_index, rep_idx, org_nr, diag);
1108 }
1109 if row_expr.is_none() {
1110 set_prop_from_cache(elem, "row", organized_data_prop, org_index + 2, rep_idx, org_nr, diag);
1111 }
1112}
1113
1114fn check_number_literal_is_positive_integer(
1118 expression: &Expression,
1119 name: &str,
1120 span: &dyn crate::diagnostics::Spanned,
1121 diag: &mut BuildDiagnostics,
1122) -> bool {
1123 match super::ignore_debug_hooks(expression) {
1124 Expression::NumberLiteral(v, Unit::None) => {
1125 if *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
1126 diag.push_error(format!("'{name}' must be a positive integer"), span);
1127 }
1128 true
1129 }
1130 Expression::UnaryOp { op: '-', sub } => {
1131 if let Expression::NumberLiteral(_, Unit::None) = super::ignore_debug_hooks(sub) {
1132 diag.push_error(format!("'{name}' must be a positive integer"), span);
1133 }
1134 true
1135 }
1136 Expression::Cast { from, .. } => {
1137 check_number_literal_is_positive_integer(from, name, span, diag)
1138 }
1139 _ => false,
1140 }
1141}
1142
1143fn recognized_layout_types() -> &'static [&'static str] {
1144 &["Row", "GridLayout", "HorizontalLayout", "VerticalLayout", "Dialog"]
1145}
1146
1147fn check_no_layout_properties(
1149 item: &ElementRc,
1150 layout_type: &Option<SmolStr>,
1151 parent_layout_type: &Option<SmolStr>,
1152 diag: &mut BuildDiagnostics,
1153) {
1154 let elem = item.borrow();
1155 for (prop, expr) in elem.bindings.iter() {
1156 if !matches!(parent_layout_type.as_deref(), Some("GridLayout") | Some("Row"))
1157 && matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan")
1158 {
1159 diag.push_error(format!("{prop} used outside of a GridLayout's cell"), &*expr.borrow());
1160 }
1161 if parent_layout_type.as_deref() != Some("Dialog")
1162 && matches!(prop.as_ref(), "dialog-button-role")
1163 {
1164 diag.push_error(
1165 format!("{prop} used outside of a Dialog's direct child"),
1166 &*expr.borrow(),
1167 );
1168 }
1169 if (layout_type.is_none()
1170 || !recognized_layout_types().contains(&layout_type.as_ref().unwrap().as_str()))
1171 && matches!(
1172 prop.as_ref(),
1173 "padding" | "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
1174 )
1175 && !check_inherits_layout(item)
1176 {
1177 diag.push_warning(
1178 format!("{prop} only has effect on layout elements"),
1179 &*expr.borrow(),
1180 );
1181 }
1182 }
1183
1184 fn check_inherits_layout(item: &ElementRc) -> bool {
1186 if let ElementType::Component(c) = &item.borrow().base_type {
1187 c.root_element.borrow().debug.iter().any(|d| d.layout.is_some())
1188 || check_inherits_layout(&c.root_element)
1189 } else {
1190 false
1191 }
1192 }
1193}
1194
1195pub fn check_window_layout(component: &Rc<Component>) {
1201 if component.root_constraints.borrow().fixed_height {
1202 adjust_window_layout(component, "height");
1203 }
1204 if component.root_constraints.borrow().fixed_width {
1205 adjust_window_layout(component, "width");
1206 }
1207}
1208
1209fn adjust_window_layout(component: &Rc<Component>, prop: &'static str) {
1210 let new_prop = crate::layout::create_new_prop(
1211 &component.root_element,
1212 format_smolstr!("fixed-{prop}"),
1213 Type::LogicalLength,
1214 );
1215 {
1216 let mut root = component.root_element.borrow_mut();
1217 if let Some(b) = root.bindings.remove(prop) {
1218 root.bindings.insert(new_prop.name().clone(), b);
1219 };
1220 let mut analysis = root.property_analysis.borrow_mut();
1221 if let Some(a) = analysis.remove(prop) {
1222 analysis.insert(new_prop.name().clone(), a);
1223 };
1224 drop(analysis);
1225 root.bindings.insert(
1226 prop.into(),
1227 RefCell::new(Expression::PropertyReference(new_prop.clone()).into()),
1228 );
1229 }
1230
1231 let old_prop = NamedReference::new(&component.root_element, SmolStr::new_static(prop));
1232 crate::object_tree::visit_all_named_references(component, &mut |nr| {
1233 if nr == &old_prop {
1234 *nr = new_prop.clone()
1235 }
1236 });
1237}