Skip to main content

slint_interpreter/
global_component.rs

1// Copyright © SixtyFPS GmbH <[email protected]>
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
93impl Default for GlobalStorage {
94    fn default() -> Self {
95        GlobalStorage::Strong(Default::default())
96    }
97}
98
99pub enum CompiledGlobal {
100    Builtin {
101        name: SmolStr,
102        element: Rc<i_slint_compiler::langtype::BuiltinElement>,
103        // dummy needed for iterator accessor
104        public_properties: BTreeMap<SmolStr, PropertyDeclaration>,
105        /// keep the Component alive as it is boing referenced by `NamedReference`s
106        _original: Rc<Component>,
107    },
108    Component {
109        component: ErasedItemTreeDescription,
110        public_properties: BTreeMap<SmolStr, PropertyDeclaration>,
111    },
112}
113
114impl CompiledGlobal {
115    pub fn names(&self) -> Vec<SmolStr> {
116        match self {
117            CompiledGlobal::Builtin { name, .. } => vec![name.clone()],
118            CompiledGlobal::Component { component, .. } => {
119                generativity::make_guard!(guard);
120                let component = component.unerase(guard);
121                let mut names = component.original.global_aliases();
122                names.push(component.original.root_element.borrow().original_name());
123                names
124            }
125        }
126    }
127
128    pub fn visible_in_public_api(&self) -> bool {
129        match self {
130            CompiledGlobal::Builtin { .. } => false,
131            CompiledGlobal::Component { component, .. } => {
132                generativity::make_guard!(guard);
133                let component = component.unerase(guard);
134                !component.original.exported_global_names.borrow().is_empty()
135            }
136        }
137    }
138
139    pub fn public_properties(&self) -> impl Iterator<Item = (&SmolStr, &PropertyDeclaration)> + '_ {
140        match self {
141            CompiledGlobal::Builtin { public_properties, .. } => public_properties.iter(),
142            CompiledGlobal::Component { public_properties, .. } => public_properties.iter(),
143        }
144    }
145
146    pub fn extend_public_properties(
147        &mut self,
148        iter: impl IntoIterator<Item = (SmolStr, PropertyDeclaration)>,
149    ) {
150        match self {
151            CompiledGlobal::Builtin { public_properties, .. } => public_properties.extend(iter),
152            CompiledGlobal::Component { public_properties, .. } => public_properties.extend(iter),
153        }
154    }
155}
156
157pub trait GlobalComponent {
158    fn invoke_callback(
159        self: Pin<&Self>,
160        callback_name: &SmolStr,
161        args: &[Value],
162    ) -> Result<Value, ()>;
163
164    fn set_callback_handler(
165        self: Pin<&Self>,
166        callback_name: &str,
167        handler: Box<dyn Fn(&[Value]) -> Value>,
168    ) -> Result<(), ()>;
169
170    fn set_property(
171        self: Pin<&Self>,
172        prop_name: &str,
173        value: Value,
174    ) -> Result<(), SetPropertyError>;
175    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()>;
176
177    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const ();
178
179    fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()>;
180
181    fn prepare_for_two_way_binding(
182        self: Pin<&Self>,
183        prop_name: &str,
184    ) -> Result<Pin<Rc<Property<Value>>>, ()>;
185}
186
187/// Instantiate the global singleton and store it in `globals`
188pub fn instantiate(
189    description: &CompiledGlobal,
190    globals: &GlobalStorage,
191    root: vtable::VWeak<ItemTreeVTable, ErasedItemTreeBox>,
192) {
193    let GlobalStorage::Strong(globals) = globals else { panic!("Global storage is not strong") };
194
195    let instance = match description {
196        CompiledGlobal::Builtin { element, .. } => {
197            trait Helper {
198                fn instantiate(name: &str) -> Pin<Rc<dyn GlobalComponent>> {
199                    panic!("Cannot find native global {name}")
200                }
201            }
202            impl Helper for () {}
203            impl<T: rtti::BuiltinGlobal + 'static, Next: Helper> Helper for (T, Next) {
204                fn instantiate(name: &str) -> Pin<Rc<dyn GlobalComponent>> {
205                    if name == T::name() { T::new() } else { Next::instantiate(name) }
206                }
207            }
208            i_slint_backend_selector::NativeGlobals::instantiate(
209                element.native_class.class_name.as_ref(),
210            )
211        }
212        CompiledGlobal::Component { component, .. } => {
213            generativity::make_guard!(guard);
214            let description = component.unerase(guard);
215            let inst = crate::dynamic_item_tree::instantiate(
216                description.clone(),
217                None,
218                Some(root),
219                None,
220                GlobalStorage::Weak(Rc::downgrade(globals)),
221            );
222            inst.run_setup_code();
223            Rc::pin(GlobalComponentInstance(inst))
224        }
225    };
226
227    globals.globals.borrow_mut().extend(
228        description
229            .names()
230            .iter()
231            .map(|name| (crate::normalize_identifier(name).to_string(), instance.clone())),
232    );
233}
234
235/// For the global components, we don't use the dynamic_type optimization,
236/// and we don't try to optimize the property to their real type
237pub struct GlobalComponentInstance(vtable::VRc<ItemTreeVTable, ErasedItemTreeBox>);
238
239impl GlobalComponent for GlobalComponentInstance {
240    fn set_property(
241        self: Pin<&Self>,
242        prop_name: &str,
243        value: Value,
244    ) -> Result<(), SetPropertyError> {
245        generativity::make_guard!(guard);
246        let comp = self.0.unerase(guard);
247        comp.description().set_property(comp.borrow(), prop_name, value)
248    }
249
250    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()> {
251        generativity::make_guard!(guard);
252        let comp = self.0.unerase(guard);
253        comp.description().get_property(comp.borrow(), prop_name)
254    }
255
256    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () {
257        generativity::make_guard!(guard);
258        let comp = self.0.unerase(guard);
259        crate::dynamic_item_tree::get_property_ptr(
260            &NamedReference::new(&comp.description().original.root_element, prop_name.clone()),
261            comp.borrow_instance(),
262        )
263    }
264
265    fn invoke_callback(
266        self: Pin<&Self>,
267        callback_name: &SmolStr,
268        args: &[Value],
269    ) -> Result<Value, ()> {
270        generativity::make_guard!(guard);
271        let comp = self.0.unerase(guard);
272        comp.description().invoke(comp.borrow(), callback_name, args)
273    }
274
275    fn set_callback_handler(
276        self: Pin<&Self>,
277        callback_name: &str,
278        handler: Box<dyn Fn(&[Value]) -> Value>,
279    ) -> Result<(), ()> {
280        generativity::make_guard!(guard);
281        let comp = self.0.unerase(guard);
282        comp.description().set_callback_handler(comp.borrow(), callback_name, handler)
283    }
284
285    fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec<Value>) -> Result<Value, ()> {
286        generativity::make_guard!(guard);
287        let comp = self.0.unerase(guard);
288        let mut ctx =
289            crate::eval::EvalLocalContext::from_function_arguments(comp.borrow_instance(), args);
290        let result = crate::eval::eval_expression(
291            &comp
292                .description()
293                .original
294                .root_element
295                .borrow()
296                .bindings
297                .get(fn_name)
298                .ok_or(())?
299                .borrow()
300                .expression,
301            &mut ctx,
302        );
303        Ok(result)
304    }
305
306    fn prepare_for_two_way_binding(
307        self: Pin<&Self>,
308        prop_name: &str,
309    ) -> Result<Pin<Rc<Property<Value>>>, ()> {
310        generativity::make_guard!(guard);
311        let comp = self.0.unerase(guard);
312        let description = comp.description();
313        let x = description.custom_properties.get(prop_name).ok_or(())?;
314        let item = unsafe { Pin::new_unchecked(&*comp.borrow_instance().as_ptr().add(x.offset)) };
315        Ok(x.prop.prepare_for_two_way_binding(item))
316    }
317}
318
319impl<T: rtti::BuiltinItem + 'static> GlobalComponent for T {
320    fn set_property(
321        self: Pin<&Self>,
322        prop_name: &str,
323        value: Value,
324    ) -> Result<(), SetPropertyError> {
325        let prop = Self::properties()
326            .into_iter()
327            .find(|(k, _)| *k == prop_name)
328            .ok_or(SetPropertyError::NoSuchProperty)?
329            .1;
330        prop.set(self, value, None).map_err(|()| SetPropertyError::WrongType)
331    }
332
333    fn get_property(self: Pin<&Self>, prop_name: &str) -> Result<Value, ()> {
334        let prop = Self::properties().into_iter().find(|(k, _)| *k == prop_name).ok_or(())?.1;
335        prop.get(self)
336    }
337
338    fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () {
339        let prop: &dyn rtti::PropertyInfo<Self, Value> =
340            Self::properties().into_iter().find(|(k, _)| *k == prop_name).unwrap().1;
341        unsafe { (self.get_ref() as *const Self as *const u8).add(prop.offset()) as *const () }
342    }
343
344    fn invoke_callback(
345        self: Pin<&Self>,
346        callback_name: &SmolStr,
347        args: &[Value],
348    ) -> Result<Value, ()> {
349        let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1;
350        cb.call(self, args)
351    }
352
353    fn set_callback_handler(
354        self: Pin<&Self>,
355        callback_name: &str,
356        handler: Box<dyn Fn(&[Value]) -> Value>,
357    ) -> Result<(), ()> {
358        let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1;
359        cb.set_handler(self, handler)
360    }
361
362    fn eval_function(self: Pin<&Self>, _fn_name: &str, _args: Vec<Value>) -> Result<Value, ()> {
363        Err(())
364    }
365
366    fn prepare_for_two_way_binding(
367        self: Pin<&Self>,
368        prop_name: &str,
369    ) -> Result<Pin<Rc<Property<Value>>>, ()> {
370        Ok(Self::properties()
371            .into_iter()
372            .find(|(k, _)| *k == prop_name)
373            .ok_or(())?
374            .1
375            .prepare_for_two_way_binding(self))
376    }
377}
378
379fn generate(component: &Rc<Component>) -> CompiledGlobal {
380    debug_assert!(component.is_global());
381    match &component.root_element.borrow().base_type {
382        ElementType::Global => {
383            generativity::make_guard!(guard);
384            CompiledGlobal::Component {
385                component: crate::dynamic_item_tree::generate_item_tree(
386                    component,
387                    None,
388                    PopupMenuDescription::Weak(Default::default()),
389                    false,
390                    guard,
391                )
392                .into(),
393                public_properties: Default::default(),
394            }
395        }
396        ElementType::Builtin(b) => CompiledGlobal::Builtin {
397            name: component.id.clone(),
398            element: b.clone(),
399            public_properties: Default::default(),
400            _original: component.clone(),
401        },
402        ElementType::Error
403        | ElementType::Interface
404        | ElementType::Native(_)
405        | ElementType::Component(_) => unreachable!(),
406    }
407}