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