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::sync::Arc;
6
7use itertools::Either;
8use smol_str::SmolStr;
9
10use super::lower_to_item_tree::LoweredElement;
11use super::{GridLayoutRepeatedElement, LayoutRepeatedElement};
12use crate::expression_tree::MinMaxOp;
13use crate::langtype::{BuiltinStruct, EnumerationValue, Struct, Type};
14use crate::layout::{FlexboxAxisRelation, GridLayoutCell, Orientation, RowColExpr};
15use crate::llr::ArrayOutput as llr_ArrayOutput;
16use crate::llr::Expression as llr_Expression;
17use crate::llr::{BoxMeasureCell, FlexboxMeasureCell};
18use crate::namedreference::NamedReference;
19use crate::object_tree::ElementRc;
20
21use super::lower_expression::{ExpressionLoweringCtx, make_struct};
22
23fn empty_int32_slice() -> llr_Expression {
24    llr_Expression::Array {
25        element_ty: Type::Int32,
26        values: Vec::new(),
27        output: llr_ArrayOutput::Slice,
28    }
29}
30
31pub(super) fn compute_grid_layout_info(
32    layout_organized_data_prop: &NamedReference,
33    layout: &crate::layout::GridLayout,
34    o: Orientation,
35    ctx: &mut ExpressionLoweringCtx,
36    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
37) -> llr_Expression {
38    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
39    let organized_cells = ctx.map_property_reference(layout_organized_data_prop);
40    let constraints_result = grid_layout_cell_constraints(layout, o, ctx, cross_axis_size_override);
41    let orientation_literal = llr_Expression::EnumerationValue(EnumerationValue {
42        value: o as _,
43        enumeration: crate::typeregister::BUILTIN.enums.Orientation.clone(),
44    });
45
46    let sub_expression = llr_Expression::ExtraBuiltinFunctionCall {
47        function: "grid_layout_info".into(),
48        arguments: vec![
49            llr_Expression::PropertyReference(organized_cells),
50            constraints_result.cells,
51            if constraints_result.compute_cells.is_none() {
52                empty_int32_slice()
53            } else {
54                llr_Expression::ReadLocalVariable {
55                    name: "repeated_indices".into(),
56                    ty: Type::Array(Type::Int32.into()),
57                }
58            },
59            if constraints_result.compute_cells.is_none() {
60                empty_int32_slice()
61            } else {
62                llr_Expression::ReadLocalVariable {
63                    name: "repeater_steps".into(),
64                    ty: Type::Array(Type::Int32.into()),
65                }
66            },
67            spacing,
68            padding,
69            orientation_literal,
70        ],
71        return_ty: crate::typeregister::layout_info_type().into(),
72    };
73    match constraints_result.compute_cells {
74        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
75            cells_variable,
76            repeater_indices_var_name: Some("repeated_indices".into()),
77            repeater_steps_var_name: Some("repeater_steps".into()),
78            elements,
79            orientation: o,
80            repeated_cross_size: None,
81            sub_expression: Box::new(sub_expression),
82        },
83        None => sub_expression,
84    }
85}
86
87/// Whether a repeated cell of `layout` measures its vertical axis through a
88/// parametrized layout-info function (height-for-width). Only then does
89/// forwarding the width to the repeated cells change anything.
90fn box_layout_has_height_for_width_repeated_cell(layout: &crate::layout::BoxLayout) -> bool {
91    layout.elems.iter().any(|item| {
92        item.element.borrow().repeated.is_some() && cell_is_height_for_width(&item.element)
93    })
94}
95
96/// Name of the local that carries the known width a measure pass measures a
97/// cell at. The generated code binds it around each static measure cell's
98/// `LayoutInfo` expression; shared by the flexbox and box layout measure
99/// passes.
100pub const MEASURE_KNOWN_W_LOCAL: &str = "measure_known_w";
101
102pub(super) fn compute_box_layout_info(
103    layout: &crate::layout::BoxLayout,
104    o: Orientation,
105    ctx: &mut ExpressionLoweringCtx,
106    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
107) -> llr_Expression {
108    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
109    // A horizontal layout's vertical info at a known width: solve the main
110    // axis at that width and measure each height-for-width cell at its solved
111    // width — feeding every cell the whole width would overestimate what the
112    // layout actually gives it, and so underestimate the height the cell needs.
113    if o == Orientation::Vertical
114        && layout.orientation == Orientation::Horizontal
115        && let Some(override_expr) = cross_axis_size_override
116        && box_layout_needs_measure(layout)
117    {
118        return compute_box_layout_info_ortho_with_measure(layout, ctx, override_expr, padding);
119    }
120    let adjusted_override = cross_axis_size_override
121        .map(|o_expr| subtract_padding(o_expr.clone(), &layout.geometry, o.orthogonal()));
122    let bld = box_layout_data(layout, o, ctx, adjusted_override.as_ref(), None, false, false);
123    let sub_expression = if o == layout.orientation {
124        llr_Expression::ExtraBuiltinFunctionCall {
125            function: "box_layout_info".into(),
126            arguments: vec![bld.cells, spacing, padding, bld.alignment],
127            return_ty: crate::typeregister::layout_info_type().into(),
128        }
129    } else {
130        llr_Expression::ExtraBuiltinFunctionCall {
131            function: "box_layout_info_ortho".into(),
132            arguments: vec![bld.cells, padding],
133            return_ty: crate::typeregister::layout_info_type().into(),
134        }
135    };
136    // On a vertical layout's main pass with a known width (a
137    // `layoutinfo-v-with-constraint` body), measure repeated cells at that
138    // width too, like the static cells.
139    let repeated_cross_size = adjusted_override
140        .as_ref()
141        .filter(|_| {
142            o == Orientation::Vertical
143                && o == layout.orientation
144                && box_layout_has_height_for_width_repeated_cell(layout)
145        })
146        .map(|e| Box::new(super::lower_expression::lower_expression(e, ctx)));
147    match bld.compute_cells {
148        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
149            cells_variable,
150            repeater_indices_var_name: None,
151            repeater_steps_var_name: None,
152            elements,
153            orientation: o,
154            repeated_cross_size,
155            sub_expression: Box::new(sub_expression),
156        },
157        None => sub_expression,
158    }
159}
160
161/// Whether any cell of the box layout is height-for-width, so its vertical
162/// info at a known width needs the solve-and-measure pass.
163fn box_layout_needs_measure(layout: &crate::layout::BoxLayout) -> bool {
164    layout.elems.iter().any(|li| cell_is_height_for_width(&li.element))
165}
166
167/// Per-element measure inputs for [`llr_Expression::BoxLayoutInfoOrthoWithMeasure`]:
168/// the cell's vertical `LayoutInfo` measured at its solved width, read from
169/// the [`MEASURE_KNOWN_W_LOCAL`] local (the same local the flexbox measure
170/// cells use). A repeated element becomes a [`BoxMeasureCell::Repeated`]: its
171/// instances are only known at solve time, so the generated code queries each
172/// instance's `layout_item_info_at_cross_width` directly.
173fn box_measure_cells_for(
174    layout: &crate::layout::BoxLayout,
175    ctx: &mut ExpressionLoweringCtx,
176) -> Vec<BoxMeasureCell> {
177    layout
178        .elems
179        .iter()
180        .map(|li| {
181            let elem = &li.element;
182            if elem.borrow().repeated.is_some() {
183                let repeater_index =
184                    match ctx.mapping.element_mapping.get(&elem.clone().into()).unwrap() {
185                        LoweredElement::Repeated { repeated_index } => *repeated_index,
186                        _ => panic!("repeated box layout element not lowered as Repeated"),
187                    };
188                return BoxMeasureCell::Repeated(LayoutRepeatedElement {
189                    repeater_index,
190                    row_child_templates: None,
191                    cross_width: None,
192                });
193            }
194            let measure_local = crate::expression_tree::Expression::ReadLocalVariable {
195                name: MEASURE_KNOWN_W_LOCAL.into(),
196                ty: Type::LogicalLength,
197            };
198            let info = cell_layout_info(
199                elem,
200                &li.constraints,
201                ctx,
202                Orientation::Vertical,
203                Some(&measure_local),
204                None,
205                false,
206            );
207            BoxMeasureCell::Static { info }
208        })
209        .collect()
210}
211
212/// Build the [`llr_Expression::BoxLayoutInfoOrthoWithMeasure`] for a
213/// horizontal layout's vertical info at the known width `cross_axis_size`.
214fn compute_box_layout_info_ortho_with_measure(
215    layout: &crate::layout::BoxLayout,
216    ctx: &mut ExpressionLoweringCtx,
217    cross_axis_size: &crate::expression_tree::Expression,
218    padding_ortho: llr_Expression,
219) -> llr_Expression {
220    let main_o = layout.orientation;
221    let (padding_main, spacing_main) =
222        generate_layout_padding_and_spacing(&layout.geometry, main_o, ctx);
223    let bld = box_layout_data(layout, main_o, ctx, None, None, false, true);
224    let size = super::lower_expression::lower_expression(cross_axis_size, ctx);
225    let solve_data = make_struct(
226        BuiltinStruct::BoxLayoutData,
227        [
228            ("size", Type::Float32, size),
229            ("spacing", Type::Float32, spacing_main),
230            ("padding", padding_main.ty(ctx), padding_main),
231            (
232                "alignment",
233                Type::Enumeration(crate::typeregister::BUILTIN.enums.LayoutAlignment.clone()),
234                bld.alignment,
235            ),
236            ("cells", bld.cells.ty(ctx), bld.cells),
237        ],
238    );
239    let sub_expression = llr_Expression::BoxLayoutInfoOrthoWithMeasure {
240        solve_data: Box::new(solve_data),
241        padding_ortho: Box::new(padding_ortho),
242        measure_cells: box_measure_cells_for(layout, ctx),
243    };
244    match bld.compute_cells {
245        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
246            cells_variable,
247            repeater_indices_var_name: None,
248            repeater_steps_var_name: None,
249            elements,
250            orientation: main_o,
251            repeated_cross_size: None,
252            sub_expression: Box::new(sub_expression),
253        },
254        None => sub_expression,
255    }
256}
257
258pub(super) fn organize_grid_layout(
259    layout: &crate::layout::GridLayout,
260    ctx: &mut ExpressionLoweringCtx,
261) -> llr_Expression {
262    let input_data = grid_layout_input_data(layout, ctx);
263
264    if let Some(button_roles) = &layout.dialog_button_roles {
265        let e = crate::typeregister::BUILTIN.enums.DialogButtonRole.clone();
266        let roles = button_roles
267            .iter()
268            .map(|r| {
269                llr_Expression::EnumerationValue(EnumerationValue {
270                    value: e.values.iter().position(|x| x == r).unwrap() as _,
271                    enumeration: e.clone(),
272                })
273            })
274            .collect();
275        let roles_expr = llr_Expression::Array {
276            element_ty: Type::Enumeration(e),
277            values: roles,
278            output: llr_ArrayOutput::Slice,
279        };
280        llr_Expression::ExtraBuiltinFunctionCall {
281            function: "organize_dialog_button_layout".into(),
282            arguments: vec![input_data.cells, roles_expr],
283            return_ty: Type::Array(Type::Int32.into()),
284        }
285    } else {
286        let sub_expression = llr_Expression::ExtraBuiltinFunctionCall {
287            function: "organize_grid_layout".into(),
288            arguments: vec![
289                input_data.cells,
290                if input_data.compute_cells.is_none() {
291                    empty_int32_slice()
292                } else {
293                    llr_Expression::ReadLocalVariable {
294                        name: SmolStr::new_static("repeated_indices"),
295                        ty: Type::Array(Type::Int32.into()),
296                    }
297                },
298                if input_data.compute_cells.is_none() {
299                    empty_int32_slice()
300                } else {
301                    llr_Expression::ReadLocalVariable {
302                        name: SmolStr::new_static("repeater_steps"),
303                        ty: Type::Array(Type::Int32.into()),
304                    }
305                },
306            ],
307            return_ty: Type::Array(Type::Int32.into()),
308        };
309        if let Some((cells_variable, elements)) = input_data.compute_cells {
310            llr_Expression::WithGridInputData {
311                cells_variable,
312                repeater_indices_var_name: SmolStr::new_static("repeated_indices"),
313                repeater_steps_var_name: SmolStr::new_static("repeater_steps"),
314                elements,
315                sub_expression: Box::new(sub_expression),
316            }
317        } else {
318            sub_expression
319        }
320    }
321}
322
323pub(super) fn solve_grid_layout(
324    layout_organized_data_prop: &NamedReference,
325    layout: &crate::layout::GridLayout,
326    o: Orientation,
327    ctx: &mut ExpressionLoweringCtx,
328) -> llr_Expression {
329    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
330    let cells = ctx.map_property_reference(layout_organized_data_prop);
331    let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
332    let orientation_expr = llr_Expression::EnumerationValue(EnumerationValue {
333        value: o as _,
334        enumeration: crate::typeregister::BUILTIN.enums.Orientation.clone(),
335    });
336    let data = make_struct(
337        BuiltinStruct::GridLayoutData,
338        [
339            ("size", Type::Float32, size),
340            ("spacing", Type::Float32, spacing),
341            ("padding", padding.ty(ctx), padding),
342            ("organized_data", Type::ArrayOfU16, llr_Expression::PropertyReference(cells)),
343        ],
344    );
345    let constraints_result = grid_layout_cell_constraints(layout, o, ctx, None);
346
347    match constraints_result.compute_cells {
348        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
349            cells_variable: cells_variable.clone(),
350            repeater_indices_var_name: Some("repeated_indices".into()),
351            repeater_steps_var_name: Some("repeater_steps".into()),
352            elements,
353            orientation: o,
354            repeated_cross_size: None,
355            sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
356                function: "solve_grid_layout".into(),
357                arguments: vec![
358                    data,
359                    llr_Expression::ReadLocalVariable {
360                        name: cells_variable.into(),
361                        ty: constraints_result.cells.ty(ctx),
362                    },
363                    orientation_expr,
364                    llr_Expression::ReadLocalVariable {
365                        name: "repeated_indices".into(),
366                        ty: Type::Array(Type::Int32.into()),
367                    },
368                    llr_Expression::ReadLocalVariable {
369                        name: "repeater_steps".into(),
370                        ty: Type::Array(Type::Int32.into()),
371                    },
372                ],
373                return_ty: Type::LayoutCache,
374            }),
375        },
376        None => llr_Expression::ExtraBuiltinFunctionCall {
377            function: "solve_grid_layout".into(),
378            arguments: vec![
379                data,
380                constraints_result.cells,
381                orientation_expr,
382                empty_int32_slice(),
383                empty_int32_slice(),
384            ],
385            return_ty: Type::LayoutCache,
386        },
387    }
388}
389
390pub(super) fn solve_box_layout(
391    layout: &crate::layout::BoxLayout,
392    o: Orientation,
393    ctx: &mut ExpressionLoweringCtx,
394) -> llr_Expression {
395    let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
396    // The main pass gives static cells no cross size: embedding `self.width`
397    // into a vertical layout's cache would let a geometry pull inside a
398    // horizontal info chain (a cell with `width: self.height`) close a binding
399    // loop through an ancestor's cache. Height-for-width children read their
400    // own laid-out width instead (see `text_layout_info` in i-slint-core).
401    // On the cross pass, the layout's content size along `o` is its
402    // cross content size; forward it so a wrapping perpendicular flex cell
403    // gets its natural single-line size instead of the compact sqrt preferred.
404    let cross_clamp =
405        (o != layout.orientation).then(|| layout_cross_content_size(layout)).flatten();
406    let bld = box_layout_data(layout, o, ctx, None, cross_clamp.as_ref(), true, false);
407    // On a vertical layout's main pass, measure repeated height-for-width cells
408    // at the layout's content width, like the flexbox solve does: their plain
409    // layout-info is measured at their preferred width, which is not the width
410    // the layout gives them.
411    let repeated_cross_size = (o == layout.orientation && o == Orientation::Vertical)
412        .then(|| layout_cross_content_size(layout))
413        .flatten()
414        .filter(|_| box_layout_has_height_for_width_repeated_cell(layout))
415        .map(|e| Box::new(super::lower_expression::lower_expression(&e, ctx)));
416    let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
417    let (data, function) = if o == layout.orientation {
418        let data = make_struct(
419            BuiltinStruct::BoxLayoutData,
420            [
421                ("size", Type::Float32, size),
422                ("spacing", Type::Float32, spacing),
423                ("padding", padding.ty(ctx), padding),
424                (
425                    "alignment",
426                    Type::Enumeration(crate::typeregister::BUILTIN.enums.LayoutAlignment.clone()),
427                    bld.alignment,
428                ),
429                ("cells", bld.cells.ty(ctx), bld.cells),
430            ],
431        );
432        (data, "solve_box_layout")
433    } else {
434        let cross_axis_alignment_ty =
435            Type::Enumeration(crate::typeregister::BUILTIN.enums.CrossAxisAlignment.clone());
436        let cross_axis_alignment = if let Some(nr) = &layout.cross_alignment {
437            llr_Expression::PropertyReference(ctx.map_property_reference(nr))
438        } else {
439            let e = crate::typeregister::BUILTIN.enums.CrossAxisAlignment.clone();
440            llr_Expression::EnumerationValue(EnumerationValue {
441                value: e.default_value,
442                enumeration: e,
443            })
444        };
445        let data = make_struct(
446            BuiltinStruct::BoxLayoutOrthoData,
447            [
448                ("size", Type::Float32, size),
449                ("padding", padding.ty(ctx), padding),
450                ("cross_axis_alignment", cross_axis_alignment_ty, cross_axis_alignment),
451                ("cells", bld.cells.ty(ctx), bld.cells),
452            ],
453        );
454        (data, "solve_box_layout_ortho")
455    };
456    match bld.compute_cells {
457        Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo {
458            cells_variable,
459            repeater_indices_var_name: Some("repeated_indices".into()),
460            repeater_steps_var_name: None,
461            elements,
462            orientation: o,
463            repeated_cross_size,
464            sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
465                function: function.into(),
466                arguments: vec![
467                    data,
468                    llr_Expression::ReadLocalVariable {
469                        name: "repeated_indices".into(),
470                        ty: Type::Array(Type::Int32.into()),
471                    },
472                ],
473                return_ty: Type::LayoutCache,
474            }),
475        },
476        None => llr_Expression::ExtraBuiltinFunctionCall {
477            function: function.into(),
478            arguments: vec![data, empty_int32_slice()],
479            return_ty: Type::LayoutCache,
480        },
481    }
482}
483
484pub(super) fn solve_flexbox_layout(
485    layout: &crate::layout::FlexboxLayout,
486    ctx: &mut ExpressionLoweringCtx,
487) -> llr_Expression {
488    let (padding_h, spacing_h) =
489        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Horizontal, ctx);
490    let (padding_v, spacing_v) =
491        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Vertical, ctx);
492    // At solve time, the container width is known (set by our parent).
493    // For column-direction flex (vertical main axis), each cell is
494    // at most as wide as the container (per-column when wrapped), an upper
495    // bound to supply as the cross-axis constraint to height-for-width children.
496    let container_width_for_cells = if matches!(
497        layout.axis_relation(Orientation::Vertical),
498        crate::layout::FlexboxAxisRelation::MainAxis
499    ) {
500        layout.geometry.rect.width_reference.as_ref().map(|nr| {
501            subtract_padding(
502                crate::expression_tree::Expression::PropertyReference(nr.clone()),
503                &layout.geometry,
504                Orientation::Horizontal,
505            )
506        })
507    } else {
508        None
509    };
510    let fld = flexbox_layout_data(layout, ctx, container_width_for_cells.as_ref());
511    let width = layout_geometry_size(&layout.geometry.rect, Orientation::Horizontal, ctx);
512    let height = layout_geometry_size(&layout.geometry.rect, Orientation::Vertical, ctx);
513    let data = make_struct(
514        BuiltinStruct::FlexboxLayoutData,
515        [
516            ("width", Type::Float32, width),
517            ("height", Type::Float32, height),
518            ("spacing_h", Type::Float32, spacing_h),
519            ("spacing_v", Type::Float32, spacing_v),
520            ("padding_h", padding_h.ty(ctx), padding_h),
521            ("padding_v", padding_v.ty(ctx), padding_v),
522            (
523                "alignment",
524                Type::Enumeration(crate::typeregister::BUILTIN.enums.LayoutAlignment.clone()),
525                fld.alignment,
526            ),
527            (
528                "direction",
529                Type::Enumeration(
530                    crate::typeregister::BUILTIN.enums.FlexboxLayoutDirection.clone(),
531                ),
532                fld.direction,
533            ),
534            (
535                "cross_axis_line_alignment",
536                Type::Enumeration(crate::typeregister::BUILTIN.enums.LayoutAlignment.clone()),
537                fld.cross_axis_line_alignment,
538            ),
539            (
540                "cross_axis_alignment",
541                Type::Enumeration(crate::typeregister::BUILTIN.enums.CrossAxisAlignment.clone()),
542                fld.cross_axis_alignment,
543            ),
544            (
545                "flex_wrap",
546                Type::Enumeration(crate::typeregister::BUILTIN.enums.FlexboxLayoutWrap.clone()),
547                fld.flex_wrap,
548            ),
549            ("cells_h", fld.cells_h.ty(ctx), fld.cells_h),
550            ("cells_v", fld.cells_v.ty(ctx), fld.cells_v),
551            ("flex_props", fld.flex_props.ty(ctx), fld.flex_props),
552        ],
553    );
554    // Forward the container width to repeated cells so a column flex re-measures
555    // each height-for-width instance at the real width (parity with static cells,
556    // which use the same `width_override`). `None` for a row flex.
557    let repeated_cross_width = container_width_for_cells
558        .as_ref()
559        .map(|e| Box::new(super::lower_expression::lower_expression(e, ctx)));
560    // Only height-for-width-capable cells benefit from re-measuring;
561    // a flexbox without any keeps the cheaper plain solve.
562    let needs_measure = flexbox_needs_measure(layout);
563    match fld.compute_cells {
564        Some((cells_h_var, cells_v_var, flex_var, elements)) => {
565            let repeated_indices = || llr_Expression::ReadLocalVariable {
566                name: "repeated_indices".into(),
567                ty: Type::Array(Type::Int32.into()),
568            };
569            let sub_expression = if needs_measure {
570                llr_Expression::SolveFlexboxLayoutWithMeasure {
571                    data: Box::new(data),
572                    repeater_indices: Box::new(repeated_indices()),
573                    measure_cells: measure_cells_for(layout, ctx),
574                }
575            } else {
576                llr_Expression::ExtraBuiltinFunctionCall {
577                    function: "solve_flexbox_layout".into(),
578                    arguments: vec![data, repeated_indices()],
579                    return_ty: Type::LayoutCache,
580                }
581            };
582            llr_Expression::WithFlexboxLayoutItemInfo {
583                cells_h_variable: cells_h_var,
584                cells_v_variable: cells_v_var,
585                flex_props_variable: Some(flex_var),
586                repeater_indices_var_name: Some("repeated_indices".into()),
587                elements,
588                repeated_cross_width,
589                sub_expression: Box::new(sub_expression),
590            }
591        }
592        None => {
593            if !needs_measure {
594                return llr_Expression::ExtraBuiltinFunctionCall {
595                    function: "solve_flexbox_layout".into(),
596                    arguments: vec![data, empty_int32_slice()],
597                    return_ty: Type::LayoutCache,
598                };
599            }
600            llr_Expression::SolveFlexboxLayoutWithMeasure {
601                data: Box::new(data),
602                repeater_indices: Box::new(empty_int32_slice()),
603                measure_cells: measure_cells_for(layout, ctx),
604            }
605        }
606    }
607}
608
609/// Whether the cell's vertical info depends on its width (height-for-width).
610/// For a repeater, check the repeated component's root: that is the element
611/// the measure callback queries.
612fn cell_is_height_for_width(elem: &ElementRc) -> bool {
613    if elem.borrow().repeated.is_some() {
614        let root = elem.borrow().base_type.as_component().root_element.clone();
615        return is_height_for_width_cell(&root);
616    }
617    is_height_for_width_cell(elem)
618}
619
620/// Whether any cell of the flexbox is height-for-width, so a solve or
621/// cross-axis info computation needs a measure callback.
622fn flexbox_needs_measure(layout: &crate::layout::FlexboxLayout) -> bool {
623    layout.elems.iter().any(|li| cell_is_height_for_width(&li.element))
624}
625
626/// Per-element measure inputs for `SolveFlexboxLayoutWithMeasure` and
627/// `FlexboxLayoutInfoCrossAxisWithMeasure`: the cell's vertical info measured
628/// at the width taffy assigns, read from the `measure_known_w` local. A
629/// repeated element becomes a [`FlexboxMeasureCell::Repeated`]: its instances
630/// are only known at solve time, so the generated callback queries the
631/// instance directly. A static element that is not height-for-width becomes a
632/// [`FlexboxMeasureCell::Fixed`] and gets no measure arm.
633fn measure_cells_for(
634    layout: &crate::layout::FlexboxLayout,
635    ctx: &mut ExpressionLoweringCtx,
636) -> Vec<FlexboxMeasureCell> {
637    layout
638        .elems
639        .iter()
640        .map(|li| {
641            let elem = &li.element;
642            if elem.borrow().repeated.is_some() {
643                let repeater_index =
644                    match ctx.mapping.element_mapping.get(&elem.clone().into()).unwrap() {
645                        LoweredElement::Repeated { repeated_index } => *repeated_index,
646                        _ => panic!("repeated flexbox element not lowered as Repeated"),
647                    };
648                return FlexboxMeasureCell::Repeated(LayoutRepeatedElement {
649                    repeater_index,
650                    row_child_templates: None,
651                    cross_width: None,
652                });
653            }
654            if !cell_is_height_for_width(elem) {
655                return FlexboxMeasureCell::Fixed;
656            }
657            let v_constraint = crate::expression_tree::Expression::ReadLocalVariable {
658                name: MEASURE_KNOWN_W_LOCAL.into(),
659                ty: Type::LogicalLength,
660            };
661            let v_info = get_flex_cell_layout_info(
662                elem,
663                ctx,
664                &li.constraints,
665                Orientation::Vertical,
666                Some(v_constraint),
667            );
668            FlexboxMeasureCell::Static { v_info }
669        })
670        .collect()
671}
672
673pub(super) fn compute_flexbox_layout_info(
674    layout: &crate::layout::FlexboxLayout,
675    orientation: Orientation,
676    ctx: &mut ExpressionLoweringCtx,
677    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
678) -> llr_Expression {
679    // A vertical override is a width, from a `layoutinfo-v-with-constraint`
680    // body: subtract padding so height-for-width cells are measured at the
681    // content width they are actually laid out at, not the padded outer width.
682    // A horizontal override is the flex's own height, from
683    // `layoutinfo-h-at-own-height`, and only sets the cross-axis constraint.
684    let width_override = cross_axis_size_override
685        .filter(|_| orientation == Orientation::Vertical)
686        .map(|e| subtract_padding(e.clone(), &layout.geometry, Orientation::Horizontal));
687    let fld = flexbox_layout_data(layout, ctx, width_override.as_ref());
688
689    match layout.axis_relation(orientation) {
690        crate::layout::FlexboxAxisRelation::MainAxis => {
691            compute_flexbox_layout_info_for_direction(layout, orientation, false, fld, ctx, None)
692        }
693        crate::layout::FlexboxAxisRelation::CrossAxis => compute_flexbox_layout_info_for_direction(
694            layout,
695            orientation,
696            true,
697            fld,
698            ctx,
699            cross_axis_size_override,
700        ),
701        crate::layout::FlexboxAxisRelation::Unknown => {
702            // Direction is not known at compile time - generate runtime conditional
703            // This ensures we only read the constraint (width/height) in the branch where it's needed
704            let row_expr = compute_flexbox_layout_info_for_direction(
705                layout,
706                orientation,
707                orientation == Orientation::Vertical, // cross-axis if orientation is vertical
708                fld.clone(),
709                ctx,
710                cross_axis_size_override,
711            );
712            let col_expr = compute_flexbox_layout_info_for_direction(
713                layout,
714                orientation,
715                orientation == Orientation::Horizontal, // cross-axis if orientation is horizontal
716                fld,
717                ctx,
718                cross_axis_size_override,
719            );
720
721            // Condition: direction == Row || direction == RowReverse
722            let direction_enum = crate::typeregister::BUILTIN.enums.FlexboxLayoutDirection.clone();
723            let direction_ref = llr_Expression::PropertyReference(
724                ctx.map_property_reference(layout.direction.as_ref().unwrap()),
725            );
726
727            let is_row_condition = llr_Expression::BinaryExpression {
728                lhs: Box::new(llr_Expression::BinaryExpression {
729                    lhs: Box::new(direction_ref.clone()),
730                    rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
731                        value: 0, // FlexboxLayoutDirection::Row
732                        enumeration: direction_enum.clone(),
733                    })),
734                    op: '=',
735                }),
736                rhs: Box::new(llr_Expression::BinaryExpression {
737                    lhs: Box::new(direction_ref),
738                    rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
739                        value: 1, // FlexboxLayoutDirection::RowReverse
740                        enumeration: direction_enum,
741                    })),
742                    op: '=',
743                }),
744                op: '|',
745            };
746
747            llr_Expression::Condition {
748                condition: Box::new(is_row_condition),
749                true_expr: Box::new(row_expr),
750                false_expr: Box::new(col_expr),
751            }
752        }
753    }
754}
755
756fn compute_flexbox_layout_info_for_direction(
757    layout: &crate::layout::FlexboxLayout,
758    orientation: Orientation,
759    is_cross_axis: bool,
760    fld: FlexboxLayoutDataResult,
761    ctx: &mut ExpressionLoweringCtx,
762    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
763) -> llr_Expression {
764    let (padding_h, spacing_h) =
765        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Horizontal, ctx);
766    let (padding_v, spacing_v) =
767        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Vertical, ctx);
768
769    if is_cross_axis {
770        // Cross-axis layout info: pass the main-axis container dimension
771        // as constraint for accurate wrapping. The override (when set)
772        // replaces a `self.width` read that would otherwise cycle if this
773        // flex is nested on the perpendicular axis. The plain horizontal info
774        // of a column flex never reads the height: the height is what the
775        // parent computes from this info, so the flex is measured unbounded,
776        // as one column, like a CSS column flex container with an auto
777        // height. `layoutinfo-h-at-own-height` passes the height instead.
778        let constraint_size = if let Some(override_expr) = cross_axis_size_override {
779            super::lower_expression::lower_expression(override_expr, ctx)
780        } else {
781            match orientation {
782                Orientation::Horizontal => llr_Expression::NumberLiteral(f32::MAX as f64),
783                Orientation::Vertical => {
784                    layout_geometry_size(&layout.geometry.rect, Orientation::Horizontal, ctx)
785                }
786            }
787        };
788
789        let arguments = vec![
790            fld.cells_h,
791            fld.cells_v,
792            fld.flex_props,
793            spacing_h,
794            spacing_v,
795            padding_h,
796            padding_v,
797            fld.direction,
798            // Under `alignment: stretch` the solve grows the cells along the
799            // main axis, changing a height-for-width cell's cross size, so
800            // this measurement must apply the same growth.
801            fld.alignment,
802            fld.flex_wrap,
803            constraint_size,
804        ];
805
806        // Re-measure height-for-width cells at the main-axis size taffy
807        // assigns them, not at the container size the cells in `arguments`
808        // were pre-measured at: e.g. a nested wrapping flexbox laid out at
809        // its preferred width can be taller than when given the full
810        // container width.
811        let sub_expression = if flexbox_needs_measure(layout) {
812            llr_Expression::FlexboxLayoutInfoCrossAxisWithMeasure {
813                arguments,
814                measure_cells: measure_cells_for(layout, ctx),
815            }
816        } else {
817            llr_Expression::ExtraBuiltinFunctionCall {
818                function: "flexbox_layout_info_cross_axis".into(),
819                arguments,
820                return_ty: crate::typeregister::layout_info_type().into(),
821            }
822        };
823        match fld.compute_cells {
824            Some((cells_h_var, cells_v_var, flex_var, elements)) => {
825                llr_Expression::WithFlexboxLayoutItemInfo {
826                    cells_h_variable: cells_h_var,
827                    cells_v_variable: cells_v_var,
828                    flex_props_variable: Some(flex_var),
829                    repeater_indices_var_name: None,
830                    elements,
831                    // Info computation, not a solve: no container width to forward.
832                    repeated_cross_width: None,
833                    sub_expression: Box::new(sub_expression),
834                }
835            }
836            None => sub_expression,
837        }
838    } else {
839        // Main axis: only needs same-axis cells, avoiding cross-axis binding loop.
840        let (cells, spacing, padding) = match orientation {
841            Orientation::Horizontal => (fld.cells_h, spacing_h, padding_h),
842            Orientation::Vertical => (fld.cells_v, spacing_v, padding_v),
843        };
844
845        match fld.compute_cells {
846            Some((cells_h_var, cells_v_var, _flex_var, elements)) => {
847                let cells_var = match orientation {
848                    Orientation::Horizontal => cells_h_var.clone(),
849                    Orientation::Vertical => cells_v_var.clone(),
850                };
851                llr_Expression::WithFlexboxLayoutItemInfo {
852                    cells_h_variable: cells_h_var,
853                    cells_v_variable: cells_v_var,
854                    flex_props_variable: None,
855                    repeater_indices_var_name: None,
856                    elements,
857                    // Info computation, not a solve: no container width to forward.
858                    repeated_cross_width: None,
859                    sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
860                        function: "flexbox_layout_info_main_axis".into(),
861                        arguments: vec![
862                            llr_Expression::ReadLocalVariable {
863                                name: cells_var.into(),
864                                ty: Type::Array(Arc::new(
865                                    crate::typeregister::layout_item_info_type(),
866                                )),
867                            },
868                            spacing,
869                            padding,
870                            fld.flex_wrap,
871                        ],
872                        return_ty: crate::typeregister::layout_info_type().into(),
873                    }),
874                }
875            }
876            None => llr_Expression::ExtraBuiltinFunctionCall {
877                function: "flexbox_layout_info_main_axis".into(),
878                arguments: vec![cells, spacing, padding, fld.flex_wrap],
879                return_ty: crate::typeregister::layout_info_type().into(),
880            },
881        }
882    }
883}
884
885#[derive(Clone)]
886struct FlexboxLayoutDataResult {
887    alignment: llr_Expression,
888    direction: llr_Expression,
889    cross_axis_line_alignment: llr_Expression,
890    cross_axis_alignment: llr_Expression,
891    flex_wrap: llr_Expression,
892    cells_h: llr_Expression,
893    cells_v: llr_Expression,
894    /// Per-item flex properties, parallel to `cells_h`/`cells_v` (or read from
895    /// the `flex_props` variable built by `WithFlexboxLayoutItemInfo`).
896    flex_props: llr_Expression,
897    /// When there are repeaters involved, we need to do a WithFlexboxLayoutItemInfo with the
898    /// given cells_h/cells_v/flex_props variable names and elements (each static element
899    /// has a tuple of (h constraint, v constraint, flex props))
900    compute_cells: Option<(
901        String,
902        String,
903        String,
904        Vec<Either<(llr_Expression, llr_Expression, llr_Expression), LayoutRepeatedElement>>,
905    )>,
906}
907
908fn flexbox_layout_data(
909    layout: &crate::layout::FlexboxLayout,
910    ctx: &mut ExpressionLoweringCtx,
911    width_override: Option<&crate::expression_tree::Expression>,
912) -> FlexboxLayoutDataResult {
913    let alignment = if let Some(expr) = &layout.geometry.alignment {
914        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
915    } else {
916        let e = crate::typeregister::BUILTIN.enums.LayoutAlignment.clone();
917        llr_Expression::EnumerationValue(EnumerationValue {
918            value: e.default_value,
919            enumeration: e,
920        })
921    };
922
923    let direction = if let Some(expr) = &layout.direction {
924        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
925    } else {
926        let e = crate::typeregister::BUILTIN.enums.FlexboxLayoutDirection.clone();
927        llr_Expression::EnumerationValue(EnumerationValue {
928            value: e.default_value,
929            enumeration: e,
930        })
931    };
932
933    let cross_axis_line_alignment = if let Some(expr) = &layout.cross_axis_line_alignment {
934        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
935    } else {
936        let e = crate::typeregister::BUILTIN.enums.LayoutAlignment.clone();
937        llr_Expression::EnumerationValue(EnumerationValue {
938            value: e.default_value,
939            enumeration: e,
940        })
941    };
942
943    let cross_axis_alignment = if let Some(expr) = &layout.cross_axis_alignment {
944        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
945    } else {
946        let e = crate::typeregister::BUILTIN.enums.CrossAxisAlignment.clone();
947        llr_Expression::EnumerationValue(EnumerationValue {
948            value: e.default_value,
949            enumeration: e,
950        })
951    };
952
953    let flex_wrap = if let Some(expr) = &layout.flex_wrap {
954        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
955    } else {
956        let e = crate::typeregister::BUILTIN.enums.FlexboxLayoutWrap.clone();
957        llr_Expression::EnumerationValue(EnumerationValue {
958            value: e.default_value,
959            enumeration: e,
960        })
961    };
962
963    let repeater_count =
964        layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
965
966    let cell_ty = crate::typeregister::layout_item_info_type();
967    let flex_props_ty = crate::typeregister::flex_item_props_type();
968
969    let flex_prop =
970        |li: &crate::layout::LayoutItem, ctx: &mut ExpressionLoweringCtx| -> FlexItemProps {
971            FlexItemProps {
972                align_self: li
973                    .cross_axis_self_alignment
974                    .as_ref()
975                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
976                    .unwrap_or(default_align_self().1),
977                order: li
978                    .layout_order
979                    .as_ref()
980                    .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
981                    .unwrap_or(llr_Expression::NumberLiteral(0.0)),
982            }
983        };
984
985    // Width constraint for a cell's cells_v entry. Use the explicit
986    // width-override when one is in scope (solve-time container width,
987    // or width parameter of a synthesized `layoutinfo-v-with-constraint`
988    // body); otherwise fall back to the element's own preferred
989    // horizontal size. Cells that are not height-for-width get `None`.
990    let cell_v_constraint = |elem: &ElementRc| -> Option<crate::expression_tree::Expression> {
991        // A component that forwards a height-for-width layout (e.g. `min-height:
992        // inner.min-height` over a wrapped Text) has a `layoutinfo-v-with-constraint`
993        // but is not a builtin height-for-width cell. It must dispatch via that
994        // function instead of reading its own width — which would cycle through the
995        // flex solve.
996        if elem.borrow().inherited_layout_info_v_with_constraint().is_some() {
997            return Some(width_override.cloned().unwrap_or_else(|| {
998                crate::expression_tree::Expression::NumberLiteral(
999                    f32::MAX as f64,
1000                    crate::expression_tree::Unit::Px,
1001                )
1002            }));
1003        }
1004        if !is_height_for_width_cell(elem) {
1005            return None;
1006        }
1007        width_override.cloned().or_else(|| default_cross_axis_constraint(elem))
1008    };
1009    if repeater_count == 0 {
1010        let cells_h = llr_Expression::Array {
1011            values: layout
1012                .elems
1013                .iter()
1014                .map(|li| {
1015                    let layout_info_h = get_flex_cell_layout_info(
1016                        &li.element,
1017                        ctx,
1018                        &li.constraints,
1019                        Orientation::Horizontal,
1020                        None,
1021                    );
1022                    make_layout_cell_data_struct(layout_info_h, None, None)
1023                })
1024                .collect(),
1025            element_ty: cell_ty.clone(),
1026            output: llr_ArrayOutput::Slice,
1027        };
1028        // For cells_v, pass a width constraint for items that need
1029        // height-for-width (Text with word-wrap, Image with aspect ratio,
1030        // and components with a synthesized
1031        // `layoutinfo-v-with-constraint`).
1032        let cells_v = llr_Expression::Array {
1033            values: layout
1034                .elems
1035                .iter()
1036                .map(|li| {
1037                    let constraint = cell_v_constraint(&li.element);
1038                    let layout_info_v = get_flex_cell_layout_info(
1039                        &li.element,
1040                        ctx,
1041                        &li.constraints,
1042                        Orientation::Vertical,
1043                        constraint,
1044                    );
1045                    make_layout_cell_data_struct(layout_info_v, None, None)
1046                })
1047                .collect(),
1048            element_ty: cell_ty,
1049            output: llr_ArrayOutput::Slice,
1050        };
1051        let flex_props = llr_Expression::Array {
1052            values: layout
1053                .elems
1054                .iter()
1055                .map(|li| make_flex_props_struct(flex_prop(li, ctx)))
1056                .collect(),
1057            element_ty: flex_props_ty,
1058            output: llr_ArrayOutput::Slice,
1059        };
1060        FlexboxLayoutDataResult {
1061            alignment,
1062            direction,
1063            cross_axis_line_alignment,
1064            cross_axis_alignment,
1065            flex_wrap,
1066            cells_h,
1067            cells_v,
1068            flex_props,
1069            compute_cells: None,
1070        }
1071    } else {
1072        let mut elements = Vec::new();
1073        for item in &layout.elems {
1074            if item.element.borrow().repeated.is_some() {
1075                let repeater_index =
1076                    match ctx.mapping.element_mapping.get(&item.element.clone().into()).unwrap() {
1077                        LoweredElement::Repeated { repeated_index } => *repeated_index,
1078                        _ => panic!(),
1079                    };
1080                elements.push(Either::Right(LayoutRepeatedElement {
1081                    repeater_index,
1082                    row_child_templates: None,
1083                    cross_width: None,
1084                }))
1085            } else {
1086                // For static elements, we need both orientations
1087                let layout_info_h = get_flex_cell_layout_info(
1088                    &item.element,
1089                    ctx,
1090                    &item.constraints,
1091                    Orientation::Horizontal,
1092                    None,
1093                );
1094                let constraint = cell_v_constraint(&item.element);
1095                let layout_info_v = get_flex_cell_layout_info(
1096                    &item.element,
1097                    ctx,
1098                    &item.constraints,
1099                    Orientation::Vertical,
1100                    constraint,
1101                );
1102                elements.push(Either::Left((
1103                    make_layout_cell_data_struct(layout_info_h, None, None),
1104                    make_layout_cell_data_struct(layout_info_v, None, None),
1105                    make_flex_props_struct(flex_prop(item, ctx)),
1106                )));
1107            }
1108        }
1109        let cells_h = llr_Expression::ReadLocalVariable {
1110            name: "cells_h".into(),
1111            ty: Type::Array(Arc::new(crate::typeregister::layout_item_info_type())),
1112        };
1113        let cells_v = llr_Expression::ReadLocalVariable {
1114            name: "cells_v".into(),
1115            ty: Type::Array(Arc::new(crate::typeregister::layout_item_info_type())),
1116        };
1117        let flex_props = llr_Expression::ReadLocalVariable {
1118            name: "flex_props".into(),
1119            ty: Type::Array(Arc::new(crate::typeregister::flex_item_props_type())),
1120        };
1121        FlexboxLayoutDataResult {
1122            alignment,
1123            direction,
1124            cross_axis_line_alignment,
1125            cross_axis_alignment,
1126            flex_wrap,
1127            cells_h,
1128            cells_v,
1129            flex_props,
1130            compute_cells: Some((
1131                "cells_h".into(),
1132                "cells_v".into(),
1133                "flex_props".into(),
1134                elements,
1135            )),
1136        }
1137    }
1138}
1139
1140struct BoxLayoutDataResult {
1141    alignment: llr_Expression,
1142    cells: llr_Expression,
1143    /// When there are repeater involved, we need to do a WithLayoutItemInfo with the
1144    /// given cell variable and elements
1145    compute_cells: Option<(String, Vec<Either<llr_Expression, LayoutRepeatedElement>>)>,
1146}
1147
1148fn default_align_self() -> (Type, llr_Expression) {
1149    let e = crate::typeregister::BUILTIN.enums.CrossAxisAlignment.clone();
1150    (
1151        Type::Enumeration(e.clone()),
1152        llr_Expression::EnumerationValue(EnumerationValue {
1153            value: e.default_value,
1154            enumeration: e,
1155        }),
1156    )
1157}
1158
1159/// Build a LayoutItemInfo struct expression with the canonical (full) field
1160/// list as its type, so the generators default the fields that are not set.
1161/// `align_self` is only set for a box layout's cross-axis cells, `order` only
1162/// for its main-axis ones.
1163fn make_layout_cell_data_struct(
1164    layout_info: llr_Expression,
1165    align_self: Option<llr_Expression>,
1166    order: Option<llr_Expression>,
1167) -> llr_Expression {
1168    let Type::Struct(ty) = crate::typeregister::layout_item_info_type() else { unreachable!() };
1169    let mut values = BTreeMap::<SmolStr, llr_Expression>::new();
1170    values.insert("constraint".into(), layout_info);
1171    if let Some(align_self) = align_self {
1172        values.insert("cross-axis-self-alignment".into(), align_self);
1173    }
1174    if let Some(order) = order {
1175        values.insert("layout-order".into(), order);
1176    }
1177    llr_Expression::Struct { ty, values }
1178}
1179
1180#[derive(Clone)]
1181struct FlexItemProps {
1182    align_self: llr_Expression,
1183    order: llr_Expression,
1184}
1185
1186fn make_flex_props_struct(fp: FlexItemProps) -> llr_Expression {
1187    let (align_self_ty, _) = default_align_self();
1188    make_struct(
1189        BuiltinStruct::FlexItemProps,
1190        [
1191            ("cross-axis-self-alignment", align_self_ty, fp.align_self),
1192            ("layout-order", Type::Int32, fp.order),
1193        ],
1194    )
1195}
1196
1197fn box_layout_data(
1198    layout: &crate::layout::BoxLayout,
1199    orientation: Orientation,
1200    ctx: &mut ExpressionLoweringCtx,
1201    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1202    cross_clamp: Option<&crate::expression_tree::Expression>,
1203    for_solve: bool,
1204    for_measure_solve: bool,
1205) -> BoxLayoutDataResult {
1206    let alignment = if let Some(expr) = &layout.geometry.alignment {
1207        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
1208    } else {
1209        let e = crate::typeregister::BUILTIN.enums.LayoutAlignment.clone();
1210        llr_Expression::EnumerationValue(EnumerationValue {
1211            value: e.default_value,
1212            enumeration: e,
1213        })
1214    };
1215
1216    let repeater_count =
1217        layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
1218
1219    let element_ty = crate::typeregister::layout_item_info_type();
1220
1221    // The per-item alignment only matters to the cross-axis solve. Leaving it
1222    // out of the main-axis cells keeps that cache independent of it, and out of
1223    // the layout-info cells because `box_layout_info_ortho` ignores it.
1224    // This covers static cells only: repeated cells go through the generated
1225    // `layout_item_info`, which is guarded on the orientation alone.
1226    let cell_align_self = |li: &crate::layout::LayoutItem, ctx: &mut ExpressionLoweringCtx| {
1227        li.cross_axis_self_alignment
1228            .as_ref()
1229            .filter(|_| for_solve && orientation != layout.orientation)
1230            .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
1231    };
1232    // `layout-order` is the mirror image: it only reorders the main-axis solve,
1233    // so keep it out of the cross-axis cache and of the layout-info cells (a
1234    // permutation changes neither the sum nor the merge of the constraints).
1235    let cell_order = |li: &crate::layout::LayoutItem, ctx: &mut ExpressionLoweringCtx| {
1236        li.layout_order
1237            .as_ref()
1238            .filter(|_| for_solve && orientation == layout.orientation)
1239            .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(nr)))
1240    };
1241    if repeater_count == 0 {
1242        let cells = llr_Expression::Array {
1243            values: layout
1244                .elems
1245                .iter()
1246                .map(|li| {
1247                    let layout_info = cell_layout_info(
1248                        &li.element,
1249                        &li.constraints,
1250                        ctx,
1251                        orientation,
1252                        cross_axis_size_override,
1253                        cross_clamp,
1254                        for_measure_solve,
1255                    );
1256                    let align_self = cell_align_self(li, ctx);
1257                    let order = cell_order(li, ctx);
1258                    make_layout_cell_data_struct(layout_info, align_self, order)
1259                })
1260                .collect(),
1261            element_ty,
1262            output: llr_ArrayOutput::Slice,
1263        };
1264        BoxLayoutDataResult { alignment, cells, compute_cells: None }
1265    } else {
1266        let mut elements = Vec::new();
1267        for item in &layout.elems {
1268            if item.element.borrow().repeated.is_some() {
1269                let repeater_index =
1270                    match ctx.mapping.element_mapping.get(&item.element.clone().into()).unwrap() {
1271                        LoweredElement::Repeated { repeated_index } => *repeated_index,
1272                        _ => panic!(),
1273                    };
1274                elements.push(Either::Right(LayoutRepeatedElement {
1275                    repeater_index,
1276                    row_child_templates: None,
1277                    cross_width: None,
1278                }))
1279            } else {
1280                let layout_info = cell_layout_info(
1281                    &item.element,
1282                    &item.constraints,
1283                    ctx,
1284                    orientation,
1285                    cross_axis_size_override,
1286                    cross_clamp,
1287                    for_measure_solve,
1288                );
1289                let align_self = cell_align_self(item, ctx);
1290                let order = cell_order(item, ctx);
1291                elements.push(Either::Left(make_layout_cell_data_struct(
1292                    layout_info,
1293                    align_self,
1294                    order,
1295                )));
1296            }
1297        }
1298        let cells = llr_Expression::ReadLocalVariable {
1299            name: "cells".into(),
1300            ty: Type::Array(Arc::new(crate::typeregister::layout_info_type().into())),
1301        };
1302        BoxLayoutDataResult { alignment, cells, compute_cells: Some(("cells".into(), elements)) }
1303    }
1304}
1305
1306/// `for_measure_solve` marks the main-axis solve inside
1307/// [`compute_box_layout_info_ortho_with_measure`]: height-for-width cells are
1308/// then measured at their preferred width rather than left unconstrained —
1309/// the unconstrained query reads the cell's current width, which can depend
1310/// on the very layout cache this solve is computed for.
1311fn cell_layout_info(
1312    elem: &ElementRc,
1313    constraints: &crate::layout::LayoutConstraints,
1314    ctx: &mut ExpressionLoweringCtx,
1315    orientation: Orientation,
1316    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1317    cross_clamp: Option<&crate::expression_tree::Expression>,
1318    for_measure_solve: bool,
1319) -> llr_Expression {
1320    let constraint = match orientation {
1321        Orientation::Vertical => cross_axis_size_override
1322            .filter(|_| is_height_for_width_cell(elem))
1323            .cloned()
1324            .or_else(|| {
1325                (for_measure_solve && is_height_for_width_cell(elem))
1326                    .then(|| default_cross_axis_constraint(elem))
1327                    .flatten()
1328            }),
1329        Orientation::Horizontal => None,
1330    };
1331    let layout_info = get_layout_info(elem, ctx, constraints, orientation, constraint);
1332    // On a box layout's cross pass (`cross_clamp` set), give a wrapping
1333    // perpendicular flex cell its natural single-line size clamped to the
1334    // available space instead of its compact sqrt preferred. An explicit
1335    // preferred size wins over the clamp, like in the interpreter (which applies
1336    // constraints after the clamp), so skip the clamp then.
1337    let has_explicit_preferred = match orientation {
1338        Orientation::Horizontal => constraints.preferred_width.is_some(),
1339        Orientation::Vertical => constraints.preferred_height.is_some(),
1340    };
1341    match cross_clamp {
1342        Some(available) if !has_explicit_preferred => {
1343            clamp_wrapping_flex_cross_preferred(layout_info, elem, orientation, available, ctx)
1344        }
1345        _ => layout_info,
1346    }
1347}
1348
1349/// Build the `flexbox_layout_unwrapped_main(cells, spacing, padding)` call for
1350/// `layout`'s main axis (= `orientation`). Returns the flex's natural
1351/// single-line main size as a float expression.
1352fn flexbox_unwrapped_main_expr(
1353    layout: &crate::layout::FlexboxLayout,
1354    orientation: Orientation,
1355    ctx: &mut ExpressionLoweringCtx,
1356) -> llr_Expression {
1357    let (padding_h, spacing_h) =
1358        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Horizontal, ctx);
1359    let (padding_v, spacing_v) =
1360        generate_layout_padding_and_spacing(&layout.geometry, Orientation::Vertical, ctx);
1361    let fld = flexbox_layout_data(layout, ctx, None);
1362    let (spacing, padding) = match orientation {
1363        Orientation::Horizontal => (spacing_h, padding_h),
1364        Orientation::Vertical => (spacing_v, padding_v),
1365    };
1366    let cell_array_ty = Type::Array(Arc::new(crate::typeregister::layout_item_info_type()));
1367    let cells_expr = match &fld.compute_cells {
1368        Some((cells_h_var, cells_v_var, _, _)) => {
1369            let cells_var = match orientation {
1370                Orientation::Horizontal => cells_h_var.clone(),
1371                Orientation::Vertical => cells_v_var.clone(),
1372            };
1373            llr_Expression::ReadLocalVariable { name: cells_var.into(), ty: cell_array_ty }
1374        }
1375        None => match orientation {
1376            Orientation::Horizontal => fld.cells_h.clone(),
1377            Orientation::Vertical => fld.cells_v.clone(),
1378        },
1379    };
1380    let call = llr_Expression::ExtraBuiltinFunctionCall {
1381        function: "flexbox_layout_unwrapped_main".into(),
1382        arguments: vec![cells_expr, spacing, padding],
1383        return_ty: Type::Float32,
1384    };
1385    match fld.compute_cells {
1386        Some((cells_h_variable, cells_v_variable, _, elements)) => {
1387            llr_Expression::WithFlexboxLayoutItemInfo {
1388                cells_h_variable,
1389                cells_v_variable,
1390                // The call only reads the cells, so don't evaluate the
1391                // per-item flex properties (that would depend on them).
1392                flex_props_variable: None,
1393                repeater_indices_var_name: None,
1394                elements,
1395                // Info computation, not a solve: no container width to forward.
1396                repeated_cross_width: None,
1397                sub_expression: Box::new(call),
1398            }
1399        }
1400        None => call,
1401    }
1402}
1403
1404/// If `elem` is a wrapping FlexboxLayout whose main axis is the parent's cross
1405/// axis (`orientation`), replace its `preferred` with
1406/// `min(available, unwrapped)`, where `unwrapped` is the flex's natural
1407/// single-line main size. Mirrors the interpreter's
1408/// `clamp_wrapping_flex_cross_preferred`. `available` is the layout's cross
1409/// content size (`layout_cross_content_size`).
1410fn clamp_wrapping_flex_cross_preferred(
1411    layout_info: llr_Expression,
1412    elem: &ElementRc,
1413    orientation: Orientation,
1414    available: &crate::expression_tree::Expression,
1415    ctx: &mut ExpressionLoweringCtx,
1416) -> llr_Expression {
1417    let Some(flex) = crate::layout::FlexboxLayout::from_element(elem) else {
1418        return layout_info;
1419    };
1420    let axis_relation = flex.axis_relation(orientation);
1421    // The flex's main axis must be this cross axis. When the direction is known
1422    // at compile time to be the cross axis, there is nothing to clamp.
1423    if axis_relation == FlexboxAxisRelation::CrossAxis {
1424        return layout_info;
1425    }
1426
1427    let unwrapped = flexbox_unwrapped_main_expr(&flex, orientation, ctx);
1428    let available = super::lower_expression::lower_expression(available, ctx);
1429    let clamped = llr_Expression::MinMax {
1430        ty: Type::Float32,
1431        op: MinMaxOp::Min,
1432        lhs: Box::new(available),
1433        rhs: Box::new(unwrapped),
1434    };
1435
1436    // Rebuild the LayoutInfo struct, overriding only `preferred`.
1437    let ty = crate::typeregister::layout_info_type();
1438    let store = llr_Expression::StoreLocalVariable {
1439        name: "layout_info".into(),
1440        value: layout_info.into(),
1441    };
1442    let stored =
1443        || llr_Expression::ReadLocalVariable { name: "layout_info".into(), ty: ty.clone().into() };
1444    let stored_field = |name: &str| llr_Expression::StructFieldAccess {
1445        base: Box::new(stored()),
1446        name: name.into(),
1447    };
1448    // A no-wrap flex keeps its preferred (single line == its preferred); only a
1449    // wrapping flex is clamped. Decide at runtime when flex-wrap is dynamic.
1450    let new_preferred = match &flex.flex_wrap {
1451        Some(nr) => {
1452            let wrap_enum = crate::typeregister::BUILTIN.enums.FlexboxLayoutWrap.clone();
1453            let is_no_wrap = llr_Expression::BinaryExpression {
1454                lhs: Box::new(llr_Expression::PropertyReference(ctx.map_property_reference(nr))),
1455                rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
1456                    value: 1, // FlexboxLayoutWrap::NoWrap
1457                    enumeration: wrap_enum,
1458                })),
1459                op: '=',
1460            };
1461            llr_Expression::Condition {
1462                condition: Box::new(is_no_wrap),
1463                true_expr: Box::new(stored_field("preferred")),
1464                false_expr: Box::new(clamped),
1465            }
1466        }
1467        None => clamped, // default flex-wrap is `wrap`
1468    };
1469
1470    let mut values =
1471        ty.fields.keys().map(|p| (p.clone(), stored_field(p))).collect::<BTreeMap<_, _>>();
1472    values.insert("preferred".into(), new_preferred);
1473    let clamped_struct = llr_Expression::Struct { ty: ty.clone(), values };
1474
1475    // When the direction is known at compile time to be the main axis, clamp
1476    // unconditionally. When it is only known at runtime, clamp only in the
1477    // branch where the main axis is this cross axis and keep the computed
1478    // layout-info otherwise -- mirrors the runtime dispatch in
1479    // `compute_flexbox_layout_info` and the interpreter's runtime direction eval.
1480    let result = match axis_relation {
1481        FlexboxAxisRelation::MainAxis => clamped_struct,
1482        FlexboxAxisRelation::CrossAxis => unreachable!("returned early above"),
1483        FlexboxAxisRelation::Unknown => {
1484            let direction_enum = crate::typeregister::BUILTIN.enums.FlexboxLayoutDirection.clone();
1485            let direction_ref = llr_Expression::PropertyReference(
1486                ctx.map_property_reference(flex.direction.as_ref().unwrap()),
1487            );
1488            // The main axis is this cross axis when the direction is, for
1489            // Horizontal: Row (0) or RowReverse (1); for Vertical: Column (2) or
1490            // ColumnReverse (3).
1491            let (main_a, main_b) = match orientation {
1492                Orientation::Horizontal => (0, 1),
1493                Orientation::Vertical => (2, 3),
1494            };
1495            let is_direction = |value: usize| llr_Expression::BinaryExpression {
1496                lhs: Box::new(direction_ref.clone()),
1497                rhs: Box::new(llr_Expression::EnumerationValue(EnumerationValue {
1498                    value,
1499                    enumeration: direction_enum.clone(),
1500                })),
1501                op: '=',
1502            };
1503            let main_is_cross = llr_Expression::BinaryExpression {
1504                lhs: Box::new(is_direction(main_a)),
1505                rhs: Box::new(is_direction(main_b)),
1506                op: '|',
1507            };
1508            llr_Expression::Condition {
1509                condition: Box::new(main_is_cross),
1510                true_expr: Box::new(clamped_struct),
1511                false_expr: Box::new(stored()),
1512            }
1513        }
1514    };
1515
1516    llr_Expression::CodeBlock([store, result].into())
1517}
1518
1519struct GridLayoutCellConstraintsResult {
1520    cells: llr_Expression,
1521    /// When there are repeater involved, we need to do a WithLayoutItemInfo with the
1522    /// given cell variable and elements
1523    compute_cells: Option<(String, Vec<Either<llr_Expression, LayoutRepeatedElement>>)>,
1524}
1525
1526/// Name of the local the generated GridLayout vertical pass binds to the index
1527/// of the repeated instance it is about to measure.
1528pub const GRID_MEASURE_REPEATER_INDEX_LOCAL: &str = "grid_measure_repeater_index";
1529
1530/// Name of the local the generated repeated-Row `layout_item_info` binds to the
1531/// flattened index of the child it is about to measure.
1532pub const GRID_MEASURE_CHILD_INDEX_LOCAL: &str = "grid_measure_child_index";
1533
1534/// Which slot of the cell's cache read the measuring loop fills in, and so
1535/// which local it binds. These are the only locals [`grid_measure_cross_width`]
1536/// introduces.
1537pub enum GridMeasureIndex {
1538    /// A repeated cell of the grid, addressed by its repeater index.
1539    Instance,
1540    /// A child of a repeated Row, addressed by its flattened index within the
1541    /// Row. That index is what the cache slot uses, so the expression built
1542    /// from one child serves every child that returns `Some` here — which is
1543    /// what `RowChildTemplateInfo::Repeated`'s `measure_at_cross_width` records.
1544    RowChild,
1545}
1546
1547/// Reads a repeated grid cell's solved column width out of the grid's
1548/// horizontal cache: the cell's own `width` binding with `index`'s slot swapped
1549/// for a local, so the measuring loop can evaluate it once per instance.
1550///
1551/// `None` when the instance is not height-for-width (nothing to re-measure) or
1552/// when its width is fixed — the grid then never assigns it one, so there is no
1553/// cache binding to read.
1554pub fn grid_measure_cross_width(
1555    ctx: &mut ExpressionLoweringCtx,
1556    elem: &ElementRc,
1557    index: GridMeasureIndex,
1558) -> Option<llr_Expression> {
1559    let comp = elem.borrow().base_type.as_component().clone();
1560    let root = &comp.root_element;
1561    if !root.borrow().has_inherited_layout_info_v_with_constraint() {
1562        return None;
1563    }
1564    let mut width = repeated_cell_width_binding(root)?;
1565    let crate::expression_tree::Expression::GridRepeaterCacheAccess {
1566        repeater_index,
1567        inner_repeater_index,
1568        ..
1569    } = width.ignore_debug_hooks_mut()
1570    else {
1571        return None;
1572    };
1573    let local = |name: &str| crate::expression_tree::Expression::ReadLocalVariable {
1574        name: name.into(),
1575        ty: Type::Int32,
1576    };
1577    match index {
1578        GridMeasureIndex::Instance => **repeater_index = local(GRID_MEASURE_REPEATER_INDEX_LOCAL),
1579        GridMeasureIndex::RowChild => {
1580            *inner_repeater_index = Some(Box::new(local(GRID_MEASURE_CHILD_INDEX_LOCAL)))
1581        }
1582    }
1583    Some(super::lower_expression::lower_expression(&width, ctx))
1584}
1585
1586/// The `width` binding a GridLayout gave a repeated cell, followed through
1587/// `geometry_props`: an injected wrapper (`Opacity`, `Transform`, …) becomes the
1588/// repeated component's root but leaves the binding on the element below it.
1589fn repeated_cell_width_binding(root: &ElementRc) -> Option<crate::expression_tree::Expression> {
1590    let width = root.borrow().geometry_props.as_ref()?.width.clone();
1591    let expr = width.element().borrow().binding(width.name())?.expression.clone();
1592    Some(expr)
1593}
1594
1595fn grid_layout_cell_constraints(
1596    layout: &crate::layout::GridLayout,
1597    orientation: Orientation,
1598    ctx: &mut ExpressionLoweringCtx,
1599    cross_axis_size_override: Option<&crate::expression_tree::Expression>,
1600) -> GridLayoutCellConstraintsResult {
1601    let repeater_count =
1602        layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count();
1603
1604    let element_ty = crate::typeregister::layout_item_info_type();
1605
1606    if repeater_count == 0 {
1607        let cells = llr_Expression::Array {
1608            element_ty,
1609            values: layout
1610                .elems
1611                .iter()
1612                .map(|li| {
1613                    let layout_info = cell_layout_info(
1614                        &li.item.element,
1615                        &li.item.constraints,
1616                        ctx,
1617                        orientation,
1618                        cross_axis_size_override,
1619                        None,
1620                        false,
1621                    );
1622                    make_layout_cell_data_struct(layout_info, None, None)
1623                })
1624                .collect(),
1625            output: llr_ArrayOutput::Slice,
1626        };
1627        GridLayoutCellConstraintsResult { cells, compute_cells: None }
1628    } else {
1629        let mut elements = Vec::new();
1630        for item in &layout.elems {
1631            if item.item.element.borrow().repeated.is_some() {
1632                let repeater_index = match ctx
1633                    .mapping
1634                    .element_mapping
1635                    .get(&item.item.element.clone().into())
1636                    .unwrap()
1637                {
1638                    LoweredElement::Repeated { repeated_index } => *repeated_index,
1639                    _ => panic!(),
1640                };
1641                let row_child_templates = get_row_child_templates(&item.item.element, ctx);
1642                // Measure a height-for-width instance at the width the grid
1643                // assigns it, instead of its preferred width. Skipped on the
1644                // `layoutinfo-v-with-constraint` path, where the caller has
1645                // not settled the grid's width yet.
1646                let cross_width = (orientation == Orientation::Vertical
1647                    && cross_axis_size_override.is_none()
1648                    && row_child_templates.is_none())
1649                .then(|| {
1650                    grid_measure_cross_width(ctx, &item.item.element, GridMeasureIndex::Instance)
1651                })
1652                .flatten();
1653                elements.push(Either::Right(LayoutRepeatedElement {
1654                    repeater_index,
1655                    row_child_templates,
1656                    cross_width,
1657                }));
1658            } else {
1659                let layout_info = cell_layout_info(
1660                    &item.item.element,
1661                    &item.item.constraints,
1662                    ctx,
1663                    orientation,
1664                    cross_axis_size_override,
1665                    None,
1666                    false,
1667                );
1668                elements.push(Either::Left(make_layout_cell_data_struct(layout_info, None, None)));
1669            }
1670        }
1671        let cells = llr_Expression::ReadLocalVariable {
1672            name: "cells".into(),
1673            ty: Type::Array(Arc::new(crate::typeregister::layout_info_type().into())),
1674        };
1675        GridLayoutCellConstraintsResult { cells, compute_cells: Some(("cells".into(), elements)) }
1676    }
1677}
1678
1679struct GridLayoutInputDataResult {
1680    cells: llr_Expression,
1681    /// When there are repeaters involved, we need to do a WithGridInputData with the
1682    /// given cell variable and elements
1683    compute_cells: Option<(String, Vec<Either<llr_Expression, GridLayoutRepeatedElement>>)>,
1684}
1685
1686// helper for organize_grid_layout()
1687fn grid_layout_input_data(
1688    layout: &crate::layout::GridLayout,
1689    ctx: &mut ExpressionLoweringCtx,
1690) -> GridLayoutInputDataResult {
1691    let propref = |named_ref: &RowColExpr| match named_ref {
1692        RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()),
1693        RowColExpr::Named(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
1694        RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _),
1695    };
1696    let input_data_for_cell = |elem: &crate::layout::GridLayoutElement,
1697                               new_row_expr: llr_Expression| {
1698        let row_expr = propref(&elem.cell.borrow().row_expr);
1699        let col_expr = propref(&elem.cell.borrow().col_expr);
1700        let rowspan_expr = propref(&elem.cell.borrow().rowspan_expr);
1701        let colspan_expr = propref(&elem.cell.borrow().colspan_expr);
1702
1703        make_struct(
1704            BuiltinStruct::GridLayoutInputData,
1705            [
1706                ("new_row", Type::Bool, new_row_expr),
1707                ("row", Type::Float32, row_expr),
1708                ("col", Type::Float32, col_expr),
1709                ("rowspan", Type::Float32, rowspan_expr),
1710                ("colspan", Type::Float32, colspan_expr),
1711            ],
1712        )
1713    };
1714    let repeater_count =
1715        layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count();
1716
1717    let element_ty = grid_layout_input_data_ty();
1718
1719    if repeater_count == 0 {
1720        let cells = llr_Expression::Array {
1721            element_ty,
1722            values: layout
1723                .elems
1724                .iter()
1725                .map(|elem| {
1726                    input_data_for_cell(
1727                        elem,
1728                        llr_Expression::BoolLiteral(elem.cell.borrow().new_row),
1729                    )
1730                })
1731                .collect(),
1732            output: llr_ArrayOutput::Slice,
1733        };
1734        GridLayoutInputDataResult { cells, compute_cells: None }
1735    } else {
1736        let mut elements = Vec::new();
1737        let mut after_repeater_in_same_row = false;
1738        for item in &layout.elems {
1739            let new_row = item.cell.borrow().new_row;
1740            if new_row {
1741                after_repeater_in_same_row = false;
1742            }
1743            if item.item.element.borrow().repeated.is_some() {
1744                let repeater_index = match ctx
1745                    .mapping
1746                    .element_mapping
1747                    .get(&item.item.element.clone().into())
1748                    .unwrap()
1749                {
1750                    LoweredElement::Repeated { repeated_index } => *repeated_index,
1751                    _ => panic!(),
1752                };
1753                let row_child_templates = get_row_child_templates(&item.item.element, ctx);
1754                let repeated_element =
1755                    GridLayoutRepeatedElement { new_row, repeater_index, row_child_templates };
1756                elements.push(Either::Right(repeated_element));
1757                after_repeater_in_same_row = true;
1758            } else {
1759                let new_row_expr = if new_row || !after_repeater_in_same_row {
1760                    llr_Expression::BoolLiteral(new_row)
1761                } else {
1762                    llr_Expression::ReadLocalVariable {
1763                        name: SmolStr::new_static("new_row"),
1764                        ty: Type::Bool,
1765                    }
1766                };
1767                elements.push(Either::Left(input_data_for_cell(item, new_row_expr)));
1768            }
1769        }
1770        let cells = llr_Expression::ReadLocalVariable {
1771            name: "cells".into(),
1772            ty: Type::Array(Arc::new(element_ty)),
1773        };
1774        GridLayoutInputDataResult { cells, compute_cells: Some(("cells".into(), elements)) }
1775    }
1776}
1777
1778pub(super) fn grid_layout_input_data_ty() -> Type {
1779    Type::Struct(Arc::new(Struct::new(
1780        IntoIterator::into_iter([
1781            (SmolStr::new_static("new_row"), Type::Bool),
1782            (SmolStr::new_static("row"), Type::Int32),
1783            (SmolStr::new_static("col"), Type::Int32),
1784            (SmolStr::new_static("rowspan"), Type::Int32),
1785            (SmolStr::new_static("colspan"), Type::Int32),
1786        ])
1787        .collect(),
1788        BuiltinStruct::GridLayoutInputData,
1789    )))
1790}
1791
1792fn generate_layout_padding_and_spacing(
1793    layout_geometry: &crate::layout::LayoutGeometry,
1794    orientation: Orientation,
1795    ctx: &ExpressionLoweringCtx,
1796) -> (llr_Expression, llr_Expression) {
1797    let padding_prop = |expr| {
1798        if let Some(expr) = expr {
1799            llr_Expression::PropertyReference(ctx.map_property_reference(expr))
1800        } else {
1801            llr_Expression::NumberLiteral(0.)
1802        }
1803    };
1804    let spacing = padding_prop(layout_geometry.spacing.orientation(orientation));
1805    let (begin, end) = layout_geometry.padding.begin_end(orientation);
1806
1807    let padding = make_struct(
1808        BuiltinStruct::Padding,
1809        [("begin", Type::Float32, padding_prop(begin)), ("end", Type::Float32, padding_prop(end))],
1810    );
1811
1812    (padding, spacing)
1813}
1814
1815/// Whether `elem` is a height-for-width cell — its vertical layout info
1816/// depends on the horizontal dimension, so a cross-axis constraint must
1817/// be supplied to get a meaningful answer.
1818///
1819/// Two cases qualify:
1820/// - Builtin height-for-width items (Text with `wrap != no-wrap`, Image with
1821///   aspect-ratio sizing).
1822/// - Components whose subtree contains a height-for-width descendant — recognized
1823///   by the presence of `Element::layout_info_v_with_constraint`.
1824fn is_height_for_width_cell(elem: &ElementRc) -> bool {
1825    let elem_b = elem.borrow();
1826
1827    // Component path: `layoutinfo-v-with-constraint` may live on `elem`
1828    // itself or on the base component's root_element.
1829    let has_constrained_layoutinfo_v = elem_b.layout_info_v_with_constraint.is_some()
1830        || matches!(
1831            &elem_b.base_type,
1832            crate::langtype::ElementType::Component(base_comp)
1833                if base_comp.root_element.borrow().layout_info_v_with_constraint.is_some()
1834        );
1835    if has_constrained_layoutinfo_v {
1836        return true;
1837    }
1838
1839    if elem_b.effective_layout_info_prop(Orientation::Vertical).is_some() {
1840        return false;
1841    }
1842    drop(elem_b);
1843
1844    // Builtin path.
1845    matches!(
1846        crate::layout::implicit_layout_info_call(
1847            elem,
1848            Orientation::Vertical,
1849            crate::layout::BuiltinFilter::All,
1850            None,
1851        ),
1852        Some(crate::expression_tree::Expression::FunctionCall { .. })
1853    )
1854}
1855
1856/// Default cross-axis (width) constraint for a height-for-width cell:
1857/// the element's own preferred horizontal size. Callers
1858/// (`flexbox_layout_data`, `box_layout_data`,
1859/// `grid_layout_cell_constraints`) may prefer the container's actual
1860/// width when it is available (i.e. at solve time, or when the caller
1861/// is the body of a `layoutinfo-v-with-constraint` function which
1862/// received the width as a parameter).
1863///
1864/// Precondition: `is_height_for_width_cell(elem)` is true. After the
1865/// `layoutinfo-v-with-constraint` synthesis pass, any element with
1866/// `layout_info_v_with_constraint` also has `layout_info_prop` set (the
1867/// constrained function is synthesized from the existing `layoutinfo-v`
1868/// binding), so the `layout_info_prop` branch covers it.
1869pub(crate) fn default_cross_axis_constraint(
1870    elem: &ElementRc,
1871) -> Option<crate::expression_tree::Expression> {
1872    let elem_b = elem.borrow();
1873
1874    // Layouts and components with their own resolved layout_info_prop.
1875    if let Some(h_nr) = elem_b.effective_layout_info_prop(Orientation::Horizontal) {
1876        return Some(crate::expression_tree::Expression::StructFieldAccess {
1877            base: Box::new(crate::expression_tree::Expression::PropertyReference(h_nr.clone())),
1878            name: "preferred".into(),
1879        });
1880    }
1881    drop(elem_b);
1882
1883    // Builtins and component instances (looked up via the base component).
1884    crate::layout::implicit_layout_info_call(
1885        elem,
1886        Orientation::Horizontal,
1887        crate::layout::BuiltinFilter::All,
1888        None,
1889    )
1890    .map(|expr| crate::expression_tree::Expression::StructFieldAccess {
1891        base: Box::new(expr),
1892        name: "preferred".into(),
1893    })
1894}
1895
1896/// Subtract `geometry`'s padding on the `axis` from `base`. Turns an outer size
1897/// into the content size a child is actually laid out at (used to constrain a
1898/// height-for-width child at its real width rather than the padded outer width).
1899fn subtract_padding(
1900    base: crate::expression_tree::Expression,
1901    geometry: &crate::layout::LayoutGeometry,
1902    axis: Orientation,
1903) -> crate::expression_tree::Expression {
1904    use crate::expression_tree::Expression;
1905    let pads = match axis {
1906        Orientation::Horizontal => [&geometry.padding.left, &geometry.padding.right],
1907        Orientation::Vertical => [&geometry.padding.top, &geometry.padding.bottom],
1908    };
1909    let mut expr = base;
1910    for p in pads.into_iter().flatten() {
1911        expr = Expression::BinaryExpression {
1912            lhs: Box::new(expr),
1913            rhs: Box::new(Expression::PropertyReference(p.clone())),
1914            op: '-',
1915            source_location: None,
1916        };
1917    }
1918    expr
1919}
1920
1921/// Build an expression for the layout's cross-axis *content* size
1922/// (`self.height` minus top/bottom padding, for a horizontal layout).
1923fn layout_cross_content_size(
1924    layout: &crate::layout::BoxLayout,
1925) -> Option<crate::expression_tree::Expression> {
1926    use crate::expression_tree::Expression;
1927    let cross = layout.orientation.orthogonal();
1928    let size_nr = layout.geometry.rect.size_reference(cross)?.clone();
1929    Some(subtract_padding(Expression::PropertyReference(size_nr), &layout.geometry, cross))
1930}
1931
1932fn layout_geometry_size(
1933    rect: &crate::layout::LayoutRect,
1934    orientation: Orientation,
1935    ctx: &ExpressionLoweringCtx,
1936) -> llr_Expression {
1937    match rect.size_reference(orientation) {
1938        Some(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
1939        None => llr_Expression::NumberLiteral(0.),
1940    }
1941}
1942
1943/// A flex cell's `LayoutInfo`: same as [`get_layout_info`] but keeps only the
1944/// cell's *locally-set* constraints on top of the measured layout-info. An
1945/// inherited intrinsic min/max/preferred is already included in `layout_info`
1946/// (via the parametrized `layoutinfo-v-with-constraint`), and re-reading it
1947/// unconstrained would reintroduce a height-for-width loop through the flex
1948/// solve. Only flex callers need this: box/grid measure the cell without the
1949/// cross-axis being under solve, so re-applying inherited constraints there
1950/// doesn't cycle — and it's necessary, because those inherited constraints
1951/// aren't merged into the layout-info of a cell with a fixed size binding
1952/// (see `default_geometry::gen_layout_info_prop`).
1953pub fn get_flex_cell_layout_info(
1954    elem: &ElementRc,
1955    ctx: &mut ExpressionLoweringCtx,
1956    constraints: &crate::layout::LayoutConstraints,
1957    orientation: Orientation,
1958    constraint: Option<crate::expression_tree::Expression>,
1959) -> llr_Expression {
1960    let effective = constraints.to_apply(elem, orientation);
1961    get_layout_info(elem, ctx, &effective, orientation, constraint)
1962}
1963
1964pub fn get_layout_info(
1965    elem: &ElementRc,
1966    ctx: &mut ExpressionLoweringCtx,
1967    constraints: &crate::layout::LayoutConstraints,
1968    orientation: Orientation,
1969    constraint: Option<crate::expression_tree::Expression>,
1970) -> llr_Expression {
1971    // With a width constraint and a parameterized layout-info function on
1972    // the child, call that function instead of reading the plain
1973    // `layoutinfo-v` property — breaks the recursion via the child's width.
1974    let layout_info = if let Some(c) = &constraint
1975        && orientation == Orientation::Vertical
1976        && let Some(parameterized_nr) = elem.borrow().layout_info_v_with_constraint.clone()
1977    {
1978        let call = crate::expression_tree::Expression::FunctionCall {
1979            function: crate::expression_tree::Callable::Function(parameterized_nr),
1980            arguments: vec![c.clone()],
1981            source_location: None,
1982        };
1983        super::lower_expression::lower_expression(&call, ctx)
1984    } else if let Some(layout_info_prop) = &elem.borrow().effective_layout_info_prop(orientation) {
1985        llr_Expression::PropertyReference(ctx.map_property_reference(layout_info_prop))
1986    } else {
1987        super::lower_expression::lower_expression(
1988            &crate::layout::implicit_layout_info_call(
1989                elem,
1990                orientation,
1991                crate::layout::BuiltinFilter::All,
1992                constraint,
1993            )
1994            .unwrap(),
1995            ctx,
1996        )
1997    };
1998
1999    if constraints.has_explicit_restrictions(orientation) {
2000        let store = llr_Expression::StoreLocalVariable {
2001            name: "layout_info".into(),
2002            value: layout_info.into(),
2003        };
2004        let ty = crate::typeregister::layout_info_type();
2005        let mut values = ty
2006            .fields
2007            .keys()
2008            .map(|p| {
2009                (
2010                    p.clone(),
2011                    llr_Expression::StructFieldAccess {
2012                        base: llr_Expression::ReadLocalVariable {
2013                            name: "layout_info".into(),
2014                            ty: ty.clone().into(),
2015                        }
2016                        .into(),
2017                        name: p.clone(),
2018                    },
2019                )
2020            })
2021            .collect::<BTreeMap<_, _>>();
2022
2023        for (nr, s) in constraints.for_each_restrictions(orientation) {
2024            values.insert(
2025                s.into(),
2026                llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
2027            );
2028        }
2029        llr_Expression::CodeBlock([store, llr_Expression::Struct { ty, values }].into())
2030    } else {
2031        layout_info
2032    }
2033}
2034
2035// Called for repeated components in a grid layout, to generate code to provide input for organize_grid_layout().
2036pub fn get_grid_layout_input_for_repeated(
2037    ctx: &mut ExpressionLoweringCtx,
2038    grid_cell: &GridLayoutCell,
2039) -> llr_Expression {
2040    let mut assignments = Vec::new();
2041
2042    fn convert_row_col_expr(expr: &RowColExpr, ctx: &ExpressionLoweringCtx) -> llr_Expression {
2043        match expr {
2044            RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()),
2045            RowColExpr::Named(nr) => {
2046                llr_Expression::PropertyReference(ctx.map_property_reference(nr))
2047            }
2048            RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _),
2049        }
2050    }
2051
2052    // Generate assignments to the `result` slice parameter: result[i] = struct { ... }
2053    let mut push_assignment =
2054        |i: usize, new_row_expr: &llr_Expression, grid_cell: &GridLayoutCell| {
2055            let row = convert_row_col_expr(&grid_cell.row_expr, &*ctx);
2056            let col = convert_row_col_expr(&grid_cell.col_expr, &*ctx);
2057            let rowspan = convert_row_col_expr(&grid_cell.rowspan_expr, &*ctx);
2058            let colspan = convert_row_col_expr(&grid_cell.colspan_expr, &*ctx);
2059            let value = make_struct(
2060                BuiltinStruct::GridLayoutInputData,
2061                [
2062                    ("new_row", Type::Bool, new_row_expr.clone()),
2063                    ("row", Type::Float32, row),
2064                    ("col", Type::Float32, col),
2065                    ("rowspan", Type::Float32, rowspan),
2066                    ("colspan", Type::Float32, colspan),
2067                ],
2068            );
2069            assignments.push(llr_Expression::SliceIndexAssignment {
2070                slice_name: SmolStr::new_static("result"),
2071                index: i,
2072                value: value.into(),
2073            });
2074        };
2075
2076    if let Some(child_items) = grid_cell.child_items.as_ref() {
2077        // Repeated Row: only handle static children here;
2078        // inner repeater children are handled by the code generators at runtime
2079        let mut new_row_expr = llr_Expression::BoolLiteral(true);
2080        let mut i = 0;
2081        for child_item in child_items.iter() {
2082            match child_item {
2083                crate::layout::RowChildTemplate::Static(layout_item) => {
2084                    let child_element = layout_item.element.borrow();
2085                    let child_cell = child_element.grid_layout_cell.as_ref().unwrap().borrow();
2086                    push_assignment(i, &new_row_expr, &child_cell);
2087                    new_row_expr = llr_Expression::BoolLiteral(false);
2088                    i += 1;
2089                }
2090                crate::layout::RowChildTemplate::Repeated { .. } => {
2091                    // Inner repeater children are filled at runtime by the code generators
2092                }
2093            }
2094        }
2095    } else {
2096        // Single repeated item
2097        // grid_cell.new_row is the static information from the slint file.
2098        // In practice, for repeated items within a row, whether we should start a new row
2099        // is more dynamic (e.g. if the previous item was in "if false"),
2100        // and tracked by a local variable "new_row" in the generated code.
2101        let new_row_expr = llr_Expression::ReadLocalVariable {
2102            name: SmolStr::new_static("new_row"),
2103            ty: Type::Bool,
2104        };
2105        push_assignment(0, &new_row_expr, grid_cell);
2106    }
2107
2108    llr_Expression::CodeBlock(assignments)
2109}
2110
2111/// Returns the row child template list for a repeated Row element.
2112///
2113/// Reads it from the already-lowered Row sub-component (which must have been
2114/// lowered before the parent's expression lowering — see the ordering in
2115/// `lower_sub_component`).
2116///
2117/// Returns `None` if this is a column-repeater (not a Row sub-component).
2118/// Returns `Some(vec)` with one entry per child in declaration order.
2119fn get_row_child_templates(
2120    outer_element: &ElementRc,
2121    ctx: &ExpressionLoweringCtx,
2122) -> Option<Vec<super::RowChildTemplateInfo>> {
2123    let comp = outer_element.borrow().base_type.as_component().clone();
2124    ctx.state.row_child_templates(&comp)
2125}
2126
2127/// Generate an expression that builds a FlexboxLayoutItemInfo for a repeated element
2128/// in a FlexboxLayout, reading flex properties from the component instance.
2129pub fn get_flexbox_layout_item_info_for_repeated(
2130    ctx: &mut ExpressionLoweringCtx,
2131    element: &ElementRc,
2132) -> llr_Expression {
2133    let prop_ref = |name: &'static str| -> Option<llr_Expression> {
2134        crate::layout::binding_reference(element, name)
2135            .map(|nr| llr_Expression::PropertyReference(ctx.map_property_reference(&nr)))
2136    };
2137
2138    let (_, align_self_default) = default_align_self();
2139
2140    let align_self = prop_ref("cross-axis-self-alignment").unwrap_or(align_self_default);
2141    let order = prop_ref("layout-order").unwrap_or(llr_Expression::NumberLiteral(0.0));
2142
2143    make_struct(
2144        BuiltinStruct::FlexboxLayoutItemInfo,
2145        [
2146            (
2147                "constraint",
2148                crate::typeregister::layout_info_type().into(),
2149                llr_Expression::default_value_for_type(
2150                    &crate::typeregister::layout_info_type().into(),
2151                )
2152                .unwrap(),
2153            ),
2154            (
2155                "props",
2156                crate::typeregister::flex_item_props_type(),
2157                make_flex_props_struct(FlexItemProps { align_self, order }),
2158            ),
2159        ],
2160    )
2161}
2162
2163/// Vertical `LayoutInfo` for a repeated element, computed with the element's
2164/// preferred width as the cross-axis constraint. A height-for-width instance
2165/// in a column FlexboxLayout computes its height from that width instead of
2166/// reading `self.width`, which would cycle through the parent flex's layout
2167/// cache. Returns `None` when the element isn't height-for-width.
2168pub fn get_layout_info_v_constrained_for_repeated(
2169    ctx: &mut ExpressionLoweringCtx,
2170    element: &ElementRc,
2171    constraints: &crate::layout::LayoutConstraints,
2172) -> Option<llr_Expression> {
2173    if !is_height_for_width_cell(element) {
2174        return None;
2175    }
2176    // Use the preferred width as the cross-axis constraint, the same default
2177    // static height-for-width cells use. This is a single-line-height
2178    // approximation; a column flex re-measures at the real container width via
2179    // `get_layout_info_v_at_cross_width_for_repeated`.
2180    //
2181    // Absent for an element with no layout info of its own to measure; fall
2182    // back to unbounded then.
2183    let width_constraint = default_cross_axis_constraint(element).unwrap_or_else(|| {
2184        crate::expression_tree::Expression::NumberLiteral(
2185            f32::MAX as f64,
2186            crate::expression_tree::Unit::Px,
2187        )
2188    });
2189    Some(get_flex_cell_layout_info(
2190        element,
2191        ctx,
2192        constraints,
2193        Orientation::Vertical,
2194        Some(width_constraint),
2195    ))
2196}
2197
2198/// Name of the local that carries the cross-axis (container) width into the
2199/// generated `flexbox_layout_item_info_at_cross_width` and
2200/// `layout_item_info_at_cross_width` method bodies (the parameter name is
2201/// derived from it).
2202pub const CROSS_WIDTH_LOCAL: &str = "cross_width";
2203
2204/// Like [`get_layout_info_v_constrained_for_repeated`], but measures at the
2205/// width passed in the [`CROSS_WIDTH_LOCAL`] local instead of the
2206/// element's preferred width. A column FlexboxLayout (or a box layout)
2207/// supplies the width it assigns the instance here at solve time, so a
2208/// repeated height-for-width instance gets the same wrapped height as an
2209/// equivalent static cell. Returns `None` when the element isn't
2210/// height-for-width.
2211///
2212/// `for_flex_cell` selects [`get_flex_cell_layout_info`] (flexbox:
2213/// re-reading inherited constraints unconstrained would reintroduce the
2214/// height-for-width cycle); every other layout kind uses [`get_layout_info`]
2215/// (inherited constraints are re-applied, like for static box cells).
2216pub fn get_layout_info_v_at_cross_width_for_repeated(
2217    ctx: &mut ExpressionLoweringCtx,
2218    element: &ElementRc,
2219    constraints: &crate::layout::LayoutConstraints,
2220    for_flex_cell: bool,
2221) -> Option<llr_Expression> {
2222    if !is_height_for_width_cell(element) {
2223        return None;
2224    }
2225    let width_constraint = crate::expression_tree::Expression::ReadLocalVariable {
2226        name: CROSS_WIDTH_LOCAL.into(),
2227        ty: Type::LogicalLength,
2228    };
2229    let get = if for_flex_cell { get_flex_cell_layout_info } else { get_layout_info };
2230    Some(get(element, ctx, constraints, Orientation::Vertical, Some(width_constraint)))
2231}