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