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