// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use crate::api::Value; use crate::dynamic_item_tree::{ ErasedItemTreeBox, ErasedItemTreeDescription, PopupMenuDescription, }; use crate::SetPropertyError; use core::cell::RefCell; use core::pin::Pin; use i_slint_compiler::langtype::ElementType; use i_slint_compiler::namedreference::NamedReference; use i_slint_compiler::object_tree::{Component, Document, PropertyDeclaration}; use i_slint_core::item_tree::ItemTreeVTable; use i_slint_core::rtti; use smol_str::SmolStr; use std::collections::{BTreeMap, HashMap}; use std::rc::Rc; pub struct CompiledGlobalCollection { /// compiled globals pub compiled_globals: Vec, /// Map of all exported global singletons and their index in the compiled_globals vector. The key /// is the normalized name of the global. pub exported_globals_by_name: BTreeMap, } impl CompiledGlobalCollection { pub fn compile(doc: &Document) -> Self { let mut exported_globals_by_name = BTreeMap::new(); let compiled_globals = doc .used_types .borrow() .globals .iter() .enumerate() .map(|(index, component)| { let mut global = generate(component); if !component.exported_global_names.borrow().is_empty() { global.extend_public_properties( component.root_element.borrow().property_declarations.clone(), ); exported_globals_by_name.extend( component .exported_global_names .borrow() .iter() .map(|exported_name| (exported_name.name.clone(), index)), ) } global }) .collect(); Self { compiled_globals, exported_globals_by_name } } } #[derive(Clone)] pub enum GlobalStorage { Strong(Rc>>>>), /// When the storage is held by another global Weak(std::rc::Weak>>>>), } impl GlobalStorage { pub fn get(&self, name: &str) -> Option>> { match self { GlobalStorage::Strong(storage) => storage.borrow().get(name).cloned(), GlobalStorage::Weak(storage) => storage.upgrade().unwrap().borrow().get(name).cloned(), } } } impl Default for GlobalStorage { fn default() -> Self { GlobalStorage::Strong(Default::default()) } } pub enum CompiledGlobal { Builtin { name: SmolStr, element: Rc, // dummy needed for iterator accessor public_properties: BTreeMap, /// keep the Component alive as it is boing referenced by `NamedReference`s _original: Rc, }, Component { component: ErasedItemTreeDescription, public_properties: BTreeMap, }, } impl CompiledGlobal { pub fn names(&self) -> Vec { match self { CompiledGlobal::Builtin { name, .. } => vec![name.clone()], CompiledGlobal::Component { component, .. } => { generativity::make_guard!(guard); let component = component.unerase(guard); let mut names = component.original.global_aliases(); names.push(component.original.root_element.borrow().original_name()); names } } } pub fn visible_in_public_api(&self) -> bool { match self { CompiledGlobal::Builtin { .. } => false, CompiledGlobal::Component { component, .. } => { generativity::make_guard!(guard); let component = component.unerase(guard); let is_exported = !component.original.exported_global_names.borrow().is_empty(); is_exported } } } pub fn public_properties(&self) -> impl Iterator + '_ { match self { CompiledGlobal::Builtin { public_properties, .. } => public_properties.iter(), CompiledGlobal::Component { public_properties, .. } => public_properties.iter(), } } pub fn extend_public_properties( &mut self, iter: impl IntoIterator, ) { match self { CompiledGlobal::Builtin { public_properties, .. } => public_properties.extend(iter), CompiledGlobal::Component { public_properties, .. } => public_properties.extend(iter), } } } pub trait GlobalComponent { fn invoke_callback( self: Pin<&Self>, callback_name: &SmolStr, args: &[Value], ) -> Result; fn set_callback_handler( self: Pin<&Self>, callback_name: &str, handler: Box Value>, ) -> Result<(), ()>; fn set_property( self: Pin<&Self>, prop_name: &str, value: Value, ) -> Result<(), SetPropertyError>; fn get_property(self: Pin<&Self>, prop_name: &str) -> Result; fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const (); fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec) -> Result; } /// Instantiate the global singleton and store it in `globals` pub fn instantiate( description: &CompiledGlobal, globals: &mut GlobalStorage, root: vtable::VWeak, ) { let GlobalStorage::Strong(ref mut globals) = globals else { panic!("Global storage is not strong") }; let instance = match description { CompiledGlobal::Builtin { element, .. } => { trait Helper { fn instantiate(name: &str) -> Pin> { panic!("Cannot find native global {name}") } } impl Helper for () {} impl Helper for (T, Next) { fn instantiate(name: &str) -> Pin> { if name == T::name() { T::new() } else { Next::instantiate(name) } } } i_slint_backend_selector::NativeGlobals::instantiate( element.native_class.class_name.as_ref(), ) } CompiledGlobal::Component { component, .. } => { generativity::make_guard!(guard); let description = component.unerase(guard); let inst = crate::dynamic_item_tree::instantiate( description.clone(), None, Some(root), None, GlobalStorage::Weak(Rc::downgrade(globals)), ); inst.run_setup_code(); Rc::pin(GlobalComponentInstance(inst)) } }; globals.borrow_mut().extend( description .names() .iter() .map(|name| (crate::normalize_identifier(name).to_string(), instance.clone())), ); } /// For the global components, we don't use the dynamic_type optimization, /// and we don't try to optimize the property to their real type pub struct GlobalComponentInstance(vtable::VRc); impl GlobalComponent for GlobalComponentInstance { fn set_property( self: Pin<&Self>, prop_name: &str, value: Value, ) -> Result<(), SetPropertyError> { generativity::make_guard!(guard); let comp = self.0.unerase(guard); comp.description().set_property(comp.borrow(), prop_name, value) } fn get_property(self: Pin<&Self>, prop_name: &str) -> Result { generativity::make_guard!(guard); let comp = self.0.unerase(guard); comp.description().get_property(comp.borrow(), prop_name) } fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () { generativity::make_guard!(guard); let comp = self.0.unerase(guard); crate::dynamic_item_tree::get_property_ptr( &NamedReference::new(&comp.description().original.root_element, prop_name.clone()), comp.borrow_instance(), ) } fn invoke_callback( self: Pin<&Self>, callback_name: &SmolStr, args: &[Value], ) -> Result { generativity::make_guard!(guard); let comp = self.0.unerase(guard); comp.description().invoke(comp.borrow(), callback_name, args) } fn set_callback_handler( self: Pin<&Self>, callback_name: &str, handler: Box Value>, ) -> Result<(), ()> { generativity::make_guard!(guard); let comp = self.0.unerase(guard); comp.description().set_callback_handler(comp.borrow(), callback_name, handler) } fn eval_function(self: Pin<&Self>, fn_name: &str, args: Vec) -> Result { generativity::make_guard!(guard); let comp = self.0.unerase(guard); let mut ctx = crate::eval::EvalLocalContext::from_function_arguments(comp.borrow_instance(), args); let result = crate::eval::eval_expression( &comp .description() .original .root_element .borrow() .bindings .get(fn_name) .ok_or(())? .borrow() .expression, &mut ctx, ); Ok(result) } } impl GlobalComponent for T { fn set_property( self: Pin<&Self>, prop_name: &str, value: Value, ) -> Result<(), SetPropertyError> { let prop = Self::properties() .into_iter() .find(|(k, _)| *k == prop_name) .ok_or(SetPropertyError::NoSuchProperty)? .1; prop.set(self, value, None).map_err(|()| SetPropertyError::WrongType) } fn get_property(self: Pin<&Self>, prop_name: &str) -> Result { let prop = Self::properties().into_iter().find(|(k, _)| *k == prop_name).ok_or(())?.1; prop.get(self) } fn get_property_ptr(self: Pin<&Self>, prop_name: &SmolStr) -> *const () { let prop: &dyn rtti::PropertyInfo = Self::properties().into_iter().find(|(k, _)| *k == prop_name).unwrap().1; unsafe { (self.get_ref() as *const Self as *const u8).add(prop.offset()) as *const () } } fn invoke_callback( self: Pin<&Self>, callback_name: &SmolStr, args: &[Value], ) -> Result { let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1; cb.call(self, args) } fn set_callback_handler( self: Pin<&Self>, callback_name: &str, handler: Box Value>, ) -> Result<(), ()> { let cb = Self::callbacks().into_iter().find(|(k, _)| *k == callback_name).ok_or(())?.1; cb.set_handler(self, handler) } fn eval_function(self: Pin<&Self>, _fn_name: &str, _args: Vec) -> Result { Err(()) } } fn generate(component: &Rc) -> CompiledGlobal { debug_assert!(component.is_global()); match &component.root_element.borrow().base_type { ElementType::Global => { generativity::make_guard!(guard); CompiledGlobal::Component { component: crate::dynamic_item_tree::generate_item_tree( component, None, PopupMenuDescription::Weak(Default::default()), false, guard, ) .into(), public_properties: Default::default(), } } ElementType::Builtin(b) => CompiledGlobal::Builtin { name: component.id.clone(), element: b.clone(), public_properties: Default::default(), _original: component.clone(), }, ElementType::Error | ElementType::Native(_) | ElementType::Component(_) => unreachable!(), } }