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