Skip to main content

slint_interpreter/
bindings.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//! Install property bindings, callback handlers, two-way bindings, change
5//! callbacks and `init_code` on an already-allocated [`Instance`].
6//!
7//! Called from [`Instance::new`] right after the `SubComponentInstance` tree
8//! has been wired up. Walks the tree recursively, visiting each sub-component
9//! (including nested ones) and copying its LLR entries onto the runtime
10//! allocations.
11
12use crate::Value;
13use crate::eval::{EvalContext, eval_expression};
14use crate::instance::{Instance, SubComponentInstance};
15use i_slint_compiler::llr::{
16    self, Animation, Expression, LocalMemberIndex, MemberReference, MutExpression, SubComponent,
17};
18use i_slint_core::Property;
19use i_slint_core::items::PropertyAnimation;
20use i_slint_core::rtti::AnimatedBindingKind;
21use std::pin::Pin;
22use std::rc::{Rc, Weak};
23use vtable::VRc;
24
25use i_slint_core::item_tree::ItemTreeVTable;
26
27/// Install every binding declared by the LLR on `instance`, skipping
28/// `init_code`; call [`run_init_code_for_instance`] separately.
29/// The listview row factory needs this split: virtualization measures row
30/// heights before `RepeatedItemTree::init` runs, so the row's bindings must
31/// be in place when the factory closure returns.
32pub fn install_bindings_only(instance: &VRc<ItemTreeVTable, Instance>) {
33    // Two passes: property bindings and two-way links across the whole
34    // sub-component tree first, then change trackers and timers.
35    // A change tracker reads its target's current value on `init()`, so it
36    // must run after every use-site override has landed; otherwise an inner
37    // component's tracker would see the default value and fire when the
38    // outer component's `property_init` supersedes it.
39    install_property_bindings(&instance.root_sub_component);
40    install_trackers_and_timers(&instance.root_sub_component);
41}
42
43pub fn run_init_code_for_instance(instance: &VRc<ItemTreeVTable, Instance>) {
44    run_init_code(&instance.root_sub_component);
45}
46
47fn install_property_bindings(sub: &Pin<Rc<SubComponentInstance>>) {
48    let cu = &sub.compilation_unit;
49    let sc: &SubComponent = &cu.sub_components[sub.sub_component_idx];
50    let weak_sub = Rc::downgrade(&Pin::into_inner(sub.clone()));
51
52    // Initialization order:
53    //   1. Initialize each nested sub-component (including its own two-way
54    //      links and property_init).
55    //   2. Install this component's two-way bindings. They reference nested
56    //      properties that now carry their defaults, so `link_two_way` can
57    //      carry the other side's value over correctly.
58    //   3. Install this component's property_init, which overrides the
59    //      defaults (including any nested default that a use-site supersedes).
60    for nested in &sub.sub_components {
61        install_property_bindings(nested);
62    }
63
64    // Pre-init code (custom font registration) runs before the two-way
65    // bindings and property_init.
66    for e in &sc.pre_init_code {
67        let expr = mut_expression_clone(e);
68        let mut ctx = EvalContext::new(sub.clone());
69        eval_expression(&mut ctx, &expr);
70    }
71
72    for twb in &sc.two_way_bindings {
73        install_two_way_binding(twb, sub);
74    }
75
76    for (target, binding) in &sc.property_init {
77        install_property_init(target, binding, sub, &weak_sub);
78    }
79
80    install_repeater_model_bindings(sub, &weak_sub);
81}
82
83fn install_trackers_and_timers(sub: &Pin<Rc<SubComponentInstance>>) {
84    for nested in &sub.sub_components {
85        install_trackers_and_timers(nested);
86    }
87    let weak_sub = Rc::downgrade(&Pin::into_inner(sub.clone()));
88    install_change_callbacks(sub, &weak_sub);
89    install_timers(sub, &weak_sub);
90}
91
92/// Configure each `Timer` declared on `sub`: evaluate its LLR `interval` /
93/// `running` / `triggered` expressions and start or stop the matching
94/// `i_slint_core::timers::Timer` in `SubComponentInstance::timers`.
95fn install_timers(sub: &Pin<Rc<SubComponentInstance>>, weak_sub: &Weak<SubComponentInstance>) {
96    let cu = &sub.compilation_unit;
97    let sc: &SubComponent = &cu.sub_components[sub.sub_component_idx];
98    if sc.timers.is_empty() {
99        return;
100    }
101    let update = {
102        let weak_sub = weak_sub.clone();
103        move || {
104            let Some(owner_rc) = weak_sub.upgrade() else { return };
105            let owner = Pin::new(owner_rc);
106            let cu = owner.compilation_unit.clone();
107            let sc = &cu.sub_components[owner.sub_component_idx];
108            for (idx, t) in sc.timers.iter().enumerate() {
109                let running_expr = t.running.borrow().clone();
110                let interval_expr = t.interval.borrow().clone();
111                let triggered_expr = t.triggered.borrow().clone();
112                let mut ctx = EvalContext::new(owner.clone());
113                let running = matches!(eval_expression(&mut ctx, &running_expr), Value::Bool(true));
114                if !running {
115                    if let Some(timer) = owner.timers.get(idx) {
116                        timer.stop();
117                    }
118                    continue;
119                }
120                let mut ctx = EvalContext::new(owner.clone());
121                let interval_ms: i64 =
122                    eval_expression(&mut ctx, &interval_expr).try_into().unwrap_or(0);
123                if interval_ms < 0 {
124                    if let Some(timer) = owner.timers.get(idx) {
125                        timer.stop();
126                    }
127                    continue;
128                }
129                let interval = std::time::Duration::from_millis(interval_ms as u64);
130                let Some(timer) = owner.timers.get(idx) else { continue };
131                if timer.running() && timer.interval() == interval {
132                    continue;
133                }
134                let weak = Rc::downgrade(&Pin::into_inner(owner.clone()));
135                let expr = triggered_expr.clone();
136                timer.start(i_slint_core::timers::TimerMode::Repeated, interval, move || {
137                    let Some(owner) = weak.upgrade() else { return };
138                    let mut ctx = EvalContext::new(Pin::new(owner));
139                    eval_expression(&mut ctx, &expr);
140                });
141            }
142        }
143    };
144
145    // Run once to start timers whose `running` is already true, then hook
146    // every `running` / `interval` expression into its own change tracker
147    // so mutations re-run the update step.
148    update();
149    for (timer_idx, t) in sc.timers.iter().enumerate() {
150        for (expr_idx, expr) in [&t.running, &t.interval].into_iter().enumerate() {
151            let get_expr = expr.borrow().clone();
152            let weak_get = weak_sub.clone();
153            let update = update.clone();
154            sub.change_trackers[2 * timer_idx + expr_idx].init(
155                (),
156                move |()| -> Value {
157                    let Some(owner) = weak_get.upgrade() else { return Value::Void };
158                    let mut ctx = EvalContext::new(Pin::new(owner));
159                    eval_expression(&mut ctx, &get_expr)
160                },
161                move |(), _| {
162                    update();
163                },
164            );
165        }
166    }
167}
168
169fn install_change_callbacks(
170    sub: &Pin<Rc<SubComponentInstance>>,
171    weak_sub: &Weak<SubComponentInstance>,
172) {
173    let cu = &sub.compilation_unit;
174    let sc: &SubComponent = &cu.sub_components[sub.sub_component_idx];
175    if sc.change_callbacks.is_empty() {
176        return;
177    }
178    let base = 2 * sc.timers.len();
179    for (idx, (target, expr)) in sc.change_callbacks.iter().enumerate() {
180        let target = target.clone();
181        let expr = mut_expression_clone(expr);
182        let weak_get = weak_sub.clone();
183        let weak_set = weak_sub.clone();
184        let expr_for_set = expr.clone();
185        sub.change_trackers[base + idx].init(
186            (),
187            move |()| -> Value {
188                let Some(owner) = weak_get.upgrade() else { return Value::Void };
189                let ctx = EvalContext::new(Pin::new(owner));
190                crate::eval::load_property(&ctx, &target)
191            },
192            move |(), _| {
193                let Some(owner) = weak_set.upgrade() else { return };
194                let mut ctx = EvalContext::new(Pin::new(owner));
195                eval_expression(&mut ctx, &expr_for_set);
196            },
197        );
198    }
199}
200
201fn install_repeater_model_bindings(
202    sub: &Pin<Rc<SubComponentInstance>>,
203    weak_sub: &Weak<SubComponentInstance>,
204) {
205    let cu = &sub.compilation_unit;
206    let sc: &SubComponent = &cu.sub_components[sub.sub_component_idx];
207    for (idx, repeated) in sc.repeated.iter_enumerated() {
208        let repeater = &sub.repeaters[idx];
209        let expr = mut_expression_clone(&repeated.model);
210        let weak_sub = weak_sub.clone();
211        if repeater.is_conditional() {
212            repeater.set_condition_binding(move || {
213                let Some(owner) = weak_sub.upgrade() else { return false };
214                let mut ctx = EvalContext::new(Pin::new(owner));
215                // The compiler guarantees the condition of an `if` is bool.
216                matches!(eval_expression(&mut ctx, &expr), Value::Bool(true))
217            });
218        } else {
219            repeater.set_model_binding(move || {
220                let Some(owner) = weak_sub.upgrade() else {
221                    return i_slint_core::model::ModelRc::default();
222                };
223                let mut ctx = EvalContext::new(Pin::new(owner));
224                match eval_expression(&mut ctx, &expr) {
225                    Value::Model(m) => m,
226                    // A number model (`for i in 42`): the `Cast { to: Model }`
227                    // evaluates to the number itself.
228                    Value::Number(n) => i_slint_core::model::ModelRc::new(
229                        crate::value_model::IntModel(n.max(0.) as usize),
230                    ),
231                    _ => i_slint_core::model::ModelRc::default(),
232                }
233            });
234        }
235    }
236}
237
238fn install_property_init(
239    target: &MemberReference,
240    binding: &llr::BindingExpression,
241    sub: &Pin<Rc<SubComponentInstance>>,
242    weak_sub: &Weak<SubComponentInstance>,
243) {
244    if let MemberReference::Global { global_index, member } = target {
245        install_global_property_init(*global_index, member, binding, weak_sub);
246        return;
247    }
248    let MemberReference::Relative { parent_level, local_reference } = target else {
249        unreachable!()
250    };
251    assert_eq!(*parent_level, 0, "property_init targets must be local to the sub-component");
252    let instance = walk_sub_path(sub.clone(), &local_reference.sub_component_path);
253
254    match &local_reference.reference {
255        LocalMemberIndex::Callback(idx) => {
256            let callback = Pin::as_ref(&instance.callbacks[*idx]);
257            let arg_types = instance.compilation_unit.sub_components[instance.sub_component_idx]
258                .callbacks[*idx]
259                .args
260                .clone();
261            let handler = make_callback_handler(
262                weak_sub.clone(),
263                mut_expression_clone(&binding.expression),
264                arg_types,
265            );
266            callback.set_handler(handler);
267        }
268        LocalMemberIndex::Property(idx) => {
269            let prop = Pin::as_ref(&instance.properties[*idx]);
270            let cu = &instance.compilation_unit;
271            let ty = &cu.sub_components[instance.sub_component_idx].properties[*idx].ty;
272            install_property_binding(prop, binding, weak_sub, ty);
273        }
274        LocalMemberIndex::Native { item_index, prop_name, kind } => {
275            // The lowering pass already disambiguates rtti properties,
276            // callbacks and member functions via `NativeMemberKind`, so
277            // dispatch on `kind` rather than probing the rtti tables by
278            // name.
279            let item = Pin::as_ref(&instance.items[*item_index]);
280            match kind {
281                i_slint_compiler::llr::NativeMemberKind::Callback => {
282                    let expr = mut_expression_clone(&binding.expression);
283                    let cb_weak = weak_sub.clone();
284                    let cu = &instance.compilation_unit;
285                    let sc = &cu.sub_components[instance.sub_component_idx];
286                    let arg_types = match sc.items[*item_index].ty.lookup_property(prop_name) {
287                        Some(i_slint_compiler::langtype::Type::Callback(f)) => f.args.clone(),
288                        _ => Vec::new(),
289                    };
290                    let _ = item.set_callback_handler(
291                        prop_name,
292                        Box::new(make_callback_handler(cb_weak, expr, arg_types)),
293                    );
294                }
295                i_slint_compiler::llr::NativeMemberKind::Property => {
296                    let expr = mut_expression_clone(&binding.expression);
297                    let weak_sub_eval = weak_sub.clone();
298                    let closure: Box<dyn Fn() -> Value> = Box::new(move || {
299                        let Some(owner) = weak_sub_eval.upgrade() else { return Value::Void };
300                        let mut ctx = EvalContext::new(Pin::new(owner));
301                        eval_expression(&mut ctx, &expr)
302                    });
303                    let animation_kind =
304                        animation_for_binding(binding.animation.as_ref(), weak_sub.clone());
305                    let _ = item.set_property_binding(prop_name, closure, animation_kind);
306                }
307                i_slint_compiler::llr::NativeMemberKind::Function => {
308                    // No state to install — `Expression::ItemMemberFunctionCall`
309                    // dispatches to the native method on demand.
310                }
311            }
312        }
313        LocalMemberIndex::Function(_) => {
314            // Function bodies live in `SubComponent::functions[*].code`;
315            // `invoke_function` reads them directly.
316        }
317        LocalMemberIndex::Timer(_) => unreachable!("a timer is not a binding target"),
318    }
319}
320
321/// Install a property_init entry that targets a global property or callback.
322/// The binding expression is evaluated in the owning sub-component's context.
323fn install_global_property_init(
324    global_index: i_slint_compiler::llr::GlobalIdx,
325    member: &LocalMemberIndex,
326    binding: &llr::BindingExpression,
327    weak_sub: &Weak<SubComponentInstance>,
328) {
329    let Some(sub) = weak_sub.upgrade() else { return };
330    let globals = sub.root.get().and_then(|w| w.upgrade()).map(|inst| inst.globals.clone());
331    let Some(globals) = globals else { return };
332    let Some(global) = globals.get(global_index) else { return };
333
334    let g = &sub.compilation_unit.globals[global_index];
335    match member {
336        LocalMemberIndex::Property(idx) => {
337            let ty = &g.properties[*idx].ty;
338            if let Some(native) = &global.native {
339                if let Some(prop) =
340                    native.as_ref().prepare_property_for_two_way_binding(&g.properties[*idx].name)
341                {
342                    install_property_binding(prop.as_ref(), binding, weak_sub, ty);
343                }
344            } else {
345                let prop = Pin::as_ref(&global.properties[*idx]);
346                install_property_binding(prop, binding, weak_sub, ty);
347            }
348        }
349        LocalMemberIndex::Callback(idx) => {
350            let handler = make_callback_handler(
351                weak_sub.clone(),
352                mut_expression_clone(&binding.expression),
353                g.callbacks[*idx].args.clone(),
354            );
355            if let Some(native) = &global.native {
356                let _ = native
357                    .as_ref()
358                    .set_callback_handler(&g.callbacks[*idx].name, Box::new(handler));
359            } else {
360                Pin::as_ref(&global.callbacks[*idx]).set_handler(handler);
361            }
362        }
363        LocalMemberIndex::Function(_)
364        | LocalMemberIndex::Native { .. }
365        | LocalMemberIndex::Timer(_) => {}
366    }
367}
368
369/// `int` (i32) and `duration` (i64) properties interpolate with per-frame
370/// rounding; reproduce that when the animation runs on a type-erased
371/// `Property<Value>`.
372pub(crate) fn animated_value_map(
373    ty: &i_slint_compiler::langtype::Type,
374) -> Option<fn(Value) -> Value> {
375    use i_slint_compiler::langtype::Type;
376    matches!(ty, Type::Int32 | Type::Duration).then_some(|v| match v {
377        Value::Number(n) => Value::Number(n.round()),
378        other => other,
379    })
380}
381
382fn install_property_binding(
383    prop: Pin<&Property<Value>>,
384    binding: &llr::BindingExpression,
385    weak_sub: &Weak<SubComponentInstance>,
386    ty: &i_slint_compiler::langtype::Type,
387) {
388    let expr = mut_expression_clone(&binding.expression);
389    let weak_sub_eval = weak_sub.clone();
390
391    use i_slint_compiler::llr::BindingKind;
392    if binding.kind == BindingKind::Constant {
393        if let Some(owner) = weak_sub_eval.upgrade() {
394            let mut ctx = EvalContext::new(Pin::new(owner));
395            prop.set(eval_expression(&mut ctx, &expr));
396        }
397        return;
398    }
399
400    if binding.kind == BindingKind::State {
401        // The expression returns the state index; `set_state_binding`
402        // tracks previous_state and change_time in the struct value.
403        let weak = weak_sub.clone();
404        i_slint_core::properties::set_state_binding(prop, move || {
405            let Some(owner) = weak.upgrade() else { return 0 };
406            let mut ctx = EvalContext::new(Pin::new(owner));
407            match eval_expression(&mut ctx, &expr) {
408                Value::Number(n) => n as i32,
409                _ => 0,
410            }
411        });
412        return;
413    }
414
415    let binding_fn: Box<dyn Fn() -> Value> = Box::new(move || {
416        let Some(owner) = weak_sub_eval.upgrade() else { return Value::Void };
417        let mut ctx = EvalContext::new(Pin::new(owner));
418        eval_expression(&mut ctx, &expr)
419    });
420
421    match (
422        animation_for_binding(binding.animation.as_ref(), weak_sub.clone()),
423        animated_value_map(ty),
424    ) {
425        (AnimatedBindingKind::NotAnimated, _) => prop.set_binding(binding_fn),
426        (AnimatedBindingKind::Animation(anim_fn), None) => {
427            prop.set_animated_binding(binding_fn, move || (anim_fn(), None));
428        }
429        (AnimatedBindingKind::Animation(anim_fn), Some(map)) => {
430            prop.set_animated_binding_with_map(binding_fn, move || (anim_fn(), None), map);
431        }
432        (AnimatedBindingKind::Transition(transition_fn), None) => {
433            prop.set_animated_binding(binding_fn, move || {
434                let (anim, change_time) = transition_fn();
435                (anim, Some(change_time))
436            });
437        }
438        (AnimatedBindingKind::Transition(transition_fn), Some(map)) => {
439            prop.set_animated_binding_with_map(
440                binding_fn,
441                move || {
442                    let (anim, change_time) = transition_fn();
443                    (anim, Some(change_time))
444                },
445                map,
446            );
447        }
448    }
449}
450
451fn animation_for_binding(
452    animation: Option<&Animation>,
453    weak_sub: Weak<SubComponentInstance>,
454) -> AnimatedBindingKind {
455    match animation {
456        None => AnimatedBindingKind::NotAnimated,
457        Some(Animation::Static(expr)) => {
458            let expr = expr.clone();
459            AnimatedBindingKind::Animation(Box::new(move || -> PropertyAnimation {
460                let Some(owner) = weak_sub.upgrade() else { return Default::default() };
461                let mut ctx = EvalContext::new(Pin::new(owner));
462                value_to_property_animation(eval_expression(&mut ctx, &expr))
463            }))
464        }
465        Some(Animation::Transition(expr)) => {
466            let expr = expr.clone();
467            AnimatedBindingKind::Transition(Box::new(
468                move || -> (PropertyAnimation, i_slint_core::animations::Instant) {
469                    let Some(owner) = weak_sub.upgrade() else {
470                        return (Default::default(), Default::default());
471                    };
472                    let mut ctx = EvalContext::new(Pin::new(owner));
473                    let v = eval_expression(&mut ctx, &expr);
474                    // The transition expression returns a struct {"0": animation, "1": change_time}
475                    let Value::Struct(s) = v else {
476                        return (Default::default(), Default::default());
477                    };
478                    let anim = s
479                        .get_field("0")
480                        .cloned()
481                        .map(value_to_property_animation)
482                        .unwrap_or_default();
483                    let change_time = s
484                        .get_field("1")
485                        .cloned()
486                        .and_then(|v| v.try_into().ok())
487                        .unwrap_or_default();
488                    (anim, change_time)
489                },
490            ))
491        }
492    }
493}
494
495/// Convert a `Value::Struct` produced by an LLR animation expression into a
496/// native `PropertyAnimation`.
497/// Unknown fields are ignored and missing fields fall back to `Default`.
498pub(crate) fn value_to_property_animation(v: Value) -> PropertyAnimation {
499    let Value::Struct(s) = v else { return PropertyAnimation::default() };
500    let mut anim = PropertyAnimation::default();
501    if let Some(Value::Number(n)) = s.get_field("delay") {
502        anim.delay = *n as i32;
503    }
504    if let Some(Value::Number(n)) = s.get_field("duration") {
505        anim.duration = *n as i32;
506    }
507    if let Some(Value::Number(n)) = s.get_field("iteration-count") {
508        anim.iteration_count = *n as f32;
509    }
510    if let Some(Value::EasingCurve(curve)) = s.get_field("easing") {
511        anim.easing = *curve;
512    }
513    if let Some(direction) = s.get_field("direction")
514        && let Ok(parsed) = direction.clone().try_into()
515    {
516        anim.direction = parsed;
517    }
518    if let Some(Value::Bool(b)) = s.get_field("enabled") {
519        anim.enabled = *b;
520    }
521    anim
522}
523
524fn install_two_way_binding(
525    twb: &i_slint_compiler::llr::TwoWayBinding,
526    sub: &Pin<Rc<SubComponentInstance>>,
527) {
528    let a: MemberReference = twb.prop1.clone().into();
529    let field_path: &[smol_str::SmolStr] = &twb.field_access;
530    let Some(pa) = prepare_two_way(&a, sub) else { return };
531
532    if let Some(index_prop) = twb.is_model {
533        // `prop2` names the `model_data` property of the enclosing `for`'s
534        // body sub-component, `parent_level` hops up from here. Bind the
535        // leaf property directly to the model row so writes go through
536        // `set_row_data` and reads see external model updates.
537        let MemberReference::Relative { parent_level, local_reference } = &twb.prop2 else {
538            return;
539        };
540        let LocalMemberIndex::Property(data_prop) = local_reference.reference else {
541            return;
542        };
543        let body = walk_parent(sub, *parent_level);
544        let body_weak = Rc::downgrade(&Pin::into_inner(body));
545        let path_get: Vec<smol_str::SmolStr> = field_path.to_vec();
546        let path_set = path_get.clone();
547        pa.as_ref().link_two_way_to_model_data(
548            body_weak,
549            move |body_weak: &std::rc::Weak<SubComponentInstance>| {
550                let body = Pin::new(body_weak.upgrade()?);
551                let data = Pin::as_ref(&body.properties[data_prop]).get();
552                if path_get.is_empty() {
553                    Some(data)
554                } else {
555                    extract_field(data, &path_get).filter(|v| !matches!(v, Value::Void))
556                }
557            },
558            move |body_weak: &std::rc::Weak<SubComponentInstance>, value: &Value| {
559                let Some(body) = body_weak.upgrade().map(Pin::new) else { return };
560                let Some((parent_weak, rep_idx)) = body.repeated_in.get() else { return };
561                let Some(parent) = parent_weak.upgrade().map(Pin::new) else { return };
562                let index: usize = match Pin::as_ref(&body.properties[index_prop]).get() {
563                    Value::Number(n) => n as usize,
564                    _ => return,
565                };
566                // Short-circuit identical writes to avoid spurious change
567                // notifications.
568                let data = if path_set.is_empty() {
569                    let current = Pin::as_ref(&body.properties[data_prop]).get();
570                    if &current == value {
571                        return;
572                    }
573                    value.clone()
574                } else {
575                    let mut data = Pin::as_ref(&body.properties[data_prop]).get();
576                    if extract_field(data.clone(), &path_set).as_ref() == Some(value) {
577                        return;
578                    }
579                    replace_field(&mut data, &path_set, value.clone());
580                    data
581                };
582                parent.repeaters[*rep_idx].model_set_row_data(index, data);
583            },
584        );
585        return;
586    }
587
588    let Some(pb) = prepare_two_way(&twb.prop2, sub) else { return };
589
590    if field_path.is_empty() {
591        Property::link_two_way(pa.as_ref(), pb.as_ref());
592        return;
593    }
594
595    // `pa` is the leaf property; `pb` is the struct that contains it.
596    // Map the struct value through `field_path` when reading and write
597    // back into the same field path when the leaf changes.
598    let path: Vec<smol_str::SmolStr> = field_path.to_vec();
599    let path_get = path.clone();
600    let path_set = path.clone();
601    // A struct value read before its binding ran (or set with missing
602    // fields) must yield the leaf type's default, never Void — native
603    // typed properties abort on a Void conversion.
604    let leaf_default = member_property_ty(&twb.prop2, sub)
605        .map(|ty| crate::eval::default_value_for_type(&field_leaf_ty(ty, field_path)))
606        .unwrap_or(Value::Void);
607    Property::link_two_way_with_map(
608        pb.as_ref(),
609        pa.as_ref(),
610        move |s| {
611            extract_field(s.clone(), &path_get)
612                .filter(|v| !matches!(v, Value::Void))
613                .unwrap_or_else(|| leaf_default.clone())
614        },
615        move |s, v| {
616            replace_field(s, &path_set, v.clone());
617        },
618    );
619}
620
621/// Walk `s.path[0].path[1]...` and return the leaf field.
622fn extract_field(value: Value, path: &[smol_str::SmolStr]) -> Option<Value> {
623    let mut current = value;
624    for p in path {
625        let Value::Struct(s) = current else { return None };
626        current = s.get_field(p.as_str()).cloned()?;
627    }
628    Some(current)
629}
630
631/// Walk `s.path[0].path[1]...` and write `new_leaf` into the leaf field.
632fn replace_field(s: &mut Value, path: &[smol_str::SmolStr], new_leaf: Value) {
633    if path.is_empty() {
634        *s = new_leaf;
635        return;
636    }
637    let Value::Struct(top) = s else { return };
638    let head = path[0].as_str();
639    let mut child = top.get_field(head).cloned().unwrap_or(Value::Void);
640    replace_field(&mut child, &path[1..], new_leaf);
641    top.set_field(head.to_string(), child);
642}
643
644/// The LLR-declared type of a member reference (properties only).
645fn member_property_ty(
646    mr: &MemberReference,
647    sub: &Pin<Rc<SubComponentInstance>>,
648) -> Option<i_slint_compiler::langtype::Type> {
649    match mr {
650        MemberReference::Relative { parent_level, local_reference } => {
651            let base = walk_parent(sub, *parent_level);
652            let instance = walk_sub_path(base, &local_reference.sub_component_path);
653            let cu = &instance.compilation_unit;
654            let sc = &cu.sub_components[instance.sub_component_idx];
655            match &local_reference.reference {
656                LocalMemberIndex::Property(idx) => Some(sc.properties[*idx].ty.clone()),
657                LocalMemberIndex::Native { item_index, prop_name, .. } => {
658                    sc.items[*item_index].ty.lookup_property(prop_name).cloned()
659                }
660                _ => None,
661            }
662        }
663        MemberReference::Global { global_index, member } => {
664            let root = sub.root.get().and_then(|w| w.upgrade())?;
665            let cu = root.root_sub_component.compilation_unit.clone();
666            let global = &cu.globals[*global_index];
667            if let LocalMemberIndex::Property(idx) = member {
668                Some(global.properties[*idx].ty.clone())
669            } else {
670                None
671            }
672        }
673    }
674}
675
676/// Descend `path` through struct field types, starting at `ty`.
677fn field_leaf_ty(
678    mut ty: i_slint_compiler::langtype::Type,
679    path: &[smol_str::SmolStr],
680) -> i_slint_compiler::langtype::Type {
681    for f in path {
682        let i_slint_compiler::langtype::Type::Struct(s) = &ty else { break };
683        match s.fields.get(f.as_str()) {
684            Some(t) => ty = t.clone(),
685            None => break,
686        }
687    }
688    ty
689}
690
691fn prepare_two_way(
692    mr: &MemberReference,
693    sub: &Pin<Rc<SubComponentInstance>>,
694) -> Option<Pin<Rc<Property<Value>>>> {
695    match mr {
696        MemberReference::Relative { parent_level, local_reference } => {
697            let base = walk_parent(sub, *parent_level);
698            let instance = walk_sub_path(base, &local_reference.sub_component_path);
699            match &local_reference.reference {
700                LocalMemberIndex::Property(idx) => Some(instance.properties[*idx].clone()),
701                LocalMemberIndex::Native { item_index, prop_name, .. } => {
702                    Pin::as_ref(&instance.items[*item_index])
703                        .prepare_property_for_two_way_binding(prop_name)
704                }
705                _ => None,
706            }
707        }
708        MemberReference::Global { global_index, member } => {
709            // A `data <=> Glo.x` two-way binding lands a `Global` reference
710            // here; return the matching `GlobalInstance`'s property storage
711            // so both sides can be linked.
712            let root = sub.root.get().and_then(|w| w.upgrade())?;
713            let global_inst = root.globals.get(*global_index)?;
714            if let LocalMemberIndex::Property(idx) = member {
715                if let Some(native) = &global_inst.native {
716                    let g = &global_inst.compilation_unit.globals[global_inst.global_idx];
717                    return native
718                        .as_ref()
719                        .prepare_property_for_two_way_binding(&g.properties[*idx].name);
720                }
721                Some(global_inst.properties[*idx].clone())
722            } else {
723                None
724            }
725        }
726    }
727}
728
729fn run_init_code(sub: &Pin<Rc<SubComponentInstance>>) {
730    // Nested sub-components run their init code first, so the outer
731    // component's use-site overrides (in its own init_code) observe the
732    // inner component's initialization.
733    for nested in &sub.sub_components {
734        run_init_code(nested);
735    }
736    let cu = sub.compilation_unit.clone();
737    let sc = &cu.sub_components[sub.sub_component_idx];
738    for e in &sc.init_code {
739        let expr = mut_expression_clone(e);
740        let mut ctx = EvalContext::new(sub.clone());
741        eval_expression(&mut ctx, &expr);
742    }
743}
744
745use crate::eval::{walk_parent, walk_sub_path};
746
747fn mut_expression_clone(e: &MutExpression) -> Expression {
748    e.borrow().clone()
749}
750
751/// A handler that evaluates `expr` with the call's arguments in `weak`'s
752/// scope; `Value::Void` once the owner is gone.
753fn make_callback_handler(
754    weak: Weak<SubComponentInstance>,
755    expr: Expression,
756    arg_types: Vec<i_slint_compiler::langtype::Type>,
757) -> impl Fn(&[Value]) -> Value + 'static {
758    move |args| {
759        let Some(owner) = weak.upgrade() else { return Value::Void };
760        let mut ctx = EvalContext::with_arguments(Pin::new(owner), args.to_vec());
761        ctx.function_arg_types = arg_types.clone();
762        eval_expression(&mut ctx, &expr)
763    }
764}