Skip to main content

slint_interpreter/
eval.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
4//! Tree-walking evaluator for [`llr::Expression`].
5//!
6//! Called from property bindings, change callbacks, callback handlers,
7//! layout info expressions and `init_code` blocks.
8//! Resolves `MemberReference`s by walking the sub-component parent chain.
9
10use crate::Value;
11use crate::globals::{GlobalInstance, GlobalStorage};
12use crate::instance::SubComponentInstance;
13use i_slint_compiler::expression_tree::{BuiltinFunction, MinMaxOp};
14use i_slint_compiler::langtype::{ConstantExpression, Type};
15use i_slint_compiler::llr::{self, Expression, LocalMemberIndex, MemberReference};
16use i_slint_core::graphics::{
17    Brush, ConicGradientBrush, GradientStop, LinearGradientBrush, RadialGradientBrush,
18};
19use i_slint_core::model::{Model, ModelExt, ModelRc, SharedVectorModel};
20use i_slint_core::{Color, SharedString, SharedVector};
21use smol_str::SmolStr;
22use std::collections::HashMap;
23use std::pin::Pin;
24use std::rc::{Rc, Weak};
25
26/// Dynamic context for one expression evaluation.
27pub struct EvalContext {
28    /// Closest sub-component, set when the expression is evaluated from one.
29    /// `None` when the expression is being evaluated in a global's init code.
30    pub current: Option<Pin<Rc<SubComponentInstance>>>,
31    /// The compilation unit, for type resolution even when `current` is
32    /// `None` (global context).
33    pub compilation_unit: Rc<llr::CompilationUnit>,
34    /// Shared global storage, used to resolve `MemberReference::Global`.
35    pub globals: Weak<GlobalStorage>,
36    /// Local variables introduced by `StoreLocalVariable`.
37    pub locals: HashMap<SmolStr, Value>,
38    /// Arguments of the current function, if any.
39    pub function_arguments: Vec<Value>,
40    /// Declared types of `function_arguments`, for
41    /// [`i_slint_compiler::llr::TypeResolutionContext::arg_type`].
42    pub function_arg_types: Vec<Type>,
43    /// Set by `return` to stop further statement evaluation in a `CodeBlock`.
44    pub return_value: Option<Value>,
45}
46
47impl EvalContext {
48    /// Context rooted in a sub-component.
49    /// The global storage is pulled from the sub-component's owning root.
50    pub fn new(current: Pin<Rc<SubComponentInstance>>) -> Self {
51        let globals = current
52            .root
53            .get()
54            .and_then(|w| w.upgrade())
55            .map(|inst| Rc::downgrade(&inst.globals))
56            .unwrap_or_default();
57        Self {
58            compilation_unit: current.compilation_unit.clone(),
59            current: Some(current),
60            globals,
61            locals: HashMap::new(),
62            function_arguments: Vec::new(),
63            function_arg_types: Vec::new(),
64            return_value: None,
65        }
66    }
67
68    /// Context rooted in a global. Only `MemberReference::Global` is valid.
69    pub fn for_global(globals: Weak<GlobalStorage>, cu: Rc<llr::CompilationUnit>) -> Self {
70        Self {
71            current: None,
72            compilation_unit: cu,
73            globals,
74            locals: HashMap::new(),
75            function_arguments: Vec::new(),
76            function_arg_types: Vec::new(),
77            return_value: None,
78        }
79    }
80
81    pub fn with_arguments(current: Pin<Rc<SubComponentInstance>>, args: Vec<Value>) -> Self {
82        let mut ctx = Self::new(current);
83        ctx.function_arguments = args;
84        ctx
85    }
86}
87
88/// The root instance, for builtins that need the window.
89/// In a global context, reach it through the global storage.
90fn root_instance(
91    ctx: &EvalContext,
92) -> Option<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>> {
93    match ctx.current.as_ref() {
94        Some(c) => c.root.get()?.upgrade(),
95        None => ctx.globals.upgrade()?.root.get()?.upgrade(),
96    }
97}
98
99/// Walk `parent_level` steps up the parent chain, or `None` if an ancestor is already gone.
100///
101/// The parent chain of a repeated element can die while one of its callbacks is still running —
102/// the enclosing popup closes itself, or the model drops the row the element belongs to — and the
103/// element's own instance outlives it because the event dispatch holds it.
104pub(crate) fn try_walk_parent(
105    start: &Pin<Rc<SubComponentInstance>>,
106    level: usize,
107) -> Option<Pin<Rc<SubComponentInstance>>> {
108    let mut current = start.clone();
109    for _ in 0..level {
110        current = Pin::new(current.parent.upgrade()?);
111    }
112    Some(current)
113}
114
115/// Walk `parent_level` steps up the parent chain.
116pub(crate) fn walk_parent(
117    start: &Pin<Rc<SubComponentInstance>>,
118    level: usize,
119) -> Pin<Rc<SubComponentInstance>> {
120    try_walk_parent(start, level).expect("parent vanished during evaluation")
121}
122
123impl i_slint_compiler::llr::TypeResolutionContext for EvalContext {
124    fn property_ty(&self, mr: &MemberReference) -> &Type {
125        let cu = &self.compilation_unit;
126        match mr {
127            MemberReference::Global { global_index, member } => {
128                let g = &cu.globals[*global_index];
129                match member {
130                    LocalMemberIndex::Property(idx) => &g.properties[*idx].ty,
131                    LocalMemberIndex::Function(idx) => &g.functions[*idx].ret_ty,
132                    // The stored `Type::Callback` — `Expression::ty()`'s
133                    // CallBackCall arm extracts the return type from it.
134                    LocalMemberIndex::Callback(idx) => &g.callbacks[*idx].ty,
135                    LocalMemberIndex::Native { .. } | LocalMemberIndex::Timer(_) => &Type::Invalid,
136                }
137            }
138            MemberReference::Relative { parent_level, local_reference } => {
139                let current =
140                    self.current.as_ref().expect("property_ty needs a sub-component context");
141                // The `Type` values live in the shared `CompilationUnit`, so
142                // resolve the target sub-component index through the runtime
143                // parent chain and borrow from `cu`.
144                let sub = walk_parent(current, *parent_level);
145                let mut sc_idx = sub.sub_component_idx;
146                for i in &local_reference.sub_component_path {
147                    sc_idx = cu.sub_components[sc_idx].sub_components[*i].ty;
148                }
149                let sc = &cu.sub_components[sc_idx];
150                match &local_reference.reference {
151                    LocalMemberIndex::Property(idx) => &sc.properties[*idx].ty,
152                    LocalMemberIndex::Function(idx) => &sc.functions[*idx].ret_ty,
153                    LocalMemberIndex::Callback(idx) => &sc.callbacks[*idx].ty,
154                    // A timer reference is only valid as the RestartTimer argument.
155                    LocalMemberIndex::Timer(_) => &Type::Invalid,
156                    LocalMemberIndex::Native { item_index, prop_name, .. } => {
157                        if prop_name == "elements" {
158                            // The `Path::elements` property is not in the NativeClass
159                            return &Type::PathData;
160                        }
161                        sc.items[*item_index]
162                            .ty
163                            .lookup_property(prop_name)
164                            .unwrap_or(&Type::Invalid)
165                    }
166                }
167            }
168        }
169    }
170
171    fn arg_type(&self, index: usize) -> &Type {
172        self.function_arg_types.get(index).unwrap_or(&Type::Invalid)
173    }
174}
175
176/// Walk down a `sub_component_path`.
177pub(crate) fn walk_sub_path(
178    mut current: Pin<Rc<SubComponentInstance>>,
179    path: &[llr::SubComponentInstanceIdx],
180) -> Pin<Rc<SubComponentInstance>> {
181    for &idx in path {
182        let next = current.sub_components[idx].clone();
183        current = next;
184    }
185    current
186}
187
188/// Walk to the sub-component that owns `local`, or `None` if it is not reachable.
189///
190/// See [`try_walk_parent`] for when that happens.
191pub(crate) fn try_walk_to(
192    ctx: &EvalContext,
193    parent_level: usize,
194    path: &[llr::SubComponentInstanceIdx],
195) -> Option<Pin<Rc<SubComponentInstance>>> {
196    Some(walk_sub_path(try_walk_parent(ctx.current.as_ref()?, parent_level)?, path))
197}
198
199/// Walk to the sub-component that owns `local`.
200///
201/// Panics if `ctx.current` is unset; the caller must check beforehand.
202pub(crate) fn walk_to(
203    ctx: &EvalContext,
204    parent_level: usize,
205    path: &[llr::SubComponentInstanceIdx],
206) -> Pin<Rc<SubComponentInstance>> {
207    let start = ctx.current.as_ref().expect("relative member reference without a sub-component");
208    walk_sub_path(walk_parent(start, parent_level), path)
209}
210
211/// Flat tree index of the `item_table` entry matching `(path, item_index)`.
212pub(crate) fn find_flat_item_index(
213    item_table: &[Option<(
214        Box<[i_slint_compiler::llr::SubComponentInstanceIdx]>,
215        i_slint_compiler::llr::ItemInstanceIdx,
216    )>],
217    path: &[i_slint_compiler::llr::SubComponentInstanceIdx],
218    item_index: i_slint_compiler::llr::ItemInstanceIdx,
219) -> Option<usize> {
220    item_table.iter().position(|entry| {
221        entry.as_ref().is_some_and(|(p, i)| p.as_ref() == path && *i == item_index)
222    })
223}
224
225fn load_local(instance: &SubComponentInstance, member: &LocalMemberIndex) -> Value {
226    match member {
227        LocalMemberIndex::Property(idx) => Pin::as_ref(&instance.properties[*idx]).get(),
228        LocalMemberIndex::Native { item_index, prop_name, .. } => {
229            Pin::as_ref(&instance.items[*item_index]).get_property(prop_name).unwrap_or(Value::Void)
230        }
231        LocalMemberIndex::Callback(_)
232        | LocalMemberIndex::Function(_)
233        | LocalMemberIndex::Timer(_) => {
234            panic!("load_local called on callback/function/timer reference")
235        }
236    }
237}
238
239/// Evaluates the predicate of `ArrayAny`/`ArrayAll`/`ArrayFindIndex` against a single row
240/// value, binding `arg_name` to it for the duration of the evaluation and restoring any
241/// shadowed local variable afterwards — like the generated code binds its closure parameter.
242/// Iteration and dependency tracking are left to the `model_any`/`model_all`/
243/// `model_find_index` helpers in [`i_slint_core::model`].
244fn eval_array_row_predicate(
245    arg_name: &SmolStr,
246    predicate: &Expression,
247    ctx: &mut EvalContext,
248    row_value: Value,
249) -> bool {
250    let previous = ctx.locals.insert(arg_name.clone(), row_value);
251    let result = eval_expression(ctx, predicate).try_into().unwrap();
252    match previous {
253        Some(prev) => {
254            ctx.locals.insert(arg_name.clone(), prev);
255        }
256        None => {
257            ctx.locals.remove(arg_name);
258        }
259    }
260    result
261}
262
263/// Set `value` on `prop`, interpolating through `animation` when present.
264fn set_maybe_animated(
265    prop: Pin<&i_slint_core::Property<Value>>,
266    ty: &Type,
267    value: Value,
268    animation: Option<i_slint_core::items::PropertyAnimation>,
269) {
270    match animation {
271        Some(anim) => match crate::bindings::animated_value_map(ty) {
272            Some(map) => prop.set_animated_value_with_map(value, anim, map),
273            None => prop.set_animated_value(value, anim),
274        },
275        None => prop.set(value),
276    }
277}
278
279fn store_local(
280    instance: &SubComponentInstance,
281    member: &LocalMemberIndex,
282    value: Value,
283    animation: Option<i_slint_core::items::PropertyAnimation>,
284) {
285    match member {
286        LocalMemberIndex::Property(idx) => {
287            let sc = &instance.compilation_unit.sub_components[instance.sub_component_idx];
288            set_maybe_animated(
289                Pin::as_ref(&instance.properties[*idx]),
290                &sc.properties[*idx].ty,
291                value,
292                animation,
293            );
294        }
295        LocalMemberIndex::Native { item_index, prop_name, .. } => {
296            let _ =
297                Pin::as_ref(&instance.items[*item_index]).set_property(prop_name, value, animation);
298        }
299        LocalMemberIndex::Callback(_)
300        | LocalMemberIndex::Function(_)
301        | LocalMemberIndex::Timer(_) => {
302            panic!("store_local called on callback/function/timer reference")
303        }
304    }
305}
306
307/// Walk down `local_reference.sub_component_path` from `start`, returning the
308/// target instance and any standalone `animate` declaration for this member.
309/// An `animate` on a child component's property lives in the enclosing
310/// component's animations map with a non-empty path; the outermost
311/// declaration wins and its expression evaluates in the scope that
312/// declared it.
313fn walk_to_target_with_animation(
314    start: Pin<Rc<SubComponentInstance>>,
315    local_reference: &llr::LocalMemberReference,
316) -> (Pin<Rc<SubComponentInstance>>, Option<i_slint_core::items::PropertyAnimation>) {
317    let cu = start.compilation_unit.clone();
318    let path = &local_reference.sub_component_path;
319    let mut animation = None;
320    let mut owner = start;
321    for depth in 0..=path.len() {
322        if animation.is_none() {
323            let sc = &cu.sub_components[owner.sub_component_idx];
324            if !sc.animations.is_empty() {
325                let key = llr::LocalMemberReference {
326                    sub_component_path: path[depth..].to_vec(),
327                    reference: local_reference.reference.clone(),
328                };
329                if let Some(expr) = sc.animations.get(&key) {
330                    animation = Some((owner.clone(), expr.clone()));
331                }
332            }
333        }
334        if let Some(&idx) = path.get(depth) {
335            let next = owner.sub_components[idx].clone();
336            owner = next;
337        }
338    }
339    let animation = animation.map(|(scope, expr)| {
340        let mut ctx = EvalContext::new(scope);
341        crate::bindings::value_to_property_animation(eval_expression(&mut ctx, &expr))
342    });
343    (owner, animation)
344}
345
346pub fn load_property(ctx: &EvalContext, mr: &MemberReference) -> Value {
347    match mr {
348        MemberReference::Global { global_index, member } => {
349            let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
350            let Some(global) = storage.get(*global_index) else { return Value::Void };
351            load_global(global, member)
352        }
353        MemberReference::Relative { parent_level, local_reference } => {
354            let instance = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
355            load_local(&instance, &local_reference.reference)
356        }
357    }
358}
359
360pub fn store_property(ctx: &EvalContext, mr: &MemberReference, value: Value) {
361    match mr {
362        MemberReference::Global { global_index, member } => {
363            let Some(storage) = ctx.globals.upgrade() else { return };
364            let Some(global) = storage.get(*global_index) else { return };
365            store_global(global, member, value);
366        }
367        MemberReference::Relative { parent_level, local_reference } => {
368            let start =
369                ctx.current.as_ref().expect("relative member reference without a sub-component");
370            let (instance, animation) =
371                walk_to_target_with_animation(walk_parent(start, *parent_level), local_reference);
372            store_local(&instance, &local_reference.reference, value, animation);
373        }
374    }
375}
376
377pub fn invoke_callback(ctx: &EvalContext, mr: &MemberReference, args: &[Value]) -> Value {
378    match mr {
379        MemberReference::Global { global_index, member } => {
380            let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
381            let Some(global) = storage.get(*global_index) else { return Value::Void };
382            let LocalMemberIndex::Callback(idx) = member else {
383                panic!("invoke_callback on non-callback global reference")
384            };
385            let cb = &global.compilation_unit.globals[global.global_idx].callbacks[*idx];
386            if let Some(native) = &global.native {
387                let res = native.as_ref().invoke_callback(&cb.name, args).unwrap_or(Value::Void);
388                return ensure_typed_default(res, &cb.ret_ty);
389            }
390            // Register a dependency on the handler so bindings invoking this
391            // callback re-evaluate when a new handler is set.
392            if let Some(tracker) = global.callback_trackers[*idx].as_ref() {
393                Pin::as_ref(tracker).get();
394            }
395            let res = Pin::as_ref(&global.callbacks[*idx]).call(args);
396            ensure_typed_default(res, &cb.ret_ty)
397        }
398        MemberReference::Relative { parent_level, local_reference } => {
399            let instance = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
400            match &local_reference.reference {
401                LocalMemberIndex::Callback(idx) => {
402                    // Register a dependency on the handler so bindings
403                    // invoking this callback re-evaluate when a new handler
404                    // is set.
405                    if let Some(tracker) = instance.callback_trackers[*idx].as_ref() {
406                        Pin::as_ref(tracker).get();
407                    }
408                    let res = Pin::as_ref(&instance.callbacks[*idx]).call(args);
409                    let ret_ty = instance.compilation_unit.sub_components
410                        [instance.sub_component_idx]
411                        .callbacks[*idx]
412                        .ret_ty
413                        .clone();
414                    ensure_typed_default(res, &ret_ty)
415                }
416                LocalMemberIndex::Native { item_index, prop_name, .. } => {
417                    Pin::as_ref(&instance.items[*item_index])
418                        .call_callback(prop_name, args)
419                        .unwrap_or(Value::Void)
420                }
421                _ => panic!("invoke_callback on non-callback reference: {mr:?}"),
422            }
423        }
424    }
425}
426
427/// Replace a `Value::Void` result (e.g. from an unset callback) with the
428/// type-appropriate default.
429pub(crate) fn ensure_typed_default(value: Value, ret_ty: &Type) -> Value {
430    if matches!(value, Value::Void) { default_value_for_type(ret_ty) } else { value }
431}
432
433pub fn invoke_function(ctx: &EvalContext, mr: &MemberReference, args: Vec<Value>) -> Value {
434    match mr {
435        MemberReference::Global { global_index, member } => {
436            let Some(storage) = ctx.globals.upgrade() else { return Value::Void };
437            let Some(global) = storage.get(*global_index) else { return Value::Void };
438            let LocalMemberIndex::Function(idx) = member else {
439                panic!("invoke_function on non-function global reference")
440            };
441            let function = &global.compilation_unit.globals[global.global_idx].functions[*idx];
442            let code = function.code.borrow().clone();
443            let mut inner_ctx =
444                EvalContext::for_global(ctx.globals.clone(), global.compilation_unit.clone());
445            inner_ctx.function_arg_types = function.args.clone();
446            inner_ctx.function_arguments = args;
447            eval_expression(&mut inner_ctx, &code)
448        }
449        MemberReference::Relative { parent_level, local_reference } => {
450            let instance = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
451            let LocalMemberIndex::Function(idx) = &local_reference.reference else {
452                panic!("invoke_function on non-function reference")
453            };
454            let sc = &instance.compilation_unit.sub_components[instance.sub_component_idx];
455            let function = &sc.functions[*idx];
456            let code = function.code.borrow().clone();
457            let mut inner_ctx = EvalContext::with_arguments(instance.clone(), args);
458            inner_ctx.function_arg_types = function.args.clone();
459            eval_expression(&mut inner_ctx, &code)
460        }
461    }
462}
463
464fn load_global(global: &Rc<GlobalInstance>, member: &LocalMemberIndex) -> Value {
465    match member {
466        LocalMemberIndex::Property(idx) => {
467            if let Some(native) = &global.native {
468                let g = &global.compilation_unit.globals[global.global_idx];
469                return native
470                    .as_ref()
471                    .get_property(&g.properties[*idx].name)
472                    .unwrap_or(Value::Void);
473            }
474            Pin::as_ref(&global.properties[*idx]).get()
475        }
476        _ => panic!("load_global called on non-property"),
477    }
478}
479
480pub(crate) fn store_global(global: &Rc<GlobalInstance>, member: &LocalMemberIndex, value: Value) {
481    if let LocalMemberIndex::Property(idx) = member {
482        let g = &global.compilation_unit.globals[global.global_idx];
483        // Globals never carry an animation (an `animate` never moves onto a global).
484        if let Some(native) = &global.native {
485            let _ = native.as_ref().set_property(&g.properties[*idx].name, value, None);
486            return;
487        }
488        set_maybe_animated(
489            Pin::as_ref(&global.properties[*idx]),
490            &g.properties[*idx].ty,
491            value,
492            None,
493        );
494    }
495}
496
497/// Build a `Value::PathData` from the `from` expression of a
498/// `Expression::Cast { to: Type::PathData, .. }`.
499///
500/// `lower_expression::compile_path` lowers `Path::Elements` to an array of
501/// builtin-struct literals, `Path::Events` to a struct with `events` /
502/// `points` fields, and `Path::Commands` to a string expression. The code
503/// generators navigate these statically; the interpreter pattern-matches on
504/// the expression itself because `Value::Struct` doesn't carry its LLR type
505/// name.
506fn cast_to_path_data(ctx: &mut EvalContext, from: &Expression) -> Value {
507    use i_slint_core::graphics::PathData;
508    use i_slint_core::items::PathEvent;
509
510    match from {
511        Expression::Array { values, .. } => {
512            let elements: SharedVector<i_slint_core::graphics::PathElement> =
513                values.iter().filter_map(|e| path_element_from_expression(ctx, e)).collect();
514            Value::PathData(PathData::Elements(elements))
515        }
516        Expression::Struct { values, .. }
517            if values.contains_key("events") && values.contains_key("points") =>
518        {
519            let events_value = eval_expression(ctx, &values["events"]);
520            let points_value = eval_expression(ctx, &values["points"]);
521            // `for_each_enums!` already produces a `TryFrom<Value>` impl for
522            // every Slint enum (via `declare_value_enum_conversion!` in
523            // `api.rs`), so model rows of `Value::EnumerationValue` convert
524            // straight to `PathEvent` without manual string matching.
525            let events: SharedVector<PathEvent> = match events_value {
526                Value::Model(m) => {
527                    (0..m.row_count()).filter_map(|i| m.row_data(i)?.try_into().ok()).collect()
528                }
529                _ => SharedVector::default(),
530            };
531            let points: SharedVector<lyon_path::math::Point> = match points_value {
532                Value::Model(m) => {
533                    (0..m.row_count()).filter_map(|i| m.row_data(i)?.try_into().ok()).collect()
534                }
535                _ => SharedVector::default(),
536            };
537            Value::PathData(PathData::Events(events, points))
538        }
539        _ => match eval_expression(ctx, from) {
540            Value::String(s) => Value::PathData(PathData::Commands(s)),
541            _ => Value::PathData(PathData::None),
542        },
543    }
544}
545
546/// Resolve an `Expression::Struct` in a `Cast`-to-`PathData` array into the
547/// matching [`PathElement`] variant, dispatching on the struct's
548/// `StructName::Builtin` tag.
549fn path_element_from_expression(
550    ctx: &mut EvalContext,
551    expr: &Expression,
552) -> Option<i_slint_core::graphics::PathElement> {
553    use i_slint_compiler::langtype::{BuiltinStruct, StructName};
554    use i_slint_core::graphics::{
555        PathArcTo, PathCubicTo, PathElement, PathLineTo, PathMoveTo, PathQuadraticTo,
556    };
557    let Expression::Struct { ty, values } = expr else { return None };
558    let StructName::Builtin(bs) = &ty.name else { return None };
559    let get_f32 = |field: &str, ctx: &mut EvalContext| -> f32 {
560        values
561            .get(field)
562            .map(|e| eval_expression(ctx, e))
563            .and_then(|v| f64::try_from(v).ok())
564            .unwrap_or(0.0) as f32
565    };
566    let get_bool = |field: &str, ctx: &mut EvalContext| -> bool {
567        values
568            .get(field)
569            .map(|e| eval_expression(ctx, e))
570            .map(|v| matches!(v, Value::Bool(true)))
571            .unwrap_or(false)
572    };
573    Some(match bs {
574        BuiltinStruct::PathMoveTo => {
575            PathElement::MoveTo(PathMoveTo { x: get_f32("x", ctx), y: get_f32("y", ctx) })
576        }
577        BuiltinStruct::PathLineTo => {
578            PathElement::LineTo(PathLineTo { x: get_f32("x", ctx), y: get_f32("y", ctx) })
579        }
580        BuiltinStruct::PathArcTo => PathElement::ArcTo(PathArcTo {
581            x: get_f32("x", ctx),
582            y: get_f32("y", ctx),
583            radius_x: get_f32("radius-x", ctx),
584            radius_y: get_f32("radius-y", ctx),
585            x_rotation: get_f32("x-rotation", ctx),
586            large_arc: get_bool("large-arc", ctx),
587            sweep: get_bool("sweep", ctx),
588        }),
589        BuiltinStruct::PathCubicTo => PathElement::CubicTo(PathCubicTo {
590            x: get_f32("x", ctx),
591            y: get_f32("y", ctx),
592            control_1_x: get_f32("control-1-x", ctx),
593            control_1_y: get_f32("control-1-y", ctx),
594            control_2_x: get_f32("control-2-x", ctx),
595            control_2_y: get_f32("control-2-y", ctx),
596        }),
597        BuiltinStruct::PathQuadraticTo => PathElement::QuadraticTo(PathQuadraticTo {
598            x: get_f32("x", ctx),
599            y: get_f32("y", ctx),
600            control_x: get_f32("control-x", ctx),
601            control_y: get_f32("control-y", ctx),
602        }),
603        BuiltinStruct::PathClose => PathElement::Close,
604        _ => return None,
605    })
606}
607
608/// Default `Value` for a type, used when a callback or model access yields
609/// nothing but the caller expects a typed value.
610pub fn default_value_for_type(ty: &Type) -> Value {
611    match ty {
612        Type::Float32
613        | Type::Int32
614        | Type::Duration
615        | Type::Angle
616        | Type::PhysicalLength
617        | Type::LogicalLength
618        | Type::Rem
619        | Type::Percent
620        | Type::UnitProduct(_) => Value::Number(0.),
621        Type::String => Value::String(Default::default()),
622        Type::Color | Type::Brush => Value::Brush(Brush::default()),
623        Type::Bool => Value::Bool(false),
624        Type::Image => Value::Image(Default::default()),
625        Type::Struct(s) => Value::Struct(
626            s.fields
627                .keys()
628                .map(|k| (k.to_string(), default_value_for_struct_field(s, k)))
629                .collect(),
630        ),
631        Type::Array(_) | Type::Model => Value::Model(ModelRc::default()),
632        Type::Keys => Value::Keys(Default::default()),
633        Type::DataTransfer => Value::DataTransfer(Default::default()),
634        Type::StyledText => Value::StyledText(Default::default()),
635        Type::Enumeration(en) => {
636            let default = en.clone().default_value();
637            Value::EnumerationValue(en.name.to_string(), default.to_string())
638        }
639        Type::ComponentFactory => Value::ComponentFactory(Default::default()),
640        Type::MouseCursor => Value::MouseCursorInner(Default::default()),
641        Type::Void => Value::Void,
642        // Types that should never appear in this situation (e.g. are not expressible
643        // by users, so cannot be returned from an unset callback or model property)
644        Type::Invalid
645        | Type::InferredProperty
646        | Type::InferredCallback
647        | Type::Callback(_)
648        | Type::Function(_)
649        | Type::PathData
650        | Type::Easing
651        | Type::ElementReference
652        | Type::ArrayOfU16
653        | Type::LayoutCache
654        | Type::Closure => Value::Void,
655    }
656}
657
658/// The default for a struct field: the user-declared default value
659/// (`struct Foo { bar: int = 42 }`) if there is one, otherwise the default for
660/// the field's type.
661pub fn default_value_for_struct_field(
662    s: &i_slint_compiler::langtype::Struct,
663    field_name: &str,
664) -> Value {
665    match s.field_defaults.get(field_name) {
666        Some(expr) => eval_constant_expression(expr),
667        None => default_value_for_type(
668            s.fields.get(field_name).expect("default value requested for unknown struct field"),
669        ),
670    }
671}
672
673/// Evaluate a constant expression as stored in
674/// [`i_slint_compiler::langtype::Struct::field_defaults`].
675fn eval_constant_expression(expr: &ConstantExpression) -> Value {
676    match expr {
677        ConstantExpression::StringLiteral(s) => Value::String(s.as_str().into()),
678        ConstantExpression::NumberLiteral(n, _unit) => Value::Number(*n),
679        ConstantExpression::BoolLiteral(b) => Value::Bool(*b),
680        ConstantExpression::EnumerationValue(value) => {
681            Value::EnumerationValue(value.enumeration.name.to_string(), value.to_string())
682        }
683        ConstantExpression::Cast { from, to } => {
684            cast_constant_value(eval_constant_expression(from), to)
685        }
686        ConstantExpression::UnaryOp { sub, op } => {
687            // The resolver only accepts unary operators on matching operand types.
688            match (eval_constant_expression(sub), op) {
689                (Value::Number(a), '+') => Value::Number(a),
690                (Value::Number(a), '-') => Value::Number(-a),
691                (Value::Bool(a), '!') => Value::Bool(!a),
692                (sub, _) => panic!("unsupported {op} {sub:?}"),
693            }
694        }
695        ConstantExpression::Struct { values, .. } => Value::Struct(
696            values
697                .iter()
698                .map(|(k, v)| (k.to_string(), eval_constant_expression(v)))
699                .collect::<crate::api::Struct>(),
700        ),
701        ConstantExpression::Array { values, .. } => {
702            Value::Model(ModelRc::new(SharedVectorModel::from(
703                values.iter().map(eval_constant_expression).collect::<SharedVector<_>>(),
704            )))
705        }
706    }
707}
708
709/// Convert a value to the given type, as [`Expression::Cast`] does.
710fn cast_constant_value(value: Value, to: &Type) -> Value {
711    match (value, to) {
712        (Value::Number(n), Type::Int32) => Value::Number(n.trunc()),
713        (Value::Number(n), Type::String) => {
714            Value::String(i_slint_core::string::shared_string_from_number(n))
715        }
716        (Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(),
717        (Value::Brush(brush), Type::Color) => brush.color().into(),
718        (Value::EnumerationValue(_, val), Type::String) => Value::String(val.into()),
719        (v, _) => v,
720    }
721}
722
723pub fn eval_expression(ctx: &mut EvalContext, expression: &Expression) -> Value {
724    if let Some(r) = &ctx.return_value {
725        return r.clone();
726    }
727    match expression {
728        Expression::StringLiteral(s) => Value::String(s.as_str().into()),
729        Expression::NumberLiteral(n) => Value::Number(*n),
730        Expression::BoolLiteral(b) => Value::Bool(*b),
731        Expression::KeysLiteral(ks) => Value::Keys({
732            let mut modifiers = i_slint_core::input::KeyboardModifiers::default();
733            modifiers.alt = ks.modifiers.alt;
734            modifiers.control = ks.modifiers.control;
735            modifiers.shift = ks.modifiers.shift;
736            modifiers.meta = ks.modifiers.meta;
737            i_slint_core::input::make_keys(
738                SharedString::from(&*ks.key),
739                modifiers,
740                ks.ignore_shift,
741                ks.ignore_alt,
742            )
743        }),
744        Expression::PropertyReference(mr) => load_property(ctx, mr),
745        Expression::FunctionParameterReference { index } => ctx.function_arguments[*index].clone(),
746        Expression::StoreLocalVariable { name, value } => {
747            let v = eval_expression(ctx, value);
748            ctx.locals.insert(name.clone(), v);
749            Value::Void
750        }
751        Expression::ReadLocalVariable { name, .. } => {
752            ctx.locals.get(name).cloned().unwrap_or(Value::Void)
753        }
754        Expression::StructFieldAccess { base, name } => {
755            if let Value::Struct(s) = eval_expression(ctx, base) {
756                s.get_field(name).cloned().unwrap_or(Value::Void)
757            } else {
758                Value::Void
759            }
760        }
761        Expression::ArrayIndex { array, index } => {
762            let array_v = eval_expression(ctx, array);
763            let index = eval_expression(ctx, index);
764            match (array_v, index) {
765                (Value::Model(m), Value::Number(i)) => {
766                    let idx = i as isize as usize;
767                    m.row_data_tracked(idx).unwrap_or_else(|| {
768                        // Out of bounds or empty model: synthesize the element
769                        // type's default.
770                        default_value_for_type(&expression.ty(&*ctx))
771                    })
772                }
773                _ => Value::Void,
774            }
775        }
776        Expression::Cast { from, to } => {
777            // The `Path` native item's rtti setter needs a real
778            // `Value::PathData`, not the raw model / struct / string that
779            // `from` evaluates to.
780            if matches!(to, Type::PathData) {
781                return cast_to_path_data(ctx, from);
782            }
783            let v = eval_expression(ctx, from);
784            match (v, to) {
785                (Value::Number(n), Type::Int32) => Value::Number(n.trunc()),
786                (Value::Number(n), Type::String) => {
787                    Value::String(i_slint_core::string::shared_string_from_number(n))
788                }
789                (Value::Number(n), Type::Color) => Color::from_argb_encoded(n as u32).into(),
790                (Value::Brush(brush), Type::Color) => brush.color().into(),
791                (Value::EnumerationValue(_, val), Type::String) => Value::String(val.into()),
792                (v, _) => v,
793            }
794        }
795        Expression::CodeBlock(sub) => {
796            let mut v = Value::Void;
797            for e in sub {
798                v = eval_expression(ctx, e);
799                if let Some(r) = &ctx.return_value {
800                    return r.clone();
801                }
802            }
803            v
804        }
805        Expression::BuiltinFunctionCall { function, arguments } => {
806            call_builtin_function(ctx, function.clone(), arguments)
807        }
808        Expression::CallBackCall { callback, arguments } => {
809            let args: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
810            invoke_callback(ctx, callback, &args)
811        }
812        Expression::FunctionCall { function, arguments } => {
813            let args: Vec<Value> = arguments.iter().map(|e| eval_expression(ctx, e)).collect();
814            invoke_function(ctx, function, args)
815        }
816        Expression::ItemMemberFunctionCall { function } => call_item_member_function(ctx, function),
817        Expression::ExtraBuiltinFunctionCall { function, arguments, .. } => {
818            crate::eval_layout::call_extra_builtin(ctx, function, arguments)
819        }
820        Expression::PropertyAssignment { property, value } => {
821            let v = eval_expression(ctx, value);
822            store_property(ctx, property, v);
823            Value::Void
824        }
825        Expression::ModelDataAssignment { level, value } => {
826            let new_value = eval_expression(ctx, value);
827            if let Some(current) = ctx.current.as_ref() {
828                let mut walker = current.clone();
829                for _ in 0..*level {
830                    let parent = walker.parent.upgrade().expect("parent vanished");
831                    walker = std::pin::Pin::new(parent);
832                }
833                if let Some((parent_weak, repeater_idx)) = walker.repeated_in.get()
834                    && let Some(parent) = parent_weak.upgrade()
835                {
836                    // Read the row index out of the repeated sub-component's
837                    // `model_index` property.
838                    let row = walker.compilation_unit.sub_components[walker.sub_component_idx]
839                        .properties
840                        .iter_enumerated()
841                        .find(|(_, p)| p.name.as_str() == "model_index")
842                        .map(|(idx, _)| {
843                            let v = std::pin::Pin::as_ref(&walker.properties[idx]).get();
844                            f64::try_from(v).unwrap_or(0.) as usize
845                        })
846                        .unwrap_or(0);
847                    let parent_pinned = std::pin::Pin::new(parent);
848                    let repeater = &parent_pinned.repeaters[*repeater_idx];
849                    repeater.model_set_row_data(row, new_value);
850                }
851            }
852            Value::Void
853        }
854        Expression::ArrayIndexAssignment { array, index, value } => {
855            let value = eval_expression(ctx, value);
856            let array = eval_expression(ctx, array);
857            let index = eval_expression(ctx, index);
858            if let (Value::Model(m), Value::Number(i)) = (array, index)
859                && i >= 0.0
860            {
861                let i = i.trunc() as usize;
862                if i < m.row_count() {
863                    m.set_row_data(i, value);
864                }
865            }
866            Value::Void
867        }
868        Expression::SliceIndexAssignment { slice_name, index, value } => {
869            let value = eval_expression(ctx, value);
870            match ctx.locals.get_mut(slice_name.as_str()) {
871                Some(Value::ArrayOfU16(vec)) => {
872                    if let Value::Number(n) = value
873                        && *index < vec.len()
874                    {
875                        vec.make_mut_slice()[*index] = n as u16;
876                    }
877                }
878                Some(Value::Model(m)) if *index < m.row_count() => {
879                    m.set_row_data(*index, value);
880                }
881                _ => {}
882            }
883            Value::Void
884        }
885        Expression::BinaryExpression { lhs, rhs, op } => {
886            let lhs = eval_expression(ctx, lhs);
887            // `&&` and `||` must short-circuit, or else rhs side effects
888            // would wrongly run.
889            match (op, &lhs) {
890                ('&', Value::Bool(false)) => return Value::Bool(false),
891                ('|', Value::Bool(true)) => return Value::Bool(true),
892                _ => {}
893            }
894            let rhs = eval_expression(ctx, rhs);
895            binary_op(*op, lhs, rhs)
896        }
897        Expression::UnaryOp { sub, op } => {
898            let sub = eval_expression(ctx, sub);
899            match (sub, op) {
900                (Value::Number(a), '+') => Value::Number(a),
901                (Value::Number(a), '-') => Value::Number(-a),
902                (Value::Bool(a), '!') => Value::Bool(!a),
903                // Coerce `Void` from uninitialized properties instead of
904                // panicking.
905                (Value::Void, '+' | '-') => Value::Number(0.0),
906                (Value::Void, '!') => Value::Bool(true),
907                (s, o) => panic!("unsupported {o} {s:?}"),
908            }
909        }
910        Expression::ImageReference { resource_ref, nine_slice } => {
911            let mut image = load_image_reference(resource_ref);
912            if let Some(n) = nine_slice {
913                image.set_nine_slice_edges(n[0], n[1], n[2], n[3]);
914            }
915            Value::Image(image)
916        }
917        Expression::Condition { condition, true_expr, false_expr } => {
918            match eval_expression(ctx, condition) {
919                Value::Bool(true) => eval_expression(ctx, true_expr),
920                Value::Bool(false) => eval_expression(ctx, false_expr),
921                _ => Value::Void,
922            }
923        }
924        Expression::Array { values, .. } => Value::Model(ModelRc::new(SharedVectorModel::from(
925            values.iter().map(|e| eval_expression(ctx, e)).collect::<SharedVector<_>>(),
926        ))),
927        Expression::Struct { values, .. } => Value::Struct(
928            values.iter().map(|(k, v)| (k.to_string(), eval_expression(ctx, v))).collect(),
929        ),
930        Expression::EasingCurve(curve) => {
931            use i_slint_compiler::expression_tree::EasingCurve as EC;
932            use i_slint_core::animations::EasingCurve as Core;
933            Value::EasingCurve(match curve {
934                EC::Linear => Core::Linear,
935                EC::EaseInElastic => Core::EaseInElastic,
936                EC::EaseOutElastic => Core::EaseOutElastic,
937                EC::EaseInOutElastic => Core::EaseInOutElastic,
938                EC::EaseInBounce => Core::EaseInBounce,
939                EC::EaseOutBounce => Core::EaseOutBounce,
940                EC::EaseInOutBounce => Core::EaseInOutBounce,
941                EC::CubicBezier(a, b, c, d) => Core::CubicBezier([*a, *b, *c, *d]),
942            })
943        }
944        Expression::MouseCursor(cursor) => {
945            use i_slint_compiler::expression_tree::MouseCursorInner as Expr;
946            use i_slint_core::cursor::MouseCursorInner as Core;
947            Value::MouseCursorInner(match cursor {
948                Expr::BuiltIn(cursor) => {
949                    Core::BuiltIn(eval_expression(ctx, cursor).try_into().unwrap_or_default())
950                }
951                Expr::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
952                    Core::CustomMouseCursor {
953                        image: eval_expression(ctx, image).try_into().unwrap_or_default(),
954                        hotspot_x: eval_expression(ctx, hotspot_x).try_into().unwrap_or_default(),
955                        hotspot_y: eval_expression(ctx, hotspot_y).try_into().unwrap_or_default(),
956                    }
957                }
958            })
959        }
960        Expression::LinearGradient { angle, stops } => {
961            let angle: f32 = eval_expression(ctx, angle).try_into().unwrap_or_default();
962            Value::Brush(Brush::LinearGradient(LinearGradientBrush::new(
963                angle,
964                eval_stops(ctx, stops),
965            )))
966        }
967        Expression::RadialGradient { stops, center, radius } => {
968            let mut g = RadialGradientBrush::new_circle(eval_stops(ctx, stops));
969            if let Some((cx, cy)) = center {
970                let cx: f32 = eval_expression(ctx, cx).try_into().unwrap_or_default();
971                let cy: f32 = eval_expression(ctx, cy).try_into().unwrap_or_default();
972                g = g.with_center(cx, cy);
973            }
974            if let Some(r) = radius {
975                let r: f32 = eval_expression(ctx, r).try_into().unwrap_or_default();
976                g = g.with_radius(r);
977            }
978            Value::Brush(Brush::RadialGradient(g))
979        }
980        Expression::ConicGradient { from_angle, stops, center } => {
981            let from_angle: f32 = eval_expression(ctx, from_angle).try_into().unwrap_or_default();
982            let mut g = ConicGradientBrush::new(from_angle, eval_stops(ctx, stops));
983            if let Some((cx, cy)) = center {
984                let cx: f32 = eval_expression(ctx, cx).try_into().unwrap_or_default();
985                let cy: f32 = eval_expression(ctx, cy).try_into().unwrap_or_default();
986                g = g.with_center(cx, cy);
987            }
988            Value::Brush(Brush::ConicGradient(g))
989        }
990        Expression::EnumerationValue(value) => {
991            Value::EnumerationValue(value.enumeration.name.to_string(), value.to_string())
992        }
993        Expression::LayoutCacheAccess {
994            layout_cache_prop,
995            index,
996            repeater_index,
997            entries_per_item,
998        } => {
999            let cache = load_property(ctx, layout_cache_prop);
1000            layout_cache_access(ctx, cache, *index, repeater_index.as_deref(), *entries_per_item)
1001        }
1002        Expression::GridRepeaterCacheAccess {
1003            layout_cache_prop,
1004            index,
1005            repeater_index,
1006            stride,
1007            child_offset,
1008            inner_repeater_index,
1009            entries_per_item,
1010        } => {
1011            let cache = load_property(ctx, layout_cache_prop);
1012            let offset: usize = eval_expression(ctx, repeater_index).try_into().unwrap_or_default();
1013            let stride_val: usize = eval_expression(ctx, stride).try_into().unwrap_or_default();
1014            let inner_offset: usize = inner_repeater_index
1015                .as_deref()
1016                .map(|e| {
1017                    let i: usize = eval_expression(ctx, e).try_into().unwrap_or_default();
1018                    i * *entries_per_item
1019                })
1020                .unwrap_or(0);
1021            grid_repeater_cache_access(
1022                cache,
1023                *index,
1024                offset,
1025                stride_val,
1026                *child_offset,
1027                inner_offset,
1028            )
1029        }
1030        Expression::WithLayoutItemInfo {
1031            cells_variable,
1032            elements,
1033            orientation,
1034            repeated_cross_size,
1035            sub_expression,
1036            ..
1037        } => with_layout_item_info(
1038            ctx,
1039            cells_variable,
1040            elements,
1041            *orientation,
1042            repeated_cross_size.as_deref(),
1043            sub_expression,
1044        ),
1045        Expression::WithFlexboxLayoutItemInfo {
1046            cells_h_variable,
1047            cells_v_variable,
1048            flex_props_variable,
1049            elements,
1050            repeated_cross_width,
1051            sub_expression,
1052            ..
1053        } => with_flexbox_layout_item_info(
1054            ctx,
1055            cells_h_variable,
1056            cells_v_variable,
1057            flex_props_variable.as_deref(),
1058            elements,
1059            repeated_cross_width.as_deref(),
1060            sub_expression,
1061        ),
1062        Expression::WithGridInputData { cells_variable, elements, sub_expression, .. } => {
1063            with_grid_input_data(ctx, cells_variable, elements, sub_expression)
1064        }
1065        Expression::MinMax { ty: _, op, lhs, rhs } => {
1066            let Value::Number(lhs) = eval_expression(ctx, lhs) else { return Value::Void };
1067            let Value::Number(rhs) = eval_expression(ctx, rhs) else { return Value::Void };
1068            match op {
1069                MinMaxOp::Min => Value::Number(lhs.min(rhs)),
1070                MinMaxOp::Max => Value::Number(lhs.max(rhs)),
1071            }
1072        }
1073        Expression::EmptyComponentFactory => Value::ComponentFactory(Default::default()),
1074        Expression::EmptyDataTransfer => Value::DataTransfer(Default::default()),
1075        Expression::SolveFlexboxLayoutWithMeasure { .. } => {
1076            crate::eval_layout::solve_flexbox_layout_with_measure(ctx, expression)
1077        }
1078        Expression::FlexboxLayoutInfoCrossAxisWithMeasure { .. } => {
1079            crate::eval_layout::flexbox_layout_info_cross_axis_with_measure(ctx, expression)
1080        }
1081        Expression::BoxLayoutInfoOrthoWithMeasure { .. } => {
1082            crate::eval_layout::box_layout_info_ortho_with_measure(ctx, expression)
1083        }
1084        Expression::TranslationReference { .. } => {
1085            // TranslationReference is only emitted when `bundle-translations`
1086            // is active, which the interpreter does not use. Runtime @tr()
1087            // goes through BuiltinFunction::Translate instead.
1088            Value::String(Default::default())
1089        }
1090        Expression::Closure { .. } => unreachable!(
1091            "closures are dispatched by their consuming builtin and should not go through eval_expression"
1092        ),
1093        Expression::DebugHook { expression, id } => {
1094            if let Some(hook_value) = crate::debug_hook::trigger_debug_hook(ctx, id) {
1095                return hook_value;
1096            }
1097            eval_expression(ctx, expression)
1098        }
1099    }
1100}
1101
1102fn with_layout_item_info(
1103    ctx: &mut EvalContext,
1104    cells_variable: &str,
1105    elements: &[itertools::Either<Expression, i_slint_compiler::llr::LayoutRepeatedElement>],
1106    orientation: i_slint_compiler::layout::Orientation,
1107    repeated_cross_size: Option<&Expression>,
1108    sub_expression: &Expression,
1109) -> Value {
1110    // On a box layout's main-axis pass, re-measure each repeated cell at the
1111    // layout's cross size so a height-for-width (resp. width-for-height)
1112    // instance measures like an equivalent static cell. On a non-numeric
1113    // value, fall back to the plain layout info rather than measuring at 0.
1114    let cross_size: Option<f32> =
1115        repeated_cross_size.and_then(|e| eval_expression(ctx, e).try_into().ok());
1116    let mut cells: Vec<Value> = Vec::with_capacity(elements.len());
1117    let mut repeated_indices: Vec<u32> = Vec::new();
1118    let mut repeater_steps: Vec<u32> = Vec::new();
1119    for el in elements {
1120        match el {
1121            itertools::Either::Left(expr) => cells.push(eval_expression(ctx, expr)),
1122            itertools::Either::Right(repeater) => {
1123                let offset = cells.len() as u32;
1124                let (instances, step) = push_repeater_layout_items(
1125                    ctx,
1126                    repeater.repeater_index,
1127                    repeater.row_child_templates.as_deref(),
1128                    orientation,
1129                    cross_size,
1130                    &mut cells,
1131                );
1132                repeated_indices.push(offset);
1133                repeated_indices.push(instances);
1134                repeater_steps.push(step);
1135            }
1136        }
1137    }
1138    let prev_cells =
1139        ctx.locals.insert(SmolStr::from(cells_variable), Value::Model(model_from_vec(cells)));
1140    let prev_ri = ctx.locals.insert(
1141        SmolStr::new_static("repeated_indices"),
1142        Value::Model(model_from_vec(
1143            repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1144        )),
1145    );
1146    let prev_rs = ctx.locals.insert(
1147        SmolStr::new_static("repeater_steps"),
1148        Value::Model(model_from_vec(
1149            repeater_steps.into_iter().map(|i| Value::Number(i as f64)).collect(),
1150        )),
1151    );
1152    let result = eval_expression(ctx, sub_expression);
1153    restore_local(ctx, cells_variable, prev_cells);
1154    restore_local(ctx, "repeated_indices", prev_ri);
1155    restore_local(ctx, "repeater_steps", prev_rs);
1156    result
1157}
1158
1159fn push_repeater_layout_items(
1160    ctx: &mut EvalContext,
1161    repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1162    row_child_templates: Option<&[i_slint_compiler::llr::RowChildTemplateInfo]>,
1163    orientation: i_slint_compiler::layout::Orientation,
1164    cross_size: Option<f32>,
1165    cells: &mut Vec<Value>,
1166) -> (u32, u32) {
1167    use i_slint_core::model::RepeatedItemTree;
1168    let Some(current) = ctx.current.as_ref() else { return (0, 0) };
1169    let repeater = &current.repeaters[repeater_idx];
1170    repeater.track_instance_changes();
1171    let instances = repeater.instances_vec();
1172    let core_orientation = llr_to_core_orientation(orientation);
1173    let push_cell = |cells: &mut Vec<Value>, info: i_slint_core::layout::LayoutItemInfo| {
1174        let mut struct_value = crate::api::Struct::default();
1175        struct_value.set_field("constraint".to_string(), info.constraint.into());
1176        // The cell's `cross-axis-self-alignment` in a box layout; `to_cells`
1177        // reads it back on the cross-axis solve, an absent field means `auto`.
1178        if info.cross_axis_self_alignment != i_slint_core::items::CrossAxisSelfAlignment::Auto {
1179            struct_value.set_field(
1180                "cross-axis-self-alignment".to_string(),
1181                Value::EnumerationValue(
1182                    "CrossAxisSelfAlignment".to_string(),
1183                    info.cross_axis_self_alignment.to_string(),
1184                ),
1185            );
1186        }
1187        cells.push(Value::Struct(struct_value));
1188    };
1189    let step = match row_child_templates {
1190        None => {
1191            // Column repeater: one cell per instance, asking the sub-component
1192            // for its own layout info — at the layout's cross size when the
1193            // main-axis pass forwards one.
1194            for instance in &instances {
1195                let info = match (cross_size, core_orientation) {
1196                    (Some(cs), i_slint_core::items::Orientation::Vertical) => {
1197                        RepeatedItemTree::layout_item_info_at_cross_width(instance.as_pin_ref(), cs)
1198                    }
1199                    (Some(cs), i_slint_core::items::Orientation::Horizontal) => {
1200                        RepeatedItemTree::layout_item_info_at_cross_height(
1201                            instance.as_pin_ref(),
1202                            cs,
1203                        )
1204                    }
1205                    (None, _) => RepeatedItemTree::layout_item_info(
1206                        instance.as_pin_ref(),
1207                        core_orientation,
1208                        None,
1209                    ),
1210                };
1211                push_cell(cells, info);
1212            }
1213            1
1214        }
1215        Some(templates) => {
1216            // Only box layouts set a cross size, and their repeaters never
1217            // have row templates.
1218            debug_assert!(cross_size.is_none());
1219            // Row repeater: the step is the maximum total child count across
1220            // instances (static children plus each instance's inner repeaters
1221            // realized via RowChildTemplateInfo::Repeated).
1222            let max_total = instances
1223                .iter()
1224                .map(|inst| total_row_child_count(&inst.root_sub_component, templates))
1225                .max()
1226                .unwrap_or(i_slint_compiler::llr::static_child_count(templates));
1227            for instance in &instances {
1228                for child_idx in 0..max_total {
1229                    let info = RepeatedItemTree::layout_item_info(
1230                        instance.as_pin_ref(),
1231                        core_orientation,
1232                        Some(child_idx),
1233                    );
1234                    push_cell(cells, info);
1235                }
1236            }
1237            max_total as u32
1238        }
1239    };
1240    (instances.len() as u32, step)
1241}
1242
1243fn total_row_child_count(
1244    sub: &Pin<std::rc::Rc<crate::instance::SubComponentInstance>>,
1245    templates: &[i_slint_compiler::llr::RowChildTemplateInfo],
1246) -> usize {
1247    use i_slint_compiler::llr::{RowChildTemplateInfo, static_child_count};
1248    let mut total = static_child_count(templates);
1249    for entry in templates {
1250        if let RowChildTemplateInfo::Repeated { repeater_index } = entry {
1251            let repeater = &sub.repeaters[*repeater_index];
1252            repeater.track_instance_changes();
1253            total += repeater.range().len();
1254        }
1255    }
1256    total
1257}
1258
1259pub(crate) fn llr_to_core_orientation(
1260    o: i_slint_compiler::layout::Orientation,
1261) -> i_slint_core::items::Orientation {
1262    match o {
1263        i_slint_compiler::layout::Orientation::Horizontal => {
1264            i_slint_core::items::Orientation::Horizontal
1265        }
1266        i_slint_compiler::layout::Orientation::Vertical => {
1267            i_slint_core::items::Orientation::Vertical
1268        }
1269    }
1270}
1271
1272fn with_flexbox_layout_item_info(
1273    ctx: &mut EvalContext,
1274    cells_h_variable: &str,
1275    cells_v_variable: &str,
1276    flex_props_variable: Option<&str>,
1277    elements: &[itertools::Either<
1278        (Expression, Expression, Expression),
1279        i_slint_compiler::llr::LayoutRepeatedElement,
1280    >],
1281    repeated_cross_width: Option<&Expression>,
1282    sub_expression: &Expression,
1283) -> Value {
1284    // For a column flex, re-measure each repeated cell at the container width so
1285    // a height-for-width instance wraps like an equivalent static cell.
1286    let cross_width =
1287        repeated_cross_width.map(|e| eval_expression(ctx, e).try_into().unwrap_or_default());
1288    let mut cells_h: Vec<Value> = Vec::with_capacity(elements.len());
1289    let mut cells_v: Vec<Value> = Vec::with_capacity(elements.len());
1290    let mut flex_props: Vec<Value> = Vec::with_capacity(elements.len());
1291    let mut repeated_indices: Vec<u32> = Vec::new();
1292    for el in elements {
1293        match el {
1294            itertools::Either::Left((h, v, props)) => {
1295                cells_h.push(eval_expression(ctx, h));
1296                cells_v.push(eval_expression(ctx, v));
1297                // With no flex-props variable the sub-expression only reads the
1298                // cells; don't evaluate (and thus depend on) the static cell's
1299                // flex properties.
1300                if flex_props_variable.is_some() {
1301                    flex_props.push(eval_expression(ctx, props));
1302                }
1303            }
1304            itertools::Either::Right(repeater) => {
1305                let offset = cells_h.len() as u32;
1306                let instances = push_repeater_flexbox_items(
1307                    ctx,
1308                    repeater.repeater_index,
1309                    cross_width,
1310                    &mut cells_h,
1311                    &mut cells_v,
1312                    flex_props_variable.is_some().then_some(&mut flex_props),
1313                );
1314                repeated_indices.push(offset);
1315                repeated_indices.push(instances);
1316            }
1317        }
1318    }
1319    let prev_h =
1320        ctx.locals.insert(SmolStr::from(cells_h_variable), Value::Model(model_from_vec(cells_h)));
1321    let prev_v =
1322        ctx.locals.insert(SmolStr::from(cells_v_variable), Value::Model(model_from_vec(cells_v)));
1323    let prev_fp = flex_props_variable.map(|name| {
1324        ctx.locals.insert(SmolStr::from(name), Value::Model(model_from_vec(flex_props)))
1325    });
1326    let prev_ri = ctx.locals.insert(
1327        SmolStr::new_static("repeated_indices"),
1328        Value::Model(model_from_vec(
1329            repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1330        )),
1331    );
1332    let result = eval_expression(ctx, sub_expression);
1333    restore_local(ctx, cells_h_variable, prev_h);
1334    restore_local(ctx, cells_v_variable, prev_v);
1335    if let Some(name) = flex_props_variable {
1336        restore_local(ctx, name, prev_fp.flatten());
1337    }
1338    restore_local(ctx, "repeated_indices", prev_ri);
1339    result
1340}
1341
1342fn push_repeater_flexbox_items(
1343    ctx: &mut EvalContext,
1344    repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1345    cross_width: Option<f32>,
1346    cells_h: &mut Vec<Value>,
1347    cells_v: &mut Vec<Value>,
1348    mut flex_props: Option<&mut Vec<Value>>,
1349) -> u32 {
1350    use i_slint_core::items::Orientation;
1351    use i_slint_core::model::RepeatedItemTree;
1352    let Some(current) = ctx.current.as_ref() else { return 0 };
1353    let repeater = &current.repeaters[repeater_idx];
1354    repeater.track_instance_changes();
1355    let instances = repeater.instances_vec();
1356    let instance_count = instances.len() as u32;
1357    for instance in instances {
1358        // Flexbox needs `FlexboxLayoutItemInfo` (constraint plus flex props);
1359        // the default `RepeatedItemTree::flexbox_layout_item_info` impl wraps
1360        // the box-layout info and default-fills the props.
1361        let info_h = RepeatedItemTree::flexbox_layout_item_info(
1362            instance.as_pin_ref(),
1363            Orientation::Horizontal,
1364            None,
1365        );
1366        // For a column flex, measure the vertical info at the container width so
1367        // a height-for-width cell wraps to the real width, not its preferred one.
1368        let info_v = match cross_width {
1369            Some(w) => instance.as_pin_ref().flexbox_layout_item_info_at_cross_width(w),
1370            None => RepeatedItemTree::flexbox_layout_item_info(
1371                instance.as_pin_ref(),
1372                Orientation::Vertical,
1373                None,
1374            ),
1375        };
1376        // The flex props are axis-independent: both bundled infos carry the
1377        // same ones, take them from the horizontal query.
1378        if let Some(fp) = flex_props.as_mut() {
1379            fp.push(flex_props_to_value(info_h.props));
1380        }
1381        cells_h.push(layout_item_info_to_value(info_h.constraint));
1382        cells_v.push(layout_item_info_to_value(info_v.constraint));
1383    }
1384    instance_count
1385}
1386
1387fn layout_item_info_to_value(constraint: i_slint_core::layout::LayoutInfo) -> Value {
1388    let mut s = crate::api::Struct::default();
1389    s.set_field("constraint".to_string(), constraint.into());
1390    Value::Struct(s)
1391}
1392
1393fn flex_props_to_value(props: i_slint_core::layout::FlexItemProps) -> Value {
1394    let mut s = crate::api::Struct::default();
1395    s.set_field(
1396        "cross-axis-self-alignment".to_string(),
1397        Value::EnumerationValue(
1398            "CrossAxisSelfAlignment".to_string(),
1399            format!("{:?}", props.cross_axis_self_alignment).to_lowercase(),
1400        ),
1401    );
1402    s.set_field("layout-order".to_string(), Value::Number(props.layout_order as f64));
1403    Value::Struct(s)
1404}
1405
1406fn with_grid_input_data(
1407    ctx: &mut EvalContext,
1408    cells_variable: &str,
1409    elements: &[itertools::Either<Expression, i_slint_compiler::llr::GridLayoutRepeatedElement>],
1410    sub_expression: &Expression,
1411) -> Value {
1412    // `repeated_indices` holds `(offset, len)` pairs into `cells`,
1413    // `repeater_steps` the per-instance item count.
1414    // The `new_row` local tracks whether the next static cell starts a new
1415    // row: each repeater resets it to its static `new_row`, and a column
1416    // repeater that ran at least once clears it. Static cells after the
1417    // repeater read it via `ReadLocalVariable("new_row")`.
1418    let saved_new_row = ctx.locals.remove("new_row");
1419    let mut cells: Vec<Value> = Vec::with_capacity(elements.len());
1420    let mut repeated_indices: Vec<u32> = Vec::new();
1421    let mut repeater_steps: Vec<u32> = Vec::new();
1422
1423    for el in elements {
1424        match el {
1425            itertools::Either::Left(expr) => cells.push(eval_expression(ctx, expr)),
1426            itertools::Either::Right(repeater) => {
1427                ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(repeater.new_row));
1428                let offset = cells.len() as u32;
1429                let is_row_repeater = repeater.row_child_templates.is_some();
1430                let (instances, step) = push_repeater_grid_input_data(
1431                    ctx,
1432                    repeater.repeater_index,
1433                    repeater.new_row,
1434                    repeater.row_child_templates.as_deref(),
1435                    &mut cells,
1436                );
1437                if !is_row_repeater && instances > 0 {
1438                    ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(false));
1439                }
1440                repeated_indices.push(offset);
1441                repeated_indices.push(instances);
1442                repeater_steps.push(step);
1443            }
1444        }
1445    }
1446    restore_local(ctx, "new_row", saved_new_row);
1447
1448    let prev_cells =
1449        ctx.locals.insert(SmolStr::from(cells_variable), Value::Model(model_from_vec(cells)));
1450    let prev_ri = ctx.locals.insert(
1451        SmolStr::new_static("repeated_indices"),
1452        Value::Model(model_from_vec(
1453            repeated_indices.into_iter().map(|i| Value::Number(i as f64)).collect(),
1454        )),
1455    );
1456    let prev_rs = ctx.locals.insert(
1457        SmolStr::new_static("repeater_steps"),
1458        Value::Model(model_from_vec(
1459            repeater_steps.into_iter().map(|i| Value::Number(i as f64)).collect(),
1460        )),
1461    );
1462
1463    let result = eval_expression(ctx, sub_expression);
1464
1465    restore_local(ctx, cells_variable, prev_cells);
1466    restore_local(ctx, "repeated_indices", prev_ri);
1467    restore_local(ctx, "repeater_steps", prev_rs);
1468    result
1469}
1470
1471pub(crate) fn restore_local(ctx: &mut EvalContext, name: &str, prev: Option<Value>) {
1472    if let Some(prev) = prev {
1473        ctx.locals.insert(SmolStr::from(name), prev);
1474    } else {
1475        ctx.locals.remove(name);
1476    }
1477}
1478
1479fn push_repeater_grid_input_data(
1480    ctx: &mut EvalContext,
1481    repeater_idx: i_slint_compiler::llr::RepeatedElementIdx,
1482    new_row: bool,
1483    row_child_templates: Option<&[i_slint_compiler::llr::RowChildTemplateInfo]>,
1484    cells: &mut Vec<Value>,
1485) -> (u32, u32) {
1486    use i_slint_compiler::llr::RowChildTemplateInfo;
1487    use i_slint_core::model::VecModel;
1488    use std::rc::Rc;
1489    let Some(current) = ctx.current.as_ref() else { return (0, 0) };
1490    let repeater = &current.repeaters[repeater_idx];
1491    repeater.track_instance_changes();
1492
1493    let is_row_repeater = row_child_templates.is_some();
1494    let static_count =
1495        row_child_templates.map(i_slint_compiler::llr::static_child_count).unwrap_or(1);
1496
1497    let instances = repeater.instances_vec();
1498    let instance_count = instances.len() as u32;
1499
1500    // Step is the max total cells per instance. Every instance contributes
1501    // exactly `step` entries so the flattened cell vector lines up with
1502    // `repeater_steps` and `repeated_indices`.
1503    let step = if let Some(templates) = row_child_templates {
1504        instances
1505            .iter()
1506            .map(|inst| total_row_child_count(&inst.root_sub_component, templates))
1507            .max()
1508            .unwrap_or(static_count)
1509    } else {
1510        1
1511    };
1512
1513    let mut current_new_row = new_row;
1514
1515    for instance in &instances {
1516        let inner_sub = instance.root_sub_component.clone();
1517        let cu = inner_sub.compilation_unit.clone();
1518        let sc = &cu.sub_components[inner_sub.sub_component_idx];
1519
1520        // Evaluate `grid_layout_input_for_repeated` to populate the `statics`
1521        // array (one entry per `RowChildTemplateInfo::Static`). For a simple
1522        // column repeater this is the full result.
1523        let mut statics: Vec<Value> = vec![Value::Void; static_count];
1524        if let Some(expr) = &sc.grid_layout_input_for_repeated {
1525            let expr = expr.borrow();
1526            let mut inner_ctx = EvalContext::new(inner_sub.clone());
1527            let result_model: Rc<VecModel<Value>> = Rc::new(VecModel::default());
1528            for _ in 0..static_count {
1529                result_model.push(Value::Void);
1530            }
1531            inner_ctx.locals.insert(
1532                SmolStr::new_static("result"),
1533                Value::Model(i_slint_core::model::ModelRc::from(result_model.clone())),
1534            );
1535            inner_ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(current_new_row));
1536            eval_expression(&mut inner_ctx, &expr);
1537            for (slot, i) in statics.iter_mut().zip(0..result_model.row_count()) {
1538                if let Some(v) = result_model.row_data(i) {
1539                    *slot = v;
1540                }
1541            }
1542        }
1543
1544        if let Some(templates) = row_child_templates {
1545            // Walk templates, interleaving statics and auto-positioned
1546            // placeholder cells for inner-repeater instances. Any leftover
1547            // slot up to `step` gets an auto-positioned default as well.
1548            let mut written = 0usize;
1549            let mut static_idx = 0usize;
1550            for entry in templates {
1551                if written >= step {
1552                    break;
1553                }
1554                match entry {
1555                    RowChildTemplateInfo::Static { .. } => {
1556                        let mut v = statics.get(static_idx).cloned().unwrap_or(Value::Void);
1557                        static_idx += 1;
1558                        override_new_row(&mut v, written == 0 && current_new_row);
1559                        cells.push(v);
1560                        written += 1;
1561                    }
1562                    RowChildTemplateInfo::Repeated { repeater_index } => {
1563                        let inner_rep = &inner_sub.repeaters[*repeater_index];
1564                        inner_rep.track_instance_changes();
1565                        // Let each inner cell report its own
1566                        // col/row/colspan/rowspan via its
1567                        // `grid_layout_input_for_repeated` expression.
1568                        for inner_inst in inner_rep.instances_vec() {
1569                            if written >= step {
1570                                break;
1571                            }
1572                            for mut v in eval_grid_input_for_repeated(
1573                                &inner_inst.root_sub_component,
1574                                written == 0 && current_new_row,
1575                            ) {
1576                                if written >= step {
1577                                    break;
1578                                }
1579                                override_new_row(&mut v, written == 0 && current_new_row);
1580                                cells.push(v);
1581                                written += 1;
1582                            }
1583                        }
1584                    }
1585                }
1586            }
1587            while written < step {
1588                cells.push(auto_grid_input_data());
1589                written += 1;
1590            }
1591        } else {
1592            // Column repeater: one cell per instance.
1593            cells.push(statics.pop().unwrap_or_else(auto_grid_input_data));
1594        }
1595
1596        if !is_row_repeater {
1597            current_new_row = false;
1598        }
1599    }
1600    (instance_count, step as u32)
1601}
1602
1603/// Evaluate a repeated cell's own `grid_layout_input_for_repeated`
1604/// expression, so it reports its declared col/row/colspan/rowspan. Falls
1605/// back to a single auto-positioned cell when the sub-component has no
1606/// grid input expression.
1607fn eval_grid_input_for_repeated(
1608    sub: &Pin<Rc<crate::instance::SubComponentInstance>>,
1609    new_row: bool,
1610) -> Vec<Value> {
1611    use i_slint_core::model::{Model, VecModel};
1612    let cu = sub.compilation_unit.clone();
1613    let sc = &cu.sub_components[sub.sub_component_idx];
1614    let count = sc
1615        .row_child_templates
1616        .as_ref()
1617        .map(|t| i_slint_compiler::llr::static_child_count(t))
1618        .unwrap_or(1)
1619        .max(1);
1620    let Some(expr) = &sc.grid_layout_input_for_repeated else {
1621        return vec![auto_grid_input_data()];
1622    };
1623    let expr = expr.borrow();
1624    let mut ctx = EvalContext::new(sub.clone());
1625    let result_model: Rc<VecModel<Value>> = Rc::new(VecModel::default());
1626    for _ in 0..count {
1627        result_model.push(Value::Void);
1628    }
1629    ctx.locals.insert(
1630        SmolStr::new_static("result"),
1631        Value::Model(i_slint_core::model::ModelRc::from(result_model.clone())),
1632    );
1633    ctx.locals.insert(SmolStr::new_static("new_row"), Value::Bool(new_row));
1634    eval_expression(&mut ctx, &expr);
1635    (0..result_model.row_count())
1636        .map(|i| result_model.row_data(i).unwrap_or_else(auto_grid_input_data))
1637        .collect()
1638}
1639
1640/// A `GridLayoutInputData` struct with auto row/col and unit span — matches
1641/// `GridLayoutInputData::default()` in `i_slint_core::layout`.
1642fn auto_grid_input_data() -> Value {
1643    let mut s = crate::api::Struct::default();
1644    s.set_field("new-row".into(), Value::Bool(false));
1645    s.set_field("row".into(), Value::Number(i_slint_common::ROW_COL_AUTO as f64));
1646    s.set_field("col".into(), Value::Number(i_slint_common::ROW_COL_AUTO as f64));
1647    s.set_field("rowspan".into(), Value::Number(1.0));
1648    s.set_field("colspan".into(), Value::Number(1.0));
1649    Value::Struct(s)
1650}
1651
1652fn override_new_row(v: &mut Value, new_row: bool) {
1653    if let Value::Struct(s) = v {
1654        s.set_field("new-row".into(), Value::Bool(new_row));
1655    }
1656}
1657
1658fn model_from_vec(values: Vec<Value>) -> ModelRc<Value> {
1659    ModelRc::new(SharedVectorModel::from(values.into_iter().collect::<SharedVector<_>>()))
1660}
1661
1662fn binary_op(op: char, lhs: Value, rhs: Value) -> Value {
1663    // Coerce a `Void` operand to the type-default of the other side so we
1664    // don't panic on uninitialized property reads.
1665    let (lhs, rhs) = match (lhs, rhs) {
1666        (Value::Void, Value::Number(b)) => (Value::Number(0.), Value::Number(b)),
1667        (Value::Number(a), Value::Void) => (Value::Number(a), Value::Number(0.)),
1668        (Value::Void, Value::Bool(b)) => (Value::Bool(false), Value::Bool(b)),
1669        (Value::Bool(a), Value::Void) => (Value::Bool(a), Value::Bool(false)),
1670        (Value::Void, Value::String(b)) => (Value::String(Default::default()), Value::String(b)),
1671        (Value::String(a), Value::Void) => (Value::String(a), Value::String(Default::default())),
1672        (a, b) => (a, b),
1673    };
1674    match (op, lhs, rhs) {
1675        ('+', Value::String(mut a), Value::String(b)) => {
1676            a.push_str(b.as_str());
1677            Value::String(a)
1678        }
1679        ('+', Value::Number(a), Value::Number(b)) => Value::Number(a + b),
1680        ('+', a @ Value::Struct(_), b @ Value::Struct(_)) => {
1681            let la: Option<i_slint_core::layout::LayoutInfo> = a.try_into().ok();
1682            let lb: Option<i_slint_core::layout::LayoutInfo> = b.try_into().ok();
1683            if let (Some(a), Some(b)) = (la, lb) {
1684                a.merge(&b).into()
1685            } else {
1686                panic!("unsupported struct + struct");
1687            }
1688        }
1689        ('-', Value::Number(a), Value::Number(b)) => Value::Number(a - b),
1690        ('/', Value::Number(a), Value::Number(b)) => Value::Number(a / b),
1691        ('*', Value::Number(a), Value::Number(b)) => Value::Number(a * b),
1692        ('<', Value::Number(a), Value::Number(b)) => Value::Bool(a < b),
1693        ('>', Value::Number(a), Value::Number(b)) => Value::Bool(a > b),
1694        ('≤', Value::Number(a), Value::Number(b)) => Value::Bool(a <= b),
1695        ('≥', Value::Number(a), Value::Number(b)) => Value::Bool(a >= b),
1696        ('<', Value::String(a), Value::String(b)) => Value::Bool(a < b),
1697        ('>', Value::String(a), Value::String(b)) => Value::Bool(a > b),
1698        ('≤', Value::String(a), Value::String(b)) => Value::Bool(a <= b),
1699        ('≥', Value::String(a), Value::String(b)) => Value::Bool(a >= b),
1700        ('=', a, b) => Value::Bool(a == b),
1701        ('!', a, b) => Value::Bool(a != b),
1702        ('&', Value::Bool(a), Value::Bool(b)) => Value::Bool(a && b),
1703        ('|', Value::Bool(a), Value::Bool(b)) => Value::Bool(a || b),
1704        (op, a, b) => panic!("unsupported {a:?} {op} {b:?}"),
1705    }
1706}
1707
1708fn eval_stops(ctx: &mut EvalContext, stops: &[(Expression, Expression)]) -> Vec<GradientStop> {
1709    stops
1710        .iter()
1711        .map(|(color, stop)| GradientStop {
1712            color: eval_expression(ctx, color).try_into().unwrap_or_default(),
1713            position: eval_expression(ctx, stop).try_into().unwrap_or_default(),
1714        })
1715        .collect()
1716}
1717
1718fn load_image_reference(
1719    resource_ref: &i_slint_compiler::expression_tree::ImageReference,
1720) -> i_slint_core::graphics::Image {
1721    use i_slint_compiler::expression_tree::ImageReference as Ref;
1722    let image = match resource_ref {
1723        Ref::None => Ok(Default::default()),
1724        Ref::DataUri(data_uri) => i_slint_compiler::data_uri::decode_data_uri(data_uri)
1725            .ok()
1726            .and_then(|(data, extension)| {
1727                i_slint_core::graphics::load_image_from_data_uri(data_uri, &data, &extension).ok()
1728            })
1729            .ok_or_else(Default::default),
1730        Ref::Url(url) if url.scheme() == "builtin" => {
1731            // Style-bundled resources (e.g. cosmic/material widget icons) are
1732            // baked into the compiler's builtin library and need to be fetched
1733            // through `fileaccess::load_file` rather than the filesystem.
1734            let path = std::path::Path::new(url.as_str());
1735            i_slint_compiler::fileaccess::load_file(path)
1736                .and_then(|virtual_file| virtual_file.builtin_contents)
1737                .map(|contents| {
1738                    let extension = path.extension().unwrap().to_str().unwrap();
1739                    i_slint_core::graphics::load_image_from_embedded_data(
1740                        i_slint_core::slice::Slice::from_slice(contents),
1741                        i_slint_core::slice::Slice::from_slice(extension.as_bytes()),
1742                    )
1743                })
1744                .ok_or_else(Default::default)
1745        }
1746        Ref::Path(path) => {
1747            i_slint_core::graphics::Image::load_from_path(std::path::Path::new(path.as_str()))
1748        }
1749        Ref::Url(url) => {
1750            #[cfg(target_arch = "wasm32")]
1751            {
1752                i_slint_core::graphics::load_as_html_image(url.as_str())
1753            }
1754            // URL image references only work on the web, where the browser fetches them.
1755            #[cfg(not(target_arch = "wasm32"))]
1756            {
1757                let _ = url;
1758                Err(Default::default())
1759            }
1760        }
1761        Ref::EmbeddedData { .. } | Ref::EmbeddedTexture { .. } => Ok(Default::default()),
1762    };
1763    image.unwrap_or_else(|_| {
1764        eprintln!("Could not load image {resource_ref:?}");
1765        Default::default()
1766    })
1767}
1768
1769fn layout_cache_access(
1770    ctx: &mut EvalContext,
1771    cache: Value,
1772    index: usize,
1773    repeater_index: Option<&Expression>,
1774    entries_per_item: usize,
1775) -> Value {
1776    match cache {
1777        Value::LayoutCache(cache) => {
1778            if let Some(ri) = repeater_index {
1779                let offset: usize = eval_expression(ctx, ri).try_into().unwrap_or_default();
1780                Value::Number(
1781                    cache
1782                        .get((cache[index] as usize) + offset * entries_per_item)
1783                        .copied()
1784                        .unwrap_or(0.)
1785                        .into(),
1786                )
1787            } else {
1788                Value::Number(cache[index].into())
1789            }
1790        }
1791        Value::ArrayOfU16(cache) => {
1792            if let Some(ri) = repeater_index {
1793                let offset: usize = eval_expression(ctx, ri).try_into().unwrap_or_default();
1794                Value::Number(
1795                    cache
1796                        .get((cache[index] as usize) + offset * entries_per_item)
1797                        .copied()
1798                        .unwrap_or(0)
1799                        .into(),
1800                )
1801            } else {
1802                Value::Number(cache[index].into())
1803            }
1804        }
1805        _ => Value::Number(0.),
1806    }
1807}
1808
1809/// Two-level indirection cache read for grid layouts with repeaters.
1810/// `base = cache[index]` points at the start of a repeated row's entries;
1811/// the final index offsets from there by `repeater_index * stride`, a
1812/// per-cell `child_offset`, and an optional inner-repeater offset.
1813fn grid_repeater_cache_access(
1814    cache: Value,
1815    index: usize,
1816    repeater_index: usize,
1817    stride: usize,
1818    child_offset: usize,
1819    inner_offset: usize,
1820) -> Value {
1821    let get = |data_idx: usize, slice_len: usize, read: &dyn Fn(usize) -> f64| {
1822        if data_idx < slice_len { Value::Number(read(data_idx)) } else { Value::Number(0.) }
1823    };
1824    match cache {
1825        Value::LayoutCache(cache) => {
1826            let base = cache.get(index).copied().unwrap_or(0.) as usize;
1827            let data_idx = base + repeater_index * stride + child_offset + inner_offset;
1828            get(data_idx, cache.len(), &|i| cache[i] as f64)
1829        }
1830        Value::ArrayOfU16(cache) => {
1831            let base = cache.get(index).copied().unwrap_or(0) as usize;
1832            let data_idx = base + repeater_index * stride + child_offset + inner_offset;
1833            get(data_idx, cache.len(), &|i| cache[i] as f64)
1834        }
1835        _ => Value::Number(0.),
1836    }
1837}
1838
1839/// Dispatch a `BuiltinFunction` call to the corresponding runtime helper.
1840fn call_builtin_function(
1841    ctx: &mut EvalContext,
1842    f: BuiltinFunction,
1843    arguments: &[Expression],
1844) -> Value {
1845    let to_num = |ctx: &mut EvalContext, e: &Expression| -> f64 {
1846        eval_expression(ctx, e).try_into().unwrap_or_default()
1847    };
1848    let to_string = |ctx: &mut EvalContext, e: &Expression| -> SharedString {
1849        eval_expression(ctx, e).try_into().unwrap_or_default()
1850    };
1851
1852    match f {
1853        BuiltinFunction::Mod => {
1854            Value::Number(to_num(ctx, &arguments[0]).rem_euclid(to_num(ctx, &arguments[1])))
1855        }
1856        BuiltinFunction::Round => Value::Number(to_num(ctx, &arguments[0]).round()),
1857        BuiltinFunction::Ceil => Value::Number(to_num(ctx, &arguments[0]).ceil()),
1858        BuiltinFunction::Floor => Value::Number(to_num(ctx, &arguments[0]).floor()),
1859        BuiltinFunction::Sqrt => Value::Number(to_num(ctx, &arguments[0]).sqrt()),
1860        BuiltinFunction::Abs => Value::Number(to_num(ctx, &arguments[0]).abs()),
1861        BuiltinFunction::Sin => Value::Number(to_num(ctx, &arguments[0]).to_radians().sin()),
1862        BuiltinFunction::Cos => Value::Number(to_num(ctx, &arguments[0]).to_radians().cos()),
1863        BuiltinFunction::Tan => Value::Number(to_num(ctx, &arguments[0]).to_radians().tan()),
1864        BuiltinFunction::ASin => Value::Number(to_num(ctx, &arguments[0]).asin().to_degrees()),
1865        BuiltinFunction::ACos => Value::Number(to_num(ctx, &arguments[0]).acos().to_degrees()),
1866        BuiltinFunction::ATan => Value::Number(to_num(ctx, &arguments[0]).atan().to_degrees()),
1867        BuiltinFunction::ATan2 => {
1868            Value::Number(to_num(ctx, &arguments[0]).atan2(to_num(ctx, &arguments[1])).to_degrees())
1869        }
1870        BuiltinFunction::Log => {
1871            Value::Number(to_num(ctx, &arguments[0]).log(to_num(ctx, &arguments[1])))
1872        }
1873        BuiltinFunction::Ln => Value::Number(to_num(ctx, &arguments[0]).ln()),
1874        BuiltinFunction::Pow => {
1875            Value::Number(to_num(ctx, &arguments[0]).powf(to_num(ctx, &arguments[1])))
1876        }
1877        BuiltinFunction::Exp => Value::Number(to_num(ctx, &arguments[0]).exp()),
1878        BuiltinFunction::ToFixed => {
1879            let n = to_num(ctx, &arguments[0]);
1880            let digits: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
1881            Value::String(i_slint_core::string::shared_string_from_number_fixed(
1882                n,
1883                digits.max(0) as usize,
1884            ))
1885        }
1886        BuiltinFunction::ToPrecision => {
1887            let n = to_num(ctx, &arguments[0]);
1888            let p: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
1889            Value::String(i_slint_core::string::shared_string_from_number_precision(
1890                n,
1891                p.max(0) as usize,
1892            ))
1893        }
1894        BuiltinFunction::StringStartsWith => Value::Bool(
1895            to_string(ctx, &arguments[0])
1896                .as_str()
1897                .starts_with(to_string(ctx, &arguments[1]).as_str()),
1898        ),
1899        BuiltinFunction::StringEndsWith => Value::Bool(
1900            to_string(ctx, &arguments[0])
1901                .as_str()
1902                .ends_with(to_string(ctx, &arguments[1]).as_str()),
1903        ),
1904        BuiltinFunction::ToStringUnlocalized => {
1905            let n = to_num(ctx, &arguments[0]);
1906            Value::String(i_slint_core::string::shared_string_from_number_unlocalized(n))
1907        }
1908        BuiltinFunction::DecimalSeparator => Value::String(
1909            find_window_adapter(ctx)
1910                .map(|adapter| {
1911                    i_slint_core::window::WindowInner::from_pub(adapter.window())
1912                        .context()
1913                        .locale_decimal_separator()
1914                })
1915                .unwrap_or_default()
1916                .into(),
1917        ),
1918        BuiltinFunction::MacosBringAllWindowsToFront => {
1919            i_slint_core::macos_bring_all_windows_to_front();
1920            Value::Void
1921        }
1922        BuiltinFunction::ColorToStyledText => {
1923            let color: i_slint_core::Color =
1924                eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
1925            Value::StyledText(i_slint_core::styled_text::color_to_styled_text(color))
1926        }
1927        BuiltinFunction::SetupSystemTrayIcon => {
1928            crate::popup::setup_system_tray_icon(ctx, arguments)
1929        }
1930        BuiltinFunction::StringIsFloat => Value::Bool(
1931            <f64 as core::str::FromStr>::from_str(to_string(ctx, &arguments[0]).as_str()).is_ok(),
1932        ),
1933        BuiltinFunction::StringToFloat => Value::Number(
1934            core::str::FromStr::from_str(to_string(ctx, &arguments[0]).as_str()).unwrap_or(0.),
1935        ),
1936        BuiltinFunction::StringIsEmpty => Value::Bool(to_string(ctx, &arguments[0]).is_empty()),
1937        BuiltinFunction::StringCharacterCount => Value::Number(
1938            unicode_segmentation::UnicodeSegmentation::graphemes(
1939                to_string(ctx, &arguments[0]).as_str(),
1940                true,
1941            )
1942            .count() as f64,
1943        ),
1944        BuiltinFunction::StringToLowercase => {
1945            Value::String(to_string(ctx, &arguments[0]).to_lowercase().into())
1946        }
1947        BuiltinFunction::StringToUppercase => {
1948            Value::String(to_string(ctx, &arguments[0]).to_uppercase().into())
1949        }
1950        BuiltinFunction::StringReplaceAll => {
1951            if arguments.len() != 3 {
1952                panic!("internal error: incorrect argument count to StringReplaceAll")
1953            }
1954
1955            if let (Value::String(s), Value::String(from), Value::String(to)) = (
1956                eval_expression(ctx, &arguments[0]),
1957                eval_expression(ctx, &arguments[1]),
1958                eval_expression(ctx, &arguments[2]),
1959            ) {
1960                Value::String(i_slint_core::string::shared_string_replace_all(
1961                    &s,
1962                    from.as_str(),
1963                    to.as_str(),
1964                ))
1965            } else {
1966                panic!("Not all arguments are strings");
1967            }
1968        }
1969        BuiltinFunction::ColorRgbaStruct => {
1970            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
1971                let color = brush.color();
1972                let values = [
1973                    ("red".to_string(), Value::Number(color.red().into())),
1974                    ("green".to_string(), Value::Number(color.green().into())),
1975                    ("blue".to_string(), Value::Number(color.blue().into())),
1976                    ("alpha".to_string(), Value::Number(color.alpha().into())),
1977                ]
1978                .into_iter()
1979                .collect();
1980                Value::Struct(values)
1981            } else {
1982                Value::Void
1983            }
1984        }
1985        BuiltinFunction::ColorHsvaStruct => {
1986            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
1987                let color = brush.color().to_hsva();
1988                let values = [
1989                    ("hue".to_string(), Value::Number(color.hue.into())),
1990                    ("saturation".to_string(), Value::Number(color.saturation.into())),
1991                    ("value".to_string(), Value::Number(color.value.into())),
1992                    ("alpha".to_string(), Value::Number(color.alpha.into())),
1993                ]
1994                .into_iter()
1995                .collect();
1996                Value::Struct(values)
1997            } else {
1998                Value::Void
1999            }
2000        }
2001        BuiltinFunction::ColorOklchStruct => {
2002            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2003                let color = brush.color().to_oklch();
2004                let values = [
2005                    ("lightness".to_string(), Value::Number(color.lightness.into())),
2006                    ("chroma".to_string(), Value::Number(color.chroma.into())),
2007                    ("hue".to_string(), Value::Number(color.hue.into())),
2008                    ("alpha".to_string(), Value::Number(color.alpha.into())),
2009                ]
2010                .into_iter()
2011                .collect();
2012                Value::Struct(values)
2013            } else {
2014                Value::Void
2015            }
2016        }
2017        BuiltinFunction::ColorBrighter => {
2018            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2019                brush.brighter(to_num(ctx, &arguments[1]) as f32).into()
2020            } else {
2021                Value::Void
2022            }
2023        }
2024        BuiltinFunction::ColorDarker => {
2025            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2026                brush.darker(to_num(ctx, &arguments[1]) as f32).into()
2027            } else {
2028                Value::Void
2029            }
2030        }
2031        BuiltinFunction::ColorTransparentize => {
2032            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2033                brush.transparentize(to_num(ctx, &arguments[1]) as f32).into()
2034            } else {
2035                Value::Void
2036            }
2037        }
2038        BuiltinFunction::ColorWithAlpha => {
2039            if let Value::Brush(brush) = eval_expression(ctx, &arguments[0]) {
2040                brush.with_alpha(to_num(ctx, &arguments[1]) as f32).into()
2041            } else {
2042                Value::Void
2043            }
2044        }
2045        BuiltinFunction::ColorMix => {
2046            let a = eval_expression(ctx, &arguments[0]);
2047            let b = eval_expression(ctx, &arguments[1]);
2048            let factor = to_num(ctx, &arguments[2]) as f32;
2049            if let (
2050                Value::Brush(i_slint_core::Brush::SolidColor(ca)),
2051                Value::Brush(i_slint_core::Brush::SolidColor(cb)),
2052            ) = (a, b)
2053            {
2054                ca.mix(&cb, factor).into()
2055            } else {
2056                Value::Void
2057            }
2058        }
2059        BuiltinFunction::ArrayPush => {
2060            if arguments.len() != 2 {
2061                panic!("internal error: incorrect argument count to ArrayPush")
2062            }
2063
2064            let model = match eval_expression(ctx, &arguments[0]) {
2065                Value::Model(m) => m,
2066                _ => panic!("First argument not an array: {:?}", arguments[0]),
2067            };
2068            let value = eval_expression(ctx, &arguments[1]);
2069
2070            model.push_row(value);
2071
2072            Value::Void
2073        }
2074        BuiltinFunction::ArrayRemove => {
2075            if arguments.len() != 2 {
2076                panic!("internal error: incorrect argument count to ArrayRemove")
2077            }
2078
2079            let model = match eval_expression(ctx, &arguments[0]) {
2080                Value::Model(m) => m,
2081                _ => panic!("First argument not an array: {:?}", arguments[0]),
2082            };
2083            let index = match eval_expression(ctx, &arguments[1]) {
2084                Value::Number(i) => i,
2085                _ => panic!("Second argument not an integer: {:?}", arguments[1]),
2086            };
2087
2088            model.remove_row(index as isize);
2089
2090            Value::Void
2091        }
2092
2093        BuiltinFunction::ArrayInsert => {
2094            if arguments.len() != 3 {
2095                panic!("internal error: incorrect argument count to ArrayInsert")
2096            }
2097
2098            let model = match eval_expression(ctx, &arguments[0]) {
2099                Value::Model(m) => m,
2100                _ => panic!("First argument not an array: {:?}", arguments[0]),
2101            };
2102            let index = match eval_expression(ctx, &arguments[1]) {
2103                Value::Number(i) => i,
2104                _ => panic!("Second argument not an integer: {:?}", arguments[1]),
2105            };
2106
2107            let value = eval_expression(ctx, &arguments[2]);
2108            model.insert_row(index as isize, value);
2109
2110            Value::Void
2111        }
2112        BuiltinFunction::Rgb => {
2113            let r: i32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2114            let g: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2115            let b: i32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0);
2116            let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2117            let r: u8 = r.clamp(0, 255) as u8;
2118            let g: u8 = g.clamp(0, 255) as u8;
2119            let b: u8 = b.clamp(0, 255) as u8;
2120            let a: u8 = (255. * a).clamp(0., 255.) as u8;
2121            Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_argb_u8(
2122                a, r, g, b,
2123            )))
2124        }
2125        BuiltinFunction::Hsv => {
2126            let h: f32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0.0);
2127            let s: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0.0);
2128            let v: f32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0.0);
2129            let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2130            let a = a.clamp(0., 1.);
2131            Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_hsva(
2132                h, s, v, a,
2133            )))
2134        }
2135        BuiltinFunction::Oklch => {
2136            let l: f32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0.0);
2137            let c: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0.0);
2138            let h: f32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0.0);
2139            let a: f32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(1.0);
2140            Value::Brush(i_slint_core::Brush::SolidColor(i_slint_core::Color::from_oklch(
2141                l.clamp(0.0, 1.0),
2142                c,
2143                h,
2144                a.clamp(0.0, 1.0),
2145            )))
2146        }
2147        BuiltinFunction::AnimationTick => {
2148            Value::Number(i_slint_core::animations::animation_tick() as f64)
2149        }
2150        BuiltinFunction::GetWindowScaleFactor => {
2151            let factor = root_instance(ctx)
2152                .and_then(|inst| inst.window_adapter_or_default())
2153                .map(|adapter| {
2154                    i_slint_core::window::WindowInner::from_pub(adapter.window()).scale_factor()
2155                        as f64
2156                })
2157                .unwrap_or(1.0);
2158            Value::Number(factor)
2159        }
2160        BuiltinFunction::GetWindowDefaultFontSize => {
2161            // Read `default-font-size` from the nearest enclosing
2162            // `WindowItem`. The walk crosses popup and embedded-tree
2163            // boundaries, so `1rem` inside a popup of an embedded component
2164            // resolves against that component's own window, not the host
2165            // window that the window adapter points at.
2166            let size = root_instance(ctx)
2167                .map(|inst| {
2168                    i_slint_core::items::WindowItem::resolved_default_font_size(
2169                        vtable::VRc::into_dyn(inst),
2170                    )
2171                    .get() as f64
2172                })
2173                .unwrap_or(12.0);
2174            Value::Number(size)
2175        }
2176        BuiltinFunction::DetectOperatingSystem => i_slint_core::detect_operating_system().into(),
2177        BuiltinFunction::Use24HourFormat => {
2178            Value::Bool(i_slint_core::date_time::use_24_hour_format())
2179        }
2180        BuiltinFunction::ColorScheme => {
2181            let scheme = root_instance(ctx)
2182                .map(vtable::VRc::into_dyn)
2183                .and_then(|root| {
2184                    i_slint_core::window::context_for_root(&root)
2185                        .map(|ctx| ctx.color_scheme(Some(&root)))
2186                })
2187                .unwrap_or(i_slint_core::items::ColorScheme::Unknown);
2188            scheme.into()
2189        }
2190        BuiltinFunction::AccentColor => {
2191            let color = root_instance(ctx)
2192                .map(vtable::VRc::into_dyn)
2193                .map(|root| i_slint_core::window::accent_color(&root))
2194                .unwrap_or_default();
2195            Value::Brush(i_slint_core::Brush::SolidColor(color))
2196        }
2197        BuiltinFunction::SupportsNativeMenuBar => {
2198            let supports = find_window_adapter(ctx).is_some_and(|a| {
2199                a.internal(i_slint_core::InternalToken)
2200                    .is_some_and(|x| x.supports_native_menu_bar())
2201            });
2202            Value::Bool(supports)
2203        }
2204        BuiltinFunction::TextInputFocused => {
2205            let focused = ctx
2206                .current
2207                .as_ref()
2208                .and_then(|c| c.root.get())
2209                .and_then(|w| w.upgrade())
2210                .and_then(|inst| inst.window_adapter_or_default())
2211                .map(|adapter| {
2212                    i_slint_core::window::WindowInner::from_pub(adapter.window())
2213                        .text_input_focused()
2214                })
2215                .unwrap_or(false);
2216            Value::Bool(focused)
2217        }
2218        BuiltinFunction::SetTextInputFocused => {
2219            let value = arguments
2220                .first()
2221                .map(|e| eval_expression(ctx, e))
2222                .and_then(|v| bool::try_from(v).ok())
2223                .unwrap_or(false);
2224            if let Some(adapter) = ctx
2225                .current
2226                .as_ref()
2227                .and_then(|c| c.root.get())
2228                .and_then(|w| w.upgrade())
2229                .and_then(|inst| inst.window_adapter_or_default())
2230            {
2231                i_slint_core::window::WindowInner::from_pub(adapter.window())
2232                    .set_text_input_focused(value);
2233            }
2234            Value::Void
2235        }
2236        BuiltinFunction::UpdateTimers => {
2237            // Timers react to property changes through the change trackers
2238            // installed in `bindings::install_timers`; nothing to do here.
2239            Value::Void
2240        }
2241        BuiltinFunction::RestartTimer => {
2242            // The timer is referenced through a member reference carrying a
2243            // `LocalMemberIndex::Timer`, so it resolves in the component that
2244            // declares it even when the call is made from (or inlined into) a
2245            // repeated/conditional child or another component.
2246            if let [
2247                Expression::PropertyReference(MemberReference::Relative {
2248                    parent_level,
2249                    local_reference,
2250                }),
2251            ] = arguments
2252                && let LocalMemberIndex::Timer(timer_idx) = &local_reference.reference
2253                && ctx.current.is_some()
2254            {
2255                let instance = walk_to(ctx, *parent_level, &local_reference.sub_component_path);
2256                if let Some(timer) = instance.timers.get(usize::from(*timer_idx)) {
2257                    timer.restart();
2258                }
2259            }
2260            Value::Void
2261        }
2262        BuiltinFunction::KeysToString => {
2263            let v = arguments.first().map(|e| eval_expression(ctx, e));
2264            if let Some(Value::Keys(keys)) = v {
2265                Value::String(keys.to_string().into())
2266            } else {
2267                Value::String(Default::default())
2268            }
2269        }
2270        BuiltinFunction::SetSelectionOffsets => {
2271            // (item_ref, start, end) — applied to a TextInput.
2272            use i_slint_core::items::TextInput;
2273            let [Expression::PropertyReference(mr), start_expr, end_expr] = arguments else {
2274                return Value::Void;
2275            };
2276            let start: i32 = eval_expression(ctx, start_expr).try_into().unwrap_or(0);
2277            let end: i32 = eval_expression(ctx, end_expr).try_into().unwrap_or(0);
2278            let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr) else {
2279                return Value::Void;
2280            };
2281            let Some(adapter) = parent_inst.window_adapter_or_default() else {
2282                return Value::Void;
2283            };
2284            let parent_dyn = vtable::VRc::into_dyn(parent_inst);
2285            let item_rc = i_slint_core::items::ItemRc::new(parent_dyn, flat_idx as u32);
2286            if let Some(text_input) = vtable::VRef::downcast_pin::<TextInput>(item_rc.borrow()) {
2287                text_input.set_selection_offsets(&adapter, &item_rc, start, end);
2288            }
2289            Value::Void
2290        }
2291        BuiltinFunction::RegisterCustomFontByPath => {
2292            if let Value::String(s) = eval_expression(ctx, &arguments[0])
2293                && let Some(root) = find_root_instance(ctx)
2294            {
2295                // Log and skip if the window adapter can't be created; the
2296                // same error resurfaces when the window is actually used.
2297                let result =
2298                    root.try_window_adapter().map_err(|e| e.to_string()).and_then(|adapter| {
2299                        adapter
2300                            .renderer()
2301                            .register_font_from_path(&std::path::PathBuf::from(s.as_str()))
2302                            .map_err(|e| format!("Cannot load custom font {}: {e}", s.as_str()))
2303                    });
2304                if let Err(err) = result {
2305                    i_slint_core::debug_log!("{err}");
2306                }
2307            }
2308            Value::Void
2309        }
2310        BuiltinFunction::SetupMenuBar => crate::popup::setup_menubar(ctx, arguments),
2311        BuiltinFunction::ItemFontMetrics => {
2312            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2313                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2314                && let Some(adapter) = inst.window_adapter_or_default()
2315            {
2316                let item_rc =
2317                    i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2318                let metrics = i_slint_core::items::slint_text_item_fontmetrics(
2319                    &adapter,
2320                    item_rc.borrow(),
2321                    &item_rc,
2322                );
2323                return metrics.into();
2324            }
2325            i_slint_core::items::FontMetrics::default().into()
2326        }
2327        BuiltinFunction::ItemAbsolutePosition => {
2328            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2329                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2330            {
2331                let item_rc =
2332                    i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2333                // Map the item's own geometry origin through the ancestor transforms so the
2334                // result is the item's absolute position (not its parent's). The lowering no
2335                // longer adds the element's x/y on top (see the ItemAbsolutePosition change).
2336                return item_rc.map_to_window(item_rc.geometry().origin).to_untyped().into();
2337            }
2338            i_slint_core::api::LogicalPosition::default().into()
2339        }
2340        BuiltinFunction::PathPointAt => {
2341            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2342                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2343            {
2344                let item_rc =
2345                    i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2346                let t: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
2347                return item_rc
2348                    .downcast::<i_slint_core::items::Path>()
2349                    .unwrap()
2350                    .as_pin_ref()
2351                    .point_at(&item_rc, t)
2352                    .to_untyped()
2353                    .into();
2354            }
2355            panic!("internal error: argument to PathPointAt must be an element")
2356        }
2357        BuiltinFunction::PathAngleAt => {
2358            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2359                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2360            {
2361                let item_rc =
2362                    i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2363                let t: f32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or_default();
2364                return item_rc
2365                    .downcast::<i_slint_core::items::Path>()
2366                    .unwrap()
2367                    .as_pin_ref()
2368                    .angle_at(&item_rc, t)
2369                    .into();
2370            }
2371            panic!("internal error: argument to PathAngleAt must be an element")
2372        }
2373        BuiltinFunction::ArrayAny | BuiltinFunction::ArrayAll => {
2374            let is_all = matches!(f, BuiltinFunction::ArrayAll);
2375            let model: i_slint_core::model::ModelRc<Value> =
2376                eval_expression(ctx, &arguments[0]).try_into().unwrap();
2377            let Expression::Closure { arg_name, expression } = &arguments[1] else {
2378                panic!("internal error: Array.any/all expects a closure as second argument")
2379            };
2380            let mut predicate =
2381                |row_value| eval_array_row_predicate(arg_name, expression, ctx, row_value);
2382            Value::Bool(if is_all {
2383                i_slint_core::model::model_all(&model, &mut predicate)
2384            } else {
2385                i_slint_core::model::model_any(&model, &mut predicate)
2386            })
2387        }
2388        BuiltinFunction::ArrayFindIndex => {
2389            let model: i_slint_core::model::ModelRc<Value> =
2390                eval_expression(ctx, &arguments[0]).try_into().unwrap();
2391            let Expression::Closure { arg_name, expression } = &arguments[1] else {
2392                panic!("internal error: Array.find-index expects a closure as second argument")
2393            };
2394            Value::Number(i_slint_core::model::model_find_index(&model, |row_value| {
2395                eval_array_row_predicate(arg_name, expression, ctx, row_value)
2396            }) as f64)
2397        }
2398        BuiltinFunction::ImplicitLayoutInfo(orient) => {
2399            // The argument is a `PropertyReference` to a `Native { prop_name: "" }`,
2400            // i.e. the item itself; the optional second argument carries the
2401            // cross-axis constraint (-1 when unconstrained).
2402            let constraint: f32 = arguments
2403                .get(1)
2404                .map(|e| eval_expression(ctx, e).try_into().unwrap_or(-1.))
2405                .unwrap_or(-1.);
2406            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2407                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2408                && let Some(adapter) = inst.window_adapter_or_default()
2409            {
2410                let item_rc =
2411                    i_slint_core::items::ItemRc::new(vtable::VRc::into_dyn(inst), flat_idx as u32);
2412                return item_rc
2413                    .borrow()
2414                    .as_ref()
2415                    .layout_info(
2416                        llr_to_core_orientation(orient),
2417                        constraint as _,
2418                        &adapter,
2419                        &item_rc,
2420                    )
2421                    .into();
2422            }
2423            i_slint_core::layout::LayoutInfo::default().into()
2424        }
2425        BuiltinFunction::Debug => {
2426            use i_slint_core::debug_log::*;
2427            let msg = to_string(ctx, &arguments[0]);
2428            let root = ctx
2429                .current
2430                .as_ref()
2431                .and_then(|c| c.root.get())
2432                .and_then(|w| w.upgrade())
2433                .map(vtable::VRc::into_dyn);
2434            if let Some(context) = root.as_ref().and_then(i_slint_core::window::context_for_root) {
2435                context.dispatch_log_message(LogMessage::new(
2436                    LogMessageSource::SlintCode,
2437                    None,
2438                    format_args!("{msg}"),
2439                ));
2440            } else {
2441                log_message(LogMessage::new(
2442                    LogMessageSource::SlintCode,
2443                    None,
2444                    format_args!("{msg}"),
2445                ));
2446            }
2447            Value::Void
2448        }
2449        BuiltinFunction::ArrayLength => match eval_expression(ctx, &arguments[0]) {
2450            // Track the row count so bindings reading `.length` re-evaluate
2451            // when rows are added or removed.
2452            Value::Model(m) => {
2453                m.model_tracker().track_row_count_changes();
2454                Value::Number(m.row_count() as f64)
2455            }
2456            _ => Value::Number(0.),
2457        },
2458        BuiltinFunction::ImageSize => {
2459            if let Value::Image(img) = eval_expression(ctx, &arguments[0]) {
2460                let size = img.size();
2461                let mut s = crate::api::Struct::default();
2462                s.set_field("width".to_string(), Value::Number(size.width as f64));
2463                s.set_field("height".to_string(), Value::Number(size.height as f64));
2464                Value::Struct(s)
2465            } else {
2466                Value::Void
2467            }
2468        }
2469        BuiltinFunction::ParseMarkdown => {
2470            let format_string: SharedString =
2471                eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
2472            let args = eval_expression(ctx, &arguments[1]);
2473            let args: Vec<i_slint_core::styled_text::StyledText> = if let Value::Model(m) = args {
2474                (0..m.row_count())
2475                    .filter_map(|i| match m.row_data(i)? {
2476                        Value::StyledText(t) => Some(t),
2477                        _ => None,
2478                    })
2479                    .collect()
2480            } else {
2481                Vec::new()
2482            };
2483            Value::StyledText(i_slint_core::styled_text::parse_markdown(&format_string, &args))
2484        }
2485        BuiltinFunction::StringToStyledText => {
2486            let string: SharedString =
2487                eval_expression(ctx, &arguments[0]).try_into().unwrap_or_default();
2488            Value::StyledText(i_slint_core::styled_text::string_to_styled_text(string.to_string()))
2489        }
2490        BuiltinFunction::Translate => {
2491            let original: SharedString = to_string(ctx, &arguments[0]);
2492            let context: SharedString = to_string(ctx, &arguments[1]);
2493            let domain: SharedString = to_string(ctx, &arguments[2]);
2494            let args = eval_expression(ctx, &arguments[3]);
2495            let Value::Model(args) = args else {
2496                return Value::String(original);
2497            };
2498            struct StringModelWrapper(ModelRc<Value>);
2499            impl i_slint_core::translations::FormatArgs for StringModelWrapper {
2500                type Output<'a> = SharedString;
2501                fn from_index(&self, index: usize) -> Option<SharedString> {
2502                    self.0.row_data(index).and_then(|v| v.try_into().ok())
2503                }
2504            }
2505            let n: i32 = eval_expression(ctx, &arguments[4]).try_into().unwrap_or(0);
2506            let plural: SharedString = to_string(ctx, &arguments[5]);
2507            Value::String(i_slint_core::translations::translate(
2508                &original,
2509                &context,
2510                &domain,
2511                &StringModelWrapper(args),
2512                n,
2513                &plural,
2514            ))
2515        }
2516        BuiltinFunction::ShowPopupWindow => crate::popup::show_popup_window(ctx, arguments),
2517        BuiltinFunction::ClosePopupWindow => crate::popup::close_popup_window(ctx, arguments),
2518        BuiltinFunction::SetFocusItem => {
2519            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2520                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2521                && let Some(adapter) = find_window_adapter(ctx)
2522            {
2523                let dyn_rc = vtable::VRc::into_dyn(inst);
2524                let item_rc = i_slint_core::items::ItemRc::new(dyn_rc, flat_idx as u32);
2525                i_slint_core::window::WindowInner::from_pub(adapter.window()).set_focus_item(
2526                    &item_rc,
2527                    true,
2528                    i_slint_core::input::FocusReason::Programmatic,
2529                );
2530            }
2531            Value::Void
2532        }
2533        BuiltinFunction::ClearFocusItem => {
2534            if let Some(Expression::PropertyReference(mr)) = arguments.first()
2535                && let Some((inst, flat_idx)) = resolve_item_rc_from_ref(ctx, mr)
2536                && let Some(adapter) = find_window_adapter(ctx)
2537            {
2538                let dyn_rc = vtable::VRc::into_dyn(inst);
2539                let item_rc = i_slint_core::items::ItemRc::new(dyn_rc, flat_idx as u32);
2540                i_slint_core::window::WindowInner::from_pub(adapter.window()).set_focus_item(
2541                    &item_rc,
2542                    false,
2543                    i_slint_core::input::FocusReason::Programmatic,
2544                );
2545            }
2546            Value::Void
2547        }
2548        BuiltinFunction::MonthDayCount => {
2549            let m: u32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2550            let y: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2551            Value::Number(i_slint_core::date_time::month_day_count(m, y).unwrap_or(0) as f64)
2552        }
2553        BuiltinFunction::MonthOffset => {
2554            let m: u32 = eval_expression(ctx, &arguments[0]).try_into().unwrap_or(0);
2555            let y: i32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2556            Value::Number(i_slint_core::date_time::month_offset(m, y) as f64)
2557        }
2558        BuiltinFunction::FormatDate => {
2559            let f: SharedString = to_string(ctx, &arguments[0]);
2560            let d: u32 = eval_expression(ctx, &arguments[1]).try_into().unwrap_or(0);
2561            let m: u32 = eval_expression(ctx, &arguments[2]).try_into().unwrap_or(0);
2562            let y: i32 = eval_expression(ctx, &arguments[3]).try_into().unwrap_or(0);
2563            Value::String(i_slint_core::date_time::format_date(&f, d, m, y))
2564        }
2565        BuiltinFunction::DateNow => {
2566            Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
2567                i_slint_core::date_time::date_now()
2568                    .into_iter()
2569                    .map(|x| Value::Number(x as f64))
2570                    .collect::<Vec<_>>(),
2571            )))
2572        }
2573        BuiltinFunction::ValidDate => {
2574            let d: SharedString = to_string(ctx, &arguments[0]);
2575            let f: SharedString = to_string(ctx, &arguments[1]);
2576            Value::Bool(i_slint_core::date_time::parse_date(d.as_str(), f.as_str()).is_some())
2577        }
2578        BuiltinFunction::ParseDate => {
2579            let d: SharedString = to_string(ctx, &arguments[0]);
2580            let f: SharedString = to_string(ctx, &arguments[1]);
2581            Value::Model(i_slint_core::model::ModelRc::new(i_slint_core::model::VecModel::from(
2582                i_slint_core::date_time::parse_date(d.as_str(), f.as_str())
2583                    .map(|v| v.into_iter().map(|x| Value::Number(x as f64)).collect::<Vec<_>>())
2584                    .unwrap_or_default(),
2585            )))
2586        }
2587        BuiltinFunction::ShowPopupMenu | BuiltinFunction::ShowPopupMenuInternal => {
2588            crate::popup::show_popup_menu(ctx, arguments)
2589        }
2590        BuiltinFunction::OpenUrl => {
2591            let url = to_string(ctx, &arguments[0]);
2592            let result = find_window_adapter(ctx)
2593                .map(|adapter| i_slint_core::open_url(&url, adapter.window()).is_ok())
2594                .unwrap_or(false);
2595            Value::Bool(result)
2596        }
2597        BuiltinFunction::RegisterCustomFontByMemory | BuiltinFunction::RegisterBitmapFont => {
2598            // Bitmap font registration is generated by build.rs, not callable from .slint.
2599            Value::Void
2600        }
2601        BuiltinFunction::StartTimer | BuiltinFunction::StopTimer => {
2602            // Lowered into property assignments by `materialize_state`; never reached.
2603            Value::Void
2604        }
2605    }
2606}
2607
2608/// Resolve a `PropertyReference` that targets a native item into the owning
2609/// `Instance` and the item's flat tree index, for builtins that need a
2610/// runtime `ItemRc` to hand to core APIs.
2611pub(crate) fn resolve_item_rc_from_ref(
2612    ctx: &EvalContext,
2613    mr: &MemberReference,
2614) -> Option<(vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>, usize)>
2615{
2616    let MemberReference::Relative { parent_level, local_reference } = mr else { return None };
2617    let LocalMemberIndex::Native { item_index, .. } = &local_reference.reference else {
2618        return None;
2619    };
2620    let owner = try_walk_to(ctx, *parent_level, &local_reference.sub_component_path)?;
2621    let parent_inst = owner.root.get().and_then(|w| w.upgrade())?;
2622    let full_path = crate::item_tree_vtable::sub_component_path_of(&owner, &parent_inst);
2623    let flat_idx = find_flat_item_index(&parent_inst.item_table, &full_path, *item_index)?;
2624    Some((parent_inst, flat_idx))
2625}
2626
2627/// Walk up the parent chain from the current context to find the root
2628/// `Instance` of the public component. A repeated or conditional sub-tree
2629/// doesn't have its own window adapter or public component index.
2630pub(crate) fn find_root_instance(
2631    ctx: &EvalContext,
2632) -> Option<vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>> {
2633    let current = ctx.current.as_ref()?;
2634    let mut sub = current.clone();
2635    loop {
2636        if let Some(root) = sub.root.get()
2637            && let Some(inst) = root.upgrade()
2638            && inst.public_component_index.is_some()
2639        {
2640            return Some(inst);
2641        }
2642        let parent = sub.parent.upgrade()?;
2643        sub = Pin::new(parent);
2644    }
2645}
2646
2647/// The root Instance's window adapter, if one can be found or created.
2648pub(crate) fn find_window_adapter(
2649    ctx: &EvalContext,
2650) -> Option<i_slint_core::window::WindowAdapterRc> {
2651    find_root_instance(ctx)?.window_adapter_or_default()
2652}
2653
2654/// Dispatch an `Expression::ItemMemberFunctionCall` (like
2655/// `TextInput.select-all()`) to the matching native item method by
2656/// downcasting the runtime `ItemRc` to its concrete item type.
2657fn call_item_member_function(ctx: &EvalContext, function: &MemberReference) -> Value {
2658    use i_slint_core::items::{ContextMenu, SwipeGestureHandler, TextInput, WindowItem};
2659    let MemberReference::Relative { local_reference, .. } = function else {
2660        return Value::Void;
2661    };
2662    let LocalMemberIndex::Native { prop_name, .. } = &local_reference.reference else {
2663        return Value::Void;
2664    };
2665    let Some((parent_inst, flat_idx)) = resolve_item_rc_from_ref(ctx, function) else {
2666        return Value::Void;
2667    };
2668    let Some(adapter) = parent_inst.window_adapter_or_default() else { return Value::Void };
2669    let parent_dyn = vtable::VRc::into_dyn(parent_inst);
2670    let item_rc = i_slint_core::items::ItemRc::new(parent_dyn, flat_idx as u32);
2671    let item_ref = item_rc.borrow();
2672
2673    // Map a Slint-side member-function name to the matching Rust method on
2674    // a downcast item type.
2675    macro_rules! dispatch {
2676        ($item:expr, $name:expr; $($slint_name:literal => $rust_method:ident $(=> $into:ty)?),* $(,)?) => {
2677            match $name {
2678                $(
2679                    $slint_name => {
2680                        let res = $item.$rust_method(&adapter, &item_rc);
2681                        $(let res: $into = res.into();)?
2682                        return res.into();
2683                    }
2684                )*
2685                _ => {}
2686            }
2687        };
2688    }
2689
2690    if let Some(text_input) = vtable::VRef::downcast_pin::<TextInput>(item_ref) {
2691        dispatch!(text_input, prop_name.as_str();
2692            "select-all" => select_all => (),
2693            "clear-selection" => clear_selection => (),
2694            "select-word" => select_word => (),
2695            "cut" => cut => (),
2696            "copy" => copy => (),
2697            "paste" => paste => (),
2698            "undo" => undo => (),
2699            "redo" => redo => (),
2700        );
2701    }
2702    if let Some(swipe) = vtable::VRef::downcast_pin::<SwipeGestureHandler>(item_rc.borrow()) {
2703        dispatch!(swipe, prop_name.as_str();
2704            "cancel" => cancel => (),
2705        );
2706    }
2707    if let Some(menu) = vtable::VRef::downcast_pin::<ContextMenu>(item_rc.borrow()) {
2708        dispatch!(menu, prop_name.as_str();
2709            "close" => close => (),
2710            "is-open" => is_open,
2711        );
2712    }
2713    if let Some(window) = vtable::VRef::downcast_pin::<WindowItem>(item_rc.borrow()) {
2714        match prop_name.as_str() {
2715            "hide" => {
2716                window.hide(&adapter, &item_rc);
2717                return Value::Void;
2718            }
2719            "close" => return Value::Bool(window.close(&adapter, &item_rc)),
2720            _ => {}
2721        }
2722    }
2723    unimplemented!("ItemMemberFunctionCall `{prop_name}`")
2724}