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