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