mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-30 19:47:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			948 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			948 lines
		
	
	
	
		
			35 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
 | ||
| 
 | ||
| use std::borrow::Cow;
 | ||
| use std::collections::{BTreeMap, HashMap};
 | ||
| use std::fmt::Display;
 | ||
| use std::rc::Rc;
 | ||
| 
 | ||
| use itertools::Itertools;
 | ||
| 
 | ||
| use smol_str::SmolStr;
 | ||
| 
 | ||
| use crate::expression_tree::{BuiltinFunction, Expression, Unit};
 | ||
| use crate::object_tree::{Component, PropertyVisibility};
 | ||
| use crate::parser::syntax_nodes;
 | ||
| use crate::typeregister::TypeRegister;
 | ||
| 
 | ||
| #[derive(Debug, Clone, Default)]
 | ||
| pub enum Type {
 | ||
|     /// Correspond to an uninitialized type, or an error
 | ||
|     #[default]
 | ||
|     Invalid,
 | ||
|     /// The type of an expression that return nothing
 | ||
|     Void,
 | ||
|     /// The type of a property two way binding whose type was not yet inferred
 | ||
|     InferredProperty,
 | ||
|     /// The type of a callback alias whose type was not yet inferred
 | ||
|     InferredCallback,
 | ||
| 
 | ||
|     Callback(Rc<Function>),
 | ||
|     Function(Rc<Function>),
 | ||
| 
 | ||
|     ComponentFactory,
 | ||
| 
 | ||
|     // Other property types:
 | ||
|     Float32,
 | ||
|     Int32,
 | ||
|     String,
 | ||
|     Color,
 | ||
|     Duration,
 | ||
|     PhysicalLength,
 | ||
|     LogicalLength,
 | ||
|     Rem,
 | ||
|     Angle,
 | ||
|     Percent,
 | ||
|     Image,
 | ||
|     Bool,
 | ||
|     /// Fake type that can represent anything that can be converted into a model.
 | ||
|     Model,
 | ||
|     PathData, // Either a vector of path elements or a two vectors of events and coordinates
 | ||
|     Easing,
 | ||
|     Brush,
 | ||
|     /// This is usually a model
 | ||
|     Array(Rc<Type>),
 | ||
|     Struct(Rc<Struct>),
 | ||
|     Enumeration(Rc<Enumeration>),
 | ||
| 
 | ||
|     /// A type made up of the product of several "unit" types.
 | ||
|     /// The first parameter is the unit, and the second parameter is the power.
 | ||
|     /// The vector should be sorted by 1) the power, 2) the unit.
 | ||
|     UnitProduct(Vec<(Unit, i8)>),
 | ||
| 
 | ||
|     ElementReference,
 | ||
| 
 | ||
|     /// This is a `SharedArray<f32>`
 | ||
|     LayoutCache,
 | ||
| }
 | ||
| 
 | ||
| impl core::cmp::PartialEq for Type {
 | ||
|     fn eq(&self, other: &Self) -> bool {
 | ||
|         match self {
 | ||
|             Type::Invalid => matches!(other, Type::Invalid),
 | ||
|             Type::Void => matches!(other, Type::Void),
 | ||
|             Type::InferredProperty => matches!(other, Type::InferredProperty),
 | ||
|             Type::InferredCallback => matches!(other, Type::InferredCallback),
 | ||
|             Type::Callback(lhs) => {
 | ||
|                 matches!(other, Type::Callback(rhs) if lhs == rhs)
 | ||
|             }
 | ||
|             Type::Function(lhs) => {
 | ||
|                 matches!(other, Type::Function(rhs) if lhs == rhs)
 | ||
|             }
 | ||
|             Type::ComponentFactory => matches!(other, Type::ComponentFactory),
 | ||
|             Type::Float32 => matches!(other, Type::Float32),
 | ||
|             Type::Int32 => matches!(other, Type::Int32),
 | ||
|             Type::String => matches!(other, Type::String),
 | ||
|             Type::Color => matches!(other, Type::Color),
 | ||
|             Type::Duration => matches!(other, Type::Duration),
 | ||
|             Type::Angle => matches!(other, Type::Angle),
 | ||
|             Type::PhysicalLength => matches!(other, Type::PhysicalLength),
 | ||
|             Type::LogicalLength => matches!(other, Type::LogicalLength),
 | ||
|             Type::Rem => matches!(other, Type::Rem),
 | ||
|             Type::Percent => matches!(other, Type::Percent),
 | ||
|             Type::Image => matches!(other, Type::Image),
 | ||
|             Type::Bool => matches!(other, Type::Bool),
 | ||
|             Type::Model => matches!(other, Type::Model),
 | ||
|             Type::PathData => matches!(other, Type::PathData),
 | ||
|             Type::Easing => matches!(other, Type::Easing),
 | ||
|             Type::Brush => matches!(other, Type::Brush),
 | ||
|             Type::Array(a) => matches!(other, Type::Array(b) if a == b),
 | ||
|             Type::Struct(lhs) => {
 | ||
|                 matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
 | ||
|             }
 | ||
|             Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
 | ||
|             Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
 | ||
|             Type::ElementReference => matches!(other, Type::ElementReference),
 | ||
|             Type::LayoutCache => matches!(other, Type::LayoutCache),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl Display for Type {
 | ||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | ||
|         match self {
 | ||
|             Type::Invalid => write!(f, "<error>"),
 | ||
|             Type::Void => write!(f, "void"),
 | ||
|             Type::InferredProperty => write!(f, "?"),
 | ||
|             Type::InferredCallback => write!(f, "callback"),
 | ||
|             Type::Callback(callback) => {
 | ||
|                 write!(f, "callback")?;
 | ||
|                 if !callback.args.is_empty() {
 | ||
|                     write!(f, "(")?;
 | ||
|                     for (i, arg) in callback.args.iter().enumerate() {
 | ||
|                         if i > 0 {
 | ||
|                             write!(f, ",")?;
 | ||
|                         }
 | ||
|                         write!(f, "{arg}")?;
 | ||
|                     }
 | ||
|                     write!(f, ")")?
 | ||
|                 }
 | ||
|                 write!(f, "-> {}", callback.return_type)?;
 | ||
|                 Ok(())
 | ||
|             }
 | ||
|             Type::ComponentFactory => write!(f, "component-factory"),
 | ||
|             Type::Function(function) => {
 | ||
|                 write!(f, "function(")?;
 | ||
|                 for (i, arg) in function.args.iter().enumerate() {
 | ||
|                     if i > 0 {
 | ||
|                         write!(f, ",")?;
 | ||
|                     }
 | ||
|                     write!(f, "{arg}")?;
 | ||
|                 }
 | ||
|                 write!(f, ") -> {}", function.return_type)
 | ||
|             }
 | ||
|             Type::Float32 => write!(f, "float"),
 | ||
|             Type::Int32 => write!(f, "int"),
 | ||
|             Type::String => write!(f, "string"),
 | ||
|             Type::Duration => write!(f, "duration"),
 | ||
|             Type::Angle => write!(f, "angle"),
 | ||
|             Type::PhysicalLength => write!(f, "physical-length"),
 | ||
|             Type::LogicalLength => write!(f, "length"),
 | ||
|             Type::Rem => write!(f, "relative-font-size"),
 | ||
|             Type::Percent => write!(f, "percent"),
 | ||
|             Type::Color => write!(f, "color"),
 | ||
|             Type::Image => write!(f, "image"),
 | ||
|             Type::Bool => write!(f, "bool"),
 | ||
|             Type::Model => write!(f, "model"),
 | ||
|             Type::Array(t) => write!(f, "[{t}]"),
 | ||
|             Type::Struct(t) => write!(f, "{t}"),
 | ||
|             Type::PathData => write!(f, "pathdata"),
 | ||
|             Type::Easing => write!(f, "easing"),
 | ||
|             Type::Brush => write!(f, "brush"),
 | ||
|             Type::Enumeration(enumeration) => write!(f, "enum {}", enumeration.name),
 | ||
|             Type::UnitProduct(vec) => {
 | ||
|                 const POWERS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
 | ||
|                 let mut x = vec.iter().map(|(unit, power)| {
 | ||
|                     if *power == 1 {
 | ||
|                         return unit.to_string();
 | ||
|                     }
 | ||
|                     let mut res = format!("{}{}", unit, if *power < 0 { "⁻" } else { "" });
 | ||
|                     let value = power.abs().to_string();
 | ||
|                     for x in value.as_bytes() {
 | ||
|                         res.push(POWERS[(x - b'0') as usize]);
 | ||
|                     }
 | ||
| 
 | ||
|                     res
 | ||
|                 });
 | ||
|                 write!(f, "({})", x.join("×"))
 | ||
|             }
 | ||
|             Type::ElementReference => write!(f, "element ref"),
 | ||
|             Type::LayoutCache => write!(f, "layout cache"),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl From<Rc<Struct>> for Type {
 | ||
|     fn from(value: Rc<Struct>) -> Self {
 | ||
|         Self::Struct(value)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl Type {
 | ||
|     /// valid type for properties
 | ||
|     pub fn is_property_type(&self) -> bool {
 | ||
|         matches!(
 | ||
|             self,
 | ||
|             Self::Float32
 | ||
|                 | Self::Int32
 | ||
|                 | Self::String
 | ||
|                 | Self::Color
 | ||
|                 | Self::ComponentFactory
 | ||
|                 | Self::Duration
 | ||
|                 | Self::Angle
 | ||
|                 | Self::PhysicalLength
 | ||
|                 | Self::LogicalLength
 | ||
|                 | Self::Rem
 | ||
|                 | Self::Percent
 | ||
|                 | Self::Image
 | ||
|                 | Self::Bool
 | ||
|                 | Self::Easing
 | ||
|                 | Self::Enumeration(_)
 | ||
|                 | Self::ElementReference
 | ||
|                 | Self::Struct { .. }
 | ||
|                 | Self::Array(_)
 | ||
|                 | Self::Brush
 | ||
|                 | Self::InferredProperty
 | ||
|         )
 | ||
|     }
 | ||
| 
 | ||
|     pub fn ok_for_public_api(&self) -> bool {
 | ||
|         !matches!(self, Self::Easing)
 | ||
|     }
 | ||
| 
 | ||
|     /// Assume it is an enumeration, panic if it isn't
 | ||
|     pub fn as_enum(&self) -> &Rc<Enumeration> {
 | ||
|         match self {
 | ||
|             Type::Enumeration(e) => e,
 | ||
|             _ => panic!("should be an enumeration, bug in compiler pass"),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Return true if the type can be converted to the other type
 | ||
|     pub fn can_convert(&self, other: &Self) -> bool {
 | ||
|         let can_convert_struct = |a: &BTreeMap<SmolStr, Type>, b: &BTreeMap<SmolStr, Type>| {
 | ||
|             // the struct `b` has property that the struct `a` doesn't
 | ||
|             let mut has_more_property = false;
 | ||
|             for (k, v) in b {
 | ||
|                 match a.get(k) {
 | ||
|                     Some(t) if !t.can_convert(v) => return false,
 | ||
|                     None => has_more_property = true,
 | ||
|                     _ => (),
 | ||
|                 }
 | ||
|             }
 | ||
|             if has_more_property {
 | ||
|                 // we should reject the conversion if `a` has property that `b` doesn't have
 | ||
|                 if a.keys().any(|k| !b.contains_key(k)) {
 | ||
|                     return false;
 | ||
|                 }
 | ||
|             }
 | ||
|             true
 | ||
|         };
 | ||
|         match (self, other) {
 | ||
|             (a, b) if a == b => true,
 | ||
|             (_, Type::Invalid)
 | ||
|             | (_, Type::Void)
 | ||
|             | (Type::Float32, Type::Int32)
 | ||
|             | (Type::Float32, Type::String)
 | ||
|             | (Type::Int32, Type::Float32)
 | ||
|             | (Type::Int32, Type::String)
 | ||
|             | (Type::Float32, Type::Model)
 | ||
|             | (Type::Int32, Type::Model)
 | ||
|             | (Type::PhysicalLength, Type::LogicalLength)
 | ||
|             | (Type::LogicalLength, Type::PhysicalLength)
 | ||
|             | (Type::Rem, Type::LogicalLength)
 | ||
|             | (Type::Rem, Type::PhysicalLength)
 | ||
|             | (Type::LogicalLength, Type::Rem)
 | ||
|             | (Type::PhysicalLength, Type::Rem)
 | ||
|             | (Type::Percent, Type::Float32)
 | ||
|             | (Type::Brush, Type::Color)
 | ||
|             | (Type::Color, Type::Brush) => true,
 | ||
|             (Type::Array(a), Type::Model) if a.is_property_type() => true,
 | ||
|             (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
 | ||
|             (Type::UnitProduct(u), o) => match o.as_unit_product() {
 | ||
|                 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
 | ||
|                 None => false,
 | ||
|             },
 | ||
|             (o, Type::UnitProduct(u)) => match o.as_unit_product() {
 | ||
|                 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
 | ||
|                 None => false,
 | ||
|             },
 | ||
|             _ => false,
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// If this is a number type which should be used with an unit, this returns the default unit
 | ||
|     /// otherwise, returns None
 | ||
|     pub fn default_unit(&self) -> Option<Unit> {
 | ||
|         match self {
 | ||
|             Type::Duration => Some(Unit::Ms),
 | ||
|             Type::PhysicalLength => Some(Unit::Phx),
 | ||
|             Type::LogicalLength => Some(Unit::Px),
 | ||
|             Type::Rem => Some(Unit::Rem),
 | ||
|             // Unit::Percent is special that it does not combine with other units like
 | ||
|             Type::Percent => None,
 | ||
|             Type::Angle => Some(Unit::Deg),
 | ||
|             Type::Invalid => None,
 | ||
|             Type::Void => None,
 | ||
|             Type::InferredProperty | Type::InferredCallback => None,
 | ||
|             Type::Callback { .. } => None,
 | ||
|             Type::ComponentFactory => None,
 | ||
|             Type::Function { .. } => None,
 | ||
|             Type::Float32 => None,
 | ||
|             Type::Int32 => None,
 | ||
|             Type::String => None,
 | ||
|             Type::Color => None,
 | ||
|             Type::Image => None,
 | ||
|             Type::Bool => None,
 | ||
|             Type::Model => None,
 | ||
|             Type::PathData => None,
 | ||
|             Type::Easing => None,
 | ||
|             Type::Brush => None,
 | ||
|             Type::Array(_) => None,
 | ||
|             Type::Struct { .. } => None,
 | ||
|             Type::Enumeration(_) => None,
 | ||
|             Type::UnitProduct(_) => None,
 | ||
|             Type::ElementReference => None,
 | ||
|             Type::LayoutCache => None,
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Return a unit product vector even for single scalar
 | ||
|     pub fn as_unit_product(&self) -> Option<Vec<(Unit, i8)>> {
 | ||
|         match self {
 | ||
|             Type::UnitProduct(u) => Some(u.clone()),
 | ||
|             Type::Float32 | Type::Int32 => Some(Vec::new()),
 | ||
|             Type::Percent => Some(Vec::new()),
 | ||
|             _ => self.default_unit().map(|u| vec![(u, 1)]),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone)]
 | ||
| pub enum BuiltinPropertyDefault {
 | ||
|     None,
 | ||
|     Expr(Expression),
 | ||
|     /// When materializing a property of this type, it will be initialized with an Expression that depends on the ElementRc
 | ||
|     WithElement(fn(&crate::object_tree::ElementRc) -> Expression),
 | ||
|     /// The property is actually not a property but a builtin function
 | ||
|     BuiltinFunction(BuiltinFunction),
 | ||
| }
 | ||
| 
 | ||
| impl BuiltinPropertyDefault {
 | ||
|     pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
 | ||
|         match self {
 | ||
|             BuiltinPropertyDefault::None => None,
 | ||
|             BuiltinPropertyDefault::Expr(expression) => Some(expression.clone()),
 | ||
|             BuiltinPropertyDefault::WithElement(init_expr) => Some(init_expr(elem)),
 | ||
|             BuiltinPropertyDefault::BuiltinFunction(..) => {
 | ||
|                 unreachable!("can't get an expression for functions")
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// Information about properties in NativeClass
 | ||
| #[derive(Debug, Clone)]
 | ||
| pub struct BuiltinPropertyInfo {
 | ||
|     /// The property type
 | ||
|     pub ty: Type,
 | ||
|     /// When != None, this is the initial value that we will have to set if no other binding were specified
 | ||
|     pub default_value: BuiltinPropertyDefault,
 | ||
|     pub property_visibility: PropertyVisibility,
 | ||
| }
 | ||
| 
 | ||
| impl BuiltinPropertyInfo {
 | ||
|     pub fn new(ty: Type) -> Self {
 | ||
|         Self {
 | ||
|             ty,
 | ||
|             default_value: BuiltinPropertyDefault::None,
 | ||
|             property_visibility: PropertyVisibility::InOut,
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     pub fn is_native_output(&self) -> bool {
 | ||
|         matches!(self.property_visibility, PropertyVisibility::InOut | PropertyVisibility::Output)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl From<BuiltinFunction> for BuiltinPropertyInfo {
 | ||
|     fn from(function: BuiltinFunction) -> Self {
 | ||
|         Self {
 | ||
|             ty: Type::Function(function.ty()),
 | ||
|             default_value: BuiltinPropertyDefault::BuiltinFunction(function),
 | ||
|             property_visibility: PropertyVisibility::Public,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /// The base of an element
 | ||
| #[derive(Clone, Debug, derive_more::From)]
 | ||
| pub enum ElementType {
 | ||
|     /// The element is based of a component
 | ||
|     Component(Rc<Component>),
 | ||
|     /// The element is a builtin element
 | ||
|     Builtin(Rc<BuiltinElement>),
 | ||
|     /// The native type was resolved by the resolve_native_class pass.
 | ||
|     Native(Rc<NativeClass>),
 | ||
|     /// The base element couldn't be looked up
 | ||
|     Error,
 | ||
|     /// This should be the base type of the root element of a global component
 | ||
|     Global,
 | ||
| }
 | ||
| 
 | ||
| impl PartialEq for ElementType {
 | ||
|     fn eq(&self, other: &Self) -> bool {
 | ||
|         match (self, other) {
 | ||
|             (Self::Component(a), Self::Component(b)) => Rc::ptr_eq(a, b),
 | ||
|             (Self::Builtin(a), Self::Builtin(b)) => Rc::ptr_eq(a, b),
 | ||
|             (Self::Native(a), Self::Native(b)) => Rc::ptr_eq(a, b),
 | ||
|             (Self::Error, Self::Error) | (Self::Global, Self::Global) => true,
 | ||
|             _ => false,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl ElementType {
 | ||
|     pub fn lookup_property<'a>(&self, name: &'a str) -> PropertyLookupResult<'a> {
 | ||
|         match self {
 | ||
|             Self::Component(c) => c.root_element.borrow().lookup_property(name),
 | ||
|             Self::Builtin(b) => {
 | ||
|                 let resolved_name =
 | ||
|                     if let Some(alias_name) = b.native_class.lookup_alias(name.as_ref()) {
 | ||
|                         Cow::Owned(alias_name.to_string())
 | ||
|                     } else {
 | ||
|                         Cow::Borrowed(name)
 | ||
|                     };
 | ||
|                 match b.properties.get(resolved_name.as_ref()) {
 | ||
|                     None => {
 | ||
|                         if b.is_non_item_type {
 | ||
|                             PropertyLookupResult::invalid(resolved_name)
 | ||
|                         } else {
 | ||
|                             crate::typeregister::reserved_property(name)
 | ||
|                         }
 | ||
|                     }
 | ||
|                     Some(p) => PropertyLookupResult {
 | ||
|                         resolved_name,
 | ||
|                         property_type: p.ty.clone(),
 | ||
|                         property_visibility: p.property_visibility,
 | ||
|                         declared_pure: None,
 | ||
|                         is_local_to_component: false,
 | ||
|                         is_in_direct_base: false,
 | ||
|                         builtin_function: match &p.default_value {
 | ||
|                             BuiltinPropertyDefault::BuiltinFunction(f) => Some(f.clone()),
 | ||
|                             _ => None,
 | ||
|                         },
 | ||
|                     },
 | ||
|                 }
 | ||
|             }
 | ||
|             Self::Native(n) => {
 | ||
|                 let resolved_name = if let Some(alias_name) = n.lookup_alias(name.as_ref()) {
 | ||
|                     Cow::Owned(alias_name.to_string())
 | ||
|                 } else {
 | ||
|                     Cow::Borrowed(name)
 | ||
|                 };
 | ||
|                 let property_type =
 | ||
|                     n.lookup_property(resolved_name.as_ref()).cloned().unwrap_or_default();
 | ||
|                 PropertyLookupResult {
 | ||
|                     resolved_name,
 | ||
|                     property_type,
 | ||
|                     property_visibility: PropertyVisibility::InOut,
 | ||
|                     declared_pure: None,
 | ||
|                     is_local_to_component: false,
 | ||
|                     is_in_direct_base: false,
 | ||
|                     builtin_function: None,
 | ||
|                 }
 | ||
|             }
 | ||
|             _ => PropertyLookupResult::invalid(Cow::Borrowed(name)),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// List of sub properties valid for the auto completion
 | ||
|     pub fn property_list(&self) -> Vec<(SmolStr, Type)> {
 | ||
|         match self {
 | ||
|             Self::Component(c) => {
 | ||
|                 let mut r = c.root_element.borrow().base_type.property_list();
 | ||
|                 r.extend(
 | ||
|                     c.root_element
 | ||
|                         .borrow()
 | ||
|                         .property_declarations
 | ||
|                         .iter()
 | ||
|                         .filter(|(_, d)| d.visibility != PropertyVisibility::Private)
 | ||
|                         .map(|(k, d)| (k.clone(), d.property_type.clone())),
 | ||
|                 );
 | ||
|                 r
 | ||
|             }
 | ||
|             Self::Builtin(b) => {
 | ||
|                 b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
 | ||
|             }
 | ||
|             Self::Native(n) => {
 | ||
|                 n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
 | ||
|             }
 | ||
|             _ => Vec::new(),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// This function looks at the element and checks whether it can have Elements of type `name` as children.
 | ||
|     /// In addition to what `accepts_child_element` does, this method also probes the type of `name`.
 | ||
|     /// It returns an Error if that is not possible or an `ElementType` if it is.
 | ||
|     pub fn lookup_type_for_child_element(
 | ||
|         &self,
 | ||
|         name: &str,
 | ||
|         tr: &TypeRegister,
 | ||
|     ) -> Result<ElementType, String> {
 | ||
|         match self {
 | ||
|             Self::Component(component) => {
 | ||
|                 let base_type = match &*component.child_insertion_point.borrow() {
 | ||
|                     Some(insert_in) => insert_in.parent.borrow().base_type.clone(),
 | ||
|                     None => {
 | ||
|                         let base_type = component.root_element.borrow().base_type.clone();
 | ||
|                         if base_type == tr.empty_type() {
 | ||
|                             return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
 | ||
|                         }
 | ||
|                         base_type
 | ||
|                     }
 | ||
|                 };
 | ||
|                 base_type.lookup_type_for_child_element(name, tr)
 | ||
|             }
 | ||
|             Self::Builtin(builtin) => {
 | ||
|                 if builtin.disallow_global_types_as_child_elements {
 | ||
|                     if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
 | ||
|                         return Ok(child_type.clone().into());
 | ||
|                     } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
 | ||
|                         return Ok(builtin.clone().into());
 | ||
|                     }
 | ||
|                     let mut valid_children: Vec<_> =
 | ||
|                         builtin.additional_accepted_child_types.keys().cloned().collect();
 | ||
|                     if builtin.additional_accept_self {
 | ||
|                         valid_children.push(builtin.native_class.class_name.clone());
 | ||
|                     }
 | ||
|                     valid_children.sort();
 | ||
| 
 | ||
|                     let err = if valid_children.is_empty() {
 | ||
|                         format!("{} cannot have children elements", builtin.native_class.class_name,)
 | ||
|                     } else {
 | ||
|                         format!(
 | ||
|                             "{} is not allowed within {}. Only {} are valid children",
 | ||
|                             name,
 | ||
|                             builtin.native_class.class_name,
 | ||
|                             valid_children.join(" ")
 | ||
|                         )
 | ||
|                     };
 | ||
|                     return Err(err);
 | ||
|                 }
 | ||
|                 let err = match tr.lookup_element(name) {
 | ||
|                     Err(e) => e,
 | ||
|                     Ok(t) => {
 | ||
|                         if !tr.expose_internal_types
 | ||
|                             && matches!(&t, Self::Builtin(e) if e.is_internal)
 | ||
|                         {
 | ||
|                             format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)")
 | ||
|                         } else {
 | ||
|                             return Ok(t);
 | ||
|                         }
 | ||
|                     }
 | ||
|                 };
 | ||
|                 if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
 | ||
|                     return Ok(child_type.clone().into());
 | ||
|                 } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
 | ||
|                     return Ok(builtin.clone().into());
 | ||
|                 }
 | ||
|                 match tr.lookup(name) {
 | ||
|                     Type::Invalid => Err(err),
 | ||
|                     ty => Err(format!("'{ty}' cannot be used as an element")),
 | ||
|                 }
 | ||
|             }
 | ||
|             _ => tr.lookup_element(name).and_then(|t| {
 | ||
|                 if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
 | ||
|                     Err(format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)"))
 | ||
|                 } else {
 | ||
|                     Ok(t)
 | ||
|                 }
 | ||
|             })
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Assume this is a builtin type, panic if it isn't
 | ||
|     pub fn as_builtin(&self) -> &BuiltinElement {
 | ||
|         match self {
 | ||
|             Self::Builtin(b) => b,
 | ||
|             Self::Component(_) => panic!("This should not happen because of inlining"),
 | ||
|             _ => panic!("invalid type"),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Assume this is a builtin type, panic if it isn't
 | ||
|     pub fn as_native(&self) -> &NativeClass {
 | ||
|         match self {
 | ||
|             Self::Native(b) => b,
 | ||
|             Self::Component(_) => {
 | ||
|                 panic!("This should not happen because of native class resolution")
 | ||
|             }
 | ||
|             _ => panic!("invalid type"),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Assume it is a Component, panic if it isn't
 | ||
|     pub fn as_component(&self) -> &Rc<Component> {
 | ||
|         match self {
 | ||
|             Self::Component(c) => c,
 | ||
|             _ => panic!("should be a component because of the repeater_component pass"),
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /// Returns the Slint type name if applicable (for example `Rectangle` or `MyButton` when `component MyButton {}` is used as `MyButton` element)
 | ||
|     pub fn type_name(&self) -> Option<&str> {
 | ||
|         match self {
 | ||
|             ElementType::Component(component) => Some(&component.id),
 | ||
|             ElementType::Builtin(b) => Some(&b.name),
 | ||
|             ElementType::Native(_) => None, // Too late, caller should call this function before the native class lowering
 | ||
|             ElementType::Error => None,
 | ||
|             ElementType::Global => None,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl Display for ElementType {
 | ||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | ||
|         match self {
 | ||
|             Self::Component(c) => c.id.fmt(f),
 | ||
|             Self::Builtin(b) => b.name.fmt(f),
 | ||
|             Self::Native(b) => b.class_name.fmt(f),
 | ||
|             Self::Error => write!(f, "<error>"),
 | ||
|             Self::Global => Ok(()),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl Default for ElementType {
 | ||
|     fn default() -> Self {
 | ||
|         Self::Error
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone, Default)]
 | ||
| pub struct NativeClass {
 | ||
|     pub parent: Option<Rc<NativeClass>>,
 | ||
|     pub class_name: SmolStr,
 | ||
|     pub cpp_vtable_getter: String,
 | ||
|     pub properties: HashMap<SmolStr, BuiltinPropertyInfo>,
 | ||
|     pub deprecated_aliases: HashMap<SmolStr, SmolStr>,
 | ||
|     pub cpp_type: Option<SmolStr>,
 | ||
|     pub rust_type_constructor: Option<SmolStr>,
 | ||
| }
 | ||
| 
 | ||
| impl NativeClass {
 | ||
|     pub fn new(class_name: &str) -> Self {
 | ||
|         let cpp_vtable_getter = format!("SLINT_GET_ITEM_VTABLE({class_name}VTable)");
 | ||
|         Self {
 | ||
|             class_name: class_name.into(),
 | ||
|             cpp_vtable_getter,
 | ||
|             properties: Default::default(),
 | ||
|             ..Default::default()
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     pub fn new_with_properties(
 | ||
|         class_name: &str,
 | ||
|         properties: impl IntoIterator<Item = (SmolStr, BuiltinPropertyInfo)>,
 | ||
|     ) -> Self {
 | ||
|         let mut class = Self::new(class_name);
 | ||
|         class.properties = properties.into_iter().collect();
 | ||
|         class
 | ||
|     }
 | ||
| 
 | ||
|     pub fn property_count(&self) -> usize {
 | ||
|         self.properties.len() + self.parent.clone().map(|p| p.property_count()).unwrap_or_default()
 | ||
|     }
 | ||
| 
 | ||
|     pub fn lookup_property(&self, name: &str) -> Option<&Type> {
 | ||
|         if let Some(bty) = self.properties.get(name) {
 | ||
|             Some(&bty.ty)
 | ||
|         } else if let Some(parent_class) = &self.parent {
 | ||
|             parent_class.lookup_property(name)
 | ||
|         } else {
 | ||
|             None
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     pub fn lookup_alias(&self, name: &str) -> Option<&str> {
 | ||
|         if let Some(alias_target) = self.deprecated_aliases.get(name) {
 | ||
|             Some(alias_target)
 | ||
|         } else if self.properties.contains_key(name) {
 | ||
|             None
 | ||
|         } else if let Some(parent_class) = &self.parent {
 | ||
|             parent_class.lookup_alias(name)
 | ||
|         } else {
 | ||
|             None
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone, Copy, PartialEq, Default)]
 | ||
| pub enum DefaultSizeBinding {
 | ||
|     /// There should not be a default binding for the size
 | ||
|     #[default]
 | ||
|     None,
 | ||
|     /// The size should default to `width:100%; height:100%`
 | ||
|     ExpandsToParentGeometry,
 | ||
|     /// The size should default to the item's implicit size
 | ||
|     ImplicitSize,
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone, Default)]
 | ||
| pub struct BuiltinElement {
 | ||
|     pub name: SmolStr,
 | ||
|     pub native_class: Rc<NativeClass>,
 | ||
|     pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
 | ||
|     /// Additional builtin element that can be accepted as child of this element
 | ||
|     /// (example `Tab` in `TabWidget`, `Row` in `GridLayout` and the path elements in `Path`)
 | ||
|     pub additional_accepted_child_types: HashMap<SmolStr, Rc<BuiltinElement>>,
 | ||
|     /// `Self` is conceptually in `additional_accepted_child_types` (which it can't otherwise that'd make a Rc loop)
 | ||
|     pub additional_accept_self: bool,
 | ||
|     pub disallow_global_types_as_child_elements: bool,
 | ||
|     /// Non-item type do not have reserved properties (x/width/rowspan/...) added to them  (eg: PropertyAnimation)
 | ||
|     pub is_non_item_type: bool,
 | ||
|     pub accepts_focus: bool,
 | ||
|     pub is_global: bool,
 | ||
|     pub default_size_binding: DefaultSizeBinding,
 | ||
|     /// When true this is an internal type not shown in the auto-completion
 | ||
|     pub is_internal: bool,
 | ||
| }
 | ||
| 
 | ||
| impl BuiltinElement {
 | ||
|     pub fn new(native_class: Rc<NativeClass>) -> Self {
 | ||
|         Self { name: native_class.class_name.clone(), native_class, ..Default::default() }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(PartialEq, Debug)]
 | ||
| pub struct PropertyLookupResult<'a> {
 | ||
|     pub resolved_name: std::borrow::Cow<'a, str>,
 | ||
|     pub property_type: Type,
 | ||
|     pub property_visibility: PropertyVisibility,
 | ||
|     pub declared_pure: Option<bool>,
 | ||
|     /// True if the property is part of the current component (for visibility purposes)
 | ||
|     pub is_local_to_component: bool,
 | ||
|     /// True if the property in the direct base of the component (for protected visibility purposes)
 | ||
|     pub is_in_direct_base: bool,
 | ||
| 
 | ||
|     /// If the property is a builtin function
 | ||
|     pub builtin_function: Option<BuiltinFunction>,
 | ||
| }
 | ||
| 
 | ||
| impl<'a> PropertyLookupResult<'a> {
 | ||
|     pub fn is_valid(&self) -> bool {
 | ||
|         self.property_type != Type::Invalid
 | ||
|     }
 | ||
| 
 | ||
|     /// Can this property be used in an assignment
 | ||
|     pub fn is_valid_for_assignment(&self) -> bool {
 | ||
|         !matches!(
 | ||
|             (self.property_visibility, self.is_local_to_component),
 | ||
|             (PropertyVisibility::Private, false)
 | ||
|                 | (PropertyVisibility::Input, true)
 | ||
|                 | (PropertyVisibility::Output, false)
 | ||
|         )
 | ||
|     }
 | ||
| 
 | ||
|     pub fn invalid(resolved_name: Cow<'a, str>) -> Self {
 | ||
|         Self {
 | ||
|             resolved_name,
 | ||
|             property_type: Type::Invalid,
 | ||
|             property_visibility: PropertyVisibility::Private,
 | ||
|             declared_pure: None,
 | ||
|             is_local_to_component: false,
 | ||
|             is_in_direct_base: false,
 | ||
|             builtin_function: None,
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone, PartialEq)]
 | ||
| pub struct Function {
 | ||
|     pub return_type: Type,
 | ||
|     pub args: Vec<Type>,
 | ||
|     /// The optional names of the arguments (empty string means not set).
 | ||
|     /// The names are not technically part of the type, but it is good to have them available for auto-completion
 | ||
|     pub arg_names: Vec<SmolStr>,
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone)]
 | ||
| pub struct Struct {
 | ||
|     pub fields: BTreeMap<SmolStr, Type>,
 | ||
|     /// When declared in .slint as  `struct Foo := { }`, then the name is "Foo"
 | ||
|     /// When there is no node, but there is a name, then it is a builtin type
 | ||
|     pub name: Option<SmolStr>,
 | ||
|     /// When declared in .slint, this is the node of the declaration.
 | ||
|     pub node: Option<syntax_nodes::ObjectType>,
 | ||
|     /// derived
 | ||
|     pub rust_attributes: Option<Vec<SmolStr>>,
 | ||
| }
 | ||
| 
 | ||
| impl Display for Struct {
 | ||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | ||
|         if let Some(name) = &self.name {
 | ||
|             if let Some(separator_pos) = name.rfind("::") {
 | ||
|                 // write the slint type and not the native type
 | ||
|                 write!(f, "{}", &name[separator_pos + 2..])
 | ||
|             } else {
 | ||
|                 write!(f, "{name}")
 | ||
|             }
 | ||
|         } else {
 | ||
|             write!(f, "{{ ")?;
 | ||
|             for (k, v) in &self.fields {
 | ||
|                 write!(f, "{k}: {v},")?;
 | ||
|             }
 | ||
|             write!(f, "}}")
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, Clone)]
 | ||
| pub struct Enumeration {
 | ||
|     pub name: SmolStr,
 | ||
|     pub values: Vec<SmolStr>,
 | ||
|     pub default_value: usize, // index in values
 | ||
|     // For non-builtins enums, this is the declaration node
 | ||
|     pub node: Option<syntax_nodes::EnumDeclaration>,
 | ||
| }
 | ||
| 
 | ||
| impl PartialEq for Enumeration {
 | ||
|     fn eq(&self, other: &Self) -> bool {
 | ||
|         self.name.eq(&other.name)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl Enumeration {
 | ||
|     pub fn default_value(self: Rc<Self>) -> EnumerationValue {
 | ||
|         EnumerationValue { value: self.default_value, enumeration: self.clone() }
 | ||
|     }
 | ||
| 
 | ||
|     pub fn try_value_from_string(self: Rc<Self>, value: &str) -> Option<EnumerationValue> {
 | ||
|         self.values.iter().enumerate().find_map(|(idx, name)| {
 | ||
|             if name == value {
 | ||
|                 Some(EnumerationValue { value: idx, enumeration: self.clone() })
 | ||
|             } else {
 | ||
|                 None
 | ||
|             }
 | ||
|         })
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Clone, Debug)]
 | ||
| pub struct EnumerationValue {
 | ||
|     pub value: usize, // index in enumeration.values
 | ||
|     pub enumeration: Rc<Enumeration>,
 | ||
| }
 | ||
| 
 | ||
| impl PartialEq for EnumerationValue {
 | ||
|     fn eq(&self, other: &Self) -> bool {
 | ||
|         Rc::ptr_eq(&self.enumeration, &other.enumeration) && self.value == other.value
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl std::fmt::Display for EnumerationValue {
 | ||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | ||
|         self.enumeration.values[self.value].fmt(f)
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| impl EnumerationValue {
 | ||
|     pub fn to_pascal_case(&self) -> String {
 | ||
|         crate::generator::to_pascal_case(&self.enumeration.values[self.value])
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| #[derive(Debug, PartialEq)]
 | ||
| pub struct LengthConversionPowers {
 | ||
|     pub rem_to_px_power: i8,
 | ||
|     pub px_to_phx_power: i8,
 | ||
| }
 | ||
| 
 | ||
| /// If the `Type::UnitProduct(a)` can be converted to `Type::UnitProduct(a)` by multiplying
 | ||
| /// by the scale factor, return that scale factor, otherwise, return None
 | ||
| pub fn unit_product_length_conversion(
 | ||
|     a: &[(Unit, i8)],
 | ||
|     b: &[(Unit, i8)],
 | ||
| ) -> Option<LengthConversionPowers> {
 | ||
|     let mut units = [0i8; 16];
 | ||
|     for (u, count) in a {
 | ||
|         units[*u as usize] += count;
 | ||
|     }
 | ||
|     for (u, count) in b {
 | ||
|         units[*u as usize] -= count;
 | ||
|     }
 | ||
| 
 | ||
|     if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
 | ||
|         return None;
 | ||
|     }
 | ||
| 
 | ||
|     if units[Unit::Rem as usize] != 0
 | ||
|         && units[Unit::Phx as usize] == -units[Unit::Rem as usize]
 | ||
|         && units[Unit::Px as usize] == 0
 | ||
|     {
 | ||
|         units[Unit::Px as usize] = -units[Unit::Rem as usize];
 | ||
|         units[Unit::Phx as usize] = -units[Unit::Rem as usize];
 | ||
|     }
 | ||
| 
 | ||
|     let result = LengthConversionPowers {
 | ||
|         rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
 | ||
|         px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
 | ||
|     };
 | ||
| 
 | ||
|     units[Unit::Px as usize] = 0;
 | ||
|     units[Unit::Phx as usize] = 0;
 | ||
|     units[Unit::Rem as usize] = 0;
 | ||
|     units.into_iter().all(|x| x == 0).then_some(result)
 | ||
| }
 | ||
| 
 | ||
| #[test]
 | ||
| fn unit_product_length_conversion_test() {
 | ||
|     use Option::None;
 | ||
|     use Unit::*;
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
 | ||
|     );
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
 | ||
|     );
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
 | ||
|     );
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(
 | ||
|             &[(Deg, 3), (Phx, 2), (Ms, -1)],
 | ||
|             &[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
 | ||
|         ),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
 | ||
|     );
 | ||
|     assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
 | ||
|     assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
 | ||
|     assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
 | ||
| 
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
 | ||
|     );
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
 | ||
|     );
 | ||
|     assert_eq!(
 | ||
|         unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
 | ||
|         Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
 | ||
|     );
 | ||
| }
 | 
