slint_interpreter/
global_component.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
4use crate::api::Value;
5use crate::dynamic_item_tree::{
6    ErasedItemTreeBox, ErasedItemTreeDescription, PopupMenuDescription,
7};
8use crate::SetPropertyError;
9use core::cell::RefCell;
10use core::pin::Pin;
11use i_slint_compiler::langtype::ElementType;
12use i_slint_compiler::namedreference::NamedReference;
13use i_slint_compiler::object_tree::{Component, Document, PropertyDeclaration};
14use i_slint_core::item_tree::ItemTreeVTable;
15use i_slint_core::rtti;
16use smol_str::SmolStr;
17use std::collections::{BTreeMap, HashMap};
18use std::rc::Rc;
19
20pub struct CompiledGlobalCollection {
21    /// compiled globals
22    pub compiled_globals: Vec<CompiledGlobal>,
23    /// Map of all exported global singletons and their index in the compiled_globals vector. The key
24    /// is the normalized name of the global.
25    pub exported_globals_by_name: BTreeMap<SmolStr, usize>,
26}
27
28impl CompiledGlobalCollection {
29    pub fn compile(doc: &Document) -> Self {
30        let mut exported_globals_by_name = BTreeMap::new();
31        let compiled_globals = doc
32            .used_types
33            .borrow()
34            .globals
35            .iter()
36            .enumerate()
37            .map(|(index, component)| {
38                let mut global = generate(component);
39
40                if !component.exported_global_names.borrow().is_empty() {
41                    global.extend_public_properties(
42                        component.root_element.borrow().property_declarations.clone(),
43                    );
44
45                    exported_globals_by_name.extend(
46                        component
47                            .exported_global_names
48                            .borrow()
49                            .iter()
50                            .map(|exported_name| (exported_name.name.clone(), index)),
51                    )
52                }
53
54                global
55            })
56            .collect();
57        Self { compiled_globals, exported_globals_by_name }
58    }
59}
60
61#[derive(Clone)]
62pub enum GlobalStorage {
63    Strong(Rc<RefCell<HashMap<String, Pin<Rc<dyn GlobalComponent>>>>>),
64    /// When the storage is held by another global
65    Weak(std::rc::Weak<RefCell<HashMap<String, Pin<Rc<dyn GlobalComponent>>>>>),
66}
67
68impl GlobalStorage {
69    pub fn get(&self, name: &str) -> Option<Pin<Rc<dyn GlobalComponent>>> {
70        match self {
71            GlobalStorage::Strong(storage) => storage.borrow().get(name).cloned(),
72            GlobalStorage::Weak(storage) => storage.upgrade().unwrap().borrow().get(name).cloned(),
73        }
74    }
75}
76
77impl Default for GlobalStorage {
78    fn default() -> Self {
79        GlobalStorage::Strong(Default::default())
80    }
81}
82
83pub enum CompiledGlobal {
84    Builtin {
85        name: SmolStr,
86        element: Rc<i_slint_compiler::langtype::BuiltinElement>,
87        // dummy needed for iterator accessor
88        public_properties: BTreeMap<SmolStr, PropertyDeclaration>,
89        /// keep the Component alive as it is boing referenced by `NamedReference`s
90        _original: Rc<Component>,
91    },
92    Component {
93        component: ErasedItemTreeDescription,
94        public_properties: BTreeMap<SmolStr, PropertyDeclaration>,
95    },
96}
97
98impl CompiledGlobal {
99    pub fn names(&self) -> Vec<SmolStr> {
100        match self {
101            CompiledGlobal::Builtin { name, .. } => vec![name.clone()],
102            CompiledGlobal::Component { component, .. } => {
103                generativity::make_guard!(guard);
104                let component = component.unerase(guard);
105                let mut names = component.original.global_aliases();
106                names.push(component.original.root_element.borrow().original_name());
107                names
108            }
109        }
110    }
111
112    pub fn visible_in_public_api(&self) -> bool {
113        match self {
114            CompiledGlobal::Builtin { .. } => false,
115            CompiledGlobal::Component { component, .. } => {
116                generativity::make_guard!(guard);
117                let component = component.unerase(guard);
118                let is_exported = !component.original.exported_global_names.borrow().is_empty();
119                is_exported
120            }
121        }
122    }
123
124    pub fn public_properties(&self) -> impl Iterator<Item = (&SmolStr, &PropertyDeclaration)> + '_ {
125        match self {
126            CompiledGlobal::Builtin { public_properties, .. } => public_properties.iter(),
127            CompiledGlobal::Component { public_properties, .. } => public_properties.iter(),
128        }
129    }
130
131    pub fn extend_public_properties(
132        &mut self,
133        iter: impl IntoIterator<Item = (SmolStr, PropertyDeclaration)>,
134    ) {
135        match self {
136            CompiledGlobal::Builtin { public_properties, .. } => public_properties.extend(iter),
137            CompiledGlobal::Component { public_properties, .. } => public_properties.extend(iter),
138        }
139    }
140}
141
142pub trait GlobalComponent {
143    fn invoke_callback(
144        self: Pin<&Self>,
145        callback_name: &SmolStr,
146        args: &[Value],
147    ) -> Result<Value, ()>;
148
149    fn set_callback_handler(
150        self: Pin<&Self>,
151        callback_name: &str,
152        handler: Box<dyn Fn(&[Value]) -> Value>,
153    ) -> Result<(), ()>;
154
155    fn set_property(
156        self: Pin<&Self>,
157        prop_name: &str,
158        value: Value,
159    ) -> Result<(), SetPropertyError>;
160    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()>;
161
162    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const ();
163
164    fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()>;
165}
166
167/// Instantiate the global singleton and store it in `globals`
168pub fn instantiate(
169    description: &CompiledGlobal,
170    globals: &mut GlobalStorage,
171    root: vtable::VWeak<ItemTreeVTable, ErasedItemTreeBox>,
172) {
173    let GlobalStorage::Strong(ref mut globals) = globals else {
174        panic!("Global storage is not strong")
175    };
176
177    let instance = match description {
178        CompiledGlobal::Builtin { element, .. } => {
179            trait Helper {
180                fn instantiate(name: &str) -> Pin<Rc<dyn GlobalComponent>> {
181                    panic!("Cannot find native global {name}")
182                }
183            }
184            impl Helper for () {}
185            impl<T: rtti::BuiltinGlobal + 'static, Next: Helper> Helper for (T, Next) {
186                fn instantiate(name: &str) -> Pin<Rc<dyn GlobalComponent>> {
187                    if name == T::name() {
188                        T::new()
189                    } else {
190                        Next::instantiate(name)
191                    }
192                }
193            }
194            i_slint_backend_selector::NativeGlobals::instantiate(
195                element.native_class.class_name.as_ref(),
196            )
197        }
198        CompiledGlobal::Component { component, .. } => {
199            generativity::make_guard!(guard);
200            let description = component.unerase(guard);
201            let inst = crate::dynamic_item_tree::instantiate(
202                description.clone(),
203                None,
204                Some(root),
205                None,
206                GlobalStorage::Weak(Rc::downgrade(globals)),
207            );
208            inst.run_setup_code();
209            Rc::pin(GlobalComponentInstance(inst))
210        }
211    };
212
213    globals.borrow_mut().extend(
214        description
215            .names()
216            .iter()
217            .map(|name| (crate::normalize_identifier(name).to_string(), instance.clone())),
218    );
219}
220
221/// For the global components, we don't use the dynamic_type optimization,
222/// and we don't try to optimize the property to their real type
223pub struct GlobalComponentInstance(vtable::VRc<ItemTreeVTable, ErasedItemTreeBox>);
224
225impl GlobalComponent for GlobalComponentInstance {
226    fn set_property(
227        self: Pin<&Self>,
228        prop_name: &str,
229        value: Value,
230    ) -> Result<(), SetPropertyError> {
231        generativity::make_guard!(guard);
232        let comp = self.0.unerase(guard);
233        comp.description().set_property(comp.borrow(), prop_name, value)
234    }
235
236    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()> {
237        generativity::make_guard!(guard);
238        let comp = self.0.unerase(guard);
239        comp.description().get_property(comp.borrow(), prop_name)
240    }
241
242    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () {
243        generativity::make_guard!(guard);
244        let comp = self.0.unerase(guard);
245        crate::dynamic_item_tree::get_property_ptr(
246            &NamedReference::new(&comp.description().original.root_element, prop_name.clone()),
247            comp.borrow_instance(),
248        )
249    }
250
251    fn invoke_callback(
252        self: Pin<&Self>,
253        callback_name: &SmolStr,
254        args: &[Value],
255    ) -> Result<Value, ()> {
256        generativity::make_guard!(guard);
257        let comp = self.0.unerase(guard);
258        comp.description().invoke(comp.borrow(), callback_name, args)
259    }
260
261    fn set_callback_handler(
262        self: Pin<&Self>,
263        callback_name: &str,
264        handler: Box<dyn Fn(&[Value]) -> Value>,
265    ) -> Result<(), ()> {
266        generativity::make_guard!(guard);
267        let comp = self.0.unerase(guard);
268        comp.description().set_callback_handler(comp.borrow(), callback_name, handler)
269    }
270
271    fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()> {
272        generativity::make_guard!(guard);
273        let comp = self.0.unerase(guard);
274        let mut ctx =
275            crate::eval::EvalLocalContext::from_function_arguments(comp.borrow_instance(), args);
276        let result = crate::eval::eval_expression(
277            &comp
278                .description()
279                .original
280                .root_element
281                .borrow()
282                .bindings
283                .get(fn_name)
284                .ok_or(())?
285                .borrow()
286                .expression,
287            &mut ctx,
288        );
289        Ok(result)
290    }
291}
292
293impl<T: rtti::BuiltinItem + 'static> GlobalComponent for T {
294    fn set_property(
295        self: Pin<&Self>,
296        prop_name: &str,
297        value: Value,
298    ) -> Result<(), SetPropertyError> {
299        let prop = Self::properties()
300            .into_iter()
301            .find(|(k, _)| *k == prop_name)
302            .ok_or(SetPropertyError::NoSuchProperty)?
303            .1;
304        prop.set(self, value, None).map_err(|()| SetPropertyError::WrongType)
305    }
306
307    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()> {
308        let prop = Self::properties().into_iter().find(|(k, _)| *k == prop_name).ok_or(())?.1;
309        prop.get(self)
310    }
311
312    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () {
313        let prop: &dyn rtti::PropertyInfo<Self, Value> =
314            Self::properties().into_iter().find(|(k, _)| *k == prop_name).unwrap().1;
315        unsafe { (self.get_ref() as *const Self as *const u8).add(prop.offset()) as *const () }
316    }
317
318    fn invoke_callback(
319        self: Pin<&Self>,
320        callback_name: &SmolStr,
321        args: &[Value],
322    ) -> Result<Value, ()> {
323        let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1;
324        cb.call(self, args)
325    }
326
327    fn set_callback_handler(
328        self: Pin<&Self>,
329        callback_name: &str,
330        handler: Box<dyn Fn(&[Value]) -> Value>,
331    ) -> Result<(), ()> {
332        let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1;
333        cb.set_handler(self, handler)
334    }
335
336    fn eval_function(self: Pin<&Self>, _fn_name: &str, _args: Vec<Value>) -> Result<Value, ()> {
337        Err(())
338    }
339}
340
341fn generate(component: &Rc<Component>) -> CompiledGlobal {
342    debug_assert!(component.is_global());
343    match &component.root_element.borrow().base_type {
344        ElementType::Global => {
345            generativity::make_guard!(guard);
346            CompiledGlobal::Component {
347                component: crate::dynamic_item_tree::generate_item_tree(
348                    component,
349                    None,
350                    PopupMenuDescription::Weak(Default::default()),
351                    false,
352                    guard,
353                )
354                .into(),
355                public_properties: Default::default(),
356            }
357        }
358        ElementType::Builtin(b) => CompiledGlobal::Builtin {
359            name: component.id.clone(),
360            element: b.clone(),
361            public_properties: Default::default(),
362            _original: component.clone(),
363        },
364        ElementType::Error | ElementType::Native(_) | ElementType::Component(_) => unreachable!(),
365    }
366}