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