1use i_slint_compiler::langtype::Type as LangType;
5use i_slint_core::component_factory::ComponentFactory;
6#[cfg(feature = "internal")]
7use i_slint_core::component_factory::FactoryContext;
8use i_slint_core::graphics::euclid::approxeq::ApproxEq as _;
9use i_slint_core::model::{Model, ModelExt, ModelRc};
10#[cfg(feature = "internal")]
11use i_slint_core::window::WindowInner;
12use i_slint_core::{PathData, SharedVector};
13use smol_str::{SmolStr, StrExt};
14use std::borrow::Cow;
15use std::collections::HashMap;
16use std::future::Future;
17use std::path::{Path, PathBuf};
18use std::rc::Rc;
19
20#[doc(inline)]
21pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel};
22
23pub use i_slint_core::api::*;
24pub use i_slint_backend_selector::api::*;
26pub use i_slint_core::graphics::{
27 Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer,
28};
29use i_slint_core::items::*;
30
31use crate::dynamic_item_tree::ErasedItemTreeBox;
32#[cfg(any(feature = "internal", target_arch = "wasm32"))]
33use crate::dynamic_item_tree::WindowOptions;
34
35#[derive(Debug, Copy, Clone, PartialEq)]
38#[repr(i8)]
39#[non_exhaustive]
40pub enum ValueType {
41 Void,
43 Number,
45 String,
47 Bool,
49 Model,
51 Struct,
53 Brush,
55 Image,
57 #[doc(hidden)]
59 Other = -1,
60}
61
62impl From<LangType> for ValueType {
63 fn from(ty: LangType) -> Self {
64 match ty {
65 LangType::Float32
66 | LangType::Int32
67 | LangType::Duration
68 | LangType::Angle
69 | LangType::PhysicalLength
70 | LangType::LogicalLength
71 | LangType::Percent
72 | LangType::UnitProduct(_) => Self::Number,
73 LangType::String => Self::String,
74 LangType::Color => Self::Brush,
75 LangType::Brush => Self::Brush,
76 LangType::Array(_) => Self::Model,
77 LangType::Bool => Self::Bool,
78 LangType::Struct { .. } => Self::Struct,
79 LangType::Void => Self::Void,
80 LangType::Image => Self::Image,
81 _ => Self::Other,
82 }
83 }
84}
85
86#[derive(Clone, Default)]
98#[non_exhaustive]
99#[repr(u8)]
100pub enum Value {
101 #[default]
104 Void = 0,
105 Number(f64) = 1,
107 String(SharedString) = 2,
109 Bool(bool) = 3,
111 Image(Image) = 4,
113 Model(ModelRc<Value>) = 5,
115 Struct(Struct) = 6,
117 Brush(Brush) = 7,
119 #[doc(hidden)]
120 PathData(PathData) = 8,
122 #[doc(hidden)]
123 EasingCurve(i_slint_core::animations::EasingCurve) = 9,
125 #[doc(hidden)]
126 EnumerationValue(String, String) = 10,
129 #[doc(hidden)]
130 LayoutCache(SharedVector<f32>) = 11,
131 #[doc(hidden)]
132 ComponentFactory(ComponentFactory) = 12,
134}
135
136impl Value {
137 pub fn value_type(&self) -> ValueType {
139 match self {
140 Value::Void => ValueType::Void,
141 Value::Number(_) => ValueType::Number,
142 Value::String(_) => ValueType::String,
143 Value::Bool(_) => ValueType::Bool,
144 Value::Model(_) => ValueType::Model,
145 Value::Struct(_) => ValueType::Struct,
146 Value::Brush(_) => ValueType::Brush,
147 Value::Image(_) => ValueType::Image,
148 _ => ValueType::Other,
149 }
150 }
151}
152
153impl PartialEq for Value {
154 fn eq(&self, other: &Self) -> bool {
155 match self {
156 Value::Void => matches!(other, Value::Void),
157 Value::Number(lhs) => matches!(other, Value::Number(rhs) if lhs.approx_eq(rhs)),
158 Value::String(lhs) => matches!(other, Value::String(rhs) if lhs == rhs),
159 Value::Bool(lhs) => matches!(other, Value::Bool(rhs) if lhs == rhs),
160 Value::Image(lhs) => matches!(other, Value::Image(rhs) if lhs == rhs),
161 Value::Model(lhs) => {
162 if let Value::Model(rhs) = other {
163 lhs == rhs
164 } else {
165 false
166 }
167 }
168 Value::Struct(lhs) => matches!(other, Value::Struct(rhs) if lhs == rhs),
169 Value::Brush(lhs) => matches!(other, Value::Brush(rhs) if lhs == rhs),
170 Value::PathData(lhs) => matches!(other, Value::PathData(rhs) if lhs == rhs),
171 Value::EasingCurve(lhs) => matches!(other, Value::EasingCurve(rhs) if lhs == rhs),
172 Value::EnumerationValue(lhs_name, lhs_value) => {
173 matches!(other, Value::EnumerationValue(rhs_name, rhs_value) if lhs_name == rhs_name && lhs_value == rhs_value)
174 }
175 Value::LayoutCache(lhs) => matches!(other, Value::LayoutCache(rhs) if lhs == rhs),
176 Value::ComponentFactory(lhs) => {
177 matches!(other, Value::ComponentFactory(rhs) if lhs == rhs)
178 }
179 }
180 }
181}
182
183impl std::fmt::Debug for Value {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 match self {
186 Value::Void => write!(f, "Value::Void"),
187 Value::Number(n) => write!(f, "Value::Number({n:?})"),
188 Value::String(s) => write!(f, "Value::String({s:?})"),
189 Value::Bool(b) => write!(f, "Value::Bool({b:?})"),
190 Value::Image(i) => write!(f, "Value::Image({i:?})"),
191 Value::Model(m) => {
192 write!(f, "Value::Model(")?;
193 f.debug_list().entries(m.iter()).finish()?;
194 write!(f, "])")
195 }
196 Value::Struct(s) => write!(f, "Value::Struct({s:?})"),
197 Value::Brush(b) => write!(f, "Value::Brush({b:?})"),
198 Value::PathData(e) => write!(f, "Value::PathElements({e:?})"),
199 Value::EasingCurve(c) => write!(f, "Value::EasingCurve({c:?})"),
200 Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({n:?}, {v:?})"),
201 Value::LayoutCache(v) => write!(f, "Value::LayoutCache({v:?})"),
202 Value::ComponentFactory(factory) => write!(f, "Value::ComponentFactory({factory:?})"),
203 }
204 }
205}
206
207macro_rules! declare_value_conversion {
216 ( $value:ident => [$($ty:ty),*] ) => {
217 $(
218 impl From<$ty> for Value {
219 fn from(v: $ty) -> Self {
220 Value::$value(v as _)
221 }
222 }
223 impl TryFrom<Value> for $ty {
224 type Error = Value;
225 fn try_from(v: Value) -> Result<$ty, Self::Error> {
226 match v {
227 Value::$value(x) => Ok(x as _),
228 _ => Err(v)
229 }
230 }
231 }
232 )*
233 };
234}
235declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] );
236declare_value_conversion!(String => [SharedString] );
237declare_value_conversion!(Bool => [bool] );
238declare_value_conversion!(Image => [Image] );
239declare_value_conversion!(Struct => [Struct] );
240declare_value_conversion!(Brush => [Brush] );
241declare_value_conversion!(PathData => [PathData]);
242declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]);
243declare_value_conversion!(LayoutCache => [SharedVector<f32>] );
244declare_value_conversion!(ComponentFactory => [ComponentFactory] );
245
246macro_rules! declare_value_struct_conversion {
248 (struct $name:path { $($field:ident),* $(, ..$extra:expr)? }) => {
249 impl From<$name> for Value {
250 fn from($name { $($field),* , .. }: $name) -> Self {
251 let mut struct_ = Struct::default();
252 $(struct_.set_field(stringify!($field).into(), $field.into());)*
253 Value::Struct(struct_)
254 }
255 }
256 impl TryFrom<Value> for $name {
257 type Error = ();
258 fn try_from(v: Value) -> Result<$name, Self::Error> {
259 #[allow(clippy::field_reassign_with_default)]
260 match v {
261 Value::Struct(x) => {
262 type Ty = $name;
263 #[allow(unused)]
264 let mut res: Ty = Ty::default();
265 $(let mut res: Ty = $extra;)?
266 $(res.$field = x.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
267 Ok(res)
268 }
269 _ => Err(()),
270 }
271 }
272 }
273 };
274 ($(
275 $(#[$struct_attr:meta])*
276 struct $Name:ident {
277 @name = $inner_name:literal
278 export {
279 $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ty, )*
280 }
281 private {
282 $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )*
283 }
284 }
285 )*) => {
286 $(
287 impl From<$Name> for Value {
288 fn from(item: $Name) -> Self {
289 let mut struct_ = Struct::default();
290 $(struct_.set_field(stringify!($pub_field).into(), item.$pub_field.into());)*
291 $(handle_private!(SET $Name $pri_field, struct_, item);)*
292 Value::Struct(struct_)
293 }
294 }
295 impl TryFrom<Value> for $Name {
296 type Error = ();
297 fn try_from(v: Value) -> Result<$Name, Self::Error> {
298 #[allow(clippy::field_reassign_with_default)]
299 match v {
300 Value::Struct(x) => {
301 type Ty = $Name;
302 #[allow(unused)]
303 let mut res: Ty = Ty::default();
304 $(res.$pub_field = x.get_field(stringify!($pub_field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
305 $(handle_private!(GET $Name $pri_field, x, res);)*
306 Ok(res)
307 }
308 _ => Err(()),
309 }
310 }
311 }
312 )*
313 };
314}
315
316macro_rules! handle_private {
317 (SET StateInfo $field:ident, $struct_:ident, $item:ident) => {
318 $struct_.set_field(stringify!($field).into(), $item.$field.into())
319 };
320 (SET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
321 (GET StateInfo $field:ident, $struct_:ident, $item:ident) => {
322 $item.$field =
323 $struct_.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_| ())?
324 };
325 (GET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
326}
327
328declare_value_struct_conversion!(struct i_slint_core::layout::LayoutInfo { min, max, min_percent, max_percent, preferred, stretch });
329declare_value_struct_conversion!(struct i_slint_core::graphics::Point { x, y, ..Default::default()});
330declare_value_struct_conversion!(struct i_slint_core::api::LogicalPosition { x, y });
331
332i_slint_common::for_each_builtin_structs!(declare_value_struct_conversion);
333
334macro_rules! declare_value_enum_conversion {
339 ($( $(#[$enum_doc:meta])* enum $Name:ident { $($body:tt)* })*) => { $(
340 impl From<i_slint_core::items::$Name> for Value {
341 fn from(v: i_slint_core::items::$Name) -> Self {
342 Value::EnumerationValue(
343 stringify!($Name).to_owned(),
344 v.to_string().trim_start_matches("r#").replace('_', "-"),
345 )
346 }
347 }
348 impl TryFrom<Value> for i_slint_core::items::$Name {
349 type Error = ();
350 fn try_from(v: Value) -> Result<i_slint_core::items::$Name, ()> {
351 use std::str::FromStr;
352 match v {
353 Value::EnumerationValue(enumeration, value) => {
354 if enumeration != stringify!($Name) {
355 return Err(());
356 }
357
358 <i_slint_core::items::$Name>::from_str(value.as_str())
359 .or_else(|_| {
360 let norm = value.as_str().replace('-', "_");
361 <i_slint_core::items::$Name>::from_str(&norm)
362 .or_else(|_| <i_slint_core::items::$Name>::from_str(&format!("r#{}", norm)))
363 })
364 .map_err(|_| ())
365 }
366 _ => Err(()),
367 }
368 }
369 }
370 )*};
371}
372
373i_slint_common::for_each_enums!(declare_value_enum_conversion);
374
375impl From<i_slint_core::animations::Instant> for Value {
376 fn from(value: i_slint_core::animations::Instant) -> Self {
377 Value::Number(value.0 as _)
378 }
379}
380impl TryFrom<Value> for i_slint_core::animations::Instant {
381 type Error = ();
382 fn try_from(v: Value) -> Result<i_slint_core::animations::Instant, Self::Error> {
383 match v {
384 Value::Number(x) => Ok(i_slint_core::animations::Instant(x as _)),
385 _ => Err(()),
386 }
387 }
388}
389
390impl From<()> for Value {
391 #[inline]
392 fn from(_: ()) -> Self {
393 Value::Void
394 }
395}
396impl TryFrom<Value> for () {
397 type Error = ();
398 #[inline]
399 fn try_from(_: Value) -> Result<(), Self::Error> {
400 Ok(())
401 }
402}
403
404impl From<Color> for Value {
405 #[inline]
406 fn from(c: Color) -> Self {
407 Value::Brush(Brush::SolidColor(c))
408 }
409}
410impl TryFrom<Value> for Color {
411 type Error = Value;
412 #[inline]
413 fn try_from(v: Value) -> Result<Color, Self::Error> {
414 match v {
415 Value::Brush(Brush::SolidColor(c)) => Ok(c),
416 _ => Err(v),
417 }
418 }
419}
420
421impl From<i_slint_core::lengths::LogicalLength> for Value {
422 #[inline]
423 fn from(l: i_slint_core::lengths::LogicalLength) -> Self {
424 Value::Number(l.get() as _)
425 }
426}
427impl TryFrom<Value> for i_slint_core::lengths::LogicalLength {
428 type Error = Value;
429 #[inline]
430 fn try_from(v: Value) -> Result<i_slint_core::lengths::LogicalLength, Self::Error> {
431 match v {
432 Value::Number(n) => Ok(i_slint_core::lengths::LogicalLength::new(n as _)),
433 _ => Err(v),
434 }
435 }
436}
437
438impl<T: Into<Value> + 'static> From<ModelRc<T>> for Value {
439 fn from(m: ModelRc<T>) -> Self {
440 if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<Value>>(&m) {
441 Value::Model(v.clone())
442 } else {
443 Value::Model(ModelRc::new(m.map(|v| v.into())))
444 }
445 }
446}
447impl<T: TryFrom<Value> + Default + 'static> TryFrom<Value> for ModelRc<T> {
448 type Error = Value;
449 #[inline]
450 fn try_from(v: Value) -> Result<ModelRc<T>, Self::Error> {
451 match v {
452 Value::Model(m) => {
453 if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<T>>(&m) {
454 Ok(v.clone())
455 } else {
456 Ok(ModelRc::new(m.map(|v| T::try_from(v).unwrap_or_default())))
457 }
458 }
459 _ => Err(v),
460 }
461 }
462}
463
464#[test]
465fn value_model_conversion() {
466 use i_slint_core::model::*;
467 let m = ModelRc::new(VecModel::from_slice(&[Value::Number(42.), Value::Number(12.)]));
468 let v = Value::from(m.clone());
469 assert_eq!(v, Value::Model(m.clone()));
470 let m2: ModelRc<Value> = v.clone().try_into().unwrap();
471 assert_eq!(m2, m);
472
473 let int_model: ModelRc<i32> = v.clone().try_into().unwrap();
474 assert_eq!(int_model.row_count(), 2);
475 assert_eq!(int_model.iter().collect::<Vec<_>>(), vec![42, 12]);
476
477 let Value::Model(m3) = int_model.clone().into() else { panic!("not a model?") };
478 assert_eq!(m3.row_count(), 2);
479 assert_eq!(m3.iter().collect::<Vec<_>>(), vec![Value::Number(42.), Value::Number(12.)]);
480
481 let str_model: ModelRc<SharedString> = v.clone().try_into().unwrap();
482 assert_eq!(str_model.row_count(), 2);
483 assert_eq!(str_model.iter().collect::<Vec<_>>(), vec!["", ""]);
485
486 let err: Result<ModelRc<Value>, _> = Value::Bool(true).try_into();
487 assert!(err.is_err());
488}
489
490pub(crate) fn normalize_identifier(ident: &str) -> Cow<'_, str> {
492 if ident.contains('_') {
493 ident.replace('_', "-").into()
494 } else {
495 ident.into()
496 }
497}
498
499pub(crate) fn normalize_identifier_smolstr(ident: &str) -> SmolStr {
500 if ident.contains('_') {
501 ident.replace_smolstr("_", "-")
502 } else {
503 ident.into()
504 }
505}
506
507#[derive(Clone, PartialEq, Debug, Default)]
529pub struct Struct(pub(crate) HashMap<String, Value>);
530impl Struct {
531 pub fn get_field(&self, name: &str) -> Option<&Value> {
533 self.0.get(&*normalize_identifier(name))
534 }
535 pub fn set_field(&mut self, name: String, value: Value) {
537 if name.contains('_') {
538 self.0.insert(name.replace('_', "-"), value);
539 } else {
540 self.0.insert(name, value);
541 }
542 }
543
544 pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
546 self.0.iter().map(|(a, b)| (a.as_str(), b))
547 }
548}
549
550impl FromIterator<(String, Value)> for Struct {
551 fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
552 Self(
553 iter.into_iter()
554 .map(|(s, v)| (if s.contains('_') { s.replace('_', "-") } else { s }, v))
555 .collect(),
556 )
557 }
558}
559
560#[deprecated(note = "Use slint_interpreter::Compiler instead")]
562pub struct ComponentCompiler {
563 config: i_slint_compiler::CompilerConfiguration,
564 diagnostics: Vec<Diagnostic>,
565}
566
567#[allow(deprecated)]
568impl Default for ComponentCompiler {
569 fn default() -> Self {
570 let mut config = i_slint_compiler::CompilerConfiguration::new(
571 i_slint_compiler::generator::OutputFormat::Interpreter,
572 );
573 config.components_to_generate = i_slint_compiler::ComponentSelection::LastExported;
574 Self { config, diagnostics: vec![] }
575 }
576}
577
578#[allow(deprecated)]
579impl ComponentCompiler {
580 pub fn new() -> Self {
582 Self::default()
583 }
584
585 pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
587 self.config.include_paths = include_paths;
588 }
589
590 pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
592 &self.config.include_paths
593 }
594
595 pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
597 self.config.library_paths = library_paths;
598 }
599
600 pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
602 &self.config.library_paths
603 }
604
605 pub fn set_style(&mut self, style: String) {
617 self.config.style = Some(style);
618 }
619
620 pub fn style(&self) -> Option<&String> {
622 self.config.style.as_ref()
623 }
624
625 pub fn set_translation_domain(&mut self, domain: String) {
627 self.config.translation_domain = Some(domain);
628 }
629
630 pub fn set_file_loader(
638 &mut self,
639 file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
640 + 'static,
641 ) {
642 self.config.open_import_fallback =
643 Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
644 }
645
646 pub fn diagnostics(&self) -> &Vec<Diagnostic> {
648 &self.diagnostics
649 }
650
651 pub async fn build_from_path<P: AsRef<Path>>(
670 &mut self,
671 path: P,
672 ) -> Option<ComponentDefinition> {
673 let path = path.as_ref();
674 let source = match i_slint_compiler::diagnostics::load_from_path(path) {
675 Ok(s) => s,
676 Err(d) => {
677 self.diagnostics = vec![d];
678 return None;
679 }
680 };
681
682 let r = crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await;
683 self.diagnostics = r.diagnostics.into_iter().collect();
684 r.components.into_values().next()
685 }
686
687 pub async fn build_from_source(
704 &mut self,
705 source_code: String,
706 path: PathBuf,
707 ) -> Option<ComponentDefinition> {
708 let r = crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await;
709 self.diagnostics = r.diagnostics.into_iter().collect();
710 r.components.into_values().next()
711 }
712}
713
714pub struct Compiler {
717 config: i_slint_compiler::CompilerConfiguration,
718}
719
720impl Default for Compiler {
721 fn default() -> Self {
722 let config = i_slint_compiler::CompilerConfiguration::new(
723 i_slint_compiler::generator::OutputFormat::Interpreter,
724 );
725 Self { config }
726 }
727}
728
729impl Compiler {
730 pub fn new() -> Self {
732 Self::default()
733 }
734
735 #[doc(hidden)]
739 #[cfg(feature = "internal")]
740 pub fn compiler_configuration(
741 &mut self,
742 _: i_slint_core::InternalToken,
743 ) -> &mut i_slint_compiler::CompilerConfiguration {
744 &mut self.config
745 }
746
747 pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
749 self.config.include_paths = include_paths;
750 }
751
752 pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
754 &self.config.include_paths
755 }
756
757 pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
759 self.config.library_paths = library_paths;
760 }
761
762 pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
764 &self.config.library_paths
765 }
766
767 pub fn set_style(&mut self, style: String) {
778 self.config.style = Some(style);
779 }
780
781 pub fn style(&self) -> Option<&String> {
783 self.config.style.as_ref()
784 }
785
786 pub fn set_translation_domain(&mut self, domain: String) {
788 self.config.translation_domain = Some(domain);
789 }
790
791 pub fn set_file_loader(
799 &mut self,
800 file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
801 + 'static,
802 ) {
803 self.config.open_import_fallback =
804 Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
805 }
806
807 pub async fn build_from_path<P: AsRef<Path>>(&self, path: P) -> CompilationResult {
826 let path = path.as_ref();
827 let source = match i_slint_compiler::diagnostics::load_from_path(path) {
828 Ok(s) => s,
829 Err(d) => {
830 let mut diagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
831 diagnostics.push_compiler_error(d);
832 return CompilationResult {
833 components: HashMap::new(),
834 diagnostics: diagnostics.into_iter().collect(),
835 #[cfg(feature = "internal")]
836 structs_and_enums: Vec::new(),
837 #[cfg(feature = "internal")]
838 named_exports: Vec::new(),
839 };
840 }
841 };
842
843 crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await
844 }
845
846 pub async fn build_from_source(&self, source_code: String, path: PathBuf) -> CompilationResult {
859 crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await
860 }
861}
862
863#[derive(Clone)]
870pub struct CompilationResult {
871 pub(crate) components: HashMap<String, ComponentDefinition>,
872 pub(crate) diagnostics: Vec<Diagnostic>,
873 #[cfg(feature = "internal")]
874 pub(crate) structs_and_enums: Vec<LangType>,
875 #[cfg(feature = "internal")]
877 pub(crate) named_exports: Vec<(String, String)>,
878}
879
880impl core::fmt::Debug for CompilationResult {
881 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
882 f.debug_struct("CompilationResult")
883 .field("components", &self.components.keys())
884 .field("diagnostics", &self.diagnostics)
885 .finish()
886 }
887}
888
889impl CompilationResult {
890 pub fn has_errors(&self) -> bool {
893 self.diagnostics().any(|diag| diag.level() == DiagnosticLevel::Error)
894 }
895
896 pub fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + '_ {
900 self.diagnostics.iter().cloned()
901 }
902
903 #[cfg(feature = "display-diagnostics")]
909 pub fn print_diagnostics(&self) {
910 print_diagnostics(&self.diagnostics)
911 }
912
913 pub fn components(&self) -> impl Iterator<Item = ComponentDefinition> + '_ {
915 self.components.values().cloned()
916 }
917
918 pub fn component_names(&self) -> impl Iterator<Item = &str> + '_ {
920 self.components.keys().map(|s| s.as_str())
921 }
922
923 pub fn component(&self, name: &str) -> Option<ComponentDefinition> {
926 self.components.get(name).cloned()
927 }
928
929 #[doc(hidden)]
931 #[cfg(feature = "internal")]
932 pub fn structs_and_enums(
933 &self,
934 _: i_slint_core::InternalToken,
935 ) -> impl Iterator<Item = &LangType> {
936 self.structs_and_enums.iter()
937 }
938
939 #[doc(hidden)]
942 #[cfg(feature = "internal")]
943 pub fn named_exports(
944 &self,
945 _: i_slint_core::InternalToken,
946 ) -> impl Iterator<Item = &(String, String)> {
947 self.named_exports.iter()
948 }
949}
950
951#[derive(Clone)]
959pub struct ComponentDefinition {
960 pub(crate) inner: crate::dynamic_item_tree::ErasedItemTreeDescription,
961}
962
963impl ComponentDefinition {
964 pub fn create(&self) -> Result<ComponentInstance, PlatformError> {
966 generativity::make_guard!(guard);
967 let instance = self.inner.unerase(guard).clone().create(Default::default())?;
968 instance.window_adapter_ref()?;
970 Ok(ComponentInstance { inner: instance })
971 }
972
973 #[doc(hidden)]
975 #[cfg(feature = "internal")]
976 pub fn create_embedded(&self, ctx: FactoryContext) -> Result<ComponentInstance, PlatformError> {
977 generativity::make_guard!(guard);
978 Ok(ComponentInstance {
979 inner: self.inner.unerase(guard).clone().create(WindowOptions::Embed {
980 parent_item_tree: ctx.parent_item_tree,
981 parent_item_tree_index: ctx.parent_item_tree_index,
982 })?,
983 })
984 }
985
986 #[cfg(target_arch = "wasm32")]
988 pub fn create_with_canvas_id(
989 &self,
990 canvas_id: &str,
991 ) -> Result<ComponentInstance, PlatformError> {
992 generativity::make_guard!(guard);
993 Ok(ComponentInstance {
994 inner: self
995 .inner
996 .unerase(guard)
997 .clone()
998 .create(WindowOptions::CreateWithCanvasId(canvas_id.into()))?,
999 })
1000 }
1001
1002 #[doc(hidden)]
1004 #[cfg(feature = "internal")]
1005 pub fn create_with_existing_window(
1006 &self,
1007 window: &Window,
1008 ) -> Result<ComponentInstance, PlatformError> {
1009 generativity::make_guard!(guard);
1010 Ok(ComponentInstance {
1011 inner: self.inner.unerase(guard).clone().create(WindowOptions::UseExistingWindow(
1012 WindowInner::from_pub(window).window_adapter(),
1013 ))?,
1014 })
1015 }
1016
1017 #[doc(hidden)]
1021 #[cfg(feature = "internal")]
1022 pub fn properties_and_callbacks(
1023 &self,
1024 ) -> impl Iterator<
1025 Item = (
1026 String,
1027 (i_slint_compiler::langtype::Type, i_slint_compiler::object_tree::PropertyVisibility),
1028 ),
1029 > + '_ {
1030 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1033 self.inner.unerase(guard).properties().map(|(s, t, v)| (s.to_string(), (t, v)))
1034 }
1035
1036 pub fn properties(&self) -> impl Iterator<Item = (String, ValueType)> + '_ {
1039 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1042 self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1043 if prop_type.is_property_type() {
1044 Some((prop_name.to_string(), prop_type.into()))
1045 } else {
1046 None
1047 }
1048 })
1049 }
1050
1051 pub fn callbacks(&self) -> impl Iterator<Item = String> + '_ {
1053 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1056 self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1057 if matches!(prop_type, LangType::Callback { .. }) {
1058 Some(prop_name.to_string())
1059 } else {
1060 None
1061 }
1062 })
1063 }
1064
1065 pub fn functions(&self) -> impl Iterator<Item = String> + '_ {
1067 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1070 self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1071 if matches!(prop_type, LangType::Function { .. }) {
1072 Some(prop_name.to_string())
1073 } else {
1074 None
1075 }
1076 })
1077 }
1078
1079 pub fn globals(&self) -> impl Iterator<Item = String> + '_ {
1084 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1087 self.inner.unerase(guard).global_names().map(|s| s.to_string())
1088 }
1089
1090 #[doc(hidden)]
1094 #[cfg(feature = "internal")]
1095 pub fn global_properties_and_callbacks(
1096 &self,
1097 global_name: &str,
1098 ) -> Option<
1099 impl Iterator<
1100 Item = (
1101 String,
1102 (
1103 i_slint_compiler::langtype::Type,
1104 i_slint_compiler::object_tree::PropertyVisibility,
1105 ),
1106 ),
1107 > + '_,
1108 > {
1109 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1112 self.inner
1113 .unerase(guard)
1114 .global_properties(global_name)
1115 .map(|o| o.map(|(s, t, v)| (s.to_string(), (t, v))))
1116 }
1117
1118 pub fn global_properties(
1120 &self,
1121 global_name: &str,
1122 ) -> Option<impl Iterator<Item = (String, ValueType)> + '_> {
1123 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1126 self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1127 iter.filter_map(|(prop_name, prop_type, _)| {
1128 if prop_type.is_property_type() {
1129 Some((prop_name.to_string(), prop_type.into()))
1130 } else {
1131 None
1132 }
1133 })
1134 })
1135 }
1136
1137 pub fn global_callbacks(&self, global_name: &str) -> Option<impl Iterator<Item = String> + '_> {
1139 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1142 self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1143 iter.filter_map(|(prop_name, prop_type, _)| {
1144 if matches!(prop_type, LangType::Callback { .. }) {
1145 Some(prop_name.to_string())
1146 } else {
1147 None
1148 }
1149 })
1150 })
1151 }
1152
1153 pub fn global_functions(&self, global_name: &str) -> Option<impl Iterator<Item = String> + '_> {
1155 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1158 self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1159 iter.filter_map(|(prop_name, prop_type, _)| {
1160 if matches!(prop_type, LangType::Function { .. }) {
1161 Some(prop_name.to_string())
1162 } else {
1163 None
1164 }
1165 })
1166 })
1167 }
1168
1169 pub fn name(&self) -> &str {
1171 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1174 self.inner.unerase(guard).id()
1175 }
1176
1177 #[cfg(feature = "internal")]
1179 #[doc(hidden)]
1180 pub fn root_component(&self) -> Rc<i_slint_compiler::object_tree::Component> {
1181 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1182 self.inner.unerase(guard).original.clone()
1183 }
1184
1185 #[cfg(feature = "internal-highlight")]
1189 pub fn type_loader(&self) -> std::rc::Rc<i_slint_compiler::typeloader::TypeLoader> {
1190 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1191 self.inner.unerase(guard).type_loader.get().unwrap().clone()
1192 }
1193
1194 #[cfg(feature = "internal-highlight")]
1202 pub fn raw_type_loader(&self) -> Option<i_slint_compiler::typeloader::TypeLoader> {
1203 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1204 self.inner
1205 .unerase(guard)
1206 .raw_type_loader
1207 .get()
1208 .unwrap()
1209 .as_ref()
1210 .and_then(|tl| i_slint_compiler::typeloader::snapshot(tl))
1211 }
1212}
1213
1214#[cfg(feature = "display-diagnostics")]
1220pub fn print_diagnostics(diagnostics: &[Diagnostic]) {
1221 let mut build_diagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
1222 for d in diagnostics {
1223 build_diagnostics.push_compiler_error(d.clone())
1224 }
1225 build_diagnostics.print();
1226}
1227
1228#[repr(C)]
1236pub struct ComponentInstance {
1237 inner: crate::dynamic_item_tree::DynamicComponentVRc,
1238}
1239
1240impl ComponentInstance {
1241 pub fn definition(&self) -> ComponentDefinition {
1243 generativity::make_guard!(guard);
1244 ComponentDefinition { inner: self.inner.unerase(guard).description().into() }
1245 }
1246
1247 pub fn get_property(&self, name: &str) -> Result<Value, GetPropertyError> {
1267 generativity::make_guard!(guard);
1268 let comp = self.inner.unerase(guard);
1269 let name = normalize_identifier(name);
1270
1271 if comp
1272 .description()
1273 .original
1274 .root_element
1275 .borrow()
1276 .property_declarations
1277 .get(name.as_ref())
1278 .is_none_or(|d| !d.expose_in_public_api)
1279 {
1280 return Err(GetPropertyError::NoSuchProperty);
1281 }
1282
1283 comp.description()
1284 .get_property(comp.borrow(), &name)
1285 .map_err(|()| GetPropertyError::NoSuchProperty)
1286 }
1287
1288 pub fn set_property(&self, name: &str, value: Value) -> Result<(), SetPropertyError> {
1290 let name = normalize_identifier(name);
1291 generativity::make_guard!(guard);
1292 let comp = self.inner.unerase(guard);
1293 let d = comp.description();
1294 let elem = d.original.root_element.borrow();
1295 let decl = elem
1296 .property_declarations
1297 .get(name.as_ref())
1298 .ok_or(SetPropertyError::NoSuchProperty)?;
1299
1300 if !decl.expose_in_public_api {
1301 return Err(SetPropertyError::NoSuchProperty);
1302 } else if decl.visibility == i_slint_compiler::object_tree::PropertyVisibility::Output {
1303 return Err(SetPropertyError::AccessDenied);
1304 }
1305
1306 d.set_property(comp.borrow(), &name, value)
1307 }
1308
1309 pub fn set_callback(
1344 &self,
1345 name: &str,
1346 callback: impl Fn(&[Value]) -> Value + 'static,
1347 ) -> Result<(), SetCallbackError> {
1348 generativity::make_guard!(guard);
1349 let comp = self.inner.unerase(guard);
1350 comp.description()
1351 .set_callback_handler(comp.borrow(), &normalize_identifier(name), Box::new(callback))
1352 .map_err(|()| SetCallbackError::NoSuchCallback)
1353 }
1354
1355 pub fn invoke(&self, name: &str, args: &[Value]) -> Result<Value, InvokeError> {
1360 generativity::make_guard!(guard);
1361 let comp = self.inner.unerase(guard);
1362 comp.description()
1363 .invoke(comp.borrow(), &normalize_identifier_smolstr(name), args)
1364 .map_err(|()| InvokeError::NoSuchCallable)
1365 }
1366
1367 pub fn get_global_property(
1392 &self,
1393 global: &str,
1394 property: &str,
1395 ) -> Result<Value, GetPropertyError> {
1396 generativity::make_guard!(guard);
1397 let comp = self.inner.unerase(guard);
1398 comp.description()
1399 .get_global(comp.borrow(), &normalize_identifier(global))
1400 .map_err(|()| GetPropertyError::NoSuchProperty)? .as_ref()
1402 .get_property(&normalize_identifier(property))
1403 .map_err(|()| GetPropertyError::NoSuchProperty)
1404 }
1405
1406 pub fn set_global_property(
1408 &self,
1409 global: &str,
1410 property: &str,
1411 value: Value,
1412 ) -> Result<(), SetPropertyError> {
1413 generativity::make_guard!(guard);
1414 let comp = self.inner.unerase(guard);
1415 comp.description()
1416 .get_global(comp.borrow(), &normalize_identifier(global))
1417 .map_err(|()| SetPropertyError::NoSuchProperty)? .as_ref()
1419 .set_property(&normalize_identifier(property), value)
1420 }
1421
1422 pub fn set_global_callback(
1457 &self,
1458 global: &str,
1459 name: &str,
1460 callback: impl Fn(&[Value]) -> Value + 'static,
1461 ) -> Result<(), SetCallbackError> {
1462 generativity::make_guard!(guard);
1463 let comp = self.inner.unerase(guard);
1464 comp.description()
1465 .get_global(comp.borrow(), &normalize_identifier(global))
1466 .map_err(|()| SetCallbackError::NoSuchCallback)? .as_ref()
1468 .set_callback_handler(&normalize_identifier(name), Box::new(callback))
1469 .map_err(|()| SetCallbackError::NoSuchCallback)
1470 }
1471
1472 pub fn invoke_global(
1477 &self,
1478 global: &str,
1479 callable_name: &str,
1480 args: &[Value],
1481 ) -> Result<Value, InvokeError> {
1482 generativity::make_guard!(guard);
1483 let comp = self.inner.unerase(guard);
1484 let g = comp
1485 .description()
1486 .get_global(comp.borrow(), &normalize_identifier(global))
1487 .map_err(|()| InvokeError::NoSuchCallable)?; let callable_name = normalize_identifier_smolstr(callable_name);
1489 if matches!(
1490 comp.description()
1491 .original
1492 .root_element
1493 .borrow()
1494 .lookup_property(&callable_name)
1495 .property_type,
1496 LangType::Function { .. }
1497 ) {
1498 g.as_ref()
1499 .eval_function(&callable_name, args.to_vec())
1500 .map_err(|()| InvokeError::NoSuchCallable)
1501 } else {
1502 g.as_ref()
1503 .invoke_callback(&callable_name, args)
1504 .map_err(|()| InvokeError::NoSuchCallable)
1505 }
1506 }
1507
1508 #[cfg(feature = "internal-highlight")]
1512 pub fn component_positions(
1513 &self,
1514 path: &Path,
1515 offset: u32,
1516 ) -> Vec<i_slint_core::lengths::LogicalRect> {
1517 crate::highlight::component_positions(&self.inner, path, offset)
1518 }
1519
1520 #[cfg(feature = "internal-highlight")]
1524 pub fn element_positions(
1525 &self,
1526 element: &i_slint_compiler::object_tree::ElementRc,
1527 ) -> Vec<i_slint_core::lengths::LogicalRect> {
1528 crate::highlight::element_positions(
1529 &self.inner,
1530 element,
1531 crate::highlight::ElementPositionFilter::IncludeClipped,
1532 )
1533 }
1534
1535 #[cfg(feature = "internal-highlight")]
1539 pub fn element_node_at_source_code_position(
1540 &self,
1541 path: &Path,
1542 offset: u32,
1543 ) -> Vec<(i_slint_compiler::object_tree::ElementRc, usize)> {
1544 crate::highlight::element_node_at_source_code_position(&self.inner, path, offset)
1545 }
1546}
1547
1548impl ComponentHandle for ComponentInstance {
1549 type Inner = crate::dynamic_item_tree::ErasedItemTreeBox;
1550
1551 fn as_weak(&self) -> Weak<Self>
1552 where
1553 Self: Sized,
1554 {
1555 Weak::new(&self.inner)
1556 }
1557
1558 fn clone_strong(&self) -> Self {
1559 Self { inner: self.inner.clone() }
1560 }
1561
1562 fn from_inner(
1563 inner: vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, Self::Inner>,
1564 ) -> Self {
1565 Self { inner }
1566 }
1567
1568 fn show(&self) -> Result<(), PlatformError> {
1569 self.inner.window_adapter_ref()?.window().show()
1570 }
1571
1572 fn hide(&self) -> Result<(), PlatformError> {
1573 self.inner.window_adapter_ref()?.window().hide()
1574 }
1575
1576 fn run(&self) -> Result<(), PlatformError> {
1577 self.show()?;
1578 run_event_loop()?;
1579 self.hide()
1580 }
1581
1582 fn window(&self) -> &Window {
1583 self.inner.window_adapter_ref().unwrap().window()
1584 }
1585
1586 fn global<'a, T: Global<'a, Self>>(&'a self) -> T
1587 where
1588 Self: Sized,
1589 {
1590 unreachable!()
1591 }
1592}
1593
1594impl From<ComponentInstance>
1595 for vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, ErasedItemTreeBox>
1596{
1597 fn from(value: ComponentInstance) -> Self {
1598 value.inner
1599 }
1600}
1601
1602#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1604#[non_exhaustive]
1605pub enum GetPropertyError {
1606 #[display("no such property")]
1608 NoSuchProperty,
1609}
1610
1611#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1613#[non_exhaustive]
1614pub enum SetPropertyError {
1615 #[display("no such property")]
1617 NoSuchProperty,
1618 #[display("wrong type")]
1624 WrongType,
1625 #[display("access denied")]
1627 AccessDenied,
1628}
1629
1630#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1632#[non_exhaustive]
1633pub enum SetCallbackError {
1634 #[display("no such callback")]
1636 NoSuchCallback,
1637}
1638
1639#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1641#[non_exhaustive]
1642pub enum InvokeError {
1643 #[display("no such callback or function")]
1645 NoSuchCallable,
1646}
1647
1648pub fn run_event_loop() -> Result<(), PlatformError> {
1652 i_slint_backend_selector::with_platform(|b| b.run_event_loop())
1653}
1654
1655pub fn spawn_local<F: Future + 'static>(fut: F) -> Result<JoinHandle<F::Output>, EventLoopError> {
1659 i_slint_backend_selector::with_global_context(|ctx| ctx.spawn_local(fut))
1660 .map_err(|_| EventLoopError::NoEventLoopProvider)?
1661}
1662
1663#[cfg(all(feature = "internal", target_arch = "wasm32"))]
1664pub fn spawn_event_loop() -> Result<(), PlatformError> {
1669 i_slint_backend_selector::with_platform(|_| i_slint_backend_winit::spawn_event_loop())
1670}
1671
1672#[doc(hidden)]
1674pub mod testing {
1675 use super::ComponentHandle;
1676 use i_slint_core::window::WindowInner;
1677
1678 pub fn send_mouse_click(comp: &super::ComponentInstance, x: f32, y: f32) {
1680 i_slint_core::tests::slint_send_mouse_click(
1681 x,
1682 y,
1683 &WindowInner::from_pub(comp.window()).window_adapter(),
1684 );
1685 }
1686
1687 pub fn send_keyboard_char(
1689 comp: &super::ComponentInstance,
1690 string: i_slint_core::SharedString,
1691 pressed: bool,
1692 ) {
1693 i_slint_core::tests::slint_send_keyboard_char(
1694 &string,
1695 pressed,
1696 &WindowInner::from_pub(comp.window()).window_adapter(),
1697 );
1698 }
1699 pub fn send_keyboard_string_sequence(
1701 comp: &super::ComponentInstance,
1702 string: i_slint_core::SharedString,
1703 ) {
1704 i_slint_core::tests::send_keyboard_string_sequence(
1705 &string,
1706 &WindowInner::from_pub(comp.window()).window_adapter(),
1707 );
1708 }
1709}
1710
1711#[test]
1712fn component_definition_properties() {
1713 i_slint_backend_testing::init_no_event_loop();
1714 let mut compiler = Compiler::default();
1715 compiler.set_style("fluent".into());
1716 let comp_def = spin_on::spin_on(
1717 compiler.build_from_source(
1718 r#"
1719 export component Dummy {
1720 in-out property <string> test;
1721 in-out property <int> underscores-and-dashes_preserved: 44;
1722 callback hello;
1723 }"#
1724 .into(),
1725 "".into(),
1726 ),
1727 )
1728 .component("Dummy")
1729 .unwrap();
1730
1731 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1732
1733 assert_eq!(props.len(), 2);
1734 assert_eq!(props[0].0, "test");
1735 assert_eq!(props[0].1, ValueType::String);
1736 assert_eq!(props[1].0, "underscores-and-dashes_preserved");
1737 assert_eq!(props[1].1, ValueType::Number);
1738
1739 let instance = comp_def.create().unwrap();
1740 assert_eq!(instance.get_property("underscores_and-dashes-preserved"), Ok(Value::Number(44.)));
1741 assert_eq!(
1742 instance.get_property("underscoresanddashespreserved"),
1743 Err(GetPropertyError::NoSuchProperty)
1744 );
1745 assert_eq!(
1746 instance.set_property("underscores-and_dashes-preserved", Value::Number(88.)),
1747 Ok(())
1748 );
1749 assert_eq!(
1750 instance.set_property("underscoresanddashespreserved", Value::Number(99.)),
1751 Err(SetPropertyError::NoSuchProperty)
1752 );
1753 assert_eq!(
1754 instance.set_property("underscores-and_dashes-preserved", Value::String("99".into())),
1755 Err(SetPropertyError::WrongType)
1756 );
1757 assert_eq!(instance.get_property("underscores-and-dashes-preserved"), Ok(Value::Number(88.)));
1758}
1759
1760#[test]
1761fn component_definition_properties2() {
1762 i_slint_backend_testing::init_no_event_loop();
1763 let mut compiler = Compiler::default();
1764 compiler.set_style("fluent".into());
1765 let comp_def = spin_on::spin_on(
1766 compiler.build_from_source(
1767 r#"
1768 export component Dummy {
1769 in-out property <string> sub-text <=> sub.text;
1770 sub := Text { property <int> private-not-exported; }
1771 out property <string> xreadonly: "the value";
1772 private property <string> xx: sub.text;
1773 callback hello;
1774 }"#
1775 .into(),
1776 "".into(),
1777 ),
1778 )
1779 .component("Dummy")
1780 .unwrap();
1781
1782 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1783
1784 assert_eq!(props.len(), 2);
1785 assert_eq!(props[0].0, "sub-text");
1786 assert_eq!(props[0].1, ValueType::String);
1787 assert_eq!(props[1].0, "xreadonly");
1788
1789 let callbacks = comp_def.callbacks().collect::<Vec<_>>();
1790 assert_eq!(callbacks.len(), 1);
1791 assert_eq!(callbacks[0], "hello");
1792
1793 let instance = comp_def.create().unwrap();
1794 assert_eq!(
1795 instance.set_property("xreadonly", SharedString::from("XXX").into()),
1796 Err(SetPropertyError::AccessDenied)
1797 );
1798 assert_eq!(instance.get_property("xreadonly"), Ok(Value::String("the value".into())));
1799 assert_eq!(
1800 instance.set_property("xx", SharedString::from("XXX").into()),
1801 Err(SetPropertyError::NoSuchProperty)
1802 );
1803 assert_eq!(
1804 instance.set_property("background", Value::default()),
1805 Err(SetPropertyError::NoSuchProperty)
1806 );
1807
1808 assert_eq!(instance.get_property("background"), Err(GetPropertyError::NoSuchProperty));
1809 assert_eq!(instance.get_property("xx"), Err(GetPropertyError::NoSuchProperty));
1810}
1811
1812#[test]
1813fn globals() {
1814 i_slint_backend_testing::init_no_event_loop();
1815 let mut compiler = Compiler::default();
1816 compiler.set_style("fluent".into());
1817 let definition = spin_on::spin_on(
1818 compiler.build_from_source(
1819 r#"
1820 export global My-Super_Global {
1821 in-out property <int> the-property : 21;
1822 callback my-callback();
1823 }
1824 export { My-Super_Global as AliasedGlobal }
1825 export component Dummy {
1826 }"#
1827 .into(),
1828 "".into(),
1829 ),
1830 )
1831 .component("Dummy")
1832 .unwrap();
1833
1834 assert_eq!(definition.globals().collect::<Vec<_>>(), vec!["My-Super_Global", "AliasedGlobal"]);
1835
1836 assert!(definition.global_properties("not-there").is_none());
1837 {
1838 let expected_properties = vec![("the-property".to_string(), ValueType::Number)];
1839 let expected_callbacks = vec!["my-callback".to_string()];
1840
1841 let assert_properties_and_callbacks = |global_name| {
1842 assert_eq!(
1843 definition
1844 .global_properties(global_name)
1845 .map(|props| props.collect::<Vec<_>>())
1846 .as_ref(),
1847 Some(&expected_properties)
1848 );
1849 assert_eq!(
1850 definition
1851 .global_callbacks(global_name)
1852 .map(|props| props.collect::<Vec<_>>())
1853 .as_ref(),
1854 Some(&expected_callbacks)
1855 );
1856 };
1857
1858 assert_properties_and_callbacks("My-Super-Global");
1859 assert_properties_and_callbacks("My_Super-Global");
1860 assert_properties_and_callbacks("AliasedGlobal");
1861 }
1862
1863 let instance = definition.create().unwrap();
1864 assert_eq!(
1865 instance.set_global_property("My_Super-Global", "the_property", Value::Number(44.)),
1866 Ok(())
1867 );
1868 assert_eq!(
1869 instance.set_global_property("AliasedGlobal", "the_property", Value::Number(44.)),
1870 Ok(())
1871 );
1872 assert_eq!(
1873 instance.set_global_property("DontExist", "the-property", Value::Number(88.)),
1874 Err(SetPropertyError::NoSuchProperty)
1875 );
1876
1877 assert_eq!(
1878 instance.set_global_property("My_Super-Global", "theproperty", Value::Number(88.)),
1879 Err(SetPropertyError::NoSuchProperty)
1880 );
1881 assert_eq!(
1882 instance.set_global_property("AliasedGlobal", "theproperty", Value::Number(88.)),
1883 Err(SetPropertyError::NoSuchProperty)
1884 );
1885 assert_eq!(
1886 instance.set_global_property("My_Super-Global", "the_property", Value::String("88".into())),
1887 Err(SetPropertyError::WrongType)
1888 );
1889 assert_eq!(
1890 instance.get_global_property("My-Super_Global", "yoyo"),
1891 Err(GetPropertyError::NoSuchProperty)
1892 );
1893 assert_eq!(
1894 instance.get_global_property("My-Super_Global", "the-property"),
1895 Ok(Value::Number(44.))
1896 );
1897
1898 assert_eq!(
1899 instance.set_property("the-property", Value::Void),
1900 Err(SetPropertyError::NoSuchProperty)
1901 );
1902 assert_eq!(instance.get_property("the-property"), Err(GetPropertyError::NoSuchProperty));
1903
1904 assert_eq!(
1905 instance.set_global_callback("DontExist", "the-property", |_| panic!()),
1906 Err(SetCallbackError::NoSuchCallback)
1907 );
1908 assert_eq!(
1909 instance.set_global_callback("My_Super_Global", "the-property", |_| panic!()),
1910 Err(SetCallbackError::NoSuchCallback)
1911 );
1912 assert_eq!(
1913 instance.set_global_callback("My_Super_Global", "yoyo", |_| panic!()),
1914 Err(SetCallbackError::NoSuchCallback)
1915 );
1916
1917 assert_eq!(
1918 instance.invoke_global("DontExist", "the-property", &[]),
1919 Err(InvokeError::NoSuchCallable)
1920 );
1921 assert_eq!(
1922 instance.invoke_global("My_Super_Global", "the-property", &[]),
1923 Err(InvokeError::NoSuchCallable)
1924 );
1925 assert_eq!(
1926 instance.invoke_global("My_Super_Global", "yoyo", &[]),
1927 Err(InvokeError::NoSuchCallable)
1928 );
1929}
1930
1931#[test]
1932fn call_functions() {
1933 i_slint_backend_testing::init_no_event_loop();
1934 let mut compiler = Compiler::default();
1935 compiler.set_style("fluent".into());
1936 let definition = spin_on::spin_on(
1937 compiler.build_from_source(
1938 r#"
1939 export global Gl {
1940 out property<string> q;
1941 public function foo-bar(a-a: string, b-b:int) -> string {
1942 q = a-a;
1943 return a-a + b-b;
1944 }
1945 }
1946 export component Test {
1947 out property<int> p;
1948 public function foo-bar(a: int, b:int) -> int {
1949 p = a;
1950 return a + b;
1951 }
1952 }"#
1953 .into(),
1954 "".into(),
1955 ),
1956 )
1957 .component("Test")
1958 .unwrap();
1959
1960 assert_eq!(definition.functions().collect::<Vec<_>>(), ["foo-bar"]);
1961 assert_eq!(definition.global_functions("Gl").unwrap().collect::<Vec<_>>(), ["foo-bar"]);
1962
1963 let instance = definition.create().unwrap();
1964
1965 assert_eq!(
1966 instance.invoke("foo_bar", &[Value::Number(3.), Value::Number(4.)]),
1967 Ok(Value::Number(7.))
1968 );
1969 assert_eq!(instance.invoke("p", &[]), Err(InvokeError::NoSuchCallable));
1970 assert_eq!(instance.get_property("p"), Ok(Value::Number(3.)));
1971
1972 assert_eq!(
1973 instance.invoke_global(
1974 "Gl",
1975 "foo_bar",
1976 &[Value::String("Hello".into()), Value::Number(10.)]
1977 ),
1978 Ok(Value::String("Hello10".into()))
1979 );
1980 assert_eq!(instance.get_global_property("Gl", "q"), Ok(Value::String("Hello".into())));
1981}
1982
1983#[test]
1984fn component_definition_struct_properties() {
1985 i_slint_backend_testing::init_no_event_loop();
1986 let mut compiler = Compiler::default();
1987 compiler.set_style("fluent".into());
1988 let comp_def = spin_on::spin_on(
1989 compiler.build_from_source(
1990 r#"
1991 export struct Settings {
1992 string_value: string,
1993 }
1994 export component Dummy {
1995 in-out property <Settings> test;
1996 }"#
1997 .into(),
1998 "".into(),
1999 ),
2000 )
2001 .component("Dummy")
2002 .unwrap();
2003
2004 let props = comp_def.properties().collect::<Vec<(_, _)>>();
2005
2006 assert_eq!(props.len(), 1);
2007 assert_eq!(props[0].0, "test");
2008 assert_eq!(props[0].1, ValueType::Struct);
2009
2010 let instance = comp_def.create().unwrap();
2011
2012 let valid_struct: Struct =
2013 [("string_value".to_string(), Value::String("hello".into()))].iter().cloned().collect();
2014
2015 assert_eq!(instance.set_property("test", Value::Struct(valid_struct.clone())), Ok(()));
2016 assert_eq!(instance.get_property("test").unwrap().value_type(), ValueType::Struct);
2017
2018 assert_eq!(instance.set_property("test", Value::Number(42.)), Err(SetPropertyError::WrongType));
2019
2020 let mut invalid_struct = valid_struct.clone();
2021 invalid_struct.set_field("other".into(), Value::Number(44.));
2022 assert_eq!(
2023 instance.set_property("test", Value::Struct(invalid_struct)),
2024 Err(SetPropertyError::WrongType)
2025 );
2026 let mut invalid_struct = valid_struct;
2027 invalid_struct.set_field("string_value".into(), Value::Number(44.));
2028 assert_eq!(
2029 instance.set_property("test", Value::Struct(invalid_struct)),
2030 Err(SetPropertyError::WrongType)
2031 );
2032}
2033
2034#[test]
2035fn component_definition_model_properties() {
2036 use i_slint_core::model::*;
2037 i_slint_backend_testing::init_no_event_loop();
2038 let mut compiler = Compiler::default();
2039 compiler.set_style("fluent".into());
2040 let comp_def = spin_on::spin_on(compiler.build_from_source(
2041 "export component Dummy { in-out property <[int]> prop: [42, 12]; }".into(),
2042 "".into(),
2043 ))
2044 .component("Dummy")
2045 .unwrap();
2046
2047 let props = comp_def.properties().collect::<Vec<(_, _)>>();
2048 assert_eq!(props.len(), 1);
2049 assert_eq!(props[0].0, "prop");
2050 assert_eq!(props[0].1, ValueType::Model);
2051
2052 let instance = comp_def.create().unwrap();
2053
2054 let int_model =
2055 Value::Model([Value::Number(14.), Value::Number(15.), Value::Number(16.)].into());
2056 let empty_model = Value::Model(ModelRc::new(VecModel::<Value>::default()));
2057 let model_with_string = Value::Model(VecModel::from_slice(&[
2058 Value::Number(1000.),
2059 Value::String("foo".into()),
2060 Value::Number(1111.),
2061 ]));
2062
2063 #[track_caller]
2064 fn check_model(val: Value, r: &[f64]) {
2065 if let Value::Model(m) = val {
2066 assert_eq!(r.len(), m.row_count());
2067 for (i, v) in r.iter().enumerate() {
2068 assert_eq!(m.row_data(i).unwrap(), Value::Number(*v));
2069 }
2070 } else {
2071 panic!("{val:?} not a model");
2072 }
2073 }
2074
2075 assert_eq!(instance.get_property("prop").unwrap().value_type(), ValueType::Model);
2076 check_model(instance.get_property("prop").unwrap(), &[42., 12.]);
2077
2078 instance.set_property("prop", int_model).unwrap();
2079 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2080
2081 assert_eq!(instance.set_property("prop", Value::Number(42.)), Err(SetPropertyError::WrongType));
2082 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2083 assert_eq!(instance.set_property("prop", model_with_string), Err(SetPropertyError::WrongType));
2084 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2085
2086 assert_eq!(instance.set_property("prop", empty_model), Ok(()));
2087 check_model(instance.get_property("prop").unwrap(), &[]);
2088}
2089
2090#[test]
2091fn lang_type_to_value_type() {
2092 use i_slint_compiler::langtype::Struct as LangStruct;
2093 use std::collections::BTreeMap;
2094
2095 assert_eq!(ValueType::from(LangType::Void), ValueType::Void);
2096 assert_eq!(ValueType::from(LangType::Float32), ValueType::Number);
2097 assert_eq!(ValueType::from(LangType::Int32), ValueType::Number);
2098 assert_eq!(ValueType::from(LangType::Duration), ValueType::Number);
2099 assert_eq!(ValueType::from(LangType::Angle), ValueType::Number);
2100 assert_eq!(ValueType::from(LangType::PhysicalLength), ValueType::Number);
2101 assert_eq!(ValueType::from(LangType::LogicalLength), ValueType::Number);
2102 assert_eq!(ValueType::from(LangType::Percent), ValueType::Number);
2103 assert_eq!(ValueType::from(LangType::UnitProduct(vec![])), ValueType::Number);
2104 assert_eq!(ValueType::from(LangType::String), ValueType::String);
2105 assert_eq!(ValueType::from(LangType::Color), ValueType::Brush);
2106 assert_eq!(ValueType::from(LangType::Brush), ValueType::Brush);
2107 assert_eq!(ValueType::from(LangType::Array(Rc::new(LangType::Void))), ValueType::Model);
2108 assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool);
2109 assert_eq!(
2110 ValueType::from(LangType::Struct(Rc::new(LangStruct {
2111 fields: BTreeMap::default(),
2112 name: None,
2113 node: None,
2114 rust_attributes: None
2115 }))),
2116 ValueType::Struct
2117 );
2118 assert_eq!(ValueType::from(LangType::Image), ValueType::Image);
2119}
2120
2121#[test]
2122fn test_multi_components() {
2123 let result = spin_on::spin_on(
2124 Compiler::default().build_from_source(
2125 r#"
2126 export struct Settings {
2127 string_value: string,
2128 }
2129 export global ExpGlo { in-out property <int> test: 42; }
2130 component Common {
2131 in-out property <Settings> settings: { string_value: "Hello", };
2132 }
2133 export component Xyz inherits Window {
2134 in-out property <int> aaa: 8;
2135 }
2136 export component Foo {
2137
2138 in-out property <int> test: 42;
2139 c := Common {}
2140 }
2141 export component Bar inherits Window {
2142 in-out property <int> blah: 78;
2143 c := Common {}
2144 }
2145 "#
2146 .into(),
2147 PathBuf::from("hello.slint"),
2148 ),
2149 );
2150
2151 assert!(!result.has_errors(), "Error {:?}", result.diagnostics().collect::<Vec<_>>());
2152 let mut components = result.component_names().collect::<Vec<_>>();
2153 components.sort();
2154 assert_eq!(components, vec!["Bar", "Xyz"]);
2155 let diag = result.diagnostics().collect::<Vec<_>>();
2156 assert_eq!(diag.len(), 1);
2157 assert_eq!(diag[0].level(), DiagnosticLevel::Warning);
2158 assert_eq!(
2159 diag[0].message(),
2160 "Exported component 'Foo' doesn't inherit Window. No code will be generated for it"
2161 );
2162
2163 let comp1 = result.component("Xyz").unwrap();
2164 assert_eq!(comp1.name(), "Xyz");
2165 let instance1a = comp1.create().unwrap();
2166 let comp2 = result.component("Bar").unwrap();
2167 let instance2 = comp2.create().unwrap();
2168 let instance1b = comp1.create().unwrap();
2169
2170 assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2172 assert_eq!(instance1a.set_global_property("ExpGlo", "test", Value::Number(88.0)), Ok(()));
2173 assert_eq!(instance2.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2174 assert_eq!(instance1b.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2175 assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(88.0)));
2176
2177 assert!(result.component("Settings").is_none());
2178 assert!(result.component("Foo").is_none());
2179 assert!(result.component("Common").is_none());
2180 assert!(result.component("ExpGlo").is_none());
2181 assert!(result.component("xyz").is_none());
2182}
2183
2184#[cfg(all(test, feature = "internal-highlight"))]
2185fn compile(code: &str) -> (ComponentInstance, PathBuf) {
2186 i_slint_backend_testing::init_no_event_loop();
2187 let mut compiler = Compiler::default();
2188 compiler.set_style("fluent".into());
2189 let path = PathBuf::from("/tmp/test.slint");
2190
2191 let compile_result =
2192 spin_on::spin_on(compiler.build_from_source(code.to_string(), path.clone()));
2193
2194 for d in &compile_result.diagnostics {
2195 eprintln!("{d}");
2196 }
2197
2198 assert!(!compile_result.has_errors());
2199
2200 let definition = compile_result.components().next().unwrap();
2201 let instance = definition.create().unwrap();
2202
2203 (instance, path)
2204}
2205
2206#[cfg(feature = "internal-highlight")]
2207#[test]
2208fn test_element_node_at_source_code_position() {
2209 let code = r#"
2210component Bar1 {}
2211
2212component Foo1 {
2213}
2214
2215export component Foo2 inherits Window {
2216 Bar1 {}
2217 Foo1 {}
2218}"#;
2219
2220 let (handle, path) = compile(code);
2221
2222 for i in 0..code.len() as u32 {
2223 let elements = handle.element_node_at_source_code_position(&path, i);
2224 eprintln!("{i}: {}", code.as_bytes()[i as usize] as char);
2225 match i {
2226 16 => assert_eq!(elements.len(), 1), 35 => assert_eq!(elements.len(), 1), 71..=78 => assert_eq!(elements.len(), 1), 85..=89 => assert_eq!(elements.len(), 1), 97..=103 => assert_eq!(elements.len(), 1), _ => assert!(elements.is_empty()),
2232 }
2233 }
2234}
2235
2236#[cfg(feature = "ffi")]
2237#[allow(missing_docs)]
2238#[path = "ffi.rs"]
2239pub(crate) mod ffi;