Skip to main content

slint_interpreter/
eval_layout.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use crate::Value;
5use crate::dynamic_item_tree::InstanceRef;
6use crate::eval::{self, EvalLocalContext};
7use i_slint_compiler::expression_tree::Expression;
8use i_slint_compiler::langtype::Type;
9use i_slint_compiler::layout::{
10    BoxLayout, GridLayout, LayoutConstraints, LayoutGeometry, Orientation, RowColExpr,
11};
12use i_slint_compiler::namedreference::NamedReference;
13use i_slint_compiler::object_tree::ElementRc;
14use i_slint_core::items::{DialogButtonRole, FlexboxLayoutDirection, ItemRc};
15use i_slint_core::layout::{self as core_layout, GridLayoutInputData, GridLayoutOrganizedData};
16use i_slint_core::model::RepeatedItemTree;
17use i_slint_core::slice::Slice;
18use i_slint_core::window::WindowAdapter;
19use std::rc::Rc;
20use std::str::FromStr;
21
22pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation {
23    match o {
24        Orientation::Horizontal => core_layout::Orientation::Horizontal,
25        Orientation::Vertical => core_layout::Orientation::Vertical,
26    }
27}
28
29pub(crate) fn from_runtime(o: core_layout::Orientation) -> Orientation {
30    match o {
31        core_layout::Orientation::Horizontal => Orientation::Horizontal,
32        core_layout::Orientation::Vertical => Orientation::Vertical,
33    }
34}
35
36pub(crate) fn compute_grid_layout_info(
37    grid_layout: &GridLayout,
38    organized_data: &GridLayoutOrganizedData,
39    orientation: Orientation,
40    local_context: &mut EvalLocalContext,
41    cross_axis_size: Option<f32>,
42) -> Value {
43    let component = local_context.component_instance;
44    let expr_eval = |nr: &NamedReference| -> f32 {
45        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
46    };
47    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
48    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
49    let repeater_indices = grid_repeater_indices(grid_layout, local_context, &repeater_steps);
50    let constraints = grid_layout_constraints(
51        grid_layout,
52        orientation,
53        local_context,
54        &repeater_steps,
55        cross_axis_size,
56    );
57    core_layout::grid_layout_info(
58        organized_data.clone(),
59        Slice::from_slice(constraints.as_slice()),
60        Slice::from_slice(repeater_indices.as_slice()),
61        Slice::from_slice(repeater_steps.as_slice()),
62        spacing,
63        &padding,
64        to_runtime(orientation),
65    )
66    .into()
67}
68
69/// Determine layout info of a box layout
70pub(crate) fn compute_box_layout_info(
71    box_layout: &BoxLayout,
72    orientation: Orientation,
73    local_context: &mut EvalLocalContext,
74    cross_axis_size: Option<f32>,
75) -> Value {
76    let component = local_context.component_instance;
77    let expr_eval = |nr: &NamedReference| -> f32 {
78        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
79    };
80    let cross_axis_size = cross_axis_size.map(|w| {
81        let (cross_pad, _) =
82            padding_and_spacing(&box_layout.geometry, orientation.orthogonal(), &expr_eval);
83        w - cross_pad.begin - cross_pad.end
84    });
85    let (cells, alignment) = box_layout_data(
86        box_layout,
87        orientation,
88        component,
89        &expr_eval,
90        None,
91        cross_axis_size,
92        local_context,
93        None,
94    );
95    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
96    if orientation == box_layout.orientation {
97        core_layout::box_layout_info(Slice::from(cells.as_slice()), spacing, &padding, alignment)
98    } else {
99        core_layout::box_layout_info_ortho(Slice::from(cells.as_slice()), &padding)
100    }
101    .into()
102}
103
104pub(crate) fn organize_grid_layout(
105    layout: &GridLayout,
106    local_context: &mut EvalLocalContext,
107) -> Value {
108    let repeater_steps = grid_repeater_steps(layout, local_context);
109    let cells = grid_layout_input_data(layout, local_context, &repeater_steps);
110    let repeater_indices = grid_repeater_indices(layout, local_context, &repeater_steps);
111    if let Some(buttons_roles) = &layout.dialog_button_roles {
112        let roles = buttons_roles
113            .iter()
114            .map(|r| DialogButtonRole::from_str(r).unwrap())
115            .collect::<Vec<_>>();
116        core_layout::organize_dialog_button_layout(
117            Slice::from_slice(cells.as_slice()),
118            Slice::from_slice(roles.as_slice()),
119        )
120        .into()
121    } else {
122        core_layout::organize_grid_layout(
123            Slice::from_slice(cells.as_slice()),
124            Slice::from_slice(repeater_indices.as_slice()),
125            Slice::from_slice(repeater_steps.as_slice()),
126        )
127        .into()
128    }
129}
130
131pub(crate) fn solve_grid_layout(
132    organized_data: &GridLayoutOrganizedData,
133    grid_layout: &GridLayout,
134    orientation: Orientation,
135    local_context: &mut EvalLocalContext,
136) -> Value {
137    let component = local_context.component_instance;
138    let expr_eval = |nr: &NamedReference| -> f32 {
139        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
140    };
141    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
142    let repeater_indices = grid_repeater_indices(grid_layout, local_context, &repeater_steps);
143    let constraints =
144        grid_layout_constraints(grid_layout, orientation, local_context, &repeater_steps, None);
145
146    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
147    let size_ref = grid_layout.geometry.rect.size_reference(orientation);
148
149    let data = core_layout::GridLayoutData {
150        size: size_ref.map(expr_eval).unwrap_or(0.),
151        spacing,
152        padding,
153        organized_data: organized_data.clone(),
154    };
155
156    core_layout::solve_grid_layout(
157        &data,
158        Slice::from_slice(constraints.as_slice()),
159        to_runtime(orientation),
160        Slice::from_slice(repeater_indices.as_slice()),
161        Slice::from_slice(repeater_steps.as_slice()),
162    )
163    .into()
164}
165
166pub(crate) fn solve_box_layout(
167    box_layout: &BoxLayout,
168    orientation: Orientation,
169    local_context: &mut EvalLocalContext,
170) -> Value {
171    let component = local_context.component_instance;
172    let expr_eval = |nr: &NamedReference| -> f32 {
173        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
174    };
175
176    let mut repeated_indices = Vec::new();
177    // For a horizontal layout's main (width) pass, supply the layout's real cross
178    // size (content height) so width-for-height children (e.g. a column
179    // FlexboxLayout) compute their width from it via layoutinfo-h-with-constraint,
180    // instead of reading self.height and cycling through our own vertical pass.
181    let cross_axis_size = (orientation == box_layout.orientation
182        && orientation == Orientation::Horizontal
183        && box_layout
184            .elems
185            .iter()
186            .any(|c| c.element.borrow().inherited_layout_info_h_with_constraint().is_some()))
187    .then(|| {
188        let cross = orientation.orthogonal();
189        box_layout.geometry.rect.size_reference(cross).map(&expr_eval).map(|s| {
190            let (pad, _) = padding_and_spacing(&box_layout.geometry, cross, &expr_eval);
191            s - pad.begin - pad.end
192        })
193    })
194    .flatten();
195    // On the cross pass, the layout's real cross size (its own content size
196    // along this orientation) is known. Forward it so a wrapping perpendicular
197    // flex cell can be given its natural single-line size instead of the
198    // compact sqrt preferred (see `clamp_wrapping_flex_cross_preferred`).
199    let available_cross = (orientation != box_layout.orientation)
200        .then(|| {
201            box_layout.geometry.rect.size_reference(orientation).map(&expr_eval).map(|s| {
202                let (pad, _) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
203                s - pad.begin - pad.end
204            })
205        })
206        .flatten();
207    let (cells, alignment) = box_layout_data(
208        box_layout,
209        orientation,
210        component,
211        &expr_eval,
212        Some(&mut repeated_indices),
213        cross_axis_size,
214        local_context,
215        available_cross,
216    );
217    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
218    let size = box_layout.geometry.rect.size_reference(orientation).map(&expr_eval).unwrap_or(0.);
219    if orientation == box_layout.orientation {
220        core_layout::solve_box_layout(
221            &core_layout::BoxLayoutData {
222                size,
223                spacing,
224                padding,
225                alignment,
226                cells: Slice::from(cells.as_slice()),
227            },
228            Slice::from(repeated_indices.as_slice()),
229        )
230        .into()
231    } else {
232        let cross_axis_alignment = box_layout
233            .cross_alignment
234            .as_ref()
235            .map(|nr| {
236                eval::load_property(component, &nr.element(), nr.name())
237                    .unwrap()
238                    .try_into()
239                    .unwrap_or_default()
240            })
241            .unwrap_or_default();
242        core_layout::solve_box_layout_ortho(
243            &core_layout::BoxLayoutOrthoData {
244                size,
245                padding,
246                cross_axis_alignment,
247                cells: Slice::from(cells.as_slice()),
248            },
249            Slice::from(repeated_indices.as_slice()),
250        )
251        .into()
252    }
253}
254
255pub(crate) fn solve_flexbox_layout(
256    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
257    local_context: &mut EvalLocalContext,
258) -> Value {
259    let component = local_context.component_instance;
260    let expr_eval = |nr: &NamedReference| -> f32 {
261        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
262    };
263
264    let width_ref = &flexbox_layout.geometry.rect.width_reference;
265    let height_ref = &flexbox_layout.geometry.rect.height_reference;
266    let direction = flexbox_layout_direction(flexbox_layout, local_context);
267
268    // For column direction, pass the container content width (outer width minus
269    // horizontal padding) so cells_v can use it as the constraint for
270    // height-for-width items — the width they are actually laid out at.
271    let container_width_for_cells = match direction {
272        i_slint_core::items::FlexboxLayoutDirection::Column
273        | i_slint_core::items::FlexboxLayoutDirection::ColumnReverse => {
274            width_ref.as_ref().map(|w| {
275                let (pad_h, _) = padding_and_spacing(
276                    &flexbox_layout.geometry,
277                    Orientation::Horizontal,
278                    &expr_eval,
279                );
280                expr_eval(w) - pad_h.begin - pad_h.end
281            })
282        }
283        _ => None,
284    };
285
286    let (cells_h, cells_v, flex_props, repeated_indices) = flexbox_layout_data(
287        flexbox_layout,
288        component,
289        &expr_eval,
290        local_context,
291        container_width_for_cells,
292        None,
293    );
294
295    let alignment = flexbox_layout
296        .geometry
297        .alignment
298        .as_ref()
299        .map_or(i_slint_core::items::LayoutAlignment::default(), |nr| {
300            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
301        });
302    let align_content = flexbox_layout
303        .align_content
304        .as_ref()
305        .map_or(i_slint_core::items::FlexboxLayoutAlignContent::default(), |nr| {
306            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
307        });
308    let cross_axis_alignment = flexbox_layout
309        .cross_axis_alignment
310        .as_ref()
311        .map_or(i_slint_core::items::CrossAxisAlignment::default(), |nr| {
312            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
313        });
314    let flex_wrap = flexbox_layout
315        .flex_wrap
316        .as_ref()
317        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
318            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
319        });
320
321    let (padding_h, spacing_h) =
322        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
323    let (padding_v, spacing_v) =
324        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
325
326    let data = core_layout::FlexboxLayoutData {
327        width: width_ref.as_ref().map(&expr_eval).unwrap_or(0.),
328        height: height_ref.as_ref().map(&expr_eval).unwrap_or(0.),
329        spacing_h,
330        spacing_v,
331        padding_h,
332        padding_v,
333        alignment,
334        direction,
335        align_content,
336        cross_axis_alignment,
337        flex_wrap,
338        cells_h: Slice::from(cells_h.as_slice()),
339        cells_v: Slice::from(cells_v.as_slice()),
340        flex_props: Slice::from(flex_props.as_slice()),
341    };
342    let ri = Slice::from(repeated_indices.as_slice());
343
344    let window_adapter = component.window_adapter();
345
346    // Build measure callback that computes constrained layout_info for items
347    // that support height-for-width (Text with wrap, Image with aspect ratio,
348    // and component instances with a synthesized
349    // `layoutinfo-{v,h}-with-constraint`).
350    //
351    // Collect `(element, has_constrained_layoutinfo_{v,h})` so we can
352    // dispatch component instances through the parametrized layout-info
353    // function rather than through the Item vtable (which returns trivial
354    // info for the Empty wrapper of a sub-component instance).
355    struct ChildElem {
356        elem: ElementRc,
357        has_constrained_layoutinfo_v: bool,
358        has_constrained_layoutinfo_h: bool,
359        /// True when the cell aggregates layoutinfo from its own subtree
360        /// (set by `default_geometry::gen_layout_info_prop`) — typical for
361        /// component-wrappers like a Rectangle containing a layout. In
362        /// that case the Item vtable's `layout_info` on the wrapper item
363        /// returns trivial info that doesn't reflect the aggregated
364        /// constraints; we read the aggregated property directly.
365        has_aggregated_info: bool,
366        /// For a repeated cell, the instance to query — its constrained
367        /// layout-info function lives in the instance's own item tree, not in
368        /// the parent `component`. `None` for a static cell.
369        repeated_instance: Option<crate::dynamic_item_tree::DynamicComponentVRc>,
370    }
371    let mut child_elems: Vec<Option<ChildElem>> = Vec::new();
372    for layout_elem in &flexbox_layout.elems {
373        let placeholder = layout_elem.item.element.borrow();
374        let repeated = placeholder.repeated.is_some();
375        // For a repeated cell, query against the repeated component's root
376        // element (where its `layoutinfo-*-with-constraint` is reachable) in the
377        // instance's own item tree; for a static cell, against the cell element
378        // in the parent `component`.
379        let query_elem = if repeated {
380            placeholder.base_type.as_component().root_element.clone()
381        } else {
382            layout_elem.item.element.clone()
383        };
384        drop(placeholder);
385        let qe = query_elem.borrow();
386        let has_constrained_layoutinfo_v = qe.inherited_layout_info_v_with_constraint().is_some();
387        let has_constrained_layoutinfo_h = qe.inherited_layout_info_h_with_constraint().is_some();
388        let has_aggregated_info = qe.layout_info_prop.is_some();
389        drop(qe);
390        if repeated {
391            // One entry per instance: each is re-measured through its own item
392            // tree at the assigned cross size.
393            let component_vec = repeater_instances(component, &layout_elem.item.element);
394            for instance in component_vec {
395                child_elems.push(Some(ChildElem {
396                    elem: query_elem.clone(),
397                    has_constrained_layoutinfo_v,
398                    has_constrained_layoutinfo_h,
399                    has_aggregated_info,
400                    repeated_instance: Some(instance),
401                }));
402            }
403        } else {
404            child_elems.push(Some(ChildElem {
405                elem: query_elem,
406                has_constrained_layoutinfo_v,
407                has_constrained_layoutinfo_h,
408                has_aggregated_info,
409                repeated_instance: None,
410            }));
411        }
412    }
413
414    let mut measure = |child_index: usize,
415                       known_w: Option<f32>,
416                       known_h: Option<f32>|
417     -> (f32, f32) {
418        let default_w = cells_h.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
419        let default_h = cells_v.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
420        let w = known_w.unwrap_or(default_w);
421        let h = known_h.unwrap_or(default_h);
422
423        let ce = match child_elems.get(child_index) {
424            Some(Some(c)) => c,
425            _ => return (w, h),
426        };
427
428        // Cells whose layoutinfo aggregates from a sub-tree (set by
429        // default_geometry) or that have a parametrized layout-info
430        // function need to be queried by NamedReference. The Item
431        // vtable's `layout_info` on the wrapper item returns trivial
432        // info that ignores the aggregated children.
433        let use_property_lookup = ce.has_aggregated_info
434            || ce.has_constrained_layoutinfo_v
435            || ce.has_constrained_layoutinfo_h;
436
437        // Query the cell's constrained layout-info. A repeated cell lives in
438        // its own instance's item tree (where its `layoutinfo-*-with-constraint`
439        // function is), so re-measure it there at the assigned cross size; a
440        // static cell is queried through the parent `component`.
441        let query = |orientation, constraint: Option<f32>| -> core_layout::LayoutInfo {
442            match &ce.repeated_instance {
443                Some(instance) => {
444                    generativity::make_guard!(guard);
445                    let unerased = instance.unerase(guard);
446                    get_layout_info_with_constraint(
447                        &ce.elem,
448                        unerased.borrow_instance(),
449                        &window_adapter,
450                        orientation,
451                        constraint,
452                    )
453                }
454                None => get_layout_info_with_constraint(
455                    &ce.elem,
456                    component,
457                    &window_adapter,
458                    orientation,
459                    constraint,
460                ),
461            }
462        };
463
464        if known_w.is_some() && known_h.is_none() {
465            if use_property_lookup {
466                let v_info =
467                    query(Orientation::Vertical, ce.has_constrained_layoutinfo_v.then_some(w));
468                return (w, v_info.preferred_bounded());
469            }
470            // Builtin path (Text, Image): use the Item vtable's layout_info,
471            // which honors the cross-axis constraint. This resolves the item in
472            // the parent `component`'s item tree, so it does not apply to a
473            // repeated cell (which lives in its own instance): a
474            // height-for-width repeated cell takes the `query` path above, and a
475            // non-height-for-width one keeps its width-independent default `h`.
476            if let Some(item_within) = ce
477                .repeated_instance
478                .is_none()
479                .then(|| component.description.items.get(ce.elem.borrow().id.as_str()))
480                .flatten()
481            {
482                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
483                let item_rc =
484                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
485                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
486                let v_info = item.as_ref().layout_info(
487                    to_runtime(Orientation::Vertical),
488                    w,
489                    &window_adapter,
490                    &item_rc,
491                );
492                return (w, v_info.preferred_bounded());
493            }
494            return (w, h);
495        }
496        if known_h.is_some() && known_w.is_none() {
497            if use_property_lookup {
498                let h_info =
499                    query(Orientation::Horizontal, ce.has_constrained_layoutinfo_h.then_some(h));
500                return (h_info.preferred_bounded(), h);
501            }
502            // Builtin path, symmetric to the known-w branch above: parent-tree
503            // lookup only, so it is skipped for a repeated cell.
504            if let Some(item_within) = ce
505                .repeated_instance
506                .is_none()
507                .then(|| component.description.items.get(ce.elem.borrow().id.as_str()))
508                .flatten()
509            {
510                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
511                let item_rc =
512                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
513                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
514                let h_info = item.as_ref().layout_info(
515                    to_runtime(Orientation::Horizontal),
516                    h,
517                    &window_adapter,
518                    &item_rc,
519                );
520                return (h_info.preferred_bounded(), h);
521            }
522            return (w, h);
523        }
524        (w, h)
525    };
526
527    core_layout::solve_flexbox_layout_with_measure(&data, ri, Some(&mut measure)).into()
528}
529
530fn flexbox_layout_direction(
531    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
532    local_context: &EvalLocalContext,
533) -> FlexboxLayoutDirection {
534    flexbox_layout
535        .direction
536        .as_ref()
537        .and_then(|nr| {
538            let value =
539                eval::load_property(local_context.component_instance, &nr.element(), nr.name())
540                    .ok()?;
541            if let Value::EnumerationValue(_, variant) = &value {
542                match variant.as_str() {
543                    "row" => Some(FlexboxLayoutDirection::Row),
544                    "row-reverse" => Some(FlexboxLayoutDirection::RowReverse),
545                    "column" => Some(FlexboxLayoutDirection::Column),
546                    "column-reverse" => Some(FlexboxLayoutDirection::ColumnReverse),
547                    _ => None,
548                }
549            } else {
550                None
551            }
552        })
553        .unwrap_or(FlexboxLayoutDirection::Row)
554}
555
556pub(crate) fn compute_flexbox_layout_info(
557    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
558    orientation: Orientation,
559    local_context: &mut EvalLocalContext,
560    cross_axis_size: Option<f32>,
561) -> Value {
562    let component = local_context.component_instance;
563    let expr_eval = |nr: &NamedReference| -> f32 {
564        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
565    };
566
567    // `cross_axis_size` carries a width when called from a
568    // `layoutinfo-v-with-constraint` body, a height from a
569    // `layoutinfo-h-with-constraint` body. Route it to the matching
570    // cell-list so cells don't receive a dimension on the wrong axis.
571    let (width_override, height_override) = match orientation {
572        Orientation::Vertical => (cross_axis_size, None),
573        Orientation::Horizontal => (None, cross_axis_size),
574    };
575    // Subtract padding so height-for-width cells are measured at the content
576    // width they are actually laid out at, not the padded outer width.
577    let width_override = width_override.map(|w| {
578        let (pad_h, _) =
579            padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
580        w - pad_h.begin - pad_h.end
581    });
582    let height_override = height_override.map(|h| {
583        let (pad_v, _) =
584            padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
585        h - pad_v.begin - pad_v.end
586    });
587    let (cells_h, cells_v, flex_props, _repeated_indices) = flexbox_layout_data(
588        flexbox_layout,
589        component,
590        &expr_eval,
591        local_context,
592        width_override,
593        height_override,
594    );
595
596    // Get the direction from the property binding
597    let direction = flexbox_layout_direction(flexbox_layout, local_context);
598
599    // Determine if we're on the main axis or cross axis
600    let is_main_axis = matches!(
601        (direction, orientation),
602        (FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse, Orientation::Horizontal)
603            | (
604                FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
605                Orientation::Vertical
606            )
607    );
608
609    let (padding_h, spacing_h) =
610        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
611    let (padding_v, spacing_v) =
612        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
613
614    let flex_wrap = flexbox_layout
615        .flex_wrap
616        .as_ref()
617        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
618            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
619        });
620
621    if is_main_axis {
622        let (cells, spacing, padding) = match orientation {
623            Orientation::Horizontal => (&cells_h, spacing_h, &padding_h),
624            Orientation::Vertical => (&cells_v, spacing_v, &padding_v),
625        };
626        core_layout::flexbox_layout_info_main_axis(
627            Slice::from(cells.as_slice()),
628            Slice::from(flex_props.as_slice()),
629            spacing,
630            padding,
631            flex_wrap,
632        )
633        .into()
634    } else {
635        // Override when set (e.g., from a `layoutinfo-h-with-constraint`
636        // body); otherwise self's perpendicular dimension. The override
637        // path breaks the cycle for nested perpendicular flexboxes.
638        let constraint_size = cross_axis_size.unwrap_or_else(|| match orientation {
639            Orientation::Horizontal => {
640                let height_ref = &flexbox_layout.geometry.rect.height_reference;
641                height_ref.as_ref().map(&expr_eval).unwrap_or(0.)
642            }
643            Orientation::Vertical => {
644                let width_ref = &flexbox_layout.geometry.rect.width_reference;
645                width_ref.as_ref().map(&expr_eval).unwrap_or(0.)
646            }
647        });
648        core_layout::flexbox_layout_info_cross_axis(
649            Slice::from(cells_h.as_slice()),
650            Slice::from(cells_v.as_slice()),
651            Slice::from(flex_props.as_slice()),
652            spacing_h,
653            spacing_v,
654            &padding_h,
655            &padding_v,
656            direction,
657            flex_wrap,
658            constraint_size,
659        )
660        .into()
661    }
662}
663
664fn flexbox_layout_data(
665    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
666    component: InstanceRef,
667    expr_eval: &impl Fn(&NamedReference) -> f32,
668    _local_context: &mut EvalLocalContext,
669    width_override: Option<f32>,
670    height_override: Option<f32>,
671) -> (
672    Vec<core_layout::LayoutItemInfo>,
673    Vec<core_layout::LayoutItemInfo>,
674    Vec<core_layout::FlexItemProps>,
675    Vec<u32>,
676) {
677    let window_adapter = component.window_adapter();
678    let mut cells_h = Vec::with_capacity(flexbox_layout.elems.len());
679    let mut cells_v = Vec::with_capacity(flexbox_layout.elems.len());
680    let mut repeated_indices = Vec::new();
681
682    // First pass: collect horizontal layout_info for all children (no cycle risk)
683    // and flex properties.
684    // Instances of each repeater, in `elems` order, so the second pass doesn't walk them again.
685    let mut repeater_instance_vecs: Vec<Vec<crate::dynamic_item_tree::DynamicComponentVRc>> =
686        Vec::new();
687
688    for layout_elem in &flexbox_layout.elems {
689        if layout_elem.item.element.borrow().repeated.is_some() {
690            let component_vec = repeater_instances(component, &layout_elem.item.element);
691            repeated_indices.push(cells_h.len() as u32);
692            repeated_indices.push(component_vec.len() as u32);
693            cells_h.extend(component_vec.iter().map(|x| {
694                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Horizontal), None)
695            }));
696            // A height-for-width repeated instance (e.g. a component forwarding a
697            // wrapped Text's min-height) must not have its vertical info read here at
698            // the still-unsolved width — that would cycle, just like the static branch
699            // warns below. Defer it to the second pass, which measures at the width
700            // constraint and copies the flex props from cells_h. Other repeated cells
701            // are safe to measure now.
702            let rep_root =
703                layout_elem.item.element.borrow().base_type.as_component().root_element.clone();
704            if rep_root.borrow().inherited_layout_info_v_with_constraint().is_some() {
705                cells_v.resize_with(cells_v.len() + component_vec.len(), Default::default);
706            } else {
707                cells_v.extend(component_vec.iter().map(|x| {
708                    let info = x
709                        .as_pin_ref()
710                        .flexbox_layout_item_info(to_runtime(Orientation::Vertical), None);
711                    core_layout::LayoutItemInfo { constraint: info.constraint }
712                }));
713            }
714            repeater_instance_vecs.push(component_vec);
715        } else {
716            // Dispatch via `layoutinfo-h-with-constraint` for cells that
717            // have one, avoiding the `self.height` read that would cycle.
718            // Use the height-override when set, else `f32::MAX` so the
719            // runtime treats it as "no wrap needed" — gives the natural
720            // max-cell-width result rather than the heuristic.
721            let h_constraint = layout_elem
722                .item
723                .element
724                .borrow()
725                .inherited_layout_info_h_with_constraint()
726                .is_some()
727                .then(|| height_override.unwrap_or(f32::MAX));
728            let mut layout_info_h = get_layout_info_with_constraint(
729                &layout_elem.item.element,
730                component,
731                &window_adapter,
732                Orientation::Horizontal,
733                h_constraint,
734            );
735            fill_layout_info_constraints(
736                &mut layout_info_h,
737                &layout_elem.item.constraints,
738                Orientation::Horizontal,
739                expr_eval,
740            );
741            // Don't collect cells_v in the first pass — it may trigger a circular
742            // dependency for height-for-width items (Text with wrap, Image).
743            // The second pass fills in cells_v with the width constraint.
744            let flex_grow = layout_elem.flex_grow.as_ref().map(&expr_eval).unwrap_or(0.0);
745            let flex_shrink = layout_elem.flex_shrink.as_ref().map(&expr_eval).unwrap_or(1.0);
746            let flex_basis = layout_elem.flex_basis.as_ref().map(&expr_eval).unwrap_or(-1.0);
747            let align_self = layout_elem
748                .align_self
749                .as_ref()
750                .map(|nr| {
751                    eval::load_property(component, &nr.element(), nr.name())
752                        .unwrap()
753                        .try_into()
754                        .unwrap()
755                })
756                .unwrap_or(i_slint_core::items::FlexboxLayoutAlignSelf::default());
757            let order = layout_elem.order.as_ref().map(expr_eval).unwrap_or(0.0) as i32;
758            cells_h.push(core_layout::FlexboxLayoutItemInfo {
759                constraint: layout_info_h,
760                props: core_layout::FlexItemProps {
761                    flex_grow,
762                    flex_shrink,
763                    flex_basis,
764                    flex_align_self: align_self,
765                    flex_order: order,
766                },
767            });
768            // Placeholder for cells_v — filled in second pass
769            cells_v.push(core_layout::LayoutItemInfo::default());
770        }
771    }
772
773    // Second pass: collect vertical layout_info with a width constraint.
774    // For column direction, use the container width (items get stretched to it).
775    // Otherwise use the item's horizontal preferred size.
776    let mut cell_idx = 0usize;
777    let mut repeater_idx = 0usize;
778    for layout_elem in &flexbox_layout.elems {
779        if layout_elem.item.element.borrow().repeated.is_some() {
780            // Re-measure each height-for-width repeated instance at the width
781            // constraint (container width for a column flex), mirroring the
782            // static branch below — the first pass filled cells_v at the
783            // instance's preferred width (single line). Keep the flex props set
784            // in the first pass; only overwrite the vertical constraint.
785            let rep_root =
786                layout_elem.item.element.borrow().base_type.as_component().root_element.clone();
787            let is_height_for_width =
788                rep_root.borrow().inherited_layout_info_v_with_constraint().is_some();
789            let component_vec = &repeater_instance_vecs[repeater_idx];
790            repeater_idx += 1;
791            for instance in component_vec {
792                if is_height_for_width {
793                    let width_constraint = width_override
794                        .unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
795                    generativity::make_guard!(guard);
796                    let unerased = instance.unerase(guard);
797                    let instance_ref = unerased.borrow_instance();
798                    let mut layout_info_v = get_layout_info_with_constraint(
799                        &rep_root,
800                        instance_ref,
801                        &window_adapter,
802                        Orientation::Vertical,
803                        Some(width_constraint),
804                    );
805                    // The constraints' NamedReferences point to elements inside
806                    // the repeated sub-component, so evaluate them in that
807                    // instance's context, not the outer component's.
808                    let instance_expr_eval = |nr: &NamedReference| -> f32 {
809                        eval::load_property(instance_ref, &nr.element(), nr.name())
810                            .unwrap()
811                            .try_into()
812                            .unwrap()
813                    };
814                    // Apply only the constraints not already merged into the cell's
815                    // own layout-info (see the static branch below): an inherited
816                    // forwarded min/max/preferred-height is in the value above, and
817                    // re-reading it unconstrained would reintroduce a height-for-width
818                    // loop through the flex solve.
819                    let effective = layout_elem
820                        .item
821                        .constraints
822                        .to_apply(&layout_elem.item.element, Orientation::Vertical);
823                    fill_layout_info_constraints(
824                        &mut layout_info_v,
825                        &effective,
826                        Orientation::Vertical,
827                        &instance_expr_eval,
828                    );
829                    // cells_v was deferred in the first pass; fill in its constraint.
830                    // The flex props live in their own array, split from cells_h.
831                    cells_v[cell_idx] = core_layout::LayoutItemInfo { constraint: layout_info_v };
832                }
833                cell_idx += 1;
834            }
835        } else {
836            let width_constraint =
837                width_override.unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
838            let mut layout_info_v = get_layout_info_with_constraint(
839                &layout_elem.item.element,
840                component,
841                &window_adapter,
842                Orientation::Vertical,
843                Some(width_constraint),
844            );
845            // Apply only the constraints not already merged into the cell's own
846            // layout-info: an inherited intrinsic min/max/preferred-height is in
847            // the value above, and re-reading it unconstrained would reintroduce
848            // a height-for-width loop through the flex solve. Locally-set overrides
849            // (`WrapCol { min-height: 70px }`) are kept.
850            let effective = layout_elem
851                .item
852                .constraints
853                .to_apply(&layout_elem.item.element, Orientation::Vertical);
854            fill_layout_info_constraints(
855                &mut layout_info_v,
856                &effective,
857                Orientation::Vertical,
858                expr_eval,
859            );
860            cells_v[cell_idx] = core_layout::LayoutItemInfo { constraint: layout_info_v };
861            cell_idx += 1;
862        }
863    }
864
865    // cells_h still bundles constraint + flex props; split it into the parallel
866    // arrays the runtime takes. cells_v already holds constraint-only cells.
867    let flex_props = cells_h.iter().map(|c| c.props).collect();
868    let cells_h = cells_h
869        .into_iter()
870        .map(|c| core_layout::LayoutItemInfo { constraint: c.constraint })
871        .collect();
872    (cells_h, cells_v, flex_props, repeated_indices)
873}
874
875/// Determine the evaluated padding and spacing values from the layout geometry
876fn padding_and_spacing(
877    layout_geometry: &LayoutGeometry,
878    orientation: Orientation,
879    expr_eval: &impl Fn(&NamedReference) -> f32,
880) -> (core_layout::Padding, f32) {
881    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
882    let (begin, end) = layout_geometry.padding.begin_end(orientation);
883    let padding =
884        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
885    (padding, spacing)
886}
887
888fn repeater_instances(
889    component: InstanceRef,
890    elem: &ElementRc,
891) -> Vec<crate::dynamic_item_tree::DynamicComponentVRc> {
892    generativity::make_guard!(guard);
893    let rep =
894        crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard);
895    rep.0.as_ref().track_instance_changes();
896    rep.0.as_ref().instances_vec()
897}
898
899fn grid_layout_input_data(
900    grid_layout: &i_slint_compiler::layout::GridLayout,
901    ctx: &EvalLocalContext,
902    repeater_steps: &[u32],
903) -> Vec<GridLayoutInputData> {
904    let component = ctx.component_instance;
905    let mut result = Vec::with_capacity(grid_layout.elems.len());
906    let mut after_repeater_in_same_row = false;
907    let mut new_row = true;
908    let mut repeater_idx = 0usize;
909    for elem in grid_layout.elems.iter() {
910        let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr {
911            RowColExpr::Literal(value) => *value as f32,
912            RowColExpr::Auto => i_slint_common::ROW_COL_AUTO,
913            RowColExpr::Named(nr) => {
914                // we could check for out-of-bounds here, but organize_grid_layout will also do it
915                eval::load_property(component, &nr.element(), nr.name())
916                    .unwrap()
917                    .try_into()
918                    .unwrap()
919            }
920        };
921
922        let cell_new_row = elem.cell.borrow().new_row;
923        if cell_new_row {
924            after_repeater_in_same_row = false;
925        }
926        if elem.item.element.borrow().repeated.is_some() {
927            let component_vec = repeater_instances(component, &elem.item.element);
928            new_row = cell_new_row;
929            for erased_sub_comp in &component_vec {
930                // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component
931                generativity::make_guard!(guard);
932                let sub_comp = erased_sub_comp.as_pin_ref();
933                let sub_instance_ref =
934                    unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) };
935
936                if let Some(children) = elem.cell.borrow().child_items.as_ref() {
937                    // Repeated row
938                    new_row = true;
939                    let start_count = result.len();
940
941                    // Single pass in declaration order: push statics and inner-repeater
942                    // auto-cells interleaved so that column assignments match template order.
943                    // (A two-pass approach that appended all inner-repeater cells after all
944                    // statics would produce wrong column assignments, and only tracking the
945                    // last Repeated entry would miss earlier conditionals/for-loops.)
946                    for child_template in children {
947                        match child_template {
948                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
949                                let (row_val, col_val, rowspan_val, colspan_val) = {
950                                    let element_ref = child_item.element.borrow();
951                                    let child_cell =
952                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
953                                    (
954                                        eval_or_default(&child_cell.row_expr, sub_instance_ref),
955                                        eval_or_default(&child_cell.col_expr, sub_instance_ref),
956                                        eval_or_default(&child_cell.rowspan_expr, sub_instance_ref),
957                                        eval_or_default(&child_cell.colspan_expr, sub_instance_ref),
958                                    )
959                                };
960                                result.push(GridLayoutInputData {
961                                    new_row,
962                                    col: col_val,
963                                    row: row_val,
964                                    colspan: colspan_val,
965                                    rowspan: rowspan_val,
966                                });
967                                new_row = false;
968                            }
969                            i_slint_compiler::layout::RowChildTemplate::Repeated {
970                                repeated_element,
971                                ..
972                            } => {
973                                // colspan/rowspan live on the inner sub-component's root,
974                                // evaluated per inner instance.
975                                let inner_root = repeated_element
976                                    .borrow()
977                                    .base_type
978                                    .as_component()
979                                    .root_element
980                                    .clone();
981                                let (rowspan_expr, colspan_expr) = {
982                                    let element_ref = inner_root.borrow();
983                                    let child_cell =
984                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
985                                    (
986                                        child_cell.rowspan_expr.clone(),
987                                        child_cell.colspan_expr.clone(),
988                                    )
989                                };
990                                let inner_instances =
991                                    repeater_instances(sub_instance_ref, repeated_element);
992                                for (i, erased_inner) in inner_instances.iter().enumerate() {
993                                    generativity::make_guard!(inner_guard);
994                                    let inner_comp = erased_inner.as_pin_ref();
995                                    let inner_instance_ref = unsafe {
996                                        InstanceRef::from_pin_ref(inner_comp.borrow(), inner_guard)
997                                    };
998                                    result.push(GridLayoutInputData {
999                                        new_row: i == 0 && new_row,
1000                                        rowspan: eval_or_default(&rowspan_expr, inner_instance_ref),
1001                                        colspan: eval_or_default(&colspan_expr, inner_instance_ref),
1002                                        ..Default::default()
1003                                    });
1004                                }
1005                                if !inner_instances.is_empty() {
1006                                    new_row = false;
1007                                }
1008                            }
1009                        }
1010                    }
1011                    // Pad to match max step count for this repeater (handles jagged arrays)
1012                    let cells_pushed = result.len() - start_count;
1013                    let expected_step =
1014                        repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
1015                    for _ in cells_pushed..expected_step {
1016                        result.push(GridLayoutInputData::default());
1017                    }
1018                } else {
1019                    // Single repeated item
1020                    let cell = elem.cell.borrow();
1021                    let row = eval_or_default(&cell.row_expr, sub_instance_ref);
1022                    let col = eval_or_default(&cell.col_expr, sub_instance_ref);
1023                    let rowspan = eval_or_default(&cell.rowspan_expr, sub_instance_ref);
1024                    let colspan = eval_or_default(&cell.colspan_expr, sub_instance_ref);
1025                    result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
1026                    new_row = false;
1027                }
1028            }
1029            repeater_idx += 1;
1030            after_repeater_in_same_row = true;
1031        } else {
1032            let new_row =
1033                if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row };
1034            let row = eval_or_default(&elem.cell.borrow().row_expr, component);
1035            let col = eval_or_default(&elem.cell.borrow().col_expr, component);
1036            let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component);
1037            let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component);
1038            result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
1039        }
1040    }
1041    result
1042}
1043
1044/// Count the actual runtime children for a repeated row.
1045/// For rows without inner repeaters, this is just the child_items count.
1046/// For rows with inner repeaters, the Repeated template expands to actual inner instances.
1047fn row_runtime_child_count(
1048    child_items: &[i_slint_compiler::layout::RowChildTemplate],
1049    sub_instance_ref: InstanceRef,
1050) -> usize {
1051    let mut count = 0;
1052    for child in child_items {
1053        if let Some(repeated_element) = child.repeated_element() {
1054            count += repeater_instances(sub_instance_ref, repeated_element).len();
1055        } else {
1056            count += 1;
1057        }
1058    }
1059    count
1060}
1061
1062fn grid_repeater_indices(
1063    grid_layout: &i_slint_compiler::layout::GridLayout,
1064    ctx: &mut EvalLocalContext,
1065    repeater_steps: &[u32],
1066) -> Vec<u32> {
1067    let component = ctx.component_instance;
1068    let mut repeater_indices = Vec::new();
1069    let mut num_cells = 0;
1070    let mut step_idx = 0;
1071    for elem in grid_layout.elems.iter() {
1072        if elem.item.element.borrow().repeated.is_some() {
1073            let component_vec = repeater_instances(component, &elem.item.element);
1074            repeater_indices.push(num_cells as _);
1075            repeater_indices.push(component_vec.len() as _);
1076            let item_count = repeater_steps[step_idx] as usize;
1077            num_cells += component_vec.len() * item_count;
1078            step_idx += 1;
1079        } else {
1080            num_cells += 1;
1081        }
1082    }
1083    repeater_indices
1084}
1085
1086fn grid_repeater_steps(
1087    grid_layout: &i_slint_compiler::layout::GridLayout,
1088    ctx: &mut EvalLocalContext,
1089) -> Vec<u32> {
1090    let component = ctx.component_instance;
1091    let mut repeater_steps = Vec::new();
1092    for elem in grid_layout.elems.iter() {
1093        if elem.item.element.borrow().repeated.is_some() {
1094            let item_count = match &elem.cell.borrow().child_items {
1095                Some(ci)
1096                    if ci.iter().any(i_slint_compiler::layout::RowChildTemplate::is_repeated) =>
1097                {
1098                    // Compute max runtime count across all instances (padding with empty cells didn't happen yet)
1099                    let component_vec = repeater_instances(component, &elem.item.element);
1100                    component_vec
1101                        .iter()
1102                        .map(|sub| {
1103                            generativity::make_guard!(guard);
1104                            let sub_pin = sub.as_pin_ref();
1105                            let sub_ref =
1106                                unsafe { InstanceRef::from_pin_ref(sub_pin.borrow(), guard) };
1107                            row_runtime_child_count(ci, sub_ref)
1108                        })
1109                        .max()
1110                        .unwrap_or(0)
1111                }
1112                Some(ci) => ci.len(),
1113                None => 1,
1114            };
1115            repeater_steps.push(item_count as u32);
1116        }
1117    }
1118    repeater_steps
1119}
1120
1121fn grid_layout_constraints(
1122    grid_layout: &i_slint_compiler::layout::GridLayout,
1123    orientation: Orientation,
1124    ctx: &mut EvalLocalContext,
1125    repeater_steps: &[u32],
1126    cross_axis_size: Option<f32>,
1127) -> Vec<core_layout::LayoutItemInfo> {
1128    let component = ctx.component_instance;
1129    let expr_eval = |nr: &NamedReference| -> f32 {
1130        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1131    };
1132    let mut constraints = Vec::with_capacity(grid_layout.elems.len());
1133
1134    let mut repeater_idx = 0usize;
1135    for layout_elem in grid_layout.elems.iter() {
1136        if layout_elem.item.element.borrow().repeated.is_some() {
1137            let component_vec = repeater_instances(component, &layout_elem.item.element);
1138            let child_items = layout_elem.cell.borrow().child_items.clone();
1139            let has_children = child_items.is_some();
1140            if has_children {
1141                // Repeated row
1142                let ci = child_items.as_ref().unwrap();
1143                let step = repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
1144                for sub_comp in &component_vec {
1145                    let per_instance_start = constraints.len();
1146                    // Evaluate constraints in the context of the repeated sub-component
1147                    generativity::make_guard!(guard);
1148                    let sub_pin = sub_comp.as_pin_ref();
1149                    let sub_borrow = sub_pin.borrow();
1150                    let sub_instance_ref = unsafe { InstanceRef::from_pin_ref(sub_borrow, guard) };
1151                    let expr_eval = |nr: &NamedReference| -> f32 {
1152                        eval::load_property(sub_instance_ref, &nr.element(), nr.name())
1153                            .unwrap()
1154                            .try_into()
1155                            .unwrap()
1156                    };
1157
1158                    // Iterate over the child templates: static children get their layout info
1159                    // from the Row sub-component; nested repeater children get theirs from the
1160                    // inner repeater instances.
1161                    for child_template in ci.iter() {
1162                        match child_template {
1163                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
1164                                let mut layout_info = crate::eval_layout::get_layout_info(
1165                                    &child_item.element,
1166                                    sub_instance_ref,
1167                                    &sub_instance_ref.window_adapter(),
1168                                    orientation,
1169                                );
1170                                fill_layout_info_constraints(
1171                                    &mut layout_info,
1172                                    &child_item.constraints,
1173                                    orientation,
1174                                    &expr_eval,
1175                                );
1176                                constraints
1177                                    .push(core_layout::LayoutItemInfo { constraint: layout_info });
1178                            }
1179                            i_slint_compiler::layout::RowChildTemplate::Repeated {
1180                                item: child_item,
1181                                repeated_element,
1182                            } => {
1183                                // Get the inner repeater instances from within this Row instance
1184                                let inner_instances =
1185                                    repeater_instances(sub_instance_ref, repeated_element);
1186                                for inner_comp in &inner_instances {
1187                                    let inner_pin = inner_comp.as_pin_ref();
1188                                    let mut layout_info =
1189                                        inner_pin.layout_item_info(to_runtime(orientation), None);
1190                                    // Constraints' NamedReferences point to elements inside the
1191                                    // inner repeated component, so evaluate in that context.
1192                                    generativity::make_guard!(inner_guard);
1193                                    let inner_borrow = inner_pin.borrow();
1194                                    let inner_instance_ref = unsafe {
1195                                        InstanceRef::from_pin_ref(inner_borrow, inner_guard)
1196                                    };
1197                                    let inner_expr_eval = |nr: &NamedReference| -> f32 {
1198                                        eval::load_property(
1199                                            inner_instance_ref,
1200                                            &nr.element(),
1201                                            nr.name(),
1202                                        )
1203                                        .unwrap()
1204                                        .try_into()
1205                                        .unwrap()
1206                                    };
1207                                    fill_layout_info_constraints(
1208                                        &mut layout_info.constraint,
1209                                        &child_item.constraints,
1210                                        orientation,
1211                                        &inner_expr_eval,
1212                                    );
1213                                    constraints.push(layout_info);
1214                                }
1215                            }
1216                        }
1217                    }
1218                    // Pad this instance to the step size (handles jagged arrays where
1219                    // inner repeaters have different lengths across outer Row instances).
1220                    let pushed = constraints.len() - per_instance_start;
1221                    for _ in pushed..step {
1222                        constraints.push(core_layout::LayoutItemInfo::default());
1223                    }
1224                }
1225            } else {
1226                // Single repeated item
1227                constraints.extend(
1228                    component_vec
1229                        .iter()
1230                        .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1231                );
1232            }
1233            repeater_idx += 1;
1234        } else {
1235            let cross_axis =
1236                cross_axis_size_for_cell(&layout_elem.item.element, orientation, cross_axis_size);
1237            let mut layout_info = get_layout_info_with_constraint(
1238                &layout_elem.item.element,
1239                component,
1240                &component.window_adapter(),
1241                orientation,
1242                cross_axis,
1243            );
1244            fill_layout_info_constraints(
1245                &mut layout_info,
1246                &layout_elem.item.constraints,
1247                orientation,
1248                &expr_eval,
1249            );
1250            constraints.push(core_layout::LayoutItemInfo { constraint: layout_info });
1251        }
1252    }
1253    constraints
1254}
1255
1256/// Collect all elements in this layout and store the LayoutItemInfo of it for further calculation
1257fn box_layout_data(
1258    box_layout: &i_slint_compiler::layout::BoxLayout,
1259    orientation: Orientation,
1260    component: InstanceRef,
1261    expr_eval: &impl Fn(&NamedReference) -> f32,
1262    mut repeater_indices: Option<&mut Vec<u32>>,
1263    cross_axis_size: Option<f32>,
1264    local_context: &mut EvalLocalContext,
1265    available_cross: Option<f32>,
1266) -> (Vec<core_layout::LayoutItemInfo>, i_slint_core::items::LayoutAlignment) {
1267    let window_adapter = component.window_adapter();
1268    let mut cells = Vec::with_capacity(box_layout.elems.len());
1269    for cell in &box_layout.elems {
1270        if cell.element.borrow().repeated.is_some() {
1271            // Collect all repeated elements
1272            let component_vec = repeater_instances(component, &cell.element);
1273            if let Some(ri) = repeater_indices.as_mut() {
1274                ri.push(cells.len() as _);
1275                ri.push(component_vec.len() as _);
1276            }
1277            cells.extend(
1278                component_vec
1279                    .iter()
1280                    .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1281            );
1282        } else {
1283            // Collect non repeated elements
1284            let cross_axis = cross_axis_size_for_cell(&cell.element, orientation, cross_axis_size);
1285            let mut layout_info = get_layout_info_with_constraint(
1286                &cell.element,
1287                component,
1288                &window_adapter,
1289                orientation,
1290                cross_axis,
1291            );
1292            clamp_wrapping_flex_cross_preferred(
1293                &mut layout_info,
1294                &cell.element,
1295                box_layout,
1296                orientation,
1297                component,
1298                &expr_eval,
1299                local_context,
1300                available_cross,
1301            );
1302            fill_layout_info_constraints(
1303                &mut layout_info,
1304                &cell.constraints,
1305                orientation,
1306                &expr_eval,
1307            );
1308            cells.push(core_layout::LayoutItemInfo { constraint: layout_info });
1309        }
1310    }
1311    let alignment = box_layout
1312        .geometry
1313        .alignment
1314        .as_ref()
1315        .map(|nr| {
1316            eval::load_property(component, &nr.element(), nr.name())
1317                .unwrap()
1318                .try_into()
1319                .unwrap_or_default()
1320        })
1321        .unwrap_or_default();
1322    (cells, alignment)
1323}
1324
1325/// When a wrapping FlexboxLayout is a cell of a perpendicular box layout (its
1326/// main axis is the parent's cross axis), a non-stretch parent gives the cell
1327/// its `preferred` cross size. The flex's plain preferred is the compact
1328/// sqrt-area "square" (it wraps), but with room it should fill a single line
1329/// and only wrap when the available cross size can't hold it. Clamp the
1330/// preferred to `min(available, unwrapped)`, so the height it is given equals
1331/// the height its width was computed at: no wrap when tall, no overlap with
1332/// siblings. Only at solve time (`available_cross` is `Some`); during
1333/// layout-info aggregation the sqrt preferred is kept so the flex can still
1334/// size a window.
1335#[allow(clippy::too_many_arguments)]
1336fn clamp_wrapping_flex_cross_preferred(
1337    layout_info: &mut core_layout::LayoutInfo,
1338    elem: &ElementRc,
1339    box_layout: &i_slint_compiler::layout::BoxLayout,
1340    orientation: Orientation,
1341    component: InstanceRef,
1342    expr_eval: &impl Fn(&NamedReference) -> f32,
1343    local_context: &mut EvalLocalContext,
1344    available_cross: Option<f32>,
1345) {
1346    let Some(available) = available_cross else { return };
1347    if orientation == box_layout.orientation {
1348        return;
1349    }
1350    let Some(fl) = i_slint_compiler::layout::FlexboxLayout::from_element(elem) else { return };
1351
1352    // The flex's main axis must be the parent's cross axis, and it must wrap.
1353    let direction = flexbox_layout_direction(&fl, local_context);
1354    let main_is_cross = matches!(
1355        (direction, orientation),
1356        (FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse, Orientation::Horizontal)
1357            | (
1358                FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
1359                Orientation::Vertical
1360            )
1361    );
1362    if !main_is_cross {
1363        return;
1364    }
1365    let flex_wrap =
1366        fl.flex_wrap.as_ref().map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
1367            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1368        });
1369    if matches!(flex_wrap, i_slint_core::items::FlexboxLayoutWrap::NoWrap) {
1370        return;
1371    }
1372
1373    let (cells_h, cells_v, flex_props, _ri) =
1374        flexbox_layout_data(&fl, component, expr_eval, local_context, None, None);
1375    let (cells, padding, spacing) = match orientation {
1376        Orientation::Horizontal => {
1377            let (padding, spacing) =
1378                padding_and_spacing(&fl.geometry, Orientation::Horizontal, expr_eval);
1379            (cells_h, padding, spacing)
1380        }
1381        Orientation::Vertical => {
1382            let (padding, spacing) =
1383                padding_and_spacing(&fl.geometry, Orientation::Vertical, expr_eval);
1384            (cells_v, padding, spacing)
1385        }
1386    };
1387    let unwrapped = core_layout::flexbox_layout_unwrapped_main(
1388        Slice::from(cells.as_slice()),
1389        Slice::from(flex_props.as_slice()),
1390        spacing,
1391        &padding,
1392    );
1393    layout_info.preferred = available.min(unwrapped);
1394}
1395
1396pub(crate) fn fill_layout_info_constraints(
1397    layout_info: &mut core_layout::LayoutInfo,
1398    constraints: &LayoutConstraints,
1399    orientation: Orientation,
1400    expr_eval: &impl Fn(&NamedReference) -> f32,
1401) {
1402    let is_percent =
1403        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
1404
1405    match orientation {
1406        Orientation::Horizontal => {
1407            if let Some(e) = constraints.min_width.as_ref() {
1408                if !is_percent(e) {
1409                    layout_info.min = expr_eval(e)
1410                } else {
1411                    layout_info.min_percent = expr_eval(e)
1412                }
1413            }
1414            if let Some(e) = constraints.max_width.as_ref() {
1415                if !is_percent(e) {
1416                    layout_info.max = expr_eval(e)
1417                } else {
1418                    layout_info.max_percent = expr_eval(e)
1419                }
1420            }
1421            if let Some(e) = constraints.preferred_width.as_ref() {
1422                layout_info.preferred = expr_eval(e);
1423            }
1424            if let Some(e) = constraints.horizontal_stretch.as_ref() {
1425                layout_info.stretch = expr_eval(e);
1426            }
1427        }
1428        Orientation::Vertical => {
1429            if let Some(e) = constraints.min_height.as_ref() {
1430                if !is_percent(e) {
1431                    layout_info.min = expr_eval(e)
1432                } else {
1433                    layout_info.min_percent = expr_eval(e)
1434                }
1435            }
1436            if let Some(e) = constraints.max_height.as_ref() {
1437                if !is_percent(e) {
1438                    layout_info.max = expr_eval(e)
1439                } else {
1440                    layout_info.max_percent = expr_eval(e)
1441                }
1442            }
1443            if let Some(e) = constraints.preferred_height.as_ref() {
1444                layout_info.preferred = expr_eval(e);
1445            }
1446            if let Some(e) = constraints.vertical_stretch.as_ref() {
1447                layout_info.stretch = expr_eval(e);
1448            }
1449        }
1450    }
1451}
1452
1453/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
1454pub(crate) fn get_layout_info(
1455    elem: &ElementRc,
1456    component: InstanceRef,
1457    window_adapter: &Rc<dyn WindowAdapter>,
1458    orientation: Orientation,
1459) -> core_layout::LayoutInfo {
1460    get_layout_info_with_constraint(elem, component, window_adapter, orientation, None)
1461}
1462
1463pub(crate) fn get_layout_info_with_constraint(
1464    elem: &ElementRc,
1465    component: InstanceRef,
1466    window_adapter: &Rc<dyn WindowAdapter>,
1467    orientation: Orientation,
1468    cross_axis_constraint: Option<f32>,
1469) -> core_layout::LayoutInfo {
1470    // With a constraint and a parameterized layout-info function on the
1471    // cell, call it instead of reading the cell's perpendicular property.
1472    // Use the inherited lookup so component-instance cells pick up a
1473    // `layoutinfo-{v,h}-with-constraint` declared on the base component's
1474    // root_element (the cell itself doesn't carry the binding).
1475    let parameterized_nr = if cross_axis_constraint.is_some() {
1476        match orientation {
1477            Orientation::Vertical => elem.borrow().inherited_layout_info_v_with_constraint(),
1478            Orientation::Horizontal => elem.borrow().inherited_layout_info_h_with_constraint(),
1479        }
1480    } else {
1481        None
1482    };
1483    if let Some(nr) = parameterized_nr {
1484        let arg = cross_axis_constraint.unwrap();
1485        let v = eval::call_function(
1486            &eval::ComponentInstance::InstanceRef(component),
1487            &nr.element(),
1488            nr.name(),
1489            vec![Value::Number(arg as f64)],
1490        )
1491        .expect("layoutinfo-{h,v}-with-constraint is a synthesized pure function");
1492        return v.try_into().unwrap();
1493    }
1494
1495    let elem = elem.borrow();
1496    if let Some(nr) = elem.layout_info_prop(orientation) {
1497        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1498    } else {
1499        let item = &component
1500            .description
1501            .items
1502            .get(elem.id.as_str())
1503            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
1504        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
1505
1506        unsafe {
1507            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
1508                to_runtime(orientation),
1509                cross_axis_constraint.unwrap_or(-1.),
1510                window_adapter,
1511                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
1512            )
1513        }
1514    }
1515}
1516
1517/// Decide the cross-axis constraint to forward to a cell's perpendicular
1518/// layout-info call. Returns `Some` only when the parent supplied the cross
1519/// dimension and the cell consumes it: a height-for-width cell on the vertical
1520/// pass (wrapped Text/Image, or a `layoutinfo-v-with-constraint` component), or a
1521/// width-for-height cell on the horizontal pass (a `layoutinfo-h-with-constraint`
1522/// component, e.g. a column FlexboxLayout).
1523fn cross_axis_size_for_cell(
1524    elem: &ElementRc,
1525    orientation: Orientation,
1526    parent_cross_axis_size: Option<f32>,
1527) -> Option<f32> {
1528    let cross = parent_cross_axis_size?;
1529    let elem_b = elem.borrow();
1530    if orientation == Orientation::Horizontal {
1531        // Width-for-height cells (e.g. a column FlexboxLayout) carry a synthesized
1532        // layoutinfo-h-with-constraint; forward the cross size so they compute their
1533        // width from it instead of reading self.height (which would cycle through
1534        // the parent's vertical pass).
1535        return elem_b.inherited_layout_info_h_with_constraint().is_some().then_some(cross);
1536    }
1537    if elem_b.layout_info_v_with_constraint.is_some() {
1538        return Some(cross);
1539    }
1540    // For builtin height-for-width items, the existing VTable cross_axis_constraint
1541    // parameter mechanism is what consumes the value; conservatively
1542    // forward it for any element without its own layoutinfo-v property
1543    // (i.e. anything that ends up calling the builtin VTable).
1544    if elem_b.layout_info_prop(Orientation::Vertical).is_none() {
1545        return Some(cross);
1546    }
1547    None
1548}