Skip to main content

slint_interpreter/
eval.rs

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