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