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            cells_v.extend(component_vec.iter().map(|x| {
697                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Vertical), None)
698            }));
699            static_children.resize_with(static_children.len() + component_vec.len(), || None);
700            repeater_instance_vecs.push(component_vec);
701        } else {
702            // Dispatch via `layoutinfo-h-with-constraint` for cells that
703            // have one, avoiding the `self.height` read that would cycle.
704            // Use the height-override when set, else `f32::MAX` so the
705            // runtime treats it as "no wrap needed" — gives the natural
706            // max-cell-width result rather than the heuristic.
707            let h_constraint = layout_elem
708                .item
709                .element
710                .borrow()
711                .inherited_layout_info_h_with_constraint()
712                .is_some()
713                .then(|| height_override.unwrap_or(f32::MAX));
714            let mut layout_info_h = get_layout_info_with_constraint(
715                &layout_elem.item.element,
716                component,
717                &window_adapter,
718                Orientation::Horizontal,
719                h_constraint,
720            );
721            fill_layout_info_constraints(
722                &mut layout_info_h,
723                &layout_elem.item.constraints,
724                Orientation::Horizontal,
725                expr_eval,
726            );
727            // Don't collect cells_v in the first pass — it may trigger a circular
728            // dependency for height-for-width items (Text with wrap, Image).
729            // The second pass fills in cells_v with the width constraint.
730            let flex_grow = layout_elem.flex_grow.as_ref().map(&expr_eval).unwrap_or(0.0);
731            let flex_shrink = layout_elem.flex_shrink.as_ref().map(&expr_eval).unwrap_or(1.0);
732            let flex_basis = layout_elem.flex_basis.as_ref().map(&expr_eval).unwrap_or(-1.0);
733            let align_self = layout_elem
734                .align_self
735                .as_ref()
736                .map(|nr| {
737                    eval::load_property(component, &nr.element(), nr.name())
738                        .unwrap()
739                        .try_into()
740                        .unwrap()
741                })
742                .unwrap_or(i_slint_core::items::FlexboxLayoutAlignSelf::default());
743            let order = layout_elem.order.as_ref().map(expr_eval).unwrap_or(0.0) as i32;
744            cells_h.push(core_layout::FlexboxLayoutItemInfo {
745                constraint: layout_info_h,
746                flex_grow,
747                flex_shrink,
748                flex_basis,
749                flex_align_self: align_self,
750                flex_order: order,
751            });
752            // Placeholder for cells_v — filled in second pass
753            cells_v.push(core_layout::FlexboxLayoutItemInfo::default());
754            static_children.push(Some(ChildInfo {
755                flex_grow,
756                flex_shrink,
757                flex_basis,
758                flex_align_self: align_self,
759                flex_order: order,
760            }));
761        }
762    }
763
764    // Second pass: collect vertical layout_info with a width constraint.
765    // For column direction, use the container width (items get stretched to it).
766    // Otherwise use the item's horizontal preferred size.
767    let mut cell_idx = 0usize;
768    let mut repeater_idx = 0usize;
769    for layout_elem in &flexbox_layout.elems {
770        if layout_elem.item.element.borrow().repeated.is_some() {
771            // Re-measure each height-for-width repeated instance at the width
772            // constraint (container width for a column flex), mirroring the
773            // static branch below — the first pass filled cells_v at the
774            // instance's preferred width (single line). Keep the flex props set
775            // in the first pass; only overwrite the vertical constraint.
776            let rep_root =
777                layout_elem.item.element.borrow().base_type.as_component().root_element.clone();
778            let is_height_for_width =
779                rep_root.borrow().inherited_layout_info_v_with_constraint().is_some();
780            let component_vec = &repeater_instance_vecs[repeater_idx];
781            repeater_idx += 1;
782            for instance in component_vec {
783                if is_height_for_width {
784                    let width_constraint = width_override
785                        .unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
786                    generativity::make_guard!(guard);
787                    let unerased = instance.unerase(guard);
788                    let instance_ref = unerased.borrow_instance();
789                    let mut layout_info_v = get_layout_info_with_constraint(
790                        &rep_root,
791                        instance_ref,
792                        &window_adapter,
793                        Orientation::Vertical,
794                        Some(width_constraint),
795                    );
796                    // The constraints' NamedReferences point to elements inside
797                    // the repeated sub-component, so evaluate them in that
798                    // instance's context, not the outer component's.
799                    let instance_expr_eval = |nr: &NamedReference| -> f32 {
800                        eval::load_property(instance_ref, &nr.element(), nr.name())
801                            .unwrap()
802                            .try_into()
803                            .unwrap()
804                    };
805                    fill_layout_info_constraints(
806                        &mut layout_info_v,
807                        &layout_elem.item.constraints,
808                        Orientation::Vertical,
809                        &instance_expr_eval,
810                    );
811                    cells_v[cell_idx].constraint = layout_info_v;
812                }
813                cell_idx += 1;
814            }
815        } else {
816            let width_constraint =
817                width_override.unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
818            let mut layout_info_v = get_layout_info_with_constraint(
819                &layout_elem.item.element,
820                component,
821                &window_adapter,
822                Orientation::Vertical,
823                Some(width_constraint),
824            );
825            fill_layout_info_constraints(
826                &mut layout_info_v,
827                &layout_elem.item.constraints,
828                Orientation::Vertical,
829                expr_eval,
830            );
831            if let Some(info) = &static_children[cell_idx] {
832                cells_v[cell_idx] = core_layout::FlexboxLayoutItemInfo {
833                    constraint: layout_info_v,
834                    flex_grow: info.flex_grow,
835                    flex_shrink: info.flex_shrink,
836                    flex_basis: info.flex_basis,
837                    flex_align_self: info.flex_align_self,
838                    flex_order: info.flex_order,
839                };
840            }
841            cell_idx += 1;
842        }
843    }
844
845    (cells_h, cells_v, repeated_indices)
846}
847
848/// Determine the evaluated padding and spacing values from the layout geometry
849fn padding_and_spacing(
850    layout_geometry: &LayoutGeometry,
851    orientation: Orientation,
852    expr_eval: &impl Fn(&NamedReference) -> f32,
853) -> (core_layout::Padding, f32) {
854    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
855    let (begin, end) = layout_geometry.padding.begin_end(orientation);
856    let padding =
857        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
858    (padding, spacing)
859}
860
861fn repeater_instances(
862    component: InstanceRef,
863    elem: &ElementRc,
864) -> Vec<crate::dynamic_item_tree::DynamicComponentVRc> {
865    generativity::make_guard!(guard);
866    let rep =
867        crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard);
868    rep.0.as_ref().track_instance_changes();
869    rep.0.as_ref().instances_vec()
870}
871
872fn grid_layout_input_data(
873    grid_layout: &i_slint_compiler::layout::GridLayout,
874    ctx: &EvalLocalContext,
875    repeater_steps: &[u32],
876) -> Vec<GridLayoutInputData> {
877    let component = ctx.component_instance;
878    let mut result = Vec::with_capacity(grid_layout.elems.len());
879    let mut after_repeater_in_same_row = false;
880    let mut new_row = true;
881    let mut repeater_idx = 0usize;
882    for elem in grid_layout.elems.iter() {
883        let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr {
884            RowColExpr::Literal(value) => *value as f32,
885            RowColExpr::Auto => i_slint_common::ROW_COL_AUTO,
886            RowColExpr::Named(nr) => {
887                // we could check for out-of-bounds here, but organize_grid_layout will also do it
888                eval::load_property(component, &nr.element(), nr.name())
889                    .unwrap()
890                    .try_into()
891                    .unwrap()
892            }
893        };
894
895        let cell_new_row = elem.cell.borrow().new_row;
896        if cell_new_row {
897            after_repeater_in_same_row = false;
898        }
899        if elem.item.element.borrow().repeated.is_some() {
900            let component_vec = repeater_instances(component, &elem.item.element);
901            new_row = cell_new_row;
902            for erased_sub_comp in &component_vec {
903                // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component
904                generativity::make_guard!(guard);
905                let sub_comp = erased_sub_comp.as_pin_ref();
906                let sub_instance_ref =
907                    unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) };
908
909                if let Some(children) = elem.cell.borrow().child_items.as_ref() {
910                    // Repeated row
911                    new_row = true;
912                    let start_count = result.len();
913
914                    // Single pass in declaration order: push statics and inner-repeater
915                    // auto-cells interleaved so that column assignments match template order.
916                    // (A two-pass approach that appended all inner-repeater cells after all
917                    // statics would produce wrong column assignments, and only tracking the
918                    // last Repeated entry would miss earlier conditionals/for-loops.)
919                    for child_template in children {
920                        match child_template {
921                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
922                                let (row_val, col_val, rowspan_val, colspan_val) = {
923                                    let element_ref = child_item.element.borrow();
924                                    let child_cell =
925                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
926                                    (
927                                        eval_or_default(&child_cell.row_expr, sub_instance_ref),
928                                        eval_or_default(&child_cell.col_expr, sub_instance_ref),
929                                        eval_or_default(&child_cell.rowspan_expr, sub_instance_ref),
930                                        eval_or_default(&child_cell.colspan_expr, sub_instance_ref),
931                                    )
932                                };
933                                result.push(GridLayoutInputData {
934                                    new_row,
935                                    col: col_val,
936                                    row: row_val,
937                                    colspan: colspan_val,
938                                    rowspan: rowspan_val,
939                                });
940                                new_row = false;
941                            }
942                            i_slint_compiler::layout::RowChildTemplate::Repeated {
943                                repeated_element,
944                                ..
945                            } => {
946                                // colspan/rowspan live on the inner sub-component's root,
947                                // evaluated per inner instance.
948                                let inner_root = repeated_element
949                                    .borrow()
950                                    .base_type
951                                    .as_component()
952                                    .root_element
953                                    .clone();
954                                let (rowspan_expr, colspan_expr) = {
955                                    let element_ref = inner_root.borrow();
956                                    let child_cell =
957                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
958                                    (
959                                        child_cell.rowspan_expr.clone(),
960                                        child_cell.colspan_expr.clone(),
961                                    )
962                                };
963                                let inner_instances =
964                                    repeater_instances(sub_instance_ref, repeated_element);
965                                for (i, erased_inner) in inner_instances.iter().enumerate() {
966                                    generativity::make_guard!(inner_guard);
967                                    let inner_comp = erased_inner.as_pin_ref();
968                                    let inner_instance_ref = unsafe {
969                                        InstanceRef::from_pin_ref(inner_comp.borrow(), inner_guard)
970                                    };
971                                    result.push(GridLayoutInputData {
972                                        new_row: i == 0 && new_row,
973                                        rowspan: eval_or_default(&rowspan_expr, inner_instance_ref),
974                                        colspan: eval_or_default(&colspan_expr, inner_instance_ref),
975                                        ..Default::default()
976                                    });
977                                }
978                                if !inner_instances.is_empty() {
979                                    new_row = false;
980                                }
981                            }
982                        }
983                    }
984                    // Pad to match max step count for this repeater (handles jagged arrays)
985                    let cells_pushed = result.len() - start_count;
986                    let expected_step =
987                        repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
988                    for _ in cells_pushed..expected_step {
989                        result.push(GridLayoutInputData::default());
990                    }
991                } else {
992                    // Single repeated item
993                    let cell = elem.cell.borrow();
994                    let row = eval_or_default(&cell.row_expr, sub_instance_ref);
995                    let col = eval_or_default(&cell.col_expr, sub_instance_ref);
996                    let rowspan = eval_or_default(&cell.rowspan_expr, sub_instance_ref);
997                    let colspan = eval_or_default(&cell.colspan_expr, sub_instance_ref);
998                    result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
999                    new_row = false;
1000                }
1001            }
1002            repeater_idx += 1;
1003            after_repeater_in_same_row = true;
1004        } else {
1005            let new_row =
1006                if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row };
1007            let row = eval_or_default(&elem.cell.borrow().row_expr, component);
1008            let col = eval_or_default(&elem.cell.borrow().col_expr, component);
1009            let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component);
1010            let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component);
1011            result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
1012        }
1013    }
1014    result
1015}
1016
1017/// Count the actual runtime children for a repeated row.
1018/// For rows without inner repeaters, this is just the child_items count.
1019/// For rows with inner repeaters, the Repeated template expands to actual inner instances.
1020fn row_runtime_child_count(
1021    child_items: &[i_slint_compiler::layout::RowChildTemplate],
1022    sub_instance_ref: InstanceRef,
1023) -> usize {
1024    let mut count = 0;
1025    for child in child_items {
1026        if let Some(repeated_element) = child.repeated_element() {
1027            count += repeater_instances(sub_instance_ref, repeated_element).len();
1028        } else {
1029            count += 1;
1030        }
1031    }
1032    count
1033}
1034
1035fn grid_repeater_indices(
1036    grid_layout: &i_slint_compiler::layout::GridLayout,
1037    ctx: &mut EvalLocalContext,
1038    repeater_steps: &[u32],
1039) -> Vec<u32> {
1040    let component = ctx.component_instance;
1041    let mut repeater_indices = Vec::new();
1042    let mut num_cells = 0;
1043    let mut step_idx = 0;
1044    for elem in grid_layout.elems.iter() {
1045        if elem.item.element.borrow().repeated.is_some() {
1046            let component_vec = repeater_instances(component, &elem.item.element);
1047            repeater_indices.push(num_cells as _);
1048            repeater_indices.push(component_vec.len() as _);
1049            let item_count = repeater_steps[step_idx] as usize;
1050            num_cells += component_vec.len() * item_count;
1051            step_idx += 1;
1052        } else {
1053            num_cells += 1;
1054        }
1055    }
1056    repeater_indices
1057}
1058
1059fn grid_repeater_steps(
1060    grid_layout: &i_slint_compiler::layout::GridLayout,
1061    ctx: &mut EvalLocalContext,
1062) -> Vec<u32> {
1063    let component = ctx.component_instance;
1064    let mut repeater_steps = Vec::new();
1065    for elem in grid_layout.elems.iter() {
1066        if elem.item.element.borrow().repeated.is_some() {
1067            let item_count = match &elem.cell.borrow().child_items {
1068                Some(ci)
1069                    if ci.iter().any(i_slint_compiler::layout::RowChildTemplate::is_repeated) =>
1070                {
1071                    // Compute max runtime count across all instances (padding with empty cells didn't happen yet)
1072                    let component_vec = repeater_instances(component, &elem.item.element);
1073                    component_vec
1074                        .iter()
1075                        .map(|sub| {
1076                            generativity::make_guard!(guard);
1077                            let sub_pin = sub.as_pin_ref();
1078                            let sub_ref =
1079                                unsafe { InstanceRef::from_pin_ref(sub_pin.borrow(), guard) };
1080                            row_runtime_child_count(ci, sub_ref)
1081                        })
1082                        .max()
1083                        .unwrap_or(0)
1084                }
1085                Some(ci) => ci.len(),
1086                None => 1,
1087            };
1088            repeater_steps.push(item_count as u32);
1089        }
1090    }
1091    repeater_steps
1092}
1093
1094fn grid_layout_constraints(
1095    grid_layout: &i_slint_compiler::layout::GridLayout,
1096    orientation: Orientation,
1097    ctx: &mut EvalLocalContext,
1098    repeater_steps: &[u32],
1099    cross_axis_size: Option<f32>,
1100) -> Vec<core_layout::LayoutItemInfo> {
1101    let component = ctx.component_instance;
1102    let expr_eval = |nr: &NamedReference| -> f32 {
1103        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1104    };
1105    let mut constraints = Vec::with_capacity(grid_layout.elems.len());
1106
1107    let mut repeater_idx = 0usize;
1108    for layout_elem in grid_layout.elems.iter() {
1109        if layout_elem.item.element.borrow().repeated.is_some() {
1110            let component_vec = repeater_instances(component, &layout_elem.item.element);
1111            let child_items = layout_elem.cell.borrow().child_items.clone();
1112            let has_children = child_items.is_some();
1113            if has_children {
1114                // Repeated row
1115                let ci = child_items.as_ref().unwrap();
1116                let step = repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
1117                for sub_comp in &component_vec {
1118                    let per_instance_start = constraints.len();
1119                    // Evaluate constraints in the context of the repeated sub-component
1120                    generativity::make_guard!(guard);
1121                    let sub_pin = sub_comp.as_pin_ref();
1122                    let sub_borrow = sub_pin.borrow();
1123                    let sub_instance_ref = unsafe { InstanceRef::from_pin_ref(sub_borrow, guard) };
1124                    let expr_eval = |nr: &NamedReference| -> f32 {
1125                        eval::load_property(sub_instance_ref, &nr.element(), nr.name())
1126                            .unwrap()
1127                            .try_into()
1128                            .unwrap()
1129                    };
1130
1131                    // Iterate over the child templates: static children get their layout info
1132                    // from the Row sub-component; nested repeater children get theirs from the
1133                    // inner repeater instances.
1134                    for child_template in ci.iter() {
1135                        match child_template {
1136                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
1137                                let mut layout_info = crate::eval_layout::get_layout_info(
1138                                    &child_item.element,
1139                                    sub_instance_ref,
1140                                    &sub_instance_ref.window_adapter(),
1141                                    orientation,
1142                                );
1143                                fill_layout_info_constraints(
1144                                    &mut layout_info,
1145                                    &child_item.constraints,
1146                                    orientation,
1147                                    &expr_eval,
1148                                );
1149                                constraints
1150                                    .push(core_layout::LayoutItemInfo { constraint: layout_info });
1151                            }
1152                            i_slint_compiler::layout::RowChildTemplate::Repeated {
1153                                item: child_item,
1154                                repeated_element,
1155                            } => {
1156                                // Get the inner repeater instances from within this Row instance
1157                                let inner_instances =
1158                                    repeater_instances(sub_instance_ref, repeated_element);
1159                                for inner_comp in &inner_instances {
1160                                    let inner_pin = inner_comp.as_pin_ref();
1161                                    let mut layout_info =
1162                                        inner_pin.layout_item_info(to_runtime(orientation), None);
1163                                    // Constraints' NamedReferences point to elements inside the
1164                                    // inner repeated component, so evaluate in that context.
1165                                    generativity::make_guard!(inner_guard);
1166                                    let inner_borrow = inner_pin.borrow();
1167                                    let inner_instance_ref = unsafe {
1168                                        InstanceRef::from_pin_ref(inner_borrow, inner_guard)
1169                                    };
1170                                    let inner_expr_eval = |nr: &NamedReference| -> f32 {
1171                                        eval::load_property(
1172                                            inner_instance_ref,
1173                                            &nr.element(),
1174                                            nr.name(),
1175                                        )
1176                                        .unwrap()
1177                                        .try_into()
1178                                        .unwrap()
1179                                    };
1180                                    fill_layout_info_constraints(
1181                                        &mut layout_info.constraint,
1182                                        &child_item.constraints,
1183                                        orientation,
1184                                        &inner_expr_eval,
1185                                    );
1186                                    constraints.push(layout_info);
1187                                }
1188                            }
1189                        }
1190                    }
1191                    // Pad this instance to the step size (handles jagged arrays where
1192                    // inner repeaters have different lengths across outer Row instances).
1193                    let pushed = constraints.len() - per_instance_start;
1194                    for _ in pushed..step {
1195                        constraints.push(core_layout::LayoutItemInfo::default());
1196                    }
1197                }
1198            } else {
1199                // Single repeated item
1200                constraints.extend(
1201                    component_vec
1202                        .iter()
1203                        .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1204                );
1205            }
1206            repeater_idx += 1;
1207        } else {
1208            let cross_axis =
1209                cross_axis_size_for_cell(&layout_elem.item.element, orientation, cross_axis_size);
1210            let mut layout_info = get_layout_info_with_constraint(
1211                &layout_elem.item.element,
1212                component,
1213                &component.window_adapter(),
1214                orientation,
1215                cross_axis,
1216            );
1217            fill_layout_info_constraints(
1218                &mut layout_info,
1219                &layout_elem.item.constraints,
1220                orientation,
1221                &expr_eval,
1222            );
1223            constraints.push(core_layout::LayoutItemInfo { constraint: layout_info });
1224        }
1225    }
1226    constraints
1227}
1228
1229/// Collect all elements in this layout and store the LayoutItemInfo of it for further calculation
1230fn box_layout_data(
1231    box_layout: &i_slint_compiler::layout::BoxLayout,
1232    orientation: Orientation,
1233    component: InstanceRef,
1234    expr_eval: &impl Fn(&NamedReference) -> f32,
1235    mut repeater_indices: Option<&mut Vec<u32>>,
1236    cross_axis_size: Option<f32>,
1237    local_context: &mut EvalLocalContext,
1238    available_cross: Option<f32>,
1239) -> (Vec<core_layout::LayoutItemInfo>, i_slint_core::items::LayoutAlignment) {
1240    let window_adapter = component.window_adapter();
1241    let mut cells = Vec::with_capacity(box_layout.elems.len());
1242    for cell in &box_layout.elems {
1243        if cell.element.borrow().repeated.is_some() {
1244            // Collect all repeated elements
1245            let component_vec = repeater_instances(component, &cell.element);
1246            if let Some(ri) = repeater_indices.as_mut() {
1247                ri.push(cells.len() as _);
1248                ri.push(component_vec.len() as _);
1249            }
1250            cells.extend(
1251                component_vec
1252                    .iter()
1253                    .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1254            );
1255        } else {
1256            // Collect non repeated elements
1257            let cross_axis = cross_axis_size_for_cell(&cell.element, orientation, cross_axis_size);
1258            let mut layout_info = get_layout_info_with_constraint(
1259                &cell.element,
1260                component,
1261                &window_adapter,
1262                orientation,
1263                cross_axis,
1264            );
1265            clamp_wrapping_flex_cross_preferred(
1266                &mut layout_info,
1267                &cell.element,
1268                box_layout,
1269                orientation,
1270                component,
1271                &expr_eval,
1272                local_context,
1273                available_cross,
1274            );
1275            fill_layout_info_constraints(
1276                &mut layout_info,
1277                &cell.constraints,
1278                orientation,
1279                &expr_eval,
1280            );
1281            cells.push(core_layout::LayoutItemInfo { constraint: layout_info });
1282        }
1283    }
1284    let alignment = box_layout
1285        .geometry
1286        .alignment
1287        .as_ref()
1288        .map(|nr| {
1289            eval::load_property(component, &nr.element(), nr.name())
1290                .unwrap()
1291                .try_into()
1292                .unwrap_or_default()
1293        })
1294        .unwrap_or_default();
1295    (cells, alignment)
1296}
1297
1298/// When a wrapping FlexboxLayout is a cell of a perpendicular box layout (its
1299/// main axis is the parent's cross axis), a non-stretch parent gives the cell
1300/// its `preferred` cross size. The flex's plain preferred is the compact
1301/// sqrt-area "square" (it wraps), but with room it should fill a single line
1302/// and only wrap when the available cross size can't hold it. Clamp the
1303/// preferred to `min(available, unwrapped)`, so the height it is given equals
1304/// the height its width was computed at: no wrap when tall, no overlap with
1305/// siblings. Only at solve time (`available_cross` is `Some`); during
1306/// layout-info aggregation the sqrt preferred is kept so the flex can still
1307/// size a window.
1308#[allow(clippy::too_many_arguments)]
1309fn clamp_wrapping_flex_cross_preferred(
1310    layout_info: &mut core_layout::LayoutInfo,
1311    elem: &ElementRc,
1312    box_layout: &i_slint_compiler::layout::BoxLayout,
1313    orientation: Orientation,
1314    component: InstanceRef,
1315    expr_eval: &impl Fn(&NamedReference) -> f32,
1316    local_context: &mut EvalLocalContext,
1317    available_cross: Option<f32>,
1318) {
1319    let Some(available) = available_cross else { return };
1320    if orientation == box_layout.orientation {
1321        return;
1322    }
1323    let Some(fl) = i_slint_compiler::layout::FlexboxLayout::from_element(elem) else { return };
1324
1325    // The flex's main axis must be the parent's cross axis, and it must wrap.
1326    let direction = flexbox_layout_direction(&fl, local_context);
1327    let main_is_cross = matches!(
1328        (direction, orientation),
1329        (FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse, Orientation::Horizontal)
1330            | (
1331                FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
1332                Orientation::Vertical
1333            )
1334    );
1335    if !main_is_cross {
1336        return;
1337    }
1338    let flex_wrap =
1339        fl.flex_wrap.as_ref().map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
1340            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1341        });
1342    if matches!(flex_wrap, i_slint_core::items::FlexboxLayoutWrap::NoWrap) {
1343        return;
1344    }
1345
1346    let (cells_h, cells_v, _ri) =
1347        flexbox_layout_data(&fl, component, expr_eval, local_context, None, None);
1348    let (cells, padding, spacing) = match orientation {
1349        Orientation::Horizontal => {
1350            let (padding, spacing) =
1351                padding_and_spacing(&fl.geometry, Orientation::Horizontal, expr_eval);
1352            (cells_h, padding, spacing)
1353        }
1354        Orientation::Vertical => {
1355            let (padding, spacing) =
1356                padding_and_spacing(&fl.geometry, Orientation::Vertical, expr_eval);
1357            (cells_v, padding, spacing)
1358        }
1359    };
1360    let unwrapped = core_layout::flexbox_layout_unwrapped_main(
1361        Slice::from(cells.as_slice()),
1362        spacing,
1363        &padding,
1364    );
1365    layout_info.preferred = available.min(unwrapped);
1366}
1367
1368pub(crate) fn fill_layout_info_constraints(
1369    layout_info: &mut core_layout::LayoutInfo,
1370    constraints: &LayoutConstraints,
1371    orientation: Orientation,
1372    expr_eval: &impl Fn(&NamedReference) -> f32,
1373) {
1374    let is_percent =
1375        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
1376
1377    match orientation {
1378        Orientation::Horizontal => {
1379            if let Some(e) = constraints.min_width.as_ref() {
1380                if !is_percent(e) {
1381                    layout_info.min = expr_eval(e)
1382                } else {
1383                    layout_info.min_percent = expr_eval(e)
1384                }
1385            }
1386            if let Some(e) = constraints.max_width.as_ref() {
1387                if !is_percent(e) {
1388                    layout_info.max = expr_eval(e)
1389                } else {
1390                    layout_info.max_percent = expr_eval(e)
1391                }
1392            }
1393            if let Some(e) = constraints.preferred_width.as_ref() {
1394                layout_info.preferred = expr_eval(e);
1395            }
1396            if let Some(e) = constraints.horizontal_stretch.as_ref() {
1397                layout_info.stretch = expr_eval(e);
1398            }
1399        }
1400        Orientation::Vertical => {
1401            if let Some(e) = constraints.min_height.as_ref() {
1402                if !is_percent(e) {
1403                    layout_info.min = expr_eval(e)
1404                } else {
1405                    layout_info.min_percent = expr_eval(e)
1406                }
1407            }
1408            if let Some(e) = constraints.max_height.as_ref() {
1409                if !is_percent(e) {
1410                    layout_info.max = expr_eval(e)
1411                } else {
1412                    layout_info.max_percent = expr_eval(e)
1413                }
1414            }
1415            if let Some(e) = constraints.preferred_height.as_ref() {
1416                layout_info.preferred = expr_eval(e);
1417            }
1418            if let Some(e) = constraints.vertical_stretch.as_ref() {
1419                layout_info.stretch = expr_eval(e);
1420            }
1421        }
1422    }
1423}
1424
1425/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
1426pub(crate) fn get_layout_info(
1427    elem: &ElementRc,
1428    component: InstanceRef,
1429    window_adapter: &Rc<dyn WindowAdapter>,
1430    orientation: Orientation,
1431) -> core_layout::LayoutInfo {
1432    get_layout_info_with_constraint(elem, component, window_adapter, orientation, None)
1433}
1434
1435pub(crate) fn get_layout_info_with_constraint(
1436    elem: &ElementRc,
1437    component: InstanceRef,
1438    window_adapter: &Rc<dyn WindowAdapter>,
1439    orientation: Orientation,
1440    cross_axis_constraint: Option<f32>,
1441) -> core_layout::LayoutInfo {
1442    // With a constraint and a parameterized layout-info function on the
1443    // cell, call it instead of reading the cell's perpendicular property.
1444    // Use the inherited lookup so component-instance cells pick up a
1445    // `layoutinfo-{v,h}-with-constraint` declared on the base component's
1446    // root_element (the cell itself doesn't carry the binding).
1447    let parameterized_nr = if cross_axis_constraint.is_some() {
1448        match orientation {
1449            Orientation::Vertical => elem.borrow().inherited_layout_info_v_with_constraint(),
1450            Orientation::Horizontal => elem.borrow().inherited_layout_info_h_with_constraint(),
1451        }
1452    } else {
1453        None
1454    };
1455    if let Some(nr) = parameterized_nr {
1456        let arg = cross_axis_constraint.unwrap();
1457        let v = eval::call_function(
1458            &eval::ComponentInstance::InstanceRef(component),
1459            &nr.element(),
1460            nr.name(),
1461            vec![Value::Number(arg as f64)],
1462        )
1463        .expect("layoutinfo-{h,v}-with-constraint is a synthesized pure function");
1464        return v.try_into().unwrap();
1465    }
1466
1467    let elem = elem.borrow();
1468    if let Some(nr) = elem.layout_info_prop(orientation) {
1469        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1470    } else {
1471        let item = &component
1472            .description
1473            .items
1474            .get(elem.id.as_str())
1475            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
1476        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
1477
1478        unsafe {
1479            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
1480                to_runtime(orientation),
1481                cross_axis_constraint.unwrap_or(-1.),
1482                window_adapter,
1483                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
1484            )
1485        }
1486    }
1487}
1488
1489/// Decide the cross-axis constraint to forward to a cell's perpendicular
1490/// layout-info call. Returns `Some` only when the parent supplied the cross
1491/// dimension and the cell consumes it: a height-for-width cell on the vertical
1492/// pass (wrapped Text/Image, or a `layoutinfo-v-with-constraint` component), or a
1493/// width-for-height cell on the horizontal pass (a `layoutinfo-h-with-constraint`
1494/// component, e.g. a column FlexboxLayout).
1495fn cross_axis_size_for_cell(
1496    elem: &ElementRc,
1497    orientation: Orientation,
1498    parent_cross_axis_size: Option<f32>,
1499) -> Option<f32> {
1500    let cross = parent_cross_axis_size?;
1501    let elem_b = elem.borrow();
1502    if orientation == Orientation::Horizontal {
1503        // Width-for-height cells (e.g. a column FlexboxLayout) carry a synthesized
1504        // layoutinfo-h-with-constraint; forward the cross size so they compute their
1505        // width from it instead of reading self.height (which would cycle through
1506        // the parent's vertical pass).
1507        return elem_b.inherited_layout_info_h_with_constraint().is_some().then_some(cross);
1508    }
1509    if elem_b.layout_info_v_with_constraint.is_some() {
1510        return Some(cross);
1511    }
1512    // For builtin height-for-width items, the existing VTable cross_axis_constraint
1513    // parameter mechanism is what consumes the value; conservatively
1514    // forward it for any element without its own layoutinfo-v property
1515    // (i.e. anything that ends up calling the builtin VTable).
1516    if elem_b.layout_info_prop(Orientation::Vertical).is_none() {
1517        return Some(cross);
1518    }
1519    None
1520}