use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{
parse_macro_input, parse_quote, Data, DeriveInput, Field, Fields, FieldsNamed, GenericParam,
Generics,
};
use darling::{FromField, FromMeta};
mod internal_paths;
mod utils;
#[derive(Debug, FromField)]
#[darling(attributes(inspect), default)]
struct AttributeArgs {
#[allow(dead_code)]
ident: Option<Ident>,
hide: bool,
no_edit: bool,
slider: bool,
min: f32,
max: f32,
multiline: bool,
custom_func: Option<String>,
custom_func_mut: Option<String>,
}
impl Default for AttributeArgs {
fn default() -> Self {
Self {
ident: None,
hide: false,
no_edit: false,
slider: true,
min: 0.0,
max: 100.0,
multiline: false,
custom_func: None,
custom_func_mut: None,
}
}
}
#[proc_macro_derive(EguiInspect, attributes(inspect))]
pub fn derive_egui_inspect(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let generics = add_trait_bounds(input.generics);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let inspect = inspect_struct(&input.data, &name, false);
let inspect_mut = inspect_struct(&input.data, &name, true);
let expanded = quote! {
impl #impl_generics egui_inspect::EguiInspect for #name #ty_generics #where_clause {
fn inspect(&self, label: &'static str, ui: &mut egui::Ui) {
#inspect
}
fn inspect_mut(&mut self, label: &'static str, ui: &mut egui::Ui) {
#inspect_mut
}
}
};
proc_macro::TokenStream::from(expanded)
}
fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param
.bounds
.push(parse_quote!(egui_inspect::EguiInspect));
}
}
generics
}
fn inspect_struct(data: &Data, _struct_name: &Ident, mutable: bool) -> TokenStream {
match *data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => handle_named_fields(fields, mutable),
_ => {
unimplemented!("Unnamed fields are not yet supported")
}
},
Data::Enum(_) | Data::Union(_) => unimplemented!("Enums and Unions are not yet supported"),
}
}
fn handle_named_fields(fields: &FieldsNamed, mutable: bool) -> TokenStream {
let recurse = fields.named.iter().map(|f| {
let attr = AttributeArgs::from_field(f).unwrap();
if attr.hide {
return quote!();
}
if let Some(ts) = handle_custom_func(&f, mutable, &attr) {
return ts;
}
if let Some(ts) = internal_paths::try_handle_internal_path(&f, mutable, &attr) {
return ts;
}
return utils::get_default_function_call(&f, mutable);
});
quote! {
ui.strong(label);
#(#recurse)*
}
}
fn handle_custom_func(field: &Field, mutable: bool, attrs: &AttributeArgs) -> Option<TokenStream> {
let name = &field.ident;
let name_str = name.clone().unwrap().to_string();
if mutable && !attrs.no_edit && attrs.custom_func_mut.is_some() {
let custom_func_mut = attrs.custom_func_mut.as_ref().unwrap();
let ident = syn::Path::from_string(custom_func_mut)
.expect(format!("Could not find function: {}", custom_func_mut).as_str());
return Some(quote_spanned! { field.span() => {
#ident(&mut self.#name, &#name_str, ui);
}
});
}
if (!mutable || (mutable && attrs.no_edit)) && attrs.custom_func.is_some() {
let custom_func = attrs.custom_func.as_ref().unwrap();
let ident = syn::Path::from_string(custom_func)
.expect(format!("Could not find function: {}", custom_func).as_str());
return Some(quote_spanned! { field.span() => {
#ident(&self.#name, &#name_str, ui);
}
});
}
return None;
}