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