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