#![doc = include_str!("README.md")]
#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro_derive(SlintElement, attributes(rtti_field))]
pub fn slint_element(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput);
let fields = match &input.data {
syn::Data::Struct(syn::DataStruct { fields: f @ syn::Fields::Named(..), .. }) => f,
_ => {
return syn::Error::new(
input.ident.span(),
"Only `struct` with named field are supported",
)
.to_compile_error()
.into()
}
};
let mut pub_prop_field_names = Vec::new();
let mut pub_prop_field_names_normalized = Vec::new();
let mut pub_prop_field_types = Vec::new();
let mut property_names = Vec::new();
let mut property_visibility = Vec::new();
let mut property_types = Vec::new();
for field in fields {
if let Some(property_type) = property_type(&field.ty) {
let name = field.ident.as_ref().unwrap();
if matches!(field.vis, syn::Visibility::Public(_)) {
pub_prop_field_names_normalized.push(normalize_identifier(name));
pub_prop_field_names.push(name);
pub_prop_field_types.push(&field.ty);
}
property_names.push(name);
property_visibility.push(field.vis.clone());
property_types.push(property_type);
}
}
let (plain_field_names, plain_field_types): (Vec<_>, Vec<_>) = fields
.iter()
.filter(|f| {
f.attrs.iter().any(|attr| {
matches!(&attr.meta, syn::Meta::Path(path) if path.get_ident().map(|ident| *ident == "rtti_field").unwrap_or(false))
})
})
.map(|f| (f.ident.as_ref().unwrap(), &f.ty))
.unzip();
let plain_field_names_normalized =
plain_field_names.iter().map(|f| normalize_identifier(f)).collect::<Vec<_>>();
let mut callback_field_names = Vec::new();
let mut callback_field_names_normalized = Vec::new();
let mut callback_args = Vec::new();
let mut callback_rets = Vec::new();
for field in fields {
if let Some((arg, ret)) = callback_arg(&field.ty) {
if matches!(field.vis, syn::Visibility::Public(_)) {
let name = field.ident.as_ref().unwrap();
callback_field_names_normalized.push(normalize_identifier(name));
callback_field_names.push(name);
callback_args.push(arg);
callback_rets.push(ret);
}
}
}
let item_name = &input.ident;
quote!(
#[allow(clippy::nonstandard_macro_braces)]
#[cfg(feature = "rtti")]
impl BuiltinItem for #item_name {
fn name() -> &'static str {
stringify!(#item_name)
}
fn properties<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn PropertyInfo<Self, Value>)> {
::alloc::vec![#( {
const O : MaybeAnimatedPropertyInfoWrapper<#item_name, #pub_prop_field_types> =
MaybeAnimatedPropertyInfoWrapper(#item_name::FIELD_OFFSETS.#pub_prop_field_names);
(#pub_prop_field_names_normalized, (&O).as_property_info())
} ),*]
}
fn fields<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn FieldInfo<Self, Value>)> {
::alloc::vec![#( {
const O : const_field_offset::FieldOffset<#item_name, #plain_field_types, const_field_offset::AllowPin> =
#item_name::FIELD_OFFSETS.#plain_field_names;
(#plain_field_names_normalized, &O as &'static dyn FieldInfo<Self, Value>)
} ),*]
}
fn callbacks<Value: ValueType>() -> ::alloc::vec::Vec<(&'static str, &'static dyn CallbackInfo<Self, Value>)> {
::alloc::vec![#( {
const O : const_field_offset::FieldOffset<#item_name, Callback<#callback_args, #callback_rets>, const_field_offset::AllowPin> =
#item_name::FIELD_OFFSETS.#callback_field_names;
(#callback_field_names_normalized, &O as &'static dyn CallbackInfo<Self, Value>)
} ),*]
}
}
impl #item_name {
#(
#property_visibility fn #property_names(self: core::pin::Pin<&Self>) -> #property_types {
Self::FIELD_OFFSETS.#property_names.apply_pin(self).get()
}
)*
}
)
.into()
}
fn normalize_identifier(name: &syn::Ident) -> String {
name.to_string().replace('_', "-")
}
fn property_type(ty: &syn::Type) -> Option<&syn::Type> {
if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
if let Some(syn::PathSegment {
ident,
arguments:
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
}) = segments.first()
{
match args.first() {
Some(syn::GenericArgument::Type(property_type)) if *ident == "Property" => {
return Some(property_type)
}
_ => {}
}
}
}
None
}
fn callback_arg(ty: &syn::Type) -> Option<(&syn::Type, Option<&syn::Type>)> {
if let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = ty {
if let Some(syn::PathSegment {
ident,
arguments:
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }),
}) = segments.first()
{
if ident != "Callback" {
return None;
}
let mut it = args.iter();
let first = match it.next() {
Some(syn::GenericArgument::Type(ty)) => ty,
_ => return None,
};
let sec = match it.next() {
Some(syn::GenericArgument::Type(ty)) => Some(ty),
_ => None,
};
return Some((first, sec));
}
}
None
}
#[proc_macro_attribute]
pub fn identity(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}