Skip to main content

i_slint_compiler/passes/
lower_layout.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//! This pass computes the layout constraint
5
6use 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    // lower the preferred-{width, height}: 100%;
28    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
89/// If the element is a layout, lower it to a Rectangle, and set the geometry property of the element inside it.
90/// Returns the name of the layout type if the element was a layout and has been lowered
91fn 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 now, the Dialog stays in the tree as a Dialog
115            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    // Create fake properties for the layout properties
125    // like alignment, spacing, spacing-horizontal, spacing-vertical
126    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// to detect mixing auto and non-literal expressions in row/col values
138#[derive(Debug, PartialEq, Eq)]
139enum RowColExpressionType {
140    Auto, // not specified
141    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; // true until the first child of a Row, or the first item after an empty Row
199    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; // the end of a Row means the next item is the first of a new row
249            if layout_child.borrow().has_popup_child {
250                // We need to keep that element otherwise the popup will malfunction
251                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        // Some compile-time checks
351        {
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                            // Store the first auto or runtime expression case we see
387                            *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                // similar to adjust_references in repeater_component.rs (which happened before these references existed)
404                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, // new_row
448            (&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                // Read colspan and rowspan from the child element
482                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                // The layout engine will set x,y,width,height,row,col for each of the repeated children
509                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            // Add a single GridLayoutElement for the repeated Row
526            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        // step=1 for box layout items (single element per repeater iteration)
663        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            // layout_child is a StandardButton
802            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
989/// Create a LayoutItem for the given `item_element`  returns None is the layout is empty
990fn 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
1080/// Helper function to set grid layout properties (x, y, width, height, col, row)
1081fn 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; // number of entries per repeated item
1095    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; // number of entries per repeated item
1106    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
1114// If it's a number literal, it must be a positive integer
1115// But also allow any other kind of expression
1116// Returns true for literals, false for other kinds of expressions
1117fn 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
1147/// Checks that there are no grid-layout specific properties used wrongly
1148fn 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    /// Check if the element inherits from a layout that was lowered
1185    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
1195/// For fixed layout, we need to dissociate the width and the height property of the WindowItem from width and height property
1196/// in slint such that the width and height property are actually constants.
1197///
1198/// The Slint runtime will change the width and height property of the native WindowItem to match those of the actual
1199/// window, but we don't want that to happen if we have a fixed layout.
1200pub 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}