slint_interpreter/
api.rs

1// Copyright © SixtyFPS GmbH <[email protected]>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use 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;
14use std::collections::HashMap;
15use std::future::Future;
16use std::path::{Path, PathBuf};
17use std::rc::Rc;
18
19#[doc(inline)]
20pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel};
21
22pub use i_slint_core::api::*;
23// keep in sync with api/rs/slint/lib.rs
24pub use i_slint_backend_selector::api::*;
25pub use i_slint_core::graphics::{
26    Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer,
27};
28use i_slint_core::items::*;
29
30use crate::dynamic_item_tree::ErasedItemTreeBox;
31#[cfg(any(feature = "internal", target_arch = "wasm32"))]
32use crate::dynamic_item_tree::WindowOptions;
33
34/// This enum represents the different public variants of the [`Value`] enum, without
35/// the contained values.
36#[derive(Debug, Copy, Clone, PartialEq)]
37#[repr(i8)]
38#[non_exhaustive]
39pub enum ValueType {
40    /// The variant that expresses the non-type. This is the default.
41    Void,
42    /// An `int` or a `float` (this is also used for unit based type such as `length` or `angle`)
43    Number,
44    /// Correspond to the `string` type in .slint
45    String,
46    /// Correspond to the `bool` type in .slint
47    Bool,
48    /// A model (that includes array in .slint)
49    Model,
50    /// An object
51    Struct,
52    /// Correspond to `brush` or `color` type in .slint.  For color, this is then a [`Brush::SolidColor`]
53    Brush,
54    /// Correspond to `image` type in .slint.
55    Image,
56    /// The type is not a public type but something internal.
57    #[doc(hidden)]
58    Other = -1,
59}
60
61impl From<LangType> for ValueType {
62    fn from(ty: LangType) -> Self {
63        match ty {
64            LangType::Float32
65            | LangType::Int32
66            | LangType::Duration
67            | LangType::Angle
68            | LangType::PhysicalLength
69            | LangType::LogicalLength
70            | LangType::Percent
71            | LangType::UnitProduct(_) => Self::Number,
72            LangType::String => Self::String,
73            LangType::Color => Self::Brush,
74            LangType::Brush => Self::Brush,
75            LangType::Array(_) => Self::Model,
76            LangType::Bool => Self::Bool,
77            LangType::Struct { .. } => Self::Struct,
78            LangType::Void => Self::Void,
79            LangType::Image => Self::Image,
80            _ => Self::Other,
81        }
82    }
83}
84
85/// This is a dynamically typed value used in the Slint interpreter.
86/// It can hold a value of different types, and you should use the
87/// [`From`] or [`TryFrom`] traits to access the value.
88///
89/// ```
90/// # use slint_interpreter::*;
91/// use core::convert::TryInto;
92/// // create a value containing an integer
93/// let v = Value::from(100u32);
94/// assert_eq!(v.try_into(), Ok(100u32));
95/// ```
96#[derive(Clone, Default)]
97#[non_exhaustive]
98#[repr(u8)]
99pub enum Value {
100    /// There is nothing in this value. That's the default.
101    /// For example, a function that does not return a result would return a Value::Void
102    #[default]
103    Void = 0,
104    /// An `int` or a `float` (this is also used for unit based type such as `length` or `angle`)
105    Number(f64) = 1,
106    /// Correspond to the `string` type in .slint
107    String(SharedString) = 2,
108    /// Correspond to the `bool` type in .slint
109    Bool(bool) = 3,
110    /// Correspond to the `image` type in .slint
111    Image(Image) = 4,
112    /// A model (that includes array in .slint)
113    Model(ModelRc<Value>) = 5,
114    /// An object
115    Struct(Struct) = 6,
116    /// Correspond to `brush` or `color` type in .slint.  For color, this is then a [`Brush::SolidColor`]
117    Brush(Brush) = 7,
118    #[doc(hidden)]
119    /// The elements of a path
120    PathData(PathData) = 8,
121    #[doc(hidden)]
122    /// An easing curve
123    EasingCurve(i_slint_core::animations::EasingCurve) = 9,
124    #[doc(hidden)]
125    /// An enumeration, like `TextHorizontalAlignment::align_center`, represented by `("TextHorizontalAlignment", "align_center")`.
126    /// FIXME: consider representing that with a number?
127    EnumerationValue(String, String) = 10,
128    #[doc(hidden)]
129    LayoutCache(SharedVector<f32>) = 11,
130    #[doc(hidden)]
131    /// Correspond to the `component-factory` type in .slint
132    ComponentFactory(ComponentFactory) = 12,
133}
134
135impl Value {
136    /// Returns the type variant that this value holds without the containing value.
137    pub fn value_type(&self) -> ValueType {
138        match self {
139            Value::Void => ValueType::Void,
140            Value::Number(_) => ValueType::Number,
141            Value::String(_) => ValueType::String,
142            Value::Bool(_) => ValueType::Bool,
143            Value::Model(_) => ValueType::Model,
144            Value::Struct(_) => ValueType::Struct,
145            Value::Brush(_) => ValueType::Brush,
146            Value::Image(_) => ValueType::Image,
147            _ => ValueType::Other,
148        }
149    }
150}
151
152impl PartialEq for Value {
153    fn eq(&self, other: &Self) -> bool {
154        match self {
155            Value::Void => matches!(other, Value::Void),
156            Value::Number(lhs) => matches!(other, Value::Number(rhs) if lhs.approx_eq(rhs)),
157            Value::String(lhs) => matches!(other, Value::String(rhs) if lhs == rhs),
158            Value::Bool(lhs) => matches!(other, Value::Bool(rhs) if lhs == rhs),
159            Value::Image(lhs) => matches!(other, Value::Image(rhs) if lhs == rhs),
160            Value::Model(lhs) => {
161                if let Value::Model(rhs) = other {
162                    lhs == rhs
163                } else {
164                    false
165                }
166            }
167            Value::Struct(lhs) => matches!(other, Value::Struct(rhs) if lhs == rhs),
168            Value::Brush(lhs) => matches!(other, Value::Brush(rhs) if lhs == rhs),
169            Value::PathData(lhs) => matches!(other, Value::PathData(rhs) if lhs == rhs),
170            Value::EasingCurve(lhs) => matches!(other, Value::EasingCurve(rhs) if lhs == rhs),
171            Value::EnumerationValue(lhs_name, lhs_value) => {
172                matches!(other, Value::EnumerationValue(rhs_name, rhs_value) if lhs_name == rhs_name && lhs_value == rhs_value)
173            }
174            Value::LayoutCache(lhs) => matches!(other, Value::LayoutCache(rhs) if lhs == rhs),
175            Value::ComponentFactory(lhs) => {
176                matches!(other, Value::ComponentFactory(rhs) if lhs == rhs)
177            }
178        }
179    }
180}
181
182impl std::fmt::Debug for Value {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        match self {
185            Value::Void => write!(f, "Value::Void"),
186            Value::Number(n) => write!(f, "Value::Number({n:?})"),
187            Value::String(s) => write!(f, "Value::String({s:?})"),
188            Value::Bool(b) => write!(f, "Value::Bool({b:?})"),
189            Value::Image(i) => write!(f, "Value::Image({i:?})"),
190            Value::Model(m) => {
191                write!(f, "Value::Model(")?;
192                f.debug_list().entries(m.iter()).finish()?;
193                write!(f, "])")
194            }
195            Value::Struct(s) => write!(f, "Value::Struct({s:?})"),
196            Value::Brush(b) => write!(f, "Value::Brush({b:?})"),
197            Value::PathData(e) => write!(f, "Value::PathElements({e:?})"),
198            Value::EasingCurve(c) => write!(f, "Value::EasingCurve({c:?})"),
199            Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({n:?}, {v:?})"),
200            Value::LayoutCache(v) => write!(f, "Value::LayoutCache({v:?})"),
201            Value::ComponentFactory(factory) => write!(f, "Value::ComponentFactory({factory:?})"),
202        }
203    }
204}
205
206/// Helper macro to implement the From / TryFrom for Value
207///
208/// For example
209/// `declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64] );`
210/// means that `Value::Number` can be converted to / from each of the said rust types
211///
212/// For `Value::Object` mapping to a rust `struct`, one can use [`declare_value_struct_conversion!`]
213/// And for `Value::EnumerationValue` which maps to a rust `enum`, one can use [`declare_value_struct_conversion!`]
214macro_rules! declare_value_conversion {
215    ( $value:ident => [$($ty:ty),*] ) => {
216        $(
217            impl From<$ty> for Value {
218                fn from(v: $ty) -> Self {
219                    Value::$value(v as _)
220                }
221            }
222            impl TryFrom<Value> for $ty {
223                type Error = Value;
224                fn try_from(v: Value) -> Result<$ty, Self::Error> {
225                    match v {
226                        Value::$value(x) => Ok(x as _),
227                        _ => Err(v)
228                    }
229                }
230            }
231        )*
232    };
233}
234declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] );
235declare_value_conversion!(String => [SharedString] );
236declare_value_conversion!(Bool => [bool] );
237declare_value_conversion!(Image => [Image] );
238declare_value_conversion!(Struct => [Struct] );
239declare_value_conversion!(Brush => [Brush] );
240declare_value_conversion!(PathData => [PathData]);
241declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]);
242declare_value_conversion!(LayoutCache => [SharedVector<f32>] );
243declare_value_conversion!(ComponentFactory => [ComponentFactory] );
244
245/// Implement From / TryFrom for Value that convert a `struct` to/from `Value::Struct`
246macro_rules! declare_value_struct_conversion {
247    (struct $name:path { $($field:ident),* $(, ..$extra:expr)? }) => {
248        impl From<$name> for Value {
249            fn from($name { $($field),* , .. }: $name) -> Self {
250                let mut struct_ = Struct::default();
251                $(struct_.set_field(stringify!($field).into(), $field.into());)*
252                Value::Struct(struct_)
253            }
254        }
255        impl TryFrom<Value> for $name {
256            type Error = ();
257            fn try_from(v: Value) -> Result<$name, Self::Error> {
258                #[allow(clippy::field_reassign_with_default)]
259                match v {
260                    Value::Struct(x) => {
261                        type Ty = $name;
262                        #[allow(unused)]
263                        let mut res: Ty = Ty::default();
264                        $(let mut res: Ty = $extra;)?
265                        $(res.$field = x.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
266                        Ok(res)
267                    }
268                    _ => Err(()),
269                }
270            }
271        }
272    };
273    ($(
274        $(#[$struct_attr:meta])*
275        struct $Name:ident {
276            @name = $inner_name:literal
277            export {
278                $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ty, )*
279            }
280            private {
281                $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )*
282            }
283        }
284    )*) => {
285        $(
286            impl From<$Name> for Value {
287                fn from(item: $Name) -> Self {
288                    let mut struct_ = Struct::default();
289                    $(struct_.set_field(stringify!($pub_field).into(), item.$pub_field.into());)*
290                    $(handle_private!(SET $Name $pri_field, struct_, item);)*
291                    Value::Struct(struct_)
292                }
293            }
294            impl TryFrom<Value> for $Name {
295                type Error = ();
296                fn try_from(v: Value) -> Result<$Name, Self::Error> {
297                    #[allow(clippy::field_reassign_with_default)]
298                    match v {
299                        Value::Struct(x) => {
300                            type Ty = $Name;
301                            #[allow(unused)]
302                            let mut res: Ty = Ty::default();
303                            $(res.$pub_field = x.get_field(stringify!($pub_field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
304                            $(handle_private!(GET $Name $pri_field, x, res);)*
305                            Ok(res)
306                        }
307                        _ => Err(()),
308                    }
309                }
310            }
311        )*
312    };
313}
314
315macro_rules! handle_private {
316    (SET StateInfo $field:ident, $struct_:ident, $item:ident) => {
317        $struct_.set_field(stringify!($field).into(), $item.$field.into())
318    };
319    (SET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
320    (GET StateInfo $field:ident, $struct_:ident, $item:ident) => {
321        $item.$field =
322            $struct_.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_| ())?
323    };
324    (GET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
325}
326
327declare_value_struct_conversion!(struct i_slint_core::layout::LayoutInfo { min, max, min_percent, max_percent, preferred, stretch });
328declare_value_struct_conversion!(struct i_slint_core::graphics::Point { x, y, ..Default::default()});
329declare_value_struct_conversion!(struct i_slint_core::api::LogicalPosition { x, y });
330
331i_slint_common::for_each_builtin_structs!(declare_value_struct_conversion);
332
333/// Implement From / TryFrom for Value that convert an `enum` to/from `Value::EnumerationValue`
334///
335/// The `enum` must derive `Display` and `FromStr`
336/// (can be done with `strum_macros::EnumString`, `strum_macros::Display` derive macro)
337macro_rules! declare_value_enum_conversion {
338    ($( $(#[$enum_doc:meta])* enum $Name:ident { $($body:tt)* })*) => { $(
339        impl From<i_slint_core::items::$Name> for Value {
340            fn from(v: i_slint_core::items::$Name) -> Self {
341                Value::EnumerationValue(stringify!($Name).to_owned(), v.to_string())
342            }
343        }
344        impl TryFrom<Value> for i_slint_core::items::$Name {
345            type Error = ();
346            fn try_from(v: Value) -> Result<i_slint_core::items::$Name, ()> {
347                use std::str::FromStr;
348                match v {
349                    Value::EnumerationValue(enumeration, value) => {
350                        if enumeration != stringify!($Name) {
351                            return Err(());
352                        }
353                        i_slint_core::items::$Name::from_str(value.as_str()).map_err(|_| ())
354                    }
355                    _ => Err(()),
356                }
357            }
358        }
359    )*};
360}
361
362i_slint_common::for_each_enums!(declare_value_enum_conversion);
363
364impl From<i_slint_core::animations::Instant> for Value {
365    fn from(value: i_slint_core::animations::Instant) -> Self {
366        Value::Number(value.0 as _)
367    }
368}
369impl TryFrom<Value> for i_slint_core::animations::Instant {
370    type Error = ();
371    fn try_from(v: Value) -> Result<i_slint_core::animations::Instant, Self::Error> {
372        match v {
373            Value::Number(x) => Ok(i_slint_core::animations::Instant(x as _)),
374            _ => Err(()),
375        }
376    }
377}
378
379impl From<()> for Value {
380    #[inline]
381    fn from(_: ()) -> Self {
382        Value::Void
383    }
384}
385impl TryFrom<Value> for () {
386    type Error = ();
387    #[inline]
388    fn try_from(_: Value) -> Result<(), Self::Error> {
389        Ok(())
390    }
391}
392
393impl From<Color> for Value {
394    #[inline]
395    fn from(c: Color) -> Self {
396        Value::Brush(Brush::SolidColor(c))
397    }
398}
399impl TryFrom<Value> for Color {
400    type Error = Value;
401    #[inline]
402    fn try_from(v: Value) -> Result<Color, Self::Error> {
403        match v {
404            Value::Brush(Brush::SolidColor(c)) => Ok(c),
405            _ => Err(v),
406        }
407    }
408}
409
410impl From<i_slint_core::lengths::LogicalLength> for Value {
411    #[inline]
412    fn from(l: i_slint_core::lengths::LogicalLength) -> Self {
413        Value::Number(l.get() as _)
414    }
415}
416impl TryFrom<Value> for i_slint_core::lengths::LogicalLength {
417    type Error = Value;
418    #[inline]
419    fn try_from(v: Value) -> Result<i_slint_core::lengths::LogicalLength, Self::Error> {
420        match v {
421            Value::Number(n) => Ok(i_slint_core::lengths::LogicalLength::new(n as _)),
422            _ => Err(v),
423        }
424    }
425}
426
427impl<T: Into<Value> + TryFrom<Value> + 'static> From<ModelRc<T>> for Value {
428    fn from(m: ModelRc<T>) -> Self {
429        if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<Value>>(&m) {
430            Value::Model(v.clone())
431        } else {
432            Value::Model(ModelRc::new(crate::value_model::ValueMapModel(m)))
433        }
434    }
435}
436impl<T: TryFrom<Value> + Default + 'static> TryFrom<Value> for ModelRc<T> {
437    type Error = Value;
438    #[inline]
439    fn try_from(v: Value) -> Result<ModelRc<T>, Self::Error> {
440        match v {
441            Value::Model(m) => {
442                if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<T>>(&m) {
443                    Ok(v.clone())
444                } else if let Some(v) =
445                    m.as_any().downcast_ref::<crate::value_model::ValueMapModel<T>>()
446                {
447                    Ok(v.0.clone())
448                } else {
449                    Ok(ModelRc::new(m.map(|v| T::try_from(v).unwrap_or_default())))
450                }
451            }
452            _ => Err(v),
453        }
454    }
455}
456
457#[test]
458fn value_model_conversion() {
459    use i_slint_core::model::*;
460    let m = ModelRc::new(VecModel::from_slice(&[Value::Number(42.), Value::Number(12.)]));
461    let v = Value::from(m.clone());
462    assert_eq!(v, Value::Model(m.clone()));
463    let m2: ModelRc<Value> = v.clone().try_into().unwrap();
464    assert_eq!(m2, m);
465
466    let int_model: ModelRc<i32> = v.clone().try_into().unwrap();
467    assert_eq!(int_model.row_count(), 2);
468    assert_eq!(int_model.iter().collect::<Vec<_>>(), vec![42, 12]);
469
470    let Value::Model(m3) = int_model.clone().into() else { panic!("not a model?") };
471    assert_eq!(m3.row_count(), 2);
472    assert_eq!(m3.iter().collect::<Vec<_>>(), vec![Value::Number(42.), Value::Number(12.)]);
473
474    let str_model: ModelRc<SharedString> = v.clone().try_into().unwrap();
475    assert_eq!(str_model.row_count(), 2);
476    // Value::Int doesn't convert to string, but since the mapping can't report error, we get the default constructed string
477    assert_eq!(str_model.iter().collect::<Vec<_>>(), vec!["", ""]);
478
479    let err: Result<ModelRc<Value>, _> = Value::Bool(true).try_into();
480    assert!(err.is_err());
481
482    let model =
483        Rc::new(VecModel::<SharedString>::from_iter(["foo".into(), "bar".into(), "baz".into()]));
484
485    let value: Value = ModelRc::from(model.clone()).into();
486    let value_model: ModelRc<Value> = value.clone().try_into().unwrap();
487    assert_eq!(value_model.row_data(2).unwrap(), Value::String("baz".into()));
488    value_model.set_row_data(1, Value::String("qux".into()));
489    value_model.set_row_data(0, Value::Bool(true));
490    assert_eq!(value_model.row_data(1).unwrap(), Value::String("qux".into()));
491    // This is backed by a string model, so changing to bool has no effect
492    assert_eq!(value_model.row_data(0).unwrap(), Value::String("foo".into()));
493
494    // The original values are changed
495    assert_eq!(model.row_data(1).unwrap(), SharedString::from("qux"));
496    assert_eq!(model.row_data(0).unwrap(), SharedString::from("foo"));
497
498    let the_model: ModelRc<SharedString> = value.try_into().unwrap();
499    assert_eq!(the_model.row_data(1).unwrap(), SharedString::from("qux"));
500    assert_eq!(
501        model.as_ref() as *const VecModel<SharedString>,
502        the_model.as_any().downcast_ref::<VecModel<SharedString>>().unwrap()
503            as *const VecModel<SharedString>
504    );
505}
506
507pub(crate) fn normalize_identifier(ident: &str) -> SmolStr {
508    i_slint_compiler::parser::normalize_identifier(ident)
509}
510
511/// This type represents a runtime instance of structure in `.slint`.
512///
513/// This can either be an instance of a name structure introduced
514/// with the `struct` keyword in the .slint file, or an anonymous struct
515/// written with the `{ key: value, }`  notation.
516///
517/// It can be constructed with the [`FromIterator`] trait, and converted
518/// into or from a [`Value`] with the [`From`], [`TryFrom`] trait
519///
520///
521/// ```
522/// # use slint_interpreter::*;
523/// use core::convert::TryInto;
524/// // Construct a value from a key/value iterator
525/// let value : Value = [("foo".into(), 45u32.into()), ("bar".into(), true.into())]
526///     .iter().cloned().collect::<Struct>().into();
527///
528/// // get the properties of a `{ foo: 45, bar: true }`
529/// let s : Struct = value.try_into().unwrap();
530/// assert_eq!(s.get_field("foo").cloned().unwrap().try_into(), Ok(45u32));
531/// ```
532#[derive(Clone, PartialEq, Debug, Default)]
533pub struct Struct(pub(crate) HashMap<SmolStr, Value>);
534impl Struct {
535    /// Get the value for a given struct field
536    pub fn get_field(&self, name: &str) -> Option<&Value> {
537        self.0.get(&*normalize_identifier(name))
538    }
539    /// Set the value of a given struct field
540    pub fn set_field(&mut self, name: String, value: Value) {
541        self.0.insert(normalize_identifier(&name), value);
542    }
543
544    /// Iterate over all the fields in this struct
545    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(iter.into_iter().map(|(s, v)| (normalize_identifier(&s), v)).collect())
553    }
554}
555
556/// ComponentCompiler is deprecated, use [`Compiler`] instead
557#[deprecated(note = "Use slint_interpreter::Compiler instead")]
558pub struct ComponentCompiler {
559    config: i_slint_compiler::CompilerConfiguration,
560    diagnostics: Vec<Diagnostic>,
561}
562
563#[allow(deprecated)]
564impl Default for ComponentCompiler {
565    fn default() -> Self {
566        let mut config = i_slint_compiler::CompilerConfiguration::new(
567            i_slint_compiler::generator::OutputFormat::Interpreter,
568        );
569        config.components_to_generate = i_slint_compiler::ComponentSelection::LastExported;
570        Self { config, diagnostics: vec![] }
571    }
572}
573
574#[allow(deprecated)]
575impl ComponentCompiler {
576    /// Returns a new ComponentCompiler.
577    pub fn new() -> Self {
578        Self::default()
579    }
580
581    /// Sets the include paths used for looking up `.slint` imports to the specified vector of paths.
582    pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
583        self.config.include_paths = include_paths;
584    }
585
586    /// Returns the include paths the component compiler is currently configured with.
587    pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
588        &self.config.include_paths
589    }
590
591    /// Sets the library paths used for looking up `@library` imports to the specified map of library names to paths.
592    pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
593        self.config.library_paths = library_paths;
594    }
595
596    /// Returns the library paths the component compiler is currently configured with.
597    pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
598        &self.config.library_paths
599    }
600
601    /// Sets the style to be used for widgets.
602    ///
603    /// Use the "material" style as widget style when compiling:
604    /// ```rust
605    /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, ComponentHandle};
606    ///
607    /// let mut compiler = ComponentCompiler::default();
608    /// compiler.set_style("material".into());
609    /// let definition =
610    ///     spin_on::spin_on(compiler.build_from_path("hello.slint"));
611    /// ```
612    pub fn set_style(&mut self, style: String) {
613        self.config.style = Some(style);
614    }
615
616    /// Returns the widget style the compiler is currently using when compiling .slint files.
617    pub fn style(&self) -> Option<&String> {
618        self.config.style.as_ref()
619    }
620
621    /// The domain used for translations
622    pub fn set_translation_domain(&mut self, domain: String) {
623        self.config.translation_domain = Some(domain);
624    }
625
626    /// Sets the callback that will be invoked when loading imported .slint files. The specified
627    /// `file_loader_callback` parameter will be called with a canonical file path as argument
628    /// and is expected to return a future that, when resolved, provides the source code of the
629    /// .slint file to be imported as a string.
630    /// If an error is returned, then the build will abort with that error.
631    /// If None is returned, it means the normal resolution algorithm will proceed as if the hook
632    /// was not in place (i.e: load from the file system following the include paths)
633    pub fn set_file_loader(
634        &mut self,
635        file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
636            + 'static,
637    ) {
638        self.config.open_import_fallback =
639            Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
640    }
641
642    /// Returns the diagnostics that were produced in the last call to [`Self::build_from_path`] or [`Self::build_from_source`].
643    pub fn diagnostics(&self) -> &Vec<Diagnostic> {
644        &self.diagnostics
645    }
646
647    /// Compile a .slint file into a ComponentDefinition
648    ///
649    /// Returns the compiled `ComponentDefinition` if there were no errors.
650    ///
651    /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
652    /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
653    /// function. The [`print_diagnostics`] function can be used to display the diagnostics
654    /// to the users.
655    ///
656    /// Diagnostics from previous calls are cleared when calling this function.
657    ///
658    /// If the path is `"-"`, the file will be read from stdin.
659    /// If the extension of the file .rs, the first `slint!` macro from a rust file will be extracted
660    ///
661    /// This function is `async` but in practice, this is only asynchronous if
662    /// [`Self::set_file_loader`] was called and its future is actually asynchronous.
663    /// If that is not used, then it is fine to use a very simple executor, such as the one
664    /// provided by the `spin_on` crate
665    pub async fn build_from_path<P: AsRef<Path>>(
666        &mut self,
667        path: P,
668    ) -> Option<ComponentDefinition> {
669        let path = path.as_ref();
670        let source = match i_slint_compiler::diagnostics::load_from_path(path) {
671            Ok(s) => s,
672            Err(d) => {
673                self.diagnostics = vec![d];
674                return None;
675            }
676        };
677
678        let r = crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await;
679        self.diagnostics = r.diagnostics.into_iter().collect();
680        r.components.into_values().next()
681    }
682
683    /// Compile some .slint code into a ComponentDefinition
684    ///
685    /// The `path` argument will be used for diagnostics and to compute relative
686    /// paths while importing.
687    ///
688    /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
689    /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
690    /// function. The [`print_diagnostics`] function can be used to display the diagnostics
691    /// to the users.
692    ///
693    /// Diagnostics from previous calls are cleared when calling this function.
694    ///
695    /// This function is `async` but in practice, this is only asynchronous if
696    /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
697    /// If that is not used, then it is fine to use a very simple executor, such as the one
698    /// provided by the `spin_on` crate
699    pub async fn build_from_source(
700        &mut self,
701        source_code: String,
702        path: PathBuf,
703    ) -> Option<ComponentDefinition> {
704        let r = crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await;
705        self.diagnostics = r.diagnostics.into_iter().collect();
706        r.components.into_values().next()
707    }
708}
709
710/// This is the entry point of the crate, it can be used to load a `.slint` file and
711/// compile it into a [`CompilationResult`].
712pub struct Compiler {
713    config: i_slint_compiler::CompilerConfiguration,
714}
715
716impl Default for Compiler {
717    fn default() -> Self {
718        let config = i_slint_compiler::CompilerConfiguration::new(
719            i_slint_compiler::generator::OutputFormat::Interpreter,
720        );
721        Self { config }
722    }
723}
724
725impl Compiler {
726    /// Returns a new Compiler.
727    pub fn new() -> Self {
728        Self::default()
729    }
730
731    /// Allow access to the underlying `CompilerConfiguration`
732    ///
733    /// This is an internal function without and ABI or API stability guarantees.
734    #[doc(hidden)]
735    #[cfg(feature = "internal")]
736    pub fn compiler_configuration(
737        &mut self,
738        _: i_slint_core::InternalToken,
739    ) -> &mut i_slint_compiler::CompilerConfiguration {
740        &mut self.config
741    }
742
743    /// Sets the include paths used for looking up `.slint` imports to the specified vector of paths.
744    pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
745        self.config.include_paths = include_paths;
746    }
747
748    /// Returns the include paths the component compiler is currently configured with.
749    pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
750        &self.config.include_paths
751    }
752
753    /// Sets the library paths used for looking up `@library` imports to the specified map of library names to paths.
754    pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
755        self.config.library_paths = library_paths;
756    }
757
758    /// Returns the library paths the component compiler is currently configured with.
759    pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
760        &self.config.library_paths
761    }
762
763    /// Sets the style to be used for widgets.
764    ///
765    /// Use the "material" style as widget style when compiling:
766    /// ```rust
767    /// use slint_interpreter::{ComponentDefinition, Compiler, ComponentHandle};
768    ///
769    /// let mut compiler = Compiler::default();
770    /// compiler.set_style("material".into());
771    /// let result = spin_on::spin_on(compiler.build_from_path("hello.slint"));
772    /// ```
773    pub fn set_style(&mut self, style: String) {
774        self.config.style = Some(style);
775    }
776
777    /// Returns the widget style the compiler is currently using when compiling .slint files.
778    pub fn style(&self) -> Option<&String> {
779        self.config.style.as_ref()
780    }
781
782    /// The domain used for translations
783    pub fn set_translation_domain(&mut self, domain: String) {
784        self.config.translation_domain = Some(domain);
785    }
786
787    /// Sets the callback that will be invoked when loading imported .slint files. The specified
788    /// `file_loader_callback` parameter will be called with a canonical file path as argument
789    /// and is expected to return a future that, when resolved, provides the source code of the
790    /// .slint file to be imported as a string.
791    /// If an error is returned, then the build will abort with that error.
792    /// If None is returned, it means the normal resolution algorithm will proceed as if the hook
793    /// was not in place (i.e: load from the file system following the include paths)
794    pub fn set_file_loader(
795        &mut self,
796        file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
797            + 'static,
798    ) {
799        self.config.open_import_fallback =
800            Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
801    }
802
803    /// Compile a .slint file
804    ///
805    /// Returns a structure that holds the diagnostics and the compiled components.
806    ///
807    /// Any diagnostics produced during the compilation, such as warnings or errors, can be retrieved
808    /// after the call using [`CompilationResult::diagnostics()`].
809    ///
810    /// If the file was compiled without error, the list of component names can be obtained with
811    /// [`CompilationResult::component_names`], and the compiled components themselves with
812    /// [`CompilationResult::component()`].
813    ///
814    /// If the path is `"-"`, the file will be read from stdin.
815    /// If the extension of the file .rs, the first `slint!` macro from a rust file will be extracted
816    ///
817    /// This function is `async` but in practice, this is only asynchronous if
818    /// [`Self::set_file_loader`] was called and its future is actually asynchronous.
819    /// If that is not used, then it is fine to use a very simple executor, such as the one
820    /// provided by the `spin_on` crate
821    pub async fn build_from_path<P: AsRef<Path>>(&self, path: P) -> CompilationResult {
822        let path = path.as_ref();
823        let source = match i_slint_compiler::diagnostics::load_from_path(path) {
824            Ok(s) => s,
825            Err(d) => {
826                let mut diagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
827                diagnostics.push_compiler_error(d);
828                return CompilationResult {
829                    components: HashMap::new(),
830                    diagnostics: diagnostics.into_iter().collect(),
831                    #[cfg(feature = "internal")]
832                    structs_and_enums: Vec::new(),
833                    #[cfg(feature = "internal")]
834                    named_exports: Vec::new(),
835                };
836            }
837        };
838
839        crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await
840    }
841
842    /// Compile some .slint code
843    ///
844    /// The `path` argument will be used for diagnostics and to compute relative
845    /// paths while importing.
846    ///
847    /// Any diagnostics produced during the compilation, such as warnings or errors, can be retrieved
848    /// after the call using [`CompilationResult::diagnostics()`].
849    ///
850    /// This function is `async` but in practice, this is only asynchronous if
851    /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
852    /// If that is not used, then it is fine to use a very simple executor, such as the one
853    /// provided by the `spin_on` crate
854    pub async fn build_from_source(&self, source_code: String, path: PathBuf) -> CompilationResult {
855        crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await
856    }
857}
858
859/// The result of a compilation
860///
861/// If [`Self::has_errors()`] is true, then the compilation failed.
862/// The [`Self::diagnostics()`] function can be used to retrieve the diagnostics (errors and/or warnings)
863/// or [`Self::print_diagnostics()`] can be used to print them to stderr.
864/// The components can be retrieved using [`Self::components()`]
865#[derive(Clone)]
866pub struct CompilationResult {
867    pub(crate) components: HashMap<String, ComponentDefinition>,
868    pub(crate) diagnostics: Vec<Diagnostic>,
869    #[cfg(feature = "internal")]
870    pub(crate) structs_and_enums: Vec<LangType>,
871    /// For `export { Foo as Bar }` this vec contains tuples of (`Foo`, `Bar`)
872    #[cfg(feature = "internal")]
873    pub(crate) named_exports: Vec<(String, String)>,
874}
875
876impl core::fmt::Debug for CompilationResult {
877    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
878        f.debug_struct("CompilationResult")
879            .field("components", &self.components.keys())
880            .field("diagnostics", &self.diagnostics)
881            .finish()
882    }
883}
884
885impl CompilationResult {
886    /// Returns true if the compilation failed.
887    /// The errors can be retrieved using the [`Self::diagnostics()`] function.
888    pub fn has_errors(&self) -> bool {
889        self.diagnostics().any(|diag| diag.level() == DiagnosticLevel::Error)
890    }
891
892    /// Return an iterator over the diagnostics.
893    ///
894    /// You can also call [`Self::print_diagnostics()`] to output the diagnostics to stderr
895    pub fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + '_ {
896        self.diagnostics.iter().cloned()
897    }
898
899    /// Print the diagnostics to stderr
900    ///
901    /// The diagnostics are printed in the same style as rustc errors
902    ///
903    /// This function is available when the `display-diagnostics` is enabled.
904    #[cfg(feature = "display-diagnostics")]
905    pub fn print_diagnostics(&self) {
906        print_diagnostics(&self.diagnostics)
907    }
908
909    /// Returns an iterator over the compiled components.
910    pub fn components(&self) -> impl Iterator<Item = ComponentDefinition> + '_ {
911        self.components.values().cloned()
912    }
913
914    /// Returns the names of the components that were compiled.
915    pub fn component_names(&self) -> impl Iterator<Item = &str> + '_ {
916        self.components.keys().map(|s| s.as_str())
917    }
918
919    /// Return the component definition for the given name.
920    /// If the component does not exist, then `None` is returned.
921    pub fn component(&self, name: &str) -> Option<ComponentDefinition> {
922        self.components.get(name).cloned()
923    }
924
925    /// This is an internal function without API stability guarantees.
926    #[doc(hidden)]
927    #[cfg(feature = "internal")]
928    pub fn structs_and_enums(
929        &self,
930        _: i_slint_core::InternalToken,
931    ) -> impl Iterator<Item = &LangType> {
932        self.structs_and_enums.iter()
933    }
934
935    /// This is an internal function without API stability guarantees.
936    /// Returns the list of named export aliases as tuples (`export { Foo as Bar}` is (`Foo`, `Bar` tuple)).
937    #[doc(hidden)]
938    #[cfg(feature = "internal")]
939    pub fn named_exports(
940        &self,
941        _: i_slint_core::InternalToken,
942    ) -> impl Iterator<Item = &(String, String)> {
943        self.named_exports.iter()
944    }
945}
946
947/// ComponentDefinition is a representation of a compiled component from .slint markup.
948///
949/// It can be constructed from a .slint file using the [`Compiler::build_from_path`] or [`Compiler::build_from_source`] functions.
950/// And then it can be instantiated with the [`Self::create`] function.
951///
952/// The ComponentDefinition acts as a factory to create new instances. When you've finished
953/// creating the instances it is safe to drop the ComponentDefinition.
954#[derive(Clone)]
955pub struct ComponentDefinition {
956    pub(crate) inner: crate::dynamic_item_tree::ErasedItemTreeDescription,
957}
958
959impl ComponentDefinition {
960    /// Set a `debug(...)` handler
961    #[doc(hidden)]
962    #[cfg(feature = "internal")]
963    pub fn set_debug_handler(
964        &self,
965        handler: impl Fn(Option<&i_slint_compiler::diagnostics::SourceLocation>, &str) + 'static,
966        _: i_slint_core::InternalToken,
967    ) {
968        let handler = Rc::new(handler);
969
970        generativity::make_guard!(guard);
971        self.inner.unerase(guard).recursively_set_debug_handler(handler);
972    }
973    /// Creates a new instance of the component and returns a shared handle to it.
974    pub fn create(&self) -> Result<ComponentInstance, PlatformError> {
975        generativity::make_guard!(guard);
976        let instance = self.inner.unerase(guard).clone().create(Default::default())?;
977        // Make sure the window adapter is created so call to `window()` do not panic later.
978        instance.window_adapter_ref()?;
979        Ok(ComponentInstance { inner: instance })
980    }
981
982    /// Creates a new instance of the component and returns a shared handle to it.
983    #[doc(hidden)]
984    #[cfg(feature = "internal")]
985    pub fn create_embedded(&self, ctx: FactoryContext) -> Result<ComponentInstance, PlatformError> {
986        generativity::make_guard!(guard);
987        Ok(ComponentInstance {
988            inner: self.inner.unerase(guard).clone().create(WindowOptions::Embed {
989                parent_item_tree: ctx.parent_item_tree,
990                parent_item_tree_index: ctx.parent_item_tree_index,
991            })?,
992        })
993    }
994
995    /// Instantiate the component using an existing window.
996    #[doc(hidden)]
997    #[cfg(feature = "internal")]
998    pub fn create_with_existing_window(
999        &self,
1000        window: &Window,
1001    ) -> Result<ComponentInstance, PlatformError> {
1002        generativity::make_guard!(guard);
1003        Ok(ComponentInstance {
1004            inner: self.inner.unerase(guard).clone().create(WindowOptions::UseExistingWindow(
1005                WindowInner::from_pub(window).window_adapter(),
1006            ))?,
1007        })
1008    }
1009
1010    /// List of publicly declared properties or callback.
1011    ///
1012    /// This is internal because it exposes the `Type` from compilerlib.
1013    #[doc(hidden)]
1014    #[cfg(feature = "internal")]
1015    pub fn properties_and_callbacks(
1016        &self,
1017    ) -> impl Iterator<
1018        Item = (
1019            String,
1020            (i_slint_compiler::langtype::Type, i_slint_compiler::object_tree::PropertyVisibility),
1021        ),
1022    > + '_ {
1023        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1024        // which is not required, but this is safe because there is only one instance of the unerased type
1025        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1026        self.inner.unerase(guard).properties().map(|(s, t, v)| (s.to_string(), (t, v)))
1027    }
1028
1029    /// Returns an iterator over all publicly declared properties. Each iterator item is a tuple of property name
1030    /// and property type for each of them.
1031    pub fn properties(&self) -> impl Iterator<Item = (String, ValueType)> + '_ {
1032        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1033        // which is not required, but this is safe because there is only one instance of the unerased type
1034        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1035        self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1036            if prop_type.is_property_type() {
1037                Some((prop_name.to_string(), prop_type.into()))
1038            } else {
1039                None
1040            }
1041        })
1042    }
1043
1044    /// Returns the names of all publicly declared callbacks.
1045    pub fn callbacks(&self) -> impl Iterator<Item = String> + '_ {
1046        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1047        // which is not required, but this is safe because there is only one instance of the unerased type
1048        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1049        self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1050            if matches!(prop_type, LangType::Callback { .. }) {
1051                Some(prop_name.to_string())
1052            } else {
1053                None
1054            }
1055        })
1056    }
1057
1058    /// Returns the names of all publicly declared functions.
1059    pub fn functions(&self) -> impl Iterator<Item = String> + '_ {
1060        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1061        // which is not required, but this is safe because there is only one instance of the unerased type
1062        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1063        self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type, _)| {
1064            if matches!(prop_type, LangType::Function { .. }) {
1065                Some(prop_name.to_string())
1066            } else {
1067                None
1068            }
1069        })
1070    }
1071
1072    /// Returns the names of all exported global singletons
1073    ///
1074    /// **Note:** Only globals that are exported or re-exported from the main .slint file will
1075    /// be exposed in the API
1076    pub fn globals(&self) -> impl Iterator<Item = String> + '_ {
1077        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1078        // which is not required, but this is safe because there is only one instance of the unerased type
1079        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1080        self.inner.unerase(guard).global_names().map(|s| s.to_string())
1081    }
1082
1083    /// List of publicly declared properties or callback in the exported global singleton specified by its name.
1084    ///
1085    /// This is internal because it exposes the `Type` from compilerlib.
1086    #[doc(hidden)]
1087    #[cfg(feature = "internal")]
1088    pub fn global_properties_and_callbacks(
1089        &self,
1090        global_name: &str,
1091    ) -> Option<
1092        impl Iterator<
1093                Item = (
1094                    String,
1095                    (
1096                        i_slint_compiler::langtype::Type,
1097                        i_slint_compiler::object_tree::PropertyVisibility,
1098                    ),
1099                ),
1100            > + '_,
1101    > {
1102        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1103        // which is not required, but this is safe because there is only one instance of the unerased type
1104        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1105        self.inner
1106            .unerase(guard)
1107            .global_properties(global_name)
1108            .map(|o| o.map(|(s, t, v)| (s.to_string(), (t, v))))
1109    }
1110
1111    /// List of publicly declared properties in the exported global singleton specified by its name.
1112    pub fn global_properties(
1113        &self,
1114        global_name: &str,
1115    ) -> Option<impl Iterator<Item = (String, ValueType)> + '_> {
1116        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1117        // which is not required, but this is safe because there is only one instance of the unerased type
1118        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1119        self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1120            iter.filter_map(|(prop_name, prop_type, _)| {
1121                if prop_type.is_property_type() {
1122                    Some((prop_name.to_string(), prop_type.into()))
1123                } else {
1124                    None
1125                }
1126            })
1127        })
1128    }
1129
1130    /// List of publicly declared callbacks in the exported global singleton specified by its name.
1131    pub fn global_callbacks(&self, global_name: &str) -> Option<impl Iterator<Item = String> + '_> {
1132        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1133        // which is not required, but this is safe because there is only one instance of the unerased type
1134        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1135        self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1136            iter.filter_map(|(prop_name, prop_type, _)| {
1137                if matches!(prop_type, LangType::Callback { .. }) {
1138                    Some(prop_name.to_string())
1139                } else {
1140                    None
1141                }
1142            })
1143        })
1144    }
1145
1146    /// List of publicly declared functions in the exported global singleton specified by its name.
1147    pub fn global_functions(&self, global_name: &str) -> Option<impl Iterator<Item = String> + '_> {
1148        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1149        // which is not required, but this is safe because there is only one instance of the unerased type
1150        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1151        self.inner.unerase(guard).global_properties(global_name).map(|iter| {
1152            iter.filter_map(|(prop_name, prop_type, _)| {
1153                if matches!(prop_type, LangType::Function { .. }) {
1154                    Some(prop_name.to_string())
1155                } else {
1156                    None
1157                }
1158            })
1159        })
1160    }
1161
1162    /// The name of this Component as written in the .slint file
1163    pub fn name(&self) -> &str {
1164        // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
1165        // which is not required, but this is safe because there is only one instance of the unerased type
1166        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1167        self.inner.unerase(guard).id()
1168    }
1169
1170    /// This gives access to the tree of Elements.
1171    #[cfg(feature = "internal")]
1172    #[doc(hidden)]
1173    pub fn root_component(&self) -> Rc<i_slint_compiler::object_tree::Component> {
1174        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1175        self.inner.unerase(guard).original.clone()
1176    }
1177
1178    /// Return the `TypeLoader` used when parsing the code in the interpreter.
1179    ///
1180    /// WARNING: this is not part of the public API
1181    #[cfg(feature = "internal-highlight")]
1182    pub fn type_loader(&self) -> std::rc::Rc<i_slint_compiler::typeloader::TypeLoader> {
1183        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1184        self.inner.unerase(guard).type_loader.get().unwrap().clone()
1185    }
1186
1187    /// Return the `TypeLoader` used when parsing the code in the interpreter in
1188    /// a state before most passes were applied by the compiler.
1189    ///
1190    /// Each returned type loader is a deep copy of the entire state connected to it,
1191    /// so this is a fairly expensive function!
1192    ///
1193    /// WARNING: this is not part of the public API
1194    #[cfg(feature = "internal-highlight")]
1195    pub fn raw_type_loader(&self) -> Option<i_slint_compiler::typeloader::TypeLoader> {
1196        let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
1197        self.inner
1198            .unerase(guard)
1199            .raw_type_loader
1200            .get()
1201            .unwrap()
1202            .as_ref()
1203            .and_then(|tl| i_slint_compiler::typeloader::snapshot(tl))
1204    }
1205}
1206
1207/// Print the diagnostics to stderr
1208///
1209/// The diagnostics are printed in the same style as rustc errors
1210///
1211/// This function is available when the `display-diagnostics` is enabled.
1212#[cfg(feature = "display-diagnostics")]
1213pub fn print_diagnostics(diagnostics: &[Diagnostic]) {
1214    let mut build_diagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
1215    for d in diagnostics {
1216        build_diagnostics.push_compiler_error(d.clone())
1217    }
1218    build_diagnostics.print();
1219}
1220
1221/// This represents an instance of a dynamic component
1222///
1223/// You can create an instance with the [`ComponentDefinition::create`] function.
1224///
1225/// Properties and callback can be accessed using the associated functions.
1226///
1227/// An instance can be put on screen with the [`ComponentInstance::run`] function.
1228#[repr(C)]
1229pub struct ComponentInstance {
1230    inner: crate::dynamic_item_tree::DynamicComponentVRc,
1231}
1232
1233impl ComponentInstance {
1234    /// Return the [`ComponentDefinition`] that was used to create this instance.
1235    pub fn definition(&self) -> ComponentDefinition {
1236        generativity::make_guard!(guard);
1237        ComponentDefinition { inner: self.inner.unerase(guard).description().into() }
1238    }
1239
1240    /// Return the value for a public property of this component.
1241    ///
1242    /// ## Examples
1243    ///
1244    /// ```
1245    /// # i_slint_backend_testing::init_no_event_loop();
1246    /// use slint_interpreter::{ComponentDefinition, Compiler, Value, SharedString};
1247    /// let code = r#"
1248    ///     export component MyWin inherits Window {
1249    ///         in-out property <int> my_property: 42;
1250    ///     }
1251    /// "#;
1252    /// let mut compiler = Compiler::default();
1253    /// let result = spin_on::spin_on(
1254    ///     compiler.build_from_source(code.into(), Default::default()));
1255    /// assert_eq!(result.diagnostics().count(), 0, "{:?}", result.diagnostics().collect::<Vec<_>>());
1256    /// let instance = result.component("MyWin").unwrap().create().unwrap();
1257    /// assert_eq!(instance.get_property("my_property").unwrap(), Value::from(42));
1258    /// ```
1259    pub fn get_property(&self, name: &str) -> Result<Value, GetPropertyError> {
1260        generativity::make_guard!(guard);
1261        let comp = self.inner.unerase(guard);
1262        let name = normalize_identifier(name);
1263
1264        if comp
1265            .description()
1266            .original
1267            .root_element
1268            .borrow()
1269            .property_declarations
1270            .get(&name)
1271            .is_none_or(|d| !d.expose_in_public_api)
1272        {
1273            return Err(GetPropertyError::NoSuchProperty);
1274        }
1275
1276        comp.description()
1277            .get_property(comp.borrow(), &name)
1278            .map_err(|()| GetPropertyError::NoSuchProperty)
1279    }
1280
1281    /// Set the value for a public property of this component.
1282    pub fn set_property(&self, name: &str, value: Value) -> Result<(), SetPropertyError> {
1283        let name = normalize_identifier(name);
1284        generativity::make_guard!(guard);
1285        let comp = self.inner.unerase(guard);
1286        let d = comp.description();
1287        let elem = d.original.root_element.borrow();
1288        let decl = elem.property_declarations.get(&name).ok_or(SetPropertyError::NoSuchProperty)?;
1289
1290        if !decl.expose_in_public_api {
1291            return Err(SetPropertyError::NoSuchProperty);
1292        } else if decl.visibility == i_slint_compiler::object_tree::PropertyVisibility::Output {
1293            return Err(SetPropertyError::AccessDenied);
1294        }
1295
1296        d.set_property(comp.borrow(), &name, value)
1297    }
1298
1299    /// Set a handler for the callback with the given name. A callback with that
1300    /// name must be defined in the document otherwise an error will be returned.
1301    ///
1302    /// Note: Since the [`ComponentInstance`] holds the handler, the handler itself should not
1303    /// contain a strong reference to the instance. So if you need to capture the instance,
1304    /// you should use [`Self::as_weak`] to create a weak reference.
1305    ///
1306    /// ## Examples
1307    ///
1308    /// ```
1309    /// # i_slint_backend_testing::init_no_event_loop();
1310    /// use slint_interpreter::{Compiler, Value, SharedString, ComponentHandle};
1311    /// use core::convert::TryInto;
1312    /// let code = r#"
1313    ///     export component MyWin inherits Window {
1314    ///         callback foo(int) -> int;
1315    ///         in-out property <int> my_prop: 12;
1316    ///     }
1317    /// "#;
1318    /// let result = spin_on::spin_on(
1319    ///     Compiler::default().build_from_source(code.into(), Default::default()));
1320    /// assert_eq!(result.diagnostics().count(), 0, "{:?}", result.diagnostics().collect::<Vec<_>>());
1321    /// let instance = result.component("MyWin").unwrap().create().unwrap();
1322    /// let instance_weak = instance.as_weak();
1323    /// instance.set_callback("foo", move |args: &[Value]| -> Value {
1324    ///     let arg: u32 = args[0].clone().try_into().unwrap();
1325    ///     let my_prop = instance_weak.unwrap().get_property("my_prop").unwrap();
1326    ///     let my_prop : u32 = my_prop.try_into().unwrap();
1327    ///     Value::from(arg + my_prop)
1328    /// }).unwrap();
1329    ///
1330    /// let res = instance.invoke("foo", &[Value::from(500)]).unwrap();
1331    /// assert_eq!(res, Value::from(500+12));
1332    /// ```
1333    pub fn set_callback(
1334        &self,
1335        name: &str,
1336        callback: impl Fn(&[Value]) -> Value + 'static,
1337    ) -> Result<(), SetCallbackError> {
1338        generativity::make_guard!(guard);
1339        let comp = self.inner.unerase(guard);
1340        comp.description()
1341            .set_callback_handler(comp.borrow(), &normalize_identifier(name), Box::new(callback))
1342            .map_err(|()| SetCallbackError::NoSuchCallback)
1343    }
1344
1345    /// Call the given callback or function with the arguments
1346    ///
1347    /// ## Examples
1348    /// See the documentation of [`Self::set_callback`] for an example
1349    pub fn invoke(&self, name: &str, args: &[Value]) -> Result<Value, InvokeError> {
1350        generativity::make_guard!(guard);
1351        let comp = self.inner.unerase(guard);
1352        comp.description()
1353            .invoke(comp.borrow(), &normalize_identifier(name), args)
1354            .map_err(|()| InvokeError::NoSuchCallable)
1355    }
1356
1357    /// Return the value for a property within an exported global singleton used by this component.
1358    ///
1359    /// The `global` parameter is the exported name of the global singleton. The `property` argument
1360    /// is the name of the property
1361    ///
1362    /// ## Examples
1363    ///
1364    /// ```
1365    /// # i_slint_backend_testing::init_no_event_loop();
1366    /// use slint_interpreter::{Compiler, Value, SharedString};
1367    /// let code = r#"
1368    ///     global Glob {
1369    ///         in-out property <int> my_property: 42;
1370    ///     }
1371    ///     export { Glob as TheGlobal }
1372    ///     export component MyWin inherits Window {
1373    ///     }
1374    /// "#;
1375    /// let mut compiler = Compiler::default();
1376    /// let result = spin_on::spin_on(compiler.build_from_source(code.into(), Default::default()));
1377    /// assert_eq!(result.diagnostics().count(), 0, "{:?}", result.diagnostics().collect::<Vec<_>>());
1378    /// let instance = result.component("MyWin").unwrap().create().unwrap();
1379    /// assert_eq!(instance.get_global_property("TheGlobal", "my_property").unwrap(), Value::from(42));
1380    /// ```
1381    pub fn get_global_property(
1382        &self,
1383        global: &str,
1384        property: &str,
1385    ) -> Result<Value, GetPropertyError> {
1386        generativity::make_guard!(guard);
1387        let comp = self.inner.unerase(guard);
1388        comp.description()
1389            .get_global(comp.borrow(), &&normalize_identifier(global))
1390            .map_err(|()| GetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error?
1391            .as_ref()
1392            .get_property(&normalize_identifier(property))
1393            .map_err(|()| GetPropertyError::NoSuchProperty)
1394    }
1395
1396    /// Set the value for a property within an exported global singleton used by this component.
1397    pub fn set_global_property(
1398        &self,
1399        global: &str,
1400        property: &str,
1401        value: Value,
1402    ) -> Result<(), SetPropertyError> {
1403        generativity::make_guard!(guard);
1404        let comp = self.inner.unerase(guard);
1405        comp.description()
1406            .get_global(comp.borrow(), &&normalize_identifier(global))
1407            .map_err(|()| SetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error?
1408            .as_ref()
1409            .set_property(&&normalize_identifier(property), value)
1410    }
1411
1412    /// Set a handler for the callback in the exported global singleton. A callback with that
1413    /// name must be defined in the specified global and the global must be exported from the
1414    /// main document otherwise an error will be returned.
1415    ///
1416    /// ## Examples
1417    ///
1418    /// ```
1419    /// # i_slint_backend_testing::init_no_event_loop();
1420    /// use slint_interpreter::{Compiler, Value, SharedString};
1421    /// use core::convert::TryInto;
1422    /// let code = r#"
1423    ///     export global Logic {
1424    ///         pure callback to_uppercase(string) -> string;
1425    ///     }
1426    ///     export component MyWin inherits Window {
1427    ///         out property <string> hello: Logic.to_uppercase("world");
1428    ///     }
1429    /// "#;
1430    /// let result = spin_on::spin_on(
1431    ///     Compiler::default().build_from_source(code.into(), Default::default()));
1432    /// let instance = result.component("MyWin").unwrap().create().unwrap();
1433    /// instance.set_global_callback("Logic", "to_uppercase", |args: &[Value]| -> Value {
1434    ///     let arg: SharedString = args[0].clone().try_into().unwrap();
1435    ///     Value::from(SharedString::from(arg.to_uppercase()))
1436    /// }).unwrap();
1437    ///
1438    /// let res = instance.get_property("hello").unwrap();
1439    /// assert_eq!(res, Value::from(SharedString::from("WORLD")));
1440    ///
1441    /// let abc = instance.invoke_global("Logic", "to_uppercase", &[
1442    ///     SharedString::from("abc").into()
1443    /// ]).unwrap();
1444    /// assert_eq!(abc, Value::from(SharedString::from("ABC")));
1445    /// ```
1446    pub fn set_global_callback(
1447        &self,
1448        global: &str,
1449        name: &str,
1450        callback: impl Fn(&[Value]) -> Value + 'static,
1451    ) -> Result<(), SetCallbackError> {
1452        generativity::make_guard!(guard);
1453        let comp = self.inner.unerase(guard);
1454        comp.description()
1455            .get_global(comp.borrow(), &normalize_identifier(global))
1456            .map_err(|()| SetCallbackError::NoSuchCallback)? // FIXME: should there be a NoSuchGlobal error?
1457            .as_ref()
1458            .set_callback_handler(&normalize_identifier(name), Box::new(callback))
1459            .map_err(|()| SetCallbackError::NoSuchCallback)
1460    }
1461
1462    /// Call the given callback or function within a global singleton with the arguments
1463    ///
1464    /// ## Examples
1465    /// See the documentation of [`Self::set_global_callback`] for an example
1466    pub fn invoke_global(
1467        &self,
1468        global: &str,
1469        callable_name: &str,
1470        args: &[Value],
1471    ) -> Result<Value, InvokeError> {
1472        generativity::make_guard!(guard);
1473        let comp = self.inner.unerase(guard);
1474        let g = comp
1475            .description()
1476            .get_global(comp.borrow(), &normalize_identifier(global))
1477            .map_err(|()| InvokeError::NoSuchCallable)?; // FIXME: should there be a NoSuchGlobal error?
1478        let callable_name = normalize_identifier(callable_name);
1479        if matches!(
1480            comp.description()
1481                .original
1482                .root_element
1483                .borrow()
1484                .lookup_property(&callable_name)
1485                .property_type,
1486            LangType::Function { .. }
1487        ) {
1488            g.as_ref()
1489                .eval_function(&callable_name, args.to_vec())
1490                .map_err(|()| InvokeError::NoSuchCallable)
1491        } else {
1492            g.as_ref()
1493                .invoke_callback(&callable_name, args)
1494                .map_err(|()| InvokeError::NoSuchCallable)
1495        }
1496    }
1497
1498    /// Find all positions of the components which are pointed by a given source location.
1499    ///
1500    /// WARNING: this is not part of the public API
1501    #[cfg(feature = "internal-highlight")]
1502    pub fn component_positions(
1503        &self,
1504        path: &Path,
1505        offset: u32,
1506    ) -> Vec<i_slint_core::lengths::LogicalRect> {
1507        crate::highlight::component_positions(&self.inner, path, offset)
1508    }
1509
1510    /// Find the position of the `element`.
1511    ///
1512    /// WARNING: this is not part of the public API
1513    #[cfg(feature = "internal-highlight")]
1514    pub fn element_positions(
1515        &self,
1516        element: &i_slint_compiler::object_tree::ElementRc,
1517    ) -> Vec<i_slint_core::lengths::LogicalRect> {
1518        crate::highlight::element_positions(
1519            &self.inner,
1520            element,
1521            crate::highlight::ElementPositionFilter::IncludeClipped,
1522        )
1523    }
1524
1525    /// Find the `element` that was defined at the text position.
1526    ///
1527    /// WARNING: this is not part of the public API
1528    #[cfg(feature = "internal-highlight")]
1529    pub fn element_node_at_source_code_position(
1530        &self,
1531        path: &Path,
1532        offset: u32,
1533    ) -> Vec<(i_slint_compiler::object_tree::ElementRc, usize)> {
1534        crate::highlight::element_node_at_source_code_position(&self.inner, path, offset)
1535    }
1536}
1537
1538impl ComponentHandle for ComponentInstance {
1539    type Inner = crate::dynamic_item_tree::ErasedItemTreeBox;
1540
1541    fn as_weak(&self) -> Weak<Self>
1542    where
1543        Self: Sized,
1544    {
1545        Weak::new(&self.inner)
1546    }
1547
1548    fn clone_strong(&self) -> Self {
1549        Self { inner: self.inner.clone() }
1550    }
1551
1552    fn from_inner(
1553        inner: vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, Self::Inner>,
1554    ) -> Self {
1555        Self { inner }
1556    }
1557
1558    fn show(&self) -> Result<(), PlatformError> {
1559        self.inner.window_adapter_ref()?.window().show()
1560    }
1561
1562    fn hide(&self) -> Result<(), PlatformError> {
1563        self.inner.window_adapter_ref()?.window().hide()
1564    }
1565
1566    fn run(&self) -> Result<(), PlatformError> {
1567        self.show()?;
1568        run_event_loop()?;
1569        self.hide()
1570    }
1571
1572    fn window(&self) -> &Window {
1573        self.inner.window_adapter_ref().unwrap().window()
1574    }
1575
1576    fn global<'a, T: Global<'a, Self>>(&'a self) -> T
1577    where
1578        Self: Sized,
1579    {
1580        unreachable!()
1581    }
1582}
1583
1584impl From<ComponentInstance>
1585    for vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, ErasedItemTreeBox>
1586{
1587    fn from(value: ComponentInstance) -> Self {
1588        value.inner
1589    }
1590}
1591
1592/// Error returned by [`ComponentInstance::get_property`]
1593#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1594#[non_exhaustive]
1595pub enum GetPropertyError {
1596    /// There is no property with the given name
1597    #[display("no such property")]
1598    NoSuchProperty,
1599}
1600
1601/// Error returned by [`ComponentInstance::set_property`]
1602#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1603#[non_exhaustive]
1604pub enum SetPropertyError {
1605    /// There is no property with the given name.
1606    #[display("no such property")]
1607    NoSuchProperty,
1608    /// The property exists but does not have a type matching the dynamic value.
1609    ///
1610    /// This happens for example when assigning a source struct value to a target
1611    /// struct property, where the source doesn't have all the fields the target struct
1612    /// requires.
1613    #[display("wrong type")]
1614    WrongType,
1615    /// Attempt to set an output property.
1616    #[display("access denied")]
1617    AccessDenied,
1618}
1619
1620/// Error returned by [`ComponentInstance::set_callback`]
1621#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1622#[non_exhaustive]
1623pub enum SetCallbackError {
1624    /// There is no callback with the given name
1625    #[display("no such callback")]
1626    NoSuchCallback,
1627}
1628
1629/// Error returned by [`ComponentInstance::invoke`]
1630#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1631#[non_exhaustive]
1632pub enum InvokeError {
1633    /// There is no callback or function with the given name
1634    #[display("no such callback or function")]
1635    NoSuchCallable,
1636}
1637
1638/// Enters the main event loop. This is necessary in order to receive
1639/// events from the windowing system in order to render to the screen
1640/// and react to user input.
1641pub fn run_event_loop() -> Result<(), PlatformError> {
1642    i_slint_backend_selector::with_platform(|b| b.run_event_loop())
1643}
1644
1645/// Spawns a [`Future`] to execute in the Slint event loop.
1646///
1647/// See the documentation of `slint::spawn_local()` for more info
1648pub fn spawn_local<F: Future + 'static>(fut: F) -> Result<JoinHandle<F::Output>, EventLoopError> {
1649    i_slint_backend_selector::with_global_context(|ctx| ctx.spawn_local(fut))
1650        .map_err(|_| EventLoopError::NoEventLoopProvider)?
1651}
1652
1653/// This module contains a few functions used by the tests
1654#[doc(hidden)]
1655pub mod testing {
1656    use super::ComponentHandle;
1657    use i_slint_core::window::WindowInner;
1658
1659    /// Wrapper around [`i_slint_core::tests::slint_send_mouse_click`]
1660    pub fn send_mouse_click(comp: &super::ComponentInstance, x: f32, y: f32) {
1661        i_slint_core::tests::slint_send_mouse_click(
1662            x,
1663            y,
1664            &WindowInner::from_pub(comp.window()).window_adapter(),
1665        );
1666    }
1667
1668    /// Wrapper around [`i_slint_core::tests::slint_send_keyboard_char`]
1669    pub fn send_keyboard_char(
1670        comp: &super::ComponentInstance,
1671        string: i_slint_core::SharedString,
1672        pressed: bool,
1673    ) {
1674        i_slint_core::tests::slint_send_keyboard_char(
1675            &string,
1676            pressed,
1677            &WindowInner::from_pub(comp.window()).window_adapter(),
1678        );
1679    }
1680    /// Wrapper around [`i_slint_core::tests::send_keyboard_string_sequence`]
1681    pub fn send_keyboard_string_sequence(
1682        comp: &super::ComponentInstance,
1683        string: i_slint_core::SharedString,
1684    ) {
1685        i_slint_core::tests::send_keyboard_string_sequence(
1686            &string,
1687            &WindowInner::from_pub(comp.window()).window_adapter(),
1688        );
1689    }
1690}
1691
1692#[test]
1693fn component_definition_properties() {
1694    i_slint_backend_testing::init_no_event_loop();
1695    let mut compiler = Compiler::default();
1696    compiler.set_style("fluent".into());
1697    let comp_def = spin_on::spin_on(
1698        compiler.build_from_source(
1699            r#"
1700    export component Dummy {
1701        in-out property <string> test;
1702        in-out property <int> underscores-and-dashes_preserved: 44;
1703        callback hello;
1704    }"#
1705            .into(),
1706            "".into(),
1707        ),
1708    )
1709    .component("Dummy")
1710    .unwrap();
1711
1712    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1713
1714    assert_eq!(props.len(), 2);
1715    assert_eq!(props[0].0, "test");
1716    assert_eq!(props[0].1, ValueType::String);
1717    assert_eq!(props[1].0, "underscores-and-dashes_preserved");
1718    assert_eq!(props[1].1, ValueType::Number);
1719
1720    let instance = comp_def.create().unwrap();
1721    assert_eq!(instance.get_property("underscores_and-dashes-preserved"), Ok(Value::Number(44.)));
1722    assert_eq!(
1723        instance.get_property("underscoresanddashespreserved"),
1724        Err(GetPropertyError::NoSuchProperty)
1725    );
1726    assert_eq!(
1727        instance.set_property("underscores-and_dashes-preserved", Value::Number(88.)),
1728        Ok(())
1729    );
1730    assert_eq!(
1731        instance.set_property("underscoresanddashespreserved", Value::Number(99.)),
1732        Err(SetPropertyError::NoSuchProperty)
1733    );
1734    assert_eq!(
1735        instance.set_property("underscores-and_dashes-preserved", Value::String("99".into())),
1736        Err(SetPropertyError::WrongType)
1737    );
1738    assert_eq!(instance.get_property("underscores-and-dashes-preserved"), Ok(Value::Number(88.)));
1739}
1740
1741#[test]
1742fn component_definition_properties2() {
1743    i_slint_backend_testing::init_no_event_loop();
1744    let mut compiler = Compiler::default();
1745    compiler.set_style("fluent".into());
1746    let comp_def = spin_on::spin_on(
1747        compiler.build_from_source(
1748            r#"
1749    export component Dummy {
1750        in-out property <string> sub-text <=> sub.text;
1751        sub := Text { property <int> private-not-exported; }
1752        out property <string> xreadonly: "the value";
1753        private property <string> xx: sub.text;
1754        callback hello;
1755    }"#
1756            .into(),
1757            "".into(),
1758        ),
1759    )
1760    .component("Dummy")
1761    .unwrap();
1762
1763    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1764
1765    assert_eq!(props.len(), 2);
1766    assert_eq!(props[0].0, "sub-text");
1767    assert_eq!(props[0].1, ValueType::String);
1768    assert_eq!(props[1].0, "xreadonly");
1769
1770    let callbacks = comp_def.callbacks().collect::<Vec<_>>();
1771    assert_eq!(callbacks.len(), 1);
1772    assert_eq!(callbacks[0], "hello");
1773
1774    let instance = comp_def.create().unwrap();
1775    assert_eq!(
1776        instance.set_property("xreadonly", SharedString::from("XXX").into()),
1777        Err(SetPropertyError::AccessDenied)
1778    );
1779    assert_eq!(instance.get_property("xreadonly"), Ok(Value::String("the value".into())));
1780    assert_eq!(
1781        instance.set_property("xx", SharedString::from("XXX").into()),
1782        Err(SetPropertyError::NoSuchProperty)
1783    );
1784    assert_eq!(
1785        instance.set_property("background", Value::default()),
1786        Err(SetPropertyError::NoSuchProperty)
1787    );
1788
1789    assert_eq!(instance.get_property("background"), Err(GetPropertyError::NoSuchProperty));
1790    assert_eq!(instance.get_property("xx"), Err(GetPropertyError::NoSuchProperty));
1791}
1792
1793#[test]
1794fn globals() {
1795    i_slint_backend_testing::init_no_event_loop();
1796    let mut compiler = Compiler::default();
1797    compiler.set_style("fluent".into());
1798    let definition = spin_on::spin_on(
1799        compiler.build_from_source(
1800            r#"
1801    export global My-Super_Global {
1802        in-out property <int> the-property : 21;
1803        callback my-callback();
1804    }
1805    export { My-Super_Global as AliasedGlobal }
1806    export component Dummy {
1807        callback alias <=> My-Super_Global.my-callback;
1808    }"#
1809            .into(),
1810            "".into(),
1811        ),
1812    )
1813    .component("Dummy")
1814    .unwrap();
1815
1816    assert_eq!(definition.globals().collect::<Vec<_>>(), vec!["My-Super_Global", "AliasedGlobal"]);
1817
1818    assert!(definition.global_properties("not-there").is_none());
1819    {
1820        let expected_properties = vec![("the-property".to_string(), ValueType::Number)];
1821        let expected_callbacks = vec!["my-callback".to_string()];
1822
1823        let assert_properties_and_callbacks = |global_name| {
1824            assert_eq!(
1825                definition
1826                    .global_properties(global_name)
1827                    .map(|props| props.collect::<Vec<_>>())
1828                    .as_ref(),
1829                Some(&expected_properties)
1830            );
1831            assert_eq!(
1832                definition
1833                    .global_callbacks(global_name)
1834                    .map(|props| props.collect::<Vec<_>>())
1835                    .as_ref(),
1836                Some(&expected_callbacks)
1837            );
1838        };
1839
1840        assert_properties_and_callbacks("My-Super-Global");
1841        assert_properties_and_callbacks("My_Super-Global");
1842        assert_properties_and_callbacks("AliasedGlobal");
1843    }
1844
1845    let instance = definition.create().unwrap();
1846    assert_eq!(
1847        instance.set_global_property("My_Super-Global", "the_property", Value::Number(44.)),
1848        Ok(())
1849    );
1850    assert_eq!(
1851        instance.set_global_property("AliasedGlobal", "the_property", Value::Number(44.)),
1852        Ok(())
1853    );
1854    assert_eq!(
1855        instance.set_global_property("DontExist", "the-property", Value::Number(88.)),
1856        Err(SetPropertyError::NoSuchProperty)
1857    );
1858
1859    assert_eq!(
1860        instance.set_global_property("My_Super-Global", "theproperty", Value::Number(88.)),
1861        Err(SetPropertyError::NoSuchProperty)
1862    );
1863    assert_eq!(
1864        instance.set_global_property("AliasedGlobal", "theproperty", Value::Number(88.)),
1865        Err(SetPropertyError::NoSuchProperty)
1866    );
1867    assert_eq!(
1868        instance.set_global_property("My_Super-Global", "the_property", Value::String("88".into())),
1869        Err(SetPropertyError::WrongType)
1870    );
1871    assert_eq!(
1872        instance.get_global_property("My-Super_Global", "yoyo"),
1873        Err(GetPropertyError::NoSuchProperty)
1874    );
1875    assert_eq!(
1876        instance.get_global_property("My-Super_Global", "the-property"),
1877        Ok(Value::Number(44.))
1878    );
1879
1880    assert_eq!(
1881        instance.set_property("the-property", Value::Void),
1882        Err(SetPropertyError::NoSuchProperty)
1883    );
1884    assert_eq!(instance.get_property("the-property"), Err(GetPropertyError::NoSuchProperty));
1885
1886    assert_eq!(
1887        instance.set_global_callback("DontExist", "the-property", |_| panic!()),
1888        Err(SetCallbackError::NoSuchCallback)
1889    );
1890    assert_eq!(
1891        instance.set_global_callback("My_Super_Global", "the-property", |_| panic!()),
1892        Err(SetCallbackError::NoSuchCallback)
1893    );
1894    assert_eq!(
1895        instance.set_global_callback("My_Super_Global", "yoyo", |_| panic!()),
1896        Err(SetCallbackError::NoSuchCallback)
1897    );
1898
1899    assert_eq!(
1900        instance.invoke_global("DontExist", "the-property", &[]),
1901        Err(InvokeError::NoSuchCallable)
1902    );
1903    assert_eq!(
1904        instance.invoke_global("My_Super_Global", "the-property", &[]),
1905        Err(InvokeError::NoSuchCallable)
1906    );
1907    assert_eq!(
1908        instance.invoke_global("My_Super_Global", "yoyo", &[]),
1909        Err(InvokeError::NoSuchCallable)
1910    );
1911
1912    // Alias to global don't crash (#8238)
1913    assert_eq!(instance.get_property("alias"), Err(GetPropertyError::NoSuchProperty));
1914}
1915
1916#[test]
1917fn call_functions() {
1918    i_slint_backend_testing::init_no_event_loop();
1919    let mut compiler = Compiler::default();
1920    compiler.set_style("fluent".into());
1921    let definition = spin_on::spin_on(
1922        compiler.build_from_source(
1923            r#"
1924    export global Gl {
1925        out property<string> q;
1926        public function foo-bar(a-a: string, b-b:int) -> string {
1927            q = a-a;
1928            return a-a + b-b;
1929        }
1930    }
1931    export component Test {
1932        out property<int> p;
1933        public function foo-bar(a: int, b:int) -> int {
1934            p = a;
1935            return a + b;
1936        }
1937    }"#
1938            .into(),
1939            "".into(),
1940        ),
1941    )
1942    .component("Test")
1943    .unwrap();
1944
1945    assert_eq!(definition.functions().collect::<Vec<_>>(), ["foo-bar"]);
1946    assert_eq!(definition.global_functions("Gl").unwrap().collect::<Vec<_>>(), ["foo-bar"]);
1947
1948    let instance = definition.create().unwrap();
1949
1950    assert_eq!(
1951        instance.invoke("foo_bar", &[Value::Number(3.), Value::Number(4.)]),
1952        Ok(Value::Number(7.))
1953    );
1954    assert_eq!(instance.invoke("p", &[]), Err(InvokeError::NoSuchCallable));
1955    assert_eq!(instance.get_property("p"), Ok(Value::Number(3.)));
1956
1957    assert_eq!(
1958        instance.invoke_global(
1959            "Gl",
1960            "foo_bar",
1961            &[Value::String("Hello".into()), Value::Number(10.)]
1962        ),
1963        Ok(Value::String("Hello10".into()))
1964    );
1965    assert_eq!(instance.get_global_property("Gl", "q"), Ok(Value::String("Hello".into())));
1966}
1967
1968#[test]
1969fn component_definition_struct_properties() {
1970    i_slint_backend_testing::init_no_event_loop();
1971    let mut compiler = Compiler::default();
1972    compiler.set_style("fluent".into());
1973    let comp_def = spin_on::spin_on(
1974        compiler.build_from_source(
1975            r#"
1976    export struct Settings {
1977        string_value: string,
1978    }
1979    export component Dummy {
1980        in-out property <Settings> test;
1981    }"#
1982            .into(),
1983            "".into(),
1984        ),
1985    )
1986    .component("Dummy")
1987    .unwrap();
1988
1989    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1990
1991    assert_eq!(props.len(), 1);
1992    assert_eq!(props[0].0, "test");
1993    assert_eq!(props[0].1, ValueType::Struct);
1994
1995    let instance = comp_def.create().unwrap();
1996
1997    let valid_struct: Struct =
1998        [("string_value".to_string(), Value::String("hello".into()))].iter().cloned().collect();
1999
2000    assert_eq!(instance.set_property("test", Value::Struct(valid_struct.clone())), Ok(()));
2001    assert_eq!(instance.get_property("test").unwrap().value_type(), ValueType::Struct);
2002
2003    assert_eq!(instance.set_property("test", Value::Number(42.)), Err(SetPropertyError::WrongType));
2004
2005    let mut invalid_struct = valid_struct.clone();
2006    invalid_struct.set_field("other".into(), Value::Number(44.));
2007    assert_eq!(
2008        instance.set_property("test", Value::Struct(invalid_struct)),
2009        Err(SetPropertyError::WrongType)
2010    );
2011    let mut invalid_struct = valid_struct;
2012    invalid_struct.set_field("string_value".into(), Value::Number(44.));
2013    assert_eq!(
2014        instance.set_property("test", Value::Struct(invalid_struct)),
2015        Err(SetPropertyError::WrongType)
2016    );
2017}
2018
2019#[test]
2020fn component_definition_model_properties() {
2021    use i_slint_core::model::*;
2022    i_slint_backend_testing::init_no_event_loop();
2023    let mut compiler = Compiler::default();
2024    compiler.set_style("fluent".into());
2025    let comp_def = spin_on::spin_on(compiler.build_from_source(
2026        "export component Dummy { in-out property <[int]> prop: [42, 12]; }".into(),
2027        "".into(),
2028    ))
2029    .component("Dummy")
2030    .unwrap();
2031
2032    let props = comp_def.properties().collect::<Vec<(_, _)>>();
2033    assert_eq!(props.len(), 1);
2034    assert_eq!(props[0].0, "prop");
2035    assert_eq!(props[0].1, ValueType::Model);
2036
2037    let instance = comp_def.create().unwrap();
2038
2039    let int_model =
2040        Value::Model([Value::Number(14.), Value::Number(15.), Value::Number(16.)].into());
2041    let empty_model = Value::Model(ModelRc::new(VecModel::<Value>::default()));
2042    let model_with_string = Value::Model(VecModel::from_slice(&[
2043        Value::Number(1000.),
2044        Value::String("foo".into()),
2045        Value::Number(1111.),
2046    ]));
2047
2048    #[track_caller]
2049    fn check_model(val: Value, r: &[f64]) {
2050        if let Value::Model(m) = val {
2051            assert_eq!(r.len(), m.row_count());
2052            for (i, v) in r.iter().enumerate() {
2053                assert_eq!(m.row_data(i).unwrap(), Value::Number(*v));
2054            }
2055        } else {
2056            panic!("{val:?} not a model");
2057        }
2058    }
2059
2060    assert_eq!(instance.get_property("prop").unwrap().value_type(), ValueType::Model);
2061    check_model(instance.get_property("prop").unwrap(), &[42., 12.]);
2062
2063    instance.set_property("prop", int_model).unwrap();
2064    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2065
2066    assert_eq!(instance.set_property("prop", Value::Number(42.)), Err(SetPropertyError::WrongType));
2067    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2068    assert_eq!(instance.set_property("prop", model_with_string), Err(SetPropertyError::WrongType));
2069    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2070
2071    assert_eq!(instance.set_property("prop", empty_model), Ok(()));
2072    check_model(instance.get_property("prop").unwrap(), &[]);
2073}
2074
2075#[test]
2076fn lang_type_to_value_type() {
2077    use i_slint_compiler::langtype::Struct as LangStruct;
2078    use std::collections::BTreeMap;
2079
2080    assert_eq!(ValueType::from(LangType::Void), ValueType::Void);
2081    assert_eq!(ValueType::from(LangType::Float32), ValueType::Number);
2082    assert_eq!(ValueType::from(LangType::Int32), ValueType::Number);
2083    assert_eq!(ValueType::from(LangType::Duration), ValueType::Number);
2084    assert_eq!(ValueType::from(LangType::Angle), ValueType::Number);
2085    assert_eq!(ValueType::from(LangType::PhysicalLength), ValueType::Number);
2086    assert_eq!(ValueType::from(LangType::LogicalLength), ValueType::Number);
2087    assert_eq!(ValueType::from(LangType::Percent), ValueType::Number);
2088    assert_eq!(ValueType::from(LangType::UnitProduct(vec![])), ValueType::Number);
2089    assert_eq!(ValueType::from(LangType::String), ValueType::String);
2090    assert_eq!(ValueType::from(LangType::Color), ValueType::Brush);
2091    assert_eq!(ValueType::from(LangType::Brush), ValueType::Brush);
2092    assert_eq!(ValueType::from(LangType::Array(Rc::new(LangType::Void))), ValueType::Model);
2093    assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool);
2094    assert_eq!(
2095        ValueType::from(LangType::Struct(Rc::new(LangStruct {
2096            fields: BTreeMap::default(),
2097            name: None,
2098            node: None,
2099            rust_attributes: None
2100        }))),
2101        ValueType::Struct
2102    );
2103    assert_eq!(ValueType::from(LangType::Image), ValueType::Image);
2104}
2105
2106#[test]
2107fn test_multi_components() {
2108    let result = spin_on::spin_on(
2109        Compiler::default().build_from_source(
2110            r#"
2111        export struct Settings {
2112            string_value: string,
2113        }
2114        export global ExpGlo { in-out property <int> test: 42; }
2115        component Common {
2116            in-out property <Settings> settings: { string_value: "Hello", };
2117        }
2118        export component Xyz inherits Window {
2119            in-out property <int> aaa: 8;
2120        }
2121        export component Foo {
2122
2123            in-out property <int> test: 42;
2124            c := Common {}
2125        }
2126        export component Bar inherits Window {
2127            in-out property <int> blah: 78;
2128            c := Common {}
2129        }
2130        "#
2131            .into(),
2132            PathBuf::from("hello.slint"),
2133        ),
2134    );
2135
2136    assert!(!result.has_errors(), "Error {:?}", result.diagnostics().collect::<Vec<_>>());
2137    let mut components = result.component_names().collect::<Vec<_>>();
2138    components.sort();
2139    assert_eq!(components, vec!["Bar", "Xyz"]);
2140    let diag = result.diagnostics().collect::<Vec<_>>();
2141    assert_eq!(diag.len(), 1);
2142    assert_eq!(diag[0].level(), DiagnosticLevel::Warning);
2143    assert_eq!(
2144        diag[0].message(),
2145        "Exported component 'Foo' doesn't inherit Window. No code will be generated for it"
2146    );
2147
2148    let comp1 = result.component("Xyz").unwrap();
2149    assert_eq!(comp1.name(), "Xyz");
2150    let instance1a = comp1.create().unwrap();
2151    let comp2 = result.component("Bar").unwrap();
2152    let instance2 = comp2.create().unwrap();
2153    let instance1b = comp1.create().unwrap();
2154
2155    // globals are not shared between instances
2156    assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2157    assert_eq!(instance1a.set_global_property("ExpGlo", "test", Value::Number(88.0)), Ok(()));
2158    assert_eq!(instance2.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2159    assert_eq!(instance1b.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2160    assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(88.0)));
2161
2162    assert!(result.component("Settings").is_none());
2163    assert!(result.component("Foo").is_none());
2164    assert!(result.component("Common").is_none());
2165    assert!(result.component("ExpGlo").is_none());
2166    assert!(result.component("xyz").is_none());
2167}
2168
2169#[cfg(all(test, feature = "internal-highlight"))]
2170fn compile(code: &str) -> (ComponentInstance, PathBuf) {
2171    i_slint_backend_testing::init_no_event_loop();
2172    let mut compiler = Compiler::default();
2173    compiler.set_style("fluent".into());
2174    let path = PathBuf::from("/tmp/test.slint");
2175
2176    let compile_result =
2177        spin_on::spin_on(compiler.build_from_source(code.to_string(), path.clone()));
2178
2179    for d in &compile_result.diagnostics {
2180        eprintln!("{d}");
2181    }
2182
2183    assert!(!compile_result.has_errors());
2184
2185    let definition = compile_result.components().next().unwrap();
2186    let instance = definition.create().unwrap();
2187
2188    (instance, path)
2189}
2190
2191#[cfg(feature = "internal-highlight")]
2192#[test]
2193fn test_element_node_at_source_code_position() {
2194    let code = r#"
2195component Bar1 {}
2196
2197component Foo1 {
2198}
2199
2200export component Foo2 inherits Window  {
2201    Bar1 {}
2202    Foo1   {}
2203}"#;
2204
2205    let (handle, path) = compile(code);
2206
2207    for i in 0..code.len() as u32 {
2208        let elements = handle.element_node_at_source_code_position(&path, i);
2209        eprintln!("{i}: {}", code.as_bytes()[i as usize] as char);
2210        match i {
2211            16 => assert_eq!(elements.len(), 1),       // Bar1 (def)
2212            35 => assert_eq!(elements.len(), 1),       // Foo1 (def)
2213            71..=78 => assert_eq!(elements.len(), 1),  // Window + WS (from Foo2)
2214            85..=89 => assert_eq!(elements.len(), 1),  // Bar1 + WS (use)
2215            97..=103 => assert_eq!(elements.len(), 1), // Foo1 + WS (use)
2216            _ => assert!(elements.is_empty()),
2217        }
2218    }
2219}
2220
2221#[cfg(feature = "ffi")]
2222#[allow(missing_docs)]
2223#[path = "ffi.rs"]
2224pub(crate) mod ffi;