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::dynamic_item_tree::InstanceRef;
5use crate::eval::{self, EvalLocalContext};
6use crate::Value;
7use i_slint_compiler::expression_tree::Expression;
8use i_slint_compiler::langtype::Type;
9use i_slint_compiler::layout::{Layout, LayoutConstraints, LayoutGeometry, Orientation};
10use i_slint_compiler::namedreference::NamedReference;
11use i_slint_compiler::object_tree::ElementRc;
12use i_slint_core::items::{DialogButtonRole, ItemRc};
13use i_slint_core::layout::{self as core_layout};
14use i_slint_core::model::RepeatedItemTree;
15use i_slint_core::slice::Slice;
16use i_slint_core::window::WindowAdapter;
17use std::rc::Rc;
18use std::str::FromStr;
19
20pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation {
21    match o {
22        Orientation::Horizontal => core_layout::Orientation::Horizontal,
23        Orientation::Vertical => core_layout::Orientation::Vertical,
24    }
25}
26
27pub(crate) fn from_runtime(o: core_layout::Orientation) -> Orientation {
28    match o {
29        core_layout::Orientation::Horizontal => Orientation::Horizontal,
30        core_layout::Orientation::Vertical => Orientation::Vertical,
31    }
32}
33
34pub(crate) fn compute_layout_info(
35    lay: &Layout,
36    orientation: Orientation,
37    local_context: &mut EvalLocalContext,
38) -> Value {
39    let component = local_context.component_instance;
40    let expr_eval = |nr: &NamedReference| -> f32 {
41        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
42    };
43    match lay {
44        Layout::GridLayout(grid_layout) => {
45            let cells = grid_layout_data(grid_layout, orientation, component, &expr_eval);
46            let (padding, spacing) =
47                padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
48            core_layout::grid_layout_info(Slice::from(cells.as_slice()), spacing, &padding).into()
49        }
50        Layout::BoxLayout(box_layout) => {
51            let (cells, alignment) =
52                box_layout_data(box_layout, orientation, component, &expr_eval, None);
53            let (padding, spacing) =
54                padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
55            if orientation == box_layout.orientation {
56                core_layout::box_layout_info(
57                    Slice::from(cells.as_slice()),
58                    spacing,
59                    &padding,
60                    alignment,
61                )
62            } else {
63                core_layout::box_layout_info_ortho(Slice::from(cells.as_slice()), &padding)
64            }
65            .into()
66        }
67    }
68}
69
70pub(crate) fn solve_layout(
71    lay: &Layout,
72    orientation: Orientation,
73    local_context: &mut EvalLocalContext,
74) -> Value {
75    let component = local_context.component_instance;
76    let expr_eval = |nr: &NamedReference| -> f32 {
77        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
78    };
79
80    match lay {
81        Layout::GridLayout(grid_layout) => {
82            let mut cells = grid_layout_data(grid_layout, orientation, component, &expr_eval);
83            if let (Some(buttons_roles), Orientation::Horizontal) =
84                (&grid_layout.dialog_button_roles, orientation)
85            {
86                let roles = buttons_roles
87                    .iter()
88                    .map(|r| DialogButtonRole::from_str(r).unwrap())
89                    .collect::<Vec<_>>();
90                core_layout::reorder_dialog_button_layout(&mut cells, &roles);
91            }
92
93            let (padding, spacing) =
94                padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
95
96            let size_ref = grid_layout.geometry.rect.size_reference(orientation);
97            core_layout::solve_grid_layout(&core_layout::GridLayoutData {
98                size: size_ref.map(expr_eval).unwrap_or(0.),
99                spacing,
100                padding,
101                cells: Slice::from(cells.as_slice()),
102            })
103            .into()
104        }
105        Layout::BoxLayout(box_layout) => {
106            let mut repeated_indices = Vec::new();
107            let (cells, alignment) = box_layout_data(
108                box_layout,
109                orientation,
110                component,
111                &expr_eval,
112                Some(&mut repeated_indices),
113            );
114            let (padding, spacing) =
115                padding_and_spacing(&box_layout.geometry, orientation, &expr_eval);
116            let size_ref = match orientation {
117                Orientation::Horizontal => &box_layout.geometry.rect.width_reference,
118                Orientation::Vertical => &box_layout.geometry.rect.height_reference,
119            };
120            core_layout::solve_box_layout(
121                &core_layout::BoxLayoutData {
122                    size: size_ref.as_ref().map(expr_eval).unwrap_or(0.),
123                    spacing,
124                    padding,
125                    alignment,
126                    cells: Slice::from(cells.as_slice()),
127                },
128                Slice::from(repeated_indices.as_slice()),
129            )
130            .into()
131        }
132    }
133}
134
135fn padding_and_spacing(
136    layout_geometry: &LayoutGeometry,
137    orientation: Orientation,
138    expr_eval: &impl Fn(&NamedReference) -> f32,
139) -> (core_layout::Padding, f32) {
140    let spacing = layout_geometry.spacing.orientation(orientation).map_or(0., expr_eval);
141    let (begin, end) = layout_geometry.padding.begin_end(orientation);
142    let padding =
143        core_layout::Padding { begin: begin.map_or(0., expr_eval), end: end.map_or(0., expr_eval) };
144    (padding, spacing)
145}
146
147/// return the celldata, the padding, and the spacing of a grid layout
148fn grid_layout_data(
149    grid_layout: &i_slint_compiler::layout::GridLayout,
150    orientation: Orientation,
151    component: InstanceRef,
152    expr_eval: &impl Fn(&NamedReference) -> f32,
153) -> Vec<core_layout::GridLayoutCellData> {
154    let cells = grid_layout
155        .elems
156        .iter()
157        .map(|cell| {
158            let mut layout_info = get_layout_info(
159                &cell.item.element,
160                component,
161                &component.window_adapter(),
162                orientation,
163            );
164            fill_layout_info_constraints(
165                &mut layout_info,
166                &cell.item.constraints,
167                orientation,
168                &expr_eval,
169            );
170            let (col_or_row, span) = cell.col_or_row_and_span(orientation);
171            core_layout::GridLayoutCellData { col_or_row, span, constraint: layout_info }
172        })
173        .collect::<Vec<_>>();
174    cells
175}
176
177fn box_layout_data(
178    box_layout: &i_slint_compiler::layout::BoxLayout,
179    orientation: Orientation,
180    component: InstanceRef,
181    expr_eval: &impl Fn(&NamedReference) -> f32,
182    mut repeater_indices: Option<&mut Vec<u32>>,
183) -> (Vec<core_layout::BoxLayoutCellData>, i_slint_core::items::LayoutAlignment) {
184    let window_adapter = component.window_adapter();
185    let mut cells = Vec::with_capacity(box_layout.elems.len());
186    for cell in &box_layout.elems {
187        if cell.element.borrow().repeated.is_some() {
188            generativity::make_guard!(guard);
189            let rep = crate::dynamic_item_tree::get_repeater_by_name(
190                component,
191                cell.element.borrow().id.as_str(),
192                guard,
193            );
194            rep.0.as_ref().ensure_updated(|| {
195                let instance = crate::dynamic_item_tree::instantiate(
196                    rep.1.clone(),
197                    component.self_weak().get().cloned(),
198                    None,
199                    None,
200                    Default::default(),
201                );
202                instance
203            });
204            let component_vec = rep.0.as_ref().instances_vec();
205            if let Some(ri) = repeater_indices.as_mut() {
206                ri.push(cells.len() as _);
207                ri.push(component_vec.len() as _);
208            }
209            cells.extend(
210                component_vec
211                    .iter()
212                    .map(|x| x.as_pin_ref().box_layout_data(to_runtime(orientation))),
213            );
214        } else {
215            let mut layout_info =
216                get_layout_info(&cell.element, component, &window_adapter, orientation);
217            fill_layout_info_constraints(
218                &mut layout_info,
219                &cell.constraints,
220                orientation,
221                &expr_eval,
222            );
223            cells.push(core_layout::BoxLayoutCellData { constraint: layout_info });
224        }
225    }
226    let alignment = box_layout
227        .geometry
228        .alignment
229        .as_ref()
230        .map(|nr| {
231            eval::load_property(component, &nr.element(), nr.name())
232                .unwrap()
233                .try_into()
234                .unwrap_or_default()
235        })
236        .unwrap_or_default();
237    (cells, alignment)
238}
239
240pub(crate) fn fill_layout_info_constraints(
241    layout_info: &mut core_layout::LayoutInfo,
242    constraints: &LayoutConstraints,
243    orientation: Orientation,
244    expr_eval: &impl Fn(&NamedReference) -> f32,
245) {
246    let is_percent =
247        |nr: &NamedReference| Expression::PropertyReference(nr.clone()).ty() == Type::Percent;
248
249    match orientation {
250        Orientation::Horizontal => {
251            if let Some(e) = constraints.min_width.as_ref() {
252                if !is_percent(e) {
253                    layout_info.min = expr_eval(e)
254                } else {
255                    layout_info.min_percent = expr_eval(e)
256                }
257            }
258            if let Some(e) = constraints.max_width.as_ref() {
259                if !is_percent(e) {
260                    layout_info.max = expr_eval(e)
261                } else {
262                    layout_info.max_percent = expr_eval(e)
263                }
264            }
265            if let Some(e) = constraints.preferred_width.as_ref() {
266                layout_info.preferred = expr_eval(e);
267            }
268            if let Some(e) = constraints.horizontal_stretch.as_ref() {
269                layout_info.stretch = expr_eval(e);
270            }
271        }
272        Orientation::Vertical => {
273            if let Some(e) = constraints.min_height.as_ref() {
274                if !is_percent(e) {
275                    layout_info.min = expr_eval(e)
276                } else {
277                    layout_info.min_percent = expr_eval(e)
278                }
279            }
280            if let Some(e) = constraints.max_height.as_ref() {
281                if !is_percent(e) {
282                    layout_info.max = expr_eval(e)
283                } else {
284                    layout_info.max_percent = expr_eval(e)
285                }
286            }
287            if let Some(e) = constraints.preferred_height.as_ref() {
288                layout_info.preferred = expr_eval(e);
289            }
290            if let Some(e) = constraints.vertical_stretch.as_ref() {
291                layout_info.stretch = expr_eval(e);
292            }
293        }
294    }
295}
296
297/// Get the layout info for an element based on the layout_info_prop or the builtin item layout_info
298pub(crate) fn get_layout_info(
299    elem: &ElementRc,
300    component: InstanceRef,
301    window_adapter: &Rc<dyn WindowAdapter>,
302    orientation: Orientation,
303) -> core_layout::LayoutInfo {
304    let elem = elem.borrow();
305    if let Some(nr) = elem.layout_info_prop(orientation) {
306        eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap()
307    } else {
308        let item = &component
309            .description
310            .items
311            .get(elem.id.as_str())
312            .unwrap_or_else(|| panic!("Internal error: Item {} not found", elem.id));
313        let item_comp = component.self_weak().get().unwrap().upgrade().unwrap();
314
315        unsafe {
316            item.item_from_item_tree(component.as_ptr()).as_ref().layout_info(
317                to_runtime(orientation),
318                window_adapter,
319                &ItemRc::new(vtable::VRc::into_dyn(item_comp), item.item_index()),
320            )
321        }
322    }
323}