Skip to main content

i_slint_compiler/llr/
lower_layout_expression.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
4use std::collections::BTreeMap;
5use std::rc::Rc;
6
7use itertools::Either;
8use smol_str::SmolStr;
9
10use super::lower_to_item_tree::LoweredElement;
11use super::{GridLayoutRepeatedElement, LayoutRepeatedElement};
12use crate::langtype::{BuiltinStruct, EnumerationValue, Struct, Type};
13use crate::layout::{GridLayoutCell, Orientation, RowColExpr};
14use crate::llr::ArrayOutput as llr_ArrayOutput;
15use crate::llr::Expression as llr_Expression;
16use crate::namedreference::NamedReference;
17use crate::object_tree::ElementRc;
18
19use super::lower_expression::{ExpressionLoweringCtx, make_struct};
20
21fn empty_int32_slice() -> llr_Expression {
22    llr_Expression::Array {
23        element_ty: Type::Int32,
24        values: Vec::new(),
25        output: llr_ArrayOutput::Slice,
26    }
27}
28
29pub(super) fn compute_grid_layout_info(
30    layout_organized_data_prop: &NamedReference,
31    layout: &crate::layout::GridLayout,
32    o: Orientation,
33    ctx: &mut ExpressionLoweringCtx,
34    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
35) -> llr_Expression {
36    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
37    let organized_cells = ctx.map_property_reference(layout_organized_data_prop);
38    let constraints_result = grid_layout_cell_constraints(layout, o, ctx, cross_axis_size_override);
39    let orientation_literal = llr_Expression::EnumerationValue(EnumerationValue {
40        value: o as _,
41        enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()),
42    });
43
44    let sub_expression = llr_Expression::ExtraBuiltinFunctionCall {
45        function: "grid_layout_info".into(),
46        arguments: vec![
47            llr_Expression::PropertyReference(organized_cells),
48            constraints_result.cells,
49            if constraints_result.compute_cells.is_none() {
50                empty_int32_slice()
51            } else {
52                llr_Expression::ReadLocalVariable {
53                    name: "repeated_indices".into(),
54                    ty: Type::Array(Type::Int32.into()),
55                }
56            },
57            if constraints_result.compute_cells.is_none() {
58                empty_int32_slice()
59            } else {
60                llr_Expression::ReadLocalVariable {
61                    name: "repeater_steps".into(),
62                    ty: Type::Array(Type::Int32.into()),
63                }
64            },
65            spacing,
66            padding,
67            orientation_literal,
68        ],
69        return_ty: crate::typeregister::layout_info_type().into(),
70    };
71    match constraints_result.compute_cells {
72        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
73            cells_variable,
74            repeater_indices_var_name: Some("repeated_indices".into()),
75            repeater_steps_var_name: Some("repeater_steps".into()),
76            elements,
77            orientation: o,
78            sub_expression: Box::new(sub_expression),
79        },
80        None => sub_expression,
81    }
82}
83
84pub(super) fn compute_box_layout_info(
85    layout: &crate::layout::BoxLayout,
86    o: Orientation,
87    ctx: &mut ExpressionLoweringCtx,
88    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
89) -> llr_Expression {
90    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
91    let bld = box_layout_data(layout, o, ctx, cross_axis_size_override);
92    let sub_expression = if o == layout.orientation {
93        llr_Expression::ExtraBuiltinFunctionCall {
94            function: "box_layout_info".into(),
95            arguments: vec![bld.cells, spacing, padding, bld.alignment],
96            return_ty: crate::typeregister::layout_info_type().into(),
97        }
98    } else {
99        llr_Expression::ExtraBuiltinFunctionCall {
100            function: "box_layout_info_ortho".into(),
101            arguments: vec![bld.cells, padding],
102            return_ty: crate::typeregister::layout_info_type().into(),
103        }
104    };
105    match bld.compute_cells {
106        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
107            cells_variable,
108            repeater_indices_var_name: None,
109            repeater_steps_var_name: None,
110            elements,
111            orientation: o,
112            sub_expression: Box::new(sub_expression),
113        },
114        None => sub_expression,
115    }
116}
117
118pub(super) fn organize_grid_layout(
119    layout: &crate::layout::GridLayout,
120    ctx: &mut ExpressionLoweringCtx,
121) -> llr_Expression {
122    let input_data = grid_layout_input_data(layout, ctx);
123
124    if let Some(button_roles) = &layout.dialog_button_roles {
125        let e = crate::typeregister::BUILTIN.with(|e| e.enums.DialogButtonRole.clone());
126        let roles = button_roles
127            .iter()
128            .map(|r| {
129                llr_Expression::EnumerationValue(EnumerationValue {
130                    value: e.values.iter().position(|x| x == r).unwrap() as _,
131                    enumeration: e.clone(),
132                })
133            })
134            .collect();
135        let roles_expr = llr_Expression::Array {
136            element_ty: Type::Enumeration(e),
137            values: roles,
138            output: llr_ArrayOutput::Slice,
139        };
140        llr_Expression::ExtraBuiltinFunctionCall {
141            function: "organize_dialog_button_layout".into(),
142            arguments: vec![input_data.cells, roles_expr],
143            return_ty: Type::Array(Type::Int32.into()),
144        }
145    } else {
146        let sub_expression = llr_Expression::ExtraBuiltinFunctionCall {
147            function: "organize_grid_layout".into(),
148            arguments: vec![
149                input_data.cells,
150                if input_data.compute_cells.is_none() {
151                    empty_int32_slice()
152                } else {
153                    llr_Expression::ReadLocalVariable {
154                        name: SmolStr::new_static("repeated_indices"),
155                        ty: Type::Array(Type::Int32.into()),
156                    }
157                },
158                if input_data.compute_cells.is_none() {
159                    empty_int32_slice()
160                } else {
161                    llr_Expression::ReadLocalVariable {
162                        name: SmolStr::new_static("repeater_steps"),
163                        ty: Type::Array(Type::Int32.into()),
164                    }
165                },
166            ],
167            return_ty: Type::Array(Type::Int32.into()),
168        };
169        if let Some((cells_variable, elements)) = input_data.compute_cells {
170            llr_Expression::WithGridInputData {
171                cells_variable,
172                repeater_indices_var_name: SmolStr::new_static("repeated_indices"),
173                repeater_steps_var_name: SmolStr::new_static("repeater_steps"),
174                elements,
175                sub_expression: Box::new(sub_expression),
176            }
177        } else {
178            sub_expression
179        }
180    }
181}
182
183pub(super) fn solve_grid_layout(
184    layout_organized_data_prop: &NamedReference,
185    layout: &crate::layout::GridLayout,
186    o: Orientation,
187    ctx: &mut ExpressionLoweringCtx,
188) -> llr_Expression {
189    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
190    let cells = ctx.map_property_reference(layout_organized_data_prop);
191    let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
192    let orientation_expr = llr_Expression::EnumerationValue(EnumerationValue {
193        value: o as _,
194        enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()),
195    });
196    let data = make_struct(
197        BuiltinStruct::GridLayoutData,
198        [
199            ("size", Type::Float32, size),
200            ("spacing", Type::Float32, spacing),
201            ("padding", padding.ty(ctx), padding),
202            ("organized_data", Type::ArrayOfU16, llr_Expression::PropertyReference(cells)),
203        ],
204    );
205    let constraints_result = grid_layout_cell_constraints(layout, o, ctx, None);
206
207    match constraints_result.compute_cells {
208        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
209            cells_variable: cells_variable.clone(),
210            repeater_indices_var_name: Some("repeated_indices".into()),
211            repeater_steps_var_name: Some("repeater_steps".into()),
212            elements,
213            orientation: o,
214            sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
215                function: "solve_grid_layout".into(),
216                arguments: vec![
217                    data,
218                    llr_Expression::ReadLocalVariable {
219                        name: cells_variable.into(),
220                        ty: constraints_result.cells.ty(ctx),
221                    },
222                    orientation_expr,
223                    llr_Expression::ReadLocalVariable {
224                        name: "repeated_indices".into(),
225                        ty: Type::Array(Type::Int32.into()),
226                    },
227                    llr_Expression::ReadLocalVariable {
228                        name: "repeater_steps".into(),
229                        ty: Type::Array(Type::Int32.into()),
230                    },
231                ],
232                return_ty: Type::LayoutCache,
233            }),
234        },
235        None => llr_Expression::ExtraBuiltinFunctionCall {
236            function: "solve_grid_layout".into(),
237            arguments: vec![
238                data,
239                constraints_result.cells,
240                orientation_expr,
241                empty_int32_slice(),
242                empty_int32_slice(),
243            ],
244            return_ty: Type::LayoutCache,
245        },
246    }
247}
248
249pub(super) fn solve_box_layout(
250    layout: &crate::layout::BoxLayout,
251    o: Orientation,
252    ctx: &mut ExpressionLoweringCtx,
253) -> llr_Expression {
254    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
255    // For a horizontal layout's main (width) pass, feed each width-for-height
256    // child the layout's real cross size (its content height) instead of the
257    // `f32::MAX` "assume infinite height" fallback, so the width reserved for the
258    // child matches the height it will actually be given.
259    let cross_override = (o == layout.orientation && o == Orientation::Horizontal)
260        .then(|| layout_cross_content_size(layout))
261        .flatten();
262    let bld = box_layout_data(layout, o, ctx, cross_override.as_ref());
263    let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
264    let (data, function) = if o == layout.orientation {
265        let data = make_struct(
266            BuiltinStruct::BoxLayoutData,
267            [
268                ("size", Type::Float32, size),
269                ("spacing", Type::Float32, spacing),
270                ("padding", padding.ty(ctx), padding),
271                (
272                    "alignment",
273                    crate::typeregister::BUILTIN
274                        .with(|e| Type::Enumeration(e.enums.LayoutAlignment.clone())),
275                    bld.alignment,
276                ),
277                ("cells", bld.cells.ty(ctx), bld.cells),
278            ],
279        );
280        (data, "solve_box_layout")
281    } else {
282        let cross_axis_alignment_ty = crate::typeregister::BUILTIN
283            .with(|e| Type::Enumeration(e.enums.CrossAxisAlignment.clone()));
284        let cross_axis_alignment = if let Some(nr) = &layout.cross_alignment {
285            llr_Expression::PropertyReference(ctx.map_property_reference(nr))
286        } else {
287            let e = crate::typeregister::BUILTIN.with(|e| e.enums.CrossAxisAlignment.clone());
288            llr_Expression::EnumerationValue(EnumerationValue {
289                value: e.default_value,
290                enumeration: e,
291            })
292        };
293        let data = make_struct(
294            BuiltinStruct::BoxLayoutOrthoData,
295            [
296                ("size", Type::Float32, size),
297                ("padding", padding.ty(ctx), padding),
298                ("cross_axis_alignment", cross_axis_alignment_ty, cross_axis_alignment),
299                ("cells", bld.cells.ty(ctx), bld.cells),
300            ],
301        );
302        (data, "solve_box_layout_ortho")
303    };
304    match bld.compute_cells {
305        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
306            cells_variable,
307            repeater_indices_var_name: Some("repeated_indices".into()),
308            repeater_steps_var_name: None,
309            elements,
310            orientation: o,
311            sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
312                function: function.into(),
313                arguments: vec![
314                    data,
315                    llr_Expression::ReadLocalVariable {
316                        name: "repeated_indices".into(),
317                        ty: Type::Array(Type::Int32.into()),
318                    },
319                ],
320                return_ty: Type::LayoutCache,
321            }),
322        },
323        None => llr_Expression::ExtraBuiltinFunctionCall {
324            function: function.into(),
325            arguments: vec![data, empty_int32_slice()],
326            return_ty: Type::LayoutCache,
327        },
328    }
329}
330
331pub(super) fn solve_flexbox_layout(
332    layout: &crate::layout::FlexboxLayout,
333    ctx: &mut ExpressionLoweringCtx,
334) -> llr_Expression {
335    let (padding_h, spacing_h) =
336        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Horizontal, ctx);
337    let (padding_v, spacing_v) =
338        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Vertical, ctx);
339    // At solve time, the container width is known (set by our parent).
340    // For column-direction flex (vertical main axis), each cell is
341    // at most as wide as the container (per-column when wrapped), an upper
342    // bound to supply as the cross-axis constraint to height-for-width children.
343    let container_width_for_cells = if matches!(
344        layout.axis_relation(Orientation::Vertical),
345        crate::layout::FlexboxAxisRelation::MainAxis
346    ) {
347        layout
348            .geometry
349            .rect
350            .width_reference
351            .as_ref()
352            .map(|nr| crate::expression_tree::Expression::PropertyReference(nr.clone()))
353    } else {
354        None
355    };
356    let fld = flexbox_layout_data(layout, ctx, container_width_for_cells.as_ref(), None);
357    let width = layout_geometry_size(&layout.geometry.rect, Orientation::Horizontal, ctx);
358    let height = layout_geometry_size(&layout.geometry.rect, Orientation::Vertical, ctx);
359    let data = make_struct(
360        BuiltinStruct::FlexboxLayoutData,
361        [
362            ("width", Type::Float32, width),
363            ("height", Type::Float32, height),
364            ("spacing_h", Type::Float32, spacing_h),
365            ("spacing_v", Type::Float32, spacing_v),
366            ("padding_h", padding_h.ty(ctx), padding_h),
367            ("padding_v", padding_v.ty(ctx), padding_v),
368            (
369                "alignment",
370                crate::typeregister::BUILTIN
371                    .with(|e| Type::Enumeration(e.enums.LayoutAlignment.clone())),
372                fld.alignment,
373            ),
374            (
375                "direction",
376                crate::typeregister::BUILTIN
377                    .with(|e| Type::Enumeration(e.enums.FlexboxLayoutDirection.clone())),
378                fld.direction,
379            ),
380            (
381                "align_content",
382                crate::typeregister::BUILTIN
383                    .with(|e| Type::Enumeration(e.enums.FlexboxLayoutAlignContent.clone())),
384                fld.align_content,
385            ),
386            (
387                "cross_axis_alignment",
388                crate::typeregister::BUILTIN
389                    .with(|e| Type::Enumeration(e.enums.CrossAxisAlignment.clone())),
390                fld.cross_axis_alignment,
391            ),
392            (
393                "flex_wrap",
394                crate::typeregister::BUILTIN
395                    .with(|e| Type::Enumeration(e.enums.FlexboxLayoutWrap.clone())),
396                fld.flex_wrap,
397            ),
398            ("cells_h", fld.cells_h.ty(ctx), fld.cells_h),
399            ("cells_v", fld.cells_v.ty(ctx), fld.cells_v),
400        ],
401    );
402    match fld.compute_cells {
403        Some((cells_h_var, cells_v_var, elements)) => llr_Expression::WithFlexboxLayoutItemInfo {
404            cells_h_variable: cells_h_var,
405            cells_v_variable: cells_v_var,
406            repeater_indices_var_name: Some("repeated_indices".into()),
407            elements,
408            sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
409                function: "solve_flexbox_layout".into(),
410                arguments: vec![
411                    data,
412                    llr_Expression::ReadLocalVariable {
413                        name: "repeated_indices".into(),
414                        ty: Type::Array(Type::Int32.into()),
415                    },
416                ],
417                return_ty: Type::LayoutCache,
418            }),
419        },
420        None => {
421            // Only height-for-width-capable cells benefit from re-measuring;
422            // a flexbox without any keeps the cheaper plain solve.
423            let needs_measure = layout.elems.iter().any(|li| {
424                let elem = &li.item.element;
425                is_height_for_width_cell(elem)
426                    || elem.borrow().inherited_layout_info_h_with_constraint().is_some()
427            });
428            if !needs_measure {
429                return llr_Expression::ExtraBuiltinFunctionCall {
430                    function: "solve_flexbox_layout".into(),
431                    arguments: vec![data, empty_int32_slice()],
432                    return_ty: Type::LayoutCache,
433                };
434            }
435            // Static cells only: emit a generated measure callback so the
436            // cross-axis size of height-for-width cells is recomputed at the
437            // width/height taffy assigns, instead of the cell's preferred
438            // size (parity with the interpreter).
439            let measure_cells = layout
440                .elems
441                .iter()
442                .map(|li| {
443                    let elem = &li.item.element;
444                    let v_constraint = is_height_for_width_cell(elem).then(|| {
445                        crate::expression_tree::Expression::ReadLocalVariable {
446                            name: "measure_known_w".into(),
447                            ty: Type::LogicalLength,
448                        }
449                    });
450                    let v_info = get_layout_info(
451                        elem,
452                        ctx,
453                        &li.item.constraints,
454                        Orientation::Vertical,
455                        v_constraint,
456                    );
457                    let h_constraint =
458                        elem.borrow().inherited_layout_info_h_with_constraint().is_some().then(
459                            || crate::expression_tree::Expression::ReadLocalVariable {
460                                name: "measure_known_h".into(),
461                                ty: Type::LogicalLength,
462                            },
463                        );
464                    let h_info = get_layout_info(
465                        elem,
466                        ctx,
467                        &li.item.constraints,
468                        Orientation::Horizontal,
469                        h_constraint,
470                    );
471                    Either::Left((h_info, v_info))
472                })
473                .collect();
474            // Preferred (default-constraint) info per cell, matching the cells
475            // carried by `data`. Returned by the measure callback when taffy
476            // asks for a dimension without a known cross-axis size, so the
477            // both-unknown case mirrors the plain `solve_flexbox_layout`.
478            let default_cells = layout
479                .elems
480                .iter()
481                .map(|li| {
482                    let elem = &li.item.element;
483                    let v_constraint = if is_height_for_width_cell(elem) {
484                        default_cross_axis_constraint(elem)
485                    } else {
486                        None
487                    };
488                    let v_info = get_layout_info(
489                        elem,
490                        ctx,
491                        &li.item.constraints,
492                        Orientation::Vertical,
493                        v_constraint,
494                    );
495                    let h_constraint =
496                        elem.borrow().inherited_layout_info_h_with_constraint().is_some().then(
497                            || {
498                                crate::expression_tree::Expression::NumberLiteral(
499                                    f32::MAX as f64,
500                                    crate::expression_tree::Unit::Px,
501                                )
502                            },
503                        );
504                    let h_info = get_layout_info(
505                        elem,
506                        ctx,
507                        &li.item.constraints,
508                        Orientation::Horizontal,
509                        h_constraint,
510                    );
511                    Either::Left((h_info, v_info))
512                })
513                .collect();
514            llr_Expression::SolveFlexboxLayoutWithMeasure {
515                data: Box::new(data),
516                repeater_indices: Box::new(empty_int32_slice()),
517                measure_cells,
518                default_cells,
519            }
520        }
521    }
522}
523
524pub(super) fn compute_flexbox_layout_info(
525    layout: &crate::layout::FlexboxLayout,
526    orientation: Orientation,
527    ctx: &mut ExpressionLoweringCtx,
528    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
529) -> llr_Expression {
530    // The override carries a width when called from a
531    // `layoutinfo-v-with-constraint` body, a height when called from a
532    // `layoutinfo-h-with-constraint` body. Route it to the matching
533    // cell-list so cells don't receive a dimension on the wrong axis.
534    let (width_override, height_override) = match orientation {
535        Orientation::Vertical => (cross_axis_size_override, None),
536        Orientation::Horizontal => (None, cross_axis_size_override),
537    };
538    let fld = flexbox_layout_data(layout, ctx, width_override, height_override);
539
540    match layout.axis_relation(orientation) {
541        crate::layout::FlexboxAxisRelation::MainAxis => {
542            compute_flexbox_layout_info_for_direction(layout, orientation, false, fld, ctx, None)
543        }
544        crate::layout::FlexboxAxisRelation::CrossAxis => compute_flexbox_layout_info_for_direction(
545            layout,
546            orientation,
547            true,
548            fld,
549            ctx,
550            cross_axis_size_override,
551        ),
552        crate::layout::FlexboxAxisRelation::Unknown => {
553            // Direction is not known at compile time - generate runtime conditional
554            // This ensures we only read the constraint (width/height) in the branch where it's needed
555            let row_expr = compute_flexbox_layout_info_for_direction(
556                layout,
557                orientation,
558                orientation == Orientation::Vertical, // cross-axis if orientation is vertical
559                fld.clone(),
560                ctx,
561                cross_axis_size_override,
562            );
563            let col_expr = compute_flexbox_layout_info_for_direction(
564                layout,
565                orientation,
566                orientation == Orientation::Horizontal, // cross-axis if orientation is horizontal
567                fld,
568                ctx,
569                cross_axis_size_override,
570            );
571
572            // Condition: direction == Row || direction == RowReverse
573            let direction_enum =
574                crate::typeregister::BUILTIN.with(|e| e.enums.FlexboxLayoutDirection.clone());
575            let direction_ref = llr_Expression::PropertyReference(
576                ctx.map_property_reference(layout.direction.as_ref().unwrap()),
577            );
578
579            let is_row_condition = llr_Expression::BinaryExpression {
580                lhs: Box::new(llr_Expression::BinaryExpression {
581                    lhs: Box::new(direction_ref.clone()),
582                    rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
583                        value: 0, // FlexboxLayoutDirection::Row
584                        enumeration: direction_enum.clone(),
585                    })),
586                    op: '=',
587                }),
588                rhs: Box::new(llr_Expression::BinaryExpression {
589                    lhs: Box::new(direction_ref),
590                    rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
591                        value: 1, // FlexboxLayoutDirection::RowReverse
592                        enumeration: direction_enum,
593                    })),
594                    op: '=',
595                }),
596                op: '|',
597            };
598
599            llr_Expression::Condition {
600                condition: Box::new(is_row_condition),
601                true_expr: Box::new(row_expr),
602                false_expr: Box::new(col_expr),
603            }
604        }
605    }
606}
607
608fn compute_flexbox_layout_info_for_direction(
609    layout: &crate::layout::FlexboxLayout,
610    orientation: Orientation,
611    is_cross_axis: bool,
612    fld: FlexboxLayoutDataResult,
613    ctx: &mut ExpressionLoweringCtx,
614    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
615) -> llr_Expression {
616    let (padding_h, spacing_h) =
617        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Horizontal, ctx);
618    let (padding_v, spacing_v) =
619        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Vertical, ctx);
620
621    if is_cross_axis {
622        // Cross-axis layout info: pass the main-axis container dimension
623        // as constraint for accurate wrapping. The override (when set)
624        // replaces a `self.{width,height}` read that would otherwise
625        // cycle if this flex is nested on the perpendicular axis.
626        let constraint_size = if let Some(override_expr) = cross_axis_size_override {
627            super::lower_expression::lower_expression(override_expr, ctx)
628        } else {
629            match orientation {
630                Orientation::Horizontal => {
631                    layout_geometry_size(&layout.geometry.rect, Orientation::Vertical, ctx)
632                }
633                Orientation::Vertical => {
634                    layout_geometry_size(&layout.geometry.rect, Orientation::Horizontal, ctx)
635                }
636            }
637        };
638
639        let arguments = vec![
640            fld.cells_h,
641            fld.cells_v,
642            spacing_h,
643            spacing_v,
644            padding_h,
645            padding_v,
646            fld.direction,
647            fld.flex_wrap,
648            constraint_size,
649        ];
650
651        match fld.compute_cells {
652            Some((cells_h_var, cells_v_var, elements)) => {
653                llr_Expression::WithFlexboxLayoutItemInfo {
654                    cells_h_variable: cells_h_var,
655                    cells_v_variable: cells_v_var,
656                    repeater_indices_var_name: None,
657                    elements,
658                    sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
659                        function: "flexbox_layout_info_cross_axis".into(),
660                        arguments,
661                        return_ty: crate::typeregister::layout_info_type().into(),
662                    }),
663                }
664            }
665            None => llr_Expression::ExtraBuiltinFunctionCall {
666                function: "flexbox_layout_info_cross_axis".into(),
667                arguments,
668                return_ty: crate::typeregister::layout_info_type().into(),
669            },
670        }
671    } else {
672        // Main axis: only needs same-axis cells, avoiding cross-axis binding loop.
673        let (cells, spacing, padding) = match orientation {
674            Orientation::Horizontal => (fld.cells_h, spacing_h, padding_h),
675            Orientation::Vertical => (fld.cells_v, spacing_v, padding_v),
676        };
677
678        match fld.compute_cells {
679            Some((cells_h_var, cells_v_var, elements)) => {
680                let cells_var = match orientation {
681                    Orientation::Horizontal => cells_h_var.clone(),
682                    Orientation::Vertical => cells_v_var.clone(),
683                };
684                llr_Expression::WithFlexboxLayoutItemInfo {
685                    cells_h_variable: cells_h_var,
686                    cells_v_variable: cells_v_var,
687                    repeater_indices_var_name: None,
688                    elements,
689                    sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
690                        function: "flexbox_layout_info_main_axis".into(),
691                        arguments: vec![
692                            llr_Expression::ReadLocalVariable {
693                                name: cells_var.into(),
694                                ty: Type::Array(Rc::new(
695                                    crate::typeregister::flexbox_layout_item_info_type(),
696                                )),
697                            },
698                            spacing,
699                            padding,
700                            fld.flex_wrap,
701                        ],
702                        return_ty: crate::typeregister::layout_info_type().into(),
703                    }),
704                }
705            }
706            None => llr_Expression::ExtraBuiltinFunctionCall {
707                function: "flexbox_layout_info_main_axis".into(),
708                arguments: vec![cells, spacing, padding, fld.flex_wrap],
709                return_ty: crate::typeregister::layout_info_type().into(),
710            },
711        }
712    }
713}
714
715#[derive(Clone)]
716struct FlexboxLayoutDataResult {
717    alignment: llr_Expression,
718    direction: llr_Expression,
719    align_content: llr_Expression,
720    cross_axis_alignment: llr_Expression,
721    flex_wrap: llr_Expression,
722    cells_h: llr_Expression,
723    cells_v: llr_Expression,
724    /// When there are repeaters involved, we need to do a WithFlexboxLayoutItemInfo with the
725    /// given cells_h/cells_v variable names and elements (each static element has a tuple of (h, v) layout info)
726    compute_cells: Option<(
727        String,
728        String,
729        Vec<Either<(llr_Expression, llr_Expression), LayoutRepeatedElement>>,
730    )>,
731}
732
733fn flexbox_layout_data(
734    layout: &crate::layout::FlexboxLayout,
735    ctx: &mut ExpressionLoweringCtx,
736    width_override: Option<&crate::expression_tree::Expression>,
737    height_override: Option<&crate::expression_tree::Expression>,
738) -> FlexboxLayoutDataResult {
739    let alignment = if let Some(expr) = &layout.geometry.alignment {
740        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
741    } else {
742        let e = crate::typeregister::BUILTIN.with(|e| e.enums.LayoutAlignment.clone());
743        llr_Expression::EnumerationValue(EnumerationValue {
744            value: e.default_value,
745            enumeration: e,
746        })
747    };
748
749    let direction = if let Some(expr) = &layout.direction {
750        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
751    } else {
752        let e = crate::typeregister::BUILTIN.with(|e| e.enums.FlexboxLayoutDirection.clone());
753        llr_Expression::EnumerationValue(EnumerationValue {
754            value: e.default_value,
755            enumeration: e,
756        })
757    };
758
759    let align_content = if let Some(expr) = &layout.align_content {
760        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
761    } else {
762        let e = crate::typeregister::BUILTIN.with(|e| e.enums.FlexboxLayoutAlignContent.clone());
763        llr_Expression::EnumerationValue(EnumerationValue {
764            value: e.default_value,
765            enumeration: e,
766        })
767    };
768
769    let cross_axis_alignment = if let Some(expr) = &layout.cross_axis_alignment {
770        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
771    } else {
772        let e = crate::typeregister::BUILTIN.with(|e| e.enums.CrossAxisAlignment.clone());
773        llr_Expression::EnumerationValue(EnumerationValue {
774            value: e.default_value,
775            enumeration: e,
776        })
777    };
778
779    let flex_wrap = if let Some(expr) = &layout.flex_wrap {
780        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
781    } else {
782        let e = crate::typeregister::BUILTIN.with(|e| e.enums.FlexboxLayoutWrap.clone());
783        llr_Expression::EnumerationValue(EnumerationValue {
784            value: e.default_value,
785            enumeration: e,
786        })
787    };
788
789    let repeater_count =
790        layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count();
791
792    let element_ty = crate::typeregister::flexbox_layout_item_info_type();
793
794    let flex_prop =
795        |li: &crate::layout::FlexboxLayoutItem, ctx: &mut ExpressionLoweringCtx| -> FlexItemProps {
796            FlexItemProps {
797                grow: li
798                    .flex_grow
799                    .as_ref()
800                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
801                    .unwrap_or(llr_Expression::NumberLiteral(0.0)),
802                shrink: li
803                    .flex_shrink
804                    .as_ref()
805                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
806                    .unwrap_or(llr_Expression::NumberLiteral(0.0)),
807                basis: li
808                    .flex_basis
809                    .as_ref()
810                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
811                    .unwrap_or(llr_Expression::NumberLiteral(-1.0)),
812                align_self: li
813                    .align_self
814                    .as_ref()
815                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
816                    .unwrap_or(default_align_self().1),
817                order: li
818                    .order
819                    .as_ref()
820                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
821                    .unwrap_or(llr_Expression::NumberLiteral(0.0)),
822            }
823        };
824
825    // Width constraint for a cell's cells_v entry. Use the explicit
826    // width-override when one is in scope (solve-time container width,
827    // or width parameter of a synthesized `layoutinfo-v-with-constraint`
828    // body); otherwise fall back to the element's own preferred
829    // horizontal size. Cells that are not height-for-width get `None`.
830    let cell_v_constraint = |elem: &ElementRc| -> Option<crate::expression_tree::Expression> {
831        if !is_height_for_width_cell(elem) {
832            return None;
833        }
834        width_override.cloned().or_else(|| default_cross_axis_constraint(elem))
835    };
836    // Height constraint for a cell's cells_h entry. Dispatch via
837    // `layoutinfo-h-with-constraint` for cells that have one. Use
838    // `f32::MAX` ("unconstrained") when no explicit height-override is
839    // in scope — that tells the runtime to treat the cell as not
840    // needing to wrap, giving the natural max-cell-width rather than
841    // the `sqrt(item-areas)` heuristic.
842    let cell_h_constraint = |elem: &ElementRc| -> Option<crate::expression_tree::Expression> {
843        if elem.borrow().inherited_layout_info_h_with_constraint().is_some() {
844            Some(height_override.cloned().unwrap_or_else(|| {
845                crate::expression_tree::Expression::NumberLiteral(
846                    f32::MAX as f64,
847                    crate::expression_tree::Unit::Px,
848                )
849            }))
850        } else {
851            None
852        }
853    };
854
855    if repeater_count == 0 {
856        let cells_h = llr_Expression::Array {
857            values: layout
858                .elems
859                .iter()
860                .map(|li| {
861                    let constraint = cell_h_constraint(&li.item.element);
862                    let layout_info_h = get_layout_info(
863                        &li.item.element,
864                        ctx,
865                        &li.item.constraints,
866                        Orientation::Horizontal,
867                        constraint,
868                    );
869                    let flex_props = flex_prop(li, ctx);
870                    make_flexbox_cell_data_struct(layout_info_h, flex_props)
871                })
872                .collect(),
873            element_ty: element_ty.clone(),
874            output: llr_ArrayOutput::Slice,
875        };
876        // For cells_v, pass a width constraint for items that need
877        // height-for-width (Text with word-wrap, Image with aspect ratio,
878        // and components with a synthesized
879        // `layoutinfo-v-with-constraint`).
880        let cells_v = llr_Expression::Array {
881            values: layout
882                .elems
883                .iter()
884                .map(|li| {
885                    let constraint = cell_v_constraint(&li.item.element);
886                    let layout_info_v = get_layout_info(
887                        &li.item.element,
888                        ctx,
889                        &li.item.constraints,
890                        Orientation::Vertical,
891                        constraint,
892                    );
893                    let flex_props = flex_prop(li, ctx);
894                    make_flexbox_cell_data_struct(layout_info_v, flex_props)
895                })
896                .collect(),
897            element_ty,
898            output: llr_ArrayOutput::Slice,
899        };
900        FlexboxLayoutDataResult {
901            alignment,
902            direction,
903            align_content,
904            cross_axis_alignment,
905            flex_wrap,
906            cells_h,
907            cells_v,
908            compute_cells: None,
909        }
910    } else {
911        let mut elements = Vec::new();
912        for item in &layout.elems {
913            if item.item.element.borrow().repeated.is_some() {
914                let repeater_index = match ctx
915                    .mapping
916                    .element_mapping
917                    .get(&item.item.element.clone().into())
918                    .unwrap()
919                {
920                    LoweredElement::Repeated { repeated_index } => *repeated_index,
921                    _ => panic!(),
922                };
923                elements.push(Either::Right(LayoutRepeatedElement {
924                    repeater_index,
925                    row_child_templates: None,
926                }))
927            } else {
928                // For static elements, we need both orientations
929                let h_constraint = cell_h_constraint(&item.item.element);
930                let layout_info_h = get_layout_info(
931                    &item.item.element,
932                    ctx,
933                    &item.item.constraints,
934                    Orientation::Horizontal,
935                    h_constraint,
936                );
937                let constraint = cell_v_constraint(&item.item.element);
938                let layout_info_v = get_layout_info(
939                    &item.item.element,
940                    ctx,
941                    &item.item.constraints,
942                    Orientation::Vertical,
943                    constraint,
944                );
945                let flex_props = flex_prop(item, ctx);
946                elements.push(Either::Left((
947                    make_flexbox_cell_data_struct(layout_info_h, flex_props.clone()),
948                    make_flexbox_cell_data_struct(layout_info_v, flex_props),
949                )));
950            }
951        }
952        let cells_h = llr_Expression::ReadLocalVariable {
953            name: "cells_h".into(),
954            ty: Type::Array(Rc::new(crate::typeregister::flexbox_layout_item_info_type())),
955        };
956        let cells_v = llr_Expression::ReadLocalVariable {
957            name: "cells_v".into(),
958            ty: Type::Array(Rc::new(crate::typeregister::flexbox_layout_item_info_type())),
959        };
960        FlexboxLayoutDataResult {
961            alignment,
962            direction,
963            align_content,
964            cross_axis_alignment,
965            flex_wrap,
966            cells_h,
967            cells_v,
968            compute_cells: Some(("cells_h".into(), "cells_v".into(), elements)),
969        }
970    }
971}
972
973struct BoxLayoutDataResult {
974    alignment: llr_Expression,
975    cells: llr_Expression,
976    /// When there are repeater involved, we need to do a WithLayoutItemInfo with the
977    /// given cell variable and elements
978    compute_cells: Option<(String, Vec<Either<llr_Expression, LayoutRepeatedElement>>)>,
979}
980
981fn default_align_self() -> (Type, llr_Expression) {
982    let e = crate::typeregister::BUILTIN.with(|e| e.enums.FlexboxLayoutAlignSelf.clone());
983    (
984        Type::Enumeration(e.clone()),
985        llr_Expression::EnumerationValue(EnumerationValue {
986            value: e.default_value,
987            enumeration: e,
988        }),
989    )
990}
991
992fn make_layout_cell_data_struct(layout_info: llr_Expression) -> llr_Expression {
993    make_struct(
994        BuiltinStruct::LayoutItemInfo,
995        [("constraint", crate::typeregister::layout_info_type().into(), layout_info)],
996    )
997}
998
999#[derive(Clone)]
1000struct FlexItemProps {
1001    grow: llr_Expression,
1002    shrink: llr_Expression,
1003    basis: llr_Expression,
1004    align_self: llr_Expression,
1005    order: llr_Expression,
1006}
1007
1008fn make_flexbox_cell_data_struct(layout_info: llr_Expression, fp: FlexItemProps) -> llr_Expression {
1009    let (align_self_ty, _) = default_align_self();
1010    make_struct(
1011        BuiltinStruct::FlexboxLayoutItemInfo,
1012        [
1013            ("constraint", crate::typeregister::layout_info_type().into(), layout_info),
1014            ("flex-grow", Type::Float32, fp.grow),
1015            ("flex-shrink", Type::Float32, fp.shrink),
1016            ("flex-basis", Type::Float32, fp.basis),
1017            ("flex-align-self", align_self_ty, fp.align_self),
1018            ("flex-order", Type::Int32, fp.order),
1019        ],
1020    )
1021}
1022
1023fn box_layout_data(
1024    layout: &crate::layout::BoxLayout,
1025    orientation: Orientation,
1026    ctx: &mut ExpressionLoweringCtx,
1027    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1028) -> BoxLayoutDataResult {
1029    let alignment = if let Some(expr) = &layout.geometry.alignment {
1030        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
1031    } else {
1032        let e = crate::typeregister::BUILTIN.with(|e| e.enums.LayoutAlignment.clone());
1033        llr_Expression::EnumerationValue(EnumerationValue {
1034            value: e.default_value,
1035            enumeration: e,
1036        })
1037    };
1038
1039    let repeater_count =
1040        layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
1041
1042    let element_ty = crate::typeregister::layout_item_info_type();
1043
1044    if repeater_count == 0 {
1045        let cells = llr_Expression::Array {
1046            values: layout
1047                .elems
1048                .iter()
1049                .map(|li| {
1050                    let layout_info = cell_layout_info(
1051                        &li.element,
1052                        &li.constraints,
1053                        ctx,
1054                        orientation,
1055                        cross_axis_size_override,
1056                    );
1057                    make_layout_cell_data_struct(layout_info)
1058                })
1059                .collect(),
1060            element_ty,
1061            output: llr_ArrayOutput::Slice,
1062        };
1063        BoxLayoutDataResult { alignment, cells, compute_cells: None }
1064    } else {
1065        let mut elements = Vec::new();
1066        for item in &layout.elems {
1067            if item.element.borrow().repeated.is_some() {
1068                let repeater_index =
1069                    match ctx.mapping.element_mapping.get(&item.element.clone().into()).unwrap() {
1070                        LoweredElement::Repeated { repeated_index } => *repeated_index,
1071                        _ => panic!(),
1072                    };
1073                elements.push(Either::Right(LayoutRepeatedElement {
1074                    repeater_index,
1075                    row_child_templates: None,
1076                }))
1077            } else {
1078                let layout_info = cell_layout_info(
1079                    &item.element,
1080                    &item.constraints,
1081                    ctx,
1082                    orientation,
1083                    cross_axis_size_override,
1084                );
1085                elements.push(Either::Left(make_layout_cell_data_struct(layout_info)));
1086            }
1087        }
1088        let cells = llr_Expression::ReadLocalVariable {
1089            name: "cells".into(),
1090            ty: Type::Array(Rc::new(crate::typeregister::layout_info_type().into())),
1091        };
1092        BoxLayoutDataResult { alignment, cells, compute_cells: Some(("cells".into(), elements)) }
1093    }
1094}
1095
1096fn cell_layout_info(
1097    elem: &ElementRc,
1098    constraints: &crate::layout::LayoutConstraints,
1099    ctx: &mut ExpressionLoweringCtx,
1100    orientation: Orientation,
1101    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1102) -> llr_Expression {
1103    let constraint = match orientation {
1104        Orientation::Vertical => {
1105            cross_axis_size_override.filter(|_| is_height_for_width_cell(elem)).cloned()
1106        }
1107        Orientation::Horizontal => {
1108            // Cells with `layoutinfo-h-with-constraint` need a constraint
1109            // to dispatch via the parametrized layout-info function
1110            // instead of reading the cell's own height — which would cycle
1111            // when the cell is a flex on the perpendicular
1112            // (horizontal-cross) axis.
1113            if elem.borrow().inherited_layout_info_h_with_constraint().is_some() {
1114                Some(cross_axis_size_override.cloned().unwrap_or_else(|| {
1115                    crate::expression_tree::Expression::NumberLiteral(
1116                        f32::MAX as f64,
1117                        crate::expression_tree::Unit::Px,
1118                    )
1119                }))
1120            } else {
1121                None
1122            }
1123        }
1124    };
1125    get_layout_info(elem, ctx, constraints, orientation, constraint)
1126}
1127
1128struct GridLayoutCellConstraintsResult {
1129    cells: llr_Expression,
1130    /// When there are repeater involved, we need to do a WithLayoutItemInfo with the
1131    /// given cell variable and elements
1132    compute_cells: Option<(String, Vec<Either<llr_Expression, LayoutRepeatedElement>>)>,
1133}
1134
1135fn grid_layout_cell_constraints(
1136    layout: &crate::layout::GridLayout,
1137    orientation: Orientation,
1138    ctx: &mut ExpressionLoweringCtx,
1139    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1140) -> GridLayoutCellConstraintsResult {
1141    let repeater_count =
1142        layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count();
1143
1144    let element_ty = crate::typeregister::layout_item_info_type();
1145
1146    if repeater_count == 0 {
1147        let cells = llr_Expression::Array {
1148            element_ty,
1149            values: layout
1150                .elems
1151                .iter()
1152                .map(|li| {
1153                    let layout_info = cell_layout_info(
1154                        &li.item.element,
1155                        &li.item.constraints,
1156                        ctx,
1157                        orientation,
1158                        cross_axis_size_override,
1159                    );
1160                    make_layout_cell_data_struct(layout_info)
1161                })
1162                .collect(),
1163            output: llr_ArrayOutput::Slice,
1164        };
1165        GridLayoutCellConstraintsResult { cells, compute_cells: None }
1166    } else {
1167        let mut elements = Vec::new();
1168        for item in &layout.elems {
1169            if item.item.element.borrow().repeated.is_some() {
1170                let repeater_index = match ctx
1171                    .mapping
1172                    .element_mapping
1173                    .get(&item.item.element.clone().into())
1174                    .unwrap()
1175                {
1176                    LoweredElement::Repeated { repeated_index } => *repeated_index,
1177                    _ => panic!(),
1178                };
1179                let row_child_templates = get_row_child_templates(&item.item.element, ctx);
1180                elements.push(Either::Right(LayoutRepeatedElement {
1181                    repeater_index,
1182                    row_child_templates,
1183                }));
1184            } else {
1185                let layout_info = cell_layout_info(
1186                    &item.item.element,
1187                    &item.item.constraints,
1188                    ctx,
1189                    orientation,
1190                    cross_axis_size_override,
1191                );
1192                elements.push(Either::Left(make_layout_cell_data_struct(layout_info)));
1193            }
1194        }
1195        let cells = llr_Expression::ReadLocalVariable {
1196            name: "cells".into(),
1197            ty: Type::Array(Rc::new(crate::typeregister::layout_info_type().into())),
1198        };
1199        GridLayoutCellConstraintsResult { cells, compute_cells: Some(("cells".into(), elements)) }
1200    }
1201}
1202
1203struct GridLayoutInputDataResult {
1204    cells: llr_Expression,
1205    /// When there are repeaters involved, we need to do a WithGridInputData with the
1206    /// given cell variable and elements
1207    compute_cells: Option<(String, Vec<Either<llr_Expression, GridLayoutRepeatedElement>>)>,
1208}
1209
1210// helper for organize_grid_layout()
1211fn grid_layout_input_data(
1212    layout: &crate::layout::GridLayout,
1213    ctx: &mut ExpressionLoweringCtx,
1214) -> GridLayoutInputDataResult {
1215    let propref = |named_ref: &RowColExpr| match named_ref {
1216        RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()),
1217        RowColExpr::Named(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
1218        RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _),
1219    };
1220    let input_data_for_cell = |elem: &crate::layout::GridLayoutElement,
1221                               new_row_expr: llr_Expression| {
1222        let row_expr = propref(&elem.cell.borrow().row_expr);
1223        let col_expr = propref(&elem.cell.borrow().col_expr);
1224        let rowspan_expr = propref(&elem.cell.borrow().rowspan_expr);
1225        let colspan_expr = propref(&elem.cell.borrow().colspan_expr);
1226
1227        make_struct(
1228            BuiltinStruct::GridLayoutInputData,
1229            [
1230                ("new_row", Type::Bool, new_row_expr),
1231                ("row", Type::Float32, row_expr),
1232                ("col", Type::Float32, col_expr),
1233                ("rowspan", Type::Float32, rowspan_expr),
1234                ("colspan", Type::Float32, colspan_expr),
1235            ],
1236        )
1237    };
1238    let repeater_count =
1239        layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count();
1240
1241    let element_ty = grid_layout_input_data_ty();
1242
1243    if repeater_count == 0 {
1244        let cells = llr_Expression::Array {
1245            element_ty,
1246            values: layout
1247                .elems
1248                .iter()
1249                .map(|elem| {
1250                    input_data_for_cell(
1251                        elem,
1252                        llr_Expression::BoolLiteral(elem.cell.borrow().new_row),
1253                    )
1254                })
1255                .collect(),
1256            output: llr_ArrayOutput::Slice,
1257        };
1258        GridLayoutInputDataResult { cells, compute_cells: None }
1259    } else {
1260        let mut elements = Vec::new();
1261        let mut after_repeater_in_same_row = false;
1262        for item in &layout.elems {
1263            let new_row = item.cell.borrow().new_row;
1264            if new_row {
1265                after_repeater_in_same_row = false;
1266            }
1267            if item.item.element.borrow().repeated.is_some() {
1268                let repeater_index = match ctx
1269                    .mapping
1270                    .element_mapping
1271                    .get(&item.item.element.clone().into())
1272                    .unwrap()
1273                {
1274                    LoweredElement::Repeated { repeated_index } => *repeated_index,
1275                    _ => panic!(),
1276                };
1277                let row_child_templates = get_row_child_templates(&item.item.element, ctx);
1278                let repeated_element =
1279                    GridLayoutRepeatedElement { new_row, repeater_index, row_child_templates };
1280                elements.push(Either::Right(repeated_element));
1281                after_repeater_in_same_row = true;
1282            } else {
1283                let new_row_expr = if new_row || !after_repeater_in_same_row {
1284                    llr_Expression::BoolLiteral(new_row)
1285                } else {
1286                    llr_Expression::ReadLocalVariable {
1287                        name: SmolStr::new_static("new_row"),
1288                        ty: Type::Bool,
1289                    }
1290                };
1291                elements.push(Either::Left(input_data_for_cell(item, new_row_expr)));
1292            }
1293        }
1294        let cells = llr_Expression::ReadLocalVariable {
1295            name: "cells".into(),
1296            ty: Type::Array(Rc::new(element_ty)),
1297        };
1298        GridLayoutInputDataResult { cells, compute_cells: Some(("cells".into(), elements)) }
1299    }
1300}
1301
1302pub(super) fn grid_layout_input_data_ty() -> Type {
1303    Type::Struct(Rc::new(Struct {
1304        fields: IntoIterator::into_iter([
1305            (SmolStr::new_static("new_row"), Type::Bool),
1306            (SmolStr::new_static("row"), Type::Int32),
1307            (SmolStr::new_static("col"), Type::Int32),
1308            (SmolStr::new_static("rowspan"), Type::Int32),
1309            (SmolStr::new_static("colspan"), Type::Int32),
1310        ])
1311        .collect(),
1312        name: BuiltinStruct::GridLayoutInputData.into(),
1313    }))
1314}
1315
1316fn generate_layout_padding_and_spacing(
1317    layout_geometry: &crate::layout::LayoutGeometry,
1318    orientation: Orientation,
1319    ctx: &ExpressionLoweringCtx,
1320) -> (llr_Expression, llr_Expression) {
1321    let padding_prop = |expr| {
1322        if let Some(expr) = expr {
1323            llr_Expression::PropertyReference(ctx.map_property_reference(expr))
1324        } else {
1325            llr_Expression::NumberLiteral(0.)
1326        }
1327    };
1328    let spacing = padding_prop(layout_geometry.spacing.orientation(orientation));
1329    let (begin, end) = layout_geometry.padding.begin_end(orientation);
1330
1331    let padding = make_struct(
1332        BuiltinStruct::Padding,
1333        [("begin", Type::Float32, padding_prop(begin)), ("end", Type::Float32, padding_prop(end))],
1334    );
1335
1336    (padding, spacing)
1337}
1338
1339/// Whether `elem` is a height-for-width cell — its vertical layout info
1340/// depends on the horizontal dimension, so a cross-axis constraint must
1341/// be supplied to get a meaningful answer.
1342///
1343/// Two cases qualify:
1344/// - Builtin height-for-width items (Text with `wrap != no-wrap`, Image with
1345///   aspect-ratio sizing).
1346/// - Components whose subtree contains a height-for-width descendant — recognized
1347///   by the presence of `Element::layout_info_v_with_constraint`.
1348fn is_height_for_width_cell(elem: &ElementRc) -> bool {
1349    let elem_b = elem.borrow();
1350
1351    // Component path: `layoutinfo-v-with-constraint` may live on `elem`
1352    // itself or on the base component's root_element.
1353    let has_constrained_layoutinfo_v = elem_b.layout_info_v_with_constraint.is_some()
1354        || matches!(
1355            &elem_b.base_type,
1356            crate::langtype::ElementType::Component(base_comp)
1357                if base_comp.root_element.borrow().layout_info_v_with_constraint.is_some()
1358        );
1359    if has_constrained_layoutinfo_v {
1360        return true;
1361    }
1362
1363    if elem_b.layout_info_prop(Orientation::Vertical).is_some() {
1364        return false;
1365    }
1366    drop(elem_b);
1367
1368    // Builtin path.
1369    matches!(
1370        crate::layout::implicit_layout_info_call(
1371            elem,
1372            Orientation::Vertical,
1373            crate::layout::BuiltinFilter::All,
1374            None,
1375        ),
1376        Some(crate::expression_tree::Expression::FunctionCall { .. })
1377    )
1378}
1379
1380/// Default cross-axis (width) constraint for a height-for-width cell:
1381/// the element's own preferred horizontal size. Callers
1382/// (`flexbox_layout_data`, `box_layout_data`,
1383/// `grid_layout_cell_constraints`) may prefer the container's actual
1384/// width when it is available (i.e. at solve time, or when the caller
1385/// is the body of a `layoutinfo-v-with-constraint` function which
1386/// received the width as a parameter).
1387///
1388/// Precondition: `is_height_for_width_cell(elem)` is true. After the
1389/// `layoutinfo-v-with-constraint` synthesis pass, any element with
1390/// `layout_info_v_with_constraint` also has `layout_info_prop` set (the
1391/// constrained function is synthesized from the existing `layoutinfo-v`
1392/// binding), so the `layout_info_prop` branch covers it.
1393fn default_cross_axis_constraint(elem: &ElementRc) -> Option<crate::expression_tree::Expression> {
1394    let elem_b = elem.borrow();
1395
1396    // Route through `layoutinfo-h-with-constraint` when available so we
1397    // don't trigger a `self.height` read (which cycles for column-direction
1398    // flexes: their layoutinfo-h depends on self.height, itself set by the
1399    // parent layout cache). The NR returned by `inherited_*` already points
1400    // to the element declaring the function (which, after
1401    // `move_declarations` runs, is the enclosing component's root with a
1402    // renamed property), so use it as-is — re-anchoring it to `elem` would
1403    // break the lookup.
1404    if let Some(constrained_nr) = elem_b.inherited_layout_info_h_with_constraint() {
1405        let call = crate::expression_tree::Expression::FunctionCall {
1406            function: crate::expression_tree::Callable::Function(constrained_nr),
1407            arguments: vec![crate::expression_tree::Expression::NumberLiteral(
1408                f32::MAX as f64,
1409                crate::expression_tree::Unit::Px,
1410            )],
1411            source_location: None,
1412        };
1413        return Some(crate::expression_tree::Expression::StructFieldAccess {
1414            base: Box::new(call),
1415            name: "preferred".into(),
1416        });
1417    }
1418
1419    // Layouts and components with their own resolved layout_info_prop.
1420    if let Some((h_nr, _v_nr)) = elem_b.layout_info_prop.as_ref() {
1421        return Some(crate::expression_tree::Expression::StructFieldAccess {
1422            base: Box::new(crate::expression_tree::Expression::PropertyReference(h_nr.clone())),
1423            name: "preferred".into(),
1424        });
1425    }
1426    drop(elem_b);
1427
1428    // Builtins and component instances (looked up via the base component).
1429    crate::layout::implicit_layout_info_call(
1430        elem,
1431        Orientation::Horizontal,
1432        crate::layout::BuiltinFilter::All,
1433        None,
1434    )
1435    .map(|expr| crate::expression_tree::Expression::StructFieldAccess {
1436        base: Box::new(expr),
1437        name: "preferred".into(),
1438    })
1439}
1440
1441/// Build an expression for the layout's cross-axis *content* size
1442/// (`self.height` minus top/bottom padding, for a horizontal layout).
1443fn layout_cross_content_size(
1444    layout: &crate::layout::BoxLayout,
1445) -> Option<crate::expression_tree::Expression> {
1446    use crate::expression_tree::Expression;
1447    let cross = layout.orientation.orthogonal();
1448    let size_nr = layout.geometry.rect.size_reference(cross)?.clone();
1449    let mut expr = Expression::PropertyReference(size_nr);
1450    let pads = match cross {
1451        Orientation::Horizontal => [&layout.geometry.padding.left, &layout.geometry.padding.right],
1452        Orientation::Vertical => [&layout.geometry.padding.top, &layout.geometry.padding.bottom],
1453    };
1454    for p in pads.into_iter().flatten() {
1455        expr = Expression::BinaryExpression {
1456            lhs: Box::new(expr),
1457            rhs: Box::new(Expression::PropertyReference(p.clone())),
1458            op: '-',
1459        };
1460    }
1461    Some(expr)
1462}
1463
1464fn layout_geometry_size(
1465    rect: &crate::layout::LayoutRect,
1466    orientation: Orientation,
1467    ctx: &ExpressionLoweringCtx,
1468) -> llr_Expression {
1469    match rect.size_reference(orientation) {
1470        Some(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
1471        None => llr_Expression::NumberLiteral(0.),
1472    }
1473}
1474
1475pub fn get_layout_info(
1476    elem: &ElementRc,
1477    ctx: &mut ExpressionLoweringCtx,
1478    constraints: &crate::layout::LayoutConstraints,
1479    orientation: Orientation,
1480    constraint: Option<crate::expression_tree::Expression>,
1481) -> llr_Expression {
1482    // With a constraint and a parameterized layout-info function on the
1483    // child, call that function instead of reading the plain
1484    // `layoutinfo-{h,v}` property — breaks the recursion via the child's
1485    // perpendicular dimension.
1486    let layout_info = if let Some(c) = &constraint
1487        && let Some(parameterized_nr) = (match orientation {
1488            Orientation::Vertical => elem.borrow().layout_info_v_with_constraint.clone(),
1489            Orientation::Horizontal => elem.borrow().layout_info_h_with_constraint.clone(),
1490        }) {
1491        let call = crate::expression_tree::Expression::FunctionCall {
1492            function: crate::expression_tree::Callable::Function(parameterized_nr),
1493            arguments: vec![c.clone()],
1494            source_location: None,
1495        };
1496        super::lower_expression::lower_expression(&call, ctx)
1497    } else if let Some(layout_info_prop) = &elem.borrow().layout_info_prop(orientation) {
1498        llr_Expression::PropertyReference(ctx.map_property_reference(layout_info_prop))
1499    } else {
1500        super::lower_expression::lower_expression(
1501            &crate::layout::implicit_layout_info_call(
1502                elem,
1503                orientation,
1504                crate::layout::BuiltinFilter::All,
1505                constraint,
1506            )
1507            .unwrap(),
1508            ctx,
1509        )
1510    };
1511
1512    if constraints.has_explicit_restrictions(orientation) {
1513        let store = llr_Expression::StoreLocalVariable {
1514            name: "layout_info".into(),
1515            value: layout_info.into(),
1516        };
1517        let ty = crate::typeregister::layout_info_type();
1518        let mut values = ty
1519            .fields
1520            .keys()
1521            .map(|p| {
1522                (
1523                    p.clone(),
1524                    llr_Expression::StructFieldAccess {
1525                        base: llr_Expression::ReadLocalVariable {
1526                            name: "layout_info".into(),
1527                            ty: ty.clone().into(),
1528                        }
1529                        .into(),
1530                        name: p.clone(),
1531                    },
1532                )
1533            })
1534            .collect::<BTreeMap<_, _>>();
1535
1536        for (nr, s) in constraints.for_each_restrictions(orientation) {
1537            values.insert(
1538                s.into(),
1539                llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
1540            );
1541        }
1542        llr_Expression::CodeBlock([store, llr_Expression::Struct { ty, values }].into())
1543    } else {
1544        layout_info
1545    }
1546}
1547
1548// Called for repeated components in a grid layout, to generate code to provide input for organize_grid_layout().
1549pub fn get_grid_layout_input_for_repeated(
1550    ctx: &mut ExpressionLoweringCtx,
1551    grid_cell: &GridLayoutCell,
1552) -> llr_Expression {
1553    let mut assignments = Vec::new();
1554
1555    fn convert_row_col_expr(expr: &RowColExpr, ctx: &ExpressionLoweringCtx) -> llr_Expression {
1556        match expr {
1557            RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()),
1558            RowColExpr::Named(nr) => {
1559                llr_Expression::PropertyReference(ctx.map_property_reference(nr))
1560            }
1561            RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _),
1562        }
1563    }
1564
1565    // Generate assignments to the `result` slice parameter: result[i] = struct { ... }
1566    let mut push_assignment =
1567        |i: usize, new_row_expr: &llr_Expression, grid_cell: &GridLayoutCell| {
1568            let row = convert_row_col_expr(&grid_cell.row_expr, &*ctx);
1569            let col = convert_row_col_expr(&grid_cell.col_expr, &*ctx);
1570            let rowspan = convert_row_col_expr(&grid_cell.rowspan_expr, &*ctx);
1571            let colspan = convert_row_col_expr(&grid_cell.colspan_expr, &*ctx);
1572            let value = make_struct(
1573                BuiltinStruct::GridLayoutInputData,
1574                [
1575                    ("new_row", Type::Bool, new_row_expr.clone()),
1576                    ("row", Type::Float32, row),
1577                    ("col", Type::Float32, col),
1578                    ("rowspan", Type::Float32, rowspan),
1579                    ("colspan", Type::Float32, colspan),
1580                ],
1581            );
1582            assignments.push(llr_Expression::SliceIndexAssignment {
1583                slice_name: SmolStr::new_static("result"),
1584                index: i,
1585                value: value.into(),
1586            });
1587        };
1588
1589    if let Some(child_items) = grid_cell.child_items.as_ref() {
1590        // Repeated Row: only handle static children here;
1591        // inner repeater children are handled by the code generators at runtime
1592        let mut new_row_expr = llr_Expression::BoolLiteral(true);
1593        let mut i = 0;
1594        for child_item in child_items.iter() {
1595            match child_item {
1596                crate::layout::RowChildTemplate::Static(layout_item) => {
1597                    let child_element = layout_item.element.borrow();
1598                    let child_cell = child_element.grid_layout_cell.as_ref().unwrap().borrow();
1599                    push_assignment(i, &new_row_expr, &child_cell);
1600                    new_row_expr = llr_Expression::BoolLiteral(false);
1601                    i += 1;
1602                }
1603                crate::layout::RowChildTemplate::Repeated { .. } => {
1604                    // Inner repeater children are filled at runtime by the code generators
1605                }
1606            }
1607        }
1608    } else {
1609        // Single repeated item
1610        // grid_cell.new_row is the static information from the slint file.
1611        // In practice, for repeated items within a row, whether we should start a new row
1612        // is more dynamic (e.g. if the previous item was in "if false"),
1613        // and tracked by a local variable "new_row" in the generated code.
1614        let new_row_expr = llr_Expression::ReadLocalVariable {
1615            name: SmolStr::new_static("new_row"),
1616            ty: Type::Bool,
1617        };
1618        push_assignment(0, &new_row_expr, grid_cell);
1619    }
1620
1621    llr_Expression::CodeBlock(assignments)
1622}
1623
1624/// Returns the row child template list for a repeated Row element.
1625///
1626/// Reads it from the already-lowered Row sub-component (which must have been
1627/// lowered before the parent's expression lowering — see the ordering in
1628/// `lower_sub_component`).
1629///
1630/// Returns `None` if this is a column-repeater (not a Row sub-component).
1631/// Returns `Some(vec)` with one entry per child in declaration order.
1632fn get_row_child_templates(
1633    outer_element: &ElementRc,
1634    ctx: &ExpressionLoweringCtx,
1635) -> Option<Vec<super::RowChildTemplateInfo>> {
1636    let comp = outer_element.borrow().base_type.as_component().clone();
1637    ctx.state.row_child_templates(&comp)
1638}
1639
1640/// Generate an expression that builds a FlexboxLayoutItemInfo for a repeated element
1641/// in a FlexboxLayout, reading flex properties from the component instance.
1642pub fn get_flexbox_layout_item_info_for_repeated(
1643    ctx: &mut ExpressionLoweringCtx,
1644    element: &ElementRc,
1645) -> llr_Expression {
1646    let prop_ref = |name: &'static str| -> Option<llr_Expression> {
1647        crate::layout::binding_reference(element, name)
1648            .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(&nr)))
1649    };
1650
1651    let (align_self_ty, align_self_default) = default_align_self();
1652
1653    let grow = prop_ref("flex-grow").unwrap_or(llr_Expression::NumberLiteral(0.0));
1654    let shrink = prop_ref("flex-shrink").unwrap_or(llr_Expression::NumberLiteral(1.0));
1655    let basis = prop_ref("flex-basis").unwrap_or(llr_Expression::NumberLiteral(-1.0));
1656    let align_self = prop_ref("flex-align-self").unwrap_or(align_self_default);
1657    let order = prop_ref("flex-order").unwrap_or(llr_Expression::NumberLiteral(0.0));
1658
1659    make_struct(
1660        BuiltinStruct::FlexboxLayoutItemInfo,
1661        [
1662            (
1663                "constraint",
1664                crate::typeregister::layout_info_type().into(),
1665                llr_Expression::default_value_for_type(
1666                    &crate::typeregister::layout_info_type().into(),
1667                )
1668                .unwrap(),
1669            ),
1670            ("flex-grow", Type::Float32, grow),
1671            ("flex-shrink", Type::Float32, shrink),
1672            ("flex-basis", Type::Float32, basis),
1673            ("flex-align-self", align_self_ty, align_self),
1674            ("flex-order", Type::Int32, order),
1675        ],
1676    )
1677}