// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.2 OR LicenseRef-Slint-commercial // cSpell: ignore imum use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; use std::rc::Rc; use crate::expression_tree::BuiltinFunction; use crate::langtype::{ BuiltinElement, BuiltinPropertyInfo, ElementType, Enumeration, PropertyLookupResult, Type, }; use crate::object_tree::{Component, PropertyVisibility}; pub const RESERVED_GEOMETRY_PROPERTIES: &[(&str, Type)] = &[ ("x", Type::LogicalLength), ("y", Type::LogicalLength), ("width", Type::LogicalLength), ("height", Type::LogicalLength), ("z", Type::Float32), ]; pub const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[ ("min-width", Type::LogicalLength), ("min-height", Type::LogicalLength), ("max-width", Type::LogicalLength), ("max-height", Type::LogicalLength), ("padding", Type::LogicalLength), ("padding-left", Type::LogicalLength), ("padding-right", Type::LogicalLength), ("padding-top", Type::LogicalLength), ("padding-bottom", Type::LogicalLength), ("preferred-width", Type::LogicalLength), ("preferred-height", Type::LogicalLength), ("horizontal-stretch", Type::Float32), ("vertical-stretch", Type::Float32), ]; pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[ ("col", Type::Int32), ("row", Type::Int32), ("colspan", Type::Int32), ("rowspan", Type::Int32), ]; macro_rules! declare_enums { ($( $(#[$enum_doc:meta])* enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => { pub struct BuiltinEnums { $(pub $Name : Rc),* } impl BuiltinEnums { fn new() -> Self { Self { $($Name : Rc::new(Enumeration { name: stringify!($Name).replace('_', "-"), values: vec![$(crate::generator::to_kebab_case(stringify!($Value).trim_start_matches("r#"))),*], default_value: 0, node: None, })),* } } fn fill_register(&self, register: &mut TypeRegister) { $(if stringify!($Name) != "PathEvent" { register.insert_type_with_name( Type::Enumeration(self.$Name.clone()), stringify!($Name).replace('_', "-") ); })* } } }; } i_slint_common::for_each_enums!(declare_enums); thread_local! { pub static BUILTIN_ENUMS: BuiltinEnums = BuiltinEnums::new(); } const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[ ("clip", Type::Bool), ("opacity", Type::Float32), ("cache-rendering-hint", Type::Bool), ("visible", Type::Bool), // ("enabled", Type::Bool), ]; pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[ ("drop-shadow-offset-x", Type::LogicalLength), ("drop-shadow-offset-y", Type::LogicalLength), ("drop-shadow-blur", Type::LogicalLength), ("drop-shadow-color", Type::Color), ]; pub const RESERVED_ROTATION_PROPERTIES: &[(&str, Type)] = &[ ("rotation-angle", Type::Angle), ("rotation-origin-x", Type::LogicalLength), ("rotation-origin-y", Type::LogicalLength), ]; pub fn reserved_accessibility_properties() -> impl Iterator { [ //("accessible-role", ...) ("accessible-checkable", Type::Bool), ("accessible-checked", Type::Bool), ("accessible-delegate-focus", Type::Int32), ("accessible-description", Type::String), ("accessible-label", Type::String), ("accessible-value", Type::String), ("accessible-value-maximum", Type::Float32), ("accessible-value-minimum", Type::Float32), ("accessible-value-step", Type::Float32), ("accessible-action-default", Type::Callback { return_type: None, args: vec![] }), ("accessible-action-increment", Type::Callback { return_type: None, args: vec![] }), ("accessible-action-decrement", Type::Callback { return_type: None, args: vec![] }), ( "accessible-action-set-value", Type::Callback { return_type: None, args: vec![Type::String] }, ), ] .into_iter() } /// list of reserved property injected in every item pub fn reserved_properties() -> impl Iterator { RESERVED_GEOMETRY_PROPERTIES .iter() .chain(RESERVED_LAYOUT_PROPERTIES.iter()) .chain(RESERVED_OTHER_PROPERTIES.iter()) .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter()) .chain(RESERVED_ROTATION_PROPERTIES.iter()) .map(|(k, v)| (*k, v.clone(), PropertyVisibility::InOut)) .chain(reserved_accessibility_properties().map(|(k, v)| (k, v, PropertyVisibility::InOut))) .chain( RESERVED_GRIDLAYOUT_PROPERTIES .iter() .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Constexpr)), ) .chain(IntoIterator::into_iter([ ("absolute-position", logical_point_type(), PropertyVisibility::Output), ("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr), ("focus", BuiltinFunction::SetFocusItem.ty(), PropertyVisibility::Public), ("clear-focus", BuiltinFunction::ClearFocusItem.ty(), PropertyVisibility::Public), ( "dialog-button-role", Type::Enumeration(BUILTIN_ENUMS.with(|e| e.DialogButtonRole.clone())), PropertyVisibility::Constexpr, ), ( "accessible-role", Type::Enumeration(BUILTIN_ENUMS.with(|e| e.AccessibleRole.clone())), PropertyVisibility::Constexpr, ), ])) .chain(std::iter::once(( "init", Type::Callback { return_type: None, args: vec![] }, PropertyVisibility::Private, ))) } /// lookup reserved property injected in every item pub fn reserved_property(name: &str) -> PropertyLookupResult { for (p, t, property_visibility) in reserved_properties() { if p == name { return PropertyLookupResult { property_type: t, resolved_name: name.into(), is_local_to_component: false, is_in_direct_base: false, property_visibility, declared_pure: None, }; } } // Report deprecated known reserved properties (maximum_width, minimum_height, ...) for pre in &["min", "max"] { if let Some(a) = name.strip_prefix(pre) { for suf in &["width", "height"] { if let Some(b) = a.strip_suffix(suf) { if b == "imum-" { return PropertyLookupResult { property_type: Type::LogicalLength, resolved_name: format!("{}-{}", pre, suf).into(), is_local_to_component: false, is_in_direct_base: false, property_visibility: crate::object_tree::PropertyVisibility::InOut, declared_pure: None, }; } } } } } PropertyLookupResult { resolved_name: name.into(), property_type: Type::Invalid, is_local_to_component: false, is_in_direct_base: false, property_visibility: crate::object_tree::PropertyVisibility::Private, declared_pure: None, } } /// These member functions are injected in every time pub fn reserved_member_function(name: &str) -> Option { for (m, e) in [ ("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property ("clear-focus", BuiltinFunction::ClearFocusItem), // match for callable "clear-focus" property ] { if m == name { return Some(e); } } None } #[derive(Debug, Default)] pub struct TypeRegister { /// The set of property types. types: HashMap, /// The set of element types elements: HashMap, supported_property_animation_types: HashSet, pub(crate) property_animation_type: ElementType, pub(crate) empty_type: ElementType, /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is /// used to construct helpful error messages, such as "Row can only be within a GridLayout element". context_restricted_types: HashMap>, parent_registry: Option>>, /// If the lookup function should return types that are marked as internal pub(crate) expose_internal_types: bool, } impl TypeRegister { /// FIXME: same as 'add' ? pub fn insert_type(&mut self, t: Type) { self.types.insert(t.to_string(), t); } pub fn insert_type_with_name(&mut self, t: Type, name: String) { self.types.insert(name, t); } fn builtin_internal() -> Self { let mut register = TypeRegister::default(); register.insert_type(Type::Float32); register.insert_type(Type::Int32); register.insert_type(Type::String); register.insert_type(Type::PhysicalLength); register.insert_type(Type::LogicalLength); register.insert_type(Type::Color); register.insert_type(Type::ComponentFactory); register.insert_type(Type::Duration); register.insert_type(Type::Image); register.insert_type(Type::Bool); register.insert_type(Type::Model); register.insert_type(Type::Percent); register.insert_type(Type::Easing); register.insert_type(Type::Angle); register.insert_type(Type::Brush); register.insert_type(Type::Rem); register.types.insert("Point".into(), logical_point_type()); BUILTIN_ENUMS.with(|e| e.fill_register(&mut register)); register.supported_property_animation_types.insert(Type::Float32.to_string()); register.supported_property_animation_types.insert(Type::Int32.to_string()); register.supported_property_animation_types.insert(Type::Color.to_string()); register.supported_property_animation_types.insert(Type::PhysicalLength.to_string()); register.supported_property_animation_types.insert(Type::LogicalLength.to_string()); register.supported_property_animation_types.insert(Type::Brush.to_string()); register.supported_property_animation_types.insert(Type::Angle.to_string()); #[rustfmt::skip] macro_rules! map_type { ($pub_type:ident, bool) => { Type::Bool }; ($pub_type:ident, i32) => { Type::Int32 }; ($pub_type:ident, f32) => { Type::Float32 }; ($pub_type:ident, SharedString) => { Type::String }; ($pub_type:ident, Coord) => { Type::LogicalLength }; ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() }; ($pub_type:ident, $_:ident) => { BUILTIN_ENUMS.with(|e| Type::Enumeration(e.$pub_type.clone())) }; } #[rustfmt::skip] macro_rules! maybe_clone { ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() }; ($pub_type:ident, $_:ident) => { $pub_type }; } macro_rules! register_builtin_structs { ($( $(#[$attr:meta])* struct $Name:ident { @name = $inner_name:literal export { $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ident, )* } private { $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )* } } )*) => { $( let $Name = Type::Struct { fields: BTreeMap::from([ $((stringify!($pub_field).replace('_', "-"), map_type!($pub_type, $pub_type))),* ]), name: Some(format!("{}", $inner_name)), node: None, rust_attributes: None, }; register.insert_type_with_name(maybe_clone!($Name, $Name), stringify!($Name).to_string()); )* }; } i_slint_common::for_each_builtin_structs!(register_builtin_structs); crate::load_builtins::load_builtins(&mut register); let mut context_restricted_types = HashMap::new(); register .elements .values() .for_each(|ty| ty.collect_contextual_types(&mut context_restricted_types)); register.context_restricted_types = context_restricted_types; match &mut register.elements.get_mut("PopupWindow").unwrap() { ElementType::Builtin(ref mut b) => { let popup = Rc::get_mut(b).unwrap(); popup.properties.insert( "show".into(), BuiltinPropertyInfo::new(BuiltinFunction::ShowPopupWindow.ty()), ); popup.member_functions.insert("show".into(), BuiltinFunction::ShowPopupWindow); popup.properties.insert( "close".into(), BuiltinPropertyInfo::new(BuiltinFunction::ClosePopupWindow.ty()), ); popup.member_functions.insert("close".into(), BuiltinFunction::ClosePopupWindow); popup.properties.get_mut("close-on-click").unwrap().property_visibility = PropertyVisibility::Constexpr; } _ => unreachable!(), }; match &mut register.elements.get_mut("TextInput").unwrap() { ElementType::Builtin(ref mut b) => { let text_input = Rc::get_mut(b).unwrap(); text_input.properties.insert( "set-selection-offsets".into(), BuiltinPropertyInfo::new(BuiltinFunction::SetSelectionOffsets.ty()), ); text_input .member_functions .insert("set-selection-offsets".into(), BuiltinFunction::SetSelectionOffsets); } _ => unreachable!(), }; register } #[doc(hidden)] /// All builtins incl. experimental ones! Do not use in production code! pub fn builtin_experimental() -> Rc> { let register = Self::builtin_internal(); Rc::new(RefCell::new(register)) } pub fn builtin() -> Rc> { let mut register = Self::builtin_internal(); register.elements.remove("ComponentContainer"); register.types.remove("component-factory"); Rc::new(RefCell::new(register)) } pub fn new(parent: &Rc>) -> Self { Self { parent_registry: Some(parent.clone()), expose_internal_types: parent.borrow().expose_internal_types, ..Default::default() } } pub fn lookup(&self, name: &str) -> Type { self.types .get(name) .cloned() .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name))) .unwrap_or_default() } fn lookup_element_as_result( &self, name: &str, ) -> Result>> { match self.elements.get(name).cloned() { Some(ty) => Ok(ty), None => match &self.parent_registry { Some(r) => r.borrow().lookup_element_as_result(name), None => Err(self.context_restricted_types.clone()), }, } } pub fn lookup_element(&self, name: &str) -> Result { self.lookup_element_as_result(name).map_err(|context_restricted_types| { if let Some(permitted_parent_types) = context_restricted_types.get(name) { if permitted_parent_types.len() == 1 { format!( "{} can only be within a {} element", name, permitted_parent_types.iter().next().unwrap() ) } else { let mut elements = permitted_parent_types.iter().cloned().collect::>(); elements.sort(); format!( "{} can only be within the following elements: {}", name, elements.join(", ") ) } } else if let Some(ty) = self.types.get(name) { format!("'{}' cannot be used as an element", ty) } else { format!("Unknown type {}", name) } }) } pub fn lookup_builtin_element(&self, name: &str) -> Option { self.parent_registry.as_ref().map_or_else( || self.elements.get(name).cloned(), |p| p.borrow().lookup_builtin_element(name), ) } pub fn lookup_qualified>(&self, qualified: &[Member]) -> Type { if qualified.len() != 1 { return Type::Invalid; } self.lookup(qualified[0].as_ref()) } pub fn add(&mut self, comp: Rc) { self.add_with_name(comp.id.clone(), comp); } pub fn add_with_name(&mut self, name: String, comp: Rc) { self.elements.insert(name, ElementType::Component(comp)); } pub fn add_builtin(&mut self, builtin: Rc) { self.elements.insert(builtin.name.clone(), ElementType::Builtin(builtin)); } pub fn property_animation_type_for_property(&self, property_type: Type) -> ElementType { if self.supported_property_animation_types.contains(&property_type.to_string()) { self.property_animation_type.clone() } else { self.parent_registry .as_ref() .map(|registry| { registry.borrow().property_animation_type_for_property(property_type) }) .unwrap_or_default() } } /// Return a hashmap with all the registered type pub fn all_types(&self) -> HashMap { let mut all = self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default(); for (k, v) in &self.types { all.insert(k.clone(), v.clone()); } all } /// Return a hashmap with all the registered element type pub fn all_elements(&self) -> HashMap { let mut all = self.parent_registry.as_ref().map(|r| r.borrow().all_elements()).unwrap_or_default(); for (k, v) in &self.elements { all.insert(k.clone(), v.clone()); } all } pub fn empty_type(&self) -> ElementType { match self.parent_registry.as_ref() { Some(parent) => parent.borrow().empty_type(), None => self.empty_type.clone(), } } } pub fn logical_point_type() -> Type { Type::Struct { fields: IntoIterator::into_iter([ ("x".to_owned(), Type::LogicalLength), ("y".to_owned(), Type::LogicalLength), ]) .collect(), name: Some("slint::LogicalPosition".into()), node: None, rust_attributes: None, } }