mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-29 05:14:48 +00:00

The Image's source property used to be a string. Now it is a Resource enum, which can either be None or an absolute file path to the image on disk. This also replaces the internal Image type. The compiler internally resolves the img bang expression to a resource reference, which shall remain just an absolute path. For now the target generator passes that through, but in the future the target generator may choose a target specific way of embedding the data and thus generating a different Resource type in the final code (through compile_expression in the cpp and rust generator). The C++ binding is a bit messy as cbindgen doesn't really support exporting enums that can be constructed on the C++ side. So instead we use cbindgen to merely export the type internally and only use the tag from it then. The public API is then a custom Resource type that is meant to be binary compatible.
363 lines
12 KiB
Rust
363 lines
12 KiB
Rust
/*! module for the C++ code generator
|
|
*/
|
|
|
|
/// This module contains some datastructure 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 INDETATION : Cell<u32> = Cell::new(0));
|
|
fn indent(f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
INDETATION.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>,
|
|
}
|
|
|
|
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)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Declarations (top level, or within a struct)
|
|
#[derive(Debug, derive_more::Display)]
|
|
pub enum Declaration {
|
|
Struct(Struct),
|
|
Function(Function),
|
|
Var(Var),
|
|
}
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct Struct {
|
|
pub name: String,
|
|
pub members: Vec<Declaration>,
|
|
}
|
|
|
|
impl Display for Struct {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
indent(f)?;
|
|
writeln!(f, "struct {} {{", self.name)?;
|
|
INDETATION.with(|x| x.set(x.get() + 1));
|
|
for m in &self.members {
|
|
// FIXME! identation
|
|
write!(f, "{}", m)?;
|
|
}
|
|
INDETATION.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: bool,
|
|
pub is_static: bool,
|
|
/// The list of statement instead the function. When None, this is just a function
|
|
/// declaration without the definition
|
|
pub statements: Option<Vec<String>>,
|
|
}
|
|
|
|
impl Display for Function {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
|
indent(f)?;
|
|
if self.is_static {
|
|
write!(f, "static ")?;
|
|
}
|
|
if !self.is_constructor {
|
|
write!(f, "auto ")?;
|
|
}
|
|
write!(f, "{} {}", self.name, self.signature)?;
|
|
if let Some(st) = &self.statements {
|
|
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 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(i) = &self.init {
|
|
write!(f, " = {}", i)?;
|
|
}
|
|
writeln!(f, ";")
|
|
}
|
|
}
|
|
|
|
pub trait CppType {
|
|
fn cpp_type(&self) -> Result<&str, crate::diagnostics::CompilerDiagnostic>;
|
|
}
|
|
}
|
|
|
|
use crate::diagnostics::{CompilerDiagnostic, Diagnostics};
|
|
use crate::object_tree::{Component, Element, PropertyDeclaration};
|
|
use crate::typeregister::Type;
|
|
use cpp_ast::*;
|
|
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
impl CppType for PropertyDeclaration {
|
|
fn cpp_type(&self) -> Result<&str, CompilerDiagnostic> {
|
|
match self.property_type {
|
|
Type::Float32 => Ok("float"),
|
|
Type::Int32 => Ok("int"),
|
|
Type::String => Ok("sixtyfps::SharedString"),
|
|
Type::Color => Ok("uint32_t"),
|
|
Type::Bool => Ok("bool"),
|
|
_ => Err(CompilerDiagnostic {
|
|
message: "Cannot map property type to C++".into(),
|
|
span: self.type_location.clone(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_item(item: &Element, main_struct: &mut Struct, init: &mut Vec<String>) {
|
|
main_struct.members.push(Declaration::Var(Var {
|
|
ty: format!("sixtyfps::{}", item.base_type.as_builtin().class_name),
|
|
name: item.id.clone(),
|
|
..Default::default()
|
|
}));
|
|
|
|
let id = &item.id;
|
|
init.extend(item.bindings.iter().map(|(s, i)| {
|
|
if matches!(item.lookup_property(s.as_str()), Type::Signal) {
|
|
let signal_accessor_prefix = if item.property_declarations.contains_key(s) {
|
|
String::new()
|
|
} else {
|
|
format!("{id}.", id = id.clone())
|
|
};
|
|
|
|
format!(
|
|
"{signal_accessor_prefix}{prop}.set_handler(
|
|
[](const sixtyfps::EvaluationContext *context) {{
|
|
auto self = reinterpret_cast<const {ty}*>(context->component.instance);
|
|
{code};
|
|
}});",
|
|
signal_accessor_prefix = signal_accessor_prefix,
|
|
prop = s,
|
|
ty = main_struct.name,
|
|
code = compile_expression(i)
|
|
)
|
|
} else {
|
|
let accessor_prefix = if item.property_declarations.contains_key(s) {
|
|
String::new()
|
|
} else {
|
|
format!("{id}.", id = id.clone())
|
|
};
|
|
|
|
let init = compile_expression(i);
|
|
if i.is_constant() {
|
|
format!(
|
|
"{accessor_prefix}{cpp_prop}.set({init});",
|
|
accessor_prefix = accessor_prefix,
|
|
cpp_prop = s,
|
|
init = init
|
|
)
|
|
} else {
|
|
format!(
|
|
"{accessor_prefix}{cpp_prop}.set_binding(
|
|
[](const sixtyfps::EvaluationContext *context) {{
|
|
auto self = reinterpret_cast<const {ty}*>(context->component.instance);
|
|
return {init};
|
|
}}
|
|
);",
|
|
accessor_prefix = accessor_prefix,
|
|
cpp_prop = s,
|
|
ty = main_struct.name,
|
|
init = init
|
|
)
|
|
}
|
|
}
|
|
}));
|
|
|
|
for i in &item.children {
|
|
handle_item(&i.borrow(), main_struct, init)
|
|
}
|
|
}
|
|
|
|
/// Returns the text of the C++ code produced by the given root component
|
|
pub fn generate(component: &Component, diag: &mut Diagnostics) -> Option<impl std::fmt::Display> {
|
|
let mut x = File::default();
|
|
|
|
x.includes.push("<sixtyfps.h>".into());
|
|
|
|
let mut main_struct = Struct { name: component.id.clone(), ..Default::default() };
|
|
|
|
main_struct.members.extend(component.root_element.borrow().property_declarations.iter().map(
|
|
|(cpp_name, property_decl)| {
|
|
let ty = if property_decl.property_type == Type::Signal {
|
|
"sixtyfps::Signal".into()
|
|
} else {
|
|
let cpp_type = property_decl.cpp_type().unwrap_or_else(|err| {
|
|
diag.push_compiler_error(err);
|
|
"".into()
|
|
});
|
|
format!("sixtyfps::Property<{}>", cpp_type)
|
|
};
|
|
Declaration::Var(Var { ty, name: cpp_name.clone(), init: None })
|
|
},
|
|
));
|
|
|
|
let mut init = vec!["auto self = this;".into()];
|
|
handle_item(&component.root_element.borrow(), &mut main_struct, &mut init);
|
|
|
|
main_struct.members.push(Declaration::Function(Function {
|
|
name: component.id.clone(),
|
|
signature: "()".to_owned(),
|
|
is_constructor: true,
|
|
statements: Some(init),
|
|
..Default::default()
|
|
}));
|
|
|
|
main_struct.members.push(Declaration::Function(Function {
|
|
name: "tree_fn".into(),
|
|
signature: "(sixtyfps::ComponentRef) -> const sixtyfps::ItemTreeNode* ".into(),
|
|
is_static: true,
|
|
..Default::default()
|
|
}));
|
|
|
|
main_struct.members.push(Declaration::Var(Var {
|
|
ty: "static const sixtyfps::ComponentVTable".to_owned(),
|
|
name: "component_type".to_owned(),
|
|
init: None,
|
|
}));
|
|
|
|
x.declarations.push(Declaration::Struct(main_struct));
|
|
|
|
let mut tree_array = String::new();
|
|
super::build_array_helper(component, |item, children_offset| {
|
|
let item = item.borrow();
|
|
tree_array = format!(
|
|
"{}{}sixtyfps::make_item_node(offsetof({}, {}), &sixtyfps::{}, {}, {})",
|
|
tree_array,
|
|
if tree_array.is_empty() { "" } else { ", " },
|
|
&component.id,
|
|
item.id,
|
|
item.base_type.as_builtin().vtable_symbol,
|
|
item.children.len(),
|
|
children_offset,
|
|
)
|
|
});
|
|
|
|
x.declarations.push(Declaration::Function(Function {
|
|
name: format!("{}::tree_fn", component.id),
|
|
signature: "(sixtyfps::ComponentRef) -> const sixtyfps::ItemTreeNode* ".into(),
|
|
statements: Some(vec![
|
|
"static const sixtyfps::ItemTreeNode children[] {".to_owned(),
|
|
format!(" {} }};", tree_array),
|
|
"return children;".to_owned(),
|
|
]),
|
|
..Default::default()
|
|
}));
|
|
|
|
x.declarations.push(Declaration::Var(Var {
|
|
ty: "const sixtyfps::ComponentVTable".to_owned(),
|
|
name: format!("{}::component_type", component.id),
|
|
init: Some("{ nullptr, sixtyfps::dummy_destory, tree_fn }".to_owned()),
|
|
}));
|
|
|
|
if diag.has_error() {
|
|
None
|
|
} else {
|
|
Some(x)
|
|
}
|
|
}
|
|
|
|
fn access_member(element: &Rc<RefCell<Element>>, name: &str) -> String {
|
|
let e = element.borrow();
|
|
if e.property_declarations.contains_key(name) {
|
|
name.into()
|
|
} else {
|
|
format!("{}.{}", e.id.as_str(), name)
|
|
}
|
|
}
|
|
|
|
fn compile_expression(e: &crate::expression_tree::Expression) -> String {
|
|
use crate::expression_tree::Expression::*;
|
|
match e {
|
|
StringLiteral(s) => format!(r#"sixtyfps::SharedString("{}")"#, s.escape_default()),
|
|
NumberLiteral(n) => n.to_string(),
|
|
PropertyReference { element, name, .. } => format!(
|
|
r#"self->{}.get(context)"#,
|
|
access_member(&element.upgrade().unwrap(), name.as_str())
|
|
),
|
|
SignalReference { element, name, .. } => {
|
|
format!(r#"self->{}"#, access_member(&element.upgrade().unwrap(), name.as_str()))
|
|
}
|
|
Cast { from, to } => {
|
|
let f = compile_expression(&*from);
|
|
match (from.ty(), to) {
|
|
(Type::Float32, Type::String) | (Type::Int32, Type::String) => {
|
|
format!("sixtyfps::SharedString::from_number({})", f)
|
|
}
|
|
_ => f,
|
|
}
|
|
}
|
|
CodeBlock(sub) => {
|
|
let mut x = sub.iter().map(|e| compile_expression(e)).collect::<Vec<_>>();
|
|
x.last_mut().map(|s| *s = format!("return {};", s));
|
|
|
|
format!("[&]{{ {} }}()", x.join(";"))
|
|
}
|
|
FunctionCall { function } => {
|
|
if matches!(function.ty(), Type::Signal) {
|
|
format!("{}.emit(context)", compile_expression(&*function))
|
|
} else {
|
|
format!("\n#error the function `{:?}` is not a signal\n", function)
|
|
}
|
|
}
|
|
SelfAssignement { lhs, rhs, op } => match &**lhs {
|
|
PropertyReference { element, name, .. } => format!(
|
|
r#"self->{lhs}.set(self->{lhs}.get(context) {op} {rhs})"#,
|
|
lhs = access_member(&element.upgrade().unwrap(), name.as_str()),
|
|
rhs = compile_expression(&*rhs),
|
|
op = op
|
|
),
|
|
_ => panic!("typechecking should make sure this was a PropertyReference"),
|
|
},
|
|
ResourceReference { absolute_source_path } => {
|
|
format!(r#"sixtyfps::Resource(sixtyfps::SharedString("{}"))"#, absolute_source_path)
|
|
}
|
|
Uncompiled(_) => panic!(),
|
|
Invalid => format!("\n#error invalid expression\n"),
|
|
}
|
|
}
|