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    pub context: i_slint_core::SlintContext,
125    /// Set through [`ComponentInstance::set_debug_hook_callback`]; see [`crate::debug_hook`].
126    pub debug_hook_callback: std::cell::RefCell<Option<crate::debug_hook::DebugHookCallback>>,
127}
128
129impl GlobalStorage {
130    /// Allocate one `GlobalInstance` per declared global.
131    /// Bindings are installed separately by [`install_global_bindings`].
132    pub fn new(
133        compilation_unit: &Rc<CompilationUnit>,
134        context: i_slint_core::SlintContext,
135    ) -> Self {
136        let globals = compilation_unit
137            .globals
138            .iter_enumerated()
139            .map(|(idx, global)| {
140                if global.is_builtin {
141                    // Backends without a matching native global leave the
142                    // slot empty and reads produce `Value::Void`.
143                    let native = instantiate_native_global(&global.name)?;
144                    return Some(Rc::new(GlobalInstance {
145                        compilation_unit: compilation_unit.clone(),
146                        global_idx: idx,
147                        properties: TiVec::new(),
148                        callbacks: TiVec::new(),
149                        callback_trackers: TiVec::new(),
150                        change_trackers: Vec::new(),
151                        native: Some(native),
152                    }));
153                }
154                let properties = global
155                    .properties
156                    .iter()
157                    .map(|p| Rc::pin(Property::new(crate::eval::default_value_for_type(&p.ty))))
158                    .collect();
159                let callbacks =
160                    global.callbacks.iter().map(|_| Rc::pin(Callback::default())).collect();
161                let callback_trackers = global
162                    .callbacks
163                    .iter()
164                    .map(|c| {
165                        c.needs_tracker
166                            .then(|| Rc::pin(i_slint_core::properties::Property::new(())))
167                    })
168                    .collect();
169                Some(Rc::new(GlobalInstance {
170                    compilation_unit: compilation_unit.clone(),
171                    global_idx: idx,
172                    properties,
173                    callbacks,
174                    callback_trackers,
175                    change_trackers: std::iter::repeat_with(Default::default)
176                        .take(global.change_callbacks.len())
177                        .collect(),
178                    native: None,
179                }))
180            })
181            .collect();
182        Self {
183            globals,
184            root: std::cell::OnceCell::new(),
185            context,
186            debug_hook_callback: std::cell::RefCell::new(None),
187        }
188    }
189
190    pub fn get(&self, idx: GlobalIdx) -> Option<&Rc<GlobalInstance>> {
191        self.globals.get(idx)?.as_ref()
192    }
193
194    /// Look up a non-builtin global by its exported name (or alias).
195    /// Returns the matching `GlobalComponent` and its runtime `GlobalInstance`.
196    pub fn find_by_name<'a>(
197        &'a self,
198        compilation_unit: &'a CompilationUnit,
199        name: &str,
200    ) -> Option<(&'a i_slint_compiler::llr::GlobalComponent, &'a Rc<GlobalInstance>)> {
201        let needle = i_slint_compiler::parser::normalize_identifier(name);
202        for (idx, global) in compilation_unit.globals.iter_enumerated() {
203            if !global.exported {
204                continue;
205            }
206            let name_matches =
207                i_slint_compiler::parser::normalize_identifier(&global.name) == needle;
208            let alias_matches = global
209                .aliases
210                .iter()
211                .any(|a| i_slint_compiler::parser::normalize_identifier(a) == needle);
212            if name_matches || alias_matches {
213                return self.get(idx).map(|inst| (global, inst));
214            }
215        }
216        None
217    }
218}
219
220/// Install every global's `init_values`, then in a second pass the change
221/// trackers for `changed X => { … }` handlers, so trackers observe the
222/// fully initialized values (matching the sub-component ordering).
223pub fn install_global_bindings(storage: &Rc<GlobalStorage>) {
224    for g in storage.globals.iter().flatten() {
225        install_for_global(g, storage);
226    }
227    for g in storage.globals.iter().flatten() {
228        install_global_change_trackers(g, storage);
229    }
230}
231
232fn install_global_change_trackers(g: &Rc<GlobalInstance>, storage: &Rc<GlobalStorage>) {
233    let cu = g.compilation_unit.clone();
234    let global = &cu.globals[g.global_idx];
235    for (idx, (prop_idx, expr)) in global.change_callbacks.iter().enumerate() {
236        let weak_storage_get = Rc::downgrade(storage);
237        let weak_storage_set = Rc::downgrade(storage);
238        let global_idx = g.global_idx;
239        let prop_idx = *prop_idx;
240        let notify_expr = expr.borrow().clone();
241        g.change_trackers[idx].init(
242            (),
243            move |()| -> Value {
244                let Some(st) = weak_storage_get.upgrade() else { return Value::Void };
245                let Some(gi) = st.get(global_idx) else { return Value::Void };
246                Pin::as_ref(&gi.properties[prop_idx]).get()
247            },
248            {
249                let cu = cu.clone();
250                move |(), _| {
251                    let Some(st) = weak_storage_set.upgrade() else { return };
252                    let mut ctx = EvalContext::for_global(Rc::downgrade(&st), cu.clone());
253                    eval_expression(&mut ctx, &notify_expr);
254                }
255            },
256        );
257    }
258}
259
260fn install_for_global(g: &Rc<GlobalInstance>, storage: &Rc<GlobalStorage>) {
261    if g.native.is_some() {
262        // Native globals carry their own state; there are no interpreted
263        // init values to install.
264        return;
265    }
266    let cu = g.compilation_unit.clone();
267    let global = &cu.globals[g.global_idx];
268    for (member, binding) in &global.init_values {
269        let expr = binding.expression.borrow().clone();
270        let weak_storage = Rc::downgrade(storage);
271
272        match member {
273            LocalMemberIndex::Property(idx) => {
274                let prop = Pin::as_ref(&g.properties[*idx]);
275                if binding.kind == i_slint_compiler::llr::BindingKind::Constant {
276                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
277                    prop.set(eval_expression(&mut ctx, &expr));
278                    continue;
279                }
280                let expr = expr.clone();
281                let cu = cu.clone();
282                prop.set_binding(move || {
283                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
284                    eval_expression(&mut ctx, &expr)
285                });
286            }
287            LocalMemberIndex::Callback(idx) => {
288                let cb = Pin::as_ref(&g.callbacks[*idx]);
289                let expr = expr.clone();
290                let cu = cu.clone();
291                let arg_types = global.callbacks[*idx].args.clone();
292                cb.set_handler(move |args: &[Value]| -> Value {
293                    let mut ctx = EvalContext::for_global(weak_storage.clone(), cu.clone());
294                    ctx.function_arg_types = arg_types.clone();
295                    ctx.function_arguments = args.to_vec();
296                    eval_expression(&mut ctx, &expr)
297                });
298            }
299            LocalMemberIndex::Function(_)
300            | LocalMemberIndex::Native { .. }
301            | LocalMemberIndex::Timer(_) => {
302                // Function bodies live on `GlobalComponent::functions[*].code`.
303                // Natives and timers don't appear on globals.
304            }
305        }
306    }
307}