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;
10
11use crate::expression_tree::BuiltinFunction;
12use crate::langtype::{
13    BuiltinElement, BuiltinPropertyDefault, BuiltinPropertyInfo, BuiltinStruct, ElementType,
14    Enumeration, 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
50// Note: flex-align-self is also a flexbox property but is added in reserved_properties()
51// because Type::Enumeration requires a runtime Rc allocation.
52pub const RESERVED_FLEXBOXLAYOUT_PROPERTIES: &[(&str, Type)] = &[
53    ("flex-grow", Type::Float32),
54    ("flex-shrink", Type::Float32),
55    ("flex-basis", Type::LogicalLength),
56    ("flex-order", Type::Int32),
57];
58
59macro_rules! declare_enums {
60    ($( $(#[$enum_doc:meta])* $vis:vis enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => {
61        #[allow(non_snake_case)]
62        pub struct BuiltinEnums {
63            $(pub $Name : Rc<Enumeration>),*
64        }
65        impl BuiltinEnums {
66            fn new() -> Self {
67                Self {
68                    $($Name : Rc::new(Enumeration {
69                        name: stringify!($Name).replace_smolstr("_", "-"),
70                        values: vec![$(crate::generator::to_kebab_case(stringify!($Value).trim_start_matches("r#")).into()),*],
71                        default_value: 0,
72                        node: None,
73                    })),*
74                }
75            }
76            fn fill_register(&self, register: &mut TypeRegister) {
77                $(if stringify!($Name) != "PathEvent" {
78                    register.insert_type_with_name(
79                        Type::Enumeration(self.$Name.clone()),
80                        stringify!($Name).replace_smolstr("_", "-")
81                    );
82                })*
83            }
84        }
85    };
86}
87
88i_slint_common::for_each_enums!(declare_enums);
89
90pub struct BuiltinTypes {
91    pub enums: BuiltinEnums,
92    pub noarg_callback_type: Type,
93    pub strarg_callback_type: Type,
94    pub logical_point_type: Rc<Struct>,
95    pub logical_size_type: Rc<Struct>,
96    pub font_metrics_type: Type,
97    pub layout_info_type: Rc<Struct>,
98    pub state_info_type: Rc<Struct>,
99    pub gridlayout_input_data_type: Type,
100    pub path_element_type: Type,
101    pub layout_item_info_type: Type,
102    pub flexbox_layout_item_info_type: Type,
103}
104
105impl BuiltinTypes {
106    fn new() -> Self {
107        let layout_info_type = Rc::new(Struct {
108            fields: ["min", "max", "preferred"]
109                .iter()
110                .map(|s| (SmolStr::new_static(s), Type::LogicalLength))
111                .chain(
112                    ["min_percent", "max_percent", "stretch"]
113                        .iter()
114                        .map(|s| (SmolStr::new_static(s), Type::Float32)),
115                )
116                .collect(),
117            name: BuiltinStruct::LayoutInfo.into(),
118        });
119        let enums = BuiltinEnums::new();
120        let flex_align_self_type = Type::Enumeration(enums.FlexboxLayoutAlignSelf.clone());
121        Self {
122            enums,
123            logical_point_type: Rc::new(Struct {
124                fields: IntoIterator::into_iter([
125                    (SmolStr::new_static("x"), Type::LogicalLength),
126                    (SmolStr::new_static("y"), Type::LogicalLength),
127                ])
128                .collect(),
129                name: BuiltinStruct::LogicalPosition.into(),
130            }),
131            logical_size_type: Rc::new(Struct {
132                fields: IntoIterator::into_iter([
133                    (SmolStr::new_static("width"), Type::LogicalLength),
134                    (SmolStr::new_static("height"), Type::LogicalLength),
135                ])
136                .collect(),
137                name: BuiltinStruct::LogicalSize.into(),
138            }),
139            font_metrics_type: Type::Struct(Rc::new(Struct {
140                fields: IntoIterator::into_iter([
141                    (SmolStr::new_static("ascent"), Type::LogicalLength),
142                    (SmolStr::new_static("descent"), Type::LogicalLength),
143                    (SmolStr::new_static("x-height"), Type::LogicalLength),
144                    (SmolStr::new_static("cap-height"), Type::LogicalLength),
145                ])
146                .collect(),
147                name: BuiltinStruct::FontMetrics.into(),
148            })),
149            noarg_callback_type: Type::Callback(Rc::new(Function {
150                return_type: Type::Void,
151                args: Vec::new(),
152                arg_names: Vec::new(),
153            })),
154            strarg_callback_type: Type::Callback(Rc::new(Function {
155                return_type: Type::Void,
156                args: vec![Type::String],
157                arg_names: Vec::new(),
158            })),
159            layout_info_type: layout_info_type.clone(),
160            state_info_type: Rc::new(Struct {
161                fields: IntoIterator::into_iter([
162                    (SmolStr::new_static("current-state"), Type::Int32),
163                    (SmolStr::new_static("previous-state"), Type::Int32),
164                    (SmolStr::new_static("change-time"), Type::Duration),
165                ])
166                .collect(),
167                name: BuiltinStruct::StateInfo.into(),
168            }),
169            path_element_type: Type::Struct(Rc::new(Struct {
170                fields: Default::default(),
171                name: BuiltinStruct::PathElement.into(),
172            })),
173            layout_item_info_type: Type::Struct(Rc::new(Struct {
174                fields: IntoIterator::into_iter([(
175                    "constraint".into(),
176                    layout_info_type.clone().into(),
177                )])
178                .collect(),
179                name: BuiltinStruct::LayoutItemInfo.into(),
180            })),
181            flexbox_layout_item_info_type: Type::Struct(Rc::new(Struct {
182                fields: IntoIterator::into_iter([
183                    ("constraint".into(), layout_info_type.into()),
184                    ("flex-grow".into(), Type::Float32),
185                    ("flex-shrink".into(), Type::Float32),
186                    ("flex-basis".into(), Type::Float32),
187                    ("flex-align-self".into(), flex_align_self_type),
188                    ("flex-order".into(), Type::Int32),
189                ])
190                .collect(),
191                name: BuiltinStruct::FlexboxLayoutItemInfo.into(),
192            })),
193            gridlayout_input_data_type: Type::Struct(Rc::new(Struct {
194                fields: IntoIterator::into_iter([
195                    ("row".into(), Type::Int32),
196                    ("column".into(), Type::Int32),
197                    ("rowspan".into(), Type::Int32),
198                    ("colspan".into(), Type::Int32),
199                ])
200                .collect(),
201                name: BuiltinStruct::GridLayoutInputData.into(),
202            })),
203        }
204    }
205}
206
207thread_local! {
208    pub static BUILTIN: BuiltinTypes = BuiltinTypes::new();
209}
210
211const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
212    ("clip", Type::Bool),
213    ("opacity", Type::Float32),
214    ("cache-rendering-hint", Type::Bool),
215    ("visible", Type::Bool), // ("enabled", Type::Bool),
216];
217
218pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[
219    ("drop-shadow-offset-x", Type::LogicalLength),
220    ("drop-shadow-offset-y", Type::LogicalLength),
221    ("drop-shadow-blur", Type::LogicalLength),
222    ("drop-shadow-spread", Type::LogicalLength),
223    ("drop-shadow-color", Type::Color),
224];
225
226pub const RESERVED_INNER_SHADOW_PROPERTIES: &[(&str, Type)] = &[
227    ("inner-shadow-offset-x", Type::LogicalLength),
228    ("inner-shadow-offset-y", Type::LogicalLength),
229    ("inner-shadow-blur", Type::LogicalLength),
230    ("inner-shadow-spread", Type::LogicalLength),
231    ("inner-shadow-color", Type::Color),
232];
233
234pub const RESERVED_TRANSFORM_PROPERTIES: &[(&str, Type)] = &[
235    ("transform-rotation", Type::Angle),
236    ("transform-scale-x", Type::Float32),
237    ("transform-scale-y", Type::Float32),
238    ("transform-scale", Type::Float32),
239];
240
241pub fn transform_origin_property() -> (&'static str, Rc<Struct>) {
242    ("transform-origin", logical_point_type())
243}
244
245pub const DEPRECATED_ROTATION_ORIGIN_PROPERTIES: [(&str, Type); 2] =
246    [("rotation-origin-x", Type::LogicalLength), ("rotation-origin-y", Type::LogicalLength)];
247
248pub fn noarg_callback_type() -> Type {
249    BUILTIN.with(|types| types.noarg_callback_type.clone())
250}
251
252fn strarg_callback_type() -> Type {
253    BUILTIN.with(|types| types.strarg_callback_type.clone())
254}
255
256pub fn reserved_accessibility_properties() -> impl Iterator<Item = (&'static str, Type)> {
257    [
258        //("accessible-role", ...)
259        ("accessible-checkable", Type::Bool),
260        ("accessible-checked", Type::Bool),
261        ("accessible-delegate-focus", Type::Int32),
262        ("accessible-description", Type::String),
263        ("accessible-enabled", Type::Bool),
264        ("accessible-expandable", Type::Bool),
265        ("accessible-expanded", Type::Bool),
266        ("accessible-id", Type::String),
267        ("accessible-label", Type::String),
268        ("accessible-value", Type::String),
269        ("accessible-value-maximum", Type::Float32),
270        ("accessible-value-minimum", Type::Float32),
271        ("accessible-value-step", Type::Float32),
272        ("accessible-placeholder-text", Type::String),
273        ("accessible-action-default", noarg_callback_type()),
274        ("accessible-action-increment", noarg_callback_type()),
275        ("accessible-action-decrement", noarg_callback_type()),
276        ("accessible-action-set-value", strarg_callback_type()),
277        ("accessible-action-expand", noarg_callback_type()),
278        ("accessible-item-selectable", Type::Bool),
279        ("accessible-item-selected", Type::Bool),
280        ("accessible-item-index", Type::Int32),
281        ("accessible-item-count", Type::Int32),
282        ("accessible-read-only", Type::Bool),
283    ]
284    .into_iter()
285}
286
287/// list of reserved property injected in every item
288pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type, PropertyVisibility)> {
289    RESERVED_GEOMETRY_PROPERTIES
290        .iter()
291        .chain(RESERVED_LAYOUT_PROPERTIES.iter())
292        .chain(RESERVED_OTHER_PROPERTIES.iter())
293        .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter())
294        .chain(RESERVED_INNER_SHADOW_PROPERTIES.iter())
295        .chain(RESERVED_TRANSFORM_PROPERTIES.iter())
296        .chain(DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter())
297        .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input))
298        .chain(
299            std::iter::once(transform_origin_property())
300                .map(|(k, v)| (k, v.into(), PropertyVisibility::Input)),
301        )
302        .chain(reserved_accessibility_properties().map(|(k, v)| (k, v, PropertyVisibility::Input)))
303        .chain(
304            RESERVED_GRIDLAYOUT_PROPERTIES
305                .iter()
306                .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input)),
307        )
308        .chain(
309            RESERVED_FLEXBOXLAYOUT_PROPERTIES
310                .iter()
311                .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Input)),
312        )
313        // flex-align-self is a flexbox-layout property but can't be in the const array
314        // because Type::Enumeration requires a runtime Rc allocation.
315        .chain(std::iter::once((
316            "flex-align-self",
317            Type::Enumeration(BUILTIN.with(|e| e.enums.FlexboxLayoutAlignSelf.clone())),
318            PropertyVisibility::Input,
319        )))
320        .chain(IntoIterator::into_iter([
321            ("absolute-position", logical_point_type().into(), PropertyVisibility::Output),
322            ("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
323            (
324                "focus",
325                Type::Function(BuiltinFunction::SetFocusItem.ty()),
326                PropertyVisibility::Public,
327            ),
328            (
329                "clear-focus",
330                Type::Function(BuiltinFunction::ClearFocusItem.ty()),
331                PropertyVisibility::Public,
332            ),
333            (
334                "dialog-button-role",
335                Type::Enumeration(BUILTIN.with(|e| e.enums.DialogButtonRole.clone())),
336                PropertyVisibility::Constexpr,
337            ),
338            (
339                "accessible-role",
340                Type::Enumeration(BUILTIN.with(|e| e.enums.AccessibleRole.clone())),
341                PropertyVisibility::Constexpr,
342            ),
343            (
344                "accessible-orientation",
345                Type::Enumeration(BUILTIN.with(|e| e.enums.Orientation.clone())),
346                PropertyVisibility::Input,
347            ),
348            (
349                "accessible-live-region",
350                Type::Enumeration(BUILTIN.with(|e| e.enums.AccessibleLiveness.clone())),
351                PropertyVisibility::Input,
352            ),
353        ]))
354        .chain(std::iter::once(("init", noarg_callback_type(), PropertyVisibility::Private)))
355}
356
357/// lookup reserved property injected in every item
358pub fn reserved_property(name: std::borrow::Cow<'_, str>) -> PropertyLookupResult<'_> {
359    thread_local! {
360        static RESERVED_PROPERTIES: HashMap<&'static str, (Type, PropertyVisibility, Option<BuiltinFunction>)>
361            = reserved_properties().map(|(name, ty, visibility)| (name, (ty, visibility, reserved_member_function(name)))).collect();
362    }
363    if let Some((ty, visibility, builtin_function)) =
364        RESERVED_PROPERTIES.with(|reserved| reserved.get(name.as_ref()).cloned())
365    {
366        return PropertyLookupResult {
367            property_type: ty,
368            resolved_name: name,
369            is_local_to_component: false,
370            is_in_direct_base: false,
371            is_shadowable: false,
372            property_visibility: visibility,
373            declared_pure: None,
374            builtin_function,
375        };
376    }
377
378    // Report deprecated known reserved properties (maximum_width, minimum_height, ...)
379    for pre in &["min", "max"] {
380        if let Some(a) = name.strip_prefix(pre) {
381            for suf in &["width", "height"] {
382                if let Some(b) = a.strip_suffix(suf)
383                    && b == "imum-"
384                {
385                    return PropertyLookupResult {
386                        property_type: Type::LogicalLength,
387                        resolved_name: format!("{pre}-{suf}").into(),
388                        is_local_to_component: false,
389                        is_in_direct_base: false,
390                        is_shadowable: false,
391                        property_visibility: crate::object_tree::PropertyVisibility::InOut,
392                        declared_pure: None,
393                        builtin_function: None,
394                    };
395                }
396            }
397        }
398    }
399    PropertyLookupResult::invalid(name)
400}
401
402/// These member functions are injected in every time
403pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
404    for (m, e) in [
405        ("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
406        ("clear-focus", BuiltinFunction::ClearFocusItem), // match for callable "clear-focus" property
407    ] {
408        if m == name {
409            return Some(e);
410        }
411    }
412    None
413}
414
415/// All types (datatypes, internal elements, properties, ...) are stored in this type
416#[derive(Debug, Default)]
417pub struct TypeRegister {
418    /// The set of property types.
419    types: HashMap<SmolStr, Type>,
420    /// The set of element types
421    elements: HashMap<SmolStr, ElementType>,
422    supported_property_animation_types: HashSet<String>,
423    pub(crate) property_animation_type: ElementType,
424    pub(crate) empty_type: ElementType,
425    /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is
426    /// used to construct helpful error messages, such as "Row can only be within a GridLayout element".
427    context_restricted_types: HashMap<SmolStr, HashSet<SmolStr>>,
428    parent_registry: Option<Rc<RefCell<TypeRegister>>>,
429    /// If the lookup function should return types that are marked as internal
430    pub(crate) expose_internal_types: bool,
431}
432
433impl TypeRegister {
434    pub(crate) fn snapshot(&self, snapshotter: &mut typeloader::Snapshotter) -> Self {
435        Self {
436            types: self.types.clone(),
437            elements: self
438                .elements
439                .iter()
440                .map(|(k, v)| (k.clone(), snapshotter.snapshot_element_type(v)))
441                .collect(),
442            supported_property_animation_types: self.supported_property_animation_types.clone(),
443            property_animation_type: snapshotter
444                .snapshot_element_type(&self.property_animation_type),
445            empty_type: snapshotter.snapshot_element_type(&self.empty_type),
446            context_restricted_types: self.context_restricted_types.clone(),
447            parent_registry: self
448                .parent_registry
449                .as_ref()
450                .map(|tr| snapshotter.snapshot_type_register(tr)),
451            expose_internal_types: self.expose_internal_types,
452        }
453    }
454
455    /// Insert a type into the type register with its builtin type name.
456    ///
457    /// Returns false if it replaced an existing type.
458    pub fn insert_type(&mut self, t: Type) -> bool {
459        self.types.insert(t.to_smolstr(), t).is_none()
460    }
461    /// Insert a type into the type register with a specified name.
462    ///
463    /// Returns false if it replaced an existing type.
464    pub fn insert_type_with_name(&mut self, t: Type, name: SmolStr) -> bool {
465        self.types.insert(name, t).is_none()
466    }
467
468    fn builtin_internal() -> Self {
469        let mut register = TypeRegister::default();
470
471        register.insert_type(Type::Float32);
472        register.insert_type(Type::Int32);
473        register.insert_type(Type::String);
474        register.insert_type(Type::PhysicalLength);
475        register.insert_type(Type::LogicalLength);
476        register.insert_type(Type::Color);
477        register.insert_type(Type::ComponentFactory);
478        register.insert_type(Type::Duration);
479        register.insert_type(Type::Image);
480        register.insert_type(Type::Bool);
481        register.insert_type(Type::Model);
482        register.insert_type(Type::Percent);
483        register.insert_type(Type::Easing);
484        register.insert_type(Type::Angle);
485        register.insert_type(Type::Brush);
486        register.insert_type(Type::Rem);
487        register.insert_type(Type::StyledText);
488        register.insert_type(Type::Keys);
489        register.insert_type(Type::DataTransfer);
490        register.types.insert("Point".into(), logical_point_type().into());
491        register.types.insert("Size".into(), logical_size_type().into());
492
493        BUILTIN.with(|e| e.enums.fill_register(&mut register));
494
495        register.supported_property_animation_types.insert(Type::Float32.to_string());
496        register.supported_property_animation_types.insert(Type::Int32.to_string());
497        register.supported_property_animation_types.insert(Type::Color.to_string());
498        register.supported_property_animation_types.insert(Type::PhysicalLength.to_string());
499        register.supported_property_animation_types.insert(Type::LogicalLength.to_string());
500        register.supported_property_animation_types.insert(Type::Brush.to_string());
501        register.supported_property_animation_types.insert(Type::Angle.to_string());
502
503        macro_rules! register_builtin_structs {
504            ($(
505                $(#[$attr:meta])*
506                $vis:vis struct $Name:ident {
507                    $( $(#[$field_attr:meta])* $field:ident : $field_type:ident, )*
508                }
509            )*) => { $(
510                register.insert_type_with_name(Type::Struct(builtin_structs::$Name()), SmolStr::new(stringify!($Name)));
511            )* };
512        }
513        i_slint_common::for_each_builtin_structs!(register_builtin_structs);
514
515        crate::load_builtins::load_builtins(&mut register);
516
517        // Walk every builtin reachable from an exported one and register each
518        // accepted child as context-restricted to its parent, so internal types
519        // like `MenuItem` report "can only be within Menu" instead of "Unknown".
520        let mut visited: HashSet<SmolStr> = HashSet::new();
521        let mut to_visit: Vec<Rc<BuiltinElement>> = register
522            .elements
523            .values()
524            .filter_map(|e| match e {
525                ElementType::Builtin(b) => Some(b.clone()),
526                _ => None,
527            })
528            .collect();
529        while let Some(b) = to_visit.pop() {
530            let parent = b.native_class.class_name.clone();
531            if !visited.insert(parent.clone()) {
532                continue;
533            }
534            for (child_name, child_type) in &b.additional_accepted_child_types {
535                register
536                    .context_restricted_types
537                    .entry(child_name.clone())
538                    .or_default()
539                    .insert(parent.clone());
540                to_visit.push(child_type.clone());
541            }
542            if b.additional_accept_self {
543                register.context_restricted_types.entry(parent.clone()).or_default().insert(parent);
544            }
545        }
546
547        match &mut register.elements.get_mut("PopupWindow").unwrap() {
548            ElementType::Builtin(b) => {
549                let popup = Rc::get_mut(b).unwrap();
550                popup.properties.insert(
551                    "show".into(),
552                    BuiltinPropertyInfo::from(BuiltinFunction::ShowPopupWindow),
553                );
554
555                popup.properties.insert(
556                    "close".into(),
557                    BuiltinPropertyInfo::from(BuiltinFunction::ClosePopupWindow),
558                );
559
560                popup.properties.get_mut("close-on-click").unwrap().property_visibility =
561                    PropertyVisibility::Constexpr;
562
563                popup.properties.get_mut("close-policy").unwrap().property_visibility =
564                    PropertyVisibility::Constexpr;
565            }
566            _ => unreachable!(),
567        };
568
569        match &mut register.elements.get_mut("Timer").unwrap() {
570            ElementType::Builtin(b) => {
571                let timer = Rc::get_mut(b).unwrap();
572                // `start` / `stop` / `restart` are declared as stub
573                // functions in `builtins.slint` so their doc comments get
574                // picked up, then replaced here with the real builtin
575                // implementations. Carry the docs over onto the
576                // replacements.
577                for (name, func) in [
578                    ("start", BuiltinFunction::StartTimer),
579                    ("stop", BuiltinFunction::StopTimer),
580                    ("restart", BuiltinFunction::RestartTimer),
581                ] {
582                    let existing_docs = timer.properties.get(name).and_then(|p| p.docs.clone());
583                    let mut info = BuiltinPropertyInfo::from(func);
584                    info.docs = existing_docs;
585                    timer.properties.insert(name.into(), info);
586                }
587            }
588            _ => unreachable!(),
589        }
590
591        let font_metrics_prop = crate::langtype::BuiltinPropertyInfo {
592            property_visibility: PropertyVisibility::Output,
593            default_value: BuiltinPropertyDefault::WithElement(|elem| {
594                crate::expression_tree::Expression::FunctionCall {
595                    function: BuiltinFunction::ItemFontMetrics.into(),
596                    arguments: vec![crate::expression_tree::Expression::ElementReference(
597                        Rc::downgrade(elem),
598                    )],
599                    source_location: None,
600                }
601            }),
602            ..crate::langtype::BuiltinPropertyInfo::new(font_metrics_type())
603        };
604
605        match &mut register.elements.get_mut("TextInput").unwrap() {
606            ElementType::Builtin(b) => {
607                let text_input = Rc::get_mut(b).unwrap();
608                // Replace the stub function with the real builtin
609                // implementation, carrying over docs and arg names.
610                let existing = text_input.properties.get("set-selection-offsets");
611                let existing_docs = existing.and_then(|p| p.docs.clone());
612                let arg_names = existing.and_then(|p| {
613                    if let Type::Function(f) = &p.ty { Some(f.arg_names.clone()) } else { None }
614                });
615                let mut info = BuiltinPropertyInfo::from(BuiltinFunction::SetSelectionOffsets);
616                info.docs = existing_docs;
617                if let (Some(names), Type::Function(f)) = (arg_names, &info.ty) {
618                    let mut func = (**f).clone();
619                    // The BuiltinFunction type includes an implicit ElementReference
620                    // first arg; skip it to match the public-facing arg names.
621                    func.arg_names =
622                        std::iter::repeat_n(SmolStr::default(), func.args.len() - names.len())
623                            .chain(names)
624                            .collect();
625                    info.ty = Type::Function(Rc::new(func));
626                }
627                text_input.properties.insert("set-selection-offsets".into(), info);
628                text_input.properties.insert("font-metrics".into(), font_metrics_prop.clone());
629            }
630
631            _ => unreachable!(),
632        };
633
634        match &mut register.elements.get_mut("Text").unwrap() {
635            ElementType::Builtin(b) => {
636                let text = Rc::get_mut(b).unwrap();
637                text.properties.insert("font-metrics".into(), font_metrics_prop);
638            }
639
640            _ => unreachable!(),
641        };
642
643        match &mut register.elements.get_mut("Path").unwrap() {
644            ElementType::Builtin(b) => {
645                let path = Rc::get_mut(b).unwrap();
646                path.properties.get_mut("commands").unwrap().property_visibility =
647                    PropertyVisibility::Fake;
648            }
649
650            _ => unreachable!(),
651        };
652
653        match &mut register.elements.get_mut("TabWidget").unwrap() {
654            ElementType::Builtin(b) => {
655                let tabwidget = Rc::get_mut(b).unwrap();
656                tabwidget.properties.get_mut("orientation").unwrap().property_visibility =
657                    PropertyVisibility::Constexpr;
658            }
659            _ => unreachable!(),
660        }
661
662        register
663    }
664
665    #[doc(hidden)]
666    /// All builtins incl. experimental ones! Do not use in production code!
667    pub fn builtin_experimental() -> Rc<RefCell<Self>> {
668        let register = Self::builtin_internal();
669        Rc::new(RefCell::new(register))
670    }
671
672    pub fn builtin() -> Rc<RefCell<Self>> {
673        let mut register = Self::builtin_internal();
674
675        register.elements.remove("ComponentContainer").unwrap();
676        register.types.remove("component-factory").unwrap();
677
678        register.elements.remove("FlexboxLayout").unwrap();
679        register.types.remove("FlexboxLayoutDirection").unwrap();
680        register.types.remove("FlexboxLayoutAlignContent").unwrap();
681        register.types.remove("FlexboxLayoutWrap").unwrap();
682        register.types.remove("FlexboxLayoutAlignSelf").unwrap();
683
684        Rc::new(RefCell::new(register))
685    }
686
687    pub fn new(parent: &Rc<RefCell<TypeRegister>>) -> Self {
688        Self {
689            parent_registry: Some(parent.clone()),
690            expose_internal_types: parent.borrow().expose_internal_types,
691            ..Default::default()
692        }
693    }
694
695    pub fn lookup(&self, name: &str) -> Type {
696        self.types
697            .get(name)
698            .cloned()
699            .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name)))
700            .unwrap_or_default()
701    }
702
703    fn lookup_element_as_result(
704        &self,
705        name: &str,
706    ) -> Result<ElementType, HashMap<SmolStr, HashSet<SmolStr>>> {
707        match self.elements.get(name).cloned() {
708            Some(ty) => Ok(ty),
709            None => match &self.parent_registry {
710                Some(r) => r.borrow().lookup_element_as_result(name),
711                None => Err(self.context_restricted_types.clone()),
712            },
713        }
714    }
715
716    pub fn lookup_element(&self, name: &str) -> Result<ElementType, String> {
717        self.lookup_element_as_result(name).map_err(|context_restricted_types| {
718            if let Some(permitted_parent_types) = context_restricted_types.get(name) {
719                if permitted_parent_types.len() == 1 {
720                    format!(
721                        "{} can only be within a {} element",
722                        name,
723                        permitted_parent_types.iter().next().unwrap()
724                    )
725                } else {
726                    let mut elements = permitted_parent_types.iter().cloned().collect::<Vec<_>>();
727                    elements.sort();
728                    format!(
729                        "{} can only be within the following elements: {}",
730                        name,
731                        elements.join(", ")
732                    )
733                }
734            } else if let Some(ty) = self.types.get(name) {
735                format!("'{ty}' cannot be used as an element")
736            } else {
737                format!("Unknown element '{name}'")
738            }
739        })
740    }
741
742    pub fn lookup_builtin_element(&self, name: &str) -> Option<ElementType> {
743        self.parent_registry.as_ref().map_or_else(
744            || self.elements.get(name).cloned(),
745            |p| p.borrow().lookup_builtin_element(name),
746        )
747    }
748
749    pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
750        if qualified.len() != 1 {
751            return Type::Invalid;
752        }
753        self.lookup(qualified[0].as_ref())
754    }
755
756    /// Add the component with its defined name
757    ///
758    /// Returns false if there was already an element with the same name
759    pub fn add(&mut self, comp: Rc<Component>) -> bool {
760        self.add_with_name(comp.id.clone(), comp)
761    }
762
763    /// Add the component with a specified name
764    ///
765    /// Returns false if there was already an element with the same name
766    pub fn add_with_name(&mut self, name: SmolStr, comp: Rc<Component>) -> bool {
767        self.elements.insert(name, ElementType::Component(comp)).is_none()
768    }
769
770    pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {
771        self.elements.insert(builtin.name.clone(), ElementType::Builtin(builtin));
772    }
773
774    pub fn property_animation_type_for_property(&self, property_type: Type) -> ElementType {
775        if self.supported_property_animation_types.contains(&property_type.to_string()) {
776            self.property_animation_type.clone()
777        } else {
778            self.parent_registry
779                .as_ref()
780                .map(|registry| {
781                    registry.borrow().property_animation_type_for_property(property_type)
782                })
783                .unwrap_or_default()
784        }
785    }
786
787    /// Return a hashmap with all the registered type
788    pub fn all_types(&self) -> HashMap<SmolStr, Type> {
789        let mut all =
790            self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default();
791        for (k, v) in &self.types {
792            all.insert(k.clone(), v.clone());
793        }
794        all
795    }
796
797    /// Return a hashmap with all the registered element type
798    pub fn all_elements(&self) -> HashMap<SmolStr, ElementType> {
799        let mut all =
800            self.parent_registry.as_ref().map(|r| r.borrow().all_elements()).unwrap_or_default();
801        for (k, v) in &self.elements {
802            all.insert(k.clone(), v.clone());
803        }
804        all
805    }
806
807    pub fn empty_type(&self) -> ElementType {
808        match self.parent_registry.as_ref() {
809            Some(parent) => parent.borrow().empty_type(),
810            None => self.empty_type.clone(),
811        }
812    }
813}
814
815/// Type definitions for each builtin struct
816pub mod builtin_structs {
817    use super::*;
818
819    thread_local! {
820        pub static BUILTIN_STRUCTS: BuiltinStructs = BuiltinStructs::new();
821    }
822
823    #[rustfmt::skip]
824    macro_rules! map_type {
825        ($pub_type:ident, bool) => { Type::Bool };
826        ($pub_type:ident, i32) => { Type::Int32 };
827        ($pub_type:ident, f32) => { Type::Float32 };
828        ($pub_type:ident, SharedString) => { Type::String };
829        ($pub_type:ident, Image) => { Type::Image };
830        ($pub_type:ident, Coord) => { Type::LogicalLength };
831        ($pub_type:ident, Keys) => { Type::Keys };
832        ($pub_type:ident, DataTransfer) => { Type::DataTransfer };
833        ($pub_type:ident, LogicalPosition) => { Type::Struct(logical_point_type()) };
834        ($pub_type:ident, LogicalSize) => { Type::Struct(logical_size_type()) };
835        // builtin structs
836        ($pub_type:ident, KeyboardModifiers) => {
837            // Note, this references the local variable in the BuiltinStructs constructor
838            Type::Struct($pub_type.clone())
839        };
840        // builtin enums
841        ($pub_type:ident, $_:ident) => {
842            BUILTIN.with(|e| Type::Enumeration(e.enums.$pub_type.clone()))
843        };
844    }
845
846    macro_rules! declare_builtin_structs {
847        ($(
848            $(#[$attr:meta])*
849            $vis:vis struct $Name:ident {
850                $( $(#[$field_attr:meta])* $field:ident : $field_type:ident, )*
851            }
852        )*) => {
853            pub struct BuiltinStructs {
854                $(
855                #[allow(non_snake_case)]
856                $Name: Rc<Struct>
857                ),*
858            }
859            impl BuiltinStructs {
860                pub fn new() -> Self {
861                    $(
862                    #[allow(non_snake_case)]
863                    let $Name = Rc::new(Struct{
864                        fields: BTreeMap::from([
865                            $((stringify!($field).replace_smolstr("_", "-"), map_type!($field_type, $field_type))),*
866                        ]),
867                        name: BuiltinStruct::$Name.into(),
868                    });
869                    )*
870
871                    Self {
872                        $($Name),*
873                    }
874                }
875            }
876
877            impl Default for BuiltinStructs {
878                fn default() -> Self {
879                    Self::new()
880                }
881            }
882
883            $(
884            #[allow(non_snake_case)]
885            pub fn $Name() -> Rc<Struct> {
886                BUILTIN_STRUCTS.with(|types| types.$Name.clone())
887            }
888            )*
889        };
890    }
891    i_slint_common::for_each_builtin_structs!(declare_builtin_structs);
892}
893
894pub fn logical_point_type() -> Rc<Struct> {
895    BUILTIN.with(|types| types.logical_point_type.clone())
896}
897
898pub fn logical_size_type() -> Rc<Struct> {
899    BUILTIN.with(|types| types.logical_size_type.clone())
900}
901
902pub fn font_metrics_type() -> Type {
903    BUILTIN.with(|types| types.font_metrics_type.clone())
904}
905
906/// The [`Type`] for a runtime LayoutInfo structure
907pub fn layout_info_type() -> Rc<Struct> {
908    BUILTIN.with(|types| types.layout_info_type.clone())
909}
910
911/// The [`Type`] for a runtime PathElement structure
912pub fn path_element_type() -> Type {
913    BUILTIN.with(|types| types.path_element_type.clone())
914}
915
916/// The [`Type`] for a runtime LayoutItemInfo structure
917pub fn layout_item_info_type() -> Type {
918    BUILTIN.with(|types| types.layout_item_info_type.clone())
919}
920
921/// The [`Type`] for a runtime FlexboxLayoutItemInfo structure
922pub fn flexbox_layout_item_info_type() -> Type {
923    BUILTIN.with(|types| types.flexbox_layout_item_info_type.clone())
924}