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 | |
6 | use std::cell::RefCell; |
7 | use std::collections::{BTreeMap, HashMap, HashSet}; |
8 | use std::rc::Rc; |
9 | |
10 | use crate::expression_tree::BuiltinFunction; |
11 | use crate::langtype::{ |
12 | BuiltinElement, BuiltinPropertyInfo, ElementType, Enumeration, PropertyLookupResult, Type, |
13 | }; |
14 | use crate::object_tree::{Component, PropertyVisibility}; |
15 | |
16 | pub 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 | |
24 | pub 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 | |
40 | pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[ |
41 | ("col" , Type::Int32), |
42 | ("row" , Type::Int32), |
43 | ("colspan" , Type::Int32), |
44 | ("rowspan" , Type::Int32), |
45 | ]; |
46 | |
47 | macro_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 | |
75 | i_slint_common::for_each_enums!(declare_enums); |
76 | |
77 | thread_local! { |
78 | pub static BUILTIN_ENUMS: BuiltinEnums = BuiltinEnums::new(); |
79 | } |
80 | |
81 | const 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 | |
88 | pub 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 | |
95 | pub 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 | |
101 | pub 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 |
115 | pub 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 |
152 | pub 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 |
196 | pub 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)] |
208 | pub 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 | |
224 | impl 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 | |
494 | pub 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 | |