1mod scene;
2mod utils;
3
4use proc_macro::TokenStream;
5use proc_macro_crate::{FoundCrate, crate_name};
6use proc_macro2::Span;
7use quote::quote;
8use syn::{Data, DeriveInput, Fields, Ident, ItemFn, parse_macro_input};
9
10use crate::scene::parse_scene_attrs;
11
12const RANIM_CRATE_NAME: &str = "ranim";
13
14fn ranim_path() -> proc_macro2::TokenStream {
15 match (
16 crate_name(RANIM_CRATE_NAME),
17 std::env::var("CARGO_CRATE_NAME").as_deref(),
18 ) {
19 (Ok(FoundCrate::Itself), Ok(RANIM_CRATE_NAME)) => quote!(crate),
20 (Ok(FoundCrate::Name(name)), _) => {
21 let ident = Ident::new(&name, Span::call_site());
22 quote!(::#ident)
23 }
24 _ => quote!(::ranim),
25 }
26}
27
28fn ranim_core_path() -> proc_macro2::TokenStream {
29 if let Ok(res) = crate_name("ranim-core") {
30 match (res, std::env::var("CARGO_CRATE_NAME").as_deref()) {
31 (FoundCrate::Itself, Ok("ranim-core") | Ok("ranim_core")) => return quote!(crate),
32 (FoundCrate::Name(name), _) => {
33 let ident = Ident::new(&name, Span::call_site());
34 return quote!(::#ident);
35 }
36 _ => (),
37 }
38 } else if let Ok(res) = crate_name("ranim") {
39 match (res, std::env::var("CARGO_CRATE_NAME").as_deref()) {
40 (FoundCrate::Itself, Ok("ranim")) => return quote!(crate::core),
41 (FoundCrate::Name(name), _) => {
42 let ident = Ident::new(&name, Span::call_site());
43 return quote!(::#ident::core);
44 }
45 _ => (),
46 }
47 }
48 ranim_path()
49}
50
51#[derive(Default)]
53struct SceneAttrs {
54 name: Option<String>, clear_color: Option<String>, wasm_demo_doc: bool, outputs: Vec<OutputDef>, }
59
60#[derive(Default)]
62struct OutputDef {
63 width: u32,
64 height: u32,
65 fps: u32,
66 save_frames: bool,
67 name: Option<String>,
68 dir: String,
69 format: Option<String>,
70}
71
72#[proc_macro_attribute]
74pub fn scene(args: TokenStream, input: TokenStream) -> TokenStream {
75 let ranim = ranim_path();
76 let input_fn = parse_macro_input!(input as ItemFn);
77 let attrs = parse_scene_attrs(args, input_fn.attrs.as_slice()).unwrap();
78
79 let fn_name = &input_fn.sig.ident;
80 let vis = &input_fn.vis;
81 let fn_body = &input_fn.block;
82 let doc_attrs: Vec<_> = input_fn
83 .attrs
84 .iter()
85 .filter(|attr| attr.path().is_ident("doc"))
86 .collect();
87
88 let scene_name = attrs.name.unwrap_or_else(|| fn_name.to_string());
90
91 let clear_color = attrs.clear_color.unwrap_or("#333333ff".to_string());
93 let scene_config = quote! {
94 #ranim::StaticSceneConfig {
95 clear_color: #clear_color,
96 }
97 };
98
99 let mut outputs = Vec::new();
101 for OutputDef {
102 width,
103 height,
104 fps,
105 save_frames,
106 name,
107 dir,
108 format,
109 } in attrs.outputs
110 {
111 let name_token = match name.as_deref() {
112 Some(n) if !n.is_empty() => quote! { Some(#n) },
113 _ => quote! { None },
114 };
115 let format_token = match format.as_deref() {
116 Some("mp4") | None => quote! { #ranim::OutputFormat::Mp4 },
117 Some("webm") => quote! { #ranim::OutputFormat::Webm },
118 Some("mov") => quote! { #ranim::OutputFormat::Mov },
119 Some("gif") => quote! { #ranim::OutputFormat::Gif },
120 Some(other) => panic!("unknown output format: {other:?}"),
121 };
122 outputs.push(quote! {
123 #ranim::StaticOutput {
124 width: #width,
125 height: #height,
126 fps: #fps,
127 save_frames: #save_frames,
128 name: #name_token,
129 dir: #dir,
130 format: #format_token,
131 }
132 });
133 }
134 if outputs.is_empty() {
135 outputs.push(quote! {
136 #ranim::StaticOutput::DEFAULT
137 });
138 }
139
140 let doc = if attrs.wasm_demo_doc {
141 quote! {
142 #[doc = concat!("<canvas id=\"ranim-app-", stringify!(#fn_name), "\" width=\"1280\" height=\"720\" style=\"width: 100%;\"></canvas>")]
143 #[doc = concat!("<script type=\"module\">")]
144 #[doc = concat!(" const { find_scene, preview_scene } = await ranim_examples;")]
145 #[doc = concat!(" preview_scene(find_scene(\"", stringify!(#fn_name), "\"));")]
146 #[doc = "</script>"]
147 }
148 } else {
149 quote! {}
150 };
151
152 let static_output_name = syn::Ident::new("__OUTPUTS", fn_name.span());
153 let static_scene_name = syn::Ident::new("__SCENE", fn_name.span());
154
155 let output_cnt = outputs.len();
156
157 let scene = quote! {
158 #ranim::StaticScene {
159 name: #scene_name,
160 constructor: super::#fn_name,
161 config: #scene_config,
162 outputs: &#static_output_name,
163 }
164 };
165
166 let expanded = quote! {
167 #doc
168 #(#doc_attrs)*
169 #vis fn #fn_name(r: &mut #ranim::RanimScene) #fn_body
170
171 #[doc(hidden)]
172 #vis mod #fn_name {
173 pub static #static_output_name: [#ranim::StaticOutput; #output_cnt] = [#(#outputs),*];
175 pub static #static_scene_name: #ranim::StaticScene = #scene;
177 #ranim::inventory::submit!{
178 #scene
179 }
180
181 pub fn scene() -> #ranim::Scene {
182 #ranim::Scene::from(&#static_scene_name)
183 }
184 }
185 };
186
187 TokenStream::from(expanded)
188}
189
190#[proc_macro_attribute]
200pub fn output(_: TokenStream, _: TokenStream) -> TokenStream {
201 TokenStream::new()
202}
203
204#[proc_macro_attribute]
210pub fn wasm_demo_doc(_attr: TokenStream, _: TokenStream) -> TokenStream {
211 TokenStream::new()
212}
213
214#[proc_macro_derive(Fill)]
217pub fn derive_fill(input: TokenStream) -> TokenStream {
218 let core = ranim_core_path();
219 impl_derive(input, quote! {#core::traits::Fill}, |field_positions| {
220 quote! {
221 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
222 #(
223 self.#field_positions.set_fill_opacity(opacity);
224 )*
225 self
226 }
227 fn fill_color(&self) -> #core::color::AlphaColor<#core::color::Srgb> {
228 [#(self.#field_positions.fill_color(), )*].first().cloned().unwrap()
229 }
230 fn set_fill_color(&mut self, color: #core::color::AlphaColor<#core::color::Srgb>) -> &mut Self {
231 #(
232 self.#field_positions.set_fill_color(color);
233 )*
234 self
235 }
236 }
237 })
238}
239
240#[proc_macro_derive(Stroke)]
241pub fn derive_stroke(input: TokenStream) -> TokenStream {
242 let core = ranim_core_path();
243 impl_derive(input, quote! {#core::traits::Stroke}, |field_positions| {
244 quote! {
245 fn stroke_color(&self) -> #core::color::AlphaColor<#core::color::Srgb> {
246 [#(self.#field_positions.stroke_color(), )*].first().cloned().unwrap()
247 }
248 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [#core::components::width::Width])) -> &mut Self {
249 #(
250 self.#field_positions.apply_stroke_func(&f);
251 )*
252 self
253 }
254 fn set_stroke_color(&mut self, color: #core::color::AlphaColor<#core::color::Srgb>) -> &mut Self {
255 #(
256 self.#field_positions.set_stroke_color(color);
257 )*
258 self
259 }
260 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
261 #(
262 self.#field_positions.set_stroke_opacity(opacity);
263 )*
264 self
265 }
266 }
267 })
268}
269
270#[proc_macro_derive(Partial)]
271pub fn derive_partial(input: TokenStream) -> TokenStream {
272 let core = ranim_core_path();
273 impl_derive(input, quote! {#core::traits::Partial}, |field_positions| {
274 quote! {
275 fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
276 Self {
277 #(
278 #field_positions: self.#field_positions.get_partial(range.clone()),
279 )*
280 }
281 }
282 fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
283 Self {
284 #(
285 #field_positions: self.#field_positions.get_partial(range.clone()),
286 )*
287 }
288 }
289 }
290 })
291}
292
293#[proc_macro_derive(Empty)]
294pub fn derive_empty(input: TokenStream) -> TokenStream {
295 let core = ranim_core_path();
296 let input = parse_macro_input!(input as DeriveInput);
297 let name = &input.ident;
298 let generics = &input.generics;
299 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
300
301 let fields = match &input.data {
302 Data::Struct(data) => &data.fields,
303 _ => panic!("Empty can only be derived for structs"),
304 };
305
306 let field_impls = match fields {
307 Fields::Named(fields) => {
308 let (field_names, field_types): (Vec<_>, Vec<_>) =
309 fields.named.iter().map(|f| (&f.ident, &f.ty)).unzip();
310
311 quote! {
312 Self {
313 #(
314 #field_names: #field_types::empty(),
315 )*
316 }
317 }
318 }
319 Fields::Unnamed(fields) => {
320 let field_types = fields.unnamed.iter().map(|f| &f.ty);
321 quote! {
322 Self (
323 #(
324 #field_types::empty(),
325 )*
326 )
327 }
328 }
329 Fields::Unit => quote! {},
330 };
331
332 let expanded = quote! {
333 impl #impl_generics #core::traits::Empty for #name #ty_generics #where_clause {
334 fn empty() -> Self {
335 #field_impls
336 }
337 }
338 };
339
340 TokenStream::from(expanded)
341}
342
343#[proc_macro_derive(Opacity)]
344pub fn derive_opacity(input: TokenStream) -> TokenStream {
345 let core = ranim_core_path();
346 impl_derive(input, quote! {#core::traits::Opacity}, |field_positions| {
347 quote! {
348 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
349 #(
350 self.#field_positions.set_opacity(opacity);
351 )*
352 self
353 }
354 }
355 })
356}
357
358#[proc_macro_derive(Alignable)]
359pub fn derive_alignable(input: TokenStream) -> TokenStream {
360 let core = ranim_core_path();
361 impl_derive(
362 input,
363 quote! {#core::traits::Alignable},
364 |field_positions| {
365 quote! {
366 fn is_aligned(&self, other: &Self) -> bool {
367 #(
368 self.#field_positions.is_aligned(&other.#field_positions) &&
369 )* true
370 }
371 fn align_with(&mut self, other: &mut Self) {
372 #(
373 self.#field_positions.align_with(&mut other.#field_positions);
374 )*
375 }
376 }
377 },
378 )
379}
380
381#[proc_macro_derive(Interpolatable)]
382pub fn derive_interpolatable(input: TokenStream) -> TokenStream {
383 let core = ranim_core_path();
384 impl_derive(
385 input,
386 quote! {#core::traits::Interpolatable},
387 |field_positions| {
388 quote! {
389 fn lerp(&self, other: &Self, t: f64) -> Self {
390 Self {
391 #(
392 #field_positions: #core::traits::Interpolatable::lerp(&self.#field_positions, &other.#field_positions, t),
393 )*
394 }
395 }
396 }
397 },
398 )
399}
400
401#[proc_macro_derive(ShiftTransform)]
402pub fn derive_shift_impl(input: TokenStream) -> TokenStream {
403 let core = ranim_core_path();
404 impl_derive(
405 input,
406 quote! {#core::traits::ShiftTransform},
407 |field_positions| {
408 quote! {
409 fn shift(&mut self, shift: #core::glam::DVec3) -> &mut Self {
410 #(self.#field_positions.shift(shift);)*
411 self
412 }
413 }
414 },
415 )
416}
417
418#[proc_macro_derive(RotateTransform)]
419pub fn derive_rotate_impl(input: TokenStream) -> TokenStream {
420 let core = ranim_core_path();
421 impl_derive(
422 input,
423 quote! {#core::traits::RotateTransform},
424 |field_positions| {
425 quote! {
426 fn rotate_on_axis(&mut self, axis: #core::glam::DVec3, angle: f64) -> &mut Self {
427 #(self.#field_positions.rotate_on_axis(axis, angle);)*
428 self
429 }
430 }
431 },
432 )
433}
434
435#[proc_macro_derive(ScaleTransform)]
436pub fn derive_scale_impl(input: TokenStream) -> TokenStream {
437 let core = ranim_core_path();
438 impl_derive(
439 input,
440 quote! {#core::traits::ScaleTransform},
441 |field_positions| {
442 quote! {
443 fn scale(&mut self, scale: #core::glam::DVec3) -> &mut Self {
444 #(self.#field_positions.scale(scale);)*
445 self
446 }
447 }
448 },
449 )
450}
451
452#[proc_macro_derive(PointsFunc)]
453pub fn derive_point_func(input: TokenStream) -> TokenStream {
454 let core = ranim_core_path();
455 impl_derive(
456 input,
457 quote! {#core::traits::PointsFunc},
458 |field_positions| {
459 quote! {
460 fn apply_points_func(&mut self, f: impl for<'a> Fn(&'a mut [DVec3])) -> &mut Self {
461 #(self.#field_positions.apply_points_func(f);)*
462 self
463 }
464 }
465 },
466 )
467}
468
469fn impl_derive(
470 input: TokenStream,
471 trait_path: proc_macro2::TokenStream,
472 impl_token: impl Fn(Vec<proc_macro2::TokenStream>) -> proc_macro2::TokenStream,
473) -> TokenStream {
474 let input = parse_macro_input!(input as DeriveInput);
475 let name = &input.ident;
476 let generics = &input.generics;
477 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
478
479 let fields = match &input.data {
480 Data::Struct(data) => &data.fields,
481 _ => panic!("Can only be derived for structs"),
482 };
483
484 let field_positions = get_field_positions(fields)
485 .ok_or("cannot get field from unit struct")
486 .unwrap();
487
488 let impl_token = impl_token(field_positions);
489 let expanded = quote! {
490 impl #impl_generics #trait_path for #name #ty_generics #where_clause {
491 #impl_token
492 }
493 };
494
495 TokenStream::from(expanded)
496}
497
498fn get_field_positions(fields: &Fields) -> Option<Vec<proc_macro2::TokenStream>> {
499 match fields {
500 Fields::Named(fields) => Some(
501 fields
502 .named
503 .iter()
504 .map(|f| {
505 let pos = &f.ident;
506 quote! { #pos }
507 })
508 .collect::<Vec<_>>(),
509 ),
510 Fields::Unnamed(fields) => Some(
511 (0..fields.unnamed.len())
512 .map(syn::Index::from)
513 .map(|i| {
514 quote! { #i }
515 })
516 .collect::<Vec<_>>(),
517 ),
518 Fields::Unit => None,
519 }
520}