1use crate::Value;
8use crate::eval::{EvalContext, eval_expression};
9use i_slint_compiler::layout::Orientation;
10use i_slint_compiler::llr::lower_layout_expression::{
11 MEASURE_KNOWN_H_LOCAL, MEASURE_KNOWN_W_LOCAL,
12};
13use i_slint_compiler::llr::{
14 BoxMeasureCell, Expression, FlexboxMeasureCell, FlexboxMeasureCellKind,
15};
16use i_slint_core::SharedVector;
17use i_slint_core::layout::{
18 BoxLayoutData, FlexboxLayoutData, FlexboxLayoutItemInfo, GridLayoutData, GridLayoutInputData,
19 LayoutInfo, LayoutItemInfo, Padding,
20};
21use i_slint_core::model::Model;
22use i_slint_core::slice::Slice;
23
24fn to_f32(v: &Value) -> f32 {
27 match v {
28 Value::Number(n) => *n as f32,
29 _ => 0.,
30 }
31}
32
33fn to_padding(v: &Value) -> Padding {
34 let Value::Struct(s) = v else { return Padding::default() };
35 let f = |k| match s.get_field(k) {
36 Some(Value::Number(n)) => *n as f32,
37 _ => 0.,
38 };
39 Padding { begin: f("begin"), end: f("end") }
40}
41
42fn to_enum<T: std::str::FromStr + Default>(v: &Value) -> T {
43 match v {
44 Value::EnumerationValue(_, n) => n.parse().unwrap_or_default(),
45 _ => T::default(),
46 }
47}
48
49fn to_cells(v: &Value) -> Vec<LayoutItemInfo> {
50 let Value::Model(m) = v else { return Vec::new() };
51 (0..m.row_count())
52 .filter_map(|i| {
53 let Value::Struct(s) = m.row_data(i)? else { return None };
54 let c = s.get_field("constraint")?;
55 Some(LayoutItemInfo {
56 constraint: c.clone().try_into().unwrap_or_default(),
57 cross_axis_self_alignment: s
59 .get_field("cross-axis-self-alignment")
60 .map(to_enum)
61 .unwrap_or_default(),
62 layout_order: match s.get_field("layout-order") {
64 Some(Value::Number(n)) => *n as i32,
65 _ => 0,
66 },
67 })
68 })
69 .collect()
70}
71
72pub(crate) fn flexbox_item_info_from_struct(s: &crate::api::Struct) -> FlexboxLayoutItemInfo {
77 let constraint: LayoutInfo =
78 s.get_field("constraint").cloned().and_then(|v| v.try_into().ok()).unwrap_or_default();
79 let props = match s.get_field("props") {
80 Some(Value::Struct(p)) => flex_props_from_struct(p),
81 _ => Default::default(),
82 };
83 FlexboxLayoutItemInfo { constraint, props }
84}
85
86pub(crate) fn flex_props_from_struct(
89 s: &crate::api::Struct,
90) -> i_slint_core::layout::FlexItemProps {
91 i_slint_core::layout::FlexItemProps {
92 cross_axis_self_alignment: s
93 .get_field("cross-axis-self-alignment")
94 .map(to_enum)
95 .unwrap_or_default(),
96 layout_order: match s.get_field("layout-order") {
97 Some(Value::Number(n)) => *n as i32,
98 _ => 0,
99 },
100 }
101}
102
103fn to_flex_props(v: &Value) -> Vec<i_slint_core::layout::FlexItemProps> {
104 let Value::Model(m) = v else { return Vec::new() };
105 (0..m.row_count())
106 .filter_map(|i| {
107 let Value::Struct(s) = m.row_data(i)? else { return None };
108 Some(flex_props_from_struct(&s))
109 })
110 .collect()
111}
112
113fn to_u32_vec(v: &Value) -> Vec<u32> {
114 let Value::Model(m) = v else { return Vec::new() };
115 (0..m.row_count())
116 .filter_map(|i| match m.row_data(i)? {
117 Value::Number(n) => Some(n as u32),
118 _ => None,
119 })
120 .collect()
121}
122
123fn to_grid_input_data(v: &Value) -> Vec<GridLayoutInputData> {
124 let Value::Model(m) = v else { return Vec::new() };
125 (0..m.row_count())
126 .filter_map(|i| {
127 let Value::Struct(s) = m.row_data(i)? else { return None };
128 let f = |k: &str| match s.get_field(k) {
129 Some(Value::Number(n)) => *n as f32,
130 _ => 0.,
131 };
132 Some(GridLayoutInputData {
133 new_row: matches!(s.get_field("new-row"), Some(Value::Bool(true))),
134 col: f("col"),
135 row: f("row"),
136 colspan: f("colspan"),
137 rowspan: f("rowspan"),
138 })
139 })
140 .collect()
141}
142
143fn to_array_of_u16(v: &Value) -> SharedVector<u16> {
144 match v {
145 Value::ArrayOfU16(v) => v.clone(),
146 _ => Default::default(),
147 }
148}
149
150fn to_dialog_roles(v: &Value) -> Vec<i_slint_core::items::DialogButtonRole> {
151 let Value::Model(m) = v else { return Vec::new() };
152 (0..m.row_count())
153 .filter_map(|i| match m.row_data(i)? {
154 Value::EnumerationValue(_, n) => n.parse().ok(),
155 _ => None,
156 })
157 .collect()
158}
159
160fn sf32(s: &crate::api::Struct, k: &str) -> f32 {
161 match s.get_field(k) {
162 Some(Value::Number(n)) => *n as f32,
163 _ => 0.,
164 }
165}
166
167pub(crate) fn call_extra_builtin(
170 ctx: &mut EvalContext,
171 name: &str,
172 arguments: &[Expression],
173) -> Value {
174 let a: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
175
176 match name {
177 "box_layout_info" => {
178 let c = to_cells(&a[0]);
179 i_slint_core::layout::box_layout_info(
180 Slice::from_slice(&c),
181 to_f32(&a[1]),
182 &to_padding(&a[2]),
183 to_enum(&a[3]),
184 )
185 .into()
186 }
187 "box_layout_info_ortho" => {
188 let c = to_cells(&a[0]);
189 i_slint_core::layout::box_layout_info_ortho(Slice::from_slice(&c), &to_padding(&a[1]))
190 .into()
191 }
192 "organize_dialog_button_layout" => {
193 let input = to_grid_input_data(&a[0]);
194 let roles = to_dialog_roles(&a[1]);
195 Value::ArrayOfU16(i_slint_core::layout::organize_dialog_button_layout(
196 Slice::from_slice(&input),
197 Slice::from_slice(&roles),
198 ))
199 }
200 "organize_grid_layout" => {
201 let (input, ri, rs) = (to_grid_input_data(&a[0]), to_u32_vec(&a[1]), to_u32_vec(&a[2]));
202 Value::ArrayOfU16(i_slint_core::layout::organize_grid_layout(
203 Slice::from_slice(&input),
204 Slice::from_slice(&ri),
205 Slice::from_slice(&rs),
206 ))
207 }
208 "grid_layout_info" => {
209 let (c, ri, rs) = (to_cells(&a[1]), to_u32_vec(&a[2]), to_u32_vec(&a[3]));
210 i_slint_core::layout::grid_layout_info(
211 to_array_of_u16(&a[0]),
212 Slice::from_slice(&c),
213 Slice::from_slice(&ri),
214 Slice::from_slice(&rs),
215 to_f32(&a[4]),
216 &to_padding(&a[5]),
217 to_enum(&a[6]),
218 )
219 .into()
220 }
221 "solve_grid_layout" => {
222 let (c, ri, rs) = (to_cells(&a[1]), to_u32_vec(&a[3]), to_u32_vec(&a[4]));
223 let Value::Struct(s) = &a[0] else { return Value::LayoutCache(Default::default()) };
224 Value::LayoutCache(i_slint_core::layout::solve_grid_layout(
225 &GridLayoutData {
226 size: sf32(s, "size"),
227 spacing: sf32(s, "spacing"),
228 padding: s.get_field("padding").map(to_padding).unwrap_or_default(),
229 organized_data: s
230 .get_field("organized-data")
231 .map(to_array_of_u16)
232 .unwrap_or_default(),
233 },
234 Slice::from_slice(&c),
235 to_enum(&a[2]),
236 Slice::from_slice(&ri),
237 Slice::from_slice(&rs),
238 ))
239 }
240 "solve_box_layout" => {
241 let ri = to_u32_vec(&a[1]);
242 let Value::Struct(s) = &a[0] else { return Value::LayoutCache(Default::default()) };
243 let cells = s.get_field("cells").map(to_cells).unwrap_or_default();
244 Value::LayoutCache(i_slint_core::layout::solve_box_layout(
245 &BoxLayoutData {
246 size: sf32(s, "size"),
247 spacing: sf32(s, "spacing"),
248 padding: s.get_field("padding").map(to_padding).unwrap_or_default(),
249 alignment: s.get_field("alignment").map(to_enum).unwrap_or_default(),
250 cells: Slice::from_slice(&cells),
251 },
252 Slice::from_slice(&ri),
253 ))
254 }
255 "solve_box_layout_ortho" => {
256 let ri = to_u32_vec(&a[1]);
257 let Value::Struct(s) = &a[0] else { return Value::LayoutCache(Default::default()) };
258 let cells = s.get_field("cells").map(to_cells).unwrap_or_default();
259 Value::LayoutCache(i_slint_core::layout::solve_box_layout_ortho(
260 &i_slint_core::layout::BoxLayoutOrthoData {
261 size: sf32(s, "size"),
262 padding: s.get_field("padding").map(to_padding).unwrap_or_default(),
263 cross_axis_alignment: s
264 .get_field("cross-axis-alignment")
265 .map(to_enum)
266 .unwrap_or_default(),
267 cells: Slice::from_slice(&cells),
268 },
269 Slice::from_slice(&ri),
270 ))
271 }
272 "solve_flexbox_layout" => {
273 let ri = to_u32_vec(&a[1]);
274 let Value::Struct(s) = &a[0] else { return Value::LayoutCache(Default::default()) };
275 let (ch, cv) = (
276 s.get_field("cells-h").map(to_cells).unwrap_or_default(),
277 s.get_field("cells-v").map(to_cells).unwrap_or_default(),
278 );
279 let fp = s.get_field("flex-props").map(to_flex_props).unwrap_or_default();
280 Value::LayoutCache(i_slint_core::layout::solve_flexbox_layout(
281 &FlexboxLayoutData {
282 width: sf32(s, "width"),
283 height: sf32(s, "height"),
284 spacing_h: sf32(s, "spacing_h"),
285 spacing_v: sf32(s, "spacing_v"),
286 padding_h: s.get_field("padding-h").map(to_padding).unwrap_or_default(),
287 padding_v: s.get_field("padding-v").map(to_padding).unwrap_or_default(),
288 alignment: s.get_field("alignment").map(to_enum).unwrap_or_default(),
289 direction: s.get_field("direction").map(to_enum).unwrap_or_default(),
290 cross_axis_line_alignment: s
291 .get_field("cross-axis-line-alignment")
292 .map(to_enum)
293 .unwrap_or_default(),
294 cross_axis_alignment: s
295 .get_field("cross-axis-alignment")
296 .map(to_enum)
297 .unwrap_or_default(),
298 flex_wrap: s.get_field("flex-wrap").map(to_enum).unwrap_or_default(),
299 cells_h: Slice::from_slice(&ch),
300 cells_v: Slice::from_slice(&cv),
301 flex_props: Slice::from_slice(&fp),
302 },
303 Slice::from_slice(&ri),
304 ))
305 }
306 "flexbox_layout_info_main_axis" => {
307 let cells = to_cells(&a[0]);
308 i_slint_core::layout::flexbox_layout_info_main_axis(
309 Slice::from_slice(&cells),
310 to_f32(&a[1]),
311 &to_padding(&a[2]),
312 to_enum(&a[3]),
313 )
314 .into()
315 }
316 "flexbox_layout_unwrapped_main" => {
317 let cells = to_cells(&a[0]);
318 Value::Number(i_slint_core::layout::flexbox_layout_unwrapped_main(
319 Slice::from_slice(&cells),
320 to_f32(&a[1]),
321 &to_padding(&a[2]),
322 ) as f64)
323 }
324 "flexbox_layout_info_cross_axis" => {
325 let (ch, cv) = (to_cells(&a[0]), to_cells(&a[1]));
326 let fp = to_flex_props(&a[2]);
327 i_slint_core::layout::flexbox_layout_info_cross_axis(
328 Slice::from_slice(&ch),
329 Slice::from_slice(&cv),
330 Slice::from_slice(&fp),
331 to_f32(&a[3]),
332 to_f32(&a[4]),
333 &to_padding(&a[5]),
334 &to_padding(&a[6]),
335 to_enum(&a[7]),
336 to_enum(&a[8]),
337 to_enum(&a[9]),
338 to_f32(&a[10]),
339 )
340 .into()
341 }
342 other => unimplemented!("ExtraBuiltinFunctionCall `{other}`"),
343 }
344}
345
346fn eval_info(ctx: &mut EvalContext, e: &Expression) -> LayoutInfo {
347 eval_expression(ctx, e).try_into().unwrap_or_default()
348}
349
350struct FlatCell<'a> {
353 kind: FlatCellKind<'a>,
354 w4h_only: bool,
355}
356
357enum FlatCellKind<'a> {
358 Static {
359 h_info: &'a Expression,
360 v_info: &'a Expression,
361 },
362 Repeated(vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>),
363 Fixed,
365}
366
367fn flatten_measure_cells<'a>(
371 ctx: &mut EvalContext,
372 measure_cells: &'a [FlexboxMeasureCell],
373) -> Vec<FlatCell<'a>> {
374 let mut flat: Vec<FlatCell> = Vec::with_capacity(measure_cells.len());
375 for item in measure_cells {
376 match &item.kind {
377 FlexboxMeasureCellKind::Static { h_info, v_info } => flat.push(FlatCell {
378 kind: FlatCellKind::Static { h_info, v_info },
379 w4h_only: item.w4h_only,
380 }),
381 FlexboxMeasureCellKind::Repeated(repeater) => {
382 if let Some(current) = ctx.current.as_ref() {
383 let rep = ¤t.repeaters[repeater.repeater_index];
384 rep.track_instance_changes();
385 flat.extend(rep.instances_vec().into_iter().map(|instance| FlatCell {
386 kind: FlatCellKind::Repeated(instance),
387 w4h_only: item.w4h_only,
388 }));
389 }
390 }
391 FlexboxMeasureCellKind::Fixed => {
392 flat.push(FlatCell { kind: FlatCellKind::Fixed, w4h_only: item.w4h_only })
393 }
394 }
395 }
396 flat
397}
398
399fn measure_flexbox_cell(
407 ctx: &mut EvalContext,
408 flat: &[FlatCell],
409 index: usize,
410 w: f32,
411 h: f32,
412 known_w: bool,
413 known_h: bool,
414) -> (f32, f32) {
415 let Some(cell) = flat.get(index) else { return (w, h) };
416 let measure_height = |ctx: &mut EvalContext| match &cell.kind {
418 FlatCellKind::Static { v_info, .. } => {
419 let prev = ctx.locals.insert(MEASURE_KNOWN_W_LOCAL.into(), Value::Number(w as f64));
420 let info = eval_info(ctx, v_info);
421 crate::eval::restore_local(ctx, MEASURE_KNOWN_W_LOCAL, prev);
422 (w, info.preferred_bounded())
423 }
424 FlatCellKind::Repeated(instance) => (
425 w,
426 instance
427 .as_pin_ref()
428 .flexbox_layout_item_info_at_cross_width(w)
429 .constraint
430 .preferred_bounded(),
431 ),
432 FlatCellKind::Fixed => (w, h),
433 };
434 let measure_width = |ctx: &mut EvalContext| match &cell.kind {
436 FlatCellKind::Static { h_info, .. } => {
437 let prev = ctx.locals.insert(MEASURE_KNOWN_H_LOCAL.into(), Value::Number(h as f64));
438 let info = eval_info(ctx, h_info);
439 crate::eval::restore_local(ctx, MEASURE_KNOWN_H_LOCAL, prev);
440 (info.preferred_bounded(), h)
441 }
442 FlatCellKind::Repeated(instance) => (
443 instance
444 .as_pin_ref()
445 .flexbox_layout_item_info_at_cross_height(h)
446 .constraint
447 .preferred_bounded(),
448 h,
449 ),
450 FlatCellKind::Fixed => (w, h),
451 };
452 match (known_w, known_h) {
453 (true, true) => (w, h),
454 (true, false) => measure_height(ctx),
455 (false, true) => measure_width(ctx),
456 (false, false) => {
457 if cell.w4h_only {
458 measure_width(ctx)
459 } else {
460 measure_height(ctx)
461 }
462 }
463 }
464}
465
466pub(crate) fn solve_flexbox_layout_with_measure(ctx: &mut EvalContext, expr: &Expression) -> Value {
468 let Expression::SolveFlexboxLayoutWithMeasure { data, repeater_indices, measure_cells } = expr
469 else {
470 return Value::Void;
471 };
472 let ri = to_u32_vec(&eval_expression(ctx, repeater_indices));
473 let data = eval_expression(ctx, data);
474 let Value::Struct(s) = &data else { return Value::LayoutCache(Default::default()) };
475 let (ch, cv) = (
476 s.get_field("cells-h").map(to_cells).unwrap_or_default(),
477 s.get_field("cells-v").map(to_cells).unwrap_or_default(),
478 );
479 let fp = s.get_field("flex-props").map(to_flex_props).unwrap_or_default();
480
481 let flat = flatten_measure_cells(ctx, measure_cells);
482 let mut measure = |index: usize, w: f32, h: f32, known_w: bool, known_h: bool| {
483 measure_flexbox_cell(ctx, &flat, index, w, h, known_w, known_h)
484 };
485
486 Value::LayoutCache(i_slint_core::layout::solve_flexbox_layout_with_measure(
487 &FlexboxLayoutData {
488 width: sf32(s, "width"),
489 height: sf32(s, "height"),
490 spacing_h: sf32(s, "spacing_h"),
491 spacing_v: sf32(s, "spacing_v"),
492 padding_h: s.get_field("padding-h").map(to_padding).unwrap_or_default(),
493 padding_v: s.get_field("padding-v").map(to_padding).unwrap_or_default(),
494 alignment: s.get_field("alignment").map(to_enum).unwrap_or_default(),
495 direction: s.get_field("direction").map(to_enum).unwrap_or_default(),
496 cross_axis_line_alignment: s
497 .get_field("cross-axis-line-alignment")
498 .map(to_enum)
499 .unwrap_or_default(),
500 cross_axis_alignment: s
501 .get_field("cross-axis-alignment")
502 .map(to_enum)
503 .unwrap_or_default(),
504 flex_wrap: s.get_field("flex-wrap").map(to_enum).unwrap_or_default(),
505 cells_h: Slice::from_slice(&ch),
506 cells_v: Slice::from_slice(&cv),
507 flex_props: Slice::from_slice(&fp),
508 },
509 Slice::from_slice(&ri),
510 Some(&mut measure),
511 ))
512}
513
514pub(crate) fn box_layout_info_ortho_with_measure(
519 ctx: &mut EvalContext,
520 expr: &Expression,
521) -> Value {
522 use i_slint_core::model::RepeatedItemTree;
523 let Expression::BoxLayoutInfoOrthoWithMeasure {
524 solve_data,
525 padding_ortho,
526 orientation,
527 measure_cells,
528 } = expr
529 else {
530 return Value::Void;
531 };
532 let known_size_local = match orientation {
533 Orientation::Vertical => MEASURE_KNOWN_W_LOCAL,
534 Orientation::Horizontal => MEASURE_KNOWN_H_LOCAL,
535 };
536 let data = eval_expression(ctx, solve_data);
537 let Value::Struct(s) = &data else { return LayoutInfo::default().into() };
538 let cells = s.get_field("cells").map(to_cells).unwrap_or_default();
539 let solved = i_slint_core::layout::solve_box_layout(
540 &BoxLayoutData {
541 size: sf32(s, "size"),
542 spacing: sf32(s, "spacing"),
543 padding: s.get_field("padding").map(to_padding).unwrap_or_default(),
544 alignment: s.get_field("alignment").map(to_enum).unwrap_or_default(),
545 cells: Slice::from_slice(&cells),
546 },
547 Slice::from_slice(&[]),
548 );
549 let solved_size = |cursor: usize| solved.as_slice().get(cursor * 2 + 1).copied().unwrap_or(0.);
550 let mut out_cells: Vec<LayoutItemInfo> = Vec::with_capacity(cells.len());
551 let mut cursor = 0usize;
552 for cell in measure_cells {
553 match cell {
554 BoxMeasureCell::Static { info } => {
555 let prev = ctx
556 .locals
557 .insert(known_size_local.into(), Value::Number(solved_size(cursor) as f64));
558 let constraint = eval_info(ctx, info);
559 crate::eval::restore_local(ctx, known_size_local, prev);
560 out_cells.push(LayoutItemInfo { constraint, ..Default::default() });
561 cursor += 1;
562 }
563 BoxMeasureCell::Repeated(repeater) => {
564 let Some(current) = ctx.current.as_ref() else {
565 debug_assert!(false, "measure pass evaluated without a current instance");
569 return LayoutInfo::default().into();
570 };
571 let rep = ¤t.repeaters[repeater.repeater_index];
572 rep.track_instance_changes();
573 for instance in rep.instances_vec() {
574 let info = match orientation {
575 Orientation::Vertical => instance
576 .as_pin_ref()
577 .layout_item_info_at_cross_width(solved_size(cursor)),
578 Orientation::Horizontal => instance
579 .as_pin_ref()
580 .layout_item_info_at_cross_height(solved_size(cursor)),
581 };
582 out_cells.push(info);
583 cursor += 1;
584 }
585 }
586 }
587 }
588 i_slint_core::layout::box_layout_info_ortho(
589 Slice::from_slice(&out_cells),
590 &to_padding(&eval_expression(ctx, padding_ortho)),
591 )
592 .into()
593}
594
595pub(crate) fn flexbox_layout_info_cross_axis_with_measure(
600 ctx: &mut EvalContext,
601 expr: &Expression,
602) -> Value {
603 let Expression::FlexboxLayoutInfoCrossAxisWithMeasure { arguments, measure_cells } = expr
604 else {
605 return Value::Void;
606 };
607 let a: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
608 let (ch, cv) = (to_cells(&a[0]), to_cells(&a[1]));
609 let fp = to_flex_props(&a[2]);
610 let flat = flatten_measure_cells(ctx, measure_cells);
611 let mut measure = |index: usize, w: f32, h: f32, known_w: bool, known_h: bool| {
612 measure_flexbox_cell(ctx, &flat, index, w, h, known_w, known_h)
613 };
614 i_slint_core::layout::flexbox_layout_info_cross_axis_with_measure(
615 Slice::from_slice(&ch),
616 Slice::from_slice(&cv),
617 Slice::from_slice(&fp),
618 to_f32(&a[3]),
619 to_f32(&a[4]),
620 &to_padding(&a[5]),
621 &to_padding(&a[6]),
622 to_enum(&a[7]),
623 to_enum(&a[8]),
624 to_enum(&a[9]),
625 to_f32(&a[10]),
626 Some(&mut measure),
627 )
628 .into()
629}