1#![warn(missing_docs)]
10
11use proc_macro::TokenStream;
12use quote::{format_ident, quote};
13use syn::parse::{Parse, ParseStream};
14use syn::punctuated::Punctuated;
15use syn::{Expr, ItemFn, LitStr, Token, parse_macro_input};
16
17fn valid_name(name: &str) -> bool {
18 !name.is_empty()
19 && name
20 .bytes()
21 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_' || b == b'-')
22}
23
24fn check_name(lit: &LitStr) -> syn::Result<()> {
25 if valid_name(&lit.value()) {
26 Ok(())
27 } else {
28 Err(syn::Error::new(
29 lit.span(),
30 "names of markers, eases and tunables must match [a-z0-9_-]+",
31 ))
32 }
33}
34
35#[proc_macro_attribute]
40pub fn scene(attr: TokenStream, item: TokenStream) -> TokenStream {
41 if !attr.is_empty() {
42 return syn::Error::new(
43 proc_macro2::TokenStream::from(attr)
44 .into_iter()
45 .next()
46 .unwrap()
47 .span(),
48 "#[scene] takes no arguments",
49 )
50 .to_compile_error()
51 .into();
52 }
53 let func = parse_macro_input!(item as ItemFn);
54 match expand_scene(func) {
55 Ok(ts) => ts.into(),
56 Err(e) => e.to_compile_error().into(),
57 }
58}
59
60fn expand_scene(func: ItemFn) -> syn::Result<proc_macro2::TokenStream> {
61 let sig = &func.sig;
62 if sig.asyncness.is_none() {
63 return Err(syn::Error::new_spanned(
64 sig.fn_token,
65 "a scene must be an `async fn`",
66 ));
67 }
68 if !sig.generics.params.is_empty() {
69 return Err(syn::Error::new_spanned(
70 &sig.generics,
71 "a scene cannot be generic",
72 ));
73 }
74 if sig.inputs.len() > 1 {
75 return Err(syn::Error::new_spanned(
76 &sig.inputs,
77 "a scene takes at most one argument: `view: View`",
78 ));
79 }
80 let name = &sig.ident;
81 let name_str = name.to_string();
82 if !valid_name(&name_str) {
83 return Err(syn::Error::new_spanned(
84 name,
85 "scene names must be lowercase snake_case ([a-z0-9_])",
86 ));
87 }
88 let vis = &func.vis;
89 let attrs = &func.attrs;
90 let inner = format_ident!("__oxframe_scene_{}", name);
91 let mut inner_fn = func.clone();
92 inner_fn.sig.ident = inner.clone();
93 inner_fn.vis = syn::Visibility::Inherited;
94 inner_fn.attrs.clear();
95 let call = if sig.inputs.is_empty() {
96 quote!(#inner())
97 } else {
98 quote!(#inner(view))
99 };
100
101 Ok(quote! {
102 #(#attrs)*
103 #vis fn #name() -> ::oxframe::SceneDef {
104 #inner_fn
105 ::oxframe::SceneDef::new(
106 #name_str,
107 ::oxframe::SourceLocation {
108 file: ::core::file!(),
109 manifest_dir: ::core::env!("CARGO_MANIFEST_DIR"),
110 },
111 |view: ::oxframe::View| -> ::oxframe::BoxFuture {
112 let _ = &view;
113 ::std::boxed::Box::pin(#call)
114 },
115 )
116 }
117 })
118}
119
120#[proc_macro]
123pub fn color(input: TokenStream) -> TokenStream {
124 let lit = parse_macro_input!(input as LitStr);
125 let s = lit.value();
126 let hex = s.strip_prefix('#').unwrap_or(&s);
127 let digits: Option<Vec<u8>> = hex
128 .chars()
129 .map(|c| c.to_digit(16).map(|d| d as u8))
130 .collect();
131 let channels = match (digits, hex.len()) {
132 (Some(d), 3 | 4) => Some(d.iter().map(|v| v * 17).collect::<Vec<_>>()),
133 (Some(d), 6 | 8) => Some(d.chunks(2).map(|p| p[0] * 16 + p[1]).collect()),
134 _ => None,
135 };
136 let Some(mut c) = channels else {
137 return syn::Error::new(lit.span(), "expected #rgb, #rgba, #rrggbb or #rrggbbaa")
138 .to_compile_error()
139 .into();
140 };
141 if c.len() == 3 {
142 c.push(255);
143 }
144 let (r, g, b, a) = (c[0], c[1], c[2], c[3]);
145 quote!(::oxframe::Color::from_rgba8(#r, #g, #b, #a)).into()
146}
147
148struct NameArgs {
149 name: LitStr,
150 rest: Punctuated<Expr, Token![,]>,
151}
152
153impl Parse for NameArgs {
154 fn parse(input: ParseStream) -> syn::Result<Self> {
155 let name: LitStr = input.parse()?;
156 check_name(&name)?;
157 let rest = if input.is_empty() {
158 Punctuated::new()
159 } else {
160 input.parse::<Token![,]>()?;
161 Punctuated::parse_terminated(input)?
162 };
163 Ok(Self { name, rest })
164 }
165}
166
167#[proc_macro]
170pub fn ease(input: TokenStream) -> TokenStream {
171 let NameArgs { name, rest } = parse_macro_input!(input as NameArgs);
172 match rest.len() {
173 0 => quote!(::oxframe::ease_named(#name, ::core::option::Option::None)).into(),
174 1 => {
175 let d = &rest[0];
176 quote!(::oxframe::ease_named(#name, ::core::option::Option::Some(#d))).into()
177 }
178 _ => syn::Error::new_spanned(&rest, "usage: ease!(\"name\") or ease!(\"name\", default)")
179 .to_compile_error()
180 .into(),
181 }
182}
183
184#[proc_macro]
187pub fn tune(input: TokenStream) -> TokenStream {
188 let NameArgs { name, rest } = parse_macro_input!(input as NameArgs);
189 if rest.len() != 1 {
190 return syn::Error::new(name.span(), "usage: tune!(\"name\", default)")
191 .to_compile_error()
192 .into();
193 }
194 let d = &rest[0];
195 quote!(::oxframe::tunable(#name, (#d) as f64)).into()
196}
197
198#[proc_macro_derive(Node, attributes(signal))]
223pub fn derive_node(input: TokenStream) -> TokenStream {
224 let input = parse_macro_input!(input as syn::DeriveInput);
225 match expand_node(input) {
226 Ok(tokens) => tokens.into(),
227 Err(err) => err.to_compile_error().into(),
228 }
229}
230
231fn expand_node(input: syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
232 let name = &input.ident;
233 let vis = &input.vis;
234 if !input.generics.params.is_empty() {
235 return Err(syn::Error::new_spanned(
236 &input.generics,
237 "#[derive(Node)] does not support generic structs",
238 ));
239 }
240 let syn::Data::Struct(data) = &input.data else {
241 return Err(syn::Error::new_spanned(
242 name,
243 "#[derive(Node)] needs a struct",
244 ));
245 };
246 let syn::Fields::Named(fields) = &data.fields else {
247 return Err(syn::Error::new_spanned(
248 name,
249 "#[derive(Node)] needs a struct with named fields",
250 ));
251 };
252 let builder = format_ident!("{name}Builder");
253 let handle = format_ident!("{name}Handle");
254 let state = format_ident!("__{name}Signals");
255 let type_name = name.to_string();
256
257 let mut builder_fields = Vec::new();
258 let mut builder_defaults = Vec::new();
259 let mut setters = Vec::new();
260 let mut state_fields = Vec::new();
261 let mut state_init = Vec::new();
262 let mut snapshot = Vec::new();
263 let mut handle_methods = Vec::new();
264 let mut getters = Vec::new();
265 for f in &fields.named {
266 let ident = f.ident.as_ref().expect("named field");
267 let ty = &f.ty;
268 let signal = f.attrs.iter().any(|a| a.path().is_ident("signal"));
269 let doc = format!("Sets `{ident}`.");
270 builder_fields.push(quote! { #ident: #ty });
271 builder_defaults.push(quote! { #ident: ::core::default::Default::default() });
272 setters.push(quote! {
273 #[doc = #doc]
274 #vis fn #ident(mut self, v: impl ::core::convert::Into<#ty>) -> Self {
275 self.#ident = v.into();
276 self
277 }
278 });
279 if signal {
280 let label = format!("{name}.{ident}");
281 let hdoc = format!("`{ident}` as an animatable signal.");
282 let gdoc = format!("The current `{ident}`.");
283 state_fields.push(quote! { #ident: ::oxframe::Signal<#ty> });
284 state_init.push(quote! {
285 #ident: ::oxframe::Signal::new(self.#ident).with_name(#label)
286 });
287 snapshot.push(quote! { #ident: s.#ident.get() });
288 handle_methods.push(quote! {
289 #[doc = #hdoc]
290 #vis fn #ident(self) -> ::oxframe::Signal<#ty> {
291 ::oxframe::nodes::node_api::state::<#state>(self.0).#ident.clone()
292 }
293 });
294 getters.push(quote! {
295 #[doc = #gdoc]
296 #vis fn #ident(&self) -> #ty {
297 ::core::clone::Clone::clone(&self.#ident)
298 }
299 });
300 } else {
301 state_fields.push(quote! { #ident: #ty });
302 state_init.push(quote! { #ident: self.#ident });
303 snapshot.push(quote! { #ident: ::core::clone::Clone::clone(&s.#ident) });
304 }
305 }
306 let bdoc = format!("Builder for [`{name}`]; pass it to `add`.");
307 let hdoc = format!("Handle to a [`{name}`] node. Derefs to `Node`.");
308 Ok(quote! {
309 #[doc = #bdoc]
310 #[must_use = "a builder does nothing until it is passed to `add`"]
311 #vis struct #builder {
312 base: ::oxframe::nodes::BaseProps,
313 #(#builder_fields,)*
314 }
315
316 impl #name {
317 #[allow(clippy::new_ret_no_self)]
319 #vis fn new() -> #builder {
320 #builder {
321 base: ::core::default::Default::default(),
322 #(#builder_defaults,)*
323 }
324 }
325 #(#getters)*
326 }
327
328 impl #builder {
329 #(#setters)*
330 }
331
332 #[doc(hidden)]
333 #vis struct #state {
334 #(#state_fields,)*
335 }
336
337 #[doc = #hdoc]
338 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
339 #vis struct #handle(::oxframe::Node);
340
341 impl #handle {
342 #(#handle_methods)*
343 #vis fn node(self) -> ::oxframe::Node {
345 self.0
346 }
347 }
348
349 impl ::core::ops::Deref for #handle {
350 type Target = ::oxframe::Node;
351 fn deref(&self) -> &::oxframe::Node {
352 &self.0
353 }
354 }
355
356 impl ::core::convert::From<#handle> for ::oxframe::Node {
357 fn from(h: #handle) -> ::oxframe::Node {
358 h.0
359 }
360 }
361
362 impl ::oxframe::NodeBuilder for #builder {
363 type Handle = #handle;
364 fn base(&mut self) -> &mut ::oxframe::nodes::BaseProps {
365 &mut self.base
366 }
367 fn build(
368 self,
369 graph: &::oxframe::nodes::SceneGraph,
370 parent: ::oxframe::NodeId,
371 ) -> #handle {
372 let state = ::std::rc::Rc::new(#state { #(#state_init,)* });
373 let s = state.clone();
374 let snapshot: ::std::rc::Rc<dyn Fn() -> #name> =
375 ::std::rc::Rc::new(move || #name { #(#snapshot,)* });
376 let id = ::oxframe::nodes::node_api::add(
377 graph,
378 parent,
379 self.base,
380 #type_name,
381 snapshot,
382 state,
383 );
384 #handle(::oxframe::Node::from_id(id))
385 }
386 }
387 })
388}