1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4use i_slint_compiler::diagnostics::SourceFileVersion;
5use i_slint_compiler::langtype::Type as LangType;
6use i_slint_core::component_factory::ComponentFactory;
7#[cfg(feature = "internal")]
8use i_slint_core::component_factory::FactoryContext;
9use i_slint_core::model::{Model, ModelRc};
10#[cfg(feature = "internal")]
11use i_slint_core::window::WindowInner;
12use i_slint_core::{PathData, SharedVector};
13use std::borrow::Cow;
14use std::collections::HashMap;
15use std::path::{Path, PathBuf};
16use std::rc::Rc;
17
18#[doc(inline)]
19pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel};
20
21pub use i_slint_core::api::*;
22// keep in sync with api/rs/slint/lib.rs
23pub use i_slint_core::graphics::{
24 Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer,
25};
26use i_slint_core::items::*;
27
28use crate::dynamic_item_tree::ErasedItemTreeBox;
29#[cfg(any(feature = "internal", target_arch = "wasm32"))]
30use crate::dynamic_item_tree::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 do 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 == 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: &f64) => write!(f, "Value::Number({:?})", n),
185 Value::String(s: &SharedString) => write!(f, "Value::String({:?})", s),
186 Value::Bool(b: &bool) => write!(f, "Value::Bool({:?})", b),
187 Value::Image(i: &Image) => write!(f, "Value::Image({:?})", i),
188 Value::Model(m: &ModelRc) => {
189 write!(f, "Value::Model(")?;
190 f.debug_list().entries(m.iter()).finish()?;
191 write!(f, "])")
192 }
193 Value::Struct(s: &Struct) => write!(f, "Value::Struct({:?})", s),
194 Value::Brush(b: &Brush) => write!(f, "Value::Brush({:?})", b),
195 Value::PathData(e: &PathData) => write!(f, "Value::PathElements({:?})", e),
196 Value::EasingCurve(c: &EasingCurve) => write!(f, "Value::EasingCurve({:?})", c),
197 Value::EnumerationValue(n: &String, v: &String) => write!(f, "Value::EnumerationValue({:?}, {:?})", n, v),
198 Value::LayoutCache(v: &SharedVector) => write!(f, "Value::LayoutCache({:?})", v),
199 Value::ComponentFactory(factory: &ComponentFactory) => 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()});
327
328i_slint_common::for_each_builtin_structs!(declare_value_struct_conversion);
329
330/// Implement From / TryFrom for Value that convert an `enum` to/from `Value::EnumerationValue`
331///
332/// The `enum` must derive `Display` and `FromStr`
333/// (can be done with `strum_macros::EnumString`, `strum_macros::Display` derive macro)
334macro_rules! declare_value_enum_conversion {
335 ($( $(#[$enum_doc:meta])* enum $Name:ident { $($body:tt)* })*) => { $(
336 impl From<i_slint_core::items::$Name> for Value {
337 fn from(v: i_slint_core::items::$Name) -> Self {
338 Value::EnumerationValue(
339 stringify!($Name).to_owned(),
340 v.to_string().trim_start_matches("r#").replace('_', "-"),
341 )
342 }
343 }
344 impl TryFrom<Value> for i_slint_core::items::$Name {
345 type Error = ();
346 fn try_from(v: Value) -> Result<i_slint_core::items::$Name, ()> {
347 use std::str::FromStr;
348 match v {
349 Value::EnumerationValue(enumeration, value) => {
350 if enumeration != stringify!($Name) {
351 return Err(());
352 }
353
354 <i_slint_core::items::$Name>::from_str(value.as_str())
355 .or_else(|_| {
356 let norm = value.as_str().replace('-', "_");
357 <i_slint_core::items::$Name>::from_str(&norm)
358 .or_else(|_| <i_slint_core::items::$Name>::from_str(&format!("r#{}", norm)))
359 })
360 .map_err(|_| ())
361 }
362 _ => Err(()),
363 }
364 }
365 }
366 )*};
367}
368
369i_slint_common::for_each_enums!(declare_value_enum_conversion);
370
371impl From<i_slint_core::animations::Instant> for Value {
372 fn from(value: i_slint_core::animations::Instant) -> Self {
373 Value::Number(value.0 as _)
374 }
375}
376impl TryFrom<Value> for i_slint_core::animations::Instant {
377 type Error = ();
378 fn try_from(v: Value) -> Result<i_slint_core::animations::Instant, Self::Error> {
379 match v {
380 Value::Number(x: f64) => Ok(i_slint_core::animations::Instant(x as _)),
381 _ => Err(()),
382 }
383 }
384}
385
386impl From<()> for Value {
387 #[inline]
388 fn from(_: ()) -> Self {
389 Value::Void
390 }
391}
392impl TryFrom<Value> for () {
393 type Error = ();
394 #[inline]
395 fn try_from(_: Value) -> Result<(), Self::Error> {
396 Ok(())
397 }
398}
399
400impl From<Color> for Value {
401 #[inline]
402 fn from(c: Color) -> Self {
403 Value::Brush(Brush::SolidColor(c))
404 }
405}
406impl TryFrom<Value> for Color {
407 type Error = Value;
408 #[inline]
409 fn try_from(v: Value) -> Result<Color, Self::Error> {
410 match v {
411 Value::Brush(Brush::SolidColor(c: Color)) => Ok(c),
412 _ => Err(v),
413 }
414 }
415}
416
417impl From<i_slint_core::lengths::LogicalLength> for Value {
418 #[inline]
419 fn from(l: i_slint_core::lengths::LogicalLength) -> Self {
420 Value::Number(l.get() as _)
421 }
422}
423impl TryFrom<Value> for i_slint_core::lengths::LogicalLength {
424 type Error = Value;
425 #[inline]
426 fn try_from(v: Value) -> Result<i_slint_core::lengths::LogicalLength, Self::Error> {
427 match v {
428 Value::Number(n: f64) => Ok(i_slint_core::lengths::LogicalLength::new(n as _)),
429 _ => Err(v),
430 }
431 }
432}
433
434/// Normalize the identifier to use dashes
435pub(crate) fn normalize_identifier(ident: &str) -> Cow<'_, str> {
436 if ident.contains('_') {
437 ident.replace(from:'_', to:"-").into()
438 } else {
439 ident.into()
440 }
441}
442
443/// This type represents a runtime instance of structure in `.slint`.
444///
445/// This can either be an instance of a name structure introduced
446/// with the `struct` keyword in the .slint file, or an anonymous struct
447/// written with the `{ key: value, }` notation.
448///
449/// It can be constructed with the [`FromIterator`] trait, and converted
450/// into or from a [`Value`] with the [`From`], [`TryFrom`] trait
451///
452///
453/// ```
454/// # use slint_interpreter::*;
455/// use core::convert::TryInto;
456/// // Construct a value from a key/value iterator
457/// let value : Value = [("foo".into(), 45u32.into()), ("bar".into(), true.into())]
458/// .iter().cloned().collect::<Struct>().into();
459///
460/// // get the properties of a `{ foo: 45, bar: true }`
461/// let s : Struct = value.try_into().unwrap();
462/// assert_eq!(s.get_field("foo").cloned().unwrap().try_into(), Ok(45u32));
463/// ```
464#[derive(Clone, PartialEq, Debug, Default)]
465pub struct Struct(HashMap<String, Value>);
466impl Struct {
467 /// Get the value for a given struct field
468 pub fn get_field(&self, name: &str) -> Option<&Value> {
469 self.0.get(&*normalize_identifier(ident:name))
470 }
471 /// Set the value of a given struct field
472 pub fn set_field(&mut self, name: String, value: Value) {
473 if name.contains('_') {
474 self.0.insert(k:name.replace('_', "-"), v:value);
475 } else {
476 self.0.insert(k:name, v:value);
477 }
478 }
479
480 /// Iterate over all the fields in this struct
481 pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
482 self.0.iter().map(|(a: &String, b: &Value)| (a.as_str(), b))
483 }
484}
485
486impl FromIterator<(String, Value)> for Struct {
487 fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
488 Self(
489 iterimpl Iterator.into_iter()
490 .map(|(s: String, v: Value)| (if s.contains('_') { s.replace(from:'_', to:"-") } else { s }, v))
491 .collect(),
492 )
493 }
494}
495
496/// ComponentCompiler is the entry point to the Slint interpreter that can be used
497/// to load .slint files or compile them on-the-fly from a string.
498pub struct ComponentCompiler {
499 config: i_slint_compiler::CompilerConfiguration,
500 diagnostics: Vec<Diagnostic>,
501}
502
503impl Default for ComponentCompiler {
504 fn default() -> Self {
505 Self {
506 config: i_slint_compiler::CompilerConfiguration::new(
507 i_slint_compiler::generator::OutputFormat::Interpreter,
508 ),
509 diagnostics: vec![],
510 }
511 }
512}
513
514impl ComponentCompiler {
515 /// Returns a new ComponentCompiler.
516 pub fn new() -> Self {
517 Self::default()
518 }
519
520 /// Allow access to the underlying `CompilerConfiguration`
521 ///
522 /// This is an internal function without and ABI or API stability guarantees.
523 #[doc(hidden)]
524 #[cfg(feature = "internal")]
525 pub fn compiler_configuration(
526 &mut self,
527 _: i_slint_core::InternalToken,
528 ) -> &mut i_slint_compiler::CompilerConfiguration {
529 &mut self.config
530 }
531
532 /// Sets the include paths used for looking up `.slint` imports to the specified vector of paths.
533 pub fn set_include_paths(&mut self, include_paths: Vec<std::path::PathBuf>) {
534 self.config.include_paths = include_paths;
535 }
536
537 /// Returns the include paths the component compiler is currently configured with.
538 pub fn include_paths(&self) -> &Vec<std::path::PathBuf> {
539 &self.config.include_paths
540 }
541
542 /// Sets the library paths used for looking up `@library` imports to the specified map of library names to paths.
543 pub fn set_library_paths(&mut self, library_paths: HashMap<String, PathBuf>) {
544 self.config.library_paths = library_paths;
545 }
546
547 /// Returns the library paths the component compiler is currently configured with.
548 pub fn library_paths(&self) -> &HashMap<String, PathBuf> {
549 &self.config.library_paths
550 }
551
552 /// Sets the style to be used for widgets.
553 ///
554 /// Use the "material" style as widget style when compiling:
555 /// ```rust
556 /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, ComponentHandle};
557 ///
558 /// let mut compiler = ComponentCompiler::default();
559 /// compiler.set_style("material".into());
560 /// let definition =
561 /// spin_on::spin_on(compiler.build_from_path("hello.slint"));
562 /// ```
563 pub fn set_style(&mut self, style: String) {
564 self.config.style = Some(style);
565 }
566
567 /// Returns the widget style the compiler is currently using when compiling .slint files.
568 pub fn style(&self) -> Option<&String> {
569 self.config.style.as_ref()
570 }
571
572 /// The domain used for translations
573 pub fn set_translation_domain(&mut self, domain: String) {
574 self.config.translation_domain = Some(domain);
575 }
576
577 /// Sets the callback that will be invoked when loading imported .slint files. The specified
578 /// `file_loader_callback` parameter will be called with a canonical file path as argument
579 /// and is expected to return a future that, when resolved, provides the source code of the
580 /// .slint file to be imported as a string.
581 /// If an error is returned, then the build will abort with that error.
582 /// If None is returned, it means the normal resolution algorithm will proceed as if the hook
583 /// was not in place (i.e: load from the file system following the include paths)
584 pub fn set_file_loader(
585 &mut self,
586 file_loader_fallback: impl Fn(
587 &Path,
588 ) -> core::pin::Pin<
589 Box<dyn core::future::Future<Output = Option<std::io::Result<String>>>>,
590 > + 'static,
591 ) {
592 self.config.open_import_fallback =
593 Some(Rc::new(move |path| file_loader_fallback(Path::new(path.as_str()))));
594 }
595
596 /// Returns the diagnostics that were produced in the last call to [`Self::build_from_path`] or [`Self::build_from_source`].
597 pub fn diagnostics(&self) -> &Vec<Diagnostic> {
598 &self.diagnostics
599 }
600
601 /// Compile a .slint file into a ComponentDefinition
602 ///
603 /// Returns the compiled `ComponentDefinition` if there were no errors.
604 ///
605 /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
606 /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
607 /// function. The [`print_diagnostics`] function can be used to display the diagnostics
608 /// to the users.
609 ///
610 /// Diagnostics from previous calls are cleared when calling this function.
611 ///
612 /// If the path is `"-"`, the file will be read from stdin.
613 /// If the extension of the file .rs, the first `slint!` macro from a rust file will be extracted
614 ///
615 /// This function is `async` but in practice, this is only asynchronous if
616 /// [`Self::set_file_loader`] was called and its future is actually asynchronous.
617 /// If that is not used, then it is fine to use a very simple executor, such as the one
618 /// provided by the `spin_on` crate
619 pub async fn build_from_path<P: AsRef<Path>>(
620 &mut self,
621 path: P,
622 ) -> Option<ComponentDefinition> {
623 let path = path.as_ref();
624 let source = match i_slint_compiler::diagnostics::load_from_path(path) {
625 Ok(s) => s,
626 Err(d) => {
627 self.diagnostics = vec![d];
628 return None;
629 }
630 };
631
632 generativity::make_guard!(guard);
633 let (c, diag) =
634 crate::dynamic_item_tree::load(source, path.into(), None, self.config.clone(), guard)
635 .await;
636 self.diagnostics = diag.into_iter().collect();
637 c.ok().map(|inner| ComponentDefinition { inner: inner.into() })
638 }
639
640 /// Compile some .slint code into a ComponentDefinition
641 ///
642 /// The `path` argument will be used for diagnostics and to compute relative
643 /// paths while importing.
644 ///
645 /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
646 /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
647 /// function. The [`print_diagnostics`] function can be used to display the diagnostics
648 /// to the users.
649 ///
650 /// Diagnostics from previous calls are cleared when calling this function.
651 ///
652 /// This function is `async` but in practice, this is only asynchronous if
653 /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
654 /// If that is not used, then it is fine to use a very simple executor, such as the one
655 /// provided by the `spin_on` crate
656 pub async fn build_from_source(
657 &mut self,
658 source_code: String,
659 path: PathBuf,
660 ) -> Option<ComponentDefinition> {
661 self.build_from_versioned_source_impl(source_code, path, None).await
662 }
663
664 /// Compile some .slint code into a ComponentDefinition
665 ///
666 /// The `path` argument will be used for diagnostics and to compute relative
667 /// paths while importing, and the version of the `SourceFileInner` will be
668 /// set to `version`
669 ///
670 /// Any diagnostics produced during the compilation, such as warnings or errors, are collected
671 /// in this ComponentCompiler and can be retrieved after the call using the [`Self::diagnostics()`]
672 /// function. The [`print_diagnostics`] function can be used to display the diagnostics
673 /// to the users.
674 ///
675 /// Diagnostics from previous calls are cleared when calling this function.
676 ///
677 /// This function is `async` but in practice, this is only asynchronous if
678 /// [`Self::set_file_loader`] is set and its future is actually asynchronous.
679 /// If that is not used, then it is fine to use a very simple executor, such as the one
680 /// provided by the `spin_on` crate
681 #[doc(hidden)]
682 #[cfg(feature = "internal")]
683 pub async fn build_from_versioned_source(
684 &mut self,
685 source_code: String,
686 path: PathBuf,
687 version: SourceFileVersion,
688 ) -> Option<ComponentDefinition> {
689 self.build_from_versioned_source_impl(source_code, path, version).await
690 }
691
692 async fn build_from_versioned_source_impl(
693 &mut self,
694 source_code: String,
695 path: PathBuf,
696 version: SourceFileVersion,
697 ) -> Option<ComponentDefinition> {
698 generativity::make_guard!(guard);
699 let (c, diag) =
700 crate::dynamic_item_tree::load(source_code, path, version, self.config.clone(), guard)
701 .await;
702 self.diagnostics = diag.into_iter().collect();
703 c.ok().map(|inner| ComponentDefinition { inner: inner.into() })
704 }
705}
706
707/// ComponentDefinition is a representation of a compiled component from .slint markup.
708///
709/// It can be constructed from a .slint file using the [`ComponentCompiler::build_from_path`] or [`ComponentCompiler::build_from_source`] functions.
710/// And then it can be instantiated with the [`Self::create`] function.
711///
712/// The ComponentDefinition acts as a factory to create new instances. When you've finished
713/// creating the instances it is safe to drop the ComponentDefinition.
714#[derive(Clone)]
715pub struct ComponentDefinition {
716 inner: crate::dynamic_item_tree::ErasedItemTreeDescription,
717}
718
719impl ComponentDefinition {
720 /// Creates a new instance of the component and returns a shared handle to it.
721 pub fn create(&self) -> Result<ComponentInstance, PlatformError> {
722 generativity::make_guard!(guard);
723 Ok(ComponentInstance {
724 inner: self.inner.unerase(guard).clone().create(Default::default())?,
725 })
726 }
727
728 /// Creates a new instance of the component and returns a shared handle to it.
729 #[doc(hidden)]
730 #[cfg(feature = "internal")]
731 pub fn create_embedded(&self, ctx: FactoryContext) -> Result<ComponentInstance, PlatformError> {
732 generativity::make_guard!(guard);
733 Ok(ComponentInstance {
734 inner: self.inner.unerase(guard).clone().create(WindowOptions::Embed {
735 parent_item_tree: ctx.parent_item_tree,
736 parent_item_tree_index: ctx.parent_item_tree_index,
737 })?,
738 })
739 }
740
741 /// Instantiate the component for wasm using the given canvas id
742 #[cfg(target_arch = "wasm32")]
743 pub fn create_with_canvas_id(
744 &self,
745 canvas_id: &str,
746 ) -> Result<ComponentInstance, PlatformError> {
747 generativity::make_guard!(guard);
748 Ok(ComponentInstance {
749 inner: self
750 .inner
751 .unerase(guard)
752 .clone()
753 .create(WindowOptions::CreateWithCanvasId(canvas_id.into()))?,
754 })
755 }
756
757 /// Instantiate the component using an existing window.
758 #[doc(hidden)]
759 #[cfg(feature = "internal")]
760 pub fn create_with_existing_window(
761 &self,
762 window: &Window,
763 ) -> Result<ComponentInstance, PlatformError> {
764 generativity::make_guard!(guard);
765 Ok(ComponentInstance {
766 inner: self.inner.unerase(guard).clone().create(WindowOptions::UseExistingWindow(
767 WindowInner::from_pub(window).window_adapter(),
768 ))?,
769 })
770 }
771
772 /// List of publicly declared properties or callback.
773 ///
774 /// This is internal because it exposes the `Type` from compilerlib.
775 #[doc(hidden)]
776 #[cfg(feature = "internal")]
777 pub fn properties_and_callbacks(
778 &self,
779 ) -> impl Iterator<Item = (String, i_slint_compiler::langtype::Type)> + '_ {
780 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
781 // which is not required, but this is safe because there is only one instance of the unerased type
782 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
783 self.inner.unerase(guard).properties()
784 }
785
786 /// Returns an iterator over all publicly declared properties. Each iterator item is a tuple of property name
787 /// and property type for each of them.
788 pub fn properties(&self) -> impl Iterator<Item = (String, ValueType)> + '_ {
789 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
790 // which is not required, but this is safe because there is only one instance of the unerased type
791 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
792 self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type)| {
793 if prop_type.is_property_type() {
794 Some((prop_name, prop_type.into()))
795 } else {
796 None
797 }
798 })
799 }
800
801 /// Returns the names of all publicly declared callbacks.
802 pub fn callbacks(&self) -> impl Iterator<Item = String> + '_ {
803 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
804 // which is not required, but this is safe because there is only one instance of the unerased type
805 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
806 self.inner.unerase(guard).properties().filter_map(|(prop_name, prop_type)| {
807 if matches!(prop_type, LangType::Callback { .. }) {
808 Some(prop_name)
809 } else {
810 None
811 }
812 })
813 }
814
815 /// Returns the names of all exported global singletons
816 ///
817 /// **Note:** Only globals that are exported or re-exported from the main .slint file will
818 /// be exposed in the API
819 pub fn globals(&self) -> impl Iterator<Item = String> + '_ {
820 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
821 // which is not required, but this is safe because there is only one instance of the unerased type
822 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
823 self.inner.unerase(guard).global_names()
824 }
825
826 /// List of publicly declared properties or callback in the exported global singleton specified by its name.
827 ///
828 /// This is internal because it exposes the `Type` from compilerlib.
829 #[doc(hidden)]
830 #[cfg(feature = "internal")]
831 pub fn global_properties_and_callbacks(
832 &self,
833 global_name: &str,
834 ) -> Option<impl Iterator<Item = (String, i_slint_compiler::langtype::Type)> + '_> {
835 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
836 // which is not required, but this is safe because there is only one instance of the unerased type
837 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
838 self.inner.unerase(guard).global_properties(global_name)
839 }
840
841 /// List of publicly declared properties in the exported global singleton specified by its name.
842 pub fn global_properties(
843 &self,
844 global_name: &str,
845 ) -> Option<impl Iterator<Item = (String, ValueType)> + '_> {
846 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
847 // which is not required, but this is safe because there is only one instance of the unerased type
848 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
849 self.inner.unerase(guard).global_properties(global_name).map(|iter| {
850 iter.filter_map(|(prop_name, prop_type)| {
851 if prop_type.is_property_type() {
852 Some((prop_name, prop_type.into()))
853 } else {
854 None
855 }
856 })
857 })
858 }
859
860 /// List of publicly declared callbacks in the exported global singleton specified by its name.
861 pub fn global_callbacks(&self, global_name: &str) -> Option<impl Iterator<Item = String> + '_> {
862 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
863 // which is not required, but this is safe because there is only one instance of the unerased type
864 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
865 self.inner.unerase(guard).global_properties(global_name).map(|iter| {
866 iter.filter_map(|(prop_name, prop_type)| {
867 if matches!(prop_type, LangType::Callback { .. }) {
868 Some(prop_name)
869 } else {
870 None
871 }
872 })
873 })
874 }
875
876 /// The name of this Component as written in the .slint file
877 pub fn name(&self) -> &str {
878 // We create here a 'static guard, because unfortunately the returned type would be restricted to the guard lifetime
879 // which is not required, but this is safe because there is only one instance of the unerased type
880 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
881 self.inner.unerase(guard).id()
882 }
883
884 /// This gives access to the tree of Elements.
885 #[cfg(feature = "internal")]
886 #[doc(hidden)]
887 pub fn root_component(&self) -> Rc<i_slint_compiler::object_tree::Component> {
888 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
889 self.inner.unerase(guard).original.clone()
890 }
891
892 /// Return the `TypeLoader` used when parsing the code in the interpreter.
893 ///
894 /// WARNING: this is not part of the public API
895 #[cfg(feature = "highlight")]
896 pub fn type_loader(&self) -> std::rc::Rc<i_slint_compiler::typeloader::TypeLoader> {
897 let guard = unsafe { generativity::Guard::new(generativity::Id::new()) };
898 self.inner.unerase(guard).type_loader.get().unwrap().clone()
899 }
900}
901
902/// Print the diagnostics to stderr
903///
904/// The diagnostics are printed in the same style as rustc errors
905///
906/// This function is available when the `display-diagnostics` is enabled.
907#[cfg(feature = "display-diagnostics")]
908pub fn print_diagnostics(diagnostics: &[Diagnostic]) {
909 let mut build_diagnostics: BuildDiagnostics = i_slint_compiler::diagnostics::BuildDiagnostics::default();
910 for d: &Diagnostic in diagnostics {
911 build_diagnostics.push_compiler_error(d.clone())
912 }
913 build_diagnostics.print();
914}
915
916/// This represent an instance of a dynamic component
917///
918/// You can create an instance with the [`ComponentDefinition::create`] function.
919///
920/// Properties and callback can be accessed using the associated functions.
921///
922/// An instance can be put on screen with the [`ComponentInstance::run`] function.
923#[repr(C)]
924pub struct ComponentInstance {
925 inner: crate::dynamic_item_tree::DynamicComponentVRc,
926}
927
928impl ComponentInstance {
929 /// Return the [`ComponentDefinition`] that was used to create this instance.
930 pub fn definition(&self) -> ComponentDefinition {
931 generativity::make_guard!(guard);
932 ComponentDefinition { inner: self.inner.unerase(guard).description().into() }
933 }
934
935 /// Return the value for a public property of this component.
936 ///
937 /// ## Examples
938 ///
939 /// ```
940 /// # i_slint_backend_testing::init();
941 /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
942 /// let code = r#"
943 /// export component MyWin inherits Window {
944 /// in-out property <int> my_property: 42;
945 /// }
946 /// "#;
947 /// let mut compiler = ComponentCompiler::default();
948 /// let definition = spin_on::spin_on(
949 /// compiler.build_from_source(code.into(), Default::default()));
950 /// assert!(compiler.diagnostics().is_empty(), "{:?}", compiler.diagnostics());
951 /// let instance = definition.unwrap().create().unwrap();
952 /// assert_eq!(instance.get_property("my_property").unwrap(), Value::from(42));
953 /// ```
954 pub fn get_property(&self, name: &str) -> Result<Value, GetPropertyError> {
955 generativity::make_guard!(guard);
956 let comp = self.inner.unerase(guard);
957 let name = normalize_identifier(name);
958
959 if comp
960 .description()
961 .original
962 .root_element
963 .borrow()
964 .property_declarations
965 .get(name.as_ref())
966 .map_or(true, |d| !d.expose_in_public_api)
967 {
968 return Err(GetPropertyError::NoSuchProperty);
969 }
970
971 comp.description()
972 .get_property(comp.borrow(), &name)
973 .map_err(|()| GetPropertyError::NoSuchProperty)
974 }
975
976 /// Set the value for a public property of this component.
977 pub fn set_property(&self, name: &str, value: Value) -> Result<(), SetPropertyError> {
978 let name = normalize_identifier(name);
979 generativity::make_guard!(guard);
980 let comp = self.inner.unerase(guard);
981 let d = comp.description();
982 let elem = d.original.root_element.borrow();
983 let decl = elem
984 .property_declarations
985 .get(name.as_ref())
986 .ok_or(SetPropertyError::NoSuchProperty)?;
987
988 if !decl.expose_in_public_api {
989 return Err(SetPropertyError::NoSuchProperty);
990 } else if decl.visibility == i_slint_compiler::object_tree::PropertyVisibility::Output {
991 return Err(SetPropertyError::AccessDenied);
992 }
993
994 d.set_property(comp.borrow(), &name, value)
995 }
996
997 /// Set a handler for the callback with the given name. A callback with that
998 /// name must be defined in the document otherwise an error will be returned.
999 ///
1000 /// Note: Since the [`ComponentInstance`] holds the handler, the handler itself should not
1001 /// contain a strong reference to the instance. So if you need to capture the instance,
1002 /// you should use [`Self::as_weak`] to create a weak reference.
1003 ///
1004 /// ## Examples
1005 ///
1006 /// ```
1007 /// # i_slint_backend_testing::init();
1008 /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString, ComponentHandle};
1009 /// use core::convert::TryInto;
1010 /// let code = r#"
1011 /// component MyWin inherits Window {
1012 /// callback foo(int) -> int;
1013 /// in-out property <int> my_prop: 12;
1014 /// }
1015 /// "#;
1016 /// let definition = spin_on::spin_on(
1017 /// ComponentCompiler::default().build_from_source(code.into(), Default::default()));
1018 /// let instance = definition.unwrap().create().unwrap();
1019 ///
1020 /// let instance_weak = instance.as_weak();
1021 /// instance.set_callback("foo", move |args: &[Value]| -> Value {
1022 /// let arg: u32 = args[0].clone().try_into().unwrap();
1023 /// let my_prop = instance_weak.unwrap().get_property("my_prop").unwrap();
1024 /// let my_prop : u32 = my_prop.try_into().unwrap();
1025 /// Value::from(arg + my_prop)
1026 /// }).unwrap();
1027 ///
1028 /// let res = instance.invoke("foo", &[Value::from(500)]).unwrap();
1029 /// assert_eq!(res, Value::from(500+12));
1030 /// ```
1031 pub fn set_callback(
1032 &self,
1033 name: &str,
1034 callback: impl Fn(&[Value]) -> Value + 'static,
1035 ) -> Result<(), SetCallbackError> {
1036 generativity::make_guard!(guard);
1037 let comp = self.inner.unerase(guard);
1038 comp.description()
1039 .set_callback_handler(comp.borrow(), &normalize_identifier(name), Box::new(callback))
1040 .map_err(|()| SetCallbackError::NoSuchCallback)
1041 }
1042
1043 /// Call the given callback or function with the arguments
1044 ///
1045 /// ## Examples
1046 /// See the documentation of [`Self::set_callback`] for an example
1047 pub fn invoke(&self, name: &str, args: &[Value]) -> Result<Value, InvokeError> {
1048 generativity::make_guard!(guard);
1049 let comp = self.inner.unerase(guard);
1050 comp.description()
1051 .invoke(comp.borrow(), &normalize_identifier(name), args)
1052 .map_err(|()| InvokeError::NoSuchCallable)
1053 }
1054
1055 /// Return the value for a property within an exported global singleton used by this component.
1056 ///
1057 /// The `global` parameter is the exported name of the global singleton. The `property` argument
1058 /// is the name of the property
1059 ///
1060 /// ## Examples
1061 ///
1062 /// ```
1063 /// # i_slint_backend_testing::init();
1064 /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
1065 /// let code = r#"
1066 /// global Glob {
1067 /// in-out property <int> my_property: 42;
1068 /// }
1069 /// export { Glob as TheGlobal }
1070 /// component MyWin inherits Window {
1071 /// }
1072 /// "#;
1073 /// let mut compiler = ComponentCompiler::default();
1074 /// let definition = spin_on::spin_on(
1075 /// compiler.build_from_source(code.into(), Default::default()));
1076 /// assert!(compiler.diagnostics().is_empty(), "{:?}", compiler.diagnostics());
1077 /// let instance = definition.unwrap().create().unwrap();
1078 /// assert_eq!(instance.get_global_property("TheGlobal", "my_property").unwrap(), Value::from(42));
1079 /// ```
1080 pub fn get_global_property(
1081 &self,
1082 global: &str,
1083 property: &str,
1084 ) -> Result<Value, GetPropertyError> {
1085 generativity::make_guard!(guard);
1086 let comp = self.inner.unerase(guard);
1087 comp.description()
1088 .get_global(comp.borrow(), &normalize_identifier(global))
1089 .map_err(|()| GetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error?
1090 .as_ref()
1091 .get_property(&normalize_identifier(property))
1092 .map_err(|()| GetPropertyError::NoSuchProperty)
1093 }
1094
1095 /// Set the value for a property within an exported global singleton used by this component.
1096 pub fn set_global_property(
1097 &self,
1098 global: &str,
1099 property: &str,
1100 value: Value,
1101 ) -> Result<(), SetPropertyError> {
1102 generativity::make_guard!(guard);
1103 let comp = self.inner.unerase(guard);
1104 comp.description()
1105 .get_global(comp.borrow(), &normalize_identifier(global))
1106 .map_err(|()| SetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error?
1107 .as_ref()
1108 .set_property(&normalize_identifier(property), value)
1109 }
1110
1111 /// Set a handler for the callback in the exported global singleton. A callback with that
1112 /// name must be defined in the specified global and the global must be exported from the
1113 /// main document otherwise an error will be returned.
1114 ///
1115 /// ## Examples
1116 ///
1117 /// ```
1118 /// # i_slint_backend_testing::init();
1119 /// use slint_interpreter::{ComponentDefinition, ComponentCompiler, Value, SharedString};
1120 /// use core::convert::TryInto;
1121 /// let code = r#"
1122 /// export global Logic {
1123 /// pure callback to_uppercase(string) -> string;
1124 /// }
1125 /// component MyWin inherits Window {
1126 /// out property <string> hello: Logic.to_uppercase("world");
1127 /// }
1128 /// "#;
1129 /// let definition = spin_on::spin_on(
1130 /// ComponentCompiler::default().build_from_source(code.into(), Default::default()));
1131 /// let instance = definition.unwrap().create().unwrap();
1132 /// instance.set_global_callback("Logic", "to_uppercase", |args: &[Value]| -> Value {
1133 /// let arg: SharedString = args[0].clone().try_into().unwrap();
1134 /// Value::from(SharedString::from(arg.to_uppercase()))
1135 /// }).unwrap();
1136 ///
1137 /// let res = instance.get_property("hello").unwrap();
1138 /// assert_eq!(res, Value::from(SharedString::from("WORLD")));
1139 ///
1140 /// let abc = instance.invoke_global("Logic", "to_uppercase", &[
1141 /// SharedString::from("abc").into()
1142 /// ]).unwrap();
1143 /// assert_eq!(abc, Value::from(SharedString::from("ABC")));
1144 /// ```
1145 pub fn set_global_callback(
1146 &self,
1147 global: &str,
1148 name: &str,
1149 callback: impl Fn(&[Value]) -> Value + 'static,
1150 ) -> Result<(), SetCallbackError> {
1151 generativity::make_guard!(guard);
1152 let comp = self.inner.unerase(guard);
1153 comp.description()
1154 .get_global(comp.borrow(), &normalize_identifier(global))
1155 .map_err(|()| SetCallbackError::NoSuchCallback)? // FIXME: should there be a NoSuchGlobal error?
1156 .as_ref()
1157 .set_callback_handler(&normalize_identifier(name), Box::new(callback))
1158 .map_err(|()| SetCallbackError::NoSuchCallback)
1159 }
1160
1161 /// Call the given callback or function within a global singleton with the arguments
1162 ///
1163 /// ## Examples
1164 /// See the documentation of [`Self::set_global_callback`] for an example
1165 pub fn invoke_global(
1166 &self,
1167 global: &str,
1168 callable_name: &str,
1169 args: &[Value],
1170 ) -> Result<Value, InvokeError> {
1171 generativity::make_guard!(guard);
1172 let comp = self.inner.unerase(guard);
1173 let g = comp
1174 .description()
1175 .get_global(comp.borrow(), &normalize_identifier(global))
1176 .map_err(|()| InvokeError::NoSuchCallable)?; // FIXME: should there be a NoSuchGlobal error?
1177 let callable_name = normalize_identifier(callable_name);
1178 if matches!(
1179 comp.description()
1180 .original
1181 .root_element
1182 .borrow()
1183 .lookup_property(&callable_name)
1184 .property_type,
1185 LangType::Function { .. }
1186 ) {
1187 g.as_ref()
1188 .eval_function(&callable_name, args.to_vec())
1189 .map_err(|()| InvokeError::NoSuchCallable)
1190 } else {
1191 g.as_ref()
1192 .invoke_callback(&callable_name, args)
1193 .map_err(|()| InvokeError::NoSuchCallable)
1194 }
1195 }
1196
1197 /// Find all positions of the components which are pointed by a given source location.
1198 ///
1199 /// WARNING: this is not part of the public API
1200 #[cfg(feature = "highlight")]
1201 pub fn component_positions(
1202 &self,
1203 path: &Path,
1204 offset: u32,
1205 ) -> crate::highlight::ComponentPositions {
1206 crate::highlight::component_positions(&self.inner, path, offset)
1207 }
1208
1209 /// Find the position of the `element`.
1210 ///
1211 /// WARNING: this is not part of the public API
1212 #[cfg(feature = "highlight")]
1213 pub fn element_position(
1214 &self,
1215 element: &i_slint_compiler::object_tree::ElementRc,
1216 ) -> Vec<i_slint_core::lengths::LogicalRect> {
1217 crate::highlight::element_position(&self.inner, element)
1218 }
1219
1220 /// Find the the `element` that was defined at the text position.
1221 ///
1222 /// WARNING: this is not part of the public API
1223 #[cfg(feature = "highlight")]
1224 pub fn element_at_source_code_position(
1225 &self,
1226 path: &Path,
1227 offset: u32,
1228 ) -> Vec<i_slint_compiler::object_tree::ElementRc> {
1229 crate::highlight::element_at_source_code_position(&self.inner, path, offset)
1230 }
1231}
1232
1233impl ComponentHandle for ComponentInstance {
1234 type Inner = crate::dynamic_item_tree::ErasedItemTreeBox;
1235
1236 fn as_weak(&self) -> Weak<Self>
1237 where
1238 Self: Sized,
1239 {
1240 Weak::new(&self.inner)
1241 }
1242
1243 fn clone_strong(&self) -> Self {
1244 Self { inner: self.inner.clone() }
1245 }
1246
1247 fn from_inner(
1248 inner: vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, Self::Inner>,
1249 ) -> Self {
1250 Self { inner }
1251 }
1252
1253 fn show(&self) -> Result<(), PlatformError> {
1254 self.inner.window_adapter_ref()?.window().show()
1255 }
1256
1257 fn hide(&self) -> Result<(), PlatformError> {
1258 self.inner.window_adapter_ref()?.window().hide()
1259 }
1260
1261 fn run(&self) -> Result<(), PlatformError> {
1262 self.show()?;
1263 run_event_loop()?;
1264 self.hide()
1265 }
1266
1267 fn window(&self) -> &Window {
1268 self.inner.window_adapter_ref().unwrap().window()
1269 }
1270
1271 fn global<'a, T: Global<'a, Self>>(&'a self) -> T
1272 where
1273 Self: Sized,
1274 {
1275 unreachable!()
1276 }
1277}
1278
1279impl From<ComponentInstance>
1280 for vtable::VRc<i_slint_core::item_tree::ItemTreeVTable, ErasedItemTreeBox>
1281{
1282 fn from(value: ComponentInstance) -> Self {
1283 value.inner
1284 }
1285}
1286
1287/// Error returned by [`ComponentInstance::get_property`]
1288#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1289#[non_exhaustive]
1290pub enum GetPropertyError {
1291 /// There is no property with the given name
1292 #[error("no such property")]
1293 NoSuchProperty,
1294}
1295
1296/// Error returned by [`ComponentInstance::set_property`]
1297#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1298#[non_exhaustive]
1299pub enum SetPropertyError {
1300 /// There is no property with the given name.
1301 #[error("no such property")]
1302 NoSuchProperty,
1303 /// The property exists but does not have a type matching the dynamic value.
1304 ///
1305 /// This happens for example when assigning a source struct value to a target
1306 /// struct property, where the source doesn't have all the fields the target struct
1307 /// requires.
1308 #[error("wrong type")]
1309 WrongType,
1310 /// Attempt to set an output property.
1311 #[error("access denied")]
1312 AccessDenied,
1313}
1314
1315/// Error returned by [`ComponentInstance::set_callback`]
1316#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1317#[non_exhaustive]
1318pub enum SetCallbackError {
1319 /// There is no callback with the given name
1320 #[error("no such callback")]
1321 NoSuchCallback,
1322}
1323
1324/// Error returned by [`ComponentInstance::invoke`]
1325#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1326#[non_exhaustive]
1327pub enum InvokeError {
1328 /// There is no callback or function with the given name
1329 #[error("no such callback or function")]
1330 NoSuchCallable,
1331}
1332
1333/// Enters the main event loop. This is necessary in order to receive
1334/// events from the windowing system in order to render to the screen
1335/// and react to user input.
1336pub fn run_event_loop() -> Result<(), PlatformError> {
1337 i_slint_backend_selector::with_platform(|b: &dyn Platform| b.run_event_loop())
1338}
1339
1340#[cfg(all(feature = "internal", target_arch = "wasm32"))]
1341/// Spawn the event loop.
1342///
1343/// Like [`run_event_loop()`], but returns immediately as the loop is running within
1344/// the browser's runtime
1345pub fn spawn_event_loop() -> Result<(), PlatformError> {
1346 i_slint_backend_selector::with_platform(|_| i_slint_backend_winit::spawn_event_loop())
1347}
1348
1349/// This module contains a few functions used by the tests
1350#[doc(hidden)]
1351pub mod testing {
1352 use super::ComponentHandle;
1353 use i_slint_core::window::WindowInner;
1354
1355 /// Wrapper around [`i_slint_core::tests::slint_send_mouse_click`]
1356 pub fn send_mouse_click(comp: &super::ComponentInstance, x: f32, y: f32) {
1357 i_slint_core::tests::slint_send_mouse_click(
1358 x,
1359 y,
1360 &WindowInner::from_pub(comp.window()).window_adapter(),
1361 );
1362 }
1363
1364 /// Wrapper around [`i_slint_core::tests::slint_send_keyboard_char`]
1365 pub fn send_keyboard_char(
1366 comp: &super::ComponentInstance,
1367 string: i_slint_core::SharedString,
1368 pressed: bool,
1369 ) {
1370 i_slint_core::tests::slint_send_keyboard_char(
1371 &string,
1372 pressed,
1373 &WindowInner::from_pub(comp.window()).window_adapter(),
1374 );
1375 }
1376 /// Wrapper around [`i_slint_core::tests::send_keyboard_string_sequence`]
1377 pub fn send_keyboard_string_sequence(
1378 comp: &super::ComponentInstance,
1379 string: i_slint_core::SharedString,
1380 ) {
1381 i_slint_core::tests::send_keyboard_string_sequence(
1382 &string,
1383 &WindowInner::from_pub(comp.window()).window_adapter(),
1384 );
1385 }
1386}
1387
1388#[test]
1389fn component_definition_properties() {
1390 i_slint_backend_testing::init();
1391 let mut compiler = ComponentCompiler::default();
1392 compiler.set_style("fluent".into());
1393 let comp_def = spin_on::spin_on(
1394 compiler.build_from_source(
1395 r#"
1396 export component Dummy {
1397 in-out property <string> test;
1398 in-out property <int> underscores-and-dashes_preserved: 44;
1399 callback hello;
1400 }"#
1401 .into(),
1402 "".into(),
1403 ),
1404 )
1405 .unwrap();
1406
1407 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1408
1409 assert_eq!(props.len(), 2);
1410 assert_eq!(props[0].0, "test");
1411 assert_eq!(props[0].1, ValueType::String);
1412 assert_eq!(props[1].0, "underscores-and-dashes_preserved");
1413 assert_eq!(props[1].1, ValueType::Number);
1414
1415 let instance = comp_def.create().unwrap();
1416 assert_eq!(instance.get_property("underscores_and-dashes-preserved"), Ok(Value::Number(44.)));
1417 assert_eq!(
1418 instance.get_property("underscoresanddashespreserved"),
1419 Err(GetPropertyError::NoSuchProperty)
1420 );
1421 assert_eq!(
1422 instance.set_property("underscores-and_dashes-preserved", Value::Number(88.)),
1423 Ok(())
1424 );
1425 assert_eq!(
1426 instance.set_property("underscoresanddashespreserved", Value::Number(99.)),
1427 Err(SetPropertyError::NoSuchProperty)
1428 );
1429 assert_eq!(
1430 instance.set_property("underscores-and_dashes-preserved", Value::String("99".into())),
1431 Err(SetPropertyError::WrongType)
1432 );
1433 assert_eq!(instance.get_property("underscores-and-dashes-preserved"), Ok(Value::Number(88.)));
1434}
1435
1436#[test]
1437fn component_definition_properties2() {
1438 i_slint_backend_testing::init();
1439 let mut compiler = ComponentCompiler::default();
1440 compiler.set_style("fluent".into());
1441 let comp_def = spin_on::spin_on(
1442 compiler.build_from_source(
1443 r#"
1444 export component Dummy {
1445 in-out property <string> sub-text <=> sub.text;
1446 sub := Text { property <int> private-not-exported; }
1447 out property <string> xreadonly: "the value";
1448 private property <string> xx: sub.text;
1449 callback hello;
1450 }"#
1451 .into(),
1452 "".into(),
1453 ),
1454 )
1455 .unwrap();
1456
1457 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1458
1459 assert_eq!(props.len(), 2);
1460 assert_eq!(props[0].0, "sub-text");
1461 assert_eq!(props[0].1, ValueType::String);
1462 assert_eq!(props[1].0, "xreadonly");
1463
1464 let callbacks = comp_def.callbacks().collect::<Vec<_>>();
1465 assert_eq!(callbacks.len(), 1);
1466 assert_eq!(callbacks[0], "hello");
1467
1468 let instance = comp_def.create().unwrap();
1469 assert_eq!(
1470 instance.set_property("xreadonly", SharedString::from("XXX").into()),
1471 Err(SetPropertyError::AccessDenied)
1472 );
1473 assert_eq!(instance.get_property("xreadonly"), Ok(Value::String("the value".into())));
1474 assert_eq!(
1475 instance.set_property("xx", SharedString::from("XXX").into()),
1476 Err(SetPropertyError::NoSuchProperty)
1477 );
1478 assert_eq!(
1479 instance.set_property("background", Value::default()),
1480 Err(SetPropertyError::NoSuchProperty)
1481 );
1482
1483 assert_eq!(instance.get_property("background"), Err(GetPropertyError::NoSuchProperty));
1484 assert_eq!(instance.get_property("xx"), Err(GetPropertyError::NoSuchProperty));
1485}
1486
1487#[test]
1488fn globals() {
1489 i_slint_backend_testing::init();
1490 let mut compiler = ComponentCompiler::default();
1491 compiler.set_style("fluent".into());
1492 let definition = spin_on::spin_on(
1493 compiler.build_from_source(
1494 r#"
1495 export global My-Super_Global {
1496 in-out property <int> the-property : 21;
1497 callback my-callback();
1498 }
1499 export { My-Super_Global as AliasedGlobal }
1500 export component Dummy {
1501 }"#
1502 .into(),
1503 "".into(),
1504 ),
1505 )
1506 .unwrap();
1507
1508 assert_eq!(definition.globals().collect::<Vec<_>>(), vec!["My-Super_Global", "AliasedGlobal"]);
1509
1510 assert!(definition.global_properties("not-there").is_none());
1511 {
1512 let expected_properties = vec![("the-property".to_string(), ValueType::Number)];
1513 let expected_callbacks = vec!["my-callback".to_string()];
1514
1515 let assert_properties_and_callbacks = |global_name| {
1516 assert_eq!(
1517 definition
1518 .global_properties(global_name)
1519 .map(|props| props.collect::<Vec<_>>())
1520 .as_ref(),
1521 Some(&expected_properties)
1522 );
1523 assert_eq!(
1524 definition
1525 .global_callbacks(global_name)
1526 .map(|props| props.collect::<Vec<_>>())
1527 .as_ref(),
1528 Some(&expected_callbacks)
1529 );
1530 };
1531
1532 assert_properties_and_callbacks("My-Super-Global");
1533 assert_properties_and_callbacks("My_Super-Global");
1534 assert_properties_and_callbacks("AliasedGlobal");
1535 }
1536
1537 let instance = definition.create().unwrap();
1538 assert_eq!(
1539 instance.set_global_property("My_Super-Global", "the_property", Value::Number(44.)),
1540 Ok(())
1541 );
1542 assert_eq!(
1543 instance.set_global_property("AliasedGlobal", "the_property", Value::Number(44.)),
1544 Ok(())
1545 );
1546 assert_eq!(
1547 instance.set_global_property("DontExist", "the-property", Value::Number(88.)),
1548 Err(SetPropertyError::NoSuchProperty)
1549 );
1550
1551 assert_eq!(
1552 instance.set_global_property("My_Super-Global", "theproperty", Value::Number(88.)),
1553 Err(SetPropertyError::NoSuchProperty)
1554 );
1555 assert_eq!(
1556 instance.set_global_property("AliasedGlobal", "theproperty", Value::Number(88.)),
1557 Err(SetPropertyError::NoSuchProperty)
1558 );
1559 assert_eq!(
1560 instance.set_global_property("My_Super-Global", "the_property", Value::String("88".into())),
1561 Err(SetPropertyError::WrongType)
1562 );
1563 assert_eq!(
1564 instance.get_global_property("My-Super_Global", "yoyo"),
1565 Err(GetPropertyError::NoSuchProperty)
1566 );
1567 assert_eq!(
1568 instance.get_global_property("My-Super_Global", "the-property"),
1569 Ok(Value::Number(44.))
1570 );
1571
1572 assert_eq!(
1573 instance.set_property("the-property", Value::Void),
1574 Err(SetPropertyError::NoSuchProperty)
1575 );
1576 assert_eq!(instance.get_property("the-property"), Err(GetPropertyError::NoSuchProperty));
1577
1578 assert_eq!(
1579 instance.set_global_callback("DontExist", "the-property", |_| panic!()),
1580 Err(SetCallbackError::NoSuchCallback)
1581 );
1582 assert_eq!(
1583 instance.set_global_callback("My_Super_Global", "the-property", |_| panic!()),
1584 Err(SetCallbackError::NoSuchCallback)
1585 );
1586 assert_eq!(
1587 instance.set_global_callback("My_Super_Global", "yoyo", |_| panic!()),
1588 Err(SetCallbackError::NoSuchCallback)
1589 );
1590
1591 assert_eq!(
1592 instance.invoke_global("DontExist", "the-property", &[]),
1593 Err(InvokeError::NoSuchCallable)
1594 );
1595 assert_eq!(
1596 instance.invoke_global("My_Super_Global", "the-property", &[]),
1597 Err(InvokeError::NoSuchCallable)
1598 );
1599 assert_eq!(
1600 instance.invoke_global("My_Super_Global", "yoyo", &[]),
1601 Err(InvokeError::NoSuchCallable)
1602 );
1603}
1604
1605#[test]
1606fn call_functions() {
1607 i_slint_backend_testing::init();
1608 let mut compiler = ComponentCompiler::default();
1609 compiler.set_style("fluent".into());
1610 let definition = spin_on::spin_on(
1611 compiler.build_from_source(
1612 r#"
1613 export global Gl {
1614 out property<string> q;
1615 public function foo-bar(a-a: string, b-b:int) -> string {
1616 q = a-a;
1617 return a-a + b-b;
1618 }
1619 }
1620 export Test := Rectangle {
1621 out property<int> p;
1622 public function foo-bar(a: int, b:int) -> int {
1623 p = a;
1624 return a + b;
1625 }
1626 }"#
1627 .into(),
1628 "".into(),
1629 ),
1630 );
1631 let instance = definition.unwrap().create().unwrap();
1632
1633 assert_eq!(
1634 instance.invoke("foo_bar", &[Value::Number(3.), Value::Number(4.)]),
1635 Ok(Value::Number(7.))
1636 );
1637 assert_eq!(instance.invoke("p", &[]), Err(InvokeError::NoSuchCallable));
1638 assert_eq!(instance.get_property("p"), Ok(Value::Number(3.)));
1639
1640 assert_eq!(
1641 instance.invoke_global(
1642 "Gl",
1643 "foo_bar",
1644 &[Value::String("Hello".into()), Value::Number(10.)]
1645 ),
1646 Ok(Value::String("Hello10".into()))
1647 );
1648 assert_eq!(instance.get_global_property("Gl", "q"), Ok(Value::String("Hello".into())));
1649}
1650
1651#[test]
1652fn component_definition_struct_properties() {
1653 i_slint_backend_testing::init();
1654 let mut compiler = ComponentCompiler::default();
1655 compiler.set_style("fluent".into());
1656 let comp_def = spin_on::spin_on(
1657 compiler.build_from_source(
1658 r#"
1659 export struct Settings {
1660 string_value: string,
1661 }
1662 export Dummy := Rectangle {
1663 property <Settings> test;
1664 }"#
1665 .into(),
1666 "".into(),
1667 ),
1668 )
1669 .unwrap();
1670
1671 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1672
1673 assert_eq!(props.len(), 1);
1674 assert_eq!(props[0].0, "test");
1675 assert_eq!(props[0].1, ValueType::Struct);
1676
1677 let instance = comp_def.create().unwrap();
1678
1679 let valid_struct: Struct =
1680 [("string_value".to_string(), Value::String("hello".into()))].iter().cloned().collect();
1681
1682 assert_eq!(instance.set_property("test", Value::Struct(valid_struct.clone())), Ok(()));
1683 assert_eq!(instance.get_property("test").unwrap().value_type(), ValueType::Struct);
1684
1685 assert_eq!(instance.set_property("test", Value::Number(42.)), Err(SetPropertyError::WrongType));
1686
1687 let mut invalid_struct = valid_struct.clone();
1688 invalid_struct.set_field("other".into(), Value::Number(44.));
1689 assert_eq!(
1690 instance.set_property("test", Value::Struct(invalid_struct)),
1691 Err(SetPropertyError::WrongType)
1692 );
1693 let mut invalid_struct = valid_struct;
1694 invalid_struct.set_field("string_value".into(), Value::Number(44.));
1695 assert_eq!(
1696 instance.set_property("test", Value::Struct(invalid_struct)),
1697 Err(SetPropertyError::WrongType)
1698 );
1699}
1700
1701#[test]
1702fn component_definition_model_properties() {
1703 use i_slint_core::model::*;
1704 i_slint_backend_testing::init();
1705 let mut compiler = ComponentCompiler::default();
1706 compiler.set_style("fluent".into());
1707 let comp_def = spin_on::spin_on(compiler.build_from_source(
1708 "export Dummy := Rectangle { property <[int]> prop: [42, 12]; }".into(),
1709 "".into(),
1710 ))
1711 .unwrap();
1712
1713 let props = comp_def.properties().collect::<Vec<(_, _)>>();
1714 assert_eq!(props.len(), 1);
1715 assert_eq!(props[0].0, "prop");
1716 assert_eq!(props[0].1, ValueType::Model);
1717
1718 let instance = comp_def.create().unwrap();
1719
1720 let int_model =
1721 Value::Model([Value::Number(14.), Value::Number(15.), Value::Number(16.)].into());
1722 let empty_model = Value::Model(ModelRc::new(VecModel::<Value>::default()));
1723 let model_with_string = Value::Model(VecModel::from_slice(&[
1724 Value::Number(1000.),
1725 Value::String("foo".into()),
1726 Value::Number(1111.),
1727 ]));
1728
1729 #[track_caller]
1730 fn check_model(val: Value, r: &[f64]) {
1731 if let Value::Model(m) = val {
1732 assert_eq!(r.len(), m.row_count());
1733 for (i, v) in r.iter().enumerate() {
1734 assert_eq!(m.row_data(i).unwrap(), Value::Number(*v));
1735 }
1736 } else {
1737 panic!("{:?} not a model", val);
1738 }
1739 }
1740
1741 assert_eq!(instance.get_property("prop").unwrap().value_type(), ValueType::Model);
1742 check_model(instance.get_property("prop").unwrap(), &[42., 12.]);
1743
1744 instance.set_property("prop", int_model).unwrap();
1745 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
1746
1747 assert_eq!(instance.set_property("prop", Value::Number(42.)), Err(SetPropertyError::WrongType));
1748 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
1749 assert_eq!(instance.set_property("prop", model_with_string), Err(SetPropertyError::WrongType));
1750 check_model(instance.get_property("prop").unwrap(), &[14., 15., 16.]);
1751
1752 assert_eq!(instance.set_property("prop", empty_model), Ok(()));
1753 check_model(instance.get_property("prop").unwrap(), &[]);
1754}
1755
1756#[test]
1757fn lang_type_to_value_type() {
1758 use std::collections::BTreeMap;
1759
1760 assert_eq!(ValueType::from(LangType::Void), ValueType::Void);
1761 assert_eq!(ValueType::from(LangType::Float32), ValueType::Number);
1762 assert_eq!(ValueType::from(LangType::Int32), ValueType::Number);
1763 assert_eq!(ValueType::from(LangType::Duration), ValueType::Number);
1764 assert_eq!(ValueType::from(LangType::Angle), ValueType::Number);
1765 assert_eq!(ValueType::from(LangType::PhysicalLength), ValueType::Number);
1766 assert_eq!(ValueType::from(LangType::LogicalLength), ValueType::Number);
1767 assert_eq!(ValueType::from(LangType::Percent), ValueType::Number);
1768 assert_eq!(ValueType::from(LangType::UnitProduct(vec![])), ValueType::Number);
1769 assert_eq!(ValueType::from(LangType::String), ValueType::String);
1770 assert_eq!(ValueType::from(LangType::Color), ValueType::Brush);
1771 assert_eq!(ValueType::from(LangType::Brush), ValueType::Brush);
1772 assert_eq!(ValueType::from(LangType::Array(Box::new(LangType::Void))), ValueType::Model);
1773 assert_eq!(ValueType::from(LangType::Bool), ValueType::Bool);
1774 assert_eq!(
1775 ValueType::from(LangType::Struct {
1776 fields: BTreeMap::default(),
1777 name: None,
1778 node: None,
1779 rust_attributes: None
1780 }),
1781 ValueType::Struct
1782 );
1783 assert_eq!(ValueType::from(LangType::Image), ValueType::Image);
1784}
1785
1786#[cfg(feature = "ffi")]
1787#[allow(missing_docs)]
1788#[path = "ffi.rs"]
1789pub(crate) mod ffi;
1790