sixtyfps_compilerlib/passes/
lower_layout.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
3
4//! Passe that compute the layout constraint
5
6use lyon_path::geom::euclid::approxeq::ApproxEq;
7
8use crate::diagnostics::BuildDiagnostics;
9use crate::diagnostics::Spanned;
10use crate::expression_tree::*;
11use crate::langtype::Type;
12use crate::layout::*;
13use crate::object_tree::*;
14use crate::typeloader::TypeLoader;
15use crate::typeregister::TypeRegister;
16use std::cell::RefCell;
17use std::collections::HashSet;
18use std::rc::Rc;
19
20pub async fn lower_layouts(
21    component: &Rc<Component>,
22    type_loader: &mut TypeLoader<'_>,
23    diag: &mut BuildDiagnostics,
24) {
25    // Ignore import errors
26    let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
27    let style_metrics = type_loader
28        .import_type("sixtyfps_widgets.60", "StyleMetrics", &mut build_diags_to_ignore)
29        .await;
30    let style_metrics =
31        style_metrics.and_then(|sm| if let Type::Component(c) = sm { Some(c) } else { None });
32
33    *component.root_constraints.borrow_mut() =
34        LayoutConstraints::new(&component.root_element, diag);
35
36    recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
37        let component = elem.borrow().enclosing_component.upgrade().unwrap();
38        lower_element_layout(
39            &component,
40            elem,
41            &type_loader.global_type_registry.borrow(),
42            &style_metrics,
43            diag,
44        );
45        check_no_layout_properties(elem, diag);
46    });
47}
48
49fn lower_element_layout(
50    component: &Rc<Component>,
51    elem: &ElementRc,
52    type_register: &TypeRegister,
53    style_metrics: &Option<Rc<Component>>,
54    diag: &mut BuildDiagnostics,
55) {
56    let base_type = if let Type::Builtin(base_type) = &elem.borrow().base_type {
57        base_type.clone()
58    } else {
59        return;
60    };
61    match base_type.name.as_str() {
62        "Row" => panic!("Error caught at element lookup time"),
63        "GridLayout" => lower_grid_layout(component, elem, diag),
64        "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
65        "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
66        "PathLayout" => lower_path_layout(elem, diag),
67        "Dialog" => {
68            lower_dialog_layout(elem, style_metrics, diag);
69            return; // the Dialog stays in the tree as a Dialog
70        }
71        _ => return,
72    };
73
74    {
75        let mut elem = elem.borrow_mut();
76        let elem = &mut *elem;
77        let prev_base = std::mem::replace(&mut elem.base_type, type_register.lookup("Rectangle"));
78        // Create fake properties for the layout properties
79        for p in elem.bindings.keys() {
80            if !elem.base_type.lookup_property(p).is_valid()
81                && !elem.property_declarations.contains_key(p)
82            {
83                let ty = prev_base.lookup_property(p).property_type;
84                if ty != Type::Invalid {
85                    elem.property_declarations.insert(p.into(), ty.into());
86                }
87            }
88        }
89    }
90}
91
92pub fn is_layout_element(element: &ElementRc) -> bool {
93    matches!(&element.borrow().base_type, Type::Builtin(n) if n.name == "GridLayout" || n.name == "HorizontalLayout" || n.name == "VerticalLayout" || n.name == "PathLayout")
94}
95
96fn lower_grid_layout(
97    component: &Rc<Component>,
98    grid_layout_element: &ElementRc,
99    diag: &mut BuildDiagnostics,
100) {
101    let mut grid = GridLayout {
102        elems: Default::default(),
103        geometry: LayoutGeometry::new(grid_layout_element),
104        dialog_button_roles: None,
105    };
106
107    let layout_cache_prop_h =
108        create_new_prop(grid_layout_element, "layout-cache-h", Type::LayoutCache);
109    let layout_cache_prop_v =
110        create_new_prop(grid_layout_element, "layout-cache-v", Type::LayoutCache);
111    let layout_info_prop_h =
112        create_new_prop(grid_layout_element, "layoutinfo-h", layout_info_type());
113    let layout_info_prop_v =
114        create_new_prop(grid_layout_element, "layoutinfo-v", layout_info_type());
115
116    let mut row = 0;
117    let mut col = 0;
118
119    let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
120    let mut collected_children = Vec::new();
121    for layout_child in layout_children {
122        let is_row = if let Type::Builtin(be) = &layout_child.borrow().base_type {
123            be.name == "Row"
124        } else {
125            false
126        };
127        if is_row {
128            if col > 0 {
129                row += 1;
130                col = 0;
131            }
132            let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
133            for x in row_children {
134                grid.add_element(
135                    &x,
136                    (&mut row, &mut col),
137                    &layout_cache_prop_h,
138                    &layout_cache_prop_v,
139                    diag,
140                );
141                col += 1;
142                collected_children.push(x);
143            }
144            if col > 0 {
145                row += 1;
146                col = 0;
147            }
148            component.optimized_elements.borrow_mut().push(layout_child);
149        } else {
150            grid.add_element(
151                &layout_child,
152                (&mut row, &mut col),
153                &layout_cache_prop_h,
154                &layout_cache_prop_v,
155                diag,
156            );
157            col += 1;
158            collected_children.push(layout_child);
159        }
160    }
161    grid_layout_element.borrow_mut().children = collected_children;
162    let span = grid_layout_element.borrow().to_source_location();
163    layout_cache_prop_h.element().borrow_mut().bindings.insert(
164        layout_cache_prop_h.name().into(),
165        BindingExpression::new_with_span(
166            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
167            span.clone(),
168        )
169        .into(),
170    );
171    layout_cache_prop_v.element().borrow_mut().bindings.insert(
172        layout_cache_prop_v.name().into(),
173        BindingExpression::new_with_span(
174            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
175            span.clone(),
176        )
177        .into(),
178    );
179    layout_info_prop_h.element().borrow_mut().bindings.insert(
180        layout_info_prop_h.name().into(),
181        BindingExpression::new_with_span(
182            Expression::ComputeLayoutInfo(
183                Layout::GridLayout(grid.clone()),
184                Orientation::Horizontal,
185            ),
186            span.clone(),
187        )
188        .into(),
189    );
190    layout_info_prop_v.element().borrow_mut().bindings.insert(
191        layout_info_prop_v.name().into(),
192        BindingExpression::new_with_span(
193            Expression::ComputeLayoutInfo(Layout::GridLayout(grid), Orientation::Vertical),
194            span,
195        )
196        .into(),
197    );
198    grid_layout_element.borrow_mut().layout_info_prop =
199        Some((layout_info_prop_h, layout_info_prop_v));
200}
201
202impl GridLayout {
203    fn add_element(
204        &mut self,
205        item_element: &ElementRc,
206        (row, col): (&mut u16, &mut u16),
207        layout_cache_prop_h: &NamedReference,
208        layout_cache_prop_v: &NamedReference,
209        diag: &mut BuildDiagnostics,
210    ) {
211        let mut get_const_value = |name: &str| {
212            item_element
213                .borrow_mut()
214                .bindings
215                .remove(name)
216                .and_then(|e| eval_const_expr(&e.borrow().expression, name, &*e.borrow(), diag))
217        };
218        let colspan = get_const_value("colspan").unwrap_or(1);
219        let rowspan = get_const_value("rowspan").unwrap_or(1);
220        if let Some(r) = get_const_value("row") {
221            *row = r;
222            *col = 0;
223        }
224        if let Some(c) = get_const_value("col") {
225            *col = c;
226        }
227
228        self.add_element_with_coord(
229            item_element,
230            (*row, *col),
231            (rowspan, colspan),
232            layout_cache_prop_h,
233            layout_cache_prop_v,
234            diag,
235        )
236    }
237
238    fn add_element_with_coord(
239        &mut self,
240        item_element: &ElementRc,
241        (row, col): (u16, u16),
242        (rowspan, colspan): (u16, u16),
243        layout_cache_prop_h: &NamedReference,
244        layout_cache_prop_v: &NamedReference,
245        diag: &mut BuildDiagnostics,
246    ) {
247        let index = self.elems.len();
248        if let Some(layout_item) = create_layout_item(item_element, diag) {
249            if layout_item.repeater_index.is_some() {
250                diag.push_error(
251                    "'if' or 'for' expressions are not currently supported in grid layouts"
252                        .to_string(),
253                    &*item_element.borrow(),
254                );
255                return;
256            }
257
258            let e = &layout_item.elem;
259            set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag);
260            if !layout_item.item.constraints.fixed_width {
261                set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag);
262            }
263            set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag);
264            if !layout_item.item.constraints.fixed_height {
265                set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag);
266            }
267
268            self.elems.push(GridLayoutElement {
269                col,
270                row,
271                colspan,
272                rowspan,
273                item: layout_item.item,
274            });
275        }
276    }
277}
278
279fn lower_box_layout(
280    layout_element: &ElementRc,
281    diag: &mut BuildDiagnostics,
282    orientation: Orientation,
283) {
284    let mut layout = BoxLayout {
285        orientation,
286        elems: Default::default(),
287        geometry: LayoutGeometry::new(layout_element),
288    };
289
290    let layout_cache_prop = create_new_prop(layout_element, "layout-cache", Type::LayoutCache);
291    let layout_info_prop_v = create_new_prop(layout_element, "layoutinfo-v", layout_info_type());
292    let layout_info_prop_h = create_new_prop(layout_element, "layoutinfo-h", layout_info_type());
293
294    let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
295
296    let (begin_padding, end_padding) = match orientation {
297        Orientation::Horizontal => (&layout.geometry.padding.top, &layout.geometry.padding.bottom),
298        Orientation::Vertical => (&layout.geometry.padding.left, &layout.geometry.padding.right),
299    };
300    let (pos, size, pad, ortho) = match orientation {
301        Orientation::Horizontal => ("x", "width", "y", "height"),
302        Orientation::Vertical => ("y", "height", "x", "width"),
303    };
304    let pad_expr = begin_padding.clone().map(Expression::PropertyReference);
305    let mut size_expr = Expression::PropertyReference(NamedReference::new(layout_element, ortho));
306    if let Some(p) = begin_padding {
307        size_expr = Expression::BinaryExpression {
308            lhs: Box::new(std::mem::take(&mut size_expr)),
309            rhs: Box::new(Expression::PropertyReference(p.clone())),
310            op: '-',
311        }
312    }
313    if let Some(p) = end_padding {
314        size_expr = Expression::BinaryExpression {
315            lhs: Box::new(std::mem::take(&mut size_expr)),
316            rhs: Box::new(Expression::PropertyReference(p.clone())),
317            op: '-',
318        }
319    }
320
321    for layout_child in &layout_children {
322        if let Some(item) = create_layout_item(layout_child, diag) {
323            let index = layout.elems.len() * 2;
324            let rep_idx = &item.repeater_index;
325            let (fixed_size, fixed_ortho) = match orientation {
326                Orientation::Horizontal => {
327                    (item.item.constraints.fixed_width, item.item.constraints.fixed_height)
328                }
329                Orientation::Vertical => {
330                    (item.item.constraints.fixed_height, item.item.constraints.fixed_width)
331                }
332            };
333            let actual_elem = &item.elem;
334            set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, diag);
335            if !fixed_size {
336                set_prop_from_cache(
337                    actual_elem,
338                    size,
339                    &layout_cache_prop,
340                    index + 1,
341                    rep_idx,
342                    diag,
343                );
344            }
345            if let Some(pad_expr) = pad_expr.clone() {
346                actual_elem.borrow_mut().bindings.insert(pad.into(), RefCell::new(pad_expr.into()));
347            }
348            if !fixed_ortho {
349                actual_elem
350                    .borrow_mut()
351                    .bindings
352                    .insert(ortho.into(), RefCell::new(size_expr.clone().into()));
353            }
354            layout.elems.push(item.item);
355        }
356    }
357    layout_element.borrow_mut().children = layout_children;
358    let span = layout_element.borrow().to_source_location();
359    layout_cache_prop.element().borrow_mut().bindings.insert(
360        layout_cache_prop.name().into(),
361        BindingExpression::new_with_span(
362            Expression::SolveLayout(Layout::BoxLayout(layout.clone()), orientation),
363            span.clone(),
364        )
365        .into(),
366    );
367    layout_info_prop_h.element().borrow_mut().bindings.insert(
368        layout_info_prop_h.name().into(),
369        BindingExpression::new_with_span(
370            Expression::ComputeLayoutInfo(
371                Layout::BoxLayout(layout.clone()),
372                Orientation::Horizontal,
373            ),
374            span.clone(),
375        )
376        .into(),
377    );
378    layout_info_prop_v.element().borrow_mut().bindings.insert(
379        layout_info_prop_v.name().into(),
380        BindingExpression::new_with_span(
381            Expression::ComputeLayoutInfo(Layout::BoxLayout(layout), Orientation::Vertical),
382            span,
383        )
384        .into(),
385    );
386    layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
387}
388
389fn lower_dialog_layout(
390    dialog_element: &ElementRc,
391    style_metrics: &Option<Rc<Component>>,
392    diag: &mut BuildDiagnostics,
393) {
394    let mut grid = GridLayout {
395        elems: Default::default(),
396        geometry: LayoutGeometry::new(dialog_element),
397        dialog_button_roles: None,
398    };
399    if let Some(metrics) = style_metrics.as_ref().map(|comp| &comp.root_element) {
400        grid.geometry.padding.bottom.get_or_insert(NamedReference::new(metrics, "layout-padding"));
401        grid.geometry.padding.top.get_or_insert(NamedReference::new(metrics, "layout-padding"));
402        grid.geometry.padding.left.get_or_insert(NamedReference::new(metrics, "layout-padding"));
403        grid.geometry.padding.right.get_or_insert(NamedReference::new(metrics, "layout-padding"));
404        grid.geometry.spacing.get_or_insert(NamedReference::new(metrics, "layout-spacing"));
405    }
406
407    let layout_cache_prop_h = create_new_prop(dialog_element, "layout-cache-h", Type::LayoutCache);
408    let layout_cache_prop_v = create_new_prop(dialog_element, "layout-cache-v", Type::LayoutCache);
409    let layout_info_prop_h = create_new_prop(dialog_element, "layoutinfo-h", layout_info_type());
410    let layout_info_prop_v = create_new_prop(dialog_element, "layoutinfo-v", layout_info_type());
411
412    let mut main_widget = None;
413    let mut button_roles = vec![];
414    let mut seen_buttons = HashSet::new();
415    let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
416    for layout_child in &layout_children {
417        let dialog_button_role_binding =
418            layout_child.borrow_mut().bindings.remove("dialog-button-role");
419        let is_button = if let Some(role_binding) = dialog_button_role_binding {
420            let role_binding = role_binding.into_inner();
421            if let Expression::EnumerationValue(val) = &role_binding.expression {
422                let en = &val.enumeration;
423                debug_assert_eq!(en.name, "DialogButtonRole");
424                button_roles.push(en.values[val.value].clone());
425                if val.value == 0 {
426                    diag.push_error(
427                        "The `dialog-button-role` cannot be set explicitly to none".into(),
428                        &role_binding,
429                    );
430                }
431            } else {
432                diag.push_error(
433                    "The `dialog-button-role` property must be known at compile-time".into(),
434                    &role_binding,
435                );
436            }
437            true
438        } else if matches!(&layout_child.borrow().lookup_property("kind").property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
439        {
440            // layout_child is a StandardButton
441            match layout_child.borrow().bindings.get("kind") {
442                None => diag.push_error(
443                    "The `kind` property of the StandardButton in a Dialog must be set".into(),
444                    &*layout_child.borrow(),
445                ),
446                Some(binding) => {
447                    let binding = &*binding.borrow();
448                    if let Expression::EnumerationValue(val) = &binding.expression {
449                        let en = &val.enumeration;
450                        debug_assert_eq!(en.name, "StandardButtonKind");
451                        let kind = &en.values[val.value];
452                        let role = match kind.as_str() {
453                            "ok" => "accept",
454                            "cancel" => "reject",
455                            "apply" => "apply",
456                            "close" => "reject",
457                            "reset" => "reset",
458                            "help" => "help",
459                            "yes" => "accept",
460                            "no" => "reject",
461                            "abort" => "reject",
462                            "retry" => "accept",
463                            "ignore" => "accept",
464                            _ => unreachable!(),
465                        };
466                        button_roles.push(role.into());
467                        if !seen_buttons.insert(val.value) {
468                            diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
469                        } else if Rc::ptr_eq(
470                            dialog_element,
471                            &dialog_element
472                                .borrow()
473                                .enclosing_component
474                                .upgrade()
475                                .unwrap()
476                                .root_element,
477                        ) {
478                            let clicked_ty =
479                                layout_child.borrow().lookup_property("clicked").property_type;
480                            if matches!(&clicked_ty, Type::Callback { .. })
481                                && layout_child.borrow().bindings.get("clicked").map_or(true, |c| {
482                                    matches!(c.borrow().expression, Expression::Invalid)
483                                })
484                            {
485                                dialog_element
486                                    .borrow_mut()
487                                    .property_declarations
488                                    .entry(format!("{}-clicked", kind))
489                                    .or_insert_with(|| PropertyDeclaration {
490                                        property_type: clicked_ty,
491                                        node: None,
492                                        expose_in_public_api: true,
493                                        is_alias: Some(NamedReference::new(
494                                            layout_child,
495                                            "clicked",
496                                        )),
497                                    });
498                            }
499                        }
500                    } else {
501                        diag.push_error(
502                            "The `kind` property of the StandardButton in a Dialog must be known at compile-time"
503                                .into(),
504                            binding,
505                        );
506                    }
507                }
508            }
509            true
510        } else {
511            false
512        };
513
514        if is_button {
515            grid.add_element_with_coord(
516                layout_child,
517                (1, button_roles.len() as u16),
518                (1, 1),
519                &layout_cache_prop_h,
520                &layout_cache_prop_v,
521                diag,
522            );
523        } else if main_widget.is_some() {
524            diag.push_error(
525                "A Dialog can have only one child element that is not a StandardButton".into(),
526                &*layout_child.borrow(),
527            );
528        } else {
529            main_widget = Some(layout_child.clone())
530        }
531    }
532    dialog_element.borrow_mut().children = layout_children;
533
534    if let Some(main_widget) = main_widget {
535        grid.add_element_with_coord(
536            &main_widget,
537            (0, 0),
538            (1, button_roles.len() as u16 + 1),
539            &layout_cache_prop_h,
540            &layout_cache_prop_v,
541            diag,
542        );
543    } else {
544        diag.push_error(
545            "A Dialog must have a single child element that is not StandardButton".into(),
546            &*dialog_element.borrow(),
547        );
548    }
549    grid.dialog_button_roles = Some(button_roles);
550
551    let span = dialog_element.borrow().to_source_location();
552    layout_cache_prop_h.element().borrow_mut().bindings.insert(
553        layout_cache_prop_h.name().into(),
554        BindingExpression::new_with_span(
555            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
556            span.clone(),
557        )
558        .into(),
559    );
560    layout_cache_prop_v.element().borrow_mut().bindings.insert(
561        layout_cache_prop_v.name().into(),
562        BindingExpression::new_with_span(
563            Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
564            span.clone(),
565        )
566        .into(),
567    );
568    layout_info_prop_h.element().borrow_mut().bindings.insert(
569        layout_info_prop_h.name().into(),
570        BindingExpression::new_with_span(
571            Expression::ComputeLayoutInfo(
572                Layout::GridLayout(grid.clone()),
573                Orientation::Horizontal,
574            ),
575            span.clone(),
576        )
577        .into(),
578    );
579    layout_info_prop_v.element().borrow_mut().bindings.insert(
580        layout_info_prop_v.name().into(),
581        BindingExpression::new_with_span(
582            Expression::ComputeLayoutInfo(Layout::GridLayout(grid), Orientation::Vertical),
583            span,
584        )
585        .into(),
586    );
587    dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
588}
589
590fn lower_path_layout(layout_element: &ElementRc, diag: &mut BuildDiagnostics) {
591    let layout_cache_prop = create_new_prop(layout_element, "layout-cache", Type::LayoutCache);
592
593    let path_elements_expr = match layout_element
594        .borrow_mut()
595        .bindings
596        .remove("elements")
597        .map(RefCell::into_inner)
598    {
599        Some(BindingExpression { expression: Expression::PathData(data), .. }) => data,
600        _ => {
601            diag.push_error("Internal error: elements binding in PathLayout does not contain path elements expression".into(), &*layout_element.borrow());
602            return;
603        }
604    };
605
606    let elements = layout_element.borrow().children.clone();
607    if elements.is_empty() {
608        return;
609    }
610    for (index, e) in elements.iter().enumerate() {
611        let (repeater_index, actual_elem) = if e.borrow().repeated.is_some() {
612            (
613                Some(Expression::RepeaterIndexReference { element: Rc::downgrade(e) }),
614                e.borrow().base_type.as_component().root_element.clone(),
615            )
616        } else {
617            (None, e.clone())
618        };
619        let set_prop_from_cache = |prop: &str, offset: usize, size_prop: &str| {
620            let size = NamedReference::new(&actual_elem, size_prop);
621            let expression = Expression::BinaryExpression {
622                lhs: Box::new(Expression::LayoutCacheAccess {
623                    layout_cache_prop: layout_cache_prop.clone(),
624                    index: index * 2 + offset,
625                    repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
626                }),
627                op: '-',
628                rhs: Box::new(Expression::BinaryExpression {
629                    lhs: Box::new(Expression::PropertyReference(size)),
630                    op: '/',
631                    rhs: Box::new(Expression::NumberLiteral(2., Unit::None)),
632                }),
633            };
634            actual_elem.borrow_mut().bindings.insert(prop.into(), RefCell::new(expression.into()));
635        };
636        set_prop_from_cache("x", 0, "width");
637        set_prop_from_cache("y", 1, "height");
638    }
639    let rect = LayoutRect::install_on_element(layout_element);
640    let path_layout = Layout::PathLayout(PathLayout {
641        elements,
642        path: path_elements_expr,
643        rect,
644        offset_reference: layout_element
645            .borrow()
646            .bindings
647            .contains_key("spacing")
648            .then(|| NamedReference::new(layout_element, "spacing")),
649    });
650    let binding = BindingExpression::new_with_span(
651        Expression::SolveLayout(path_layout, Orientation::Horizontal),
652        layout_element.borrow().to_source_location(),
653    );
654    layout_cache_prop
655        .element()
656        .borrow_mut()
657        .bindings
658        .insert(layout_cache_prop.name().into(), binding.into());
659}
660
661struct CreateLayoutItemResult {
662    item: LayoutItem,
663    elem: ElementRc,
664    repeater_index: Option<Expression>,
665}
666
667/// Create a LayoutItem for the given `item_element`  returns None is the layout is empty
668fn create_layout_item(
669    item_element: &ElementRc,
670    diag: &mut BuildDiagnostics,
671) -> Option<CreateLayoutItemResult> {
672    let fix_explicit_percent = |prop: &str, item: &ElementRc| {
673        if !item.borrow().bindings.get(prop).map_or(false, |b| b.borrow().ty() == Type::Percent) {
674            return;
675        }
676        let mut item = item.borrow_mut();
677        let b = item.bindings.remove(prop).unwrap();
678        item.bindings.insert(format!("min-{}", prop), b.clone());
679        item.bindings.insert(format!("max-{}", prop), b);
680        item.property_declarations.insert(
681            format!("min-{}", prop),
682            PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
683        );
684        item.property_declarations.insert(
685            format!("max-{}", prop),
686            PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
687        );
688    };
689    fix_explicit_percent("width", item_element);
690    fix_explicit_percent("height", item_element);
691
692    item_element.borrow_mut().child_of_layout = true;
693    let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
694        let rep_comp = item_element.borrow().base_type.as_component().clone();
695        fix_explicit_percent("width", &rep_comp.root_element);
696        fix_explicit_percent("height", &rep_comp.root_element);
697
698        *rep_comp.root_constraints.borrow_mut() =
699            LayoutConstraints::new(&rep_comp.root_element, diag);
700        rep_comp.root_element.borrow_mut().child_of_layout = true;
701        (
702            Some(if r.is_conditional_element {
703                Expression::NumberLiteral(0., Unit::None)
704            } else {
705                Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
706            }),
707            rep_comp.root_element.clone(),
708        )
709    } else {
710        (None, item_element.clone())
711    };
712
713    let constraints = LayoutConstraints::new(&actual_elem, diag);
714    Some(CreateLayoutItemResult {
715        item: LayoutItem { element: item_element.clone(), constraints },
716        elem: actual_elem,
717        repeater_index,
718    })
719}
720
721fn set_prop_from_cache(
722    elem: &ElementRc,
723    prop: &str,
724    layout_cache_prop: &NamedReference,
725    index: usize,
726    repeater_index: &Option<Expression>,
727    diag: &mut BuildDiagnostics,
728) {
729    let old = elem.borrow_mut().bindings.insert(
730        prop.into(),
731        BindingExpression::new_with_span(
732            Expression::LayoutCacheAccess {
733                layout_cache_prop: layout_cache_prop.clone(),
734                index,
735                repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
736            },
737            layout_cache_prop.element().borrow().to_source_location(),
738        )
739        .into(),
740    );
741    if let Some(old) = old.map(RefCell::into_inner) {
742        diag.push_error(
743            format!("The property '{}' cannot be set for elements placed in a layout, because the layout is already setting it", prop),
744            &old,
745        );
746    }
747}
748
749fn eval_const_expr(
750    expression: &Expression,
751    name: &str,
752    span: &dyn crate::diagnostics::Spanned,
753    diag: &mut BuildDiagnostics,
754) -> Option<u16> {
755    match expression {
756        Expression::NumberLiteral(v, Unit::None) => {
757            if *v < 0. || *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
758                diag.push_error(format!("'{}' must be a positive integer", name), span);
759                None
760            } else {
761                Some(*v as u16)
762            }
763        }
764        Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag),
765        _ => {
766            diag.push_error(format!("'{}' must be an integer literal", name), span);
767            None
768        }
769    }
770}
771
772/// Create a new property based on the name. (it might get a different name if that property exist)
773pub fn create_new_prop(elem: &ElementRc, tentative_name: &str, ty: Type) -> NamedReference {
774    let mut e = elem.borrow_mut();
775    if !e.lookup_property(tentative_name).is_valid() {
776        e.property_declarations.insert(tentative_name.into(), ty.into());
777        drop(e);
778        NamedReference::new(elem, tentative_name)
779    } else {
780        let mut counter = 0;
781        loop {
782            counter += 1;
783            let name = format!("{}{}", tentative_name, counter);
784            if !e.lookup_property(&name).is_valid() {
785                e.property_declarations.insert(name.clone(), ty.into());
786                drop(e);
787                return NamedReference::new(elem, &name);
788            }
789        }
790    }
791}
792
793/// Checks that there is grid-layout specific properties left
794fn check_no_layout_properties(item: &ElementRc, diag: &mut BuildDiagnostics) {
795    for (prop, expr) in item.borrow().bindings.iter() {
796        if matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan") {
797            diag.push_error(format!("{} used outside of a GridLayout", prop), &*expr.borrow());
798        }
799        if matches!(prop.as_ref(), "dialog-button-role") {
800            diag.push_error(format!("{} used outside of a Dialog", prop), &*expr.borrow());
801        }
802    }
803}