Skip to main content

slint_interpreter/
eval_layout.rs

1// Copyright © SixtyFPS GmbH <[email protected]>
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    GridLayout, Layout, LayoutConstraints, LayoutGeometry, Orientation, RowColExpr,
11};
12use i_slint_compiler::namedreference::NamedReference;
13use i_slint_compiler::object_tree::ElementRc;
14use i_slint_core::items::{DialogButtonRole, ItemRc};
15use i_slint_core::layout::{self as core_layout, 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_indices = grid_repeater_indices(grid_layout, local_context);
48    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
49    let constraints = grid_layout_constraints(grid_layout, orientation, local_context);
50    core_layout::grid_layout_info(
51        organized_data.clone(),
52        Slice::from_slice(constraints.as_slice()),
53        Slice::from_slice(repeater_indices.as_slice()),
54        Slice::from_slice(repeater_steps.as_slice()),
55        spacing,
56        &padding,
57        to_runtime(orientation),
58    )
59    .into()
60}
61
62pub(crate) fn compute_layout_info(
63    lay: &Layout,
64    orientation: Orientation,
65    local_context: &mut EvalLocalContext,
66) -> Value {
67    let component = local_context.component_instance;
68    let expr_eval = |nr: &NamedReference| -> f32 {
69        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
70    };
71    match lay {
72        Layout::GridLayout(_) => {
73            panic!("only BoxLayout is supported");
74        }
75        Layout::BoxLayout(box_layout) => {
76            let (cells, alignment) =
77                box_layout_data(box_layout, orientation, component, &expr_eval, None);
78            let (padding, spacing) =
79                padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
80            if orientation == box_layout.orientation {
81                core_layout::box_layout_info(
82                    Slice::from(cells.as_slice()),
83                    spacing,
84                    &padding,
85                    alignment,
86                )
87            } else {
88                core_layout::box_layout_info_ortho(Slice::from(cells.as_slice()), &padding)
89            }
90            .into()
91        }
92    }
93}
94
95pub(crate) fn organize_grid_layout(
96    layout: &GridLayout,
97    local_context: &mut EvalLocalContext,
98) -> Value {
99    let cells = grid_layout_input_data(layout, local_context);
100    let repeater_indices = grid_repeater_indices(layout, local_context);
101    let repeater_steps = grid_repeater_steps(layout, local_context);
102    if let Some(buttons_roles) = &layout.dialog_button_roles {
103        let roles = buttons_roles
104            .iter()
105            .map(|r| DialogButtonRole::from_str(r).unwrap())
106            .collect::<Vec<_>>();
107        core_layout::organize_dialog_button_layout(
108            Slice::from_slice(cells.as_slice()),
109            Slice::from_slice(roles.as_slice()),
110        )
111        .into()
112    } else {
113        core_layout::organize_grid_layout(
114            Slice::from_slice(cells.as_slice()),
115            Slice::from_slice(repeater_indices.as_slice()),
116            Slice::from_slice(repeater_steps.as_slice()),
117        )
118        .into()
119    }
120}
121
122pub(crate) fn solve_grid_layout(
123    organized_data: &GridLayoutOrganizedData,
124    grid_layout: &GridLayout,
125    orientation: Orientation,
126    local_context: &mut EvalLocalContext,
127) -> Value {
128    let component = local_context.component_instance;
129    let expr_eval = |nr: &NamedReference| -> f32 {
130        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
131    };
132    let repeater_indices = grid_repeater_indices(grid_layout, local_context);
133    let repeater_steps = grid_repeater_steps(grid_layout, local_context);
134    let constraints = grid_layout_constraints(grid_layout, orientation, local_context);
135
136    let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
137    let size_ref = grid_layout.geometry.rect.size_reference(orientation);
138
139    let data = core_layout::GridLayoutData {
140        size: size_ref.map(expr_eval).unwrap_or(0.),
141        spacing,
142        padding,
143        organized_data: organized_data.clone(),
144    };
145
146    core_layout::solve_grid_layout(
147        &data,
148        Slice::from_slice(constraints.as_slice()),
149        to_runtime(orientation),
150        Slice::from_slice(repeater_indices.as_slice()),
151        Slice::from_slice(repeater_steps.as_slice()),
152    )
153    .into()
154}
155
156pub(crate) fn solve_layout(
157    lay: &Layout,
158    orientation: Orientation,
159    local_context: &mut EvalLocalContext,
160) -> Value {
161    let component = local_context.component_instance;
162    let expr_eval = |nr: &NamedReference| -> f32 {
163        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
164    };
165
166    match lay {
167        Layout::GridLayout(_) => {
168            panic!("solve_layout called on GridLayout; use solve_grid_layout instead");
169        }
170        Layout::BoxLayout(box_layout) => {
171            let mut repeated_indices = Vec::new();
172            let (cells, alignment) = box_layout_data(
173                box_layout,
174                orientation,
175                component,
176                &expr_eval,
177                Some(&mut repeated_indices),
178            );
179            let (padding, spacing) =
180                padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
181            let size_ref = match orientation {
182                Orientation::Horizontal => &box_layout.geometry.rect.width_reference,
183                Orientation::Vertical => &box_layout.geometry.rect.height_reference,
184            };
185            core_layout::solve_box_layout(
186                &core_layout::BoxLayoutData {
187                    size: size_ref.as_ref().map(expr_eval).unwrap_or(0.),
188                    spacing,
189                    padding,
190                    alignment,
191                    cells: Slice::from(cells.as_slice()),
192                },
193                Slice::from(repeated_indices.as_slice()),
194            )
195            .into()
196        }
197    }
198}
199
200fn padding_and_spacing(
201    layout_geometry: &LayoutGeometry,
202    orientation: Orientation,
203    expr_eval: &impl Fn(&NamedReference) -> f32,
204) -> (core_layout::Padding, f32) {
205    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
206    let (begin, end) = layout_geometry.padding.begin_end(orientation);
207    let padding =
208        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
209    (padding, spacing)
210}
211
212fn repeater_instances(
213    component: InstanceRef,
214    elem: &ElementRc,
215) -> Vec<crate::dynamic_item_tree::DynamicComponentVRc> {
216    generativity::make_guard!(guard);
217    let rep =
218        crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard);
219    let extra_data = component.description.extra_data_offset.apply(component.as_ref());
220    rep.0.as_ref().ensure_updated(|| {
221        crate::dynamic_item_tree::instantiate(
222            rep.1.clone(),
223            component.self_weak().get().cloned(),
224            None,
225            None,
226            extra_data.globals.get().unwrap().clone(),
227        )
228    });
229    rep.0.as_ref().instances_vec()
230}
231
232fn grid_layout_input_data(
233    grid_layout: &i_slint_compiler::layout::GridLayout,
234    ctx: &EvalLocalContext,
235) -> Vec<core_layout::GridLayoutInputData> {
236    let component = ctx.component_instance;
237    let mut result = Vec::with_capacity(grid_layout.elems.len());
238    let mut after_repeater_in_same_row = false;
239    let mut new_row = true;
240    for elem in grid_layout.elems.iter() {
241        let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr {
242            RowColExpr::Literal(value) => *value as f32,
243            RowColExpr::Auto => i_slint_common::ROW_COL_AUTO,
244            RowColExpr::Named(nr) => {
245                // we could check for out-of-bounds here, but organize_grid_layout will also do it
246                eval::load_property(component, &nr.element(), nr.name())
247                    .unwrap()
248                    .try_into()
249                    .unwrap()
250            }
251        };
252
253        let cell_new_row = elem.cell.borrow().new_row;
254        if cell_new_row {
255            after_repeater_in_same_row = false;
256        }
257        if elem.item.element.borrow().repeated.is_some() {
258            let component_vec = repeater_instances(component, &elem.item.element);
259            new_row = cell_new_row;
260            for erased_sub_comp in &component_vec {
261                // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component
262                generativity::make_guard!(guard);
263                let sub_comp = erased_sub_comp.as_pin_ref();
264                let sub_instance_ref =
265                    unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) };
266
267                let mut push_cell = |cell: &i_slint_compiler::layout::GridLayoutCell,
268                                     new_row: bool| {
269                    let row = eval_or_default(&cell.row_expr, sub_instance_ref);
270                    let col = eval_or_default(&cell.col_expr, sub_instance_ref);
271                    let rowspan = eval_or_default(&cell.rowspan_expr, sub_instance_ref);
272                    let colspan = eval_or_default(&cell.colspan_expr, sub_instance_ref);
273
274                    result.push(core_layout::GridLayoutInputData {
275                        new_row,
276                        col,
277                        row,
278                        colspan,
279                        rowspan,
280                    });
281                };
282
283                if let Some(children) = elem.cell.borrow().child_items.as_ref() {
284                    // Repeated row
285                    new_row = true;
286                    for child_item in children {
287                        let element_ref = &child_item.element.borrow();
288                        let child_cell = element_ref.grid_layout_cell.as_ref().unwrap().borrow();
289                        push_cell(&child_cell, new_row);
290                        new_row = false;
291                    }
292                } else {
293                    // Single repeated item
294                    let cell = elem.cell.borrow();
295                    push_cell(&cell, new_row);
296                    new_row = false;
297                }
298            }
299            after_repeater_in_same_row = true;
300        } else {
301            let new_row =
302                if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row };
303            let row = eval_or_default(&elem.cell.borrow().row_expr, component);
304            let col = eval_or_default(&elem.cell.borrow().col_expr, component);
305            let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component);
306            let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component);
307            result.push(core_layout::GridLayoutInputData { new_row, col, row, colspan, rowspan });
308        }
309    }
310    result
311}
312
313fn grid_repeater_indices(
314    grid_layout: &i_slint_compiler::layout::GridLayout,
315    ctx: &mut EvalLocalContext,
316) -> Vec<u32> {
317    let component = ctx.component_instance;
318    let mut repeater_indices = Vec::new();
319
320    let mut num_cells = 0;
321    for elem in grid_layout.elems.iter() {
322        if elem.item.element.borrow().repeated.is_some() {
323            let component_vec = repeater_instances(component, &elem.item.element);
324            repeater_indices.push(num_cells as _);
325            repeater_indices.push(component_vec.len() as _);
326            let item_count = elem.cell.borrow().child_items.as_ref().map_or(1, |c| c.len());
327            num_cells += component_vec.len() * item_count;
328        } else {
329            num_cells += 1;
330        }
331    }
332    repeater_indices
333}
334
335fn grid_repeater_steps(
336    grid_layout: &i_slint_compiler::layout::GridLayout,
337    _ctx: &mut EvalLocalContext,
338) -> Vec<u32> {
339    let mut repeater_steps = Vec::new();
340    for elem in grid_layout.elems.iter() {
341        if elem.item.element.borrow().repeated.is_some() {
342            let item_count = elem.cell.borrow().child_items.as_ref().map_or(1, |c| c.len());
343            repeater_steps.push(item_count as u32);
344        }
345    }
346    repeater_steps
347}
348
349fn grid_layout_constraints(
350    grid_layout: &i_slint_compiler::layout::GridLayout,
351    orientation: Orientation,
352    ctx: &mut EvalLocalContext,
353) -> Vec<core_layout::LayoutItemInfo> {
354    let component = ctx.component_instance;
355    let expr_eval = |nr: &NamedReference| -> f32 {
356        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
357    };
358    let mut constraints = Vec::with_capacity(grid_layout.elems.len());
359
360    for layout_elem in grid_layout.elems.iter() {
361        if layout_elem.item.element.borrow().repeated.is_some() {
362            let component_vec = repeater_instances(component, &layout_elem.item.element);
363            let child_items = layout_elem.cell.borrow().child_items.clone();
364            let repeated_children_count = child_items.as_ref().map(|c| c.len());
365            if let Some(num) = repeated_children_count {
366                // Repeated row
367                for sub_comp in &component_vec {
368                    // Evaluate constraints in the context of the repeated sub-component
369                    generativity::make_guard!(guard);
370                    let sub_pin = sub_comp.as_pin_ref();
371                    let sub_borrow = sub_pin.borrow();
372                    let sub_instance_ref = unsafe { InstanceRef::from_pin_ref(sub_borrow, guard) };
373                    let expr_eval = |nr: &NamedReference| -> f32 {
374                        eval::load_property(sub_instance_ref, &nr.element(), nr.name())
375                            .unwrap()
376                            .try_into()
377                            .unwrap()
378                    };
379                    for idx in 0..num {
380                        let mut layout_info =
381                            sub_pin.layout_item_info(to_runtime(orientation), Some(idx));
382                        if let Some(child_item) = child_items.as_ref().and_then(|cc| cc.get(idx)) {
383                            fill_layout_info_constraints(
384                                &mut layout_info.constraint,
385                                &child_item.constraints,
386                                orientation,
387                                &expr_eval,
388                            );
389                        }
390                        constraints.push(layout_info);
391                    }
392                }
393            } else {
394                // Single repeated item
395                constraints.extend(
396                    component_vec
397                        .iter()
398                        .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
399                );
400            }
401        } else {
402            let mut layout_info = get_layout_info(
403                &layout_elem.item.element,
404                component,
405                &component.window_adapter(),
406                orientation,
407            );
408            fill_layout_info_constraints(
409                &mut layout_info,
410                &layout_elem.item.constraints,
411                orientation,
412                &expr_eval,
413            );
414            constraints.push(core_layout::LayoutItemInfo { constraint: layout_info });
415        }
416    }
417    constraints
418}
419
420fn box_layout_data(
421    box_layout: &i_slint_compiler::layout::BoxLayout,
422    orientation: Orientation,
423    component: InstanceRef,
424    expr_eval: &impl Fn(&NamedReference) -> f32,
425    mut repeater_indices: Option<&mut Vec<u32>>,
426) -> (Vec<core_layout::LayoutItemInfo>, i_slint_core::items::LayoutAlignment) {
427    let window_adapter = component.window_adapter();
428    let mut cells = Vec::with_capacity(box_layout.elems.len());
429    for cell in &box_layout.elems {
430        if cell.element.borrow().repeated.is_some() {
431            let component_vec = repeater_instances(component, &cell.element);
432            if let Some(ri) = repeater_indices.as_mut() {
433                ri.push(cells.len() as _);
434                ri.push(component_vec.len() as _);
435            }
436            cells.extend(
437                component_vec
438                    .iter()
439                    .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation), None)),
440            );
441        } else {
442            let mut layout_info =
443                get_layout_info(&cell.element, component, &window_adapter, orientation);
444            fill_layout_info_constraints(
445                &mut layout_info,
446                &cell.constraints,
447                orientation,
448                &expr_eval,
449            );
450            cells.push(core_layout::LayoutItemInfo { constraint: layout_info });
451        }
452    }
453    let alignment = box_layout
454        .geometry
455        .alignment
456        .as_ref()
457        .map(|nr| {
458            eval::load_property(component, &nr.element(), nr.name())
459                .unwrap()
460                .try_into()
461                .unwrap_or_default()
462        })
463        .unwrap_or_default();
464    (cells, alignment)
465}
466
467pub(crate) fn fill_layout_info_constraints(
468    layout_info: &mut core_layout::LayoutInfo,
469    constraints: &LayoutConstraints,
470    orientation: Orientation,
471    expr_eval: &impl Fn(&NamedReference) -> f32,
472) {
473    let is_percent =
474        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
475
476    match orientation {
477        Orientation::Horizontal => {
478            if let Some(e) = constraints.min_width.as_ref() {
479                if !is_percent(e) {
480                    layout_info.min = expr_eval(e)
481                } else {
482                    layout_info.min_percent = expr_eval(e)
483                }
484            }
485            if let Some(e) = constraints.max_width.as_ref() {
486                if !is_percent(e) {
487                    layout_info.max = expr_eval(e)
488                } else {
489                    layout_info.max_percent = expr_eval(e)
490                }
491            }
492            if let Some(e) = constraints.preferred_width.as_ref() {
493                layout_info.preferred = expr_eval(e);
494            }
495            if let Some(e) = constraints.horizontal_stretch.as_ref() {
496                layout_info.stretch = expr_eval(e);
497            }
498        }
499        Orientation::Vertical => {
500            if let Some(e) = constraints.min_height.as_ref() {
501                if !is_percent(e) {
502                    layout_info.min = expr_eval(e)
503                } else {
504                    layout_info.min_percent = expr_eval(e)
505                }
506            }
507            if let Some(e) = constraints.max_height.as_ref() {
508                if !is_percent(e) {
509                    layout_info.max = expr_eval(e)
510                } else {
511                    layout_info.max_percent = expr_eval(e)
512                }
513            }
514            if let Some(e) = constraints.preferred_height.as_ref() {
515                layout_info.preferred = expr_eval(e);
516            }
517            if let Some(e) = constraints.vertical_stretch.as_ref() {
518                layout_info.stretch = expr_eval(e);
519            }
520        }
521    }
522}
523
524/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
525pub(crate) fn get_layout_info(
526    elem: &ElementRc,
527    component: InstanceRef,
528    window_adapter: &Rc<dyn WindowAdapter>,
529    orientation: Orientation,
530) -> core_layout::LayoutInfo {
531    let elem = elem.borrow();
532    if let Some(nr) = elem.layout_info_prop(orientation) {
533        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
534    } else {
535        let item = &component
536            .description
537            .items
538            .get(elem.id.as_str())
539            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
540        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
541
542        unsafe {
543            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
544                to_runtime(orientation),
545                window_adapter,
546                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
547            )
548        }
549    }
550}