Skip to content

Commit

Permalink
add derive macro for gen state structs
Browse files Browse the repository at this point in the history
  • Loading branch information
jrmoulton committed Oct 30, 2024
1 parent 2be12bd commit bb8487d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["renderer", "vello", "vger", "tiny_skia", "reactive", "editor-core", "examples/*"]
members = ["renderer", "vello", "vger", "tiny_skia", "reactive", "macros", "editor-core", "examples/*"]

[workspace.package]
license = "MIT"
Expand Down Expand Up @@ -33,6 +33,7 @@ parking_lot = { version = "0.12.1" }
swash = { version = "0.1.17" }

[dependencies]
macros = {path = "macros", version = "0.1.0"}
slotmap = "1.0.7"
sha2 = "0.10.6"
bitflags = "2.6.0"
Expand Down
13 changes: 13 additions & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "macros"
edition = "2021"
license.workspace = true
version.workspace = true

[lib]
proc-macro = true

[dependencies]
syn = { version = "2.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
125 changes: 125 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
#[allow(unused_imports)]
use syn::{
parse::Parse, parse2, parse_macro_input, punctuated::Punctuated, Attribute, Data, DeriveInput,
Fields, Ident, Path, Token, Visibility,
};

struct StateArgs {
derives: Punctuated<Path, Token![,]>,
}

impl Parse for StateArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(StateArgs {
derives: input.parse_terminated(Path::parse, Token![,])?,
})
}
}

#[proc_macro_derive(
State,
attributes(state_skip, state_read_only, state_write_only, state_derives)
)]
pub fn derive_state(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let state_struct_name = format_ident!("{}State", struct_name);

let state_derives = input
.attrs
.iter()
.find(|attr| attr.path().is_ident("state_derives"))
.and_then(|attr| attr.parse_args::<StateArgs>().ok())
.map(|args| args.derives)
.unwrap_or_else(Punctuated::new);

let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => &fields.named,
_ => panic!("Only named fields are supported"),
},
_ => panic!("State can only be derived for structs"),
};

let state_fields = fields.iter().map(|f| {
let name = &f.ident;
let ty = &f.ty;
let vis = &f.vis;

let skip = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_skip"));
let read_only = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_read_only"));
let write_only = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_write_only"));

if skip {
quote! { #vis #name: #ty }
} else if read_only {
quote! { #vis #name: ::floem::reactive::ReadSignal<#ty> }
} else if write_only {
quote! { #vis #name: ::floem::reactive::WriteSignal<#ty> }
} else {
quote! { #vis #name: ::floem::reactive::RwSignal<#ty> }
}
});

let impl_fields = fields.iter().map(|f| {
let name = &f.ident;

let skip = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_skip"));
let read_only = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_read_only"));
let write_only = f
.attrs
.iter()
.any(|attr| attr.path().is_ident("state_write_only"));

if skip {
quote! { #name: self.#name }
} else if read_only {
quote! { #name: ::floem::reactive::create_signal(self.#name).0 }
} else if write_only {
quote! { #name: ::floem::reactive::create_signal(self.#name).1 }
} else {
quote! { #name: ::floem::reactive::create_rw_signal(self.#name) }
}
});

let derive_list = if state_derives.is_empty() {
quote! {}
} else {
let derives = state_derives.iter().collect::<Vec<_>>();
quote! { #[derive(#(#derives),*)] }
};

let expanded = quote! {
#derive_list
pub struct #state_struct_name {
#(#state_fields,)*
}

impl #struct_name {
pub fn to_state(self) -> #state_struct_name {
#state_struct_name {
#(#impl_fields,)*
}
}
}
};

TokenStream::from(expanded)
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ pub use floem_reactive as reactive;
pub use floem_renderer::text;
use floem_renderer::Renderer;
pub use id::ViewId;
pub use macros::State;
pub use peniko;
pub use peniko::kurbo;
pub use screen_layout::ScreenLayout;
Expand Down

0 comments on commit bb8487d

Please sign in to comment.