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, PropertyLookupMode, PropertyLookupResult, Type};
9use crate::object_tree::{Component, ElementRc};
10
11use smol_str::{SmolStr, ToSmolStr};
12
13use std::cell::RefCell;
14use std::rc::{Rc, Weak};
15
16/// Number of slots a cell occupies in a box layout cache: position and size.
17pub const BOX_LAYOUT_CACHE_ENTRIES_PER_CELL: usize = 2;
18
19#[derive(Clone, Debug, Copy, Eq, PartialEq)]
20pub enum Orientation {
21    Horizontal,
22    Vertical,
23}
24
25impl Orientation {
26    pub fn orthogonal(self) -> Self {
27        match self {
28            Orientation::Horizontal => Orientation::Vertical,
29            Orientation::Vertical => Orientation::Horizontal,
30        }
31    }
32}
33
34#[derive(Clone, Debug, Copy, Eq, PartialEq, Default)]
35pub enum FlexboxLayoutDirection {
36    /// Items are laid out in rows (horizontal primary axis)
37    #[default]
38    Row,
39    /// Items are laid out in rows in reverse order (horizontal primary axis, right to left)
40    RowReverse,
41    /// Items are laid out in columns (vertical primary axis)
42    Column,
43    /// Items are laid out in columns in reverse order (vertical primary axis, bottom to top)
44    ColumnReverse,
45}
46
47/// Relationship between a queried orientation and a FlexboxLayout's direction.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum FlexboxAxisRelation {
50    /// The queried orientation is the main axis (e.g., Horizontal for a Row flex)
51    MainAxis,
52    /// The queried orientation is the cross axis (e.g., Vertical for a Row flex)
53    CrossAxis,
54    /// The flex direction is not known at compile time
55    Unknown,
56}
57
58#[derive(Clone, Debug, derive_more::From)]
59pub enum Layout {
60    GridLayout(GridLayout),
61    BoxLayout(BoxLayout),
62    FlexboxLayout(FlexboxLayout),
63}
64
65impl Layout {
66    /// Call the visitor for each NamedReference stored in the layout
67    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
68        match self {
69            Layout::GridLayout(grid) => grid.visit_named_references(visitor),
70            Layout::BoxLayout(l) => l.visit_named_references(visitor),
71            Layout::FlexboxLayout(l) => l.visit_named_references(visitor),
72        }
73    }
74}
75
76/// An Item in the layout tree
77#[derive(Debug, Default, Clone)]
78pub struct LayoutItem {
79    pub element: ElementRc,
80    pub constraints: LayoutConstraints,
81    /// The `cross-axis-self-alignment` property, if set.
82    /// Used by box layouts and FlexboxLayout; always `None` in a GridLayout.
83    pub cross_axis_self_alignment: Option<NamedReference>,
84    /// The `layout-order` property, if set.
85    /// Used by box layouts and FlexboxLayout; always `None` in a GridLayout.
86    pub layout_order: Option<NamedReference>,
87}
88
89/// A child within a repeated Row in a GridLayout.
90/// Can be either a static item or a nested repeater (`for y in model: ...`).
91#[derive(Debug, Clone)]
92pub enum RowChildTemplate {
93    Static(LayoutItem),
94    Repeated {
95        item: LayoutItem,
96        /// The repeated element (the `for y in ...` element inside the Row)
97        repeated_element: ElementRc,
98    },
99}
100
101impl RowChildTemplate {
102    pub fn layout_item(&self) -> &LayoutItem {
103        match self {
104            RowChildTemplate::Static(item) => item,
105            RowChildTemplate::Repeated { item, .. } => item,
106        }
107    }
108
109    pub fn layout_item_mut(&mut self) -> &mut LayoutItem {
110        match self {
111            RowChildTemplate::Static(item) => item,
112            RowChildTemplate::Repeated { item, .. } => item,
113        }
114    }
115
116    pub fn repeated_element(&self) -> Option<&ElementRc> {
117        match self {
118            RowChildTemplate::Static(_) => None,
119            RowChildTemplate::Repeated { repeated_element, .. } => Some(repeated_element),
120        }
121    }
122
123    pub fn is_repeated(&self) -> bool {
124        self.repeated_element().is_some()
125    }
126}
127
128impl LayoutItem {
129    pub fn rect(&self) -> LayoutRect {
130        let p = |unresolved_name: &str| {
131            let PropertyLookupResult { resolved_name, property_type, .. } = self
132                .element
133                .borrow()
134                .lookup_property(unresolved_name, PropertyLookupMode::ComponentLocal);
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 dyn 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    /// For each constraint, whether it is set directly on the element (an
199    /// override) rather than inherited from a base component. Inherited layout
200    /// constraints are already baked into an element's own `layoutinfo-*`, so a
201    /// parent layout that measured the cell through its layout-info must not
202    /// re-apply them (double-count / height-for-width loop); locally-set ones
203    /// must be applied. See [`Self::to_apply`].
204    pub local: LayoutConstraintLocality,
205}
206
207/// Which [`LayoutConstraints`] are set directly on the element (depth 0) rather
208/// than inherited from a base component.
209#[derive(Debug, Default, Clone)]
210pub struct LayoutConstraintLocality {
211    pub min_width: bool,
212    pub max_width: bool,
213    pub min_height: bool,
214    pub max_height: bool,
215    pub preferred_width: bool,
216    pub preferred_height: bool,
217    pub horizontal_stretch: bool,
218    pub vertical_stretch: bool,
219}
220
221/// The [`LayoutConstraints`] fields along one orientation.
222pub struct OrientationConstraints<'a> {
223    pub min: &'a Option<NamedReference>,
224    pub max: &'a Option<NamedReference>,
225    pub preferred: &'a Option<NamedReference>,
226    pub stretch: &'a Option<NamedReference>,
227    /// The size is set by an explicit `width`/`height` binding.
228    pub fixed: bool,
229}
230
231impl LayoutConstraints {
232    /// Build the constraints for the given element.
233    ///
234    /// When `diag` is `Some`, a redundant size constraint (e.g. both `width` and `min-width`) is
235    /// reported at the given level; pass `None` to compute the constraints without reporting (e.g.
236    /// when another pass owns that diagnostic).
237    pub fn new(
238        element: &ElementRc,
239        mut diag: Option<(&mut BuildDiagnostics, DiagnosticLevel)>,
240    ) -> Self {
241        let mut constraints = Self {
242            min_width: binding_reference(element, "min-width"),
243            max_width: binding_reference(element, "max-width"),
244            min_height: binding_reference(element, "min-height"),
245            max_height: binding_reference(element, "max-height"),
246            preferred_width: binding_reference(element, "preferred-width"),
247            preferred_height: binding_reference(element, "preferred-height"),
248            horizontal_stretch: binding_reference(element, "horizontal-stretch"),
249            vertical_stretch: binding_reference(element, "vertical-stretch"),
250            fixed_width: false,
251            fixed_height: false,
252            local: LayoutConstraintLocality {
253                // min/max-{width,height} may be derived from a local fixed
254                // `width`/`height` binding (see below), which is just as local
255                // an override as an explicit min/max constraint.
256                min_width: is_local_binding(element, "min-width")
257                    || is_local_binding(element, "width"),
258                max_width: is_local_binding(element, "max-width")
259                    || is_local_binding(element, "width"),
260                min_height: is_local_binding(element, "min-height")
261                    || is_local_binding(element, "height"),
262                max_height: is_local_binding(element, "max-height")
263                    || is_local_binding(element, "height"),
264                preferred_width: is_local_binding(element, "preferred-width"),
265                preferred_height: is_local_binding(element, "preferred-height"),
266                horizontal_stretch: is_local_binding(element, "horizontal-stretch"),
267                vertical_stretch: is_local_binding(element, "vertical-stretch"),
268            },
269        };
270        let mut apply_size_constraint =
271            |prop: &'static str,
272             binding: &BindingExpression,
273             enclosing1: &Weak<Component>,
274             depth,
275             op: &mut Option<NamedReference>| {
276                if let Some(other_prop) = op {
277                    find_binding(
278                        &other_prop.element(),
279                        other_prop.name(),
280                        |old, enclosing2, d2| {
281                            if let Some((diag, level)) = &mut diag
282                                && Weak::ptr_eq(enclosing1, enclosing2)
283                                && old.priority.saturating_add(d2)
284                                    <= binding.priority.saturating_add(depth)
285                            {
286                                diag.push_diagnostic_with_span(
287                                    format!(
288                                        "Cannot specify both '{prop}' and '{}'",
289                                        other_prop.name()
290                                    ),
291                                    binding.to_source_location(),
292                                    *level,
293                                );
294                            }
295                        },
296                    );
297                }
298                *op = Some(NamedReference::new(element, SmolStr::new_static(prop)))
299            };
300        find_binding(element, "height", |s, enclosing, depth| {
301            constraints.fixed_height = true;
302            apply_size_constraint("height", s, enclosing, depth, &mut constraints.min_height);
303            apply_size_constraint("height", s, enclosing, depth, &mut constraints.max_height);
304        });
305        find_binding(element, "width", |s, enclosing, depth| {
306            constraints.fixed_width = true;
307            if s.expression.ty() == Type::Percent {
308                apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
309            } else {
310                apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
311                apply_size_constraint("width", s, enclosing, depth, &mut constraints.max_width);
312            }
313        });
314
315        constraints
316    }
317
318    pub fn has_explicit_restrictions(&self, orientation: Orientation) -> bool {
319        match orientation {
320            Orientation::Horizontal => {
321                self.min_width.is_some()
322                    || self.max_width.is_some()
323                    || self.preferred_width.is_some()
324                    || self.horizontal_stretch.is_some()
325            }
326            Orientation::Vertical => {
327                self.min_height.is_some()
328                    || self.max_height.is_some()
329                    || self.preferred_height.is_some()
330                    || self.vertical_stretch.is_some()
331            }
332        }
333    }
334
335    /// The constraints a parent layout should apply on top of a cell's measured
336    /// layout-info for `orientation`. Native items (whose layout-info doesn't
337    /// merge their constraints) keep everything. For elements whose `layoutinfo-*`
338    /// already includes their intrinsic constraints, only locally-set overrides
339    /// are kept — inherited constraints are already in the measured value, and
340    /// re-reading them unconstrained can reintroduce a height-for-width loop.
341    pub fn to_apply(&self, element: &ElementRc, orientation: Orientation) -> Self {
342        if !element.borrow().layout_info_includes_own_constraints(orientation) {
343            return self.clone();
344        }
345        let mut c = self.clone();
346        match orientation {
347            Orientation::Horizontal => {
348                if !self.local.min_width {
349                    c.min_width = None;
350                }
351                if !self.local.max_width {
352                    c.max_width = None;
353                }
354                if !self.local.preferred_width {
355                    c.preferred_width = None;
356                }
357                if !self.local.horizontal_stretch {
358                    c.horizontal_stretch = None;
359                }
360            }
361            Orientation::Vertical => {
362                if !self.local.min_height {
363                    c.min_height = None;
364                }
365                if !self.local.max_height {
366                    c.max_height = None;
367                }
368                if !self.local.preferred_height {
369                    c.preferred_height = None;
370                }
371                if !self.local.vertical_stretch {
372                    c.vertical_stretch = None;
373                }
374            }
375        }
376        c
377    }
378
379    pub fn for_orientation(&self, orientation: Orientation) -> OrientationConstraints<'_> {
380        match orientation {
381            Orientation::Horizontal => OrientationConstraints {
382                min: &self.min_width,
383                max: &self.max_width,
384                preferred: &self.preferred_width,
385                stretch: &self.horizontal_stretch,
386                fixed: self.fixed_width,
387            },
388            Orientation::Vertical => OrientationConstraints {
389                min: &self.min_height,
390                max: &self.max_height,
391                preferred: &self.preferred_height,
392                stretch: &self.vertical_stretch,
393                fixed: self.fixed_height,
394            },
395        }
396    }
397
398    // Iterate over the constraint with a reference to a property, and the corresponding member in the i_slint_core::layout::LayoutInfo struct
399    pub fn for_each_restrictions(
400        &self,
401        orientation: Orientation,
402    ) -> impl Iterator<Item = (&NamedReference, &'static str)> {
403        let c = self.for_orientation(orientation);
404        std::iter::empty()
405            .chain(c.min.as_ref().map(|x| {
406                if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
407                    (x, "min")
408                } else {
409                    (x, "min_percent")
410                }
411            }))
412            .chain(c.max.as_ref().map(|x| {
413                if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
414                    (x, "max")
415                } else {
416                    (x, "max_percent")
417                }
418            }))
419            .chain(c.preferred.as_ref().map(|x| (x, "preferred")))
420            .chain(c.stretch.as_ref().map(|x| (x, "stretch")))
421    }
422
423    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
424        if let Some(e) = self.max_width.as_mut() {
425            visitor(&mut *e);
426        }
427        if let Some(e) = self.min_width.as_mut() {
428            visitor(&mut *e);
429        }
430        if let Some(e) = self.max_height.as_mut() {
431            visitor(&mut *e);
432        }
433        if let Some(e) = self.min_height.as_mut() {
434            visitor(&mut *e);
435        }
436        if let Some(e) = self.preferred_width.as_mut() {
437            visitor(&mut *e);
438        }
439        if let Some(e) = self.preferred_height.as_mut() {
440            visitor(&mut *e);
441        }
442        if let Some(e) = self.horizontal_stretch.as_mut() {
443            visitor(&mut *e);
444        }
445        if let Some(e) = self.vertical_stretch.as_mut() {
446            visitor(&mut *e);
447        }
448    }
449}
450
451#[derive(Debug, Clone)]
452pub enum RowColExpr {
453    Named(NamedReference),
454    Literal(u16),
455    Auto,
456}
457
458#[derive(Debug, Clone)]
459pub struct GridLayoutCell {
460    pub new_row: bool,
461    pub col_expr: RowColExpr,
462    pub row_expr: RowColExpr,
463    pub colspan_expr: RowColExpr,
464    pub rowspan_expr: RowColExpr,
465    pub child_items: Option<Vec<RowChildTemplate>>, // for repeated rows
466}
467
468impl GridLayoutCell {
469    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
470        if let RowColExpr::Named(ref mut e) = self.col_expr {
471            visitor(e);
472        }
473        if let RowColExpr::Named(ref mut e) = self.row_expr {
474            visitor(e);
475        }
476        if let RowColExpr::Named(ref mut e) = self.colspan_expr {
477            visitor(e);
478        }
479        if let RowColExpr::Named(ref mut e) = self.rowspan_expr {
480            visitor(e);
481        }
482        if let Some(children) = &mut self.child_items {
483            for child in children {
484                child.layout_item_mut().constraints.visit_named_references(visitor);
485            }
486        }
487    }
488}
489
490/// An element in a GridLayout
491#[derive(Debug, Clone)]
492pub struct GridLayoutElement {
493    /// `Rc<RefCell<GridLayoutCell>>` because shared with the repeated component's element
494    pub cell: Rc<RefCell<GridLayoutCell>>,
495    pub item: LayoutItem,
496}
497
498impl GridLayoutElement {
499    pub fn span(&self, orientation: Orientation) -> RowColExpr {
500        let cell = self.cell.borrow();
501        match orientation {
502            Orientation::Horizontal => cell.colspan_expr.clone(),
503            Orientation::Vertical => cell.rowspan_expr.clone(),
504        }
505    }
506}
507
508#[derive(Debug, Clone)]
509pub struct Padding {
510    pub left: Option<NamedReference>,
511    pub right: Option<NamedReference>,
512    pub top: Option<NamedReference>,
513    pub bottom: Option<NamedReference>,
514}
515
516impl Padding {
517    fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
518        if let Some(e) = self.left.as_mut() {
519            visitor(&mut *e)
520        }
521        if let Some(e) = self.right.as_mut() {
522            visitor(&mut *e)
523        }
524        if let Some(e) = self.top.as_mut() {
525            visitor(&mut *e)
526        }
527        if let Some(e) = self.bottom.as_mut() {
528            visitor(&mut *e)
529        }
530    }
531
532    // Return reference to the begin and end padding for a given orientation
533    pub fn begin_end(&self, o: Orientation) -> (Option<&NamedReference>, Option<&NamedReference>) {
534        match o {
535            Orientation::Horizontal => (self.left.as_ref(), self.right.as_ref()),
536            Orientation::Vertical => (self.top.as_ref(), self.bottom.as_ref()),
537        }
538    }
539}
540
541#[derive(Debug, Clone)]
542pub struct Spacing {
543    pub horizontal: Option<NamedReference>,
544    pub vertical: Option<NamedReference>,
545}
546
547impl Spacing {
548    fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
549        if let Some(e) = self.horizontal.as_mut() {
550            visitor(&mut *e);
551        }
552        if let Some(e) = self.vertical.as_mut() {
553            visitor(&mut *e);
554        }
555    }
556
557    pub fn orientation(&self, o: Orientation) -> Option<&NamedReference> {
558        match o {
559            Orientation::Horizontal => self.horizontal.as_ref(),
560            Orientation::Vertical => self.vertical.as_ref(),
561        }
562    }
563}
564
565#[derive(Debug, Clone)]
566pub struct LayoutGeometry {
567    pub rect: LayoutRect,
568    pub spacing: Spacing,
569    pub alignment: Option<NamedReference>,
570    pub padding: Padding,
571}
572
573impl LayoutGeometry {
574    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
575        self.rect.visit_named_references(visitor);
576        if let Some(e) = self.alignment.as_mut() {
577            visitor(&mut *e)
578        }
579        self.spacing.visit_named_references(visitor);
580        self.padding.visit_named_references(visitor);
581    }
582
583    pub fn new(layout_element: &ElementRc) -> Self {
584        let spacing = || binding_reference(layout_element, "spacing");
585        init_fake_property(layout_element, "spacing-horizontal", spacing);
586        init_fake_property(layout_element, "spacing-vertical", spacing);
587
588        let alignment = binding_reference(layout_element, "alignment");
589
590        let padding = || binding_reference(layout_element, "padding");
591        init_fake_property(layout_element, "padding-left", padding);
592        init_fake_property(layout_element, "padding-right", padding);
593        init_fake_property(layout_element, "padding-top", padding);
594        init_fake_property(layout_element, "padding-bottom", padding);
595
596        let padding = Padding {
597            left: binding_reference(layout_element, "padding-left").or_else(padding),
598            right: binding_reference(layout_element, "padding-right").or_else(padding),
599            top: binding_reference(layout_element, "padding-top").or_else(padding),
600            bottom: binding_reference(layout_element, "padding-bottom").or_else(padding),
601        };
602
603        let spacing = Spacing {
604            horizontal: binding_reference(layout_element, "spacing-horizontal").or_else(spacing),
605            vertical: binding_reference(layout_element, "spacing-vertical").or_else(spacing),
606        };
607
608        let rect = LayoutRect::install_on_element(layout_element);
609
610        Self { rect, spacing, padding, alignment }
611    }
612}
613
614/// If this element or any of the parent has a binding to the property, call the functor with that binding, and the depth.
615/// Return None if the binding does not exist in any of the sub component, or Some with the result of the functor otherwise
616pub(crate) fn find_binding<R>(
617    element: &ElementRc,
618    name: &str,
619    f: impl FnOnce(&BindingExpression, &Weak<Component>, i32) -> R,
620) -> Option<R> {
621    let mut element = element.clone();
622    let mut depth = 0;
623    loop {
624        if let Some(b) = element.borrow().binding(name)
625            && b.has_binding()
626        {
627            return Some(f(&b, &element.borrow().enclosing_component, depth));
628        }
629        let e = match &element.borrow().base_type {
630            ElementType::Component(base) => base.root_element.clone(),
631            _ => return None,
632        };
633        element = e;
634        depth += 1;
635    }
636}
637
638/// Return a named reference to a property if a binding is set on that property
639pub fn binding_reference(element: &ElementRc, name: &'static str) -> Option<NamedReference> {
640    find_binding(element, name, |_, _, _| NamedReference::new(element, SmolStr::new_static(name)))
641}
642
643/// Whether `name`'s binding is set directly on `element` (depth 0) rather than
644/// inherited from a base component. Must be evaluated while the binding is still
645/// present (i.e. when building [`LayoutConstraints`]); later passes may move it.
646fn is_local_binding(element: &ElementRc, name: &str) -> bool {
647    find_binding(element, name, |_, _, depth| depth == 0) == Some(true)
648}
649
650fn init_fake_property(
651    grid_layout_element: &ElementRc,
652    name: &str,
653    lazy_default: impl Fn() -> Option<NamedReference>,
654) {
655    if grid_layout_element.borrow().property_declarations.contains_key(name)
656        && grid_layout_element.borrow().binding(name).is_none()
657        && let Some(e) = lazy_default()
658    {
659        if e.name() == name && Rc::ptr_eq(&e.element(), grid_layout_element) {
660            // Don't reference self
661            return;
662        }
663        grid_layout_element
664            .borrow_mut()
665            .set_binding(name.into(), Expression::PropertyReference(e).into());
666    }
667}
668
669/// Internal representation of a grid layout
670#[derive(Debug, Clone)]
671pub struct GridLayout {
672    /// All the elements which will be laid out within that element.
673    pub elems: Vec<GridLayoutElement>,
674
675    pub geometry: LayoutGeometry,
676
677    /// When this GridLayout is actually the layout of a Dialog, then the cells start with all the buttons,
678    /// and this variable contains their roles. The string is actually one of the values from the i_slint_core::layout::DialogButtonRole
679    pub dialog_button_roles: Option<Vec<SmolStr>>,
680
681    /// Whether any of the row/column expressions use 'auto'
682    pub uses_auto: bool,
683}
684
685impl GridLayout {
686    /// Clone each element's cell into a new Rc, breaking any Rc sharing with the original.
687    pub fn clone_cells(&mut self) {
688        for e in &mut self.elems {
689            let cloned = Rc::new(RefCell::new(e.cell.borrow().clone()));
690            e.cell = cloned;
691        }
692    }
693
694    pub fn visit_rowcol_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
695        for elem in &mut self.elems {
696            let mut cell = elem.cell.borrow_mut();
697            if let RowColExpr::Named(ref mut e) = cell.col_expr {
698                visitor(e);
699            }
700            if let RowColExpr::Named(ref mut e) = cell.row_expr {
701                visitor(e);
702            }
703            if let RowColExpr::Named(ref mut e) = cell.colspan_expr {
704                visitor(e);
705            }
706            if let RowColExpr::Named(ref mut e) = cell.rowspan_expr {
707                visitor(e);
708            }
709        }
710    }
711
712    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
713        self.visit_rowcol_named_references(visitor);
714        for layout_elem in &mut self.elems {
715            layout_elem.item.constraints.visit_named_references(visitor);
716            if let Some(child_items) = &mut layout_elem.cell.borrow_mut().child_items {
717                for child in child_items {
718                    child.layout_item_mut().constraints.visit_named_references(visitor);
719                }
720            }
721        }
722        self.geometry.visit_named_references(visitor);
723    }
724}
725
726/// Internal representation of a BoxLayout
727#[derive(Debug, Clone)]
728pub struct BoxLayout {
729    /// Whether this is a HorizontalLayout or a VerticalLayout
730    pub orientation: Orientation,
731    pub elems: Vec<LayoutItem>,
732    pub geometry: LayoutGeometry,
733    /// The `cross-axis-alignment` property, if set.
734    pub cross_alignment: Option<NamedReference>,
735}
736
737impl BoxLayout {
738    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
739        for cell in &mut self.elems {
740            cell.constraints.visit_named_references(visitor);
741            if let Some(e) = cell.cross_axis_self_alignment.as_mut() {
742                visitor(&mut *e);
743            }
744            if let Some(e) = cell.layout_order.as_mut() {
745                visitor(&mut *e);
746            }
747        }
748        self.geometry.visit_named_references(visitor);
749        if let Some(e) = self.cross_alignment.as_mut() {
750            visitor(&mut *e);
751        }
752    }
753}
754
755/// Internal representation of a FlexboxLayout (row or column direction with wrapping)
756#[derive(Debug, Clone)]
757pub struct FlexboxLayout {
758    pub elems: Vec<LayoutItem>,
759    pub geometry: LayoutGeometry,
760    pub direction: Option<NamedReference>,
761    pub cross_axis_line_alignment: Option<NamedReference>,
762    pub cross_axis_alignment: Option<NamedReference>,
763    pub flex_wrap: Option<NamedReference>,
764}
765
766impl FlexboxLayout {
767    /// If `elem` is a (lowered, inline) FlexboxLayout, return its layout
768    /// description. The struct is embedded in the synthesized
769    /// `layoutinfo-{h,v}` / `layout-cache` bindings on the element.
770    pub fn from_element(elem: &ElementRc) -> Option<FlexboxLayout> {
771        use crate::expression_tree::Expression;
772        // The `layoutinfo-{h,v}` property's binding (on this element or its
773        // base component's root) holds a `ComputeFlexboxLayoutInfo` with the
774        // layout when the element is a FlexboxLayout.
775        let nr = {
776            let eb = elem.borrow();
777            eb.effective_layout_info_prop(Orientation::Vertical)
778                .or_else(|| eb.effective_layout_info_prop(Orientation::Horizontal))
779                .cloned()
780        }?;
781        let target = nr.element();
782        let target = target.borrow();
783        let binding = target.binding(nr.name())?;
784        match binding.value_expression() {
785            Expression::ComputeFlexboxLayoutInfo { layout, .. } => Some(layout.clone()),
786            _ => None,
787        }
788    }
789
790    /// Try to determine the flex direction at compile time from a constant binding.
791    /// Returns None if the direction is set at runtime.
792    fn compile_time_direction(&self) -> Option<FlexboxLayoutDirection> {
793        match self.direction.as_ref() {
794            None => Some(FlexboxLayoutDirection::Row),
795            Some(nr) => nr.element().borrow().binding(nr.name()).and_then(|binding| {
796                if let crate::expression_tree::Expression::EnumerationValue(ev) =
797                    binding.value_expression()
798                {
799                    match ev.enumeration.values[ev.value].as_str() {
800                        "row" => Some(FlexboxLayoutDirection::Row),
801                        "row-reverse" => Some(FlexboxLayoutDirection::RowReverse),
802                        "column" => Some(FlexboxLayoutDirection::Column),
803                        "column-reverse" => Some(FlexboxLayoutDirection::ColumnReverse),
804                        _ => None,
805                    }
806                } else {
807                    None
808                }
809            }),
810        }
811    }
812
813    /// Determine the relationship between a queried orientation and this flex's direction.
814    pub fn axis_relation(&self, orientation: Orientation) -> FlexboxAxisRelation {
815        match self.compile_time_direction() {
816            None => FlexboxAxisRelation::Unknown,
817            Some(dir) => {
818                let is_main = matches!(
819                    (dir, orientation),
820                    (
821                        FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse,
822                        Orientation::Horizontal
823                    ) | (
824                        FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
825                        Orientation::Vertical
826                    )
827                );
828                if is_main { FlexboxAxisRelation::MainAxis } else { FlexboxAxisRelation::CrossAxis }
829            }
830        }
831    }
832
833    pub fn visit_named_references(&mut self, visitor: &mut dyn FnMut(&mut NamedReference)) {
834        for cell in &mut self.elems {
835            cell.constraints.visit_named_references(visitor);
836            if let Some(e) = cell.cross_axis_self_alignment.as_mut() {
837                visitor(&mut *e)
838            }
839            if let Some(e) = cell.layout_order.as_mut() {
840                visitor(&mut *e)
841            }
842        }
843        self.geometry.visit_named_references(visitor);
844        if let Some(e) = self.direction.as_mut() {
845            visitor(&mut *e)
846        }
847        if let Some(e) = self.cross_axis_line_alignment.as_mut() {
848            visitor(&mut *e)
849        }
850        if let Some(e) = self.cross_axis_alignment.as_mut() {
851            visitor(&mut *e)
852        }
853        if let Some(e) = self.flex_wrap.as_mut() {
854            visitor(&mut *e)
855        }
856    }
857}
858
859/// Whether the builtin — or the native class it resolves to after the
860/// `resolve_native_classes` pass — has no intrinsic size (Rectangle, Empty,
861/// TouchArea, etc.): its layout info is the static default, never
862/// height-for-width.
863fn has_no_intrinsic_size(base: &ElementType) -> bool {
864    let name = match base {
865        ElementType::Builtin(b) => b.name.as_str(),
866        ElementType::Native(n) => n.class_name.as_str(),
867        _ => return false,
868    };
869    matches!(
870        name,
871        "Rectangle"
872            | "BasicBorderRectangle"
873            | "BorderRectangle"
874            | "Empty"
875            | "TouchArea"
876            | "FocusScope"
877            | "Opacity"
878            | "Layer"
879            | "BoxShadow"
880            | "Clip"
881    )
882}
883
884/// Controls whether `implicit_layout_info_call` returns layout info for builtins
885/// that don't have an intrinsic size (Rectangle, Empty, TouchArea, etc.).
886#[derive(Clone, Copy, PartialEq)]
887pub enum BuiltinFilter {
888    /// Return layout info for all builtins (existing behavior).
889    All,
890    /// Skip builtins whose `default_size_binding` is not `ImplicitSize`.
891    SkipNonImplicit,
892}
893
894/// Get the implicit layout info of a particular element.
895/// When `constraint` is `Some`, it's passed as the `cross_axis_constraint`
896/// parameter to `Item::layout_info` for height-for-width support.
897pub fn implicit_layout_info_call(
898    elem: &ElementRc,
899    orientation: Orientation,
900    filter: BuiltinFilter,
901    constraint: Option<Expression>,
902) -> Option<Expression> {
903    let mut elem_it = elem.clone();
904    // The instance decides, not the base it walks down to: a base cannot see a
905    // height the instance sets. Loop-invariant, so read it once.
906    let height_settled = elem.borrow().height_is_literal;
907    loop {
908        return match &elem_it.clone().borrow().base_type {
909            ElementType::Component(base_comp) => {
910                // Flexbox supplies a width constraint to break its
911                // h/v cache cycle; call the base component's parametrized
912                // layout-info function when present.
913                let parametrized_nr =
914                    constraint.as_ref().filter(|_| orientation == Orientation::Vertical).and_then(
915                        |_| base_comp.root_element.borrow().layout_info_v_with_constraint.clone(),
916                    );
917                if let Some(nr) = parametrized_nr
918                    && let Some(c) = &constraint
919                {
920                    debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
921                    return Some(Expression::FunctionCall {
922                        function: crate::expression_tree::Callable::Function(NamedReference::new(
923                            elem,
924                            nr.name().clone(),
925                        )),
926                        arguments: vec![c.clone()],
927                        source_location: None,
928                    });
929                }
930                let base_prop = elem_it.borrow().base_layout_info_prop(orientation, height_settled);
931                match base_prop {
932                    Some(nr) => {
933                        // We cannot take nr as is because it is relative to the elem's component. We therefore need to
934                        // use `elem` as an element for the PropertyReference, not `root` within the base of elem
935                        debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
936                        Some(Expression::PropertyReference(NamedReference::new(
937                            elem,
938                            nr.name().clone(),
939                        )))
940                    }
941                    None => {
942                        elem_it = base_comp.root_element.clone();
943                        continue;
944                    }
945                }
946            }
947            base @ (ElementType::Builtin(_) | ElementType::Native(_))
948                if has_no_intrinsic_size(base) =>
949            {
950                if filter == BuiltinFilter::SkipNonImplicit {
951                    return None;
952                }
953                // hard-code the value for rectangle because many rectangle end up optimized away and we
954                // don't want to depend on the element.
955                Some(Expression::Struct {
956                    ty: crate::typeregister::layout_info_type(),
957                    values: [("min", 0.), ("max", f32::MAX), ("preferred", 0.)]
958                        .iter()
959                        .map(|(s, v)| {
960                            (SmolStr::new_static(s), Expression::NumberLiteral(*v as _, Unit::Px))
961                        })
962                        .chain(
963                            [("min_percent", 0.), ("max_percent", 100.), ("stretch", 1.)]
964                                .iter()
965                                .map(|(s, v)| {
966                                    (
967                                        SmolStr::new_static(s),
968                                        Expression::NumberLiteral(*v, Unit::None),
969                                    )
970                                }),
971                        )
972                        .collect(),
973                })
974            }
975            ElementType::Builtin(base_type)
976                if filter == BuiltinFilter::SkipNonImplicit
977                    && base_type.default_size_binding
978                        != crate::langtype::DefaultSizeBinding::ImplicitSize =>
979            {
980                None
981            }
982            _ => Some(Expression::FunctionCall {
983                function: BuiltinFunction::ImplicitLayoutInfo(orientation).into(),
984                arguments: vec![
985                    Expression::ElementReference(Rc::downgrade(elem)),
986                    constraint.unwrap_or(Expression::NumberLiteral(-1., Unit::None)),
987                ],
988                source_location: None,
989            }),
990        };
991    }
992}
993
994/// The stretch factor of elements based on text or image items, which never
995/// stretch: their `layout_info` always reports stretch 0, and a layoutinfo
996/// property synthesized later can only merge it with smaller values.
997pub fn static_native_stretch(elem: &ElementRc) -> Option<Expression> {
998    elem.borrow()
999        .builtin_type()
1000        .filter(|b| matches!(b.name.as_str(), "Text" | "StyledText" | "TextInput" | "Image"))
1001        .map(|_| Expression::NumberLiteral(0., Unit::None))
1002}
1003
1004/// Create a new property based on the name. (it might get a different name if that property exist)
1005pub fn create_new_prop(elem: &ElementRc, tentative_name: SmolStr, ty: Type) -> NamedReference {
1006    let mut e = elem.borrow_mut();
1007    let name = if e.lookup_property(&tentative_name, PropertyLookupMode::InternalName).is_valid() {
1008        e.unique_member_name(&tentative_name)
1009    } else {
1010        tentative_name
1011    };
1012    e.property_declarations.insert(name.clone(), ty.into());
1013    drop(e);
1014    NamedReference::new(elem, name)
1015}
1016
1017/// Return true if this type is a layout that has constraints
1018pub fn is_layout(base_type: &ElementType) -> bool {
1019    match base_type {
1020        ElementType::Component(c) => is_layout(&c.root_element.borrow().base_type),
1021        ElementType::Builtin(be) => {
1022            matches!(
1023                be.name.as_str(),
1024                "GridLayout" | "HorizontalLayout" | "VerticalLayout" | "FlexboxLayout"
1025            )
1026        }
1027        _ => false,
1028    }
1029}