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 (cells, alignment) =
81        box_layout_data(box_layout, orientation, component, &expr_eval, None, cross_axis_size);
82    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
83    if orientation == box_layout.orientation {
84        core_layout::box_layout_info(Slice::from(cells.as_slice()), spacing, &padding, alignment)
85    } else {
86        core_layout::box_layout_info_ortho(Slice::from(cells.as_slice()), &padding)
87    }
88    .into()
89}
90
91pub(crate) fn organize_grid_layout(
92    layout: &GridLayout,
93    local_context: &mut EvalLocalContext,
94) -> Value {
95    let repeater_steps = grid_repeater_steps(layout, local_context);
96    let cells = grid_layout_input_data(layout, local_context, &repeater_steps);
97    let repeater_indices = grid_repeater_indices(layout, local_context, &repeater_steps);
98    if let Some(buttons_roles) = &layout.dialog_button_roles {
99        let roles = buttons_roles
100            .iter()
101            .map(|r| DialogButtonRole::from_str(r).unwrap())
102            .collect::<Vec<_>>();
103        core_layout::organize_dialog_button_layout(
104            Slice::from_slice(cells.as_slice()),
105            Slice::from_slice(roles.as_slice()),
106        )
107        .into()
108    } else {
109        core_layout::organize_grid_layout(
110            Slice::from_slice(cells.as_slice()),
111            Slice::from_slice(repeater_indices.as_slice()),
112            Slice::from_slice(repeater_steps.as_slice()),
113        )
114        .into()
115    }
116}
117
118pub(crate) fn solve_grid_layout(
119    organized_data: &GridLayoutOrganizedData,
120    grid_layout: &GridLayout,
121    orientation: Orientation,
122    local_context: &mut EvalLocalContext,
123) -> Value {
124    let component = local_context.component_instance;
125    let expr_eval = |nr: &NamedReference| -> f32 {
126        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
127    };
128    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
129    let repeater_indices = grid_repeater_indices(grid_layout, local_context, &repeater_steps);
130    let constraints =
131        grid_layout_constraints(grid_layout, orientation, local_context, &repeater_steps, None);
132
133    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
134    let size_ref = grid_layout.geometry.rect.size_reference(orientation);
135
136    let data = core_layout::GridLayoutData {
137        size: size_ref.map(expr_eval).unwrap_or(0.),
138        spacing,
139        padding,
140        organized_data: organized_data.clone(),
141    };
142
143    core_layout::solve_grid_layout(
144        &data,
145        Slice::from_slice(constraints.as_slice()),
146        to_runtime(orientation),
147        Slice::from_slice(repeater_indices.as_slice()),
148        Slice::from_slice(repeater_steps.as_slice()),
149    )
150    .into()
151}
152
153pub(crate) fn solve_box_layout(
154    box_layout: &BoxLayout,
155    orientation: Orientation,
156    local_context: &mut EvalLocalContext,
157) -> Value {
158    let component = local_context.component_instance;
159    let expr_eval = |nr: &NamedReference| -> f32 {
160        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
161    };
162
163    let mut repeated_indices = Vec::new();
164    // For a horizontal layout's main (width) pass, supply the layout's real cross
165    // size (content height) so width-for-height children (e.g. a column
166    // FlexboxLayout) compute their width from it via layoutinfo-h-with-constraint,
167    // instead of reading self.height and cycling through our own vertical pass.
168    let cross_axis_size = (orientation == box_layout.orientation
169        && orientation == Orientation::Horizontal
170        && box_layout
171            .elems
172            .iter()
173            .any(|c| c.element.borrow().inherited_layout_info_h_with_constraint().is_some()))
174    .then(|| {
175        let cross = orientation.orthogonal();
176        box_layout.geometry.rect.size_reference(cross).map(&expr_eval).map(|s| {
177            let (pad, _) = padding_and_spacing(&box_layout.geometry, cross, &expr_eval);
178            s - pad.begin - pad.end
179        })
180    })
181    .flatten();
182    let (cells, alignment) = box_layout_data(
183        box_layout,
184        orientation,
185        component,
186        &expr_eval,
187        Some(&mut repeated_indices),
188        cross_axis_size,
189    );
190    let (padding, spacing) = padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
191    let size = box_layout.geometry.rect.size_reference(orientation).map(&expr_eval).unwrap_or(0.);
192    if orientation == box_layout.orientation {
193        core_layout::solve_box_layout(
194            &core_layout::BoxLayoutData {
195                size,
196                spacing,
197                padding,
198                alignment,
199                cells: Slice::from(cells.as_slice()),
200            },
201            Slice::from(repeated_indices.as_slice()),
202        )
203        .into()
204    } else {
205        let cross_axis_alignment = box_layout
206            .cross_alignment
207            .as_ref()
208            .map(|nr| {
209                eval::load_property(component, &nr.element(), nr.name())
210                    .unwrap()
211                    .try_into()
212                    .unwrap_or_default()
213            })
214            .unwrap_or_default();
215        core_layout::solve_box_layout_ortho(
216            &core_layout::BoxLayoutOrthoData {
217                size,
218                padding,
219                cross_axis_alignment,
220                cells: Slice::from(cells.as_slice()),
221            },
222            Slice::from(repeated_indices.as_slice()),
223        )
224        .into()
225    }
226}
227
228pub(crate) fn solve_flexbox_layout(
229    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
230    local_context: &mut EvalLocalContext,
231) -> Value {
232    let component = local_context.component_instance;
233    let expr_eval = |nr: &NamedReference| -> f32 {
234        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
235    };
236
237    let width_ref = &flexbox_layout.geometry.rect.width_reference;
238    let height_ref = &flexbox_layout.geometry.rect.height_reference;
239    let direction = flexbox_layout_direction(flexbox_layout, local_context);
240
241    // For column direction, pass the container width so cells_v can use it
242    // as the constraint for height-for-width items (items stretch to it).
243    let container_width_for_cells = match direction {
244        i_slint_core::items::FlexboxLayoutDirection::Column
245        | i_slint_core::items::FlexboxLayoutDirection::ColumnReverse => {
246            width_ref.as_ref().map(&expr_eval)
247        }
248        _ => None,
249    };
250
251    let (cells_h, cells_v, repeated_indices) = flexbox_layout_data(
252        flexbox_layout,
253        component,
254        &expr_eval,
255        local_context,
256        container_width_for_cells,
257        None,
258    );
259
260    let alignment = flexbox_layout
261        .geometry
262        .alignment
263        .as_ref()
264        .map_or(i_slint_core::items::LayoutAlignment::default(), |nr| {
265            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
266        });
267    let align_content = flexbox_layout
268        .align_content
269        .as_ref()
270        .map_or(i_slint_core::items::FlexboxLayoutAlignContent::default(), |nr| {
271            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
272        });
273    let cross_axis_alignment = flexbox_layout
274        .cross_axis_alignment
275        .as_ref()
276        .map_or(i_slint_core::items::CrossAxisAlignment::default(), |nr| {
277            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
278        });
279    let flex_wrap = flexbox_layout
280        .flex_wrap
281        .as_ref()
282        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
283            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
284        });
285
286    let (padding_h, spacing_h) =
287        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
288    let (padding_v, spacing_v) =
289        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
290
291    let data = core_layout::FlexboxLayoutData {
292        width: width_ref.as_ref().map(&expr_eval).unwrap_or(0.),
293        height: height_ref.as_ref().map(&expr_eval).unwrap_or(0.),
294        spacing_h,
295        spacing_v,
296        padding_h,
297        padding_v,
298        alignment,
299        direction,
300        align_content,
301        cross_axis_alignment,
302        flex_wrap,
303        cells_h: Slice::from(cells_h.as_slice()),
304        cells_v: Slice::from(cells_v.as_slice()),
305    };
306    let ri = Slice::from(repeated_indices.as_slice());
307
308    let window_adapter = component.window_adapter();
309
310    // Build measure callback that computes constrained layout_info for items
311    // that support height-for-width (Text with wrap, Image with aspect ratio,
312    // and component instances with a synthesized
313    // `layoutinfo-{v,h}-with-constraint`).
314    //
315    // Collect `(element, has_constrained_layoutinfo_{v,h})` so we can
316    // dispatch component instances through the parametrized layout-info
317    // function rather than through the Item vtable (which returns trivial
318    // info for the Empty wrapper of a sub-component instance).
319    struct ChildElem {
320        elem: ElementRc,
321        has_constrained_layoutinfo_v: bool,
322        has_constrained_layoutinfo_h: bool,
323        /// True when the cell aggregates layoutinfo from its own subtree
324        /// (set by `default_geometry::gen_layout_info_prop`) — typical for
325        /// component-wrappers like a Rectangle containing a layout. In
326        /// that case the Item vtable's `layout_info` on the wrapper item
327        /// returns trivial info that doesn't reflect the aggregated
328        /// constraints; we read the aggregated property directly.
329        has_aggregated_info: bool,
330    }
331    let mut child_elems: Vec<Option<ChildElem>> = Vec::new();
332    for layout_elem in &flexbox_layout.elems {
333        if layout_elem.item.element.borrow().repeated.is_some() {
334            let component_vec = repeater_instances(component, &layout_elem.item.element);
335            for _ in 0..component_vec.len() {
336                child_elems.push(None);
337            }
338        } else {
339            let elem_b = layout_elem.item.element.borrow();
340            let has_constrained_layoutinfo_v =
341                elem_b.inherited_layout_info_v_with_constraint().is_some();
342            let has_constrained_layoutinfo_h =
343                elem_b.inherited_layout_info_h_with_constraint().is_some();
344            let has_aggregated_info = elem_b.layout_info_prop.is_some();
345            drop(elem_b);
346            child_elems.push(Some(ChildElem {
347                elem: layout_elem.item.element.clone(),
348                has_constrained_layoutinfo_v,
349                has_constrained_layoutinfo_h,
350                has_aggregated_info,
351            }));
352        }
353    }
354
355    let mut measure = |child_index: usize,
356                       known_w: Option<f32>,
357                       known_h: Option<f32>|
358     -> (f32, f32) {
359        let default_w = cells_h.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
360        let default_h = cells_v.get(child_index).map_or(0., |c| c.constraint.preferred_bounded());
361        let w = known_w.unwrap_or(default_w);
362        let h = known_h.unwrap_or(default_h);
363
364        let ce = match child_elems.get(child_index) {
365            Some(Some(c)) => c,
366            _ => return (w, h),
367        };
368
369        // Cells whose layoutinfo aggregates from a sub-tree (set by
370        // default_geometry) or that have a parametrized layout-info
371        // function need to be queried by NamedReference. The Item
372        // vtable's `layout_info` on the wrapper item returns trivial
373        // info that ignores the aggregated children.
374        let use_property_lookup = ce.has_aggregated_info
375            || ce.has_constrained_layoutinfo_v
376            || ce.has_constrained_layoutinfo_h;
377
378        if known_w.is_some() && known_h.is_none() {
379            if use_property_lookup {
380                let v_info = get_layout_info_with_constraint(
381                    &ce.elem,
382                    component,
383                    &window_adapter,
384                    Orientation::Vertical,
385                    ce.has_constrained_layoutinfo_v.then_some(w),
386                );
387                return (w, v_info.preferred_bounded());
388            }
389            // Builtin path (Text, Image): use the Item vtable's
390            // layout_info, which honors the cross-axis constraint.
391            let elem_id = ce.elem.borrow().id.clone();
392            if let Some(item_within) = component.description.items.get(elem_id.as_str()) {
393                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
394                let item_rc =
395                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
396                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
397                let v_info = item.as_ref().layout_info(
398                    to_runtime(Orientation::Vertical),
399                    w,
400                    &window_adapter,
401                    &item_rc,
402                );
403                return (w, v_info.preferred_bounded());
404            }
405            return (w, h);
406        }
407        if known_h.is_some() && known_w.is_none() {
408            if use_property_lookup {
409                let h_info = get_layout_info_with_constraint(
410                    &ce.elem,
411                    component,
412                    &window_adapter,
413                    Orientation::Horizontal,
414                    ce.has_constrained_layoutinfo_h.then_some(h),
415                );
416                return (h_info.preferred_bounded(), h);
417            }
418            let elem_id = ce.elem.borrow().id.clone();
419            if let Some(item_within) = component.description.items.get(elem_id.as_str()) {
420                let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
421                let item_rc =
422                    ItemRc::new(vtable::VRc::into_dyn(item_comp), item_within.item_index());
423                let item = unsafe { item_within.item_from_item_tree(component.as_ptr()) };
424                let h_info = item.as_ref().layout_info(
425                    to_runtime(Orientation::Horizontal),
426                    h,
427                    &window_adapter,
428                    &item_rc,
429                );
430                return (h_info.preferred_bounded(), h);
431            }
432            return (w, h);
433        }
434        (w, h)
435    };
436
437    core_layout::solve_flexbox_layout_with_measure(&data, ri, Some(&mut measure)).into()
438}
439
440fn flexbox_layout_direction(
441    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
442    local_context: &EvalLocalContext,
443) -> FlexboxLayoutDirection {
444    flexbox_layout
445        .direction
446        .as_ref()
447        .and_then(|nr| {
448            let value =
449                eval::load_property(local_context.component_instance, &nr.element(), nr.name())
450                    .ok()?;
451            if let Value::EnumerationValue(_, variant) = &value {
452                match variant.as_str() {
453                    "row" => Some(FlexboxLayoutDirection::Row),
454                    "row-reverse" => Some(FlexboxLayoutDirection::RowReverse),
455                    "column" => Some(FlexboxLayoutDirection::Column),
456                    "column-reverse" => Some(FlexboxLayoutDirection::ColumnReverse),
457                    _ => None,
458                }
459            } else {
460                None
461            }
462        })
463        .unwrap_or(FlexboxLayoutDirection::Row)
464}
465
466pub(crate) fn compute_flexbox_layout_info(
467    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
468    orientation: Orientation,
469    local_context: &mut EvalLocalContext,
470    cross_axis_size: Option<f32>,
471) -> Value {
472    let component = local_context.component_instance;
473    let expr_eval = |nr: &NamedReference| -> f32 {
474        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
475    };
476
477    // `cross_axis_size` carries a width when called from a
478    // `layoutinfo-v-with-constraint` body, a height from a
479    // `layoutinfo-h-with-constraint` body. Route it to the matching
480    // cell-list so cells don't receive a dimension on the wrong axis.
481    let (width_override, height_override) = match orientation {
482        Orientation::Vertical => (cross_axis_size, None),
483        Orientation::Horizontal => (None, cross_axis_size),
484    };
485    let (cells_h, cells_v, _repeated_indices) = flexbox_layout_data(
486        flexbox_layout,
487        component,
488        &expr_eval,
489        local_context,
490        width_override,
491        height_override,
492    );
493
494    // Get the direction from the property binding
495    let direction = flexbox_layout_direction(flexbox_layout, local_context);
496
497    // Determine if we're on the main axis or cross axis
498    let is_main_axis = matches!(
499        (direction, orientation),
500        (FlexboxLayoutDirection::Row | FlexboxLayoutDirection::RowReverse, Orientation::Horizontal)
501            | (
502                FlexboxLayoutDirection::Column | FlexboxLayoutDirection::ColumnReverse,
503                Orientation::Vertical
504            )
505    );
506
507    let (padding_h, spacing_h) =
508        padding_and_spacing(&flexbox_layout.geometry, Orientation::Horizontal, &expr_eval);
509    let (padding_v, spacing_v) =
510        padding_and_spacing(&flexbox_layout.geometry, Orientation::Vertical, &expr_eval);
511
512    let flex_wrap = flexbox_layout
513        .flex_wrap
514        .as_ref()
515        .map_or(i_slint_core::items::FlexboxLayoutWrap::default(), |nr| {
516            eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
517        });
518
519    if is_main_axis {
520        let (cells, spacing, padding) = match orientation {
521            Orientation::Horizontal => (&cells_h, spacing_h, &padding_h),
522            Orientation::Vertical => (&cells_v, spacing_v, &padding_v),
523        };
524        core_layout::flexbox_layout_info_main_axis(
525            Slice::from(cells.as_slice()),
526            spacing,
527            padding,
528            flex_wrap,
529        )
530        .into()
531    } else {
532        // Override when set (e.g., from a `layoutinfo-h-with-constraint`
533        // body); otherwise self's perpendicular dimension. The override
534        // path breaks the cycle for nested perpendicular flexboxes.
535        let constraint_size = cross_axis_size.unwrap_or_else(|| match orientation {
536            Orientation::Horizontal => {
537                let height_ref = &flexbox_layout.geometry.rect.height_reference;
538                height_ref.as_ref().map(&expr_eval).unwrap_or(0.)
539            }
540            Orientation::Vertical => {
541                let width_ref = &flexbox_layout.geometry.rect.width_reference;
542                width_ref.as_ref().map(&expr_eval).unwrap_or(0.)
543            }
544        });
545        core_layout::flexbox_layout_info_cross_axis(
546            Slice::from(cells_h.as_slice()),
547            Slice::from(cells_v.as_slice()),
548            spacing_h,
549            spacing_v,
550            &padding_h,
551            &padding_v,
552            direction,
553            flex_wrap,
554            constraint_size,
555        )
556        .into()
557    }
558}
559
560fn flexbox_layout_data(
561    flexbox_layout: &i_slint_compiler::layout::FlexboxLayout,
562    component: InstanceRef,
563    expr_eval: &impl Fn(&NamedReference) -> f32,
564    _local_context: &mut EvalLocalContext,
565    width_override: Option<f32>,
566    height_override: Option<f32>,
567) -> (Vec<core_layout::FlexboxLayoutItemInfo>, Vec<core_layout::FlexboxLayoutItemInfo>, Vec<u32>) {
568    let window_adapter = component.window_adapter();
569    let mut cells_h = Vec::with_capacity(flexbox_layout.elems.len());
570    let mut cells_v = Vec::with_capacity(flexbox_layout.elems.len());
571    let mut repeated_indices = Vec::new();
572
573    // First pass: collect horizontal layout_info for all children (no cycle risk)
574    // and flex properties. Store element refs for the second pass.
575    struct ChildInfo {
576        flex_grow: f32,
577        flex_shrink: f32,
578        flex_basis: f32,
579        flex_align_self: i_slint_core::items::FlexboxLayoutAlignSelf,
580        flex_order: i32,
581    }
582    let mut static_children: Vec<Option<ChildInfo>> = Vec::new(); // None = repeater
583
584    for layout_elem in &flexbox_layout.elems {
585        if layout_elem.item.element.borrow().repeated.is_some() {
586            let component_vec = repeater_instances(component, &layout_elem.item.element);
587            repeated_indices.push(cells_h.len() as u32);
588            repeated_indices.push(component_vec.len() as u32);
589            cells_h.extend(component_vec.iter().map(|x| {
590                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Horizontal), None)
591            }));
592            cells_v.extend(component_vec.iter().map(|x| {
593                x.as_pin_ref().flexbox_layout_item_info(to_runtime(Orientation::Vertical), None)
594            }));
595            for _ in 0..component_vec.len() {
596                static_children.push(None);
597            }
598        } else {
599            // Dispatch via `layoutinfo-h-with-constraint` for cells that
600            // have one, avoiding the `self.height` read that would cycle.
601            // Use the height-override when set, else `f32::MAX` so the
602            // runtime treats it as "no wrap needed" — gives the natural
603            // max-cell-width result rather than the heuristic.
604            let h_constraint = layout_elem
605                .item
606                .element
607                .borrow()
608                .inherited_layout_info_h_with_constraint()
609                .is_some()
610                .then(|| height_override.unwrap_or(f32::MAX));
611            let mut layout_info_h = get_layout_info_with_constraint(
612                &layout_elem.item.element,
613                component,
614                &window_adapter,
615                Orientation::Horizontal,
616                h_constraint,
617            );
618            fill_layout_info_constraints(
619                &mut layout_info_h,
620                &layout_elem.item.constraints,
621                Orientation::Horizontal,
622                expr_eval,
623            );
624            // Don't collect cells_v in the first pass — it may trigger a circular
625            // dependency for height-for-width items (Text with wrap, Image).
626            // The second pass fills in cells_v with the width constraint.
627            let flex_grow = layout_elem.flex_grow.as_ref().map(&expr_eval).unwrap_or(0.0);
628            let flex_shrink = layout_elem.flex_shrink.as_ref().map(&expr_eval).unwrap_or(1.0);
629            let flex_basis = layout_elem.flex_basis.as_ref().map(&expr_eval).unwrap_or(-1.0);
630            let align_self = layout_elem
631                .align_self
632                .as_ref()
633                .map(|nr| {
634                    eval::load_property(component, &nr.element(), nr.name())
635                        .unwrap()
636                        .try_into()
637                        .unwrap()
638                })
639                .unwrap_or(i_slint_core::items::FlexboxLayoutAlignSelf::default());
640            let order = layout_elem.order.as_ref().map(expr_eval).unwrap_or(0.0) as i32;
641            cells_h.push(core_layout::FlexboxLayoutItemInfo {
642                constraint: layout_info_h,
643                flex_grow,
644                flex_shrink,
645                flex_basis,
646                flex_align_self: align_self,
647                flex_order: order,
648            });
649            // Placeholder for cells_v — filled in second pass
650            cells_v.push(core_layout::FlexboxLayoutItemInfo::default());
651            static_children.push(Some(ChildInfo {
652                flex_grow,
653                flex_shrink,
654                flex_basis,
655                flex_align_self: align_self,
656                flex_order: order,
657            }));
658        }
659    }
660
661    // Second pass: collect vertical layout_info with a width constraint.
662    // For column direction, use the container width (items get stretched to it).
663    // Otherwise use the item's horizontal preferred size.
664    let mut cell_idx = 0usize;
665    for layout_elem in &flexbox_layout.elems {
666        if layout_elem.item.element.borrow().repeated.is_some() {
667            let component_vec = repeater_instances(component, &layout_elem.item.element);
668            cell_idx += component_vec.len();
669            // repeater cells_v already filled in first pass
670        } else {
671            let width_constraint =
672                width_override.unwrap_or_else(|| cells_h[cell_idx].constraint.preferred_bounded());
673            let mut layout_info_v = get_layout_info_with_constraint(
674                &layout_elem.item.element,
675                component,
676                &window_adapter,
677                Orientation::Vertical,
678                Some(width_constraint),
679            );
680            fill_layout_info_constraints(
681                &mut layout_info_v,
682                &layout_elem.item.constraints,
683                Orientation::Vertical,
684                expr_eval,
685            );
686            if let Some(info) = &static_children[cell_idx] {
687                cells_v[cell_idx] = core_layout::FlexboxLayoutItemInfo {
688                    constraint: layout_info_v,
689                    flex_grow: info.flex_grow,
690                    flex_shrink: info.flex_shrink,
691                    flex_basis: info.flex_basis,
692                    flex_align_self: info.flex_align_self,
693                    flex_order: info.flex_order,
694                };
695            }
696            cell_idx += 1;
697        }
698    }
699
700    (cells_h, cells_v, repeated_indices)
701}
702
703/// Determine the evaluated padding and spacing values from the layout geometry
704fn padding_and_spacing(
705    layout_geometry: &LayoutGeometry,
706    orientation: Orientation,
707    expr_eval: &impl Fn(&NamedReference) -> f32,
708) -> (core_layout::Padding, f32) {
709    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
710    let (begin, end) = layout_geometry.padding.begin_end(orientation);
711    let padding =
712        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
713    (padding, spacing)
714}
715
716fn repeater_instances(
717    component: InstanceRef,
718    elem: &ElementRc,
719) -> Vec<crate::dynamic_item_tree::DynamicComponentVRc> {
720    generativity::make_guard!(guard);
721    let rep =
722        crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard);
723    rep.0.as_ref().track_instance_changes();
724    rep.0.as_ref().instances_vec()
725}
726
727fn grid_layout_input_data(
728    grid_layout: &i_slint_compiler::layout::GridLayout,
729    ctx: &EvalLocalContext,
730    repeater_steps: &[u32],
731) -> Vec<GridLayoutInputData> {
732    let component = ctx.component_instance;
733    let mut result = Vec::with_capacity(grid_layout.elems.len());
734    let mut after_repeater_in_same_row = false;
735    let mut new_row = true;
736    let mut repeater_idx = 0usize;
737    for elem in grid_layout.elems.iter() {
738        let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr {
739            RowColExpr::Literal(value) => *value as f32,
740            RowColExpr::Auto => i_slint_common::ROW_COL_AUTO,
741            RowColExpr::Named(nr) => {
742                // we could check for out-of-bounds here, but organize_grid_layout will also do it
743                eval::load_property(component, &nr.element(), nr.name())
744                    .unwrap()
745                    .try_into()
746                    .unwrap()
747            }
748        };
749
750        let cell_new_row = elem.cell.borrow().new_row;
751        if cell_new_row {
752            after_repeater_in_same_row = false;
753        }
754        if elem.item.element.borrow().repeated.is_some() {
755            let component_vec = repeater_instances(component, &elem.item.element);
756            new_row = cell_new_row;
757            for erased_sub_comp in &component_vec {
758                // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component
759                generativity::make_guard!(guard);
760                let sub_comp = erased_sub_comp.as_pin_ref();
761                let sub_instance_ref =
762                    unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) };
763
764                if let Some(children) = elem.cell.borrow().child_items.as_ref() {
765                    // Repeated row
766                    new_row = true;
767                    let start_count = result.len();
768
769                    // Single pass in declaration order: push statics and inner-repeater
770                    // auto-cells interleaved so that column assignments match template order.
771                    // (A two-pass approach that appended all inner-repeater cells after all
772                    // statics would produce wrong column assignments, and only tracking the
773                    // last Repeated entry would miss earlier conditionals/for-loops.)
774                    for child_template in children {
775                        match child_template {
776                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
777                                let (row_val, col_val, rowspan_val, colspan_val) = {
778                                    let element_ref = child_item.element.borrow();
779                                    let child_cell =
780                                        element_ref.grid_layout_cell.as_ref().unwrap().borrow();
781                                    (
782                                        eval_or_default(&child_cell.row_expr, sub_instance_ref),
783                                        eval_or_default(&child_cell.col_expr, sub_instance_ref),
784                                        eval_or_default(&child_cell.rowspan_expr, sub_instance_ref),
785                                        eval_or_default(&child_cell.colspan_expr, sub_instance_ref),
786                                    )
787                                };
788                                result.push(GridLayoutInputData {
789                                    new_row,
790                                    col: col_val,
791                                    row: row_val,
792                                    colspan: colspan_val,
793                                    rowspan: rowspan_val,
794                                });
795                                new_row = false;
796                            }
797                            i_slint_compiler::layout::RowChildTemplate::Repeated {
798                                repeated_element,
799                                ..
800                            } => {
801                                let inner_instances =
802                                    repeater_instances(sub_instance_ref, repeated_element);
803                                for i in 0..inner_instances.len() {
804                                    result.push(GridLayoutInputData {
805                                        new_row: i == 0 && new_row,
806                                        ..Default::default()
807                                    });
808                                }
809                                if !inner_instances.is_empty() {
810                                    new_row = false;
811                                }
812                            }
813                        }
814                    }
815                    // Pad to match max step count for this repeater (handles jagged arrays)
816                    let cells_pushed = result.len() - start_count;
817                    let expected_step =
818                        repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
819                    for _ in cells_pushed..expected_step {
820                        result.push(GridLayoutInputData::default());
821                    }
822                } else {
823                    // Single repeated item
824                    let cell = elem.cell.borrow();
825                    let row = eval_or_default(&cell.row_expr, sub_instance_ref);
826                    let col = eval_or_default(&cell.col_expr, sub_instance_ref);
827                    let rowspan = eval_or_default(&cell.rowspan_expr, sub_instance_ref);
828                    let colspan = eval_or_default(&cell.colspan_expr, sub_instance_ref);
829                    result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
830                    new_row = false;
831                }
832            }
833            repeater_idx += 1;
834            after_repeater_in_same_row = true;
835        } else {
836            let new_row =
837                if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row };
838            let row = eval_or_default(&elem.cell.borrow().row_expr, component);
839            let col = eval_or_default(&elem.cell.borrow().col_expr, component);
840            let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component);
841            let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component);
842            result.push(GridLayoutInputData { new_row, col, row, colspan, rowspan });
843        }
844    }
845    result
846}
847
848/// Count the actual runtime children for a repeated row.
849/// For rows without inner repeaters, this is just the child_items count.
850/// For rows with inner repeaters, the Repeated template expands to actual inner instances.
851fn row_runtime_child_count(
852    child_items: &[i_slint_compiler::layout::RowChildTemplate],
853    sub_instance_ref: InstanceRef,
854) -> usize {
855    let mut count = 0;
856    for child in child_items {
857        if let Some(repeated_element) = child.repeated_element() {
858            count += repeater_instances(sub_instance_ref, repeated_element).len();
859        } else {
860            count += 1;
861        }
862    }
863    count
864}
865
866fn grid_repeater_indices(
867    grid_layout: &i_slint_compiler::layout::GridLayout,
868    ctx: &mut EvalLocalContext,
869    repeater_steps: &[u32],
870) -> Vec<u32> {
871    let component = ctx.component_instance;
872    let mut repeater_indices = Vec::new();
873    let mut num_cells = 0;
874    let mut step_idx = 0;
875    for elem in grid_layout.elems.iter() {
876        if elem.item.element.borrow().repeated.is_some() {
877            let component_vec = repeater_instances(component, &elem.item.element);
878            repeater_indices.push(num_cells as _);
879            repeater_indices.push(component_vec.len() as _);
880            let item_count = repeater_steps[step_idx] as usize;
881            num_cells += component_vec.len() * item_count;
882            step_idx += 1;
883        } else {
884            num_cells += 1;
885        }
886    }
887    repeater_indices
888}
889
890fn grid_repeater_steps(
891    grid_layout: &i_slint_compiler::layout::GridLayout,
892    ctx: &mut EvalLocalContext,
893) -> Vec<u32> {
894    let component = ctx.component_instance;
895    let mut repeater_steps = Vec::new();
896    for elem in grid_layout.elems.iter() {
897        if elem.item.element.borrow().repeated.is_some() {
898            let item_count = match &elem.cell.borrow().child_items {
899                Some(ci)
900                    if ci.iter().any(i_slint_compiler::layout::RowChildTemplate::is_repeated) =>
901                {
902                    // Compute max runtime count across all instances (padding with empty cells didn't happen yet)
903                    let component_vec = repeater_instances(component, &elem.item.element);
904                    component_vec
905                        .iter()
906                        .map(|sub| {
907                            generativity::make_guard!(guard);
908                            let sub_pin = sub.as_pin_ref();
909                            let sub_ref =
910                                unsafe { InstanceRef::from_pin_ref(sub_pin.borrow(), guard) };
911                            row_runtime_child_count(ci, sub_ref)
912                        })
913                        .max()
914                        .unwrap_or(0)
915                }
916                Some(ci) => ci.len(),
917                None => 1,
918            };
919            repeater_steps.push(item_count as u32);
920        }
921    }
922    repeater_steps
923}
924
925fn grid_layout_constraints(
926    grid_layout: &i_slint_compiler::layout::GridLayout,
927    orientation: Orientation,
928    ctx: &mut EvalLocalContext,
929    repeater_steps: &[u32],
930    cross_axis_size: Option<f32>,
931) -> Vec<core_layout::LayoutItemInfo> {
932    let component = ctx.component_instance;
933    let expr_eval = |nr: &NamedReference| -> f32 {
934        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
935    };
936    let mut constraints = Vec::with_capacity(grid_layout.elems.len());
937
938    let mut repeater_idx = 0usize;
939    for layout_elem in grid_layout.elems.iter() {
940        if layout_elem.item.element.borrow().repeated.is_some() {
941            let component_vec = repeater_instances(component, &layout_elem.item.element);
942            let child_items = layout_elem.cell.borrow().child_items.clone();
943            let has_children = child_items.is_some();
944            if has_children {
945                // Repeated row
946                let ci = child_items.as_ref().unwrap();
947                let step = repeater_steps.get(repeater_idx).copied().unwrap_or(0) as usize;
948                for sub_comp in &component_vec {
949                    let per_instance_start = constraints.len();
950                    // Evaluate constraints in the context of the repeated sub-component
951                    generativity::make_guard!(guard);
952                    let sub_pin = sub_comp.as_pin_ref();
953                    let sub_borrow = sub_pin.borrow();
954                    let sub_instance_ref = unsafe { InstanceRef::from_pin_ref(sub_borrow, guard) };
955                    let expr_eval = |nr: &NamedReference| -> f32 {
956                        eval::load_property(sub_instance_ref, &nr.element(), nr.name())
957                            .unwrap()
958                            .try_into()
959                            .unwrap()
960                    };
961
962                    // Iterate over the child templates: static children get their layout info
963                    // from the Row sub-component; nested repeater children get theirs from the
964                    // inner repeater instances.
965                    for child_template in ci.iter() {
966                        match child_template {
967                            i_slint_compiler::layout::RowChildTemplate::Static(child_item) => {
968                                let mut layout_info = crate::eval_layout::get_layout_info(
969                                    &child_item.element,
970                                    sub_instance_ref,
971                                    &sub_instance_ref.window_adapter(),
972                                    orientation,
973                                );
974                                fill_layout_info_constraints(
975                                    &mut layout_info,
976                                    &child_item.constraints,
977                                    orientation,
978                                    &expr_eval,
979                                );
980                                constraints
981                                    .push(core_layout::LayoutItemInfo { constraint: layout_info });
982                            }
983                            i_slint_compiler::layout::RowChildTemplate::Repeated {
984                                item: child_item,
985                                repeated_element,
986                            } => {
987                                // Get the inner repeater instances from within this Row instance
988                                let inner_instances =
989                                    repeater_instances(sub_instance_ref, repeated_element);
990                                for inner_comp in &inner_instances {
991                                    let inner_pin = inner_comp.as_pin_ref();
992                                    let mut layout_info =
993                                        inner_pin.layout_item_info(to_runtime(orientation), None);
994                                    // Constraints' NamedReferences point to elements inside the
995                                    // inner repeated component, so evaluate in that context.
996                                    generativity::make_guard!(inner_guard);
997                                    let inner_borrow = inner_pin.borrow();
998                                    let inner_instance_ref = unsafe {
999                                        InstanceRef::from_pin_ref(inner_borrow, inner_guard)
1000                                    };
1001                                    let inner_expr_eval = |nr: &NamedReference| -> f32 {
1002                                        eval::load_property(
1003                                            inner_instance_ref,
1004                                            &nr.element(),
1005                                            nr.name(),
1006                                        )
1007                                        .unwrap()
1008                                        .try_into()
1009                                        .unwrap()
1010                                    };
1011                                    fill_layout_info_constraints(
1012                                        &mut layout_info.constraint,
1013                                        &child_item.constraints,
1014                                        orientation,
1015                                        &inner_expr_eval,
1016                                    );
1017                                    constraints.push(layout_info);
1018                                }
1019                            }
1020                        }
1021                    }
1022                    // Pad this instance to the step size (handles jagged arrays where
1023                    // inner repeaters have different lengths across outer Row instances).
1024                    let pushed = constraints.len() - per_instance_start;
1025                    for _ in pushed..step {
1026                        constraints.push(core_layout::LayoutItemInfo::default());
1027                    }
1028                }
1029            } else {
1030                // Single repeated item
1031                constraints.extend(
1032                    component_vec
1033                        .iter()
1034                        .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1035                );
1036            }
1037            repeater_idx += 1;
1038        } else {
1039            let cross_axis =
1040                cross_axis_size_for_cell(&layout_elem.item.element, orientation, cross_axis_size);
1041            let mut layout_info = get_layout_info_with_constraint(
1042                &layout_elem.item.element,
1043                component,
1044                &component.window_adapter(),
1045                orientation,
1046                cross_axis,
1047            );
1048            fill_layout_info_constraints(
1049                &mut layout_info,
1050                &layout_elem.item.constraints,
1051                orientation,
1052                &expr_eval,
1053            );
1054            constraints.push(core_layout::LayoutItemInfo { constraint: layout_info });
1055        }
1056    }
1057    constraints
1058}
1059
1060/// Collect all elements in this layout and store the LayoutItemInfo of it for further calculation
1061fn box_layout_data(
1062    box_layout: &i_slint_compiler::layout::BoxLayout,
1063    orientation: Orientation,
1064    component: InstanceRef,
1065    expr_eval: &impl Fn(&NamedReference) -> f32,
1066    mut repeater_indices: Option<&mut Vec<u32>>,
1067    cross_axis_size: Option<f32>,
1068) -> (Vec<core_layout::LayoutItemInfo>, i_slint_core::items::LayoutAlignment) {
1069    let window_adapter = component.window_adapter();
1070    let mut cells = Vec::with_capacity(box_layout.elems.len());
1071    for cell in &box_layout.elems {
1072        if cell.element.borrow().repeated.is_some() {
1073            // Collect all repeated elements
1074            let component_vec = repeater_instances(component, &cell.element);
1075            if let Some(ri) = repeater_indices.as_mut() {
1076                ri.push(cells.len() as _);
1077                ri.push(component_vec.len() as _);
1078            }
1079            cells.extend(
1080                component_vec
1081                    .iter()
1082                    .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
1083            );
1084        } else {
1085            // Collect non repeated elements
1086            let cross_axis = cross_axis_size_for_cell(&cell.element, orientation, cross_axis_size);
1087            let mut layout_info = get_layout_info_with_constraint(
1088                &cell.element,
1089                component,
1090                &window_adapter,
1091                orientation,
1092                cross_axis,
1093            );
1094            fill_layout_info_constraints(
1095                &mut layout_info,
1096                &cell.constraints,
1097                orientation,
1098                &expr_eval,
1099            );
1100            cells.push(core_layout::LayoutItemInfo { constraint: layout_info });
1101        }
1102    }
1103    let alignment = box_layout
1104        .geometry
1105        .alignment
1106        .as_ref()
1107        .map(|nr| {
1108            eval::load_property(component, &nr.element(), nr.name())
1109                .unwrap()
1110                .try_into()
1111                .unwrap_or_default()
1112        })
1113        .unwrap_or_default();
1114    (cells, alignment)
1115}
1116
1117pub(crate) fn fill_layout_info_constraints(
1118    layout_info: &mut core_layout::LayoutInfo,
1119    constraints: &LayoutConstraints,
1120    orientation: Orientation,
1121    expr_eval: &impl Fn(&NamedReference) -> f32,
1122) {
1123    let is_percent =
1124        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
1125
1126    match orientation {
1127        Orientation::Horizontal => {
1128            if let Some(e) = constraints.min_width.as_ref() {
1129                if !is_percent(e) {
1130                    layout_info.min = expr_eval(e)
1131                } else {
1132                    layout_info.min_percent = expr_eval(e)
1133                }
1134            }
1135            if let Some(e) = constraints.max_width.as_ref() {
1136                if !is_percent(e) {
1137                    layout_info.max = expr_eval(e)
1138                } else {
1139                    layout_info.max_percent = expr_eval(e)
1140                }
1141            }
1142            if let Some(e) = constraints.preferred_width.as_ref() {
1143                layout_info.preferred = expr_eval(e);
1144            }
1145            if let Some(e) = constraints.horizontal_stretch.as_ref() {
1146                layout_info.stretch = expr_eval(e);
1147            }
1148        }
1149        Orientation::Vertical => {
1150            if let Some(e) = constraints.min_height.as_ref() {
1151                if !is_percent(e) {
1152                    layout_info.min = expr_eval(e)
1153                } else {
1154                    layout_info.min_percent = expr_eval(e)
1155                }
1156            }
1157            if let Some(e) = constraints.max_height.as_ref() {
1158                if !is_percent(e) {
1159                    layout_info.max = expr_eval(e)
1160                } else {
1161                    layout_info.max_percent = expr_eval(e)
1162                }
1163            }
1164            if let Some(e) = constraints.preferred_height.as_ref() {
1165                layout_info.preferred = expr_eval(e);
1166            }
1167            if let Some(e) = constraints.vertical_stretch.as_ref() {
1168                layout_info.stretch = expr_eval(e);
1169            }
1170        }
1171    }
1172}
1173
1174/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
1175pub(crate) fn get_layout_info(
1176    elem: &ElementRc,
1177    component: InstanceRef,
1178    window_adapter: &Rc<dyn WindowAdapter>,
1179    orientation: Orientation,
1180) -> core_layout::LayoutInfo {
1181    get_layout_info_with_constraint(elem, component, window_adapter, orientation, None)
1182}
1183
1184pub(crate) fn get_layout_info_with_constraint(
1185    elem: &ElementRc,
1186    component: InstanceRef,
1187    window_adapter: &Rc<dyn WindowAdapter>,
1188    orientation: Orientation,
1189    cross_axis_constraint: Option<f32>,
1190) -> core_layout::LayoutInfo {
1191    // With a constraint and a parameterized layout-info function on the
1192    // cell, call it instead of reading the cell's perpendicular property.
1193    // Use the inherited lookup so component-instance cells pick up a
1194    // `layoutinfo-{v,h}-with-constraint` declared on the base component's
1195    // root_element (the cell itself doesn't carry the binding).
1196    let parameterized_nr = if cross_axis_constraint.is_some() {
1197        match orientation {
1198            Orientation::Vertical => elem.borrow().inherited_layout_info_v_with_constraint(),
1199            Orientation::Horizontal => elem.borrow().inherited_layout_info_h_with_constraint(),
1200        }
1201    } else {
1202        None
1203    };
1204    if let Some(nr) = parameterized_nr {
1205        let arg = cross_axis_constraint.unwrap();
1206        let v = eval::call_function(
1207            &eval::ComponentInstance::InstanceRef(component),
1208            &nr.element(),
1209            nr.name(),
1210            vec![Value::Number(arg as f64)],
1211        )
1212        .expect("layoutinfo-{h,v}-with-constraint is a synthesized pure function");
1213        return v.try_into().unwrap();
1214    }
1215
1216    let elem = elem.borrow();
1217    if let Some(nr) = elem.layout_info_prop(orientation) {
1218        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
1219    } else {
1220        let item = &component
1221            .description
1222            .items
1223            .get(elem.id.as_str())
1224            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
1225        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
1226
1227        unsafe {
1228            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
1229                to_runtime(orientation),
1230                cross_axis_constraint.unwrap_or(-1.),
1231                window_adapter,
1232                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
1233            )
1234        }
1235    }
1236}
1237
1238/// Decide the cross-axis constraint to forward to a cell's perpendicular
1239/// layout-info call. Returns `Some` only when the parent supplied the cross
1240/// dimension and the cell consumes it: a height-for-width cell on the vertical
1241/// pass (wrapped Text/Image, or a `layoutinfo-v-with-constraint` component), or a
1242/// width-for-height cell on the horizontal pass (a `layoutinfo-h-with-constraint`
1243/// component, e.g. a column FlexboxLayout).
1244fn cross_axis_size_for_cell(
1245    elem: &ElementRc,
1246    orientation: Orientation,
1247    parent_cross_axis_size: Option<f32>,
1248) -> Option<f32> {
1249    let cross = parent_cross_axis_size?;
1250    let elem_b = elem.borrow();
1251    if orientation == Orientation::Horizontal {
1252        // Width-for-height cells (e.g. a column FlexboxLayout) carry a synthesized
1253        // layoutinfo-h-with-constraint; forward the cross size so they compute their
1254        // width from it instead of reading self.height (which would cycle through
1255        // the parent's vertical pass).
1256        return elem_b.inherited_layout_info_h_with_constraint().is_some().then_some(cross);
1257    }
1258    if elem_b.layout_info_v_with_constraint.is_some() {
1259        return Some(cross);
1260    }
1261    // For builtin height-for-width items, the existing VTable cross_axis_constraint
1262    // parameter mechanism is what consumes the value; conservatively
1263    // forward it for any element without its own layoutinfo-v property
1264    // (i.e. anything that ends up calling the builtin VTable).
1265    if elem_b.layout_info_prop(Orientation::Vertical).is_none() {
1266        return Some(cross);
1267    }
1268    None
1269}