Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/animation/animation_library_editor.cpp
20852 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/io/resource_loader.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(U"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
const Vector<String> collapsed_libs = _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
784
if (collapsed_libs.has(String(K))) {
785
libitem->set_collapsed_recursive(true);
786
}
787
}
788
}
789
790
void AnimationLibraryEditor::_save_mixer_lib_folding(TreeItem *p_item) {
791
//Check if ti is a library or animation
792
if (p_item->get_parent()->get_parent() != nullptr) {
793
return;
794
}
795
796
Ref<ConfigFile> config;
797
config.instantiate();
798
799
String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
800
Error err = config->load(path);
801
if (err != OK && err != ERR_FILE_NOT_FOUND) {
802
ERR_PRINT("Error loading lib_folding.cfg: " + itos(err));
803
}
804
805
// Get unique identifier for this scene+mixer combination.
806
const String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + String(mixer->get_path())).md5_text();
807
808
Vector<String> collapsed_libs;
809
if (config->has_section(md)) {
810
collapsed_libs = config->get_value(md, "folding");
811
}
812
813
for (int i = collapsed_libs.size() - 1; i >= 0; i--) {
814
if (!mixer->has_animation_library(collapsed_libs[i])) {
815
collapsed_libs.remove_at(i);
816
}
817
}
818
819
const String lib_name = p_item->get_text(0);
820
if (p_item->is_collapsed()) {
821
if (!collapsed_libs.has(lib_name)) {
822
collapsed_libs.append(lib_name);
823
}
824
} else {
825
collapsed_libs.erase(lib_name);
826
}
827
828
// Plan B recovery mechanism.
829
config->set_value(md, "mixer_signature", _get_mixer_signature());
830
831
// Save folding state as text and runtime ID.
832
config->set_value(md, "folding", collapsed_libs);
833
834
// Remove deprecated keys.
835
if (config->has_section_key(md, "id")) {
836
config->erase_section_key(md, "id");
837
}
838
if (config->has_section_key(md, "root")) {
839
config->erase_section_key(md, "root");
840
}
841
if (config->has_section_key(md, "mixer")) {
842
config->erase_section_key(md, "mixer");
843
}
844
845
err = config->save(path);
846
if (err != OK) {
847
ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
848
}
849
}
850
851
Vector<String> AnimationLibraryEditor::_load_mixer_libs_folding() {
852
Ref<ConfigFile> config;
853
config.instantiate();
854
855
String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg");
856
Error err = config->load(path);
857
ERR_FAIL_COND_V_MSG(err != OK && err != ERR_FILE_NOT_FOUND, Vector<String>(), "Error loading lib_folding.cfg: " + itos(err));
858
859
// Get unique identifier for this scene+mixer combination.
860
const String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + String(mixer->get_path())).md5_text();
861
862
if (!config->has_section(md)) {
863
// The scene/mixer combination is no longer valid and we'll try to recover.
864
String current_mixer_signature = _get_mixer_signature();
865
866
for (const String &section : config->get_sections()) {
867
if (config->get_value(section, "mixer_signature") == current_mixer_signature) {
868
config->set_value(md, "mixer_signature", current_mixer_signature);
869
config->set_value(md, "folding", config->get_value(section, "folding"));
870
871
config->erase_section(section);
872
873
err = config->save(path);
874
if (err != OK) {
875
ERR_PRINT("Error saving lib_folding.cfg: " + itos(err));
876
}
877
break;
878
}
879
}
880
}
881
882
return config->get_value(md, "folding");
883
}
884
885
String AnimationLibraryEditor::_get_mixer_signature() const {
886
String signature = String();
887
888
// Get all libraries sorted for consistency
889
List<StringName> libs;
890
mixer->get_animation_library_list(&libs);
891
libs.sort_custom<StringName::AlphCompare>();
892
893
// Add libraries and their animations to signature
894
for (const StringName &lib_name : libs) {
895
signature += "::" + String(lib_name);
896
Ref<AnimationLibrary> lib = mixer->get_animation_library(lib_name);
897
if (lib.is_valid()) {
898
List<StringName> anims;
899
lib->get_animation_list(&anims);
900
anims.sort_custom<StringName::AlphCompare>();
901
for (const StringName &anim_name : anims) {
902
signature += "," + String(anim_name);
903
}
904
}
905
}
906
907
return signature.md5_text();
908
}
909
910
void AnimationLibraryEditor::show_dialog() {
911
update_tree();
912
popup_centered_ratio(0.5);
913
}
914
915
void AnimationLibraryEditor::_notification(int p_what) {
916
switch (p_what) {
917
case NOTIFICATION_THEME_CHANGED: {
918
new_library_button->set_button_icon(get_editor_theme_icon(SNAME("Add")));
919
load_library_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
920
}
921
}
922
}
923
924
void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
925
emit_signal("update_editor", p_mixer);
926
}
927
928
void AnimationLibraryEditor::shortcut_input(const Ref<InputEvent> &p_event) {
929
const Ref<InputEventKey> k = p_event;
930
if (k.is_valid() && k->is_pressed()) {
931
bool handled = false;
932
933
if (ED_IS_SHORTCUT("ui_undo", p_event)) {
934
EditorNode::get_singleton()->undo();
935
handled = true;
936
}
937
938
if (ED_IS_SHORTCUT("ui_redo", p_event)) {
939
EditorNode::get_singleton()->redo();
940
handled = true;
941
}
942
943
if (handled) {
944
set_input_as_handled();
945
}
946
}
947
}
948
949
void AnimationLibraryEditor::_bind_methods() {
950
ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
951
ADD_SIGNAL(MethodInfo("update_editor"));
952
}
953
954
AnimationLibraryEditor::AnimationLibraryEditor() {
955
set_title(TTR("Edit Animation Libraries"));
956
set_process_shortcut_input(true);
957
958
file_dialog = memnew(EditorFileDialog);
959
add_child(file_dialog);
960
file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file));
961
file_dialog->connect("files_selected", callable_mp(this, &AnimationLibraryEditor::_load_files));
962
963
add_library_dialog = memnew(ConfirmationDialog);
964
VBoxContainer *dialog_vb = memnew(VBoxContainer);
965
add_library_name = memnew(LineEdit);
966
dialog_vb->add_child(add_library_name);
967
add_library_name->connect(SceneStringName(text_changed), callable_mp(this, &AnimationLibraryEditor::_add_library_validate));
968
add_child(add_library_dialog);
969
970
add_library_validate = memnew(Label);
971
dialog_vb->add_child(add_library_validate);
972
add_library_dialog->add_child(dialog_vb);
973
add_library_dialog->connect(SceneStringName(confirmed), callable_mp(this, &AnimationLibraryEditor::_add_library_confirm));
974
add_library_dialog->register_text_enter(add_library_name);
975
976
VBoxContainer *vb = memnew(VBoxContainer);
977
HBoxContainer *hb = memnew(HBoxContainer);
978
hb->add_spacer(true);
979
new_library_button = memnew(Button(TTR("New Library")));
980
new_library_button->set_tooltip_text(TTR("Create new empty animation library."));
981
new_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_add_library));
982
hb->add_child(new_library_button);
983
load_library_button = memnew(Button(TTR("Load Library")));
984
load_library_button->set_tooltip_text(TTR("Load animation library from disk."));
985
load_library_button->connect(SceneStringName(pressed), callable_mp(this, &AnimationLibraryEditor::_load_library));
986
hb->add_child(load_library_button);
987
vb->add_child(hb);
988
tree = memnew(Tree);
989
vb->add_child(tree);
990
991
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
992
tree->set_theme_type_variation("TreeTable");
993
tree->set_columns(2);
994
tree->set_column_titles_visible(true);
995
tree->set_column_title(0, TTR("Resource"));
996
tree->set_column_title(1, TTR("Storage"));
997
tree->set_column_expand(0, true);
998
tree->set_column_custom_minimum_width(1, EDSCALE * 250);
999
tree->set_column_expand(1, false);
1000
tree->set_hide_root(true);
1001
tree->set_hide_folding(false);
1002
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1003
1004
tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
1005
tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
1006
tree->connect("item_collapsed", callable_mp(this, &AnimationLibraryEditor::_save_mixer_lib_folding));
1007
1008
file_popup = memnew(PopupMenu);
1009
add_child(file_popup);
1010
file_popup->connect(SceneStringName(id_pressed), callable_mp(this, &AnimationLibraryEditor::_file_popup_selected));
1011
1012
add_child(vb);
1013
1014
error_dialog = memnew(AcceptDialog);
1015
error_dialog->set_title(TTR("Error:"));
1016
add_child(error_dialog);
1017
}
1018
1019