animation_library_editor.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  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("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("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. anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
  503. String base_name;
  504. if (anim->get_name() != "") {
  505. base_name = anim->get_name();
  506. } else {
  507. base_name = TTR("Pasted Animation");
  508. }
  509. String name = base_name;
  510. int attempt = 1;
  511. while (al->has_animation(name)) {
  512. attempt++;
  513. name = base_name + " (" + itos(attempt) + ")";
  514. }
  515. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  516. undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name));
  517. undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
  518. undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
  519. undo_redo->add_do_method(this, "_update_editor", mixer);
  520. undo_redo->add_undo_method(this, "_update_editor", mixer);
  521. undo_redo->commit_action();
  522. } break;
  523. case LIB_BUTTON_FILE: {
  524. file_popup->clear();
  525. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY);
  526. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY);
  527. file_popup->add_separator();
  528. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE);
  529. file_popup->add_separator();
  530. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY);
  531. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  532. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
  533. file_popup->popup(Rect2(popup_pos, Size2()));
  534. file_dialog_animation = StringName();
  535. file_dialog_library = lib_name;
  536. } break;
  537. case LIB_BUTTON_DELETE: {
  538. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  539. undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name));
  540. undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
  541. undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
  542. undo_redo->add_do_method(this, "_update_editor", mixer);
  543. undo_redo->add_undo_method(this, "_update_editor", mixer);
  544. undo_redo->commit_action();
  545. } break;
  546. }
  547. } else {
  548. // Animation
  549. StringName lib_name = p_item->get_parent()->get_metadata(0);
  550. StringName anim_name = p_item->get_metadata(0);
  551. Ref<AnimationLibrary> al = mixer->get_animation_library(lib_name);
  552. Ref<Animation> anim = al->get_animation(anim_name);
  553. ERR_FAIL_COND(!anim.is_valid());
  554. switch (p_id) {
  555. case ANIM_BUTTON_COPY: {
  556. if (anim->get_name() == "") {
  557. anim->set_name(anim_name); // Keep the name around
  558. }
  559. EditorSettings::get_singleton()->set_resource_clipboard(anim);
  560. } break;
  561. case ANIM_BUTTON_FILE: {
  562. file_popup->clear();
  563. file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION);
  564. file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION);
  565. file_popup->add_separator();
  566. file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE);
  567. file_popup->add_separator();
  568. file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION);
  569. Rect2 pos = tree->get_item_rect(p_item, 1, 0);
  570. Vector2 popup_pos = tree->get_screen_transform().xform(pos.position + Vector2(0, pos.size.height));
  571. file_popup->popup(Rect2(popup_pos, Size2()));
  572. file_dialog_animation = anim_name;
  573. file_dialog_library = lib_name;
  574. } break;
  575. case ANIM_BUTTON_DELETE: {
  576. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
  577. undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name));
  578. undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
  579. undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
  580. undo_redo->add_do_method(this, "_update_editor", mixer);
  581. undo_redo->add_undo_method(this, "_update_editor", mixer);
  582. undo_redo->commit_action();
  583. } break;
  584. }
  585. }
  586. }
  587. void AnimationLibraryEditor::update_tree() {
  588. if (updating) {
  589. return;
  590. }
  591. tree->clear();
  592. ERR_FAIL_NULL(mixer);
  593. Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
  594. TreeItem *root = tree->create_item();
  595. List<StringName> libs;
  596. mixer->get_animation_library_list(&libs);
  597. for (const StringName &K : libs) {
  598. TreeItem *libitem = tree->create_item(root);
  599. libitem->set_text(0, K);
  600. if (K == StringName()) {
  601. libitem->set_suffix(0, TTR("[Global]"));
  602. } else {
  603. libitem->set_suffix(0, "");
  604. }
  605. Ref<AnimationLibrary> al = mixer->get_animation_library(K);
  606. bool animation_library_is_foreign = false;
  607. String al_path = al->get_path();
  608. if (!al_path.is_resource_file()) {
  609. libitem->set_text(1, TTR("[built-in]"));
  610. libitem->set_tooltip_text(1, al_path);
  611. int srpos = al_path.find("::");
  612. if (srpos != -1) {
  613. String base = al_path.substr(0, srpos);
  614. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  615. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  616. animation_library_is_foreign = true;
  617. libitem->set_text(1, TTR("[foreign]"));
  618. }
  619. } else {
  620. if (FileAccess::exists(base + ".import")) {
  621. animation_library_is_foreign = true;
  622. libitem->set_text(1, TTR("[imported]"));
  623. }
  624. }
  625. }
  626. } else {
  627. if (FileAccess::exists(al_path + ".import")) {
  628. animation_library_is_foreign = true;
  629. libitem->set_text(1, TTR("[imported]"));
  630. } else {
  631. libitem->set_text(1, al_path.get_file());
  632. }
  633. }
  634. libitem->set_editable(0, true);
  635. libitem->set_metadata(0, K);
  636. libitem->set_icon(0, get_editor_theme_icon("AnimationLibrary"));
  637. libitem->add_button(0, get_editor_theme_icon("Add"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add animation to library."));
  638. 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."));
  639. libitem->add_button(0, get_editor_theme_icon("ActionPaste"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste animation to library from clipboard."));
  640. libitem->add_button(1, get_editor_theme_icon("Save"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk."));
  641. libitem->add_button(1, get_editor_theme_icon("Remove"), LIB_BUTTON_DELETE, false, TTR("Remove animation library."));
  642. libitem->set_custom_bg_color(0, ss_color);
  643. List<StringName> animations;
  644. al->get_animation_list(&animations);
  645. for (const StringName &L : animations) {
  646. TreeItem *anitem = tree->create_item(libitem);
  647. anitem->set_text(0, L);
  648. anitem->set_editable(0, !animation_library_is_foreign);
  649. anitem->set_metadata(0, L);
  650. anitem->set_icon(0, get_editor_theme_icon("Animation"));
  651. anitem->add_button(0, get_editor_theme_icon("ActionCopy"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard."));
  652. Ref<Animation> anim = al->get_animation(L);
  653. String anim_path = anim->get_path();
  654. if (!anim_path.is_resource_file()) {
  655. anitem->set_text(1, TTR("[built-in]"));
  656. anitem->set_tooltip_text(1, anim_path);
  657. int srpos = anim_path.find("::");
  658. if (srpos != -1) {
  659. String base = anim_path.substr(0, srpos);
  660. if (ResourceLoader::get_resource_type(base) == "PackedScene") {
  661. if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
  662. anitem->set_text(1, TTR("[foreign]"));
  663. }
  664. } else {
  665. if (FileAccess::exists(base + ".import")) {
  666. anitem->set_text(1, TTR("[imported]"));
  667. }
  668. }
  669. }
  670. } else {
  671. if (FileAccess::exists(anim_path + ".import")) {
  672. anitem->set_text(1, TTR("[imported]"));
  673. } else {
  674. anitem->set_text(1, anim_path.get_file());
  675. }
  676. }
  677. anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk."));
  678. anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library."));
  679. }
  680. }
  681. }
  682. void AnimationLibraryEditor::show_dialog() {
  683. update_tree();
  684. popup_centered_ratio(0.5);
  685. }
  686. void AnimationLibraryEditor::_notification(int p_what) {
  687. switch (p_what) {
  688. case NOTIFICATION_THEME_CHANGED: {
  689. new_library_button->set_icon(get_editor_theme_icon(SNAME("Add")));
  690. load_library_button->set_icon(get_editor_theme_icon(SNAME("Load")));
  691. }
  692. }
  693. }
  694. void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
  695. emit_signal("update_editor", p_mixer);
  696. }
  697. void AnimationLibraryEditor::_bind_methods() {
  698. ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
  699. ADD_SIGNAL(MethodInfo("update_editor"));
  700. }
  701. AnimationLibraryEditor::AnimationLibraryEditor() {
  702. set_title(TTR("Edit Animation Libraries"));
  703. file_dialog = memnew(EditorFileDialog);
  704. add_child(file_dialog);
  705. file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file));
  706. file_dialog->connect("files_selected", callable_mp(this, &AnimationLibraryEditor::_load_files));
  707. add_library_dialog = memnew(ConfirmationDialog);
  708. VBoxContainer *dialog_vb = memnew(VBoxContainer);
  709. add_library_name = memnew(LineEdit);
  710. dialog_vb->add_child(add_library_name);
  711. add_library_name->connect("text_changed", callable_mp(this, &AnimationLibraryEditor::_add_library_validate));
  712. add_child(add_library_dialog);
  713. add_library_validate = memnew(Label);
  714. dialog_vb->add_child(add_library_validate);
  715. add_library_dialog->add_child(dialog_vb);
  716. add_library_dialog->connect("confirmed", callable_mp(this, &AnimationLibraryEditor::_add_library_confirm));
  717. add_library_dialog->register_text_enter(add_library_name);
  718. VBoxContainer *vb = memnew(VBoxContainer);
  719. HBoxContainer *hb = memnew(HBoxContainer);
  720. hb->add_spacer(true);
  721. new_library_button = memnew(Button(TTR("New Library")));
  722. new_library_button->set_tooltip_text(TTR("Create new empty animation library."));
  723. new_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_add_library));
  724. hb->add_child(new_library_button);
  725. load_library_button = memnew(Button(TTR("Load Library")));
  726. load_library_button->set_tooltip_text(TTR("Load animation library from disk."));
  727. load_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_load_library));
  728. hb->add_child(load_library_button);
  729. vb->add_child(hb);
  730. tree = memnew(Tree);
  731. vb->add_child(tree);
  732. tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
  733. tree->set_columns(2);
  734. tree->set_column_titles_visible(true);
  735. tree->set_column_title(0, TTR("Resource"));
  736. tree->set_column_title(1, TTR("Storage"));
  737. tree->set_column_expand(0, true);
  738. tree->set_column_custom_minimum_width(1, EDSCALE * 250);
  739. tree->set_column_expand(1, false);
  740. tree->set_hide_root(true);
  741. tree->set_hide_folding(true);
  742. tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
  743. tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
  744. tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
  745. file_popup = memnew(PopupMenu);
  746. add_child(file_popup);
  747. file_popup->connect("id_pressed", callable_mp(this, &AnimationLibraryEditor::_file_popup_selected));
  748. add_child(vb);
  749. error_dialog = memnew(AcceptDialog);
  750. error_dialog->set_title(TTR("Error:"));
  751. add_child(error_dialog);
  752. }