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::{layout_info_type, TypeRegister};
16use smol_str::{format_smolstr, 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            let layout_type = lower_element_layout(
53                &component,
54                elem,
55                &type_loader.global_type_registry.borrow(),
56                style_metrics,
57                diag,
58            );
59            check_no_layout_properties(elem, &layout_type, &parent_layout_type, diag);
60            layout_type
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    diag: &mut BuildDiagnostics,
97) -> Option<SmolStr> {
98    let base_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type {
99        base_type.clone()
100    } else {
101        return None;
102    };
103    match base_type.name.as_str() {
104        "Row" => {
105            // We shouldn't lower layout if we have a Row in there. Unless the Row is the root of a repeated item,
106            // in which case another error has been reported
107            assert!(
108                diag.has_errors()
109                    && Rc::ptr_eq(&component.root_element, elem)
110                    && component
111                        .parent_element
112                        .upgrade()
113                        .is_some_and(|e| e.borrow().repeated.is_some()),
114                "Error should have been caught at element lookup time"
115            );
116            return None;
117        }
118        "GridLayout" => lower_grid_layout(component, elem, diag, type_register),
119        "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
120        "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
121        "Dialog" => {
122            lower_dialog_layout(elem, style_metrics, diag);
123            // return now, the Dialog stays in the tree as a Dialog
124            return Some(base_type.name.clone());
125        }
126        _ => return None,
127    };
128
129    let mut elem = elem.borrow_mut();
130    let elem = &mut *elem;
131    let prev_base = std::mem::replace(&mut elem.base_type, type_register.empty_type());
132    elem.default_fill_parent = (true, true);
133    // Create fake properties for the layout properties
134    // like alignment, spacing, spacing-horizontal, spacing-vertical
135    for (p, ty) in prev_base.property_list() {
136        if !elem.base_type.lookup_property(&p).is_valid()
137            && !elem.property_declarations.contains_key(&p)
138        {
139            elem.property_declarations.insert(p, ty.into());
140        }
141    }
142
143    Some(base_type.name.clone())
144}
145
146fn lower_grid_layout(
147    component: &Rc<Component>,
148    grid_layout_element: &ElementRc,
149    diag: &mut BuildDiagnostics,
150    type_register: &TypeRegister,
151) {
152    let mut grid = GridLayout {
153        elems: Default::default(),
154        geometry: LayoutGeometry::new(grid_layout_element),
155        dialog_button_roles: None,
156    };
157
158    let layout_cache_prop_h = create_new_prop(
159        grid_layout_element,
160        SmolStr::new_static("layout-cache-h"),
161        Type::LayoutCache,
162    );
163    let layout_cache_prop_v = create_new_prop(
164        grid_layout_element,
165        SmolStr::new_static("layout-cache-v"),
166        Type::LayoutCache,
167    );
168    let layout_info_prop_h = create_new_prop(
169        grid_layout_element,
170        SmolStr::new_static("layoutinfo-h"),
171        layout_info_type().into(),
172    );
173    let layout_info_prop_v = create_new_prop(
174        grid_layout_element,
175        SmolStr::new_static("layoutinfo-v"),
176        layout_info_type().into(),
177    );
178
179    let mut row = 0;
180    let mut col = 0;
181
182    let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
183    let mut collected_children = Vec::new();
184    for layout_child in layout_children {
185        let is_row = if let ElementType::Builtin(be) = &layout_child.borrow().base_type {
186            be.name == "Row"
187        } else {
188            false
189        };
190        if is_row {
191            if col > 0 {
192                row += 1;
193                col = 0;
194            }
195            let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
196            for x in row_children {
197                grid.add_element(
198                    &x,
199                    (&mut row, &mut col),
200                    &layout_cache_prop_h,
201                    &layout_cache_prop_v,
202                    diag,
203                );
204                col += 1;
205                collected_children.push(x);
206            }
207            if col > 0 {
208                row += 1;
209                col = 0;
210            }
211            if layout_child.borrow().has_popup_child {
212                // We need to keep that element otherwise the popup will malfunction
213                layout_child.borrow_mut().base_type = type_register.empty_type();
214                collected_children.push(layout_child);
215            } else {
216                component.optimized_elements.borrow_mut().push(layout_child);
217            }
218        } else {
219            grid.add_element(
220                &layout_child,
221                (&mut row, &mut col),
222                &layout_cache_prop_h,
223                &layout_cache_prop_v,
224                diag,
225            );
226            col += 1;
227            collected_children.push(layout_child);
228        }
229    }
230    grid_layout_element.borrow_mut().children = collected_children;
231    let span = grid_layout_element.borrow().to_source_location();
232    layout_cache_prop_h.element().borrow_mut().bindings.insert(
233        layout_cache_prop_h.name().clone(),
234        BindingExpression::new_with_span(
235            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
236            span.clone(),
237        )
238        .into(),
239    );
240    layout_cache_prop_v.element().borrow_mut().bindings.insert(
241        layout_cache_prop_v.name().clone(),
242        BindingExpression::new_with_span(
243            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
244            span.clone(),
245        )
246        .into(),
247    );
248    layout_info_prop_h.element().borrow_mut().bindings.insert(
249        layout_info_prop_h.name().clone(),
250        BindingExpression::new_with_span(
251            Expression::ComputeLayoutInfo(
252                Layout::GridLayout(grid.clone()),
253                Orientation::Horizontal,
254            ),
255            span.clone(),
256        )
257        .into(),
258    );
259    layout_info_prop_v.element().borrow_mut().bindings.insert(
260        layout_info_prop_v.name().clone(),
261        BindingExpression::new_with_span(
262            Expression::ComputeLayoutInfo(Layout::GridLayout(grid.clone()), Orientation::Vertical),
263            span,
264        )
265        .into(),
266    );
267    grid_layout_element.borrow_mut().layout_info_prop =
268        Some((layout_info_prop_h, layout_info_prop_v));
269    for d in grid_layout_element.borrow_mut().debug.iter_mut() {
270        d.layout = Some(Layout::GridLayout(grid.clone()));
271    }
272}
273
274impl GridLayout {
275    fn add_element(
276        &mut self,
277        item_element: &ElementRc,
278        (row, col): (&mut u16, &mut u16),
279        layout_cache_prop_h: &NamedReference,
280        layout_cache_prop_v: &NamedReference,
281        diag: &mut BuildDiagnostics,
282    ) {
283        let mut get_const_value = |name: &str| {
284            item_element
285                .borrow_mut()
286                .bindings
287                .get(name)
288                .and_then(|e| eval_const_expr(&e.borrow().expression, name, &*e.borrow(), diag))
289        };
290        let colspan = get_const_value("colspan").unwrap_or(1);
291        let rowspan = get_const_value("rowspan").unwrap_or(1);
292        if let Some(r) = get_const_value("row") {
293            *row = r;
294            *col = 0;
295        }
296        if let Some(c) = get_const_value("col") {
297            *col = c;
298        }
299
300        let result = self.add_element_with_coord(
301            item_element,
302            (*row, *col),
303            (rowspan, colspan),
304            layout_cache_prop_h,
305            layout_cache_prop_v,
306            diag,
307        );
308        if let Some(layout_item) = result {
309            let e = &layout_item.elem;
310            insert_fake_property(e, "row", Expression::NumberLiteral(*row as f64, Unit::None));
311            insert_fake_property(e, "col", Expression::NumberLiteral(*col as f64, Unit::None));
312        }
313    }
314
315    fn add_element_with_coord(
316        &mut self,
317        item_element: &ElementRc,
318        (row, col): (u16, u16),
319        (rowspan, colspan): (u16, u16),
320        layout_cache_prop_h: &NamedReference,
321        layout_cache_prop_v: &NamedReference,
322        diag: &mut BuildDiagnostics,
323    ) -> Option<CreateLayoutItemResult> {
324        let index = self.elems.len();
325        let result = create_layout_item(item_element, diag);
326        if let Some(ref layout_item) = result {
327            if layout_item.repeater_index.is_some() {
328                diag.push_error(
329                    "'if' or 'for' expressions are not currently supported in grid layouts"
330                        .to_string(),
331                    &*item_element.borrow(),
332                );
333                return None;
334            }
335
336            let e = &layout_item.elem;
337            set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag);
338            if !layout_item.item.constraints.fixed_width {
339                set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag);
340            }
341            set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag);
342            if !layout_item.item.constraints.fixed_height {
343                set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag);
344            }
345
346            self.elems.push(GridLayoutElement {
347                col,
348                row,
349                colspan,
350                rowspan,
351                item: layout_item.item.clone(),
352            });
353        }
354        result
355    }
356}
357
358fn lower_box_layout(
359    layout_element: &ElementRc,
360    diag: &mut BuildDiagnostics,
361    orientation: Orientation,
362) {
363    let mut layout = BoxLayout {
364        orientation,
365        elems: Default::default(),
366        geometry: LayoutGeometry::new(layout_element),
367    };
368
369    let layout_cache_prop =
370        create_new_prop(layout_element, SmolStr::new_static("layout-cache"), Type::LayoutCache);
371    let layout_info_prop_v = create_new_prop(
372        layout_element,
373        SmolStr::new_static("layoutinfo-v"),
374        layout_info_type().into(),
375    );
376    let layout_info_prop_h = create_new_prop(
377        layout_element,
378        SmolStr::new_static("layoutinfo-h"),
379        layout_info_type().into(),
380    );
381
382    let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
383
384    let (begin_padding, end_padding) = match orientation {
385        Orientation::Horizontal => (&layout.geometry.padding.top, &layout.geometry.padding.bottom),
386        Orientation::Vertical => (&layout.geometry.padding.left, &layout.geometry.padding.right),
387    };
388    let (pos, size, pad, ortho) = match orientation {
389        Orientation::Horizontal => ("x", "width", "y", "height"),
390        Orientation::Vertical => ("y", "height", "x", "width"),
391    };
392    let pad_expr = begin_padding.clone().map(Expression::PropertyReference);
393    let mut size_expr = Expression::PropertyReference(NamedReference::new(
394        layout_element,
395        SmolStr::new_static(ortho),
396    ));
397    if let Some(p) = begin_padding {
398        size_expr = Expression::BinaryExpression {
399            lhs: Box::new(std::mem::take(&mut size_expr)),
400            rhs: Box::new(Expression::PropertyReference(p.clone())),
401            op: '-',
402        }
403    }
404    if let Some(p) = end_padding {
405        size_expr = Expression::BinaryExpression {
406            lhs: Box::new(std::mem::take(&mut size_expr)),
407            rhs: Box::new(Expression::PropertyReference(p.clone())),
408            op: '-',
409        }
410    }
411
412    for layout_child in &layout_children {
413        if let Some(item) = create_layout_item(layout_child, diag) {
414            let index = layout.elems.len() * 2;
415            let rep_idx = &item.repeater_index;
416            let (fixed_size, fixed_ortho) = match orientation {
417                Orientation::Horizontal => {
418                    (item.item.constraints.fixed_width, item.item.constraints.fixed_height)
419                }
420                Orientation::Vertical => {
421                    (item.item.constraints.fixed_height, item.item.constraints.fixed_width)
422                }
423            };
424            let actual_elem = &item.elem;
425            set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, diag);
426            if !fixed_size {
427                set_prop_from_cache(
428                    actual_elem,
429                    size,
430                    &layout_cache_prop,
431                    index + 1,
432                    rep_idx,
433                    diag,
434                );
435            }
436            if let Some(pad_expr) = pad_expr.clone() {
437                actual_elem.borrow_mut().bindings.insert(pad.into(), RefCell::new(pad_expr.into()));
438            }
439            if !fixed_ortho {
440                actual_elem
441                    .borrow_mut()
442                    .bindings
443                    .insert(ortho.into(), RefCell::new(size_expr.clone().into()));
444            }
445            layout.elems.push(item.item);
446        }
447    }
448    layout_element.borrow_mut().children = layout_children;
449    let span = layout_element.borrow().to_source_location();
450    layout_cache_prop.element().borrow_mut().bindings.insert(
451        layout_cache_prop.name().clone(),
452        BindingExpression::new_with_span(
453            Expression::SolveLayout(Layout::BoxLayout(layout.clone()), orientation),
454            span.clone(),
455        )
456        .into(),
457    );
458    layout_info_prop_h.element().borrow_mut().bindings.insert(
459        layout_info_prop_h.name().clone(),
460        BindingExpression::new_with_span(
461            Expression::ComputeLayoutInfo(
462                Layout::BoxLayout(layout.clone()),
463                Orientation::Horizontal,
464            ),
465            span.clone(),
466        )
467        .into(),
468    );
469    layout_info_prop_v.element().borrow_mut().bindings.insert(
470        layout_info_prop_v.name().clone(),
471        BindingExpression::new_with_span(
472            Expression::ComputeLayoutInfo(Layout::BoxLayout(layout.clone()), Orientation::Vertical),
473            span,
474        )
475        .into(),
476    );
477    layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
478    for d in layout_element.borrow_mut().debug.iter_mut() {
479        d.layout = Some(Layout::BoxLayout(layout.clone()));
480    }
481}
482
483fn lower_dialog_layout(
484    dialog_element: &ElementRc,
485    style_metrics: &Rc<Component>,
486    diag: &mut BuildDiagnostics,
487) {
488    let mut grid = GridLayout {
489        elems: Default::default(),
490        geometry: LayoutGeometry::new(dialog_element),
491        dialog_button_roles: None,
492    };
493    let metrics = &style_metrics.root_element;
494    grid.geometry
495        .padding
496        .bottom
497        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
498    grid.geometry
499        .padding
500        .top
501        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
502    grid.geometry
503        .padding
504        .left
505        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
506    grid.geometry
507        .padding
508        .right
509        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-padding")));
510    grid.geometry
511        .spacing
512        .horizontal
513        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
514    grid.geometry
515        .spacing
516        .vertical
517        .get_or_insert(NamedReference::new(metrics, SmolStr::new_static("layout-spacing")));
518
519    let layout_cache_prop_h =
520        create_new_prop(dialog_element, SmolStr::new_static("layout-cache-h"), Type::LayoutCache);
521    let layout_cache_prop_v =
522        create_new_prop(dialog_element, SmolStr::new_static("layout-cache-v"), Type::LayoutCache);
523    let layout_info_prop_h = create_new_prop(
524        dialog_element,
525        SmolStr::new_static("layoutinfo-h"),
526        layout_info_type().into(),
527    );
528    let layout_info_prop_v = create_new_prop(
529        dialog_element,
530        SmolStr::new_static("layoutinfo-v"),
531        layout_info_type().into(),
532    );
533
534    let mut main_widget = None;
535    let mut button_roles = vec![];
536    let mut seen_buttons = HashSet::new();
537    let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
538    for layout_child in &layout_children {
539        let dialog_button_role_binding =
540            layout_child.borrow_mut().bindings.remove("dialog-button-role");
541        let is_button = if let Some(role_binding) = dialog_button_role_binding {
542            let role_binding = role_binding.into_inner();
543            if let Expression::EnumerationValue(val) =
544                super::ignore_debug_hooks(&role_binding.expression)
545            {
546                let en = &val.enumeration;
547                debug_assert_eq!(en.name, "DialogButtonRole");
548                button_roles.push(en.values[val.value].clone());
549                if val.value == 0 {
550                    diag.push_error(
551                        "The `dialog-button-role` cannot be set explicitly to none".into(),
552                        &role_binding,
553                    );
554                }
555            } else {
556                diag.push_error(
557                    "The `dialog-button-role` property must be known at compile-time".into(),
558                    &role_binding,
559                );
560            }
561            true
562        } else if matches!(&layout_child.borrow().lookup_property("kind").property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
563        {
564            // layout_child is a StandardButton
565            match layout_child.borrow().bindings.get("kind") {
566                None => diag.push_error(
567                    "The `kind` property of the StandardButton in a Dialog must be set".into(),
568                    &*layout_child.borrow(),
569                ),
570                Some(binding) => {
571                    let binding = &*binding.borrow();
572                    if let Expression::EnumerationValue(val) =
573                        super::ignore_debug_hooks(&binding.expression)
574                    {
575                        let en = &val.enumeration;
576                        debug_assert_eq!(en.name, "StandardButtonKind");
577                        let kind = &en.values[val.value];
578                        let role = match kind.as_str() {
579                            "ok" => "accept",
580                            "cancel" => "reject",
581                            "apply" => "apply",
582                            "close" => "reject",
583                            "reset" => "reset",
584                            "help" => "help",
585                            "yes" => "accept",
586                            "no" => "reject",
587                            "abort" => "reject",
588                            "retry" => "accept",
589                            "ignore" => "accept",
590                            _ => unreachable!(),
591                        };
592                        button_roles.push(role.into());
593                        if !seen_buttons.insert(val.value) {
594                            diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
595                        } else if Rc::ptr_eq(
596                            dialog_element,
597                            &dialog_element
598                                .borrow()
599                                .enclosing_component
600                                .upgrade()
601                                .unwrap()
602                                .root_element,
603                        ) {
604                            let clicked_ty =
605                                layout_child.borrow().lookup_property("clicked").property_type;
606                            if matches!(&clicked_ty, Type::Callback { .. })
607                                && layout_child.borrow().bindings.get("clicked").is_none_or(|c| {
608                                    matches!(c.borrow().expression, Expression::Invalid)
609                                })
610                            {
611                                dialog_element
612                                    .borrow_mut()
613                                    .property_declarations
614                                    .entry(format_smolstr!("{}-clicked", kind))
615                                    .or_insert_with(|| PropertyDeclaration {
616                                        property_type: clicked_ty,
617                                        node: None,
618                                        expose_in_public_api: true,
619                                        is_alias: Some(NamedReference::new(
620                                            layout_child,
621                                            SmolStr::new_static("clicked"),
622                                        )),
623                                        visibility: PropertyVisibility::InOut,
624                                        pure: None,
625                                    });
626                            }
627                        }
628                    } else {
629                        diag.push_error(
630                            "The `kind` property of the StandardButton in a Dialog must be known at compile-time"
631                                .into(),
632                            binding,
633                        );
634                    }
635                }
636            }
637            true
638        } else {
639            false
640        };
641
642        if is_button {
643            grid.add_element_with_coord(
644                layout_child,
645                (1, button_roles.len() as u16),
646                (1, 1),
647                &layout_cache_prop_h,
648                &layout_cache_prop_v,
649                diag,
650            );
651        } else if main_widget.is_some() {
652            diag.push_error(
653                "A Dialog can have only one child element that is not a StandardButton".into(),
654                &*layout_child.borrow(),
655            );
656        } else {
657            main_widget = Some(layout_child.clone())
658        }
659    }
660    dialog_element.borrow_mut().children = layout_children;
661
662    if let Some(main_widget) = main_widget {
663        grid.add_element_with_coord(
664            &main_widget,
665            (0, 0),
666            (1, button_roles.len() as u16 + 1),
667            &layout_cache_prop_h,
668            &layout_cache_prop_v,
669            diag,
670        );
671    } else {
672        diag.push_error(
673            "A Dialog must have a single child element that is not StandardButton".into(),
674            &*dialog_element.borrow(),
675        );
676    }
677    grid.dialog_button_roles = Some(button_roles);
678
679    let span = dialog_element.borrow().to_source_location();
680    layout_cache_prop_h.element().borrow_mut().bindings.insert(
681        layout_cache_prop_h.name().clone(),
682        BindingExpression::new_with_span(
683            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
684            span.clone(),
685        )
686        .into(),
687    );
688    layout_cache_prop_v.element().borrow_mut().bindings.insert(
689        layout_cache_prop_v.name().clone(),
690        BindingExpression::new_with_span(
691            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
692            span.clone(),
693        )
694        .into(),
695    );
696    layout_info_prop_h.element().borrow_mut().bindings.insert(
697        layout_info_prop_h.name().clone(),
698        BindingExpression::new_with_span(
699            Expression::ComputeLayoutInfo(
700                Layout::GridLayout(grid.clone()),
701                Orientation::Horizontal,
702            ),
703            span.clone(),
704        )
705        .into(),
706    );
707    layout_info_prop_v.element().borrow_mut().bindings.insert(
708        layout_info_prop_v.name().clone(),
709        BindingExpression::new_with_span(
710            Expression::ComputeLayoutInfo(Layout::GridLayout(grid.clone()), Orientation::Vertical),
711            span,
712        )
713        .into(),
714    );
715    dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
716    for d in dialog_element.borrow_mut().debug.iter_mut() {
717        d.layout = Some(Layout::GridLayout(grid.clone()));
718    }
719}
720
721struct CreateLayoutItemResult {
722    item: LayoutItem,
723    elem: ElementRc,
724    repeater_index: Option<Expression>,
725}
726
727/// Create a LayoutItem for the given `item_element`  returns None is the layout is empty
728fn create_layout_item(
729    item_element: &ElementRc,
730    diag: &mut BuildDiagnostics,
731) -> Option<CreateLayoutItemResult> {
732    let fix_explicit_percent = |prop: &str, item: &ElementRc| {
733        if !item.borrow().bindings.get(prop).is_some_and(|b| b.borrow().ty() == Type::Percent) {
734            return;
735        }
736        let min_name = format_smolstr!("min-{}", prop);
737        let max_name = format_smolstr!("max-{}", prop);
738        let mut min_ref = BindingExpression::from(Expression::PropertyReference(
739            NamedReference::new(item, min_name.clone()),
740        ));
741        let mut item = item.borrow_mut();
742        let b = item.bindings.remove(prop).unwrap().into_inner();
743        min_ref.span = b.span.clone();
744        min_ref.priority = b.priority;
745        item.bindings.insert(max_name.clone(), min_ref.into());
746        item.bindings.insert(min_name.clone(), b.into());
747        item.property_declarations.insert(
748            min_name,
749            PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
750        );
751        item.property_declarations.insert(
752            max_name,
753            PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
754        );
755    };
756    fix_explicit_percent("width", item_element);
757    fix_explicit_percent("height", item_element);
758
759    item_element.borrow_mut().child_of_layout = true;
760    let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
761        let rep_comp = item_element.borrow().base_type.as_component().clone();
762        fix_explicit_percent("width", &rep_comp.root_element);
763        fix_explicit_percent("height", &rep_comp.root_element);
764
765        *rep_comp.root_constraints.borrow_mut() =
766            LayoutConstraints::new(&rep_comp.root_element, diag, DiagnosticLevel::Error);
767        rep_comp.root_element.borrow_mut().child_of_layout = true;
768        (
769            Some(if r.is_conditional_element {
770                Expression::NumberLiteral(0., Unit::None)
771            } else {
772                Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
773            }),
774            rep_comp.root_element.clone(),
775        )
776    } else {
777        (None, item_element.clone())
778    };
779
780    let constraints = LayoutConstraints::new(&actual_elem, diag, DiagnosticLevel::Error);
781    Some(CreateLayoutItemResult {
782        item: LayoutItem { element: item_element.clone(), constraints },
783        elem: actual_elem,
784        repeater_index,
785    })
786}
787
788fn insert_fake_property(elem: &ElementRc, prop: &str, expr: Expression) {
789    let mut elem_mut = elem.borrow_mut();
790    let span = elem_mut.to_source_location();
791    if let std::collections::btree_map::Entry::Vacant(e) = elem_mut.bindings.entry(prop.into()) {
792        let binding = BindingExpression::new_with_span(expr, span);
793        e.insert(binding.into());
794    }
795}
796
797fn set_prop_from_cache(
798    elem: &ElementRc,
799    prop: &str,
800    layout_cache_prop: &NamedReference,
801    index: usize,
802    repeater_index: &Option<Expression>,
803    diag: &mut BuildDiagnostics,
804) {
805    let old = elem.borrow_mut().bindings.insert(
806        prop.into(),
807        BindingExpression::new_with_span(
808            Expression::LayoutCacheAccess {
809                layout_cache_prop: layout_cache_prop.clone(),
810                index,
811                repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
812            },
813            layout_cache_prop.element().borrow().to_source_location(),
814        )
815        .into(),
816    );
817    if let Some(old) = old.map(RefCell::into_inner) {
818        diag.push_error(
819            format!("The property '{prop}' cannot be set for elements placed in this layout, because the layout is already setting it"),
820            &old,
821        );
822    }
823}
824
825fn eval_const_expr(
826    expression: &Expression,
827    name: &str,
828    span: &dyn crate::diagnostics::Spanned,
829    diag: &mut BuildDiagnostics,
830) -> Option<u16> {
831    match super::ignore_debug_hooks(expression) {
832        Expression::NumberLiteral(v, Unit::None) => {
833            if *v < 0. || *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
834                diag.push_error(format!("'{name}' must be a positive integer"), span);
835                None
836            } else {
837                Some(*v as u16)
838            }
839        }
840        Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag),
841        _ => {
842            diag.push_error(format!("'{name}' must be an integer literal"), span);
843            None
844        }
845    }
846}
847
848/// Checks that there is grid-layout specific properties left
849fn check_no_layout_properties(
850    item: &ElementRc,
851    layout_type: &Option<SmolStr>,
852    parent_layout_type: &Option<SmolStr>,
853    diag: &mut BuildDiagnostics,
854) {
855    let elem = item.borrow();
856    for (prop, expr) in elem.bindings.iter() {
857        if parent_layout_type.as_deref() != Some("GridLayout")
858            && matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan")
859        {
860            diag.push_error(format!("{prop} used outside of a GridLayout's cell"), &*expr.borrow());
861        }
862        if parent_layout_type.as_deref() != Some("Dialog")
863            && matches!(prop.as_ref(), "dialog-button-role")
864        {
865            diag.push_error(
866                format!("{prop} used outside of a Dialog's direct child"),
867                &*expr.borrow(),
868            );
869        }
870        if layout_type.is_none()
871            && matches!(
872                prop.as_ref(),
873                "padding" | "padding-left" | "padding-right" | "padding-top" | "padding-bottom"
874            )
875        {
876            diag.push_warning(
877                format!("{prop} only has effect on layout elements"),
878                &*expr.borrow(),
879            );
880        }
881    }
882}
883
884/// For fixed layout, we need to dissociate the width and the height property of the WindowItem from width and height property
885/// in slint such that the width and height property are actually constants.
886///
887/// The Slint runtime will change the width and height property of the native WindowItem to match those of the actual
888/// window, but we don't want that to happen if we have a fixed layout.
889pub fn check_window_layout(component: &Rc<Component>) {
890    if component.root_constraints.borrow().fixed_height {
891        adjust_window_layout(component, "height");
892    }
893    if component.root_constraints.borrow().fixed_width {
894        adjust_window_layout(component, "width");
895    }
896}
897
898fn adjust_window_layout(component: &Rc<Component>, prop: &'static str) {
899    let new_prop = crate::layout::create_new_prop(
900        &component.root_element,
901        format_smolstr!("fixed-{prop}"),
902        Type::LogicalLength,
903    );
904    {
905        let mut root = component.root_element.borrow_mut();
906        if let Some(b) = root.bindings.remove(prop) {
907            root.bindings.insert(new_prop.name().clone(), b);
908        };
909        let mut analysis = root.property_analysis.borrow_mut();
910        if let Some(a) = analysis.remove(prop) {
911            analysis.insert(new_prop.name().clone(), a);
912        };
913        drop(analysis);
914        root.bindings.insert(
915            prop.into(),
916            RefCell::new(Expression::PropertyReference(new_prop.clone()).into()),
917        );
918    }
919
920    let old_prop = NamedReference::new(&component.root_element, SmolStr::new_static(prop));
921    crate::object_tree::visit_all_named_references(component, &mut |nr| {
922        if nr == &old_prop {
923            *nr = new_prop.clone()
924        }
925    });
926}