1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4// cSpell: ignore imum
5
6use std::cell::RefCell;
7use std::collections::{BTreeMap, HashMap, HashSet};
8use std::rc::Rc;
9
10use crate::expression_tree::BuiltinFunction;
11use crate::langtype::{
12 BuiltinElement, BuiltinPropertyInfo, ElementType, Enumeration, PropertyLookupResult, Type,
13};
14use crate::object_tree::{Component, PropertyVisibility};
15
16pub const RESERVED_GEOMETRY_PROPERTIES: &[(&str, Type)] = &[
17 ("x", Type::LogicalLength),
18 ("y", Type::LogicalLength),
19 ("width", Type::LogicalLength),
20 ("height", Type::LogicalLength),
21 ("z", Type::Float32),
22];
23
24pub const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[
25 ("min-width", Type::LogicalLength),
26 ("min-height", Type::LogicalLength),
27 ("max-width", Type::LogicalLength),
28 ("max-height", Type::LogicalLength),
29 ("padding", Type::LogicalLength),
30 ("padding-left", Type::LogicalLength),
31 ("padding-right", Type::LogicalLength),
32 ("padding-top", Type::LogicalLength),
33 ("padding-bottom", Type::LogicalLength),
34 ("preferred-width", Type::LogicalLength),
35 ("preferred-height", Type::LogicalLength),
36 ("horizontal-stretch", Type::Float32),
37 ("vertical-stretch", Type::Float32),
38];
39
40pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[
41 ("col", Type::Int32),
42 ("row", Type::Int32),
43 ("colspan", Type::Int32),
44 ("rowspan", Type::Int32),
45];
46
47macro_rules! declare_enums {
48 ($( $(#[$enum_doc:meta])* enum $Name:ident { $( $(#[$value_doc:meta])* $Value:ident,)* })*) => {
49 pub struct BuiltinEnums {
50 $(pub $Name : Rc<Enumeration>),*
51 }
52 impl BuiltinEnums {
53 fn new() -> Self {
54 Self {
55 $($Name : Rc::new(Enumeration {
56 name: stringify!($Name).replace('_', "-"),
57 values: vec![$(crate::generator::to_kebab_case(stringify!($Value).trim_start_matches("r#"))),*],
58 default_value: 0,
59 node: None,
60 })),*
61 }
62 }
63 fn fill_register(&self, register: &mut TypeRegister) {
64 $(if stringify!($Name) != "PathEvent" {
65 register.insert_type_with_name(
66 Type::Enumeration(self.$Name.clone()),
67 stringify!($Name).replace('_', "-")
68 );
69 })*
70 }
71 }
72 };
73}
74
75i_slint_common::for_each_enums!(declare_enums);
76
77thread_local! {
78 pub static BUILTIN_ENUMS: BuiltinEnums = BuiltinEnums::new();
79}
80
81const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
82 ("clip", Type::Bool),
83 ("opacity", Type::Float32),
84 ("cache-rendering-hint", Type::Bool),
85 ("visible", Type::Bool), // ("enabled", Type::Bool),
86];
87
88pub const RESERVED_DROP_SHADOW_PROPERTIES: &[(&str, Type)] = &[
89 ("drop-shadow-offset-x", Type::LogicalLength),
90 ("drop-shadow-offset-y", Type::LogicalLength),
91 ("drop-shadow-blur", Type::LogicalLength),
92 ("drop-shadow-color", Type::Color),
93];
94
95pub const RESERVED_ROTATION_PROPERTIES: &[(&str, Type)] = &[
96 ("rotation-angle", Type::Angle),
97 ("rotation-origin-x", Type::LogicalLength),
98 ("rotation-origin-y", Type::LogicalLength),
99];
100
101pub const RESERVED_ACCESSIBILITY_PROPERTIES: &[(&str, Type)] = &[
102 //("accessible-role", ...)
103 ("accessible-checkable", Type::Bool),
104 ("accessible-checked", Type::Bool),
105 ("accessible-delegate-focus", Type::Int32),
106 ("accessible-description", Type::String),
107 ("accessible-label", Type::String),
108 ("accessible-value", Type::String),
109 ("accessible-value-maximum", Type::Float32),
110 ("accessible-value-minimum", Type::Float32),
111 ("accessible-value-step", Type::Float32),
112];
113
114/// list of reserved property injected in every item
115pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type, PropertyVisibility)> {
116 RESERVED_GEOMETRY_PROPERTIES
117 .iter()
118 .chain(RESERVED_LAYOUT_PROPERTIES.iter())
119 .chain(RESERVED_OTHER_PROPERTIES.iter())
120 .chain(RESERVED_DROP_SHADOW_PROPERTIES.iter())
121 .chain(RESERVED_ROTATION_PROPERTIES.iter())
122 .chain(RESERVED_ACCESSIBILITY_PROPERTIES.iter())
123 .map(|(k, v)| (*k, v.clone(), PropertyVisibility::InOut))
124 .chain(
125 RESERVED_GRIDLAYOUT_PROPERTIES
126 .iter()
127 .map(|(k, v)| (*k, v.clone(), PropertyVisibility::Constexpr)),
128 )
129 .chain(IntoIterator::into_iter([
130 ("absolute-position", logical_point_type(), PropertyVisibility::Output),
131 ("forward-focus", Type::ElementReference, PropertyVisibility::Constexpr),
132 ("focus", BuiltinFunction::SetFocusItem.ty(), PropertyVisibility::Public),
133 (
134 "dialog-button-role",
135 Type::Enumeration(BUILTIN_ENUMS.with(|e| e.DialogButtonRole.clone())),
136 PropertyVisibility::Constexpr,
137 ),
138 (
139 "accessible-role",
140 Type::Enumeration(BUILTIN_ENUMS.with(|e| e.AccessibleRole.clone())),
141 PropertyVisibility::Constexpr,
142 ),
143 ]))
144 .chain(std::iter::once((
145 "init",
146 Type::Callback { return_type: None, args: vec![] },
147 PropertyVisibility::Private,
148 )))
149}
150
151/// lookup reserved property injected in every item
152pub fn reserved_property(name: &str) -> PropertyLookupResult {
153 for (p, t, property_visibility) in reserved_properties() {
154 if p == name {
155 return PropertyLookupResult {
156 property_type: t,
157 resolved_name: name.into(),
158 is_local_to_component: false,
159 is_in_direct_base: false,
160 property_visibility,
161 declared_pure: None,
162 };
163 }
164 }
165
166 // Report deprecated known reserved properties (maximum_width, minimum_height, ...)
167 for pre in &["min", "max"] {
168 if let Some(a) = name.strip_prefix(pre) {
169 for suf in &["width", "height"] {
170 if let Some(b) = a.strip_suffix(suf) {
171 if b == "imum-" {
172 return PropertyLookupResult {
173 property_type: Type::LogicalLength,
174 resolved_name: format!("{}-{}", pre, suf).into(),
175 is_local_to_component: false,
176 is_in_direct_base: false,
177 property_visibility: crate::object_tree::PropertyVisibility::InOut,
178 declared_pure: None,
179 };
180 }
181 }
182 }
183 }
184 }
185 PropertyLookupResult {
186 resolved_name: name.into(),
187 property_type: Type::Invalid,
188 is_local_to_component: false,
189 is_in_direct_base: false,
190 property_visibility: crate::object_tree::PropertyVisibility::Private,
191 declared_pure: None,
192 }
193}
194
195/// These member functions are injected in every time
196pub fn reserved_member_function(name: &str) -> Option<BuiltinFunction> {
197 for (m: &str, e: BuiltinFunction) in [
198 ("focus", BuiltinFunction::SetFocusItem), // match for callable "focus" property
199 ] {
200 if m == name {
201 return Some(e);
202 }
203 }
204 None
205}
206
207#[derive(Debug, Default)]
208pub struct TypeRegister {
209 /// The set of property types.
210 types: HashMap<String, Type>,
211 /// The set of element types
212 elements: HashMap<String, ElementType>,
213 supported_property_animation_types: HashSet<String>,
214 pub(crate) property_animation_type: ElementType,
215 pub(crate) empty_type: ElementType,
216 /// Map from a context restricted type to the list of contexts (parent type) it is allowed in. This is
217 /// used to construct helpful error messages, such as "Row can only be within a GridLayout element".
218 context_restricted_types: HashMap<String, HashSet<String>>,
219 parent_registry: Option<Rc<RefCell<TypeRegister>>>,
220 /// If the lookup function should return types that are marked as internal
221 pub(crate) expose_internal_types: bool,
222}
223
224impl TypeRegister {
225 /// FIXME: same as 'add' ?
226 pub fn insert_type(&mut self, t: Type) {
227 self.types.insert(t.to_string(), t);
228 }
229 pub fn insert_type_with_name(&mut self, t: Type, name: String) {
230 self.types.insert(name, t);
231 }
232
233 fn builtin_internal() -> Self {
234 let mut register = TypeRegister::default();
235
236 register.insert_type(Type::Float32);
237 register.insert_type(Type::Int32);
238 register.insert_type(Type::String);
239 register.insert_type(Type::PhysicalLength);
240 register.insert_type(Type::LogicalLength);
241 register.insert_type(Type::Color);
242 register.insert_type(Type::ComponentFactory);
243 register.insert_type(Type::Duration);
244 register.insert_type(Type::Image);
245 register.insert_type(Type::Bool);
246 register.insert_type(Type::Model);
247 register.insert_type(Type::Percent);
248 register.insert_type(Type::Easing);
249 register.insert_type(Type::Angle);
250 register.insert_type(Type::Brush);
251 register.insert_type(Type::Rem);
252 register.types.insert("Point".into(), logical_point_type());
253
254 BUILTIN_ENUMS.with(|e| e.fill_register(&mut register));
255
256 register.supported_property_animation_types.insert(Type::Float32.to_string());
257 register.supported_property_animation_types.insert(Type::Int32.to_string());
258 register.supported_property_animation_types.insert(Type::Color.to_string());
259 register.supported_property_animation_types.insert(Type::PhysicalLength.to_string());
260 register.supported_property_animation_types.insert(Type::LogicalLength.to_string());
261 register.supported_property_animation_types.insert(Type::Brush.to_string());
262 register.supported_property_animation_types.insert(Type::Angle.to_string());
263
264 #[rustfmt::skip]
265 macro_rules! map_type {
266 ($pub_type:ident, bool) => { Type::Bool };
267 ($pub_type:ident, i32) => { Type::Int32 };
268 ($pub_type:ident, f32) => { Type::Float32 };
269 ($pub_type:ident, SharedString) => { Type::String };
270 ($pub_type:ident, Coord) => { Type::LogicalLength };
271 ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
272 ($pub_type:ident, $_:ident) => {
273 BUILTIN_ENUMS.with(|e| Type::Enumeration(e.$pub_type.clone()))
274 };
275 }
276 #[rustfmt::skip]
277 macro_rules! maybe_clone {
278 ($pub_type:ident, KeyboardModifiers) => { $pub_type.clone() };
279 ($pub_type:ident, $_:ident) => { $pub_type };
280 }
281 macro_rules! register_builtin_structs {
282 ($(
283 $(#[$attr:meta])*
284 struct $Name:ident {
285 @name = $inner_name:literal
286 export {
287 $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ident, )*
288 }
289 private {
290 $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )*
291 }
292 }
293 )*) => { $(
294 let $Name = Type::Struct {
295 fields: BTreeMap::from([
296 $((stringify!($pub_field).replace('_', "-"), map_type!($pub_type, $pub_type))),*
297 ]),
298 name: Some(format!("{}", $inner_name)),
299 node: None,
300 rust_attributes: None,
301 };
302 register.insert_type_with_name(maybe_clone!($Name, $Name), stringify!($Name).to_string());
303 )* };
304 }
305 i_slint_common::for_each_builtin_structs!(register_builtin_structs);
306
307 crate::load_builtins::load_builtins(&mut register);
308
309 let mut context_restricted_types = HashMap::new();
310 register
311 .elements
312 .values()
313 .for_each(|ty| ty.collect_contextual_types(&mut context_restricted_types));
314 register.context_restricted_types = context_restricted_types;
315
316 match &mut register.elements.get_mut("PopupWindow").unwrap() {
317 ElementType::Builtin(ref mut b) => {
318 let popup = Rc::get_mut(b).unwrap();
319 popup.properties.insert(
320 "show".into(),
321 BuiltinPropertyInfo::new(BuiltinFunction::ShowPopupWindow.ty()),
322 );
323 popup.member_functions.insert("show".into(), BuiltinFunction::ShowPopupWindow);
324
325 popup.properties.insert(
326 "close".into(),
327 BuiltinPropertyInfo::new(BuiltinFunction::ClosePopupWindow.ty()),
328 );
329 popup.member_functions.insert("close".into(), BuiltinFunction::ClosePopupWindow);
330
331 popup.properties.get_mut("close-on-click").unwrap().property_visibility =
332 PropertyVisibility::Constexpr;
333 }
334
335 _ => unreachable!(),
336 };
337
338 match &mut register.elements.get_mut("TextInput").unwrap() {
339 ElementType::Builtin(ref mut b) => {
340 let text_input = Rc::get_mut(b).unwrap();
341 text_input.properties.insert(
342 "set-selection-offsets".into(),
343 BuiltinPropertyInfo::new(BuiltinFunction::SetSelectionOffsets.ty()),
344 );
345 text_input
346 .member_functions
347 .insert("set-selection-offsets".into(), BuiltinFunction::SetSelectionOffsets);
348 }
349
350 _ => unreachable!(),
351 };
352
353 register
354 }
355
356 #[doc(hidden)]
357 /// All builtins incl. experimental ones! Do not use in production code!
358 pub fn builtin_experimental() -> Rc<RefCell<Self>> {
359 let register = Self::builtin_internal();
360 Rc::new(RefCell::new(register))
361 }
362
363 pub fn builtin() -> Rc<RefCell<Self>> {
364 let mut register = Self::builtin_internal();
365
366 register.elements.remove("ComponentContainer");
367 register.types.remove("component-factory");
368
369 Rc::new(RefCell::new(register))
370 }
371
372 pub fn new(parent: &Rc<RefCell<TypeRegister>>) -> Self {
373 Self {
374 parent_registry: Some(parent.clone()),
375 expose_internal_types: parent.borrow().expose_internal_types,
376 ..Default::default()
377 }
378 }
379
380 pub fn lookup(&self, name: &str) -> Type {
381 self.types
382 .get(name)
383 .cloned()
384 .or_else(|| self.parent_registry.as_ref().map(|r| r.borrow().lookup(name)))
385 .unwrap_or_default()
386 }
387
388 fn lookup_element_as_result(
389 &self,
390 name: &str,
391 ) -> Result<ElementType, HashMap<String, HashSet<String>>> {
392 match self.elements.get(name).cloned() {
393 Some(ty) => Ok(ty),
394 None => match &self.parent_registry {
395 Some(r) => r.borrow().lookup_element_as_result(name),
396 None => Err(self.context_restricted_types.clone()),
397 },
398 }
399 }
400
401 pub fn lookup_element(&self, name: &str) -> Result<ElementType, String> {
402 self.lookup_element_as_result(name).map_err(|context_restricted_types| {
403 if let Some(permitted_parent_types) = context_restricted_types.get(name) {
404 if permitted_parent_types.len() == 1 {
405 format!(
406 "{} can only be within a {} element",
407 name,
408 permitted_parent_types.iter().next().unwrap()
409 )
410 } else {
411 let mut elements = permitted_parent_types.iter().cloned().collect::<Vec<_>>();
412 elements.sort();
413 format!(
414 "{} can only be within the following elements: {}",
415 name,
416 elements.join(", ")
417 )
418 }
419 } else if let Some(ty) = self.types.get(name) {
420 format!("'{}' cannot be used as an element", ty)
421 } else {
422 format!("Unknown type {}", name)
423 }
424 })
425 }
426
427 pub fn lookup_builtin_element(&self, name: &str) -> Option<ElementType> {
428 self.parent_registry.as_ref().map_or_else(
429 || self.elements.get(name).cloned(),
430 |p| p.borrow().lookup_builtin_element(name),
431 )
432 }
433
434 pub fn lookup_qualified<Member: AsRef<str>>(&self, qualified: &[Member]) -> Type {
435 if qualified.len() != 1 {
436 return Type::Invalid;
437 }
438 self.lookup(qualified[0].as_ref())
439 }
440
441 pub fn add(&mut self, comp: Rc<Component>) {
442 self.add_with_name(comp.id.clone(), comp);
443 }
444
445 pub fn add_with_name(&mut self, name: String, comp: Rc<Component>) {
446 self.elements.insert(name, ElementType::Component(comp));
447 }
448
449 pub fn add_builtin(&mut self, builtin: Rc<BuiltinElement>) {
450 self.elements.insert(builtin.name.clone(), ElementType::Builtin(builtin));
451 }
452
453 pub fn property_animation_type_for_property(&self, property_type: Type) -> ElementType {
454 if self.supported_property_animation_types.contains(&property_type.to_string()) {
455 self.property_animation_type.clone()
456 } else {
457 self.parent_registry
458 .as_ref()
459 .map(|registry| {
460 registry.borrow().property_animation_type_for_property(property_type)
461 })
462 .unwrap_or_default()
463 }
464 }
465
466 /// Return a hashmap with all the registered type
467 pub fn all_types(&self) -> HashMap<String, Type> {
468 let mut all =
469 self.parent_registry.as_ref().map(|r| r.borrow().all_types()).unwrap_or_default();
470 for (k, v) in &self.types {
471 all.insert(k.clone(), v.clone());
472 }
473 all
474 }
475
476 /// Return a hashmap with all the registered element type
477 pub fn all_elements(&self) -> HashMap<String, ElementType> {
478 let mut all =
479 self.parent_registry.as_ref().map(|r| r.borrow().all_elements()).unwrap_or_default();
480 for (k, v) in &self.elements {
481 all.insert(k.clone(), v.clone());
482 }
483 all
484 }
485
486 pub fn empty_type(&self) -> ElementType {
487 match self.parent_registry.as_ref() {
488 Some(parent) => parent.borrow().empty_type(),
489 None => self.empty_type.clone(),
490 }
491 }
492}
493
494pub fn logical_point_type() -> Type {
495 Type::Struct {
496 fields: IntoIteratorIntoIter<(String, Type), 2>::into_iter([
497 ("x".to_owned(), Type::LogicalLength),
498 ("y".to_owned(), Type::LogicalLength),
499 ])
500 .collect(),
501 name: Some("slint::LogicalPosition".into()),
502 node: None,
503 rust_attributes: None,
504 }
505}
506