1#![allow(clippy::single_match_else)]
2
3use std::{
9 fs::File,
10 io::{BufRead, BufReader}
11};
12
13use hill_vacuum_shared::{
14 continue_if_no_match,
15 match_or_panic,
16 return_if_no_match,
17 ManualItem,
18 NextValue,
19 TEXTURE_HEIGHT_RANGE
20};
21use proc_macro::{Ident, TokenStream, TokenTree};
22
23#[inline]
32fn is_comma(value: TokenTree)
33{
34 assert!(match_or_panic!(value, TokenTree::Punct(p), p).as_char() == ',');
35}
36
37fn for_each_ident_in_group<F: FnMut(Ident)>(group: TokenTree, mut f: F)
43{
44 for ident in match_or_panic!(group, TokenTree::Group(g), g)
45 .stream()
46 .into_iter()
47 .filter_map(|item| return_if_no_match!(item, TokenTree::Ident(ident), Some(ident), None))
48 {
49 f(ident);
50 }
51}
52
53#[inline]
59#[must_use]
60fn enum_ident(iter: &mut impl Iterator<Item = TokenTree>) -> Ident
61{
62 for item in iter.by_ref()
63 {
64 let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident);
65
66 if &ident.to_string() == "enum"
67 {
68 return match_or_panic!(iter.next_value(), TokenTree::Ident(i), i);
69 }
70 }
71
72 panic!();
73}
74
75#[proc_macro_derive(EnumSize)]
80#[allow(clippy::missing_panics_doc)]
81#[must_use]
82pub fn enum_size(input: TokenStream) -> TokenStream
83{
84 let mut iter = input.into_iter();
85 format!(
86 "impl {} {{ pub const SIZE: usize = {}; }}",
87 enum_ident(&mut iter),
88 enum_len(iter)
89 )
90 .parse()
91 .unwrap()
92}
93
94#[allow(clippy::missing_panics_doc)]
98#[inline]
99#[must_use]
100fn enum_len(mut iter: impl Iterator<Item = TokenTree>) -> usize
101{
102 let mut i = 0;
103 for_each_ident_in_group(iter.next_value(), |_| i += 1);
104 i
105}
106
107#[proc_macro_derive(EnumFromUsize)]
113#[must_use]
114pub fn enum_from_usize(input: TokenStream) -> TokenStream
115{
116 let mut iter = input.into_iter();
117 let enum_ident = enum_ident(&mut iter).to_string();
118
119 let mut from_impl = format!(
120 "impl From<usize> for {enum_ident}
121 {{
122 #[inline]
123 #[must_use] fn from(value: usize) -> Self
124 {{
125 match value
126 {{
127 "
128 );
129
130 let mut i = 0;
131
132 for_each_ident_in_group(iter.next_value(), |ident| {
133 from_impl.push_str(&format!("{i} => {enum_ident}::{ident},\n"));
134 i += 1;
135 });
136
137 from_impl.push_str("_ => unreachable!() } } }");
138 from_impl.parse().unwrap()
139}
140
141#[proc_macro_derive(EnumIter)]
145#[allow(clippy::missing_panics_doc)]
146#[must_use]
147pub fn enum_iter(input: TokenStream) -> TokenStream
148{
149 let mut iter = input.into_iter();
150 let enum_ident = enum_ident(&mut iter).to_string();
151 let enum_len = enum_len(iter.clone());
152 let mut enum_match = String::new();
153
154 let mut i = 0;
155 for_each_ident_in_group(iter.next_value(), |ident| {
156 enum_match.push_str(&format!("{i} => Some({enum_ident}::{ident}),\n"));
157 i += 1;
158 });
159
160 enum_match.push_str("_ => None");
161
162 format!(
163 "
164 impl {enum_ident}
165 {{
166 #[inline]
167 pub fn iter() -> impl ExactSizeIterator<Item = Self>
168 {{
169 struct EnumIterator(usize, usize);
170
171 impl ExactSizeIterator for EnumIterator
172 {{
173 #[inline]
174 #[must_use]
175 fn len(&self) -> usize {{ self.1 - self.0 }}
176 }}
177
178 impl Iterator for EnumIterator
179 {{
180 type Item = {enum_ident};
181
182 #[inline]
183 fn next(&mut self) -> Option<Self::Item>
184 {{
185 let value = match self.0
186 {{
187 {enum_match}
188 }};
189
190 self.0 += 1;
191 value
192 }}
193 }}
194
195 EnumIterator(0, {enum_len})
196 }}
197 }}
198 "
199 )
200 .parse()
201 .unwrap()
202}
203
204#[proc_macro]
216pub fn str_array(input: TokenStream) -> TokenStream
217{
218 let mut iter = input.into_iter();
219
220 let ident = iter.next_value().to_string();
221 is_comma(iter.next_value());
222
223 let amount = iter.next_value().to_string().parse::<u16>().unwrap();
224
225 let prefix = if let Some(v) = iter.next()
226 {
227 is_comma(v);
228 let v = iter.next_value();
229 assert!(iter.next().is_none());
230 v.to_string()
231 }
232 else
233 {
234 String::new()
235 };
236
237 let mut result = format!("const {ident}: [&'static str; {amount}] = [");
238
239 for i in 0..amount
240 {
241 result.push_str(&format!("\"{prefix}{i}\", "));
242 }
243
244 result.push_str("];");
245 result.parse().unwrap()
246}
247
248#[allow(clippy::missing_panics_doc)]
252#[proc_macro]
253pub fn generate_manual(_: TokenStream) -> TokenStream
254{
255 const SHOW_EXPLANATION: &str = "
256 use crate::map::editor::state::{ui::{Tool, SubTool}, core::tool::ToolInterface};
257
258 #[inline]
259 fn show_explanation<F: FnOnce(&mut egui::Ui)>(ui: &mut egui::Ui, left: F, explanation: &str)
260 {
261 ui.horizontal_wrapped(|ui| {
262 egui_extras::StripBuilder::new(ui)
263 .size(egui_extras::Size::exact(250f32))
264 .size(egui_extras::Size::remainder())
265 .horizontal(|mut strip| {
266 strip.cell(|ui| {
267 left(ui);
268 });
269
270 strip.cell(|ui| {
271 ui.label(explanation);
272 });
273 });
274 });
275 }";
276
277 let path = std::env::current_dir().unwrap();
278 let path = path.to_str().unwrap();
279
280 let body = hill_vacuum_shared::process_docs(
281 |string| {
282 string.push_str("ui.collapsing(\n");
283 },
284 |string, name, item| {
285 match item
286 {
287 ManualItem::Regular =>
288 {
289 string.push('\"');
290 string.push_str(&name.to_ascii_uppercase());
291 string.push_str("\",\n");
292 string.push_str("|ui| {\nui.vertical(|ui| {\n");
293 },
294 ManualItem::Tool =>
295 {
296 let mut chars = name.chars();
297 let mut tool = chars.next_value().to_ascii_uppercase().to_string();
298
299 while let Some(mut c) = chars.next()
300 {
301 if c == ' '
302 {
303 c = chars.next_value().to_ascii_uppercase();
304 }
305
306 tool.push(c);
307 }
308
309 string.push_str(&format!(
310 "Tool::{tool}.header(),\n|ui| {{\nui.vertical(|ui| \
311 {{\ntools_buttons.image(ui, Tool::{tool});\n"
312 ));
313 },
314 ManualItem::Texture => unreachable!()
315 };
316 },
317 |string, name, file, item| {
318 let processed = file
319 .trim()
320 .replace("### ", "")
321 .replace("```ini", "")
322 .replace('\"', "\\\"")
323 .replace(" ", "")
324 .replace('`', "");
325
326 match item
327 {
328 ManualItem::Regular =>
329 {
330 let mut lines = processed.lines();
331 let command = lines.next_value();
332 let mut exp = String::new();
333
334 for line in lines
335 {
336 exp.push_str(line);
337 exp.push('\n');
338 }
339
340 exp.pop();
341 string.push_str(&format!(
342 "show_explanation(ui, |ui| {{ ui.label(\"{command}\"); }}, \"{exp}\");\n"
343 ));
344 },
345 ManualItem::Tool =>
346 {
347 let mut chars = name.chars();
348 let mut subtool = chars.next_value().to_ascii_uppercase().to_string();
349
350 while let Some(mut c) = chars.next()
351 {
352 if c == '_'
353 {
354 c = chars.next_value().to_ascii_uppercase();
355 }
356
357 subtool.push(c);
358 }
359
360 let mut lines = processed.lines();
361 let mut exp = lines.next_value().to_string();
362 exp.push_str(" (");
363 exp.push_str(
364 &std::fs::read_to_string(format!("{path}/docs/subtools binds/{name}.md"))
365 .unwrap()
366 );
367 exp.push_str(")\n");
368
369 for line in lines
370 {
371 exp.push_str(line);
372 exp.push('\n');
373 }
374
375 exp.pop();
376
377 string.push_str(&format!(
378 "show_explanation(ui, |ui| {{ tools_buttons.image(ui, \
379 SubTool::{subtool}); }}, \"{exp}\");\n"
380 ));
381 },
382 ManualItem::Texture =>
383 {
384 string.push_str(&format!(
385 "show_explanation(ui, |ui| {{ ui.label(\"TEXTURE EDITING\"); }}, \
386 \"{processed}\");\n"
387 ));
388 }
389 };
390 },
391 |string, last| {
392 string.push_str("})\n});\n\n");
393
394 if !last
395 {
396 string.push_str("ui.separator();\n\n");
397 }
398 }
399 );
400
401 format!("{SHOW_EXPLANATION}\n\n{body}").parse().unwrap()
402}
403
404#[allow(clippy::missing_panics_doc)]
409#[proc_macro]
410pub fn color_enum(stream: TokenStream) -> TokenStream
411{
412 #[inline]
413 fn is_column<I: Iterator<Item = TokenTree>>(stream: &mut I)
414 {
415 assert!(match_or_panic!(stream.next_value(), TokenTree::Punct(p), p).as_char() == ':');
416 }
417
418 #[inline]
419 fn push_key_and_label(item: &str, label_func: &mut String, key_func: &mut String)
420 {
421 let mut chars = item.chars();
422 let c = chars.next_value();
423 key_func.push_str(&format!("Self::{item} => \"{}", c.to_ascii_lowercase()));
424 label_func.push_str(&format!("Self::{item} => \"{c}"));
425
426 for c in chars
427 {
428 if c.is_uppercase()
429 {
430 key_func.push('_');
431 key_func.push(c.to_ascii_lowercase());
432
433 label_func.push(' ');
434 label_func.push(c);
435
436 continue;
437 }
438
439 for func in [&mut *key_func, &mut *label_func]
440 {
441 func.push(c);
442 }
443 }
444
445 for func in [key_func, label_func]
446 {
447 func.push_str("\",\n");
448 }
449 }
450
451 #[inline]
452 #[must_use]
453 fn extract<I: Iterator<Item = TokenTree>>(
454 stream: &mut I,
455 end_tag: &str,
456 label_func: &mut String,
457 key_func: &mut String
458 ) -> Vec<String>
459 {
460 let mut vec: Vec<String> = Vec::new();
461
462 while let Some(item) = stream.next()
463 {
464 if let TokenTree::Punct(p) = item
465 {
466 let c = p.as_char();
467
468 match c
469 {
470 ',' => (),
471 '|' =>
472 {
473 let last = vec.last_mut().unwrap();
474 let item = stream.next_value().to_string();
475 push_key_and_label(&item, label_func, key_func);
476 last.push_str(&format!(" | Self::{item}"));
477 },
478 _ => panic!()
479 }
480
481 continue;
482 }
483
484 let item = item.to_string();
485
486 if item == end_tag
487 {
488 is_column(stream);
489 break;
490 }
491
492 push_key_and_label(&item, label_func, key_func);
493 vec.push(format!("Self::{item}"));
494 }
495
496 vec
497 }
498
499 #[inline]
500 #[must_use]
501 fn generate_height_func<'a, I: Iterator<Item = &'a str>>(
502 start: &str,
503 mut start_height: f32,
504 interval: f32,
505 iter: I
506 ) -> (String, f32)
507 {
508 let mut height_func = start.to_string();
509
510 for item in iter
511 {
512 height_func.push_str(&format!("{item} => {start_height}f32,\n"));
513 start_height += interval;
514 }
515
516 height_func.push_str("_ => panic!(\"Invalid color: {self:?}\")\n}\n}");
517 (height_func, start_height)
518 }
519
520 let textures_interval = f32::from(*TEXTURE_HEIGHT_RANGE.end());
521 let mut stream = stream.into_iter();
522
523 let mut key_func = "
524 /// The config file key relative to the drawn color associated with [`Color`].
525 #[inline]
526 #[must_use]
527 pub const fn config_file_key(self) -> &'static str
528 {
529 match self
530 {
531 "
532 .to_string();
533
534 let mut label_func = "
535 /// The text label representing [`Color`] in UI elements.
536 #[inline]
537 #[must_use]
538 pub const fn label(self) -> &'static str
539 {
540 match self
541 {
542 "
543 .to_string();
544
545 assert!(stream.next_value().to_string() == "clear");
546 is_column(&mut stream);
547 let clear = stream.next_value().to_string();
548 push_key_and_label(&clear, &mut label_func, &mut key_func);
549 is_comma(stream.next_value());
550
551 assert!(stream.next_value().to_string() == "extensions");
552 is_column(&mut stream);
553 let extensions = stream.next_value().to_string();
554 push_key_and_label(&extensions, &mut label_func, &mut key_func);
555 let extensions = format!("Self::{extensions}");
556 is_comma(stream.next_value());
557
558 assert!(stream.next_value().to_string() == "grid");
559 is_column(&mut stream);
560 let grid = extract(&mut stream, "entities", &mut label_func, &mut key_func);
561 let entities = extract(&mut stream, "ui", &mut label_func, &mut key_func);
562 let ui = extract(&mut stream, "", &mut label_func, &mut key_func);
563
564 for func in [&mut key_func, &mut label_func]
565 {
566 func.push_str("}\n}");
567 }
568
569 let (height_func, clip_height) = generate_height_func(
570 "
571 /// The height at which map elements colored with a certain [`Color`] should be drawn.
572 #[inline]
573 #[must_use]
574 pub fn entity_height(self) -> f32
575 {
576 match self
577 {",
578 1f32,
579 textures_interval + 1f32,
580 entities.iter().map(String::as_str)
581 );
582
583 let (line_height_func, thing_angle_height) = generate_height_func(
584 "
585 /// The draw height of the lines.
586 #[inline]
587 #[must_use]
588 pub fn line_height(self) -> f32
589 {
590 match self
591 {
592 ",
593 clip_height + 1f32,
594 1f32,
595 grid.iter()
596 .chain(Some(&extensions).iter().copied())
597 .chain(&entities)
598 .chain(&ui)
599 .map(String::as_str)
600 );
601
602 let (square_hgl_height_func, _) = generate_height_func(
603 "
604 /// The draw height of the square highlights.
605 #[inline]
606 #[must_use]
607 pub fn square_hgl_height(self) -> f32
608 {
609 match self
610 {
611 ",
612 thing_angle_height + 2f32,
613 1f32,
614 ui.iter().map(String::as_str)
615 );
616
617 format!(
618 "
619 {height_func}
620
621 /// The draw height of an untextured polygon.
622 #[inline]
623 #[must_use]
624 pub(in crate::map::drawer) fn polygon_height(self) -> f32 {{ self.entity_height() - 1f32 }}
625
626 /// The draw height of the clip overlay.
627 #[inline]
628 #[must_use]
629 pub(in crate::map::drawer) const fn clip_height() -> f32 {{ {clip_height}f32 }}
630
631 {line_height_func}
632
633 /// The draw height of the thing angle indicator.
634 #[inline]
635 #[must_use]
636 pub(in crate::map::drawer) fn thing_angle_indicator_height() -> [f32; 2]
637 {{
638 [
639 {thing_angle_height}f32,
640 {thing_angle_height}f32 + 1f32
641 ]
642 }}
643
644 {square_hgl_height_func}
645
646 {key_func}
647
648 {label_func}"
649 )
650 .parse()
651 .unwrap()
652}
653
654#[proc_macro]
660pub fn bind_enum(input: TokenStream) -> TokenStream
661{
662 let mut binds = "{".to_string();
663 binds.push_str(&input.to_string());
664 binds.push(',');
665
666 let mut path = std::env::current_dir().unwrap();
667 path.push("src/map/editor/state/core/tool.rs");
668
669 let mut lines = BufReader::new(File::open(path).unwrap()).lines().map(Result::unwrap);
670 lines.find(|line| line.ends_with("enum Tool"));
671 lines.next();
672
673 for line in lines
674 {
675 binds.push_str(&line);
676 binds.push('\n');
677
678 if line.contains('}')
679 {
680 break;
681 }
682 }
683
684 let mut iter = binds.clone().parse::<TokenStream>().unwrap().into_iter();
685
686 let mut key_func = "
687 /// Returns the string key used in the config file associated with this `Bind`.
688 #[inline]
689 #[must_use]
690 pub(in crate::config::controls) const fn config_file_key(self) -> &'static str
691 {
692 match self
693 {\n"
694 .to_string();
695
696 let mut label_func = "
697 /// Returns the text representing this `Bind` in UI elements.
698 #[inline]
699 #[must_use]
700 pub const fn label(self) -> &'static str
701 {
702 match self
703 {\n"
704 .to_string();
705
706 for item in match_or_panic!(iter.next_value(), TokenTree::Group(g), g).stream()
707 {
708 if let TokenTree::Ident(ident) = item
709 {
710 let ident = ident.to_string();
711 let mut chars = ident.chars();
712 let mut value = chars.next_value().to_string();
713
714 for ch in chars
715 {
716 if ch.is_ascii_uppercase()
717 {
718 value.push(' ');
719 }
720
721 value.push(ch);
722 }
723
724 label_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
725
726 value = value.to_ascii_lowercase().replace(' ', "_");
727 key_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
728 }
729 }
730
731 for func in [&mut key_func, &mut label_func]
732 {
733 func.push_str("}\n}");
734 }
735
736 format!(
737 "
738 /// The binds associated with the editor actions.
739 #[derive(Clone, Copy, Debug, PartialEq, EnumIter, EnumSize)]
740 pub enum Bind
741 {binds}
742
743 impl Bind
744 {{
745 {key_func}
746
747 {label_func}
748 }}"
749 )
750 .parse()
751 .unwrap()
752}
753
754#[inline]
758#[must_use]
759fn tools_common(stream: TokenStream, id: &str) -> [String; 2]
760{
761 let mut header_func = "
762 /// The uppercase tool name.
763 #[inline]
764 #[must_use]
765 fn header(self) -> &'static str
766 {
767 match self
768 {\n"
769 .to_string();
770
771 let mut icon_file_name_func = "
772 /// The file name of the associated icon.
773 #[inline]
774 #[must_use]
775 fn icon_file_name(self) -> &'static str
776 {
777 match self
778 {\n"
779 .to_string();
780
781 for item in stream
782 {
783 let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
784 let mut chars = ident.chars();
785
786 let mut value = chars.next_value().to_string();
788
789 for ch in chars
790 {
791 if ch.is_ascii_uppercase()
792 {
793 value.push(' ');
794 }
795
796 value.push(ch);
797 }
798
799 value = value.to_ascii_uppercase();
801 header_func.push_str(&format!("Self::{ident} => \"{value} {id}\",\n"));
802
803 value = value.to_ascii_lowercase().replace(' ', "_");
805 icon_file_name_func.push_str(&format!("Self::{ident} => \"{value}.png\",\n"));
806 }
807
808 for func in [&mut icon_file_name_func, &mut header_func]
809 {
810 func.push_str("}\n}");
811 }
812
813 [header_func, icon_file_name_func]
814}
815
816#[proc_macro_derive(ToolEnum)]
822#[must_use]
823pub fn declare_tool_enum(input: TokenStream) -> TokenStream
824{
825 let mut iter = input.into_iter();
826 assert!(enum_ident(&mut iter).to_string() == "Tool");
827 let group = match_or_panic!(iter.next_value(), TokenTree::Group(g), g);
828 let [header_func, icon_file_name_func] = tools_common(group.stream(), "TOOL");
829
830 let mut bind_func = "#[inline]
831 pub const fn bind(self) -> Bind
832 {
833 match self
834 {\n"
835 .to_string();
836
837 let mut label_func = "#[inline]
838 fn label(self) -> &'static str
839 {
840 match self
841 {\n"
842 .to_string();
843
844 for item in group.stream()
845 {
846 let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
847 let mut chars = ident.chars();
848
849 bind_func.push_str(&format!("Self::{ident} => Bind::{ident},\n"));
851
852 let mut value = chars.next_value().to_string();
854
855 for ch in chars
856 {
857 if ch.is_ascii_uppercase()
858 {
859 value.push(' ');
860 }
861
862 value.push(ch);
863 }
864
865 label_func.push_str(&format!("Self::{ident} => \"{value}\",\n"));
866 }
867
868 for func in [&mut label_func, &mut bind_func]
869 {
870 func.push_str("}\n}");
871 }
872
873 format!(
874 "
875 impl ToolInterface for Tool
876 {{
877 {label_func}
878
879 {header_func}
880
881 {icon_file_name_func}
882
883 #[inline]
884 fn tooltip_label(self, binds: &BindsKeyCodes) -> String
885 {{
886 format!(\"{{}} ({{}})\", self.label(), self.keycode_str(binds))
887 }}
888
889 #[inline]
890 fn change_conditions_met(self, change_conditions: &ChangeConditions) -> bool
891 {{
892 self.conditions_met(change_conditions)
893 }}
894
895 #[inline]
896 fn subtool(self) -> bool {{ false }}
897
898 #[inline]
899 fn index(self) -> usize {{ self as usize }}
900 }}
901
902 impl Tool
903 {{
904 {bind_func}
905 }}"
906 )
907 .parse()
908 .unwrap()
909}
910
911#[proc_macro_derive(SubToolEnum)]
917#[allow(clippy::too_many_lines)]
918#[must_use]
919pub fn subtool_enum(input: TokenStream) -> TokenStream
920{
921 let mut iter = input.into_iter();
922 assert!(enum_ident(&mut iter).to_string() == "SubTool");
923 let group = match_or_panic!(iter.next_value(), TokenTree::Group(g), g);
924 let [header_func, icon_file_name_func] = tools_common(group.stream(), "SUBTOOL");
925
926 let mut label_func = "
927 #[inline]
928 fn label(self) -> &'static str
929 {
930 match self
931 {\n"
932 .to_string();
933
934 let mut bind_func = "
935 #[inline]
936 fn bind(self) -> &'static str
937 {
938 match self
939 {\n"
940 .to_string();
941
942 let mut tool_func = "
943 #[inline]
944 const fn tool(self) -> Tool
945 {
946 match self
947 {\n"
948 .to_string();
949
950 let mut tool = String::new();
951 let mut label = String::new();
952 let mut bind = String::new();
953 let mut subtool_binds_path = std::env::current_dir().unwrap();
954 subtool_binds_path.push("docs");
955 subtool_binds_path.push("subtools binds");
956
957 for item in group.stream()
958 {
959 let ident = continue_if_no_match!(item, TokenTree::Ident(ident), ident).to_string();
960 let mut chars = ident.chars();
961 let first = chars.next_value();
962
963 for s in [&mut tool, &mut label, &mut bind]
964 {
965 s.clear();
966 }
967
968 tool.push(first);
969 bind.push(first.to_ascii_lowercase());
970
971 for ch in chars.by_ref()
972 {
973 if ch.is_ascii_uppercase()
974 {
975 label.push(ch);
976
977 bind.push('_');
978 bind.push(ch.to_ascii_lowercase());
979 break;
980 }
981
982 tool.push(ch);
983 bind.push(ch);
984 }
985
986 for ch in chars
987 {
988 if ch.is_ascii_uppercase()
989 {
990 label.push(' ');
991 bind.push('_');
992 }
993
994 label.push(ch);
995 bind.push(ch.to_ascii_lowercase());
996 }
997
998 subtool_binds_path.push(format!("{bind}.md"));
999
1000 label_func.push_str(&format!("Self::{ident} => \"{label}\",\n"));
1001 tool_func.push_str(&format!("Self::{ident} => Tool::{tool},\n"));
1002 bind_func.push_str(&format!("Self::{ident} => include_str!({:?}),\n", subtool_binds_path));
1003
1004 subtool_binds_path.pop();
1005 }
1006
1007 for func in [&mut label_func, &mut tool_func, &mut bind_func]
1008 {
1009 func.push_str("}\n}");
1010 }
1011
1012 format!(
1013 "
1014 impl ToolInterface for SubTool
1015 {{
1016 {label_func}
1017
1018 {header_func}
1019
1020 {icon_file_name_func}
1021
1022 #[inline]
1023 fn tooltip_label(self, _: &BindsKeyCodes) -> String
1024 {{
1025 format!(\"{{}} ({{}})\", self.label(), self.bind())
1026 }}
1027
1028 #[inline]
1029 fn change_conditions_met(self, change_conditions: &ChangeConditions) -> bool
1030 {{
1031 self.conditions_met(change_conditions)
1032 }}
1033
1034 #[inline]
1035 fn subtool(self) -> bool {{ true }}
1036
1037 #[inline]
1038 fn index(self) -> usize {{ self as usize }}
1039 }}
1040
1041 impl SubTool
1042 {{
1043 {tool_func}
1044
1045 {bind_func}
1046 }}
1047 "
1048 )
1049 .parse()
1050 .unwrap()
1051}
1052
1053#[allow(clippy::missing_panics_doc)]
1060#[proc_macro]
1061pub fn embedded_assets(_: TokenStream) -> TokenStream
1062{
1063 let mut path = std::env::current_dir().unwrap();
1064 path.push("src/embedded_assets/");
1065
1066 let directory = std::fs::read_dir(path).unwrap();
1068 let mut values = String::new();
1069 values.push_str("use bevy::asset::embedded_asset;\n");
1070
1071 for file in directory.into_iter().map(|p| p.unwrap().file_name())
1072 {
1073 let file_name = file.to_str().unwrap();
1074 values.push_str(&format!("bevy::asset::embedded_asset!(app, \"{file_name}\");\n"));
1075 }
1076
1077 values.parse().unwrap()
1078}
1079
1080#[allow(clippy::missing_panics_doc)]
1084#[proc_macro]
1085pub fn meshes_indexes(stream: TokenStream) -> TokenStream
1086{
1087 let mut stream = stream.into_iter();
1088 let ident = stream.next_value().to_string();
1089 is_comma(stream.next_value());
1090 let size = stream.next_value().to_string().parse::<u16>().unwrap();
1091 assert!(stream.next().is_none());
1092
1093 let mut indexes = format!(
1094 "
1095 const MAX_MESH_TRIANGLES: usize = {size};
1096 static mut {ident}: *mut [u16] = &mut [\n"
1097 );
1098
1099 for i in 1..=size
1100 {
1101 indexes.push_str(&format!("0u16, {i}, {i} + 1,\n"));
1102 }
1103
1104 indexes.push_str("];");
1105 indexes.parse().unwrap()
1106}
1107
1108#[allow(clippy::cast_precision_loss)]
1112#[allow(clippy::missing_panics_doc)]
1113#[proc_macro]
1114pub fn sin_cos_tan_array(_: TokenStream) -> TokenStream
1115{
1116 let mut array = "
1117 #[allow(clippy::approx_constant)]
1118 #[allow(clippy::unreadable_literal)]
1119 const SIN_COS_TAN_LOOKUP: [(f32, f32, f32); 361] = [\n"
1120 .to_string();
1121
1122 for a in 0..=360
1123 {
1124 let a = (a as f32).to_radians();
1125 array.push_str(&format!("({}f32, {}f32, {}f32),\n", a.sin(), a.cos(), a.tan()));
1126 }
1127
1128 array.push_str("];");
1129 array.parse().unwrap()
1130}