slint_interpreter/
api.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
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, WindowOptions};
31
32/// This enum represents the different public variants of the [`Value`] enum, without
33/// the contained values.
34#[derive(Debug, Copy, Clone, PartialEq)]
35#[repr(i8)]
36#[non_exhaustive]
37pub enum ValueType {
38    /// The variant that expresses the non-type. This is the default.
39    Void,
40    /// An `int` or a `float` (this is also used for unit based type such as `length` or `angle`)
41    Number,
42    /// Correspond to the `string` type in .slint
43    String,
44    /// Correspond to the `bool` type in .slint
45    Bool,
46    /// A model (that includes array in .slint)
47    Model,
48    /// An object
49    Struct,
50    /// Correspond to `brush` or `color` type in .slint.  For color, this is then a [`Brush::SolidColor`]
51    Brush,
52    /// Correspond to `image` type in .slint.
53    Image,
54    /// The type is not a public type but something internal.
55    #[doc(hidden)]
56    Other = -1,
57}
58
59impl From<LangType> for ValueType {
60    fn from(ty: LangType) -> Self {
61        match ty {
62            LangType::Float32
63            | LangType::Int32
64            | LangType::Duration
65            | LangType::Angle
66            | LangType::PhysicalLength
67            | LangType::LogicalLength
68            | LangType::Percent
69            | LangType::UnitProduct(_) => Self::Number,
70            LangType::String => Self::String,
71            LangType::Color => Self::Brush,
72            LangType::Brush => Self::Brush,
73            LangType::Array(_) => Self::Model,
74            LangType::Bool => Self::Bool,
75            LangType::Struct { .. } => Self::Struct,
76            LangType::Void => Self::Void,
77            LangType::Image => Self::Image,
78            _ => Self::Other,
79        }
80    }
81}
82
83/// This is a dynamically typed value used in the Slint interpreter.
84/// It can hold a value of different types, and you should use the
85/// [`From`] or [`TryFrom`] traits to access the value.
86///
87/// ```
88/// # use slint_interpreter::*;
89/// use core::convert::TryInto;
90/// // create a value containing an integer
91/// let v = Value::from(100u32);
92/// assert_eq!(v.try_into(), Ok(100u32));
93/// ```
94#[derive(Clone, Default)]
95#[non_exhaustive]
96#[repr(u8)]
97pub enum Value {
98    /// There is nothing in this value. That's the default.
99    /// For example, a function that does not return a result would return a Value::Void
100    #[default]
101    Void = 0,
102    /// An `int` or a `float` (this is also used for unit based type such as `length` or `angle`)
103    Number(f64) = 1,
104    /// Correspond to the `string` type in .slint
105    String(SharedString) = 2,
106    /// Correspond to the `bool` type in .slint
107    Bool(bool) = 3,
108    /// Correspond to the `image` type in .slint
109    Image(Image) = 4,
110    /// A model (that includes array in .slint)
111    Model(ModelRc<Value>) = 5,
112    /// An object
113    Struct(Struct) = 6,
114    /// Correspond to `brush` or `color` type in .slint.  For color, this is then a [`Brush::SolidColor`]
115    Brush(Brush) = 7,
116    #[doc(hidden)]
117    /// The elements of a path
118    PathData(PathData) = 8,
119    #[doc(hidden)]
120    /// An easing curve
121    EasingCurve(i_slint_core::animations::EasingCurve) = 9,
122    #[doc(hidden)]
123    /// An enumeration, like `TextHorizontalAlignment::align_center`, represented by `("TextHorizontalAlignment", "align_center")`.
124    /// FIXME: consider representing that with a number?
125    EnumerationValue(String, String) = 10,
126    #[doc(hidden)]
127    LayoutCache(SharedVector<f32>) = 11,
128    #[doc(hidden)]
129    /// Correspond to the `component-factory` type in .slint
130    ComponentFactory(ComponentFactory) = 12,
131}
132
133impl Value {
134    /// Returns the type variant that this value holds without the containing value.
135    pub fn value_type(&self) -> ValueType {
136        match self {
137            Value::Void => ValueType::Void,
138            Value::Number(_) => ValueType::Number,
139            Value::String(_) => ValueType::String,
140            Value::Bool(_) => ValueType::Bool,
141            Value::Model(_) => ValueType::Model,
142            Value::Struct(_) => ValueType::Struct,
143            Value::Brush(_) => ValueType::Brush,
144            Value::Image(_) => ValueType::Image,
145            _ => ValueType::Other,
146        }
147    }
148}
149
150impl PartialEq for Value {
151    fn eq(&self, other: &Self) -> bool {
152        match self {
153            Value::Void => matches!(other, Value::Void),
154            Value::Number(lhs) => matches!(other, Value::Number(rhs) if lhs.approx_eq(rhs)),
155            Value::String(lhs) => matches!(other, Value::String(rhs) if lhs == rhs),
156            Value::Bool(lhs) => matches!(other, Value::Bool(rhs) if lhs == rhs),
157            Value::Image(lhs) => matches!(other, Value::Image(rhs) if lhs == rhs),
158            Value::Model(lhs) => {
159                if let Value::Model(rhs) = other {
160                    lhs == rhs
161                } else {
162                    false
163                }
164            }
165            Value::Struct(lhs) => matches!(other, Value::Struct(rhs) if lhs == rhs),
166            Value::Brush(lhs) => matches!(other, Value::Brush(rhs) if lhs == rhs),
167            Value::PathData(lhs) => matches!(other, Value::PathData(rhs) if lhs == rhs),
168            Value::EasingCurve(lhs) => matches!(other, Value::EasingCurve(rhs) if lhs == rhs),
169            Value::EnumerationValue(lhs_name, lhs_value) => {
170                matches!(other, Value::EnumerationValue(rhs_name, rhs_value) if lhs_name == rhs_name && lhs_value == rhs_value)
171            }
172            Value::LayoutCache(lhs) => matches!(other, Value::LayoutCache(rhs) if lhs == rhs),
173            Value::ComponentFactory(lhs) => {
174                matches!(other, Value::ComponentFactory(rhs) if lhs == rhs)
175            }
176        }
177    }
178}
179
180impl std::fmt::Debug for Value {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        match self {
183            Value::Void => write!(f, "Value::Void"),
184            Value::Number(n) => write!(f, "Value::Number({n:?})"),
185            Value::String(s) => write!(f, "Value::String({s:?})"),
186            Value::Bool(b) => write!(f, "Value::Bool({b:?})"),
187            Value::Image(i) => write!(f, "Value::Image({i:?})"),
188            Value::Model(m) => {
189                write!(f, "Value::Model(")?;
190                f.debug_list().entries(m.iter()).finish()?;
191                write!(f, "])")
192            }
193            Value::Struct(s) => write!(f, "Value::Struct({s:?})"),
194            Value::Brush(b) => write!(f, "Value::Brush({b:?})"),
195            Value::PathData(e) => write!(f, "Value::PathElements({e:?})"),
196            Value::EasingCurve(c) => write!(f, "Value::EasingCurve({c:?})"),
197            Value::EnumerationValue(n, v) => write!(f, "Value::EnumerationValue({n:?}, {v:?})"),
198            Value::LayoutCache(v) => write!(f, "Value::LayoutCache({v:?})"),
199            Value::ComponentFactory(factory) => write!(f, "Value::ComponentFactory({factory:?})"),
200        }
201    }
202}
203
204/// Helper macro to implement the From / TryFrom for Value
205///
206/// For example
207/// `declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64] );`
208/// means that `Value::Number` can be converted to / from each of the said rust types
209///
210/// For `Value::Object` mapping to a rust `struct`, one can use [`declare_value_struct_conversion!`]
211/// And for `Value::EnumerationValue` which maps to a rust `enum`, one can use [`declare_value_struct_conversion!`]
212macro_rules! declare_value_conversion {
213    ( $value:ident => [$($ty:ty),*] ) => {
214        $(
215            impl From<$ty> for Value {
216                fn from(v: $ty) -> Self {
217                    Value::$value(v as _)
218                }
219            }
220            impl TryFrom<Value> for $ty {
221                type Error = Value;
222                fn try_from(v: Value) -> Result<$ty, Self::Error> {
223                    match v {
224                        Value::$value(x) => Ok(x as _),
225                        _ => Err(v)
226                    }
227                }
228            }
229        )*
230    };
231}
232declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] );
233declare_value_conversion!(String => [SharedString] );
234declare_value_conversion!(Bool => [bool] );
235declare_value_conversion!(Image => [Image] );
236declare_value_conversion!(Struct => [Struct] );
237declare_value_conversion!(Brush => [Brush] );
238declare_value_conversion!(PathData => [PathData]);
239declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]);
240declare_value_conversion!(LayoutCache => [SharedVector<f32>] );
241declare_value_conversion!(ComponentFactory => [ComponentFactory] );
242
243/// Implement From / TryFrom for Value that convert a `struct` to/from `Value::Struct`
244macro_rules! declare_value_struct_conversion {
245    (struct $name:path { $($field:ident),* $(, ..$extra:expr)? }) => {
246        impl From<$name> for Value {
247            fn from($name { $($field),* , .. }: $name) -> Self {
248                let mut struct_ = Struct::default();
249                $(struct_.set_field(stringify!($field).into(), $field.into());)*
250                Value::Struct(struct_)
251            }
252        }
253        impl TryFrom<Value> for $name {
254            type Error = ();
255            fn try_from(v: Value) -> Result<$name, Self::Error> {
256                #[allow(clippy::field_reassign_with_default)]
257                match v {
258                    Value::Struct(x) => {
259                        type Ty = $name;
260                        #[allow(unused)]
261                        let mut res: Ty = Ty::default();
262                        $(let mut res: Ty = $extra;)?
263                        $(res.$field = x.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
264                        Ok(res)
265                    }
266                    _ => Err(()),
267                }
268            }
269        }
270    };
271    ($(
272        $(#[$struct_attr:meta])*
273        struct $Name:ident {
274            @name = $inner_name:literal
275            export {
276                $( $(#[$pub_attr:meta])* $pub_field:ident : $pub_type:ty, )*
277            }
278            private {
279                $( $(#[$pri_attr:meta])* $pri_field:ident : $pri_type:ty, )*
280            }
281        }
282    )*) => {
283        $(
284            impl From<$Name> for Value {
285                fn from(item: $Name) -> Self {
286                    let mut struct_ = Struct::default();
287                    $(struct_.set_field(stringify!($pub_field).into(), item.$pub_field.into());)*
288                    $(handle_private!(SET $Name $pri_field, struct_, item);)*
289                    Value::Struct(struct_)
290                }
291            }
292            impl TryFrom<Value> for $Name {
293                type Error = ();
294                fn try_from(v: Value) -> Result<$Name, Self::Error> {
295                    #[allow(clippy::field_reassign_with_default)]
296                    match v {
297                        Value::Struct(x) => {
298                            type Ty = $Name;
299                            #[allow(unused)]
300                            let mut res: Ty = Ty::default();
301                            $(res.$pub_field = x.get_field(stringify!($pub_field)).ok_or(())?.clone().try_into().map_err(|_|())?;)*
302                            $(handle_private!(GET $Name $pri_field, x, res);)*
303                            Ok(res)
304                        }
305                        _ => Err(()),
306                    }
307                }
308            }
309        )*
310    };
311}
312
313macro_rules! handle_private {
314    (SET StateInfo $field:ident, $struct_:ident, $item:ident) => {
315        $struct_.set_field(stringify!($field).into(), $item.$field.into())
316    };
317    (SET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
318    (GET StateInfo $field:ident, $struct_:ident, $item:ident) => {
319        $item.$field =
320            $struct_.get_field(stringify!($field)).ok_or(())?.clone().try_into().map_err(|_| ())?
321    };
322    (GET $_:ident $field:ident, $struct_:ident, $item:ident) => {{}};
323}
324
325declare_value_struct_conversion!(struct i_slint_core::layout::LayoutInfo { min, max, min_percent, max_percent, preferred, stretch });
326declare_value_struct_conversion!(struct i_slint_core::graphics::Point { x, y, ..Default::default()});
327declare_value_struct_conversion!(struct i_slint_core::api::LogicalPosition { x, y });
328
329i_slint_common::for_each_builtin_structs!(declare_value_struct_conversion);
330
331/// Implement From / TryFrom for Value that convert an `enum` to/from `Value::EnumerationValue`
332///
333/// The `enum` must derive `Display` and `FromStr`
334/// (can be done with `strum_macros::EnumString`, `strum_macros::Display` derive macro)
335macro_rules! declare_value_enum_conversion {
336    ($( $(#[$enum_doc:meta])* enum $Name:ident { $($body:tt)* })*) => { $(
337        impl From<i_slint_core::items::$Name> for Value {
338            fn from(v: i_slint_core::items::$Name) -> Self {
339                Value::EnumerationValue(stringify!($Name).to_owned(), v.to_string())
340            }
341        }
342        impl TryFrom<Value> for i_slint_core::items::$Name {
343            type Error = ();
344            fn try_from(v: Value) -> Result<i_slint_core::items::$Name, ()> {
345                use std::str::FromStr;
346                match v {
347                    Value::EnumerationValue(enumeration, value) => {
348                        if enumeration != stringify!($Name) {
349                            return Err(());
350                        }
351                        i_slint_core::items::$Name::from_str(value.as_str()).map_err(|_| ())
352                    }
353                    _ => Err(()),
354                }
355            }
356        }
357    )*};
358}
359
360i_slint_common::for_each_enums!(declare_value_enum_conversion);
361
362impl From<i_slint_core::animations::Instant> for Value {
363    fn from(value: i_slint_core::animations::Instant) -> Self {
364        Value::Number(value.0 as _)
365    }
366}
367impl TryFrom<Value> for i_slint_core::animations::Instant {
368    type Error = ();
369    fn try_from(v: Value) -> Result<i_slint_core::animations::Instant, Self::Error> {
370        match v {
371            Value::Number(x) => Ok(i_slint_core::animations::Instant(x as _)),
372            _ => Err(()),
373        }
374    }
375}
376
377impl From<()> for Value {
378    #[inline]
379    fn from(_: ()) -> Self {
380        Value::Void
381    }
382}
383impl TryFrom<Value> for () {
384    type Error = ();
385    #[inline]
386    fn try_from(_: Value) -> Result<(), Self::Error> {
387        Ok(())
388    }
389}
390
391impl From<Color> for Value {
392    #[inline]
393    fn from(c: Color) -> Self {
394        Value::Brush(Brush::SolidColor(c))
395    }
396}
397impl TryFrom<Value> for Color {
398    type Error = Value;
399    #[inline]
400    fn try_from(v: Value) -> Result<Color, Self::Error> {
401        match v {
402            Value::Brush(Brush::SolidColor(c)) => Ok(c),
403            _ => Err(v),
404        }
405    }
406}
407
408impl From<i_slint_core::lengths::LogicalLength> for Value {
409    #[inline]
410    fn from(l: i_slint_core::lengths::LogicalLength) -> Self {
411        Value::Number(l.get() as _)
412    }
413}
414impl TryFrom<Value> for i_slint_core::lengths::LogicalLength {
415    type Error = Value;
416    #[inline]
417    fn try_from(v: Value) -> Result<i_slint_core::lengths::LogicalLength, Self::Error> {
418        match v {
419            Value::Number(n) => Ok(i_slint_core::lengths::LogicalLength::new(n as _)),
420            _ => Err(v),
421        }
422    }
423}
424
425impl<T: Into<Value> + TryFrom<Value> + 'static> From<ModelRc<T>> for Value {
426    fn from(m: ModelRc<T>) -> Self {
427        if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<Value>>(&m) {
428            Value::Model(v.clone())
429        } else {
430            Value::Model(ModelRc::new(crate::value_model::ValueMapModel(m)))
431        }
432    }
433}
434impl<T: TryFrom<Value> + Default + 'static> TryFrom<Value> for ModelRc<T> {
435    type Error = Value;
436    #[inline]
437    fn try_from(v: Value) -> Result<ModelRc<T>, Self::Error> {
438        match v {
439            Value::Model(m) => {
440                if let Some(v) = <dyn core::any::Any>::downcast_ref::<ModelRc<T>>(&m) {
441                    Ok(v.clone())
442                } else if let Some(v) =
443                    m.as_any().downcast_ref::<crate::value_model::ValueMapModel<T>>()
444                {
445                    Ok(v.0.clone())
446                } else {
447                    Ok(ModelRc::new(m.map(|v| T::try_from(v).unwrap_or_default())))
448                }
449            }
450            _ => Err(v),
451        }
452    }
453}
454
455#[test]
456fn value_model_conversion() {
457    use i_slint_core::model::*;
458    let m = ModelRc::new(VecModel::from_slice(&[Value::Number(42.), Value::Number(12.)]));
459    let v = Value::from(m.clone());
460    assert_eq!(v, Value::Model(m.clone()));
461    let m2: ModelRc<Value> = v.clone().try_into().unwrap();
462    assert_eq!(m2, m);
463
464    let int_model: ModelRc<i32> = v.clone().try_into().unwrap();
465    assert_eq!(int_model.row_count(), 2);
466    assert_eq!(int_model.iter().collect::<Vec<_>>(), vec![42, 12]);
467
468    let Value::Model(m3) = int_model.clone().into() else { panic!("not a model?") };
469    assert_eq!(m3.row_count(), 2);
470    assert_eq!(m3.iter().collect::<Vec<_>>(), vec![Value::Number(42.), Value::Number(12.)]);
471
472    let str_model: ModelRc<SharedString> = v.clone().try_into().unwrap();
473    assert_eq!(str_model.row_count(), 2);
474    // Value::Int doesn't convert to string, but since the mapping can't report error, we get the default constructed string
475    assert_eq!(str_model.iter().collect::<Vec<_>>(), vec!["", ""]);
476
477    let err: Result<ModelRc<Value>, _> = Value::Bool(true).try_into();
478    assert!(err.is_err());
479
480    let model =
481        Rc::new(VecModel::<SharedString>::from_iter(["foo".into(), "bar".into(), "baz".into()]));
482
483    let value: Value = ModelRc::from(model.clone()).into();
484    let value_model: ModelRc<Value> = value.clone().try_into().unwrap();
485    assert_eq!(value_model.row_data(2).unwrap(), Value::String("baz".into()));
486    value_model.set_row_data(1, Value::String("qux".into()));
487    value_model.set_row_data(0, Value::Bool(true));
488    assert_eq!(value_model.row_data(1).unwrap(), Value::String("qux".into()));
489    // This is backed by a string model, so changing to bool has no effect
490    assert_eq!(value_model.row_data(0).unwrap(), Value::String("foo".into()));
491
492    // The original values are changed
493    assert_eq!(model.row_data(1).unwrap(), SharedString::from("qux"));
494    assert_eq!(model.row_data(0).unwrap(), SharedString::from("foo"));
495
496    let the_model: ModelRc<SharedString> = value.try_into().unwrap();
497    assert_eq!(the_model.row_data(1).unwrap(), SharedString::from("qux"));
498    assert_eq!(
499        model.as_ref() as *const VecModel<SharedString>,
500        the_model.as_any().downcast_ref::<VecModel<SharedString>>().unwrap()
501            as *const VecModel<SharedString>
502    );
503}
504
505pub(crate) fn normalize_identifier(ident: &str) -> SmolStr {
506    i_slint_compiler::parser::normalize_identifier(ident)
507}
508
509/// This type represents a runtime instance of structure in `.slint`.
510///
511/// This can either be an instance of a name structure introduced
512/// with the `struct` keyword in the .slint file, or an anonymous struct
513/// written with the `{ key: value, }`  notation.
514///
515/// It can be constructed with the [`FromIterator`] trait, and converted
516/// into or from a [`Value`] with the [`From`], [`TryFrom`] trait
517///
518///
519/// ```
520/// # use slint_interpreter::*;
521/// use core::convert::TryInto;
522/// // Construct a value from a key/value iterator
523/// let value : Value = [("foo".into(), 45u32.into()), ("bar".into(), true.into())]
524///     .iter().cloned().collect::<Struct>().into();
525///
526/// // get the properties of a `{ foo: 45, bar: true }`
527/// let s : Struct = value.try_into().unwrap();
528/// assert_eq!(s.get_field("foo").cloned().unwrap().try_into(), Ok(45u32));
529/// ```
530#[derive(Clone, PartialEq, Debug, Default)]
531pub struct Struct(pub(crate) HashMap<SmolStr, Value>);
532impl Struct {
533    /// Get the value for a given struct field
534    pub fn get_field(&self, name: &str) -> Option<&Value> {
535        self.0.get(&*normalize_identifier(name))
536    }
537    /// Set the value of a given struct field
538    pub fn set_field(&mut self, name: String, value: Value) {
539        self.0.insert(normalize_identifier(&name), value);
540    }
541
542    /// Iterate over all the fields in this struct
543    pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
544        self.0.iter().map(|(a, b)| (a.as_str(), b))
545    }
546}
547
548impl FromIterator<(String, Value)> for Struct {
549    fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
550        Self(iter.into_iter().map(|(s, v)| (normalize_identifier(&s), v)).collect())
551    }
552}
553
554/// ComponentCompiler is deprecated, use [`Compiler`] instead
555#[deprecated(note = "Use slint_interpreter::Compiler instead")]
556pub struct ComponentCompiler {
557    config: i_slint_compiler::CompilerConfiguration,
558    diagnostics: Vec<Diagnostic>,
559}
560
561#[allow(deprecated)]
562impl Default for ComponentCompiler {
563    fn default() -> Self {
564        let mut config = i_slint_compiler::CompilerConfiguration::new(
565            i_slint_compiler::generator::OutputFormat::Interpreter,
566        );
567        config.components_to_generate = i_slint_compiler::ComponentSelection::LastExported;
568        Self { config, diagnostics: vec![] }
569    }
570}
571
572#[allow(deprecated)]
573impl ComponentCompiler {
574    /// Returns a new ComponentCompiler.
575    pub fn new() -> Self {
576        Self::default()
577    }
578
579    /// Sets the include paths used for looking up `.slint` imports to the specified vector of paths.
580    pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
581        self.config.include_paths = include_paths;
582    }
583
584    /// Returns the include paths the component compiler is currently configured with.
585    pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
586        &self.config.include_paths
587    }
588
589    /// Sets the library paths used for looking up `@library` imports to the specified map of library names to paths.
590    pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
591        self.config.library_paths = library_paths;
592    }
593
594    /// Returns the library paths the component compiler is currently configured with.
595    pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
596        &self.config.library_paths
597    }
598
599    /// Sets the style to be used for widgets.
600    ///
601    /// Use the "material" style as widget style when compiling:
602    /// ```rust
603    /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, ComponentHandle};
604    ///
605    /// let mut compiler = ComponentCompiler::default();
606    /// compiler.set_style("material".into());
607    /// let definition =
608    ///     spin_on::spin_on(compiler.build_from_path("hello.slint"));
609    /// ```
610    pub fn set_style(&mut self, style: String) {
611        self.config.style = Some(style);
612    }
613
614    /// Returns the widget style the compiler is currently using when compiling .slint files.
615    pub fn style(&self) -> Option<&String> {
616        self.config.style.as_ref()
617    }
618
619    /// The domain used for translations
620    pub fn set_translation_domain(&mut self, domain: String) {
621        self.config.translation_domain = Some(domain);
622    }
623
624    /// Sets the callback that will be invoked when loading imported .slint files. The specified
625    /// `file_loader_callback` parameter will be called with a canonical file path as argument
626    /// and is expected to return a future that, when resolved, provides the source code of the
627    /// .slint file to be imported as a string.
628    /// If an error is returned, then the build will abort with that error.
629    /// If None is returned, it means the normal resolution algorithm will proceed as if the hook
630    /// was not in place (i.e: load from the file system following the include paths)
631    pub fn set_file_loader(
632        &mut self,
633        file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
634            + 'static,
635    ) {
636        self.config.open_import_fallback =
637            Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
638    }
639
640    /// Returns the diagnostics that were produced in the last call to [`Self::build_from_path`] or [`Self::build_from_source`].
641    pub fn diagnostics(&self) -> &Vec<Diagnostic> {
642        &self.diagnostics
643    }
644
645    /// Compile a .slint file into a ComponentDefinition
646    ///
647    /// Returns the compiled `ComponentDefinition` if there were no errors.
648    ///
649    /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
650    /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
651    /// function. The [`print_diagnostics`] function can be used to display the diagnostics
652    /// to the users.
653    ///
654    /// Diagnostics from previous calls are cleared when calling this function.
655    ///
656    /// If the path is `"-"`, the file will be read from stdin.
657    /// If the extension of the file .rs, the first `slint!` macro from a rust file will be extracted
658    ///
659    /// This function is `async` but in practice, this is only asynchronous if
660    /// [`Self::set_file_loader`] was called and its future is actually asynchronous.
661    /// If that is not used, then it is fine to use a very simple executor, such as the one
662    /// provided by the `spin_on` crate
663    pub async fn build_from_path<P: AsRef<Path>>(
664        &mut self,
665        path: P,
666    ) -> Option<ComponentDefinition> {
667        let path = path.as_ref();
668        let source = match i_slint_compiler::diagnostics::load_from_path(path) {
669            Ok(s) => s,
670            Err(d) => {
671                self.diagnostics = vec![d];
672                return None;
673            }
674        };
675
676        let r = crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await;
677        self.diagnostics = r.diagnostics.into_iter().collect();
678        r.components.into_values().next()
679    }
680
681    /// Compile some .slint code into a ComponentDefinition
682    ///
683    /// The `path` argument will be used for diagnostics and to compute relative
684    /// paths while importing.
685    ///
686    /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
687    /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
688    /// function. The [`print_diagnostics`] function can be used to display the diagnostics
689    /// to the users.
690    ///
691    /// Diagnostics from previous calls are cleared when calling this function.
692    ///
693    /// This function is `async` but in practice, this is only asynchronous if
694    /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
695    /// If that is not used, then it is fine to use a very simple executor, such as the one
696    /// provided by the `spin_on` crate
697    pub async fn build_from_source(
698        &mut self,
699        source_code: String,
700        path: PathBuf,
701    ) -> Option<ComponentDefinition> {
702        let r = crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await;
703        self.diagnostics = r.diagnostics.into_iter().collect();
704        r.components.into_values().next()
705    }
706}
707
708/// This is the entry point of the crate, it can be used to load a `.slint` file and
709/// compile it into a [`CompilationResult`].
710pub struct Compiler {
711    config: i_slint_compiler::CompilerConfiguration,
712}
713
714impl Default for Compiler {
715    fn default() -> Self {
716        let config = i_slint_compiler::CompilerConfiguration::new(
717            i_slint_compiler::generator::OutputFormat::Interpreter,
718        );
719        Self { config }
720    }
721}
722
723impl Compiler {
724    /// Returns a new Compiler.
725    pub fn new() -> Self {
726        Self::default()
727    }
728
729    /// Allow access to the underlying `CompilerConfiguration`
730    ///
731    /// This is an internal function without and ABI or API stability guarantees.
732    #[doc(hidden)]
733    #[cfg(feature = "internal")]
734    pub fn compiler_configuration(
735        &mut self,
736        _: i_slint_core::InternalToken,
737    ) -> &mut i_slint_compiler::CompilerConfiguration {
738        &mut self.config
739    }
740
741    /// Sets the include paths used for looking up `.slint` imports to the specified vector of paths.
742    pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
743        self.config.include_paths = include_paths;
744    }
745
746    /// Returns the include paths the component compiler is currently configured with.
747    pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
748        &self.config.include_paths
749    }
750
751    /// Sets the library paths used for looking up `@library` imports to the specified map of library names to paths.
752    pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
753        self.config.library_paths = library_paths;
754    }
755
756    /// Returns the library paths the component compiler is currently configured with.
757    pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
758        &self.config.library_paths
759    }
760
761    /// Sets the style to be used for widgets.
762    ///
763    /// Use the "material" style as widget style when compiling:
764    /// ```rust
765    /// use slint_interpreter::{ComponentDefinition, Compiler, ComponentHandle};
766    ///
767    /// let mut compiler = Compiler::default();
768    /// compiler.set_style("material".into());
769    /// let result = spin_on::spin_on(compiler.build_from_path("hello.slint"));
770    /// ```
771    pub fn set_style(&mut self, style: String) {
772        self.config.style = Some(style);
773    }
774
775    /// Returns the widget style the compiler is currently using when compiling .slint files.
776    pub fn style(&self) -> Option<&String> {
777        self.config.style.as_ref()
778    }
779
780    /// The domain used for translations
781    pub fn set_translation_domain(&mut self, domain: String) {
782        self.config.translation_domain = Some(domain);
783    }
784
785    /// Sets the callback that will be invoked when loading imported .slint files. The specified
786    /// `file_loader_callback` parameter will be called with a canonical file path as argument
787    /// and is expected to return a future that, when resolved, provides the source code of the
788    /// .slint file to be imported as a string.
789    /// If an error is returned, then the build will abort with that error.
790    /// If None is returned, it means the normal resolution algorithm will proceed as if the hook
791    /// was not in place (i.e: load from the file system following the include paths)
792    pub fn set_file_loader(
793        &mut self,
794        file_loader_fallback: impl Fn(&Path) -> core::pin::Pin<Box<dyn Future<Output = Option<std::io::Result<String>>>>>
795            + 'static,
796    ) {
797        self.config.open_import_fallback =
798            Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
799    }
800
801    /// Compile a .slint file
802    ///
803    /// Returns a structure that holds the diagnostics and the compiled components.
804    ///
805    /// Any diagnostics produced during the compilation, such as warnings or errors, can be retrieved
806    /// after the call using [`CompilationResult::diagnostics()`].
807    ///
808    /// If the file was compiled without error, the list of component names can be obtained with
809    /// [`CompilationResult::component_names`], and the compiled components themselves with
810    /// [`CompilationResult::component()`].
811    ///
812    /// If the path is `"-"`, the file will be read from stdin.
813    /// If the extension of the file .rs, the first `slint!` macro from a rust file will be extracted
814    ///
815    /// This function is `async` but in practice, this is only asynchronous if
816    /// [`Self::set_file_loader`] was called and its future is actually asynchronous.
817    /// If that is not used, then it is fine to use a very simple executor, such as the one
818    /// provided by the `spin_on` crate
819    pub async fn build_from_path<P: AsRef<Path>>(&self, path: P) -> CompilationResult {
820        let path = path.as_ref();
821        let source = match i_slint_compiler::diagnostics::load_from_path(path) {
822            Ok(s) => s,
823            Err(d) => {
824                let mut diagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
825                diagnostics.push_compiler_error(d);
826                return CompilationResult {
827                    components: HashMap::new(),
828                    diagnostics: diagnostics.into_iter().collect(),
829                    #[cfg(feature = "internal")]
830                    structs_and_enums: Vec::new(),
831                    #[cfg(feature = "internal")]
832                    named_exports: Vec::new(),
833                };
834            }
835        };
836
837        crate::dynamic_item_tree::load(source, path.into(), self.config.clone()).await
838    }
839
840    /// Compile some .slint code
841    ///
842    /// The `path` argument will be used for diagnostics and to compute relative
843    /// paths while importing.
844    ///
845    /// Any diagnostics produced during the compilation, such as warnings or errors, can be retrieved
846    /// after the call using [`CompilationResult::diagnostics()`].
847    ///
848    /// This function is `async` but in practice, this is only asynchronous if
849    /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
850    /// If that is not used, then it is fine to use a very simple executor, such as the one
851    /// provided by the `spin_on` crate
852    pub async fn build_from_source(&self, source_code: String, path: PathBuf) -> CompilationResult {
853        crate::dynamic_item_tree::load(source_code, path, self.config.clone()).await
854    }
855}
856
857/// The result of a compilation
858///
859/// If [`Self::has_errors()`] is true, then the compilation failed.
860/// The [`Self::diagnostics()`] function can be used to retrieve the diagnostics (errors and/or warnings)
861/// or [`Self::print_diagnostics()`] can be used to print them to stderr.
862/// The components can be retrieved using [`Self::components()`]
863#[derive(Clone)]
864pub struct CompilationResult {
865    pub(crate) components: HashMap<String, ComponentDefinition>,
866    pub(crate) diagnostics: Vec<Diagnostic>,
867    #[cfg(feature = "internal")]
868    pub(crate) structs_and_enums: Vec<LangType>,
869    /// For `export { Foo as Bar }` this vec contains tuples of (`Foo`, `Bar`)
870    #[cfg(feature = "internal")]
871    pub(crate) named_exports: Vec<(String, String)>,
872}
873
874impl core::fmt::Debug for CompilationResult {
875    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
876        f.debug_struct("CompilationResult")
877            .field("components", &self.components.keys())
878            .field("diagnostics", &self.diagnostics)
879            .finish()
880    }
881}
882
883impl CompilationResult {
884    /// Returns true if the compilation failed.
885    /// The errors can be retrieved using the [`Self::diagnostics()`] function.
886    pub fn has_errors(&self) -> bool {
887        self.diagnostics().any(|diag| diag.level() == DiagnosticLevel::Error)
888    }
889
890    /// Return an iterator over the diagnostics.
891    ///
892    /// You can also call [`Self::print_diagnostics()`] to output the diagnostics to stderr
893    pub fn diagnostics(&self) -> impl Iterator<Item = Diagnostic> + '_ {
894        self.diagnostics.iter().cloned()
895    }
896
897    /// Print the diagnostics to stderr
898    ///
899    /// The diagnostics are printed in the same style as rustc errors
900    ///
901    /// This function is available when the `display-diagnostics` is enabled.
902    #[cfg(feature = "display-diagnostics")]
903    pub fn print_diagnostics(&self) {
904        print_diagnostics(&self.diagnostics)
905    }
906
907    /// Returns an iterator over the compiled components.
908    pub fn components(&self) -> impl Iterator<Item = ComponentDefinition> + '_ {
909        self.components.values().cloned()
910    }
911
912    /// Returns the names of the components that were compiled.
913    pub fn component_names(&self) -> impl Iterator<Item = &str> + '_ {
914        self.components.keys().map(|s| s.as_str())
915    }
916
917    /// Return the component definition for the given name.
918    /// If the component does not exist, then `None` is returned.
919    pub fn component(&self, name: &str) -> Option<ComponentDefinition> {
920        self.components.get(name).cloned()
921    }
922
923    /// This is an internal function without API stability guarantees.
924    #[doc(hidden)]
925    #[cfg(feature = "internal")]
926    pub fn structs_and_enums(
927        &self,
928        _: i_slint_core::InternalToken,
929    ) -> impl Iterator<Item = &LangType> {
930        self.structs_and_enums.iter()
931    }
932
933    /// This is an internal function without API stability guarantees.
934    /// Returns the list of named export aliases as tuples (`export { Foo as Bar}` is (`Foo`, `Bar` tuple)).
935    #[doc(hidden)]
936    #[cfg(feature = "internal")]
937    pub fn named_exports(
938        &self,
939        _: i_slint_core::InternalToken,
940    ) -> impl Iterator<Item = &(String, String)> {
941        self.named_exports.iter()
942    }
943}
944
945/// ComponentDefinition is a representation of a compiled component from .slint markup.
946///
947/// It can be constructed from a .slint file using the [`Compiler::build_from_path`] or [`Compiler::build_from_source`] functions.
948/// And then it can be instantiated with the [`Self::create`] function.
949///
950/// The ComponentDefinition acts as a factory to create new instances. When you've finished
951/// creating the instances it is safe to drop the ComponentDefinition.
952#[derive(Clone)]
953pub struct ComponentDefinition {
954    pub(crate) inner: crate::dynamic_item_tree::ErasedItemTreeDescription,
955}
956
957impl ComponentDefinition {
958    /// Set a `debug(...)` handler
959    #[doc(hidden)]
960    #[cfg(feature = "internal")]
961    pub fn set_debug_handler(
962        &self,
963        handler: impl Fn(Option<&i_slint_compiler::diagnostics::SourceLocation>, &str) + 'static,
964        _: i_slint_core::InternalToken,
965    ) {
966        let handler = Rc::new(handler);
967
968        generativity::make_guard!(guard);
969        self.inner.unerase(guard).recursively_set_debug_handler(handler);
970    }
971    /// Creates a new instance of the component and returns a shared handle to it.
972    pub fn create(&self) -> Result<ComponentInstance, PlatformError> {
973        let instance = self.create_with_options(Default::default())?;
974        // Make sure the window adapter is created so call to `window()` do not panic later.
975        instance.inner.window_adapter_ref()?;
976        Ok(instance)
977    }
978
979    /// Creates a new instance of the component and returns a shared handle to it.
980    #[doc(hidden)]
981    #[cfg(feature = "internal")]
982    pub fn create_embedded(&self, ctx: FactoryContext) -> Result<ComponentInstance, PlatformError> {
983        self.create_with_options(WindowOptions::Embed {
984            parent_item_tree: ctx.parent_item_tree,
985            parent_item_tree_index: ctx.parent_item_tree_index,
986        })
987    }
988
989    /// Instantiate the component using an existing window.
990    #[doc(hidden)]
991    #[cfg(feature = "internal")]
992    pub fn create_with_existing_window(
993        &self,
994        window: &Window,
995    ) -> Result<ComponentInstance, PlatformError> {
996        self.create_with_options(WindowOptions::UseExistingWindow(
997            WindowInner::from_pub(window).window_adapter(),
998        ))
999    }
1000
1001    /// Private implementation of create
1002    pub(crate) fn create_with_options(
1003        &self,
1004        options: WindowOptions,
1005    ) -> Result<ComponentInstance, PlatformError> {
1006        generativity::make_guard!(guard);
1007        Ok(ComponentInstance { inner: self.inner.unerase(guard).clone().create(options)? })
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    pub(crate) 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 WeakInner = vtable::VWeak<ItemTreeVTable, crate::dynamic_item_tree::ErasedItemTreeBox>;
1540
1541    fn as_weak(&self) -> Weak<Self>
1542    where
1543        Self: Sized,
1544    {
1545        Weak::new(vtable::VRc::downgrade(&self.inner))
1546    }
1547
1548    fn clone_strong(&self) -> Self {
1549        Self { inner: self.inner.clone() }
1550    }
1551
1552    fn upgrade_from_weak_inner(inner: &Self::WeakInner) -> Option<Self> {
1553        Some(Self { inner: inner.upgrade()? })
1554    }
1555
1556    fn show(&self) -> Result<(), PlatformError> {
1557        self.inner.window_adapter_ref()?.window().show()
1558    }
1559
1560    fn hide(&self) -> Result<(), PlatformError> {
1561        self.inner.window_adapter_ref()?.window().hide()
1562    }
1563
1564    fn run(&self) -> Result<(), PlatformError> {
1565        self.show()?;
1566        run_event_loop()?;
1567        self.hide()
1568    }
1569
1570    fn window(&self) -> &Window {
1571        self.inner.window_adapter_ref().unwrap().window()
1572    }
1573
1574    fn global<'a, T: Global<'a, Self>>(&'a self) -> T
1575    where
1576        Self: Sized,
1577    {
1578        unreachable!()
1579    }
1580}
1581
1582impl From<ComponentInstance>
1583    for vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, ErasedItemTreeBox>
1584{
1585    fn from(value: ComponentInstance) -> Self {
1586        value.inner
1587    }
1588}
1589
1590/// Error returned by [`ComponentInstance::get_property`]
1591#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1592#[non_exhaustive]
1593pub enum GetPropertyError {
1594    /// There is no property with the given name
1595    #[display("no such property")]
1596    NoSuchProperty,
1597}
1598
1599/// Error returned by [`ComponentInstance::set_property`]
1600#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1601#[non_exhaustive]
1602pub enum SetPropertyError {
1603    /// There is no property with the given name.
1604    #[display("no such property")]
1605    NoSuchProperty,
1606    /// The property exists but does not have a type matching the dynamic value.
1607    ///
1608    /// This happens for example when assigning a source struct value to a target
1609    /// struct property, where the source doesn't have all the fields the target struct
1610    /// requires.
1611    #[display("wrong type")]
1612    WrongType,
1613    /// Attempt to set an output property.
1614    #[display("access denied")]
1615    AccessDenied,
1616}
1617
1618/// Error returned by [`ComponentInstance::set_callback`]
1619#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1620#[non_exhaustive]
1621pub enum SetCallbackError {
1622    /// There is no callback with the given name
1623    #[display("no such callback")]
1624    NoSuchCallback,
1625}
1626
1627/// Error returned by [`ComponentInstance::invoke`]
1628#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Error, derive_more::Display)]
1629#[non_exhaustive]
1630pub enum InvokeError {
1631    /// There is no callback or function with the given name
1632    #[display("no such callback or function")]
1633    NoSuchCallable,
1634}
1635
1636/// Enters the main event loop. This is necessary in order to receive
1637/// events from the windowing system in order to render to the screen
1638/// and react to user input.
1639pub fn run_event_loop() -> Result<(), PlatformError> {
1640    i_slint_backend_selector::with_platform(|b| b.run_event_loop())
1641}
1642
1643/// Spawns a [`Future`] to execute in the Slint event loop.
1644///
1645/// See the documentation of `slint::spawn_local()` for more info
1646pub fn spawn_local<F: Future + 'static>(fut: F) -> Result<JoinHandle<F::Output>, EventLoopError> {
1647    i_slint_backend_selector::with_global_context(|ctx| ctx.spawn_local(fut))
1648        .map_err(|_| EventLoopError::NoEventLoopProvider)?
1649}
1650
1651/// This module contains a few functions used by the tests
1652#[doc(hidden)]
1653pub mod testing {
1654    use super::ComponentHandle;
1655    use i_slint_core::window::WindowInner;
1656
1657    /// Wrapper around [`i_slint_core::tests::slint_send_mouse_click`]
1658    pub fn send_mouse_click(comp: &super::ComponentInstance, x: f32, y: f32) {
1659        i_slint_core::tests::slint_send_mouse_click(
1660            x,
1661            y,
1662            &WindowInner::from_pub(comp.window()).window_adapter(),
1663        );
1664    }
1665
1666    /// Wrapper around [`i_slint_core::tests::slint_send_keyboard_char`]
1667    pub fn send_keyboard_char(
1668        comp: &super::ComponentInstance,
1669        string: i_slint_core::SharedString,
1670        pressed: bool,
1671    ) {
1672        i_slint_core::tests::slint_send_keyboard_char(
1673            &string,
1674            pressed,
1675            &WindowInner::from_pub(comp.window()).window_adapter(),
1676        );
1677    }
1678    /// Wrapper around [`i_slint_core::tests::send_keyboard_string_sequence`]
1679    pub fn send_keyboard_string_sequence(
1680        comp: &super::ComponentInstance,
1681        string: i_slint_core::SharedString,
1682    ) {
1683        i_slint_core::tests::send_keyboard_string_sequence(
1684            &string,
1685            &WindowInner::from_pub(comp.window()).window_adapter(),
1686        );
1687    }
1688}
1689
1690#[test]
1691fn component_definition_properties() {
1692    i_slint_backend_testing::init_no_event_loop();
1693    let mut compiler = Compiler::default();
1694    compiler.set_style("fluent".into());
1695    let comp_def = spin_on::spin_on(
1696        compiler.build_from_source(
1697            r#"
1698    export component Dummy {
1699        in-out property <string> test;
1700        in-out property <int> underscores-and-dashes_preserved: 44;
1701        callback hello;
1702    }"#
1703            .into(),
1704            "".into(),
1705        ),
1706    )
1707    .component("Dummy")
1708    .unwrap();
1709
1710    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1711
1712    assert_eq!(props.len(), 2);
1713    assert_eq!(props[0].0, "test");
1714    assert_eq!(props[0].1, ValueType::String);
1715    assert_eq!(props[1].0, "underscores-and-dashes_preserved");
1716    assert_eq!(props[1].1, ValueType::Number);
1717
1718    let instance = comp_def.create().unwrap();
1719    assert_eq!(instance.get_property("underscores_and-dashes-preserved"), Ok(Value::Number(44.)));
1720    assert_eq!(
1721        instance.get_property("underscoresanddashespreserved"),
1722        Err(GetPropertyError::NoSuchProperty)
1723    );
1724    assert_eq!(
1725        instance.set_property("underscores-and_dashes-preserved", Value::Number(88.)),
1726        Ok(())
1727    );
1728    assert_eq!(
1729        instance.set_property("underscoresanddashespreserved", Value::Number(99.)),
1730        Err(SetPropertyError::NoSuchProperty)
1731    );
1732    assert_eq!(
1733        instance.set_property("underscores-and_dashes-preserved", Value::String("99".into())),
1734        Err(SetPropertyError::WrongType)
1735    );
1736    assert_eq!(instance.get_property("underscores-and-dashes-preserved"), Ok(Value::Number(88.)));
1737}
1738
1739#[test]
1740fn component_definition_properties2() {
1741    i_slint_backend_testing::init_no_event_loop();
1742    let mut compiler = Compiler::default();
1743    compiler.set_style("fluent".into());
1744    let comp_def = spin_on::spin_on(
1745        compiler.build_from_source(
1746            r#"
1747    export component Dummy {
1748        in-out property <string> sub-text <=> sub.text;
1749        sub := Text { property <int> private-not-exported; }
1750        out property <string> xreadonly: "the value";
1751        private property <string> xx: sub.text;
1752        callback hello;
1753    }"#
1754            .into(),
1755            "".into(),
1756        ),
1757    )
1758    .component("Dummy")
1759    .unwrap();
1760
1761    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1762
1763    assert_eq!(props.len(), 2);
1764    assert_eq!(props[0].0, "sub-text");
1765    assert_eq!(props[0].1, ValueType::String);
1766    assert_eq!(props[1].0, "xreadonly");
1767
1768    let callbacks = comp_def.callbacks().collect::<Vec<_>>();
1769    assert_eq!(callbacks.len(), 1);
1770    assert_eq!(callbacks[0], "hello");
1771
1772    let instance = comp_def.create().unwrap();
1773    assert_eq!(
1774        instance.set_property("xreadonly", SharedString::from("XXX").into()),
1775        Err(SetPropertyError::AccessDenied)
1776    );
1777    assert_eq!(instance.get_property("xreadonly"), Ok(Value::String("the value".into())));
1778    assert_eq!(
1779        instance.set_property("xx", SharedString::from("XXX").into()),
1780        Err(SetPropertyError::NoSuchProperty)
1781    );
1782    assert_eq!(
1783        instance.set_property("background", Value::default()),
1784        Err(SetPropertyError::NoSuchProperty)
1785    );
1786
1787    assert_eq!(instance.get_property("background"), Err(GetPropertyError::NoSuchProperty));
1788    assert_eq!(instance.get_property("xx"), Err(GetPropertyError::NoSuchProperty));
1789}
1790
1791#[test]
1792fn globals() {
1793    i_slint_backend_testing::init_no_event_loop();
1794    let mut compiler = Compiler::default();
1795    compiler.set_style("fluent".into());
1796    let definition = spin_on::spin_on(
1797        compiler.build_from_source(
1798            r#"
1799    export global My-Super_Global {
1800        in-out property <int> the-property : 21;
1801        callback my-callback();
1802    }
1803    export { My-Super_Global as AliasedGlobal }
1804    export component Dummy {
1805        callback alias <=> My-Super_Global.my-callback;
1806    }"#
1807            .into(),
1808            "".into(),
1809        ),
1810    )
1811    .component("Dummy")
1812    .unwrap();
1813
1814    assert_eq!(definition.globals().collect::<Vec<_>>(), vec!["My-Super_Global", "AliasedGlobal"]);
1815
1816    assert!(definition.global_properties("not-there").is_none());
1817    {
1818        let expected_properties = vec![("the-property".to_string(), ValueType::Number)];
1819        let expected_callbacks = vec!["my-callback".to_string()];
1820
1821        let assert_properties_and_callbacks = |global_name| {
1822            assert_eq!(
1823                definition
1824                    .global_properties(global_name)
1825                    .map(|props| props.collect::<Vec<_>>())
1826                    .as_ref(),
1827                Some(&expected_properties)
1828            );
1829            assert_eq!(
1830                definition
1831                    .global_callbacks(global_name)
1832                    .map(|props| props.collect::<Vec<_>>())
1833                    .as_ref(),
1834                Some(&expected_callbacks)
1835            );
1836        };
1837
1838        assert_properties_and_callbacks("My-Super-Global");
1839        assert_properties_and_callbacks("My_Super-Global");
1840        assert_properties_and_callbacks("AliasedGlobal");
1841    }
1842
1843    let instance = definition.create().unwrap();
1844    assert_eq!(
1845        instance.set_global_property("My_Super-Global", "the_property", Value::Number(44.)),
1846        Ok(())
1847    );
1848    assert_eq!(
1849        instance.set_global_property("AliasedGlobal", "the_property", Value::Number(44.)),
1850        Ok(())
1851    );
1852    assert_eq!(
1853        instance.set_global_property("DontExist", "the-property", Value::Number(88.)),
1854        Err(SetPropertyError::NoSuchProperty)
1855    );
1856
1857    assert_eq!(
1858        instance.set_global_property("My_Super-Global", "theproperty", Value::Number(88.)),
1859        Err(SetPropertyError::NoSuchProperty)
1860    );
1861    assert_eq!(
1862        instance.set_global_property("AliasedGlobal", "theproperty", Value::Number(88.)),
1863        Err(SetPropertyError::NoSuchProperty)
1864    );
1865    assert_eq!(
1866        instance.set_global_property("My_Super-Global", "the_property", Value::String("88".into())),
1867        Err(SetPropertyError::WrongType)
1868    );
1869    assert_eq!(
1870        instance.get_global_property("My-Super_Global", "yoyo"),
1871        Err(GetPropertyError::NoSuchProperty)
1872    );
1873    assert_eq!(
1874        instance.get_global_property("My-Super_Global", "the-property"),
1875        Ok(Value::Number(44.))
1876    );
1877
1878    assert_eq!(
1879        instance.set_property("the-property", Value::Void),
1880        Err(SetPropertyError::NoSuchProperty)
1881    );
1882    assert_eq!(instance.get_property("the-property"), Err(GetPropertyError::NoSuchProperty));
1883
1884    assert_eq!(
1885        instance.set_global_callback("DontExist", "the-property", |_| panic!()),
1886        Err(SetCallbackError::NoSuchCallback)
1887    );
1888    assert_eq!(
1889        instance.set_global_callback("My_Super_Global", "the-property", |_| panic!()),
1890        Err(SetCallbackError::NoSuchCallback)
1891    );
1892    assert_eq!(
1893        instance.set_global_callback("My_Super_Global", "yoyo", |_| panic!()),
1894        Err(SetCallbackError::NoSuchCallback)
1895    );
1896
1897    assert_eq!(
1898        instance.invoke_global("DontExist", "the-property", &[]),
1899        Err(InvokeError::NoSuchCallable)
1900    );
1901    assert_eq!(
1902        instance.invoke_global("My_Super_Global", "the-property", &[]),
1903        Err(InvokeError::NoSuchCallable)
1904    );
1905    assert_eq!(
1906        instance.invoke_global("My_Super_Global", "yoyo", &[]),
1907        Err(InvokeError::NoSuchCallable)
1908    );
1909
1910    // Alias to global don't crash (#8238)
1911    assert_eq!(instance.get_property("alias"), Err(GetPropertyError::NoSuchProperty));
1912}
1913
1914#[test]
1915fn call_functions() {
1916    i_slint_backend_testing::init_no_event_loop();
1917    let mut compiler = Compiler::default();
1918    compiler.set_style("fluent".into());
1919    let definition = spin_on::spin_on(
1920        compiler.build_from_source(
1921            r#"
1922    export global Gl {
1923        out property<string> q;
1924        public function foo-bar(a-a: string, b-b:int) -> string {
1925            q = a-a;
1926            return a-a + b-b;
1927        }
1928    }
1929    export component Test {
1930        out property<int> p;
1931        public function foo-bar(a: int, b:int) -> int {
1932            p = a;
1933            return a + b;
1934        }
1935    }"#
1936            .into(),
1937            "".into(),
1938        ),
1939    )
1940    .component("Test")
1941    .unwrap();
1942
1943    assert_eq!(definition.functions().collect::<Vec<_>>(), ["foo-bar"]);
1944    assert_eq!(definition.global_functions("Gl").unwrap().collect::<Vec<_>>(), ["foo-bar"]);
1945
1946    let instance = definition.create().unwrap();
1947
1948    assert_eq!(
1949        instance.invoke("foo_bar", &[Value::Number(3.), Value::Number(4.)]),
1950        Ok(Value::Number(7.))
1951    );
1952    assert_eq!(instance.invoke("p", &[]), Err(InvokeError::NoSuchCallable));
1953    assert_eq!(instance.get_property("p"), Ok(Value::Number(3.)));
1954
1955    assert_eq!(
1956        instance.invoke_global(
1957            "Gl",
1958            "foo_bar",
1959            &[Value::String("Hello".into()), Value::Number(10.)]
1960        ),
1961        Ok(Value::String("Hello10".into()))
1962    );
1963    assert_eq!(instance.get_global_property("Gl", "q"), Ok(Value::String("Hello".into())));
1964}
1965
1966#[test]
1967fn component_definition_struct_properties() {
1968    i_slint_backend_testing::init_no_event_loop();
1969    let mut compiler = Compiler::default();
1970    compiler.set_style("fluent".into());
1971    let comp_def = spin_on::spin_on(
1972        compiler.build_from_source(
1973            r#"
1974    export struct Settings {
1975        string_value: string,
1976    }
1977    export component Dummy {
1978        in-out property <Settings> test;
1979    }"#
1980            .into(),
1981            "".into(),
1982        ),
1983    )
1984    .component("Dummy")
1985    .unwrap();
1986
1987    let props = comp_def.properties().collect::<Vec<(_, _)>>();
1988
1989    assert_eq!(props.len(), 1);
1990    assert_eq!(props[0].0, "test");
1991    assert_eq!(props[0].1, ValueType::Struct);
1992
1993    let instance = comp_def.create().unwrap();
1994
1995    let valid_struct: Struct =
1996        [("string_value".to_string(), Value::String("hello".into()))].iter().cloned().collect();
1997
1998    assert_eq!(instance.set_property("test", Value::Struct(valid_struct.clone())), Ok(()));
1999    assert_eq!(instance.get_property("test").unwrap().value_type(), ValueType::Struct);
2000
2001    assert_eq!(instance.set_property("test", Value::Number(42.)), Err(SetPropertyError::WrongType));
2002
2003    let mut invalid_struct = valid_struct.clone();
2004    invalid_struct.set_field("other".into(), Value::Number(44.));
2005    assert_eq!(
2006        instance.set_property("test", Value::Struct(invalid_struct)),
2007        Err(SetPropertyError::WrongType)
2008    );
2009    let mut invalid_struct = valid_struct;
2010    invalid_struct.set_field("string_value".into(), Value::Number(44.));
2011    assert_eq!(
2012        instance.set_property("test", Value::Struct(invalid_struct)),
2013        Err(SetPropertyError::WrongType)
2014    );
2015}
2016
2017#[test]
2018fn component_definition_model_properties() {
2019    use i_slint_core::model::*;
2020    i_slint_backend_testing::init_no_event_loop();
2021    let mut compiler = Compiler::default();
2022    compiler.set_style("fluent".into());
2023    let comp_def = spin_on::spin_on(compiler.build_from_source(
2024        "export component Dummy { in-out property <[int]> prop: [42, 12]; }".into(),
2025        "".into(),
2026    ))
2027    .component("Dummy")
2028    .unwrap();
2029
2030    let props = comp_def.properties().collect::<Vec<(_, _)>>();
2031    assert_eq!(props.len(), 1);
2032    assert_eq!(props[0].0, "prop");
2033    assert_eq!(props[0].1, ValueType::Model);
2034
2035    let instance = comp_def.create().unwrap();
2036
2037    let int_model =
2038        Value::Model([Value::Number(14.), Value::Number(15.), Value::Number(16.)].into());
2039    let empty_model = Value::Model(ModelRc::new(VecModel::<Value>::default()));
2040    let model_with_string = Value::Model(VecModel::from_slice(&[
2041        Value::Number(1000.),
2042        Value::String("foo".into()),
2043        Value::Number(1111.),
2044    ]));
2045
2046    #[track_caller]
2047    fn check_model(val: Value, r: &[f64]) {
2048        if let Value::Model(m) = val {
2049            assert_eq!(r.len(), m.row_count());
2050            for (i, v) in r.iter().enumerate() {
2051                assert_eq!(m.row_data(i).unwrap(), Value::Number(*v));
2052            }
2053        } else {
2054            panic!("{val:?} not a model");
2055        }
2056    }
2057
2058    assert_eq!(instance.get_property("prop").unwrap().value_type(), ValueType::Model);
2059    check_model(instance.get_property("prop").unwrap(), &[42., 12.]);
2060
2061    instance.set_property("prop", int_model).unwrap();
2062    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2063
2064    assert_eq!(instance.set_property("prop", Value::Number(42.)), Err(SetPropertyError::WrongType));
2065    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2066    assert_eq!(instance.set_property("prop", model_with_string), Err(SetPropertyError::WrongType));
2067    check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
2068
2069    assert_eq!(instance.set_property("prop", empty_model), Ok(()));
2070    check_model(instance.get_property("prop").unwrap(), &[]);
2071}
2072
2073#[test]
2074fn lang_type_to_value_type() {
2075    use i_slint_compiler::langtype::Struct as LangStruct;
2076    use std::collections::BTreeMap;
2077
2078    assert_eq!(ValueType::from(LangType::Void), ValueType::Void);
2079    assert_eq!(ValueType::from(LangType::Float32), ValueType::Number);
2080    assert_eq!(ValueType::from(LangType::Int32), ValueType::Number);
2081    assert_eq!(ValueType::from(LangType::Duration), ValueType::Number);
2082    assert_eq!(ValueType::from(LangType::Angle), ValueType::Number);
2083    assert_eq!(ValueType::from(LangType::PhysicalLength), ValueType::Number);
2084    assert_eq!(ValueType::from(LangType::LogicalLength), ValueType::Number);
2085    assert_eq!(ValueType::from(LangType::Percent), ValueType::Number);
2086    assert_eq!(ValueType::from(LangType::UnitProduct(vec![])), ValueType::Number);
2087    assert_eq!(ValueType::from(LangType::String), ValueType::String);
2088    assert_eq!(ValueType::from(LangType::Color), ValueType::Brush);
2089    assert_eq!(ValueType::from(LangType::Brush), ValueType::Brush);
2090    assert_eq!(ValueType::from(LangType::Array(Rc::new(LangType::Void))), ValueType::Model);
2091    assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool);
2092    assert_eq!(
2093        ValueType::from(LangType::Struct(Rc::new(LangStruct {
2094            fields: BTreeMap::default(),
2095            name: None,
2096            node: None,
2097            rust_attributes: None
2098        }))),
2099        ValueType::Struct
2100    );
2101    assert_eq!(ValueType::from(LangType::Image), ValueType::Image);
2102}
2103
2104#[test]
2105fn test_multi_components() {
2106    let result = spin_on::spin_on(
2107        Compiler::default().build_from_source(
2108            r#"
2109        export struct Settings {
2110            string_value: string,
2111        }
2112        export global ExpGlo { in-out property <int> test: 42; }
2113        component Common {
2114            in-out property <Settings> settings: { string_value: "Hello", };
2115        }
2116        export component Xyz inherits Window {
2117            in-out property <int> aaa: 8;
2118        }
2119        export component Foo {
2120
2121            in-out property <int> test: 42;
2122            c := Common {}
2123        }
2124        export component Bar inherits Window {
2125            in-out property <int> blah: 78;
2126            c := Common {}
2127        }
2128        "#
2129            .into(),
2130            PathBuf::from("hello.slint"),
2131        ),
2132    );
2133
2134    assert!(!result.has_errors(), "Error {:?}", result.diagnostics().collect::<Vec<_>>());
2135    let mut components = result.component_names().collect::<Vec<_>>();
2136    components.sort();
2137    assert_eq!(components, vec!["Bar", "Xyz"]);
2138    let diag = result.diagnostics().collect::<Vec<_>>();
2139    assert_eq!(diag.len(), 1);
2140    assert_eq!(diag[0].level(), DiagnosticLevel::Warning);
2141    assert_eq!(
2142        diag[0].message(),
2143        "Exported component 'Foo' doesn't inherit Window. No code will be generated for it"
2144    );
2145
2146    let comp1 = result.component("Xyz").unwrap();
2147    assert_eq!(comp1.name(), "Xyz");
2148    let instance1a = comp1.create().unwrap();
2149    let comp2 = result.component("Bar").unwrap();
2150    let instance2 = comp2.create().unwrap();
2151    let instance1b = comp1.create().unwrap();
2152
2153    // globals are not shared between instances
2154    assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2155    assert_eq!(instance1a.set_global_property("ExpGlo", "test", Value::Number(88.0)), Ok(()));
2156    assert_eq!(instance2.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2157    assert_eq!(instance1b.get_global_property("ExpGlo", "test"), Ok(Value::Number(42.0)));
2158    assert_eq!(instance1a.get_global_property("ExpGlo", "test"), Ok(Value::Number(88.0)));
2159
2160    assert!(result.component("Settings").is_none());
2161    assert!(result.component("Foo").is_none());
2162    assert!(result.component("Common").is_none());
2163    assert!(result.component("ExpGlo").is_none());
2164    assert!(result.component("xyz").is_none());
2165}
2166
2167#[cfg(all(test, feature = "internal-highlight"))]
2168fn compile(code: &str) -> (ComponentInstance, PathBuf) {
2169    i_slint_backend_testing::init_no_event_loop();
2170    let mut compiler = Compiler::default();
2171    compiler.set_style("fluent".into());
2172    let path = PathBuf::from("/tmp/test.slint");
2173
2174    let compile_result =
2175        spin_on::spin_on(compiler.build_from_source(code.to_string(), path.clone()));
2176
2177    for d in &compile_result.diagnostics {
2178        eprintln!("{d}");
2179    }
2180
2181    assert!(!compile_result.has_errors());
2182
2183    let definition = compile_result.components().next().unwrap();
2184    let instance = definition.create().unwrap();
2185
2186    (instance, path)
2187}
2188
2189#[cfg(feature = "internal-highlight")]
2190#[test]
2191fn test_element_node_at_source_code_position() {
2192    let code = r#"
2193component Bar1 {}
2194
2195component Foo1 {
2196}
2197
2198export component Foo2 inherits Window  {
2199    Bar1 {}
2200    Foo1   {}
2201}"#;
2202
2203    let (handle, path) = compile(code);
2204
2205    for i in 0..code.len() as u32 {
2206        let elements = handle.element_node_at_source_code_position(&path, i);
2207        eprintln!("{i}: {}", code.as_bytes()[i as usize] as char);
2208        match i {
2209            16 => assert_eq!(elements.len(), 1),       // Bar1 (def)
2210            35 => assert_eq!(elements.len(), 1),       // Foo1 (def)
2211            71..=78 => assert_eq!(elements.len(), 1),  // Window + WS (from Foo2)
2212            85..=89 => assert_eq!(elements.len(), 1),  // Bar1 + WS (use)
2213            97..=103 => assert_eq!(elements.len(), 1), // Foo1 + WS (use)
2214            _ => assert!(elements.is_empty()),
2215        }
2216    }
2217}
2218
2219#[cfg(feature = "ffi")]
2220#[allow(missing_docs)]
2221#[path = "ffi.rs"]
2222pub(crate) mod ffi;