mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-02 18:03:07 +00:00
parent
bef2e3617d
commit
3a4f3c61d5
17 changed files with 196 additions and 16 deletions
|
@ -138,6 +138,8 @@ log = { version = "0.4.17", optional = true }
|
|||
[dev-dependencies]
|
||||
slint-build = { path = "../build" }
|
||||
i-slint-backend-testing = { version = "=1.0.3", path = "../../../internal/backends/testing" }
|
||||
serde_json = "1.0.96"
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = [
|
||||
|
|
|
@ -208,6 +208,26 @@ struct MyStruct {
|
|||
}
|
||||
```
|
||||
|
||||
The `.slint` file allows you to utilize Rust attributes and features for defining structures using the `@rust-attr()` directive.
|
||||
This enables you to customize the generated code by applying additional traits, derivations, or annotations.
|
||||
Consider the following structure defined in the `.slint` file with Rust attributes:
|
||||
```slint,ignore
|
||||
@rust-attr(derive(serde::Serialize, serde::Deserialize))
|
||||
struct MyStruct {
|
||||
foo : i32,
|
||||
}
|
||||
```
|
||||
|
||||
Based on this structure, the following Rust code would be generated:
|
||||
|
||||
```rust
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, Clone, Debug, PartialEq)]
|
||||
struct MyStruct {
|
||||
foo : i32,
|
||||
}
|
||||
```
|
||||
|
||||
## Exported Global singletons
|
||||
|
||||
*/
|
||||
|
|
|
@ -15,3 +15,19 @@ fn empty_stuff() {
|
|||
slint!(export struct Hei { abcd: bool });
|
||||
slint!(export global G { });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_deserialize_struct() {
|
||||
i_slint_backend_testing::init();
|
||||
slint! {
|
||||
@rust-attr(derive(serde::Serialize, serde::Deserialize))
|
||||
export struct TestStruct {
|
||||
foo: int,
|
||||
}
|
||||
export component Test { }
|
||||
}
|
||||
let data = TestStruct { foo: 1 };
|
||||
let serialized = serde_json::to_string(&data).unwrap();
|
||||
let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
|
||||
assert_eq!(data, deserialized);
|
||||
}
|
||||
|
|
|
@ -173,6 +173,7 @@ impl BuiltinFunction {
|
|||
.collect(),
|
||||
name: Some("Size".to_string()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
}),
|
||||
args: vec![Type::Image],
|
||||
},
|
||||
|
@ -1027,7 +1028,7 @@ impl Expression {
|
|||
},
|
||||
(
|
||||
Type::Struct { fields: ref left, .. },
|
||||
Type::Struct { fields: right, name, node: n },
|
||||
Type::Struct { fields: right, name, node: n, rust_attributes },
|
||||
) if left != right => {
|
||||
if let Expression::Struct { mut values, .. } = self {
|
||||
let mut new_values = HashMap::new();
|
||||
|
@ -1051,6 +1052,7 @@ impl Expression {
|
|||
fields: left.clone(),
|
||||
name: name.clone(),
|
||||
node: n.clone(),
|
||||
rust_attributes: rust_attributes.clone(),
|
||||
},
|
||||
}),
|
||||
name: key.clone(),
|
||||
|
|
|
@ -579,7 +579,7 @@ pub fn generate(doc: &Document) -> impl std::fmt::Display {
|
|||
}
|
||||
|
||||
for ty in doc.root_component.used_types.borrow().structs.iter() {
|
||||
if let Type::Struct { fields, name: Some(name), node: Some(node) } = ty {
|
||||
if let Type::Struct { fields, name: Some(name), node: Some(node), .. } = ty {
|
||||
generate_struct(&mut file, name, fields, node);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ use proc_macro2::{Ident, TokenStream};
|
|||
use quote::{format_ident, quote};
|
||||
use std::collections::BTreeMap;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::str::FromStr;
|
||||
|
||||
type EvaluationContext<'a> = llr_EvaluationContext<'a, TokenStream>;
|
||||
type ParentCtx<'a> = llr_ParentCtx<'a, TokenStream>;
|
||||
|
@ -139,8 +140,8 @@ pub fn generate(doc: &Document) -> TokenStream {
|
|||
.structs
|
||||
.iter()
|
||||
.filter_map(|ty| {
|
||||
if let Type::Struct { fields, name: Some(name), node: Some(_) } = ty {
|
||||
Some((ident(name), generate_struct(name, fields)))
|
||||
if let Type::Struct { fields, name: Some(name), node: Some(_), rust_attributes } = ty {
|
||||
Some((ident(name), generate_struct(name, fields, rust_attributes)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -422,12 +423,28 @@ fn generate_public_component(llr: &llr::PublicComponent) -> TokenStream {
|
|||
)
|
||||
}
|
||||
|
||||
fn generate_struct(name: &str, fields: &BTreeMap<String, Type>) -> TokenStream {
|
||||
fn generate_struct(
|
||||
name: &str,
|
||||
fields: &BTreeMap<String, Type>,
|
||||
rust_attributes: &Option<Vec<String>>,
|
||||
) -> TokenStream {
|
||||
let component_id = struct_name_to_tokens(name);
|
||||
let (declared_property_vars, declared_property_types): (Vec<_>, Vec<_>) =
|
||||
fields.iter().map(|(name, ty)| (ident(name), rust_primitive_type(ty).unwrap())).unzip();
|
||||
|
||||
let attributes = if let Some(feature) = rust_attributes {
|
||||
let attr =
|
||||
feature.iter().map(|f| match TokenStream::from_str(format!(r#"#[{}]"#, f).as_str()) {
|
||||
Ok(eval) => eval,
|
||||
Err(_) => quote! {},
|
||||
});
|
||||
quote! { #(#attr)* }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
quote! {
|
||||
#attributes
|
||||
#[derive(Default, PartialEq, Debug, Clone)]
|
||||
pub struct #component_id {
|
||||
#(pub #declared_property_vars : #declared_property_types),*
|
||||
|
|
|
@ -60,6 +60,8 @@ pub enum Type {
|
|||
name: Option<String>,
|
||||
/// When declared in .slint, this is the node of the declaration.
|
||||
node: Option<syntax_nodes::ObjectType>,
|
||||
/// deriven
|
||||
rust_attributes: Option<Vec<String>>,
|
||||
},
|
||||
Enumeration(Rc<Enumeration>),
|
||||
|
||||
|
@ -104,8 +106,8 @@ impl core::cmp::PartialEq for Type {
|
|||
Type::Easing => matches!(other, Type::Easing),
|
||||
Type::Brush => matches!(other, Type::Brush),
|
||||
Type::Array(a) => matches!(other, Type::Array(b) if a == b),
|
||||
Type::Struct { fields, name, node: _ } => {
|
||||
matches!(other, Type::Struct{fields: f, name: n, node: _} if fields == f && name == n)
|
||||
Type::Struct { fields, name, node: _, rust_attributes: _ } => {
|
||||
matches!(other, Type::Struct{fields:f,name:n,node:_, rust_attributes: _ } if fields == f && name == n)
|
||||
}
|
||||
Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
|
||||
Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
|
||||
|
|
|
@ -460,6 +460,7 @@ pub fn layout_info_type() -> Type {
|
|||
.collect(),
|
||||
name: Some("LayoutInfo".into()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -398,6 +398,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
|
|||
fields: animation_fields().collect(),
|
||||
name: Some("PropertyAnimation".into()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,6 +436,7 @@ pub fn lower_animation(a: &PropertyAnimation, ctx: &ExpressionContext<'_>) -> An
|
|||
.collect(),
|
||||
name: None,
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
},
|
||||
values: IntoIterator::into_iter([
|
||||
("0".to_string(), get_anim),
|
||||
|
@ -661,6 +663,7 @@ fn box_layout_data(
|
|||
.collect(),
|
||||
name: Some("BoxLayoutCellData".into()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
};
|
||||
|
||||
if repeater_count == 0 {
|
||||
|
@ -747,6 +750,7 @@ pub(super) fn grid_layout_cell_data_ty() -> Type {
|
|||
.collect(),
|
||||
name: Some("GridLayoutCellData".into()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -843,6 +847,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
|
|||
fields: Default::default(),
|
||||
name: Some("PathElement".to_owned()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
},
|
||||
values: elements,
|
||||
as_model: false,
|
||||
|
@ -866,6 +871,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
|
|||
.collect(),
|
||||
name: element.element_type.native_class.cpp_type.clone(),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
};
|
||||
|
||||
llr_Expression::Struct {
|
||||
|
@ -917,6 +923,7 @@ fn compile_path(path: &crate::expression_tree::Path, ctx: &ExpressionContext) ->
|
|||
.collect(),
|
||||
name: None,
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
},
|
||||
values: IntoIterator::into_iter([
|
||||
(
|
||||
|
@ -960,5 +967,8 @@ fn make_struct(
|
|||
values.insert(name.to_string(), expr);
|
||||
}
|
||||
|
||||
llr_Expression::Struct { ty: Type::Struct { fields, name: Some(name), node: None }, values }
|
||||
llr_Expression::Struct {
|
||||
ty: Type::Struct { fields, name: Some(name), node: None, rust_attributes: None },
|
||||
values,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ pub fn load_builtins(register: &mut TypeRegister) {
|
|||
// parse structs
|
||||
for s in doc.StructDeclaration().chain(doc.ExportsList().flat_map(|e| e.StructDeclaration())) {
|
||||
let external_name = identifier_text(&s.DeclaredIdentifier()).unwrap();
|
||||
let mut ty = object_tree::type_struct_from_node(s.ObjectType(), &mut diag, register);
|
||||
let mut ty = object_tree::type_struct_from_node(s.ObjectType(), &mut diag, register, None);
|
||||
if let Type::Struct { name, .. } = &mut ty {
|
||||
*name = Some(
|
||||
parse_annotation("name", &s.ObjectType())
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
// cSpell: ignore qualname
|
||||
|
||||
use itertools::Either;
|
||||
use itertools::{Either, Itertools};
|
||||
|
||||
use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
|
||||
use crate::expression_tree::{self, BindingExpression, Expression, Unit};
|
||||
|
@ -77,7 +77,21 @@ impl Document {
|
|||
|n: syntax_nodes::StructDeclaration,
|
||||
diag: &mut BuildDiagnostics,
|
||||
local_registry: &mut TypeRegister| {
|
||||
let mut ty = type_struct_from_node(n.ObjectType(), diag, local_registry);
|
||||
let rust_attributes: Vec<String> = n
|
||||
.children()
|
||||
.filter(|child| child.kind() == SyntaxKind::AtRustAttr)
|
||||
.map(|child| {
|
||||
let mut text = child.text().to_string();
|
||||
text.pop();
|
||||
text
|
||||
})
|
||||
.collect_vec();
|
||||
let mut ty = type_struct_from_node(
|
||||
n.ObjectType(),
|
||||
diag,
|
||||
local_registry,
|
||||
Some(rust_attributes),
|
||||
);
|
||||
if let Type::Struct { name, .. } = &mut ty {
|
||||
*name = parser::identifier_text(&n.DeclaredIdentifier());
|
||||
} else {
|
||||
|
@ -1630,7 +1644,7 @@ pub fn type_from_node(
|
|||
}
|
||||
prop_type
|
||||
} else if let Some(object_node) = node.ObjectType() {
|
||||
type_struct_from_node(object_node, diag, tr)
|
||||
type_struct_from_node(object_node, diag, tr, None)
|
||||
} else if let Some(array_node) = node.ArrayType() {
|
||||
Type::Array(Box::new(type_from_node(array_node.Type(), diag, tr)))
|
||||
} else {
|
||||
|
@ -1644,6 +1658,7 @@ pub fn type_struct_from_node(
|
|||
object_node: syntax_nodes::ObjectType,
|
||||
diag: &mut BuildDiagnostics,
|
||||
tr: &TypeRegister,
|
||||
rust_attributes: Option<Vec<String>>,
|
||||
) -> Type {
|
||||
let fields = object_node
|
||||
.ObjectTypeMember()
|
||||
|
@ -1654,7 +1669,7 @@ pub fn type_struct_from_node(
|
|||
)
|
||||
})
|
||||
.collect();
|
||||
Type::Struct { fields, name: None, node: Some(object_node) }
|
||||
Type::Struct { fields, name: None, node: Some(object_node), rust_attributes }
|
||||
}
|
||||
|
||||
fn animation_element_from_node(
|
||||
|
|
|
@ -422,8 +422,9 @@ declare_syntax! {
|
|||
/// `[ type ]`
|
||||
ArrayType -> [ Type ],
|
||||
/// `struct Foo := { ... }
|
||||
StructDeclaration -> [DeclaredIdentifier, ObjectType],
|
||||
|
||||
StructDeclaration -> [DeclaredIdentifier, ObjectType, ?AtRustAttr],
|
||||
/// `@rust-attr(...)`
|
||||
AtRustAttr -> [],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
use super::element::{parse_element, parse_element_content};
|
||||
use super::prelude::*;
|
||||
use super::r#type::parse_struct_declaration;
|
||||
use crate::parser::r#type::parse_rustattr;
|
||||
|
||||
#[cfg_attr(test, parser_test)]
|
||||
/// ```test,Document
|
||||
|
@ -45,6 +46,35 @@ pub fn parse_document(p: &mut impl Parser) -> bool {
|
|||
break;
|
||||
}
|
||||
}
|
||||
"@" if p.nth(1).as_str() == "rust-attr" => {
|
||||
let mut is_export = false;
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let value = p.nth(i);
|
||||
if value.as_str() == ")" && p.nth(i + 1).as_str() == "export" {
|
||||
is_export = true;
|
||||
break;
|
||||
} else if (value.as_str() == ")"
|
||||
&& p.nth(i + 1).as_str() != "struct"
|
||||
&& p.nth(i + 1).as_str() != "export"
|
||||
&& p.nth(i + 1).as_str() != ")")
|
||||
|| (value.as_str() == ")" && p.nth(i + 1).as_str() == "struct")
|
||||
{
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if is_export {
|
||||
let mut p = p.start_node(SyntaxKind::ExportsList);
|
||||
if !parse_rustattr(&mut *p) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if !parse_rustattr(&mut *p) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if !parse_component(&mut *p) {
|
||||
break;
|
||||
|
|
|
@ -85,3 +85,57 @@ pub fn parse_struct_declaration(p: &mut impl Parser) -> bool {
|
|||
parse_type_object(&mut *p);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn parse_rustattr(p: &mut impl Parser) -> bool {
|
||||
let checkpoint = p.checkpoint();
|
||||
debug_assert_eq!(p.peek().as_str(), "@");
|
||||
p.consume(); // "@"
|
||||
if p.peek().as_str() != "rust-attr" {
|
||||
p.expect(SyntaxKind::AtRustAttr);
|
||||
}
|
||||
p.consume(); // "rust-attr"
|
||||
p.expect(SyntaxKind::LParent);
|
||||
parse_parentheses(&mut *p);
|
||||
if p.peek().as_str() == "export" {
|
||||
p.consume();
|
||||
}
|
||||
let mut p = p.start_node_at(checkpoint, SyntaxKind::StructDeclaration);
|
||||
p.consume(); // "struct"
|
||||
{
|
||||
let mut p = p.start_node(SyntaxKind::DeclaredIdentifier);
|
||||
p.expect(SyntaxKind::Identifier);
|
||||
}
|
||||
|
||||
if p.peek().kind() == SyntaxKind::ColonEqual {
|
||||
p.warning("':=' to declare a struct is deprecated. Remove the ':='");
|
||||
p.consume();
|
||||
}
|
||||
|
||||
parse_type_object(&mut *p);
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_parentheses(p: &mut impl Parser) -> bool {
|
||||
let mut p = p.start_node(SyntaxKind::AtRustAttr);
|
||||
let mut opened = 0;
|
||||
let mut closed = 0;
|
||||
while closed <= opened {
|
||||
if p.peek().kind() == SyntaxKind::LParent {
|
||||
opened += 1;
|
||||
}
|
||||
if p.peek().kind() == SyntaxKind::RParent {
|
||||
closed += 1;
|
||||
}
|
||||
if closed == opened && opened != 0 && closed != 0 && p.peek().kind() != SyntaxKind::RParent
|
||||
{
|
||||
p.error("Parse error: `)` or `,`");
|
||||
return false;
|
||||
}
|
||||
p.consume();
|
||||
}
|
||||
if p.peek().as_str() != "struct" && p.peek().as_str() != "export" {
|
||||
p.error("Parse error: expected `struct` or `export`");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
|
@ -139,6 +139,7 @@ fn compile_path_from_string_literal(
|
|||
.collect(),
|
||||
name: Some("Point".into()),
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
};
|
||||
|
||||
let mut points = Vec::new();
|
||||
|
|
|
@ -969,6 +969,7 @@ impl Expression {
|
|||
fields: values.iter().map(|(k, v)| (k.clone(), v.ty())).collect(),
|
||||
name: None,
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
};
|
||||
Expression::Struct { ty, values }
|
||||
}
|
||||
|
@ -1030,8 +1031,14 @@ impl Expression {
|
|||
fields: mut result_fields,
|
||||
name: result_name,
|
||||
node: result_node,
|
||||
rust_attributes,
|
||||
},
|
||||
Type::Struct {
|
||||
fields: elem_fields,
|
||||
name: elem_name,
|
||||
node: elem_node,
|
||||
rust_attributes: deriven,
|
||||
},
|
||||
Type::Struct { fields: elem_fields, name: elem_name, node: elem_node },
|
||||
) => {
|
||||
for (elem_name, elem_ty) in elem_fields.into_iter() {
|
||||
match result_fields.entry(elem_name) {
|
||||
|
@ -1052,6 +1059,7 @@ impl Expression {
|
|||
name: result_name.or(elem_name),
|
||||
fields: result_fields,
|
||||
node: result_node.or(elem_node),
|
||||
rust_attributes: rust_attributes.or(deriven),
|
||||
}
|
||||
}
|
||||
(Type::Color, Type::Brush) | (Type::Brush, Type::Color) => Type::Brush,
|
||||
|
|
|
@ -355,6 +355,7 @@ fn add_highlight_items(doc: &Document) {
|
|||
.collect(),
|
||||
name: None,
|
||||
node: None,
|
||||
rust_attributes: None,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue