slint/internal/compiler/generator/cpp.rs
Milian Wolff 69c68b22b2 Also wrap langtype::Type::Struct in an Rc
This makes copying such types much cheaper and will allow us to
intern common struct types in the future too. This further
drops the sample cost for langtype.rs from ~6.6% down to 4.0%.

We are now also able to share/intern common struct types.

Before:
```
  Time (mean ± σ):      1.073 s ±  0.021 s    [User: 0.759 s, System: 0.215 s]
  Range (min … max):    1.034 s …  1.105 s    10 runs

        allocations:            3074261
```

After:
```
  Time (mean ± σ):      1.034 s ±  0.026 s    [User: 0.733 s, System: 0.201 s]
  Range (min … max):    1.000 s …  1.078 s    10 runs

        allocations:            2917476
```
2024-10-28 09:39:54 +01:00

3839 lines
153 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// 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 lyon_path::geom::euclid::approxeq::ApproxEq;
use std::collections::HashSet;
use std::fmt::Write;
use std::io::BufWriter;
use std::sync::OnceLock;
use smol_str::{format_smolstr, SmolStr, StrExt};
/// The configuration for the C++ code generator
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Config {
pub namespace: Option<String>,
pub cpp_files: Vec<std::path::PathBuf>,
pub header_include: String,
}
// Check if word is one of C++ keywords
fn is_cpp_keyword(word: &str) -> bool {
static CPP_KEYWORDS: OnceLock<HashSet<&'static str>> = OnceLock::new();
let keywords = CPP_KEYWORDS.get_or_init(|| {
#[rustfmt::skip]
let keywords: HashSet<&str> = HashSet::from([
"alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit",
"atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch",
"char", "char8_t", "char16_t", "char32_t", "class", "compl", "concept", "const",
"consteval", "constexpr", "constinit", "const_cast", "continue", "co_await",
"co_return", "co_yield", "decltype", "default", "delete", "do", "double",
"dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float",
"for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace",
"new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private",
"protected", "public", "reflexpr", "register", "reinterpret_cast", "requires",
"return", "short", "signed", "sizeof", "static", "static_assert", "static_cast",
"struct", "switch", "synchronized", "template", "this", "thread_local", "throw",
"true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using",
"virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq",
]);
keywords
});
keywords.contains(word)
}
fn ident(ident: &str) -> SmolStr {
let mut new_ident = SmolStr::from(ident);
if ident.contains('-') {
new_ident = ident.replace_smolstr("-", "_");
}
if is_cpp_keyword(new_ident.as_str()) {
new_ident = format_smolstr!("{}_", new_ident);
}
new_ident
}
fn concatenate_ident(ident: &str) -> SmolStr {
if ident.contains('-') {
ident.replace_smolstr("-", "_")
} 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};
use smol_str::{format_smolstr, SmolStr};
thread_local!(static INDENTATION : Cell<u32> = 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 is_cpp_file: bool,
pub includes: Vec<SmolStr>,
pub after_includes: String,
pub namespace: Option<String>,
pub declarations: Vec<Declaration>,
pub resources: Vec<Declaration>,
pub definitions: Vec<Declaration>,
}
impl File {
pub fn split_off_cpp_files(&mut self, header_file_name: String, count: usize) -> Vec<File> {
let mut cpp_files = Vec::with_capacity(count);
if count > 0 {
let mut definitions = Vec::new();
let mut i = 0;
while i < self.definitions.len() {
if matches!(
&self.definitions[i],
Declaration::Function(Function { template_parameters: Some(..), .. })
| Declaration::TypeAlias(..)
) {
i += 1;
continue;
}
definitions.push(self.definitions.remove(i));
}
let mut cpp_resources = self
.resources
.iter_mut()
.filter_map(|header_resource| match header_resource {
Declaration::Var(var) => {
var.is_extern = true;
Some(Declaration::Var(Var {
ty: var.ty.clone(),
name: var.name.clone(),
array_size: var.array_size.clone(),
init: std::mem::take(&mut var.init),
is_extern: false,
..Default::default()
}))
}
_ => None,
})
.collect::<Vec<_>>();
let cpp_includes = vec![format_smolstr!("\"{header_file_name}\"")];
let def_chunk_size = definitions.len() / count;
let res_chunk_size = cpp_resources.len() / count;
cpp_files.extend((0..count - 1).map(|_| File {
is_cpp_file: true,
includes: cpp_includes.clone(),
after_includes: String::new(),
namespace: self.namespace.clone(),
declarations: Default::default(),
resources: cpp_resources.drain(0..res_chunk_size).collect(),
definitions: definitions.drain(0..def_chunk_size).collect(),
}));
cpp_files.push(File {
is_cpp_file: true,
includes: cpp_includes,
after_includes: String::new(),
namespace: self.namespace.clone(),
declarations: Default::default(),
resources: cpp_resources,
definitions,
});
cpp_files.resize_with(count, Default::default);
}
// Any definition in the header file is inline.
self.definitions.iter_mut().for_each(|def| match def {
Declaration::Function(f) => f.is_inline = true,
Declaration::Var(v) => v.is_inline = true,
_ => {}
});
cpp_files
}
}
impl Display for File {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
writeln!(f, "// This file is auto-generated")?;
if !self.is_cpp_file {
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.iter().chain(self.resources.iter()) {
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: SmolStr,
pub members: Vec<(Access, Declaration)>,
pub friends: Vec<SmolStr>,
}
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<Item = Declaration> + '_ {
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_smolstr!("{}::{}", 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(),
..Default::default()
}))
}
_ => None,
})
}
}
#[derive(Default, Debug)]
pub struct Enum {
pub name: SmolStr,
pub values: Vec<SmolStr>,
}
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: SmolStr,
/// "(...) -> ..."
pub signature: String,
/// The function does not have return type
pub is_constructor_or_destructor: bool,
pub is_static: bool,
pub is_friend: bool,
pub is_inline: bool,
/// The list of statement instead the function. When None, this is just a function
/// declaration without the definition
pub statements: Option<Vec<String>>,
/// What's inside template<...> if any
pub template_parameters: Option<String>,
/// Explicit initializers, such as FooClass::FooClass() : someMember(42) {}
pub constructor_member_initializers: Vec<String>,
}
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 ")?;
}
if self.is_inline {
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 is_inline: bool,
pub is_extern: bool,
pub ty: SmolStr,
pub name: SmolStr,
pub array_size: Option<usize>,
pub init: Option<String>,
}
impl Display for Var {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
indent(f)?;
if self.is_extern {
write!(f, "extern ")?;
}
if self.is_inline {
write!(f, "inline ")?;
}
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: SmolStr,
pub old_name: SmolStr,
}
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<SmolStr>;
}
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::{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<bool>,
cstdlib: Cell<bool>,
cmath: Cell<bool>,
}
#[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<SmolStr> {
match self {
Type::Void => Some("void".into()),
Type::Float32 => Some("float".into()),
Type::Int32 => Some("int".into()),
Type::String => Some("slint::SharedString".into()),
Type::Color => Some("slint::Color".into()),
Type::Duration => Some("std::int64_t".into()),
Type::Angle => Some("float".into()),
Type::PhysicalLength => Some("float".into()),
Type::LogicalLength => Some("float".into()),
Type::Rem => Some("float".into()),
Type::Percent => Some("float".into()),
Type::Bool => Some("bool".into()),
Type::Struct(s) => match (&s.name, &s.node) {
(Some(name), Some(_)) => Some(ident(name)),
(Some(name), None) => Some(if name.starts_with("slint::") {
name.clone()
} else {
format_smolstr!("slint::cbindgen_private::{}", ident(name))
}),
_ => {
let elem =
s.fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
Some(format_smolstr!("std::tuple<{}>", elem.join(", ")))
}
},
Type::Array(i) => {
Some(format_smolstr!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?))
}
Type::Image => Some("slint::Image".into()),
Type::Enumeration(enumeration) => {
if enumeration.node.is_some() {
Some(ident(&enumeration.name))
} else {
Some(format_smolstr!("slint::cbindgen_private::{}", ident(&enumeration.name)))
}
}
Type::Brush => Some("slint::Brush".into()),
Type::LayoutCache => Some("slint::SharedVector<float>".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<String>,
ctx: &EvaluationContext,
) {
let prop_access = access_member(prop, ctx);
let prop_type = ctx.property_ty(prop);
if let Type::Callback(callback) = &prop_type {
let mut ctx2 = ctx.clone();
ctx2.argument_types = &callback.args;
let mut params = callback.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,
callback.return_type.as_ref()
)
));
} 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,
) -> std::io::Result<impl std::fmt::Display> {
let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
file.includes.push("<array>".into());
file.includes.push("<limits>".into());
file.includes.push("<slint.h>".into());
for (path, er) in doc.embedded_file_resources.borrow().iter() {
embed_resource(er, path, &mut file.resources);
}
for ty in doc.used_types.borrow().structs_and_enums.iter() {
match ty {
Type::Struct(s) if s.name.is_some() && s.node.is_some() => {
generate_struct(
&mut file,
s.name.as_ref().unwrap(),
&s.fields,
s.node.as_ref().unwrap(),
);
}
Type::Enumeration(en) => {
generate_enum(&mut file, en);
}
_ => (),
}
}
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::<Vec<_>>());
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<slint::Window>".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()
}),
));
let mut window_creation_code = vec![
format!("auto self = const_cast<SharedGlobals *>(this);"),
"if (!self->m_window.has_value()) {".into(),
" auto &window = self->m_window.emplace(slint::private_api::WindowAdapterRc());".into(),
];
if !compiler_config.const_scale_factor.approx_eq(&1.0) {
window_creation_code.push(format!(
"window.dispatch_scale_factor_change_event({});",
compiler_config.const_scale_factor
));
}
window_creation_code.extend([
" window.window_handle().set_component(self->root_weak);".into(),
"}".into(),
"return *self->m_window;".into(),
]);
globals_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "window".into(),
signature: "() const -> slint::Window&".into(),
statements: Some(window_creation_code),
..Default::default()
}),
));
for glob in &llr.globals {
let ty = if glob.is_builtin {
format_smolstr!("slint::cbindgen_private::{}", glob.name)
} else if glob.must_generate() {
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)
} else {
continue;
};
globals_struct.members.push((
Access::Public,
Declaration::Var(Var {
ty: format_smolstr!("std::shared_ptr<{}>", ty),
name: format_smolstr!("global_{}", concatenate_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("<iostream>".into());
}
if conditional_includes.cstdlib.get() {
file.includes.push("<cstdlib>".into());
}
if conditional_includes.cmath.get() {
file.includes.push("<cmath>".into());
}
let cpp_files = file.split_off_cpp_files(config.header_include, config.cpp_files.len());
for (cpp_file_name, cpp_file) in config.cpp_files.iter().zip(cpp_files) {
use std::io::Write;
write!(&mut BufWriter::new(std::fs::File::create(&cpp_file_name)?), "{}", cpp_file)?;
}
Ok(file)
}
fn embed_resource(
resource: &crate::embedded_resources::EmbeddedResources,
path: &SmolStr,
declarations: &mut Vec<Declaration>,
) {
match &resource.kind {
crate::embedded_resources::EmbeddedResourcesKind::ListOnly => {}
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('}');
declarations.push(Declaration::Var(Var {
ty: "const uint8_t".into(),
name: format_smolstr!("slint_embedded_resource_{}", resource.id),
array_size: Some(data.len()),
init: Some(init),
..Default::default()
}));
}
#[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_smolstr!("slint_embedded_resource_{}_data", resource.id);
declarations.push(Declaration::Var(Var {
ty: "const uint8_t".into(),
name: data_name.clone(),
array_size: Some(count),
init: Some(format!("{{ {data} }}")),
..Default::default()
}));
let texture_name = format_smolstr!("slint_embedded_resource_{}_texture", resource.id);
declarations.push(Declaration::Var(Var {
ty: "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,
}}"
)),
..Default::default()
}));
let init = format!("slint::cbindgen_private::types::StaticTextures {{
.size = {{ {width}, {height} }},
.original_size = {{ {unscaled_width}, {unscaled_height} }},
.data = slint::cbindgen_private::Slice<uint8_t>{{ {data_name} , {count} }},
.textures = slint::cbindgen_private::Slice<slint::cbindgen_private::types::StaticTexture>{{ &{texture_name}, 1 }}
}}");
declarations.push(Declaration::Var(Var {
ty: "const slint::cbindgen_private::types::StaticTextures".into(),
name: format_smolstr!("slint_embedded_resource_{}", resource.id),
array_size: None,
init: Some(init),
..Default::default()
}))
}
#[cfg(feature = "software-renderer")]
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
crate::embedded_resources::BitmapFont {
family_name,
character_map,
units_per_em,
ascent,
descent,
x_height,
cap_height,
glyphs,
weight,
italic,
sdf,
},
) => {
let family_name_var =
format_smolstr!("slint_embedded_resource_{}_family_name", resource.id);
let family_name_size = family_name.len();
declarations.push(Declaration::Var(Var {
ty: "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(", ")
)),
..Default::default()
}));
let charmap_var = format_smolstr!("slint_embedded_resource_{}_charmap", resource.id);
let charmap_size = character_map.len();
declarations.push(Declaration::Var(Var {
ty: "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(", ")
)),
..Default::default()
}));
for (glyphset_index, glyphset) in glyphs.iter().enumerate() {
for (glyph_index, glyph) in glyphset.glyph_data.iter().enumerate() {
declarations.push(Declaration::Var(Var {
ty: "const uint8_t".into(),
name: format_smolstr!(
"slint_embedded_resource_{}_gs_{}_gd_{}",
resource.id,
glyphset_index,
glyph_index
),
array_size: Some(glyph.data.len()),
init: Some(format!(
"{{ {} }}",
glyph.data.iter().map(ToString::to_string).join(", ")
)),
..Default::default()
}));
}
declarations.push(Declaration::Var(Var{
ty: "const slint::cbindgen_private::BitmapGlyph".into(),
name: format_smolstr!("slint_embedded_resource_{}_glyphset_{}", resource.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<uint8_t>{{ {}, {} }} }}",
glyph.x, glyph.y, glyph.width, glyph.height, glyph.x_advance,
format!("slint_embedded_resource_{}_gs_{}_gd_{}", resource.id, glyphset_index, glyph_index),
glyph.data.len()
)
}).join(", \n"))),
..Default::default()
}));
}
let glyphsets_var =
format_smolstr!("slint_embedded_resource_{}_glyphsets", resource.id);
let glyphsets_size = glyphs.len();
declarations.push(Declaration::Var(Var {
ty: "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<slint::cbindgen_private::BitmapGlyph>{{
{}, {}
}}
}}",
glyphset.pixel_size, format!("slint_embedded_resource_{}_glyphset_{}", resource.id, glyphset_index), glyphset.glyph_data.len()
))
.join(", \n")
)),
..Default::default()
}));
let init = format!(
"slint::cbindgen_private::BitmapFont {{
.family_name = slint::cbindgen_private::Slice<uint8_t>{{ {family_name_var} , {family_name_size} }},
.character_map = slint::cbindgen_private::Slice<slint::cbindgen_private::CharacterMapEntry>{{ {charmap_var}, {charmap_size} }},
.units_per_em = {units_per_em},
.ascent = {ascent},
.descent = {descent},
.x_height = {x_height},
.cap_height = {cap_height},
.glyphs = slint::cbindgen_private::Slice<slint::cbindgen_private::BitmapGlyphs>{{ {glyphsets_var}, {glyphsets_size} }},
.weight = {weight},
.italic = {italic},
.sdf = {sdf},
}}"
);
declarations.push(Declaration::Var(Var {
ty: "const slint::cbindgen_private::BitmapFont".into(),
name: format_smolstr!("slint_embedded_resource_{}", resource.id),
array_size: None,
init: Some(init),
..Default::default()
}))
}
}
}
fn generate_struct(
file: &mut File,
name: &str,
fields: &BTreeMap<SmolStr, Type>,
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::<Vec<_>>();
members.push((
Access::Public,
Declaration::Function(Function {
name: "operator==".into(),
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<Enumeration>) {
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.must_generate()) {
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.must_generate()) {
let accessor_statement = format!(
"{0}if constexpr(std::is_same_v<T, {1}>) {{ return *m_globals.global_{1}.get(); }}",
if global_accessor_function_body.is_empty() { "" } else { "else " },
concatenate_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: "() -> void".into(),
statements: Some(vec!["window().show();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "hide".into(),
signature: "() -> void".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: "() -> void".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<SmolStr>, 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.item_tree.root, false)
}
}
file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
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<ParentCtx>,
item_tree_class_name: SmolStr,
field_access: Access,
file: &mut File,
conditional_includes: &ConditionalIncludes,
) {
target_struct.friends.push(format_smolstr!(
"vtable::VRc<slint::private_api::ItemTreeVTable, {}>",
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<String> = Default::default();
let mut item_array: Vec<String> = 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<const {}*>(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<const {}*>(component.instance);", item_tree_class_name),
"return self->subtree_range(dyn_index);".to_owned(),
];
subtree_component_statement = vec![
format!("auto self = reinterpret_cast<const {}*>(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<const {}*>(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<slint::private_api::ItemTreeNode>".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<const {}*>(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<uintptr_t>::max();".into()]),
..Default::default()
}),
));
target_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "item_tree".into(),
signature: "() -> slint::cbindgen_private::Slice<slint::private_api::ItemTreeNode>".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<slint::private_api::ItemTreeNode*>(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<slint::private_api::ItemArrayEntry*>(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<const {}*>(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<const {}*>(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<const {}*>(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<const {}*>(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<const {}*>(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<const {}*>(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, [[maybe_unused]] uint32_t index, [[maybe_unused]] slint::SharedString *result) -> bool"
.into(),
is_static: true,
statements: Some(if root.has_debug_info {
vec![
format!("if (auto infos = reinterpret_cast<const {}*>(component.instance)->element_infos(index)) {{ *result = *infos; }};",
item_tree_class_name),
"return true;".into()
]
} else {
vec!["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<slint::private_api::WindowAdapterRc>* 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".into(),
name: "static_vtable".into(),
..Default::default()
}),
));
file.definitions.push(Declaration::Var(Var {
ty: "const slint::private_api::ItemTreeVTable".into(),
name: format_smolstr!("{}::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<slint::private_api::ItemTreeVTable, {0}>::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_smolstr!("~{}", 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<ParentCtx>,
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<String> =
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.into(),
name: "globals".into(),
..Default::default()
}),
));
init.push("self->globals = globals;".into());
target_struct.members.push((
field_access,
Declaration::Var(Var {
ty: "uint32_t".into(),
name: "tree_index_of_first_child".into(),
..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".into(),
name: "tree_index".into(),
..Default::default()
}),
));
init.push("self->tree_index = tree_index;".into());
if let Some(parent_ctx) = &parent_ctx {
let parent_type = format_smolstr!(
"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".into(), ..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(|popup| {
let component_id = ident(&popup.item_tree.root.name);
let mut popup_struct = Struct { name: component_id.clone(), ..Default::default() };
generate_item_tree(
&mut popup_struct,
&popup.item_tree,
root,
Some(ParentCtx::new(&ctx, None)),
component_id,
Access::Public,
file,
conditional_includes,
);
file.definitions.extend(popup_struct.extract_definitions().collect::<Vec<_>>());
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(callback) = &property.ty {
let param_types =
callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let return_type = callback
.return_type
.as_ref()
.map_or(SmolStr::new_static("void"), |t| t.cpp_type().unwrap());
format_smolstr!(
"slint::private_api::Callback<{}({})>",
return_type,
param_types.join(", ")
)
} else {
format_smolstr!("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_smolstr!("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 && component.prop_used(prop) {
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_smolstr!("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_smolstr!("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<slint::private_api::UIntModel>({})", 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_smolstr!(
"slint::private_api::Repeater<class {}, {}>",
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}; }}, []([[maybe_unused]] auto self, auto) {{ {code}; }});")
}));
if !component.timers.is_empty() {
let mut update_timers = vec!["auto self = this;".into()];
for (i, tmr) in component.timers.iter().enumerate() {
user_init.push(format!("self->update_timers();"));
let name = format_smolstr!("timer{}", i);
let running = compile_expression(&tmr.running.borrow(), &ctx);
let interval = compile_expression(&tmr.interval.borrow(), &ctx);
let callback = compile_expression(&tmr.triggered.borrow(), &ctx);
update_timers.push(format!("if ({running}) {{"));
update_timers
.push(format!(" auto interval = std::chrono::milliseconds({interval});"));
update_timers.push(format!(
" if (!self->{name}.running() || self->{name}.interval() != interval)"
));
update_timers.push(format!(" self->{name}.start(slint::TimerMode::Repeated, interval, [self] {{ {callback}; }});"));
update_timers.push(format!("}} else {{ self->{name}.stop(); }}").into());
target_struct.members.push((
field_access,
Declaration::Var(Var { ty: "slint::Timer".into(), name, ..Default::default() }),
));
}
target_struct.members.push((
field_access,
Declaration::Function(Function {
name: "update_timers".into(),
signature: "() -> void".into(),
statements: Some(update_timers),
..Default::default()
}),
));
}
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".into(),
signature: format!("({}) -> void", init_parameters.join(",")),
statements: Some(init),
..Default::default()
}),
));
target_struct.members.push((
field_access,
Declaration::Function(Function {
name: "user_init".into(),
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<String>| {
let mut code = ["[[maybe_unused]] auto self = this;".into()]
.into_iter()
.chain(code)
.collect::<Vec<_>>();
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("->") && !signature.contains("-> void") { "{}" } 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::<u32, BTreeSet<_>>::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<slint::SharedString>",
", what",
accessible_string_cases,
);
dispatch_item_function(
"accessibility_action",
"(uint32_t index, const slint::cbindgen_private::AccessibilityAction &action) const -> void",
", 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<slint::SharedString>",
"",
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<float> *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<void *>(static_cast<const void *>(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<const {}*>(component.instance);",
repeater_id
),
format!("return {}.get();", index),
]);
}
}
file.definitions.extend(repeater_struct.extract_definitions().collect::<Vec<_>>());
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(callback) = &property.ty {
let param_types =
callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let return_type = callback
.return_type
.as_ref()
.map_or(SmolStr::new_static("void"), |t| t.cpp_type().unwrap());
format_smolstr!(
"slint::private_api::Callback<{}({})>",
return_type,
param_types.join(", ")
)
} else {
format_smolstr!("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,
)
}
}
for (i, _) in global.change_callbacks.iter() {
global_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: "slint::private_api::ChangeTracker".into(),
name: format_smolstr!("change_tracker{}", i),
..Default::default()
}),
));
}
init.extend(global.change_callbacks.iter().map(|(p, e)| {
let code = compile_expression(&e.borrow(), &ctx);
let prop = access_member(&llr::PropertyReference::Local { sub_component_path: vec![], property_index: *p }, &ctx);
format!("this->change_tracker{p}.init(this, [this]([[maybe_unused]] auto self) {{ return {prop}.get(); }}, [this]([[maybe_unused]] auto self, auto) {{ {code}; }});")
}));
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*".into(),
name: "globals".into(),
..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::<Vec<_>>());
file.declarations.push(Declaration::Struct(global_struct));
}
fn generate_functions<'a>(
functions: &'a [llr::Function],
ctx: &'a EvaluationContext<'_>,
) -> impl Iterator<Item = Declaration> + '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: concatenate_ident(&format_smolstr!("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 = concatenate_ident(&p.name);
let access = access_member(&p.prop, ctx);
if let Type::Callback(callback) = &p.ty {
let param_types =
callback.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let return_type =
callback.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..callback.args.len()).map(|i| format!("arg_{}", i)).join(", ")
),
];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("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_smolstr!("on_{}", concatenate_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<Functor>(callback_handler));", access),
]),
..Default::default()
}),
));
} else if let Type::Function(function) = &p.ty {
let param_types =
function.args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
let ret = function.return_type.cpp_type().unwrap();
let call_code = vec![
"[[maybe_unused]] auto self = this;".into(),
format!(
"{}{access}({});",
if function.return_type == Type::Void { "" } else { "return " },
(0..function.args.len()).map(|i| format!("arg_{}", i)).join(", ")
),
];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("invoke_{}", concatenate_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<String> = vec![
"[[maybe_unused]] auto self = this;".into(),
format!("return {}.get();", access),
];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("get_{}", &prop_ident),
signature: format!("() const -> {}", &cpp_property_type),
statements: Some(prop_getter),
..Default::default()
}),
));
if !p.read_only {
let prop_setter: Vec<String> = vec![
"[[maybe_unused]] auto self = this;".into(),
property_set_value_code(&p.prop, "value", ctx) + ";",
];
declarations.push((
Access::Public,
Declaration::Function(Function {
name: format_smolstr!("set_{}", &prop_ident),
signature: format!("(const {} &value) const -> void", &cpp_property_type),
statements: Some(prop_setter),
..Default::default()
}),
));
} else {
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("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 = concatenate_ident(name);
if let Type::Function(function) = &ty {
let param_types = function.args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
declarations.push((
Access::Private,
Declaration::Function(Function {
name: format_smolstr!("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_smolstr!("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_smolstr!("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(&current_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(&current_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_{}", concatenate_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_{}", concatenate_ident(&global.name));
let name = concatenate_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).to_string(),
Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
Type::Struct(s)=> {
if s.name.is_none() {
let index = s.fields
.keys()
.position(|k| k == name)
.expect("Expression::ObjectAccess: Cannot find a key in an object");
format!("std::get<{}>({})", index, compile_expression(base, ctx))
} else {
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) {
(Type::Float32, Type::Int32) => {
format!("static_cast<int>({f})")
}
(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<slint::private_api::UIntModel>(std::max<int>(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(s)) if s.name.is_some() => {
format!(
"[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})",
struct_name = to.cpp_type().unwrap(),
fields = s.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(s) if s.name.is_some() => (s.fields.len(), s.name.as_ref().unwrap().clone()),
_ => 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::<Vec<_>>();
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 lhs_str = compile_expression(lhs, ctx);
let rhs_str = compile_expression(rhs, ctx);
let lhs_ty = lhs.ty(ctx);
if lhs_ty.as_unit_product().is_some() && (*op == '=' || *op == '!') {
let op = if *op == '=' { "<" } else { ">=" };
format!("(std::abs(float({lhs_str} - {rhs_str})) {op} std::numeric_limits<float>::epsilon())")
} else {
let mut buffer = [0; 3];
format!(
"({lhs_str} {op} {rhs_str})",
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".into()),
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<slint::private_api::ArrayModel<{count},{ty}>>({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 } => {
match ty {
Type::Struct(s) if s.name.is_none() => {
let mut elem = s.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(", "))
},
Type::Struct(_) => {
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(", "),
)
},
_ => {
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(SmolStr::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<slint::cbindgen_private::GridLayoutCellData> {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
)
}
Expression::EmptyComponentFactory => panic!("component-factory not yet supported in C++"),
}
}
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!("slint::private_api::debug({});", a.join(","))
}
BuiltinFunction::Mod => {
ctx.generator_state.conditional_includes.cmath.set(true);
format!("([](float a, float b) {{ return a >= 0 ? std::fmod(a, b) : std::fmod(a, b) + std::abs(b); }})({},{})", 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::ATan2 => {
ctx.generator_state.conditional_includes.cmath.set(true);
format!("std::atan2({}, {}) / {}", a.next().unwrap(), 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<float>({a}) * 255., 0., 255.), std::clamp(static_cast<int>({r}), 0, 255), std::clamp(static_cast<int>({g}), 0, 255), std::clamp(static_cast<int>({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<float>({h}), 0.f, 360.f), std::clamp(static_cast<float>({s}), 0.f, 1.f), std::clamp(static_cast<float>({v}), 0.f, 1.f), std::clamp(static_cast<float>({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<slint::private_api::ArrayModel<3,int32_t>>(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<slint::private_api::ArrayModel<3,int32_t>>(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), close_policy, 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 = &current_sub_component.popup_windows[*popup_index as usize];
let popup_window_id =
ident(&popup.item_tree.root.name);
let parent_component = access_item_rc(parent_ref, ctx);
let popup_ctx = EvaluationContext::new_sub_component(
ctx.compilation_unit,
&popup.item_tree.root,
CppGeneratorContext { global_access: "self->globals".into(), conditional_includes: ctx.generator_state.conditional_includes },
Some(ParentCtx::new(&ctx, None)),
);
let position = compile_expression(&popup.position.borrow(), &popup_ctx);
let close_policy = compile_expression(close_policy, ctx);
format!(
"{window}.show_popup<{popup_window_id}>({component_access}, [=](auto self) {{ return {position}; }}, {close_policy}, {{ {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<int>({start}), static_cast<int>({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::ItemFontMetrics => {
if let [llr::Expression::PropertyReference(pr)] = arguments {
let item_rc = access_item_rc(pr, ctx);
let window = access_window_field(ctx);
format!("slint_cpp_text_item_fontmetrics(&{window}.handle(), &{item_rc})")
} else {
panic!("internal error: invalid args to ItemFontMetrics {:?}", 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<slint::cbindgen_private::{ty}*>(&{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(","))
}
BuiltinFunction::UpdateTimers => {
"self->update_timers()".into()
}
}
}
fn box_layout_function(
cells_variable: &str,
repeated_indices: Option<&str>,
elements: &[Either<llr::Expression, u32>],
orientation: Orientation,
sub_expression: &llr::Expression,
ctx: &llr_EvaluationContext<CppGeneratorContext>,
) -> String {
let repeated_indices = repeated_indices.map(ident);
let mut push_code =
"std::vector<slint::cbindgen_private::BoxLayoutCellData> 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<int> {ri}{{ {ri}_array.data(), {ri}_array.size() }};",
ri = ri
)
.unwrap();
format!("std::array<int, {}> {}_array;", 2 * repeater_idx, ri)
});
format!(
"[&]{{ {} {} slint::cbindgen_private::Slice<slint::cbindgen_private::BoxLayoutCellData>{}{{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(s) if s.name.is_some() && s.node.is_some() => {
Some((&export.0.name, s.name.as_ref().unwrap()))
}
Type::Enumeration(en) => Some((&export.0.name, &en.name)),
_ => None,
},
_ => None,
})
.filter(|(export_name, type_name)| export_name != type_name)
.map(|(export_name, type_name)| {
Declaration::TypeAlias(TypeAlias {
old_name: ident(type_name),
new_name: ident(export_name),
})
});
file.declarations.extend(type_aliases);
}