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