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