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