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 | |
4 | use i_slint_compiler::diagnostics::SourceFileVersion; |
5 | use i_slint_compiler::langtype::Type as LangType; |
6 | use i_slint_core::component_factory::ComponentFactory; |
7 | #[cfg (feature = "internal" )] |
8 | use i_slint_core::component_factory::FactoryContext; |
9 | use i_slint_core::model::{Model, ModelRc}; |
10 | #[cfg (feature = "internal" )] |
11 | use i_slint_core::window::WindowInner; |
12 | use i_slint_core::{PathData, SharedVector}; |
13 | use std::borrow::Cow; |
14 | use std::collections::HashMap; |
15 | use std::path::{Path, PathBuf}; |
16 | use std::rc::Rc; |
17 | |
18 | #[doc (inline)] |
19 | pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel}; |
20 | |
21 | pub use i_slint_core::api::*; |
22 | // keep in sync with api/rs/slint/lib.rs |
23 | pub use i_slint_core::graphics::{ |
24 | Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer, |
25 | }; |
26 | use i_slint_core::items::*; |
27 | |
28 | use crate::dynamic_item_tree::ErasedItemTreeBox; |
29 | #[cfg (any(feature = "internal" , target_arch = "wasm32" ))] |
30 | use 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 ] |
37 | pub 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 | |
59 | impl 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)] |
97 | pub 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 | |
133 | impl 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 | |
150 | impl 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 | |
180 | impl 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!`] |
212 | macro_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 | } |
232 | declare_value_conversion!(Number => [u32, u64, i32, i64, f32, f64, usize, isize] ); |
233 | declare_value_conversion!(String => [SharedString] ); |
234 | declare_value_conversion!(Bool => [bool] ); |
235 | declare_value_conversion!(Image => [Image] ); |
236 | declare_value_conversion!(Struct => [Struct] ); |
237 | declare_value_conversion!(Brush => [Brush] ); |
238 | declare_value_conversion!(PathData => [PathData]); |
239 | declare_value_conversion!(EasingCurve => [i_slint_core::animations::EasingCurve]); |
240 | declare_value_conversion!(LayoutCache => [SharedVector<f32>] ); |
241 | declare_value_conversion!(ComponentFactory => [ComponentFactory] ); |
242 | |
243 | /// Implement From / TryFrom for Value that convert a `struct` to/from `Value::Struct` |
244 | macro_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 | |
313 | macro_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 | |
325 | declare_value_struct_conversion!(struct i_slint_core::layout::LayoutInfo { min, max, min_percent, max_percent, preferred, stretch }); |
326 | declare_value_struct_conversion!(struct i_slint_core::graphics::Point { x, y, ..Default::default()}); |
327 | |
328 | i_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) |
334 | macro_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 | |
369 | i_slint_common::for_each_enums!(declare_value_enum_conversion); |
370 | |
371 | impl 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 | } |
376 | impl 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 | |
386 | impl From<()> for Value { |
387 | #[inline ] |
388 | fn from(_: ()) -> Self { |
389 | Value::Void |
390 | } |
391 | } |
392 | impl TryFrom<Value> for () { |
393 | type Error = (); |
394 | #[inline ] |
395 | fn try_from(_: Value) -> Result<(), Self::Error> { |
396 | Ok(()) |
397 | } |
398 | } |
399 | |
400 | impl From<Color> for Value { |
401 | #[inline ] |
402 | fn from(c: Color) -> Self { |
403 | Value::Brush(Brush::SolidColor(c)) |
404 | } |
405 | } |
406 | impl 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 | |
417 | impl 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 | } |
423 | impl 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 |
435 | pub(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)] |
465 | pub struct Struct(HashMap<String, Value>); |
466 | impl 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 | |
486 | impl 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. |
498 | pub struct ComponentCompiler { |
499 | config: i_slint_compiler::CompilerConfiguration, |
500 | diagnostics: Vec<Diagnostic>, |
501 | } |
502 | |
503 | impl 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 | |
514 | impl 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)] |
715 | pub struct ComponentDefinition { |
716 | inner: crate::dynamic_item_tree::ErasedItemTreeDescription, |
717 | } |
718 | |
719 | impl 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" )] |
908 | pub 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)] |
924 | pub struct ComponentInstance { |
925 | inner: crate::dynamic_item_tree::DynamicComponentVRc, |
926 | } |
927 | |
928 | impl 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 | |
1233 | impl 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 | |
1279 | impl 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 ] |
1290 | pub 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 ] |
1299 | pub 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 ] |
1318 | pub 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 ] |
1327 | pub 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. |
1336 | pub 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 |
1345 | pub 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)] |
1351 | pub 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 ] |
1389 | fn 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 ] |
1437 | fn 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 ] |
1488 | fn 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 ] |
1606 | fn 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 ] |
1652 | fn 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 ] |
1702 | fn 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 ] |
1757 | fn 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" ] |
1789 | pub(crate) mod ffi; |
1790 | |