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