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