Skip to main content

slint_interpreter/
component.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Internal compiled-component / running-instance types used by
5//! [`crate::api::ComponentDefinition`] and [`crate::api::ComponentInstance`].
6//! The public API wraps these thin structs so downstream callers never
7//! see the compilation-unit surface directly.
8
9use crate::instance::Instance;
10use crate::public_api;
11use crate::{AnimationMode, Value};
12use i_slint_compiler::expression_tree::BuiltinFunction;
13use i_slint_compiler::langtype::Type as LangType;
14use i_slint_compiler::llr::{CompilationUnit, Expression, GlobalComponent, PublicComponentIdx};
15use i_slint_compiler::object_tree::PropertyVisibility;
16use i_slint_compiler::parser::normalize_identifier;
17use i_slint_compiler::source_path::SourcePath;
18use i_slint_core::item_tree::ItemTreeVTable;
19use smol_str::SmolStr;
20use std::rc::Rc;
21use vtable::VRc;
22
23/// Pair of `TypeLoader`s retained alongside a compiled component for
24/// internal tooling (highlight, live preview, LSP).
25///
26/// `type_loader` holds the post-pass state — the compiler's lowered object
27/// tree, which `highlight.rs` walks to resolve elements to runtime items.
28/// `raw_type_loader` is a snapshot taken *before* most passes run, which
29/// the LSP hands to `common::DocumentCache::new_from_raw_parts` so its
30/// panels see the tree as the user wrote it. Neither can be derived from
31/// the other; passes are destructive.
32#[derive(Clone, Default)]
33pub struct TypeLoaders {
34    #[cfg_attr(not(any(feature = "internal", feature = "internal-highlight")), allow(dead_code))]
35    pub type_loader: Option<std::rc::Rc<i_slint_compiler::typeloader::TypeLoader>>,
36    #[cfg_attr(not(feature = "internal-highlight"), allow(dead_code))]
37    pub raw_type_loader: Option<std::rc::Rc<i_slint_compiler::typeloader::TypeLoader>>,
38    /// The object-tree component of each public component, indexed like
39    /// `CompilationUnit::public_components`. Highlighting and the LSP
40    /// resolve elements against the exact component the definition was
41    /// built from — a name lookup could hit a same-named component from
42    /// another document.
43    pub originals: std::rc::Rc<
44        typed_index_collections::TiVec<
45            PublicComponentIdx,
46            std::rc::Rc<i_slint_compiler::object_tree::Component>,
47        >,
48    >,
49}
50
51/// Compiled component, one per exported public component in the
52/// source file. Produced by [`build_from_source`] and held behind
53/// [`crate::api::ComponentDefinition`].
54#[derive(Clone)]
55pub struct ComponentDefinitionInner {
56    pub compilation_unit: Rc<CompilationUnit>,
57    pub public_index: PublicComponentIdx,
58    /// `None` on both sides when the definition comes from a running
59    /// instance without `TypeLoader` references.
60    pub type_loaders: TypeLoaders,
61}
62
63impl ComponentDefinitionInner {
64    pub fn name(&self) -> &str {
65        self.public().name.as_str()
66    }
67
68    /// Instantiate the component, reusing the given `WindowAdapter` instead
69    /// of creating a fresh one via the backend selector.
70    pub fn create_with_existing_window(
71        &self,
72        window_adapter: i_slint_core::window::WindowAdapterRc,
73    ) -> ComponentInstanceInner {
74        let vrc = Instance::new_with_window(
75            self.compilation_unit.clone(),
76            self.public_index,
77            window_adapter,
78            self.type_loaders.clone(),
79        );
80        ComponentInstanceInner(vrc)
81    }
82
83    /// Instantiate the component with `context` instead of the thread's.
84    pub fn create_with_context(
85        &self,
86        context: i_slint_core::SlintContext,
87    ) -> ComponentInstanceInner {
88        let vrc = Instance::new_with_context(
89            self.compilation_unit.clone(),
90            self.public_index,
91            self.type_loaders.clone(),
92            context,
93        );
94        ComponentInstanceInner(vrc)
95    }
96
97    #[cfg(feature = "internal")]
98    pub fn create_detached_with_existing_window(
99        &self,
100        window_adapter: i_slint_core::window::WindowAdapterRc,
101    ) -> ComponentInstanceInner {
102        let vrc = Instance::new_detached_with_window(
103            self.compilation_unit.clone(),
104            self.public_index,
105            window_adapter,
106            self.type_loaders.clone(),
107        );
108        ComponentInstanceInner(vrc)
109    }
110
111    /// Instantiate the component and embed it at `parent_item_tree_index`
112    /// in the given outer item tree. Used by the `ComponentFactory` path
113    /// to embed an interpreter-built component inside a natively compiled
114    /// one.
115    pub fn create_embedded(
116        &self,
117        parent: vtable::VWeak<ItemTreeVTable>,
118        parent_item_tree_index: u32,
119    ) -> ComponentInstanceInner {
120        let vrc = Instance::new_embedded(
121            self.compilation_unit.clone(),
122            self.public_index,
123            self.type_loaders.clone(),
124            parent,
125            parent_item_tree_index,
126        );
127        ComponentInstanceInner(vrc)
128    }
129
130    fn public(&self) -> &i_slint_compiler::llr::PublicComponent {
131        &self.compilation_unit.public_components[self.public_index]
132    }
133
134    /// Whether the root inherits `Window` or a non-windowed type such as
135    /// `SystemTrayIcon`.
136    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
137    pub fn top_level_type(&self) -> i_slint_compiler::llr::TopLevelComponentType {
138        self.public().top_level_type
139    }
140
141    fn properties_with_info(
142        &self,
143    ) -> impl Iterator<Item = (SmolStr, LangType, PropertyVisibility)> + '_ {
144        public_properties_info(&self.public().public_properties)
145    }
146
147    /// Iterator of `(name, type, visibility)` for every property, callback and
148    /// function declared on this component. Exposed through the `internal`
149    /// feature; the public `ComponentDefinition::properties()` / `callbacks()`
150    /// / `functions()` helpers filter on top of it.
151    #[cfg_attr(not(feature = "internal"), allow(dead_code))]
152    pub fn properties_and_callbacks(
153        &self,
154    ) -> impl Iterator<Item = (SmolStr, LangType, PropertyVisibility)> + '_ {
155        self.properties_with_info()
156    }
157
158    /// Iterator of `(name, type)` limited to property-typed declarations
159    /// (excludes callbacks and functions).
160    pub fn properties(&self) -> impl Iterator<Item = (SmolStr, LangType)> + '_ {
161        self.properties_with_info()
162            .filter(|(_, ty, _)| ty.is_property_type())
163            .map(|(n, ty, _)| (n, ty))
164    }
165
166    pub fn callbacks(&self) -> impl Iterator<Item = SmolStr> + '_ {
167        self.properties_with_info()
168            .filter(|(_, ty, _)| matches!(ty, LangType::Callback(_)))
169            .map(|(n, _, _)| n)
170    }
171
172    pub fn functions(&self) -> impl Iterator<Item = SmolStr> + '_ {
173        self.properties_with_info()
174            .filter(|(_, ty, _)| matches!(ty, LangType::Function(_)))
175            .map(|(n, _, _)| n)
176    }
177
178    /// Names of every exported global declared by the compilation unit,
179    /// listing aliases before the canonical component name.
180    pub fn globals(&self) -> impl Iterator<Item = SmolStr> + '_ {
181        self.compilation_unit
182            .globals
183            .iter()
184            .filter(|g| visible_in_public_api(g))
185            .flat_map(|g| g.aliases.iter().cloned().chain(std::iter::once(g.name.clone())))
186    }
187
188    fn global_by_name(&self, name: &str) -> Option<&GlobalComponent> {
189        // Names on `GlobalComponent` preserve whatever form the compiler
190        // stored (often source-form with dashes), so normalize both sides.
191        let needle = normalize_identifier(name);
192        self.compilation_unit.globals.iter().filter(|g| visible_in_public_api(g)).find(|g| {
193            normalize_identifier(&g.name) == needle
194                || g.aliases.iter().any(|a| normalize_identifier(a) == needle)
195        })
196    }
197
198    pub fn global_properties_and_callbacks(
199        &self,
200        name: &str,
201    ) -> Option<impl Iterator<Item = (SmolStr, LangType, PropertyVisibility)> + '_> {
202        self.global_by_name(name).map(|g| public_properties_info(&g.public_properties))
203    }
204
205    pub fn global_properties(
206        &self,
207        name: &str,
208    ) -> Option<impl Iterator<Item = (SmolStr, LangType)> + '_> {
209        self.global_properties_and_callbacks(name)
210            .map(|it| it.filter(|(_, ty, _)| ty.is_property_type()).map(|(n, ty, _)| (n, ty)))
211    }
212
213    pub fn global_callbacks(&self, name: &str) -> Option<impl Iterator<Item = SmolStr> + '_> {
214        self.global_properties_and_callbacks(name).map(|it| {
215            it.filter(|(_, ty, _)| matches!(ty, LangType::Callback(_))).map(|(n, _, _)| n)
216        })
217    }
218
219    pub fn global_functions(&self, name: &str) -> Option<impl Iterator<Item = SmolStr> + '_> {
220        self.global_properties_and_callbacks(name).map(|it| {
221            it.filter(|(_, ty, _)| matches!(ty, LangType::Function(_))).map(|(n, _, _)| n)
222        })
223    }
224}
225
226fn public_properties_info<'a>(
227    public_properties: &'a i_slint_compiler::llr::PublicProperties,
228) -> impl Iterator<Item = (SmolStr, LangType, PropertyVisibility)> + 'a {
229    // Return the source-form identifier (dashes preserved) so the
230    // public API matches the names as written in the `.slint` file.
231    public_properties.values().map(|p| (p.display_name.clone(), p.ty.clone(), p.visibility))
232}
233
234fn visible_in_public_api(g: &GlobalComponent) -> bool {
235    // A builtin global has no public surface of its own in the API.
236    g.exported && !g.is_builtin
237}
238
239/// Live instance of a compiled component.
240///
241/// `repr(transparent)` so the C++ side can treat the `#[repr(C)]`
242/// `ComponentInstance` wrapping this as the `VRc` itself.
243#[repr(transparent)]
244pub struct ComponentInstanceInner(pub VRc<ItemTreeVTable, Instance>);
245
246impl Clone for ComponentInstanceInner {
247    fn clone(&self) -> Self {
248        Self(self.0.clone())
249    }
250}
251
252impl ComponentInstanceInner {
253    /// Access the underlying vtable VRc so host code can downgrade to a weak
254    /// reference or forward it to the window adapter.
255    pub fn vrc(&self) -> &VRc<ItemTreeVTable, Instance> {
256        &self.0
257    }
258
259    pub fn get_property(&self, name: &str) -> Option<Value> {
260        public_api::get(&self.0, name)
261    }
262
263    pub fn set_property(
264        &self,
265        name: &str,
266        value: Value,
267    ) -> Result<(), crate::api::SetPropertyError> {
268        public_api::set(&self.0, name, value)
269    }
270
271    pub fn invoke(&self, name: &str, args: &[Value]) -> Option<Value> {
272        public_api::invoke(&self.0, name, args)
273    }
274
275    pub fn set_callback(
276        &self,
277        name: &str,
278        handler: impl Fn(&[Value]) -> Value + 'static,
279    ) -> Result<(), ()> {
280        public_api::set_callback(&self.0, name, Box::new(handler))
281    }
282
283    pub fn get_global_property(&self, global: &str, property: &str) -> Option<Value> {
284        public_api::get_global(&self.0, global, property)
285    }
286
287    pub fn set_global_property(
288        &self,
289        global: &str,
290        property: &str,
291        value: Value,
292    ) -> Result<(), crate::api::SetPropertyError> {
293        public_api::set_global(&self.0, global, property, value)
294    }
295
296    pub fn set_global_callback(
297        &self,
298        global: &str,
299        name: &str,
300        handler: impl Fn(&[Value]) -> Value + 'static,
301    ) -> Result<(), ()> {
302        public_api::set_global_callback(&self.0, global, name, Box::new(handler))
303    }
304
305    pub fn invoke_global(&self, global: &str, name: &str, args: &[Value]) -> Option<Value> {
306        public_api::invoke_global(&self.0, global, name, args)
307    }
308
309    /// Return a borrowed reference to the window adapter, creating one
310    /// through the backend selector if necessary. The returned reference
311    /// lives as long as the instance.
312    pub fn window_adapter_ref(
313        &self,
314    ) -> Result<&i_slint_core::window::WindowAdapterRc, i_slint_core::api::PlatformError> {
315        self.0.try_window_adapter()?;
316        Ok(self.0.window_adapter.get().expect("window_adapter just initialized above"))
317    }
318
319    /// Whether the root inherits `Window` or a non-windowed type such as
320    /// `SystemTrayIcon`.
321    pub fn top_level_type(&self) -> i_slint_compiler::llr::TopLevelComponentType {
322        let unit = &self.0.root_sub_component.compilation_unit;
323        match self.0.public_component_index {
324            Some(idx) => unit.public_components[idx].top_level_type,
325            None => i_slint_compiler::llr::TopLevelComponentType::Window,
326        }
327    }
328
329    /// Definition this instance was created from.
330    pub fn definition(&self) -> ComponentDefinitionInner {
331        let public_index = self.0.public_component_index.unwrap_or(0.into());
332        ComponentDefinitionInner {
333            compilation_unit: self.0.root_sub_component.compilation_unit.clone(),
334            public_index,
335            type_loaders: self.0.type_loaders.clone(),
336        }
337    }
338}
339
340/// Lower a compiled `Document` to a `CompilationUnit` and wrap each public
341/// component in a `ComponentDefinitionInner`.
342pub fn build_from_document(
343    document: &i_slint_compiler::object_tree::Document,
344    compiler_config: &i_slint_compiler::CompilerConfiguration,
345    mut type_loaders: TypeLoaders,
346    animation_mode: AnimationMode,
347) -> Vec<ComponentDefinitionInner> {
348    let mut unit =
349        i_slint_compiler::llr::lower_to_item_tree::lower_to_item_tree(document, compiler_config);
350    if matches!(animation_mode, AnimationMode::Static) {
351        make_static(&mut unit);
352    }
353    let unit = Rc::new(unit);
354    // `lower_to_item_tree` builds `public_components` from `exported_roots()`
355    // in iteration order, so the indices line up.
356    type_loaders.originals = std::rc::Rc::new(document.exported_roots().collect());
357    unit.public_components
358        .keys()
359        .map(|public_index| ComponentDefinitionInner {
360            compilation_unit: unit.clone(),
361            public_index,
362            type_loaders: type_loaders.clone(),
363        })
364        .collect()
365}
366
367fn make_static(compilation_unit: &mut CompilationUnit) {
368    fn make_expression_static(expression: &mut Expression) {
369        let replacement = match expression {
370            Expression::BuiltinFunctionCall { function, .. } => match function {
371                BuiltinFunction::AnimationTick => Some(Expression::NumberLiteral(0.)),
372                BuiltinFunction::RestartTimer | BuiltinFunction::UpdateTimers => {
373                    Some(Expression::CodeBlock(Vec::new()))
374                }
375                _ => None,
376            },
377            _ => None,
378        };
379        if let Some(replacement) = replacement {
380            *expression = replacement;
381        }
382    }
383
384    compilation_unit.for_each_expression(&mut |expression, _| {
385        expression.borrow_mut().visit_recursive_mut(&mut make_expression_static);
386    });
387    for sub_component in &compilation_unit.sub_components {
388        for popup in &sub_component.popup_windows {
389            popup.position.borrow_mut().visit_recursive_mut(&mut make_expression_static);
390        }
391    }
392    for sub_component in &mut compilation_unit.sub_components {
393        sub_component.timers.clear();
394        sub_component.animations.clear();
395        for (_, binding) in &mut sub_component.property_init {
396            binding.animation = None;
397        }
398    }
399}
400
401/// What [`build_from_source`] produces: the diagnostics, a map of public
402/// component name → `ComponentDefinitionInner` for each exported root in the
403/// document, and the extra document metadata that the `internal` API of
404/// [`crate::CompilationResult`] exposes for the LSP and live preview.
405pub struct BuildResult {
406    pub diagnostics: Vec<i_slint_compiler::diagnostics::Diagnostic>,
407    pub components: std::collections::HashMap<String, ComponentDefinitionInner>,
408    #[cfg(feature = "internal")]
409    pub watch_paths: Vec<SourcePath>,
410    #[cfg(feature = "internal")]
411    pub structs_and_enums: Vec<LangType>,
412}
413
414/// Compile a `.slint` source string.
415pub async fn build_from_source(
416    source_code: String,
417    path: SourcePath,
418    mut config: i_slint_compiler::CompilerConfiguration,
419    animation_mode: AnimationMode,
420) -> BuildResult {
421    // If the native style should be used, resolve it here as we know the backend.
422    if config.style.as_deref() == Some("native") {
423        // On wasm, look at the browser user agent
424        #[cfg(target_arch = "wasm32")]
425        let target = web_sys::window()
426            .and_then(|window| window.navigator().platform().ok())
427            .map_or("wasm", |platform| {
428                let platform = platform.to_ascii_lowercase();
429                if platform.contains("mac")
430                    || platform.contains("iphone")
431                    || platform.contains("ipad")
432                {
433                    "apple"
434                } else if platform.contains("android") {
435                    "android"
436                } else if platform.contains("win") {
437                    "windows"
438                } else if platform.contains("linux") {
439                    "linux"
440                } else {
441                    "wasm"
442                }
443            });
444        #[cfg(not(target_arch = "wasm32"))]
445        let target = "";
446        config.style = Some(
447            i_slint_common::get_native_style(i_slint_backend_selector::HAS_NATIVE_STYLE, target)
448                .to_string(),
449        );
450    }
451    // Element inlining is off by default: the interpreter preserves
452    // sub-components so `@children` and friends resolve at runtime via the
453    // item tree. `SLINT_INLINING` forces it back on.
454    if std::env::var_os("SLINT_INLINING").is_none() {
455        config.inline_all_elements = false;
456    }
457    // Populate the LLR debug-info side table so highlight/live-preview can
458    // map source-level elements back to runtime items.
459    config.debug_info = true;
460    let diag = i_slint_compiler::diagnostics::BuildDiagnostics::default();
461    let (mut diag, loader, raw_loader) = i_slint_compiler::load_root_file_with_raw_type_loader(
462        &path,
463        source_code,
464        diag,
465        config.clone(),
466    )
467    .await;
468    #[cfg(feature = "internal")]
469    let watch_paths = loader.all_files_to_watch().into_iter().collect();
470    let error_result = |diagnostics| BuildResult {
471        diagnostics,
472        components: Default::default(),
473        #[cfg(feature = "internal")]
474        watch_paths: Vec::new(),
475        #[cfg(feature = "internal")]
476        structs_and_enums: Vec::new(),
477    };
478    if diag.has_errors() {
479        return BuildResult {
480            #[cfg(feature = "internal")]
481            watch_paths,
482            ..error_result(diag.into_iter().collect())
483        };
484    }
485    let type_loader = std::rc::Rc::new(loader);
486    let type_loaders = TypeLoaders {
487        type_loader: Some(type_loader.clone()),
488        raw_type_loader: raw_loader.map(std::rc::Rc::new),
489        originals: Default::default(),
490    };
491    let doc = match type_loader.get_document(&path) {
492        Some(doc) => doc,
493        None => {
494            return BuildResult {
495                #[cfg(feature = "internal")]
496                watch_paths,
497                ..error_result(diag.into_iter().collect())
498            };
499        }
500    };
501    let mut components = std::collections::HashMap::new();
502    for def in build_from_document(doc, &config, type_loaders, animation_mode) {
503        components.insert(def.name().to_string(), def);
504    }
505    if components.is_empty() {
506        diag.push_error_with_span("No component found".into(), Default::default());
507    }
508    #[cfg(feature = "internal")]
509    let structs_and_enums = doc.used_types.borrow().structs_and_enums.clone();
510    BuildResult {
511        diagnostics: diag.into_iter().collect(),
512        components,
513        #[cfg(feature = "internal")]
514        watch_paths,
515        #[cfg(feature = "internal")]
516        structs_and_enums,
517    }
518}