editor_asset_installer.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. /**************************************************************************/
  2. /* editor_asset_installer.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #include "editor_asset_installer.h"
  31. #include "core/io/dir_access.h"
  32. #include "core/io/file_access.h"
  33. #include "core/io/zip_io.h"
  34. #include "editor/editor_file_system.h"
  35. #include "editor/editor_node.h"
  36. #include "editor/editor_scale.h"
  37. #include "editor/editor_string_names.h"
  38. #include "editor/gui/editor_file_dialog.h"
  39. #include "editor/gui/editor_toaster.h"
  40. #include "editor/progress_dialog.h"
  41. #include "scene/gui/check_box.h"
  42. #include "scene/gui/label.h"
  43. #include "scene/gui/link_button.h"
  44. #include "scene/gui/separator.h"
  45. #include "scene/gui/split_container.h"
  46. void EditorAssetInstaller::_item_checked_cbk() {
  47. if (updating_source || !source_tree->get_edited()) {
  48. return;
  49. }
  50. updating_source = true;
  51. TreeItem *item = source_tree->get_edited();
  52. item->propagate_check(0);
  53. _update_confirm_button();
  54. _rebuild_destination_tree();
  55. updating_source = false;
  56. }
  57. void EditorAssetInstaller::_check_propagated_to_item(Object *p_obj, int p_column) {
  58. TreeItem *affected_item = Object::cast_to<TreeItem>(p_obj);
  59. if (!affected_item) {
  60. return;
  61. }
  62. Dictionary item_meta = affected_item->get_metadata(0);
  63. bool is_conflict = item_meta.get("is_conflict", false);
  64. if (is_conflict) {
  65. affected_item->set_checked(0, false);
  66. affected_item->propagate_check(0, false);
  67. }
  68. }
  69. bool EditorAssetInstaller::_is_item_checked(const String &p_source_path) const {
  70. return file_item_map.has(p_source_path) && (file_item_map[p_source_path]->is_checked(0) || file_item_map[p_source_path]->is_indeterminate(0));
  71. }
  72. void EditorAssetInstaller::open_asset(const String &p_path, bool p_autoskip_toplevel) {
  73. package_path = p_path;
  74. asset_files.clear();
  75. Ref<FileAccess> io_fa;
  76. zlib_filefunc_def io = zipio_create_io(&io_fa);
  77. unzFile pkg = unzOpen2(p_path.utf8().get_data(), &io);
  78. if (!pkg) {
  79. EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);
  80. return;
  81. }
  82. int ret = unzGoToFirstFile(pkg);
  83. while (ret == UNZ_OK) {
  84. //get filename
  85. unz_file_info info;
  86. char fname[16384];
  87. unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
  88. String source_name = String::utf8(fname);
  89. // Create intermediate directories if they aren't reported by unzip.
  90. // We are only interested in subfolders, so skip the root slash.
  91. int separator = source_name.find("/", 1);
  92. while (separator != -1) {
  93. String dir_name = source_name.substr(0, separator + 1);
  94. if (!dir_name.is_empty() && !asset_files.has(dir_name)) {
  95. asset_files.insert(dir_name);
  96. }
  97. separator = source_name.find("/", separator + 1);
  98. }
  99. if (!source_name.is_empty() && !asset_files.has(source_name)) {
  100. asset_files.insert(source_name);
  101. }
  102. ret = unzGoToNextFile(pkg);
  103. }
  104. unzClose(pkg);
  105. asset_title_label->set_text(asset_name);
  106. _check_has_toplevel();
  107. // Default to false, unless forced.
  108. skip_toplevel = p_autoskip_toplevel;
  109. skip_toplevel_check->set_block_signals(true);
  110. skip_toplevel_check->set_pressed(!skip_toplevel_check->is_disabled() && skip_toplevel);
  111. skip_toplevel_check->set_block_signals(false);
  112. _update_file_mappings();
  113. _rebuild_source_tree();
  114. _rebuild_destination_tree();
  115. popup_centered_clamped(Size2(620, 640) * EDSCALE);
  116. }
  117. void EditorAssetInstaller::_update_file_mappings() {
  118. mapped_files.clear();
  119. bool first = true;
  120. for (const String &E : asset_files) {
  121. if (first) {
  122. first = false;
  123. if (!toplevel_prefix.is_empty() && skip_toplevel) {
  124. continue;
  125. }
  126. }
  127. String path = E; // We're going to mutate it.
  128. if (!toplevel_prefix.is_empty() && skip_toplevel) {
  129. path = path.trim_prefix(toplevel_prefix);
  130. }
  131. mapped_files[E] = path;
  132. }
  133. }
  134. void EditorAssetInstaller::_rebuild_source_tree() {
  135. updating_source = true;
  136. source_tree->clear();
  137. TreeItem *root = source_tree->create_item();
  138. root->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  139. root->set_checked(0, true);
  140. root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
  141. root->set_text(0, "/");
  142. root->set_editable(0, true);
  143. file_item_map.clear();
  144. HashMap<String, TreeItem *> directory_item_map;
  145. int num_file_conflicts = 0;
  146. first_file_conflict = nullptr;
  147. for (const String &E : asset_files) {
  148. String path = E; // We're going to mutate it.
  149. bool is_directory = false;
  150. if (path.ends_with("/")) {
  151. path = path.trim_suffix("/");
  152. is_directory = true;
  153. }
  154. TreeItem *parent_item;
  155. int separator = path.rfind("/");
  156. if (separator == -1) {
  157. parent_item = root;
  158. } else {
  159. String parent_path = path.substr(0, separator);
  160. HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);
  161. ERR_CONTINUE(!I);
  162. parent_item = I->value;
  163. }
  164. TreeItem *ti;
  165. if (is_directory) {
  166. ti = _create_dir_item(source_tree, parent_item, path, directory_item_map);
  167. } else {
  168. ti = _create_file_item(source_tree, parent_item, path, &num_file_conflicts);
  169. }
  170. file_item_map[E] = ti;
  171. }
  172. _update_conflict_status(num_file_conflicts);
  173. _update_confirm_button();
  174. updating_source = false;
  175. }
  176. void EditorAssetInstaller::_update_source_tree() {
  177. int num_file_conflicts = 0;
  178. first_file_conflict = nullptr;
  179. for (const KeyValue<String, TreeItem *> &E : file_item_map) {
  180. TreeItem *ti = E.value;
  181. Dictionary item_meta = ti->get_metadata(0);
  182. if ((bool)item_meta.get("is_dir", false)) {
  183. continue;
  184. }
  185. String asset_path = item_meta.get("asset_path", "");
  186. ERR_CONTINUE(asset_path.is_empty());
  187. bool target_exists = _update_source_item_status(ti, asset_path);
  188. if (target_exists) {
  189. if (first_file_conflict == nullptr) {
  190. first_file_conflict = ti;
  191. }
  192. num_file_conflicts += 1;
  193. }
  194. item_meta["is_conflict"] = target_exists;
  195. ti->set_metadata(0, item_meta);
  196. }
  197. _update_conflict_status(num_file_conflicts);
  198. _update_confirm_button();
  199. }
  200. bool EditorAssetInstaller::_update_source_item_status(TreeItem *p_item, const String &p_path) {
  201. ERR_FAIL_COND_V(!mapped_files.has(p_path), false);
  202. String target_path = target_dir_path.path_join(mapped_files[p_path]);
  203. bool target_exists = FileAccess::exists(target_path);
  204. if (target_exists) {
  205. p_item->set_custom_color(0, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  206. p_item->set_tooltip_text(0, vformat(TTR("%s (already exists)"), target_path));
  207. p_item->set_checked(0, false);
  208. } else {
  209. p_item->clear_custom_color(0);
  210. p_item->set_tooltip_text(0, target_path);
  211. p_item->set_checked(0, true);
  212. }
  213. p_item->propagate_check(0);
  214. return target_exists;
  215. }
  216. void EditorAssetInstaller::_rebuild_destination_tree() {
  217. destination_tree->clear();
  218. TreeItem *root = destination_tree->create_item();
  219. root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
  220. root->set_text(0, target_dir_path + (target_dir_path == "res://" ? "" : "/"));
  221. HashMap<String, TreeItem *> directory_item_map;
  222. for (const KeyValue<String, String> &E : mapped_files) {
  223. if (!_is_item_checked(E.key)) {
  224. continue;
  225. }
  226. String path = E.value; // We're going to mutate it.
  227. bool is_directory = false;
  228. if (path.ends_with("/")) {
  229. path = path.trim_suffix("/");
  230. is_directory = true;
  231. }
  232. TreeItem *parent_item;
  233. int separator = path.rfind("/");
  234. if (separator == -1) {
  235. parent_item = root;
  236. } else {
  237. String parent_path = path.substr(0, separator);
  238. HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);
  239. ERR_CONTINUE(!I);
  240. parent_item = I->value;
  241. }
  242. if (is_directory) {
  243. _create_dir_item(destination_tree, parent_item, path, directory_item_map);
  244. } else {
  245. int num_file_conflicts = 0; // Don't need it, but need to pass something.
  246. _create_file_item(destination_tree, parent_item, path, &num_file_conflicts);
  247. }
  248. }
  249. }
  250. TreeItem *EditorAssetInstaller::_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map) {
  251. TreeItem *ti = p_tree->create_item(p_parent);
  252. if (p_tree == source_tree) {
  253. ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  254. ti->set_editable(0, true);
  255. ti->set_checked(0, true);
  256. ti->propagate_check(0);
  257. Dictionary meta;
  258. meta["asset_path"] = p_path + "/";
  259. meta["is_dir"] = true;
  260. meta["is_conflict"] = false;
  261. ti->set_metadata(0, meta);
  262. }
  263. ti->set_text(0, p_path.get_file() + "/");
  264. ti->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));
  265. p_item_map[p_path] = ti;
  266. return ti;
  267. }
  268. TreeItem *EditorAssetInstaller::_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts) {
  269. TreeItem *ti = p_tree->create_item(p_parent);
  270. if (p_tree == source_tree) {
  271. ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
  272. ti->set_editable(0, true);
  273. bool target_exists = _update_source_item_status(ti, p_path);
  274. if (target_exists) {
  275. if (first_file_conflict == nullptr) {
  276. first_file_conflict = ti;
  277. }
  278. *r_conflicts += 1;
  279. }
  280. Dictionary meta;
  281. meta["asset_path"] = p_path;
  282. meta["is_dir"] = false;
  283. meta["is_conflict"] = target_exists;
  284. ti->set_metadata(0, meta);
  285. }
  286. String file = p_path.get_file();
  287. String extension = file.get_extension().to_lower();
  288. if (extension_icon_map.has(extension)) {
  289. ti->set_icon(0, extension_icon_map[extension]);
  290. } else {
  291. ti->set_icon(0, generic_extension_icon);
  292. }
  293. ti->set_text(0, file);
  294. return ti;
  295. }
  296. void EditorAssetInstaller::_update_conflict_status(int p_conflicts) {
  297. if (p_conflicts >= 1) {
  298. asset_conflicts_link->set_text(vformat(TTRN("%d file conflicts with your project and won't be installed", "%d files conflict with your project and won't be installed", p_conflicts), p_conflicts));
  299. asset_conflicts_link->show();
  300. asset_conflicts_label->hide();
  301. } else {
  302. asset_conflicts_link->hide();
  303. asset_conflicts_label->show();
  304. }
  305. }
  306. void EditorAssetInstaller::_update_confirm_button() {
  307. TreeItem *root = source_tree->get_root();
  308. get_ok_button()->set_disabled(!root || (!root->is_checked(0) && !root->is_indeterminate(0)));
  309. }
  310. void EditorAssetInstaller::_toggle_source_tree(bool p_visible, bool p_scroll_to_error) {
  311. source_tree_vb->set_visible(p_visible);
  312. show_source_files_button->set_pressed_no_signal(p_visible); // To keep in sync if triggered by something else.
  313. if (p_visible) {
  314. show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
  315. } else {
  316. show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
  317. }
  318. if (p_visible && p_scroll_to_error && first_file_conflict) {
  319. source_tree->scroll_to_item(first_file_conflict, true);
  320. }
  321. }
  322. void EditorAssetInstaller::_check_has_toplevel() {
  323. // Check if the file structure has a distinct top-level directory. This is typical
  324. // for archives generated by GitHub, etc, but not for manually created ZIPs.
  325. toplevel_prefix = "";
  326. skip_toplevel_check->set_pressed(false);
  327. skip_toplevel_check->set_disabled(true);
  328. skip_toplevel_check->set_tooltip_text(TTR("This asset doesn't have a root directory, so it can't be ignored."));
  329. if (asset_files.is_empty()) {
  330. return;
  331. }
  332. String first_asset;
  333. for (const String &E : asset_files) {
  334. if (first_asset.is_empty()) { // Checking the first file/directory.
  335. if (!E.ends_with("/")) {
  336. return; // No directories in this asset.
  337. }
  338. // We will match everything else against this directory.
  339. first_asset = E;
  340. continue;
  341. }
  342. if (!E.begins_with(first_asset)) {
  343. return; // Found a file or a directory that doesn't share the same base path.
  344. }
  345. }
  346. toplevel_prefix = first_asset;
  347. skip_toplevel_check->set_disabled(false);
  348. skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
  349. }
  350. void EditorAssetInstaller::_set_skip_toplevel(bool p_checked) {
  351. if (skip_toplevel == p_checked) {
  352. return;
  353. }
  354. skip_toplevel = p_checked;
  355. _update_file_mappings();
  356. _update_source_tree();
  357. _rebuild_destination_tree();
  358. }
  359. void EditorAssetInstaller::_open_target_dir_dialog() {
  360. if (!target_dir_dialog) {
  361. target_dir_dialog = memnew(EditorFileDialog);
  362. target_dir_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
  363. target_dir_dialog->set_title(TTR("Select Install Folder"));
  364. target_dir_dialog->set_current_dir(target_dir_path);
  365. target_dir_dialog->connect("dir_selected", callable_mp(this, &EditorAssetInstaller::_target_dir_selected));
  366. add_child(target_dir_dialog);
  367. }
  368. target_dir_dialog->popup_file_dialog();
  369. }
  370. void EditorAssetInstaller::_target_dir_selected(const String &p_target_path) {
  371. if (target_dir_path == p_target_path) {
  372. return;
  373. }
  374. target_dir_path = p_target_path;
  375. _update_file_mappings();
  376. _update_source_tree();
  377. _rebuild_destination_tree();
  378. }
  379. void EditorAssetInstaller::ok_pressed() {
  380. _install_asset();
  381. }
  382. void EditorAssetInstaller::_install_asset() {
  383. Ref<FileAccess> io_fa;
  384. zlib_filefunc_def io = zipio_create_io(&io_fa);
  385. unzFile pkg = unzOpen2(package_path.utf8().get_data(), &io);
  386. if (!pkg) {
  387. EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);
  388. return;
  389. }
  390. Vector<String> failed_files;
  391. int ret = unzGoToFirstFile(pkg);
  392. ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Assets"), file_item_map.size());
  393. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
  394. for (int idx = 0; ret == UNZ_OK; ret = unzGoToNextFile(pkg), idx++) {
  395. unz_file_info info;
  396. char fname[16384];
  397. ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);
  398. if (ret != UNZ_OK) {
  399. break;
  400. }
  401. String source_name = String::utf8(fname);
  402. if (!_is_item_checked(source_name)) {
  403. continue;
  404. }
  405. HashMap<String, String>::Iterator E = mapped_files.find(source_name);
  406. if (!E) {
  407. continue; // No remapped path means we don't want it; most likely the root.
  408. }
  409. String target_path = target_dir_path.path_join(E->value);
  410. Dictionary asset_meta = file_item_map[source_name]->get_metadata(0);
  411. bool is_dir = asset_meta.get("is_dir", false);
  412. if (is_dir) {
  413. if (target_path.ends_with("/")) {
  414. target_path = target_path.substr(0, target_path.length() - 1);
  415. }
  416. da->make_dir_recursive(target_path);
  417. } else {
  418. Vector<uint8_t> uncomp_data;
  419. uncomp_data.resize(info.uncompressed_size);
  420. unzOpenCurrentFile(pkg);
  421. unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());
  422. unzCloseCurrentFile(pkg);
  423. // Ensure that the target folder exists.
  424. da->make_dir_recursive(target_path.get_base_dir());
  425. Ref<FileAccess> f = FileAccess::open(target_path, FileAccess::WRITE);
  426. if (f.is_valid()) {
  427. f->store_buffer(uncomp_data.ptr(), uncomp_data.size());
  428. } else {
  429. failed_files.push_back(target_path);
  430. }
  431. ProgressDialog::get_singleton()->task_step("uncompress", target_path, idx);
  432. }
  433. }
  434. ProgressDialog::get_singleton()->end_task("uncompress");
  435. unzClose(pkg);
  436. if (failed_files.size()) {
  437. String msg = vformat(TTR("The following files failed extraction from asset \"%s\":"), asset_name) + "\n\n";
  438. for (int i = 0; i < failed_files.size(); i++) {
  439. if (i > 10) {
  440. msg += "\n" + vformat(TTR("(and %s more files)"), itos(failed_files.size() - i));
  441. break;
  442. }
  443. msg += "\n" + failed_files[i];
  444. }
  445. if (EditorNode::get_singleton() != nullptr) {
  446. EditorNode::get_singleton()->show_warning(msg);
  447. }
  448. } else {
  449. if (EditorNode::get_singleton() != nullptr) {
  450. EditorNode::get_singleton()->show_warning(vformat(TTR("Asset \"%s\" installed successfully!"), asset_name), TTR("Success!"));
  451. }
  452. }
  453. EditorFileSystem::get_singleton()->scan_changes();
  454. }
  455. void EditorAssetInstaller::set_asset_name(const String &p_asset_name) {
  456. asset_name = p_asset_name;
  457. }
  458. String EditorAssetInstaller::get_asset_name() const {
  459. return asset_name;
  460. }
  461. void EditorAssetInstaller::_notification(int p_what) {
  462. switch (p_what) {
  463. case NOTIFICATION_THEME_CHANGED: {
  464. if (show_source_files_button->is_pressed()) {
  465. show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Back")));
  466. } else {
  467. show_source_files_button->set_icon(get_editor_theme_icon(SNAME("Forward")));
  468. }
  469. asset_conflicts_link->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  470. generic_extension_icon = get_editor_theme_icon(SNAME("Object"));
  471. extension_icon_map.clear();
  472. {
  473. extension_icon_map["bmp"] = get_editor_theme_icon(SNAME("ImageTexture"));
  474. extension_icon_map["dds"] = get_editor_theme_icon(SNAME("ImageTexture"));
  475. extension_icon_map["exr"] = get_editor_theme_icon(SNAME("ImageTexture"));
  476. extension_icon_map["hdr"] = get_editor_theme_icon(SNAME("ImageTexture"));
  477. extension_icon_map["jpg"] = get_editor_theme_icon(SNAME("ImageTexture"));
  478. extension_icon_map["jpeg"] = get_editor_theme_icon(SNAME("ImageTexture"));
  479. extension_icon_map["png"] = get_editor_theme_icon(SNAME("ImageTexture"));
  480. extension_icon_map["svg"] = get_editor_theme_icon(SNAME("ImageTexture"));
  481. extension_icon_map["tga"] = get_editor_theme_icon(SNAME("ImageTexture"));
  482. extension_icon_map["webp"] = get_editor_theme_icon(SNAME("ImageTexture"));
  483. extension_icon_map["wav"] = get_editor_theme_icon(SNAME("AudioStreamWAV"));
  484. extension_icon_map["ogg"] = get_editor_theme_icon(SNAME("AudioStreamOggVorbis"));
  485. extension_icon_map["mp3"] = get_editor_theme_icon(SNAME("AudioStreamMP3"));
  486. extension_icon_map["scn"] = get_editor_theme_icon(SNAME("PackedScene"));
  487. extension_icon_map["tscn"] = get_editor_theme_icon(SNAME("PackedScene"));
  488. extension_icon_map["escn"] = get_editor_theme_icon(SNAME("PackedScene"));
  489. extension_icon_map["dae"] = get_editor_theme_icon(SNAME("PackedScene"));
  490. extension_icon_map["gltf"] = get_editor_theme_icon(SNAME("PackedScene"));
  491. extension_icon_map["glb"] = get_editor_theme_icon(SNAME("PackedScene"));
  492. extension_icon_map["gdshader"] = get_editor_theme_icon(SNAME("Shader"));
  493. extension_icon_map["gdshaderinc"] = get_editor_theme_icon(SNAME("TextFile"));
  494. extension_icon_map["gd"] = get_editor_theme_icon(SNAME("GDScript"));
  495. if (Engine::get_singleton()->has_singleton("GodotSharp")) {
  496. extension_icon_map["cs"] = get_editor_theme_icon(SNAME("CSharpScript"));
  497. } else {
  498. // Mark C# support as unavailable.
  499. extension_icon_map["cs"] = get_editor_theme_icon(SNAME("ImportFail"));
  500. }
  501. extension_icon_map["res"] = get_editor_theme_icon(SNAME("Resource"));
  502. extension_icon_map["tres"] = get_editor_theme_icon(SNAME("Resource"));
  503. extension_icon_map["atlastex"] = get_editor_theme_icon(SNAME("AtlasTexture"));
  504. // By default, OBJ files are imported as Mesh resources rather than PackedScenes.
  505. extension_icon_map["obj"] = get_editor_theme_icon(SNAME("MeshItem"));
  506. extension_icon_map["txt"] = get_editor_theme_icon(SNAME("TextFile"));
  507. extension_icon_map["md"] = get_editor_theme_icon(SNAME("TextFile"));
  508. extension_icon_map["rst"] = get_editor_theme_icon(SNAME("TextFile"));
  509. extension_icon_map["json"] = get_editor_theme_icon(SNAME("TextFile"));
  510. extension_icon_map["yml"] = get_editor_theme_icon(SNAME("TextFile"));
  511. extension_icon_map["yaml"] = get_editor_theme_icon(SNAME("TextFile"));
  512. extension_icon_map["toml"] = get_editor_theme_icon(SNAME("TextFile"));
  513. extension_icon_map["cfg"] = get_editor_theme_icon(SNAME("TextFile"));
  514. extension_icon_map["ini"] = get_editor_theme_icon(SNAME("TextFile"));
  515. }
  516. } break;
  517. }
  518. }
  519. void EditorAssetInstaller::_bind_methods() {
  520. }
  521. EditorAssetInstaller::EditorAssetInstaller() {
  522. VBoxContainer *vb = memnew(VBoxContainer);
  523. add_child(vb);
  524. // Status bar.
  525. HBoxContainer *asset_status = memnew(HBoxContainer);
  526. vb->add_child(asset_status);
  527. Label *asset_label = memnew(Label);
  528. asset_label->set_text(TTR("Asset:"));
  529. asset_label->set_theme_type_variation("HeaderSmall");
  530. asset_status->add_child(asset_label);
  531. asset_title_label = memnew(Label);
  532. asset_status->add_child(asset_title_label);
  533. // File remapping controls.
  534. HBoxContainer *remapping_tools = memnew(HBoxContainer);
  535. vb->add_child(remapping_tools);
  536. show_source_files_button = memnew(Button);
  537. show_source_files_button->set_toggle_mode(true);
  538. show_source_files_button->set_tooltip_text(TTR("Open the list of the asset contents and select which files to install."));
  539. remapping_tools->add_child(show_source_files_button);
  540. show_source_files_button->connect("toggled", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));
  541. Button *target_dir_button = memnew(Button);
  542. target_dir_button->set_text(TTR("Change Install Folder"));
  543. target_dir_button->set_tooltip_text(TTR("Change the folder where the contents of the asset are going to be installed."));
  544. remapping_tools->add_child(target_dir_button);
  545. target_dir_button->connect("pressed", callable_mp(this, &EditorAssetInstaller::_open_target_dir_dialog));
  546. remapping_tools->add_child(memnew(VSeparator));
  547. skip_toplevel_check = memnew(CheckBox);
  548. skip_toplevel_check->set_text(TTR("Ignore asset root"));
  549. skip_toplevel_check->set_tooltip_text(TTR("Ignore the root directory when extracting files."));
  550. skip_toplevel_check->connect("toggled", callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));
  551. remapping_tools->add_child(skip_toplevel_check);
  552. remapping_tools->add_spacer();
  553. asset_conflicts_label = memnew(Label);
  554. asset_conflicts_label->set_theme_type_variation("HeaderSmall");
  555. asset_conflicts_label->set_text(TTR("No files conflict with your project"));
  556. remapping_tools->add_child(asset_conflicts_label);
  557. asset_conflicts_link = memnew(LinkButton);
  558. asset_conflicts_link->set_theme_type_variation("HeaderSmallLink");
  559. asset_conflicts_link->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
  560. asset_conflicts_link->set_tooltip_text(TTR("Show contents of the asset and conflicting files."));
  561. asset_conflicts_link->set_visible(false);
  562. remapping_tools->add_child(asset_conflicts_link);
  563. asset_conflicts_link->connect("pressed", callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(true, true));
  564. // File hierarchy trees.
  565. HSplitContainer *tree_split = memnew(HSplitContainer);
  566. tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  567. vb->add_child(tree_split);
  568. source_tree_vb = memnew(VBoxContainer);
  569. source_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  570. source_tree_vb->set_visible(show_source_files_button->is_pressed());
  571. tree_split->add_child(source_tree_vb);
  572. Label *source_tree_label = memnew(Label);
  573. source_tree_label->set_text(TTR("Contents of the asset:"));
  574. source_tree_label->set_theme_type_variation("HeaderSmall");
  575. source_tree_vb->add_child(source_tree_label);
  576. source_tree = memnew(Tree);
  577. source_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  578. source_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
  579. source_tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item));
  580. source_tree_vb->add_child(source_tree);
  581. VBoxContainer *destination_tree_vb = memnew(VBoxContainer);
  582. destination_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
  583. tree_split->add_child(destination_tree_vb);
  584. Label *destination_tree_label = memnew(Label);
  585. destination_tree_label->set_text(TTR("Installation preview:"));
  586. destination_tree_label->set_theme_type_variation("HeaderSmall");
  587. destination_tree_vb->add_child(destination_tree_label);
  588. destination_tree = memnew(Tree);
  589. destination_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  590. destination_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));
  591. destination_tree->connect("check_propagated_to_item", callable_mp(this, &EditorAssetInstaller::_check_propagated_to_item));
  592. destination_tree_vb->add_child(destination_tree);
  593. // Dialog configuration.
  594. set_title(TTR("Configure Asset Before Installing"));
  595. set_ok_button_text(TTR("Install"));
  596. set_hide_on_ok(true);
  597. }