mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-16 01:25:27 +00:00

Round the value. The previous behavior is that - For the interpreter, we were rounding (same as new behavior) - for Rust and C++ we were truncating, unless the properties were inlinined and then we were keeping it as float
3537 lines
142 KiB
Rust
3537 lines
142 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 std::fmt::Write;
|
|
|
|
/// The configuration for the C++ code generator
|
|
#[derive(Clone, Debug, Default, PartialEq)]
|
|
pub struct Config {
|
|
pub namespace: Option<String>,
|
|
}
|
|
|
|
fn ident(ident: &str) -> String {
|
|
if ident.contains('-') {
|
|
ident.replace('-', "_")
|
|
} else {
|
|
ident.into()
|
|
}
|
|
}
|
|
|
|
/// Given a property reference to a native item (eg, the property name is empty)
|
|
/// return tokens to the `ItemRc`
|
|
fn access_item_rc(pr: &llr::PropertyReference, ctx: &EvaluationContext) -> String {
|
|
let mut ctx = ctx;
|
|
let mut component_access = "self->".into();
|
|
|
|
let pr = match pr {
|
|
llr::PropertyReference::InParent { level, parent_reference } => {
|
|
for _ in 0..level.get() {
|
|
component_access = format!("{component_access}parent->");
|
|
ctx = ctx.parent.as_ref().unwrap().ctx;
|
|
}
|
|
parent_reference
|
|
}
|
|
other => other,
|
|
};
|
|
|
|
match pr {
|
|
llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => {
|
|
assert!(prop_name.is_empty());
|
|
let (sub_compo_path, sub_component) =
|
|
follow_sub_component_path(ctx.current_sub_component.unwrap(), sub_component_path);
|
|
if !sub_component_path.is_empty() {
|
|
component_access += &sub_compo_path;
|
|
}
|
|
let component_rc = format!("{component_access}self_weak.lock()->into_dyn()");
|
|
let item_index_in_tree = sub_component.items[*item_index as usize].index_in_tree;
|
|
let item_index = if item_index_in_tree == 0 {
|
|
format!("{component_access}tree_index")
|
|
} else {
|
|
format!("{component_access}tree_index_of_first_child + {item_index_in_tree} - 1")
|
|
};
|
|
|
|
format!("{}, {}", &component_rc, item_index)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// This module contains some data structure that helps represent a C++ code.
|
|
/// It is then rendered into an actual C++ text using the Display trait
|
|
mod cpp_ast {
|
|
|
|
use std::cell::Cell;
|
|
use std::fmt::{Display, Error, Formatter};
|
|
thread_local!(static INDENTATION : Cell<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 includes: Vec<String>,
|
|
pub after_includes: String,
|
|
pub namespace: Option<String>,
|
|
pub declarations: Vec<Declaration>,
|
|
pub definitions: Vec<Declaration>,
|
|
}
|
|
|
|
impl Display for File {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
writeln!(f, "// This file is auto-generated")?;
|
|
writeln!(f, "#pragma once")?;
|
|
for i in &self.includes {
|
|
writeln!(f, "#include {}", i)?;
|
|
}
|
|
if let Some(namespace) = &self.namespace {
|
|
writeln!(f, "namespace {} {{", namespace)?;
|
|
INDENTATION.with(|x| x.set(x.get() + 1));
|
|
}
|
|
|
|
write!(f, "{}", self.after_includes)?;
|
|
for d in &self.declarations {
|
|
write!(f, "\n{}", d)?;
|
|
}
|
|
for d in &self.definitions {
|
|
write!(f, "\n{}", d)?;
|
|
}
|
|
if let Some(namespace) = &self.namespace {
|
|
writeln!(f, "}} // namespace {}", namespace)?;
|
|
INDENTATION.with(|x| x.set(x.get() - 1));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Declarations (top level, or within a struct)
|
|
#[derive(Debug, derive_more::Display)]
|
|
pub enum Declaration {
|
|
Struct(Struct),
|
|
Function(Function),
|
|
Var(Var),
|
|
TypeAlias(TypeAlias),
|
|
Enum(Enum),
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub enum Access {
|
|
Public,
|
|
Private,
|
|
/*Protected,*/
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct Struct {
|
|
pub name: String,
|
|
pub members: Vec<(Access, Declaration)>,
|
|
pub friends: Vec<String>,
|
|
}
|
|
|
|
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!("{}::{}", struct_name, f.name),
|
|
signature: f.signature.clone(),
|
|
is_constructor_or_destructor: f.is_constructor_or_destructor,
|
|
is_static: false,
|
|
is_friend: false,
|
|
statements: f.statements.take(),
|
|
template_parameters: f.template_parameters.clone(),
|
|
constructor_member_initializers: f.constructor_member_initializers.clone(),
|
|
}))
|
|
}
|
|
_ => None,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct Enum {
|
|
pub name: String,
|
|
pub values: Vec<String>,
|
|
}
|
|
|
|
impl Display for Enum {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
indent(f)?;
|
|
writeln!(f, "enum class {} {{", self.name)?;
|
|
INDENTATION.with(|x| x.set(x.get() + 1));
|
|
for value in &self.values {
|
|
write!(f, "{value},")?;
|
|
}
|
|
INDENTATION.with(|x| x.set(x.get() - 1));
|
|
indent(f)?;
|
|
writeln!(f, "}};")
|
|
}
|
|
}
|
|
|
|
/// Function or method
|
|
#[derive(Default, Debug)]
|
|
pub struct Function {
|
|
pub name: String,
|
|
/// "(...) -> ..."
|
|
pub signature: String,
|
|
/// The function does not have return type
|
|
pub is_constructor_or_destructor: bool,
|
|
pub is_static: bool,
|
|
pub is_friend: bool,
|
|
/// The list of statement instead the function. When None, this is just a function
|
|
/// declaration without the definition
|
|
pub statements: Option<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 ")?;
|
|
}
|
|
// all functions are `inline` because we are in a header
|
|
write!(f, "inline ")?;
|
|
if !self.is_constructor_or_destructor {
|
|
write!(f, "auto ")?;
|
|
}
|
|
write!(f, "{} {}", self.name, self.signature)?;
|
|
if let Some(st) = &self.statements {
|
|
if !self.constructor_member_initializers.is_empty() {
|
|
writeln!(f, "\n : {}", self.constructor_member_initializers.join(","))?;
|
|
}
|
|
writeln!(f, "{{")?;
|
|
for s in st {
|
|
indent(f)?;
|
|
writeln!(f, " {}", s)?;
|
|
}
|
|
indent(f)?;
|
|
writeln!(f, "}}")
|
|
} else {
|
|
writeln!(f, ";")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A variable or a member declaration.
|
|
#[derive(Default, Debug)]
|
|
pub struct Var {
|
|
pub ty: String,
|
|
pub name: String,
|
|
pub array_size: Option<usize>,
|
|
pub init: Option<String>,
|
|
}
|
|
|
|
impl Display for Var {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
indent(f)?;
|
|
write!(f, "{} {}", self.ty, self.name)?;
|
|
if let Some(size) = self.array_size {
|
|
write!(f, "[{}]", size)?;
|
|
}
|
|
if let Some(i) = &self.init {
|
|
write!(f, " = {}", i)?;
|
|
}
|
|
writeln!(f, ";")
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct TypeAlias {
|
|
pub new_name: String,
|
|
pub old_name: String,
|
|
}
|
|
|
|
impl Display for TypeAlias {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
indent(f)?;
|
|
writeln!(f, "using {} = {};", self.new_name, self.old_name)
|
|
}
|
|
}
|
|
|
|
pub trait CppType {
|
|
fn cpp_type(&self) -> Option<String>;
|
|
}
|
|
|
|
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>,
|
|
}
|
|
|
|
#[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<String> {
|
|
match self {
|
|
Type::Void => Some("void".to_owned()),
|
|
Type::Float32 => Some("float".to_owned()),
|
|
Type::Int32 => Some("int".to_owned()),
|
|
Type::String => Some("slint::SharedString".to_owned()),
|
|
Type::Color => Some("slint::Color".to_owned()),
|
|
Type::Duration => Some("std::int64_t".to_owned()),
|
|
Type::Angle => Some("float".to_owned()),
|
|
Type::PhysicalLength => Some("float".to_owned()),
|
|
Type::LogicalLength => Some("float".to_owned()),
|
|
Type::Rem => Some("float".to_owned()),
|
|
Type::Percent => Some("float".to_owned()),
|
|
Type::Bool => Some("bool".to_owned()),
|
|
Type::Struct { name: Some(name), node: Some(_), .. } => Some(ident(name)),
|
|
Type::Struct { name: Some(name), node: None, .. } => {
|
|
Some(if name.starts_with("slint::") {
|
|
name.clone()
|
|
} else {
|
|
format!("slint::cbindgen_private::{}", ident(name))
|
|
})
|
|
}
|
|
Type::Struct { fields, .. } => {
|
|
let elem = fields.values().map(|v| v.cpp_type()).collect::<Option<Vec<_>>>()?;
|
|
|
|
Some(format!("std::tuple<{}>", elem.join(", ")))
|
|
}
|
|
|
|
Type::Array(i) => Some(format!("std::shared_ptr<slint::Model<{}>>", i.cpp_type()?)),
|
|
Type::Image => Some("slint::Image".to_owned()),
|
|
Type::Enumeration(enumeration) => {
|
|
if enumeration.node.is_some() {
|
|
Some(ident(&enumeration.name))
|
|
} else {
|
|
Some(format!("slint::cbindgen_private::{}", ident(&enumeration.name)))
|
|
}
|
|
}
|
|
Type::Brush => Some("slint::Brush".to_owned()),
|
|
Type::LayoutCache => Some("slint::SharedVector<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 { args, return_type, .. } = &prop_type {
|
|
let mut ctx2 = ctx.clone();
|
|
ctx2.argument_types = args;
|
|
|
|
let mut params = args.iter().enumerate().map(|(i, ty)| {
|
|
format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i)
|
|
});
|
|
|
|
init.push(format!(
|
|
"{prop_access}.set_handler(
|
|
[this]({params}) {{
|
|
[[maybe_unused]] auto self = this;
|
|
{code};
|
|
}});",
|
|
prop_access = prop_access,
|
|
params = params.join(", "),
|
|
code = return_compile_expression(
|
|
&binding_expression.expression.borrow(),
|
|
&ctx2,
|
|
return_type.as_ref().map(|x| &**x)
|
|
)
|
|
));
|
|
} else {
|
|
let init_expr = compile_expression(&binding_expression.expression.borrow(), ctx);
|
|
|
|
init.push(if binding_expression.is_constant && !binding_expression.is_state_info {
|
|
format!("{}.set({});", prop_access, init_expr)
|
|
} else {
|
|
let binding_code = format!(
|
|
"[this]() {{
|
|
[[maybe_unused]] auto self = this;
|
|
return {init};
|
|
}}",
|
|
init = init_expr
|
|
);
|
|
|
|
if binding_expression.is_state_info {
|
|
format!("slint::private_api::set_state_binding({}, {});", prop_access, binding_code)
|
|
} else {
|
|
match &binding_expression.animation {
|
|
Some(llr::Animation::Static(anim)) => {
|
|
let anim = compile_expression(anim, ctx);
|
|
format!("{}.set_animated_binding({}, {});", prop_access, binding_code, anim)
|
|
}
|
|
Some(llr::Animation::Transition (
|
|
anim
|
|
)) => {
|
|
let anim = compile_expression(anim, ctx);
|
|
format!(
|
|
"{}.set_animated_binding_for_transition({},
|
|
[this](uint64_t *start_time) -> slint::cbindgen_private::PropertyAnimation {{
|
|
[[maybe_unused]] auto self = this;
|
|
auto [anim, time] = {};
|
|
*start_time = time;
|
|
return anim;
|
|
}});",
|
|
prop_access,
|
|
binding_code,
|
|
anim,
|
|
)
|
|
}
|
|
None => format!("{}.set_binding({});", prop_access, binding_code),
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Returns the text of the C++ code produced by the given root component
|
|
pub fn generate(
|
|
doc: &Document,
|
|
config: Config,
|
|
compiler_config: &CompilerConfiguration,
|
|
) -> impl std::fmt::Display {
|
|
let mut file = File { namespace: config.namespace.clone(), ..Default::default() };
|
|
|
|
file.includes.push("<array>".into());
|
|
file.includes.push("<limits>".into());
|
|
file.includes.push("<slint.h>".into());
|
|
file.includes.push("<cmath>".into());
|
|
|
|
for (path, er) in doc.embedded_file_resources.borrow().iter() {
|
|
match &er.kind {
|
|
crate::embedded_resources::EmbeddedResourcesKind::RawData => {
|
|
let resource_file =
|
|
crate::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists
|
|
let data = resource_file.read();
|
|
|
|
let mut init = "{ ".to_string();
|
|
|
|
for (index, byte) in data.iter().enumerate() {
|
|
if index > 0 {
|
|
init.push(',');
|
|
}
|
|
write!(&mut init, "0x{:x}", byte).unwrap();
|
|
if index % 16 == 0 {
|
|
init.push('\n');
|
|
}
|
|
}
|
|
|
|
init.push('}');
|
|
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "const inline uint8_t".into(),
|
|
name: format!("slint_embedded_resource_{}", er.id),
|
|
array_size: Some(data.len()),
|
|
init: Some(init),
|
|
}));
|
|
}
|
|
#[cfg(feature = "software-renderer")]
|
|
crate::embedded_resources::EmbeddedResourcesKind::TextureData(
|
|
crate::embedded_resources::Texture {
|
|
data,
|
|
format,
|
|
rect,
|
|
total_size: crate::embedded_resources::Size { width, height },
|
|
original_size:
|
|
crate::embedded_resources::Size {
|
|
width: unscaled_width,
|
|
height: unscaled_height,
|
|
},
|
|
},
|
|
) => {
|
|
let (r_x, r_y, r_w, r_h) = (rect.x(), rect.y(), rect.width(), rect.height());
|
|
let color =
|
|
if let crate::embedded_resources::PixelFormat::AlphaMap([r, g, b]) = format {
|
|
format!("slint::Color::from_rgb_uint8({r}, {g}, {b})")
|
|
} else {
|
|
"slint::Color{}".to_string()
|
|
};
|
|
let count = data.len();
|
|
let data = data.iter().map(ToString::to_string).join(", ");
|
|
let data_name = format!("slint_embedded_resource_{}_data", er.id);
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const uint8_t".into(),
|
|
name: data_name.clone(),
|
|
array_size: Some(count),
|
|
init: Some(format!("{{ {data} }}")),
|
|
}));
|
|
let texture_name = format!("slint_embedded_resource_{}_texture", er.id);
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const slint::cbindgen_private::types::StaticTexture".into(),
|
|
name: texture_name.clone(),
|
|
array_size: None,
|
|
init: Some(format!(
|
|
"{{
|
|
.rect = {{ {r_x}, {r_y}, {r_w}, {r_h} }},
|
|
.format = slint::cbindgen_private::types::PixelFormat::{format},
|
|
.color = {color},
|
|
.index = 0,
|
|
}}"
|
|
)),
|
|
}));
|
|
let init = format!("slint::cbindgen_private::types::StaticTextures {{
|
|
.size = {{ {width}, {height} }},
|
|
.original_size = {{ {unscaled_width}, {unscaled_height} }},
|
|
.data = slint::cbindgen_private::Slice<uint8_t>{{ {data_name} , {count} }},
|
|
.textures = slint::cbindgen_private::Slice<slint::cbindgen_private::types::StaticTexture>{{ &{texture_name}, 1 }}
|
|
}}");
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const slint::cbindgen_private::types::StaticTextures".into(),
|
|
name: format!("slint_embedded_resource_{}", er.id),
|
|
array_size: None,
|
|
init: Some(init),
|
|
}))
|
|
}
|
|
#[cfg(feature = "software-renderer")]
|
|
crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
|
|
crate::embedded_resources::BitmapFont {
|
|
family_name,
|
|
character_map,
|
|
units_per_em,
|
|
ascent,
|
|
descent,
|
|
glyphs,
|
|
weight,
|
|
italic,
|
|
},
|
|
) => {
|
|
let family_name_var = format!("slint_embedded_resource_{}_family_name", er.id);
|
|
let family_name_size = family_name.len();
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const uint8_t".into(),
|
|
name: family_name_var.clone(),
|
|
array_size: Some(family_name_size),
|
|
init: Some(format!(
|
|
"{{ {} }}",
|
|
family_name.as_bytes().iter().map(ToString::to_string).join(", ")
|
|
)),
|
|
}));
|
|
|
|
let charmap_var = format!("slint_embedded_resource_{}_charmap", er.id);
|
|
let charmap_size = character_map.len();
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const slint::cbindgen_private::CharacterMapEntry".into(),
|
|
name: charmap_var.clone(),
|
|
array_size: Some(charmap_size),
|
|
init: Some(format!(
|
|
"{{ {} }}",
|
|
character_map
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{{ .code_point = {}, .glyph_index = {} }}",
|
|
entry.code_point as u32, entry.glyph_index
|
|
))
|
|
.join(", ")
|
|
)),
|
|
}));
|
|
|
|
for (glyphset_index, glyphset) in glyphs.iter().enumerate() {
|
|
for (glyph_index, glyph) in glyphset.glyph_data.iter().enumerate() {
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const uint8_t".into(),
|
|
name: format!(
|
|
"slint_embedded_resource_{}_gs_{}_gd_{}",
|
|
er.id, glyphset_index, glyph_index
|
|
),
|
|
array_size: Some(glyph.data.len()),
|
|
init: Some(format!(
|
|
"{{ {} }}",
|
|
glyph.data.iter().map(ToString::to_string).join(", ")
|
|
)),
|
|
}));
|
|
}
|
|
|
|
file.declarations.push(Declaration::Var(Var{
|
|
ty: "inline const slint::cbindgen_private::BitmapGlyph".into(),
|
|
name: format!("slint_embedded_resource_{}_glyphset_{}", er.id, glyphset_index),
|
|
array_size: Some(glyphset.glyph_data.len()),
|
|
init: Some(format!("{{ {} }}", glyphset.glyph_data.iter().enumerate().map(|(glyph_index, glyph)| {
|
|
format!("{{ .x = {}, .y = {}, .width = {}, .height = {}, .x_advance = {}, .data = slint::cbindgen_private::Slice<uint8_t>{{ {}, {} }} }}",
|
|
glyph.x, glyph.y, glyph.width, glyph.height, glyph.x_advance,
|
|
format!("slint_embedded_resource_{}_gs_{}_gd_{}", er.id, glyphset_index, glyph_index),
|
|
glyph.data.len()
|
|
)
|
|
}).join(", \n"))),
|
|
}));
|
|
}
|
|
|
|
let glyphsets_var = format!("slint_embedded_resource_{}_glyphsets", er.id);
|
|
let glyphsets_size = glyphs.len();
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const slint::cbindgen_private::BitmapGlyphs".into(),
|
|
name: glyphsets_var.clone(),
|
|
array_size: Some(glyphsets_size),
|
|
init: Some(format!(
|
|
"{{ {} }}",
|
|
glyphs
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(glyphset_index, glyphset)| format!(
|
|
"{{ .pixel_size = {}, .glyph_data = slint::cbindgen_private::Slice<slint::cbindgen_private::BitmapGlyph>{{
|
|
{}, {}
|
|
}}
|
|
}}",
|
|
glyphset.pixel_size, format!("slint_embedded_resource_{}_glyphset_{}", er.id, glyphset_index), glyphset.glyph_data.len()
|
|
))
|
|
.join(", \n")
|
|
)),
|
|
}));
|
|
|
|
let init = format!(
|
|
"slint::cbindgen_private::BitmapFont {{
|
|
.family_name = slint::cbindgen_private::Slice<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},
|
|
.glyphs = slint::cbindgen_private::Slice<slint::cbindgen_private::BitmapGlyphs>{{ {glyphsets_var}, {glyphsets_size} }},
|
|
.weight = {weight},
|
|
.italic = {italic},
|
|
}}"
|
|
);
|
|
|
|
file.declarations.push(Declaration::Var(Var {
|
|
ty: "inline const slint::cbindgen_private::BitmapFont".into(),
|
|
name: format!("slint_embedded_resource_{}", er.id),
|
|
array_size: None,
|
|
init: Some(init),
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
for ty in doc.used_types.borrow().structs_and_enums.iter() {
|
|
match ty {
|
|
Type::Struct { fields, name: Some(name), node: Some(node), .. } => {
|
|
generate_struct(&mut file, name, fields, node);
|
|
}
|
|
Type::Enumeration(en) => {
|
|
generate_enum(&mut file, en);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
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()
|
|
}),
|
|
));
|
|
|
|
globals_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "window".into(),
|
|
signature: "() const -> slint::Window&".into(),
|
|
statements: Some(vec![
|
|
format!("auto self = const_cast<SharedGlobals *>(this);"),
|
|
"if (!self->m_window.has_value()) {".into(),
|
|
" auto &window = self->m_window.emplace(slint::private_api::WindowAdapterRc());"
|
|
.into(),
|
|
" window.window_handle().set_component(self->root_weak);".into(),
|
|
"}".into(),
|
|
"return *self->m_window;".into(),
|
|
]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
for glob in &llr.globals {
|
|
let ty = if glob.is_builtin {
|
|
format!("slint::cbindgen_private::{}", glob.name)
|
|
} else 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!("std::shared_ptr<{}>", ty),
|
|
name: format!("global_{}", ident(&glob.name)),
|
|
init: Some(format!("std::make_shared<{}>(this)", ty)),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
file.declarations.push(Declaration::Struct(globals_struct));
|
|
|
|
for p in &llr.public_components {
|
|
generate_public_component(&mut file, &conditional_includes, p, &llr);
|
|
}
|
|
|
|
generate_type_aliases(&mut file, doc);
|
|
|
|
file.after_includes = format!(
|
|
"static_assert({x} == SLINT_VERSION_MAJOR && {y} == SLINT_VERSION_MINOR && {z} == SLINT_VERSION_PATCH, \
|
|
\"This file was generated with Slint compiler version {x}.{y}.{z}, but the Slint library used is \" \
|
|
SLINT_VERSION_STRING \". The version numbers must match exactly.\");",
|
|
x = env!("CARGO_PKG_VERSION_MAJOR"),
|
|
y = env!("CARGO_PKG_VERSION_MINOR"),
|
|
z = env!("CARGO_PKG_VERSION_PATCH")
|
|
);
|
|
|
|
if conditional_includes.iostream.get() {
|
|
file.includes.push("<iostream>".into());
|
|
}
|
|
|
|
if conditional_includes.cstdlib.get() {
|
|
file.includes.push("<cstdlib>".into());
|
|
}
|
|
|
|
file
|
|
}
|
|
|
|
fn generate_struct(
|
|
file: &mut File,
|
|
name: &str,
|
|
fields: &BTreeMap<String, 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==".to_owned(),
|
|
signature: format!("(const class {0} &a, const class {0} &b) -> bool = default", name),
|
|
is_friend: true,
|
|
statements: None,
|
|
..Function::default()
|
|
}),
|
|
));
|
|
|
|
file.declarations.push(Declaration::Struct(Struct { name, members, ..Default::default() }))
|
|
}
|
|
|
|
fn generate_enum(file: &mut File, en: &std::rc::Rc<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 " },
|
|
ident(&glob.name),
|
|
);
|
|
global_accessor_function_body.push(accessor_statement);
|
|
}
|
|
if !global_accessor_function_body.is_empty() {
|
|
global_accessor_function_body.push(
|
|
"else { static_assert(!sizeof(T*), \"The type is not global/or exported\"); }".into(),
|
|
);
|
|
|
|
component_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "global".into(),
|
|
signature: "() const -> const T&".into(),
|
|
statements: Some(global_accessor_function_body),
|
|
template_parameters: Some("typename T".into()),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
|
|
let ctx = EvaluationContext {
|
|
compilation_unit: unit,
|
|
current_sub_component: Some(&component.item_tree.root),
|
|
current_global: None,
|
|
generator_state: CppGeneratorContext {
|
|
global_access: "(&this->m_globals)".to_string(),
|
|
conditional_includes,
|
|
},
|
|
parent: None,
|
|
argument_types: &[],
|
|
};
|
|
|
|
let old_declarations = file.declarations.len();
|
|
|
|
generate_item_tree(
|
|
&mut component_struct,
|
|
&component.item_tree,
|
|
unit,
|
|
None,
|
|
component_id,
|
|
Access::Private, // Hide properties and other fields from the C++ API
|
|
file,
|
|
conditional_includes,
|
|
);
|
|
|
|
// Give generated sub-components, etc. access to our fields
|
|
|
|
for new_decl in file.declarations.iter().skip(old_declarations) {
|
|
if let Declaration::Struct(struc @ Struct { .. }) = new_decl {
|
|
component_struct.friends.push(struc.name.clone());
|
|
};
|
|
}
|
|
|
|
generate_public_api_for_properties(
|
|
&mut component_struct.members,
|
|
&component.public_properties,
|
|
&component.private_properties,
|
|
&ctx,
|
|
);
|
|
|
|
component_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "show".into(),
|
|
signature: "()".into(),
|
|
statements: Some(vec!["window().show();".into()]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
component_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "hide".into(),
|
|
signature: "()".into(),
|
|
statements: Some(vec!["window().hide();".into()]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
component_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "window".into(),
|
|
signature: "() const -> slint::Window&".into(),
|
|
statements: Some(vec!["return m_globals.window();".into()]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
component_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: "run".into(),
|
|
signature: "()".into(),
|
|
statements: Some(vec![
|
|
"show();".into(),
|
|
"slint::run_event_loop();".into(),
|
|
"hide();".into(),
|
|
]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
component_struct.friends.push("slint::private_api::WindowAdapterRc".into());
|
|
|
|
add_friends(&mut component_struct.friends, &component.item_tree.root, true);
|
|
|
|
fn add_friends(friends: &mut Vec<String>, 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: String,
|
|
field_access: Access,
|
|
file: &mut File,
|
|
conditional_includes: &ConditionalIncludes,
|
|
) {
|
|
target_struct
|
|
.friends
|
|
.push(format!("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".to_owned(),
|
|
name: "static_vtable".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
file.definitions.push(Declaration::Var(Var {
|
|
ty: "inline const slint::private_api::ItemTreeVTable".to_owned(),
|
|
name: format!("{}::static_vtable", item_tree_class_name),
|
|
init: Some(format!(
|
|
"{{ visit_children, get_item_ref, get_subtree_range, get_subtree, \
|
|
get_item_tree, parent_node, embed_component, subtree_index, layout_info, \
|
|
item_geometry, accessible_role, accessible_string_property, accessibility_action, \
|
|
supported_accessibility_actions, element_infos, window_adapter, \
|
|
slint::private_api::drop_in_place<{}>, slint::private_api::dealloc }}",
|
|
item_tree_class_name
|
|
)),
|
|
..Default::default()
|
|
}));
|
|
|
|
let mut create_parameters = Vec::new();
|
|
let mut init_parent_parameters = "";
|
|
|
|
if let Some(parent) = &parent_ctx {
|
|
let parent_type =
|
|
format!("class {} const *", ident(&parent.ctx.current_sub_component.unwrap().name));
|
|
create_parameters.push(format!("{} parent", parent_type));
|
|
|
|
init_parent_parameters = ", parent";
|
|
}
|
|
|
|
let mut create_code = vec![
|
|
format!(
|
|
"auto self_rc = vtable::VRc<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!("~{}", 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.to_owned(),
|
|
name: "globals".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
init.push("self->globals = globals;".into());
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: "uint32_t".to_owned(),
|
|
name: "tree_index_of_first_child".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
init.push("this->tree_index_of_first_child = tree_index_of_first_child;".into());
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: "uint32_t".to_owned(),
|
|
name: "tree_index".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
init.push("self->tree_index = tree_index;".into());
|
|
|
|
if let Some(parent_ctx) = &parent_ctx {
|
|
let parent_type =
|
|
format!("class {} const *", ident(&parent_ctx.ctx.current_sub_component.unwrap().name));
|
|
init_parameters.push(format!("{} parent", parent_type));
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: parent_type,
|
|
name: "parent".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
init.push("self->parent = parent;".into());
|
|
}
|
|
|
|
let ctx = EvaluationContext::new_sub_component(
|
|
root,
|
|
component,
|
|
CppGeneratorContext { global_access: "self->globals".into(), conditional_includes },
|
|
parent_ctx,
|
|
);
|
|
|
|
component.popup_windows.iter().for_each(|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 { args, return_type } = &property.ty {
|
|
let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
|
|
let return_type =
|
|
return_type.as_ref().map_or("void".to_owned(), |t| t.cpp_type().unwrap());
|
|
format!("slint::private_api::Callback<{}({})>", return_type, param_types.join(", "))
|
|
} else {
|
|
format!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap())
|
|
};
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
|
|
));
|
|
}
|
|
|
|
for (i, _) in component.change_callbacks.iter().enumerate() {
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: "slint::private_api::ChangeTracker".into(),
|
|
name: format!("change_tracker{}", i),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
|
|
let mut user_init = vec!["[[maybe_unused]] auto self = this;".into()];
|
|
|
|
let mut children_visitor_cases = Vec::new();
|
|
let mut subtrees_ranges_cases = Vec::new();
|
|
let mut subtrees_components_cases = Vec::new();
|
|
|
|
for sub in &component.sub_components {
|
|
let field_name = ident(&sub.name);
|
|
let local_tree_index: u32 = sub.index_in_tree as _;
|
|
let local_index_of_first_child: u32 = sub.index_of_first_child_in_tree as _;
|
|
|
|
// For children of sub-components, the item index generated by the generate_item_indices pass
|
|
// starts at 1 (0 is the root element).
|
|
let global_index = if local_tree_index == 0 {
|
|
"tree_index".into()
|
|
} else {
|
|
format!("tree_index_of_first_child + {} - 1", local_tree_index)
|
|
};
|
|
let global_children = if local_index_of_first_child == 0 {
|
|
"0".into()
|
|
} else {
|
|
format!("tree_index_of_first_child + {} - 1", local_index_of_first_child)
|
|
};
|
|
|
|
init.push(format!(
|
|
"this->{}.init(globals, self_weak.into_dyn(), {}, {});",
|
|
field_name, global_index, global_children
|
|
));
|
|
user_init.push(format!("this->{}.user_init();", field_name));
|
|
|
|
let sub_component_repeater_count = sub.ty.repeater_count();
|
|
if sub_component_repeater_count > 0 {
|
|
let mut case_code = String::new();
|
|
let repeater_offset = sub.repeater_offset;
|
|
|
|
for local_repeater_index in 0..sub_component_repeater_count {
|
|
write!(case_code, "case {}: ", repeater_offset + local_repeater_index).unwrap();
|
|
}
|
|
|
|
children_visitor_cases.push(format!(
|
|
"\n {case_code} {{
|
|
return self->{id}.visit_dynamic_children(dyn_index - {base}, order, visitor);
|
|
}}",
|
|
case_code = case_code,
|
|
id = field_name,
|
|
base = repeater_offset,
|
|
));
|
|
subtrees_ranges_cases.push(format!(
|
|
"\n {case_code} {{
|
|
return self->{id}.subtree_range(dyn_index - {base});
|
|
}}",
|
|
case_code = case_code,
|
|
id = field_name,
|
|
base = repeater_offset,
|
|
));
|
|
subtrees_components_cases.push(format!(
|
|
"\n {case_code} {{
|
|
self->{id}.subtree_component(dyn_index - {base}, subtree_index, result);
|
|
return;
|
|
}}",
|
|
case_code = case_code,
|
|
id = field_name,
|
|
base = repeater_offset,
|
|
));
|
|
}
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: ident(&sub.ty.name),
|
|
name: field_name,
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
|
|
for (prop1, prop2) in &component.two_way_bindings {
|
|
init.push(format!(
|
|
"slint::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2});",
|
|
ty = ctx.property_ty(prop1).cpp_type().unwrap(),
|
|
p1 = access_member(prop1, &ctx),
|
|
p2 = access_member(prop2, &ctx),
|
|
));
|
|
}
|
|
|
|
let mut properties_init_code = Vec::new();
|
|
for (prop, expression) in &component.property_init {
|
|
if expression.use_count.get() > 0 {
|
|
handle_property_init(prop, expression, &mut properties_init_code, &ctx)
|
|
}
|
|
}
|
|
|
|
for item in &component.items {
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Var(Var {
|
|
ty: format!("slint::cbindgen_private::{}", ident(&item.ty.class_name)),
|
|
name: ident(&item.name),
|
|
init: Some("{}".to_owned()),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
|
|
for (idx, repeated) in component.repeated.iter().enumerate() {
|
|
let idx = idx as u32;
|
|
let data_type = if let Some(data_prop) = repeated.data_prop {
|
|
repeated.sub_tree.root.properties[data_prop].ty.clone()
|
|
} else {
|
|
Type::Int32
|
|
};
|
|
|
|
generate_repeated_component(
|
|
repeated,
|
|
root,
|
|
ParentCtx::new(&ctx, Some(idx)),
|
|
&data_type,
|
|
file,
|
|
conditional_includes,
|
|
);
|
|
|
|
let repeater_id = format!("repeater_{}", idx);
|
|
|
|
let mut model = compile_expression(&repeated.model.borrow(), &ctx);
|
|
if repeated.model.ty(&ctx) == Type::Bool {
|
|
// bool converts to int
|
|
// FIXME: don't do a heap allocation here
|
|
model = format!("std::make_shared<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!(
|
|
"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}; }});")
|
|
}));
|
|
|
|
target_struct
|
|
.members
|
|
.extend(generate_functions(&component.functions, &ctx).map(|x| (Access::Public, x)));
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Function(Function {
|
|
name: "init".to_owned(),
|
|
signature: format!("({}) -> void", init_parameters.join(",")),
|
|
statements: Some(init),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Function(Function {
|
|
name: "user_init".to_owned(),
|
|
signature: "() -> void".into(),
|
|
statements: Some(user_init),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
target_struct.members.push((
|
|
field_access,
|
|
Declaration::Function(Function {
|
|
name: "layout_info".into(),
|
|
signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::LayoutInfo"
|
|
.into(),
|
|
statements: Some(vec![
|
|
"[[maybe_unused]] auto self = this;".into(),
|
|
format!(
|
|
"return o == slint::cbindgen_private::Orientation::Horizontal ? {} : {};",
|
|
compile_expression(&component.layout_info_h.borrow(), &ctx),
|
|
compile_expression(&component.layout_info_v.borrow(), &ctx)
|
|
),
|
|
]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
let mut dispatch_item_function = |name: &str,
|
|
signature: &str,
|
|
forward_args: &str,
|
|
code: Vec<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("->") { "{}" } 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",
|
|
", 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 { args, return_type } = &property.ty {
|
|
let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
|
|
let return_type =
|
|
return_type.as_ref().map_or("void".to_owned(), |t| t.cpp_type().unwrap());
|
|
format!("slint::private_api::Callback<{}({})>", return_type, param_types.join(", "))
|
|
} else {
|
|
format!("slint::private_api::Property<{}>", property.ty.cpp_type().unwrap())
|
|
};
|
|
|
|
global_struct.members.push((
|
|
// FIXME: this is public (and also was public in the pre-llr generator) because other generated code accesses the
|
|
// fields directly. But it shouldn't be from an API point of view since the same `global_struct` class is public API
|
|
// when the global is exported and exposed in the public component.
|
|
Access::Public,
|
|
Declaration::Var(Var { ty, name: cpp_name, ..Default::default() }),
|
|
));
|
|
}
|
|
|
|
let mut init = vec!["(void)this->globals;".into()];
|
|
let ctx = EvaluationContext::new_global(
|
|
root,
|
|
global,
|
|
CppGeneratorContext { global_access: "this->globals".into(), conditional_includes },
|
|
);
|
|
|
|
for (property_index, expression) in global.init_values.iter().enumerate() {
|
|
if global.properties[property_index].use_count.get() == 0 {
|
|
continue;
|
|
}
|
|
|
|
if let Some(expression) = expression.as_ref() {
|
|
handle_property_init(
|
|
&llr::PropertyReference::Local { sub_component_path: vec![], property_index },
|
|
expression,
|
|
&mut init,
|
|
&ctx,
|
|
)
|
|
}
|
|
}
|
|
|
|
global_struct.members.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: ident(&global.name),
|
|
signature: "(const class SharedGlobals *globals)".into(),
|
|
is_constructor_or_destructor: true,
|
|
statements: Some(init),
|
|
constructor_member_initializers: vec!["globals(globals)".into()],
|
|
..Default::default()
|
|
}),
|
|
));
|
|
global_struct.members.push((
|
|
Access::Private,
|
|
Declaration::Var(Var {
|
|
ty: "const class SharedGlobals*".to_owned(),
|
|
name: "globals".to_owned(),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
generate_public_api_for_properties(
|
|
&mut global_struct.members,
|
|
&global.public_properties,
|
|
&global.private_properties,
|
|
&ctx,
|
|
);
|
|
global_struct
|
|
.members
|
|
.extend(generate_functions(&global.functions, &ctx).map(|x| (Access::Public, x)));
|
|
|
|
file.definitions.extend(global_struct.extract_definitions().collect::<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: ident(&format!("fn_{}", f.name)),
|
|
signature: format!(
|
|
"({}) const -> {}",
|
|
f.args
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| format!("{} arg_{}", ty.cpp_type().unwrap(), i))
|
|
.join(", "),
|
|
f.ret_ty.cpp_type().unwrap()
|
|
),
|
|
statements: Some(body),
|
|
..Default::default()
|
|
})
|
|
})
|
|
}
|
|
|
|
fn generate_public_api_for_properties(
|
|
declarations: &mut Vec<(Access, Declaration)>,
|
|
public_properties: &llr::PublicProperties,
|
|
private_properties: &llr::PrivateProperties,
|
|
ctx: &EvaluationContext,
|
|
) {
|
|
for p in public_properties {
|
|
let prop_ident = ident(&p.name);
|
|
|
|
let access = access_member(&p.prop, ctx);
|
|
|
|
if let Type::Callback { args, return_type } = &p.ty {
|
|
let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
|
|
let return_type = return_type.as_ref().map_or("void".into(), |t| t.cpp_type().unwrap());
|
|
let callback_emitter = vec![
|
|
"[[maybe_unused]] auto self = this;".into(),
|
|
format!(
|
|
"return {}.call({});",
|
|
access,
|
|
(0..args.len()).map(|i| format!("arg_{}", i)).join(", ")
|
|
),
|
|
];
|
|
declarations.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: format!("invoke_{prop_ident}"),
|
|
signature: format!(
|
|
"({}) const -> {}",
|
|
param_types
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| format!("{} arg_{}", ty, i))
|
|
.join(", "),
|
|
return_type
|
|
),
|
|
statements: Some(callback_emitter),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
declarations.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: format!("on_{}", ident(&p.name)),
|
|
template_parameters: Some(format!(
|
|
"std::invocable<{}> Functor",
|
|
param_types.join(", "),
|
|
)),
|
|
signature: "(Functor && callback_handler) const".into(),
|
|
statements: Some(vec![
|
|
"[[maybe_unused]] auto self = this;".into(),
|
|
format!("{}.set_handler(std::forward<Functor>(callback_handler));", access),
|
|
]),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
} else if let Type::Function { return_type, args } = &p.ty {
|
|
let param_types = args.iter().map(|t| t.cpp_type().unwrap()).collect::<Vec<_>>();
|
|
let ret = return_type.cpp_type().unwrap();
|
|
let call_code = vec![
|
|
"[[maybe_unused]] auto self = this;".into(),
|
|
format!(
|
|
"{}{access}({});",
|
|
if **return_type == Type::Void { "" } else { "return " },
|
|
(0..args.len()).map(|i| format!("arg_{}", i)).join(", ")
|
|
),
|
|
];
|
|
declarations.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: format!("invoke_{}", ident(&p.name)),
|
|
signature: format!(
|
|
"({}) const -> {ret}",
|
|
param_types
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| format!("{} arg_{}", ty, i))
|
|
.join(", "),
|
|
),
|
|
statements: Some(call_code),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
} else {
|
|
let cpp_property_type = p.ty.cpp_type().expect("Invalid type in public properties");
|
|
let prop_getter: Vec<String> = vec![
|
|
"[[maybe_unused]] auto self = this;".into(),
|
|
format!("return {}.get();", access),
|
|
];
|
|
declarations.push((
|
|
Access::Public,
|
|
Declaration::Function(Function {
|
|
name: format!("get_{}", &prop_ident),
|
|
signature: format!("() const -> {}", &cpp_property_type),
|
|
statements: Some(prop_getter),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
|
|
if !p.read_only {
|
|
let prop_setter: Vec<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!("set_{}", &prop_ident),
|
|
signature: format!("(const {} &value) const", &cpp_property_type),
|
|
statements: Some(prop_setter),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
} else {
|
|
declarations.push((
|
|
Access::Private,
|
|
Declaration::Function(Function {
|
|
name: format!("set_{}", &prop_ident),
|
|
signature: format!(
|
|
"(const {cpp_property_type} &) const = delete /* property '{}' is declared as 'out' (read-only). Declare it as 'in' or 'in-out' to enable the setter */", p.name
|
|
),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (name, ty) in private_properties {
|
|
let prop_ident = ident(name);
|
|
|
|
if let Type::Function { args, .. } = &ty {
|
|
let param_types = args.iter().map(|t| t.cpp_type().unwrap()).join(", ");
|
|
declarations.push((
|
|
Access::Private,
|
|
Declaration::Function(Function {
|
|
name: format!("invoke_{prop_ident}"),
|
|
signature: format!(
|
|
"({param_types}) const = delete /* the function '{name}' is declared as private. Declare it as 'public' */",
|
|
),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
} else {
|
|
declarations.push((
|
|
Access::Private,
|
|
Declaration::Function(Function {
|
|
name: format!("get_{prop_ident}"),
|
|
signature: format!(
|
|
"() const = delete /* the property '{name}' is declared as private. Declare it as 'in', 'out', or 'in-out' to make it public */",
|
|
),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
declarations.push((
|
|
Access::Private,
|
|
Declaration::Function(Function {
|
|
name: format!("set_{}", &prop_ident),
|
|
signature: format!(
|
|
"(const auto &) const = delete /* property '{name}' is declared as private. Declare it as 'in' or 'in-out' to make it public */",
|
|
),
|
|
..Default::default()
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn follow_sub_component_path<'a>(
|
|
root: &'a llr::SubComponent,
|
|
sub_component_path: &[usize],
|
|
) -> (String, &'a llr::SubComponent) {
|
|
let mut compo_path = String::new();
|
|
let mut sub_component = root;
|
|
for i in sub_component_path {
|
|
let sub_component_name = ident(&sub_component.sub_components[*i].name);
|
|
write!(compo_path, "{}.", sub_component_name).unwrap();
|
|
sub_component = &sub_component.sub_components[*i].ty;
|
|
}
|
|
(compo_path, sub_component)
|
|
}
|
|
|
|
fn access_window_field(ctx: &EvaluationContext) -> String {
|
|
format!("{}->window().window_handle()", ctx.generator_state.global_access)
|
|
}
|
|
|
|
/// Returns the code that can access the given property (but without the set or get)
|
|
///
|
|
/// to be used like:
|
|
/// ```ignore
|
|
/// let access = access_member(...);
|
|
/// format!("{}.get()", access)
|
|
/// ```
|
|
/// or for a function
|
|
/// ```ignore
|
|
/// let access = access_member(...);
|
|
/// format!("{access}(...)")
|
|
/// ```
|
|
fn access_member(reference: &llr::PropertyReference, ctx: &EvaluationContext) -> String {
|
|
fn in_native_item(
|
|
ctx: &EvaluationContext,
|
|
sub_component_path: &[usize],
|
|
item_index: u32,
|
|
prop_name: &str,
|
|
path: &str,
|
|
) -> String {
|
|
let (compo_path, sub_component) =
|
|
follow_sub_component_path(ctx.current_sub_component.unwrap(), sub_component_path);
|
|
let item_name = ident(&sub_component.items[item_index as usize].name);
|
|
if prop_name.is_empty() {
|
|
// then this is actually a reference to the element itself
|
|
format!("{}->{}{}", path, compo_path, item_name)
|
|
} else {
|
|
let property_name = ident(prop_name);
|
|
|
|
format!("{path}->{compo_path}{item_name}.{property_name}")
|
|
}
|
|
}
|
|
|
|
match reference {
|
|
llr::PropertyReference::Local { sub_component_path, property_index } => {
|
|
if let Some(sub_component) = ctx.current_sub_component {
|
|
let (compo_path, sub_component) =
|
|
follow_sub_component_path(sub_component, sub_component_path);
|
|
let property_name = ident(&sub_component.properties[*property_index].name);
|
|
format!("self->{}{}", compo_path, property_name)
|
|
} else if let Some(current_global) = ctx.current_global {
|
|
format!("this->{}", ident(¤t_global.properties[*property_index].name))
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
llr::PropertyReference::Function { sub_component_path, function_index } => {
|
|
if let Some(sub_component) = ctx.current_sub_component {
|
|
let (compo_path, sub_component) =
|
|
follow_sub_component_path(sub_component, sub_component_path);
|
|
let name = ident(&sub_component.functions[*function_index].name);
|
|
format!("self->{compo_path}fn_{name}")
|
|
} else if let Some(current_global) = ctx.current_global {
|
|
format!("this->fn_{}", ident(¤t_global.functions[*function_index].name))
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => {
|
|
in_native_item(ctx, sub_component_path, *item_index, prop_name, "self")
|
|
}
|
|
llr::PropertyReference::InParent { level, parent_reference } => {
|
|
let mut ctx = ctx;
|
|
let mut path = "self".to_string();
|
|
for _ in 0..level.get() {
|
|
write!(path, "->parent").unwrap();
|
|
ctx = ctx.parent.as_ref().unwrap().ctx;
|
|
}
|
|
|
|
match &**parent_reference {
|
|
llr::PropertyReference::Local { sub_component_path, property_index } => {
|
|
let sub_component = ctx.current_sub_component.unwrap();
|
|
let (compo_path, sub_component) =
|
|
follow_sub_component_path(sub_component, sub_component_path);
|
|
let property_name = ident(&sub_component.properties[*property_index].name);
|
|
format!("{}->{}{}", path, compo_path, property_name)
|
|
}
|
|
llr::PropertyReference::Function { sub_component_path, function_index } => {
|
|
let sub_component = ctx.current_sub_component.unwrap();
|
|
let (compo_path, sub_component) =
|
|
follow_sub_component_path(sub_component, sub_component_path);
|
|
let name = ident(&sub_component.functions[*function_index].name);
|
|
format!("{path}->{compo_path}fn_{name}")
|
|
}
|
|
llr::PropertyReference::InNativeItem {
|
|
sub_component_path,
|
|
item_index,
|
|
prop_name,
|
|
} => in_native_item(ctx, sub_component_path, *item_index, prop_name, &path),
|
|
llr::PropertyReference::InParent { .. }
|
|
| llr::PropertyReference::Global { .. }
|
|
| llr::PropertyReference::GlobalFunction { .. } => {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
llr::PropertyReference::Global { global_index, property_index } => {
|
|
let global_access = &ctx.generator_state.global_access;
|
|
let global = &ctx.compilation_unit.globals[*global_index];
|
|
let global_id = format!("global_{}", ident(&global.name));
|
|
let property_name = ident(
|
|
&ctx.compilation_unit.globals[*global_index].properties[*property_index].name,
|
|
);
|
|
format!("{}->{}->{}", global_access, global_id, property_name)
|
|
}
|
|
llr::PropertyReference::GlobalFunction { global_index, function_index } => {
|
|
let global_access = &ctx.generator_state.global_access;
|
|
let global = &ctx.compilation_unit.globals[*global_index];
|
|
let global_id = format!("global_{}", ident(&global.name));
|
|
let name =
|
|
ident(&ctx.compilation_unit.globals[*global_index].functions[*function_index].name);
|
|
format!("{global_access}->{global_id}->fn_{name}")
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the NativeClass for a PropertyReference::InNativeItem
|
|
/// (or a InParent of InNativeItem )
|
|
fn native_item<'a>(
|
|
item_ref: &llr::PropertyReference,
|
|
ctx: &'a EvaluationContext,
|
|
) -> &'a NativeClass {
|
|
match item_ref {
|
|
llr::PropertyReference::InNativeItem { sub_component_path, item_index, prop_name: _ } => {
|
|
let mut sub_component = ctx.current_sub_component.unwrap();
|
|
for i in sub_component_path {
|
|
sub_component = &sub_component.sub_components[*i].ty;
|
|
}
|
|
&sub_component.items[*item_index as usize].ty
|
|
}
|
|
llr::PropertyReference::InParent { level, parent_reference } => {
|
|
let mut ctx = ctx;
|
|
for _ in 0..level.get() {
|
|
ctx = ctx.parent.as_ref().unwrap().ctx;
|
|
}
|
|
native_item(parent_reference, ctx)
|
|
}
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String {
|
|
use llr::Expression;
|
|
match expr {
|
|
Expression::StringLiteral(s) => {
|
|
format!(r#"slint::SharedString(u8"{}")"#, escape_string(s.as_str()))
|
|
}
|
|
Expression::NumberLiteral(num) => {
|
|
if !num.is_finite() {
|
|
// just print something
|
|
"0.0".to_string()
|
|
} else if num.abs() > 1_000_000_000. {
|
|
// If the numbers are too big, decimal notation will give too many digit
|
|
format!("{:+e}", num)
|
|
} else {
|
|
num.to_string()
|
|
}
|
|
}
|
|
Expression::BoolLiteral(b) => b.to_string(),
|
|
Expression::PropertyReference(nr) => {
|
|
let access = access_member(nr, ctx);
|
|
format!(r#"{}.get()"#, access)
|
|
}
|
|
Expression::BuiltinFunctionCall { function, arguments } => {
|
|
compile_builtin_function_call(function.clone(), arguments, ctx)
|
|
}
|
|
Expression::CallBackCall{ callback, arguments } => {
|
|
let f = access_member(callback, ctx);
|
|
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
|
|
format!("{}.call({})", f, a.join(","))
|
|
}
|
|
Expression::FunctionCall{ function, arguments } => {
|
|
let f = access_member(function, ctx);
|
|
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
|
|
format!("{}({})", f, a.join(","))
|
|
}
|
|
|
|
Expression::ExtraBuiltinFunctionCall { function, arguments, return_ty: _ } => {
|
|
let mut a = arguments.iter().map(|a| compile_expression(a, ctx));
|
|
format!("slint::private_api::{}({})", ident(function), a.join(","))
|
|
}
|
|
Expression::FunctionParameterReference { index, .. } => format!("arg_{}", index),
|
|
Expression::StoreLocalVariable { name, value } => {
|
|
format!("auto {} = {};", ident(name), compile_expression(value, ctx))
|
|
}
|
|
Expression::ReadLocalVariable { name, .. } => ident(name),
|
|
Expression::StructFieldAccess { base, name } => match base.ty(ctx) {
|
|
Type::Struct { fields, name : None, .. } => {
|
|
let index = fields
|
|
.keys()
|
|
.position(|k| k == name)
|
|
.expect("Expression::ObjectAccess: Cannot find a key in an object");
|
|
format!("std::get<{}>({})", index, compile_expression(base, ctx))
|
|
}
|
|
Type::Struct{..} => {
|
|
format!("{}.{}", compile_expression(base, ctx), ident(name))
|
|
}
|
|
_ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
|
|
},
|
|
Expression::ArrayIndex { array, index } => {
|
|
format!(
|
|
"slint::private_api::access_array_index({}, {})",
|
|
compile_expression(array, ctx), compile_expression(index, ctx)
|
|
)
|
|
},
|
|
Expression::Cast { from, to } => {
|
|
let f = compile_expression(from, ctx);
|
|
match (from.ty(ctx), to) {
|
|
(Type::Float32, Type::Int32) => {
|
|
format!("std::round({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{ fields, name: Some(_), ..}) => {
|
|
format!(
|
|
"[&](const auto &o){{ {struct_name} s; {fields} return s; }}({obj})",
|
|
struct_name = to.cpp_type().unwrap(),
|
|
fields = fields.keys().enumerate().map(|(i, n)| format!("s.{} = std::get<{}>(o); ", ident(n), i)).join(""),
|
|
obj = f,
|
|
)
|
|
}
|
|
(Type::Array(..), Type::PathData)
|
|
if matches!(
|
|
from.as_ref(),
|
|
Expression::Array { element_ty: Type::Struct { .. }, .. }
|
|
) =>
|
|
{
|
|
let path_elements = match from.as_ref() {
|
|
Expression::Array { element_ty: _, values, as_model: _ } => values
|
|
.iter()
|
|
.map(|path_elem_expr| {
|
|
let (field_count, qualified_elem_type_name) = match path_elem_expr.ty(ctx) {
|
|
Type::Struct{ fields, name: Some(name), .. } => (fields.len(), name),
|
|
_ => unreachable!()
|
|
};
|
|
// Turn slint::private_api::PathLineTo into `LineTo`
|
|
let elem_type_name = qualified_elem_type_name.split("::").last().unwrap().strip_prefix("Path").unwrap();
|
|
let elem_init = if field_count > 0 {
|
|
compile_expression(path_elem_expr, ctx)
|
|
} else {
|
|
String::new()
|
|
};
|
|
format!("slint::private_api::PathElement::{}({})", elem_type_name, elem_init)
|
|
}),
|
|
_ => {
|
|
unreachable!()
|
|
}
|
|
}.collect::<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 mut buffer = [0; 3];
|
|
format!(
|
|
"({lhs} {op} {rhs})",
|
|
lhs = compile_expression(lhs, ctx),
|
|
rhs = compile_expression(rhs, ctx),
|
|
op = match op {
|
|
'=' => "==",
|
|
'!' => "!=",
|
|
'≤' => "<=",
|
|
'≥' => ">=",
|
|
'&' => "&&",
|
|
'|' => "||",
|
|
'/' => "/(float)",
|
|
_ => op.encode_utf8(&mut buffer),
|
|
},
|
|
)
|
|
}
|
|
Expression::UnaryOp { sub, op } => {
|
|
format!("({op} {sub})", sub = compile_expression(sub, ctx), op = op,)
|
|
}
|
|
Expression::ImageReference { resource_ref, nine_slice } => {
|
|
let image = match resource_ref {
|
|
crate::expression_tree::ImageReference::None => r#"slint::Image()"#.to_string(),
|
|
crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#, escape_string(path.as_str())),
|
|
crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
|
|
let symbol = format!("slint_embedded_resource_{}", resource_id);
|
|
format!(r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#, escape_string(extension))
|
|
}
|
|
crate::expression_tree::ImageReference::EmbeddedTexture{resource_id} => {
|
|
format!("slint::private_api::image_from_embedded_textures(&slint_embedded_resource_{resource_id})")
|
|
},
|
|
};
|
|
match &nine_slice {
|
|
Some([a, b, c, d]) => {
|
|
format!("([&] {{ auto image = {image}; image.set_nine_slice_edges({a}, {b}, {c}, {d}); return image; }})()")
|
|
}
|
|
None => image,
|
|
}
|
|
}
|
|
Expression::Condition { condition, true_expr, false_expr } => {
|
|
let ty = expr.ty(ctx);
|
|
let cond_code = compile_expression(condition, ctx);
|
|
let cond_code = remove_parentheses(&cond_code);
|
|
let true_code = return_compile_expression(true_expr, ctx, Some(&ty));
|
|
let false_code = return_compile_expression(false_expr, ctx, Some(&ty));
|
|
format!(
|
|
r#"[&]() -> {} {{ if ({}) {{ {}; }} else {{ {}; }}}}()"#,
|
|
ty.cpp_type().unwrap_or_else(|| "void".to_string()),
|
|
cond_code,
|
|
true_code,
|
|
false_code
|
|
)
|
|
}
|
|
Expression::Array { element_ty, values, as_model } => {
|
|
let ty = element_ty.cpp_type().unwrap();
|
|
let mut val = values.iter().map(|e| format!("{ty} ( {expr} )", expr = compile_expression(e, ctx), ty = ty));
|
|
if *as_model {
|
|
format!(
|
|
"std::make_shared<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 } => {
|
|
if let Type::Struct{fields, name: None, ..} = ty {
|
|
let mut elem = fields.iter().map(|(k, t)| {
|
|
values
|
|
.get(k)
|
|
.map(|e| compile_expression(e, ctx))
|
|
.map(|e| {
|
|
// explicit conversion to avoid warning C4244 (possible loss of data) with MSVC
|
|
if t.as_unit_product().is_some() { format!("{}({e})", t.cpp_type().unwrap()) } else {e}
|
|
})
|
|
.unwrap_or_else(|| "(Error: missing member in object)".to_owned())
|
|
});
|
|
format!("std::make_tuple({})", elem.join(", "))
|
|
} else if let Type::Struct{ name: Some(_), .. } = ty {
|
|
format!(
|
|
"[&]({args}){{ {ty} o{{}}; {fields}return o; }}({vals})",
|
|
args = (0..values.len()).map(|i| format!("const auto &a_{}", i)).join(", "),
|
|
ty = ty.cpp_type().unwrap(),
|
|
fields = values.keys().enumerate().map(|(i, f)| format!("o.{} = a_{}; ", ident(f), i)).join(""),
|
|
vals = values.values().map(|e| compile_expression(e, ctx)).join(", "),
|
|
)
|
|
} else {
|
|
panic!("Expression::Object is not a Type::Object")
|
|
}
|
|
}
|
|
Expression::EasingCurve(EasingCurve::Linear) => "slint::cbindgen_private::EasingCurve()".into(),
|
|
Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!(
|
|
"slint::cbindgen_private::EasingCurve(slint::cbindgen_private::EasingCurve::Tag::CubicBezier, {}, {}, {}, {})",
|
|
a, b, c, d
|
|
),
|
|
Expression::EasingCurve(EasingCurve::EaseInElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseInElastic".into(),
|
|
Expression::EasingCurve(EasingCurve::EaseOutElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseOutElastic".into(),
|
|
Expression::EasingCurve(EasingCurve::EaseInOutElastic) => "slint::cbindgen_private::EasingCurve::Tag::EaseInOutElastic".into(),
|
|
Expression::EasingCurve(EasingCurve::EaseInBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseInBounce".into(),
|
|
Expression::EasingCurve(EasingCurve::EaseOutBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseOutElastic".into(),
|
|
Expression::EasingCurve(EasingCurve::EaseInOutBounce) => "slint::cbindgen_private::EasingCurve::Tag::EaseInOutElastic".into(),
|
|
Expression::LinearGradient{angle, stops} => {
|
|
let angle = compile_expression(angle, ctx);
|
|
let mut stops_it = stops.iter().map(|(color, stop)| {
|
|
let color = compile_expression(color, ctx);
|
|
let position = compile_expression(stop, ctx);
|
|
format!("slint::private_api::GradientStop{{ {}, {}, }}", color, position)
|
|
});
|
|
format!(
|
|
"[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::LinearGradientBrush({}, stops, {})); }}()",
|
|
stops_it.join(", "), angle, stops.len()
|
|
)
|
|
}
|
|
Expression::RadialGradient{ stops} => {
|
|
let mut stops_it = stops.iter().map(|(color, stop)| {
|
|
let color = compile_expression(color, ctx);
|
|
let position = compile_expression(stop, ctx);
|
|
format!("slint::private_api::GradientStop{{ {}, {}, }}", color, position)
|
|
});
|
|
format!(
|
|
"[&] {{ const slint::private_api::GradientStop stops[] = {{ {} }}; return slint::Brush(slint::private_api::RadialGradientBrush(stops, {})); }}()",
|
|
stops_it.join(", "), stops.len()
|
|
)
|
|
}
|
|
Expression::EnumerationValue(value) => {
|
|
let prefix = if value.enumeration.node.is_some() { "" } else {"slint::cbindgen_private::"};
|
|
format!(
|
|
"{prefix}{}::{}",
|
|
ident(&value.enumeration.name),
|
|
ident(&value.to_pascal_case()),
|
|
)
|
|
}
|
|
Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
|
|
let cache = access_member(layout_cache_prop, ctx);
|
|
if let Some(ri) = repeater_index {
|
|
format!("slint::private_api::layout_cache_access({}.get(), {}, {})", cache, index, compile_expression(ri, ctx))
|
|
} else {
|
|
format!("{}.get()[{}]", cache, index)
|
|
}
|
|
}
|
|
Expression::BoxLayoutFunction {
|
|
cells_variable,
|
|
repeater_indices,
|
|
elements,
|
|
orientation,
|
|
sub_expression,
|
|
} => box_layout_function(
|
|
cells_variable,
|
|
repeater_indices.as_ref().map(String::as_str),
|
|
elements,
|
|
*orientation,
|
|
sub_expression,
|
|
ctx,
|
|
),
|
|
Expression::ComputeDialogLayoutCells { cells_variable, roles, unsorted_cells } => {
|
|
let cells_variable = ident(cells_variable);
|
|
let mut cells = match &**unsorted_cells {
|
|
Expression::Array { values, .. } => {
|
|
values.iter().map(|v| compile_expression(v, ctx))
|
|
}
|
|
_ => panic!("dialog layout unsorted cells not an array"),
|
|
};
|
|
format!("slint::cbindgen_private::GridLayoutCellData {cv}_array [] = {{ {c} }};\
|
|
slint::cbindgen_private::slint_reorder_dialog_button_layout({cv}_array, {r});\
|
|
slint::cbindgen_private::Slice<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!("std::cout << {} << std::endl;", a.join("<<"))
|
|
}
|
|
BuiltinFunction::Mod => {
|
|
format!("std::fmod({}, {})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Round => {
|
|
format!("std::round({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Ceil => {
|
|
format!("std::ceil({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Floor => {
|
|
format!("std::floor({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Sqrt => {
|
|
format!("std::sqrt({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Abs => {
|
|
format!("std::abs({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Log => {
|
|
format!("std::log({}) / std::log({})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Pow => {
|
|
format!("std::pow(({}), ({}))", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Sin => {
|
|
format!("std::sin(({}) * {})", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::Cos => {
|
|
format!("std::cos(({}) * {})", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::Tan => {
|
|
format!("std::tan(({}) * {})", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::ASin => {
|
|
format!("std::asin({}) / {}", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::ACos => {
|
|
format!("std::acos({}) / {}", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::ATan => {
|
|
format!("std::atan({}) / {}", a.next().unwrap(), pi_180)
|
|
}
|
|
BuiltinFunction::SetFocusItem => {
|
|
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
|
let window = access_window_field(ctx);
|
|
let focus_item = access_item_rc(pr, ctx);
|
|
format!("{}.set_focus_item({}, true);", window, focus_item)
|
|
} else {
|
|
panic!("internal error: invalid args to SetFocusItem {:?}", arguments)
|
|
}
|
|
}
|
|
BuiltinFunction::ClearFocusItem => {
|
|
if let [llr::Expression::PropertyReference(pr)] = arguments {
|
|
let window = access_window_field(ctx);
|
|
let focus_item = access_item_rc(pr, ctx);
|
|
format!("{}.set_focus_item({}, false);", window, focus_item)
|
|
} else {
|
|
panic!("internal error: invalid args to ClearFocusItem {:?}", arguments)
|
|
}
|
|
}
|
|
/* std::from_chars is unfortunately not yet implemented in all stdlib compiler we support.
|
|
* And std::strtod depends on the locale. Use slint_string_to_float implemented in Rust
|
|
BuiltinFunction::StringIsFloat => {
|
|
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a); }"
|
|
.into()
|
|
}
|
|
BuiltinFunction::StringToFloat => {
|
|
"[](const auto &a){ double v; auto r = std::from_chars(std::begin(a), std::end(a), v); return r.ptr == std::end(a) ? v : 0; }"
|
|
.into()
|
|
}*/
|
|
BuiltinFunction::StringIsFloat => {
|
|
ctx.generator_state.conditional_includes.cstdlib.set(true);
|
|
format!("[](const auto &a){{ float res = 0; return slint::cbindgen_private::slint_string_to_float(&a, &res); }}({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::StringToFloat => {
|
|
ctx.generator_state.conditional_includes.cstdlib.set(true);
|
|
format!("[](const auto &a){{ float res = 0; slint::cbindgen_private::slint_string_to_float(&a, &res); return res; }}({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorRgbaStruct => {
|
|
format!("{}.to_argb_uint()", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorHsvaStruct => {
|
|
format!("{}.to_hsva()", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorBrighter => {
|
|
format!("{}.brighter({})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorDarker => {
|
|
format!("{}.darker({})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorTransparentize => {
|
|
format!("{}.transparentize({})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorMix => {
|
|
format!("{}.mix({}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ColorWithAlpha => {
|
|
format!("{}.with_alpha({})", a.next().unwrap(), a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ImageSize => {
|
|
format!("{}.size()", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::ArrayLength => {
|
|
format!("slint::private_api::model_length({})", a.next().unwrap())
|
|
}
|
|
BuiltinFunction::Rgb => {
|
|
format!("slint::Color::from_argb_uint8(std::clamp(static_cast<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_on_click, llr::Expression::PropertyReference(parent_ref)] =
|
|
arguments
|
|
{
|
|
let mut parent_ctx = ctx;
|
|
let mut component_access = "self".into();
|
|
|
|
if let llr::PropertyReference::InParent { level, .. } = parent_ref {
|
|
for _ in 0..level.get() {
|
|
component_access = format!("{}->parent", component_access);
|
|
parent_ctx = parent_ctx.parent.as_ref().unwrap().ctx;
|
|
}
|
|
};
|
|
|
|
let window = access_window_field(ctx);
|
|
let current_sub_component = parent_ctx.current_sub_component.unwrap();
|
|
let popup = ¤t_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_on_click = compile_expression(close_on_click, ctx);
|
|
format!(
|
|
"{window}.show_popup<{popup_window_id}>({component_access}, [=](auto self) {{ return {position}; }}, {close_on_click}, {{ {parent_component} }})"
|
|
)
|
|
} else {
|
|
panic!("internal error: invalid args to ShowPopupWindow {:?}", arguments)
|
|
}
|
|
}
|
|
BuiltinFunction::ClosePopupWindow => {
|
|
let window = access_window_field(ctx);
|
|
format!("{window}.close_popup()")
|
|
}
|
|
BuiltinFunction::SetSelectionOffsets => {
|
|
if let [llr::Expression::PropertyReference(pr), from, to] = arguments {
|
|
let item = access_member(pr, ctx);
|
|
let item_rc = access_item_rc(pr, ctx);
|
|
let window = access_window_field(ctx);
|
|
let start = compile_expression(from, ctx);
|
|
let end = compile_expression(to, ctx);
|
|
|
|
format!("slint_textinput_set_selection_offsets(&{item}, &{window}.handle(), &{item_rc}, static_cast<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::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(","))
|
|
}
|
|
}
|
|
}
|
|
|
|
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 { name: Some(name), node: Some(_), .. } => {
|
|
Some((&export.0.name, name))
|
|
}
|
|
Type::Enumeration(en) => Some((&export.0.name, &en.name)),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
})
|
|
.filter(|(export_name, type_name)| export_name != type_name)
|
|
.map(|(export_name, type_name)| {
|
|
Declaration::TypeAlias(TypeAlias {
|
|
old_name: ident(type_name),
|
|
new_name: ident(export_name),
|
|
})
|
|
});
|
|
|
|
file.declarations.extend(type_aliases);
|
|
}
|