i_slint_compiler/llr/
lower_expression.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
4use std::cell::RefCell;
5use std::collections::{BTreeMap, HashMap};
6use std::num::NonZeroUsize;
7use std::rc::{Rc, Weak};
8
9use itertools::Either;
10use smol_str::{format_smolstr, SmolStr};
11
12use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState};
13use super::{Animation, PropertyIdx, PropertyReference, RepeatedElementIdx};
14use crate::expression_tree::{BuiltinFunction, Callable, Expression as tree_Expression};
15use crate::langtype::{EnumerationValue, Struct, Type};
16use crate::layout::Orientation;
17use crate::llr::Expression as llr_Expression;
18use crate::namedreference::NamedReference;
19use crate::object_tree::{Element, ElementRc, PropertyAnimation};
20use crate::typeregister::BUILTIN;
21
22pub struct ExpressionLoweringCtxInner<'a> {
23    pub component: &'a Rc<crate::object_tree::Component>,
24    /// The mapping for the current component
25    pub mapping: &'a LoweredSubComponentMapping,
26    pub parent: Option<&'a ExpressionLoweringCtxInner<'a>>,
27}
28#[derive(derive_more::Deref)]
29pub struct ExpressionLoweringCtx<'a> {
30    pub state: &'a mut LoweringState,
31    #[deref]
32    pub inner: ExpressionLoweringCtxInner<'a>,
33}
34
35impl ExpressionLoweringCtx<'_> {
36    pub fn map_property_reference(&self, from: &NamedReference) -> PropertyReference {
37        let element = from.element();
38        let enclosing = &element.borrow().enclosing_component.upgrade().unwrap();
39        if !enclosing.is_global() {
40            let mut map = &self.inner;
41            let mut level = 0;
42            while !Rc::ptr_eq(enclosing, map.component) {
43                map = map.parent.unwrap();
44                level += 1;
45            }
46            if let Some(level) = NonZeroUsize::new(level) {
47                return PropertyReference::InParent {
48                    level,
49                    parent_reference: Box::new(
50                        map.mapping.map_property_reference(from, self.state),
51                    ),
52                };
53            }
54        }
55        self.mapping.map_property_reference(from, self.state)
56    }
57}
58
59impl super::TypeResolutionContext for ExpressionLoweringCtx<'_> {
60    fn property_ty(&self, _: &PropertyReference) -> &Type {
61        todo!()
62    }
63}
64
65pub fn lower_expression(
66    expression: &tree_Expression,
67    ctx: &mut ExpressionLoweringCtx<'_>,
68) -> llr_Expression {
69    match expression {
70        tree_Expression::Invalid => {
71            panic!("internal error, encountered invalid expression at code generation time")
72        }
73        tree_Expression::Uncompiled(_) => panic!(),
74        tree_Expression::StringLiteral(s) => llr_Expression::StringLiteral(s.clone()),
75        tree_Expression::NumberLiteral(n, unit) => {
76            llr_Expression::NumberLiteral(unit.normalize(*n))
77        }
78        tree_Expression::BoolLiteral(b) => llr_Expression::BoolLiteral(*b),
79        tree_Expression::PropertyReference(nr) => {
80            llr_Expression::PropertyReference(ctx.map_property_reference(nr))
81        }
82        tree_Expression::ElementReference(e) => {
83            let elem = e.upgrade().unwrap();
84            let enclosing = elem.borrow().enclosing_component.upgrade().unwrap();
85            // When within a ShowPopupMenu builtin function, this is a reference to the root of the menu item tree
86            if Rc::ptr_eq(&elem, &enclosing.root_element) {
87                if let Some(idx) = ctx
88                    .component
89                    .menu_item_tree
90                    .borrow()
91                    .iter()
92                    .position(|c| Rc::ptr_eq(c, &enclosing))
93                {
94                    return llr_Expression::NumberLiteral(idx as _);
95                }
96            }
97
98            // We map an element reference to a reference to the property "" inside that native item
99            llr_Expression::PropertyReference(
100                ctx.map_property_reference(&NamedReference::new(&elem, SmolStr::default())),
101            )
102        }
103        tree_Expression::RepeaterIndexReference { element } => {
104            repeater_special_property(element, ctx.component, 1usize.into())
105        }
106        tree_Expression::RepeaterModelReference { element } => {
107            repeater_special_property(element, ctx.component, 0usize.into())
108        }
109        tree_Expression::FunctionParameterReference { index, .. } => {
110            llr_Expression::FunctionParameterReference { index: *index }
111        }
112        tree_Expression::StoreLocalVariable { name, value } => llr_Expression::StoreLocalVariable {
113            name: name.clone(),
114            value: Box::new(lower_expression(value, ctx)),
115        },
116        tree_Expression::ReadLocalVariable { name, ty } => {
117            llr_Expression::ReadLocalVariable { name: name.clone(), ty: ty.clone() }
118        }
119        tree_Expression::StructFieldAccess { base, name } => llr_Expression::StructFieldAccess {
120            base: Box::new(lower_expression(base, ctx)),
121            name: name.clone(),
122        },
123        tree_Expression::ArrayIndex { array, index } => llr_Expression::ArrayIndex {
124            array: Box::new(lower_expression(array, ctx)),
125            index: Box::new(lower_expression(index, ctx)),
126        },
127        tree_Expression::Cast { from, to } => {
128            llr_Expression::Cast { from: Box::new(lower_expression(from, ctx)), to: to.clone() }
129        }
130        tree_Expression::CodeBlock(expr) => {
131            llr_Expression::CodeBlock(expr.iter().map(|e| lower_expression(e, ctx)).collect::<_>())
132        }
133        tree_Expression::FunctionCall { function, arguments, .. } => match function {
134            Callable::Builtin(BuiltinFunction::RestartTimer) => lower_restart_timer(arguments),
135            Callable::Builtin(BuiltinFunction::ShowPopupWindow) => {
136                lower_show_popup_window(arguments, ctx)
137            }
138            Callable::Builtin(BuiltinFunction::ClosePopupWindow) => {
139                lower_close_popup_window(arguments, ctx)
140            }
141            Callable::Builtin(f) => {
142                let mut arguments =
143                    arguments.iter().map(|e| lower_expression(e, ctx)).collect::<Vec<_>>();
144                if *f == BuiltinFunction::Translate {
145                    if let llr_Expression::Array { as_model, .. } = &mut arguments[3] {
146                        *as_model = false;
147                    }
148                    #[cfg(feature = "bundle-translations")]
149                    if let Some(translation_builder) = ctx.state.translation_builder.as_mut() {
150                        return translation_builder.lower_translate_call(arguments);
151                    }
152                }
153                llr_Expression::BuiltinFunctionCall { function: f.clone(), arguments }
154            }
155            Callable::Callback(nr) => {
156                let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
157                llr_Expression::CallBackCall { callback: ctx.map_property_reference(nr), arguments }
158            }
159            Callable::Function(nr)
160                if nr
161                    .element()
162                    .borrow()
163                    .native_class()
164                    .is_some_and(|n| n.properties.contains_key(nr.name())) =>
165            {
166                llr_Expression::ItemMemberFunctionCall { function: ctx.map_property_reference(nr) }
167            }
168            Callable::Function(nr) => {
169                let arguments = arguments.iter().map(|e| lower_expression(e, ctx)).collect::<_>();
170                llr_Expression::FunctionCall { function: ctx.map_property_reference(nr), arguments }
171            }
172        },
173        tree_Expression::SelfAssignment { lhs, rhs, op, .. } => {
174            lower_assignment(lhs, rhs, *op, ctx)
175        }
176        tree_Expression::BinaryExpression { lhs, rhs, op } => llr_Expression::BinaryExpression {
177            lhs: Box::new(lower_expression(lhs, ctx)),
178            rhs: Box::new(lower_expression(rhs, ctx)),
179            op: *op,
180        },
181        tree_Expression::UnaryOp { sub, op } => {
182            llr_Expression::UnaryOp { sub: Box::new(lower_expression(sub, ctx)), op: *op }
183        }
184        tree_Expression::ImageReference { resource_ref, nine_slice, .. } => {
185            llr_Expression::ImageReference {
186                resource_ref: resource_ref.clone(),
187                nine_slice: *nine_slice,
188            }
189        }
190        tree_Expression::Condition { condition, true_expr, false_expr } => {
191            let (true_ty, false_ty) = (true_expr.ty(), false_expr.ty());
192            llr_Expression::Condition {
193                condition: Box::new(lower_expression(condition, ctx)),
194                true_expr: Box::new(lower_expression(true_expr, ctx)),
195                false_expr: if false_ty == Type::Invalid
196                    || false_ty == Type::Void
197                    || true_ty == false_ty
198                {
199                    Box::new(lower_expression(false_expr, ctx))
200                } else {
201                    // Because the type of the Condition is based on the false expression, we need to insert a cast
202                    Box::new(llr_Expression::Cast {
203                        from: Box::new(lower_expression(false_expr, ctx)),
204                        to: Type::Void,
205                    })
206                },
207            }
208        }
209        tree_Expression::Array { element_ty, values } => llr_Expression::Array {
210            element_ty: element_ty.clone(),
211            values: values.iter().map(|e| lower_expression(e, ctx)).collect::<_>(),
212            as_model: true,
213        },
214        tree_Expression::Struct { ty, values } => llr_Expression::Struct {
215            ty: ty.clone(),
216            values: values
217                .iter()
218                .map(|(s, e)| (s.clone(), lower_expression(e, ctx)))
219                .collect::<_>(),
220        },
221        tree_Expression::PathData(data) => compile_path(data, ctx),
222        tree_Expression::EasingCurve(x) => llr_Expression::EasingCurve(x.clone()),
223        tree_Expression::LinearGradient { angle, stops } => llr_Expression::LinearGradient {
224            angle: Box::new(lower_expression(angle, ctx)),
225            stops: stops
226                .iter()
227                .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
228                .collect::<_>(),
229        },
230        tree_Expression::RadialGradient { stops } => llr_Expression::RadialGradient {
231            stops: stops
232                .iter()
233                .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
234                .collect::<_>(),
235        },
236        tree_Expression::ConicGradient { stops } => llr_Expression::ConicGradient {
237            stops: stops
238                .iter()
239                .map(|(a, b)| (lower_expression(a, ctx), lower_expression(b, ctx)))
240                .collect::<_>(),
241        },
242        tree_Expression::EnumerationValue(e) => llr_Expression::EnumerationValue(e.clone()),
243        tree_Expression::ReturnStatement(..) => {
244            panic!("The remove return pass should have removed all return")
245        }
246        tree_Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
247            llr_Expression::LayoutCacheAccess {
248                layout_cache_prop: ctx.map_property_reference(layout_cache_prop),
249                index: *index,
250                repeater_index: repeater_index.as_ref().map(|e| lower_expression(e, ctx).into()),
251            }
252        }
253        tree_Expression::ComputeLayoutInfo(l, o) => compute_layout_info(l, *o, ctx),
254        tree_Expression::SolveLayout(l, o) => solve_layout(l, *o, ctx),
255        tree_Expression::MinMax { ty, op, lhs, rhs } => llr_Expression::MinMax {
256            ty: ty.clone(),
257            op: *op,
258            lhs: Box::new(lower_expression(lhs, ctx)),
259            rhs: Box::new(lower_expression(rhs, ctx)),
260        },
261        tree_Expression::EmptyComponentFactory => llr_Expression::EmptyComponentFactory,
262        tree_Expression::DebugHook { expression, .. } => lower_expression(expression, ctx),
263    }
264}
265
266fn lower_assignment(
267    lhs: &tree_Expression,
268    rhs: &tree_Expression,
269    op: char,
270    ctx: &mut ExpressionLoweringCtx,
271) -> llr_Expression {
272    match lhs {
273        tree_Expression::PropertyReference(nr) => {
274            let rhs = lower_expression(rhs, ctx);
275            let property = ctx.map_property_reference(nr);
276            let value = if op == '=' {
277                rhs
278            } else {
279                llr_Expression::BinaryExpression {
280                    lhs: llr_Expression::PropertyReference(property.clone()).into(),
281                    rhs: rhs.into(),
282                    op,
283                }
284            }
285            .into();
286            llr_Expression::PropertyAssignment { property, value }
287        }
288        tree_Expression::StructFieldAccess { base, name } => {
289            let ty = base.ty();
290
291            static COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
292            let unique_name = format_smolstr!(
293                "struct_assignment{}",
294                COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
295            );
296            let s = tree_Expression::StoreLocalVariable {
297                name: unique_name.clone(),
298                value: base.clone(),
299            };
300            let lower_base =
301                tree_Expression::ReadLocalVariable { name: unique_name, ty: ty.clone() };
302            let mut values = HashMap::new();
303            let Type::Struct(ty) = ty else { unreachable!() };
304
305            for field in ty.fields.keys() {
306                let e = if field != name {
307                    tree_Expression::StructFieldAccess {
308                        base: lower_base.clone().into(),
309                        name: field.clone(),
310                    }
311                } else if op == '=' {
312                    rhs.clone()
313                } else {
314                    tree_Expression::BinaryExpression {
315                        lhs: tree_Expression::StructFieldAccess {
316                            base: lower_base.clone().into(),
317                            name: field.clone(),
318                        }
319                        .into(),
320                        rhs: Box::new(rhs.clone()),
321                        op,
322                    }
323                };
324                values.insert(field.clone(), e);
325            }
326
327            let new_value =
328                tree_Expression::CodeBlock(vec![s, tree_Expression::Struct { ty, values }]);
329            lower_assignment(base, &new_value, '=', ctx)
330        }
331        tree_Expression::RepeaterModelReference { element } => {
332            let rhs = lower_expression(rhs, ctx);
333            let prop = repeater_special_property(element, ctx.component, 0usize.into());
334
335            let level = match &prop {
336                llr_Expression::PropertyReference(PropertyReference::InParent {
337                    level, ..
338                }) => (*level).into(),
339                _ => 0,
340            };
341
342            let value = Box::new(if op == '=' {
343                rhs
344            } else {
345                llr_Expression::BinaryExpression { lhs: prop.into(), rhs: rhs.into(), op }
346            });
347
348            llr_Expression::ModelDataAssignment { level, value }
349        }
350        tree_Expression::ArrayIndex { array, index } => {
351            let rhs = lower_expression(rhs, ctx);
352            let array = Box::new(lower_expression(array, ctx));
353            let index = Box::new(lower_expression(index, ctx));
354            let value = Box::new(if op == '=' {
355                rhs
356            } else {
357                // FIXME: this will compute the index and the array twice:
358                // Ideally we should store the index and the array in local variable
359                llr_Expression::BinaryExpression {
360                    lhs: llr_Expression::ArrayIndex { array: array.clone(), index: index.clone() }
361                        .into(),
362                    rhs: rhs.into(),
363                    op,
364                }
365            });
366
367            llr_Expression::ArrayIndexAssignment { array, index, value }
368        }
369        _ => panic!("not a rvalue"),
370    }
371}
372
373fn repeater_special_property(
374    element: &Weak<RefCell<Element>>,
375    component: &Rc<crate::object_tree::Component>,
376    property_index: PropertyIdx,
377) -> llr_Expression {
378    let mut r = PropertyReference::Local { sub_component_path: vec![], property_index };
379    let enclosing = element.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
380    let mut level = 0;
381    let mut component = component.clone();
382    while !Rc::ptr_eq(&enclosing, &component) {
383        component = component
384            .parent_element
385            .upgrade()
386            .unwrap()
387            .borrow()
388            .enclosing_component
389            .upgrade()
390            .unwrap();
391        level += 1;
392    }
393    if let Some(level) = NonZeroUsize::new(level - 1) {
394        r = PropertyReference::InParent { level, parent_reference: Box::new(r) };
395    }
396    llr_Expression::PropertyReference(r)
397}
398
399fn lower_restart_timer(args: &[tree_Expression]) -> llr_Expression {
400    if let [tree_Expression::ElementReference(e)] = args {
401        let timer_element = e.upgrade().unwrap();
402        let timer_comp = timer_element.borrow().enclosing_component.upgrade().unwrap();
403
404        let timer_list = timer_comp.timers.borrow();
405        let timer_index = timer_list
406            .iter()
407            .position(|t| Rc::ptr_eq(&t.element.upgrade().unwrap(), &timer_element))
408            .unwrap();
409
410        llr_Expression::BuiltinFunctionCall {
411            function: BuiltinFunction::RestartTimer,
412            arguments: vec![llr_Expression::NumberLiteral(timer_index as _)],
413        }
414    } else {
415        panic!("invalid arguments to RestartTimer");
416    }
417}
418
419fn lower_show_popup_window(
420    args: &[tree_Expression],
421    ctx: &mut ExpressionLoweringCtx,
422) -> llr_Expression {
423    if let [tree_Expression::ElementReference(e)] = args {
424        let popup_window = e.upgrade().unwrap();
425        let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
426        let parent_component = pop_comp
427            .parent_element
428            .upgrade()
429            .unwrap()
430            .borrow()
431            .enclosing_component
432            .upgrade()
433            .unwrap();
434        let popup_list = parent_component.popup_windows.borrow();
435        let (popup_index, popup) = popup_list
436            .iter()
437            .enumerate()
438            .find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp))
439            .unwrap();
440        let item_ref = lower_expression(
441            &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
442            ctx,
443        );
444
445        llr_Expression::BuiltinFunctionCall {
446            function: BuiltinFunction::ShowPopupWindow,
447            arguments: vec![
448                llr_Expression::NumberLiteral(popup_index as _),
449                llr_Expression::EnumerationValue(popup.close_policy.clone()),
450                item_ref,
451            ],
452        }
453    } else {
454        panic!("invalid arguments to ShowPopupWindow");
455    }
456}
457
458fn lower_close_popup_window(
459    args: &[tree_Expression],
460    ctx: &mut ExpressionLoweringCtx,
461) -> llr_Expression {
462    if let [tree_Expression::ElementReference(e)] = args {
463        let popup_window = e.upgrade().unwrap();
464        let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
465        let parent_component = pop_comp
466            .parent_element
467            .upgrade()
468            .unwrap()
469            .borrow()
470            .enclosing_component
471            .upgrade()
472            .unwrap();
473        let popup_list = parent_component.popup_windows.borrow();
474        let (popup_index, popup) = popup_list
475            .iter()
476            .enumerate()
477            .find(|(_, p)| Rc::ptr_eq(&p.component, &pop_comp))
478            .unwrap();
479        let item_ref = lower_expression(
480            &tree_Expression::ElementReference(Rc::downgrade(&popup.parent_element)),
481            ctx,
482        );
483
484        llr_Expression::BuiltinFunctionCall {
485            function: BuiltinFunction::ClosePopupWindow,
486            arguments: vec![llr_Expression::NumberLiteral(popup_index as _), item_ref],
487        }
488    } else {
489        panic!("invalid arguments to ShowPopupWindow");
490    }
491}
492
493pub fn lower_animation(a: &PropertyAnimation, ctx: &mut ExpressionLoweringCtx<'_>) -> Animation {
494    fn lower_animation_element(
495        a: &ElementRc,
496        ctx: &mut ExpressionLoweringCtx<'_>,
497    ) -> llr_Expression {
498        llr_Expression::Struct {
499            values: animation_fields()
500                .map(|(k, ty)| {
501                    let e = a.borrow().bindings.get(&k).map_or_else(
502                        || llr_Expression::default_value_for_type(&ty).unwrap(),
503                        |v| lower_expression(&v.borrow().expression, ctx),
504                    );
505                    (k, e)
506                })
507                .collect::<_>(),
508            ty: animation_ty(),
509        }
510    }
511
512    fn animation_fields() -> impl Iterator<Item = (SmolStr, Type)> {
513        IntoIterator::into_iter([
514            (SmolStr::new_static("duration"), Type::Int32),
515            (SmolStr::new_static("iteration-count"), Type::Float32),
516            (
517                SmolStr::new_static("direction"),
518                Type::Enumeration(BUILTIN.with(|e| e.enums.AnimationDirection.clone())),
519            ),
520            (SmolStr::new_static("easing"), Type::Easing),
521            (SmolStr::new_static("delay"), Type::Int32),
522        ])
523    }
524
525    fn animation_ty() -> Rc<Struct> {
526        Rc::new(Struct {
527            fields: animation_fields().collect(),
528            name: Some("slint::private_api::PropertyAnimation".into()),
529            node: None,
530            rust_attributes: None,
531        })
532    }
533
534    match a {
535        PropertyAnimation::Static(a) => Animation::Static(lower_animation_element(a, ctx)),
536        PropertyAnimation::Transition { state_ref, animations } => {
537            let set_state = llr_Expression::StoreLocalVariable {
538                name: "state".into(),
539                value: Box::new(lower_expression(state_ref, ctx)),
540            };
541            let animation_ty = Type::Struct(animation_ty());
542            let mut get_anim = llr_Expression::default_value_for_type(&animation_ty).unwrap();
543            for tr in animations.iter().rev() {
544                let condition = lower_expression(
545                    &tr.condition(tree_Expression::ReadLocalVariable {
546                        name: "state".into(),
547                        ty: state_ref.ty(),
548                    }),
549                    ctx,
550                );
551                get_anim = llr_Expression::Condition {
552                    condition: Box::new(condition),
553                    true_expr: Box::new(lower_animation_element(&tr.animation, ctx)),
554                    false_expr: Box::new(get_anim),
555                }
556            }
557            let result = llr_Expression::Struct {
558                // This is going to be a tuple
559                ty: Rc::new(Struct {
560                    fields: IntoIterator::into_iter([
561                        (SmolStr::new_static("0"), animation_ty),
562                        // The type is an instant, which does not exist in our type system
563                        (SmolStr::new_static("1"), Type::Invalid),
564                    ])
565                    .collect(),
566                    name: None,
567                    node: None,
568                    rust_attributes: None,
569                }),
570                values: IntoIterator::into_iter([
571                    (SmolStr::new_static("0"), get_anim),
572                    (
573                        SmolStr::new_static("1"),
574                        llr_Expression::StructFieldAccess {
575                            base: llr_Expression::ReadLocalVariable {
576                                name: "state".into(),
577                                ty: state_ref.ty(),
578                            }
579                            .into(),
580                            name: "change_time".into(),
581                        },
582                    ),
583                ])
584                .collect(),
585            };
586            Animation::Transition(llr_Expression::CodeBlock(vec![set_state, result]))
587        }
588    }
589}
590
591fn compute_layout_info(
592    l: &crate::layout::Layout,
593    o: Orientation,
594    ctx: &mut ExpressionLoweringCtx,
595) -> llr_Expression {
596    match l {
597        crate::layout::Layout::GridLayout(layout) => {
598            let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
599            let cells = grid_layout_cell_data(layout, o, ctx);
600            llr_Expression::ExtraBuiltinFunctionCall {
601                function: "grid_layout_info".into(),
602                arguments: vec![cells, spacing, padding],
603                return_ty: crate::typeregister::layout_info_type().into(),
604            }
605        }
606        crate::layout::Layout::BoxLayout(layout) => {
607            let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
608            let bld = box_layout_data(layout, o, ctx);
609            let sub_expression = if o == layout.orientation {
610                llr_Expression::ExtraBuiltinFunctionCall {
611                    function: "box_layout_info".into(),
612                    arguments: vec![bld.cells, spacing, padding, bld.alignment],
613                    return_ty: crate::typeregister::layout_info_type().into(),
614                }
615            } else {
616                llr_Expression::ExtraBuiltinFunctionCall {
617                    function: "box_layout_info_ortho".into(),
618                    arguments: vec![bld.cells, padding],
619                    return_ty: crate::typeregister::layout_info_type().into(),
620                }
621            };
622            match bld.compute_cells {
623                Some((cells_variable, elements)) => llr_Expression::BoxLayoutFunction {
624                    cells_variable,
625                    repeater_indices: None,
626                    elements,
627                    orientation: o,
628                    sub_expression: Box::new(sub_expression),
629                },
630                None => sub_expression,
631            }
632        }
633    }
634}
635
636fn solve_layout(
637    l: &crate::layout::Layout,
638    o: Orientation,
639    ctx: &mut ExpressionLoweringCtx,
640) -> llr_Expression {
641    match l {
642        crate::layout::Layout::GridLayout(layout) => {
643            let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
644            let cells = grid_layout_cell_data(layout, o, ctx);
645            let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
646            if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, o)
647            {
648                let cells_ty = cells.ty(ctx);
649                let e = crate::typeregister::BUILTIN.with(|e| e.enums.DialogButtonRole.clone());
650                let roles = button_roles
651                    .iter()
652                    .map(|r| {
653                        llr_Expression::EnumerationValue(EnumerationValue {
654                            value: e.values.iter().position(|x| x == r).unwrap() as _,
655                            enumeration: e.clone(),
656                        })
657                    })
658                    .collect();
659                llr_Expression::CodeBlock(vec![
660                    llr_Expression::ComputeDialogLayoutCells {
661                        cells_variable: "cells".into(),
662                        roles: llr_Expression::Array {
663                            element_ty: Type::Enumeration(e),
664                            values: roles,
665                            as_model: false,
666                        }
667                        .into(),
668                        unsorted_cells: Box::new(cells),
669                    },
670                    llr_Expression::ExtraBuiltinFunctionCall {
671                        function: "solve_grid_layout".into(),
672                        arguments: vec![make_struct(
673                            "GridLayoutData",
674                            [
675                                ("size", Type::Float32, size),
676                                ("spacing", Type::Float32, spacing),
677                                ("padding", padding.ty(ctx), padding),
678                                (
679                                    "cells",
680                                    cells_ty.clone(),
681                                    llr_Expression::ReadLocalVariable {
682                                        name: "cells".into(),
683                                        ty: cells_ty,
684                                    },
685                                ),
686                            ],
687                        )],
688                        return_ty: Type::LayoutCache,
689                    },
690                ])
691            } else {
692                llr_Expression::ExtraBuiltinFunctionCall {
693                    function: "solve_grid_layout".into(),
694                    arguments: vec![make_struct(
695                        "GridLayoutData",
696                        [
697                            ("size", Type::Float32, size),
698                            ("spacing", Type::Float32, spacing),
699                            ("padding", padding.ty(ctx), padding),
700                            ("cells", cells.ty(ctx), cells),
701                        ],
702                    )],
703                    return_ty: Type::LayoutCache,
704                }
705            }
706        }
707        crate::layout::Layout::BoxLayout(layout) => {
708            let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx);
709            let bld = box_layout_data(layout, o, ctx);
710            let size = layout_geometry_size(&layout.geometry.rect, o, ctx);
711            let data = make_struct(
712                "BoxLayoutData",
713                [
714                    ("size", Type::Float32, size),
715                    ("spacing", Type::Float32, spacing),
716                    ("padding", padding.ty(ctx), padding),
717                    (
718                        "alignment",
719                        crate::typeregister::BUILTIN
720                            .with(|e| Type::Enumeration(e.enums.LayoutAlignment.clone())),
721                        bld.alignment,
722                    ),
723                    ("cells", bld.cells.ty(ctx), bld.cells),
724                ],
725            );
726            match bld.compute_cells {
727                Some((cells_variable, elements)) => llr_Expression::BoxLayoutFunction {
728                    cells_variable,
729                    repeater_indices: Some("repeated_indices".into()),
730                    elements,
731                    orientation: o,
732                    sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall {
733                        function: "solve_box_layout".into(),
734                        arguments: vec![
735                            data,
736                            llr_Expression::ReadLocalVariable {
737                                name: "repeated_indices".into(),
738                                ty: Type::Array(Type::Int32.into()),
739                            },
740                        ],
741                        return_ty: Type::LayoutCache,
742                    }),
743                },
744                None => llr_Expression::ExtraBuiltinFunctionCall {
745                    function: "solve_box_layout".into(),
746                    arguments: vec![
747                        data,
748                        llr_Expression::Array {
749                            element_ty: Type::Int32,
750                            values: vec![],
751                            as_model: false,
752                        },
753                    ],
754                    return_ty: Type::LayoutCache,
755                },
756            }
757        }
758    }
759}
760
761struct BoxLayoutDataResult {
762    alignment: llr_Expression,
763    cells: llr_Expression,
764    /// When there are repeater involved, we need to do a BoxLayoutFunction with the
765    /// given cell variable and elements
766    compute_cells: Option<(String, Vec<Either<llr_Expression, RepeatedElementIdx>>)>,
767}
768
769fn box_layout_data(
770    layout: &crate::layout::BoxLayout,
771    orientation: Orientation,
772    ctx: &mut ExpressionLoweringCtx,
773) -> BoxLayoutDataResult {
774    let alignment = if let Some(expr) = &layout.geometry.alignment {
775        llr_Expression::PropertyReference(ctx.map_property_reference(expr))
776    } else {
777        let e = crate::typeregister::BUILTIN.with(|e| e.enums.LayoutAlignment.clone());
778        llr_Expression::EnumerationValue(EnumerationValue {
779            value: e.default_value,
780            enumeration: e,
781        })
782    };
783
784    let repeater_count =
785        layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
786
787    let element_ty = crate::typeregister::box_layout_cell_data_type();
788
789    if repeater_count == 0 {
790        let cells = llr_Expression::Array {
791            values: layout
792                .elems
793                .iter()
794                .map(|li| {
795                    let layout_info =
796                        get_layout_info(&li.element, ctx, &li.constraints, orientation);
797                    make_struct(
798                        "BoxLayoutCellData",
799                        [(
800                            "constraint",
801                            crate::typeregister::layout_info_type().into(),
802                            layout_info,
803                        )],
804                    )
805                })
806                .collect(),
807            element_ty,
808            as_model: false,
809        };
810        BoxLayoutDataResult { alignment, cells, compute_cells: None }
811    } else {
812        let mut elements = vec![];
813        for item in &layout.elems {
814            if item.element.borrow().repeated.is_some() {
815                let repeater_index =
816                    match ctx.mapping.element_mapping.get(&item.element.clone().into()).unwrap() {
817                        LoweredElement::Repeated { repeated_index } => *repeated_index,
818                        _ => panic!(),
819                    };
820                elements.push(Either::Right(repeater_index))
821            } else {
822                let layout_info =
823                    get_layout_info(&item.element, ctx, &item.constraints, orientation);
824                elements.push(Either::Left(make_struct(
825                    "BoxLayoutCellData",
826                    [("constraint", crate::typeregister::layout_info_type().into(), layout_info)],
827                )));
828            }
829        }
830        let cells = llr_Expression::ReadLocalVariable {
831            name: "cells".into(),
832            ty: Type::Array(Rc::new(crate::typeregister::layout_info_type().into())),
833        };
834        BoxLayoutDataResult { alignment, cells, compute_cells: Some(("cells".into(), elements)) }
835    }
836}
837
838fn grid_layout_cell_data(
839    layout: &crate::layout::GridLayout,
840    orientation: Orientation,
841    ctx: &mut ExpressionLoweringCtx,
842) -> llr_Expression {
843    llr_Expression::Array {
844        element_ty: grid_layout_cell_data_ty(),
845        values: layout
846            .elems
847            .iter()
848            .map(|c| {
849                let (col_or_row, span) = c.col_or_row_and_span(orientation);
850                let layout_info =
851                    get_layout_info(&c.item.element, ctx, &c.item.constraints, orientation);
852
853                make_struct(
854                    "GridLayoutCellData",
855                    [
856                        ("constraint", crate::typeregister::layout_info_type().into(), layout_info),
857                        ("col_or_row", Type::Int32, llr_Expression::NumberLiteral(col_or_row as _)),
858                        ("span", Type::Int32, llr_Expression::NumberLiteral(span as _)),
859                    ],
860                )
861            })
862            .collect(),
863        as_model: false,
864    }
865}
866
867pub(super) fn grid_layout_cell_data_ty() -> Type {
868    Type::Struct(Rc::new(Struct {
869        fields: IntoIterator::into_iter([
870            (SmolStr::new_static("col_or_row"), Type::Int32),
871            (SmolStr::new_static("span"), Type::Int32),
872            (SmolStr::new_static("constraint"), crate::typeregister::layout_info_type().into()),
873        ])
874        .collect(),
875        name: Some("GridLayoutCellData".into()),
876        node: None,
877        rust_attributes: None,
878    }))
879}
880
881fn generate_layout_padding_and_spacing(
882    layout_geometry: &crate::layout::LayoutGeometry,
883    orientation: Orientation,
884    ctx: &ExpressionLoweringCtx,
885) -> (llr_Expression, llr_Expression) {
886    let padding_prop = |expr| {
887        if let Some(expr) = expr {
888            llr_Expression::PropertyReference(ctx.map_property_reference(expr))
889        } else {
890            llr_Expression::NumberLiteral(0.)
891        }
892    };
893    let spacing = padding_prop(layout_geometry.spacing.orientation(orientation));
894    let (begin, end) = layout_geometry.padding.begin_end(orientation);
895
896    let padding = make_struct(
897        "Padding",
898        [("begin", Type::Float32, padding_prop(begin)), ("end", Type::Float32, padding_prop(end))],
899    );
900
901    (padding, spacing)
902}
903
904fn layout_geometry_size(
905    rect: &crate::layout::LayoutRect,
906    orientation: Orientation,
907    ctx: &ExpressionLoweringCtx,
908) -> llr_Expression {
909    match rect.size_reference(orientation) {
910        Some(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
911        None => llr_Expression::NumberLiteral(0.),
912    }
913}
914
915pub fn get_layout_info(
916    elem: &ElementRc,
917    ctx: &mut ExpressionLoweringCtx,
918    constraints: &crate::layout::LayoutConstraints,
919    orientation: Orientation,
920) -> llr_Expression {
921    let layout_info = if let Some(layout_info_prop) = &elem.borrow().layout_info_prop(orientation) {
922        llr_Expression::PropertyReference(ctx.map_property_reference(layout_info_prop))
923    } else {
924        lower_expression(&crate::layout::implicit_layout_info_call(elem, orientation), ctx)
925    };
926
927    if constraints.has_explicit_restrictions(orientation) {
928        let store = llr_Expression::StoreLocalVariable {
929            name: "layout_info".into(),
930            value: layout_info.into(),
931        };
932        let ty = crate::typeregister::layout_info_type();
933        let mut values = ty
934            .fields
935            .keys()
936            .map(|p| {
937                (
938                    p.clone(),
939                    llr_Expression::StructFieldAccess {
940                        base: llr_Expression::ReadLocalVariable {
941                            name: "layout_info".into(),
942                            ty: ty.clone().into(),
943                        }
944                        .into(),
945                        name: p.clone(),
946                    },
947                )
948            })
949            .collect::<BTreeMap<_, _>>();
950
951        for (nr, s) in constraints.for_each_restrictions(orientation) {
952            values.insert(
953                s.into(),
954                llr_Expression::PropertyReference(ctx.map_property_reference(nr)),
955            );
956        }
957        llr_Expression::CodeBlock([store, llr_Expression::Struct { ty, values }].into())
958    } else {
959        layout_info
960    }
961}
962
963fn compile_path(
964    path: &crate::expression_tree::Path,
965    ctx: &mut ExpressionLoweringCtx,
966) -> llr_Expression {
967    fn llr_path_elements(elements: Vec<llr_Expression>) -> llr_Expression {
968        llr_Expression::Cast {
969            from: llr_Expression::Array {
970                element_ty: crate::typeregister::path_element_type(),
971                values: elements,
972                as_model: false,
973            }
974            .into(),
975            to: Type::PathData,
976        }
977    }
978
979    match path {
980        crate::expression_tree::Path::Elements(elements) => {
981            let converted_elements = elements
982                .iter()
983                .map(|element| {
984                    let element_type = Rc::new(Struct {
985                        fields: element
986                            .element_type
987                            .properties
988                            .iter()
989                            .map(|(k, v)| (k.clone(), v.ty.clone()))
990                            .collect(),
991                        name: element.element_type.native_class.cpp_type.clone(),
992                        node: None,
993                        rust_attributes: None,
994                    });
995
996                    llr_Expression::Struct {
997                        ty: element_type,
998                        values: element
999                            .element_type
1000                            .properties
1001                            .iter()
1002                            .map(|(element_field_name, element_property)| {
1003                                (
1004                                    element_field_name.clone(),
1005                                    element.bindings.get(element_field_name).map_or_else(
1006                                        || {
1007                                            llr_Expression::default_value_for_type(
1008                                                &element_property.ty,
1009                                            )
1010                                            .unwrap()
1011                                        },
1012                                        |expr| lower_expression(&expr.borrow().expression, ctx),
1013                                    ),
1014                                )
1015                            })
1016                            .collect(),
1017                    }
1018                })
1019                .collect();
1020            llr_path_elements(converted_elements)
1021        }
1022        crate::expression_tree::Path::Events(events, points) => {
1023            if events.is_empty() || points.is_empty() {
1024                return llr_path_elements(vec![]);
1025            }
1026
1027            let events: Vec<_> = events.iter().map(|event| lower_expression(event, ctx)).collect();
1028
1029            let event_type = events.first().unwrap().ty(ctx);
1030
1031            let points: Vec<_> = points.iter().map(|point| lower_expression(point, ctx)).collect();
1032
1033            let point_type = points.first().unwrap().ty(ctx);
1034
1035            llr_Expression::Cast {
1036                from: llr_Expression::Struct {
1037                    ty: Rc::new(Struct {
1038                        fields: IntoIterator::into_iter([
1039                            (SmolStr::new_static("events"), Type::Array(event_type.clone().into())),
1040                            (SmolStr::new_static("points"), Type::Array(point_type.clone().into())),
1041                        ])
1042                        .collect(),
1043                        name: None,
1044                        node: None,
1045                        rust_attributes: None,
1046                    }),
1047                    values: IntoIterator::into_iter([
1048                        (
1049                            SmolStr::new_static("events"),
1050                            llr_Expression::Array {
1051                                element_ty: event_type,
1052                                values: events,
1053                                as_model: false,
1054                            },
1055                        ),
1056                        (
1057                            SmolStr::new_static("points"),
1058                            llr_Expression::Array {
1059                                element_ty: point_type,
1060                                values: points,
1061                                as_model: false,
1062                            },
1063                        ),
1064                    ])
1065                    .collect(),
1066                }
1067                .into(),
1068                to: Type::PathData,
1069            }
1070        }
1071        crate::expression_tree::Path::Commands(commands) => llr_Expression::Cast {
1072            from: lower_expression(commands, ctx).into(),
1073            to: Type::PathData,
1074        },
1075    }
1076}
1077
1078pub fn make_struct(
1079    name: &str,
1080    it: impl IntoIterator<Item = (&'static str, Type, llr_Expression)>,
1081) -> llr_Expression {
1082    let mut fields = BTreeMap::<SmolStr, Type>::new();
1083    let mut values = BTreeMap::<SmolStr, llr_Expression>::new();
1084    for (name, ty, expr) in it {
1085        fields.insert(SmolStr::new(name), ty);
1086        values.insert(SmolStr::new(name), expr);
1087    }
1088
1089    llr_Expression::Struct {
1090        ty: Rc::new(Struct {
1091            fields,
1092            name: Some(format_smolstr!("slint::private_api::{name}")),
1093            node: None,
1094            rust_attributes: None,
1095        }),
1096        values,
1097    }
1098}