sixtyfps_compilerlib/
typeregister.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
3
4use std::cell::RefCell;
5use std::collections::{HashMap, HashSet};
6use std::rc::Rc;
7
8use crate::expression_tree::{BuiltinFunction, Expression};
9use crate::langtype::{BuiltinPropertyInfo, Enumeration, PropertyLookupResult, Type};
10use crate::object_tree::Component;
11
12pub(crate) const RESERVED_GEOMETRY_PROPERTIES: &[(&str, Type)] = &[
13    ("x", Type::LogicalLength),
14    ("y", Type::LogicalLength),
15    ("width", Type::LogicalLength),
16    ("height", Type::LogicalLength),
17    ("z", Type::Float32),
18];
19
20const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[
21    ("min-width", Type::LogicalLength),
22    ("min-height", Type::LogicalLength),
23    ("max-width", Type::LogicalLength),
24    ("max-height", Type::LogicalLength),
25    ("padding", Type::LogicalLength),
26    ("padding-left", Type::LogicalLength),
27    ("padding-right", Type::LogicalLength),
28    ("padding-top", Type::LogicalLength),
29    ("padding-bottom", Type::LogicalLength),
30    ("preferred-width", Type::LogicalLength),
31    ("preferred-height", Type::LogicalLength),
32    ("horizontal-stretch", Type::Float32),
33    ("vertical-stretch", Type::Float32),
34    ("col", Type::Int32),
35    ("row", Type::Int32),
36    ("colspan", Type::Int32),
37    ("rowspan", Type::Int32),
38];
39
40thread_local! {
41    pub static DIALOG_BUTTON_ROLE_ENUM: Rc<Enumeration> =
42        Rc::new(Enumeration {
43            name: "DialogButtonRole".into(),
44            values: IntoIterator::into_iter([
45                "none".to_owned(),
46                "accept".to_owned(),
47                "reject".to_owned(),
48                "apply".to_owned(),
49                "reset".to_owned(),
50                "action".to_owned(),
51                "help".to_owned(),
52            ])
53            .collect(),
54            default_value: 0,
55        });
56
57    pub static LAYOUT_ALIGNMENT_ENUM: Rc<Enumeration> =
58        Rc::new(Enumeration {
59            name: "LayoutAlignment".into(),
60            values: IntoIterator::into_iter(
61                ["stretch", "center", "start", "end", "space-between", "space-around"]
62            ).map(String::from).collect(),
63            default_value: 0,
64        });
65
66    pub static PATH_EVENT_ENUM: Rc<Enumeration> =
67    Rc::new(Enumeration {
68        name: "PathEvent".into(),
69        values: IntoIterator::into_iter(
70            ["begin", "line", "quadratic", "cubic", "end_open", "end_closed"]
71        ).map(String::from).collect(),
72        default_value: 0,
73    });
74}
75
76const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
77    ("clip", Type::Bool),
78    ("opacity", Type::Float32),
79    ("visible", Type::Bool), // ("enabled", Type::Bool),
80];
81
82pub(crate) const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[
83    ("drop-shadow-offset-x", Type::LogicalLength),
84    ("drop-shadow-offset-y", Type::LogicalLength),
85    ("drop-shadow-blur", Type::LogicalLength),
86    ("drop-shadow-color", Type::Color),
87];
88
89/// list of reserved property injected in every item
90pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type)> {
91    RESERVED_GEOMETRY_PROPERTIES
92        .iter()
93        .chain(RESERVED_LAYOUT_PROPERTIES.iter())
94        .chain(RESERVED_OTHER_PROPERTIES.iter())
95        .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter())
96        .map(|(k, v)| (*k, v.clone()))
97        .chain(IntoIterator::into_iter([
98            ("forward-focus", Type::ElementReference),
99            ("focus", BuiltinFunction::SetFocusItem.ty()),
100            ("dialog-button-role", Type::Enumeration(DIALOG_BUTTON_ROLE_ENUM.with(|e| e.clone()))),
101        ]))
102}
103
104/// lookup reserved property injected in every item
105pub fn reserved_property(name: &str) -> PropertyLookupResult {
106    for (p, t) in reserved_properties() {
107        if p == name {
108            return PropertyLookupResult { property_type: t, resolved_name: name.into() };
109        }
110    }
111
112    // Report deprecated known reserved properties (maximum_width, minimum_height, ...)
113    for pre in &["min", "max"] {
114        if let Some(a) = name.strip_prefix(pre) {
115            for suf in &["width", "height"] {
116                if let Some(b) = a.strip_suffix(suf) {
117                    if b == "imum-" {
118                        return PropertyLookupResult {
119                            property_type: Type::LogicalLength,
120                            resolved_name: format!("{}-{}", pre, suf).into(),
121                        };
122                    }
123                }
124            }
125        }
126    }
127    PropertyLookupResult { resolved_name: name.into(), property_type: Type::Invalid }
128}
129
130/// These member functions are injected in every time
131pub fn reserved_member_function(name: &str) -> Expression {
132    for (m, e) in [
133        ("focus", Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem, None)), // match for callable "focus" property
134    ]
135    .iter()
136    {
137        if *m == name {
138            return e.clone();
139        }
140    }
141    Expression::Invalid
142}
143
144#[derive(Debug, Default)]
145pub struct TypeRegister {
146    /// The set of types.
147    types: HashMap<String, Type>,
148    supported_property_animation_types: HashSet<String>,
149    pub(crate) property_animation_type: Type,
150    /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is
151    /// used to construct helpful error messages, such as "Row can only be within a GridLayout element".
152    context_restricted_types: HashMap<String, HashSet<String>>,
153    parent_registry: Option<Rc<RefCell<TypeRegister>>>,
154    /// If the lookup function should return types that are marked as internal
155    pub(crate) expose_internal_types: bool,
156}
157
158impl TypeRegister {
159    /// FIXME: same as 'add' ?
160    pub fn insert_type(&mut self, t: Type) {
161        self.types.insert(t.to_string(), t);
162    }
163    pub fn insert_type_with_name(&mut self, t: Type, name: String) {
164        self.types.insert(name, t);
165    }
166
167    pub fn builtin() -> Rc<RefCell<Self>> {
168        let mut register = TypeRegister::default();
169
170        register.insert_type(Type::Float32);
171        register.insert_type(Type::Int32);
172        register.insert_type(Type::String);
173        register.insert_type(Type::PhysicalLength);
174        register.insert_type(Type::LogicalLength);
175        register.insert_type(Type::Color);
176        register.insert_type(Type::Duration);
177        register.insert_type(Type::Image);
178        register.insert_type(Type::Bool);
179        register.insert_type(Type::Model);
180        register.insert_type(Type::Percent);
181        register.insert_type(Type::Easing);
182        register.insert_type(Type::Angle);
183        register.insert_type(Type::Brush);
184
185        let mut declare_enum = |name: &str, values: &[&str]| {
186            register.insert_type_with_name(
187                Type::Enumeration(Rc::new(Enumeration {
188                    name: name.to_owned(),
189                    values: values.iter().cloned().map(String::from).collect(),
190                    default_value: 0,
191                })),
192                name.to_owned(),
193            );
194        };
195
196        declare_enum("TextHorizontalAlignment", &["left", "center", "right"]);
197        declare_enum("TextVerticalAlignment", &["top", "center", "bottom"]);
198        declare_enum("TextWrap", &["no-wrap", "word-wrap"]);
199        declare_enum("TextOverflow", &["clip", "elide"]);
200        declare_enum("ImageFit", &["fill", "contain", "cover"]);
201        declare_enum("ImageRendering", &["smooth", "pixelated"]);
202        declare_enum("EventResult", &["reject", "accept"]);
203        declare_enum("FillRule", &["nonzero", "evenodd"]);
204        declare_enum(
205            "MouseCursor",
206            &[
207                "default",
208                "none",
209                "help",
210                "pointer",
211                "progress",
212                "wait",
213                "crosshair",
214                "text",
215                "alias",
216                "copy",
217                "no-drop",
218                "not-allowed",
219                "grab",
220                "grabbing",
221                "col-resize",
222                "row-resize",
223                "n-resize",
224                "e-resize",
225                "s-resize",
226                "w-resize",
227                "ne-resize",
228                "nw-resize",
229                "se-resize",
230                "sw-resize",
231                "ew-resize",
232                "ns-resize",
233                "nesw-resize",
234                "nwse-resize",
235            ],
236        );
237        declare_enum(
238            "StandardButtonKind",
239            &[
240                "ok", "cancel", "apply", "close", "reset", "help", "yes", "no", "abort", "retry",
241                "ignore",
242            ],
243        );
244        declare_enum("PointerEventKind", &["cancel", "down", "up"]);
245        declare_enum("PointerEventButton", &["none", "left", "right", "middle"]);
246        DIALOG_BUTTON_ROLE_ENUM
247            .with(|e| register.insert_type_with_name(Type::Enumeration(e.clone()), e.name.clone()));
248        LAYOUT_ALIGNMENT_ENUM
249            .with(|e| register.insert_type_with_name(Type::Enumeration(e.clone()), e.name.clone()));
250
251        register.supported_property_animation_types.insert(Type::Float32.to_string());
252        register.supported_property_animation_types.insert(Type::Int32.to_string());
253        register.supported_property_animation_types.insert(Type::Color.to_string());
254        register.supported_property_animation_types.insert(Type::PhysicalLength.to_string());
255        register.supported_property_animation_types.insert(Type::LogicalLength.to_string());
256        register.supported_property_animation_types.insert(Type::Brush.to_string());
257        register.supported_property_animation_types.insert(Type::Angle.to_string());
258
259        crate::load_builtins::load_builtins(&mut register);
260
261        let mut context_restricted_types = HashMap::new();
262        register
263            .types
264            .values()
265            .for_each(|ty| ty.collect_contextual_types(&mut context_restricted_types));
266        register.context_restricted_types = context_restricted_types;
267
268        match &mut register.types.get_mut("PopupWindow").unwrap() {
269            Type::Builtin(ref mut b) => {
270                Rc::get_mut(b).unwrap().properties.insert(
271                    "show".into(),
272                    BuiltinPropertyInfo::new(BuiltinFunction::ShowPopupWindow.ty()),
273                );
274                Rc::get_mut(b).unwrap().member_functions.insert(
275                    "show".into(),
276                    Expression::BuiltinFunctionReference(BuiltinFunction::ShowPopupWindow, None),
277                );
278            }
279            _ => unreachable!(),
280        };
281
282        Rc::new(RefCell::new(register))
283    }
284
285    pub fn new(parent: &Rc<RefCell<TypeRegister>>) -> Self {
286        Self {
287            parent_registry: Some(parent.clone()),
288            expose_internal_types: parent.borrow().expose_internal_types,
289            ..Default::default()
290        }
291    }
292
293    pub fn lookup(&self, name: &str) -> Type {
294        self.types
295            .get(name)
296            .cloned()
297            .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name)))
298            .unwrap_or_default()
299    }
300
301    fn lookup_element_as_result(
302        &self,
303        name: &str,
304    ) -> Result<Type, HashMap<String, HashSet<String>>> {
305        match self.types.get(name).cloned() {
306            Some(ty) => Ok(ty),
307            None => match &self.parent_registry {
308                Some(r) => r.borrow().lookup_element_as_result(name),
309                None => Err(self.context_restricted_types.clone()),
310            },
311        }
312    }
313
314    pub fn lookup_element(&self, name: &str) -> Result<Type, String> {
315        self.lookup_element_as_result(name).map_err(|context_restricted_types| {
316            if let Some(permitted_parent_types) = context_restricted_types.get(name) {
317                if permitted_parent_types.len() == 1 {
318                    format!(
319                        "{} can only be within a {} element",
320                        name,
321                        permitted_parent_types.iter().next().unwrap()
322                    )
323                } else {
324                    let mut elements = permitted_parent_types.iter().cloned().collect::<Vec<_>>();
325                    elements.sort();
326                    format!(
327                        "{} can only be within the following elements: {}",
328                        name,
329                        elements.join(", ")
330                    )
331                }
332            } else {
333                format!("Unknown type {}", name)
334            }
335        })
336    }
337
338    pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
339        if qualified.len() != 1 {
340            return Type::Invalid;
341        }
342        self.lookup(qualified[0].as_ref())
343    }
344
345    pub fn add(&mut self, comp: Rc<Component>) {
346        self.add_with_name(comp.id.clone(), comp);
347    }
348
349    pub fn add_with_name(&mut self, name: String, comp: Rc<Component>) {
350        self.types.insert(name, Type::Component(comp));
351    }
352
353    pub fn property_animation_type_for_property(&self, property_type: Type) -> Type {
354        if self.supported_property_animation_types.contains(&property_type.to_string()) {
355            self.property_animation_type.clone()
356        } else {
357            self.parent_registry
358                .as_ref()
359                .map(|registry| {
360                    registry.borrow().property_animation_type_for_property(property_type)
361                })
362                .unwrap_or_default()
363        }
364    }
365
366    /// Return a hashmap with all the registered type
367    pub fn all_types(&self) -> HashMap<String, Type> {
368        let mut all =
369            self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default();
370        for (k, v) in &self.types {
371            all.insert(k.clone(), v.clone());
372        }
373        all
374    }
375}