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
5
6use smol_str::{format_smolstr, SmolStr, StrExt, ToSmolStr};
7use std::cell::RefCell;
8use std::collections::{BTreeMap, HashMap, HashSet};
9use std::rc::Rc;
10
11use crate::expression_tree::BuiltinFunction;
12use crate::langtype::{
13    BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, ElementType, Enumeration,
14    Function, PropertyLookupResult, Struct, Type,
15};
16use crate::object_tree::{Component, PropertyVisibility};
17use crate::typeloader;
18
19pub const RESERVED_GEOMETRY_PROPERTIES: &[(&str, Type)] = &[
20    ("x", Type::LogicalLength),
21    ("y", Type::LogicalLength),
22    ("width", Type::LogicalLength),
23    ("height", Type::LogicalLength),
24    ("z", Type::Float32),
25];
26
27pub const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[
28    ("min-width", Type::LogicalLength),
29    ("min-height", Type::LogicalLength),
30    ("max-width", Type::LogicalLength),
31    ("max-height", Type::LogicalLength),
32    ("padding", Type::LogicalLength),
33    ("padding-left", Type::LogicalLength),
34    ("padding-right", Type::LogicalLength),
35    ("padding-top", Type::LogicalLength),
36    ("padding-bottom", Type::LogicalLength),
37    ("preferred-width", Type::LogicalLength),
38    ("preferred-height", Type::LogicalLength),
39    ("horizontal-stretch", Type::Float32),
40    ("vertical-stretch", Type::Float32),
41];
42
43pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[
44    ("col", Type::Int32),
45    ("row", Type::Int32),
46    ("colspan", Type::Int32),
47    ("rowspan", Type::Int32),
48];
49
50macro_rules! declare_enums {
51    ($( $(#[$enum_doc:meta])* enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => {
52        #[allow(non_snake_case)]
53        pub struct BuiltinEnums {
54            $(pub $Name : Rc<Enumeration>),*
55        }
56        impl BuiltinEnums {
57            fn new() -> Self {
58                Self {
59                    $($Name : Rc::new(Enumeration {
60                        name: stringify!($Name).replace_smolstr("_", "-"),
61                        values: vec![$(crate::generator::to_kebab_case(stringify!($Value).trim_start_matches("r#")).into()),*],
62                        default_value: 0,
63                        node: None,
64                    })),*
65                }
66            }
67            fn fill_register(&self, register: &mut TypeRegister) {
68                $(if stringify!($Name) != "PathEvent" {
69                    register.insert_type_with_name(
70                        Type::Enumeration(self.$Name.clone()),
71                        stringify!($Name).replace_smolstr("_", "-")
72                    );
73                })*
74            }
75        }
76    };
77}
78
79i_slint_common::for_each_enums!(declare_enums);
80
81pub struct BuiltinTypes {
82    pub enums: BuiltinEnums,
83    pub noarg_callback_type: Type,
84    pub strarg_callback_type: Type,
85    pub logical_point_type: Rc<Struct>,
86    pub font_metrics_type: Type,
87    pub layout_info_type: Rc<Struct>,
88    pub path_element_type: Type,
89    pub box_layout_cell_data_type: Type,
90}
91
92impl BuiltinTypes {
93    fn new() -> Self {
94        let layout_info_type = Rc::new(Struct {
95            fields: ["min", "max", "preferred"]
96                .iter()
97                .map(|s| (SmolStr::new_static(s), Type::LogicalLength))
98                .chain(
99                    ["min_percent", "max_percent", "stretch"]
100                        .iter()
101                        .map(|s| (SmolStr::new_static(s), Type::Float32)),
102                )
103                .collect(),
104            name: Some("slint::private_api::LayoutInfo".into()),
105            node: None,
106            rust_attributes: None,
107        });
108        Self {
109            enums: BuiltinEnums::new(),
110            logical_point_type: Rc::new(Struct {
111                fields: IntoIterator::into_iter([
112                    (SmolStr::new_static("x"), Type::LogicalLength),
113                    (SmolStr::new_static("y"), Type::LogicalLength),
114                ])
115                .collect(),
116                name: Some("slint::LogicalPosition".into()),
117                node: None,
118                rust_attributes: None,
119            }),
120            font_metrics_type: Type::Struct(Rc::new(Struct {
121                fields: IntoIterator::into_iter([
122                    (SmolStr::new_static("ascent"), Type::LogicalLength),
123                    (SmolStr::new_static("descent"), Type::LogicalLength),
124                    (SmolStr::new_static("x-height"), Type::LogicalLength),
125                    (SmolStr::new_static("cap-height"), Type::LogicalLength),
126                ])
127                .collect(),
128                name: Some("slint::private_api::FontMetrics".into()),
129                node: None,
130                rust_attributes: None,
131            })),
132            noarg_callback_type: Type::Callback(Rc::new(Function {
133                return_type: Type::Void,
134                args: vec![],
135                arg_names: vec![],
136            })),
137            strarg_callback_type: Type::Callback(Rc::new(Function {
138                return_type: Type::Void,
139                args: vec![Type::String],
140                arg_names: vec![],
141            })),
142            layout_info_type: layout_info_type.clone(),
143            path_element_type: Type::Struct(Rc::new(Struct {
144                fields: Default::default(),
145                name: Some("PathElement".into()),
146                node: None,
147                rust_attributes: None,
148            })),
149            box_layout_cell_data_type: Type::Struct(Rc::new(Struct {
150                fields: IntoIterator::into_iter([("constraint".into(), layout_info_type.into())])
151                    .collect(),
152                name: Some("BoxLayoutCellData".into()),
153                node: None,
154                rust_attributes: None,
155            })),
156        }
157    }
158}
159
160thread_local! {
161    pub static BUILTIN: BuiltinTypes = BuiltinTypes::new();
162}
163
164const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
165    ("clip", Type::Bool),
166    ("opacity", Type::Float32),
167    ("cache-rendering-hint", Type::Bool),
168    ("visible", Type::Bool), // ("enabled", Type::Bool),
169];
170
171pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[
172    ("drop-shadow-offset-x", Type::LogicalLength),
173    ("drop-shadow-offset-y", Type::LogicalLength),
174    ("drop-shadow-blur", Type::LogicalLength),
175    ("drop-shadow-color", Type::Color),
176];
177
178pub const RESERVED_TRANSFORM_PROPERTIES: &[(&str, Type)] = &[
179    ("transform-rotation", Type::Angle),
180    ("transform-scale-x", Type::Float32),
181    ("transform-scale-y", Type::Float32),
182    ("transform-scale", Type::Float32),
183];
184
185pub fn transform_origin_property() -> (&'static str, Rc<Struct>) {
186    ("transform-origin", logical_point_type())
187}
188
189pub const DEPRECATED_ROTATION_ORIGIN_PROPERTIES: [(&str, Type); 2] =
190    [("rotation-origin-x", Type::LogicalLength), ("rotation-origin-y", Type::LogicalLength)];
191
192pub fn noarg_callback_type() -> Type {
193    BUILTIN.with(|types| types.noarg_callback_type.clone())
194}
195
196fn strarg_callback_type() -> Type {
197    BUILTIN.with(|types| types.strarg_callback_type.clone())
198}
199
200pub fn reserved_accessibility_properties() -> impl Iterator<Item = (&'static str, Type)> {
201    [
202        //("accessible-role", ...)
203        ("accessible-checkable", Type::Bool),
204        ("accessible-checked", Type::Bool),
205        ("accessible-delegate-focus", Type::Int32),
206        ("accessible-description", Type::String),
207        ("accessible-enabled", Type::Bool),
208        ("accessible-expandable", Type::Bool),
209        ("accessible-expanded", Type::Bool),
210        ("accessible-label", Type::String),
211        ("accessible-value", Type::String),
212        ("accessible-value-maximum", Type::Float32),
213        ("accessible-value-minimum", Type::Float32),
214        ("accessible-value-step", Type::Float32),
215        ("accessible-placeholder-text", Type::String),
216        ("accessible-action-default", noarg_callback_type()),
217        ("accessible-action-increment", noarg_callback_type()),
218        ("accessible-action-decrement", noarg_callback_type()),
219        ("accessible-action-set-value", strarg_callback_type()),
220        ("accessible-action-expand", noarg_callback_type()),
221        ("accessible-item-selectable", Type::Bool),
222        ("accessible-item-selected", Type::Bool),
223        ("accessible-item-index", Type::Int32),
224        ("accessible-item-count", Type::Int32),
225        ("accessible-read-only", Type::Bool),
226    ]
227    .into_iter()
228}
229
230/// list of reserved property injected in every item
231pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type, PropertyVisibility)> {
232    RESERVED_GEOMETRY_PROPERTIES
233        .iter()
234        .chain(RESERVED_LAYOUT_PROPERTIES.iter())
235        .chain(RESERVED_OTHER_PROPERTIES.iter())
236        .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter())
237        .chain(RESERVED_TRANSFORM_PROPERTIES.iter())
238        .chain(DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter())
239        .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input))
240        .chain(
241            std::iter::once(transform_origin_property())
242                .map(|(k, v)| (k, v.into(), PropertyVisibility::Input)),
243        )
244        .chain(reserved_accessibility_properties().map(|(k, v)| (k, v, PropertyVisibility::Input)))
245        .chain(
246            RESERVED_GRIDLAYOUT_PROPERTIES
247                .iter()
248                .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Constexpr)),
249        )
250        .chain(IntoIterator::into_iter([
251            ("absolute-position", logical_point_type().into(), PropertyVisibility::Output),
252            ("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
253            (
254                "focus",
255                Type::Function(BuiltinFunction::SetFocusItem.ty()),
256                PropertyVisibility::Public,
257            ),
258            (
259                "clear-focus",
260                Type::Function(BuiltinFunction::ClearFocusItem.ty()),
261                PropertyVisibility::Public,
262            ),
263            (
264                "dialog-button-role",
265                Type::Enumeration(BUILTIN.with(|e| e.enums.DialogButtonRole.clone())),
266                PropertyVisibility::Constexpr,
267            ),
268            (
269                "accessible-role",
270                Type::Enumeration(BUILTIN.with(|e| e.enums.AccessibleRole.clone())),
271                PropertyVisibility::Constexpr,
272            ),
273        ]))
274        .chain(std::iter::once(("init", noarg_callback_type(), PropertyVisibility::Private)))
275}
276
277/// lookup reserved property injected in every item
278pub fn reserved_property(name: std::borrow::Cow<'_, str>) -> PropertyLookupResult<'_> {
279    thread_local! {
280        static RESERVED_PROPERTIES: HashMap<&'static str, (Type, PropertyVisibility, Option<BuiltinFunction>)>
281            = reserved_properties().map(|(name, ty, visibility)| (name, (ty, visibility, reserved_member_function(name)))).collect();
282    }
283    if let Some((ty, visibility, builtin_function)) =
284        RESERVED_PROPERTIES.with(|reserved| reserved.get(name.as_ref()).cloned())
285    {
286        return PropertyLookupResult {
287            property_type: ty,
288            resolved_name: name,
289            is_local_to_component: false,
290            is_in_direct_base: false,
291            property_visibility: visibility,
292            declared_pure: None,
293            builtin_function,
294        };
295    }
296
297    // Report deprecated known reserved properties (maximum_width, minimum_height, ...)
298    for pre in &["min", "max"] {
299        if let Some(a) = name.strip_prefix(pre) {
300            for suf in &["width", "height"] {
301                if let Some(b) = a.strip_suffix(suf) {
302                    if b == "imum-" {
303                        return PropertyLookupResult {
304                            property_type: Type::LogicalLength,
305                            resolved_name: format!("{pre}-{suf}").into(),
306                            is_local_to_component: false,
307                            is_in_direct_base: false,
308                            property_visibility: crate::object_tree::PropertyVisibility::InOut,
309                            declared_pure: None,
310                            builtin_function: None,
311                        };
312                    }
313                }
314            }
315        }
316    }
317    PropertyLookupResult::invalid(name)
318}
319
320/// These member functions are injected in every time
321pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
322    for (m, e) in [
323        ("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
324        ("clear-focus", BuiltinFunction::ClearFocusItem), // match for callable "clear-focus" property
325    ] {
326        if m == name {
327            return Some(e);
328        }
329    }
330    None
331}
332
333#[derive(Debug, Default)]
334pub struct TypeRegister {
335    /// The set of property types.
336    types: HashMap<SmolStr, Type>,
337    /// The set of element types
338    elements: HashMap<SmolStr, ElementType>,
339    supported_property_animation_types: HashSet<String>,
340    pub(crate) property_animation_type: ElementType,
341    pub(crate) empty_type: ElementType,
342    /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is
343    /// used to construct helpful error messages, such as "Row can only be within a GridLayout element".
344    context_restricted_types: HashMap<SmolStr, HashSet<SmolStr>>,
345    parent_registry: Option<Rc<RefCell<TypeRegister>>>,
346    /// If the lookup function should return types that are marked as internal
347    pub(crate) expose_internal_types: bool,
348}
349
350impl TypeRegister {
351    pub(crate) fn snapshot(&self, snapshotter: &mut typeloader::Snapshotter) -> Self {
352        Self {
353            types: self.types.clone(),
354            elements: self
355                .elements
356                .iter()
357                .map(|(k, v)| (k.clone(), snapshotter.snapshot_element_type(v)))
358                .collect(),
359            supported_property_animation_types: self.supported_property_animation_types.clone(),
360            property_animation_type: snapshotter
361                .snapshot_element_type(&self.property_animation_type),
362            empty_type: snapshotter.snapshot_element_type(&self.empty_type),
363            context_restricted_types: self.context_restricted_types.clone(),
364            parent_registry: self
365                .parent_registry
366                .as_ref()
367                .map(|tr| snapshotter.snapshot_type_register(tr)),
368            expose_internal_types: self.expose_internal_types,
369        }
370    }
371
372    /// Insert a type into the type register with its builtin type name.
373    ///
374    /// Returns false if it replaced an existing type.
375    pub fn insert_type(&mut self, t: Type) -> bool {
376        self.types.insert(t.to_smolstr(), t).is_none()
377    }
378    /// Insert a type into the type register with a specified name.
379    ///
380    /// Returns false if it replaced an existing type.
381    pub fn insert_type_with_name(&mut self, t: Type, name: SmolStr) -> bool {
382        self.types.insert(name, t).is_none()
383    }
384
385    fn builtin_internal() -> Self {
386        let mut register = TypeRegister::default();
387
388        register.insert_type(Type::Float32);
389        register.insert_type(Type::Int32);
390        register.insert_type(Type::String);
391        register.insert_type(Type::PhysicalLength);
392        register.insert_type(Type::LogicalLength);
393        register.insert_type(Type::Color);
394        register.insert_type(Type::ComponentFactory);
395        register.insert_type(Type::Duration);
396        register.insert_type(Type::Image);
397        register.insert_type(Type::Bool);
398        register.insert_type(Type::Model);
399        register.insert_type(Type::Percent);
400        register.insert_type(Type::Easing);
401        register.insert_type(Type::Angle);
402        register.insert_type(Type::Brush);
403        register.insert_type(Type::Rem);
404        register.types.insert("Point".into(), logical_point_type().into());
405
406        BUILTIN.with(|e| e.enums.fill_register(&mut register));
407
408        register.supported_property_animation_types.insert(Type::Float32.to_string());
409        register.supported_property_animation_types.insert(Type::Int32.to_string());
410        register.supported_property_animation_types.insert(Type::Color.to_string());
411        register.supported_property_animation_types.insert(Type::PhysicalLength.to_string());
412        register.supported_property_animation_types.insert(Type::LogicalLength.to_string());
413        register.supported_property_animation_types.insert(Type::Brush.to_string());
414        register.supported_property_animation_types.insert(Type::Angle.to_string());
415
416        #[rustfmt::skip]
417        macro_rules! map_type {
418            ($pub_type:ident, bool) => { Type::Bool };
419            ($pub_type:ident, i32) => { Type::Int32 };
420            ($pub_type:ident, f32) => { Type::Float32 };
421            ($pub_type:ident, SharedString) => { Type::String };
422            ($pub_type:ident, Image) => { Type::Image };
423            ($pub_type:ident, Coord) => { Type::LogicalLength };
424            ($pub_type:ident, LogicalPosition) => { Type::Struct(logical_point_type()) };
425            ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
426            ($pub_type:ident, $_:ident) => {
427                BUILTIN.with(|e| Type::Enumeration(e.enums.$pub_type.clone()))
428            };
429        }
430        #[rustfmt::skip]
431        macro_rules! maybe_clone {
432            ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
433            ($pub_type:ident, $_:ident) => { $pub_type };
434        }
435        macro_rules! register_builtin_structs {
436            ($(
437                $(#[$attr:meta])*
438                struct $Name:ident {
439                    @name = $inner_name:literal
440                    export {
441                        $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ident, )*
442                    }
443                    private {
444                        $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )*
445                    }
446                }
447            )*) => { $(
448                #[allow(non_snake_case)]
449                let $Name = Type::Struct(Rc::new(Struct{
450                    fields: BTreeMap::from([
451                        $((stringify!($pub_field).replace_smolstr("_", "-"), map_type!($pub_type, $pub_type))),*
452                    ]),
453                    name: Some(format_smolstr!("{}", $inner_name)),
454                    node: None,
455                    rust_attributes: None,
456                }));
457                register.insert_type_with_name(maybe_clone!($Name, $Name), SmolStr::new(stringify!($Name)));
458            )* };
459        }
460        i_slint_common::for_each_builtin_structs!(register_builtin_structs);
461
462        crate::load_builtins::load_builtins(&mut register);
463
464        for e in register.elements.values() {
465            if let ElementType::Builtin(b) = e {
466                for accepted_child_type_name in b.additional_accepted_child_types.keys() {
467                    register
468                        .context_restricted_types
469                        .entry(accepted_child_type_name.clone())
470                        .or_default()
471                        .insert(b.native_class.class_name.clone());
472                }
473                if b.additional_accept_self {
474                    register
475                        .context_restricted_types
476                        .entry(b.native_class.class_name.clone())
477                        .or_default()
478                        .insert(b.native_class.class_name.clone());
479                }
480            }
481        }
482
483        match &mut register.elements.get_mut("PopupWindow").unwrap() {
484            ElementType::Builtin(ref mut b) => {
485                let popup = Rc::get_mut(b).unwrap();
486                popup.properties.insert(
487                    "show".into(),
488                    BuiltinPropertyInfo::from(BuiltinFunction::ShowPopupWindow),
489                );
490
491                popup.properties.insert(
492                    "close".into(),
493                    BuiltinPropertyInfo::from(BuiltinFunction::ClosePopupWindow),
494                );
495
496                popup.properties.get_mut("close-on-click").unwrap().property_visibility =
497                    PropertyVisibility::Constexpr;
498
499                popup.properties.get_mut("close-policy").unwrap().property_visibility =
500                    PropertyVisibility::Constexpr;
501            }
502            _ => unreachable!(),
503        };
504
505        match &mut register.elements.get_mut("Timer").unwrap() {
506            ElementType::Builtin(ref mut b) => {
507                let timer = Rc::get_mut(b).unwrap();
508                timer
509                    .properties
510                    .insert("start".into(), BuiltinPropertyInfo::from(BuiltinFunction::StartTimer));
511                timer
512                    .properties
513                    .insert("stop".into(), BuiltinPropertyInfo::from(BuiltinFunction::StopTimer));
514                timer.properties.insert(
515                    "restart".into(),
516                    BuiltinPropertyInfo::from(BuiltinFunction::RestartTimer),
517                );
518            }
519            _ => unreachable!(),
520        }
521
522        let font_metrics_prop = crate::langtype::BuiltinPropertyInfo {
523            ty: font_metrics_type(),
524            property_visibility: PropertyVisibility::Output,
525            default_value: BuiltinPropertyDefault::WithElement(|elem| {
526                crate::expression_tree::Expression::FunctionCall {
527                    function: BuiltinFunction::ItemFontMetrics.into(),
528                    arguments: vec![crate::expression_tree::Expression::ElementReference(
529                        Rc::downgrade(elem),
530                    )],
531                    source_location: None,
532                }
533            }),
534        };
535
536        match &mut register.elements.get_mut("TextInput").unwrap() {
537            ElementType::Builtin(ref mut b) => {
538                let text_input = Rc::get_mut(b).unwrap();
539                text_input.properties.insert(
540                    "set-selection-offsets".into(),
541                    BuiltinPropertyInfo::from(BuiltinFunction::SetSelectionOffsets),
542                );
543                text_input.properties.insert("font-metrics".into(), font_metrics_prop.clone());
544            }
545
546            _ => unreachable!(),
547        };
548
549        match &mut register.elements.get_mut("Text").unwrap() {
550            ElementType::Builtin(ref mut b) => {
551                let text = Rc::get_mut(b).unwrap();
552                text.properties.insert("font-metrics".into(), font_metrics_prop);
553            }
554
555            _ => unreachable!(),
556        };
557
558        match &mut register.elements.get_mut("Path").unwrap() {
559            ElementType::Builtin(ref mut b) => {
560                let path = Rc::get_mut(b).unwrap();
561                path.properties.get_mut("commands").unwrap().property_visibility =
562                    PropertyVisibility::Fake;
563            }
564
565            _ => unreachable!(),
566        };
567
568        register
569    }
570
571    #[doc(hidden)]
572    /// All builtins incl. experimental ones! Do not use in production code!
573    pub fn builtin_experimental() -> Rc<RefCell<Self>> {
574        let register = Self::builtin_internal();
575        Rc::new(RefCell::new(register))
576    }
577
578    pub fn builtin() -> Rc<RefCell<Self>> {
579        let mut register = Self::builtin_internal();
580
581        register.elements.remove("ComponentContainer").unwrap();
582        register.types.remove("component-factory").unwrap();
583
584        register.elements.remove("DragArea").unwrap();
585        register.elements.remove("DropArea").unwrap();
586        register.types.remove("DropEvent").unwrap(); // Also removed in xtask/src/slintdocs.rs
587
588        register.elements.remove("MarkdownText").unwrap();
589
590        Rc::new(RefCell::new(register))
591    }
592
593    pub fn new(parent: &Rc<RefCell<TypeRegister>>) -> Self {
594        Self {
595            parent_registry: Some(parent.clone()),
596            expose_internal_types: parent.borrow().expose_internal_types,
597            ..Default::default()
598        }
599    }
600
601    pub fn lookup(&self, name: &str) -> Type {
602        self.types
603            .get(name)
604            .cloned()
605            .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name)))
606            .unwrap_or_default()
607    }
608
609    fn lookup_element_as_result(
610        &self,
611        name: &str,
612    ) -> Result<ElementType, HashMap<SmolStr, HashSet<SmolStr>>> {
613        match self.elements.get(name).cloned() {
614            Some(ty) => Ok(ty),
615            None => match &self.parent_registry {
616                Some(r) => r.borrow().lookup_element_as_result(name),
617                None => Err(self.context_restricted_types.clone()),
618            },
619        }
620    }
621
622    pub fn lookup_element(&self, name: &str) -> Result<ElementType, String> {
623        self.lookup_element_as_result(name).map_err(|context_restricted_types| {
624            if let Some(permitted_parent_types) = context_restricted_types.get(name) {
625                if permitted_parent_types.len() == 1 {
626                    format!(
627                        "{} can only be within a {} element",
628                        name,
629                        permitted_parent_types.iter().next().unwrap()
630                    )
631                } else {
632                    let mut elements = permitted_parent_types.iter().cloned().collect::<Vec<_>>();
633                    elements.sort();
634                    format!(
635                        "{} can only be within the following elements: {}",
636                        name,
637                        elements.join(", ")
638                    )
639                }
640            } else if let Some(ty) = self.types.get(name) {
641                format!("'{ty}' cannot be used as an element")
642            } else {
643                format!("Unknown element '{name}'")
644            }
645        })
646    }
647
648    pub fn lookup_builtin_element(&self, name: &str) -> Option<ElementType> {
649        self.parent_registry.as_ref().map_or_else(
650            || self.elements.get(name).cloned(),
651            |p| p.borrow().lookup_builtin_element(name),
652        )
653    }
654
655    pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
656        if qualified.len() != 1 {
657            return Type::Invalid;
658        }
659        self.lookup(qualified[0].as_ref())
660    }
661
662    /// Add the component with its defined name
663    ///
664    /// Returns false if there was already an element with the same name
665    pub fn add(&mut self, comp: Rc<Component>) -> bool {
666        self.add_with_name(comp.id.clone(), comp)
667    }
668
669    /// Add the component with a specified name
670    ///
671    /// Returns false if there was already an element with the same name
672    pub fn add_with_name(&mut self, name: SmolStr, comp: Rc<Component>) -> bool {
673        self.elements.insert(name, ElementType::Component(comp)).is_none()
674    }
675
676    pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {
677        self.elements.insert(builtin.name.clone(), ElementType::Builtin(builtin));
678    }
679
680    pub fn property_animation_type_for_property(&self, property_type: Type) -> ElementType {
681        if self.supported_property_animation_types.contains(&property_type.to_string()) {
682            self.property_animation_type.clone()
683        } else {
684            self.parent_registry
685                .as_ref()
686                .map(|registry| {
687                    registry.borrow().property_animation_type_for_property(property_type)
688                })
689                .unwrap_or_default()
690        }
691    }
692
693    /// Return a hashmap with all the registered type
694    pub fn all_types(&self) -> HashMap<SmolStr, Type> {
695        let mut all =
696            self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default();
697        for (k, v) in &self.types {
698            all.insert(k.clone(), v.clone());
699        }
700        all
701    }
702
703    /// Return a hashmap with all the registered element type
704    pub fn all_elements(&self) -> HashMap<SmolStr, ElementType> {
705        let mut all =
706            self.parent_registry.as_ref().map(|r| r.borrow().all_elements()).unwrap_or_default();
707        for (k, v) in &self.elements {
708            all.insert(k.clone(), v.clone());
709        }
710        all
711    }
712
713    pub fn empty_type(&self) -> ElementType {
714        match self.parent_registry.as_ref() {
715            Some(parent) => parent.borrow().empty_type(),
716            None => self.empty_type.clone(),
717        }
718    }
719}
720
721pub fn logical_point_type() -> Rc<Struct> {
722    BUILTIN.with(|types| types.logical_point_type.clone())
723}
724
725pub fn font_metrics_type() -> Type {
726    BUILTIN.with(|types| types.font_metrics_type.clone())
727}
728
729/// The [`Type`] for a runtime LayoutInfo structure
730pub fn layout_info_type() -> Rc<Struct> {
731    BUILTIN.with(|types| types.layout_info_type.clone())
732}
733
734/// The [`Type`] for a runtime PathElement structure
735pub fn path_element_type() -> Type {
736    BUILTIN.with(|types| types.path_element_type.clone())
737}
738
739/// The [`Type`] for a runtime BoxLayoutCellData structure
740pub fn box_layout_cell_data_type() -> Type {
741    BUILTIN.with(|types| types.box_layout_cell_data_type.clone())
742}