generate_icons.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { join, extname } from 'path';
  2. import fs = require('fs');
  3. const dark_colors = {
  4. '#fc7f7f': '#fc9c9c',
  5. '#8da5f3': '#a5b7f3',
  6. '#e0e0e0': '#e0e0e0',
  7. '#c38ef1': '#cea4f1',
  8. '#8eef97': '#a5efac',
  9. };
  10. const light_colors = {
  11. '#fc7f7f': '#ff5f5f',
  12. '#8da5f3': '#6d90ff',
  13. '#e0e0e0': '#4f4f4f',
  14. '#c38ef1': '#bb6dff',
  15. '#8eef97': '#29d739',
  16. };
  17. function replace_colors(colors: Object, data: String) {
  18. for (const [from, to] of Object.entries(colors)) {
  19. data = data.replace(from, to);
  20. }
  21. return data;
  22. }
  23. const iconsPath = 'editor/icons';
  24. const modulesPath = 'modules';
  25. const outputPath = 'resources/godot_icons';
  26. const godotPath = process.argv[2];
  27. const util = require('node:util');
  28. const _exec = util.promisify(require('node:child_process').exec);
  29. async function exec(command) {
  30. const { stdout, stderr } = await _exec(command);
  31. return stdout;
  32. }
  33. const git = {
  34. diff: 'git diff HEAD',
  35. check_branch: 'git rev-parse --abbrev-ref HEAD',
  36. reset: 'git reset --hard',
  37. stash_push: 'git stash push',
  38. stash_pop: 'git stash pop',
  39. checkout: 'git checkout ',
  40. checkout_4: 'git checkout master',
  41. checkout_3: 'git checkout 3.x',
  42. };
  43. function to_title_case(str) {
  44. return str.replace(
  45. /\w\S*/g,
  46. function (txt) {
  47. return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  48. }
  49. );
  50. }
  51. function get_class_list(modules) {
  52. const classes = [];
  53. const files = ['scene/register_scene_types.cpp'];
  54. modules.forEach(mod => {
  55. files.push(join(mod, 'register_types.cpp'));
  56. });
  57. const patterns = [
  58. /GDREGISTER_CLASS\((\w*)\)/,
  59. /register_class<(\w*)>/,
  60. ];
  61. files.forEach(fileName => {
  62. const file = fs.readFileSync(fileName, 'utf8');
  63. file.split('\n').forEach(line => {
  64. patterns.forEach(pattern => {
  65. const match = line.match(pattern);
  66. if (match) {
  67. classes.push(match[1] + '.svg');
  68. }
  69. });
  70. });
  71. });
  72. return classes;
  73. }
  74. function discover_modules() {
  75. const modules = [];
  76. // a valid module is a subdir of modulesPath, and contains a subdir 'icons'
  77. fs.readdirSync(modulesPath, {withFileTypes:true}).forEach(mod => {
  78. if (mod.isDirectory()) {
  79. fs.readdirSync(join(modulesPath, mod.name), {withFileTypes:true}).forEach(child => {
  80. if (child.isDirectory() && child.name == 'icons') {
  81. modules.push(join(modulesPath, mod.name));
  82. }
  83. });
  84. }
  85. });
  86. return modules;
  87. }
  88. function get_icons() {
  89. const modules = discover_modules();
  90. const classes = get_class_list(modules);
  91. const searchPaths = [iconsPath];
  92. modules.forEach(mod => {
  93. searchPaths.push(join(mod, 'icons'));
  94. });
  95. const icons = [];
  96. searchPaths.forEach(searchPath => {
  97. fs.readdirSync(searchPath).forEach(file => {
  98. if (extname(file) === '.svg') {
  99. let name = file;
  100. if (name.startsWith('icon_')) {
  101. name = name.replace('icon_', '');
  102. let parts = name.split('_');
  103. parts = parts.map(to_title_case);
  104. name = parts.join('');
  105. }
  106. if (!classes.includes(name)) {
  107. return;
  108. }
  109. const f = {
  110. name: name,
  111. contents: fs.readFileSync(join(searchPath, file), 'utf8')
  112. };
  113. icons.push(f);
  114. }
  115. });
  116. });
  117. return icons;
  118. }
  119. function ensure_paths() {
  120. const paths = [
  121. outputPath,
  122. join(outputPath, 'light'),
  123. join(outputPath, 'dark'),
  124. ];
  125. paths.forEach(path => {
  126. if (!fs.existsSync(path)) {
  127. fs.mkdirSync(path);
  128. }
  129. });
  130. }
  131. async function run() {
  132. if (godotPath == undefined) {
  133. console.log('Please provide the absolute path to your godot repo');
  134. return;
  135. }
  136. const original_cwd = process.cwd();
  137. process.chdir(godotPath);
  138. const diff = (await exec(git.diff)).trim();
  139. if (diff) {
  140. console.log('There appear to be uncommitted changes in your godot repo');
  141. console.log('Revert or stash these changes and try again');
  142. return;
  143. }
  144. const branch = (await exec(git.check_branch)).trim();
  145. console.log('Gathering Godot 3 icons...');
  146. await exec(git.checkout_3);
  147. const g3 = get_icons();
  148. console.log('Gathering Godot 4 icons...');
  149. await exec(git.checkout_4);
  150. const g4 = get_icons();
  151. await exec(git.checkout + branch);
  152. process.chdir(original_cwd);
  153. console.log(`Found ${g3.length + g4.length} icons...`);
  154. const light_icons = {};
  155. const dark_icons = {};
  156. console.log('Generating themed icons...');
  157. g3.forEach(file => {
  158. light_icons[file.name] = replace_colors(light_colors, file.contents);
  159. });
  160. g4.forEach(file => {
  161. light_icons[file.name] = replace_colors(light_colors, file.contents);
  162. });
  163. g3.forEach(file => {
  164. dark_icons[file.name] = replace_colors(dark_colors, file.contents);
  165. });
  166. g4.forEach(file => {
  167. dark_icons[file.name] = replace_colors(dark_colors, file.contents);
  168. });
  169. console.log('Ensuring output directory...');
  170. ensure_paths();
  171. console.log('Writing icons to output directory...');
  172. for (const [file, contents] of Object.entries(light_icons)) {
  173. fs.writeFileSync(join(outputPath, 'light', file), contents);
  174. }
  175. for (const [file, contents] of Object.entries(dark_icons)) {
  176. fs.writeFileSync(join(outputPath, 'dark', file), contents);
  177. }
  178. }
  179. run();