1use 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 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 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 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 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 for sub_comp in &component_vec {
368 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 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
524pub(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}