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