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