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