Skip to main content

slint_interpreter/
eval.rs

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