1use std::borrow::Cow;
5use std::collections::{BTreeMap, HashMap};
6use std::fmt::Display;
7use std::rc::Rc;
8
9use itertools::Itertools;
10
11use smol_str::SmolStr;
12
13use crate::expression_tree::{BuiltinFunction, Expression, Unit};
14use crate::object_tree::{Component, PropertyVisibility};
15use crate::parser::syntax_nodes;
16use crate::typeregister::TypeRegister;
17
18#[derive(Debug, Clone, Default)]
19pub enum Type {
20 #[default]
22 Invalid,
23 Void,
25 InferredProperty,
27 InferredCallback,
29
30 Callback(Rc<Function>),
31 Function(Rc<Function>),
32
33 ComponentFactory,
34
35 Float32,
37 Int32,
38 String,
39 Color,
40 Duration,
41 PhysicalLength,
42 LogicalLength,
43 Rem,
44 Angle,
45 Percent,
46 Image,
47 Bool,
48 Model,
50 PathData, Easing,
52 Brush,
53 Array(Rc<Type>),
55 Struct(Rc<Struct>),
56 Enumeration(Rc<Enumeration>),
57 Keys,
58 DataTransfer,
61
62 UnitProduct(Vec<(Unit, i8)>),
66
67 ElementReference,
68
69 LayoutCache,
71 ArrayOfU16,
73
74 StyledText,
75}
76
77impl core::cmp::PartialEq for Type {
78 fn eq(&self, other: &Self) -> bool {
79 match self {
80 Type::Invalid => matches!(other, Type::Invalid),
81 Type::Void => matches!(other, Type::Void),
82 Type::InferredProperty => matches!(other, Type::InferredProperty),
83 Type::InferredCallback => matches!(other, Type::InferredCallback),
84 Type::Callback(lhs) => {
85 matches!(other, Type::Callback(rhs) if lhs == rhs)
86 }
87 Type::Function(lhs) => {
88 matches!(other, Type::Function(rhs) if lhs == rhs)
89 }
90 Type::ComponentFactory => matches!(other, Type::ComponentFactory),
91 Type::Float32 => matches!(other, Type::Float32),
92 Type::Int32 => matches!(other, Type::Int32),
93 Type::String => matches!(other, Type::String),
94 Type::Color => matches!(other, Type::Color),
95 Type::Duration => matches!(other, Type::Duration),
96 Type::Angle => matches!(other, Type::Angle),
97 Type::PhysicalLength => matches!(other, Type::PhysicalLength),
98 Type::LogicalLength => matches!(other, Type::LogicalLength),
99 Type::Rem => matches!(other, Type::Rem),
100 Type::Percent => matches!(other, Type::Percent),
101 Type::Image => matches!(other, Type::Image),
102 Type::Bool => matches!(other, Type::Bool),
103 Type::Model => matches!(other, Type::Model),
104 Type::PathData => matches!(other, Type::PathData),
105 Type::Easing => matches!(other, Type::Easing),
106 Type::Brush => matches!(other, Type::Brush),
107 Type::Array(a) => matches!(other, Type::Array(b) if a == b),
108 Type::Struct(lhs) => {
109 matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
110 }
111 Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
112 Type::Keys => matches!(other, Type::Keys),
113 Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
114 Type::ElementReference => matches!(other, Type::ElementReference),
115 Type::LayoutCache => matches!(other, Type::LayoutCache),
116 Type::ArrayOfU16 => matches!(other, Type::ArrayOfU16),
117 Type::StyledText => matches!(other, Type::StyledText),
118 Type::DataTransfer => matches!(other, Type::DataTransfer),
119 }
120 }
121}
122
123impl Display for Type {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self {
126 Type::Invalid => write!(f, "<error>"),
127 Type::Void => write!(f, "void"),
128 Type::InferredProperty => write!(f, "?"),
129 Type::InferredCallback => write!(f, "callback"),
130 Type::Callback(callback) => {
131 write!(f, "callback")?;
132 if !callback.args.is_empty() {
133 write!(f, "(")?;
134 for (i, arg) in callback.args.iter().enumerate() {
135 if i > 0 {
136 write!(f, ",")?;
137 }
138 write!(f, "{arg}")?;
139 }
140 write!(f, ")")?
141 }
142 write!(f, "-> {}", callback.return_type)?;
143 Ok(())
144 }
145 Type::ComponentFactory => write!(f, "component-factory"),
146 Type::Function(function) => {
147 write!(f, "function(")?;
148 for (i, arg) in function.args.iter().enumerate() {
149 if i > 0 {
150 write!(f, ",")?;
151 }
152 write!(f, "{arg}")?;
153 }
154 write!(f, ") -> {}", function.return_type)
155 }
156 Type::Float32 => write!(f, "float"),
157 Type::Int32 => write!(f, "int"),
158 Type::String => write!(f, "string"),
159 Type::Duration => write!(f, "duration"),
160 Type::Angle => write!(f, "angle"),
161 Type::PhysicalLength => write!(f, "physical-length"),
162 Type::LogicalLength => write!(f, "length"),
163 Type::Rem => write!(f, "relative-font-size"),
164 Type::Percent => write!(f, "percent"),
165 Type::Color => write!(f, "color"),
166 Type::Image => write!(f, "image"),
167 Type::Bool => write!(f, "bool"),
168 Type::Model => write!(f, "model"),
169 Type::Array(t) => write!(f, "[{t}]"),
170 Type::Struct(t) => write!(f, "{t}"),
171 Type::PathData => write!(f, "pathdata"),
172 Type::Easing => write!(f, "easing"),
173 Type::Brush => write!(f, "brush"),
174 Type::Enumeration(enumeration) => write!(f, "enum {}", enumeration.name),
175 Type::Keys => write!(f, "keys"),
176 Type::DataTransfer => write!(f, "data-transfer"),
177 Type::UnitProduct(vec) => {
178 const POWERS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
179 let mut x = vec.iter().map(|(unit, power)| {
180 if *power == 1 {
181 return unit.to_string();
182 }
183 let mut res = format!("{}{}", unit, if *power < 0 { "⁻" } else { "" });
184 let value = power.abs().to_string();
185 for x in value.as_bytes() {
186 res.push(POWERS[(x - b'0') as usize]);
187 }
188
189 res
190 });
191 write!(f, "({})", x.join("×"))
192 }
193 Type::ElementReference => write!(f, "element ref"),
194 Type::LayoutCache => write!(f, "layout cache"),
195 Type::ArrayOfU16 => write!(f, "[u16]"),
196 Type::StyledText => write!(f, "styled-text"),
197 }
198 }
199}
200
201impl From<Rc<Struct>> for Type {
202 fn from(value: Rc<Struct>) -> Self {
203 Self::Struct(value)
204 }
205}
206
207impl Type {
208 pub fn is_property_type(&self) -> bool {
210 matches!(
211 self,
212 Self::Float32
213 | Self::Int32
214 | Self::String
215 | Self::Color
216 | Self::ComponentFactory
217 | Self::Duration
218 | Self::Angle
219 | Self::PhysicalLength
220 | Self::LogicalLength
221 | Self::Rem
222 | Self::Percent
223 | Self::Image
224 | Self::Bool
225 | Self::Easing
226 | Self::Enumeration(_)
227 | Self::Keys
228 | Self::DataTransfer
229 | Self::ElementReference
230 | Self::Struct { .. }
231 | Self::Array(_)
232 | Self::Brush
233 | Self::InferredProperty
234 | Self::StyledText
235 )
236 }
237
238 pub fn ok_for_public_api(&self) -> bool {
239 !matches!(self, Self::Easing)
240 }
241
242 pub fn as_enum(&self) -> &Rc<Enumeration> {
244 match self {
245 Type::Enumeration(e) => e,
246 _ => panic!("should be an enumeration, bug in compiler pass"),
247 }
248 }
249
250 pub fn can_convert(&self, other: &Self) -> bool {
252 let can_convert_struct = |a: &BTreeMap<SmolStr, Type>, b: &BTreeMap<SmolStr, Type>| {
253 let mut has_more_property = false;
255 for (k, v) in b {
256 match a.get(k) {
257 Some(t) if !t.can_convert(v) => return false,
258 None => has_more_property = true,
259 _ => (),
260 }
261 }
262 if has_more_property {
263 if a.keys().any(|k| !b.contains_key(k)) {
265 return false;
266 }
267 }
268 true
269 };
270 match (self, other) {
271 (a, b) if a == b => true,
272 (_, Type::Invalid)
273 | (_, Type::Void)
274 | (Type::Float32, Type::Int32)
275 | (Type::Float32, Type::String)
276 | (Type::Int32, Type::Float32)
277 | (Type::Int32, Type::String)
278 | (Type::Float32, Type::Model)
279 | (Type::Int32, Type::Model)
280 | (Type::PhysicalLength, Type::LogicalLength)
281 | (Type::LogicalLength, Type::PhysicalLength)
282 | (Type::Rem, Type::LogicalLength)
283 | (Type::Rem, Type::PhysicalLength)
284 | (Type::LogicalLength, Type::Rem)
285 | (Type::PhysicalLength, Type::Rem)
286 | (Type::Percent, Type::Float32)
287 | (Type::Brush, Type::Color)
288 | (Type::Color, Type::Brush) => true,
289 (Type::Array(a), Type::Model) if a.is_property_type() => true,
290 (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
291 (Type::UnitProduct(u), o) => match o.as_unit_product() {
292 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
293 None => false,
294 },
295 (o, Type::UnitProduct(u)) => match o.as_unit_product() {
296 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
297 None => false,
298 },
299 _ => false,
300 }
301 }
302
303 pub fn default_unit(&self) -> Option<Unit> {
306 match self {
307 Type::Duration => Some(Unit::Ms),
308 Type::PhysicalLength => Some(Unit::Phx),
309 Type::LogicalLength => Some(Unit::Px),
310 Type::Rem => Some(Unit::Rem),
311 Type::Percent => None,
313 Type::Angle => Some(Unit::Deg),
314 Type::Invalid => None,
315 Type::Void => None,
316 Type::InferredProperty | Type::InferredCallback => None,
317 Type::Callback { .. } => None,
318 Type::ComponentFactory => None,
319 Type::Function { .. } => None,
320 Type::Float32 => None,
321 Type::Int32 => None,
322 Type::String => None,
323 Type::Color => None,
324 Type::Image => None,
325 Type::Bool => None,
326 Type::Model => None,
327 Type::PathData => None,
328 Type::Easing => None,
329 Type::Brush => None,
330 Type::Array(_) => None,
331 Type::Struct { .. } => None,
332 Type::Enumeration(_) => None,
333 Type::Keys => None,
334 Type::DataTransfer => None,
335 Type::UnitProduct(_) => None,
336 Type::ElementReference => None,
337 Type::LayoutCache => None,
338 Type::ArrayOfU16 => None,
339 Type::StyledText => None,
340 }
341 }
342
343 pub fn as_unit_product(&self) -> Option<Vec<(Unit, i8)>> {
345 match self {
346 Type::UnitProduct(u) => Some(u.clone()),
347 Type::Float32 | Type::Int32 => Some(Vec::new()),
348 Type::Percent => Some(Vec::new()),
349 _ => self.default_unit().map(|u| vec![(u, 1)]),
350 }
351 }
352}
353
354#[derive(Debug, Clone)]
355pub enum BuiltinPropertyDefault {
356 None,
357 Expr(Expression),
358 WithElement(fn(&crate::object_tree::ElementRc) -> Expression),
360 BuiltinFunction(BuiltinFunction),
362}
363
364impl BuiltinPropertyDefault {
365 pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
366 match self {
367 BuiltinPropertyDefault::None => None,
368 BuiltinPropertyDefault::Expr(expression) => Some(expression.clone()),
369 BuiltinPropertyDefault::WithElement(init_expr) => Some(init_expr(elem)),
370 BuiltinPropertyDefault::BuiltinFunction(..) => {
371 unreachable!("can't get an expression for functions")
372 }
373 }
374 }
375}
376
377#[derive(Debug, Clone)]
379pub struct BuiltinPropertyInfo {
380 pub ty: Type,
382 pub default_value: BuiltinPropertyDefault,
384 pub property_visibility: PropertyVisibility,
385 pub docs: Option<String>,
387 pub shadowable: bool,
394}
395
396impl BuiltinPropertyInfo {
397 pub fn new(ty: Type) -> Self {
398 Self {
399 ty,
400 default_value: BuiltinPropertyDefault::None,
401 property_visibility: PropertyVisibility::InOut,
402 docs: None,
403 shadowable: false,
404 }
405 }
406
407 pub fn is_native_output(&self) -> bool {
408 matches!(self.property_visibility, PropertyVisibility::InOut | PropertyVisibility::Output)
409 }
410}
411
412impl From<BuiltinFunction> for BuiltinPropertyInfo {
413 fn from(function: BuiltinFunction) -> Self {
414 Self {
415 ty: Type::Function(function.ty()),
416 default_value: BuiltinPropertyDefault::BuiltinFunction(function),
417 property_visibility: PropertyVisibility::Public,
418 docs: None,
419 shadowable: false,
420 }
421 }
422}
423
424#[derive(Clone, Debug, derive_more::From, Default)]
426pub enum ElementType {
427 Component(Rc<Component>),
429 Builtin(Rc<BuiltinElement>),
431 Native(Rc<NativeClass>),
433 #[default]
435 Error,
436 Global,
438 Interface,
440}
441
442impl PartialEq for ElementType {
443 fn eq(&self, other: &Self) -> bool {
444 match (self, other) {
445 (Self::Component(a), Self::Component(b)) => Rc::ptr_eq(a, b),
446 (Self::Builtin(a), Self::Builtin(b)) => Rc::ptr_eq(a, b),
447 (Self::Native(a), Self::Native(b)) => Rc::ptr_eq(a, b),
448 (Self::Error, Self::Error)
449 | (Self::Global, Self::Global)
450 | (Self::Interface, Self::Interface) => true,
451 _ => false,
452 }
453 }
454}
455
456impl ElementType {
457 pub fn lookup_property<'a>(&self, name: &'a str) -> PropertyLookupResult<'a> {
458 match self {
459 Self::Component(c) => c.root_element.borrow().lookup_property(name),
460 Self::Builtin(b) => {
461 let resolved_name =
462 if let Some(alias_name) = b.native_class.lookup_alias(name.as_ref()) {
463 Cow::Owned(alias_name.to_string())
464 } else {
465 Cow::Borrowed(name)
466 };
467 match b.properties.get(resolved_name.as_ref()) {
468 None => {
469 if b.is_non_item_type || b.is_global {
470 PropertyLookupResult::invalid(resolved_name)
471 } else {
472 crate::typeregister::reserved_property(resolved_name)
473 }
474 }
475 Some(p) => PropertyLookupResult {
476 resolved_name,
477 property_type: p.ty.clone(),
478 property_visibility: p.property_visibility,
479 declared_pure: None,
480 is_local_to_component: false,
481 is_in_direct_base: false,
482 is_shadowable: p.shadowable,
483 builtin_function: match &p.default_value {
484 BuiltinPropertyDefault::BuiltinFunction(f) => Some(f.clone()),
485 _ => None,
486 },
487 },
488 }
489 }
490 Self::Native(n) => {
491 let resolved_name = if let Some(alias_name) = n.lookup_alias(name.as_ref()) {
492 Cow::Owned(alias_name.to_string())
493 } else {
494 Cow::Borrowed(name)
495 };
496 let property_type =
497 n.lookup_property(resolved_name.as_ref()).cloned().unwrap_or_default();
498 PropertyLookupResult {
499 resolved_name,
500 property_type,
501 property_visibility: PropertyVisibility::InOut,
502 declared_pure: None,
503 is_local_to_component: false,
504 is_in_direct_base: false,
505 is_shadowable: false,
506 builtin_function: None,
507 }
508 }
509 _ => PropertyLookupResult::invalid(Cow::Borrowed(name)),
510 }
511 }
512
513 pub fn property_list(&self) -> Vec<(SmolStr, Type)> {
515 match self {
516 Self::Component(c) => {
517 let mut r = c.root_element.borrow().base_type.property_list();
518 r.extend(
519 c.root_element
520 .borrow()
521 .property_declarations
522 .iter()
523 .filter(|(_, d)| d.visibility != PropertyVisibility::Private)
524 .map(|(k, d)| (k.clone(), d.property_type.clone())),
525 );
526 r
527 }
528 Self::Builtin(b) => {
529 b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
530 }
531 Self::Native(n) => {
532 n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
533 }
534 _ => Vec::new(),
535 }
536 }
537
538 pub fn lookup_type_for_child_element(
542 &self,
543 name: &str,
544 tr: &TypeRegister,
545 ) -> Result<ElementType, String> {
546 match self {
547 Self::Component(component) => {
548 let base_type = match &*component.child_insertion_point.borrow() {
549 Some(insert_in) => insert_in.parent.borrow().base_type.clone(),
550 None => {
551 let base_type = component.root_element.borrow().base_type.clone();
552 if base_type == tr.empty_type() {
553 let element = tr.lookup_element(name)?;
554 if matches!(&element, ElementType::Builtin(b) if b.can_be_declared_without_children_slot) {
555 return Ok(element);
556 }
557 return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
558 }
559 base_type
560 }
561 };
562 base_type.lookup_type_for_child_element(name, tr)
563 }
564 Self::Builtin(builtin) => {
565 let looked_up = tr.lookup_element(name);
566 if let Ok(ElementType::Builtin(b)) = &looked_up
567 && b.can_be_declared_without_children_slot
568 {
569 return Ok(ElementType::Builtin(b.clone()));
570 }
571 if builtin.disallow_global_types_as_child_elements {
572 if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
573 return Ok(child_type.clone().into());
574 } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
575 return Ok(builtin.clone().into());
576 }
577 let mut valid_children: Vec<_> =
578 builtin.additional_accepted_child_types.keys().cloned().collect();
579 if builtin.additional_accept_self {
580 valid_children.push(builtin.native_class.class_name.clone());
581 }
582 valid_children.sort();
583
584 let err = if valid_children.is_empty() {
585 looked_up?;
587 format!("{} cannot have children elements", builtin.native_class.class_name,)
588 } else {
589 format!(
590 "{} is not allowed within {}. Only {} are valid children",
591 name,
592 builtin.native_class.class_name,
593 valid_children.join(" ")
594 )
595 };
596 return Err(err);
597 }
598 let err = match looked_up {
599 Err(e) => e,
600 Ok(t) => {
601 if !tr.expose_internal_types
602 && matches!(&t, Self::Builtin(e) if e.is_internal)
603 {
604 format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)")
605 } else {
606 return Ok(t);
607 }
608 }
609 };
610 if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
611 return Ok(child_type.clone().into());
612 } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
613 return Ok(builtin.clone().into());
614 }
615 match tr.lookup(name) {
616 Type::Invalid => Err(err),
617 ty => Err(format!("'{ty}' cannot be used as an element")),
618 }
619 }
620 _ => tr.lookup_element(name).and_then(|t| {
621 if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
622 Err(format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)"))
623 } else {
624 Ok(t)
625 }
626 })
627 }
628 }
629
630 pub fn as_builtin(&self) -> &BuiltinElement {
632 match self {
633 Self::Builtin(b) => b,
634 Self::Component(_) => panic!("This should not happen because of inlining"),
635 _ => panic!("invalid type"),
636 }
637 }
638
639 pub fn as_native(&self) -> &NativeClass {
641 match self {
642 Self::Native(b) => b,
643 Self::Component(_) => {
644 panic!("This should not happen because of native class resolution")
645 }
646 _ => panic!("invalid type"),
647 }
648 }
649
650 pub fn as_component(&self) -> &Rc<Component> {
652 match self {
653 Self::Component(c) => c,
654 _ => panic!("should be a component because of the repeater_component pass"),
655 }
656 }
657
658 pub fn type_name(&self) -> Option<&str> {
660 match self {
661 ElementType::Component(component) => Some(&component.id),
662 ElementType::Builtin(b) => Some(&b.name),
663 ElementType::Native(_) => None, ElementType::Error => None,
665 ElementType::Global => None,
666 ElementType::Interface => None,
667 }
668 }
669}
670
671impl Display for ElementType {
672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673 match self {
674 Self::Component(c) => c.id.fmt(f),
675 Self::Builtin(b) => b.name.fmt(f),
676 Self::Native(b) => b.class_name.fmt(f),
677 Self::Error => write!(f, "<error>"),
678 Self::Global => Ok(()),
679 Self::Interface => Ok(()),
680 }
681 }
682}
683
684macro_rules! define_builtin_struct_enum {
685 ($(
686 $(#[$attr:meta])*
687 $vis:vis struct $Name:ident {
688 $( $(#[$field_attr:meta])* $field:ident : $field_type:ty, )*
689 }
690 )*) => {
691 #[derive(Debug, Clone, PartialEq, strum::EnumString, strum::IntoStaticStr)]
692 pub enum BuiltinStruct {
693 $($Name,)*
695
696 Color,
698 LogicalPosition,
699 LogicalSize,
700
701 PathMoveTo,
704 PathLineTo,
705 PathArcTo,
706 PathCubicTo,
707 PathQuadraticTo,
708 PathClose,
709 PathElement,
710
711 Point,
716 Size,
719 StateInfo,
720 PropertyAnimation,
721 GridLayoutData,
722 GridLayoutInputData,
723 BoxLayoutData,
724 BoxLayoutOrthoData,
725 FlexboxLayoutData,
726 LayoutItemInfo,
727 FlexboxLayoutItemInfo,
728 Padding,
729 LayoutInfo,
730 }
731
732 impl BuiltinStruct {
733 pub fn is_public(&self) -> bool {
734 match self {
735 $(Self::$Name => stringify!($vis) == "pub",)*
737 Self::Color | Self::LogicalPosition | Self::LogicalSize => true,
739 _ => false,
740 }
741 }
742
743 pub fn slint_name(&self) -> Option<SmolStr> {
746 match self {
747 $(Self::$Name => {
749 Some(SmolStr::new_static(stringify!($Name)))
750 })*
751 Self::Color => Some(SmolStr::new_static("color")),
753 Self::LogicalPosition => Some(SmolStr::new_static("Point")),
754 Self::LogicalSize => Some(SmolStr::new_static("Size")),
755 _ => None,
756 }
757 }
758 }
759 };
760}
761i_slint_common::for_each_builtin_structs!(define_builtin_struct_enum);
762
763impl BuiltinStruct {
764 pub fn is_layout_data(&self) -> bool {
765 matches!(
766 self,
767 Self::GridLayoutInputData
768 | Self::GridLayoutData
769 | Self::BoxLayoutData
770 | Self::BoxLayoutOrthoData
771 | Self::FlexboxLayoutData
772 )
773 }
774}
775
776#[derive(Debug, Clone, Default)]
777pub struct NativeClass {
778 pub parent: Option<Rc<NativeClass>>,
779 pub class_name: SmolStr,
780 pub cpp_vtable_getter: String,
781 pub properties: HashMap<SmolStr, BuiltinPropertyInfo>,
782 pub deprecated_aliases: HashMap<SmolStr, SmolStr>,
783 pub builtin_struct: Option<BuiltinStruct>,
786}
787
788impl NativeClass {
789 pub fn new(class_name: &str) -> Self {
790 let cpp_vtable_getter = format!("SLINT_GET_ITEM_VTABLE({class_name}VTable)");
791 Self {
792 class_name: class_name.into(),
793 cpp_vtable_getter,
794 properties: Default::default(),
795 ..Default::default()
796 }
797 }
798
799 pub fn new_with_properties(
800 class_name: &str,
801 properties: impl IntoIterator<Item = (SmolStr, BuiltinPropertyInfo)>,
802 ) -> Self {
803 let mut class = Self::new(class_name);
804 class.properties = properties.into_iter().collect();
805 class
806 }
807
808 pub fn property_count(&self) -> usize {
809 self.properties.len() + self.parent.clone().map(|p| p.property_count()).unwrap_or_default()
810 }
811
812 pub fn lookup_property(&self, name: &str) -> Option<&Type> {
813 if let Some(bty) = self.properties.get(name) {
814 Some(&bty.ty)
815 } else if let Some(parent_class) = &self.parent {
816 parent_class.lookup_property(name)
817 } else {
818 None
819 }
820 }
821
822 pub fn lookup_alias(&self, name: &str) -> Option<&str> {
823 if let Some(alias_target) = self.deprecated_aliases.get(name) {
824 Some(alias_target)
825 } else if self.properties.contains_key(name) {
826 None
827 } else if let Some(parent_class) = &self.parent {
828 parent_class.lookup_alias(name)
829 } else {
830 None
831 }
832 }
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Default)]
836pub enum DefaultSizeBinding {
837 #[default]
839 None,
840 ExpandsToParentGeometry,
842 ImplicitSize,
844}
845
846#[derive(Debug, Clone, Default)]
847pub struct BuiltinElement {
848 pub name: SmolStr,
849 pub native_class: Rc<NativeClass>,
850 pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
851 pub additional_accepted_child_types: HashMap<SmolStr, Rc<BuiltinElement>>,
854 pub additional_accept_self: bool,
856 pub disallow_global_types_as_child_elements: bool,
857 pub is_non_item_type: bool,
859 pub accepts_focus: bool,
860 pub is_global: bool,
861 pub default_size_binding: DefaultSizeBinding,
862 pub is_internal: bool,
864 pub docs: Vec<crate::doc_comments::ElementDocEntry>,
868 pub can_be_declared_without_children_slot: bool,
871 pub slint_sc: bool,
873}
874
875impl BuiltinElement {
876 pub fn new(native_class: Rc<NativeClass>) -> Self {
877 Self { name: native_class.class_name.clone(), native_class, ..Default::default() }
878 }
879}
880
881#[derive(PartialEq, Debug)]
882pub struct PropertyLookupResult<'a> {
883 pub resolved_name: std::borrow::Cow<'a, str>,
884 pub property_type: Type,
885 pub property_visibility: PropertyVisibility,
886 pub declared_pure: Option<bool>,
887 pub is_local_to_component: bool,
889 pub is_in_direct_base: bool,
891 pub is_shadowable: bool,
894
895 pub builtin_function: Option<BuiltinFunction>,
897}
898
899impl<'a> PropertyLookupResult<'a> {
900 pub fn is_valid(&self) -> bool {
901 self.property_type != Type::Invalid
902 }
903
904 pub fn is_valid_for_assignment(&self) -> bool {
906 !matches!(
907 (self.property_visibility, self.is_local_to_component),
908 (PropertyVisibility::Private, false)
909 | (PropertyVisibility::Input, true)
910 | (PropertyVisibility::Output, false)
911 )
912 }
913
914 pub fn invalid(resolved_name: Cow<'a, str>) -> Self {
915 Self {
916 resolved_name,
917 property_type: Type::Invalid,
918 property_visibility: PropertyVisibility::Private,
919 declared_pure: None,
920 is_local_to_component: false,
921 is_in_direct_base: false,
922 is_shadowable: false,
923 builtin_function: None,
924 }
925 }
926}
927
928#[derive(Debug, Clone, PartialEq)]
929pub struct Function {
930 pub return_type: Type,
931 pub args: Vec<Type>,
932 pub arg_names: Vec<SmolStr>,
935}
936
937#[derive(Debug, Clone)]
938pub enum StructName {
939 None,
940 User {
942 name: SmolStr,
943 node: syntax_nodes::ObjectType,
945 },
946 Builtin(BuiltinStruct),
947}
948
949impl PartialEq for StructName {
950 fn eq(&self, other: &Self) -> bool {
951 match (self, other) {
952 (
953 Self::User { name: l_user_name, node: _ },
954 Self::User { name: r_user_name, node: _ },
955 ) => l_user_name == r_user_name,
956 (Self::Builtin(l0), Self::Builtin(r0)) => l0 == r0,
957 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
958 }
959 }
960}
961
962impl StructName {
963 pub fn slint_name(&self) -> Option<SmolStr> {
964 match self {
965 StructName::None => None,
966 StructName::User { name, .. } => Some(name.clone()),
967 StructName::Builtin(builtin) => builtin.slint_name(),
968 }
969 }
970
971 pub fn is_none(&self) -> bool {
972 matches!(self, Self::None)
973 }
974
975 pub fn is_some(&self) -> bool {
976 !matches!(self, Self::None)
977 }
978
979 pub fn or(self, other: Self) -> Self {
980 match self {
981 Self::None => other,
982 this => this,
983 }
984 }
985}
986
987impl From<BuiltinStruct> for StructName {
988 fn from(value: BuiltinStruct) -> Self {
989 Self::Builtin(value)
990 }
991}
992
993#[derive(Debug, Clone)]
994pub struct Struct {
995 pub fields: BTreeMap<SmolStr, Type>,
996 pub name: StructName,
997}
998
999impl Struct {
1000 pub fn node(&self) -> Option<&syntax_nodes::ObjectType> {
1001 match &self.name {
1002 StructName::User { node, .. } => Some(node),
1003 _ => None,
1004 }
1005 }
1006}
1007
1008impl Display for Struct {
1009 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1010 if let Some(name) = &self.name.slint_name() {
1011 write!(f, "{name}")
1012 } else {
1013 write!(f, "{{ ")?;
1014 for (k, v) in &self.fields {
1015 write!(f, "{k}: {v},")?;
1016 }
1017 write!(f, "}}")
1018 }
1019 }
1020}
1021
1022#[derive(Debug, Clone)]
1023pub struct Enumeration {
1024 pub name: SmolStr,
1025 pub values: Vec<SmolStr>,
1026 pub default_value: usize, pub node: Option<syntax_nodes::EnumDeclaration>,
1029}
1030
1031impl PartialEq for Enumeration {
1032 fn eq(&self, other: &Self) -> bool {
1033 self.name.eq(&other.name)
1034 }
1035}
1036
1037impl Enumeration {
1038 pub fn default_value(self: Rc<Self>) -> EnumerationValue {
1039 EnumerationValue { value: self.default_value, enumeration: self.clone() }
1040 }
1041
1042 pub fn try_value_from_string(self: Rc<Self>, value: &str) -> Option<EnumerationValue> {
1043 self.values.iter().enumerate().find_map(|(idx, name)| {
1044 if name == value {
1045 Some(EnumerationValue { value: idx, enumeration: self.clone() })
1046 } else {
1047 None
1048 }
1049 })
1050 }
1051}
1052
1053#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1054pub struct KeyboardModifiers {
1055 pub alt: bool,
1056 pub control: bool,
1057 pub meta: bool,
1058 pub shift: bool,
1059}
1060
1061#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1062pub struct Keys {
1063 pub key: SmolStr,
1064 pub modifiers: KeyboardModifiers,
1065 pub ignore_shift: bool,
1066 pub ignore_alt: bool,
1067}
1068
1069impl std::fmt::Display for Keys {
1070 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072 if self.key.is_empty() {
1073 write!(f, "")
1074 } else {
1075 let alt = self
1076 .ignore_alt
1077 .then_some("Alt?+")
1078 .or(self.modifiers.alt.then_some("Alt+"))
1079 .unwrap_or_default();
1080 let ctrl = if self.modifiers.control { "Control+" } else { "" };
1081 let meta = if self.modifiers.meta { "Meta+" } else { "" };
1082 let shift = self
1083 .ignore_shift
1084 .then_some("Shift?+")
1085 .or(self.modifiers.shift.then_some("Shift+"))
1086 .unwrap_or_default();
1087 let keycode: String = self
1088 .key
1089 .chars()
1090 .flat_map(|character| {
1091 let mut escaped = vec![];
1092 if character.is_control() {
1093 escaped.extend(character.escape_unicode());
1094 } else {
1095 escaped.push(character);
1096 }
1097 escaped
1098 })
1099 .collect();
1100 write!(f, "{meta}{ctrl}{alt}{shift}\"{keycode}\"")
1101 }
1102 }
1103}
1104
1105#[derive(Clone, Debug)]
1106pub struct EnumerationValue {
1107 pub value: usize, pub enumeration: Rc<Enumeration>,
1109}
1110
1111impl PartialEq for EnumerationValue {
1112 fn eq(&self, other: &Self) -> bool {
1113 Rc::ptr_eq(&self.enumeration, &other.enumeration) && self.value == other.value
1114 }
1115}
1116
1117impl std::fmt::Display for EnumerationValue {
1118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1119 self.enumeration.values[self.value].fmt(f)
1120 }
1121}
1122
1123impl EnumerationValue {
1124 pub fn to_pascal_case(&self) -> String {
1125 crate::generator::to_pascal_case(&self.enumeration.values[self.value])
1126 }
1127}
1128
1129#[derive(Debug, PartialEq)]
1130pub struct LengthConversionPowers {
1131 pub rem_to_px_power: i8,
1132 pub px_to_phx_power: i8,
1133}
1134
1135pub fn unit_product_length_conversion(
1138 a: &[(Unit, i8)],
1139 b: &[(Unit, i8)],
1140) -> Option<LengthConversionPowers> {
1141 if a.is_empty() && b.is_empty() {
1143 return Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 });
1144 }
1145
1146 let mut units = [0i8; 16];
1147 for (u, count) in a {
1148 units[*u as usize] += count;
1149 }
1150 for (u, count) in b {
1151 units[*u as usize] -= count;
1152 }
1153
1154 if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
1155 return None;
1156 }
1157
1158 if units[Unit::Rem as usize] != 0
1159 && units[Unit::Phx as usize] == -units[Unit::Rem as usize]
1160 && units[Unit::Px as usize] == 0
1161 {
1162 units[Unit::Px as usize] = -units[Unit::Rem as usize];
1163 units[Unit::Phx as usize] = -units[Unit::Rem as usize];
1164 }
1165
1166 let result = LengthConversionPowers {
1167 rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
1168 px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
1169 };
1170
1171 units[Unit::Px as usize] = 0;
1172 units[Unit::Phx as usize] = 0;
1173 units[Unit::Rem as usize] = 0;
1174 units.into_iter().all(|x| x == 0).then_some(result)
1175}
1176
1177#[test]
1178fn unit_product_length_conversion_test() {
1179 use Option::None;
1180 use Unit::*;
1181 assert_eq!(
1182 unit_product_length_conversion(&[], &[]),
1183 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 })
1184 );
1185 assert_eq!(
1186 unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
1187 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1188 );
1189 assert_eq!(
1190 unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
1191 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1192 );
1193 assert_eq!(
1194 unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
1195 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1196 );
1197 assert_eq!(
1198 unit_product_length_conversion(
1199 &[(Deg, 3), (Phx, 2), (Ms, -1)],
1200 &[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
1201 ),
1202 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1203 );
1204 assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1205 assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
1206 assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1207
1208 assert_eq!(
1209 unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
1210 Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
1211 );
1212 assert_eq!(
1213 unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
1214 Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
1215 );
1216 assert_eq!(
1217 unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
1218 Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
1219 );
1220}