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