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