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
4//! Dispatch for `Expression::ExtraBuiltinFunctionCall` — layout helper
5//! functions generated by the LLR's layout lowering pass.
6
7use 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
24// ── Value → layout-type converters ──────────────────────────────────────────
25
26fn 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                // Only set for a box layout's cross-axis cells; absent means `auto`.
58                cross_axis_self_alignment: s
59                    .get_field("cross-axis-self-alignment")
60                    .map(to_enum)
61                    .unwrap_or_default(),
62                // Only set for a box layout's main-axis cells; absent means 0.
63                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
72/// Convert one `Value::Struct` produced by the LLR's flexbox lowering:
73/// a `FlexboxLayoutItemInfo` with a `constraint` and a nested `props` field.
74/// `Struct::get_field` normalizes identifiers, so the kebab-case keys the
75/// lowering emits match regardless of spelling.
76pub(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
86/// Convert one `Value::Struct` produced by the LLR's flexbox lowering for a
87/// `FlexItemProps`.
88pub(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
167// ── Dispatch ────────────────────────────────────────────────────────────────
168
169pub(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
350/// One flexbox cell as seen by the measure callback, after expanding
351/// repeaters (a repeater contributes one entry per instance).
352struct 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    /// No constrained layout info: the pre-resolved sizes are already correct.
364    Fixed,
365}
366
367/// Flatten `measure_cells` into one entry per taffy cell. Static cells carry
368/// their `(h_info, v_info)` expressions; a repeater expands to one instance
369/// per row (re-measured through its own item tree at the assigned cross size).
370fn 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 = &current.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
399/// Measure callback body shared by the solve and cross-axis-info paths:
400/// re-evaluate the cell's perpendicular layout info with the
401/// `measure_known_w` / `measure_known_h` local set to the dimension taffy
402/// assigned (a dimension it did not assign, `known_* == false`, arrives
403/// pre-resolved to the cell's preferred size). A probe with neither dimension
404/// known measures the cell's free axis at the default size (see
405/// `FlexboxMeasureFn` in i-slint-core).
406fn 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    // measure the height at the width `w`
417    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    // measure the width at the height `h`
435    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
466/// Interpret [`Expression::SolveFlexboxLayoutWithMeasure`].
467pub(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
514/// Interpret [`Expression::BoxLayoutInfoOrthoWithMeasure`]: solve the box
515/// layout's main axis at the known cross-axis size, then fold the cells'
516/// cross-axis infos with `box_layout_info_ortho`, measuring each
517/// height-for-width (resp. width-for-height) cell at its solved main size.
518pub(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                    // Without an instance, the repeater's cell count is
566                    // unknown, so the later cells' solved sizes can't be
567                    // located either.
568                    debug_assert!(false, "measure pass evaluated without a current instance");
569                    return LayoutInfo::default().into();
570                };
571                let rep = &current.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
595/// Interpret [`Expression::FlexboxLayoutInfoCrossAxisWithMeasure`]: the
596/// `flexbox_layout_info_cross_axis` builtin plus the measure callback, so
597/// height-for-width cells are measured at the main-axis size taffy assigns
598/// them rather than at the container size the cells were pre-measured at.
599pub(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}