// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore conv gdata powf punct vref /*! module for the Rust code generator Some convention used in the generated code: - `_self` is of type `Pin<&ComponentType>` where ComponentType is the type of the generated sub component, this is existing for any evaluation of a binding - `self_rc` is of type `VRc` or `Rc` for globals this is usually a local variable to the init code that shouldn't rbe relied upon by the binding code. */ use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp, OperatorClass}; use crate::langtype::{Enumeration, EnumerationValue, Struct, Type}; use crate::layout::Orientation; use crate::llr::{ self, EvaluationContext as llr_EvaluationContext, Expression, ParentCtx as llr_ParentCtx, TypeResolutionContext as _, }; use crate::object_tree::Document; use crate::CompilerConfiguration; use itertools::Either; use lyon_path::geom::euclid::approxeq::ApproxEq; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use smol_str::SmolStr; use std::collections::{BTreeMap, BTreeSet}; use std::num::NonZeroUsize; use std::str::FromStr; #[derive(Clone)] struct RustGeneratorContext { /// Path to the SharedGlobals structure that contains the global and the WindowAdaptor global_access: TokenStream, } type EvaluationContext<'a> = llr_EvaluationContext<'a, RustGeneratorContext>; type ParentCtx<'a> = llr_ParentCtx<'a, RustGeneratorContext>; fn ident(ident: &str) -> proc_macro2::Ident { if ident.contains('-') { format_ident!("r#{}", ident.replace('-', "_")) } else { format_ident!("r#{}", ident) } } impl quote::ToTokens for Orientation { fn to_tokens(&self, tokens: &mut TokenStream) { let tks = match self { Orientation::Horizontal => { quote!(sp::Orientation::Horizontal) } Orientation::Vertical => { quote!(sp::Orientation::Vertical) } }; tokens.extend(tks); } } impl quote::ToTokens for crate::embedded_resources::PixelFormat { fn to_tokens(&self, tokens: &mut TokenStream) { use crate::embedded_resources::PixelFormat::*; let tks = match self { Rgb => quote!(sp::TexturePixelFormat::Rgb), Rgba => quote!(sp::TexturePixelFormat::Rgba), RgbaPremultiplied => { quote!(sp::TexturePixelFormat::RgbaPremultiplied) } AlphaMap(_) => quote!(sp::TexturePixelFormat::AlphaMap), }; tokens.extend(tks); } } fn rust_primitive_type(ty: &Type) -> Option { match ty { Type::Void => Some(quote!(())), Type::Int32 => Some(quote!(i32)), Type::Float32 => Some(quote!(f32)), Type::String => Some(quote!(sp::SharedString)), Type::Color => Some(quote!(sp::Color)), Type::ComponentFactory => Some(quote!(slint::ComponentFactory)), Type::Duration => Some(quote!(i64)), Type::Angle => Some(quote!(f32)), Type::PhysicalLength => Some(quote!(sp::Coord)), Type::LogicalLength => Some(quote!(sp::Coord)), Type::Rem => Some(quote!(f32)), Type::Percent => Some(quote!(f32)), Type::Bool => Some(quote!(bool)), Type::Image => Some(quote!(sp::Image)), Type::Struct(s) => { if let Some(name) = &s.name { Some(struct_name_to_tokens(name)) } else { let elem = s.fields.values().map(rust_primitive_type).collect::>>()?; // This will produce a tuple Some(quote!((#(#elem,)*))) } } Type::Array(o) => { let inner = rust_primitive_type(o)?; Some(quote!(sp::ModelRc<#inner>)) } Type::Enumeration(e) => { let i = ident(&e.name); if e.node.is_some() { Some(quote!(#i)) } else { Some(quote!(sp::#i)) } } Type::Brush => Some(quote!(slint::Brush)), Type::LayoutCache => Some(quote!( sp::SharedVector< sp::Coord, > )), _ => None, } } fn rust_property_type(ty: &Type) -> Option { match ty { Type::LogicalLength => Some(quote!(sp::LogicalLength)), Type::Easing => Some(quote!(sp::EasingCurve)), _ => rust_primitive_type(ty), } } fn primitive_property_value(ty: &Type, property_accessor: MemberAccess) -> TokenStream { let value = property_accessor.get_property(); match ty { Type::LogicalLength => quote!(#value.get()), _ => value, } } fn set_primitive_property_value(ty: &Type, value_expression: TokenStream) -> TokenStream { match ty { Type::LogicalLength => { let rust_ty = rust_primitive_type(ty).unwrap_or(quote!(_)); quote!(sp::LogicalLength::new(#value_expression as #rust_ty)) } _ => value_expression, } } /// Generate the rust code for the given component. pub fn generate( doc: &Document, compiler_config: &CompilerConfiguration, ) -> std::io::Result { let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = doc .used_types .borrow() .structs_and_enums .iter() .filter_map(|ty| match ty { Type::Struct(s) => match s.as_ref() { Struct { fields, name: Some(name), node: Some(_), rust_attributes } => { Some((ident(name), generate_struct(name, fields, rust_attributes))) } _ => None, }, Type::Enumeration(en) => Some((ident(&en.name), generate_enum(en))), _ => None, }) .unzip(); let llr = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config)?; if llr.public_components.is_empty() { return Ok(Default::default()); } let sub_compos = llr .used_sub_components .iter() .map(|sub_compo| generate_sub_component(*sub_compo, &llr, None, None, false)) .collect::>(); let public_components = llr.public_components.iter().map(|p| generate_public_component(p, &llr)); let popup_menu = llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true)); let version_check = format_ident!( "VersionCheck_{}_{}_{}", env!("CARGO_PKG_VERSION_MAJOR"), env!("CARGO_PKG_VERSION_MINOR"), env!("CARGO_PKG_VERSION_PATCH"), ); let globals = llr .globals .iter_enumerated() .filter(|(_, glob)| glob.must_generate()) .map(|(idx, glob)| generate_global(idx, glob, &llr)); let shared_globals = generate_shared_globals(&llr, compiler_config); let globals_ids = llr.globals.iter().filter(|glob| glob.exported).flat_map(|glob| { std::iter::once(ident(&glob.name)).chain(glob.aliases.iter().map(|x| ident(x))) }); let compo_ids = llr.public_components.iter().map(|c| ident(&c.name)); let resource_symbols = generate_resources(doc); let named_exports = generate_named_exports(doc); // The inner module was meant to be internal private, but projects have been reaching into it // so we can't change the name of this module let generated_mod = doc .last_exported_component() .map(|c| format_ident!("slint_generated{}", ident(&c.id))) .unwrap_or_else(|| format_ident!("slint_generated")); #[cfg(not(feature = "bundle-translations"))] let translations = quote!(); #[cfg(feature = "bundle-translations")] let translations = llr.translations.as_ref().map(|t| (generate_translations(t, &llr))); Ok(quote! { #[allow(non_snake_case, non_camel_case_types)] #[allow(unused_braces, unused_parens)] #[allow(clippy::all, clippy::pedantic, clippy::nursery)] #[allow(unknown_lints, if_let_rescope, tail_expr_drop_order)] // We don't have fancy Drop mod #generated_mod { use slint::private_unstable_api::re_exports as sp; #[allow(unused_imports)] use sp::{RepeatedItemTree as _, ModelExt as _, Model as _, Float as _}; #(#structs_and_enum_def)* #(#globals)* #(#sub_compos)* #popup_menu #(#public_components)* #shared_globals #(#resource_symbols)* #translations const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check; } #[allow(unused_imports)] pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*}; #[allow(unused_imports)] pub use slint::{ComponentHandle as _, Global as _, ModelExt as _}; }) } fn generate_public_component( llr: &llr::PublicComponent, unit: &llr::CompilationUnit, ) -> TokenStream { let public_component_id = ident(&llr.name); let inner_component_id = inner_component_id(&unit.sub_components[llr.item_tree.root]); let component = generate_item_tree(&llr.item_tree, unit, None, None, false); let ctx = EvaluationContext { compilation_unit: unit, current_sub_component: Some(llr.item_tree.root), current_global: None, generator_state: RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()), }, parent: None, argument_types: &[], }; let property_and_callback_accessors = public_api( &llr.public_properties, &llr.private_properties, quote!(sp::VRc::as_pin_ref(&self.0)), &ctx, ); #[cfg(feature = "bundle-translations")] let init_bundle_translations = unit .translations .as_ref() .map(|_| quote!(sp::set_bundled_languages(_SLINT_BUNDLED_LANGUAGES);)); #[cfg(not(feature = "bundle-translations"))] let init_bundle_translations = quote!(); quote!( #component pub struct #public_component_id(sp::VRc); impl #public_component_id { pub fn new() -> core::result::Result { let inner = #inner_component_id::new()?; #init_bundle_translations // ensure that the window exist as this point so further call to window() don't panic inner.globals.get().unwrap().window_adapter_ref()?; #inner_component_id::user_init(sp::VRc::map(inner.clone(), |x| x)); core::result::Result::Ok(Self(inner)) } #property_and_callback_accessors } impl From<#public_component_id> for sp::VRc { fn from(value: #public_component_id) -> Self { value.0 } } impl slint::ComponentHandle for #public_component_id { type Inner = #inner_component_id; fn as_weak(&self) -> slint::Weak { slint::Weak::new(&self.0) } fn clone_strong(&self) -> Self { Self(self.0.clone()) } fn from_inner(inner: sp::VRc) -> Self { Self(inner) } fn run(&self) -> core::result::Result<(), slint::PlatformError> { self.show()?; slint::run_event_loop()?; self.hide()?; core::result::Result::Ok(()) } fn show(&self) -> core::result::Result<(), slint::PlatformError> { self.0.globals.get().unwrap().window_adapter_ref()?.window().show() } fn hide(&self) -> core::result::Result<(), slint::PlatformError> { self.0.globals.get().unwrap().window_adapter_ref()?.window().hide() } fn window(&self) -> &slint::Window { self.0.globals.get().unwrap().window_adapter_ref().unwrap().window() } fn global<'a, T: slint::Global<'a, Self>>(&'a self) -> T { T::get(&self) } } ) } fn generate_shared_globals( llr: &llr::CompilationUnit, compiler_config: &CompilerConfiguration, ) -> TokenStream { let global_names = llr .globals .iter() .filter(|g| g.is_builtin || g.must_generate()) .map(|g| format_ident!("global_{}", ident(&g.name))) .collect::>(); let global_types = llr .globals .iter() .filter(|g| g.is_builtin || g.must_generate()) .map(global_inner_name) .collect::>(); let apply_constant_scale_factor = if !compiler_config.const_scale_factor.approx_eq(&1.0) { let factor = compiler_config.const_scale_factor as f32; Some( quote!(adapter.window().try_dispatch_event(slint::platform::WindowEvent::ScaleFactorChanged{ scale_factor: #factor })?;), ) } else { None }; quote! { struct SharedGlobals { #(#global_names : ::core::pin::Pin>,)* window_adapter : sp::OnceCell, root_item_tree_weak : sp::VWeak, } impl SharedGlobals { fn new(root_item_tree_weak : sp::VWeak) -> sp::Rc { let _self = sp::Rc::new(Self { #(#global_names : #global_types::new(),)* window_adapter : ::core::default::Default::default(), root_item_tree_weak, }); #(_self.#global_names.clone().init(&_self);)* _self } fn window_adapter_impl(&self) -> sp::Rc { sp::Rc::clone(self.window_adapter_ref().unwrap()) } fn window_adapter_ref(&self) -> sp::Result<&sp::Rc, slint::PlatformError> { self.window_adapter.get_or_try_init(|| { let adapter = slint::private_unstable_api::create_window_adapter()?; let root_rc = self.root_item_tree_weak.upgrade().unwrap(); sp::WindowInner::from_pub(adapter.window()).set_component(&root_rc); #apply_constant_scale_factor core::result::Result::Ok(adapter) }) } fn maybe_window_adapter_impl(&self) -> sp::Option> { self.window_adapter.get().cloned() } } } } fn generate_struct( name: &str, fields: &BTreeMap, rust_attributes: &Option>, ) -> TokenStream { let component_id = struct_name_to_tokens(name); let (declared_property_vars, declared_property_types): (Vec<_>, Vec<_>) = fields.iter().map(|(name, ty)| (ident(name), rust_primitive_type(ty).unwrap())).unzip(); let attributes = if let Some(feature) = rust_attributes { let attr = feature.iter().map(|f| match TokenStream::from_str(format!(r#"#[{f}]"#).as_str()) { Ok(eval) => eval, Err(_) => quote! {}, }); quote! { #(#attr)* } } else { quote! {} }; quote! { #attributes #[derive(Default, PartialEq, Debug, Clone)] pub struct #component_id { #(pub #declared_property_vars : #declared_property_types),* } } } fn generate_enum(en: &std::rc::Rc) -> TokenStream { let enum_name = ident(&en.name); let enum_values = (0..en.values.len()).map(|value| { let i = ident(&EnumerationValue { value, enumeration: en.clone() }.to_pascal_case()); if value == en.default_value { quote!(#[default] #i) } else { quote!(#i) } }); let rust_attr = en.node.as_ref().and_then(|node| { node.AtRustAttr().map(|attr| { match TokenStream::from_str(format!(r#"#[{}]"#, attr.text()).as_str()) { Ok(eval) => eval, Err(_) => quote! {}, } }) }); quote! { #[allow(dead_code)] #[derive(Default, Copy, Clone, PartialEq, Debug)] #rust_attr pub enum #enum_name { #(#enum_values,)* } } } fn handle_property_init( prop: &llr::PropertyReference, binding_expression: &llr::BindingExpression, init: &mut Vec, ctx: &EvaluationContext, ) { let rust_property = access_member(prop, ctx).unwrap(); let prop_type = ctx.property_ty(prop); let init_self_pin_ref = if ctx.current_global.is_some() { quote!(let _self = self_rc.as_ref();) } else { quote!(let _self = self_rc.as_pin_ref();) }; if let Type::Callback(callback) = &prop_type { let mut ctx2 = ctx.clone(); ctx2.argument_types = &callback.args; let tokens_for_expression = compile_expression(&binding_expression.expression.borrow(), &ctx2); let as_ = if matches!(callback.return_type, Type::Void) { quote!(;) } else { quote!(as _) }; init.push(quote!({ #[allow(unreachable_code, unused)] slint::private_unstable_api::set_callback_handler(#rust_property, &self_rc, { move |self_rc, args| { #init_self_pin_ref (#tokens_for_expression) #as_ } }); })); } else { let tokens_for_expression = compile_expression(&binding_expression.expression.borrow(), ctx); let tokens_for_expression = set_primitive_property_value(prop_type, tokens_for_expression); init.push(if binding_expression.is_constant && !binding_expression.is_state_info { let t = rust_property_type(prop_type).unwrap_or(quote!(_)); quote! { #rust_property.set({ (#tokens_for_expression) as #t }); } } else { let maybe_cast_to_property_type = if binding_expression.expression.borrow().ty(ctx) == Type::Invalid { // Don't cast if the Rust code is the never type, as with return statements inside a block, the // type of the return expression is `()` instead of `!`. None } else { Some(quote!(as _)) }; let binding_tokens = quote!(move |self_rc| { #init_self_pin_ref (#tokens_for_expression) #maybe_cast_to_property_type }); if binding_expression.is_state_info { quote! { { slint::private_unstable_api::set_property_state_binding(#rust_property, &self_rc, #binding_tokens); } } } else { match &binding_expression.animation { Some(llr::Animation::Static(anim)) => { let anim = compile_expression(anim, ctx); quote! { { #init_self_pin_ref slint::private_unstable_api::set_animated_property_binding(#rust_property, &self_rc, #binding_tokens, #anim); } } } Some(llr::Animation::Transition(anim)) => { let anim = compile_expression(anim, ctx); quote! { slint::private_unstable_api::set_animated_property_binding_for_transition( #rust_property, &self_rc, #binding_tokens, move |self_rc| { #init_self_pin_ref #anim } ); } } None => { quote! { { slint::private_unstable_api::set_property_binding(#rust_property, &self_rc, #binding_tokens); } } } } } }); } } /// Public API for Global and root component fn public_api( public_properties: &llr::PublicProperties, private_properties: &llr::PrivateProperties, self_init: TokenStream, ctx: &EvaluationContext, ) -> TokenStream { let mut property_and_callback_accessors: Vec = vec![]; for p in public_properties { let prop_ident = ident(&p.name); let prop = access_member(&p.prop, ctx).unwrap(); if let Type::Callback(callback) = &p.ty { let callback_args = callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::>(); let return_type = rust_primitive_type(&callback.return_type).unwrap(); let args_name = (0..callback.args.len()).map(|i| format_ident!("arg_{}", i)).collect::>(); let caller_ident = format_ident!("invoke_{}", prop_ident); property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type { let _self = #self_init; #prop.call(&(#(#args_name,)*)) } )); let on_ident = format_ident!("on_{}", prop_ident); let args_index = (0..callback_args.len()).map(proc_macro2::Literal::usize_unsuffixed); property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #on_ident(&self, mut f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) { let _self = #self_init; #[allow(unused)] #prop.set_handler( // FIXME: why do i need to clone here? move |args| f(#(args.#args_index.clone()),*) ) } )); } else if let Type::Function(function) = &p.ty { let callback_args = function.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::>(); let return_type = rust_primitive_type(&function.return_type).unwrap(); let args_name = (0..function.args.len()).map(|i| format_ident!("arg_{}", i)).collect::>(); let caller_ident = format_ident!("invoke_{}", prop_ident); property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #caller_ident(&self, #(#args_name : #callback_args,)*) -> #return_type { let _self = #self_init; #prop(#(#args_name,)*) } )); } else { let rust_property_type = rust_primitive_type(&p.ty).unwrap(); let getter_ident = format_ident!("get_{}", prop_ident); let prop_expression = primitive_property_value(&p.ty, MemberAccess::Direct(prop)); property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #getter_ident(&self) -> #rust_property_type { #[allow(unused_imports)] let _self = #self_init; #prop_expression } )); let setter_ident = format_ident!("set_{}", prop_ident); if !p.read_only { let set_value = property_set_value_tokens(&p.prop, quote!(value), ctx); property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #setter_ident(&self, value: #rust_property_type) { #[allow(unused_imports)] let _self = #self_init; #set_value } )); } else { property_and_callback_accessors.push(quote!( #[allow(dead_code)] fn #setter_ident(&self, _read_only_property : ()) { } )); } } } for (name, ty) in private_properties { let prop_ident = ident(name); if let Type::Function { .. } = ty { let caller_ident = format_ident!("invoke_{}", prop_ident); property_and_callback_accessors.push( quote!( #[allow(dead_code)] fn #caller_ident(&self, _private_function: ()) {} ), ); } else { let getter_ident = format_ident!("get_{}", prop_ident); let setter_ident = format_ident!("set_{}", prop_ident); property_and_callback_accessors.push(quote!( #[allow(dead_code)] fn #getter_ident(&self, _private_property: ()) {} #[allow(dead_code)] fn #setter_ident(&self, _private_property: ()) {} )); } } quote!(#(#property_and_callback_accessors)*) } /// Generate the rust code for the given component. fn generate_sub_component( component_idx: llr::SubComponentIdx, root: &llr::CompilationUnit, parent_ctx: Option, index_property: Option, pinned_drop: bool, ) -> TokenStream { let component = &root.sub_components[component_idx]; let inner_component_id = inner_component_id(component); let ctx = EvaluationContext::new_sub_component( root, component_idx, RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()) }, parent_ctx, ); let mut extra_components = component .popup_windows .iter() .map(|popup| { generate_item_tree( &popup.item_tree, root, Some(ParentCtx::new(&ctx, None)), None, false, ) }) .chain(component.menu_item_trees.iter().map(|tree| { generate_item_tree(tree, root, Some(ParentCtx::new(&ctx, None)), None, false) })) .collect::>(); let mut declared_property_vars = vec![]; let mut declared_property_types = vec![]; let mut declared_callbacks = vec![]; let mut declared_callbacks_types = vec![]; let mut declared_callbacks_ret = vec![]; for property in component.properties.iter().filter(|p| p.use_count.get() > 0) { let prop_ident = ident(&property.name); if let Type::Callback(callback) = &property.ty { let callback_args = callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::>(); let return_type = rust_primitive_type(&callback.return_type).unwrap(); declared_callbacks.push(prop_ident.clone()); declared_callbacks_types.push(callback_args); declared_callbacks_ret.push(return_type); } else { let rust_property_type = rust_property_type(&property.ty).unwrap(); declared_property_vars.push(prop_ident.clone()); declared_property_types.push(rust_property_type.clone()); } } let change_tracker_names = component .change_callbacks .iter() .enumerate() .map(|(idx, _)| format_ident!("change_tracker{idx}")); let declared_functions = generate_functions(component.functions.as_ref(), &ctx); let mut init = vec![]; let mut item_names = vec![]; let mut item_types = vec![]; #[cfg(slint_debug_property)] init.push(quote!( #(self_rc.#declared_property_vars.debug_name.replace( concat!(stringify!(#inner_component_id), ".", stringify!(#declared_property_vars)).into());)* )); for item in &component.items { item_names.push(ident(&item.name)); item_types.push(ident(&item.ty.class_name)); #[cfg(slint_debug_property)] { let mut it = Some(&item.ty); let elem_name = ident(&item.name); while let Some(ty) = it { for (prop, info) in &ty.properties { if info.ty.is_property_type() && !prop.starts_with("viewport") && prop != "commands" { let name = format!("{}::{}.{}", component.name, item.name, prop); let prop = ident(&prop); init.push( quote!(self_rc.#elem_name.#prop.debug_name.replace(#name.into());), ); } } it = ty.parent.as_ref(); } } } let mut repeated_visit_branch: Vec = vec![]; let mut repeated_element_components: Vec = vec![]; let mut repeated_subtree_ranges: Vec = vec![]; let mut repeated_subtree_components: Vec = vec![]; for (idx, repeated) in component.repeated.iter_enumerated() { extra_components.push(generate_repeated_component( repeated, root, ParentCtx::new(&ctx, Some(idx)), )); let idx = usize::from(idx) as u32; if let Some(item_index) = repeated.container_item_index { let embed_item = access_member( &llr::PropertyReference::InNativeItem { sub_component_path: vec![], item_index, prop_name: String::new(), }, &ctx, ) .unwrap(); let ensure_updated = { quote! { #embed_item.ensure_updated(); } }; repeated_visit_branch.push(quote!( #idx => { #ensure_updated #embed_item.visit_children_item(-1, order, visitor) } )); repeated_subtree_ranges.push(quote!( #idx => { #ensure_updated #embed_item.subtree_range() } )); repeated_subtree_components.push(quote!( #idx => { #ensure_updated if subtree_index == 0 { *result = #embed_item.subtree_component() } } )); } else { let repeater_id = format_ident!("repeater{}", idx); let rep_inner_component_id = self::inner_component_id(&root.sub_components[repeated.sub_tree.root]); let model = compile_expression(&repeated.model.borrow(), &ctx); init.push(quote! { _self.#repeater_id.set_model_binding({ let self_weak = sp::VRcMapped::downgrade(&self_rc); move || { let self_rc = self_weak.upgrade().unwrap(); let _self = self_rc.as_pin_ref(); (#model) as _ } }); }); let ensure_updated = if let Some(listview) = &repeated.listview { let vp_y = access_member(&listview.viewport_y, &ctx).unwrap(); let vp_h = access_member(&listview.viewport_height, &ctx).unwrap(); let lv_h = access_member(&listview.listview_height, &ctx).unwrap(); let vp_w = access_member(&listview.viewport_width, &ctx).unwrap(); let lv_w = access_member(&listview.listview_width, &ctx).unwrap(); quote! { #inner_component_id::FIELD_OFFSETS.#repeater_id.apply_pin(_self).ensure_updated_listview( || { #rep_inner_component_id::new(_self.self_weak.get().unwrap().clone()).unwrap().into() }, #vp_w, #vp_h, #vp_y, #lv_w.get(), #lv_h ); } } else { quote! { #inner_component_id::FIELD_OFFSETS.#repeater_id.apply_pin(_self).ensure_updated( || #rep_inner_component_id::new(_self.self_weak.get().unwrap().clone()).unwrap().into() ); } }; repeated_visit_branch.push(quote!( #idx => { #ensure_updated _self.#repeater_id.visit(order, visitor) } )); repeated_subtree_ranges.push(quote!( #idx => { #ensure_updated sp::IndexRange::from(_self.#repeater_id.range()) } )); repeated_subtree_components.push(quote!( #idx => { #ensure_updated if let Some(instance) = _self.#repeater_id.instance_at(subtree_index) { *result = sp::VRc::downgrade(&sp::VRc::into_dyn(instance)); } } )); repeated_element_components.push(if repeated.index_prop.is_some() { quote!(#repeater_id: sp::Repeater<#rep_inner_component_id>) } else { quote!(#repeater_id: sp::Conditional<#rep_inner_component_id>) }); } } let mut accessible_role_branch = vec![]; let mut accessible_string_property_branch = vec![]; let mut accessibility_action_branch = vec![]; let mut supported_accessibility_actions = BTreeMap::>::new(); for ((index, what), expr) in &component.accessible_prop { let e = compile_expression(&expr.borrow(), &ctx); if what == "Role" { accessible_role_branch.push(quote!(#index => #e,)); } else if let Some(what) = what.strip_prefix("Action") { let what = ident(what); let has_args = matches!(&*expr.borrow(), Expression::CallBackCall { arguments, .. } if !arguments.is_empty()); accessibility_action_branch.push(if has_args { quote!((#index, sp::AccessibilityAction::#what(args)) => { let args = (args,); #e }) } else { quote!((#index, sp::AccessibilityAction::#what) => { #e }) }); supported_accessibility_actions.entry(*index).or_default().insert(what); } else { let what = ident(what); accessible_string_property_branch .push(quote!((#index, sp::AccessibleStringProperty::#what) => sp::Some(#e),)); } } let mut supported_accessibility_actions_branch = supported_accessibility_actions .into_iter() .map(|(index, values)| quote!(#index => #(sp::SupportedAccessibilityAction::#values)|*,)) .collect::>(); let mut item_geometry_branch = component .geometries .iter() .enumerate() .filter_map(|(i, x)| x.as_ref().map(|x| (i, x))) .map(|(index, expr)| { let expr = compile_expression(&expr.borrow(), &ctx); let index = index as u32; quote!(#index => #expr,) }) .collect::>(); let mut item_element_infos_branch = component .element_infos .iter() .map(|(item_index, ids)| quote!(#item_index => { return sp::Some(#ids.into()); })) .collect::>(); let mut user_init_code: Vec = Vec::new(); let mut sub_component_names: Vec = vec![]; let mut sub_component_types: Vec = vec![]; for sub in &component.sub_components { let field_name = ident(&sub.name); let sc = &root.sub_components[sub.ty]; let sub_component_id = self::inner_component_id(sc); let local_tree_index: u32 = sub.index_in_tree as _; let local_index_of_first_child: u32 = sub.index_of_first_child_in_tree as _; let global_access = &ctx.generator_state.global_access; // For children of sub-components, the item index generated by the generate_item_indices pass // starts at 1 (0 is the root element). let global_index = if local_tree_index == 0 { quote!(tree_index) } else { quote!(tree_index_of_first_child + #local_tree_index - 1) }; let global_children = if local_index_of_first_child == 0 { quote!(0) } else { quote!(tree_index_of_first_child + #local_index_of_first_child - 1) }; let sub_compo_field = access_component_field_offset(&format_ident!("Self"), &field_name); init.push(quote!(#sub_component_id::init( sp::VRcMapped::map(self_rc.clone(), |x| #sub_compo_field.apply_pin(x)), #global_access.clone(), #global_index, #global_children );)); user_init_code.push(quote!(#sub_component_id::user_init( sp::VRcMapped::map(self_rc.clone(), |x| #sub_compo_field.apply_pin(x)), );)); let sub_component_repeater_count = sc.repeater_count(root); if sub_component_repeater_count > 0 { let repeater_offset = sub.repeater_offset; let last_repeater = repeater_offset + sub_component_repeater_count - 1; repeated_visit_branch.push(quote!( #repeater_offset..=#last_repeater => { #sub_compo_field.apply_pin(_self).visit_dynamic_children(dyn_index - #repeater_offset, order, visitor) } )); repeated_subtree_ranges.push(quote!( #repeater_offset..=#last_repeater => { #sub_compo_field.apply_pin(_self).subtree_range(dyn_index - #repeater_offset) } )); repeated_subtree_components.push(quote!( #repeater_offset..=#last_repeater => { #sub_compo_field.apply_pin(_self).subtree_component(dyn_index - #repeater_offset, subtree_index, result) } )); } let sub_items_count = sc.child_item_count(root); accessible_role_branch.push(quote!( #local_tree_index => #sub_compo_field.apply_pin(_self).accessible_role(0), )); accessible_string_property_branch.push(quote!( (#local_tree_index, _) => #sub_compo_field.apply_pin(_self).accessible_string_property(0, what), )); accessibility_action_branch.push(quote!( (#local_tree_index, _) => #sub_compo_field.apply_pin(_self).accessibility_action(0, action), )); supported_accessibility_actions_branch.push(quote!( #local_tree_index => #sub_compo_field.apply_pin(_self).supported_accessibility_actions(0), )); if sub_items_count > 1 { let range_begin = local_index_of_first_child; let range_end = range_begin + sub_items_count - 2 + sc.repeater_count(root); accessible_role_branch.push(quote!( #range_begin..=#range_end => #sub_compo_field.apply_pin(_self).accessible_role(index - #range_begin + 1), )); accessible_string_property_branch.push(quote!( (#range_begin..=#range_end, _) => #sub_compo_field.apply_pin(_self).accessible_string_property(index - #range_begin + 1, what), )); item_geometry_branch.push(quote!( #range_begin..=#range_end => return #sub_compo_field.apply_pin(_self).item_geometry(index - #range_begin + 1), )); accessibility_action_branch.push(quote!( (#range_begin..=#range_end, _) => #sub_compo_field.apply_pin(_self).accessibility_action(index - #range_begin + 1, action), )); supported_accessibility_actions_branch.push(quote!( #range_begin..=#range_end => #sub_compo_field.apply_pin(_self).supported_accessibility_actions(index - #range_begin + 1), )); item_element_infos_branch.push(quote!( #range_begin..=#range_end => #sub_compo_field.apply_pin(_self).item_element_infos(index - #range_begin + 1), )); } sub_component_names.push(field_name); sub_component_types.push(sub_component_id); } let popup_id_names = component.popup_windows.iter().enumerate().map(|(i, _)| internal_popup_id(i)); for (prop1, prop2) in &component.two_way_bindings { let p1 = access_member(prop1, &ctx); let p2 = access_member(prop2, &ctx); let r = p1.then(|p1| p2.then(|p2| quote!(sp::Property::link_two_way(#p1, #p2)))); init.push(quote!(#r;)) } for (prop, expression) in &component.property_init { if expression.use_count.get() > 0 && component.prop_used(prop, root) { handle_property_init(prop, expression, &mut init, &ctx) } } for prop in &component.const_properties { if component.prop_used(prop, root) { let rust_property = access_member(prop, &ctx).unwrap(); init.push(quote!(#rust_property.set_constant();)) } } let parent_component_type = parent_ctx.iter().map(|parent| { let parent_component_id = self::inner_component_id(parent.ctx.current_sub_component().unwrap()); quote!(sp::VWeakMapped::) }); user_init_code.extend(component.init_code.iter().map(|e| { let code = compile_expression(&e.borrow(), &ctx); quote!(#code;) })); user_init_code.extend(component.change_callbacks.iter().enumerate().map(|(idx, (p, e))| { let code = compile_expression(&e.borrow(), &ctx); let prop = compile_expression(&Expression::PropertyReference(p.clone()), &ctx); let change_tracker = format_ident!("change_tracker{idx}"); quote! { let self_weak = sp::VRcMapped::downgrade(&self_rc); #[allow(dead_code, unused)] _self.#change_tracker.init( self_weak, move |self_weak| { let self_rc = self_weak.upgrade().unwrap(); let _self = self_rc.as_pin_ref(); #prop }, move |self_weak, _| { let self_rc = self_weak.upgrade().unwrap(); let _self = self_rc.as_pin_ref(); #code; } ); } })); let layout_info_h = compile_expression(&component.layout_info_h.borrow(), &ctx); let layout_info_v = compile_expression(&component.layout_info_v.borrow(), &ctx); // FIXME! this is only public because of the ComponentHandle::Inner. we should find another way let visibility = parent_ctx.is_none().then(|| quote!(pub)); let subtree_index_function = if let Some(property_index) = index_property { let prop = access_member( &llr::PropertyReference::Local { sub_component_path: vec![], property_index }, &ctx, ) .unwrap(); quote!(#prop.get() as usize) } else { quote!(usize::MAX) }; let timer_names = component.timers.iter().enumerate().map(|(idx, _)| format_ident!("timer{idx}")); let update_timers = (!component.timers.is_empty()).then(|| { let updt = component.timers.iter().enumerate().map(|(idx, tmr)| { let ident = format_ident!("timer{idx}"); let interval = compile_expression(&tmr.interval.borrow(), &ctx); let running = compile_expression(&tmr.running.borrow(), &ctx); let callback = compile_expression(&tmr.triggered.borrow(), &ctx); quote!( if #running { let interval = core::time::Duration::from_millis(#interval as u64); if !self.#ident.running() || interval != self.#ident.interval() { let self_weak = self.self_weak.get().unwrap().clone(); self.#ident.start(sp::TimerMode::Repeated, interval, move || { if let Some(self_rc) = self_weak.upgrade() { let _self = self_rc.as_pin_ref(); #callback } }); } } else { self.#ident.stop(); } ) }); user_init_code.push(quote!(_self.update_timers();)); quote!( fn update_timers(self: ::core::pin::Pin<&Self>) { let _self = self; #(#updt)* } ) }); let pin_macro = if pinned_drop { quote!(#[pin_drop]) } else { quote!(#[pin]) }; quote!( #[derive(sp::FieldOffsets, Default)] #[const_field_offset(sp::const_field_offset)] #[repr(C)] #pin_macro #visibility struct #inner_component_id { #(#item_names : sp::#item_types,)* #(#sub_component_names : #sub_component_types,)* #(#popup_id_names : ::core::cell::Cell>,)* #(#declared_property_vars : sp::Property<#declared_property_types>,)* #(#declared_callbacks : sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)* #(#repeated_element_components,)* #(#change_tracker_names : sp::ChangeTracker,)* #(#timer_names : sp::Timer,)* self_weak : sp::OnceCell>, #(parent : #parent_component_type,)* globals: sp::OnceCell>, tree_index: ::core::cell::Cell, tree_index_of_first_child: ::core::cell::Cell, } impl #inner_component_id { fn init(self_rc: sp::VRcMapped, globals : sp::Rc, tree_index: u32, tree_index_of_first_child: u32) { #![allow(unused)] let _self = self_rc.as_pin_ref(); _self.self_weak.set(sp::VRcMapped::downgrade(&self_rc)); _self.globals.set(globals); _self.tree_index.set(tree_index); _self.tree_index_of_first_child.set(tree_index_of_first_child); #(#init)* } fn user_init(self_rc: sp::VRcMapped) { #![allow(unused)] let _self = self_rc.as_pin_ref(); #(#user_init_code)* } fn visit_dynamic_children( self: ::core::pin::Pin<&Self>, dyn_index: u32, order: sp::TraversalOrder, visitor: sp::ItemVisitorRefMut<'_> ) -> sp::VisitChildrenResult { #![allow(unused)] let _self = self; match dyn_index { #(#repeated_visit_branch)* _ => panic!("invalid dyn_index {}", dyn_index), } } fn layout_info(self: ::core::pin::Pin<&Self>, orientation: sp::Orientation) -> sp::LayoutInfo { #![allow(unused)] let _self = self; match orientation { sp::Orientation::Horizontal => #layout_info_h, sp::Orientation::Vertical => #layout_info_v, } } fn subtree_range(self: ::core::pin::Pin<&Self>, dyn_index: u32) -> sp::IndexRange { #![allow(unused)] let _self = self; match dyn_index { #(#repeated_subtree_ranges)* _ => panic!("invalid dyn_index {}", dyn_index), } } fn subtree_component(self: ::core::pin::Pin<&Self>, dyn_index: u32, subtree_index: usize, result: &mut sp::ItemTreeWeak) { #![allow(unused)] let _self = self; match dyn_index { #(#repeated_subtree_components)* _ => panic!("invalid dyn_index {}", dyn_index), }; } fn index_property(self: ::core::pin::Pin<&Self>) -> usize { #![allow(unused)] let _self = self; #subtree_index_function } fn item_geometry(self: ::core::pin::Pin<&Self>, index: u32) -> sp::LogicalRect { #![allow(unused)] let _self = self; // The result of the expression is an anonymous struct, `{height: length, width: length, x: length, y: length}` // fields are in alphabetical order let (h, w, x, y) = match index { #(#item_geometry_branch)* _ => return ::core::default::Default::default() }; sp::euclid::rect(x, y, w, h) } fn accessible_role(self: ::core::pin::Pin<&Self>, index: u32) -> sp::AccessibleRole { #![allow(unused)] let _self = self; match index { #(#accessible_role_branch)* //#(#forward_sub_ranges => #forward_sub_field.apply_pin(_self).accessible_role())* _ => sp::AccessibleRole::default(), } } fn accessible_string_property( self: ::core::pin::Pin<&Self>, index: u32, what: sp::AccessibleStringProperty, ) -> sp::Option { #![allow(unused)] let _self = self; match (index, what) { #(#accessible_string_property_branch)* _ => sp::None, } } fn accessibility_action(self: ::core::pin::Pin<&Self>, index: u32, action: &sp::AccessibilityAction) { #![allow(unused)] let _self = self; match (index, action) { #(#accessibility_action_branch)* _ => (), } } fn supported_accessibility_actions(self: ::core::pin::Pin<&Self>, index: u32) -> sp::SupportedAccessibilityAction { #![allow(unused)] let _self = self; match index { #(#supported_accessibility_actions_branch)* _ => ::core::default::Default::default(), } } fn item_element_infos(self: ::core::pin::Pin<&Self>, index: u32) -> sp::Option { #![allow(unused)] let _self = self; match index { #(#item_element_infos_branch)* _ => { ::core::default::Default::default() } } } #update_timers #(#declared_functions)* } #(#extra_components)* ) } fn generate_functions(functions: &[llr::Function], ctx: &EvaluationContext) -> Vec { functions .iter() .map(|f| { let mut ctx2 = ctx.clone(); ctx2.argument_types = &f.args; let tokens_for_expression = compile_expression(&f.code, &ctx2); let as_ = if f.ret_ty == Type::Void { Some(quote!(;)) } else if f.code.ty(&ctx2) == Type::Invalid { // Don't cast if the Rust code is the never type, as with return statements inside a block, the // type of the return expression is `()` instead of `!`. None } else { Some(quote!(as _)) }; let fn_id = ident(&format!("fn_{}", f.name)); let args_ty = f.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::>(); let return_type = rust_primitive_type(&f.ret_ty).unwrap(); let args_name = (0..f.args.len()).map(|i| format_ident!("arg_{}", i)).collect::>(); quote! { #[allow(dead_code, unused)] pub fn #fn_id(self: ::core::pin::Pin<&Self>, #(#args_name : #args_ty,)*) -> #return_type { let _self = self; let args = (#(#args_name,)*); (#tokens_for_expression) #as_ } } }) .collect() } fn generate_global( global_idx: llr::GlobalIdx, global: &llr::GlobalComponent, root: &llr::CompilationUnit, ) -> TokenStream { let mut declared_property_vars = vec![]; let mut declared_property_types = vec![]; let mut declared_callbacks = vec![]; let mut declared_callbacks_types = vec![]; let mut declared_callbacks_ret = vec![]; for property in global.properties.iter().filter(|p| p.use_count.get() > 0) { let prop_ident = ident(&property.name); if let Type::Callback(callback) = &property.ty { let callback_args = callback.args.iter().map(|a| rust_primitive_type(a).unwrap()).collect::>(); let return_type = rust_primitive_type(&callback.return_type); declared_callbacks.push(prop_ident.clone()); declared_callbacks_types.push(callback_args); declared_callbacks_ret.push(return_type); } else { let rust_property_type = rust_property_type(&property.ty).unwrap(); declared_property_vars.push(prop_ident.clone()); declared_property_types.push(rust_property_type.clone()); } } let mut init = vec![]; let inner_component_id = format_ident!("Inner{}", ident(&global.name)); #[cfg(slint_debug_property)] init.push(quote!( #(self_rc.#declared_property_vars.debug_name.replace( concat!(stringify!(#inner_component_id), ".", stringify!(#declared_property_vars)).into());)* )); let ctx = EvaluationContext::new_global( root, global_idx, RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap().upgrade().unwrap()), }, ); let declared_functions = generate_functions(global.functions.as_ref(), &ctx); for (property_index, expression) in global.init_values.iter_enumerated() { if global.properties[property_index].use_count.get() == 0 { continue; } if let Some(expression) = expression.as_ref() { handle_property_init( &llr::PropertyReference::Local { sub_component_path: vec![], property_index }, expression, &mut init, &ctx, ) } } for (property_index, cst) in global.const_properties.iter_enumerated() { if global.properties[property_index].use_count.get() == 0 { continue; } if *cst { let rust_property = access_member( &llr::PropertyReference::Local { sub_component_path: vec![], property_index }, &ctx, ) .unwrap(); init.push(quote!(#rust_property.set_constant();)) } } let public_component_id = ident(&global.name); let global_id = format_ident!("global_{}", public_component_id); let change_tracker_names = global .change_callbacks .keys() .map(|idx| format_ident!("change_tracker{}", usize::from(*idx))); init.extend(global.change_callbacks.iter().map(|(p, e)| { let code = compile_expression(&e.borrow(), &ctx); let prop = access_member( &llr::PropertyReference::Local { sub_component_path: vec![], property_index: *p }, &ctx, ) .unwrap(); let change_tracker = format_ident!("change_tracker{}", usize::from(*p)); quote! { #[allow(dead_code, unused)] _self.#change_tracker.init( self_rc.globals.get().unwrap().clone(), move |global_weak| { let self_rc = global_weak.upgrade().unwrap().#global_id.clone(); let _self = self_rc.as_ref(); #prop.get() }, move |global_weak, _| { let self_rc = global_weak.upgrade().unwrap().#global_id.clone(); let _self = self_rc.as_ref(); #code; } ); } })); let public_interface = global.exported.then(|| { let property_and_callback_accessors = public_api( &global.public_properties, &global.private_properties, quote!(self.0.as_ref()), &ctx, ); let aliases = global.aliases.iter().map(|name| ident(name)); let getters = root.public_components.iter().map(|c| { let root_component_id = ident(&c.name); quote! { impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> { fn get(component: &'a #root_component_id) -> Self { Self(&component.0.globals.get().unwrap().#global_id) } } } }); quote!( #[allow(unused)] pub struct #public_component_id<'a>(&'a ::core::pin::Pin>); impl<'a> #public_component_id<'a> { #property_and_callback_accessors } #(pub type #aliases<'a> = #public_component_id<'a>;)* #(#getters)* ) }); quote!( #[derive(sp::FieldOffsets, Default)] #[const_field_offset(sp::const_field_offset)] #[repr(C)] #[pin] struct #inner_component_id { #(#declared_property_vars: sp::Property<#declared_property_types>,)* #(#declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)* #(#change_tracker_names : sp::ChangeTracker,)* globals : sp::OnceCell>, } impl #inner_component_id { fn new() -> ::core::pin::Pin> { sp::Rc::pin(Self::default()) } fn init(self: ::core::pin::Pin>, globals: &sp::Rc) { #![allow(unused)] self.globals.set(sp::Rc::downgrade(globals)); let self_rc = self; let _self = self_rc.as_ref(); #(#init)* } #(#declared_functions)* } #public_interface ) } fn generate_item_tree( sub_tree: &llr::ItemTree, root: &llr::CompilationUnit, parent_ctx: Option, index_property: Option, is_popup_menu: bool, ) -> TokenStream { let sub_comp = generate_sub_component(sub_tree.root, root, parent_ctx, index_property, true); let inner_component_id = self::inner_component_id(&root.sub_components[sub_tree.root]); let parent_component_type = parent_ctx .iter() .map(|parent| { let parent_component_id = self::inner_component_id(parent.ctx.current_sub_component().unwrap()); quote!(sp::VWeakMapped::) }) .collect::>(); let globals = if is_popup_menu { quote!(globals) } else if parent_ctx.is_some() { quote!(parent.upgrade().unwrap().globals.get().unwrap().clone()) } else { quote!(SharedGlobals::new(sp::VRc::downgrade(&self_dyn_rc))) }; let globals_arg = is_popup_menu.then(|| quote!(globals: sp::Rc)); let embedding_function = if parent_ctx.is_some() { quote!(todo!("Components written in Rust can not get embedded yet.")) } else { quote!(false) }; let parent_item_expression = parent_ctx.and_then(|parent| { parent.repeater_index.map(|idx| { let sub_component_offset = parent.ctx.current_sub_component().unwrap().repeated[idx].index_in_tree; quote!(if let Some((parent_component, parent_index)) = self .parent .clone() .upgrade() .map(|sc| (sp::VRcMapped::origin(&sc), sc.tree_index_of_first_child.get())) { *_result = sp::ItemRc::new(parent_component, parent_index + #sub_component_offset - 1) .downgrade(); }) }) }); let mut item_tree_array = vec![]; let mut item_array = vec![]; sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| { let parent_index = parent_index as u32; let (path, component) = follow_sub_component_path(root, sub_tree.root, &node.sub_component_path); match node.item_index { Either::Right(mut repeater_index) => { assert_eq!(node.children.len(), 0); let mut sub_component = &root.sub_components[sub_tree.root]; for i in &node.sub_component_path { repeater_index += sub_component.sub_components[*i].repeater_offset; sub_component = &root.sub_components[sub_component.sub_components[*i].ty]; } item_tree_array.push(quote!( sp::ItemTreeNode::DynamicTree { index: #repeater_index, parent_index: #parent_index, } )); } Either::Left(item_index) => { let item = &component.items[item_index]; let field = access_component_field_offset( &self::inner_component_id(component), &ident(&item.name), ); let children_count = node.children.len() as u32; let children_index = children_offset as u32; let item_array_len = item_array.len() as u32; let is_accessible = node.is_accessible; item_tree_array.push(quote!( sp::ItemTreeNode::Item { is_accessible: #is_accessible, children_count: #children_count, children_index: #children_index, parent_index: #parent_index, item_array_index: #item_array_len, } )); item_array.push(quote!(sp::VOffset::new(#path #field))); } } }); let item_tree_array_len = item_tree_array.len(); let item_array_len = item_array.len(); let element_info_body = if root.has_debug_info { quote!( *_result = self.item_element_infos(_index).unwrap_or_default(); true ) } else { quote!(false) }; quote!( #sub_comp impl #inner_component_id { fn new(#(parent: #parent_component_type,)* #globals_arg) -> core::result::Result, slint::PlatformError> { #![allow(unused)] slint::private_unstable_api::ensure_backend()?; let mut _self = Self::default(); #(_self.parent = parent.clone() as #parent_component_type;)* let self_rc = sp::VRc::new(_self); let self_dyn_rc = sp::VRc::into_dyn(self_rc.clone()); let globals = #globals; sp::register_item_tree(&self_dyn_rc, globals.maybe_window_adapter_impl()); Self::init(sp::VRc::map(self_rc.clone(), |x| x), globals, 0, 1); core::result::Result::Ok(self_rc) } fn item_tree() -> &'static [sp::ItemTreeNode] { const ITEM_TREE : [sp::ItemTreeNode; #item_tree_array_len] = [#(#item_tree_array),*]; &ITEM_TREE } fn item_array() -> &'static [sp::VOffset] { // FIXME: ideally this should be a const, but we can't because of the pointer to the vtable static ITEM_ARRAY : sp::OnceBox< [sp::VOffset<#inner_component_id, sp::ItemVTable, sp::AllowPin>; #item_array_len] > = sp::OnceBox::new(); &*ITEM_ARRAY.get_or_init(|| sp::vec![#(#item_array),*].into_boxed_slice().try_into().unwrap()) } } const _ : () = { use slint::private_unstable_api::re_exports::*; ItemTreeVTable_static!(static VT for self::#inner_component_id); }; impl sp::PinnedDrop for #inner_component_id { fn drop(self: core::pin::Pin<&mut #inner_component_id>) { sp::vtable::new_vref!(let vref : VRef for sp::ItemTree = self.as_ref().get_ref()); if let Some(wa) = self.globals.get().unwrap().maybe_window_adapter_impl() { sp::unregister_item_tree(self.as_ref(), vref, Self::item_array(), &wa); } } } impl sp::ItemTree for #inner_component_id { fn visit_children_item(self: ::core::pin::Pin<&Self>, index: isize, order: sp::TraversalOrder, visitor: sp::ItemVisitorRefMut<'_>) -> sp::VisitChildrenResult { return sp::visit_item_tree(self, &sp::VRcMapped::origin(&self.as_ref().self_weak.get().unwrap().upgrade().unwrap()), self.get_item_tree().as_slice(), index, order, visitor, visit_dynamic); #[allow(unused)] fn visit_dynamic(_self: ::core::pin::Pin<&#inner_component_id>, order: sp::TraversalOrder, visitor: sp::ItemVisitorRefMut<'_>, dyn_index: u32) -> sp::VisitChildrenResult { _self.visit_dynamic_children(dyn_index, order, visitor) } } fn get_item_ref(self: ::core::pin::Pin<&Self>, index: u32) -> ::core::pin::Pin> { match &self.get_item_tree().as_slice()[index as usize] { sp::ItemTreeNode::Item { item_array_index, .. } => { Self::item_array()[*item_array_index as usize].apply_pin(self) } sp::ItemTreeNode::DynamicTree { .. } => panic!("get_item_ref called on dynamic tree"), } } fn get_item_tree( self: ::core::pin::Pin<&Self>) -> sp::Slice<'_, sp::ItemTreeNode> { Self::item_tree().into() } fn get_subtree_range( self: ::core::pin::Pin<&Self>, index: u32) -> sp::IndexRange { self.subtree_range(index) } fn get_subtree( self: ::core::pin::Pin<&Self>, index: u32, subtree_index: usize, result: &mut sp::ItemTreeWeak) { self.subtree_component(index, subtree_index, result); } fn subtree_index( self: ::core::pin::Pin<&Self>) -> usize { self.index_property() } fn parent_node(self: ::core::pin::Pin<&Self>, _result: &mut sp::ItemWeak) { #parent_item_expression } fn embed_component(self: ::core::pin::Pin<&Self>, _parent_component: &sp::ItemTreeWeak, _item_tree_index: u32) -> bool { #embedding_function } fn layout_info(self: ::core::pin::Pin<&Self>, orientation: sp::Orientation) -> sp::LayoutInfo { self.layout_info(orientation) } fn item_geometry(self: ::core::pin::Pin<&Self>, index: u32) -> sp::LogicalRect { self.item_geometry(index) } fn accessible_role(self: ::core::pin::Pin<&Self>, index: u32) -> sp::AccessibleRole { self.accessible_role(index) } fn accessible_string_property( self: ::core::pin::Pin<&Self>, index: u32, what: sp::AccessibleStringProperty, result: &mut sp::SharedString, ) -> bool { if let Some(r) = self.accessible_string_property(index, what) { *result = r; true } else { false } } fn accessibility_action(self: ::core::pin::Pin<&Self>, index: u32, action: &sp::AccessibilityAction) { self.accessibility_action(index, action); } fn supported_accessibility_actions(self: ::core::pin::Pin<&Self>, index: u32) -> sp::SupportedAccessibilityAction { self.supported_accessibility_actions(index) } fn item_element_infos( self: ::core::pin::Pin<&Self>, _index: u32, _result: &mut sp::SharedString, ) -> bool { #element_info_body } fn window_adapter( self: ::core::pin::Pin<&Self>, do_create: bool, result: &mut sp::Option>, ) { if do_create { *result = sp::Some(self.globals.get().unwrap().window_adapter_impl()); } else { *result = self.globals.get().unwrap().maybe_window_adapter_impl(); } } } ) } fn generate_repeated_component( repeated: &llr::RepeatedElement, unit: &llr::CompilationUnit, parent_ctx: ParentCtx, ) -> TokenStream { let component = generate_item_tree(&repeated.sub_tree, unit, Some(parent_ctx), repeated.index_prop, false); let ctx = EvaluationContext { compilation_unit: unit, current_sub_component: Some(repeated.sub_tree.root), current_global: None, generator_state: RustGeneratorContext { global_access: quote!(_self) }, parent: Some(parent_ctx), argument_types: &[], }; let root_sc = &unit.sub_components[repeated.sub_tree.root]; let inner_component_id = self::inner_component_id(root_sc); let extra_fn = if let Some(listview) = &repeated.listview { let p_y = access_member(&listview.prop_y, &ctx).unwrap(); let p_height = access_member(&listview.prop_height, &ctx).unwrap(); quote! { fn listview_layout( self: ::core::pin::Pin<&Self>, offset_y: &mut sp::LogicalLength, ) -> sp::LogicalLength { let _self = self; #p_y.set(*offset_y); *offset_y += #p_height.get(); sp::LogicalLength::new(self.as_ref().layout_info(sp::Orientation::Horizontal).min) } } } else { // TODO: we could generate this code only if we know that this component is in a box layout quote! { fn box_layout_data(self: ::core::pin::Pin<&Self>, o: sp::Orientation) -> sp::BoxLayoutCellData { sp::BoxLayoutCellData { constraint: self.as_ref().layout_info(o) } } } }; let data_type = if let Some(data_prop) = repeated.data_prop { rust_primitive_type(&root_sc.properties[data_prop].ty).unwrap() } else { quote!(()) }; let access_prop = |property_index| { access_member( &llr::PropertyReference::Local { sub_component_path: vec![], property_index }, &ctx, ) .unwrap() }; let index_prop = repeated.index_prop.into_iter().map(access_prop); let set_data_expr = repeated.data_prop.into_iter().map(|property_index| { let prop_type = ctx.property_ty(&llr::PropertyReference::Local { sub_component_path: vec![], property_index, }); let data_prop = access_prop(property_index); let value_tokens = set_primitive_property_value(prop_type, quote!(_data)); quote!(#data_prop.set(#value_tokens);) }); quote!( #component impl sp::RepeatedItemTree for #inner_component_id { type Data = #data_type; fn update(&self, _index: usize, _data: Self::Data) { let self_rc = self.self_weak.get().unwrap().upgrade().unwrap(); let _self = self_rc.as_pin_ref(); #(#index_prop.set(_index as _);)* #(#set_data_expr)* } fn init(&self) { let self_rc = self.self_weak.get().unwrap().upgrade().unwrap(); #inner_component_id::user_init( sp::VRcMapped::map(self_rc, |x| x), ); } #extra_fn } ) } /// Return an identifier suitable for this component for internal use fn inner_component_id(component: &llr::SubComponent) -> proc_macro2::Ident { format_ident!("Inner{}", ident(&component.name)) } fn internal_popup_id(index: usize) -> proc_macro2::Ident { let mut name = index.to_string(); name.insert_str(0, "popup_id_"); ident(&name) } fn global_inner_name(g: &llr::GlobalComponent) -> TokenStream { if g.is_builtin { let i = ident(&g.name); quote!(sp::#i) } else { let i = format_ident!("Inner{}", ident(&g.name)); quote!(#i) } } fn property_set_value_tokens( property: &llr::PropertyReference, value_tokens: TokenStream, ctx: &EvaluationContext, ) -> TokenStream { let prop = access_member(property, ctx); let prop_type = ctx.property_ty(property); let value_tokens = set_primitive_property_value(prop_type, value_tokens); if let Some((animation, map)) = &ctx.property_info(property).animation { let mut animation = (*animation).clone(); map.map_expression(&mut animation); let animation_tokens = compile_expression(&animation, ctx); return prop .then(|prop| quote!(#prop.set_animated_value(#value_tokens as _, #animation_tokens))); } prop.then(|prop| quote!(#prop.set(#value_tokens as _))) } /// Returns the code that can access the given property or callback fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) -> MemberAccess { fn in_native_item( ctx: &EvaluationContext, sub_component_path: &[llr::SubComponentInstanceIdx], item_index: llr::ItemInstanceIdx, prop_name: &str, path: TokenStream, ) -> (TokenStream, Option) { let (compo_path, sub_component) = follow_sub_component_path( ctx.compilation_unit, ctx.current_sub_component.unwrap(), sub_component_path, ); let component_id = inner_component_id(sub_component); let item_name = ident(&sub_component.items[item_index].name); let item_field = access_component_field_offset(&component_id, &item_name); if prop_name.is_empty() { // then this is actually a reference to the element itself (quote!((#compo_path #item_field).apply_pin(#path)), None) } else if matches!( sub_component.items[item_index].ty.lookup_property(prop_name), Some(&Type::Function(..)) ) { let property_name = ident(prop_name); (quote!((#compo_path #item_field).apply_pin(#path)), Some(quote!(.#property_name))) } else { let property_name = ident(prop_name); let item_ty = ident(&sub_component.items[item_index].ty.class_name); ( quote!((#compo_path #item_field + sp::#item_ty::FIELD_OFFSETS.#property_name).apply_pin(#path)), None, ) } } match reference { llr::PropertyReference::Local { sub_component_path, property_index } => { if let Some(sub_component) = ctx.current_sub_component { let (compo_path, sub_component) = follow_sub_component_path( ctx.compilation_unit, sub_component, sub_component_path, ); let component_id = inner_component_id(sub_component); let property_name = ident(&sub_component.properties[*property_index].name); let property_field = access_component_field_offset(&component_id, &property_name); MemberAccess::Direct(quote!((#compo_path #property_field).apply_pin(_self))) } else if let Some(current_global) = ctx.current_global() { let global_name = global_inner_name(current_global); let property_name = ident(¤t_global.properties[*property_index].name); let property_field = quote!({ *&#global_name::FIELD_OFFSETS.#property_name }); MemberAccess::Direct(quote!(#property_field.apply_pin(_self))) } else { unreachable!() } } llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => { let (a, b) = in_native_item(ctx, sub_component_path, *item_index, prop_name, quote!(_self)); MemberAccess::Direct(quote!(#a #b)) } llr::PropertyReference::InParent { level, parent_reference } => { let mut path = quote!(_self.parent.upgrade()); let mut ctx = ctx.parent.as_ref().unwrap().ctx; for _ in 1..level.get() { path = quote!(#path.and_then(|x| x.parent.upgrade())); ctx = ctx.parent.as_ref().unwrap().ctx; } match &**parent_reference { llr::PropertyReference::Local { sub_component_path, property_index } => { let sub_component = ctx.current_sub_component.unwrap(); let (compo_path, sub_component) = follow_sub_component_path( ctx.compilation_unit, sub_component, sub_component_path, ); let component_id = inner_component_id(sub_component); let property_name = ident(&sub_component.properties[*property_index].name); MemberAccess::Option( quote!((#path.as_ref().map(|x| (#compo_path #component_id::FIELD_OFFSETS.#property_name).apply_pin(x.as_pin_ref())))), ) } llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name, } => { let (a, b) = in_native_item( ctx, sub_component_path, *item_index, prop_name, quote!(x.as_pin_ref()), ); let opt = quote!(#path.as_ref().map(|x| #a)); match b { None => MemberAccess::Option(opt), Some(b) => MemberAccess::OptionFn(opt, quote!(|x| x #b)), } } llr::PropertyReference::Function { sub_component_path, function_index } => { let mut sub_component = ctx.current_sub_component().unwrap(); let mut compo_path = quote!(x.as_pin_ref()); for i in sub_component_path { let component_id = inner_component_id(sub_component); let sub_component_name = ident(&sub_component.sub_components[*i].name); compo_path = quote!( #component_id::FIELD_OFFSETS.#sub_component_name.apply_pin(#compo_path)); sub_component = &ctx.compilation_unit.sub_components [sub_component.sub_components[*i].ty]; } let fn_id = ident(&format!("fn_{}", sub_component.functions[*function_index].name)); MemberAccess::OptionFn(path, quote!(|x| #compo_path.#fn_id)) } llr::PropertyReference::InParent { .. } | llr::PropertyReference::Global { .. } | llr::PropertyReference::GlobalFunction { .. } => { unreachable!() } } } llr::PropertyReference::Global { global_index, property_index } => { let global_access = &ctx.generator_state.global_access; let global = &ctx.compilation_unit.globals[*global_index]; let global_id = format_ident!("global_{}", ident(&global.name)); let global_name = global_inner_name(global); let property_name = ident( &ctx.compilation_unit.globals[*global_index].properties[*property_index].name, ); MemberAccess::Direct( quote!(#global_name::FIELD_OFFSETS.#property_name.apply_pin(#global_access.#global_id.as_ref())), ) } llr::PropertyReference::Function { sub_component_path, function_index } => { if let Some(mut sub_component) = ctx.current_sub_component() { let mut compo_path = quote!(_self); for i in sub_component_path { let component_id = inner_component_id(sub_component); let sub_component_name = ident(&sub_component.sub_components[*i].name); compo_path = quote!( #component_id::FIELD_OFFSETS.#sub_component_name.apply_pin(#compo_path)); sub_component = &ctx.compilation_unit.sub_components[sub_component.sub_components[*i].ty]; } let fn_id = ident(&format!("fn_{}", sub_component.functions[*function_index].name)); MemberAccess::Direct(quote!(#compo_path.#fn_id)) } else if let Some(current_global) = ctx.current_global() { let fn_id = ident(&format!("fn_{}", current_global.functions[*function_index].name)); MemberAccess::Direct(quote!(_self.#fn_id)) } else { unreachable!() } } llr::PropertyReference::GlobalFunction { global_index, function_index } => { let global_access = &ctx.generator_state.global_access; let global = &ctx.compilation_unit.globals[*global_index]; let global_id = format_ident!("global_{}", ident(&global.name)); let fn_id = ident(&format!( "fn_{}", ctx.compilation_unit.globals[*global_index].functions[*function_index].name )); MemberAccess::Direct(quote!(#global_access.#global_id.as_ref().#fn_id)) } } } /// Helper to access a member property/callback of a component. /// /// Because the parent can be deleted (issue #3464), this might be an option when accessing the parent #[derive(Clone)] enum MemberAccess { /// The token stream is just an expression to the member Direct(TokenStream), /// The token stream is a an expression to an option of the member Option(TokenStream), /// the first token stream is an option, and the second is a path to the function in a `.map` and it must be called OptionFn(TokenStream, TokenStream), } impl MemberAccess { /// Used for code that is meant to return `()` fn then(self, f: impl FnOnce(TokenStream) -> TokenStream) -> TokenStream { match self { MemberAccess::Direct(t) => f(t), MemberAccess::Option(t) => { let r = f(quote!(x)); quote!({ let _ = #t.map(|x| #r); }) } MemberAccess::OptionFn(opt, inner) => { let r = f(inner); quote!({ let _ = #opt.as_ref().map(#r); }) } } } fn map_or_default(self, f: impl FnOnce(TokenStream) -> TokenStream) -> TokenStream { match self { MemberAccess::Direct(t) => f(t), MemberAccess::Option(t) => { let r = f(quote!(x)); quote!(#t.map(|x| #r).unwrap_or_default()) } MemberAccess::OptionFn(opt, inner) => { let r = f(inner); quote!(#opt.as_ref().map(#r).unwrap_or_default()) } } } fn get_property(self) -> TokenStream { match self { MemberAccess::Direct(t) => quote!(#t.get()), MemberAccess::Option(t) => { quote!(#t.map(|x| x.get()).unwrap_or_default()) } MemberAccess::OptionFn(..) => panic!("function is not a property"), } } /// To be used when we know that the reference was local #[track_caller] fn unwrap(&self) -> TokenStream { match self { MemberAccess::Direct(t) => quote!(#t), _ => panic!("not a local property?"), } } } fn follow_sub_component_path<'a>( compilation_unit: &'a llr::CompilationUnit, root: llr::SubComponentIdx, sub_component_path: &[llr::SubComponentInstanceIdx], ) -> (TokenStream, &'a llr::SubComponent) { let mut compo_path = quote!(); let mut sub_component = &compilation_unit.sub_components[root]; for i in sub_component_path { let component_id = inner_component_id(sub_component); let sub_component_name = ident(&sub_component.sub_components[*i].name); compo_path = quote!(#compo_path {#component_id::FIELD_OFFSETS.#sub_component_name} +); sub_component = &compilation_unit.sub_components[sub_component.sub_components[*i].ty]; } (compo_path, sub_component) } fn access_window_adapter_field(ctx: &EvaluationContext) -> TokenStream { let global_access = &ctx.generator_state.global_access; quote!((&#global_access.window_adapter_impl())) } /// Given a property reference to a native item (eg, the property name is empty) /// return tokens to the `ItemRc` fn access_item_rc(pr: &llr::PropertyReference, ctx: &EvaluationContext) -> TokenStream { let mut ctx = ctx; let mut component_access_tokens = quote!(_self); let pr = match pr { llr::PropertyReference::InParent { level, parent_reference } => { for _ in 0..level.get() { component_access_tokens = quote!(#component_access_tokens.parent.upgrade().unwrap().as_pin_ref()); ctx = ctx.parent.as_ref().unwrap().ctx; } parent_reference } other => other, }; match pr { llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name: _ } => { let root = ctx.current_sub_component().unwrap(); let mut sub_component = root; for i in sub_component_path { let sub_component_name = ident(&sub_component.sub_components[*i].name); component_access_tokens = quote!(#component_access_tokens . #sub_component_name); sub_component = &ctx.compilation_unit.sub_components[sub_component.sub_components[*i].ty]; } let component_rc_tokens = quote!(sp::VRcMapped::origin(&#component_access_tokens.self_weak.get().unwrap().upgrade().unwrap())); let item_index_in_tree = sub_component.items[*item_index].index_in_tree; let item_index_tokens = if item_index_in_tree == 0 { quote!(#component_access_tokens.tree_index.get()) } else { quote!(#component_access_tokens.tree_index_of_first_child.get() + #item_index_in_tree - 1) }; quote!(&sp::ItemRc::new(#component_rc_tokens, #item_index_tokens)) } _ => unreachable!(), } } fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream { match expr { Expression::StringLiteral(s) => { let s = s.as_str(); quote!(sp::SharedString::from(#s)) } Expression::NumberLiteral(n) if n.is_finite() => quote!(#n), Expression::NumberLiteral(_) => quote!(0.), Expression::BoolLiteral(b) => quote!(#b), Expression::Cast { from, to } => { let f = compile_expression(from, ctx); match (from.ty(ctx), to) { (Type::Float32, Type::Int32) => { quote!((#f as i32)) } (from, Type::String) if from.as_unit_product().is_some() => { quote!(sp::shared_string_from_number((#f) as f64)) } (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => { quote!(sp::ModelRc::new(#f.max(::core::default::Default::default()) as usize)) } (Type::Float32, Type::Color) => { quote!(sp::Color::from_argb_encoded(#f as u32)) } (Type::Color, Type::Brush) => { quote!(slint::Brush::SolidColor(#f)) } (Type::Brush, Type::Color) => { quote!(#f.color()) } (Type::Struct (lhs), Type::Struct (rhs)) if rhs.name.is_some() => { let fields = lhs.fields.iter().enumerate().map(|(index, (name, _))| { let index = proc_macro2::Literal::usize_unsuffixed(index); let name = ident(name); quote!(the_struct.#name = obj.#index as _;) }); let id = struct_name_to_tokens(rhs.name.as_ref().unwrap()); quote!({ let obj = #f; let mut the_struct = #id::default(); #(#fields)* the_struct }) } (Type::Array(..), Type::PathData) if matches!( from.as_ref(), Expression::Array { element_ty: Type::Struct { .. }, .. } ) => { let path_elements = match from.as_ref() { Expression::Array { element_ty: _, values, as_model: _ } => values .iter() .map(|path_elem_expr| // Close{} is a struct with no fields in markup, and PathElement::Close has no fields if matches!(path_elem_expr, Expression::Struct { ty, .. } if ty.fields.is_empty()) { quote!(sp::PathElement::Close) } else { compile_expression(path_elem_expr, ctx) } ), _ => { unreachable!() } }; quote!(sp::PathData::Elements(sp::SharedVector::<_>::from_slice(&[#((#path_elements).into()),*]))) } (Type::Struct { .. }, Type::PathData) if matches!(from.as_ref(), Expression::Struct { .. }) => { let (events, points) = match from.as_ref() { Expression::Struct { ty: _, values } => ( compile_expression(&values["events"], ctx), compile_expression(&values["points"], ctx), ), _ => { unreachable!() } }; quote!(sp::PathData::Events(sp::SharedVector::<_>::from_slice(&#events), sp::SharedVector::<_>::from_slice(&#points))) } (Type::String, Type::PathData) => { quote!(sp::PathData::Commands(#f)) } (_, Type::Void) => { quote!(#f;) } _ => f, } } Expression::PropertyReference(nr) => { let access = access_member(nr, ctx); let prop_type = ctx.property_ty(nr); primitive_property_value(prop_type, access) } Expression::BuiltinFunctionCall { function, arguments } => { compile_builtin_function_call(function.clone(), arguments, ctx) } Expression::CallBackCall { callback, arguments } => { let f = access_member(callback, ctx); let a = arguments.iter().map(|a| compile_expression(a, ctx)); if expr.ty(ctx) == Type::Void { f.then(|f| quote!(#f.call(&(#(#a as _,)*)))) } else { f.map_or_default(|f| quote!(#f.call(&(#(#a as _,)*)))) } } Expression::FunctionCall { function, arguments } => { let a = arguments.iter().map(|a| compile_expression(a, ctx)); let f = access_member(function, ctx); if expr.ty(ctx) == Type::Void { f.then(|f| quote!(#f( #(#a as _),*))) } else { f.map_or_default(|f| quote!(#f( #(#a as _),*))) } } Expression::ItemMemberFunctionCall { function } => { let fun = access_member(function, ctx); let item_rc = access_item_rc(function, ctx); let window_adapter_tokens = access_window_adapter_field(ctx); fun.map_or_default(|fun| quote!(#fun(#window_adapter_tokens, #item_rc))) } Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => { let f = ident(function); let a = arguments.iter().map(|a| { let arg = compile_expression(a, ctx); if matches!(a.ty(ctx), Type::Struct { .. }) { quote!(&#arg) } else { arg } }); quote! { sp::#f(#(#a as _),*) } } Expression::FunctionParameterReference { index } => { let i = proc_macro2::Literal::usize_unsuffixed(*index); quote! {args.#i.clone()} } Expression::StructFieldAccess { base, name } => match base.ty(ctx) { Type::Struct (s) if s.name.is_none() => { let index = s.fields .keys() .position(|k| k == name) .expect("Expression::StructFieldAccess: Cannot find a key in an object"); let index = proc_macro2::Literal::usize_unsuffixed(index); let base_e = compile_expression(base, ctx); quote!((#base_e).#index ) } Type::Struct { .. } => { let name = ident(name); let base_e = compile_expression(base, ctx); quote!((#base_e).#name) } _ => panic!("Expression::StructFieldAccess's base expression is not an Object type"), }, Expression::ArrayIndex { array, index } => { debug_assert!(matches!(array.ty(ctx), Type::Array(_))); let base_e = compile_expression(array, ctx); let index_e = compile_expression(index, ctx); quote!(match &#base_e { x => { let index = (#index_e) as usize; x.row_data_tracked(index).unwrap_or_default() }}) } Expression::CodeBlock(sub) => { let map = sub.iter().map(|e| compile_expression(e, ctx)); quote!({ #(#map);* }) } Expression::PropertyAssignment { property, value } => { let value = compile_expression(value, ctx); property_set_value_tokens(property, value, ctx) } Expression::ModelDataAssignment { level, value } => { let value = compile_expression(value, ctx); let mut path = quote!(_self); let mut ctx2 = ctx; let mut repeater_index = None; for _ in 0..=*level { let x = ctx2.parent.unwrap(); ctx2 = x.ctx; repeater_index = x.repeater_index; path = quote!(#path.parent.upgrade().unwrap()); } let repeater_index = repeater_index.unwrap(); let mut index_prop = llr::PropertyReference::Local { sub_component_path: vec![], property_index: ctx2.current_sub_component().unwrap().repeated[repeater_index].index_prop.unwrap(), }; if let Some(level) = NonZeroUsize::new(*level) { index_prop = llr::PropertyReference::InParent { level, parent_reference: index_prop.into() }; } let index_access = access_member(&index_prop, ctx).get_property(); let repeater = access_component_field_offset( &inner_component_id(ctx2.current_sub_component().unwrap()), &format_ident!("repeater{}", usize::from(repeater_index)), ); quote!(#repeater.apply_pin(#path.as_pin_ref()).model_set_row_data(#index_access as _, #value as _)) } Expression::ArrayIndexAssignment { array, index, value } => { debug_assert!(matches!(array.ty(ctx), Type::Array(_))); let base_e = compile_expression(array, ctx); let index_e = compile_expression(index, ctx); let value_e = compile_expression(value, ctx); quote!((#base_e).set_row_data(#index_e as isize as usize, #value_e as _)) } Expression::BinaryExpression { lhs, rhs, op } => { let lhs_ty = lhs.ty(ctx); let lhs = compile_expression(lhs, ctx); let rhs = compile_expression(rhs, ctx); if lhs_ty.as_unit_product().is_some() && (*op == '=' || *op == '!') { let maybe_negate = if *op == '!' { quote!(!) } else { quote!() }; quote!(#maybe_negate sp::ApproxEq::::approx_eq(&(#lhs as f64), &(#rhs as f64))) } else { let (conv1, conv2) = match crate::expression_tree::operator_class(*op) { OperatorClass::ArithmeticOp => match lhs_ty { Type::String => (None, Some(quote!(.as_str()))), Type::Struct { .. } => (None, None), _ => (Some(quote!(as f64)), Some(quote!(as f64))), }, OperatorClass::ComparisonOp if matches!( lhs_ty, Type::Int32 | Type::Float32 | Type::Duration | Type::PhysicalLength | Type::LogicalLength | Type::Angle | Type::Percent | Type::Rem ) => { (Some(quote!(as f64)), Some(quote!(as f64))) } _ => (None, None), }; let op = match op { '=' => quote!(==), '!' => quote!(!=), '≤' => quote!(<=), '≥' => quote!(>=), '&' => quote!(&&), '|' => quote!(||), _ => proc_macro2::TokenTree::Punct(proc_macro2::Punct::new( *op, proc_macro2::Spacing::Alone, )) .into(), }; quote!( ((#lhs #conv1 ) #op (#rhs #conv2)) ) } } Expression::UnaryOp { sub, op } => { let sub = compile_expression(sub, ctx); if *op == '+' { // there is no unary '+' in rust return sub; } let op = proc_macro2::Punct::new(*op, proc_macro2::Spacing::Alone); quote!( (#op #sub) ) } Expression::ImageReference { resource_ref, nine_slice } => { let image = match resource_ref { crate::expression_tree::ImageReference::None => { quote!(sp::Image::default()) } crate::expression_tree::ImageReference::AbsolutePath(path) => { let path = path.as_str(); quote!(sp::Image::load_from_path(::std::path::Path::new(#path)).unwrap_or_default()) } crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); let format = proc_macro2::Literal::byte_string(extension.as_bytes()); quote!(sp::load_image_from_embedded_data(#symbol.into(), sp::Slice::from_slice(#format))) } crate::expression_tree::ImageReference::EmbeddedTexture { resource_id } => { let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); quote!( sp::Image::from(sp::ImageInner::StaticTextures(&#symbol)) ) } }; match &nine_slice { Some([a, b, c, d]) => { quote! {{ let mut image = #image; image.set_nine_slice_edges(#a, #b, #c, #d); image }} } None => image, } } Expression::Condition { condition, true_expr, false_expr } => { let condition_code = compile_expression(condition, ctx); let true_code = compile_expression(true_expr, ctx); let false_code = compile_expression(false_expr, ctx); let semi = if false_expr.ty(ctx) == Type::Void { quote!(;) } else { quote!(as _) }; quote!( if #condition_code { (#true_code) #semi } else { #false_code } ) } Expression::Array { values, element_ty, as_model } => { let val = values.iter().map(|e| compile_expression(e, ctx)); if *as_model { let rust_element_ty = rust_primitive_type(element_ty).unwrap(); quote!(sp::ModelRc::new( sp::VecModel::<#rust_element_ty>::from( sp::vec![#(#val as _),*] ) )) } else { quote!(sp::Slice::from_slice(&[#(#val),*])) } } Expression::Struct { ty, values } => { let elem = ty.fields.keys().map(|k| values.get(k).map(|e| compile_expression(e, ctx))); if let Some(name) = &ty.name { let name_tokens: TokenStream = struct_name_to_tokens(name.as_str()); let keys = ty.fields.keys().map(|k| ident(k)); if name.starts_with("slint::private_api::") && name.ends_with("LayoutData") { quote!(#name_tokens{#(#keys: #elem as _,)*}) } else { quote!({ let mut the_struct = #name_tokens::default(); #(the_struct.#keys = #elem as _;)* the_struct}) } } else { let as_ = ty.fields.values().map(|t| { if t.as_unit_product().is_some() { // number needs to be converted to the right things because intermediate // result might be f64 and that's usually not what the type of the tuple is in the end let t = rust_primitive_type(t).unwrap(); quote!(as #t) } else { quote!() } }); // This will produce a tuple quote!((#(#elem #as_,)*)) } } Expression::StoreLocalVariable { name, value } => { let value = compile_expression(value, ctx); let name = ident(name); quote!(let #name = #value;) } Expression::ReadLocalVariable { name, .. } => { let name = ident(name); quote!(#name.clone()) } Expression::EasingCurve(EasingCurve::Linear) => { quote!(sp::EasingCurve::Linear) } Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => { quote!(sp::EasingCurve::CubicBezier([#a, #b, #c, #d])) } Expression::EasingCurve(EasingCurve::EaseInElastic) => { quote!(sp::EasingCurve::EaseInElastic) } Expression::EasingCurve(EasingCurve::EaseOutElastic) => { quote!(sp::EasingCurve::EaseOutElastic) } Expression::EasingCurve(EasingCurve::EaseInOutElastic) => { quote!(sp::EasingCurve::EaseInOutElastic) } Expression::EasingCurve(EasingCurve::EaseInBounce) => { quote!(sp::EasingCurve::EaseInBounce) } Expression::EasingCurve(EasingCurve::EaseOutBounce) => { quote!(sp::EasingCurve::EaseOutBounce) } Expression::EasingCurve(EasingCurve::EaseInOutBounce) => { quote!(sp::EasingCurve::EaseInOutBounce) } Expression::LinearGradient { angle, stops } => { let angle = compile_expression(angle, ctx); let stops = stops.iter().map(|(color, stop)| { let color = compile_expression(color, ctx); let position = compile_expression(stop, ctx); quote!(sp::GradientStop{ color: #color, position: #position as _ }) }); quote!(slint::Brush::LinearGradient( sp::LinearGradientBrush::new(#angle as _, [#(#stops),*]) )) } Expression::RadialGradient { stops } => { let stops = stops.iter().map(|(color, stop)| { let color = compile_expression(color, ctx); let position = compile_expression(stop, ctx); quote!(sp::GradientStop{ color: #color, position: #position as _ }) }); quote!(slint::Brush::RadialGradient( sp::RadialGradientBrush::new_circle([#(#stops),*]) )) } Expression::EnumerationValue(value) => { let base_ident = ident(&value.enumeration.name); let value_ident = ident(&value.to_pascal_case()); if value.enumeration.node.is_some() { quote!(#base_ident::#value_ident) } else { quote!(sp::#base_ident::#value_ident) } } Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { access_member(layout_cache_prop, ctx).map_or_default(|cache| { if let Some(ri) = repeater_index { let offset = compile_expression(ri, ctx); quote!({ let cache = #cache.get(); *cache.get((cache[#index] as usize) + #offset as usize * 2).unwrap_or(&(0 as sp::Coord)) }) } else { quote!(#cache.get()[#index]) } }) } Expression::BoxLayoutFunction { cells_variable, repeater_indices, elements, orientation, sub_expression, } => box_layout_function( cells_variable, repeater_indices.as_ref().map(SmolStr::as_str), elements.as_ref(), *orientation, sub_expression, ctx, ), Expression::ComputeDialogLayoutCells { cells_variable, roles, unsorted_cells } => { let cells_variable = ident(cells_variable); let roles = compile_expression(roles, ctx); let cells = match &**unsorted_cells { Expression::Array { values, .. } => { values.iter().map(|v| compile_expression(v, ctx)) } _ => panic!("dialog layout unsorted cells not an array"), }; quote! { let mut #cells_variable = [#(#cells),*]; sp::reorder_dialog_button_layout(&mut #cells_variable, &#roles); let #cells_variable = sp::Slice::from_slice(&#cells_variable); } } Expression::MinMax { ty, op, lhs, rhs } => { let t = rust_primitive_type(ty); let wrap = |expr| match &t { Some(t) => quote!((#expr as #t)), None => expr, }; let lhs = wrap(compile_expression(lhs, ctx)); let rhs = wrap(compile_expression(rhs, ctx)); match op { MinMaxOp::Min => { quote!(#lhs.min(#rhs)) } MinMaxOp::Max => { quote!(#lhs.max(#rhs)) } } } Expression::EmptyComponentFactory => quote!(slint::ComponentFactory::default()), Expression::TranslationReference { format_args, string_index, plural } => { let args = compile_expression(format_args, ctx); match plural { Some(plural) => { let plural = compile_expression(plural, ctx); quote!(sp::translate_from_bundle_with_plural( &self::_SLINT_TRANSLATED_STRINGS_PLURALS[#string_index], &self::_SLINT_TRANSLATED_PLURAL_RULES, sp::Slice::::from(#args).as_slice(), #plural as _ )) } None => quote!(sp::translate_from_bundle(&self::_SLINT_TRANSLATED_STRINGS[#string_index], sp::Slice::::from(#args).as_slice())), } }, } } fn compile_builtin_function_call( function: BuiltinFunction, arguments: &[Expression], ctx: &EvaluationContext, ) -> TokenStream { let mut a = arguments.iter().map(|a| compile_expression(a, ctx)); match function { BuiltinFunction::SetFocusItem => { if let [Expression::PropertyReference(pr)] = arguments { let window_tokens = access_window_adapter_field(ctx); let focus_item = access_item_rc(pr, ctx); quote!( sp::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item, true, sp::FocusReason::Programmatic) ) } else { panic!("internal error: invalid args to SetFocusItem {arguments:?}") } } BuiltinFunction::ClearFocusItem => { if let [Expression::PropertyReference(pr)] = arguments { let window_tokens = access_window_adapter_field(ctx); let focus_item = access_item_rc(pr, ctx); quote!( sp::WindowInner::from_pub(#window_tokens.window()).set_focus_item(#focus_item, false, sp::FocusReason::Programmatic) ) } else { panic!("internal error: invalid args to ClearFocusItem {arguments:?}") } } BuiltinFunction::ShowPopupWindow => { if let [Expression::NumberLiteral(popup_index), close_policy, Expression::PropertyReference(parent_ref)] = arguments { let mut parent_ctx = ctx; let mut component_access_tokens = quote!(_self); if let llr::PropertyReference::InParent { level, .. } = parent_ref { for _ in 0..level.get() { component_access_tokens = quote!(#component_access_tokens.parent.upgrade().unwrap().as_pin_ref()); parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx; } } let current_sub_component = parent_ctx.current_sub_component().unwrap(); let popup = ¤t_sub_component.popup_windows[*popup_index as usize]; let popup_window_id = inner_component_id(&ctx.compilation_unit.sub_components[popup.item_tree.root]); let parent_component = access_item_rc(parent_ref, ctx); let popup_ctx = EvaluationContext::new_sub_component( ctx.compilation_unit, popup.item_tree.root, RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()) }, Some(ParentCtx::new(ctx, None)), ); let position = compile_expression(&popup.position.borrow(), &popup_ctx); let close_policy = compile_expression(close_policy, ctx); let window_adapter_tokens = access_window_adapter_field(ctx); let popup_id_name = internal_popup_id(*popup_index as usize); quote!({ let popup_instance = #popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).unwrap(); let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x); let position = { let _self = popup_instance_vrc.as_pin_ref(); #position }; if let Some(current_id) = #component_access_tokens.#popup_id_name.take() { sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup(current_id); } #component_access_tokens.#popup_id_name.set(Some( sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup( &sp::VRc::into_dyn(popup_instance.into()), position, #close_policy, #parent_component, false, // is_menu )) ); #popup_window_id::user_init(popup_instance_vrc.clone()); }) } else { panic!("internal error: invalid args to ShowPopupWindow {arguments:?}") } } BuiltinFunction::ClosePopupWindow => { if let [Expression::NumberLiteral(popup_index), Expression::PropertyReference(parent_ref)] = arguments { let mut parent_ctx = ctx; let mut component_access_tokens = quote!(_self); if let llr::PropertyReference::InParent { level, .. } = parent_ref { for _ in 0..level.get() { component_access_tokens = quote!(#component_access_tokens.parent.upgrade().unwrap().as_pin_ref()); parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx; } } let window_adapter_tokens = access_window_adapter_field(ctx); let popup_id_name = internal_popup_id(*popup_index as usize); quote!( if let Some(current_id) = #component_access_tokens.#popup_id_name.take() { sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup(current_id); } ) } else { panic!("internal error: invalid args to ClosePopupWindow {arguments:?}") } } BuiltinFunction::ShowPopupMenu => { let [Expression::PropertyReference(context_menu_ref), entries, position] = arguments else { panic!("internal error: invalid args to ShowPopupMenu {arguments:?}") }; let context_menu = access_member(context_menu_ref, ctx); let context_menu_rc = access_item_rc(context_menu_ref, ctx); let position = compile_expression(position, ctx); let popup = ctx .compilation_unit .popup_menu .as_ref() .expect("there should be a popup menu if we want to show it"); let popup_id = inner_component_id(&ctx.compilation_unit.sub_components[popup.item_tree.root]); let window_adapter_tokens = access_window_adapter_field(ctx); let popup_ctx = EvaluationContext::new_sub_component( ctx.compilation_unit, popup.item_tree.root, RustGeneratorContext { global_access: quote!(_self.globals.get().unwrap()) }, None, ); let access_entries = access_member(&popup.entries, &popup_ctx).unwrap(); let access_sub_menu = access_member(&popup.sub_menu, &popup_ctx).unwrap(); let access_activated = access_member(&popup.activated, &popup_ctx).unwrap(); let access_close = access_member(&popup.close, &popup_ctx).unwrap(); let close_popup = context_menu.clone().then(|context_menu| quote!{ if let Some(current_id) = #context_menu.popup_id.take() { sp::WindowInner::from_pub(#window_adapter_tokens.window()).close_popup(current_id); } }); let init_popup = if let Expression::NumberLiteral(tree_index) = entries { // We have an MenuItem tree let current_sub_component = ctx.current_sub_component().unwrap(); let item_tree_id = inner_component_id( &ctx.compilation_unit.sub_components [current_sub_component.menu_item_trees[*tree_index as usize].root], ); quote!({ let menu_item_tree_instance = #item_tree_id::new(_self.self_weak.get().unwrap().clone()).unwrap(); let context_menu_item_tree = sp::Rc::new(sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance))); let mut entries = sp::SharedVector::default(); sp::Menu::sub_menu(&*context_menu_item_tree, sp::Option::None, &mut entries); let _self = popup_instance_vrc.as_pin_ref(); #access_entries.set(sp::ModelRc::new(sp::SharedVectorModel::from(entries))); let context_menu_item_tree_ = context_menu_item_tree.clone(); #access_sub_menu.set_handler(move |entry| { let mut entries = sp::SharedVector::default(); sp::Menu::sub_menu(&*context_menu_item_tree_, sp::Option::Some(&entry.0), &mut entries); sp::ModelRc::new(sp::SharedVectorModel::from(entries)) }); #access_activated.set_handler(move |entry| { sp::Menu::activate(&*context_menu_item_tree, &entry.0); }); let self_weak = parent_weak.clone(); #access_close.set_handler(move |()| { let Some(self_rc) = self_weak.upgrade() else { return }; let _self = self_rc.as_pin_ref(); #close_popup }); }) } else { // entries should be an expression of type array of MenuEntry debug_assert!( matches!(entries.ty(ctx), Type::Array(ty) if matches!(&*ty, Type::Struct{..})) ); let entries = compile_expression(entries, ctx); let forward_callback = |access, cb| { let call = context_menu .clone() .map_or_default(|context_menu| quote!(#context_menu.#cb.call(entry))); quote!( let self_weak = parent_weak.clone(); #access.set_handler(move |entry| { if let Some(self_rc) = self_weak.upgrade() { let _self = self_rc.as_pin_ref(); #call } else { Default::default() } }); ) }; let fw_sub_menu = forward_callback(access_sub_menu, quote!(sub_menu)); let fw_activated = forward_callback(access_activated, quote!(activated)); quote! { let entries = #entries; { let _self = popup_instance_vrc.as_pin_ref(); #access_entries.set(entries); #fw_sub_menu #fw_activated let self_weak = parent_weak.clone(); #access_close.set_handler(move |()| { let Some(self_rc) = self_weak.upgrade() else { return }; let _self = self_rc.as_pin_ref(); #close_popup }); } } }; let context_menu = context_menu.unwrap(); quote!({ let position = #position; let popup_instance = #popup_id::new(_self.globals.get().unwrap().clone()).unwrap(); let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x); let parent_weak = _self.self_weak.get().unwrap().clone(); #init_popup #close_popup let id = sp::WindowInner::from_pub(#window_adapter_tokens.window()).show_popup( &sp::VRc::into_dyn(popup_instance.into()), position, sp::PopupClosePolicy::CloseOnClickOutside, #context_menu_rc, true, // is_menu ); #context_menu.popup_id.set(Some(id)); #popup_id::user_init(popup_instance_vrc); }) } BuiltinFunction::SetSelectionOffsets => { if let [llr::Expression::PropertyReference(pr), from, to] = arguments { let item = access_member(pr, ctx); let item_rc = access_item_rc(pr, ctx); let window_adapter_tokens = access_window_adapter_field(ctx); let start = compile_expression(from, ctx); let end = compile_expression(to, ctx); item.then(|item| quote!( #item.set_selection_offsets(#window_adapter_tokens, #item_rc, #start as i32, #end as i32) )) } else { panic!("internal error: invalid args to set-selection-offsets {arguments:?}") } } BuiltinFunction::ItemFontMetrics => { if let [Expression::PropertyReference(pr)] = arguments { let item = access_member(pr, ctx); let item_rc = access_item_rc(pr, ctx); let window_adapter_tokens = access_window_adapter_field(ctx); item.then(|item| { quote!( #item.font_metrics(#window_adapter_tokens, #item_rc) ) }) } else { panic!("internal error: invalid args to ItemMemberFunction {arguments:?}") } } BuiltinFunction::ImplicitLayoutInfo(orient) => { if let [Expression::PropertyReference(pr)] = arguments { let item = access_member(pr, ctx); let window_adapter_tokens = access_window_adapter_field(ctx); item.then(|item| { let item_rc = access_item_rc(pr, ctx); quote!( sp::Item::layout_info(#item, #orient, #window_adapter_tokens, &#item_rc) ) }) } else { panic!("internal error: invalid args to ImplicitLayoutInfo {arguments:?}") } } BuiltinFunction::RegisterCustomFontByPath => { if let [Expression::StringLiteral(path)] = arguments { let window_adapter_tokens = access_window_adapter_field(ctx); let path = path.as_str(); quote!(#window_adapter_tokens.renderer().register_font_from_path(&std::path::PathBuf::from(#path)).unwrap()) } else { panic!("internal error: invalid args to RegisterCustomFontByPath {arguments:?}") } } BuiltinFunction::RegisterCustomFontByMemory => { if let [Expression::NumberLiteral(resource_id)] = &arguments { let resource_id: usize = *resource_id as _; let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); let window_adapter_tokens = access_window_adapter_field(ctx); quote!(#window_adapter_tokens.renderer().register_font_from_memory(#symbol.into()).unwrap()) } else { panic!("internal error: invalid args to RegisterCustomFontByMemory {arguments:?}") } } BuiltinFunction::RegisterBitmapFont => { if let [Expression::NumberLiteral(resource_id)] = &arguments { let resource_id: usize = *resource_id as _; let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id); let window_adapter_tokens = access_window_adapter_field(ctx); quote!(#window_adapter_tokens.renderer().register_bitmap_font(&#symbol)) } else { panic!("internal error: invalid args to RegisterBitmapFont must be a number") } } BuiltinFunction::GetWindowScaleFactor => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).scale_factor()) } BuiltinFunction::GetWindowDefaultFontSize => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).window_item().unwrap().as_pin_ref().default_font_size().get()) } BuiltinFunction::AnimationTick => { quote!(sp::animation_tick()) } BuiltinFunction::Debug => quote!(slint::private_unstable_api::debug(#(#a)*)), BuiltinFunction::Mod => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::Euclid::rem_euclid(&(#a1 as f64), &(#a2 as f64))) } BuiltinFunction::Round => quote!((#(#a)* as f64).round()), BuiltinFunction::Ceil => quote!((#(#a)* as f64).ceil()), BuiltinFunction::Floor => quote!((#(#a)* as f64).floor()), BuiltinFunction::Sqrt => quote!((#(#a)* as f64).sqrt()), BuiltinFunction::Abs => quote!((#(#a)* as f64).abs()), BuiltinFunction::Sin => quote!((#(#a)* as f64).to_radians().sin()), BuiltinFunction::Cos => quote!((#(#a)* as f64).to_radians().cos()), BuiltinFunction::Tan => quote!((#(#a)* as f64).to_radians().tan()), BuiltinFunction::ASin => quote!((#(#a)* as f64).asin().to_degrees()), BuiltinFunction::ACos => quote!((#(#a)* as f64).acos().to_degrees()), BuiltinFunction::ATan => quote!((#(#a)* as f64).atan().to_degrees()), BuiltinFunction::ATan2 => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!((#a1 as f64).atan2(#a2 as f64).to_degrees()) } BuiltinFunction::Log => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!((#a1 as f64).log(#a2 as f64)) } BuiltinFunction::Ln => quote!((#(#a)* as f64).ln()), BuiltinFunction::Pow => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!((#a1 as f64).powf(#a2 as f64)) } BuiltinFunction::Exp => quote!((#(#a)* as f64).exp()), BuiltinFunction::ToFixed => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::shared_string_from_number_fixed(#a1 as f64, (#a2 as i32).max(0) as usize)) } BuiltinFunction::ToPrecision => { let (a1, a2) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::shared_string_from_number_precision(#a1 as f64, (#a2 as i32).max(0) as usize)) } BuiltinFunction::StringToFloat => { quote!(#(#a)*.as_str().parse::().unwrap_or_default()) } BuiltinFunction::StringIsFloat => quote!(#(#a)*.as_str().parse::().is_ok()), BuiltinFunction::StringIsEmpty => quote!(#(#a)*.is_empty()), BuiltinFunction::StringCharacterCount => { quote!( sp::UnicodeSegmentation::graphemes(#(#a)*.as_str(), true).count() as i32 ) } BuiltinFunction::StringToLowercase => quote!(sp::SharedString::from(#(#a)*.to_lowercase())), BuiltinFunction::StringToUppercase => quote!(sp::SharedString::from(#(#a)*.to_uppercase())), BuiltinFunction::ColorRgbaStruct => quote!( #(#a)*.to_argb_u8()), BuiltinFunction::ColorHsvaStruct => quote!( #(#a)*.to_hsva()), BuiltinFunction::ColorBrighter => { let x = a.next().unwrap(); let factor = a.next().unwrap(); quote!(#x.brighter(#factor as f32)) } BuiltinFunction::ColorDarker => { let x = a.next().unwrap(); let factor = a.next().unwrap(); quote!(#x.darker(#factor as f32)) } BuiltinFunction::ColorTransparentize => { let x = a.next().unwrap(); let factor = a.next().unwrap(); quote!(#x.transparentize(#factor as f32)) } BuiltinFunction::ColorMix => { let x = a.next().unwrap(); let y = a.next().unwrap(); let factor = a.next().unwrap(); quote!(#x.mix(&#y.into(), #factor as f32)) } BuiltinFunction::ColorWithAlpha => { let x = a.next().unwrap(); let alpha = a.next().unwrap(); quote!(#x.with_alpha(#alpha as f32)) } BuiltinFunction::ImageSize => quote!( #(#a)*.size()), BuiltinFunction::ArrayLength => { quote!(match &#(#a)* { x => { x.model_tracker().track_row_count_changes(); x.row_count() as i32 }}) } BuiltinFunction::Rgb => { let (r, g, b, a) = (a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()); quote!({ let r: u8 = (#r as u32).min(255) as u8; let g: u8 = (#g as u32).min(255) as u8; let b: u8 = (#b as u32).min(255) as u8; let a: u8 = (255. * (#a as f32)).max(0.).min(255.) as u8; sp::Color::from_argb_u8(a, r, g, b) }) } BuiltinFunction::Hsv => { let (h, s, v, a) = (a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()); quote!({ let s: f32 = (#s as f32).max(0.).min(1.) as f32; let v: f32 = (#v as f32).max(0.).min(1.) as f32; let a: f32 = (1. * (#a as f32)).max(0.).min(1.) as f32; sp::Color::from_hsva(#h as f32, s, v, a) }) } BuiltinFunction::ColorScheme => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).color_scheme()) } BuiltinFunction::SupportsNativeMenuBar => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar()) } BuiltinFunction::SetupNativeMenuBar => { let window_adapter_tokens = access_window_adapter_field(ctx); if let [Expression::PropertyReference(entries_r), Expression::PropertyReference(sub_menu_r), Expression::PropertyReference(activated_r), Expression::NumberLiteral(tree_index), Expression::BoolLiteral(no_native)] = arguments { // We have an MenuItem tree let current_sub_component = ctx.current_sub_component().unwrap(); let item_tree_id = inner_component_id( &ctx.compilation_unit.sub_components [current_sub_component.menu_item_trees[*tree_index as usize].root], ); let access_entries = access_member(entries_r, ctx).unwrap(); let access_sub_menu = access_member(sub_menu_r, ctx).unwrap(); let access_activated = access_member(activated_r, ctx).unwrap(); let native_impl = if *no_native { quote!() } else { quote!(if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() { sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(menu_item_tree)); } else) }; quote!({ let menu_item_tree_instance = #item_tree_id::new(_self.self_weak.get().unwrap().clone()).unwrap(); let menu_item_tree = sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance)); #native_impl /*else*/ { let menu_item_tree = sp::Rc::new(menu_item_tree); let menu_item_tree_ = menu_item_tree.clone(); #access_entries.set_binding(move || { let mut entries = sp::SharedVector::default(); sp::Menu::sub_menu(&*menu_item_tree_, sp::Option::None, &mut entries); sp::ModelRc::new(sp::SharedVectorModel::from(entries)) }); let menu_item_tree_ = menu_item_tree.clone(); #access_sub_menu.set_handler(move |entry| { let mut entries = sp::SharedVector::default(); sp::Menu::sub_menu(&*menu_item_tree_, sp::Option::Some(&entry.0), &mut entries); sp::ModelRc::new(sp::SharedVectorModel::from(entries)) }); #access_activated.set_handler(move |entry| { sp::Menu::activate(&*menu_item_tree, &entry.0); }); } }) } else if let [entries, Expression::PropertyReference(sub_menu), Expression::PropertyReference(activated)] = arguments { let entries = compile_expression(entries, ctx); let sub_menu = access_member(sub_menu, ctx).unwrap(); let activated = access_member(activated, ctx).unwrap(); let inner_component_id = self::inner_component_id(ctx.current_sub_component().unwrap()); quote! { if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() { // May seem overkill to have an instance of the struct for each call, but there should only be one call per component anyway struct MenuBarWrapper(sp::VWeakMapped); const _ : () = { use slint::private_unstable_api::re_exports::*; MenuVTable_static!(static VT for MenuBarWrapper); }; impl sp::Menu for MenuBarWrapper { fn sub_menu(&self, parent: sp::Option<&sp::MenuEntry>, result: &mut sp::SharedVector) { let Some(self_rc) = self.0.upgrade() else { return }; let _self = self_rc.as_pin_ref(); let model = match parent { None => #entries, Some(parent) => #sub_menu.call(&(parent.clone(),)) }; *result = model.iter().map(|v| v.try_into().unwrap()).collect(); } fn activate(&self, entry: &sp::MenuEntry) { let Some(self_rc) = self.0.upgrade() else { return }; let _self = self_rc.as_pin_ref(); #activated.call(&(entry.clone(),)) } } sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(sp::VBox::new(MenuBarWrapper(_self.self_weak.get().unwrap().clone()))); } } } else { panic!("internal error: incorrect arguments to SetupNativeMenuBar") } } BuiltinFunction::MonthDayCount => { let (m, y) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::month_day_count(#m as u32, #y as i32).unwrap_or(0)) } BuiltinFunction::MonthOffset => { let (m, y) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::month_offset(#m as u32, #y as i32)) } BuiltinFunction::FormatDate => { let (f, d, m, y) = (a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()); quote!(sp::format_date(&#f, #d as u32, #m as u32, #y as i32)) } BuiltinFunction::ValidDate => { let (d, f) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::parse_date(#d.as_str(), #f.as_str()).is_some()) } BuiltinFunction::ParseDate => { let (d, f) = (a.next().unwrap(), a.next().unwrap()); quote!(sp::ModelRc::new(sp::parse_date(#d.as_str(), #f.as_str()).map(|d| sp::VecModel::from_slice(&d)).unwrap_or_default())) } BuiltinFunction::DateNow => { quote!(sp::ModelRc::new(sp::VecModel::from_slice(&sp::date_now()))) } BuiltinFunction::TextInputFocused => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).text_input_focused()) } BuiltinFunction::SetTextInputFocused => { let window_adapter_tokens = access_window_adapter_field(ctx); quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).set_text_input_focused(#(#a)*)) } BuiltinFunction::Translate => { quote!(slint::private_unstable_api::translate(#((#a) as _),*)) } BuiltinFunction::Use24HourFormat => { quote!(slint::private_unstable_api::use_24_hour_format()) } BuiltinFunction::ItemAbsolutePosition => { if let [Expression::PropertyReference(pr)] = arguments { let item_rc = access_item_rc(pr, ctx); quote!( sp::logical_position_to_api((*#item_rc).map_to_window(::core::default::Default::default())) ) } else { panic!("internal error: invalid args to MapPointToWindow {arguments:?}") } } BuiltinFunction::UpdateTimers => { quote!(_self.update_timers()) } BuiltinFunction::DetectOperatingSystem => { quote!(sp::detect_operating_system()) } } } /// Return a TokenStream for a name (as in [`Type::Struct::name`]) fn struct_name_to_tokens(name: &str) -> TokenStream { // the name match the C++ signature so we need to change that to the rust namespace let mut name = name.replace("slint::private_api::", "sp::").replace('-', "_"); if !name.contains("::") { name.insert_str(0, "r#") } name.parse().unwrap() } fn box_layout_function( cells_variable: &str, repeated_indices: Option<&str>, elements: &[Either], orientation: Orientation, sub_expression: &Expression, ctx: &EvaluationContext, ) -> TokenStream { let repeated_indices = repeated_indices.map(ident); let inner_component_id = self::inner_component_id(ctx.current_sub_component().unwrap()); let mut fixed_count = 0usize; let mut repeated_count = quote!(); let mut push_code = vec![]; let mut repeater_idx = 0usize; for item in elements { match item { Either::Left(value) => { let value = compile_expression(value, ctx); fixed_count += 1; push_code.push(quote!(items_vec.push(#value);)) } Either::Right(repeater) => { let repeater_id = format_ident!("repeater{}", usize::from(*repeater)); let rep_inner_component_id = self::inner_component_id( &ctx.compilation_unit.sub_components [ctx.current_sub_component().unwrap().repeated[*repeater].sub_tree.root], ); repeated_count = quote!(#repeated_count + _self.#repeater_id.len()); let ri = repeated_indices.as_ref().map(|ri| { quote!( #ri[#repeater_idx * 2] = items_vec.len() as u32; #ri[#repeater_idx * 2 + 1] = internal_vec.len() as u32; ) }); repeater_idx += 1; push_code.push(quote!( #inner_component_id::FIELD_OFFSETS.#repeater_id.apply_pin(_self).ensure_updated( || { #rep_inner_component_id::new(_self.self_weak.get().unwrap().clone()).unwrap().into() } ); let internal_vec = _self.#repeater_id.instances_vec(); #ri for sub_comp in &internal_vec { items_vec.push(sub_comp.as_pin_ref().box_layout_data(#orientation)) } )); } } } let ri = repeated_indices.as_ref().map(|ri| quote!(let mut #ri = [0u32; 2 * #repeater_idx];)); let ri2 = repeated_indices.map(|ri| quote!(let #ri = sp::Slice::from_slice(&#ri);)); let cells_variable = ident(cells_variable); let sub_expression = compile_expression(sub_expression, ctx); quote! { { #ri let mut items_vec = sp::Vec::with_capacity(#fixed_count #repeated_count); #(#push_code)* let #cells_variable = sp::Slice::from_slice(&items_vec); #ri2 #sub_expression } } } // In Rust debug builds, accessing the member of the FIELD_OFFSETS ends up copying the // entire FIELD_OFFSETS into a new stack allocation, which with large property // binding initialization functions isn't re-used and with large generated inner // components ends up large amounts of stack space (see issue #133) fn access_component_field_offset(component_id: &Ident, field: &Ident) -> TokenStream { quote!({ *&#component_id::FIELD_OFFSETS.#field }) } fn embedded_file_tokens(path: &str) -> TokenStream { let file = crate::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists match file.builtin_contents { Some(static_data) => { let literal = proc_macro2::Literal::byte_string(static_data); quote!(#literal) } None => quote!(::core::include_bytes!(#path)), } } fn generate_resources(doc: &Document) -> Vec { #[cfg(feature = "software-renderer")] let link_section = std::env::var("SLINT_ASSET_SECTION") .ok() .map(|section| quote!(#[unsafe(link_section = #section)])); doc.embedded_file_resources .borrow() .iter() .map(|(path, er)| { let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", er.id); match &er.kind { &crate::embedded_resources::EmbeddedResourcesKind::ListOnly => { quote!() }, crate::embedded_resources::EmbeddedResourcesKind::RawData => { let data = embedded_file_tokens(path); quote!(static #symbol: &'static [u8] = #data;) } #[cfg(feature = "software-renderer")] crate::embedded_resources::EmbeddedResourcesKind::TextureData(crate::embedded_resources::Texture { data, format, rect, total_size: crate::embedded_resources::Size{width, height}, original_size: crate::embedded_resources::Size{width: unscaled_width, height: unscaled_height}, }) => { let (r_x, r_y, r_w, r_h) = (rect.x(), rect.y(), rect.width(), rect.height()); let color = if let crate::embedded_resources::PixelFormat::AlphaMap([r, g, b]) = format { quote!(sp::Color::from_rgb_u8(#r, #g, #b)) } else { quote!(sp::Color::from_argb_encoded(0)) }; let symbol_data = format_ident!("SLINT_EMBEDDED_RESOURCE_{}_DATA", er.id); let data_size = data.len(); quote!( #link_section static #symbol_data : [u8; #data_size]= [#(#data),*]; #link_section static #symbol: sp::StaticTextures = sp::StaticTextures{ size: sp::IntSize::new(#width as _, #height as _), original_size: sp::IntSize::new(#unscaled_width as _, #unscaled_height as _), data: sp::Slice::from_slice(&#symbol_data), textures: sp::Slice::from_slice(&[ sp::StaticTexture { rect: sp::euclid::rect(#r_x as _, #r_y as _, #r_w as _, #r_h as _), format: #format, color: #color, index: 0, } ]) }; ) }, #[cfg(feature = "software-renderer")] crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, x_height, cap_height, glyphs, weight, italic, sdf }) => { let character_map_size = character_map.len(); let character_map = character_map.iter().map(|crate::embedded_resources::CharacterMapEntry{code_point, glyph_index}| quote!(sp::CharacterMapEntry { code_point: #code_point, glyph_index: #glyph_index })); let glyphs_size = glyphs.len(); let glyphs = glyphs.iter().map(|crate::embedded_resources::BitmapGlyphs{pixel_size, glyph_data}| { let glyph_data_size = glyph_data.len(); let glyph_data = glyph_data.iter().map(|crate::embedded_resources::BitmapGlyph{x, y, width, height, x_advance, data}|{ let data_size = data.len(); quote!( sp::BitmapGlyph { x: #x, y: #y, width: #width, height: #height, x_advance: #x_advance, data: sp::Slice::from_slice({ #link_section static DATA : [u8; #data_size] = [#(#data),*]; &DATA }), } ) }); quote!( sp::BitmapGlyphs { pixel_size: #pixel_size, glyph_data: sp::Slice::from_slice({ #link_section static GDATA : [sp::BitmapGlyph; #glyph_data_size] = [#(#glyph_data),*]; &GDATA }), } ) }); quote!( #link_section static #symbol: sp::BitmapFont = sp::BitmapFont { family_name: sp::Slice::from_slice(#family_name.as_bytes()), character_map: sp::Slice::from_slice({ #link_section static CM : [sp::CharacterMapEntry; #character_map_size] = [#(#character_map),*]; &CM }), units_per_em: #units_per_em, ascent: #ascent, descent: #descent, x_height: #x_height, cap_height: #cap_height, glyphs: sp::Slice::from_slice({ #link_section static GLYPHS : [sp::BitmapGlyphs; #glyphs_size] = [#(#glyphs),*]; &GLYPHS }), weight: #weight, italic: #italic, sdf: #sdf, }; ) }, } }) .collect() } fn generate_named_exports(doc: &Document) -> Vec { doc.exports .iter() .filter_map(|export| match &export.1 { Either::Left(component) if !component.is_global() => { Some((&export.0.name, &component.id)) } Either::Right(ty) => match &ty { Type::Struct(s) if s.name.is_some() && s.node.is_some() => { Some((&export.0.name, s.name.as_ref().unwrap())) } Type::Enumeration(en) => Some((&export.0.name, &en.name)), _ => None, }, _ => None, }) .filter(|(export_name, type_name)| export_name != type_name) .map(|(export_name, type_name)| { let type_id = ident(type_name); let export_id = ident(export_name); quote!(#type_id as #export_id) }) .collect::>() } #[cfg(feature = "bundle-translations")] fn generate_translations( translations: &crate::translations::Translations, compilation_unit: &llr::CompilationUnit, ) -> TokenStream { let strings = translations.strings.iter().map(|strings| { let array = strings.iter().map(|s| match s.as_ref().map(SmolStr::as_str) { Some(s) => quote!(Some(#s)), None => quote!(None), }); quote!(&[#(#array),*]) }); let plurals = translations.plurals.iter().map(|plurals| { let array = plurals.iter().map(|p| match p { Some(p) => { let p = p.iter().map(SmolStr::as_str); quote!(Some(&[#(#p),*])) } None => quote!(None), }); quote!(&[#(#array),*]) }); let ctx = EvaluationContext { compilation_unit, current_sub_component: None, current_global: None, generator_state: RustGeneratorContext { global_access: quote!(compile_error!("language rule can't access state")), }, parent: None, argument_types: &[Type::Int32], }; let rules = translations.plural_rules.iter().map(|rule| { let rule = match rule { Some(rule) => { let rule = compile_expression(rule, &ctx); quote!(Some(|arg: i32| { let args = (arg,); (#rule) as usize } )) } None => quote!(None), }; quote!(#rule) }); let lang = translations.languages.iter().map(SmolStr::as_str).map(|lang| quote!(#lang)); quote!( const _SLINT_TRANSLATED_STRINGS: &[&[sp::Option<&str>]] = &[#(#strings),*]; const _SLINT_TRANSLATED_STRINGS_PLURALS: &[&[sp::Option<&[&str]>]] = &[#(#plurals),*]; #[allow(unused)] const _SLINT_TRANSLATED_PLURAL_RULES: &[sp::Option usize>] = &[#(#rules),*]; const _SLINT_BUNDLED_LANGUAGES: &[&str] = &[#(#lang),*]; ) }