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