use std::collections::HashSet;
use std::string::ToString;
use proc_macro2::{Literal, TokenStream};
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
use syn::{Attribute, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type};
use super::error::Error;
macro_rules! tag {
(
$(#[$meta:meta])*
pub struct $tag:ident {
fields { $( $field:ident : Lit ,)* },
optional { $( $opt_field:ident : Option<Lit> ,)* }
}
) => {
$(#[$meta])*
pub struct $tag {
$( $field : Lit ,)*
$( $opt_field : Option<Lit> ,)*
}
impl $tag {
fn from_meta_list(list: &MetaList) -> Result<Self, Error> {
$( let mut $field = None; )*
$( let mut $opt_field = None; )*
for param in list.nested.iter() {
match param {
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) => {
let ident = path_to_ident(&path);
match ident.to_string().as_str() {
$( stringify!($opt_field) => {
if $opt_field.is_some() {
return Err(Error::already_defined(ident.span()))
}
$opt_field = Some(lit.clone());
},)*
$( stringify!($field) => {
if $field.is_some() {
return Err(Error::already_defined(ident.span()))
}
$field = Some(lit.clone());
},)*
_ => return Err(Error::unexpected_param(ident.span())),
}
}
_ => return Err(Error::invalid_format(list.span())),
}
}
Ok(Self {
$( $field : $field.ok_or(Error::missing_param(list.span(), stringify!($field)))?,)*
$( $opt_field,)*
})
}
}
}
}
fn path_to_ident(path: &Path) -> &Ident {
let segments = &path.segments;
assert_eq!(1, segments.len());
&segments.first().unwrap().ident
}
pub enum DisplayParam {
Literal(Lit),
Ident(Ident),
}
#[derive(Default)]
pub struct Display {
label: Option<Lit>,
display: Option<Lit>,
params: Vec<DisplayParam>,
}
impl Display {
pub fn from_meta_list(params: &MetaList) -> Result<Self, Error> {
#[derive(Clone, Copy)]
enum State {
Init,
Display,
}
let mut state = State::Init;
let mut display = Display::default();
for attr in params.nested.iter() {
match (state, attr) {
(State::Display, NestedMeta::Lit(lit)) => {
display.params.push(DisplayParam::Literal(lit.clone()));
}
(State::Display, NestedMeta::Meta(Meta::Path(path))) => {
let ident = path_to_ident(&path);
display.params.push(DisplayParam::Ident(ident.clone()));
}
(
State::Init,
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })),
) if path_to_ident(&path).to_string() == "label" => {
display.label = Some(lit.clone());
}
(
State::Init,
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })),
) if path_to_ident(&path).to_string() == "display" => {
display.display = Some(lit.clone());
state = State::Display;
}
_ => return Err(Error::invalid_format(params.span())),
}
}
Ok(display)
}
}
tag! {
#[derive(Default)]
pub struct Checkbox {
fields {
},
optional {
label: Option<Lit>,
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct Input {
fields {
},
optional {
label: Option<Lit>,
flags: Option<Lit>,
step: Option<Lit>,
step_fast: Option<Lit>,
catch: Option<Lit>,
size: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
pub struct Slider {
fields {
min: Lit,
max: Lit,
},
optional {
label: Option<Lit>,
format: Option<Lit>,
power: Option<Lit>,
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct Drag {
fields {
},
optional {
label: Option<Lit>,
min: Option<Lit>,
max: Option<Lit>,
speed: Option<Lit>,
power: Option<Lit>,
format: Option<Lit>,
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
pub struct Button {
fields {
label: Lit,
},
optional {
size: Option<Lit>,
catch: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct Bullet {
fields {
},
optional {
text: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct Nested {
fields {
},
optional {
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct Progress {
fields {
},
optional {
overlay: Option<Lit>,
size: Option<Lit>,
}
}
}
tag! {
pub struct Image {
fields {
size: Lit,
},
optional {
border: Option<Lit>,
tint: Option<Lit>,
uv0: Option<Lit>,
uv1: Option<Lit>,
}
}
}
tag! {
pub struct ImageButton {
fields {
size: Lit,
},
optional {
background: Option<Lit>,
frame_padding: Option<Lit>,
tint: Option<Lit>,
uv0: Option<Lit>,
uv1: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct ColorButton {
fields {
},
optional {
label: Option<Lit>,
flags: Option<Lit>,
preview: Option<Lit>,
size: Option<Lit>,
catch: Option<Lit>,
input_mode: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct ColorPicker {
fields {
},
optional {
label: Option<Lit>,
flags: Option<Lit>,
preview: Option<Lit>,
mode: Option<Lit>,
input_mode: Option<Lit>,
format: Option<Lit>,
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
#[derive(Default)]
pub struct ColorEdit {
fields {
},
optional {
label: Option<Lit>,
flags: Option<Lit>,
preview: Option<Lit>,
display_mode: Option<Lit>,
input_mode: Option<Lit>,
format: Option<Lit>,
catch: Option<Lit>,
map: Option<Lit>,
}
}
}
tag! {
pub struct Text {
fields {
lit: Lit,
},
optional {
}
}
}
impl Text {
fn from_meta_list2(list: &MetaList) -> Result<Self, Error> {
let mut iter = list.nested.iter();
let first = iter.next();
let second = iter.next();
match (first, second) {
(Some(NestedMeta::Lit(Lit::Str(s))), None) => Ok(Self {
lit: Lit::Str(s.clone()),
}),
_ => Self::from_meta_list(list),
}
}
}
#[derive(Default)]
pub struct Vars {
style: Option<Lit>,
color: Option<Lit>,
content: Option<Vec<Tag>>,
}
impl Vars {
fn from_meta_list(list: &MetaList) -> Result<Self, Error> {
let mut style: Option<Lit> = None;
let mut color: Option<Lit> = None;
let mut content: Option<Vec<Tag>> = None;
for meta in list.nested.iter() {
match meta {
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) => {
let ident = path_to_ident(&path);
match &ident.to_string()[..] {
"color" => {
if color.is_some() {
return Err(Error::already_defined(ident.span()));
} else {
color = Some(lit.clone());
}
}
"style" => {
if style.is_some() {
return Err(Error::already_defined(ident.span()));
} else {
style = Some(lit.clone());
}
}
_ => return Err(Error::unexpected_param(ident.span())),
}
}
NestedMeta::Meta(Meta::List(list))
if path_to_ident(&list.path).to_string() == "content" =>
{
if content.is_some() {
return Err(Error::already_defined(list.span()));
} else {
content = Some(parse_meta_list(&list)?);
}
}
_ => return Err(Error::invalid_format(list.span())),
}
}
Ok(Self {
content,
style,
color,
})
}
}
#[derive(Default)]
pub struct Tree {
label: Option<Lit>,
cond: Option<Lit>,
flags: Option<Lit>,
node: Option<Vec<Tag>>,
}
impl Tree {
fn from_meta_list(list: &MetaList) -> Result<Self, Error> {
let mut label: Option<Lit> = None;
let mut cond: Option<Lit> = None;
let mut flags: Option<Lit> = None;
let mut node: Option<Vec<Tag>> = None;
for meta in list.nested.iter() {
match meta {
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) => {
let ident = path_to_ident(&path);
match &ident.to_string()[..] {
"label" => {
if label.is_some() {
return Err(Error::already_defined(ident.span()));
} else {
label = Some(lit.clone());
}
}
"flags" => {
if flags.is_some() {
return Err(Error::already_defined(ident.span()));
} else {
flags = Some(lit.clone());
}
}
"cond" => {
if cond.is_some() {
return Err(Error::already_defined(ident.span()));
} else {
cond = Some(lit.clone());
}
}
_ => return Err(Error::unexpected_param(ident.span())),
}
}
NestedMeta::Meta(Meta::List(list))
if path_to_ident(&list.path).to_string() == "node" =>
{
if node.is_some() {
return Err(Error::already_defined(list.span()));
} else {
node = Some(parse_meta_list(&list)?);
}
}
_ => panic!(),
}
}
Ok(Self {
label,
node,
cond,
flags,
})
}
}
pub enum Tag {
None,
Display(Display),
Checkbox(Checkbox),
Input(Input),
Slider(Slider),
Drag(Drag),
Nested(Nested),
Progress(Progress),
Image(Image),
ImageButton(ImageButton),
Button(Button),
ColorButton(ColorButton),
ColorPicker(ColorPicker),
ColorEdit(ColorEdit),
Separator,
NewLine,
Text(Text),
TextWrap(Text),
BulletParent,
Bullet(Bullet),
Tree(Tree),
Vars(Vars),
}
pub fn parse_meta(meta: Meta) -> Result<Vec<Tag>, Error> {
match meta {
Meta::NameValue(named) => Err(Error::invalid_format(named.span())),
Meta::Path(_) => Ok(vec![Tag::Display(Display::default())]),
Meta::List(meta_list) => parse_meta_list(&meta_list),
}
}
fn parse_meta_list(meta_list: &MetaList) -> Result<Vec<Tag>, Error> {
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum State {
Init,
Tags,
}
let mut state = State::Init;
let mut tags = vec![];
for nested in meta_list.nested.iter() {
match (state, nested) {
(_, NestedMeta::Lit(_)) => return Err(Error::invalid_format(meta_list.span())),
(State::Init, NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, .. })))
if path_to_ident(&path).to_string() == "label"
|| path_to_ident(&path).to_string() == "display" =>
{
tags.push(Tag::Display(Display::from_meta_list(&meta_list)?));
break;
}
(s, NestedMeta::Meta(Meta::Path(path))) if s == State::Init || s == State::Tags => {
let ident = path_to_ident(&path);
match ident.to_string().as_str() {
"separator" => tags.push(Tag::Separator),
"new_line" => tags.push(Tag::NewLine),
"nested" => tags.push(Tag::Nested(Default::default())),
"display" => tags.push(Tag::Display(Default::default())),
"checkbox" => tags.push(Tag::Checkbox(Default::default())),
"input" => tags.push(Tag::Input(Default::default())),
"drag" => tags.push(Tag::Drag(Default::default())),
"bullet" => tags.push(Tag::Bullet(Default::default())),
"progress" => tags.push(Tag::Progress(Default::default())),
"tree" => tags.push(Tag::Tree(Default::default())),
"vars" => tags.push(Tag::Vars(Default::default())),
"color" => return Err(Error::invalid_format(meta_list.span())),
"text" => return Err(Error::invalid_format(meta_list.span())),
"text_wrap" => return Err(Error::invalid_format(meta_list.span())),
"slider" => {
Tag::Slider(Slider::from_meta_list(&meta_list)?);
}
"button" => {
Tag::Button(Button::from_meta_list(&meta_list)?);
}
"image" => {
Tag::Image(Image::from_meta_list(&meta_list)?);
}
_ => return Err(Error::unexpected_mode(meta_list.span())),
}
state = State::Tags;
}
(s, NestedMeta::Meta(Meta::List(meta_list)))
if s == State::Init || s == State::Tags =>
{
let tag = match path_to_ident(&meta_list.path).to_string().as_str() {
"separator" => Tag::Separator,
"new_line" => Tag::NewLine,
"display" => Tag::Display(Display::from_meta_list(&meta_list)?),
"nested" => Tag::Nested(Nested::from_meta_list(meta_list)?),
"checkbox" => Tag::Checkbox(Checkbox::from_meta_list(meta_list)?),
"input" => Tag::Input(Input::from_meta_list(meta_list)?),
"drag" => Tag::Drag(Drag::from_meta_list(meta_list)?),
"slider" => Tag::Slider(Slider::from_meta_list(meta_list)?),
"button" => Tag::Button(Button::from_meta_list(meta_list)?),
"progress" => Tag::Progress(Progress::from_meta_list(meta_list)?),
"image" => Tag::Image(Image::from_meta_list(meta_list)?),
"image_button" => Tag::ImageButton(ImageButton::from_meta_list(meta_list)?),
"text" => Tag::Text(Text::from_meta_list2(meta_list)?),
"text_wrap" => Tag::TextWrap(Text::from_meta_list2(meta_list)?),
"tree" => Tag::Tree(Tree::from_meta_list(meta_list)?),
"vars" => Tag::Vars(Vars::from_meta_list(meta_list)?),
"color" => {
for nested in meta_list.nested.iter() {
match nested {
NestedMeta::Meta(Meta::Path(path)) => {
let ident = path_to_ident(&path);
match ident.to_string().as_str() {
"edit" => tags.push(Tag::ColorEdit(Default::default())),
"picker" => tags.push(Tag::ColorPicker(Default::default())),
"button" => tags.push(Tag::ColorButton(Default::default())),
_ => return Err(Error::unexpected_mode(ident.span())),
}
}
NestedMeta::Meta(Meta::List(color_meta_list)) => {
let ident = path_to_ident(&color_meta_list.path);
match ident.to_string().as_str() {
"edit" => tags.push(Tag::ColorEdit(
ColorEdit::from_meta_list(color_meta_list)?,
)),
"picker" => tags.push(Tag::ColorPicker(
ColorPicker::from_meta_list(color_meta_list)?,
)),
"button" => tags.push(Tag::ColorButton(
ColorButton::from_meta_list(color_meta_list)?,
)),
_ => {
return Err(Error::unexpected_mode(
color_meta_list.path.span(),
))
}
}
}
_ => return Err(Error::invalid_format(meta_list.span())),
}
}
Tag::None
}
"bullet" => match Bullet::from_meta_list(meta_list) {
Ok(bullet) => Tag::Bullet(bullet),
Err(_err) => {
let mut inner = parse_meta_list(meta_list)?.into_iter();
match (inner.next(), inner.next()) {
(Some(first), None) => {
tags.push(Tag::BulletParent);
Ok(first)
}
(None, None) => Ok(Tag::BulletParent),
(Some(_first), Some(_second)) => {
Err(Error::bullet(meta_list.span()))
}
_ => Err(Error::invalid_format(meta_list.span())),
}?
}
},
_ => return Err(Error::unexpected_mode(meta_list.span())),
};
tags.push(tag);
state = State::Tags;
}
_ => panic!(),
}
}
Ok(tags)
}
pub fn emmit_tag_tokens(
ident: &Ident,
_ty: &Type,
attr: &Attribute,
tag: &Tag,
fields: &mut TokenStream,
methods: &mut TokenStream,
input_fields: &mut HashSet<String>,
) -> Result<TokenStream, Error> {
let tokens = match tag {
Tag::None => quote!(),
Tag::Separator => quote!({ ui.separator() }),
Tag::NewLine => quote!({ ui.new_line() }),
Tag::Vars(Vars {
color,
style,
content,
}) => {
let mut tokens = TokenStream::new();
if let Some(tags) = content.as_ref() {
for tag in tags.iter() {
tokens.extend(emmit_tag_tokens(
ident,
_ty,
attr,
tag,
fields,
methods,
input_fields,
)?);
}
}
let tokens = match color {
Some(Lit::Str(color)) => {
let ident = Ident::new(&color.value(), color.span());
quote! {
{
let _color = ui.push_style_colors(#ident().into_iter() );
#tokens
}
}
}
None => tokens,
_ => return Err(Error::invalid_format(attr.span())),
};
let tokens = match style {
Some(Lit::Str(style)) => {
let ident = Ident::new(&style.value(), style.span());
quote! {{
let _style = ui.push_style_vars(#ident().into_iter() );
#tokens
}}
}
None => tokens,
_ => return Err(Error::invalid_format(attr.span())),
};
quote!( { #tokens } )
}
Tag::Tree(Tree {
label,
node,
cond,
flags,
}) => {
let label = match label {
Some(Lit::Str(s)) => s.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut node_tokens = TokenStream::new();
if let Some(tags) = node.as_ref() {
for tag in tags.iter() {
node_tokens.extend(emmit_tag_tokens(
ident,
_ty,
attr,
tag,
fields,
methods,
input_fields,
)?);
}
}
let mut tree_tokens = TokenStream::default();
match flags {
Some(Lit::Str(flags)) => {
let fn_ident: syn::Path =
syn::parse_str(&flags.value()).expect("Error parsing function path.");
tree_tokens.extend(quote! {tree = tree.flags(#fn_ident());});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match cond {
Some(Lit::Str(cond)) => {
let ident = Ident::new(&cond.value(), flags.span());
tree_tokens
.extend(quote! {tree = tree.opened(true, imgui::Condition::#ident);});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
quote! {{
let mut tree = imgui::TreeNode::new(ui, imgui::im_str!(#label));
{ #tree_tokens }
tree.build(|| { #node_tokens })
}}
}
Tag::ImageButton(ImageButton {
size,
background,
frame_padding,
uv0,
uv1,
tint,
}) => {
let size = match size {
Lit::Str(size) => Ident::new(&size.value(), size.span()),
_ => return Err(Error::invalid_format(attr.span())),
};
let mut params = quote! {
use imgui_ext::image_button::ImageButtonParams as Params;
use imgui::im_str;
let mut params = Params {
size: #size().into(),
background: None,
frame_padding: None,
tint: None,
uv0: None,
uv1: None,
};
};
match frame_padding {
Some(Lit::Str(value_str)) => {
let value = value_str
.value()
.parse()
.map(Literal::i32_unsuffixed)
.expect("frame_padding expected to be numeric (i32).");
params.extend(quote!(params.frame_padding = Some(#value);));
}
Some(Lit::Int(value)) => {
params.extend(quote!(params.frame_padding = Some(#value);));
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match uv0 {
Some(Lit::Str(uv0)) => {
let fn_ident: syn::Path =
syn::parse_str(&uv0.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.uv0 = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match uv1 {
Some(Lit::Str(uv1)) => {
let fn_ident: syn::Path =
syn::parse_str(&uv1.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.uv1 = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match tint {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.tint = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match background {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.background = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
quote! {{
use imgui_ext::image::Image;
Image::build(ui, ext.#ident, { #params ; params });
}}
}
Tag::Image(Image {
size,
border,
tint,
uv0,
uv1,
}) => {
let size = match size {
Lit::Str(size) => Ident::new(&size.value(), size.span()),
_ => return Err(Error::invalid_format(attr.span())),
};
let mut params = quote! {
use imgui_ext::image::ImageParams as Params;
use imgui::im_str;
let mut params = Params {
size: #size().into(),
border: None,
tint: None,
uv0: None,
uv1: None,
};
};
match uv0 {
Some(Lit::Str(uv0)) => {
let fn_ident: syn::Path =
syn::parse_str(&uv0.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.uv0 = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match uv1 {
Some(Lit::Str(uv1)) => {
let fn_ident: syn::Path =
syn::parse_str(&uv1.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.uv1 = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match tint {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.tint = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match border {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.border = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
quote! {{
use imgui_ext::image::Image;
Image::build(ui, ext.#ident, { #params ; params });
}}
}
Tag::Progress(Progress { overlay, size }) => {
let mut params = quote! {
use imgui_ext::progress::ProgressParams as Params;
use imgui::im_str;
let mut params = Params {
overlay: None,
size: None,
};
};
let ident_str = ident.to_string();
match (overlay, ident_str.as_bytes()[0]) {
(Some(Lit::Str(stri)), _) => {
params.extend(quote! {{ params.overlay = Some(im_str!(#stri)); }})
}
(None, b'_') => {}
(None, _) => {
let overlay = Literal::string(&ident_str);
params.extend(quote! {{ params.overlay = Some(im_str!(#overlay)); }});
}
_ => return Err(Error::invalid_format(attr.span())),
}
match size {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.size = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
quote! {{
use imgui_ext::progress::Progress;
Progress::build(ui, &ext.#ident, { #params; params });
}}
}
Tag::Text(Text { lit }) => {
match lit {
Lit::Str(lit) => quote! { ui.text(#lit); },
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::TextWrap(Text { lit }) => {
match lit {
Lit::Str(lit) => quote! { ui.text_wrapped(imgui::im_str!(#lit)); },
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::ColorEdit(ColorEdit {
label,
flags,
preview,
display_mode,
input_mode,
format,
catch,
map,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut params = quote! {
use imgui_ext::color::ColorEditParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
flags: None,
preview: None,
input_mode: None,
display_mode: None,
format: None,
};
};
match flags {
Some(Lit::Str(flags)) => {
let ident = Ident::new(&flags.value(), flags.span());
params.extend(quote! { params.flags = Some( #ident() ); });
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match preview {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.preview = Some( imgui::ColorPreview::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match input_mode {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.input_mode = Some( imgui::ColorEditInputMode::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match display_mode {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.display_mode = Some( imgui::ColorEditDisplayMode::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match format {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.format = Some( imgui::ColorFormat::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
match map {
None => {
quote! {{
use imgui_ext::color::ColorEdit;
let _ev = ColorEdit::build(ui, &mut ext.#ident, { #params ; params });
events.#catch_ident |= _ev;
}}
}
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote! {{
use imgui_ext::color::ColorEdit;
let _ev = ColorEdit::build(ui, #map_path(&mut ext.#ident), { #params ; params });
events.#catch_ident |= _ev;
}}
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::ColorPicker(ColorPicker {
label,
flags,
preview,
mode,
input_mode,
format,
catch,
map,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut params = quote! {
use imgui_ext::color::ColorPickerParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
flags: None,
preview: None,
input_mode: None,
format: None,
mode: None,
};
};
match flags {
Some(Lit::Str(flags)) => {
let fn_ident: syn::Path =
syn::parse_str(&flags.value()).expect("Error parsing function path.");
params.extend(quote! { params.flags = Some( #fn_ident() ); });
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match preview {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.preview = Some( imgui::ColorPreview::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match mode {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.mode = Some( imgui::ColorPickerMode::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match input_mode {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.input_mode = Some( imgui::ColorEditInputMode::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match format {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.format = Some( imgui::ColorFormat::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
match map {
None => {
quote! {{
use imgui_ext::color::ColorPicker;
let _ev = ColorPicker::build(ui, &mut ext.#ident, { #params ; params });
events.#catch_ident |= _ev;
}}
}
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote! {{
use imgui_ext::color::ColorPicker;
let _ev = ColorPicker::build(ui, #map_path(&mut ext.#ident), { #params ; params });
events.#catch_ident |= _ev;
}}
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::ColorButton(ColorButton {
label,
flags,
preview,
size,
catch,
map,
input_mode,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut params = quote! {
use imgui_ext::color::ColorButtonParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
flags: None,
size: None,
preview: None,
input_mode: None,
};
};
match input_mode {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
params.input_mode = Some( imgui::ColorEditInputMode::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match flags {
Some(Lit::Str(flags)) => {
let fn_ident: syn::Path =
syn::parse_str(&flags.value()).expect("Error parsing function path.");
params.extend(quote! { params.flags = Some( #fn_ident() ); });
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match size {
Some(Lit::Str(size)) => {
let ident = Ident::new(&size.value(), size.span());
params.extend(quote! { params.size = Some( #ident().into() ); });
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match preview {
Some(Lit::Str(c)) => {
let var = Ident::new(&c.value(), ident.span());
params.extend(quote! {{
use imgui::ColorPreview;
params.preview = Some( ColorPreview::#var );
}});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
match map {
None => {
quote! {{
use imgui_ext::color::ColorButton;
let _ev = ColorButton::build(ui, ext.#ident, { #params ; params });
events.#catch_ident |= _ev;
}}
}
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote! {{
use imgui_ext::color::ColorButton;
let _ev = ColorButton::build(ui, #map_path(ext.#ident), { #params ; params });
events.#catch_ident |= _ev;
}}
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Input(Input {
label,
step,
step_fast,
flags,
catch,
size,
map,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut params = quote! {
use imgui_ext::input::InputParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
step: None,
step_fast: None,
flags: None,
size: None,
};
};
match size {
Some(Lit::Str(size)) => {
let fn_ident: syn::Path =
syn::parse_str(&size.value()).expect("Error parsing function path.");
params.extend(quote! {{ params.size = Some( #fn_ident().into() ); }});
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match step {
Some(Lit::Float(step)) => params.extend(quote! { params.step = Some(#step); }),
Some(Lit::Int(step)) => params.extend(quote! { params.step = Some(#step); }),
Some(Lit::Str(step)) => {
let step_i64 = step.value().parse().map(Literal::i64_unsuffixed);
let step_f64 = step.value().parse().map(Literal::f64_unsuffixed);
match (step_i64, step_f64) {
(Err(_), Ok(step)) => params.extend(quote!(params.step = Some(#step);)),
(Ok(step), _) => params.extend(quote!(params.step = Some(#step);)),
_ => return Err(Error::parsing_error(step.span())),
}
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match step_fast {
Some(Lit::Float(step)) => params.extend(quote! { params.step_fast = Some(#step); }),
Some(Lit::Int(step)) => params.extend(quote! { params.step_fast = Some(#step); }),
Some(Lit::Str(step)) => {
let step_i64 = step.value().parse().map(Literal::i64_unsuffixed);
let step_f64 = step.value().parse().map(Literal::f64_unsuffixed);
match (step_i64, step_f64) {
(Err(_), Ok(step)) => {
params.extend(quote!(params.step_fast = Some(#step);))
}
(Ok(step), _) => params.extend(quote!(params.step_fast = Some(#step);)),
_ => return Err(Error::parsing_error(step.span())),
}
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match flags {
Some(Lit::Str(flags)) => {
let fn_ident: syn::Path =
syn::parse_str(&flags.value()).expect("Error parsing function path.");
params.extend(quote! { params.flags = Some( #fn_ident() ); });
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
params.extend(quote!(params));
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
match map {
None => quote!({
use imgui_ext::input::Input;
let _ev = Input::build(ui, &mut ext.#ident, { #params });
events.#catch_ident |= _ev;
}),
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote!({
use imgui_ext::input::Input;
let _ev = Input::build(ui, #map_path (&mut ext.#ident), { #params });
events.#catch_ident |= _ev;
})
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Drag(Drag {
label,
min,
max,
speed,
power,
format,
catch,
map,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let mut params = quote! {
use imgui_ext::drag::DragParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
min: None,
max: None,
speed: None,
power: None,
format: None,
};
};
match min {
Some(Lit::Float(min)) => params.extend(quote!(params.min = Some(#min);)),
Some(Lit::Int(min)) => params.extend(quote!(params.min = Some(#min);)),
Some(Lit::Str(min)) => {
let min_i64 = min.value().parse().map(Literal::i64_unsuffixed);
let min_f64 = min.value().parse().map(Literal::f64_unsuffixed);
match (min_i64, min_f64) {
(Err(_), Ok(min)) => params.extend(quote!(params.min = Some(#min);)),
(Ok(min), _) => params.extend(quote!(params.min = Some(#min);)),
_ => return Err(Error::parsing_error(min.span())),
}
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match max {
Some(Lit::Float(max)) => params.extend(quote!(params.max = Some(#max);)),
Some(Lit::Int(max)) => params.extend(quote!(params.max = Some(#max);)),
Some(Lit::Str(max)) => {
let max_i64 = max.value().parse().map(Literal::i64_unsuffixed);
let max_f64 = max.value().parse().map(Literal::f64_unsuffixed);
match (max_i64, max_f64) {
(Err(_), Ok(max)) => params.extend(quote!(params.max = Some(#max);)),
(Ok(max), _) => params.extend(quote!(params.max = Some(#max);)),
_ => return Err(Error::parsing_error(max.span())),
}
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match speed {
Some(Lit::Float(value)) => params.extend(quote! { params.speed = Some(#value); }),
Some(Lit::Str(value)) => match value.value().parse::<f32>() {
Ok(value) => params.extend(quote! { params.speed = Some(#value); }),
Err(_) => return Err(Error::parsing_error(value.span())),
},
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match power {
Some(Lit::Float(value)) => params.extend(quote! { params.power = Some(#value); }),
Some(Lit::Str(value)) => match value.value().parse::<f32>() {
Ok(value) => params.extend(quote! { params.power = Some(#value); }),
Err(_) => return Err(Error::parsing_error(value.span())),
},
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match format {
Some(Lit::Str(value)) => {
params.extend(quote!(params.format = Some(im_str!(#value));))
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
params.extend(quote!(params));
match map {
None => quote!({
use imgui_ext::drag::Drag;
let _ev = Drag::build(ui, &mut ext.#ident, { #params });
events.#catch_ident |= _ev;
}),
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote!({
use imgui_ext::drag::Drag;
let _ev = Drag::build(ui, #map_path(&mut ext.#ident), { #params });
events.#catch_ident |= _ev;
})
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Button(Button { label, size, catch }) => {
let label = match label {
Lit::Str(stri) => Literal::string(&stri.value()),
_ => return Err(Error::invalid_format(attr.span())),
};
let catch = if let Some(Lit::Str(c)) = catch {
let id = Ident::new(&c.value(), ident.span());
let q = quote! { events.#id = _ev; };
fields.extend(quote! { pub #id: bool , });
methods.extend(quote! { pub fn #id(&self) -> bool { self.#id } });
q
} else {
quote!()
};
if let Some(size) = size {
let size_fn = match size {
Lit::Str(size) => Ident::new(&size.value(), size.span()),
_ => return Err(Error::invalid_format(attr.span())),
};
quote! {{
let _ev = ui.button( imgui::im_str!( #label ), { #size_fn().into() } );
#catch
}}
} else {
quote! {{
let _ev = ui.small_button( imgui::im_str!( #label ) );
#catch
}}
}
}
Tag::BulletParent => {
quote! { ui.bullet(); }
}
Tag::Bullet(Bullet { text }) => {
let text = match text {
Some(Lit::Str(text)) => Some(text),
None => None,
_ => return Err(Error::invalid_format(attr.span())),
};
if let Some(text) = text {
quote! {{
use imgui::im_str;
ui.bullet_text( im_str!( #text ));
}}
} else {
quote! { ui.bullet(); }
}
}
Tag::Slider(Slider {
label,
min,
max,
format,
power,
catch,
map,
}) => {
let label = match label {
Some(Lit::Str(stri)) => stri.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let min_max = match (min, max) {
(Lit::Int(min), Lit::Int(max)) => quote! { min: #min, max: #max },
(Lit::Float(min), Lit::Float(max)) => quote! { min: #min, max: #max },
(Lit::Str(min), Lit::Int(max)) => {
let min = min
.value()
.parse()
.map(Literal::i64_unsuffixed)
.map_err(|_| Error::parsing_error(min.span()))?;
quote! { min: #min, max: #max }
}
(Lit::Str(min), Lit::Float(max)) => {
let min = min
.value()
.parse()
.map(Literal::f64_unsuffixed)
.map_err(|_| Error::parsing_error(min.span()))?;
quote! { min: #min, max: #max }
}
(Lit::Int(min), Lit::Str(max)) => {
let max = max
.value()
.parse()
.map(Literal::i64_unsuffixed)
.map_err(|_| Error::parsing_error(max.span()))?;
quote! { min: #min, max: #max }
}
(Lit::Float(min), Lit::Str(max)) => {
let max = max
.value()
.parse()
.map(Literal::f64_unsuffixed)
.map_err(|_| Error::parsing_error(max.span()))?;
quote! { min: #min, max: #max }
}
(Lit::Str(min), Lit::Str(max)) => {
let min_f64 = min.value().parse().map(Literal::f64_unsuffixed);
let max_f64 = max.value().parse().map(Literal::f64_unsuffixed);
let min_i32 = min.value().parse().map(Literal::i64_unsuffixed);
let max_i32 = max.value().parse().map(Literal::i64_unsuffixed);
match (min_f64, max_f64, min_i32, max_i32) {
(_, _, Ok(min), Ok(max)) => quote! { min: #min, max: #max },
(Ok(min), Ok(max), _, _) => quote! { min: #min, max: #max },
_ => return Err(Error::parsing_error(max.span())),
}
}
_ => return Err(Error::invalid_format(attr.span())),
};
let mut params = quote! {
use imgui_ext::slider::SliderParams as Params;
use imgui::im_str;
let mut params = Params {
label: im_str!( #label ),
format: None,
#min_max,
power: None,
};
};
match format {
Some(Lit::Str(value)) => {
params.extend(quote!(params.format = Some( im_str!(#value) );))
}
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
match power {
Some(Lit::Float(value)) => params.extend(quote!(params.power = Some(#value);)),
Some(Lit::Str(value)) => match value.value().parse::<f32>() {
Ok(value) => params.extend(quote! { params.power = Some(#value); }),
Err(_) => return Err(Error::parsing_error(value.span())),
},
None => {}
_ => return Err(Error::invalid_format(attr.span())),
}
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
params.extend(quote!(params));
match map {
None => quote!({
use imgui_ext::slider::Slider;
let _ev = Slider::build(ui, &mut ext.#ident, { #params });
events.#catch_ident |= _ev;
}),
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote!({
use imgui_ext::slider::Slider;
let _ev = Slider::build(ui, #map_path(&mut ext.#ident), { #params });
events.#catch_ident |= _ev;
})
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Checkbox(Checkbox { label, catch, map }) => {
let label = match label {
Some(Lit::Str(lab)) => lab.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let catch_ident =
catch_ident(attr, ident, catch.as_ref(), input_fields, fields, methods)?;
match map {
None => quote!({
use imgui_ext::checkbox::Checkbox;
use imgui_ext::checkbox::CheckboxParams as Params;
use imgui::im_str;
let _ev = Checkbox::build(ui, &mut ext.#ident, Params { label: im_str!(#label) });
events.#catch_ident |= _ev;
}),
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote!({
use imgui_ext::checkbox::Checkbox;
use imgui_ext::checkbox::CheckboxParams as Params;
use imgui::im_str;
let _ev = Checkbox::build(ui, #map_path(&mut ext.#ident), Params { label: im_str!(#label) });
events.#catch_ident |= _ev;
})
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Nested(Nested { catch, map }) => {
let catch_ident = catch_ident_nested(
attr,
_ty,
ident,
catch.as_ref(),
input_fields,
fields,
methods,
)?;
match map {
None => {
quote! {{
use imgui_ext::Gui;
let _ev = Gui::draw_gui(ui, &mut ext.#ident);
events.#catch_ident = _ev;
}}
}
Some(Lit::Str(map)) => {
let map_path: syn::Path =
syn::parse_str(&map.value()).expect("Error parsing parth to function.");
quote! {{
use imgui_ext::Gui;
let _ev = Gui::draw_gui(ui, #map_path(&mut ext.#ident));
events.#catch_ident = _ev;
}}
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
Tag::Display(Display {
label,
display,
params,
}) => {
let label = match label {
Some(Lit::Str(lab)) => lab.value(),
None => ident.to_string(),
_ => return Err(Error::invalid_format(attr.span())),
};
let label = Literal::string(&label);
let display = match display {
Some(Lit::Str(disp)) => Some(disp.value()),
None => None,
_ => return Err(Error::invalid_format(attr.span())),
};
let display = if let Some(display) = display {
let literal = Literal::string(display.as_str());
let params: Vec<_> = params
.into_iter()
.map(|field| match field {
DisplayParam::Literal(lit) => quote!( ext.#ident.#lit ),
DisplayParam::Ident(ident) => quote!( ext.#ident.#ident ),
})
.collect();
quote!(#literal , #( #params ),*)
} else {
quote!("{}", ext.#ident)
};
quote!({
use imgui::im_str;
ui.label_text(im_str!(#label), &im_str!(#display));
})
}
};
Ok(tokens)
}
fn catch_ident(
attr: &Attribute,
field: &Ident,
catch: Option<&Lit>,
field_set: &mut HashSet<String>,
fields: &mut TokenStream,
methods: &mut TokenStream,
) -> Result<Ident, Error> {
match catch {
Some(Lit::Str(lit)) => {
let ident = Ident::new(&lit.value(), field.span());
fields.extend(quote! { pub #ident: bool , });
methods.extend(quote! { pub fn #ident(&self) -> bool { self.#ident } });
Ok(ident)
}
None => {
if field_set.insert(field.to_string()) {
fields.extend(quote! { pub #field: bool , });
methods.extend(
quote! { #[inline(always)] pub fn #field(&self) -> bool { self.#field } },
);
}
Ok(field.clone())
}
_ => return Err(Error::invalid_format(attr.span())),
}
}
fn catch_ident_nested(
attr: &Attribute,
_ty: &Type,
field: &Ident,
catch: Option<&Lit>,
field_set: &mut HashSet<String>,
fields: &mut TokenStream,
methods: &mut TokenStream,
) -> Result<Ident, Error> {
let tp = _ty.clone().into_token_stream();
match catch {
Some(Lit::Str(lit)) => {
let ident = Ident::new(&lit.value(), field.span());
fields.extend(quote! { pub #ident: <#tp as imgui_ext::Gui>::Events , });
methods.extend(
quote! { pub fn #ident(&self) -> &<#tp as imgui_ext::Gui>::Events { &self.#ident } },
);
Ok(ident)
}
None => {
if field_set.insert(field.to_string()) {
fields.extend(quote! { pub #field: <#tp as imgui_ext::Gui>::Events , });
methods.extend(
quote! { pub fn #field(&self) -> &<#tp as imgui_ext::Gui>::Events { &self.#field } },
);
}
Ok(field.clone())
}
_ => return Err(Error::invalid_format(attr.span())),
}
}