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