Skip to main content

i_slint_compiler/
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//! Datastructures used to represent layouts in the compiler
5
6use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
7use crate::expression_tree::*;
8use crate::langtype::{ElementType, PropertyLookupResult, Type};
9use crate::object_tree::{Component, ElementRc};
10
11use smol_str::{SmolStr, ToSmolStr, format_smolstr};
12
13use std::cell::RefCell;
14use std::rc::{Rc, Weak};
15
16#[derive(Clone, Debug, Copy, Eq, PartialEq)]
17pub enum Orientation {
18    Horizontal,
19    Vertical,
20}
21
22impl Orientation {
23    pub fn orthogonal(self) -> Self {
24        match self {
25            Orientation::Horizontal => Orientation::Vertical,
26            Orientation::Vertical => Orientation::Horizontal,
27        }
28    }
29}
30
31#[derive(Clone, Debug, Copy, Eq, PartialEq, Default)]
32pub enum FlexboxLayoutDirection {
33    /// Items are laid out in rows (horizontal primary axis)
34    #[default]
35    Row,
36    /// Items are laid out in rows in reverse order (horizontal primary axis, right to left)
37    RowReverse,
38    /// Items are laid out in columns (vertical primary axis)
39    Column,
40    /// Items are laid out in columns in reverse order (vertical primary axis, bottom to top)
41    ColumnReverse,
42}
43
44/// Relationship between a queried orientation and a FlexboxLayout's direction.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum FlexboxAxisRelation {
47    /// The queried orientation is the main axis (e.g., Horizontal for a Row flex)
48    MainAxis,
49    /// The queried orientation is the cross axis (e.g., Vertical for a Row flex)
50    CrossAxis,
51    /// The flex direction is not known at compile time
52    Unknown,
53}
54
55#[derive(Clone, Debug, derive_more::From)]
56pub enum Layout {
57    GridLayout(GridLayout),
58    BoxLayout(BoxLayout),
59    FlexboxLayout(FlexboxLayout),
60}
61
62impl Layout {
63    /// Call the visitor for each NamedReference stored in the layout
64    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
65        match self {
66            Layout::GridLayout(grid) => grid.visit_named_references(visitor),
67            Layout::BoxLayout(l) => l.visit_named_references(visitor),
68            Layout::FlexboxLayout(l) => l.visit_named_references(visitor),
69        }
70    }
71}
72
73/// An Item in the layout tree
74#[derive(Debug, Default, Clone)]
75pub struct LayoutItem {
76    pub element: ElementRc,
77    pub constraints: LayoutConstraints,
78}
79
80/// A FlexboxLayout child item, wrapping a LayoutItem with flex-specific properties.
81#[derive(Debug, Clone)]
82pub struct FlexboxLayoutItem {
83    pub item: LayoutItem,
84    pub flex_grow: Option<NamedReference>,
85    pub flex_shrink: Option<NamedReference>,
86    pub flex_basis: Option<NamedReference>,
87    pub align_self: Option<NamedReference>,
88    pub order: Option<NamedReference>,
89}
90
91/// A child within a repeated Row in a GridLayout.
92/// Can be either a static item or a nested repeater (`for y in model: ...`).
93#[derive(Debug, Clone)]
94pub enum RowChildTemplate {
95    Static(LayoutItem),
96    Repeated {
97        item: LayoutItem,
98        /// The repeated element (the `for y in ...` element inside the Row)
99        repeated_element: ElementRc,
100    },
101}
102
103impl RowChildTemplate {
104    pub fn layout_item(&self) -> &LayoutItem {
105        match self {
106            RowChildTemplate::Static(item) => item,
107            RowChildTemplate::Repeated { item, .. } => item,
108        }
109    }
110
111    pub fn layout_item_mut(&mut self) -> &mut LayoutItem {
112        match self {
113            RowChildTemplate::Static(item) => item,
114            RowChildTemplate::Repeated { item, .. } => item,
115        }
116    }
117
118    pub fn repeated_element(&self) -> Option<&ElementRc> {
119        match self {
120            RowChildTemplate::Static(_) => None,
121            RowChildTemplate::Repeated { repeated_element, .. } => Some(repeated_element),
122        }
123    }
124
125    pub fn is_repeated(&self) -> bool {
126        self.repeated_element().is_some()
127    }
128}
129
130impl LayoutItem {
131    pub fn rect(&self) -> LayoutRect {
132        let p = |unresolved_name: &str| {
133            let PropertyLookupResult { resolved_name, property_type, .. } =
134                self.element.borrow().lookup_property(unresolved_name);
135            if property_type == Type::LogicalLength {
136                Some(NamedReference::new(&self.element, resolved_name.to_smolstr()))
137            } else {
138                None
139            }
140        };
141        LayoutRect {
142            x_reference: p("x"),
143            y_reference: p("y"),
144            width_reference: if !self.constraints.fixed_width { p("width") } else { None },
145            height_reference: if !self.constraints.fixed_height { p("height") } else { None },
146        }
147    }
148}
149
150#[derive(Debug, Clone, Default)]
151pub struct LayoutRect {
152    pub width_reference: Option<NamedReference>,
153    pub height_reference: Option<NamedReference>,
154    pub x_reference: Option<NamedReference>,
155    pub y_reference: Option<NamedReference>,
156}
157
158impl LayoutRect {
159    pub fn install_on_element(element: &ElementRc) -> Self {
160        let install_prop =
161            |name: &'static str| Some(NamedReference::new(element, SmolStr::new_static(name)));
162
163        Self {
164            x_reference: install_prop("x"),
165            y_reference: install_prop("y"),
166            width_reference: install_prop("width"),
167            height_reference: install_prop("height"),
168        }
169    }
170
171    fn visit_named_references(&mut self, mut visitor: &mut impl FnMut(&mut NamedReference)) {
172        self.width_reference.as_mut().map(&mut visitor);
173        self.height_reference.as_mut().map(&mut visitor);
174        self.x_reference.as_mut().map(&mut visitor);
175        self.y_reference.as_mut().map(&mut visitor);
176    }
177
178    pub fn size_reference(&self, orientation: Orientation) -> Option<&NamedReference> {
179        match orientation {
180            Orientation::Horizontal => self.width_reference.as_ref(),
181            Orientation::Vertical => self.height_reference.as_ref(),
182        }
183    }
184}
185
186#[derive(Debug, Default, Clone)]
187pub struct LayoutConstraints {
188    pub min_width: Option<NamedReference>,
189    pub max_width: Option<NamedReference>,
190    pub min_height: Option<NamedReference>,
191    pub max_height: Option<NamedReference>,
192    pub preferred_width: Option<NamedReference>,
193    pub preferred_height: Option<NamedReference>,
194    pub horizontal_stretch: Option<NamedReference>,
195    pub vertical_stretch: Option<NamedReference>,
196    pub fixed_width: bool,
197    pub fixed_height: bool,
198}
199
200impl LayoutConstraints {
201    /// Build the constraints for the given element.
202    ///
203    /// When `diag` is `Some`, a redundant size constraint (e.g. both `width` and `min-width`) is
204    /// reported at the given level; pass `None` to compute the constraints without reporting (e.g.
205    /// when another pass owns that diagnostic).
206    pub fn new(
207        element: &ElementRc,
208        mut diag: Option<(&mut BuildDiagnostics, DiagnosticLevel)>,
209    ) -> Self {
210        let mut constraints = Self {
211            min_width: binding_reference(element, "min-width"),
212            max_width: binding_reference(element, "max-width"),
213            min_height: binding_reference(element, "min-height"),
214            max_height: binding_reference(element, "max-height"),
215            preferred_width: binding_reference(element, "preferred-width"),
216            preferred_height: binding_reference(element, "preferred-height"),
217            horizontal_stretch: binding_reference(element, "horizontal-stretch"),
218            vertical_stretch: binding_reference(element, "vertical-stretch"),
219            fixed_width: false,
220            fixed_height: false,
221        };
222        let mut apply_size_constraint =
223            |prop: &'static str,
224             binding: &BindingExpression,
225             enclosing1: &Weak<Component>,
226             depth,
227             op: &mut Option<NamedReference>| {
228                if let Some(other_prop) = op {
229                    find_binding(
230                        &other_prop.element(),
231                        other_prop.name(),
232                        |old, enclosing2, d2| {
233                            if let Some((diag, level)) = &mut diag
234                                && Weak::ptr_eq(enclosing1, enclosing2)
235                                && old.priority.saturating_add(d2)
236                                    <= binding.priority.saturating_add(depth)
237                            {
238                                diag.push_diagnostic_with_span(
239                                    format!(
240                                        "Cannot specify both '{prop}' and '{}'",
241                                        other_prop.name()
242                                    ),
243                                    binding.to_source_location(),
244                                    *level,
245                                );
246                            }
247                        },
248                    );
249                }
250                *op = Some(NamedReference::new(element, SmolStr::new_static(prop)))
251            };
252        find_binding(element, "height", |s, enclosing, depth| {
253            constraints.fixed_height = true;
254            apply_size_constraint("height", s, enclosing, depth, &mut constraints.min_height);
255            apply_size_constraint("height", s, enclosing, depth, &mut constraints.max_height);
256        });
257        find_binding(element, "width", |s, enclosing, depth| {
258            constraints.fixed_width = true;
259            if s.expression.ty() == Type::Percent {
260                apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
261            } else {
262                apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
263                apply_size_constraint("width", s, enclosing, depth, &mut constraints.max_width);
264            }
265        });
266
267        constraints
268    }
269
270    pub fn has_explicit_restrictions(&self, orientation: Orientation) -> bool {
271        match orientation {
272            Orientation::Horizontal => {
273                self.min_width.is_some()
274                    || self.max_width.is_some()
275                    || self.preferred_width.is_some()
276                    || self.horizontal_stretch.is_some()
277            }
278            Orientation::Vertical => {
279                self.min_height.is_some()
280                    || self.max_height.is_some()
281                    || self.preferred_height.is_some()
282                    || self.vertical_stretch.is_some()
283            }
284        }
285    }
286
287    // Iterate over the constraint with a reference to a property, and the corresponding member in the i_slint_core::layout::LayoutInfo struct
288    pub fn for_each_restrictions(
289        &self,
290        orientation: Orientation,
291    ) -> impl Iterator<Item = (&NamedReference, &'static str)> {
292        let (min, max, preferred, stretch) = match orientation {
293            Orientation::Horizontal => {
294                (&self.min_width, &self.max_width, &self.preferred_width, &self.horizontal_stretch)
295            }
296            Orientation::Vertical => {
297                (&self.min_height, &self.max_height, &self.preferred_height, &self.vertical_stretch)
298            }
299        };
300        std::iter::empty()
301            .chain(min.as_ref().map(|x| {
302                if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
303                    (x, "min")
304                } else {
305                    (x, "min_percent")
306                }
307            }))
308            .chain(max.as_ref().map(|x| {
309                if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
310                    (x, "max")
311                } else {
312                    (x, "max_percent")
313                }
314            }))
315            .chain(preferred.as_ref().map(|x| (x, "preferred")))
316            .chain(stretch.as_ref().map(|x| (x, "stretch")))
317    }
318
319    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
320        if let Some(e) = self.max_width.as_mut() {
321            visitor(&mut *e);
322        }
323        if let Some(e) = self.min_width.as_mut() {
324            visitor(&mut *e);
325        }
326        if let Some(e) = self.max_height.as_mut() {
327            visitor(&mut *e);
328        }
329        if let Some(e) = self.min_height.as_mut() {
330            visitor(&mut *e);
331        }
332        if let Some(e) = self.preferred_width.as_mut() {
333            visitor(&mut *e);
334        }
335        if let Some(e) = self.preferred_height.as_mut() {
336            visitor(&mut *e);
337        }
338        if let Some(e) = self.horizontal_stretch.as_mut() {
339            visitor(&mut *e);
340        }
341        if let Some(e) = self.vertical_stretch.as_mut() {
342            visitor(&mut *e);
343        }
344    }
345}
346
347#[derive(Debug, Clone)]
348pub enum RowColExpr {
349    Named(NamedReference),
350    Literal(u16),
351    Auto,
352}
353
354#[derive(Debug, Clone)]
355pub struct GridLayoutCell {
356    pub new_row: bool,
357    pub col_expr: RowColExpr,
358    pub row_expr: RowColExpr,
359    pub colspan_expr: RowColExpr,
360    pub rowspan_expr: RowColExpr,
361    pub child_items: Option<Vec<RowChildTemplate>>, // for repeated rows
362}
363
364impl GridLayoutCell {
365    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
366        if let RowColExpr::Named(ref mut e) = self.col_expr {
367            visitor(e);
368        }
369        if let RowColExpr::Named(ref mut e) = self.row_expr {
370            visitor(e);
371        }
372        if let RowColExpr::Named(ref mut e) = self.colspan_expr {
373            visitor(e);
374        }
375        if let RowColExpr::Named(ref mut e) = self.rowspan_expr {
376            visitor(e);
377        }
378        if let Some(children) = &mut self.child_items {
379            for child in children {
380                child.layout_item_mut().constraints.visit_named_references(visitor);
381            }
382        }
383    }
384}
385
386/// An element in a GridLayout
387#[derive(Debug, Clone)]
388pub struct GridLayoutElement {
389    /// `Rc<RefCell<GridLayoutCell>>` because shared with the repeated component's element
390    pub cell: Rc<RefCell<GridLayoutCell>>,
391    pub item: LayoutItem,
392}
393
394impl GridLayoutElement {
395    pub fn span(&self, orientation: Orientation) -> RowColExpr {
396        let cell = self.cell.borrow();
397        match orientation {
398            Orientation::Horizontal => cell.colspan_expr.clone(),
399            Orientation::Vertical => cell.rowspan_expr.clone(),
400        }
401    }
402}
403
404#[derive(Debug, Clone)]
405pub struct Padding {
406    pub left: Option<NamedReference>,
407    pub right: Option<NamedReference>,
408    pub top: Option<NamedReference>,
409    pub bottom: Option<NamedReference>,
410}
411
412impl Padding {
413    fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
414        if let Some(e) = self.left.as_mut() {
415            visitor(&mut *e)
416        }
417        if let Some(e) = self.right.as_mut() {
418            visitor(&mut *e)
419        }
420        if let Some(e) = self.top.as_mut() {
421            visitor(&mut *e)
422        }
423        if let Some(e) = self.bottom.as_mut() {
424            visitor(&mut *e)
425        }
426    }
427
428    // Return reference to the begin and end padding for a given orientation
429    pub fn begin_end(&self, o: Orientation) -> (Option<&NamedReference>, Option<&NamedReference>) {
430        match o {
431            Orientation::Horizontal => (self.left.as_ref(), self.right.as_ref()),
432            Orientation::Vertical => (self.top.as_ref(), self.bottom.as_ref()),
433        }
434    }
435}
436
437#[derive(Debug, Clone)]
438pub struct Spacing {
439    pub horizontal: Option<NamedReference>,
440    pub vertical: Option<NamedReference>,
441}
442
443impl Spacing {
444    fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
445        if let Some(e) = self.horizontal.as_mut() {
446            visitor(&mut *e);
447        }
448        if let Some(e) = self.vertical.as_mut() {
449            visitor(&mut *e);
450        }
451    }
452
453    pub fn orientation(&self, o: Orientation) -> Option<&NamedReference> {
454        match o {
455            Orientation::Horizontal => self.horizontal.as_ref(),
456            Orientation::Vertical => self.vertical.as_ref(),
457        }
458    }
459}
460
461#[derive(Debug, Clone)]
462pub struct LayoutGeometry {
463    pub rect: LayoutRect,
464    pub spacing: Spacing,
465    pub alignment: Option<NamedReference>,
466    pub padding: Padding,
467}
468
469impl LayoutGeometry {
470    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
471        self.rect.visit_named_references(visitor);
472        if let Some(e) = self.alignment.as_mut() {
473            visitor(&mut *e)
474        }
475        self.spacing.visit_named_references(visitor);
476        self.padding.visit_named_references(visitor);
477    }
478
479    pub fn new(layout_element: &ElementRc) -> Self {
480        let spacing = || binding_reference(layout_element, "spacing");
481        init_fake_property(layout_element, "spacing-horizontal", spacing);
482        init_fake_property(layout_element, "spacing-vertical", spacing);
483
484        let alignment = binding_reference(layout_element, "alignment");
485
486        let padding = || binding_reference(layout_element, "padding");
487        init_fake_property(layout_element, "padding-left", padding);
488        init_fake_property(layout_element, "padding-right", padding);
489        init_fake_property(layout_element, "padding-top", padding);
490        init_fake_property(layout_element, "padding-bottom", padding);
491
492        let padding = Padding {
493            left: binding_reference(layout_element, "padding-left").or_else(padding),
494            right: binding_reference(layout_element, "padding-right").or_else(padding),
495            top: binding_reference(layout_element, "padding-top").or_else(padding),
496            bottom: binding_reference(layout_element, "padding-bottom").or_else(padding),
497        };
498
499        let spacing = Spacing {
500            horizontal: binding_reference(layout_element, "spacing-horizontal").or_else(spacing),
501            vertical: binding_reference(layout_element, "spacing-vertical").or_else(spacing),
502        };
503
504        let rect = LayoutRect::install_on_element(layout_element);
505
506        Self { rect, spacing, padding, alignment }
507    }
508}
509
510/// If this element or any of the parent has a binding to the property, call the functor with that binding, and the depth.
511/// Return None if the binding does not exist in any of the sub component, or Some with the result of the functor otherwise
512fn find_binding<R>(
513    element: &ElementRc,
514    name: &str,
515    f: impl FnOnce(&BindingExpression, &Weak<Component>, i32) -> R,
516) -> Option<R> {
517    let mut element = element.clone();
518    let mut depth = 0;
519    loop {
520        if let Some(b) = element.borrow().bindings.get(name)
521            && b.borrow().has_binding()
522        {
523            return Some(f(&b.borrow(), &element.borrow().enclosing_component, depth));
524        }
525        let e = match &element.borrow().base_type {
526            ElementType::Component(base) => base.root_element.clone(),
527            _ => return None,
528        };
529        element = e;
530        depth += 1;
531    }
532}
533
534/// Return a named reference to a property if a binding is set on that property
535pub fn binding_reference(element: &ElementRc, name: &'static str) -> Option<NamedReference> {
536    find_binding(element, name, |_, _, _| NamedReference::new(element, SmolStr::new_static(name)))
537}
538
539fn init_fake_property(
540    grid_layout_element: &ElementRc,
541    name: &str,
542    lazy_default: impl Fn() -> Option<NamedReference>,
543) {
544    if grid_layout_element.borrow().property_declarations.contains_key(name)
545        && !grid_layout_element.borrow().bindings.contains_key(name)
546        && let Some(e) = lazy_default()
547    {
548        if e.name() == name && Rc::ptr_eq(&e.element(), grid_layout_element) {
549            // Don't reference self
550            return;
551        }
552        grid_layout_element
553            .borrow_mut()
554            .bindings
555            .insert(name.into(), RefCell::new(Expression::PropertyReference(e).into()));
556    }
557}
558
559/// Internal representation of a grid layout
560#[derive(Debug, Clone)]
561pub struct GridLayout {
562    /// All the elements which will be laid out within that element.
563    pub elems: Vec<GridLayoutElement>,
564
565    pub geometry: LayoutGeometry,
566
567    /// When this GridLayout is actually the layout of a Dialog, then the cells start with all the buttons,
568    /// and this variable contains their roles. The string is actually one of the values from the i_slint_core::layout::DialogButtonRole
569    pub dialog_button_roles: Option<Vec<SmolStr>>,
570
571    /// Whether any of the row/column expressions use 'auto'
572    pub uses_auto: bool,
573}
574
575impl GridLayout {
576    /// Clone each element's cell into a new Rc, breaking any Rc sharing with the original.
577    pub fn clone_cells(&mut self) {
578        for e in &mut self.elems {
579            let cloned = Rc::new(RefCell::new(e.cell.borrow().clone()));
580            e.cell = cloned;
581        }
582    }
583
584    pub fn visit_rowcol_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
585        for elem in &mut self.elems {
586            let mut cell = elem.cell.borrow_mut();
587            if let RowColExpr::Named(ref mut e) = cell.col_expr {
588                visitor(e);
589            }
590            if let RowColExpr::Named(ref mut e) = cell.row_expr {
591                visitor(e);
592            }
593            if let RowColExpr::Named(ref mut e) = cell.colspan_expr {
594                visitor(e);
595            }
596            if let RowColExpr::Named(ref mut e) = cell.rowspan_expr {
597                visitor(e);
598            }
599        }
600    }
601
602    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
603        self.visit_rowcol_named_references(visitor);
604        for layout_elem in &mut self.elems {
605            layout_elem.item.constraints.visit_named_references(visitor);
606            if let Some(child_items) = &mut layout_elem.cell.borrow_mut().child_items {
607                for child in child_items {
608                    child.layout_item_mut().constraints.visit_named_references(visitor);
609                }
610            }
611        }
612        self.geometry.visit_named_references(visitor);
613    }
614}
615
616/// Internal representation of a BoxLayout
617#[derive(Debug, Clone)]
618pub struct BoxLayout {
619    /// Whether this is a HorizontalLayout or a VerticalLayout
620    pub orientation: Orientation,
621    pub elems: Vec<LayoutItem>,
622    pub geometry: LayoutGeometry,
623    /// The `cross-axis-alignment` property, if set.
624    pub cross_alignment: Option<NamedReference>,
625}
626
627impl BoxLayout {
628    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
629        for cell in &mut self.elems {
630            cell.constraints.visit_named_references(visitor);
631        }
632        self.geometry.visit_named_references(visitor);
633        if let Some(e) = self.cross_alignment.as_mut() {
634            visitor(&mut *e);
635        }
636    }
637}
638
639/// Internal representation of a FlexboxLayout (row or column direction with wrapping)
640#[derive(Debug, Clone)]
641pub struct FlexboxLayout {
642    pub elems: Vec<FlexboxLayoutItem>,
643    pub geometry: LayoutGeometry,
644    pub direction: Option<NamedReference>,
645    pub align_content: Option<NamedReference>,
646    pub cross_axis_alignment: Option<NamedReference>,
647    pub flex_wrap: Option<NamedReference>,
648}
649
650impl FlexboxLayout {
651    /// Try to determine the flex direction at compile time from a constant binding.
652    /// Returns None if the direction is set at runtime.
653    fn compile_time_direction(&self) -> Option<FlexboxLayoutDirection> {
654        match self.direction.as_ref() {
655            None => Some(FlexboxLayoutDirection::Row),
656            Some(nr) => nr.element().borrow().bindings.get(nr.name()).and_then(|binding| {
657                if let crate::expression_tree::Expression::EnumerationValue(ev) =
658                    &binding.borrow().expression
659                {
660                    match ev.enumeration.values[ev.value].as_str() {
661                        "row" => Some(FlexboxLayoutDirection::Row),
662                        "row-reverse" => Some(FlexboxLayoutDirection::RowReverse),
663                        "column" => Some(FlexboxLayoutDirection::Column),
664                        "column-reverse" => Some(FlexboxLayoutDirection::ColumnReverse),
665                        _ => None,
666                    }
667                } else {
668                    None
669                }
670            }),
671        }
672    }
673
674    /// Determine the relationship between a queried orientation and this flex's direction.
675    pub fn axis_relation(&self, orientation: Orientation) -> FlexboxAxisRelation {
676        match self.compile_time_direction() {
677            None => FlexboxAxisRelation::Unknown,
678            Some(dir) => {
679                let is_main = matches!(
680                    (dir, orientation),
681                    (
682                        FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse,
683                        Orientation::Horizontal
684                    ) | (
685                        FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
686                        Orientation::Vertical
687                    )
688                );
689                if is_main { FlexboxAxisRelation::MainAxis } else { FlexboxAxisRelation::CrossAxis }
690            }
691        }
692    }
693
694    pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
695        for cell in &mut self.elems {
696            cell.item.constraints.visit_named_references(visitor);
697            if let Some(e) = cell.flex_grow.as_mut() {
698                visitor(&mut *e)
699            }
700            if let Some(e) = cell.flex_shrink.as_mut() {
701                visitor(&mut *e)
702            }
703            if let Some(e) = cell.flex_basis.as_mut() {
704                visitor(&mut *e)
705            }
706            if let Some(e) = cell.align_self.as_mut() {
707                visitor(&mut *e)
708            }
709            if let Some(e) = cell.order.as_mut() {
710                visitor(&mut *e)
711            }
712        }
713        self.geometry.visit_named_references(visitor);
714        if let Some(e) = self.direction.as_mut() {
715            visitor(&mut *e)
716        }
717        if let Some(e) = self.align_content.as_mut() {
718            visitor(&mut *e)
719        }
720        if let Some(e) = self.cross_axis_alignment.as_mut() {
721            visitor(&mut *e)
722        }
723        if let Some(e) = self.flex_wrap.as_mut() {
724            visitor(&mut *e)
725        }
726    }
727}
728
729/// Controls whether `implicit_layout_info_call` returns layout info for builtins
730/// that don't have an intrinsic size (Rectangle, Empty, TouchArea, etc.).
731#[derive(Clone, Copy, PartialEq)]
732pub enum BuiltinFilter {
733    /// Return layout info for all builtins (existing behavior).
734    All,
735    /// Skip builtins whose `default_size_binding` is not `ImplicitSize`.
736    SkipNonImplicit,
737}
738
739/// Get the implicit layout info of a particular element.
740/// When `constraint` is `Some`, it's passed as the `cross_axis_constraint`
741/// parameter to `Item::layout_info` for height-for-width support.
742pub fn implicit_layout_info_call(
743    elem: &ElementRc,
744    orientation: Orientation,
745    filter: BuiltinFilter,
746    constraint: Option<Expression>,
747) -> Option<Expression> {
748    let mut elem_it = elem.clone();
749    loop {
750        return match &elem_it.clone().borrow().base_type {
751            ElementType::Component(base_comp) => {
752                // Flexbox supplies a cross-axis constraint to break its
753                // h/v cache cycle; call the base component's parametrized
754                // layout-info function when present.
755                let parametrized_nr = constraint.as_ref().and_then(|_| match orientation {
756                    Orientation::Vertical => {
757                        base_comp.root_element.borrow().layout_info_v_with_constraint.clone()
758                    }
759                    Orientation::Horizontal => {
760                        base_comp.root_element.borrow().layout_info_h_with_constraint.clone()
761                    }
762                });
763                if let Some(nr) = parametrized_nr
764                    && let Some(c) = &constraint
765                {
766                    debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
767                    return Some(Expression::FunctionCall {
768                        function: crate::expression_tree::Callable::Function(NamedReference::new(
769                            elem,
770                            nr.name().clone(),
771                        )),
772                        arguments: vec![c.clone()],
773                        source_location: None,
774                    });
775                }
776                match base_comp.root_element.borrow().layout_info_prop(orientation) {
777                    Some(nr) => {
778                        // We cannot take nr as is because it is relative to the elem's component. We therefore need to
779                        // use `elem` as an element for the PropertyReference, not `root` within the base of elem
780                        debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
781                        Some(Expression::PropertyReference(NamedReference::new(
782                            elem,
783                            nr.name().clone(),
784                        )))
785                    }
786                    None => {
787                        elem_it = base_comp.root_element.clone();
788                        continue;
789                    }
790                }
791            }
792            ElementType::Builtin(base_type)
793                if matches!(
794                    base_type.name.as_str(),
795                    "Rectangle"
796                        | "Empty"
797                        | "TouchArea"
798                        | "FocusScope"
799                        | "Opacity"
800                        | "Layer"
801                        | "BoxShadow"
802                        | "Clip"
803                ) =>
804            {
805                if filter == BuiltinFilter::SkipNonImplicit {
806                    return None;
807                }
808                // hard-code the value for rectangle because many rectangle end up optimized away and we
809                // don't want to depend on the element.
810                Some(Expression::Struct {
811                    ty: crate::typeregister::layout_info_type(),
812                    values: [("min", 0.), ("max", f32::MAX), ("preferred", 0.)]
813                        .iter()
814                        .map(|(s, v)| {
815                            (SmolStr::new_static(s), Expression::NumberLiteral(*v as _, Unit::Px))
816                        })
817                        .chain(
818                            [("min_percent", 0.), ("max_percent", 100.), ("stretch", 1.)]
819                                .iter()
820                                .map(|(s, v)| {
821                                    (
822                                        SmolStr::new_static(s),
823                                        Expression::NumberLiteral(*v, Unit::None),
824                                    )
825                                }),
826                        )
827                        .collect(),
828                })
829            }
830            ElementType::Builtin(base_type)
831                if filter == BuiltinFilter::SkipNonImplicit
832                    && base_type.default_size_binding
833                        != crate::langtype::DefaultSizeBinding::ImplicitSize =>
834            {
835                None
836            }
837            _ => Some(Expression::FunctionCall {
838                function: BuiltinFunction::ImplicitLayoutInfo(orientation).into(),
839                arguments: vec![
840                    Expression::ElementReference(Rc::downgrade(elem)),
841                    constraint.unwrap_or(Expression::NumberLiteral(-1., Unit::None)),
842                ],
843                source_location: None,
844            }),
845        };
846    }
847}
848
849/// Create a new property based on the name. (it might get a different name if that property exist)
850pub fn create_new_prop(elem: &ElementRc, tentative_name: SmolStr, ty: Type) -> NamedReference {
851    let mut e = elem.borrow_mut();
852    if !e.lookup_property(&tentative_name).is_valid() {
853        e.property_declarations.insert(tentative_name.clone(), ty.into());
854        drop(e);
855        NamedReference::new(elem, tentative_name)
856    } else {
857        let mut counter = 0;
858        loop {
859            counter += 1;
860            let name = format_smolstr!("{}{}", tentative_name, counter);
861            if !e.lookup_property(&name).is_valid() {
862                e.property_declarations.insert(name.clone(), ty.into());
863                drop(e);
864                return NamedReference::new(elem, name);
865            }
866        }
867    }
868}
869
870/// Return true if this type is a layout that has constraints
871pub fn is_layout(base_type: &ElementType) -> bool {
872    match base_type {
873        ElementType::Component(c) => is_layout(&c.root_element.borrow().base_type),
874        ElementType::Builtin(be) => {
875            matches!(
876                be.name.as_str(),
877                "GridLayout" | "HorizontalLayout" | "VerticalLayout" | "FlexboxLayout"
878            )
879        }
880        _ => false,
881    }
882}