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