mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-03 07:04:34 +00:00

Use this snapshot to keep a unoptimized typeloader around, so that the preview does not need to do another parsing run. Move the document cache in the preview over to use the snapshot.
539 lines
20 KiB
Rust
539 lines
20 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
// 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};
|
|
use crate::typeloader;
|
|
|
|
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<Enumeration>),*
|
|
}
|
|
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<Item = (&'static str, Type)> {
|
|
[
|
|
//("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<Item = (&'static str, Type, PropertyVisibility)> {
|
|
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<BuiltinFunction> {
|
|
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<String, Type>,
|
|
/// The set of element types
|
|
elements: HashMap<String, ElementType>,
|
|
supported_property_animation_types: HashSet<String>,
|
|
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<String, HashSet<String>>,
|
|
parent_registry: Option<Rc<RefCell<TypeRegister>>>,
|
|
/// If the lookup function should return types that are marked as internal
|
|
pub(crate) expose_internal_types: bool,
|
|
}
|
|
|
|
impl TypeRegister {
|
|
pub(crate) fn snapshot(&self, snapshotter: &mut typeloader::Snapshotter) -> Self {
|
|
Self {
|
|
types: self.types.clone(),
|
|
elements: self
|
|
.elements
|
|
.iter()
|
|
.map(|(k, v)| (k.clone(), snapshotter.snapshot_element_type(v)))
|
|
.collect(),
|
|
supported_property_animation_types: self.supported_property_animation_types.clone(),
|
|
property_animation_type: snapshotter
|
|
.snapshot_element_type(&self.property_animation_type),
|
|
empty_type: snapshotter.snapshot_element_type(&self.empty_type),
|
|
context_restricted_types: self.context_restricted_types.clone(),
|
|
parent_registry: self
|
|
.parent_registry
|
|
.as_ref()
|
|
.map(|tr| snapshotter.snapshot_type_register(tr)),
|
|
expose_internal_types: self.expose_internal_types,
|
|
}
|
|
}
|
|
|
|
/// 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<RefCell<Self>> {
|
|
let register = Self::builtin_internal();
|
|
Rc::new(RefCell::new(register))
|
|
}
|
|
|
|
pub fn builtin() -> Rc<RefCell<Self>> {
|
|
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<RefCell<TypeRegister>>) -> 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<ElementType, HashMap<String, HashSet<String>>> {
|
|
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<ElementType, String> {
|
|
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::<Vec<_>>();
|
|
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<ElementType> {
|
|
self.parent_registry.as_ref().map_or_else(
|
|
|| self.elements.get(name).cloned(),
|
|
|p| p.borrow().lookup_builtin_element(name),
|
|
)
|
|
}
|
|
|
|
pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
|
|
if qualified.len() != 1 {
|
|
return Type::Invalid;
|
|
}
|
|
self.lookup(qualified[0].as_ref())
|
|
}
|
|
|
|
pub fn add(&mut self, comp: Rc<Component>) {
|
|
self.add_with_name(comp.id.clone(), comp);
|
|
}
|
|
|
|
pub fn add_with_name(&mut self, name: String, comp: Rc<Component>) {
|
|
self.elements.insert(name, ElementType::Component(comp));
|
|
}
|
|
|
|
pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {
|
|
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<String, Type> {
|
|
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<String, ElementType> {
|
|
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,
|
|
}
|
|
}
|