Skip to main content

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