// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 /*! module for the C++ code generator */ // cSpell:ignore cmath constexpr cstdlib decltype intptr itertools nullptr prepended struc subcomponent uintptr vals use std::fmt::Write; /// The configuration for the C++ code generator #[derive(Clone, Debug, Default, PartialEq)] pub struct Config { pub namespace: Option, } fn ident(ident: &str) -> String { if ident.contains('-') { ident.replace('-', "_") } else { ident.into() } } /// 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) -> String { let mut ctx = ctx; let mut component_access = "self->".into(); let pr = match pr { llr::PropertyReference::InParent { level, parent_reference } => { for _ in 0..level.get() { component_access = format!("{component_access}parent->"); ctx = ctx.parent.as_ref().unwrap().ctx; } parent_reference } other => other, }; match pr { llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => { assert!(prop_name.is_empty()); let (sub_compo_path, sub_component) = follow_sub_component_path(ctx.current_sub_component.unwrap(), sub_component_path); if !sub_component_path.is_empty() { component_access += &sub_compo_path; } let component_rc = format!("{component_access}self_weak.lock()->into_dyn()"); let item_index_in_tree = sub_component.items[*item_index as usize].index_in_tree; let item_index = if item_index_in_tree == 0 { format!("{component_access}tree_index") } else { format!("{component_access}tree_index_of_first_child + {item_index_in_tree} - 1") }; format!("{}, {}", &component_rc, item_index) } _ => unreachable!(), } } /// This module contains some data structure that helps represent a C++ code. /// It is then rendered into an actual C++ text using the Display trait mod cpp_ast { use std::cell::Cell; use std::fmt::{Display, Error, Formatter}; thread_local!(static INDENTATION : Cell = Cell::new(0)); fn indent(f: &mut Formatter<'_>) -> Result<(), Error> { INDENTATION.with(|i| { for _ in 0..(i.get()) { write!(f, " ")?; } Ok(()) }) } ///A full C++ file #[derive(Default, Debug)] pub struct File { pub includes: Vec, pub after_includes: String, pub namespace: Option, pub declarations: Vec, pub definitions: Vec, } impl Display for File { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { writeln!(f, "// This file is auto-generated")?; writeln!(f, "#pragma once")?; for i in &self.includes { writeln!(f, "#include {}", i)?; } if let Some(namespace) = &self.namespace { writeln!(f, "namespace {} {{", namespace)?; INDENTATION.with(|x| x.set(x.get() + 1)); } write!(f, "{}", self.after_includes)?; for d in &self.declarations { write!(f, "\n{}", d)?; } for d in &self.definitions { write!(f, "\n{}", d)?; } if let Some(namespace) = &self.namespace { writeln!(f, "}} // namespace {}", namespace)?; INDENTATION.with(|x| x.set(x.get() - 1)); } Ok(()) } } /// Declarations (top level, or within a struct) #[derive(Debug, derive_more::Display)] pub enum Declaration { Struct(Struct), Function(Function), Var(Var), TypeAlias(TypeAlias), Enum(Enum), } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Access { Public, Private, /*Protected,*/ } #[derive(Default, Debug)] pub struct Struct { pub name: String, pub members: Vec<(Access, Declaration)>, pub friends: Vec, } impl Display for Struct { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; if self.members.is_empty() && self.friends.is_empty() { writeln!(f, "class {};", self.name) } else { writeln!(f, "class {} {{", self.name)?; INDENTATION.with(|x| x.set(x.get() + 1)); let mut access = Access::Private; for m in &self.members { if m.0 != access { access = m.0; indent(f)?; match access { Access::Public => writeln!(f, "public:")?, Access::Private => writeln!(f, "private:")?, } } write!(f, "{}", m.1)?; } for friend in &self.friends { indent(f)?; writeln!(f, "friend class {};", friend)?; } INDENTATION.with(|x| x.set(x.get() - 1)); indent(f)?; writeln!(f, "}};") } } } impl Struct { pub fn extract_definitions(&mut self) -> impl Iterator + '_ { let struct_name = self.name.clone(); self.members.iter_mut().filter_map(move |x| match &mut x.1 { Declaration::Function(f) if f.statements.is_some() => { Some(Declaration::Function(Function { name: format!("{}::{}", struct_name, f.name), signature: f.signature.clone(), is_constructor_or_destructor: f.is_constructor_or_destructor, is_static: false, is_friend: false, statements: f.statements.take(), template_parameters: f.template_parameters.clone(), constructor_member_initializers: f.constructor_member_initializers.clone(), })) } _ => None, }) } } #[derive(Default, Debug)] pub struct Enum { pub name: String, pub values: Vec, } impl Display for Enum { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; writeln!(f, "enum class {} {{", self.name)?; INDENTATION.with(|x| x.set(x.get() + 1)); for value in &self.values { write!(f, "{value},")?; } INDENTATION.with(|x| x.set(x.get() - 1)); indent(f)?; writeln!(f, "}};") } } /// Function or method #[derive(Default, Debug)] pub struct Function { pub name: String, /// "(...) -> ..." pub signature: String, /// The function does not have return type pub is_constructor_or_destructor: bool, pub is_static: bool, pub is_friend: bool, /// The list of statement instead the function. When None, this is just a function /// declaration without the definition pub statements: Option>, /// What's inside template<...> if any pub template_parameters: Option, /// Explicit initializers, such as FooClass::FooClass() : someMember(42) {} pub constructor_member_initializers: Vec, } impl Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; if let Some(tpl) = &self.template_parameters { write!(f, "template<{}> ", tpl)?; } if self.is_static { write!(f, "static ")?; } if self.is_friend { write!(f, "friend ")?; } // all functions are `inline` because we are in a header write!(f, "inline ")?; if !self.is_constructor_or_destructor { write!(f, "auto ")?; } write!(f, "{} {}", self.name, self.signature)?; if let Some(st) = &self.statements { if !self.constructor_member_initializers.is_empty() { writeln!(f, "\n : {}", self.constructor_member_initializers.join(","))?; } writeln!(f, "{{")?; for s in st { indent(f)?; writeln!(f, " {}", s)?; } indent(f)?; writeln!(f, "}}") } else { writeln!(f, ";") } } } /// A variable or a member declaration. #[derive(Default, Debug)] pub struct Var { pub ty: String, pub name: String, pub array_size: Option, pub init: Option, } impl Display for Var { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { indent(f)?; write!(f, "{} {}", self.ty, self.name)?; if let Some(size) = self.array_size { write!(f, "[{}]", size)?; } if let Some(i) = &self.init { write!(f, " = {}", i)?; } writeln!(f, ";") } } #[derive(Default, Debug)] pub struct TypeAlias { pub new_name: String, pub old_name: String, } impl Display for TypeAlias { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { indent(f)?; writeln!(f, "using {} = {};", self.new_name, self.old_name) } } pub trait CppType { fn cpp_type(&self) -> Option; } pub fn escape_string(str: &str) -> String { let mut result = String::with_capacity(str.len()); for x in str.chars() { match x { '\n' => result.push_str("\\n"), '\\' => result.push_str("\\\\"), '\"' => result.push_str("\\\""), '\t' => result.push_str("\\t"), '\r' => result.push_str("\\r"), _ if !x.is_ascii() || (x as u32) < 32 => { use std::fmt::Write; write!(result, "\\U{:0>8x}", x as u32).unwrap(); } _ => result.push(x), } } result } } use crate::expression_tree::{BuiltinFunction, EasingCurve, MinMaxOp}; use crate::langtype::{ElementType, Enumeration, EnumerationValue, NativeClass, Type}; use crate::layout::Orientation; use crate::llr::{ self, EvaluationContext as llr_EvaluationContext, ParentCtx as llr_ParentCtx, TypeResolutionContext as _, }; use crate::object_tree::Document; use crate::parser::syntax_nodes; use crate::CompilerConfiguration; use cpp_ast::*; use itertools::{Either, Itertools}; use std::cell::Cell; use std::collections::{BTreeMap, BTreeSet}; use std::num::NonZeroUsize; #[derive(Default)] struct ConditionalIncludes { iostream: Cell, cstdlib: Cell, cmath: Cell, } #[derive(Clone)] struct CppGeneratorContext<'a> { global_access: String, conditional_includes: &'a ConditionalIncludes, } type EvaluationContext<'a> = llr_EvaluationContext<'a, CppGeneratorContext<'a>>; type ParentCtx<'a> = llr_ParentCtx<'a, CppGeneratorContext<'a>>; impl CppType for Type { fn cpp_type(&self) -> Option { match self { Type::Void => Some("void".to_owned()), Type::Float32 => Some("float".to_owned()), Type::Int32 => Some("int".to_owned()), Type::String => Some("slint::SharedString".to_owned()), Type::Color => Some("slint::Color".to_owned()), Type::Duration => Some("std::int64_t".to_owned()), Type::Angle => Some("float".to_owned()), Type::PhysicalLength => Some("float".to_owned()), Type::LogicalLength => Some("float".to_owned()), Type::Rem => Some("float".to_owned()), Type::Percent => Some("float".to_owned()), Type::Bool => Some("bool".to_owned()), Type::Struct { name: Some(name), node: Some(_), .. } => Some(ident(name)), Type::Struct { name: Some(name), node: None, .. } => { Some(if name.starts_with("slint::") { name.clone() } else { format!("slint::cbindgen_private::{}", ident(name)) }) } Type::Struct { fields, .. } => { let elem = fields.values().map(|v| v.cpp_type()).collect::>>()?; Some(format!("std::tuple<{}>", elem.join(", "))) } Type::Array(i) => Some(format!("std::shared_ptr>", i.cpp_type()?)), Type::Image => Some("slint::Image".to_owned()), Type::Enumeration(enumeration) => { if enumeration.node.is_some() { Some(ident(&enumeration.name)) } else { Some(format!("slint::cbindgen_private::{}", ident(&enumeration.name))) } } Type::Brush => Some("slint::Brush".to_owned()), Type::LayoutCache => Some("slint::SharedVector".into()), Type::Easing => Some("slint::cbindgen_private::EasingCurve".into()), _ => None, } } } fn to_cpp_orientation(o: Orientation) -> &'static str { match o { Orientation::Horizontal => "slint::cbindgen_private::Orientation::Horizontal", Orientation::Vertical => "slint::cbindgen_private::Orientation::Vertical", } } /// If the expression is surrounded with parentheses, remove these parentheses fn remove_parentheses(expr: &str) -> &str { if expr.starts_with('(') && expr.ends_with(')') { let mut level = 0; // check that the opening and closing parentheses are on the same level for byte in expr[1..expr.len() - 1].as_bytes() { match byte { b')' if level == 0 => return expr, b')' => level -= 1, b'(' => level += 1, _ => (), } } &expr[1..expr.len() - 1] } else { expr } } #[test] fn remove_parentheses_test() { assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)"); assert_eq!(remove_parentheses("(foo).bar"), "(foo).bar"); assert_eq!(remove_parentheses("(foo(bar))"), "foo(bar)"); assert_eq!(remove_parentheses("(foo)(bar)"), "(foo)(bar)"); assert_eq!(remove_parentheses("(foo).get()"), "(foo).get()"); assert_eq!(remove_parentheses("((foo).get())"), "(foo).get()"); assert_eq!(remove_parentheses("(((()())()))"), "((()())())"); assert_eq!(remove_parentheses("((()())())"), "(()())()"); assert_eq!(remove_parentheses("(()())()"), "(()())()"); assert_eq!(remove_parentheses("()())("), "()())("); } fn property_set_value_code( property: &llr::PropertyReference, value_expr: &str, ctx: &EvaluationContext, ) -> String { let prop = access_member(property, ctx); if let Some((animation, map)) = &ctx.property_info(property).animation { let mut animation = (*animation).clone(); map.map_expression(&mut animation); let animation_code = compile_expression(&animation, ctx); return format!("{}.set_animated_value({}, {})", prop, value_expr, animation_code); } format!("{}.set({})", prop, value_expr) } fn handle_property_init( prop: &llr::PropertyReference, binding_expression: &llr::BindingExpression, init: &mut Vec, ctx: &EvaluationContext, ) { let prop_access = access_member(prop, ctx); let prop_type = ctx.property_ty(prop); if let Type::Callback { args, return_type, .. } = &prop_type { let mut ctx2 = ctx.clone(); ctx2.argument_types = args; let mut params = args.iter().enumerate().map(|(i, ty)| { format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i) }); init.push(format!( "{prop_access}.set_handler( [this]({params}) {{ [[maybe_unused]] auto self = this; {code}; }});", prop_access = prop_access, params = params.join(", "), code = return_compile_expression( &binding_expression.expression.borrow(), &ctx2, return_type.as_ref().map(|x| &**x) ) )); } else { let init_expr = compile_expression(&binding_expression.expression.borrow(), ctx); init.push(if binding_expression.is_constant && !binding_expression.is_state_info { format!("{}.set({});", prop_access, init_expr) } else { let binding_code = format!( "[this]() {{ [[maybe_unused]] auto self = this; return {init}; }}", init = init_expr ); if binding_expression.is_state_info { format!("slint::private_api::set_state_binding({}, {});", prop_access, binding_code) } else { match &binding_expression.animation { Some(llr::Animation::Static(anim)) => { let anim = compile_expression(anim, ctx); format!("{}.set_animated_binding({}, {});", prop_access, binding_code, anim) } Some(llr::Animation::Transition ( anim )) => { let anim = compile_expression(anim, ctx); format!( "{}.set_animated_binding_for_transition({}, [this](uint64_t *start_time) -> slint::cbindgen_private::PropertyAnimation {{ [[maybe_unused]] auto self = this; auto [anim, time] = {}; *start_time = time; return anim; }});", prop_access, binding_code, anim, ) } None => format!("{}.set_binding({});", prop_access, binding_code), } } }); } } /// Returns the text of the C++ code produced by the given root component pub fn generate( doc: &Document, config: Config, compiler_config: &CompilerConfiguration, ) -> impl std::fmt::Display { let mut file = File { namespace: config.namespace.clone(), ..Default::default() }; file.includes.push("".into()); file.includes.push("".into()); file.includes.push("".into()); for (path, er) in doc.embedded_file_resources.borrow().iter() { match &er.kind { crate::embedded_resources::EmbeddedResourcesKind::RawData => { let resource_file = crate::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists let data = resource_file.read(); let mut init = "{ ".to_string(); for (index, byte) in data.iter().enumerate() { if index > 0 { init.push(','); } write!(&mut init, "0x{:x}", byte).unwrap(); if index % 16 == 0 { init.push('\n'); } } init.push('}'); file.declarations.push(Declaration::Var(Var { ty: "const inline uint8_t".into(), name: format!("slint_embedded_resource_{}", er.id), array_size: Some(data.len()), init: Some(init), })); } #[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 { format!("slint::Color::from_rgb_uint8({r}, {g}, {b})") } else { "slint::Color{}".to_string() }; let count = data.len(); let data = data.iter().map(ToString::to_string).join(", "); let data_name = format!("slint_embedded_resource_{}_data", er.id); file.declarations.push(Declaration::Var(Var { ty: "inline const uint8_t".into(), name: data_name.clone(), array_size: Some(count), init: Some(format!("{{ {data} }}")), })); let texture_name = format!("slint_embedded_resource_{}_texture", er.id); file.declarations.push(Declaration::Var(Var { ty: "inline const slint::cbindgen_private::types::StaticTexture".into(), name: texture_name.clone(), array_size: None, init: Some(format!( "{{ .rect = {{ {r_x}, {r_y}, {r_w}, {r_h} }}, .format = slint::cbindgen_private::types::PixelFormat::{format}, .color = {color}, .index = 0, }}" )), })); let init = format!("slint::cbindgen_private::types::StaticTextures {{ .size = {{ {width}, {height} }}, .original_size = {{ {unscaled_width}, {unscaled_height} }}, .data = slint::cbindgen_private::Slice{{ {data_name} , {count} }}, .textures = slint::cbindgen_private::Slice{{ &{texture_name}, 1 }} }}"); file.declarations.push(Declaration::Var(Var { ty: "inline const slint::cbindgen_private::types::StaticTextures".into(), name: format!("slint_embedded_resource_{}", er.id), array_size: None, init: Some(init), })) } #[cfg(feature = "software-renderer")] crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData( crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, glyphs, weight, italic, }, ) => { let family_name_var = format!("slint_embedded_resource_{}_family_name", er.id); let family_name_size = family_name.len(); file.declarations.push(Declaration::Var(Var { ty: "inline const uint8_t".into(), name: family_name_var.clone(), array_size: Some(family_name_size), init: Some(format!( "{{ {} }}", family_name.as_bytes().iter().map(ToString::to_string).join(", ") )), })); let charmap_var = format!("slint_embedded_resource_{}_charmap", er.id); let charmap_size = character_map.len(); file.declarations.push(Declaration::Var(Var { ty: "inline const slint::cbindgen_private::CharacterMapEntry".into(), name: charmap_var.clone(), array_size: Some(charmap_size), init: Some(format!( "{{ {} }}", character_map .iter() .map(|entry| format!( "{{ .code_point = {}, .glyph_index = {} }}", entry.code_point as u32, entry.glyph_index )) .join(", ") )), })); for (glyphset_index, glyphset) in glyphs.iter().enumerate() { for (glyph_index, glyph) in glyphset.glyph_data.iter().enumerate() { file.declarations.push(Declaration::Var(Var { ty: "inline const uint8_t".into(), name: format!( "slint_embedded_resource_{}_gs_{}_gd_{}", er.id, glyphset_index, glyph_index ), array_size: Some(glyph.data.len()), init: Some(format!( "{{ {} }}", glyph.data.iter().map(ToString::to_string).join(", ") )), })); } file.declarations.push(Declaration::Var(Var{ ty: "inline const slint::cbindgen_private::BitmapGlyph".into(), name: format!("slint_embedded_resource_{}_glyphset_{}", er.id, glyphset_index), array_size: Some(glyphset.glyph_data.len()), init: Some(format!("{{ {} }}", glyphset.glyph_data.iter().enumerate().map(|(glyph_index, glyph)| { format!("{{ .x = {}, .y = {}, .width = {}, .height = {}, .x_advance = {}, .data = slint::cbindgen_private::Slice{{ {}, {} }} }}", glyph.x, glyph.y, glyph.width, glyph.height, glyph.x_advance, format!("slint_embedded_resource_{}_gs_{}_gd_{}", er.id, glyphset_index, glyph_index), glyph.data.len() ) }).join(", \n"))), })); } let glyphsets_var = format!("slint_embedded_resource_{}_glyphsets", er.id); let glyphsets_size = glyphs.len(); file.declarations.push(Declaration::Var(Var { ty: "inline const slint::cbindgen_private::BitmapGlyphs".into(), name: glyphsets_var.clone(), array_size: Some(glyphsets_size), init: Some(format!( "{{ {} }}", glyphs .iter() .enumerate() .map(|(glyphset_index, glyphset)| format!( "{{ .pixel_size = {}, .glyph_data = slint::cbindgen_private::Slice{{ {}, {} }} }}", glyphset.pixel_size, format!("slint_embedded_resource_{}_glyphset_{}", er.id, glyphset_index), glyphset.glyph_data.len() )) .join(", \n") )), })); let init = format!( "slint::cbindgen_private::BitmapFont {{ .family_name = slint::cbindgen_private::Slice{{ {family_name_var} , {family_name_size} }}, .character_map = slint::cbindgen_private::Slice{{ {charmap_var}, {charmap_size} }}, .units_per_em = {units_per_em}, .ascent = {ascent}, .descent = {descent}, .glyphs = slint::cbindgen_private::Slice{{ {glyphsets_var}, {glyphsets_size} }}, .weight = {weight}, .italic = {italic}, }}" ); file.declarations.push(Declaration::Var(Var { ty: "inline const slint::cbindgen_private::BitmapFont".into(), name: format!("slint_embedded_resource_{}", er.id), array_size: None, init: Some(init), })) } } } for ty in doc.used_types.borrow().structs_and_enums.iter() { match ty { Type::Struct { fields, name: Some(name), node: Some(node), .. } => { generate_struct(&mut file, name, fields, node); } Type::Enumeration(en) => { generate_enum(&mut file, en); } _ => (), } } if matches!( doc.root_component.root_element.borrow().base_type, ElementType::Error | ElementType::Global ) { // empty document, nothing to generate return file; } let llr = llr::lower_to_item_tree::lower_to_item_tree(&doc, compiler_config); // Forward-declare the root so that sub-components can access singletons, the window, etc. file.declarations.extend( llr.public_components .iter() .map(|c| Declaration::Struct(Struct { name: ident(&c.name), ..Default::default() })), ); let conditional_includes = ConditionalIncludes::default(); for sub_compo in &llr.sub_components { let sub_compo_id = ident(&sub_compo.name); let mut sub_compo_struct = Struct { name: sub_compo_id.clone(), ..Default::default() }; generate_sub_component( &mut sub_compo_struct, sub_compo, &llr, None, Access::Public, &mut file, &conditional_includes, ); file.definitions.extend(sub_compo_struct.extract_definitions().collect::>()); file.declarations.push(Declaration::Struct(sub_compo_struct)); } let mut globals_struct = Struct { name: "SharedGlobals".into(), ..Default::default() }; // The window need to be the first member so it is destroyed last globals_struct.members.push(( // FIXME: many of the different component bindings need to access this Access::Public, Declaration::Var(Var { ty: "std::optional".into(), name: "m_window".into(), ..Default::default() }), )); globals_struct.members.push(( Access::Public, Declaration::Var(Var { ty: "slint::cbindgen_private::ItemTreeWeak".into(), name: "root_weak".into(), ..Default::default() }), )); globals_struct.members.push(( Access::Public, Declaration::Function(Function { name: "window".into(), signature: "() const -> slint::Window&".into(), statements: Some(vec![ format!("auto self = const_cast(this);"), "if (!self->m_window.has_value()) {".into(), " auto &window = self->m_window.emplace(slint::private_api::WindowAdapterRc());" .into(), " window.window_handle().set_component(self->root_weak);".into(), "}".into(), "return *self->m_window;".into(), ]), ..Default::default() }), )); for glob in &llr.globals { let ty = if glob.is_builtin { format!("slint::cbindgen_private::{}", glob.name) } else { generate_global(&mut file, &conditional_includes, glob, &llr); file.definitions.extend(glob.aliases.iter().map(|name| { Declaration::TypeAlias(TypeAlias { old_name: ident(&glob.name), new_name: ident(name), }) })); ident(&glob.name) }; globals_struct.members.push(( Access::Public, Declaration::Var(Var { ty: format!("std::shared_ptr<{}>", ty), name: format!("global_{}", ident(&glob.name)), init: Some(format!("std::make_shared<{}>(this)", ty)), ..Default::default() }), )); } file.declarations.push(Declaration::Struct(globals_struct)); for p in &llr.public_components { generate_public_component(&mut file, &conditional_includes, p, &llr); } generate_type_aliases(&mut file, doc); file.after_includes = format!( "static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \ \"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \ SLINT_VERSION_STRING \". The version numbers must match exactly.\");", x = env!("CARGO_PKG_VERSION_MAJOR"), y = env!("CARGO_PKG_VERSION_MINOR"), z = env!("CARGO_PKG_VERSION_PATCH") ); if conditional_includes.iostream.get() { file.includes.push("".into()); } if conditional_includes.cstdlib.get() { file.includes.push("".into()); } if conditional_includes.cmath.get() { file.includes.push("".into()); } file } fn generate_struct( file: &mut File, name: &str, fields: &BTreeMap, node: &syntax_nodes::ObjectType, ) { let name = ident(name); let mut members = node .ObjectTypeMember() .map(|n| crate::parser::identifier_text(&n).unwrap()) .map(|name| { ( Access::Public, Declaration::Var(Var { ty: fields.get(&name).unwrap().cpp_type().unwrap(), name: ident(&name), ..Default::default() }), ) }) .collect::>(); members.push(( Access::Public, Declaration::Function(Function { name: "operator==".to_owned(), signature: format!("(const class {0} &a, const class {0} &b) -> bool = default", name), is_friend: true, statements: None, ..Function::default() }), )); file.declarations.push(Declaration::Struct(Struct { name, members, ..Default::default() })) } fn generate_enum(file: &mut File, en: &std::rc::Rc) { file.declarations.push(Declaration::Enum(Enum { name: ident(&en.name), values: (0..en.values.len()) .map(|value| { ident(&EnumerationValue { value, enumeration: en.clone() }.to_pascal_case()) }) .collect(), })) } /// Generate the component in `file`. /// /// `sub_components`, if Some, will be filled with all the sub component which needs to be added as friends fn generate_public_component( file: &mut File, conditional_includes: &ConditionalIncludes, component: &llr::PublicComponent, unit: &llr::CompilationUnit, ) { let component_id = ident(&component.name); let mut component_struct = Struct { name: component_id.clone(), ..Default::default() }; // need to be the first member, because it contains the window which is to be destroyed last component_struct.members.push(( Access::Private, Declaration::Var(Var { ty: "SharedGlobals".into(), name: "m_globals".into(), ..Default::default() }), )); for glob in unit.globals.iter().filter(|glob| !glob.is_builtin) { component_struct.friends.push(ident(&glob.name)); } let mut global_accessor_function_body = Vec::new(); for glob in unit.globals.iter().filter(|glob| glob.exported && !glob.is_builtin) { let accessor_statement = format!( "{0}if constexpr(std::is_same_v) {{ return *m_globals.global_{1}.get(); }}", if global_accessor_function_body.is_empty() { "" } else { "else " }, ident(&glob.name), ); global_accessor_function_body.push(accessor_statement); } if !global_accessor_function_body.is_empty() { global_accessor_function_body.push( "else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(), ); component_struct.members.push(( Access::Public, Declaration::Function(Function { name: "global".into(), signature: "() const -> const T&".into(), statements: Some(global_accessor_function_body), template_parameters: Some("typename T".into()), ..Default::default() }), )); } let ctx = EvaluationContext { compilation_unit: unit, current_sub_component: Some(&component.item_tree.root), current_global: None, generator_state: CppGeneratorContext { global_access: "(&this->m_globals)".to_string(), conditional_includes, }, parent: None, argument_types: &[], }; let old_declarations = file.declarations.len(); generate_item_tree( &mut component_struct, &component.item_tree, unit, None, component_id, Access::Private, // Hide properties and other fields from the C++ API file, conditional_includes, ); // Give generated sub-components, etc. access to our fields for new_decl in file.declarations.iter().skip(old_declarations) { if let Declaration::Struct(struc @ Struct { .. }) = new_decl { component_struct.friends.push(struc.name.clone()); }; } generate_public_api_for_properties( &mut component_struct.members, &component.public_properties, &component.private_properties, &ctx, ); component_struct.members.push(( Access::Public, Declaration::Function(Function { name: "show".into(), signature: "()".into(), statements: Some(vec!["window().show();".into()]), ..Default::default() }), )); component_struct.members.push(( Access::Public, Declaration::Function(Function { name: "hide".into(), signature: "()".into(), statements: Some(vec!["window().hide();".into()]), ..Default::default() }), )); component_struct.members.push(( Access::Public, Declaration::Function(Function { name: "window".into(), signature: "() const -> slint::Window&".into(), statements: Some(vec!["return m_globals.window();".into()]), ..Default::default() }), )); component_struct.members.push(( Access::Public, Declaration::Function(Function { name: "run".into(), signature: "()".into(), statements: Some(vec![ "show();".into(), "slint::run_event_loop();".into(), "hide();".into(), ]), ..Default::default() }), )); component_struct.friends.push("slint::private_api::WindowAdapterRc".into()); add_friends(&mut component_struct.friends, &component.item_tree.root, true); fn add_friends(friends: &mut Vec, sc: &llr::SubComponent, is_root: bool) { if !is_root { friends.push(ident(&sc.name)); } for repeater in &sc.repeated { add_friends(friends, &repeater.sub_tree.root, false) } for popup in &sc.popup_windows { add_friends(friends, &popup.root, false) } } file.definitions.extend(component_struct.extract_definitions().collect::>()); file.declarations.push(Declaration::Struct(component_struct)); } fn generate_item_tree( target_struct: &mut Struct, sub_tree: &llr::ItemTree, root: &llr::CompilationUnit, parent_ctx: Option, item_tree_class_name: String, field_access: Access, file: &mut File, conditional_includes: &ConditionalIncludes, ) { target_struct .friends .push(format!("vtable::VRc", item_tree_class_name)); generate_sub_component( target_struct, &sub_tree.root, root, parent_ctx, field_access, file, conditional_includes, ); let mut item_tree_array: Vec = Default::default(); let mut item_array: Vec = Default::default(); sub_tree.tree.visit_in_array(&mut |node, children_offset, parent_index| { let parent_index = parent_index as u32; if node.repeated { assert_eq!(node.children.len(), 0); let mut repeater_index = node.item_index; let mut sub_component = &sub_tree.root; for i in &node.sub_component_path { repeater_index += sub_component.sub_components[*i].repeater_offset; sub_component = &sub_component.sub_components[*i].ty; } item_tree_array.push(format!( "slint::private_api::make_dyn_node({}, {})", repeater_index, parent_index )); } else { let mut compo_offset = String::new(); let mut sub_component = &sub_tree.root; for i in &node.sub_component_path { let next_sub_component_name = ident(&sub_component.sub_components[*i].name); write!( compo_offset, "offsetof({}, {}) + ", ident(&sub_component.name), next_sub_component_name ) .unwrap(); sub_component = &sub_component.sub_components[*i].ty; } let item = &sub_component.items[node.item_index as usize]; let children_count = node.children.len() as u32; let children_index = children_offset as u32; let item_array_index = item_array.len() as u32; item_tree_array.push(format!( "slint::private_api::make_item_node({}, {}, {}, {}, {})", children_count, children_index, parent_index, item_array_index, node.is_accessible )); item_array.push(format!( "{{ {}, {} offsetof({}, {}) }}", item.ty.cpp_vtable_getter, compo_offset, &ident(&sub_component.name), ident(&item.name), )); } }); let mut visit_children_statements = vec![ "static const auto dyn_visit = [] (const void *base, [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor, [[maybe_unused]] uint32_t dyn_index) -> uint64_t {".to_owned(), format!(" [[maybe_unused]] auto self = reinterpret_cast(base);", item_tree_class_name)]; let mut subtree_range_statement = vec![" std::abort();".into()]; let mut subtree_component_statement = vec![" std::abort();".into()]; if target_struct.members.iter().any(|(_, declaration)| { matches!(&declaration, Declaration::Function(func @ Function { .. }) if func.name == "visit_dynamic_children") }) { visit_children_statements .push(" return self->visit_dynamic_children(dyn_index, order, visitor);".into()); subtree_range_statement = vec![ format!("auto self = reinterpret_cast(component.instance);", item_tree_class_name), "return self->subtree_range(dyn_index);".to_owned(), ]; subtree_component_statement = vec![ format!("auto self = reinterpret_cast(component.instance);", item_tree_class_name), "self->subtree_component(dyn_index, subtree_index, result);".to_owned(), ]; } else { visit_children_statements.push(" std::abort();".into()); } visit_children_statements.extend([ "};".into(), format!("auto self_rc = reinterpret_cast(component.instance)->self_weak.lock()->into_dyn();", item_tree_class_name), "return slint::cbindgen_private::slint_visit_item_tree(&self_rc, get_item_tree(component) , index, order, visitor, dyn_visit);".to_owned(), ]); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "visit_children".into(), signature: "(slint::private_api::ItemTreeRef component, intptr_t index, slint::private_api::TraversalOrder order, slint::private_api::ItemVisitorRefMut visitor) -> uint64_t".into(), is_static: true, statements: Some(visit_children_statements), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "get_item_ref".into(), signature: "(slint::private_api::ItemTreeRef component, uint32_t index) -> slint::private_api::ItemRef".into(), is_static: true, statements: Some(vec![ "return slint::private_api::get_item_ref(component, get_item_tree(component), item_array(), index);".to_owned(), ]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "get_subtree_range".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index) -> slint::private_api::IndexRange".into(), is_static: true, statements: Some(subtree_range_statement), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "get_subtree".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] uint32_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) -> void".into(), is_static: true, statements: Some(subtree_component_statement), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "get_item_tree".into(), signature: "(slint::private_api::ItemTreeRef) -> slint::cbindgen_private::Slice".into(), is_static: true, statements: Some(vec![ "return item_tree();".to_owned(), ]), ..Default::default() }), )); let parent_item_from_parent_component = parent_ctx.as_ref() .and_then(|parent| { parent .repeater_index .map(|idx| parent.ctx.current_sub_component.unwrap().repeated[idx as usize].index_in_tree) }).map(|parent_index| vec![ format!( "auto self = reinterpret_cast(component.instance);", item_tree_class_name, ), format!( "*result = {{ self->parent->self_weak, self->parent->tree_index_of_first_child + {} - 1 }};", parent_index, ) ]) .unwrap_or_default(); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "parent_node".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] slint::private_api::ItemWeak *result) -> void".into(), is_static: true, statements: Some(parent_item_from_parent_component,), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "embed_component".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] const slint::private_api::ItemTreeWeak *parent_component, [[maybe_unused]] const uint32_t parent_index) -> bool".into(), is_static: true, statements: Some(vec!["return false; /* todo! */".into()]), ..Default::default() }), )); // Statements will be overridden for repeated components! target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "subtree_index".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component) -> uintptr_t" .into(), is_static: true, statements: Some(vec!["return std::numeric_limits::max();".into()]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "item_tree".into(), signature: "() -> slint::cbindgen_private::Slice".into(), is_static: true, statements: Some(vec![ "static const slint::private_api::ItemTreeNode children[] {".to_owned(), format!(" {} }};", item_tree_array.join(", \n")), "return { const_cast(children), std::size(children) };" .to_owned(), ]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "item_array".into(), signature: "() -> const slint::private_api::ItemArray".into(), is_static: true, statements: Some(vec![ "static const slint::private_api::ItemArrayEntry items[] {".to_owned(), format!(" {} }};", item_array.join(", \n")), "return { const_cast(items), std::size(items) };" .to_owned(), ]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "layout_info".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, slint::cbindgen_private::Orientation o) -> slint::cbindgen_private::LayoutInfo" .into(), is_static: true, statements: Some(vec![format!( "return reinterpret_cast(component.instance)->layout_info(o);", item_tree_class_name )]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "item_geometry".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::LogicalRect" .into(), is_static: true, statements: Some(vec![format!( "return reinterpret_cast(component.instance)->item_geometry(index);", item_tree_class_name ), ]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "accessible_role".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> slint::cbindgen_private::AccessibleRole" .into(), is_static: true, statements: Some(vec![format!( "return reinterpret_cast(component.instance)->accessible_role(index);", item_tree_class_name )]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "accessible_string_property".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, slint::cbindgen_private::AccessibleStringProperty what, slint::SharedString *result) -> bool" .into(), is_static: true, statements: Some(vec![format!( "if (auto r = reinterpret_cast(component.instance)->accessible_string_property(index, what)) {{ *result = *r; return true; }} else {{ return false; }}", item_tree_class_name )]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "accessibility_action".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, const slint::cbindgen_private::AccessibilityAction *action) -> void" .into(), is_static: true, statements: Some(vec![format!( "reinterpret_cast(component.instance)->accessibility_action(index, *action);", item_tree_class_name )]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "supported_accessibility_actions".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index) -> uint32_t" .into(), is_static: true, statements: Some(vec![format!( "return reinterpret_cast(component.instance)->supported_accessibility_actions(index);", item_tree_class_name )]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "element_infos".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, uint32_t index, slint::SharedString *result) -> bool" .into(), is_static: true, statements: Some(vec![format!("if (auto infos = reinterpret_cast(component.instance)->element_infos(index)) {{ *result = *infos; return true; }};", item_tree_class_name), "return false;".into()]), ..Default::default() }), )); target_struct.members.push(( Access::Private, Declaration::Function(Function { name: "window_adapter".into(), signature: "([[maybe_unused]] slint::private_api::ItemTreeRef component, [[maybe_unused]] bool do_create, [[maybe_unused]] slint::cbindgen_private::Option* result) -> void" .into(), is_static: true, statements: Some(vec![format!( "/* TODO: implement this! */", )]), ..Default::default() }), )); target_struct.members.push(( Access::Public, Declaration::Var(Var { ty: "static const slint::private_api::ItemTreeVTable".to_owned(), name: "static_vtable".to_owned(), ..Default::default() }), )); file.definitions.push(Declaration::Var(Var { ty: "inline const slint::private_api::ItemTreeVTable".to_owned(), name: format!("{}::static_vtable", item_tree_class_name), init: Some(format!( "{{ visit_children, get_item_ref, get_subtree_range, get_subtree, \ get_item_tree, parent_node, embed_component, subtree_index, layout_info, \ item_geometry, accessible_role, accessible_string_property, accessibility_action, \ supported_accessibility_actions, element_infos, window_adapter, \ slint::private_api::drop_in_place<{}>, slint::private_api::dealloc }}", item_tree_class_name )), ..Default::default() })); let mut create_parameters = Vec::new(); let mut init_parent_parameters = ""; if let Some(parent) = &parent_ctx { let parent_type = format!("class {} const *", ident(&parent.ctx.current_sub_component.unwrap().name)); create_parameters.push(format!("{} parent", parent_type)); init_parent_parameters = ", parent"; } let mut create_code = vec![ format!( "auto self_rc = vtable::VRc::make();", target_struct.name ), format!("auto self = const_cast<{0} *>(&*self_rc);", target_struct.name), "self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(), ]; if parent_ctx.is_none() { create_code.push("self->globals = &self->m_globals;".into()); create_code.push("self->m_globals.root_weak = self->self_weak;".into()); create_code.push("slint::cbindgen_private::slint_ensure_backend();".into()); } let global_access = if parent_ctx.is_some() { "parent->globals" } else { "self->globals" }; create_code.extend([ format!( "slint::private_api::register_item_tree(&self_rc.into_dyn(), {global_access}->m_window);", ), format!("self->init({}, self->self_weak, 0, 1 {});", global_access, init_parent_parameters), ]); // Repeaters run their user_init() code from Repeater::ensure_updated() after update() initialized model_data/index. // So always call user_init(), unless this component is a repeated. if parent_ctx.map_or(true, |parent_ctx| parent_ctx.repeater_index.is_none()) { create_code.push("self->user_init();".to_string()); } create_code .push(format!("return slint::ComponentHandle<{0}>{{ self_rc }};", target_struct.name)); target_struct.members.push(( Access::Public, Declaration::Function(Function { name: "create".into(), signature: format!( "({}) -> slint::ComponentHandle<{}>", create_parameters.join(","), target_struct.name ), statements: Some(create_code), is_static: true, ..Default::default() }), )); let destructor = vec![format!( "if (auto &window = globals->m_window) window->window_handle().unregister_item_tree(this, item_array());" )]; target_struct.members.push(( Access::Public, Declaration::Function(Function { name: format!("~{}", target_struct.name), signature: "()".to_owned(), is_constructor_or_destructor: true, statements: Some(destructor), ..Default::default() }), )); } fn generate_sub_component( target_struct: &mut Struct, component: &llr::SubComponent, root: &llr::CompilationUnit, parent_ctx: Option, field_access: Access, file: &mut File, conditional_includes: &ConditionalIncludes, ) { let globals_type_ptr = "const class SharedGlobals*"; let mut init_parameters = vec![ format!("{} globals", globals_type_ptr), "slint::cbindgen_private::ItemTreeWeak enclosing_component".into(), "uint32_t tree_index".into(), "uint32_t tree_index_of_first_child".into(), ]; let mut init: Vec = vec!["auto self = this;".into(), "self->self_weak = enclosing_component;".into()]; target_struct.members.push(( Access::Public, Declaration::Var(Var { ty: "slint::cbindgen_private::ItemTreeWeak".into(), name: "self_weak".into(), ..Default::default() }), )); target_struct.members.push(( field_access, Declaration::Var(Var { ty: globals_type_ptr.to_owned(), name: "globals".to_owned(), ..Default::default() }), )); init.push("self->globals = globals;".into()); target_struct.members.push(( field_access, Declaration::Var(Var { ty: "uint32_t".to_owned(), name: "tree_index_of_first_child".to_owned(), ..Default::default() }), )); init.push("this->tree_index_of_first_child = tree_index_of_first_child;".into()); target_struct.members.push(( field_access, Declaration::Var(Var { ty: "uint32_t".to_owned(), name: "tree_index".to_owned(), ..Default::default() }), )); init.push("self->tree_index = tree_index;".into()); if let Some(parent_ctx) = &parent_ctx { let parent_type = format!("class {} const *", ident(&parent_ctx.ctx.current_sub_component.unwrap().name)); init_parameters.push(format!("{} parent", parent_type)); target_struct.members.push(( field_access, Declaration::Var(Var { ty: parent_type, name: "parent".to_owned(), ..Default::default() }), )); init.push("self->parent = parent;".into()); } let ctx = EvaluationContext::new_sub_component( root, component, CppGeneratorContext { global_access: "self->globals".into(), conditional_includes }, parent_ctx, ); component.popup_windows.iter().for_each(|c| { let component_id = ident(&c.root.name); let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() }; generate_item_tree( &mut popup_struct, c, root, Some(ParentCtx::new(&ctx, None)), component_id, Access::Public, file, conditional_includes, ); file.definitions.extend(popup_struct.extract_definitions().collect::>()); file.declarations.push(Declaration::Struct(popup_struct)); }); for property in component.properties.iter().filter(|p| p.use_count.get() > 0) { let cpp_name = ident(&property.name); let ty = if let Type::Callback { args, return_type } = &property.ty { let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::>(); let return_type = return_type.as_ref().map_or("void".to_owned(), |t| t.cpp_type().unwrap()); format!("slint::private_api::Callback<{}({})>", return_type, param_types.join(", ")) } else { format!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap()) }; target_struct.members.push(( field_access, Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }), )); } for (i, _) in component.change_callbacks.iter().enumerate() { target_struct.members.push(( field_access, Declaration::Var(Var { ty: "slint::private_api::ChangeTracker".into(), name: format!("change_tracker{}", i), ..Default::default() }), )); } let mut user_init = vec!["[[maybe_unused]] auto self = this;".into()]; let mut children_visitor_cases = Vec::new(); let mut subtrees_ranges_cases = Vec::new(); let mut subtrees_components_cases = Vec::new(); for sub in &component.sub_components { let field_name = ident(&sub.name); 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 _; // 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 { "tree_index".into() } else { format!("tree_index_of_first_child + {} - 1", local_tree_index) }; let global_children = if local_index_of_first_child == 0 { "0".into() } else { format!("tree_index_of_first_child + {} - 1", local_index_of_first_child) }; init.push(format!( "this->{}.init(globals, self_weak.into_dyn(), {}, {});", field_name, global_index, global_children )); user_init.push(format!("this->{}.user_init();", field_name)); let sub_component_repeater_count = sub.ty.repeater_count(); if sub_component_repeater_count > 0 { let mut case_code = String::new(); let repeater_offset = sub.repeater_offset; for local_repeater_index in 0..sub_component_repeater_count { write!(case_code, "case {}: ", repeater_offset + local_repeater_index).unwrap(); } children_visitor_cases.push(format!( "\n {case_code} {{ return self->{id}.visit_dynamic_children(dyn_index - {base}, order, visitor); }}", case_code = case_code, id = field_name, base = repeater_offset, )); subtrees_ranges_cases.push(format!( "\n {case_code} {{ return self->{id}.subtree_range(dyn_index - {base}); }}", case_code = case_code, id = field_name, base = repeater_offset, )); subtrees_components_cases.push(format!( "\n {case_code} {{ self->{id}.subtree_component(dyn_index - {base}, subtree_index, result); return; }}", case_code = case_code, id = field_name, base = repeater_offset, )); } target_struct.members.push(( field_access, Declaration::Var(Var { ty: ident(&sub.ty.name), name: field_name, ..Default::default() }), )); } for (prop1, prop2) in &component.two_way_bindings { init.push(format!( "slint::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2});", ty = ctx.property_ty(prop1).cpp_type().unwrap(), p1 = access_member(prop1, &ctx), p2 = access_member(prop2, &ctx), )); } let mut properties_init_code = Vec::new(); for (prop, expression) in &component.property_init { if expression.use_count.get() > 0 { handle_property_init(prop, expression, &mut properties_init_code, &ctx) } } for item in &component.items { target_struct.members.push(( field_access, Declaration::Var(Var { ty: format!("slint::cbindgen_private::{}", ident(&item.ty.class_name)), name: ident(&item.name), init: Some("{}".to_owned()), ..Default::default() }), )); } for (idx, repeated) in component.repeated.iter().enumerate() { let idx = idx as u32; let data_type = if let Some(data_prop) = repeated.data_prop { repeated.sub_tree.root.properties[data_prop].ty.clone() } else { Type::Int32 }; generate_repeated_component( repeated, root, ParentCtx::new(&ctx, Some(idx)), &data_type, file, conditional_includes, ); let repeater_id = format!("repeater_{}", idx); let mut model = compile_expression(&repeated.model.borrow(), &ctx); if repeated.model.ty(&ctx) == Type::Bool { // bool converts to int // FIXME: don't do a heap allocation here model = format!("std::make_shared({})", model) } // FIXME: optimize if repeated.model.is_constant() properties_init_code.push(format!( "self->{repeater_id}.set_model_binding([self] {{ (void)self; return {model}; }});", repeater_id = repeater_id, model = model, )); let ensure_updated = if let Some(listview) = &repeated.listview { let vp_y = access_member(&listview.viewport_y, &ctx); let vp_h = access_member(&listview.viewport_height, &ctx); let lv_h = access_member(&listview.listview_height, &ctx); let vp_w = access_member(&listview.viewport_width, &ctx); let lv_w = access_member(&listview.listview_width, &ctx); format!( "self->{}.ensure_updated_listview(self, &{}, &{}, &{}, {}.get(), {}.get());", repeater_id, vp_w, vp_h, vp_y, lv_w, lv_h ) } else { format!("self->{id}.ensure_updated(self);", id = repeater_id) }; children_visitor_cases.push(format!( "\n case {i}: {{ {e_u} return self->{id}.visit(order, visitor); }}", id = repeater_id, i = idx, e_u = ensure_updated, )); subtrees_ranges_cases.push(format!( "\n case {i}: {{ {e_u} return self->{id}.index_range(); }}", i = idx, e_u = ensure_updated, id = repeater_id, )); subtrees_components_cases.push(format!( "\n case {i}: {{ {e_u} *result = self->{id}.instance_at(subtree_index); return; }}", i = idx, e_u = ensure_updated, id = repeater_id, )); target_struct.members.push(( field_access, Declaration::Var(Var { ty: format!( "slint::private_api::Repeater", ident(&repeated.sub_tree.root.name), data_type.cpp_type().unwrap(), ), name: repeater_id, ..Default::default() }), )); } init.extend(properties_init_code); user_init.extend(component.init_code.iter().map(|e| { let mut expr_str = compile_expression(&e.borrow(), &ctx); expr_str.push(';'); expr_str })); user_init.extend(component.change_callbacks.iter().enumerate().map(|(idx, (p, e))| { let code = compile_expression(&e.borrow(), &ctx); let prop = compile_expression(&llr::Expression::PropertyReference(p.clone()), &ctx); format!("self->change_tracker{idx}.init(self, [](auto self) {{ return {prop}; }}, [](auto self, auto) {{ {code}; }});") })); target_struct .members .extend(generate_functions(&component.functions, &ctx).map(|x| (Access::Public, x))); target_struct.members.push(( field_access, Declaration::Function(Function { name: "init".to_owned(), signature: format!("({}) -> void", init_parameters.join(",")), statements: Some(init), ..Default::default() }), )); target_struct.members.push(( field_access, Declaration::Function(Function { name: "user_init".to_owned(), signature: "() -> void".into(), statements: Some(user_init), ..Default::default() }), )); target_struct.members.push(( field_access, Declaration::Function(Function { name: "layout_info".into(), signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::LayoutInfo" .into(), statements: Some(vec![ "[[maybe_unused]] auto self = this;".into(), format!( "return o == slint::cbindgen_private::Orientation::Horizontal ? {} : {};", compile_expression(&component.layout_info_h.borrow(), &ctx), compile_expression(&component.layout_info_v.borrow(), &ctx) ), ]), ..Default::default() }), )); let mut dispatch_item_function = |name: &str, signature: &str, forward_args: &str, code: Vec| { let mut code = ["[[maybe_unused]] auto self = this;".into()] .into_iter() .chain(code) .collect::>(); let mut else_ = ""; for sub in &component.sub_components { let sub_items_count = sub.ty.child_item_count(); code.push(format!("{else_}if (index == {}) {{", sub.index_in_tree,)); code.push(format!(" return self->{}.{name}(0{forward_args});", ident(&sub.name))); if sub_items_count > 1 { code.push(format!( "}} else if (index >= {} && index < {}) {{", sub.index_of_first_child_in_tree, sub.index_of_first_child_in_tree + sub_items_count - 1 + sub.ty.repeater_count() )); code.push(format!( " return self->{}.{name}(index - {}{forward_args});", ident(&sub.name), sub.index_of_first_child_in_tree - 1 )); } else_ = "} else "; } let ret = if signature.contains("->") { "{}" } else { "" }; code.push(format!("{else_}return {ret};")); target_struct.members.push(( field_access, Declaration::Function(Function { name: name.into(), signature: signature.into(), statements: Some(code), ..Default::default() }), )); }; let mut item_geometry_cases = vec!["switch (index) {".to_string()]; item_geometry_cases.extend( component .geometries .iter() .enumerate() .filter_map(|(i, x)| x.as_ref().map(|x| (i, x))) .map(|(index, expr)| { format!( " case {index}: return slint::private_api::convert_anonymous_rect({});", compile_expression(&expr.borrow(), &ctx) ) }), ); item_geometry_cases.push("}".into()); dispatch_item_function( "item_geometry", "(uint32_t index) const -> slint::cbindgen_private::Rect", "", item_geometry_cases, ); let mut accessible_role_cases = vec!["switch (index) {".into()]; let mut accessible_string_cases = vec!["switch ((index << 8) | uintptr_t(what)) {".into()]; let mut accessibility_action_cases = vec!["switch ((index << 8) | uintptr_t(action.tag)) {".into()]; 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_cases.push(format!(" case {index}: return {e};")); } else if let Some(what) = what.strip_prefix("Action") { let has_args = matches!(&*expr.borrow(), llr::Expression::CallBackCall { arguments, .. } if !arguments.is_empty()); accessibility_action_cases.push(if has_args { let member = ident(&crate::generator::to_kebab_case(what)); format!(" case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibilityAction::Tag::{what}): {{ auto arg_0 = action.{member}._0; return {e}; }}") } else { format!(" case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibilityAction::Tag::{what}): return {e};") }); supported_accessibility_actions .entry(*index) .or_default() .insert(format!("slint::cbindgen_private::SupportedAccessibilityAction_{what}")); } else { accessible_string_cases.push(format!(" case ({index} << 8) | uintptr_t(slint::cbindgen_private::AccessibleStringProperty::{what}): return {e};")); } } accessible_role_cases.push("}".into()); accessible_string_cases.push("}".into()); accessibility_action_cases.push("}".into()); let mut supported_accessibility_actions_cases = vec!["switch (index) {".into()]; supported_accessibility_actions_cases.extend(supported_accessibility_actions.into_iter().map( |(index, values)| format!(" case {index}: return {};", values.into_iter().join("|")), )); supported_accessibility_actions_cases.push("}".into()); dispatch_item_function( "accessible_role", "(uint32_t index) const -> slint::cbindgen_private::AccessibleRole", "", accessible_role_cases, ); dispatch_item_function( "accessible_string_property", "(uint32_t index, slint::cbindgen_private::AccessibleStringProperty what) const -> std::optional", ", what", accessible_string_cases, ); dispatch_item_function( "accessibility_action", "(uint32_t index, const slint::cbindgen_private::AccessibilityAction &action) const", ", action", accessibility_action_cases, ); dispatch_item_function( "supported_accessibility_actions", "(uint32_t index) const -> uint32_t", "", supported_accessibility_actions_cases, ); let mut element_infos_cases = vec!["switch (index) {".to_string()]; element_infos_cases.extend( component .element_infos .iter() .map(|(index, ids)| format!(" case {index}: return \"{}\";", ids)), ); element_infos_cases.push("}".into()); dispatch_item_function( "element_infos", "(uint32_t index) const -> std::optional", "", element_infos_cases, ); if !children_visitor_cases.is_empty() { target_struct.members.push(( field_access, Declaration::Function(Function { name: "visit_dynamic_children".into(), signature: "(uint32_t dyn_index, [[maybe_unused]] slint::private_api::TraversalOrder order, [[maybe_unused]] slint::private_api::ItemVisitorRefMut visitor) const -> uint64_t".into(), statements: Some(vec![ " auto self = this;".to_owned(), format!(" switch(dyn_index) {{ {} }};", children_visitor_cases.join("")), " std::abort();".to_owned(), ]), ..Default::default() }), )); target_struct.members.push(( field_access, Declaration::Function(Function { name: "subtree_range".into(), signature: "(uintptr_t dyn_index) const -> slint::private_api::IndexRange".into(), statements: Some(vec![ "[[maybe_unused]] auto self = this;".to_owned(), format!(" switch(dyn_index) {{ {} }};", subtrees_ranges_cases.join("")), " std::abort();".to_owned(), ]), ..Default::default() }), )); target_struct.members.push(( field_access, Declaration::Function(Function { name: "subtree_component".into(), signature: "(uintptr_t dyn_index, [[maybe_unused]] uintptr_t subtree_index, [[maybe_unused]] slint::private_api::ItemTreeWeak *result) const -> void".into(), statements: Some(vec![ "[[maybe_unused]] auto self = this;".to_owned(), format!(" switch(dyn_index) {{ {} }};", subtrees_components_cases.join("")), " std::abort();".to_owned(), ]), ..Default::default() }), )); } } fn generate_repeated_component( repeated: &llr::RepeatedElement, root: &llr::CompilationUnit, parent_ctx: ParentCtx, model_data_type: &Type, file: &mut File, conditional_includes: &ConditionalIncludes, ) { let repeater_id = ident(&repeated.sub_tree.root.name); let mut repeater_struct = Struct { name: repeater_id.clone(), ..Default::default() }; generate_item_tree( &mut repeater_struct, &repeated.sub_tree, root, Some(parent_ctx), repeater_id.clone(), Access::Public, file, conditional_includes, ); let ctx = EvaluationContext { compilation_unit: root, current_sub_component: Some(&repeated.sub_tree.root), current_global: None, generator_state: CppGeneratorContext { global_access: "self".into(), conditional_includes }, parent: Some(parent_ctx), argument_types: &[], }; let access_prop = |&property_index| { access_member( &llr::PropertyReference::Local { sub_component_path: vec![], property_index }, &ctx, ) }; let index_prop = repeated.index_prop.iter().map(access_prop); let data_prop = repeated.data_prop.iter().map(access_prop); let mut update_statements = vec!["[[maybe_unused]] auto self = this;".into()]; update_statements.extend(index_prop.map(|prop| format!("{}.set(i);", prop))); update_statements.extend(data_prop.map(|prop| format!("{}.set(data);", prop))); repeater_struct.members.push(( Access::Public, // Because Repeater accesses it Declaration::Function(Function { name: "update_data".into(), signature: format!( "([[maybe_unused]] int i, [[maybe_unused]] const {} &data) const -> void", model_data_type.cpp_type().unwrap() ), statements: Some(update_statements), ..Function::default() }), )); repeater_struct.members.push(( Access::Public, // Because Repeater accesses it Declaration::Function(Function { name: "init".into(), signature: "() -> void".into(), statements: Some(vec!["user_init();".into()]), ..Function::default() }), )); if let Some(listview) = &repeated.listview { let p_y = access_member(&listview.prop_y, &ctx); let p_height = access_member(&listview.prop_height, &ctx); let p_width = access_member(&listview.prop_width, &ctx); repeater_struct.members.push(( Access::Public, // Because Repeater accesses it Declaration::Function(Function { name: "listview_layout".into(), signature: "(float *offset_y, const slint::private_api::Property *viewport_width) const -> void" .to_owned(), statements: Some(vec![ "[[maybe_unused]] auto self = this;".into(), "float vp_w = viewport_width->get();".to_owned(), format!("{}.set(*offset_y);", p_y), // FIXME: shouldn't that be handled by apply layout? format!("*offset_y += {}.get();", p_height), format!("float w = {}.get();", p_width), "if (vp_w < w)".to_owned(), " viewport_width->set(w);".to_owned(), ]), ..Function::default() }), )); } else { repeater_struct.members.push(( Access::Public, // Because Repeater accesses it Declaration::Function(Function { name: "box_layout_data".into(), signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::BoxLayoutCellData".to_owned(), statements: Some(vec!["return { layout_info({&static_vtable, const_cast(static_cast(this))}, o) };".into()]), ..Function::default() }), )); } if let Some(index_prop) = repeated.index_prop { // Override default subtree_index function implementation let subtree_index_func = repeater_struct .members .iter_mut() .find(|(_, d)| matches!(d, Declaration::Function(f) if f.name == "subtree_index")); if let Declaration::Function(f) = &mut subtree_index_func.unwrap().1 { let index = access_prop(&index_prop); f.statements = Some(vec![ format!( "auto self = reinterpret_cast(component.instance);", repeater_id ), format!("return {}.get();", index), ]); } } file.definitions.extend(repeater_struct.extract_definitions().collect::>()); file.declarations.push(Declaration::Struct(repeater_struct)); } fn generate_global( file: &mut File, conditional_includes: &ConditionalIncludes, global: &llr::GlobalComponent, root: &llr::CompilationUnit, ) { let mut global_struct = Struct { name: ident(&global.name), ..Default::default() }; for property in global.properties.iter().filter(|p| p.use_count.get() > 0) { let cpp_name = ident(&property.name); let ty = if let Type::Callback { args, return_type } = &property.ty { let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::>(); let return_type = return_type.as_ref().map_or("void".to_owned(), |t| t.cpp_type().unwrap()); format!("slint::private_api::Callback<{}({})>", return_type, param_types.join(", ")) } else { format!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap()) }; global_struct.members.push(( // FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the // fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API // when the global is exported and exposed in the public component. Access::Public, Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }), )); } let mut init = vec!["(void)this->globals;".into()]; let ctx = EvaluationContext::new_global( root, global, CppGeneratorContext { global_access: "this->globals".into(), conditional_includes }, ); for (property_index, expression) in global.init_values.iter().enumerate() { 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, ) } } global_struct.members.push(( Access::Public, Declaration::Function(Function { name: ident(&global.name), signature: "(const class SharedGlobals *globals)".into(), is_constructor_or_destructor: true, statements: Some(init), constructor_member_initializers: vec!["globals(globals)".into()], ..Default::default() }), )); global_struct.members.push(( Access::Private, Declaration::Var(Var { ty: "const class SharedGlobals*".to_owned(), name: "globals".to_owned(), ..Default::default() }), )); generate_public_api_for_properties( &mut global_struct.members, &global.public_properties, &global.private_properties, &ctx, ); global_struct .members .extend(generate_functions(&global.functions, &ctx).map(|x| (Access::Public, x))); file.definitions.extend(global_struct.extract_definitions().collect::>()); file.declarations.push(Declaration::Struct(global_struct)); } fn generate_functions<'a>( functions: &'a [llr::Function], ctx: &'a EvaluationContext<'_>, ) -> impl Iterator + 'a { functions.iter().map(|f| { let mut ctx2 = ctx.clone(); ctx2.argument_types = &f.args; let ret = if f.ret_ty != Type::Void { "return " } else { "" }; let body = vec![ "[[maybe_unused]] auto self = this;".into(), format!("{ret}{};", compile_expression(&f.code, &ctx2)), ]; Declaration::Function(Function { name: ident(&format!("fn_{}", f.name)), signature: format!( "({}) const -> {}", f.args .iter() .enumerate() .map(|(i, ty)| format!("{} arg_{}", ty.cpp_type().unwrap(), i)) .join(", "), f.ret_ty.cpp_type().unwrap() ), statements: Some(body), ..Default::default() }) }) } fn generate_public_api_for_properties( declarations: &mut Vec<(Access, Declaration)>, public_properties: &llr::PublicProperties, private_properties: &llr::PrivateProperties, ctx: &EvaluationContext, ) { for p in public_properties { let prop_ident = ident(&p.name); let access = access_member(&p.prop, ctx); if let Type::Callback { args, return_type } = &p.ty { let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::>(); let return_type = return_type.as_ref().map_or("void".into(), |t| t.cpp_type().unwrap()); let callback_emitter = vec![ "[[maybe_unused]] auto self = this;".into(), format!( "return {}.call({});", access, (0..args.len()).map(|i| format!("arg_{}", i)).join(", ") ), ]; declarations.push(( Access::Public, Declaration::Function(Function { name: format!("invoke_{prop_ident}"), signature: format!( "({}) const -> {}", param_types .iter() .enumerate() .map(|(i, ty)| format!("{} arg_{}", ty, i)) .join(", "), return_type ), statements: Some(callback_emitter), ..Default::default() }), )); declarations.push(( Access::Public, Declaration::Function(Function { name: format!("on_{}", ident(&p.name)), template_parameters: Some(format!( "std::invocable<{}> Functor", param_types.join(", "), )), signature: "(Functor && callback_handler) const".into(), statements: Some(vec![ "[[maybe_unused]] auto self = this;".into(), format!("{}.set_handler(std::forward(callback_handler));", access), ]), ..Default::default() }), )); } else if let Type::Function { return_type, args } = &p.ty { let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::>(); let ret = return_type.cpp_type().unwrap(); let call_code = vec![ "[[maybe_unused]] auto self = this;".into(), format!( "{}{access}({});", if **return_type == Type::Void { "" } else { "return " }, (0..args.len()).map(|i| format!("arg_{}", i)).join(", ") ), ]; declarations.push(( Access::Public, Declaration::Function(Function { name: format!("invoke_{}", ident(&p.name)), signature: format!( "({}) const -> {ret}", param_types .iter() .enumerate() .map(|(i, ty)| format!("{} arg_{}", ty, i)) .join(", "), ), statements: Some(call_code), ..Default::default() }), )); } else { let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties"); let prop_getter: Vec = vec![ "[[maybe_unused]] auto self = this;".into(), format!("return {}.get();", access), ]; declarations.push(( Access::Public, Declaration::Function(Function { name: format!("get_{}", &prop_ident), signature: format!("() const -> {}", &cpp_property_type), statements: Some(prop_getter), ..Default::default() }), )); if !p.read_only { let prop_setter: Vec = vec![ "[[maybe_unused]] auto self = this;".into(), property_set_value_code(&p.prop, "value", ctx) + ";", ]; declarations.push(( Access::Public, Declaration::Function(Function { name: format!("set_{}", &prop_ident), signature: format!("(const {} &value) const", &cpp_property_type), statements: Some(prop_setter), ..Default::default() }), )); } else { declarations.push(( Access::Private, Declaration::Function(Function { name: format!("set_{}", &prop_ident), signature: format!( "(const {cpp_property_type} &) const = delete /* property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter */", p.name ), ..Default::default() }), )); } } } for (name, ty) in private_properties { let prop_ident = ident(name); if let Type::Function { args, .. } = &ty { let param_types = args.iter().map(|t| t.cpp_type().unwrap()).join(", "); declarations.push(( Access::Private, Declaration::Function(Function { name: format!("invoke_{prop_ident}"), signature: format!( "({param_types}) const = delete /* the function '{name}' is declared as private. Declare it as 'public' */", ), ..Default::default() }), )); } else { declarations.push(( Access::Private, Declaration::Function(Function { name: format!("get_{prop_ident}"), signature: format!( "() const = delete /* the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public */", ), ..Default::default() }), )); declarations.push(( Access::Private, Declaration::Function(Function { name: format!("set_{}", &prop_ident), signature: format!( "(const auto &) const = delete /* property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public */", ), ..Default::default() }), )); } } } fn follow_sub_component_path<'a>( root: &'a llr::SubComponent, sub_component_path: &[usize], ) -> (String, &'a llr::SubComponent) { let mut compo_path = String::new(); let mut sub_component = root; for i in sub_component_path { let sub_component_name = ident(&sub_component.sub_components[*i].name); write!(compo_path, "{}.", sub_component_name).unwrap(); sub_component = &sub_component.sub_components[*i].ty; } (compo_path, sub_component) } fn access_window_field(ctx: &EvaluationContext) -> String { format!("{}->window().window_handle()", ctx.generator_state.global_access) } /// Returns the code that can access the given property (but without the set or get) /// /// to be used like: /// ```ignore /// let access = access_member(...); /// format!("{}.get()", access) /// ``` /// or for a function /// ```ignore /// let access = access_member(...); /// format!("{access}(...)") /// ``` fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) -> String { fn in_native_item( ctx: &EvaluationContext, sub_component_path: &[usize], item_index: u32, prop_name: &str, path: &str, ) -> String { let (compo_path, sub_component) = follow_sub_component_path(ctx.current_sub_component.unwrap(), sub_component_path); let item_name = ident(&sub_component.items[item_index as usize].name); if prop_name.is_empty() { // then this is actually a reference to the element itself format!("{}->{}{}", path, compo_path, item_name) } else { let property_name = ident(prop_name); format!("{path}->{compo_path}{item_name}.{property_name}") } } 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(sub_component, sub_component_path); let property_name = ident(&sub_component.properties[*property_index].name); format!("self->{}{}", compo_path, property_name) } else if let Some(current_global) = ctx.current_global { format!("this->{}", ident(¤t_global.properties[*property_index].name)) } else { unreachable!() } } llr::PropertyReference::Function { sub_component_path, function_index } => { if let Some(sub_component) = ctx.current_sub_component { let (compo_path, sub_component) = follow_sub_component_path(sub_component, sub_component_path); let name = ident(&sub_component.functions[*function_index].name); format!("self->{compo_path}fn_{name}") } else if let Some(current_global) = ctx.current_global { format!("this->fn_{}", ident(¤t_global.functions[*function_index].name)) } else { unreachable!() } } llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => { in_native_item(ctx, sub_component_path, *item_index, prop_name, "self") } llr::PropertyReference::InParent { level, parent_reference } => { let mut ctx = ctx; let mut path = "self".to_string(); for _ in 0..level.get() { write!(path, "->parent").unwrap(); 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(sub_component, sub_component_path); let property_name = ident(&sub_component.properties[*property_index].name); format!("{}->{}{}", path, compo_path, property_name) } llr::PropertyReference::Function { sub_component_path, function_index } => { let sub_component = ctx.current_sub_component.unwrap(); let (compo_path, sub_component) = follow_sub_component_path(sub_component, sub_component_path); let name = ident(&sub_component.functions[*function_index].name); format!("{path}->{compo_path}fn_{name}") } llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name, } => in_native_item(ctx, sub_component_path, *item_index, prop_name, &path), 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!("global_{}", ident(&global.name)); let property_name = ident( &ctx.compilation_unit.globals[*global_index].properties[*property_index].name, ); format!("{}->{}->{}", global_access, global_id, property_name) } 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!("global_{}", ident(&global.name)); let name = ident(&ctx.compilation_unit.globals[*global_index].functions[*function_index].name); format!("{global_access}->{global_id}->fn_{name}") } } } /// Returns the NativeClass for a PropertyReference::InNativeItem /// (or a InParent of InNativeItem ) fn native_item<'a>( item_ref: &llr::PropertyReference, ctx: &'a EvaluationContext, ) -> &'a NativeClass { match item_ref { llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name: _ } => { let mut sub_component = ctx.current_sub_component.unwrap(); for i in sub_component_path { sub_component = &sub_component.sub_components[*i].ty; } &sub_component.items[*item_index as usize].ty } llr::PropertyReference::InParent { level, parent_reference } => { let mut ctx = ctx; for _ in 0..level.get() { ctx = ctx.parent.as_ref().unwrap().ctx; } native_item(parent_reference, ctx) } _ => unreachable!(), } } fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String { use llr::Expression; match expr { Expression::StringLiteral(s) => { format!(r#"slint::SharedString(u8"{}")"#, escape_string(s.as_str())) } Expression::NumberLiteral(num) => { if !num.is_finite() { // just print something "0.0".to_string() } else if num.abs() > 1_000_000_000. { // If the numbers are too big, decimal notation will give too many digit format!("{:+e}", num) } else { num.to_string() } } Expression::BoolLiteral(b) => b.to_string(), Expression::PropertyReference(nr) => { let access = access_member(nr, ctx); format!(r#"{}.get()"#, access) } Expression::BuiltinFunctionCall { function, arguments } => { compile_builtin_function_call(function.clone(), arguments, ctx) } Expression::CallBackCall{ callback, arguments } => { let f = access_member(callback, ctx); let mut a = arguments.iter().map(|a| compile_expression(a, ctx)); format!("{}.call({})", f, a.join(",")) } Expression::FunctionCall{ function, arguments } => { let f = access_member(function, ctx); let mut a = arguments.iter().map(|a| compile_expression(a, ctx)); format!("{}({})", f, a.join(",")) } Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => { let mut a = arguments.iter().map(|a| compile_expression(a, ctx)); format!("slint::private_api::{}({})", ident(function), a.join(",")) } Expression::FunctionParameterReference { index, .. } => format!("arg_{}", index), Expression::StoreLocalVariable { name, value } => { format!("auto {} = {};", ident(name), compile_expression(value, ctx)) } Expression::ReadLocalVariable { name, .. } => ident(name), Expression::StructFieldAccess { base, name } => match base.ty(ctx) { Type::Struct { fields, name : None, .. } => { let index = fields .keys() .position(|k| k == name) .expect("Expression::ObjectAccess: Cannot find a key in an object"); format!("std::get<{}>({})", index, compile_expression(base, ctx)) } Type::Struct{..} => { format!("{}.{}", compile_expression(base, ctx), ident(name)) } _ => panic!("Expression::ObjectAccess's base expression is not an Object type"), }, Expression::ArrayIndex { array, index } => { format!( "slint::private_api::access_array_index({}, {})", compile_expression(array, ctx), compile_expression(index, ctx) ) }, Expression::Cast { from, to } => { let f = compile_expression(from, ctx); match (from.ty(ctx), to) { (from, Type::String) if from.as_unit_product().is_some() => { format!("slint::SharedString::from_number({})", f) } (Type::Float32, Type::Model) | (Type::Int32, Type::Model) => { format!("std::make_shared(std::max(0, {}))", f) } (Type::Array(_), Type::Model) => f, (Type::Float32, Type::Color) => { format!("slint::Color::from_argb_encoded({})", f) } (Type::Color, Type::Brush) => { format!("slint::Brush({})", f) } (Type::Brush, Type::Color) => { format!("{}.color()", f) } (Type::Struct { .. }, Type::Struct{ fields, name: Some(_), ..}) => { format!( "[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})", struct_name = to.cpp_type().unwrap(), fields = fields.keys().enumerate().map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i)).join(""), obj = f, ) } (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| { let (field_count, qualified_elem_type_name) = match path_elem_expr.ty(ctx) { Type::Struct{ fields, name: Some(name), .. } => (fields.len(), name), _ => unreachable!() }; // Turn slint::private_api::PathLineTo into `LineTo` let elem_type_name = qualified_elem_type_name.split("::").last().unwrap().strip_prefix("Path").unwrap(); let elem_init = if field_count > 0 { compile_expression(path_elem_expr, ctx) } else { String::new() }; format!("slint::private_api::PathElement::{}({})", elem_type_name, elem_init) }), _ => { unreachable!() } }.collect::>(); format!( r#"[&](){{ slint::private_api::PathElement elements[{}] = {{ {} }}; return slint::private_api::PathData(&elements[0], std::size(elements)); }}()"#, path_elements.len(), path_elements.join(",") ) } (Type::Struct { .. }, Type::PathData) if matches!( from.as_ref(), Expression::Struct { ty: Type::Struct { .. }, .. } ) => { let (events, points) = match from.as_ref() { Expression::Struct { ty: _, values } => ( compile_expression(&values["events"], ctx), compile_expression(&values["points"], ctx), ), _ => { unreachable!() } }; format!( r#"[&](auto events, auto points){{ return slint::private_api::PathData(events.ptr, events.len, points.ptr, points.len); }}({}, {})"#, events, points ) } _ => f, } } Expression::CodeBlock(sub) => { match sub.len() { 0 => String::new(), 1 => compile_expression(&sub[0], ctx), len => { let mut x = sub.iter().enumerate().map(|(i, e)| { if i == len - 1 { return_compile_expression(e, ctx, None) + ";" } else { compile_expression(e, ctx) } }); format!("[&]{{ {} }}()", x.join(";")) } } } Expression::PropertyAssignment { property, value} => { let value = compile_expression(value, ctx); property_set_value_code(property, &value, ctx) } Expression::ModelDataAssignment { level, value } => { let value = compile_expression(value, ctx); let mut path = "self".to_string(); 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; write!(path, "->parent").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 as usize] .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); write!(path, "->repeater_{}", repeater_index).unwrap(); format!("{}.model_set_row_data({}.get(), {})", path, index_access, value) } 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); format!( "{}->set_row_data({}, {})", base_e, index_e, value_e ) } Expression::BinaryExpression { lhs, rhs, op } => { let mut buffer = [0; 3]; format!( "({lhs} {op} {rhs})", lhs = compile_expression(lhs, ctx), rhs = compile_expression(rhs, ctx), op = match op { '=' => "==", '!' => "!=", '≤' => "<=", '≥' => ">=", '&' => "&&", '|' => "||", '/' => "/(float)", _ => op.encode_utf8(&mut buffer), }, ) } Expression::UnaryOp { sub, op } => { format!("({op} {sub})", sub = compile_expression(sub, ctx), op = op,) } Expression::ImageReference { resource_ref, nine_slice } => { let image = match resource_ref { crate::expression_tree::ImageReference::None => r#"slint::Image()"#.to_string(), crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#, escape_string(path.as_str())), crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => { let symbol = format!("slint_embedded_resource_{}", resource_id); format!(r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#, escape_string(extension)) } crate::expression_tree::ImageReference::EmbeddedTexture{resource_id} => { format!("slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})") }, }; match &nine_slice { Some([a, b, c, d]) => { format!("([&] {{ auto image = {image}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()") } None => image, } } Expression::Condition { condition, true_expr, false_expr } => { let ty = expr.ty(ctx); let cond_code = compile_expression(condition, ctx); let cond_code = remove_parentheses(&cond_code); let true_code = return_compile_expression(true_expr, ctx, Some(&ty)); let false_code = return_compile_expression(false_expr, ctx, Some(&ty)); format!( r#"[&]() -> {} {{ if ({}) {{ {}; }} else {{ {}; }}}}()"#, ty.cpp_type().unwrap_or_else(|| "void".to_string()), cond_code, true_code, false_code ) } Expression::Array { element_ty, values, as_model } => { let ty = element_ty.cpp_type().unwrap(); let mut val = values.iter().map(|e| format!("{ty} ( {expr} )", expr = compile_expression(e, ctx), ty = ty)); if *as_model { format!( "std::make_shared>({val})", count = values.len(), ty = ty, val = val.join(", ") ) } else { format!( "slint::cbindgen_private::Slice<{ty}>{{ std::array<{ty}, {count}>{{ {val} }}.data(), {count} }}", count = values.len(), ty = ty, val = val.join(", ") ) } } Expression::Struct { ty, values } => { if let Type::Struct{fields, name: None, ..} = ty { let mut elem = fields.iter().map(|(k, t)| { values .get(k) .map(|e| compile_expression(e, ctx)) .map(|e| { // explicit conversion to avoid warning C4244 (possible loss of data) with MSVC if t.as_unit_product().is_some() { format!("{}({e})", t.cpp_type().unwrap()) } else {e} }) .unwrap_or_else(|| "(Error: missing member in object)".to_owned()) }); format!("std::make_tuple({})", elem.join(", ")) } else if let Type::Struct{ name: Some(_), .. } = ty { format!( "[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})", args = (0..values.len()).map(|i| format!("const auto &a_{}", i)).join(", "), ty = ty.cpp_type().unwrap(), fields = values.keys().enumerate().map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i)).join(""), vals = values.values().map(|e| compile_expression(e, ctx)).join(", "), ) } else { panic!("Expression::Object is not a Type::Object") } } Expression::EasingCurve(EasingCurve::Linear) => "slint::cbindgen_private::EasingCurve()".into(), Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!( "slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {}, {}, {}, {})", a, b, c, d ), Expression::EasingCurve(EasingCurve::EaseInElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseInElastic".into(), Expression::EasingCurve(EasingCurve::EaseOutElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseOutElastic".into(), Expression::EasingCurve(EasingCurve::EaseInOutElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseInOutElastic".into(), Expression::EasingCurve(EasingCurve::EaseInBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseInBounce".into(), Expression::EasingCurve(EasingCurve::EaseOutBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseOutElastic".into(), Expression::EasingCurve(EasingCurve::EaseInOutBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseInOutElastic".into(), Expression::LinearGradient{angle, stops} => { let angle = compile_expression(angle, ctx); let mut stops_it = stops.iter().map(|(color, stop)| { let color = compile_expression(color, ctx); let position = compile_expression(stop, ctx); format!("slint::private_api::GradientStop{{ {}, {}, }}", color, position) }); format!( "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::LinearGradientBrush({}, stops, {})); }}()", stops_it.join(", "), angle, stops.len() ) } Expression::RadialGradient{ stops} => { let mut stops_it = stops.iter().map(|(color, stop)| { let color = compile_expression(color, ctx); let position = compile_expression(stop, ctx); format!("slint::private_api::GradientStop{{ {}, {}, }}", color, position) }); format!( "[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::RadialGradientBrush(stops, {})); }}()", stops_it.join(", "), stops.len() ) } Expression::EnumerationValue(value) => { let prefix = if value.enumeration.node.is_some() { "" } else {"slint::cbindgen_private::"}; format!( "{prefix}{}::{}", ident(&value.enumeration.name), ident(&value.to_pascal_case()), ) } Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { let cache = access_member(layout_cache_prop, ctx); if let Some(ri) = repeater_index { format!("slint::private_api::layout_cache_access({}.get(), {}, {})", cache, index, compile_expression(ri, ctx)) } else { format!("{}.get()[{}]", cache, index) } } Expression::BoxLayoutFunction { cells_variable, repeater_indices, elements, orientation, sub_expression, } => box_layout_function( cells_variable, repeater_indices.as_ref().map(String::as_str), elements, *orientation, sub_expression, ctx, ), Expression::ComputeDialogLayoutCells { cells_variable, roles, unsorted_cells } => { let cells_variable = ident(cells_variable); let mut cells = match &**unsorted_cells { Expression::Array { values, .. } => { values.iter().map(|v| compile_expression(v, ctx)) } _ => panic!("dialog layout unsorted cells not an array"), }; format!("slint::cbindgen_private::GridLayoutCellData {cv}_array [] = {{ {c} }};\ slint::cbindgen_private::slint_reorder_dialog_button_layout({cv}_array, {r});\ slint::cbindgen_private::Slice {cv} {{ std::data({cv}_array), std::size({cv}_array) }}", r = compile_expression(roles, ctx), cv = cells_variable, c = cells.join(", "), ) } Expression::MinMax { ty, op, lhs, rhs } => { let ident = match op { MinMaxOp::Min => "min", MinMaxOp::Max => "max", }; let lhs_code = compile_expression(lhs, ctx); let rhs_code = compile_expression(rhs, ctx); format!( r#"std::{ident}<{ty}>({lhs_code}, {rhs_code})"#, ty = ty.cpp_type().unwrap_or_default(), ident = ident, lhs_code = lhs_code, rhs_code = rhs_code ) } } } fn compile_builtin_function_call( function: BuiltinFunction, arguments: &[llr::Expression], ctx: &EvaluationContext, ) -> String { let mut a = arguments.iter().map(|a| compile_expression(a, ctx)); let pi_180 = std::f64::consts::PI / 180.0; match function { BuiltinFunction::GetWindowScaleFactor => { format!("{}.scale_factor()", access_window_field(ctx)) } BuiltinFunction::GetWindowDefaultFontSize => { format!("{}.default_font_size()", access_window_field(ctx)) } BuiltinFunction::AnimationTick => "slint::cbindgen_private::slint_animation_tick()".into(), BuiltinFunction::Debug => { ctx.generator_state.conditional_includes.iostream.set(true); format!("std::cout << {} << std::endl;", a.join("<<")) } BuiltinFunction::Mod => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::fmod({}, {})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::Round => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::round({})", a.next().unwrap()) } BuiltinFunction::Ceil => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::ceil({})", a.next().unwrap()) } BuiltinFunction::Floor => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::floor({})", a.next().unwrap()) } BuiltinFunction::Sqrt => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::sqrt({})", a.next().unwrap()) } BuiltinFunction::Abs => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::abs({})", a.next().unwrap()) } BuiltinFunction::Log => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::log({}) / std::log({})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::Pow => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::pow(({}), ({}))", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::Sin => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::sin(({}) * {})", a.next().unwrap(), pi_180) } BuiltinFunction::Cos => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::cos(({}) * {})", a.next().unwrap(), pi_180) } BuiltinFunction::Tan => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::tan(({}) * {})", a.next().unwrap(), pi_180) } BuiltinFunction::ASin => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::asin({}) / {}", a.next().unwrap(), pi_180) } BuiltinFunction::ACos => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::acos({}) / {}", a.next().unwrap(), pi_180) } BuiltinFunction::ATan => { ctx.generator_state.conditional_includes.cmath.set(true); format!("std::atan({}) / {}", a.next().unwrap(), pi_180) } BuiltinFunction::SetFocusItem => { if let [llr::Expression::PropertyReference(pr)] = arguments { let window = access_window_field(ctx); let focus_item = access_item_rc(pr, ctx); format!("{}.set_focus_item({}, true);", window, focus_item) } else { panic!("internal error: invalid args to SetFocusItem {:?}", arguments) } } BuiltinFunction::ClearFocusItem => { if let [llr::Expression::PropertyReference(pr)] = arguments { let window = access_window_field(ctx); let focus_item = access_item_rc(pr, ctx); format!("{}.set_focus_item({}, false);", window, focus_item) } else { panic!("internal error: invalid args to ClearFocusItem {:?}", arguments) } } /* std::from_chars is unfortunately not yet implemented in all stdlib compiler we support. * And std::strtod depends on the locale. Use slint_string_to_float implemented in Rust BuiltinFunction::StringIsFloat => { "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }" .into() } BuiltinFunction::StringToFloat => { "[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a) ? v : 0; }" .into() }*/ BuiltinFunction::StringIsFloat => { ctx.generator_state.conditional_includes.cstdlib.set(true); format!("[](const auto &a){{ float res = 0; return slint::cbindgen_private::slint_string_to_float(&a, &res); }}({})", a.next().unwrap()) } BuiltinFunction::StringToFloat => { ctx.generator_state.conditional_includes.cstdlib.set(true); format!("[](const auto &a){{ float res = 0; slint::cbindgen_private::slint_string_to_float(&a, &res); return res; }}({})", a.next().unwrap()) } BuiltinFunction::ColorRgbaStruct => { format!("{}.to_argb_uint()", a.next().unwrap()) } BuiltinFunction::ColorHsvaStruct => { format!("{}.to_hsva()", a.next().unwrap()) } BuiltinFunction::ColorBrighter => { format!("{}.brighter({})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::ColorDarker => { format!("{}.darker({})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::ColorTransparentize => { format!("{}.transparentize({})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::ColorMix => { format!("{}.mix({}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::ColorWithAlpha => { format!("{}.with_alpha({})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::ImageSize => { format!("{}.size()", a.next().unwrap()) } BuiltinFunction::ArrayLength => { format!("slint::private_api::model_length({})", a.next().unwrap()) } BuiltinFunction::Rgb => { format!("slint::Color::from_argb_uint8(std::clamp(static_cast({a}) * 255., 0., 255.), std::clamp(static_cast({r}), 0, 255), std::clamp(static_cast({g}), 0, 255), std::clamp(static_cast({b}), 0, 255))", r = a.next().unwrap(), g = a.next().unwrap(), b = a.next().unwrap(), a = a.next().unwrap(), ) } BuiltinFunction::Hsv => { format!("slint::Color::from_hsva(std::clamp(static_cast({h}), 0.f, 360.f), std::clamp(static_cast({s}), 0.f, 1.f), std::clamp(static_cast({v}), 0.f, 1.f), std::clamp(static_cast({a}), 0.f, 1.f))", h = a.next().unwrap(), s = a.next().unwrap(), v = a.next().unwrap(), a = a.next().unwrap(), ) } BuiltinFunction::ColorScheme => { format!("{}.color_scheme()", access_window_field(ctx)) } BuiltinFunction::Use24HourFormat => { format!("slint::cbindgen_private::slint_date_time_use_24_hour_format()") } BuiltinFunction::MonthDayCount => { format!("slint::cbindgen_private::slint_date_time_month_day_count({}, {})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::MonthOffset => { format!("slint::cbindgen_private::slint_date_time_month_offset({}, {})", a.next().unwrap(), a.next().unwrap()) } BuiltinFunction::FormatDate => { format!("[](const auto &format, int d, int m, int y) {{ slint::SharedString out; slint::cbindgen_private::slint_date_time_format_date(&format, d, m, y, &out); return out; }}({}, {}, {}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap() ) } BuiltinFunction::DateNow => { "[] { int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_date_now(&d, &m, &y); return std::make_shared>(d, m, y); }()".into() } BuiltinFunction::ValidDate => { format!( "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; return slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); }}({}, {})", a.next().unwrap(), a.next().unwrap() ) } BuiltinFunction::ParseDate => { format!( "[](const auto &a, const auto &b) {{ int32_t d=0, m=0, y=0; slint::cbindgen_private::slint_date_time_parse_date(&a, &b, &d, &m, &y); return std::make_shared>(d, m, y); }}({}, {})", a.next().unwrap(), a.next().unwrap() ) } BuiltinFunction::SetTextInputFocused => { format!("{}.set_text_input_focused({})", access_window_field(ctx), a.next().unwrap()) } BuiltinFunction::TextInputFocused => { format!("{}.text_input_focused()", access_window_field(ctx)) } BuiltinFunction::ShowPopupWindow => { if let [llr::Expression::NumberLiteral(popup_index), x, y, close_on_click, llr::Expression::PropertyReference(parent_ref)] = arguments { let mut parent_ctx = ctx; let mut component_access = "self".into(); if let llr::PropertyReference::InParent { level, .. } = parent_ref { for _ in 0..level.get() { component_access = format!("{}->parent", component_access); parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx; } }; let window = access_window_field(ctx); let current_sub_component = parent_ctx.current_sub_component.unwrap(); let popup_window_id = ident(¤t_sub_component.popup_windows[*popup_index as usize].root.name); let parent_component = access_item_rc(parent_ref, ctx); let x = compile_expression(x, ctx); let y = compile_expression(y, ctx); let close_on_click = compile_expression(close_on_click, ctx); format!( "{window}.show_popup<{popup_window_id}>({component_access}, {{ static_cast({x}), static_cast({y}) }}, {close_on_click}, {{ {parent_component} }})" ) } else { panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments) } } BuiltinFunction::ClosePopupWindow => { let window = access_window_field(ctx); format!("{window}.close_popup()") } 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 = access_window_field(ctx); let start = compile_expression(from, ctx); let end = compile_expression(to, ctx); format!("slint_textinput_set_selection_offsets(&{item}, &{window}.handle(), &{item_rc}, static_cast({start}), static_cast({end}))") } else { panic!("internal error: invalid args to set-selection-offsets {:?}", arguments) } } BuiltinFunction::ItemMemberFunction(name) => { if let [llr::Expression::PropertyReference(pr)] = arguments { let item = access_member(pr, ctx); let item_rc = access_item_rc(pr, ctx); let window = access_window_field(ctx); let native = native_item(pr, ctx); let function_name = format!( "slint_{}_{}", native.class_name.to_lowercase(), ident(&name).to_lowercase() ); format!("{function_name}(&{item}, &{window}.handle(), &{item_rc})") } else { panic!("internal error: invalid args to ItemMemberFunction {:?}", arguments) } } BuiltinFunction::ItemAbsolutePosition => { if let [llr::Expression::PropertyReference(pr)] = arguments { let item_rc = access_item_rc(pr, ctx); format!("slint::LogicalPosition(slint::cbindgen_private::slint_item_absolute_position(&{item_rc}))") } else { panic!("internal error: invalid args to ItemAbsolutePosition {:?}", arguments) } } BuiltinFunction::RegisterCustomFontByPath => { if let [llr::Expression::StringLiteral(path)] = arguments { let window = access_window_field(ctx); format!("{window}.register_font_from_path(\"{}\");", escape_string(path)) } else { panic!( "internal error: argument to RegisterCustomFontByPath must be a string literal" ) } } BuiltinFunction::RegisterCustomFontByMemory => { if let [llr::Expression::NumberLiteral(resource_id)] = &arguments { let window = access_window_field(ctx); let resource_id: usize = *resource_id as _; let symbol = format!("slint_embedded_resource_{}", resource_id); format!("{window}.register_font_from_data({symbol}, std::size({symbol}));") } else { panic!("internal error: invalid args to RegisterCustomFontByMemory {:?}", arguments) } } BuiltinFunction::RegisterBitmapFont => { if let [llr::Expression::NumberLiteral(resource_id)] = &arguments { let window = access_window_field(ctx); let resource_id: usize = *resource_id as _; let symbol = format!("slint_embedded_resource_{}", resource_id); format!("{window}.register_bitmap_font({symbol});") } else { panic!("internal error: invalid args to RegisterBitmapFont {:?}", arguments) } } BuiltinFunction::ImplicitLayoutInfo(orient) => { if let [llr::Expression::PropertyReference(pr)] = arguments { let native = native_item(pr, ctx); format!( "{vt}->layout_info({{{vt}, const_cast(&{i})}}, {o}, &{window})", vt = native.cpp_vtable_getter, ty = native.class_name, o = to_cpp_orientation(orient), i = access_member(pr, ctx), window = access_window_field(ctx) ) } else { panic!("internal error: invalid args to ImplicitLayoutInfo {:?}", arguments) } } BuiltinFunction::Translate => { format!("slint::private_api::translate({})", a.join(",")) } } } fn box_layout_function( cells_variable: &str, repeated_indices: Option<&str>, elements: &[Either], orientation: Orientation, sub_expression: &llr::Expression, ctx: &llr_EvaluationContext, ) -> String { let repeated_indices = repeated_indices.map(ident); let mut push_code = "std::vector cells_vector;".to_owned(); let mut repeater_idx = 0usize; for item in elements { match item { Either::Left(value) => { write!( push_code, "cells_vector.push_back({{ {} }});", compile_expression(value, ctx) ) .unwrap(); } Either::Right(repeater) => { write!(push_code, "self->repeater_{}.ensure_updated(self);", repeater).unwrap(); if let Some(ri) = &repeated_indices { write!(push_code, "{}_array[{}] = cells_vector.size();", ri, repeater_idx * 2) .unwrap(); write!(push_code, "{ri}_array[{c}] = self->repeater_{id}.inner ? self->repeater_{id}.inner->data.size() : 0;", ri = ri, c = repeater_idx * 2 + 1, id = repeater, ).unwrap(); } repeater_idx += 1; write!( push_code, "if (self->repeater_{id}.inner) \ for (auto &&sub_comp : self->repeater_{id}.inner->data) \ cells_vector.push_back((*sub_comp.ptr)->box_layout_data({o}));", id = repeater, o = to_cpp_orientation(orientation), ) .unwrap(); } } } let ri = repeated_indices.as_ref().map_or(String::new(), |ri| { write!( push_code, "slint::cbindgen_private::Slice {ri}{{ {ri}_array.data(), {ri}_array.size() }};", ri = ri ) .unwrap(); format!("std::array {}_array;", 2 * repeater_idx, ri) }); format!( "[&]{{ {} {} slint::cbindgen_private::Slice{}{{cells_vector.data(), cells_vector.size()}}; return {}; }}()", ri, push_code, ident(cells_variable), compile_expression(sub_expression, ctx) ) } /// Like compile expression, but prepended with `return` if not void. /// ret_type is the expecting type that should be returned with that return statement fn return_compile_expression( expr: &llr::Expression, ctx: &EvaluationContext, ret_type: Option<&Type>, ) -> String { let e = compile_expression(expr, ctx); if ret_type == Some(&Type::Void) || ret_type == Some(&Type::Invalid) { e } else { let ty = expr.ty(ctx); if ty == Type::Invalid && ret_type.is_some() { // e is unreachable so it probably throws. But we still need to return something to avoid a warning format!("{}; return {{}}", e) } else if ty == Type::Invalid || ty == Type::Void { e } else { format!("return {}", e) } } } fn generate_type_aliases(file: &mut File, doc: &Document) { let type_aliases = 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 { name: Some(name), node: Some(_), .. } => { Some((&export.0.name, name)) } 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)| { Declaration::TypeAlias(TypeAlias { old_name: ident(type_name), new_name: ident(export_name), }) }); file.declarations.extend(type_aliases); }