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