animation_library_editor.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  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 "editor/editor_node.h"
  32. #include "editor/editor_settings.h"
  33. #include "editor/editor_string_names.h"
  34. #include "editor/editor_undo_redo_manager.h"
  35. #include "editor/gui/editor_file_dialog.h"
  36. #include "editor/themes/editor_scale.h"
  37. #include "scene/animation/animation_mixer.h"
  38. void AnimationLibraryEditor::set_animation_mixer(Object *p_mixer) {
  39. mixer = Object::cast_to<AnimationMixer>(p_mixer);
  40. }
  41. void AnimationLibraryEditor::_add_library() {
  42. add_library_dialog->set_title(TTR("Library Name:"));
  43. add_library_name->set_text("");
  44. add_library_dialog->popup_centered();
  45. add_library_name->grab_focus();
  46. adding_animation = false;
  47. adding_animation_to_library = StringName();
  48. _add_library_validate("");
  49. }
  50. void AnimationLibraryEditor::_add_library_validate(const String &p_name) {
  51. String error;
  52. if (adding_animation) {
  53. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  54. ERR_FAIL_COND(al.is_null());
  55. if (p_name == "") {
  56. error = TTR("Animation name can't be empty.");
  57. } else if (!AnimationLibrary::is_valid_animation_name(p_name)) {
  58. error = TTR("Animation name contains invalid characters: '/', ':', ',' or '['.");
  59. } else if (al->has_animation(p_name)) {
  60. error = TTR("Animation with the same name already exists.");
  61. }
  62. } else {
  63. if (p_name == "" && mixer->has_animation_library("")) {
  64. error = TTR("Enter a library name.");
  65. } else if (!AnimationLibrary::is_valid_library_name(p_name)) {
  66. error = TTR("Library name contains invalid characters: '/', ':', ',' or '['.");
  67. } else if (mixer->has_animation_library(p_name)) {
  68. error = TTR("Library with the same name already exists.");
  69. }
  70. }
  71. if (error != "") {
  72. add_library_validate->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
  73. add_library_validate->set_text(error);
  74. add_library_dialog->get_ok_button()->set_disabled(true);
  75. } else {
  76. if (adding_animation) {
  77. add_library_validate->set_text(TTR("Animation name is valid."));
  78. } else {
  79. if (p_name == "") {
  80. add_library_validate->set_text(TTR("Global library will be created."));
  81. } else {
  82. add_library_validate->set_text(TTR("Library name is valid."));
  83. }
  84. }
  85. add_library_validate->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("success_color"), EditorStringName(Editor)));
  86. add_library_dialog->get_ok_button()->set_disabled(false);
  87. }
  88. }
  89. void AnimationLibraryEditor::_add_library_confirm() {
  90. if (adding_animation) {
  91. String anim_name = add_library_name->get_text();
  92. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  93. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  94. ERR_FAIL_COND(!al.is_valid());
  95. Ref<Animation> anim;
  96. anim.instantiate();
  97. undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name));
  98. undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim);
  99. undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
  100. undo_redo->add_do_method(this, "_update_editor", mixer);
  101. undo_redo->add_undo_method(this, "_update_editor", mixer);
  102. undo_redo->commit_action();
  103. } else {
  104. String lib_name = add_library_name->get_text();
  105. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  106. Ref<AnimationLibrary> al;
  107. al.instantiate();
  108. undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name));
  109. undo_redo->add_do_method(mixer, "add_animation_library", lib_name, al);
  110. undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
  111. undo_redo->add_do_method(this, "_update_editor", mixer);
  112. undo_redo->add_undo_method(this, "_update_editor", mixer);
  113. undo_redo->commit_action();
  114. }
  115. }
  116. void AnimationLibraryEditor::_load_library() {
  117. List<String> extensions;
  118. ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &extensions);
  119. file_dialog->set_title(TTR("Load Animation"));
  120. file_dialog->clear_filters();
  121. for (const String &K : extensions) {
  122. file_dialog->add_filter("*." + K);
  123. }
  124. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
  125. file_dialog->set_current_file("");
  126. file_dialog->popup_centered_ratio();
  127. file_dialog_action = FILE_DIALOG_ACTION_OPEN_LIBRARY;
  128. }
  129. void AnimationLibraryEditor::_file_popup_selected(int p_id) {
  130. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  131. Ref<Animation> anim;
  132. if (file_dialog_animation != StringName()) {
  133. anim = al->get_animation(file_dialog_animation);
  134. ERR_FAIL_COND(anim.is_null());
  135. }
  136. switch (p_id) {
  137. case FILE_MENU_SAVE_LIBRARY: {
  138. if (al->get_path().is_resource_file() && !FileAccess::exists(al->get_path() + ".import")) {
  139. EditorNode::get_singleton()->save_resource(al);
  140. break;
  141. }
  142. [[fallthrough]];
  143. }
  144. case FILE_MENU_SAVE_AS_LIBRARY: {
  145. // Check if we're allowed to save this
  146. {
  147. String al_path = al->get_path();
  148. if (!al_path.is_resource_file()) {
  149. int srpos = al_path.find("::");
  150. if (srpos != -1) {
  151. String base = al_path.substr(0, srpos);
  152. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  153. 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."));
  154. error_dialog->popup_centered();
  155. return;
  156. }
  157. }
  158. } else {
  159. if (FileAccess::exists(al_path + ".import")) {
  160. error_dialog->set_text(TTR("This animation library can't be saved because it was imported from another file. Make it unique first."));
  161. error_dialog->popup_centered();
  162. return;
  163. }
  164. }
  165. }
  166. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  167. file_dialog->set_title(TTR("Save Library"));
  168. if (al->get_path().is_resource_file()) {
  169. file_dialog->set_current_path(al->get_path());
  170. } else {
  171. file_dialog->set_current_file(String(file_dialog_library) + ".res");
  172. }
  173. file_dialog->clear_filters();
  174. List<String> exts;
  175. ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &exts);
  176. for (const String &K : exts) {
  177. file_dialog->add_filter("*." + K);
  178. }
  179. file_dialog->popup_centered_ratio();
  180. file_dialog_action = FILE_DIALOG_ACTION_SAVE_LIBRARY;
  181. } break;
  182. case FILE_MENU_MAKE_LIBRARY_UNIQUE: {
  183. StringName lib_name = file_dialog_library;
  184. List<StringName> animation_list;
  185. Ref<AnimationLibrary> ald = memnew(AnimationLibrary);
  186. al->get_animation_list(&animation_list);
  187. for (const StringName &animation_name : animation_list) {
  188. Ref<Animation> animation = al->get_animation(animation_name);
  189. if (EditorNode::get_singleton()->is_resource_read_only(animation)) {
  190. animation = animation->duplicate();
  191. }
  192. ald->add_animation(animation_name, animation);
  193. }
  194. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  195. undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
  196. undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
  197. undo_redo->add_do_method(mixer, "add_animation_library", lib_name, ald);
  198. undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
  199. undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
  200. undo_redo->add_do_method(this, "_update_editor", mixer);
  201. undo_redo->add_undo_method(this, "_update_editor", mixer);
  202. undo_redo->commit_action();
  203. update_tree();
  204. } break;
  205. case FILE_MENU_EDIT_LIBRARY: {
  206. EditorNode::get_singleton()->push_item(al.ptr());
  207. } break;
  208. case FILE_MENU_SAVE_ANIMATION: {
  209. if (anim->get_path().is_resource_file() && !FileAccess::exists(anim->get_path() + ".import")) {
  210. EditorNode::get_singleton()->save_resource(anim);
  211. break;
  212. }
  213. [[fallthrough]];
  214. }
  215. case FILE_MENU_SAVE_AS_ANIMATION: {
  216. // Check if we're allowed to save this
  217. {
  218. String anim_path = al->get_path();
  219. if (!anim_path.is_resource_file()) {
  220. int srpos = anim_path.find("::");
  221. if (srpos != -1) {
  222. String base = anim_path.substr(0, srpos);
  223. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  224. error_dialog->set_text(TTR("This animation can't be saved because it does not belong to the edited scene. Make it unique first."));
  225. error_dialog->popup_centered();
  226. return;
  227. }
  228. }
  229. } else {
  230. if (FileAccess::exists(anim_path + ".import")) {
  231. error_dialog->set_text(TTR("This animation can't be saved because it was imported from another file. Make it unique first."));
  232. error_dialog->popup_centered();
  233. return;
  234. }
  235. }
  236. }
  237. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
  238. file_dialog->set_title(TTR("Save Animation"));
  239. if (anim->get_path().is_resource_file()) {
  240. file_dialog->set_current_path(anim->get_path());
  241. } else {
  242. file_dialog->set_current_file(String(file_dialog_animation) + ".res");
  243. }
  244. file_dialog->clear_filters();
  245. List<String> exts;
  246. ResourceLoader::get_recognized_extensions_for_type("Animation", &exts);
  247. for (const String &K : exts) {
  248. file_dialog->add_filter("*." + K);
  249. }
  250. file_dialog->popup_centered_ratio();
  251. file_dialog_action = FILE_DIALOG_ACTION_SAVE_ANIMATION;
  252. } break;
  253. case FILE_MENU_MAKE_ANIMATION_UNIQUE: {
  254. StringName anim_name = file_dialog_animation;
  255. Ref<Animation> animd = anim->duplicate();
  256. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  257. undo_redo->create_action(vformat(TTR("Make Animation Unique: %s"), anim_name));
  258. undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
  259. undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd);
  260. undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
  261. undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
  262. undo_redo->add_do_method(this, "_update_editor", mixer);
  263. undo_redo->add_undo_method(this, "_update_editor", mixer);
  264. undo_redo->commit_action();
  265. update_tree();
  266. } break;
  267. case FILE_MENU_EDIT_ANIMATION: {
  268. EditorNode::get_singleton()->push_item(anim.ptr());
  269. } break;
  270. }
  271. }
  272. void AnimationLibraryEditor::_load_file(const String &p_path) {
  273. switch (file_dialog_action) {
  274. case FILE_DIALOG_ACTION_SAVE_LIBRARY: {
  275. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  276. String prev_path = al->get_path();
  277. EditorNode::get_singleton()->save_resource_in_path(al, p_path);
  278. if (al->get_path() != prev_path) { // Save successful.
  279. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  280. undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library));
  281. undo_redo->add_do_method(al.ptr(), "set_path", al->get_path());
  282. undo_redo->add_undo_method(al.ptr(), "set_path", prev_path);
  283. undo_redo->add_do_method(this, "_update_editor", mixer);
  284. undo_redo->add_undo_method(this, "_update_editor", mixer);
  285. undo_redo->commit_action();
  286. }
  287. } break;
  288. case FILE_DIALOG_ACTION_SAVE_ANIMATION: {
  289. Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library);
  290. Ref<Animation> anim;
  291. if (file_dialog_animation != StringName()) {
  292. anim = al->get_animation(file_dialog_animation);
  293. ERR_FAIL_COND(anim.is_null());
  294. }
  295. String prev_path = anim->get_path();
  296. EditorNode::get_singleton()->save_resource_in_path(anim, p_path);
  297. if (anim->get_path() != prev_path) { // Save successful.
  298. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  299. undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation));
  300. undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path());
  301. undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path);
  302. undo_redo->add_do_method(this, "_update_editor", mixer);
  303. undo_redo->add_undo_method(this, "_update_editor", mixer);
  304. undo_redo->commit_action();
  305. }
  306. } break;
  307. default: {
  308. }
  309. }
  310. }
  311. void AnimationLibraryEditor::_load_files(const PackedStringArray &p_paths) {
  312. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  313. bool has_created_action = false;
  314. bool show_error_diag = false;
  315. List<String> name_list;
  316. switch (file_dialog_action) {
  317. case FILE_DIALOG_ACTION_OPEN_LIBRARY: {
  318. for (const String &path : p_paths) {
  319. Ref<AnimationLibrary> al = ResourceLoader::load(path);
  320. if (al.is_null()) {
  321. show_error_diag = true;
  322. error_dialog->set_text(TTR("Some AnimationLibrary files were invalid."));
  323. continue;
  324. }
  325. List<StringName> libs;
  326. mixer->get_animation_library_list(&libs);
  327. bool is_already_added = false;
  328. for (const StringName &K : libs) {
  329. if (mixer->get_animation_library(K) == al) {
  330. // Prioritize the "invalid" error message.
  331. if (!show_error_diag) {
  332. show_error_diag = true;
  333. error_dialog->set_text(TTR("Some of the selected libraries were already added to the mixer."));
  334. }
  335. is_already_added = true;
  336. break;
  337. }
  338. }
  339. if (is_already_added) {
  340. continue;
  341. }
  342. String name = AnimationLibrary::validate_library_name(path.get_file().get_basename());
  343. int attempt = 1;
  344. while (bool(mixer->has_animation_library(name)) || name_list.find(name)) {
  345. attempt++;
  346. name = path.get_file().get_basename() + " " + itos(attempt);
  347. }
  348. name_list.push_back(name);
  349. if (!has_created_action) {
  350. has_created_action = true;
  351. undo_redo->create_action(p_paths.size() > 1 ? TTR("Add Animation Libraries") : vformat(TTR("Add Animation Library: %s"), name));
  352. }
  353. undo_redo->add_do_method(mixer, "add_animation_library", name, al);
  354. undo_redo->add_undo_method(mixer, "remove_animation_library", name);
  355. }
  356. } break;
  357. case FILE_DIALOG_ACTION_OPEN_ANIMATION: {
  358. Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library);
  359. for (const String &path : p_paths) {
  360. Ref<Animation> anim = ResourceLoader::load(path);
  361. if (anim.is_null()) {
  362. show_error_diag = true;
  363. error_dialog->set_text(TTR("Some Animation files were invalid."));
  364. continue;
  365. }
  366. List<StringName> anims;
  367. al->get_animation_list(&anims);
  368. bool is_already_added = false;
  369. for (const StringName &K : anims) {
  370. if (al->get_animation(K) == anim) {
  371. // Prioritize the "invalid" error message.
  372. if (!show_error_diag) {
  373. show_error_diag = true;
  374. error_dialog->set_text(TTR("Some of the selected animations were already added to the library."));
  375. }
  376. is_already_added = true;
  377. break;
  378. }
  379. }
  380. if (is_already_added) {
  381. continue;
  382. }
  383. String name = path.get_file().get_basename();
  384. int attempt = 1;
  385. while (al->has_animation(name) || name_list.find(name)) {
  386. attempt++;
  387. name = path.get_file().get_basename() + " " + itos(attempt);
  388. }
  389. name_list.push_back(name);
  390. if (!has_created_action) {
  391. has_created_action = true;
  392. undo_redo->create_action(p_paths.size() > 1 ? TTR("Load Animations into Library") : vformat(TTR("Load Animation into Library: %s"), name));
  393. }
  394. undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
  395. undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
  396. }
  397. } break;
  398. default: {
  399. }
  400. }
  401. if (has_created_action) {
  402. undo_redo->add_do_method(this, "_update_editor", mixer);
  403. undo_redo->add_undo_method(this, "_update_editor", mixer);
  404. undo_redo->commit_action();
  405. }
  406. if (show_error_diag) {
  407. error_dialog->popup_centered();
  408. }
  409. }
  410. void AnimationLibraryEditor::_item_renamed() {
  411. TreeItem *ti = tree->get_edited();
  412. String text = ti->get_text(0);
  413. String old_text = ti->get_metadata(0);
  414. bool restore_text = false;
  415. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  416. if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) {
  417. restore_text = true;
  418. } else {
  419. if (ti->get_parent() == tree->get_root()) {
  420. // Renamed library
  421. if (mixer->has_animation_library(text)) {
  422. restore_text = true;
  423. } else {
  424. undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text));
  425. undo_redo->add_do_method(mixer, "rename_animation_library", old_text, text);
  426. undo_redo->add_undo_method(mixer, "rename_animation_library", text, old_text);
  427. undo_redo->add_do_method(this, "_update_editor", mixer);
  428. undo_redo->add_undo_method(this, "_update_editor", mixer);
  429. updating = true;
  430. undo_redo->commit_action();
  431. updating = false;
  432. ti->set_metadata(0, text);
  433. if (text == "") {
  434. ti->set_suffix(0, TTR("[Global]"));
  435. } else {
  436. ti->set_suffix(0, "");
  437. }
  438. }
  439. } else {
  440. // Renamed anim
  441. StringName library = ti->get_parent()->get_metadata(0);
  442. Ref<AnimationLibrary> al = mixer->get_animation_library(library);
  443. if (al.is_valid()) {
  444. if (al->has_animation(text)) {
  445. restore_text = true;
  446. } else {
  447. undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text));
  448. undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text);
  449. undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text);
  450. undo_redo->add_do_method(this, "_update_editor", mixer);
  451. undo_redo->add_undo_method(this, "_update_editor", mixer);
  452. updating = true;
  453. undo_redo->commit_action();
  454. updating = false;
  455. ti->set_metadata(0, text);
  456. }
  457. } else {
  458. restore_text = true;
  459. }
  460. }
  461. }
  462. if (restore_text) {
  463. ti->set_text(0, old_text);
  464. }
  465. }
  466. void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) {
  467. if (p_item->get_parent() == tree->get_root()) {
  468. // Library
  469. StringName lib_name = p_item->get_metadata(0);
  470. Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
  471. switch (p_id) {
  472. case LIB_BUTTON_ADD: {
  473. add_library_dialog->set_title(TTR("Animation Name:"));
  474. add_library_name->set_text("");
  475. add_library_dialog->popup_centered();
  476. add_library_name->grab_focus();
  477. adding_animation = true;
  478. adding_animation_to_library = p_item->get_metadata(0);
  479. _add_library_validate("");
  480. } break;
  481. case LIB_BUTTON_LOAD: {
  482. adding_animation_to_library = p_item->get_metadata(0);
  483. List<String> extensions;
  484. ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions);
  485. file_dialog->clear_filters();
  486. for (const String &K : extensions) {
  487. file_dialog->add_filter("*." + K);
  488. }
  489. file_dialog->set_title(TTR("Load Animation"));
  490. file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
  491. file_dialog->set_current_file("");
  492. file_dialog->popup_centered_ratio();
  493. file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION;
  494. } break;
  495. case LIB_BUTTON_PASTE: {
  496. Ref<Animation> anim = EditorSettings::get_singleton()->get_resource_clipboard();
  497. if (!anim.is_valid()) {
  498. error_dialog->set_text(TTR("No animation resource in clipboard!"));
  499. error_dialog->popup_centered();
  500. return;
  501. }
  502. if (!anim->get_path().is_resource_file()) {
  503. anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
  504. }
  505. String base_name;
  506. if (anim->get_name() != "") {
  507. base_name = anim->get_name();
  508. } else {
  509. base_name = TTR("Pasted Animation");
  510. }
  511. String name = base_name;
  512. int attempt = 1;
  513. while (al->has_animation(name)) {
  514. attempt++;
  515. name = base_name + " (" + itos(attempt) + ")";
  516. }
  517. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  518. undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name));
  519. undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
  520. undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
  521. undo_redo->add_do_method(this, "_update_editor", mixer);
  522. undo_redo->add_undo_method(this, "_update_editor", mixer);
  523. undo_redo->commit_action();
  524. } break;
  525. case LIB_BUTTON_FILE: {
  526. file_popup->clear();
  527. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY);
  528. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY);
  529. file_popup->add_separator();
  530. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE);
  531. file_popup->add_separator();
  532. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY);
  533. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  534. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll();
  535. file_popup->popup(Rect2(popup_pos, Size2()));
  536. file_dialog_animation = StringName();
  537. file_dialog_library = lib_name;
  538. } break;
  539. case LIB_BUTTON_DELETE: {
  540. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  541. undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name));
  542. undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
  543. undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
  544. undo_redo->add_do_method(this, "_update_editor", mixer);
  545. undo_redo->add_undo_method(this, "_update_editor", mixer);
  546. undo_redo->commit_action();
  547. } break;
  548. }
  549. } else {
  550. // Animation
  551. StringName lib_name = p_item->get_parent()->get_metadata(0);
  552. StringName anim_name = p_item->get_metadata(0);
  553. Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
  554. Ref<Animation> anim = al->get_animation(anim_name);
  555. ERR_FAIL_COND(!anim.is_valid());
  556. switch (p_id) {
  557. case ANIM_BUTTON_COPY: {
  558. if (anim->get_name() == "") {
  559. anim->set_name(anim_name); // Keep the name around
  560. }
  561. EditorSettings::get_singleton()->set_resource_clipboard(anim);
  562. } break;
  563. case ANIM_BUTTON_FILE: {
  564. file_popup->clear();
  565. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION);
  566. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION);
  567. file_popup->add_separator();
  568. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE);
  569. file_popup->add_separator();
  570. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION);
  571. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  572. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height)) - tree->get_scroll();
  573. file_popup->popup(Rect2(popup_pos, Size2()));
  574. file_dialog_animation = anim_name;
  575. file_dialog_library = lib_name;
  576. } break;
  577. case ANIM_BUTTON_DELETE: {
  578. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  579. undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name));
  580. undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
  581. undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
  582. undo_redo->add_do_method(this, "_update_editor", mixer);
  583. undo_redo->add_undo_method(this, "_update_editor", mixer);
  584. undo_redo->commit_action();
  585. } break;
  586. }
  587. }
  588. }
  589. void AnimationLibraryEditor::update_tree() {
  590. if (updating) {
  591. return;
  592. }
  593. tree->clear();
  594. ERR_FAIL_NULL(mixer);
  595. Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
  596. TreeItem *root = tree->create_item();
  597. List<StringName> libs;
  598. mixer->get_animation_library_list(&libs);
  599. for (const StringName &K : libs) {
  600. TreeItem *libitem = tree->create_item(root);
  601. libitem->set_text(0, K);
  602. if (K == StringName()) {
  603. libitem->set_suffix(0, TTR("[Global]"));
  604. } else {
  605. libitem->set_suffix(0, "");
  606. }
  607. Ref<AnimationLibrary> al = mixer->get_animation_library(K);
  608. bool animation_library_is_foreign = false;
  609. String al_path = al->get_path();
  610. if (!al_path.is_resource_file()) {
  611. libitem->set_text(1, TTR("[built-in]"));
  612. libitem->set_tooltip_text(1, al_path);
  613. int srpos = al_path.find("::");
  614. if (srpos != -1) {
  615. String base = al_path.substr(0, srpos);
  616. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  617. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  618. animation_library_is_foreign = true;
  619. libitem->set_text(1, TTR("[foreign]"));
  620. }
  621. } else {
  622. if (FileAccess::exists(base + ".import")) {
  623. animation_library_is_foreign = true;
  624. libitem->set_text(1, TTR("[imported]"));
  625. }
  626. }
  627. }
  628. } else {
  629. if (FileAccess::exists(al_path + ".import")) {
  630. animation_library_is_foreign = true;
  631. libitem->set_text(1, TTR("[imported]"));
  632. } else {
  633. libitem->set_text(1, al_path.get_file());
  634. }
  635. }
  636. libitem->set_editable(0, true);
  637. libitem->set_metadata(0, K);
  638. libitem->set_icon(0, get_editor_theme_icon("AnimationLibrary"));
  639. libitem->add_button(0, get_editor_theme_icon("Add"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add animation to library."));
  640. 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."));
  641. libitem->add_button(0, get_editor_theme_icon("ActionPaste"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste animation to library from clipboard."));
  642. libitem->add_button(1, get_editor_theme_icon("Save"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk."));
  643. libitem->add_button(1, get_editor_theme_icon("Remove"), LIB_BUTTON_DELETE, false, TTR("Remove animation library."));
  644. libitem->set_custom_bg_color(0, ss_color);
  645. List<StringName> animations;
  646. al->get_animation_list(&animations);
  647. for (const StringName &L : animations) {
  648. TreeItem *anitem = tree->create_item(libitem);
  649. anitem->set_text(0, L);
  650. anitem->set_editable(0, !animation_library_is_foreign);
  651. anitem->set_metadata(0, L);
  652. anitem->set_icon(0, get_editor_theme_icon("Animation"));
  653. anitem->add_button(0, get_editor_theme_icon("ActionCopy"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard."));
  654. Ref<Animation> anim = al->get_animation(L);
  655. String anim_path = anim->get_path();
  656. if (!anim_path.is_resource_file()) {
  657. anitem->set_text(1, TTR("[built-in]"));
  658. anitem->set_tooltip_text(1, anim_path);
  659. int srpos = anim_path.find("::");
  660. if (srpos != -1) {
  661. String base = anim_path.substr(0, srpos);
  662. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  663. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  664. anitem->set_text(1, TTR("[foreign]"));
  665. }
  666. } else {
  667. if (FileAccess::exists(base + ".import")) {
  668. anitem->set_text(1, TTR("[imported]"));
  669. }
  670. }
  671. }
  672. } else {
  673. if (FileAccess::exists(anim_path + ".import")) {
  674. anitem->set_text(1, TTR("[imported]"));
  675. } else {
  676. anitem->set_text(1, anim_path.get_file());
  677. }
  678. }
  679. anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk."));
  680. anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library."));
  681. }
  682. }
  683. }
  684. void AnimationLibraryEditor::show_dialog() {
  685. update_tree();
  686. popup_centered_ratio(0.5);
  687. }
  688. void AnimationLibraryEditor::_notification(int p_what) {
  689. switch (p_what) {
  690. case NOTIFICATION_THEME_CHANGED: {
  691. new_library_button->set_icon(get_editor_theme_icon(SNAME("Add")));
  692. load_library_button->set_icon(get_editor_theme_icon(SNAME("Load")));
  693. }
  694. }
  695. }
  696. void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
  697. emit_signal("update_editor", p_mixer);
  698. }
  699. void AnimationLibraryEditor::shortcut_input(const Ref<InputEvent> &p_event) {
  700. const Ref<InputEventKey> k = p_event;
  701. if (k.is_valid() && k->is_pressed()) {
  702. bool handled = false;
  703. if (ED_IS_SHORTCUT("ui_undo", p_event)) {
  704. EditorNode::get_singleton()->undo();
  705. handled = true;
  706. }
  707. if (ED_IS_SHORTCUT("ui_redo", p_event)) {
  708. EditorNode::get_singleton()->redo();
  709. handled = true;
  710. }
  711. if (handled) {
  712. set_input_as_handled();
  713. }
  714. }
  715. }
  716. void AnimationLibraryEditor::_bind_methods() {
  717. ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
  718. ADD_SIGNAL(MethodInfo("update_editor"));
  719. }
  720. AnimationLibraryEditor::AnimationLibraryEditor() {
  721. set_title(TTR("Edit Animation Libraries"));
  722. set_process_shortcut_input(true);
  723. file_dialog = memnew(EditorFileDialog);
  724. add_child(file_dialog);
  725. file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file));
  726. file_dialog->connect("files_selected", callable_mp(this, &AnimationLibraryEditor::_load_files));
  727. add_library_dialog = memnew(ConfirmationDialog);
  728. VBoxContainer *dialog_vb = memnew(VBoxContainer);
  729. add_library_name = memnew(LineEdit);
  730. dialog_vb->add_child(add_library_name);
  731. add_library_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationLibraryEditor::_add_library_validate));
  732. add_child(add_library_dialog);
  733. add_library_validate = memnew(Label);
  734. dialog_vb->add_child(add_library_validate);
  735. add_library_dialog->add_child(dialog_vb);
  736. add_library_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationLibraryEditor::_add_library_confirm));
  737. add_library_dialog->register_text_enter(add_library_name);
  738. VBoxContainer *vb = memnew(VBoxContainer);
  739. HBoxContainer *hb = memnew(HBoxContainer);
  740. hb->add_spacer(true);
  741. new_library_button = memnew(Button(TTR("New Library")));
  742. new_library_button->set_tooltip_text(TTR("Create new empty animation library."));
  743. new_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_add_library));
  744. hb->add_child(new_library_button);
  745. load_library_button = memnew(Button(TTR("Load Library")));
  746. load_library_button->set_tooltip_text(TTR("Load animation library from disk."));
  747. load_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_load_library));
  748. hb->add_child(load_library_button);
  749. vb->add_child(hb);
  750. tree = memnew(Tree);
  751. vb->add_child(tree);
  752. tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  753. tree->set_columns(2);
  754. tree->set_column_titles_visible(true);
  755. tree->set_column_title(0, TTR("Resource"));
  756. tree->set_column_title(1, TTR("Storage"));
  757. tree->set_column_expand(0, true);
  758. tree->set_column_custom_minimum_width(1, EDSCALE * 250);
  759. tree->set_column_expand(1, false);
  760. tree->set_hide_root(true);
  761. tree->set_hide_folding(true);
  762. tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  763. tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
  764. tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
  765. file_popup = memnew(PopupMenu);
  766. add_child(file_popup);
  767. file_popup->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationLibraryEditor::_file_popup_selected));
  768. add_child(vb);
  769. error_dialog = memnew(AcceptDialog);
  770. error_dialog->set_title(TTR("Error:"));
  771. add_child(error_dialog);
  772. }