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