Skip to main content

i_slint_compiler/
typeregister.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore imum noarg strarg
5
6use smol_str::{SmolStr, StrExt, ToSmolStr};
7use std::cell::RefCell;
8use std::collections::{BTreeMap, HashMap, HashSet};
9use std::rc::Rc;
10use std::sync::Arc;
11
12use crate::expression_tree::BuiltinFunction;
13use crate::langtype::{
14    BuiltinElement, BuiltinStruct, ElementType, Enumeration, Function, PropertyLookupResult,
15    Struct, Type,
16};
17use crate::object_tree::{Component, PropertyVisibility};
18use crate::typeloader;
19
20pub const RESERVED_GEOMETRY_PROPERTIES: &[(&str, Type)] = &[
21    ("x", Type::LogicalLength),
22    ("y", Type::LogicalLength),
23    ("width", Type::LogicalLength),
24    ("height", Type::LogicalLength),
25    ("z", Type::Float32),
26];
27
28pub const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[
29    ("min-width", Type::LogicalLength),
30    ("min-height", Type::LogicalLength),
31    ("max-width", Type::LogicalLength),
32    ("max-height", Type::LogicalLength),
33    ("padding", Type::LogicalLength),
34    ("padding-left", Type::LogicalLength),
35    ("padding-right", Type::LogicalLength),
36    ("padding-top", Type::LogicalLength),
37    ("padding-bottom", Type::LogicalLength),
38    ("preferred-width", Type::LogicalLength),
39    ("preferred-height", Type::LogicalLength),
40    ("horizontal-stretch", Type::Float32),
41    ("vertical-stretch", Type::Float32),
42];
43
44pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[
45    ("col", Type::Int32),
46    ("row", Type::Int32),
47    ("colspan", Type::Int32),
48    ("rowspan", Type::Int32),
49];
50
51// Per-item properties of a FlexboxLayout, HorizontalLayout or VerticalLayout cell.
52// Note: cross-axis-self-alignment is added in reserved_properties() instead,
53// because Type::Enumeration requires a runtime Arc allocation.
54pub const RESERVED_LAYOUT_CELL_PROPERTIES: &[(&str, Type)] = &[("layout-order", Type::Int32)];
55
56macro_rules! declare_enums {
57    ($( $(#[$enum_doc:meta])* $vis:vis enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => {
58        #[allow(non_snake_case)]
59        pub struct BuiltinEnums {
60            $(pub $Name : Arc<Enumeration>),*
61        }
62        impl BuiltinEnums {
63            fn new() -> Self {
64                Self { $($Name: enumeration(stringify!($Name), &[$(stringify!($Value)),*])),* }
65            }
66            fn all(&self) -> impl Iterator<Item = &Arc<Enumeration>> {
67                [$(&self.$Name),*].into_iter()
68            }
69            fn fill_register(&self, register: &mut TypeRegister) {
70                for e in self.all() {
71                    if !matches!(e.name.as_str(), "PathEvent" | "BuiltInMouseCursor") {
72                        register.insert_type_with_name(Type::Enumeration(e.clone()), e.name.clone());
73                    }
74                }
75            }
76        }
77    };
78}
79
80i_slint_common::for_each_enums!(declare_enums);
81
82fn enumeration(name: &str, values: &[&str]) -> Arc<Enumeration> {
83    Arc::new(Enumeration {
84        name: name.into(),
85        values: values
86            .iter()
87            .map(|v| crate::generator::to_kebab_case(v.trim_start_matches("r#")).into())
88            .collect(),
89        default_value: 0,
90        node: None,
91        rust_attributes: Vec::new(),
92    })
93}
94
95pub struct BuiltinTypes {
96    pub enums: BuiltinEnums,
97    pub noarg_callback_type: Type,
98    pub strarg_callback_type: Type,
99    pub set_selection_offsets_callback_type: Type,
100    pub logical_point_type: Arc<Struct>,
101    pub logical_size_type: Arc<Struct>,
102    pub layout_info_type: Arc<Struct>,
103    pub state_info_type: Arc<Struct>,
104    pub gridlayout_input_data_type: Type,
105    pub path_element_type: Type,
106    pub layout_item_info_type: Type,
107    pub flexbox_layout_item_info_type: Type,
108    pub flex_item_props_type: Type,
109}
110
111impl BuiltinTypes {
112    fn new() -> Self {
113        let layout_info_type = Arc::new(Struct::new(
114            ["min", "max", "preferred"]
115                .iter()
116                .map(|s| (SmolStr::new_static(s), Type::LogicalLength))
117                .chain(
118                    ["min_percent", "max_percent", "stretch"]
119                        .iter()
120                        .map(|s| (SmolStr::new_static(s), Type::Float32)),
121                )
122                .collect(),
123            BuiltinStruct::LayoutInfo,
124        ));
125        let enums = BuiltinEnums::new();
126        let align_self_type = Type::Enumeration(enums.CrossAxisAlignment.clone());
127        // Shared by `flex_item_props_type` and nested as `props` in
128        // `flexbox_layout_item_info_type`, so the field list is defined once.
129        let flex_item_props_struct = Arc::new(Struct::new(
130            IntoIterator::into_iter([
131                ("cross-axis-self-alignment".into(), align_self_type),
132                ("layout-order".into(), Type::Int32),
133            ])
134            .collect(),
135            BuiltinStruct::FlexItemProps,
136        ));
137        Self {
138            logical_point_type: Arc::new(Struct::new(
139                IntoIterator::into_iter([
140                    (SmolStr::new_static("x"), Type::LogicalLength),
141                    (SmolStr::new_static("y"), Type::LogicalLength),
142                ])
143                .collect(),
144                BuiltinStruct::LogicalPosition,
145            )),
146            logical_size_type: Arc::new(Struct::new(
147                IntoIterator::into_iter([
148                    (SmolStr::new_static("width"), Type::LogicalLength),
149                    (SmolStr::new_static("height"), Type::LogicalLength),
150                ])
151                .collect(),
152                BuiltinStruct::LogicalSize,
153            )),
154            noarg_callback_type: Type::Callback(Arc::new(Function {
155                return_type: Type::Void,
156                args: Vec::new(),
157                arg_names: Vec::new(),
158            })),
159            strarg_callback_type: Type::Callback(Arc::new(Function {
160                return_type: Type::Void,
161                args: vec![Type::String],
162                arg_names: Vec::new(),
163            })),
164            set_selection_offsets_callback_type: Type::Callback(Arc::new(Function {
165                return_type: Type::Void,
166                args: vec![Type::Int32, Type::Int32],
167                arg_names: vec![SmolStr::new_static("anchor"), SmolStr::new_static("focus")],
168            })),
169            layout_info_type: layout_info_type.clone(),
170            state_info_type: Arc::new(Struct::new(
171                IntoIterator::into_iter([
172                    (SmolStr::new_static("current-state"), Type::Int32),
173                    (SmolStr::new_static("previous-state"), Type::Int32),
174                    (SmolStr::new_static("change-time"), Type::Duration),
175                ])
176                .collect(),
177                BuiltinStruct::StateInfo,
178            )),
179            path_element_type: Type::Struct(Arc::new(Struct::new(
180                Default::default(),
181                BuiltinStruct::PathElement,
182            ))),
183            layout_item_info_type: Type::Struct(Arc::new(Struct::new(
184                IntoIterator::into_iter([
185                    ("constraint".into(), layout_info_type.clone().into()),
186                    (
187                        "cross-axis-self-alignment".into(),
188                        Type::Enumeration(enums.CrossAxisAlignment.clone()),
189                    ),
190                    ("layout-order".into(), Type::Int32),
191                ])
192                .collect(),
193                BuiltinStruct::LayoutItemInfo,
194            ))),
195            flexbox_layout_item_info_type: Type::Struct(Arc::new(Struct::new(
196                IntoIterator::into_iter([
197                    ("constraint".into(), layout_info_type.into()),
198                    ("props".into(), Type::Struct(flex_item_props_struct.clone())),
199                ])
200                .collect(),
201                BuiltinStruct::FlexboxLayoutItemInfo,
202            ))),
203            flex_item_props_type: Type::Struct(flex_item_props_struct),
204            gridlayout_input_data_type: Type::Struct(Arc::new(Struct::new(
205                IntoIterator::into_iter([
206                    ("row".into(), Type::Int32),
207                    ("column".into(), Type::Int32),
208                    ("rowspan".into(), Type::Int32),
209                    ("colspan".into(), Type::Int32),
210                ])
211                .collect(),
212                BuiltinStruct::GridLayoutInputData,
213            ))),
214            // Last: the field initializers above still borrow from it.
215            enums,
216        }
217    }
218}
219
220pub static BUILTIN: std::sync::LazyLock<BuiltinTypes> = std::sync::LazyLock::new(BuiltinTypes::new);
221
222const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
223    ("clip", Type::Bool),
224    ("opacity", Type::Float32),
225    ("cache-rendering-hint", Type::Bool),
226    ("visible", Type::Bool), // ("enabled", Type::Bool),
227];
228
229pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[
230    ("drop-shadow-offset-x", Type::LogicalLength),
231    ("drop-shadow-offset-y", Type::LogicalLength),
232    ("drop-shadow-blur", Type::LogicalLength),
233    ("drop-shadow-spread", Type::LogicalLength),
234    ("drop-shadow-color", Type::Color),
235];
236
237pub const RESERVED_INNER_SHADOW_PROPERTIES: &[(&str, Type)] = &[
238    ("inner-shadow-offset-x", Type::LogicalLength),
239    ("inner-shadow-offset-y", Type::LogicalLength),
240    ("inner-shadow-blur", Type::LogicalLength),
241    ("inner-shadow-spread", Type::LogicalLength),
242    ("inner-shadow-color", Type::Color),
243];
244
245pub const RESERVED_TRANSFORM_PROPERTIES: &[(&str, Type)] = &[
246    ("transform-rotation", Type::Angle),
247    ("transform-scale-x", Type::Float32),
248    ("transform-scale-y", Type::Float32),
249    ("transform-scale", Type::Float32),
250];
251
252pub fn transform_origin_property() -> (&'static str, Arc<Struct>) {
253    ("transform-origin", logical_point_type())
254}
255
256pub const DEPRECATED_ROTATION_ORIGIN_PROPERTIES: [(&str, Type); 2] =
257    [("rotation-origin-x", Type::LogicalLength), ("rotation-origin-y", Type::LogicalLength)];
258
259pub fn noarg_callback_type() -> Type {
260    BUILTIN.noarg_callback_type.clone()
261}
262
263fn strarg_callback_type() -> Type {
264    BUILTIN.strarg_callback_type.clone()
265}
266
267fn set_selection_offsets_callback_type() -> Type {
268    BUILTIN.set_selection_offsets_callback_type.clone()
269}
270
271pub fn reserved_accessibility_properties() -> impl Iterator<Item = (&'static str, Type)> {
272    [
273        //("accessible-role", ...)
274        ("accessible-checkable", Type::Bool),
275        ("accessible-checked", Type::Bool),
276        ("accessible-delegate-focus", Type::Int32),
277        ("accessible-description", Type::String),
278        ("accessible-enabled", Type::Bool),
279        ("accessible-expandable", Type::Bool),
280        ("accessible-expanded", Type::Bool),
281        ("accessible-id", Type::String),
282        ("accessible-label", Type::String),
283        ("accessible-value", Type::String),
284        ("accessible-value-maximum", Type::Float32),
285        ("accessible-value-minimum", Type::Float32),
286        ("accessible-value-step", Type::Float32),
287        ("accessible-placeholder-text", Type::String),
288        ("accessible-action-default", noarg_callback_type()),
289        ("accessible-action-increment", noarg_callback_type()),
290        ("accessible-action-decrement", noarg_callback_type()),
291        ("accessible-action-set-value", strarg_callback_type()),
292        ("accessible-action-set-selection-offsets", set_selection_offsets_callback_type()),
293        ("accessible-action-expand", noarg_callback_type()),
294        ("accessible-item-selectable", Type::Bool),
295        ("accessible-item-selected", Type::Bool),
296        ("accessible-item-index", Type::Int32),
297        ("accessible-item-count", Type::Int32),
298        ("accessible-read-only", Type::Bool),
299    ]
300    .into_iter()
301}
302
303/// list of reserved property injected in every item
304pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type, PropertyVisibility)> {
305    RESERVED_GEOMETRY_PROPERTIES
306        .iter()
307        .chain(RESERVED_LAYOUT_PROPERTIES.iter())
308        .chain(RESERVED_OTHER_PROPERTIES.iter())
309        .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter())
310        .chain(RESERVED_INNER_SHADOW_PROPERTIES.iter())
311        .chain(RESERVED_TRANSFORM_PROPERTIES.iter())
312        .chain(DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter())
313        .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input))
314        .chain(
315            std::iter::once(transform_origin_property())
316                .map(|(k, v)| (k, v.into(), PropertyVisibility::Input)),
317        )
318        .chain(reserved_accessibility_properties().map(|(k, v)| (k, v, PropertyVisibility::Input)))
319        .chain(
320            RESERVED_GRIDLAYOUT_PROPERTIES
321                .iter()
322                .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input)),
323        )
324        .chain(
325            RESERVED_LAYOUT_CELL_PROPERTIES
326                .iter()
327                .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input)),
328        )
329        // The per-item cross-axis-self-alignment (flexbox and box layouts) can't be in a
330        // const array because Type::Enumeration requires a runtime Arc allocation.
331        .chain(std::iter::once((
332            "cross-axis-self-alignment",
333            Type::Enumeration(BUILTIN.enums.CrossAxisAlignment.clone()),
334            PropertyVisibility::Input,
335        )))
336        .chain(IntoIterator::into_iter([
337            ("absolute-position", logical_point_type().into(), PropertyVisibility::Output),
338            ("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
339            (
340                "focus",
341                Type::Function(BuiltinFunction::SetFocusItem.ty()),
342                PropertyVisibility::Public,
343            ),
344            (
345                "clear-focus",
346                Type::Function(BuiltinFunction::ClearFocusItem.ty()),
347                PropertyVisibility::Public,
348            ),
349            (
350                "dialog-button-role",
351                Type::Enumeration(BUILTIN.enums.DialogButtonRole.clone()),
352                PropertyVisibility::Constexpr,
353            ),
354            (
355                "accessible-role",
356                Type::Enumeration(BUILTIN.enums.AccessibleRole.clone()),
357                PropertyVisibility::Constexpr,
358            ),
359            (
360                "accessible-orientation",
361                Type::Enumeration(BUILTIN.enums.Orientation.clone()),
362                PropertyVisibility::Input,
363            ),
364            (
365                "accessible-live-region",
366                Type::Enumeration(BUILTIN.enums.AccessibleLiveness.clone()),
367                PropertyVisibility::Input,
368            ),
369        ]))
370        .chain(std::iter::once(("init", noarg_callback_type(), PropertyVisibility::Private)))
371}
372
373/// lookup reserved property injected in every item
374pub fn reserved_property(name: std::borrow::Cow<'_, str>) -> PropertyLookupResult<'_> {
375    static RESERVED_PROPERTIES: std::sync::LazyLock<
376        HashMap<&'static str, (Type, PropertyVisibility, Option<BuiltinFunction>)>,
377    > = std::sync::LazyLock::new(|| {
378        reserved_properties()
379            .map(|(name, ty, visibility)| (name, (ty, visibility, reserved_member_function(name))))
380            .collect()
381    });
382    if let Some((ty, visibility, builtin_function)) =
383        RESERVED_PROPERTIES.get(name.as_ref()).cloned()
384    {
385        return PropertyLookupResult {
386            property_type: ty,
387            is_slint_sc: matches!(name.as_ref(), "x" | "y" | "width" | "height"),
388            resolved_name: name,
389            is_local_to_component: false,
390            is_in_direct_base: false,
391            is_shadowable: false,
392            property_visibility: visibility,
393            declared_pure: None,
394            builtin_function,
395            internal_name: None,
396            deprecated: None,
397        };
398    }
399
400    // Report deprecated known reserved properties (maximum_width, minimum_height, ...)
401    for pre in &["min", "max"] {
402        if let Some(a) = name.strip_prefix(pre) {
403            for suf in &["width", "height"] {
404                if let Some(b) = a.strip_suffix(suf)
405                    && b == "imum-"
406                {
407                    return PropertyLookupResult {
408                        property_type: Type::LogicalLength,
409                        resolved_name: format!("{pre}-{suf}").into(),
410                        is_local_to_component: false,
411                        is_in_direct_base: false,
412                        is_shadowable: false,
413                        property_visibility: crate::object_tree::PropertyVisibility::InOut,
414                        declared_pure: None,
415                        builtin_function: None,
416                        is_slint_sc: false,
417                        internal_name: None,
418                        deprecated: None,
419                    };
420                }
421            }
422        }
423    }
424    PropertyLookupResult::invalid(name)
425}
426
427/// These member functions are injected in every time
428pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
429    for (m, e) in [
430        ("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
431        ("clear-focus", BuiltinFunction::ClearFocusItem), // match for callable "clear-focus" property
432    ] {
433        if m == name {
434            return Some(e);
435        }
436    }
437    None
438}
439
440/// All types (datatypes, internal elements, properties, ...) are stored in this type
441#[derive(Debug, Default)]
442pub struct TypeRegister {
443    /// The set of property types.
444    types: HashMap<SmolStr, Type>,
445    /// The set of element types
446    elements: HashMap<SmolStr, ElementType>,
447    supported_property_animation_types: HashSet<String>,
448    pub(crate) property_animation_type: ElementType,
449    pub(crate) empty_type: ElementType,
450    /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is
451    /// used to construct helpful error messages, such as "Row can only be within a GridLayout element".
452    pub(crate) context_restricted_types: HashMap<SmolStr, HashSet<SmolStr>>,
453    parent_registry: Option<Rc<RefCell<TypeRegister>>>,
454    /// If the lookup function should return types that are marked as internal
455    pub(crate) expose_internal_types: bool,
456}
457
458impl TypeRegister {
459    pub(crate) fn snapshot(&self, snapshotter: &mut typeloader::Snapshotter) -> Self {
460        Self {
461            types: self.types.clone(),
462            elements: self
463                .elements
464                .iter()
465                .map(|(k, v)| (k.clone(), snapshotter.snapshot_element_type(v)))
466                .collect(),
467            supported_property_animation_types: self.supported_property_animation_types.clone(),
468            property_animation_type: snapshotter
469                .snapshot_element_type(&self.property_animation_type),
470            empty_type: snapshotter.snapshot_element_type(&self.empty_type),
471            context_restricted_types: self.context_restricted_types.clone(),
472            parent_registry: self
473                .parent_registry
474                .as_ref()
475                .map(|tr| snapshotter.snapshot_type_register(tr)),
476            expose_internal_types: self.expose_internal_types,
477        }
478    }
479
480    /// Insert a type into the type register with its builtin type name.
481    ///
482    /// Returns false if it replaced an existing type.
483    pub fn insert_type(&mut self, t: Type) -> bool {
484        self.types.insert(t.to_smolstr(), t).is_none()
485    }
486    /// Insert a type into the type register with a specified name.
487    ///
488    /// Returns false if it replaced an existing type.
489    pub fn insert_type_with_name(&mut self, t: Type, name: SmolStr) -> bool {
490        self.types.insert(name, t).is_none()
491    }
492
493    fn builtin_internal() -> Self {
494        let mut register = TypeRegister::default();
495
496        register.insert_type(Type::Float32);
497        register.insert_type(Type::Int32);
498        register.insert_type(Type::String);
499        register.insert_type(Type::PhysicalLength);
500        register.insert_type(Type::LogicalLength);
501        register.insert_type(Type::Color);
502        register.insert_type(Type::ComponentFactory);
503        register.insert_type(Type::Duration);
504        register.insert_type(Type::Image);
505        register.insert_type(Type::Bool);
506        register.insert_type(Type::Model);
507        register.insert_type(Type::Percent);
508        register.insert_type(Type::Easing);
509        register.insert_type(Type::Angle);
510        register.insert_type(Type::Brush);
511        register.insert_type(Type::Rem);
512        register.insert_type(Type::StyledText);
513        register.insert_type(Type::Keys);
514        register.insert_type(Type::DataTransfer);
515        register.insert_type(Type::MouseCursor);
516        register.types.insert("Point".into(), logical_point_type().into());
517        register.types.insert("Size".into(), logical_size_type().into());
518
519        BUILTIN.enums.fill_register(&mut register);
520
521        register.supported_property_animation_types.insert(Type::Float32.to_string());
522        register.supported_property_animation_types.insert(Type::Int32.to_string());
523        register.supported_property_animation_types.insert(Type::Color.to_string());
524        register.supported_property_animation_types.insert(Type::PhysicalLength.to_string());
525        register.supported_property_animation_types.insert(Type::LogicalLength.to_string());
526        register.supported_property_animation_types.insert(Type::Brush.to_string());
527        register.supported_property_animation_types.insert(Type::Angle.to_string());
528
529        macro_rules! register_builtin_structs {
530            ($(
531                $(#[$attr:meta])*
532                $vis:vis struct $Name:ident {
533                    $( $(#[$field_attr:meta])* $field:ident : $field_type:ident $(= $field_default:expr)?, )*
534                }
535            )*) => { $(
536                register.insert_type_with_name(Type::Struct(builtin_structs::$Name()), SmolStr::new(stringify!($Name)));
537            )* };
538        }
539        i_slint_common::for_each_builtin_structs!(register_builtin_structs);
540
541        crate::builtin_elements::load(&mut register);
542
543        register
544    }
545
546    #[doc(hidden)]
547    /// All builtins incl. experimental ones! Do not use in production code!
548    pub fn builtin_experimental() -> Rc<RefCell<Self>> {
549        let register = Self::builtin_internal();
550        Rc::new(RefCell::new(register))
551    }
552
553    pub fn builtin() -> Rc<RefCell<Self>> {
554        let mut register = Self::builtin_internal();
555
556        register.elements.remove("ComponentContainer").unwrap();
557        register.types.remove("component-factory").unwrap();
558
559        Rc::new(RefCell::new(register))
560    }
561
562    pub fn new(parent: &Rc<RefCell<TypeRegister>>) -> Self {
563        Self {
564            parent_registry: Some(parent.clone()),
565            expose_internal_types: parent.borrow().expose_internal_types,
566            ..Default::default()
567        }
568    }
569
570    pub fn lookup(&self, name: &str) -> Type {
571        self.types
572            .get(name)
573            .cloned()
574            .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name)))
575            .unwrap_or_default()
576    }
577
578    fn lookup_element_as_result(
579        &self,
580        name: &str,
581    ) -> Result<ElementType, HashMap<SmolStr, HashSet<SmolStr>>> {
582        match self.elements.get(name).cloned() {
583            Some(ty) => Ok(ty),
584            None => match &self.parent_registry {
585                Some(r) => r.borrow().lookup_element_as_result(name),
586                None => Err(self.context_restricted_types.clone()),
587            },
588        }
589    }
590
591    pub fn lookup_element(&self, name: &str) -> Result<ElementType, String> {
592        self.lookup_element_as_result(name).map_err(|context_restricted_types| {
593            if let Some(permitted_parent_types) = context_restricted_types.get(name) {
594                if permitted_parent_types.len() == 1 {
595                    format!(
596                        "{} can only be within a {} element",
597                        name,
598                        permitted_parent_types.iter().next().unwrap()
599                    )
600                } else {
601                    let mut elements = permitted_parent_types.iter().cloned().collect::<Vec<_>>();
602                    elements.sort();
603                    format!(
604                        "{} can only be within the following elements: {}",
605                        name,
606                        elements.join(", ")
607                    )
608                }
609            } else if let Some(ty) = self.types.get(name) {
610                format!("'{ty}' cannot be used as an element")
611            } else {
612                format!("Unknown element '{name}'")
613            }
614        })
615    }
616
617    pub fn lookup_builtin_element(&self, name: &str) -> Option<ElementType> {
618        self.parent_registry.as_ref().map_or_else(
619            || self.elements.get(name).cloned(),
620            |p| p.borrow().lookup_builtin_element(name),
621        )
622    }
623
624    pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
625        if qualified.len() != 1 {
626            return Type::Invalid;
627        }
628        self.lookup(qualified[0].as_ref())
629    }
630
631    /// Add the component with its defined name
632    ///
633    /// Returns false if there was already an element with the same name
634    pub fn add(&mut self, comp: Rc<Component>) -> bool {
635        self.add_with_name(comp.id.clone(), comp)
636    }
637
638    /// Add the component with a specified name
639    ///
640    /// Returns false if there was already an element with the same name
641    pub fn add_with_name(&mut self, name: SmolStr, comp: Rc<Component>) -> bool {
642        self.elements.insert(name, ElementType::Component(comp)).is_none()
643    }
644
645    pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {
646        self.elements.insert(builtin.name.clone(), ElementType::Builtin(builtin));
647    }
648
649    pub fn property_animation_type_for_property(&self, property_type: Type) -> ElementType {
650        if self.supported_property_animation_types.contains(&property_type.to_string()) {
651            self.property_animation_type.clone()
652        } else {
653            self.parent_registry
654                .as_ref()
655                .map(|registry| {
656                    registry.borrow().property_animation_type_for_property(property_type)
657                })
658                .unwrap_or_default()
659        }
660    }
661
662    /// Return a hashmap with all the registered type
663    pub fn all_types(&self) -> HashMap<SmolStr, Type> {
664        let mut all =
665            self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default();
666        for (k, v) in &self.types {
667            all.insert(k.clone(), v.clone());
668        }
669        all
670    }
671
672    /// Return a hashmap with all the registered element type
673    pub fn all_elements(&self) -> HashMap<SmolStr, ElementType> {
674        let mut all =
675            self.parent_registry.as_ref().map(|r| r.borrow().all_elements()).unwrap_or_default();
676        for (k, v) in &self.elements {
677            all.insert(k.clone(), v.clone());
678        }
679        all
680    }
681
682    pub fn empty_type(&self) -> ElementType {
683        match self.parent_registry.as_ref() {
684            Some(parent) => parent.borrow().empty_type(),
685            None => self.empty_type.clone(),
686        }
687    }
688}
689
690/// Type definitions for each builtin struct
691pub mod builtin_structs {
692    use super::*;
693    use crate::langtype::ConstantExpression;
694
695    pub static BUILTIN_STRUCTS: std::sync::LazyLock<BuiltinStructs> =
696        std::sync::LazyLock::new(BuiltinStructs::new);
697
698    #[rustfmt::skip]
699    macro_rules! map_type {
700        ($pub_type:ident, bool) => { Type::Bool };
701        ($pub_type:ident, i32) => { Type::Int32 };
702        ($pub_type:ident, f32) => { Type::Float32 };
703        ($pub_type:ident, SharedString) => { Type::String };
704        ($pub_type:ident, Image) => { Type::Image };
705        ($pub_type:ident, Coord) => { Type::LogicalLength };
706        ($pub_type:ident, Keys) => { Type::Keys };
707        ($pub_type:ident, DataTransfer) => { Type::DataTransfer };
708        ($pub_type:ident, LogicalPosition) => { Type::Struct(logical_point_type()) };
709        // A builtin struct declared earlier: `$pub_type` names the local of `BuiltinStructs::new`
710        ($pub_type:ident, KeyboardModifiers) => { Type::Struct($pub_type.clone()) };
711        ($pub_type:ident, $enum:ident) => { Type::Enumeration(BUILTIN.enums.$enum.clone()) };
712    }
713
714    #[rustfmt::skip]
715    macro_rules! field_default {
716        () => { None };
717        (true) => { Some(ConstantExpression::BoolLiteral(true)) };
718        (false) => { Some(ConstantExpression::BoolLiteral(false)) };
719        ($enum:ident :: $value:ident) => {
720            Some(ConstantExpression::EnumerationValue(
721                BUILTIN.enums.$enum.clone()
722                    .try_value_from_string(&crate::generator::to_kebab_case(stringify!($value)))
723                    .expect(concat!("unknown enum variant in field default ", stringify!($enum), "::", stringify!($value))),
724            ))
725        };
726        (($($tt:tt)*)) => { field_default!($($tt)*) };
727    }
728
729    macro_rules! declare_builtin_structs {
730        ($(
731            $(#[$attr:meta])*
732            $vis:vis struct $Name:ident {
733                $( $(#[$field_attr:meta])* $field:ident : $field_type:ident $(= $field_default:tt)?, )*
734            }
735        )*) => {
736            pub struct BuiltinStructs {
737                $(
738                #[allow(non_snake_case)]
739                $Name: Arc<Struct>
740                ),*
741            }
742            impl BuiltinStructs {
743                pub fn new() -> Self {
744                    $(
745                        #[allow(non_snake_case)]
746                        let $Name = build_struct(BuiltinStruct::$Name, &[$(
747                            (stringify!($field), map_type!($field_type, $field_type), field_default!($($field_default)?)),
748                        )*]);
749                    )*
750                    Self { $($Name),* }
751                }
752            }
753
754            impl Default for BuiltinStructs {
755                fn default() -> Self {
756                    Self::new()
757                }
758            }
759
760            $(
761            #[allow(non_snake_case)]
762            pub fn $Name() -> Arc<Struct> {
763                BUILTIN_STRUCTS.$Name.clone()
764            }
765            )*
766        };
767    }
768    i_slint_common::for_each_builtin_structs!(declare_builtin_structs);
769
770    fn build_struct(
771        name: BuiltinStruct,
772        fields: &[(&str, Type, Option<ConstantExpression>)],
773    ) -> Arc<Struct> {
774        let mut s =
775            Struct { fields: BTreeMap::new(), field_defaults: BTreeMap::new(), name: name.into() };
776        for (field, ty, default) in fields {
777            let field = field.replace_smolstr("_", "-");
778            if let Some(default) = default {
779                s.field_defaults.insert(field.clone(), default.clone());
780            }
781            s.fields.insert(field, ty.clone());
782        }
783        Arc::new(s)
784    }
785}
786
787pub fn logical_point_type() -> Arc<Struct> {
788    BUILTIN.logical_point_type.clone()
789}
790
791pub fn logical_size_type() -> Arc<Struct> {
792    BUILTIN.logical_size_type.clone()
793}
794
795pub fn font_metrics_type() -> Type {
796    Type::Struct(builtin_structs::FontMetrics())
797}
798
799/// The [`Type`] for a runtime LayoutInfo structure
800pub fn layout_info_type() -> Arc<Struct> {
801    BUILTIN.layout_info_type.clone()
802}
803
804/// The [`Type`] for a runtime PathElement structure
805pub fn path_element_type() -> Type {
806    BUILTIN.path_element_type.clone()
807}
808
809/// The [`Type`] for a runtime LayoutItemInfo structure
810pub fn layout_item_info_type() -> Type {
811    BUILTIN.layout_item_info_type.clone()
812}
813
814/// The [`Type`] for a runtime FlexboxLayoutItemInfo structure
815pub fn flexbox_layout_item_info_type() -> Type {
816    BUILTIN.flexbox_layout_item_info_type.clone()
817}
818
819/// The [`Type`] for a runtime FlexItemProps structure
820pub fn flex_item_props_type() -> Type {
821    BUILTIN.flex_item_props_type.clone()
822}