slint/sixtyfps_compiler/generator/cpp.rs
2021-10-28 16:25:56 +02:00

2533 lines
102 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
/*! module for the C++ code generator
*/
// cSpell:ignore cstdlib cmath constexpr nullptr decltype intptr uintptr
/// 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 declarations: Vec<Declaration>,
pub definitions: Vec<Declaration>,
}
impl Display for File {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for i in &self.includes {
writeln!(f, "#include {}", i)?;
}
for d in &self.declarations {
write!(f, "\n{}", d)?;
}
for d in &self.definitions {
write!(f, "\n{}", d)?;
}
Ok(())
}
}
/// Declarations (top level, or within a struct)
#[derive(Debug, derive_more::Display)]
pub enum Declaration {
Struct(Struct),
Function(Function),
Var(Var),
TypeAlias(TypeAlias),
}
#[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)?;
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,
})
}
}
/// 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::diagnostics::{BuildDiagnostics, Spanned};
use crate::expression_tree::{
BindingExpression, BuiltinFunction, EasingCurve, Expression, NamedReference,
};
use crate::langtype::Type;
use crate::layout::{Layout, LayoutGeometry, LayoutRect, Orientation};
use crate::object_tree::{
Component, Document, ElementRc, PropertyDeclaration, RepeatedElementInfo,
};
use cpp_ast::*;
use itertools::Itertools;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::rc::Rc;
fn ident(ident: &str) -> Cow<'_, str> {
if ident.contains('-') {
ident.replace('-', "_").into()
} else {
ident.into()
}
}
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("sixtyfps::SharedString".to_owned()),
Type::Color => Some("sixtyfps::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::Percent => Some("float".to_owned()),
Type::Bool => Some("bool".to_owned()),
Type::Struct { name: Some(name), node: Some(_), .. } => Some(ident(name).into_owned()),
Type::Struct { name: Some(name), node: None, .. } => {
Some(format!("sixtyfps::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<sixtyfps::Model<{}>>", i.cpp_type()?)),
Type::Image => Some("sixtyfps::Image".to_owned()),
Type::Builtin(elem) => elem.native_class.cpp_type.clone(),
Type::Enumeration(enumeration) => {
Some(format!("sixtyfps::cbindgen_private::{}", ident(&enumeration.name)))
}
Type::Brush => Some("sixtyfps::Brush".to_owned()),
Type::LayoutCache => Some("sixtyfps::SharedVector<float>".into()),
_ => None,
}
}
}
fn get_cpp_type(ty: &Type, decl: &PropertyDeclaration, diag: &mut BuildDiagnostics) -> String {
ty.cpp_type().unwrap_or_else(|| {
diag.push_error("Cannot map property type to C++".into(), &decl.type_node());
"".into()
})
}
fn to_cpp_orientation(o: Orientation) -> &'static str {
match o {
Orientation::Horizontal => "sixtyfps::Orientation::Horizontal",
Orientation::Vertical => "sixtyfps::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(')') {
&expr[1..expr.len() - 1]
} else {
expr
}
}
fn new_struct_with_bindings(
type_name: &str,
bindings: &BTreeMap<String, BindingExpression>,
component: &Rc<Component>,
) -> String {
let bindings_initialization: Vec<String> = bindings
.iter()
.map(|(prop, initializer)| {
let initializer = compile_expression(initializer, component);
format!("var.{} = {};", ident(prop), initializer)
})
.collect();
format!(
r#"[&](){{
{} var{{}};
{}
return var;
}}()"#,
type_name,
bindings_initialization.join("\n")
)
}
fn property_animation_code(component: &Rc<Component>, animation: &ElementRc) -> String {
new_struct_with_bindings(
"sixtyfps::cbindgen_private::PropertyAnimation",
&animation.borrow().bindings,
component,
)
}
fn property_set_value_code(
component: &Rc<Component>,
element: &ElementRc,
property_name: &str,
value_expr: &str,
) -> String {
match element.borrow().bindings.get(property_name).and_then(|b| b.animation.as_ref()) {
Some(crate::object_tree::PropertyAnimation::Static(animation)) => {
let animation_code = property_animation_code(component, animation);
format!(
"set_animated_value({value}, {animation})",
value = value_expr,
animation = animation_code
)
}
_ => format!("set({})", value_expr),
}
}
fn handle_property_binding(
elem: &ElementRc,
prop_name: &str,
binding_expression: &BindingExpression,
init: &mut Vec<String>,
) {
let item = elem.borrow();
let component = item.enclosing_component.upgrade().unwrap();
let accessor_prefix = if item.property_declarations.contains_key(prop_name) {
String::new()
} else if item.is_flickable_viewport {
format!(
"{id}.viewport.",
id = ident(&crate::object_tree::find_parent_element(elem).unwrap().borrow().id)
)
} else {
format!("{id}.", id = ident(&item.id))
};
let prop_type = item.lookup_property(prop_name).property_type;
if let Type::Callback { args, .. } = &prop_type {
if matches!(binding_expression.expression, Expression::Invalid) {
return;
}
let mut params = args.iter().enumerate().map(|(i, ty)| {
format!("[[maybe_unused]] {} arg_{}", ty.cpp_type().unwrap_or_default(), i)
});
init.push(format!(
"{accessor_prefix}{prop}.set_handler(
[this]({params}) {{
[[maybe_unused]] auto self = this;
return {code};
}});",
accessor_prefix = accessor_prefix,
prop = ident(prop_name),
params = params.join(", "),
code = compile_expression_wrap_return(binding_expression, &component)
));
} else {
for nr in &binding_expression.two_way_bindings {
init.push(format!(
"sixtyfps::private_api::Property<{ty}>::link_two_way(&{p1}, &{p2});",
ty = prop_type.cpp_type().unwrap_or_default(),
p1 = access_member(elem, prop_name, &component, "this"),
p2 = access_named_reference(nr, &component, "this")
));
}
if matches!(binding_expression.expression, Expression::Invalid) {
return;
}
let component = &item.enclosing_component.upgrade().unwrap();
let init_expr = compile_expression_wrap_return(binding_expression, component);
let cpp_prop = format!("{}{}", accessor_prefix, ident(prop_name));
let is_constant =
binding_expression.analysis.borrow().as_ref().map_or(false, |a| a.is_const);
init.push(if is_constant {
format!("{}.set({});", cpp_prop, init_expr)
} else {
let binding_code = format!(
"[this]() {{
[[maybe_unused]] auto self = this;
return {init};
}}",
init = init_expr
);
let is_state_info = matches!(prop_type, Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo"));
if is_state_info {
format!("sixtyfps::private_api::set_state_binding({}, {});", cpp_prop, binding_code)
} else {
match &binding_expression.animation {
Some(crate::object_tree::PropertyAnimation::Static(anim)) => {
let anim = property_animation_code(component, anim);
format!("{}.set_animated_binding({}, {});", cpp_prop, binding_code, anim)
}
Some(crate::object_tree::PropertyAnimation::Transition {
state_ref,
animations,
}) => {
let state_tokens = compile_expression(state_ref, component);
let mut anim_expr = animations.iter().map(|a| {
let cond = compile_expression(
&a.condition(Expression::ReadLocalVariable {
name: "state".into(),
ty: state_ref.ty(),
}),
component,
);
let anim = property_animation_code(component, &a.animation);
format!("if ({}) {{ return {}; }}", remove_parentheses(&cond), anim)
});
format!(
"{}.set_animated_binding_for_transition({},
[this](uint64_t *start_time) -> sixtyfps::cbindgen_private::PropertyAnimation {{
[[maybe_unused]] auto self = this;
auto state = {};
*start_time = state.change_time;
{}
return {{}};
}});",
cpp_prop,
binding_code,
state_tokens,
anim_expr.join(" ")
)
}
None => format!("{}.set_binding({});", cpp_prop, binding_code),
}
}
});
}
}
fn handle_item(elem: &ElementRc, main_struct: &mut Struct) {
let item = elem.borrow();
main_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: format!(
"sixtyfps::cbindgen_private::{}",
ident(&item.base_type.as_native().class_name)
),
name: ident(&item.id).into_owned(),
init: Some("{}".to_owned()),
..Default::default()
}),
));
}
fn handle_repeater(
repeated: &RepeatedElementInfo,
base_component: &Rc<Component>,
parent_component: &Rc<Component>,
repeater_count: i32,
component_struct: &mut Struct,
init: &mut Vec<String>,
children_visitor_cases: &mut Vec<String>,
repeated_input_branch: &mut Vec<String>,
layout_repeater_code: &mut Vec<String>,
diag: &mut BuildDiagnostics,
) {
let parent_element = base_component.parent_element.upgrade().unwrap();
let repeater_id = format!("repeater_{}", ident(&parent_element.borrow().id));
let mut model = compile_expression(&repeated.model, parent_component);
if repeated.is_conditional_element {
// bool converts to int
// FIXME: don't do a heap allocation here
model = format!("std::make_shared<sixtyfps::private_api::IntModel>({})", model)
};
// FIXME: optimize if repeated.model.is_constant()
init.push(format!(
"self->{repeater_id}.set_model_binding([self] {{ (void)self; return {model}; }});",
repeater_id = repeater_id,
model = model,
));
if let Some(listview) = &repeated.is_listview {
let vp_y = access_named_reference(&listview.viewport_y, parent_component, "self");
let vp_h = access_named_reference(&listview.viewport_height, parent_component, "self");
let lv_h = access_named_reference(&listview.listview_height, parent_component, "self");
let vp_w = access_named_reference(&listview.viewport_width, parent_component, "self");
let lv_w = access_named_reference(&listview.listview_width, parent_component, "self");
let ensure_updated = format!(
"self->{}.ensure_updated_listview(self, &{}, &{}, &{}, {}.get(), {}.get());",
repeater_id, vp_w, vp_h, vp_y, lv_w, lv_h
);
children_visitor_cases.push(format!(
"\n case {i}: {{
{e_u}
return self->{id}.visit(order, visitor);
}}",
i = repeater_count,
e_u = ensure_updated,
id = repeater_id,
));
layout_repeater_code.push(ensure_updated)
} else {
children_visitor_cases.push(format!(
"\n case {i}: {{
self->{id}.ensure_updated(self);
return self->{id}.visit(order, visitor);
}}",
id = repeater_id,
i = repeater_count,
));
layout_repeater_code.push(format!("self->{}.ensure_updated(self);", repeater_id));
}
repeated_input_branch.push(format!(
"\n case {i}: return self->{id}.item_at(rep_index);",
i = repeater_count,
id = repeater_id,
));
component_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: format!(
"sixtyfps::private_api::Repeater<class {}, {}>",
component_id(base_component),
model_data_type(&parent_element, diag)
),
name: repeater_id,
..Default::default()
}),
));
}
/// Returns the text of the C++ code produced by the given root component
pub fn generate(doc: &Document, diag: &mut BuildDiagnostics) -> Option<impl std::fmt::Display> {
let mut file = File::default();
file.includes.push("<array>".into());
file.includes.push("<limits>".into());
file.includes.push("<cstdlib>".into()); // TODO: ideally only include this if needed (by to_float)
file.includes.push("<cmath>".into()); // TODO: ideally only include this if needed (by floor/ceil/round)
file.includes.push("<sixtyfps.h>".into());
file.declarations.extend(doc.root_component.embedded_file_resources.borrow().iter().map(
|(path, id)| {
let file = crate::fileaccess::load_file(std::path::Path::new(path)).unwrap(); // embedding pass ensured that the file exists
let data = file.read();
let mut init = "{ ".to_string();
use std::fmt::Write;
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_str("}");
Declaration::Var(Var {
ty: "inline uint8_t".into(),
name: format!("sfps_embedded_resource_{}", id),
array_size: Some(data.len()),
init: Some(init),
})
},
));
for ty in doc.root_component.used_types.borrow().structs.iter() {
if let Type::Struct { fields, name: Some(name), node: Some(_) } = ty {
generate_struct(&mut file, name, fields, diag);
}
}
for sub_comp in doc.root_component.used_types.borrow().sub_components.iter() {
generate_component(&mut file, sub_comp, diag, None);
}
for glob in doc
.root_component
.used_types
.borrow()
.globals
.iter()
.filter(|glob| glob.requires_code_generation())
{
generate_component(&mut file, glob, diag, None);
if glob.visible_in_public_api() {
file.definitions.extend(glob.global_aliases().into_iter().map(|name| {
Declaration::TypeAlias(TypeAlias {
old_name: ident(&glob.root_element.borrow().id).into_owned(),
new_name: name,
})
}))
}
}
generate_component(&mut file, &doc.root_component, diag, None);
file.definitions.push(Declaration::Var(Var{
ty: format!(
"[[maybe_unused]] constexpr sixtyfps::private_api::VersionCheckHelper<{}, {}, {}>",
env!("CARGO_PKG_VERSION_MAJOR"),
env!("CARGO_PKG_VERSION_MINOR"),
env!("CARGO_PKG_VERSION_PATCH")),
name: "THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME".into(),
init: Some("sixtyfps::private_api::VersionCheckHelper<int(sixtyfps::private_api::VersionCheck::Major), int(sixtyfps::private_api::VersionCheck::Minor), int(sixtyfps::private_api::VersionCheck::Patch)>()".into()),
..Default::default()
}));
if diag.has_error() {
None
} else {
Some(file)
}
}
fn generate_struct(
file: &mut File,
name: &str,
fields: &BTreeMap<String, Type>,
diag: &mut BuildDiagnostics,
) {
let mut operator_eq = String::new();
let mut members = fields
.iter()
.map(|(name, t)| {
use std::fmt::Write;
write!(operator_eq, " && a.{0} == b.{0}", ident(name)).unwrap();
(
Access::Public,
Declaration::Var(Var {
ty: t.cpp_type().unwrap_or_else(|| {
diag.push_error(
format!("Cannot map {} to a C++ type", t),
&Option::<crate::parser::SyntaxNode>::None,
);
Default::default()
}),
name: ident(name).into_owned(),
..Default::default()
}),
)
})
.collect::<Vec<_>>();
members.sort_unstable_by(|a, b| match (&a.1, &b.1) {
(Declaration::Var(a), Declaration::Var(b)) => a.name.cmp(&b.name),
_ => unreachable!(),
});
members.push((
Access::Public,
Declaration::Function(Function {
name: "operator==".to_owned(),
signature: format!("(const {0} &a, const {0} &b) -> bool", name),
is_friend: true,
statements: Some(vec![format!("return true{};", operator_eq)]),
..Function::default()
}),
));
members.push((
Access::Public,
Declaration::Function(Function {
name: "operator!=".to_owned(),
signature: format!("(const {0} &a, const {0} &b) -> bool", name),
is_friend: true,
statements: Some(vec!["return !(a == b);".into()]),
..Function::default()
}),
));
file.declarations.push(Declaration::Struct(Struct {
name: name.into(),
members,
..Default::default()
}))
}
/// 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_component(
file: &mut File,
component: &Rc<Component>,
diag: &mut BuildDiagnostics,
mut sub_components: Option<&mut Vec<String>>,
) {
let component_id = component_id(component);
let mut component_struct = Struct { name: component_id.clone(), ..Default::default() };
let is_child_component = component.parent_element.upgrade().is_some();
let is_sub_component =
!component.is_root_component.get() && !is_child_component && !component.is_global();
for c in component.popup_windows.borrow().iter() {
let mut friends = vec![self::component_id(&c.component)];
generate_component(file, &c.component, diag, Some(&mut friends));
if let Some(sub_components) = sub_components.as_mut() {
sub_components.extend_from_slice(friends.as_slice());
}
component_struct.friends.append(&mut friends);
}
let expose_property = |property: &PropertyDeclaration| -> bool {
if component.is_global() || component.is_root_component.get() {
property.expose_in_public_api
} else {
false
}
};
let mut init = vec!["[[maybe_unused]] auto self = this;".into()];
for (prop_name, property_decl) in component.root_element.borrow().property_declarations.iter() {
let cpp_name = ident(prop_name);
let access = if let Some(alias) = &property_decl.is_alias {
access_named_reference(alias, component, "this")
} else {
format!("this->{}", cpp_name)
};
let ty = if let Type::Callback { args, return_type } = &property_decl.property_type {
let param_types =
args.iter().map(|t| get_cpp_type(t, property_decl, diag)).collect::<Vec<_>>();
let return_type = return_type
.as_ref()
.map_or("void".into(), |t| get_cpp_type(t, property_decl, diag));
if expose_property(property_decl) {
let callback_emitter = vec![format!(
"return {}.call({});",
access,
(0..args.len()).map(|i| format!("arg_{}", i)).join(", ")
)];
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: format!("invoke_{}", cpp_name),
signature: format!(
"({}) const -> {}",
param_types
.iter()
.enumerate()
.map(|(i, ty)| format!("{} arg_{}", ty, i))
.join(", "),
return_type
),
statements: Some(callback_emitter),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: format!("on_{}", cpp_name),
template_parameters: Some("typename Functor".into()),
signature: "(Functor && callback_handler) const".into(),
statements: Some(vec![format!(
"{}.set_handler(std::forward<Functor>(callback_handler));",
access
)]),
..Default::default()
}),
));
}
format!("sixtyfps::private_api::Callback<{}({})>", return_type, param_types.join(", "))
} else {
let cpp_type = get_cpp_type(&property_decl.property_type, property_decl, diag);
if expose_property(property_decl) {
let prop_getter: Vec<String> = vec![format!("return {}.get();", access)];
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: format!("get_{}", cpp_name),
signature: format!("() const -> {}", cpp_type),
statements: Some(prop_getter),
..Default::default()
}),
));
let set_value = if let Some(alias) = &property_decl.is_alias {
property_set_value_code(component, &alias.element(), alias.name(), "value")
} else {
property_set_value_code(component, &component.root_element, prop_name, "value")
};
let prop_setter: Vec<String> = vec![
"[[maybe_unused]] auto self = this;".into(),
format!("{}.{};", access, set_value),
];
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: format!("set_{}", cpp_name),
signature: format!("(const {} &value) const", cpp_type),
statements: Some(prop_setter),
..Default::default()
}),
));
}
format!("sixtyfps::private_api::Property<{}>", cpp_type)
};
if property_decl.is_alias.is_none() {
component_struct.members.push((
if component.is_global() { Access::Public } else { Access::Private },
Declaration::Var(Var { ty, name: cpp_name.into_owned(), ..Default::default() }),
));
}
}
let mut constructor_parent_arg = String::new();
let mut constructor_member_initializers = vec![];
if component.is_root_component.get() || is_child_component {
let mut window_init = None;
let mut access = Access::Private;
if component.is_root_component.get() {
window_init = Some("sixtyfps::Window{sixtyfps::private_api::WindowRc()}".into());
// FIXME: many of the different component bindings need to access this
access = Access::Public;
} else {
constructor_member_initializers
.push("m_window(parent->m_window.window_handle())".into());
}
component_struct.members.push((
access,
Declaration::Var(Var {
ty: "sixtyfps::Window".into(),
name: "m_window".into(),
init: window_init,
..Default::default()
}),
));
component_struct.members.push((
access,
Declaration::Var(Var {
ty: format!(
"vtable::VWeak<sixtyfps::private_api::ComponentVTable, {}>",
component_id
),
name: "self_weak".into(),
..Var::default()
}),
));
}
if let Some(parent_element) = component.parent_element.upgrade() {
if parent_element.borrow().repeated.as_ref().map_or(false, |r| !r.is_conditional_element) {
let cpp_model_data_type = model_data_type(&parent_element, diag);
component_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: "sixtyfps::private_api::Property<int>".into(),
name: "index".into(),
..Default::default()
}),
));
component_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: format!("sixtyfps::private_api::Property<{}>", cpp_model_data_type),
name: "model_data".into(),
..Default::default()
}),
));
let update_statements = vec!["index.set(i);".into(), "model_data.set(data);".into()];
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
name: "update_data".into(),
signature: format!(
"(int i, const {} &data) const -> void",
cpp_model_data_type
),
statements: Some(update_statements),
..Function::default()
}),
));
} else if parent_element.borrow().repeated.is_some() {
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
name: "update_data".into(),
signature: "(int, int) const -> void".into(),
statements: Some(vec![]),
..Function::default()
}),
));
}
let parent_component_id = self::component_id(
&component
.parent_element
.upgrade()
.unwrap()
.borrow()
.enclosing_component
.upgrade()
.unwrap(),
);
let parent_type = format!("class {} const *", parent_component_id);
constructor_parent_arg = format!("{} parent", parent_type);
init.push("this->parent = parent;".into());
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Var(Var {
ty: parent_type,
name: "parent".into(),
init: Some("nullptr".to_owned()),
..Default::default()
}),
));
component_struct.friends.push(parent_component_id);
if parent_element.borrow().repeated.as_ref().map_or(false, |r| r.is_listview.is_some()) {
let p_y = access_member(&component.root_element, "y", component, "this");
let p_height = access_member(&component.root_element, "height", component, "this");
let p_width = access_member(&component.root_element, "width", component, "this");
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
name: "listview_layout".into(),
signature:
"(float *offset_y, const sixtyfps::private_api::Property<float> *viewport_width) const -> void"
.to_owned(),
statements: Some(vec![
"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 if parent_element.borrow().repeated.is_some() {
component_struct.members.push((
Access::Public, // Because Repeater accesses it
Declaration::Function(Function {
name: "box_layout_data".into(),
signature: "(sixtyfps::Orientation o) const -> sixtyfps::BoxLayoutCellData".to_owned(),
statements: Some(vec!["return { layout_info({&static_vtable, const_cast<void *>(static_cast<const void *>(this))}, o) };".into()]),
..Function::default()
}),
));
}
}
if is_sub_component {
constructor_parent_arg = "uintptr_t item_index_start".into();
}
if component.is_root_component.get() {
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "show".into(),
signature: "()".into(),
statements: Some(vec!["m_window.show();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "hide".into(),
signature: "()".into(),
statements: Some(vec!["m_window.hide();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "window".into(),
signature: "() const -> sixtyfps::Window&".into(),
statements: Some(vec![format!(
"return const_cast<{} *>(this)->m_window;",
component_struct.name
)]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "run".into(),
signature: "()".into(),
statements: Some(vec![
"show();".into(),
"sixtyfps::run_event_loop();".into(),
"hide();".into(),
]),
..Default::default()
}),
));
init.push("self->m_window.window_handle().init_items(this, item_tree());".into());
component_struct.friends.push("sixtyfps::private_api::WindowRc".into());
}
let mut children_visitor_cases = vec![];
let mut repeated_input_branch = vec![];
let mut repeater_layout_code = vec![];
let mut tree_array = vec![];
let mut item_names_and_vt_symbols = vec![];
let mut repeater_count = 0;
fn append_to_item_tree(
component: &Rc<Component>,
item_rc: &ElementRc,
subcomponent_accessor: &str,
subcomponent_base_offset: u32,
children_offset: u32,
parent_index: u32,
tree_array: &mut Vec<String>,
repeater_count: &mut i32,
item_names_and_vt_symbols: &mut Vec<(String, String)>,
) {
let item = item_rc.borrow();
if item.base_type == Type::Void {
assert!(component.is_global());
} else if item.repeated.is_some() {
tree_array.push(format!(
"sixtyfps::private_api::make_dyn_node({}, {})",
repeater_count, parent_index
));
*repeater_count += 1;
} else if let Type::Native(native_class) = &item.base_type {
if item.is_flickable_viewport {
tree_array.push(format!(
"sixtyfps::private_api::make_item_node({}offsetof({}, {}) + offsetof(sixtyfps::cbindgen_private::Flickable, viewport), SIXTYFPS_GET_ITEM_VTABLE(RectangleVTable), {}, {}, {})",
subcomponent_accessor,
&self::component_id(component),
ident(&crate::object_tree::find_parent_element(item_rc).unwrap().borrow().id),
item.children.len(),
children_offset,
parent_index,
));
} else {
tree_array.push(format!(
"sixtyfps::private_api::make_item_node({}offsetof({}, {}), {}, {}, {}, {})",
subcomponent_accessor,
&self::component_id(component),
ident(&item.id),
native_class.cpp_vtable_getter,
item.children.len(),
children_offset,
parent_index,
));
}
item_names_and_vt_symbols.push((
ident(&item.id).into_owned(),
item.base_type.as_native().cpp_vtable_getter.clone(),
));
} else if let Type::Component(sub_component) = &item.base_type {
let subcomponent_base_offset =
subcomponent_base_offset + *item.item_index.get().unwrap() as u32;
super::build_array_helper(
&sub_component,
|nested_item_rc, nested_children_offset, nested_parent_index| {
append_to_item_tree(
sub_component,
nested_item_rc,
&format!(
"{}offsetof({}, {}) + ",
subcomponent_accessor,
&self::component_id(component),
ident(&item.id)
),
subcomponent_base_offset,
subcomponent_base_offset + nested_children_offset,
subcomponent_base_offset + nested_parent_index,
tree_array,
repeater_count,
item_names_and_vt_symbols,
);
},
);
}
}
// Build the item tree separately from the members, as the former needs to be flattened all the way, while the former
// does not nest into sub-components, for example.
super::build_array_helper(component, |item_rc, children_offset, parent_index| {
append_to_item_tree(
component,
item_rc,
"",
0,
children_offset,
parent_index,
&mut tree_array,
&mut repeater_count,
&mut item_names_and_vt_symbols,
)
});
let mut repeater_count = 0;
crate::object_tree::recurse_elem_level_order(&component.root_element, &mut |item_rc| {
let item = item_rc.borrow();
if item.base_type == Type::Void {
assert!(component.is_global());
} else if let Some(repeated) = &item.repeated {
let base_component = item.base_type.as_component();
let mut friends = Vec::new();
generate_component(file, base_component, diag, Some(&mut friends));
if let Some(sub_components) = sub_components.as_mut() {
sub_components.extend_from_slice(friends.as_slice());
sub_components.push(self::component_id(base_component))
}
component_struct.friends.append(&mut friends);
component_struct.friends.push(self::component_id(base_component));
handle_repeater(
repeated,
base_component,
component,
repeater_count,
&mut component_struct,
&mut init,
&mut children_visitor_cases,
&mut repeated_input_branch,
&mut repeater_layout_code,
diag,
);
repeater_count += 1;
} else if let Type::Native(_) = &item.base_type {
handle_item(item_rc, &mut component_struct);
} else if let Type::Component(sub_component) = &item.base_type {
let class_name = self::component_id(&sub_component);
let member_name = ident(&item.id).into_owned();
let parent_index = if is_sub_component { "item_index_start + " } else { "" };
constructor_member_initializers.push(format!(
"{}{{{}{}}}",
member_name,
parent_index,
item.item_index.get().unwrap()
));
component_struct.members.push((
if component.is_root_component.get() { Access::Private } else { Access::Public },
Declaration::Var(Var { ty: class_name, name: member_name, ..Default::default() }),
));
}
});
super::handle_property_bindings_init(component, |elem, prop, binding| {
handle_property_binding(elem, prop, binding, &mut init)
});
component_struct.members.push((
if !component.is_global() && !is_sub_component { Access::Private } else { Access::Public },
Declaration::Function(Function {
name: component_id.clone(),
signature: format!("({})", constructor_parent_arg),
is_constructor_or_destructor: true,
statements: Some(init),
constructor_member_initializers,
..Default::default()
}),
));
if is_child_component || component.is_root_component.get() {
let maybe_constructor_param = if constructor_parent_arg.is_empty() { "" } else { "parent" };
let mut create_code = vec![
format!("auto self_rc = vtable::VRc<sixtyfps::private_api::ComponentVTable, {0}>::make({1});", component_id, maybe_constructor_param),
format!("auto self = const_cast<{0} *>(&*self_rc);", component_id),
"self->self_weak = vtable::VWeak(self_rc);".into(),
];
if component.is_root_component.get() {
create_code.push(
"self->m_window.window_handle().set_component(**self->self_weak.lock());".into(),
);
}
create_code.extend(
component.setup_code.borrow().iter().map(|code| compile_expression(code, component)),
);
create_code
.push(format!("return sixtyfps::ComponentHandle<{0}>{{ self_rc }};", component_id));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "create".into(),
signature: format!(
"({}) -> sixtyfps::ComponentHandle<{}>",
constructor_parent_arg, component_id
),
statements: Some(create_code),
is_static: true,
..Default::default()
}),
));
let mut destructor = vec!["[[maybe_unused]] auto self = this;".to_owned()];
if is_child_component {
destructor.push("if (!parent) return;".to_owned())
}
if !item_names_and_vt_symbols.is_empty() {
destructor.push("sixtyfps::private_api::ItemRef items[] = {".into());
destructor.push(
item_names_and_vt_symbols
.iter()
.map(|(item_name, vt_symbol)| {
format!(
"{{ {vt}, const_cast<decltype(this->{id})*>(&this->{id}) }}",
id = item_name,
vt = vt_symbol
)
})
.join(","),
);
destructor.push("};".into());
destructor.push("m_window.window_handle().free_graphics_resources(sixtyfps::Slice<sixtyfps::private_api::ItemRef>{items, std::size(items)});".into());
}
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: format!("~{}", component_id),
signature: "()".to_owned(),
is_constructor_or_destructor: true,
statements: Some(destructor),
..Default::default()
}),
));
generate_component_vtable(
&mut component_struct,
component_id,
component,
children_visitor_cases,
tree_array,
file,
);
}
let used_types = component.used_types.borrow();
let global_field_name = |glob| format!("global_{}", self::component_id(glob));
for glob in used_types.globals.iter() {
let ty = match &glob.root_element.borrow().base_type {
Type::Void => self::component_id(glob),
Type::Builtin(b) => {
format!("sixtyfps::cbindgen_private::{}", b.native_class.class_name)
}
_ => unreachable!(),
};
component_struct.members.push((
Access::Private,
Declaration::Var(Var {
ty: format!("std::shared_ptr<{}>", ty),
name: global_field_name(glob),
init: Some(format!("std::make_shared<{}>()", ty)),
..Default::default()
}),
));
}
let mut global_accessor_function_body = Vec::new();
for glob in used_types
.globals
.iter()
.filter(|glob| glob.visible_in_public_api() && glob.requires_code_generation())
{
let mut accessor_statement = String::new();
if !global_accessor_function_body.is_empty() {
accessor_statement.push_str("else ");
}
accessor_statement.push_str(&format!(
"if constexpr(std::is_same_v<T, {}>) {{ return *{}.get(); }}",
self::component_id(glob),
global_field_name(glob)
));
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()
}),
));
}
file.definitions.extend(component_struct.extract_definitions().collect::<Vec<_>>());
file.declarations.push(Declaration::Struct(component_struct));
}
fn generate_component_vtable(
component_struct: &mut Struct,
component_id: String,
component: &Rc<Component>,
children_visitor_cases: Vec<String>,
tree_array: Vec<String>,
file: &mut File,
) {
component_struct
.friends
.push(format!("vtable::VRc<sixtyfps::private_api::ComponentVTable, {}>", component_id));
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "visit_children".into(),
signature: "(sixtyfps::private_api::ComponentRef component, intptr_t index, sixtyfps::private_api::TraversalOrder order, sixtyfps::private_api::ItemVisitorRefMut visitor) -> int64_t".into(),
is_static: true,
statements: Some(vec![
"static const auto dyn_visit = [] (const uint8_t *base, [[maybe_unused]] sixtyfps::private_api::TraversalOrder order, [[maybe_unused]] sixtyfps::private_api::ItemVisitorRefMut visitor, uintptr_t dyn_index) -> int64_t {".to_owned(),
format!(" [[maybe_unused]] auto self = reinterpret_cast<const {}*>(base);", component_id),
format!(" switch(dyn_index) {{ {} }};", children_visitor_cases.join("")),
" std::abort();\n};".to_owned(),
format!("auto self_rc = reinterpret_cast<const {}*>(component.instance)->self_weak.lock()->into_dyn();", component_id),
"return sixtyfps::cbindgen_private::sixtyfps_visit_item_tree(&self_rc, item_tree() , index, order, visitor, dyn_visit);".to_owned(),
]),
..Default::default()
}),
));
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "get_item_ref".into(),
signature: "(sixtyfps::private_api::ComponentRef component, uintptr_t index) -> sixtyfps::private_api::ItemRef".into(),
is_static: true,
statements: Some(vec![
"return sixtyfps::private_api::get_item_ref(component, item_tree(), index);".to_owned(),
]),
..Default::default()
}),
));
let parent_item_from_parent_component = if let Some(parent_index) =
component.parent_element.upgrade().and_then(|e| e.borrow().item_index.get().copied())
{
format!(
" *result = sixtyfps::private_api::parent_item(self->parent->self_weak.into_dyn(), self->parent->item_tree(), {});",
parent_index,
)
} else {
"".to_owned()
};
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "parent_item".into(),
signature: "(sixtyfps::private_api::ComponentRef component, uintptr_t index, sixtyfps::private_api::ItemWeak *result) -> void".into(),
is_static: true,
statements: Some(vec![
format!("auto self = reinterpret_cast<const {}*>(component.instance);", component_id),
"if (index == 0) {".into(),
parent_item_from_parent_component,
" return;".into(),
"}".into(),
"*result = sixtyfps::private_api::parent_item(self->self_weak.into_dyn(), item_tree(), index);".into(),
]),
..Default::default()
}),
));
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "item_tree".into(),
signature: "() -> sixtyfps::Slice<sixtyfps::private_api::ItemTreeNode>".into(),
is_static: true,
statements: Some(vec![
"static const sixtyfps::private_api::ItemTreeNode children[] {".to_owned(),
format!(" {} }};", tree_array.join(", ")),
"return { const_cast<sixtyfps::private_api::ItemTreeNode*>(children), std::size(children) };"
.to_owned(),
]),
..Default::default()
}),
));
component_struct.members.push((
Access::Private,
Declaration::Function(Function {
name: "layout_info".into(),
signature:
"([[maybe_unused]] sixtyfps::private_api::ComponentRef component, sixtyfps::Orientation o) -> sixtyfps::LayoutInfo"
.into(),
is_static: true,
statements: Some(vec![
format!("[[maybe_unused]] auto self = reinterpret_cast<const {}*>(component.instance);", component_id),
format!("if (o == sixtyfps::Orientation::Horizontal) return {};",
get_layout_info(&component.root_element, component, &component.root_constraints.borrow(), Orientation::Horizontal)),
format!("else return {};",
get_layout_info(&component.root_element, component, &component.root_constraints.borrow(), Orientation::Vertical))
]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Var(Var {
ty: "static const sixtyfps::private_api::ComponentVTable".to_owned(),
name: "static_vtable".to_owned(),
..Default::default()
}),
));
let root_elem = component.root_element.borrow();
let get_root_item_ref = if let Type::Component(_) = root_elem.base_type {
format!("return this->{id}.root_item();", id = ident(&root_elem.id))
} else {
format!(
"return {{ {vt}, const_cast<decltype(this->{id})*>(&this->{id}) }};",
vt = root_elem.base_type.as_native().cpp_vtable_getter,
id = ident(&root_elem.id)
)
};
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "root_item".into(),
signature: "() const -> sixtyfps::private_api::ItemRef".into(),
statements: Some(vec![get_root_item_ref]),
..Default::default()
}),
));
file.definitions.push(Declaration::Var(Var {
ty: "const sixtyfps::private_api::ComponentVTable".to_owned(),
name: format!("{}::static_vtable", component_id),
init: Some(format!(
"{{ visit_children, get_item_ref, parent_item, layout_info, sixtyfps::private_api::drop_in_place<{}>, sixtyfps::private_api::dealloc }}",
component_id)
),
..Default::default()
}));
}
fn component_id(component: &Rc<Component>) -> String {
if component.is_global() {
ident(&component.root_element.borrow().id).into_owned()
} else if component.id.is_empty() {
format!("Component_{}", ident(&component.root_element.borrow().id))
} else {
ident(&component.id).into_owned()
}
}
fn model_data_type(parent_element: &ElementRc, diag: &mut BuildDiagnostics) -> String {
if parent_element.borrow().repeated.as_ref().unwrap().is_conditional_element {
return "int".into();
}
let model_data_type = crate::expression_tree::Expression::RepeaterModelReference {
element: Rc::downgrade(parent_element),
}
.ty();
model_data_type.cpp_type().unwrap_or_else(|| {
diag.push_error_with_span(
format!("Cannot map property type {} to C++", model_data_type),
parent_element
.borrow()
.node
.as_ref()
.map(|n| n.to_source_location())
.unwrap_or_default(),
);
String::default()
})
}
/// 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)
/// ```
fn access_member(
element: &ElementRc,
name: &str,
component: &Rc<Component>,
component_cpp: &str,
) -> String {
let e = element.borrow();
let enclosing_component = e.enclosing_component.upgrade().unwrap();
if Rc::ptr_eq(component, &enclosing_component) {
if e.property_declarations.contains_key(name) || name.is_empty() || component.is_global() {
format!("{}->{}", component_cpp, ident(name))
} else if e.is_flickable_viewport {
format!(
"{}->{}.viewport.{}",
component_cpp,
ident(&crate::object_tree::find_parent_element(element).unwrap().borrow().id),
ident(name)
)
} else {
format!("{}->{}.{}", component_cpp, ident(&e.id), ident(name))
}
} else if enclosing_component.is_global() {
let mut root_component = component.clone();
let mut component_cpp = component_cpp.to_owned();
while let Some(p) = root_component.parent_element.upgrade() {
root_component = p.borrow().enclosing_component.upgrade().unwrap();
component_cpp = format!("{}->parent", component_cpp);
}
let global_comp =
format!("{}->global_{}", component_cpp, component_id(&enclosing_component));
access_member(element, name, &enclosing_component, &global_comp)
} else {
access_member(
element,
name,
&component
.parent_element
.upgrade()
.unwrap()
.borrow()
.enclosing_component
.upgrade()
.unwrap(),
&format!("{}->parent", component_cpp),
)
}
}
/// Call access_member for a NamedReference
fn access_named_reference(
nr: &NamedReference,
component: &Rc<Component>,
component_cpp: &str,
) -> String {
access_member(&nr.element(), nr.name(), component, component_cpp)
}
/// Returns the code that can access the component of the given element
fn access_element_component<'a>(
element: &ElementRc,
current_component: &Rc<Component>,
component_cpp: &'a str,
) -> Cow<'a, str> {
let e = element.borrow();
let enclosing_component = e.enclosing_component.upgrade().unwrap();
if Rc::ptr_eq(current_component, &enclosing_component) {
component_cpp.into()
} else {
access_element_component(
element,
&current_component
.parent_element
.upgrade()
.unwrap()
.borrow()
.enclosing_component
.upgrade()
.unwrap(),
&format!("{}->parent", component_cpp),
)
.to_string()
.into()
}
}
fn compile_expression(
expr: &crate::expression_tree::Expression,
component: &Rc<Component>,
) -> String {
match expr {
Expression::StringLiteral(s) => {
format!(r#"sixtyfps::SharedString(u8"{}")"#, escape_string(s.as_str()))
}
Expression::NumberLiteral(n, unit) => {
let num = unit.normalize(*n);
if num > 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_named_reference(nr, component, "self");
format!(r#"{}.get()"#, access)
}
Expression::CallbackReference(nr) => format!(
"{}.call",
access_named_reference(nr, component, "self")
),
Expression::BuiltinFunctionReference(funcref, _) => match funcref {
BuiltinFunction::GetWindowScaleFactor => {
"self->m_window.window_handle().scale_factor".into()
}
BuiltinFunction::Debug => {
"[](auto... args){ (std::cout << ... << args) << std::endl; return nullptr; }"
.into()
}
BuiltinFunction::Mod => "[](auto a1, auto a2){ return static_cast<int>(a1) % static_cast<int>(a2); }".into(),
BuiltinFunction::Round => "std::round".into(),
BuiltinFunction::Ceil => "std::ceil".into(),
BuiltinFunction::Floor => "std::floor".into(),
BuiltinFunction::Sqrt => "std::sqrt".into(),
BuiltinFunction::Abs => "std::abs".into(),
BuiltinFunction::Sin => format!("[](float a){{ return std::sin(a * {}); }}", std::f32::consts::PI / 180.),
BuiltinFunction::Cos => format!("[](float a){{ return std::cos(a * {}); }}", std::f32::consts::PI / 180.),
BuiltinFunction::Tan => format!("[](float a){{ return std::tan(a * {}); }}", std::f32::consts::PI / 180.),
BuiltinFunction::ASin => format!("[](float a){{ return std::asin(a) / {}; }}", std::f32::consts::PI / 180.),
BuiltinFunction::ACos => format!("[](float a){{ return std::acos(a) / {}; }}", std::f32::consts::PI / 180.),
BuiltinFunction::ATan => format!("[](float a){{ return std::atan(a) / {}; }}", std::f32::consts::PI / 180.),
BuiltinFunction::SetFocusItem => {
"self->m_window.window_handle().set_focus_item".into()
}
BuiltinFunction::ShowPopupWindow => {
"self->m_window.window_handle().show_popup".into()
}
/* std::from_chars is unfortunately not yet implemented in gcc
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 => {
"[](const auto &a){ auto e1 = std::end(a); auto e2 = const_cast<char*>(e1); std::strtod(std::begin(a), &e2); return e1 == e2; }"
.into()
}
BuiltinFunction::StringToFloat => {
"[](const auto &a){ auto e1 = std::end(a); auto e2 = const_cast<char*>(e1); auto r = std::strtod(std::begin(a), &e2); return e1 == e2 ? r : 0; }"
.into()
}
BuiltinFunction::ImplicitLayoutInfo(_) => {
unreachable!()
}
BuiltinFunction::ColorBrighter => {
"[](const auto &color, float factor) { return color.brighter(factor); }".into()
}
BuiltinFunction::ColorDarker => {
"[](const auto &color, float factor) { return color.darker(factor); }".into()
}
BuiltinFunction::ImageSize => {
"[](const sixtyfps::Image &img) { return img.size(); }".into()
}
BuiltinFunction::ArrayLength => {
"[](const auto &model) { (*model).track_row_count_changes(); return (*model).row_count(); }".into()
}
BuiltinFunction::Rgb => {
"[](int r, int g, int b, float a) {{ return sixtyfps::Color::from_argb_uint8(std::clamp(a * 255., 0., 255.), std::clamp(r, 0, 255), std::clamp(g, 0, 255), std::clamp(b, 0, 255)); }}".into()
}
BuiltinFunction::RegisterCustomFontByPath => {
panic!("internal error: RegisterCustomFontByPath can only be evaluated from within a FunctionCall expression")
}
BuiltinFunction::RegisterCustomFontByMemory => {
panic!("internal error: RegisterCustomFontByMemory can only be evaluated from within a FunctionCall expression")
}
},
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
Expression::MemberFunction { .. } => panic!("member function expressions must not appear in the code generator anymore"),
Expression::BuiltinMacroReference { .. } => panic!("macro expressions must not appear in the code generator anymore"),
Expression::RepeaterIndexReference { element } => {
let access = access_member(
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
"",
component,
"self",
);
format!(r#"{}index.get()"#, access)
}
Expression::RepeaterModelReference { element } => {
let access = access_member(
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
"",
component,
"self",
);
format!(r#"{}model_data.get()"#, access)
}
Expression::FunctionParameterReference { index, .. } => format!("arg_{}", index),
Expression::StoreLocalVariable { name, value } => {
format!("auto {} = {};", ident(name), compile_expression(value, component))
}
Expression::ReadLocalVariable { name, .. } => ident(name).into_owned(),
Expression::StructFieldAccess { base, name } => match base.ty() {
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, component))
}
Type::Struct{..} => {
format!("{}.{}", compile_expression(base, component), ident(name))
}
_ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
},
Expression::Cast { from, to } => {
let f = compile_expression(&*from, component);
match (from.ty(), to) {
(Type::Float32, Type::String) | (Type::Int32, Type::String) => {
format!("sixtyfps::SharedString::from_number({})", f)
}
(Type::Float32, Type::Model) | (Type::Int32, Type::Model) => {
format!("std::make_shared<sixtyfps::private_api::IntModel>({})", f)
}
(Type::Array(_), Type::Model) => f,
(Type::Float32, Type::Color) => {
format!("sixtyfps::Color::from_argb_encoded({})", f)
}
(Type::Color, Type::Brush) => {
format!("sixtyfps::Brush({})", f)
}
(Type::Brush, Type::Color) => {
format!("{}.color()", f)
}
(Type::Struct { .. }, Type::Struct{ fields, name: Some(_), ..}) => {
format!(
"[&](const auto &o){{ {struct_name} s; auto& [{field_members}] = s; {fields}; return s; }}({obj})",
struct_name = to.cpp_type().unwrap(),
field_members = (0..fields.len()).map(|idx| format!("f_{}", idx)).join(", "),
obj = f,
fields = (0..fields.len())
.map(|idx| format!("f_{} = std::get<{}>(o)", idx, idx))
.join("; ")
)
}
_ => f,
}
}
Expression::CodeBlock(sub) => {
let len = sub.len();
let mut x = sub.iter().enumerate().map(|(i, e)| {
if i == len - 1 {
return_compile_expression(e, component, None) + ";"
}
else {
compile_expression(e, component)
}
});
format!("[&]{{ {} }}()", x.join(";"))
}
Expression::FunctionCall { function, arguments, source_location: _ } => match &**function {
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem call");
}
if let Expression::ElementReference(focus_item) = &arguments[0] {
let focus_item = focus_item.upgrade().unwrap();
let component_ref = access_element_component(&focus_item, component, "self");
let focus_item = focus_item.borrow();
format!("self->m_window.window_handle().set_focus_item({}->self_weak.lock()->into_dyn(), {});", component_ref, focus_item.item_index.get().unwrap())
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::ShowPopupWindow, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem call");
}
if let Expression::ElementReference(popup_window) = &arguments[0] {
let popup_window = popup_window.upgrade().unwrap();
let pop_comp = popup_window.borrow().enclosing_component.upgrade().unwrap();
let popup_window_rcid = component_id(&pop_comp);
let parent_component = pop_comp.parent_element.upgrade().unwrap().borrow().enclosing_component.upgrade().unwrap();
let popup_list = parent_component.popup_windows.borrow();
let popup = popup_list.iter().find(|p| Rc::ptr_eq(&p.component, &pop_comp)).unwrap();
let x = access_named_reference(&popup.x, component, "self");
let y = access_named_reference(&popup.y, component, "self");
let parent_component_ref = access_element_component(&popup.parent_element, component, "self");
format!(
"self->m_window.window_handle().show_popup<{}>(self, {{ {}.get(), {}.get() }}, {{ {}->self_weak.lock()->into_dyn(), {} }} );",
popup_window_rcid, x, y,
parent_component_ref, popup.parent_element.borrow().item_index.get().unwrap(),
)
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::ImplicitLayoutInfo(orientation), _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to ImplicitLayoutInfo call");
}
if let Expression::ElementReference(item) = &arguments[0] {
let item = item.upgrade().unwrap();
let item = item.borrow();
let native_item = item.base_type.as_native();
format!("{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &m_window.window_handle())",
vt = native_item.cpp_vtable_getter,
ty = native_item.class_name,
id = ident(&item.id),
o = to_cpp_orientation(*orientation),
)
} else {
panic!("internal error: argument to ImplicitLayoutInfo must be an element")
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::RegisterCustomFontByPath, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to RegisterCustomFontByPath call");
}
if let Expression::StringLiteral(font_path) = &arguments[0] {
format!("sixtyfps::private_api::register_font_from_path(\"{}\");", escape_string(font_path))
} else {
panic!("internal error: argument to RegisterCustomFontByPath must be a string literal")
}
}
Expression::BuiltinFunctionReference(BuiltinFunction::RegisterCustomFontByMemory, _) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to RegisterCustomFontByMemory call");
}
if let Expression::NumberLiteral(resource_id, _) = &arguments[0] {
let resource_id: usize = *resource_id as _;
let symbol = format!("sfps_embedded_resource_{}", resource_id);
format!("sixtyfps::private_api::register_font_from_data({}, std::size({}));", symbol, symbol)
} else {
panic!("internal error: argument to RegisterCustomFontByMemory must be a number")
}
}
_ => {
let mut args = arguments.iter().map(|e| compile_expression(e, component));
format!("{}({})", compile_expression(function, component), args.join(", "))
}
},
Expression::SelfAssignment { lhs, rhs, op } => {
let rhs = compile_expression(&*rhs, component);
compile_assignment(lhs, *op, rhs, component)
}
Expression::BinaryExpression { lhs, rhs, op } => {
let mut buffer = [0; 3];
format!(
"({lhs} {op} {rhs})",
lhs = compile_expression(&*lhs, component),
rhs = compile_expression(&*rhs, component),
op = match op {
'=' => "==",
'!' => "!=",
'≤' => "<=",
'≥' => ">=",
'&' => "&&",
'|' => "||",
'/' => "/(double)",
_ => op.encode_utf8(&mut buffer),
},
)
}
Expression::UnaryOp { sub, op } => {
format!("({op} {sub})", sub = compile_expression(&*sub, component), op = op,)
}
Expression::ImageReference { resource_ref, .. } => {
match resource_ref {
crate::expression_tree::ImageReference::None => r#"sixtyfps::Image()"#.to_string(),
crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"sixtyfps::Image::load_from_path(sixtyfps::SharedString(u8"{}"))"#, escape_string(path.as_str())),
crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
let symbol = format!("sfps_embedded_resource_{}", resource_id);
format!(
r#"sixtyfps::Image(sixtyfps::cbindgen_private::types::ImageInner::EmbeddedData(sixtyfps::Slice<uint8_t>{{std::data({}), std::size({})}}, sixtyfps::Slice<uint8_t>{{const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(u8"{}")), {}}}))"#,
symbol, symbol, escape_string(extension), extension.as_bytes().len()
)
}
}
}
Expression::Condition { condition, true_expr, false_expr } => {
let ty = expr.ty();
let cond_code = compile_expression(condition, component);
let cond_code = remove_parentheses(&cond_code);
let true_code = return_compile_expression(true_expr, component, Some(&ty));
let false_code = return_compile_expression(false_expr, component, 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 } => {
let ty = element_ty.cpp_type().unwrap_or_else(|| "FIXME: report error".to_owned());
format!(
"std::make_shared<sixtyfps::private_api::ArrayModel<{count},{ty}>>({val})",
count = values.len(),
ty = ty,
val = values
.iter()
.map(|e| format!(
"{ty} ( {expr} )",
expr = compile_expression(e, component),
ty = ty,
))
.join(", ")
)
}
Expression::Struct { ty, values } => {
if let Type::Struct{fields, ..} = ty {
let mut elem = fields.keys().map(|k| {
values
.get(k)
.map(|e| compile_expression(e, component))
.unwrap_or_else(|| "(Error: missing member in object)".to_owned())
});
format!("{}{{{}}}", ty.cpp_type().unwrap(), elem.join(", "))
} else {
panic!("Expression::Object is not a Type::Object")
}
}
Expression::PathElements { elements } => compile_path(elements, component),
Expression::EasingCurve(EasingCurve::Linear) => "sixtyfps::cbindgen_private::EasingCurve()".into(),
Expression::EasingCurve(EasingCurve::CubicBezier(a, b, c, d)) => format!(
"sixtyfps::cbindgen_private::EasingCurve(sixtyfps::cbindgen_private::EasingCurve::Tag::CubicBezier, {}, {}, {}, {})",
a, b, c, d
),
Expression::LinearGradient{angle, stops} => {
let angle = compile_expression(angle, component);
let mut stops_it = stops.iter().map(|(color, stop)| {
let color = compile_expression(color, component);
let position = compile_expression(stop, component);
format!("sixtyfps::private_api::GradientStop{{ {}, {}, }}", color, position)
});
format!(
"[&] {{ const sixtyfps::private_api::GradientStop stops[] = {{ {} }}; return sixtyfps::Brush(sixtyfps::private_api::LinearGradientBrush({}, stops, {})); }}()",
stops_it.join(", "), angle, stops.len()
)
}
Expression::EnumerationValue(value) => {
format!("sixtyfps::cbindgen_private::{}::{}", value.enumeration.name, ident(&value.to_string()))
}
Expression::ReturnStatement(Some(expr)) => format!(
"throw sixtyfps::private_api::ReturnWrapper<{}>({})",
expr.ty().cpp_type().unwrap_or_default(),
compile_expression(expr, component)
),
Expression::ReturnStatement(None) => "throw sixtyfps::private_api::ReturnWrapper<void>()".to_owned(),
Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
let cache = access_named_reference(layout_cache_prop, component, "self");
if let Some(ri) = repeater_index {
format!("sixtyfps::private_api::layout_cache_access({}.get(), {}, {})", cache, index, compile_expression(ri, component))
} else {
format!("{}.get()[{}]", cache, index)
}
}
Expression::ComputeLayoutInfo(Layout::GridLayout(layout), o) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let cells = grid_layout_cell_data(layout, *o, component);
format!("[&] {{ \
const auto padding = {};\
sixtyfps::GridLayoutCellData cells[] = {{ {} }}; \
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}}; \
return sixtyfps::sixtyfps_grid_layout_info(slice, {}, &padding);\
}}()",
padding, cells, spacing
)
}
Expression::ComputeLayoutInfo(Layout::BoxLayout(layout), o) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let (cells, alignment) = box_layout_data(layout, *o, component, None);
let call = if *o == layout.orientation {
format!("sixtyfps_box_layout_info(slice, {}, &padding, {})", spacing, alignment)
} else {
"sixtyfps_box_layout_info_ortho(slice, &padding)".to_string()
};
format!("[&] {{ \
const auto padding = {};\
{}\
const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> slice{{ std::data(cells), std::size(cells)}}; \
return sixtyfps::cbindgen_private::{};\
}}()",
padding, cells, call
)
}
Expression::ComputeLayoutInfo(Layout::PathLayout(_), _) => unimplemented!(),
Expression::SolveLayout(Layout::GridLayout(layout), o) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let cells = grid_layout_cell_data(layout, *o, component);
let size = layout_geometry_size(&layout.geometry.rect, *o, component);
let dialog = if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, *o) {
format!("sixtyfps::cbindgen_private::DialogButtonRole roles[] = {{ {r} }};\
sixtyfps::cbindgen_private::sixtyfps_reorder_dialog_button_layout(cells,\
sixtyfps::Slice<sixtyfps::cbindgen_private::DialogButtonRole>{{ roles, std::size(roles) }});\
",
r = button_roles.iter().map(|r| format!("sixtyfps::cbindgen_private::DialogButtonRole::{}", r)).join(", ")
)
} else { String::new() };
format!("[&] {{\
const auto padding = {p};\
sixtyfps::GridLayoutCellData cells[] = {{ {c} }};\
{dialog}
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}};\
const sixtyfps::GridLayoutData grid {{ {sz}, {s}, &padding, slice }};\
sixtyfps::SharedVector<float> result;\
sixtyfps::sixtyfps_solve_grid_layout(&grid, &result);\
return result;\
}}()",
dialog = dialog, p = padding, c = cells, s = spacing, sz = size
)
}
Expression::SolveLayout(Layout::BoxLayout(layout), o) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let mut repeated_indices = Default::default();
let mut repeated_indices_init = Default::default();
let (cells, alignment) = box_layout_data(layout, *o, component, Some((&mut repeated_indices, &mut repeated_indices_init)));
let size = layout_geometry_size(&layout.geometry.rect, *o, component);
format!("[&] {{ \
{ri_init}\
const auto padding = {p};\
{c}\
const sixtyfps::Slice<sixtyfps::BoxLayoutCellData> slice{{ std::data(cells), std::size(cells)}}; \
sixtyfps::BoxLayoutData box {{ {sz}, {s}, &padding, {a}, slice }};
sixtyfps::SharedVector<float> result;
sixtyfps::sixtyfps_solve_box_layout(&box, {ri}, &result);\
return result;
}}()",
ri_init = repeated_indices_init, ri = repeated_indices,
p = padding, c = cells, s = spacing, sz = size, a = alignment,
)
}
Expression::SolveLayout(Layout::PathLayout(layout), _) => {
let width = layout_geometry_size(&layout.rect, Orientation::Horizontal, component);
let height = layout_geometry_size(&layout.rect, Orientation::Vertical, component);
let elements = compile_path(&layout.path, component);
let prop = |expr: &Option<NamedReference>| {
if let Some(nr) = expr.as_ref() {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
// FIXME! repeater
format!("[&] {{ \
const auto elements = {e};\
sixtyfps::PathLayoutData path {{ &elements, {c}, 0, 0, {w}, {h}, {o} }};
sixtyfps::SharedVector<float> result;
sixtyfps::sixtyfps_solve_path_layout(&path, {{}}, &result);\
return result;
}}()",
e = elements, c = layout.elements.len(), w = width, h = height, o = prop(&layout.offset_reference)
)
}
Expression::Uncompiled(_) => panic!(),
Expression::Invalid => "\n#error invalid expression\n".to_string(),
}
}
fn compile_assignment(
lhs: &Expression,
op: char,
rhs: String,
component: &Rc<Component>,
) -> String {
match lhs {
Expression::PropertyReference(nr) => {
let access = access_named_reference(nr, component, "self");
let set = property_set_value_code(
component,
&nr.element(),
nr.name(),
&(if op == '=' { rhs } else { format!("{}.get() {} {}", access, op, rhs) }),
);
format!("{}.{}", access, set)
}
Expression::StructFieldAccess { base, name } => {
let tmpobj = "tmpobj";
let get_obj = compile_expression(base, component);
let ty = base.ty();
let member = match &ty {
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, tmpobj)
}
Type::Struct { .. } => format!("{}.{}", tmpobj, ident(name)),
_ => panic!("Expression::ObjectAccess's base expression is not an Object type"),
};
let op = if op == '=' { ' ' } else { op };
let new_value = format!(
"[&]{{ auto {tmp} = {get}; {member} {op}= {rhs}; return {tmp}; }}()",
tmp = tmpobj,
get = get_obj,
member = member,
op = op,
rhs = rhs,
);
compile_assignment(base, '=', new_value, component)
}
Expression::RepeaterModelReference { element } => {
let element = element.upgrade().unwrap();
let parent_component = element.borrow().base_type.as_component().clone();
let repeater_access = access_member(
&parent_component
.parent_element
.upgrade()
.unwrap()
.borrow()
.enclosing_component
.upgrade()
.unwrap()
.root_element,
"",
component,
"self",
);
let index_access = access_member(&parent_component.root_element, "", component, "self");
let repeater_id = format!("repeater_{}", ident(&element.borrow().id));
if op == '=' {
format!(
"{}{}.model_set_row_data({}index.get(), {})",
repeater_access, repeater_id, index_access, rhs
)
} else {
format!(
"{}{}.model_set_row_data({}index.get(), {} {} {})",
repeater_access,
repeater_id,
index_access,
rhs,
op,
compile_expression(lhs, component)
)
}
}
_ => panic!("typechecking should make sure this was a PropertyReference"),
}
}
fn grid_layout_cell_data(
layout: &crate::layout::GridLayout,
orientation: Orientation,
component: &Rc<Component>,
) -> String {
layout
.elems
.iter()
.map(|c| {
let (col_or_row, span) = c.col_or_row_and_span(orientation);
format!(
"sixtyfps::GridLayoutCellData {{ {}, {}, {} }}",
col_or_row,
span,
get_layout_info(&c.item.element, component, &c.item.constraints, orientation),
)
})
.join(", ")
}
/// Returns `(cells, alignment)`.
/// The repeated_indices initialize the repeated_indices (var, init_code)
fn box_layout_data(
layout: &crate::layout::BoxLayout,
orientation: Orientation,
component: &Rc<Component>,
mut repeated_indices: Option<(&mut String, &mut String)>,
) -> (String, String) {
let alignment = if let Some(nr) = &layout.geometry.alignment {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"{}".into()
};
let repeater_count =
layout.elems.iter().filter(|i| i.element.borrow().repeated.is_some()).count();
if repeater_count == 0 {
let mut cells = layout.elems.iter().map(|li| {
format!(
"sixtyfps::BoxLayoutCellData{{ {} }}",
get_layout_info(&li.element, component, &li.constraints, orientation)
)
});
if let Some((ri, _)) = &mut repeated_indices {
**ri = "{}".into();
}
(format!("sixtyfps::BoxLayoutCellData cells[] = {{ {} }};", cells.join(", ")), alignment)
} else {
let mut push_code = "std::vector<sixtyfps::BoxLayoutCellData> cells;".to_owned();
if let Some((ri, init)) = &mut repeated_indices {
**ri =
"sixtyfps::Slice<unsigned int>{std::data(repeater_indices), std::size(repeater_indices)}"
.to_owned();
**init = format!("std::array<unsigned int, {}> repeater_indices;", repeater_count * 2);
}
let mut repeater_idx = 0usize;
for item in &layout.elems {
if item.element.borrow().repeated.is_some() {
push_code += &format!(
"self->repeater_{}.ensure_updated(self);",
ident(&item.element.borrow().id)
);
if repeated_indices.is_some() {
push_code += &format!("repeater_indices[{}] = cells.size();", repeater_idx * 2);
push_code += &format!(
"repeater_indices[{c}] = self->repeater_{id}.inner ? self->repeater_{id}.inner->data.size() : 0;",
c = repeater_idx * 2 + 1,
id = ident(&item.element.borrow().id)
);
}
repeater_idx += 1;
push_code += &format!(
"if (self->repeater_{id}.inner) \
for (auto &&sub_comp : self->repeater_{id}.inner->data) \
cells.push_back((*sub_comp.ptr)->box_layout_data({o}));",
id = ident(&item.element.borrow().id),
o = to_cpp_orientation(orientation),
);
} else {
push_code += &format!(
"cells.push_back({{ {} }});",
get_layout_info(&item.element, component, &item.constraints, orientation)
);
}
}
(push_code, alignment)
}
}
fn generate_layout_padding_and_spacing(
layout_geometry: &LayoutGeometry,
orientation: Orientation,
component: &Rc<Component>,
) -> (String, String) {
let prop = |expr: Option<&NamedReference>| {
if let Some(nr) = expr {
format!("{}.get()", access_named_reference(nr, component, "self"))
} else {
"0.".into()
}
};
let spacing = prop(layout_geometry.spacing.as_ref());
let (begin, end) = layout_geometry.padding.begin_end(orientation);
let padding = format!("sixtyfps::Padding {{ {}, {} }};", prop(begin), prop(end));
(padding, spacing)
}
fn layout_geometry_size(
rect: &LayoutRect,
orientation: Orientation,
component: &Rc<Component>,
) -> String {
rect.size_reference(orientation).map_or_else(
|| "0.".into(),
|nr| format!("{}.get()", access_named_reference(nr, component, "self")),
)
}
fn get_layout_info(
elem: &ElementRc,
component: &Rc<Component>,
constraints: &crate::layout::LayoutConstraints,
orientation: Orientation,
) -> String {
let mut layout_info = if let Some(layout_info_prop) =
&elem.borrow().layout_info_prop(orientation)
{
format!("{}.get()", access_named_reference(layout_info_prop, component, "self"))
} else if let Type::Component(_sub_component) = &elem.borrow().base_type {
format!(
"self->{id}.layouting_info({o}, &self->m_window.window_handle())",
id = ident(&elem.borrow().id),
o = to_cpp_orientation(orientation),
)
} else {
format!(
"{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &self->m_window.window_handle())",
vt = elem.borrow().base_type.as_native().cpp_vtable_getter,
ty = elem.borrow().base_type.as_native().class_name,
id = ident(&elem.borrow().id),
o = to_cpp_orientation(orientation),
)
};
if constraints.has_explicit_restrictions() {
layout_info = format!("[&]{{ auto layout_info = {};", layout_info);
for (expr, name) in constraints.for_each_restrictions(orientation) {
layout_info += &format!(
" layout_info.{} = {}.get();",
name,
access_named_reference(expr, component, "self")
);
}
layout_info += " return layout_info; }()";
}
layout_info
}
fn compile_path(path: &crate::expression_tree::Path, component: &Rc<Component>) -> String {
match path {
crate::expression_tree::Path::Elements(elements) => {
let converted_elements: Vec<String> = elements
.iter()
.map(|element| {
let element_initializer = element
.element_type
.native_class
.cpp_type
.as_ref()
.map(|cpp_type| {
new_struct_with_bindings(cpp_type, &element.bindings, component)
})
.unwrap_or_default();
format!(
"sixtyfps::private_api::PathElement::{}({})",
element.element_type.native_class.class_name, element_initializer
)
})
.collect();
format!(
r#"[&](){{
sixtyfps::private_api::PathElement elements[{}] = {{
{}
}};
return sixtyfps::private_api::PathData(&elements[0], std::size(elements));
}}()"#,
converted_elements.len(),
converted_elements.join(",")
)
}
crate::expression_tree::Path::Events(events) => {
let (converted_events, converted_coordinates) = compile_path_events(events);
format!(
r#"[&](){{
sixtyfps::private_api::PathEvent events[{}] = {{
{}
}};
sixtyfps::private_api::Point coordinates[{}] = {{
{}
}};
return sixtyfps::private_api::PathData(&events[0], std::size(events), &coordinates[0], std::size(coordinates));
}}()"#,
converted_events.len(),
converted_events.join(","),
converted_coordinates.len(),
converted_coordinates.join(",")
)
}
}
}
fn compile_path_events(events: &[crate::expression_tree::PathEvent]) -> (Vec<String>, Vec<String>) {
use lyon_path::Event;
let mut coordinates = Vec::new();
let events = events
.iter()
.map(|event| match event {
Event::Begin { at } => {
coordinates.push(at);
"sixtyfps::private_api::PathEvent::Begin"
}
Event::Line { from, to } => {
coordinates.push(from);
coordinates.push(to);
"sixtyfps::private_api::PathEvent::Line"
}
Event::Quadratic { from, ctrl, to } => {
coordinates.push(from);
coordinates.push(ctrl);
coordinates.push(to);
"sixtyfps::private_api::PathEvent::Quadratic"
}
Event::Cubic { from, ctrl1, ctrl2, to } => {
coordinates.push(from);
coordinates.push(ctrl1);
coordinates.push(ctrl2);
coordinates.push(to);
"sixtyfps::private_api::PathEvent::Cubic"
}
Event::End { close, .. } => {
if *close {
"sixtyfps::private_api::PathEvent::EndClosed"
} else {
"sixtyfps::private_api::PathEvent::EndOpen"
}
}
})
.map(String::from)
.collect();
let coordinates = coordinates
.into_iter()
.map(|pt| format!("sixtyfps::private_api::Point{{{}, {}}}", pt.x, pt.y))
.collect();
(events, coordinates)
}
/// Like compile_expression, but wrap inside a try{}catch{} block to intercept the return
fn compile_expression_wrap_return(expr: &Expression, component: &Rc<Component>) -> String {
/// Return a type if there is any `return` in sub expressions
fn return_type(expr: &Expression) -> Option<Type> {
if let Expression::ReturnStatement(val) = expr {
return Some(val.as_ref().map_or(Type::Void, |v| v.ty()));
}
let mut ret = None;
expr.visit(|e| {
if ret.is_none() {
ret = return_type(e)
}
});
ret
}
if let Some(ty) = return_type(expr) {
if ty == Type::Void || ty == Type::Invalid {
format!(
"[&]{{ try {{ {}; }} catch(const sixtyfps::private_api::ReturnWrapper<void> &w) {{ }} }}()",
compile_expression(expr, component)
)
} else {
let cpp_ty = ty.cpp_type().unwrap_or_default();
format!(
"[&]() -> {} {{ try {{ {}; }} catch(const sixtyfps::private_api::ReturnWrapper<{}> &w) {{ return w.value; }} }}()",
cpp_ty,
return_compile_expression(expr, component, Some(&ty)),
cpp_ty
)
}
} else {
compile_expression(expr, component)
}
}
/// 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: &Expression,
component: &Rc<Component>,
ret_type: Option<&Type>,
) -> String {
let e = compile_expression(expr, component);
if ret_type == Some(&Type::Void) || ret_type == Some(&Type::Invalid) {
e
} else {
let ty = expr.ty();
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)
}
}
}