Skip to main content

slint_interpreter/
globals.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//! Storage for `global <Name> { … }` runtime instances.
5//!
6//! Created once per root instance and shared across the sub-component tree.
7//! Globals carry properties, callbacks and functions, but no items or children.
8
9use crate::Value;
10use crate::erased::{SubComponentCallback, SubComponentProperty};
11use crate::eval::{EvalContext, eval_expression};
12use i_slint_compiler::llr::{CompilationUnit, GlobalIdx, LocalMemberIndex};
13use i_slint_core::rtti;
14use i_slint_core::{Callback, Property};
15use std::pin::Pin;
16use std::rc::Rc;
17use typed_index_collections::TiVec;
18
19/// Name-based access to a native (builtin) global like `NativeStyleMetrics`,
20/// whose state lives in a backend-provided struct instead of interpreter
21/// `Property<Value>` slots.
22pub trait NativeGlobal {
23    fn get_property(self: Pin<&Self>, name: &str) -> Option<Value>;
24    fn set_property(
25        self: Pin<&Self>,
26        name: &str,
27        value: Value,
28        animation: Option<i_slint_core::items::PropertyAnimation>,
29    ) -> Result<(), ()>;
30    fn invoke_callback(self: Pin<&Self>, name: &str, args: &[Value]) -> Option<Value>;
31    fn set_callback_handler(
32        self: Pin<&Self>,
33        name: &str,
34        handler: Box<dyn Fn(&[Value]) -> Value>,
35    ) -> Result<(), ()>;
36    fn prepare_property_for_two_way_binding(
37        self: Pin<&Self>,
38        name: &str,
39    ) -> Option<Pin<Rc<Property<Value>>>>;
40}
41
42impl<T: rtti::BuiltinGlobal + 'static> NativeGlobal for T {
43    fn get_property(self: Pin<&Self>, name: &str) -> Option<Value> {
44        let (_, prop) = T::properties::<Value>().into_iter().find(|(k, _)| *k == name)?;
45        prop.get(self).ok()
46    }
47
48    fn set_property(
49        self: Pin<&Self>,
50        name: &str,
51        value: Value,
52        animation: Option<i_slint_core::items::PropertyAnimation>,
53    ) -> Result<(), ()> {
54        let (_, prop) = T::properties::<Value>().into_iter().find(|(k, _)| *k == name).ok_or(())?;
55        prop.set(self, value, animation)
56    }
57
58    fn invoke_callback(self: Pin<&Self>, name: &str, args: &[Value]) -> Option<Value> {
59        let (_, cb) = T::callbacks::<Value>().into_iter().find(|(k, _)| *k == name)?;
60        cb.call(self, args).ok()
61    }
62
63    fn set_callback_handler(
64        self: Pin<&Self>,
65        name: &str,
66        handler: Box<dyn Fn(&[Value]) -> Value>,
67    ) -> Result<(), ()> {
68        let (_, cb) = T::callbacks::<Value>().into_iter().find(|(k, _)| *k == name).ok_or(())?;
69        cb.set_handler(self, handler)
70    }
71
72    fn prepare_property_for_two_way_binding(
73        self: Pin<&Self>,
74        name: &str,
75    ) -> Option<Pin<Rc<Property<Value>>>> {
76        let (_, prop) = T::properties::<Value>().into_iter().find(|(k, _)| *k == name)?;
77        Some(prop.prepare_for_two_way_binding(self))
78    }
79}
80
81/// Instantiate the backend-provided global with the given class name.
82/// `None` when the selected backend has no native global of that name.
83fn instantiate_native_global(class_name: &str) -> Option<Pin<Rc<dyn NativeGlobal>>> {
84    trait Helper {
85        fn instantiate(_name: &str) -> Option<Pin<Rc<dyn NativeGlobal>>> {
86            None
87        }
88    }
89    impl Helper for () {}
90    impl<T: rtti::BuiltinGlobal + 'static, Next: Helper> Helper for (T, Next) {
91        fn instantiate(name: &str) -> Option<Pin<Rc<dyn NativeGlobal>>> {
92            if name == T::name() { Some(T::new()) } else { Next::instantiate(name) }
93        }
94    }
95    <i_slint_backend_selector::NativeGlobals as Helper>::instantiate(class_name)
96}
97
98pub struct GlobalInstance {
99    pub compilation_unit: Rc<CompilationUnit>,
100    pub global_idx: GlobalIdx,
101    pub properties: TiVec<i_slint_compiler::llr::PropertyIdx, SubComponentProperty>,
102    pub callbacks: TiVec<i_slint_compiler::llr::CallbackIdx, SubComponentCallback>,
103    /// `Property<()>` per callback with `needs_tracker`; see
104    /// `SubComponentInstance::callback_trackers`.
105    pub callback_trackers: TiVec<
106        i_slint_compiler::llr::CallbackIdx,
107        Option<std::pin::Pin<Rc<i_slint_core::properties::Property<()>>>>,
108    >,
109    /// One `ChangeTracker` per entry of the global's `change_callbacks`.
110    pub change_trackers: Vec<i_slint_core::properties::ChangeTracker>,
111    /// Backend-provided state for a builtin global (`NativeStyleMetrics`,
112    /// `NativePalette`, …); access goes by member name through the rtti
113    /// tables. The interpreter-level slots above stay empty.
114    pub native: Option<Pin<Rc<dyn NativeGlobal>>>,
115}
116
117/// All globals for one root component.
118pub struct GlobalStorage {
119    globals: TiVec<GlobalIdx, Option<Rc<GlobalInstance>>>,
120    /// The owning instance, so global bindings can reach the window.
121    pub root: std::cell::OnceCell<
122        vtable::VWeak<i_slint_core::item_tree::ItemTreeVTable, crate::instance::Instance>,
123    >,
124    /// Set through [`ComponentInstance::set_debug_hook_callback`]; see [`crate::debug_hook`].
125    pub debug_hook_callback: std::cell::RefCell<Option<crate::debug_hook::DebugHookCallback>>,
126}
127
128impl GlobalStorage {
129    /// Allocate one `GlobalInstance` per declared global.
130    /// Bindings are installed separately by [`install_global_bindings`].
131    pub fn new(compilation_unit: &Rc<CompilationUnit>) -> Self {
132        let globals = compilation_unit
133            .globals
134            .iter_enumerated()
135            .map(|(idx, global)| {
136                if global.is_builtin {
137                    // Backends without a matching native global leave the
138                    // slot empty and reads produce `Value::Void`.
139                    let native = instantiate_native_global(&global.name)?;
140                    return Some(Rc::new(GlobalInstance {
141                        compilation_unit: compilation_unit.clone(),
142                        global_idx: idx,
143                        properties: TiVec::new(),
144                        callbacks: TiVec::new(),
145                        callback_trackers: TiVec::new(),
146                        change_trackers: Vec::new(),
147                        native: Some(native),
148                    }));
149                }
150                let properties = global
151                    .properties
152                    .iter()
153                    .map(|p| Rc::pin(Property::new(crate::eval::default_value_for_type(&p.ty))))
154                    .collect();
155                let callbacks =
156                    global.callbacks.iter().map(|_| Rc::pin(Callback::default())).collect();
157                let callback_trackers = global
158                    .callbacks
159                    .iter()
160                    .map(|c| {
161                        c.needs_tracker
162                            .then(|| Rc::pin(i_slint_core::properties::Property::new(())))
163                    })
164                    .collect();
165                Some(Rc::new(GlobalInstance {
166                    compilation_unit: compilation_unit.clone(),
167                    global_idx: idx,
168                    properties,
169                    callbacks,
170                    callback_trackers,
171                    change_trackers: std::iter::repeat_with(Default::default)
172                        .take(global.change_callbacks.len())
173                        .collect(),
174                    native: None,
175                }))
176            })
177            .collect();
178        Self {
179            globals,
180            root: std::cell::OnceCell::new(),
181            debug_hook_callback: std::cell::RefCell::new(None),
182        }
183    }
184
185    pub fn get(&self, idx: GlobalIdx) -> Option<&Rc<GlobalInstance>> {
186        self.globals.get(idx)?.as_ref()
187    }
188
189    /// Look up a non-builtin global by its exported name (or alias).
190    /// Returns the matching `GlobalComponent` and its runtime `GlobalInstance`.
191    pub fn find_by_name<'a>(
192        &'a self,
193        compilation_unit: &'a CompilationUnit,
194        name: &str,
195    ) -> Option<(&'a i_slint_compiler::llr::GlobalComponent, &'a Rc<GlobalInstance>)> {
196        let needle = i_slint_compiler::parser::normalize_identifier(name);
197        for (idx, global) in compilation_unit.globals.iter_enumerated() {
198            if !global.exported {
199                continue;
200            }
201            let name_matches =
202                i_slint_compiler::parser::normalize_identifier(&global.name) == needle;
203            let alias_matches = global
204                .aliases
205                .iter()
206                .any(|a| i_slint_compiler::parser::normalize_identifier(a) == needle);
207            if name_matches || alias_matches {
208                return self.get(idx).map(|inst| (global, inst));
209            }
210        }
211        None
212    }
213}
214
215/// Install every global's `init_values`, then in a second pass the change
216/// trackers for `changed X => { … }` handlers, so trackers observe the
217/// fully initialized values (matching the sub-component ordering).
218pub fn install_global_bindings(storage: &Rc<GlobalStorage>) {
219    for g in storage.globals.iter().flatten() {
220        install_for_global(g, storage);
221    }
222    for g in storage.globals.iter().flatten() {
223        install_global_change_trackers(g, storage);
224    }
225}
226
227fn install_global_change_trackers(g: &Rc<GlobalInstance>, storage: &Rc<GlobalStorage>) {
228    let cu = g.compilation_unit.clone();
229    let global = &cu.globals[g.global_idx];
230    for (idx, (prop_idx, expr)) in global.change_callbacks.iter().enumerate() {
231        let weak_storage_get = Rc::downgrade(storage);
232        let weak_storage_set = Rc::downgrade(storage);
233        let global_idx = g.global_idx;
234        let prop_idx = *prop_idx;
235        let notify_expr = expr.borrow().clone();
236        g.change_trackers[idx].init(
237            (),
238            move |()| -> Value {
239                let Some(st) = weak_storage_get.upgrade() else { return Value::Void };
240                let Some(gi) = st.get(global_idx) else { return Value::Void };
241                Pin::as_ref(&gi.properties[prop_idx]).get()
242            },
243            {
244                let cu = cu.clone();
245                move |(), _| {
246                    let Some(st) = weak_storage_set.upgrade() else { return };
247                    let mut ctx = EvalContext::for_global(Rc::downgrade(&st), cu.clone());
248                    eval_expression(&mut ctx, &notify_expr);
249                }
250            },
251        );
252    }
253}
254
255fn install_for_global(g: &Rc<GlobalInstance>, storage: &Rc<GlobalStorage>) {
256    if g.native.is_some() {
257        // Native globals carry their own state; there are no interpreted
258        // init values to install.
259        return;
260    }
261    let cu = g.compilation_unit.clone();
262    let global = &cu.globals[g.global_idx];
263    for (member, binding) in &global.init_values {
264        let expr = binding.expression.borrow().clone();
265        let weak_storage = Rc::downgrade(storage);
266
267        match member {
268            LocalMemberIndex::Property(idx) => {
269                let prop = Pin::as_ref(&g.properties[*idx]);
270                if binding.kind == i_slint_compiler::llr::BindingKind::Constant {
271                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
272                    prop.set(eval_expression(&mut ctx, &expr));
273                    continue;
274                }
275                let expr = expr.clone();
276                let cu = cu.clone();
277                prop.set_binding(move || {
278                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
279                    eval_expression(&mut ctx, &expr)
280                });
281            }
282            LocalMemberIndex::Callback(idx) => {
283                let cb = Pin::as_ref(&g.callbacks[*idx]);
284                let expr = expr.clone();
285                let cu = cu.clone();
286                let arg_types = global.callbacks[*idx].args.clone();
287                cb.set_handler(move |args: &[Value]| -> Value {
288                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
289                    ctx.function_arg_types = arg_types.clone();
290                    ctx.function_arguments = args.to_vec();
291                    eval_expression(&mut ctx, &expr)
292                });
293            }
294            LocalMemberIndex::Function(_)
295            | LocalMemberIndex::Native { .. }
296            | LocalMemberIndex::Timer(_) => {
297                // Function bodies live on `GlobalComponent::functions[*].code`.
298                // Natives and timers don't appear on globals.
299            }
300        }
301    }
302}