animation_library_editor.cpp 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075
  1. /**************************************************************************/
  2. /* animation_library_editor.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 "animation_library_editor.h"
  31. #include "core/string/print_string.h"
  32. #include "core/string/ustring.h"
  33. #include "core/templates/vector.h"
  34. #include "core/variant/variant.h"
  35. #include "editor/editor_node.h"
  36. #include "editor/editor_paths.h"
  37. #include "editor/editor_settings.h"
  38. #include "editor/editor_string_names.h"
  39. #include "editor/editor_undo_redo_manager.h"
  40. #include "editor/gui/editor_file_dialog.h"
  41. #include "editor/themes/editor_scale.h"
  42. #include "scene/animation/animation_mixer.h"
  43. void AnimationLibraryEditor::set_animation_mixer(Object *p_mixer) {
  44. mixer = Object::cast_to<AnimationMixer>(p_mixer);
  45. }
  46. void AnimationLibraryEditor::_add_library() {
  47. add_library_dialog->set_title(TTR("Library Name:"));
  48. add_library_name->set_text("");
  49. add_library_dialog->popup_centered();
  50. add_library_name->grab_focus();
  51. adding_animation = false;
  52. adding_animation_to_library = StringName();
  53. _add_library_validate("");
  54. }
  55. void AnimationLibraryEditor::_add_library_validate(const String &p_name) {
  56. String error;
  57. if (adding_animation) {
  58. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  59. ERR_FAIL_COND(al.is_null());
  60. if (p_name == "") {
  61. error = TTR("Animation name can't be empty.");
  62. } else if (!AnimationLibrary::is_valid_animation_name(p_name)) {
  63. error = TTR("Animation name contains invalid characters: '/', ':', ',' or '['.");
  64. } else if (al->has_animation(p_name)) {
  65. error = TTR("Animation with the same name already exists.");
  66. }
  67. } else {
  68. if (p_name == "" && mixer->has_animation_library("")) {
  69. error = TTR("Enter a library name.");
  70. } else if (!AnimationLibrary::is_valid_library_name(p_name)) {
  71. error = TTR("Library name contains invalid characters: '/', ':', ',' or '['.");
  72. } else if (mixer->has_animation_library(p_name)) {
  73. error = TTR("Library with the same name already exists.");
  74. }
  75. }
  76. if (error != "") {
  77. add_library_validate->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  78. add_library_validate->set_text(error);
  79. add_library_dialog->get_ok_button()->set_disabled(true);
  80. } else {
  81. if (adding_animation) {
  82. add_library_validate->set_text(TTR("Animation name is valid."));
  83. } else {
  84. if (p_name == "") {
  85. add_library_validate->set_text(TTR("Global library will be created."));
  86. } else {
  87. add_library_validate->set_text(TTR("Library name is valid."));
  88. }
  89. }
  90. add_library_validate->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
  91. add_library_dialog->get_ok_button()->set_disabled(false);
  92. }
  93. }
  94. void AnimationLibraryEditor::_add_library_confirm() {
  95. if (adding_animation) {
  96. String anim_name = add_library_name->get_text();
  97. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  98. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  99. ERR_FAIL_COND(al.is_null());
  100. Ref<Animation> anim;
  101. anim.instantiate();
  102. undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name));
  103. undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim);
  104. undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
  105. undo_redo->add_do_method(this, "_update_editor", mixer);
  106. undo_redo->add_undo_method(this, "_update_editor", mixer);
  107. undo_redo->commit_action();
  108. } else {
  109. String lib_name = add_library_name->get_text();
  110. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  111. Ref<AnimationLibrary> al;
  112. al.instantiate();
  113. undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name));
  114. undo_redo->add_do_method(mixer, "add_animation_library", lib_name, al);
  115. undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
  116. undo_redo->add_do_method(this, "_update_editor", mixer);
  117. undo_redo->add_undo_method(this, "_update_editor", mixer);
  118. undo_redo->commit_action();
  119. }
  120. }
  121. void AnimationLibraryEditor::_load_library() {
  122. List<String> extensions;
  123. ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &extensions);
  124. file_dialog->set_title(TTR("Load Animation"));
  125. file_dialog->clear_filters();
  126. for (const String &K : extensions) {
  127. file_dialog->add_filter("*." + K);
  128. }
  129. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
  130. file_dialog->set_current_file("");
  131. file_dialog->popup_centered_ratio();
  132. file_dialog_action = FILE_DIALOG_ACTION_OPEN_LIBRARY;
  133. }
  134. void AnimationLibraryEditor::_file_popup_selected(int p_id) {
  135. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  136. Ref<Animation> anim;
  137. if (file_dialog_animation != StringName()) {
  138. anim = al->get_animation(file_dialog_animation);
  139. ERR_FAIL_COND(anim.is_null());
  140. }
  141. switch (p_id) {
  142. case FILE_MENU_SAVE_LIBRARY: {
  143. if (al->get_path().is_resource_file() && !FileAccess::exists(al->get_path() + ".import")) {
  144. EditorNode::get_singleton()->save_resource(al);
  145. break;
  146. }
  147. [[fallthrough]];
  148. }
  149. case FILE_MENU_SAVE_AS_LIBRARY: {
  150. // Check if we're allowed to save this
  151. {
  152. String al_path = al->get_path();
  153. if (!al_path.is_resource_file()) {
  154. int srpos = al_path.find("::");
  155. if (srpos != -1) {
  156. String base = al_path.substr(0, srpos);
  157. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  158. error_dialog->set_text(TTR("This animation library can't be saved because it does not belong to the edited scene. Make it unique first."));
  159. error_dialog->popup_centered();
  160. return;
  161. }
  162. }
  163. } else {
  164. if (FileAccess::exists(al_path + ".import")) {
  165. error_dialog->set_text(TTR("This animation library can't be saved because it was imported from another file. Make it unique first."));
  166. error_dialog->popup_centered();
  167. return;
  168. }
  169. }
  170. }
  171. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  172. file_dialog->set_title(TTR("Save Library"));
  173. if (al->get_path().is_resource_file()) {
  174. file_dialog->set_current_path(al->get_path());
  175. } else {
  176. file_dialog->set_current_file(String(file_dialog_library) + ".res");
  177. }
  178. file_dialog->clear_filters();
  179. List<String> exts;
  180. ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &exts);
  181. for (const String &K : exts) {
  182. file_dialog->add_filter("*." + K);
  183. }
  184. file_dialog->popup_centered_ratio();
  185. file_dialog_action = FILE_DIALOG_ACTION_SAVE_LIBRARY;
  186. } break;
  187. case FILE_MENU_MAKE_LIBRARY_UNIQUE: {
  188. StringName lib_name = file_dialog_library;
  189. List<StringName> animation_list;
  190. Ref<AnimationLibrary> ald = memnew(AnimationLibrary);
  191. al->get_animation_list(&animation_list);
  192. for (const StringName &animation_name : animation_list) {
  193. Ref<Animation> animation = al->get_animation(animation_name);
  194. if (EditorNode::get_singleton()->is_resource_read_only(animation)) {
  195. animation = animation->duplicate();
  196. }
  197. ald->add_animation(animation_name, animation);
  198. }
  199. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  200. undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
  201. undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
  202. undo_redo->add_do_method(mixer, "add_animation_library", lib_name, ald);
  203. undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
  204. undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
  205. undo_redo->add_do_method(this, "_update_editor", mixer);
  206. undo_redo->add_undo_method(this, "_update_editor", mixer);
  207. undo_redo->commit_action();
  208. update_tree();
  209. } break;
  210. case FILE_MENU_EDIT_LIBRARY: {
  211. EditorNode::get_singleton()->push_item(al.ptr());
  212. } break;
  213. case FILE_MENU_SAVE_ANIMATION: {
  214. if (anim->get_path().is_resource_file() && !FileAccess::exists(anim->get_path() + ".import")) {
  215. EditorNode::get_singleton()->save_resource(anim);
  216. break;
  217. }
  218. [[fallthrough]];
  219. }
  220. case FILE_MENU_SAVE_AS_ANIMATION: {
  221. // Check if we're allowed to save this
  222. {
  223. String anim_path = al->get_path();
  224. if (!anim_path.is_resource_file()) {
  225. int srpos = anim_path.find("::");
  226. if (srpos != -1) {
  227. String base = anim_path.substr(0, srpos);
  228. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  229. error_dialog->set_text(TTR("This animation can't be saved because it does not belong to the edited scene. Make it unique first."));
  230. error_dialog->popup_centered();
  231. return;
  232. }
  233. }
  234. } else {
  235. if (FileAccess::exists(anim_path + ".import")) {
  236. error_dialog->set_text(TTR("This animation can't be saved because it was imported from another file. Make it unique first."));
  237. error_dialog->popup_centered();
  238. return;
  239. }
  240. }
  241. }
  242. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  243. file_dialog->set_title(TTR("Save Animation"));
  244. if (anim->get_path().is_resource_file()) {
  245. file_dialog->set_current_path(anim->get_path());
  246. } else {
  247. file_dialog->set_current_file(String(file_dialog_animation) + ".res");
  248. }
  249. file_dialog->clear_filters();
  250. List<String> exts;
  251. ResourceLoader::get_recognized_extensions_for_type("Animation", &exts);
  252. for (const String &K : exts) {
  253. file_dialog->add_filter("*." + K);
  254. }
  255. file_dialog->popup_centered_ratio();
  256. file_dialog_action = FILE_DIALOG_ACTION_SAVE_ANIMATION;
  257. } break;
  258. case FILE_MENU_MAKE_ANIMATION_UNIQUE: {
  259. StringName anim_name = file_dialog_animation;
  260. Ref<Animation> animd = anim->duplicate();
  261. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  262. undo_redo->create_action(vformat(TTR("Make Animation Unique: %s"), anim_name));
  263. undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
  264. undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd);
  265. undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
  266. undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
  267. undo_redo->add_do_method(this, "_update_editor", mixer);
  268. undo_redo->add_undo_method(this, "_update_editor", mixer);
  269. undo_redo->commit_action();
  270. update_tree();
  271. } break;
  272. case FILE_MENU_EDIT_ANIMATION: {
  273. EditorNode::get_singleton()->push_item(anim.ptr());
  274. } break;
  275. }
  276. }
  277. void AnimationLibraryEditor::_load_file(const String &p_path) {
  278. switch (file_dialog_action) {
  279. case FILE_DIALOG_ACTION_SAVE_LIBRARY: {
  280. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  281. String prev_path = al->get_path();
  282. EditorNode::get_singleton()->save_resource_in_path(al, p_path);
  283. if (al->get_path() != prev_path) { // Save successful.
  284. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  285. undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library));
  286. undo_redo->add_do_method(al.ptr(), "set_path", al->get_path());
  287. undo_redo->add_undo_method(al.ptr(), "set_path", prev_path);
  288. undo_redo->add_do_method(this, "_update_editor", mixer);
  289. undo_redo->add_undo_method(this, "_update_editor", mixer);
  290. undo_redo->commit_action();
  291. }
  292. } break;
  293. case FILE_DIALOG_ACTION_SAVE_ANIMATION: {
  294. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  295. Ref<Animation> anim;
  296. if (file_dialog_animation != StringName()) {
  297. anim = al->get_animation(file_dialog_animation);
  298. ERR_FAIL_COND(anim.is_null());
  299. }
  300. String prev_path = anim->get_path();
  301. EditorNode::get_singleton()->save_resource_in_path(anim, p_path);
  302. if (anim->get_path() != prev_path) { // Save successful.
  303. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  304. undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation));
  305. undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path());
  306. undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path);
  307. undo_redo->add_do_method(this, "_update_editor", mixer);
  308. undo_redo->add_undo_method(this, "_update_editor", mixer);
  309. undo_redo->commit_action();
  310. }
  311. } break;
  312. default: {
  313. }
  314. }
  315. }
  316. void AnimationLibraryEditor::_load_files(const PackedStringArray &p_paths) {
  317. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  318. bool has_created_action = false;
  319. bool show_error_diag = false;
  320. List<String> name_list;
  321. switch (file_dialog_action) {
  322. case FILE_DIALOG_ACTION_OPEN_LIBRARY: {
  323. for (const String &path : p_paths) {
  324. Ref<AnimationLibrary> al = ResourceLoader::load(path);
  325. if (al.is_null()) {
  326. show_error_diag = true;
  327. error_dialog->set_text(TTR("Some AnimationLibrary files were invalid."));
  328. continue;
  329. }
  330. List<StringName> libs;
  331. mixer->get_animation_library_list(&libs);
  332. bool is_already_added = false;
  333. for (const StringName &K : libs) {
  334. if (mixer->get_animation_library(K) == al) {
  335. // Prioritize the "invalid" error message.
  336. if (!show_error_diag) {
  337. show_error_diag = true;
  338. error_dialog->set_text(TTR("Some of the selected libraries were already added to the mixer."));
  339. }
  340. is_already_added = true;
  341. break;
  342. }
  343. }
  344. if (is_already_added) {
  345. continue;
  346. }
  347. String name = AnimationLibrary::validate_library_name(path.get_file().get_basename());
  348. int attempt = 1;
  349. while (bool(mixer->has_animation_library(name)) || name_list.find(name)) {
  350. attempt++;
  351. name = path.get_file().get_basename() + " " + itos(attempt);
  352. }
  353. name_list.push_back(name);
  354. if (!has_created_action) {
  355. has_created_action = true;
  356. undo_redo->create_action(p_paths.size() > 1 ? TTR("Add Animation Libraries") : vformat(TTR("Add Animation Library: %s"), name));
  357. }
  358. undo_redo->add_do_method(mixer, "add_animation_library", name, al);
  359. undo_redo->add_undo_method(mixer, "remove_animation_library", name);
  360. }
  361. } break;
  362. case FILE_DIALOG_ACTION_OPEN_ANIMATION: {
  363. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  364. for (const String &path : p_paths) {
  365. Ref<Animation> anim = ResourceLoader::load(path);
  366. if (anim.is_null()) {
  367. show_error_diag = true;
  368. error_dialog->set_text(TTR("Some Animation files were invalid."));
  369. continue;
  370. }
  371. List<StringName> anims;
  372. al->get_animation_list(&anims);
  373. bool is_already_added = false;
  374. for (const StringName &K : anims) {
  375. if (al->get_animation(K) == anim) {
  376. // Prioritize the "invalid" error message.
  377. if (!show_error_diag) {
  378. show_error_diag = true;
  379. error_dialog->set_text(TTR("Some of the selected animations were already added to the library."));
  380. }
  381. is_already_added = true;
  382. break;
  383. }
  384. }
  385. if (is_already_added) {
  386. continue;
  387. }
  388. String name = path.get_file().get_basename();
  389. int attempt = 1;
  390. while (al->has_animation(name) || name_list.find(name)) {
  391. attempt++;
  392. name = path.get_file().get_basename() + " " + itos(attempt);
  393. }
  394. name_list.push_back(name);
  395. if (!has_created_action) {
  396. has_created_action = true;
  397. undo_redo->create_action(p_paths.size() > 1 ? TTR("Load Animations into Library") : vformat(TTR("Load Animation into Library: %s"), name));
  398. }
  399. undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
  400. undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
  401. }
  402. } break;
  403. default: {
  404. }
  405. }
  406. if (has_created_action) {
  407. undo_redo->add_do_method(this, "_update_editor", mixer);
  408. undo_redo->add_undo_method(this, "_update_editor", mixer);
  409. undo_redo->commit_action();
  410. }
  411. if (show_error_diag) {
  412. error_dialog->popup_centered();
  413. }
  414. }
  415. void AnimationLibraryEditor::_item_renamed() {
  416. TreeItem *ti = tree->get_edited();
  417. String text = ti->get_text(0);
  418. String old_text = ti->get_metadata(0);
  419. bool restore_text = false;
  420. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  421. if (String(text).contains_char('/') || String(text).contains_char(':') || String(text).contains_char(',') || String(text).contains_char('[')) {
  422. restore_text = true;
  423. } else {
  424. if (ti->get_parent() == tree->get_root()) {
  425. // Renamed library
  426. if (mixer->has_animation_library(text)) {
  427. restore_text = true;
  428. } else {
  429. undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text));
  430. undo_redo->add_do_method(mixer, "rename_animation_library", old_text, text);
  431. undo_redo->add_undo_method(mixer, "rename_animation_library", text, old_text);
  432. undo_redo->add_do_method(this, "_update_editor", mixer);
  433. undo_redo->add_undo_method(this, "_update_editor", mixer);
  434. updating = true;
  435. undo_redo->commit_action();
  436. updating = false;
  437. ti->set_metadata(0, text);
  438. if (text == "") {
  439. ti->set_suffix(0, TTR("[Global]"));
  440. } else {
  441. ti->set_suffix(0, "");
  442. }
  443. }
  444. } else {
  445. // Renamed anim
  446. StringName library = ti->get_parent()->get_metadata(0);
  447. Ref<AnimationLibrary> al = mixer->get_animation_library(library);
  448. if (al.is_valid()) {
  449. if (al->has_animation(text)) {
  450. restore_text = true;
  451. } else {
  452. undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text));
  453. undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text);
  454. undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text);
  455. undo_redo->add_do_method(this, "_update_editor", mixer);
  456. undo_redo->add_undo_method(this, "_update_editor", mixer);
  457. updating = true;
  458. undo_redo->commit_action();
  459. updating = false;
  460. ti->set_metadata(0, text);
  461. }
  462. } else {
  463. restore_text = true;
  464. }
  465. }
  466. }
  467. if (restore_text) {
  468. ti->set_text(0, old_text);
  469. }
  470. _save_mixer_lib_folding(ti);
  471. }
  472. void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
  473. if (p_item->get_parent() == tree->get_root()) {
  474. // Library
  475. StringName lib_name = p_item->get_metadata(0);
  476. Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
  477. switch (p_id) {
  478. case LIB_BUTTON_ADD: {
  479. add_library_dialog->set_title(TTR("Animation Name:"));
  480. add_library_name->set_text("");
  481. add_library_dialog->popup_centered();
  482. add_library_name->grab_focus();
  483. adding_animation = true;
  484. adding_animation_to_library = p_item->get_metadata(0);
  485. _add_library_validate("");
  486. } break;
  487. case LIB_BUTTON_LOAD: {
  488. adding_animation_to_library = p_item->get_metadata(0);
  489. List<String> extensions;
  490. ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions);
  491. file_dialog->clear_filters();
  492. for (const String &K : extensions) {
  493. file_dialog->add_filter("*." + K);
  494. }
  495. file_dialog->set_title(TTR("Load Animation"));
  496. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
  497. file_dialog->set_current_file("");
  498. file_dialog->popup_centered_ratio();
  499. file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION;
  500. } break;
  501. case LIB_BUTTON_PASTE: {
  502. Ref<Animation> anim = EditorSettings::get_singleton()->get_resource_clipboard();
  503. if (anim.is_null()) {
  504. error_dialog->set_text(TTR("No animation resource in clipboard!"));
  505. error_dialog->popup_centered();
  506. return;
  507. }
  508. if (!anim->get_path().is_resource_file()) {
  509. anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
  510. }
  511. String base_name;
  512. if (anim->get_name() != "") {
  513. base_name = anim->get_name();
  514. } else {
  515. base_name = TTR("Pasted Animation");
  516. }
  517. String name = base_name;
  518. int attempt = 1;
  519. while (al->has_animation(name)) {
  520. attempt++;
  521. name = base_name + " (" + itos(attempt) + ")";
  522. }
  523. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  524. undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name));
  525. undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
  526. undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
  527. undo_redo->add_do_method(this, "_update_editor", mixer);
  528. undo_redo->add_undo_method(this, "_update_editor", mixer);
  529. undo_redo->commit_action();
  530. } break;
  531. case LIB_BUTTON_FILE: {
  532. file_popup->clear();
  533. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY);
  534. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY);
  535. file_popup->add_separator();
  536. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE);
  537. file_popup->add_separator();
  538. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY);
  539. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  540. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
  541. file_popup->popup(Rect2(popup_pos, Size2()));
  542. file_dialog_animation = StringName();
  543. file_dialog_library = lib_name;
  544. } break;
  545. case LIB_BUTTON_DELETE: {
  546. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  547. undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name));
  548. undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
  549. undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
  550. undo_redo->add_do_method(this, "_update_editor", mixer);
  551. undo_redo->add_undo_method(this, "_update_editor", mixer);
  552. undo_redo->commit_action();
  553. } break;
  554. }
  555. } else {
  556. // Animation
  557. StringName lib_name = p_item->get_parent()->get_metadata(0);
  558. StringName anim_name = p_item->get_metadata(0);
  559. Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
  560. Ref<Animation> anim = al->get_animation(anim_name);
  561. ERR_FAIL_COND(anim.is_null());
  562. switch (p_id) {
  563. case ANIM_BUTTON_COPY: {
  564. if (anim->get_name() == "") {
  565. anim->set_name(anim_name); // Keep the name around
  566. }
  567. EditorSettings::get_singleton()->set_resource_clipboard(anim);
  568. } break;
  569. case ANIM_BUTTON_FILE: {
  570. file_popup->clear();
  571. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION);
  572. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION);
  573. file_popup->add_separator();
  574. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE);
  575. file_popup->add_separator();
  576. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION);
  577. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  578. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
  579. file_popup->popup(Rect2(popup_pos, Size2()));
  580. file_dialog_animation = anim_name;
  581. file_dialog_library = lib_name;
  582. } break;
  583. case ANIM_BUTTON_DELETE: {
  584. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  585. undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name));
  586. undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
  587. undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
  588. undo_redo->add_do_method(this, "_update_editor", mixer);
  589. undo_redo->add_undo_method(this, "_update_editor", mixer);
  590. undo_redo->commit_action();
  591. } break;
  592. }
  593. }
  594. }
  595. void AnimationLibraryEditor::update_tree() {
  596. if (updating) {
  597. return;
  598. }
  599. tree->clear();
  600. ERR_FAIL_NULL(mixer);
  601. Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
  602. TreeItem *root = tree->create_item();
  603. List<StringName> libs;
  604. Vector<uint64_t> collapsed_lib_ids = _load_mixer_libs_folding();
  605. mixer->get_animation_library_list(&libs);
  606. for (const StringName &K : libs) {
  607. TreeItem *libitem = tree->create_item(root);
  608. libitem->set_text(0, K);
  609. if (K == StringName()) {
  610. libitem->set_suffix(0, TTR("[Global]"));
  611. } else {
  612. libitem->set_suffix(0, "");
  613. }
  614. Ref<AnimationLibrary> al = mixer->get_animation_library(K);
  615. bool animation_library_is_foreign = false;
  616. String al_path = al->get_path();
  617. if (!al_path.is_resource_file()) {
  618. libitem->set_text(1, TTR("[built-in]"));
  619. libitem->set_tooltip_text(1, al_path);
  620. int srpos = al_path.find("::");
  621. if (srpos != -1) {
  622. String base = al_path.substr(0, srpos);
  623. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  624. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  625. animation_library_is_foreign = true;
  626. libitem->set_text(1, TTR("[foreign]"));
  627. }
  628. } else {
  629. if (FileAccess::exists(base + ".import")) {
  630. animation_library_is_foreign = true;
  631. libitem->set_text(1, TTR("[imported]"));
  632. }
  633. }
  634. }
  635. } else {
  636. if (FileAccess::exists(al_path + ".import")) {
  637. animation_library_is_foreign = true;
  638. libitem->set_text(1, TTR("[imported]"));
  639. } else {
  640. libitem->set_text(1, al_path.get_file());
  641. }
  642. }
  643. libitem->set_editable(0, true);
  644. libitem->set_metadata(0, K);
  645. libitem->set_icon(0, get_editor_theme_icon("AnimationLibrary"));
  646. libitem->add_button(0, get_editor_theme_icon("Add"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add animation to library."));
  647. libitem->add_button(0, get_editor_theme_icon("Load"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library."));
  648. libitem->add_button(0, get_editor_theme_icon("ActionPaste"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste animation to library from clipboard."));
  649. libitem->add_button(1, get_editor_theme_icon("Save"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk."));
  650. libitem->add_button(1, get_editor_theme_icon("Remove"), LIB_BUTTON_DELETE, false, TTR("Remove animation library."));
  651. libitem->set_custom_bg_color(0, ss_color);
  652. List<StringName> animations;
  653. al->get_animation_list(&animations);
  654. for (const StringName &L : animations) {
  655. TreeItem *anitem = tree->create_item(libitem);
  656. anitem->set_text(0, L);
  657. anitem->set_editable(0, !animation_library_is_foreign);
  658. anitem->set_metadata(0, L);
  659. anitem->set_icon(0, get_editor_theme_icon("Animation"));
  660. anitem->add_button(0, get_editor_theme_icon("ActionCopy"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard."));
  661. Ref<Animation> anim = al->get_animation(L);
  662. String anim_path = anim->get_path();
  663. if (!anim_path.is_resource_file()) {
  664. anitem->set_text(1, TTR("[built-in]"));
  665. anitem->set_tooltip_text(1, anim_path);
  666. int srpos = anim_path.find("::");
  667. if (srpos != -1) {
  668. String base = anim_path.substr(0, srpos);
  669. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  670. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  671. anitem->set_text(1, TTR("[foreign]"));
  672. }
  673. } else {
  674. if (FileAccess::exists(base + ".import")) {
  675. anitem->set_text(1, TTR("[imported]"));
  676. }
  677. }
  678. }
  679. } else {
  680. if (FileAccess::exists(anim_path + ".import")) {
  681. anitem->set_text(1, TTR("[imported]"));
  682. } else {
  683. anitem->set_text(1, anim_path.get_file());
  684. }
  685. }
  686. anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk."));
  687. anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library."));
  688. for (const uint64_t &lib_id : collapsed_lib_ids) {
  689. Object *lib_obj = ObjectDB::get_instance(ObjectID(lib_id));
  690. AnimationLibrary *cur_lib = Object::cast_to<AnimationLibrary>(lib_obj);
  691. StringName M = mixer->get_animation_library_name(cur_lib);
  692. if (M == K) {
  693. libitem->set_collapsed_recursive(true);
  694. }
  695. }
  696. }
  697. }
  698. }
  699. void AnimationLibraryEditor::_save_mixer_lib_folding(TreeItem *p_item) {
  700. //Check if ti is a library or animation
  701. if (p_item->get_parent()->get_parent() != nullptr) {
  702. return;
  703. }
  704. Ref<ConfigFile> config;
  705. config.instantiate();
  706. String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
  707. Error err = config->load(path);
  708. if (err != OK && err != ERR_FILE_NOT_FOUND) {
  709. ERR_PRINT("Error loading lib_folding.cfg: " + itos(err));
  710. }
  711. // Get unique identifier for this scene+mixer combination
  712. String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text();
  713. PackedStringArray collapsed_lib_names;
  714. PackedStringArray collapsed_lib_ids;
  715. if (config->has_section(md)) {
  716. collapsed_lib_names = String(config->get_value(md, "folding")).split("\n");
  717. collapsed_lib_ids = String(config->get_value(md, "id")).split("\n");
  718. }
  719. String lib_name = p_item->get_text(0);
  720. // Get library reference and check validity
  721. Ref<AnimationLibrary> al;
  722. uint64_t lib_id = 0;
  723. if (mixer->has_animation_library(lib_name)) {
  724. al = mixer->get_animation_library(lib_name);
  725. ERR_FAIL_COND(al.is_null());
  726. lib_id = uint64_t(al->get_instance_id());
  727. } else {
  728. ERR_PRINT("Library not found: " + lib_name);
  729. }
  730. int at = collapsed_lib_names.find(lib_name);
  731. if (p_item->is_collapsed()) {
  732. if (at != -1) {
  733. //Entry exists and needs updating
  734. collapsed_lib_ids.set(at, String::num_int64(lib_id + INT64_MIN));
  735. } else {
  736. //Check if it's a rename
  737. int id_at = collapsed_lib_ids.find(String::num_int64(lib_id + INT64_MIN));
  738. if (id_at != -1) {
  739. //It's actually a rename
  740. collapsed_lib_names.set(id_at, lib_name);
  741. } else {
  742. //It's a new entry
  743. collapsed_lib_names.append(lib_name);
  744. collapsed_lib_ids.append(String::num_int64(lib_id + INT64_MIN));
  745. }
  746. }
  747. } else {
  748. if (at != -1) {
  749. collapsed_lib_names.remove_at(at);
  750. collapsed_lib_ids.remove_at(at);
  751. }
  752. }
  753. //Runtime IDs
  754. config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id()));
  755. config->set_value(md, "mixer", uint64_t(mixer->get_instance_id()));
  756. //Plan B recovery mechanism
  757. config->set_value(md, "mixer_signature", _get_mixer_signature());
  758. //Save folding state as text and runtime ID
  759. config->set_value(md, "folding", String("\n").join(collapsed_lib_names));
  760. config->set_value(md, "id", String("\n").join(collapsed_lib_ids));
  761. err = config->save(path);
  762. if (err != OK) {
  763. ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
  764. }
  765. }
  766. Vector<uint64_t> AnimationLibraryEditor::_load_mixer_libs_folding() {
  767. Ref<ConfigFile> config;
  768. config.instantiate();
  769. String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
  770. Error err = config->load(path);
  771. if (err != OK && err != ERR_FILE_NOT_FOUND) {
  772. ERR_PRINT("Error loading lib_folding.cfg: " + itos(err));
  773. return Vector<uint64_t>();
  774. }
  775. // Get unique identifier for this scene+mixer combination
  776. String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text();
  777. Vector<uint64_t> collapsed_lib_ids;
  778. if (config->has_section(md)) {
  779. _load_config_libs_folding(collapsed_lib_ids, config.ptr(), md);
  780. } else {
  781. //The scene/mixer combination is no longer valid and we'll try to recover
  782. uint64_t current_mixer_id = uint64_t(mixer->get_instance_id());
  783. String current_mixer_signature = _get_mixer_signature();
  784. List<String> sections;
  785. config->get_sections(&sections);
  786. for (const String &section : sections) {
  787. Variant mixer_id = config->get_value(section, "mixer");
  788. if ((mixer_id.get_type() == Variant::INT && uint64_t(mixer_id) == current_mixer_id) || config->get_value(section, "mixer_signature") == current_mixer_signature) { // Ensure value exists and is correct type
  789. // Found the mixer in a different section!
  790. _load_config_libs_folding(collapsed_lib_ids, config.ptr(), section);
  791. //Cleanup old entry and copy fold data into new one!
  792. String collapsed_lib_names_str = String(config->get_value(section, "folding"));
  793. String collapsed_lib_ids_str = String(config->get_value(section, "id"));
  794. config->erase_section(section);
  795. config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id()));
  796. config->set_value(md, "mixer", uint64_t(mixer->get_instance_id()));
  797. config->set_value(md, "mixer_signature", _get_mixer_signature());
  798. config->set_value(md, "folding", collapsed_lib_names_str);
  799. config->set_value(md, "id", collapsed_lib_ids_str);
  800. err = config->save(path);
  801. if (err != OK) {
  802. ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
  803. }
  804. break;
  805. }
  806. }
  807. }
  808. return collapsed_lib_ids;
  809. }
  810. void AnimationLibraryEditor::_load_config_libs_folding(Vector<uint64_t> &p_lib_ids, ConfigFile *p_config, String p_section) {
  811. if (uint64_t(p_config->get_value(p_section, "root", 0)) != uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())) {
  812. // Root changed - tries to match by library names
  813. PackedStringArray collapsed_lib_names = String(p_config->get_value(p_section, "folding", "")).split("\n");
  814. for (const String &lib_name : collapsed_lib_names) {
  815. if (mixer->has_animation_library(lib_name)) {
  816. p_lib_ids.append(mixer->get_animation_library(lib_name)->get_instance_id());
  817. } else {
  818. print_line("Can't find ", lib_name, " in mixer");
  819. }
  820. }
  821. } else {
  822. // Root same - uses saved instance IDs
  823. for (const String &saved_id : String(p_config->get_value(p_section, "id")).split("\n")) {
  824. p_lib_ids.append(uint64_t(saved_id.to_int() - INT64_MIN));
  825. }
  826. }
  827. }
  828. String AnimationLibraryEditor::_get_mixer_signature() const {
  829. String signature = String();
  830. // Get all libraries sorted for consistency
  831. List<StringName> libs;
  832. mixer->get_animation_library_list(&libs);
  833. libs.sort_custom<StringName::AlphCompare>();
  834. // Add libraries and their animations to signature
  835. for (const StringName &lib_name : libs) {
  836. signature += "::" + String(lib_name);
  837. Ref<AnimationLibrary> lib = mixer->get_animation_library(lib_name);
  838. if (lib.is_valid()) {
  839. List<StringName> anims;
  840. lib->get_animation_list(&anims);
  841. anims.sort_custom<StringName::AlphCompare>();
  842. for (const StringName &anim_name : anims) {
  843. signature += "," + String(anim_name);
  844. }
  845. }
  846. }
  847. return signature.md5_text();
  848. }
  849. void AnimationLibraryEditor::show_dialog() {
  850. update_tree();
  851. popup_centered_ratio(0.5);
  852. }
  853. void AnimationLibraryEditor::_notification(int p_what) {
  854. switch (p_what) {
  855. case NOTIFICATION_THEME_CHANGED: {
  856. new_library_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
  857. load_library_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
  858. }
  859. }
  860. }
  861. void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
  862. emit_signal("update_editor", p_mixer);
  863. }
  864. void AnimationLibraryEditor::shortcut_input(const Ref<InputEvent> &p_event) {
  865. const Ref<InputEventKey> k = p_event;
  866. if (k.is_valid() && k->is_pressed()) {
  867. bool handled = false;
  868. if (ED_IS_SHORTCUT("ui_undo", p_event)) {
  869. EditorNode::get_singleton()->undo();
  870. handled = true;
  871. }
  872. if (ED_IS_SHORTCUT("ui_redo", p_event)) {
  873. EditorNode::get_singleton()->redo();
  874. handled = true;
  875. }
  876. if (handled) {
  877. set_input_as_handled();
  878. }
  879. }
  880. }
  881. void AnimationLibraryEditor::_bind_methods() {
  882. ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
  883. ADD_SIGNAL(MethodInfo("update_editor"));
  884. }
  885. AnimationLibraryEditor::AnimationLibraryEditor() {
  886. set_title(TTR("Edit Animation Libraries"));
  887. set_process_shortcut_input(true);
  888. file_dialog = memnew(EditorFileDialog);
  889. add_child(file_dialog);
  890. file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file));
  891. file_dialog->connect("files_selected", callable_mp(this, &AnimationLibraryEditor::_load_files));
  892. add_library_dialog = memnew(ConfirmationDialog);
  893. VBoxContainer *dialog_vb = memnew(VBoxContainer);
  894. add_library_name = memnew(LineEdit);
  895. dialog_vb->add_child(add_library_name);
  896. add_library_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationLibraryEditor::_add_library_validate));
  897. add_child(add_library_dialog);
  898. add_library_validate = memnew(Label);
  899. dialog_vb->add_child(add_library_validate);
  900. add_library_dialog->add_child(dialog_vb);
  901. add_library_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationLibraryEditor::_add_library_confirm));
  902. add_library_dialog->register_text_enter(add_library_name);
  903. VBoxContainer *vb = memnew(VBoxContainer);
  904. HBoxContainer *hb = memnew(HBoxContainer);
  905. hb->add_spacer(true);
  906. new_library_button = memnew(Button(TTR("New Library")));
  907. new_library_button->set_tooltip_text(TTR("Create new empty animation library."));
  908. new_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_add_library));
  909. hb->add_child(new_library_button);
  910. load_library_button = memnew(Button(TTR("Load Library")));
  911. load_library_button->set_tooltip_text(TTR("Load animation library from disk."));
  912. load_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_load_library));
  913. hb->add_child(load_library_button);
  914. vb->add_child(hb);
  915. tree = memnew(Tree);
  916. vb->add_child(tree);
  917. tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  918. tree->set_columns(2);
  919. tree->set_column_titles_visible(true);
  920. tree->set_column_title(0, TTR("Resource"));
  921. tree->set_column_title(1, TTR("Storage"));
  922. tree->set_column_expand(0, true);
  923. tree->set_column_custom_minimum_width(1, EDSCALE * 250);
  924. tree->set_column_expand(1, false);
  925. tree->set_hide_root(true);
  926. tree->set_hide_folding(false);
  927. tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  928. tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
  929. tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
  930. tree->connect("item_collapsed", callable_mp(this, &AnimationLibraryEditor::_save_mixer_lib_folding));
  931. file_popup = memnew(PopupMenu);
  932. add_child(file_popup);
  933. file_popup->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationLibraryEditor::_file_popup_selected));
  934. add_child(vb);
  935. error_dialog = memnew(AcceptDialog);
  936. error_dialog->set_title(TTR("Error:"));
  937. add_child(error_dialog);
  938. }