diff --git a/doc/classes/AnimationLibrary.xml b/doc/classes/AnimationLibrary.xml
new file mode 100644
index 00000000000..0a731edadd2
--- /dev/null
+++ b/doc/classes/AnimationLibrary.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index b1d04ce1f21..625cf3c47c9 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -14,12 +14,11 @@
https://godotengine.org/asset-library/asset/678
-
+
-
+
- Adds [code]animation[/code] to the player accessible with the key [code]name[/code].
@@ -63,6 +62,12 @@
Returns the name of [code]animation[/code] or an empty string if not found.
+
+
+
+
+
+
@@ -70,6 +75,17 @@
Returns the [Animation] with key [code]name[/code] or [code]null[/code] if not found.
+
+
+
+
+
+
+
+
+
+
+
@@ -103,6 +119,12 @@
Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [code]name[/code].
+
+
+
+
+
+
@@ -138,19 +160,17 @@
[b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow.
-
+
- Removes the animation with key [code]name[/code].
-
+
- Renames an existing animation with key [code]name[/code] to [code]newname[/code].
diff --git a/doc/classes/OptionButton.xml b/doc/classes/OptionButton.xml
index 25e41116a2e..b7145ab9239 100644
--- a/doc/classes/OptionButton.xml
+++ b/doc/classes/OptionButton.xml
@@ -30,8 +30,9 @@
+
- Adds a separator to the list of items. Separators help to group items. Separator also takes up an index and is appended at the end.
+ Adds a separator to the list of items. Separators help to group items, and can optionally be given a [code]text[/code] header. A separator also gets an index assigned, and is appended at the end of the item list.
@@ -89,6 +90,12 @@
[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member Window.visible] property.
+
+
+
+
+
+
@@ -101,6 +108,11 @@
Gets the metadata of the selected item. Metadata for items can be set using [method set_item_metadata].
+
+
+
+
+
@@ -108,6 +120,12 @@
Returns [code]true[/code] if the item at index [code]idx[/code] is disabled.
+
+
+
+
+
+
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index b8c39bee499..5abec4b437b 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -179,8 +179,9 @@
+
- Returns the rectangle area for the specified [TreeItem]. If [code]column[/code] is specified, only get the position and size of that column, otherwise get the rectangle containing all columns.
+ Returns the rectangle area for the specified [TreeItem]. If [code]column[/code] is specified, only get the position and size of that column, otherwise get the rectangle containing all columns. If a button index is specified, the rectangle of that button will be returned.
diff --git a/editor/icons/AnimationLibrary.svg b/editor/icons/AnimationLibrary.svg
new file mode 100644
index 00000000000..0bac67d302e
--- /dev/null
+++ b/editor/icons/AnimationLibrary.svg
@@ -0,0 +1 @@
+
diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp
index 69fa64c24cc..97fc33ad25d 100644
--- a/editor/import/editor_import_collada.cpp
+++ b/editor/import/editor_import_collada.cpp
@@ -1801,7 +1801,14 @@ Node *EditorSceneFormatImporterCollada::import_scene(const String &p_path, uint3
name = state.animations[i]->get_name();
}
- ap->add_animation(name, state.animations[i]);
+ Ref library;
+ if (!ap->has_animation_library("")) {
+ library.instantiate();
+ ap->add_animation_library("", library);
+ } else {
+ library = ap->get_animation_library("");
+ }
+ library->add_animation(name, state.animations[i]);
}
state.scene->add_child(ap, true);
ap->set_owner(state.scene);
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index ad9537f85b7..bfa71bfda42 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -473,7 +473,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map[set_loop_mode(Animation::LoopMode::LOOP_LINEAR);
animname = _fixstr(animname, loop_strings[i]);
- ap->rename_animation(E, animname);
+
+ Ref library = ap->get_animation_library(ap->find_animation_library(anim));
+ library->rename_animation(E, animname);
}
}
}
@@ -1019,7 +1021,8 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, Map][ saved_anim = _save_animation_to_file(anim, save, path, keep_custom);
if (saved_anim != anim) {
- ap->add_animation(name, saved_anim); //replace
+ Ref al = ap->get_animation_library(ap->find_animation_library(anim));
+ al->add_animation(name, saved_anim); //replace
}
}
}
@@ -1109,6 +1112,7 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
}
Ref default_anim = anim->get_animation("default");
+ Ref al = anim->get_animation_library(anim->find_animation(default_anim));
for (int i = 0; i < p_clips.size(); i += 7) {
String name = p_clips[i];
@@ -1246,15 +1250,16 @@ void ResourceImporterScene::_create_clips(AnimationPlayer *anim, const Array &p_
new_anim->set_loop_mode(loop_mode);
new_anim->set_length(to - from);
- anim->add_animation(name, new_anim);
+
+ al->add_animation(name, new_anim);
Ref saved_anim = _save_animation_to_file(new_anim, save_to_file, save_to_path, keep_current);
if (saved_anim != new_anim) {
- anim->add_animation(name, saved_anim);
+ al->add_animation(name, saved_anim);
}
}
- anim->remove_animation("default"); //remove default (no longer needed)
+ al->remove_animation("default"); // Remove default (no longer needed).
}
void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle) {
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp
new file mode 100644
index 00000000000..2e9a82a7c21
--- /dev/null
+++ b/editor/plugins/animation_library_editor.cpp
@@ -0,0 +1,689 @@
+/*************************************************************************/
+/* animation_library_editor.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "animation_library_editor.h"
+#include "editor/editor_file_dialog.h"
+#include "editor/editor_node.h"
+#include "editor/editor_scale.h"
+
+void AnimationLibraryEditor::set_animation_player(Object *p_player) {
+ player = p_player;
+}
+
+void AnimationLibraryEditor::_add_library() {
+ add_library_dialog->set_title(TTR("Library Name:"));
+ add_library_name->set_text("");
+ add_library_dialog->popup_centered();
+ add_library_name->grab_focus();
+ adding_animation = false;
+ adding_animation_to_library = StringName();
+ _add_library_validate("");
+}
+
+void AnimationLibraryEditor::_add_library_validate(const String &p_name) {
+ String error;
+
+ if (adding_animation) {
+ Ref al = player->call("get_animation_library", adding_animation_to_library);
+ ERR_FAIL_COND(al.is_null());
+ if (p_name == "") {
+ error = TTR("Animation name can't be empty.");
+
+ } else if (String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("[")) {
+ error = TTR("Animation name contains invalid characters: '/', ':', ',' or '['.");
+ } else if (al->has_animation(p_name)) {
+ error = TTR("Animation with the same name already exists.");
+ }
+
+ } else {
+ if (p_name == "" && bool(player->call("has_animation_library", ""))) {
+ error = TTR("Enter a library name.");
+ } else if (String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("[")) {
+ error = TTR("Library name contains invalid characters: '/', ':', ',' or '['.");
+ } else if (bool(player->call("has_animation_library", p_name))) {
+ error = TTR("Library with the same name already exists.");
+ }
+ }
+
+ if (error != "") {
+ add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor")));
+ add_library_validate->set_text(error);
+ add_library_dialog->get_ok_button()->set_disabled(true);
+ } else {
+ add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor")));
+ if (p_name == "") {
+ add_library_validate->set_text(TTR("Global library will be created."));
+ } else {
+ add_library_validate->set_text(TTR("Library name is valid."));
+ }
+ add_library_dialog->get_ok_button()->set_disabled(false);
+ }
+}
+
+void AnimationLibraryEditor::_add_library_confirm() {
+ if (adding_animation) {
+ String anim_name = add_library_name->get_text();
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ Ref al = player->call("get_animation_library", adding_animation_to_library);
+ ERR_FAIL_COND(!al.is_valid());
+
+ Ref anim;
+ anim.instantiate();
+
+ undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name));
+ undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim);
+ undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+
+ } else {
+ String lib_name = add_library_name->get_text();
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ Ref al;
+ al.instantiate();
+
+ undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name));
+ undo_redo->add_do_method(player, "add_animation_library", lib_name, al);
+ undo_redo->add_undo_method(player, "remove_animation_library", lib_name);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ }
+}
+
+void AnimationLibraryEditor::_load_library() {
+ List extensions;
+ ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &extensions);
+
+ file_dialog->set_title(TTR("Load Animation"));
+ file_dialog->clear_filters();
+ for (const String &K : extensions) {
+ file_dialog->add_filter("*." + K);
+ }
+
+ file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ file_dialog->set_current_file("");
+ file_dialog->popup_centered_ratio();
+
+ file_dialog_action = FILE_DIALOG_ACTION_OPEN_LIBRARY;
+}
+
+void AnimationLibraryEditor::_file_popup_selected(int p_id) {
+ Ref al = player->call("get_animation_library", file_dialog_library);
+ Ref anim;
+ if (file_dialog_animation != StringName()) {
+ anim = al->get_animation(file_dialog_animation);
+ ERR_FAIL_COND(anim.is_null());
+ }
+ switch (p_id) {
+ case FILE_MENU_SAVE_LIBRARY: {
+ if (al->get_path().is_resource_file()) {
+ EditorNode::get_singleton()->save_resource(al);
+ break;
+ }
+ [[fallthrough]];
+ }
+ case FILE_MENU_SAVE_AS_LIBRARY: {
+ file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ file_dialog->set_title(TTR("Save Library"));
+ if (al->get_path().is_resource_file()) {
+ file_dialog->set_current_path(al->get_path());
+ } else {
+ file_dialog->set_current_file(String(file_dialog_library) + ".res");
+ }
+ file_dialog->clear_filters();
+ List exts;
+ ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &exts);
+ for (const String &K : exts) {
+ file_dialog->add_filter("*." + K);
+ }
+
+ file_dialog->popup_centered_ratio();
+ file_dialog_action = FILE_DIALOG_ACTION_SAVE_LIBRARY;
+ } break;
+ case FILE_MENU_MAKE_LIBRARY_UNIQUE: {
+ StringName lib_name = file_dialog_library;
+
+ Ref ald = al->duplicate();
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
+ undo_redo->add_do_method(player, "remove_animation_library", lib_name);
+ undo_redo->add_do_method(player, "add_animation_library", lib_name, ald);
+ undo_redo->add_undo_method(player, "remove_animation_library", lib_name);
+ undo_redo->add_undo_method(player, "add_animation_library", lib_name, al);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+
+ } break;
+ case FILE_MENU_EDIT_LIBRARY: {
+ EditorNode::get_singleton()->push_item(al.ptr());
+ } break;
+
+ case FILE_MENU_SAVE_ANIMATION: {
+ if (anim->get_path().is_resource_file()) {
+ EditorNode::get_singleton()->save_resource(anim);
+ break;
+ }
+ [[fallthrough]];
+ }
+ case FILE_MENU_SAVE_AS_ANIMATION: {
+ file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
+ file_dialog->set_title(TTR("Save Animation"));
+ if (anim->get_path().is_resource_file()) {
+ file_dialog->set_current_path(anim->get_path());
+ } else {
+ file_dialog->set_current_file(String(file_dialog_animation) + ".res");
+ }
+ file_dialog->clear_filters();
+ List exts;
+ ResourceLoader::get_recognized_extensions_for_type("Animation", &exts);
+ for (const String &K : exts) {
+ file_dialog->add_filter("*." + K);
+ }
+
+ file_dialog->popup_centered_ratio();
+ file_dialog_action = FILE_DIALOG_ACTION_SAVE_ANIMATION;
+ } break;
+ case FILE_MENU_MAKE_ANIMATION_UNIQUE: {
+ StringName anim_name = file_dialog_animation;
+
+ Ref animd = anim->duplicate();
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ undo_redo->create_action(vformat(TTR("Make Animation Unique: %s"), anim_name));
+ undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
+ undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd);
+ undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
+ undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ } break;
+ case FILE_MENU_EDIT_ANIMATION: {
+ EditorNode::get_singleton()->push_item(anim.ptr());
+ } break;
+ }
+}
+void AnimationLibraryEditor::_load_file(String p_path) {
+ switch (file_dialog_action) {
+ case FILE_DIALOG_ACTION_OPEN_LIBRARY: {
+ Ref al = ResourceLoader::load(p_path);
+ if (al.is_null()) {
+ error_dialog->set_text(TTR("Invalid AnimationLibrary file."));
+ error_dialog->popup_centered();
+ return;
+ }
+
+ TypedArray libs = player->call("get_animation_library_list");
+ for (int i = 0; i < libs.size(); i++) {
+ const StringName K = libs[i];
+ Ref al2 = player->call("get_animation_library", K);
+ if (al2 == al) {
+ error_dialog->set_text(TTR("This library is already added to the player."));
+ error_dialog->popup_centered();
+
+ return;
+ }
+ }
+
+ String name = p_path.get_file().get_basename();
+
+ int attempt = 1;
+
+ while (bool(player->call("has_animation_library", name))) {
+ attempt++;
+ name = p_path.get_file().get_basename() + " " + itos(attempt);
+ }
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name));
+ undo_redo->add_do_method(player, "add_animation_library", name, al);
+ undo_redo->add_undo_method(player, "remove_animation_library", name);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ } break;
+ case FILE_DIALOG_ACTION_OPEN_ANIMATION: {
+ Ref anim = ResourceLoader::load(p_path);
+ if (anim.is_null()) {
+ error_dialog->set_text(TTR("Invalid Animation file."));
+ error_dialog->popup_centered();
+ return;
+ }
+
+ Ref al = player->call("get_animation_library", adding_animation_to_library);
+ List anims;
+ al->get_animation_list(&anims);
+ for (const StringName &K : anims) {
+ Ref a2 = al->get_animation(K);
+ if (a2 == anim) {
+ error_dialog->set_text(TTR("This animation is already added to the library."));
+ error_dialog->popup_centered();
+ return;
+ }
+ }
+
+ String name = p_path.get_file().get_basename();
+
+ int attempt = 1;
+
+ while (al->has_animation(name)) {
+ attempt++;
+ name = p_path.get_file().get_basename() + " " + itos(attempt);
+ }
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name));
+ undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
+ undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ } break;
+
+ case FILE_DIALOG_ACTION_SAVE_LIBRARY: {
+ Ref al = player->call("get_animation_library", file_dialog_library);
+ String prev_path = al->get_path();
+ EditorNode::get_singleton()->save_resource_in_path(al, p_path);
+
+ if (al->get_path() != prev_path) { // Save successful.
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library));
+ undo_redo->add_do_method(al.ptr(), "set_path", al->get_path());
+ undo_redo->add_undo_method(al.ptr(), "set_path", prev_path);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ }
+
+ } break;
+ case FILE_DIALOG_ACTION_SAVE_ANIMATION: {
+ Ref al = player->call("get_animation_library", file_dialog_library);
+ Ref anim;
+ if (file_dialog_animation != StringName()) {
+ anim = al->get_animation(file_dialog_animation);
+ ERR_FAIL_COND(anim.is_null());
+ }
+ String prev_path = anim->get_path();
+ EditorNode::get_singleton()->save_resource_in_path(anim, p_path);
+ if (anim->get_path() != prev_path) { // Save successful.
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation));
+ undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path());
+ undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ }
+ } break;
+ }
+}
+
+void AnimationLibraryEditor::_item_renamed() {
+ TreeItem *ti = tree->get_edited();
+ String text = ti->get_text(0);
+ String old_text = ti->get_metadata(0);
+ bool restore_text = false;
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) {
+ restore_text = true;
+ } else {
+ if (ti->get_parent() == tree->get_root()) {
+ // Renamed library
+
+ if (player->call("has_animation_library", text)) {
+ restore_text = true;
+ } else {
+ undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text));
+ undo_redo->add_do_method(player, "rename_animation_library", old_text, text);
+ undo_redo->add_undo_method(player, "rename_animation_library", text, old_text);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ updating = true;
+ undo_redo->commit_action();
+ updating = false;
+ ti->set_metadata(0, text);
+ if (text == "") {
+ ti->set_suffix(0, TTR("[Global]"));
+ } else {
+ ti->set_suffix(0, "");
+ }
+ }
+ } else {
+ // Renamed anim
+ StringName library = ti->get_parent()->get_metadata(0);
+ Ref al = player->call("get_animation_library", library);
+
+ if (al.is_valid()) {
+ if (al->has_animation(text)) {
+ restore_text = true;
+ } else {
+ undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text));
+ undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text);
+ undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ updating = true;
+ undo_redo->commit_action();
+ updating = false;
+
+ ti->set_metadata(0, text);
+ }
+ } else {
+ restore_text = true;
+ }
+ }
+ }
+
+ if (restore_text) {
+ ti->set_text(0, old_text);
+ }
+}
+
+void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_button) {
+ if (p_item->get_parent() == tree->get_root()) {
+ // Library
+ StringName lib_name = p_item->get_metadata(0);
+ Ref al = player->call("get_animation_library", lib_name);
+ switch (p_button) {
+ case LIB_BUTTON_ADD: {
+ add_library_dialog->set_title(TTR("Animation Name:"));
+ add_library_name->set_text("");
+ add_library_dialog->popup_centered();
+ add_library_name->grab_focus();
+ adding_animation = true;
+ adding_animation_to_library = p_item->get_metadata(0);
+ _add_library_validate("");
+ } break;
+ case LIB_BUTTON_LOAD: {
+ adding_animation_to_library = p_item->get_metadata(0);
+ List extensions;
+ ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions);
+
+ file_dialog->clear_filters();
+ for (const String &K : extensions) {
+ file_dialog->add_filter("*." + K);
+ }
+
+ file_dialog->set_title(TTR("Load Animation"));
+ file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ file_dialog->set_current_file("");
+ file_dialog->popup_centered_ratio();
+
+ file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION;
+
+ } break;
+ case LIB_BUTTON_PASTE: {
+ Ref anim = EditorSettings::get_singleton()->get_resource_clipboard();
+ if (!anim.is_valid()) {
+ error_dialog->set_text(TTR("No animation resource in clipboard!"));
+ error_dialog->popup_centered();
+ return;
+ }
+
+ anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here.
+
+ String base_name;
+ if (anim->get_name() != "") {
+ base_name = anim->get_name();
+ } else {
+ base_name = TTR("Pasted Animation");
+ }
+
+ String name = base_name;
+ int attempt = 1;
+ while (al->has_animation(name)) {
+ attempt++;
+ name = base_name + " " + itos(attempt);
+ }
+
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+
+ undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name));
+ undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
+ undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+
+ } break;
+ case LIB_BUTTON_FILE: {
+ file_popup->clear();
+ file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY);
+ file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY);
+ file_popup->add_separator();
+ file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE);
+ file_popup->add_separator();
+ file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY);
+ Rect2 pos = tree->get_item_rect(p_item, 1, 0);
+ Vector2 popup_pos = tree->get_screen_position() + pos.position + Vector2(0, pos.size.height);
+ file_popup->popup(Rect2(popup_pos, Size2()));
+
+ file_dialog_animation = StringName();
+ file_dialog_library = lib_name;
+ } break;
+ case LIB_BUTTON_DELETE: {
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name));
+ undo_redo->add_do_method(player, "remove_animation_library", lib_name);
+ undo_redo->add_undo_method(player, "add_animation_library", lib_name, al);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ } break;
+ }
+
+ } else {
+ // Animation
+ StringName lib_name = p_item->get_parent()->get_metadata(0);
+ StringName anim_name = p_item->get_metadata(0);
+ Ref al = player->call("get_animation_library", lib_name);
+ Ref anim = al->get_animation(anim_name);
+ ERR_FAIL_COND(!anim.is_valid());
+ switch (p_button) {
+ case ANIM_BUTTON_COPY: {
+ if (anim->get_name() == "") {
+ anim->set_name(anim_name); // Keep the name around
+ }
+ EditorSettings::get_singleton()->set_resource_clipboard(anim);
+ } break;
+ case ANIM_BUTTON_FILE: {
+ file_popup->clear();
+ file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION);
+ file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION);
+ file_popup->add_separator();
+ file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE);
+ file_popup->add_separator();
+ file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION);
+ Rect2 pos = tree->get_item_rect(p_item, 1, 0);
+ Vector2 popup_pos = tree->get_screen_position() + pos.position + Vector2(0, pos.size.height);
+ file_popup->popup(Rect2(popup_pos, Size2()));
+
+ file_dialog_animation = anim_name;
+ file_dialog_library = lib_name;
+
+ } break;
+ case ANIM_BUTTON_DELETE: {
+ UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name));
+ undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
+ undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
+ undo_redo->add_do_method(this, "_update_editor", player);
+ undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->commit_action();
+ } break;
+ }
+ }
+}
+
+void AnimationLibraryEditor::update_tree() {
+ if (updating) {
+ return;
+ }
+
+ tree->clear();
+ ERR_FAIL_COND(!player);
+
+ Color ss_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor"));
+
+ TreeItem *root = tree->create_item();
+ TypedArray libs = player->call("get_animation_library_list");
+
+ for (int i = 0; i < libs.size(); i++) {
+ const StringName K = libs[i];
+ TreeItem *libitem = tree->create_item(root);
+ libitem->set_text(0, K);
+ if (K == StringName()) {
+ libitem->set_suffix(0, TTR("[Global]"));
+ } else {
+ libitem->set_suffix(0, "");
+ }
+ libitem->set_editable(0, true);
+ libitem->set_metadata(0, K);
+ libitem->set_icon(0, get_theme_icon("AnimationLibrary", "EditorIcons"));
+ libitem->add_button(0, get_theme_icon("Add", "EditorIcons"), LIB_BUTTON_ADD, false, TTR("Add Animation to Library"));
+ libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, false, TTR("Load animation from file and add to library"));
+ libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, false, TTR("Paste Animation to Library from clipboard"));
+ Ref al = player->call("get_animation_library", K);
+ if (al->get_path().is_resource_file()) {
+ libitem->set_text(1, al->get_path().get_file());
+ libitem->set_tooltip(1, al->get_path());
+ } else {
+ libitem->set_text(1, TTR("[built-in]"));
+ }
+ libitem->add_button(1, get_theme_icon("Save", "EditorIcons"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk"));
+ libitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), LIB_BUTTON_DELETE, false, TTR("Remove animation library"));
+
+ libitem->set_custom_bg_color(0, ss_color);
+
+ List animations;
+ al->get_animation_list(&animations);
+ for (const StringName &L : animations) {
+ TreeItem *anitem = tree->create_item(libitem);
+ anitem->set_text(0, L);
+ anitem->set_editable(0, true);
+ anitem->set_metadata(0, L);
+ anitem->set_icon(0, get_theme_icon("Animation", "EditorIcons"));
+ anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, false, TTR("Copy animation to clipboard"));
+ Ref anim = al->get_animation(L);
+
+ if (anim->get_path().is_resource_file()) {
+ anitem->set_text(1, anim->get_path().get_file());
+ anitem->set_tooltip(1, anim->get_path());
+ } else {
+ anitem->set_text(1, TTR("[built-in]"));
+ }
+ anitem->add_button(1, get_theme_icon("Save", "EditorIcons"), ANIM_BUTTON_FILE, false, TTR("Save animation to resource on disk"));
+ anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, false, TTR("Remove animation from Library"));
+ }
+ }
+}
+
+void AnimationLibraryEditor::show_dialog() {
+ update_tree();
+ popup_centered_ratio(0.5);
+}
+
+void AnimationLibraryEditor::_update_editor(Object *p_player) {
+ emit_signal("update_editor", p_player);
+}
+
+void AnimationLibraryEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_update_editor", "player"), &AnimationLibraryEditor::_update_editor);
+ ADD_SIGNAL(MethodInfo("update_editor"));
+}
+
+AnimationLibraryEditor::AnimationLibraryEditor() {
+ set_title(TTR("Edit Animation Libraries"));
+
+ file_dialog = memnew(EditorFileDialog);
+ add_child(file_dialog);
+ file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file));
+
+ add_library_dialog = memnew(ConfirmationDialog);
+ VBoxContainer *dialog_vb = memnew(VBoxContainer);
+ add_library_name = memnew(LineEdit);
+ dialog_vb->add_child(add_library_name);
+ add_library_name->connect("text_changed", callable_mp(this, &AnimationLibraryEditor::_add_library_validate));
+ add_child(add_library_dialog);
+
+ add_library_validate = memnew(Label);
+ dialog_vb->add_child(add_library_validate);
+ add_library_dialog->add_child(dialog_vb);
+ add_library_dialog->connect("confirmed", callable_mp(this, &AnimationLibraryEditor::_add_library_confirm));
+ add_library_dialog->register_text_enter(add_library_name);
+
+ VBoxContainer *vb = memnew(VBoxContainer);
+ HBoxContainer *hb = memnew(HBoxContainer);
+ hb->add_spacer(true);
+ Button *b = memnew(Button(TTR("Add Library")));
+ b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_add_library));
+ hb->add_child(b);
+ b = memnew(Button(TTR("Load Library")));
+ b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_load_library));
+ hb->add_child(b);
+ vb->add_child(hb);
+ tree = memnew(Tree);
+ vb->add_child(tree);
+
+ tree->set_columns(2);
+ tree->set_column_titles_visible(true);
+ tree->set_column_title(0, TTR("Resource"));
+ tree->set_column_title(1, TTR("Storage"));
+ tree->set_column_expand(0, true);
+ tree->set_column_custom_minimum_width(1, EDSCALE * 250);
+ tree->set_column_expand(1, false);
+ tree->set_hide_root(true);
+ tree->set_hide_folding(true);
+ tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+
+ tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed));
+ tree->connect("button_pressed", callable_mp(this, &AnimationLibraryEditor::_button_pressed));
+
+ file_popup = memnew(PopupMenu);
+ add_child(file_popup);
+ file_popup->connect("id_pressed", callable_mp(this, &AnimationLibraryEditor::_file_popup_selected));
+
+ add_child(vb);
+
+ error_dialog = memnew(AcceptDialog);
+ error_dialog->set_title(TTR("Error:"));
+ add_child(error_dialog);
+}
diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h
new file mode 100644
index 00000000000..5bd4e8d9e2b
--- /dev/null
+++ b/editor/plugins/animation_library_editor.h
@@ -0,0 +1,119 @@
+/*************************************************************************/
+/* animation_library_editor.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANIMATION_LIBRARY_EDITOR_H
+#define ANIMATION_LIBRARY_EDITOR_H
+
+#include "editor/animation_track_editor.h"
+#include "editor/editor_plugin.h"
+#include "scene/animation/animation_player.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/tree.h"
+
+class EditorFileDialog;
+
+class AnimationLibraryEditor : public AcceptDialog {
+ GDCLASS(AnimationLibraryEditor, AcceptDialog)
+
+ enum {
+ LIB_BUTTON_ADD,
+ LIB_BUTTON_LOAD,
+ LIB_BUTTON_PASTE,
+ LIB_BUTTON_FILE,
+ LIB_BUTTON_DELETE,
+ };
+ enum {
+ ANIM_BUTTON_COPY,
+ ANIM_BUTTON_FILE,
+ ANIM_BUTTON_DELETE,
+ };
+
+ enum FileMenuAction {
+ FILE_MENU_SAVE_LIBRARY,
+ FILE_MENU_SAVE_AS_LIBRARY,
+ FILE_MENU_MAKE_LIBRARY_UNIQUE,
+ FILE_MENU_EDIT_LIBRARY,
+
+ FILE_MENU_SAVE_ANIMATION,
+ FILE_MENU_SAVE_AS_ANIMATION,
+ FILE_MENU_MAKE_ANIMATION_UNIQUE,
+ FILE_MENU_EDIT_ANIMATION,
+ };
+
+ enum FileDialogAction {
+ FILE_DIALOG_ACTION_OPEN_LIBRARY,
+ FILE_DIALOG_ACTION_SAVE_LIBRARY,
+ FILE_DIALOG_ACTION_OPEN_ANIMATION,
+ FILE_DIALOG_ACTION_SAVE_ANIMATION,
+ };
+
+ FileDialogAction file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION;
+
+ StringName file_dialog_animation;
+ StringName file_dialog_library;
+
+ AcceptDialog *error_dialog = nullptr;
+ bool adding_animation = false;
+ StringName adding_animation_to_library;
+ EditorFileDialog *file_dialog = nullptr;
+ ConfirmationDialog *add_library_dialog = nullptr;
+ LineEdit *add_library_name = nullptr;
+ Label *add_library_validate = nullptr;
+ PopupMenu *file_popup = nullptr;
+
+ Tree *tree = nullptr;
+
+ Object *player = nullptr;
+
+ void _add_library();
+ void _add_library_validate(const String &p_name);
+ void _add_library_confirm();
+ void _load_library();
+ void _load_file(String p_path);
+
+ void _item_renamed();
+ void _button_pressed(TreeItem *p_item, int p_column, int p_button);
+
+ void _file_popup_selected(int p_id);
+
+ bool updating = false;
+
+protected:
+ void _update_editor(Object *p_player);
+ static void _bind_methods();
+
+public:
+ void set_animation_player(Object *p_player);
+ void show_dialog();
+ void update_tree();
+ AnimationLibraryEditor();
+};
+
+#endif // ANIMATIONPLAYERLIBRARYEDITOR_H
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index af7c092d03a..2081edca253 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -47,6 +47,8 @@
#include "scene/scene_string_names.h"
#include "servers/rendering_server.h"
+///////////////////////////////////
+
void AnimationPlayerEditor::_node_removed(Node *p_node) {
if (player && player == p_node) {
player = nullptr;
@@ -148,9 +150,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
#define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_theme_icon(SNAME(m_icon), SNAME("EditorIcons")))
ITEM_ICON(TOOL_NEW_ANIM, "New");
- ITEM_ICON(TOOL_LOAD_ANIM, "Load");
- ITEM_ICON(TOOL_SAVE_ANIM, "Save");
- ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save");
+ ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary");
ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
@@ -166,7 +166,7 @@ void AnimationPlayerEditor::_autoplay_pressed() {
if (updating) {
return;
}
- if (animation->get_item_count() == 0) {
+ if (animation->has_selectable_items() == 0) {
return;
}
@@ -192,10 +192,7 @@ void AnimationPlayerEditor::_autoplay_pressed() {
}
void AnimationPlayerEditor::_play_pressed() {
- String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
- current = animation->get_item_text(animation->get_selected());
- }
+ String current = _get_current();
if (!current.is_empty()) {
if (current == player->get_assigned_animation()) {
@@ -209,10 +206,7 @@ void AnimationPlayerEditor::_play_pressed() {
}
void AnimationPlayerEditor::_play_from_pressed() {
- String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
- current = animation->get_item_text(animation->get_selected());
- }
+ String current = _get_current();
if (!current.is_empty()) {
float time = player->get_current_animation_position();
@@ -229,12 +223,15 @@ void AnimationPlayerEditor::_play_from_pressed() {
stop->set_pressed(false);
}
-void AnimationPlayerEditor::_play_bw_pressed() {
+String AnimationPlayerEditor::_get_current() const {
String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
+ if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count() && !animation->is_item_separator(animation->get_selected())) {
current = animation->get_item_text(animation->get_selected());
}
-
+ return current;
+}
+void AnimationPlayerEditor::_play_bw_pressed() {
+ String current = _get_current();
if (!current.is_empty()) {
if (current == player->get_assigned_animation()) {
player->stop(); //so it won't blend with itself
@@ -247,10 +244,7 @@ void AnimationPlayerEditor::_play_bw_pressed() {
}
void AnimationPlayerEditor::_play_bw_from_pressed() {
- String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
- current = animation->get_item_text(animation->get_selected());
- }
+ String current = _get_current();
if (!current.is_empty()) {
float time = player->get_current_animation_position();
@@ -282,10 +276,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
}
// when selecting an animation, the idea is that the only interesting behavior
// ui-wise is that it should play/blend the next one if currently playing
- String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
- current = animation->get_item_text(animation->get_selected());
- }
+ String current = _get_current();
if (!current.is_empty()) {
player->set_assigned_animation(current);
@@ -330,6 +321,20 @@ void AnimationPlayerEditor::_animation_new() {
break;
}
+ List libraries;
+ player->get_animation_library_list(&libraries);
+ library->clear();
+ for (const StringName &K : libraries) {
+ library->add_item((K == StringName()) ? String(TTR("[Global]")) : String(K));
+ library->set_item_metadata(0, String(K));
+ }
+
+ if (libraries.size() > 1) {
+ library->show();
+ } else {
+ library->hide();
+ }
+
name->set_text(base);
name_dialog->popup_centered(Size2(300, 90));
name->select_all();
@@ -337,7 +342,7 @@ void AnimationPlayerEditor::_animation_new() {
}
void AnimationPlayerEditor::_animation_rename() {
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
return;
}
int selected = animation->get_selected();
@@ -349,84 +354,11 @@ void AnimationPlayerEditor::_animation_rename() {
name_dialog->popup_centered(Size2(300, 90));
name->select_all();
name->grab_focus();
-}
-
-void AnimationPlayerEditor::_animation_load() {
- ERR_FAIL_COND(!player);
- file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
- file->clear_filters();
- List extensions;
-
- ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions);
- for (const String &E : extensions) {
- file->add_filter("*." + E + " ; " + E.to_upper());
- }
-
- file->popup_file_dialog();
-}
-
-void AnimationPlayerEditor::_animation_save_in_path(const Ref &p_resource, const String &p_path) {
- int flg = 0;
- if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) {
- flg |= ResourceSaver::FLAG_COMPRESS;
- }
-
- String path = ProjectSettings::get_singleton()->localize_path(p_path);
- Error err = ResourceSaver::save(path, p_resource, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS);
-
- if (err != OK) {
- EditorNode::get_singleton()->show_warning(TTR("Error saving resource!"));
- return;
- }
-
- ((Resource *)p_resource.ptr())->set_path(path);
- EditorNode::get_singleton()->emit_signal(SNAME("resource_saved"), p_resource);
-}
-
-void AnimationPlayerEditor::_animation_save(const Ref &p_resource) {
- if (p_resource->get_path().is_resource_file()) {
- _animation_save_in_path(p_resource, p_resource->get_path());
- } else {
- _animation_save_as(p_resource);
- }
-}
-
-void AnimationPlayerEditor::_animation_save_as(const Ref &p_resource) {
- file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
-
- List extensions;
- ResourceSaver::get_recognized_extensions(p_resource, &extensions);
- file->clear_filters();
- for (int i = 0; i < extensions.size(); i++) {
- file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper());
- }
-
- String path;
- //file->set_current_path(current_path);
- if (!p_resource->get_path().is_empty()) {
- path = p_resource->get_path();
- if (extensions.size()) {
- if (extensions.find(p_resource->get_path().get_extension().to_lower()) == nullptr) {
- path = p_resource->get_path().get_base_dir() + p_resource->get_name() + "." + extensions.front()->get();
- }
- }
- } else {
- if (extensions.size()) {
- if (!p_resource->get_name().is_empty()) {
- path = p_resource->get_name() + "." + extensions.front()->get().to_lower();
- } else {
- String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore();
- path = "new_" + resource_name_snake_case + "." + extensions.front()->get().to_lower();
- }
- }
- }
- file->set_current_path(path);
- file->set_title(TTR("Save Resource As..."));
- file->popup_file_dialog();
+ library->hide();
}
void AnimationPlayerEditor::_animation_remove() {
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
return;
}
@@ -440,6 +372,9 @@ void AnimationPlayerEditor::_animation_remove_confirmed() {
String current = animation->get_item_text(animation->get_selected());
Ref anim = player->get_animation(current);
+ Ref al = player->get_animation_library(player->find_animation_library(anim));
+ ERR_FAIL_COND(al.is_null());
+
undo_redo->create_action(TTR("Remove Animation"));
if (player->get_autoplay() == current) {
undo_redo->add_do_method(player, "set_autoplay", "");
@@ -447,11 +382,11 @@ void AnimationPlayerEditor::_animation_remove_confirmed() {
// Avoid having the autoplay icon linger around if there is only one animation in the player.
undo_redo->add_do_method(this, "_animation_player_changed", player);
}
- undo_redo->add_do_method(player, "remove_animation", current);
- undo_redo->add_undo_method(player, "add_animation", current, anim);
+ undo_redo->add_do_method(al.ptr(), "remove_animation", current);
+ undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim);
undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_method(this, "_animation_player_changed", player);
- if (animation->get_item_count() == 1) {
+ if (animation->has_selectable_items() && animation->get_selectable_item(false) == animation->get_selectable_item(true)) { // Last item remaining.
undo_redo->add_do_method(this, "_stop_onion_skinning");
undo_redo->add_undo_method(this, "_start_onion_skinning");
}
@@ -498,7 +433,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
return;
}
- if (name_dialog_op == TOOL_RENAME_ANIM && animation->get_item_count() > 0 && animation->get_item_text(animation->get_selected()) == new_name) {
+ if (name_dialog_op == TOOL_RENAME_ANIM && animation->has_selectable_items() && animation->get_item_text(animation->get_selected()) == new_name) {
name_dialog->hide();
return;
}
@@ -514,10 +449,13 @@ void AnimationPlayerEditor::_animation_name_edited() {
String current = animation->get_item_text(animation->get_selected());
Ref anim = player->get_animation(current);
+ Ref al = player->get_animation_library(player->find_animation_library(anim));
+ ERR_FAIL_COND(al.is_null());
+
undo_redo->create_action(TTR("Rename Animation"));
- undo_redo->add_do_method(player, "rename_animation", current, new_name);
+ undo_redo->add_do_method(al.ptr(), "rename_animation", current, new_name);
undo_redo->add_do_method(anim.ptr(), "set_name", new_name);
- undo_redo->add_undo_method(player, "rename_animation", new_name, current);
+ undo_redo->add_undo_method(al.ptr(), "rename_animation", new_name, current);
undo_redo->add_undo_method(anim.ptr(), "set_name", current);
undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_method(this, "_animation_player_changed", player);
@@ -530,15 +468,35 @@ void AnimationPlayerEditor::_animation_name_edited() {
Ref new_anim = Ref(memnew(Animation));
new_anim->set_name(new_name);
+ Ref al;
+ if (library->is_visible()) {
+ al = player->get_animation_library(library->get_item_metadata(library->get_selected()));
+ } else {
+ if (player->has_animation_library("")) {
+ al = player->get_animation_library("");
+ }
+ }
+
undo_redo->create_action(TTR("Add Animation"));
- undo_redo->add_do_method(player, "add_animation", new_name, new_anim);
- undo_redo->add_undo_method(player, "remove_animation", new_name);
+
+ bool lib_added = false;
+ if (al.is_null()) {
+ al.instantiate();
+ lib_added = true;
+ undo_redo->add_do_method(player, "add_animation_library", "", al);
+ }
+
+ undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim);
+ undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name);
undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_method(this, "_animation_player_changed", player);
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
undo_redo->add_do_method(this, "_start_onion_skinning");
undo_redo->add_undo_method(this, "_stop_onion_skinning");
}
+ if (lib_added) {
+ undo_redo->add_undo_method(player, "remove_animation_library", "");
+ }
undo_redo->commit_action();
_select_anim_by_name(new_name);
@@ -551,9 +509,11 @@ void AnimationPlayerEditor::_animation_name_edited() {
Ref new_anim = _animation_clone(anim);
new_anim->set_name(new_name);
+ Ref library = player->get_animation_library(player->find_animation_library(anim));
+
undo_redo->create_action(TTR("Duplicate Animation"));
- undo_redo->add_do_method(player, "add_animation", new_name, new_anim);
- undo_redo->add_undo_method(player, "remove_animation", new_name);
+ undo_redo->add_do_method(library.ptr(), "add_animation", new_name, new_anim);
+ undo_redo->add_undo_method(library.ptr(), "remove_animation", new_name);
undo_redo->add_do_method(player, "animation_set_next", new_name, player->animation_get_next(current));
undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_method(this, "_animation_player_changed", player);
@@ -567,7 +527,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
}
void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) {
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
return;
}
@@ -588,7 +548,7 @@ void AnimationPlayerEditor::_animation_blend() {
blend_editor.tree->clear();
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
return;
}
@@ -643,7 +603,7 @@ void AnimationPlayerEditor::_blend_edited() {
return;
}
- if (animation->get_item_count() == 0) {
+ if (!animation->has_selectable_items()) {
return;
}
@@ -722,16 +682,16 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
}
void AnimationPlayerEditor::_animation_resource_edit() {
- if (animation->get_item_count()) {
- String current = animation->get_item_text(animation->get_selected());
+ String current = _get_current();
+ if (current != String()) {
Ref anim = player->get_animation(current);
EditorNode::get_singleton()->edit_resource(anim);
}
}
void AnimationPlayerEditor::_animation_edit() {
- if (animation->get_item_count()) {
- String current = animation->get_item_text(animation->get_selected());
+ String current = _get_current();
+ if (current != String()) {
Ref anim = player->get_animation(current);
track_editor->set_animation(anim);
@@ -745,51 +705,6 @@ void AnimationPlayerEditor::_animation_edit() {
}
}
-void AnimationPlayerEditor::_save_animation(String p_file) {
- String current = animation->get_item_text(animation->get_selected());
- if (!current.is_empty()) {
- Ref anim = player->get_animation(current);
-
- ERR_FAIL_COND(!Object::cast_to(*anim));
-
- RES current_res = RES(Object::cast_to(*anim));
-
- _animation_save_in_path(current_res, p_file);
- }
-}
-
-void AnimationPlayerEditor::_load_animations(Vector p_files) {
- ERR_FAIL_COND(!player);
-
- for (int i = 0; i < p_files.size(); i++) {
- String file = p_files[i];
-
- Ref res = ResourceLoader::load(file, "Animation");
- ERR_FAIL_COND_MSG(res.is_null(), "Cannot load Animation from file '" + file + "'.");
- ERR_FAIL_COND_MSG(!res->is_class("Animation"), "Loaded resource from file '" + file + "' is not Animation.");
- if (file.rfind("/") != -1) {
- file = file.substr(file.rfind("/") + 1, file.length());
- }
- if (file.rfind("\\") != -1) {
- file = file.substr(file.rfind("\\") + 1, file.length());
- }
-
- if (file.contains(".")) {
- file = file.substr(0, file.find("."));
- }
-
- undo_redo->create_action(TTR("Load Animation"));
- undo_redo->add_do_method(player, "add_animation", file, res);
- undo_redo->add_undo_method(player, "remove_animation", file);
- if (player->has_animation(file)) {
- undo_redo->add_undo_method(player, "add_animation", file, player->get_animation(file));
- }
- undo_redo->add_do_method(this, "_animation_player_changed", player);
- undo_redo->add_undo_method(this, "_animation_player_changed", player);
- undo_redo->commit_action();
- }
-}
-
void AnimationPlayerEditor::_scale_changed(const String &p_scale) {
player->set_speed_scale(p_scale.to_float());
}
@@ -824,49 +739,66 @@ void AnimationPlayerEditor::_update_animation() {
void AnimationPlayerEditor::_update_player() {
updating = true;
- List animlist;
- if (player) {
- player->get_animation_list(&animlist);
- }
animation->clear();
-#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled)
-
- ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0);
- ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0);
- ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0);
- ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0);
- ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0);
- ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0);
- ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0);
-
- stop->set_disabled(animlist.size() == 0);
- play->set_disabled(animlist.size() == 0);
- play_bw->set_disabled(animlist.size() == 0);
- play_bw_from->set_disabled(animlist.size() == 0);
- play_from->set_disabled(animlist.size() == 0);
- frame->set_editable(animlist.size() != 0);
- animation->set_disabled(animlist.size() == 0);
- autoplay->set_disabled(animlist.size() == 0);
- tool_anim->set_disabled(player == nullptr);
- onion_toggle->set_disabled(animlist.size() == 0);
- onion_skinning->set_disabled(animlist.size() == 0);
- pin->set_disabled(player == nullptr);
-
if (!player) {
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
return;
}
- int active_idx = -1;
- for (const StringName &E : animlist) {
- animation->add_item(E);
+ List libraries;
+ if (player) {
+ player->get_animation_library_list(&libraries);
+ }
- if (player->get_assigned_animation() == E) {
- active_idx = animation->get_item_count() - 1;
+ int active_idx = -1;
+ bool no_anims_found = true;
+
+ for (const StringName &K : libraries) {
+ if (K != StringName()) {
+ animation->add_separator(K);
+ }
+
+ Ref library = player->get_animation_library(K);
+ List animlist;
+ library->get_animation_list(&animlist);
+
+ for (const StringName &E : animlist) {
+ String path = K;
+ if (path != "") {
+ path += "/";
+ }
+ path += E;
+ animation->add_item(path);
+ if (player->get_assigned_animation() == path) {
+ active_idx = animation->get_selectable_item(true);
+ }
+ no_anims_found = false;
}
}
+#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found)
+
+ ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
+ ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
+ ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS);
+ ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM);
+
+#undef ITEM_CHECK_DISABLED
+
+ stop->set_disabled(no_anims_found);
+ play->set_disabled(no_anims_found);
+ play_bw->set_disabled(no_anims_found);
+ play_bw_from->set_disabled(no_anims_found);
+ play_from->set_disabled(no_anims_found);
+ frame->set_editable(!no_anims_found);
+ animation->set_disabled(no_anims_found);
+ autoplay->set_disabled(no_anims_found);
+ tool_anim->set_disabled(player == nullptr);
+ onion_toggle->set_disabled(no_anims_found);
+ onion_skinning->set_disabled(no_anims_found);
+ pin->set_disabled(player == nullptr);
+
_update_animation_list_icons();
updating = false;
@@ -874,16 +806,16 @@ void AnimationPlayerEditor::_update_player() {
animation->select(active_idx);
autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay());
_animation_selected(active_idx);
-
- } else if (animation->get_item_count() > 0) {
- animation->select(0);
- autoplay->set_pressed(animation->get_item_text(0) == player->get_autoplay());
- _animation_selected(0);
+ } else if (animation->has_selectable_items()) {
+ int item = animation->get_selectable_item();
+ animation->select(item);
+ autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay());
+ _animation_selected(item);
} else {
_animation_selected(0);
}
- if (animation->get_item_count()) {
+ if (!no_anims_found) {
String current = animation->get_item_text(animation->get_selected());
Ref anim = player->get_animation(current);
track_editor->set_animation(anim);
@@ -899,6 +831,9 @@ void AnimationPlayerEditor::_update_player() {
void AnimationPlayerEditor::_update_animation_list_icons() {
for (int i = 0; i < animation->get_item_count(); i++) {
String name = animation->get_item_text(i);
+ if (animation->is_item_disabled(i) || animation->is_item_separator(i)) {
+ continue;
+ }
Ref icon;
if (name == player->get_autoplay()) {
@@ -925,7 +860,7 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
_update_player();
if (onion.enabled) {
- if (animation->get_item_count() > 0) {
+ if (animation->has_selectable_items()) {
_start_onion_skinning();
} else {
_stop_onion_skinning();
@@ -940,6 +875,8 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
track_editor->show_select_node_warning(true);
}
+
+ library_editor->set_animation_player(player);
}
void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) {
@@ -993,7 +930,7 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay)
}
void AnimationPlayerEditor::_animation_duplicate() {
- if (!animation->get_item_count()) {
+ if (!animation->has_selectable_items()) {
return;
}
@@ -1031,29 +968,6 @@ Ref AnimationPlayerEditor::_animation_clone(Ref p_anim) {
return new_anim;
}
-void AnimationPlayerEditor::_animation_paste(Ref p_anim) {
- String name = p_anim->get_name();
- if (name.is_empty()) {
- name = TTR("Pasted Animation");
- }
-
- int idx = 1;
- String base = name;
- while (player->has_animation(name)) {
- idx++;
- name = base + " " + itos(idx);
- }
-
- undo_redo->create_action(TTR("Paste Animation"));
- undo_redo->add_do_method(player, "add_animation", name, p_anim);
- undo_redo->add_undo_method(player, "remove_animation", name);
- undo_redo->add_do_method(this, "_animation_player_changed", player);
- undo_redo->add_undo_method(this, "_animation_player_changed", player);
- undo_redo->commit_action();
-
- _select_anim_by_name(name);
-}
-
void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) {
if (updating || !player || player->is_playing()) {
return;
@@ -1095,6 +1009,9 @@ void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
if (blend_editor.dialog->is_visible()) {
_animation_blend(); // Update.
}
+ if (library_editor->is_visible()) {
+ library_editor->update_tree();
+ }
}
}
@@ -1134,10 +1051,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag,
}
void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
- String current;
- if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
- current = animation->get_item_text(animation->get_selected());
- }
+ String current = _get_current();
Ref anim;
if (!current.is_empty()) {
@@ -1148,18 +1062,9 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
case TOOL_NEW_ANIM: {
_animation_new();
} break;
- case TOOL_LOAD_ANIM: {
- _animation_load();
- } break;
- case TOOL_SAVE_ANIM: {
- if (anim.is_valid()) {
- _animation_save(anim);
- }
- } break;
- case TOOL_SAVE_AS_ANIM: {
- if (anim.is_valid()) {
- _animation_save_as(anim);
- }
+ case TOOL_ANIM_LIBRARY: {
+ library_editor->set_animation_player(player);
+ library_editor->show_dialog();
} break;
case TOOL_DUPLICATE_ANIM: {
_animation_duplicate();
@@ -1173,39 +1078,8 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
case TOOL_REMOVE_ANIM: {
_animation_remove();
} break;
- case TOOL_COPY_ANIM: {
- if (!animation->get_item_count()) {
- error_dialog->set_text(TTR("No animation to copy!"));
- error_dialog->popup_centered();
- return;
- }
-
- String current2 = animation->get_item_text(animation->get_selected());
- Ref anim2 = player->get_animation(current2);
- EditorSettings::get_singleton()->set_resource_clipboard(anim2);
- } break;
- case TOOL_PASTE_ANIM: {
- Ref anim2 = EditorSettings::get_singleton()->get_resource_clipboard();
- if (!anim2.is_valid()) {
- error_dialog->set_text(TTR("No animation resource in clipboard!"));
- error_dialog->popup_centered();
- return;
- }
- Ref new_anim = _animation_clone(anim2);
- _animation_paste(new_anim);
- } break;
- case TOOL_PASTE_ANIM_REF: {
- Ref anim2 = EditorSettings::get_singleton()->get_resource_clipboard();
- if (!anim2.is_valid()) {
- error_dialog->set_text(TTR("No animation resource in clipboard!"));
- error_dialog->popup_centered();
- return;
- }
-
- _animation_paste(anim2);
- } break;
case TOOL_EDIT_RESOURCE: {
- if (!animation->get_item_count()) {
+ if (!animation->has_selectable_items()) {
error_dialog->set_text(TTR("No animation to edit!"));
error_dialog->popup_centered();
return;
@@ -1300,7 +1174,7 @@ void AnimationPlayerEditor::shortcut_input(const Ref &p_ev) {
}
void AnimationPlayerEditor::_editor_visibility_changed() {
- if (is_visible() && animation->get_item_count() > 0) {
+ if (is_visible() && animation->has_selectable_items()) {
_start_onion_skinning();
}
}
@@ -1536,7 +1410,6 @@ void AnimationPlayerEditor::_pin_pressed() {
void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new);
ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename);
- ClassDB::bind_method(D_METHOD("_animation_load"), &AnimationPlayerEditor::_animation_load);
ClassDB::bind_method(D_METHOD("_animation_remove"), &AnimationPlayerEditor::_animation_remove);
ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend);
ClassDB::bind_method(D_METHOD("_animation_edit"), &AnimationPlayerEditor::_animation_edit);
@@ -1623,13 +1496,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
tool_anim->set_text(TTR("Animation"));
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM);
tool_anim->get_popup()->add_separator();
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load")), TOOL_LOAD_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As...")), TOOL_SAVE_AS_ANIM);
- tool_anim->get_popup()->add_separator();
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM);
- tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation_as_reference", TTR("Paste As Reference")), TOOL_PASTE_ANIM_REF);
+ tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM);
tool_anim->get_popup()->add_separator();
@@ -1638,6 +1505,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE);
tool_anim->get_popup()->add_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM);
+ tool_anim->set_disabled(true);
hb->add_child(tool_anim);
animation = memnew(OptionButton);
@@ -1705,8 +1573,14 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
name_title = memnew(Label(TTR("Animation Name:")));
vb->add_child(name_title);
+ HBoxContainer *name_hb = memnew(HBoxContainer);
name = memnew(LineEdit);
- vb->add_child(name);
+ name_hb->add_child(name);
+ name->set_h_size_flags(SIZE_EXPAND_FILL);
+ library = memnew(OptionButton);
+ name_hb->add_child(library);
+ library->hide();
+ vb->add_child(name_hb);
name_dialog->register_text_enter(name);
error_dialog = memnew(ConfirmationDialog);
@@ -1742,8 +1616,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected));
- file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_save_animation));
- file->connect("files_selected", callable_mp(this, &AnimationPlayerEditor::_load_animations));
frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false));
scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
@@ -1759,6 +1631,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
_update_player();
+ library_editor = memnew(AnimationLibraryEditor);
+ add_child(library_editor);
+ library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
+
// Onion skinning.
track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4f6a9c534f2..0cc04460ca8 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -33,6 +33,7 @@
#include "editor/animation_track_editor.h"
#include "editor/editor_plugin.h"
+#include "editor/plugins/animation_library_editor.h"
#include "scene/animation/animation_player.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/slider.h"
@@ -40,7 +41,6 @@
#include "scene/gui/texture_button.h"
#include "scene/gui/tree.h"
-class EditorFileDialog;
class AnimationPlayerEditorPlugin;
class AnimationPlayerEditor : public VBoxContainer {
@@ -51,16 +51,11 @@ class AnimationPlayerEditor : public VBoxContainer {
enum {
TOOL_NEW_ANIM,
- TOOL_LOAD_ANIM,
- TOOL_SAVE_ANIM,
- TOOL_SAVE_AS_ANIM,
+ TOOL_ANIM_LIBRARY,
TOOL_DUPLICATE_ANIM,
TOOL_RENAME_ANIM,
TOOL_EDIT_TRANSITIONS,
TOOL_REMOVE_ANIM,
- TOOL_COPY_ANIM,
- TOOL_PASTE_ANIM,
- TOOL_PASTE_ANIM_REF,
TOOL_EDIT_RESOURCE
};
@@ -103,8 +98,10 @@ class AnimationPlayerEditor : public VBoxContainer {
SpinBox *frame = nullptr;
LineEdit *scale = nullptr;
LineEdit *name = nullptr;
+ OptionButton *library = nullptr;
Label *name_title = nullptr;
UndoRedo *undo_redo = nullptr;
+
Ref autoplay_icon;
Ref reset_icon;
Ref autoplay_reset_icon;
@@ -114,6 +111,8 @@ class AnimationPlayerEditor : public VBoxContainer {
EditorFileDialog *file = nullptr;
ConfirmationDialog *delete_dialog = nullptr;
+ AnimationLibraryEditor *library_editor = nullptr;
+
struct BlendEditor {
AcceptDialog *dialog = nullptr;
Tree *tree = nullptr;
@@ -173,11 +172,6 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_new();
void _animation_rename();
void _animation_name_edited();
- void _animation_load();
-
- void _animation_save_in_path(const Ref &p_resource, const String &p_path);
- void _animation_save(const Ref &p_resource);
- void _animation_save_as(const Ref &p_resource);
void _animation_remove();
void _animation_remove_confirmed();
@@ -185,11 +179,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_edit();
void _animation_duplicate();
Ref _animation_clone(const Ref p_anim);
- void _animation_paste(const Ref p_anim);
void _animation_resource_edit();
void _scale_changed(const String &p_scale);
- void _save_animation(String p_file);
- void _load_animations(Vector p_files);
void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false);
void _blend_editor_next_changed(const int p_idx);
@@ -219,6 +210,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _stop_onion_skinning();
void _pin_pressed();
+ String _get_current() const;
~AnimationPlayerEditor();
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 36681698a16..73222330dbd 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -6095,7 +6095,14 @@ void GLTFDocument::_import_animation(Ref state, AnimationPlayer *ap,
animation->set_length(length);
- ap->add_animation(name, animation);
+ Ref library;
+ if (!ap->has_animation_library("")) {
+ library.instantiate();
+ ap->add_animation_library("", library);
+ } else {
+ library = ap->get_animation_library("");
+ }
+ library->add_animation(name, animation);
}
void GLTFDocument::_convert_mesh_instances(Ref state) {
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 1ab2e2419ef..6949e3681c7 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -83,8 +83,31 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) {
set_current_animation(p_value);
} else if (name.begins_with("anims/")) {
+ // Backwards compatibility with 3.x, add them to "default" library.
String which = name.get_slicec('/', 1);
- add_animation(which, p_value);
+
+ Ref anim = p_value;
+ Ref al;
+ if (!has_animation_library(StringName())) {
+ al.instantiate();
+ add_animation_library(StringName(), al);
+ } else {
+ al = get_animation_library(StringName());
+ }
+ al->add_animation(which, anim);
+
+ } else if (name.begins_with("libraries")) {
+ Dictionary d = p_value;
+ while (animation_libraries.size()) {
+ remove_animation_library(animation_libraries[0].name);
+ }
+ List keys;
+ d.get_key_list(&keys);
+ for (const Variant &K : keys) {
+ StringName lib_name = K;
+ Ref lib = d[lib_name];
+ add_animation_library(lib_name, lib);
+ }
} else if (name.begins_with("next/")) {
String which = name.get_slicec('/', 1);
@@ -117,9 +140,13 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const {
r_ret = get_current_animation();
- } else if (name.begins_with("anims/")) {
- String which = name.get_slicec('/', 1);
- r_ret = get_animation(which);
+ } else if (name.begins_with("libraries")) {
+ Dictionary d;
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ d[animation_libraries[i].name] = animation_libraries[i].library;
+ }
+
+ r_ret = d;
} else if (name.begins_with("next/")) {
String which = name.get_slicec('/', 1);
@@ -173,8 +200,9 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const {
void AnimationPlayer::_get_property_list(List *p_list) const {
List anim_names;
+ anim_names.push_back(PropertyInfo(Variant::DICTIONARY, "libraries"));
+
for (const KeyValue &E : animation_set) {
- anim_names.push_back(PropertyInfo(Variant::OBJECT, "anims/" + String(E.key), PROPERTY_HINT_RESOURCE_TYPE, "Animation", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
if (E.value.next != StringName()) {
anim_names.push_back(PropertyInfo(Variant::STRING, "next/" + String(E.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
@@ -1155,71 +1183,106 @@ void AnimationPlayer::_animation_process(double p_delta) {
}
}
-Error AnimationPlayer::add_animation(const StringName &p_name, const Ref &p_animation) {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
-#endif
+void AnimationPlayer::_animation_set_cache_update() {
+ // Relatively fast function to update all animations.
- ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
+ animation_set_update_pass++;
+ bool clear_cache_needed = false;
- if (animation_set.has(p_name)) {
- _unref_anim(animation_set[p_name].animation);
- animation_set[p_name].animation = p_animation;
- clear_caches();
- } else {
- AnimationData ad;
- ad.animation = p_animation;
- ad.name = p_name;
- animation_set[p_name] = ad;
+ // Update changed and add otherwise
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ for (const KeyValue> &K : animation_libraries[i].library->animations) {
+ StringName key = animation_libraries[i].name == StringName() ? K.key : StringName(String(animation_libraries[i].name) + "/" + String(K.key));
+ if (!animation_set.has(key)) {
+ AnimationData ad;
+ ad.animation = K.value;
+ ad.animation_library = animation_libraries[i].name;
+ ad.name = key;
+ ad.last_update = animation_set_update_pass;
+ animation_set.insert(ad.name, ad);
+ } else {
+ AnimationData &ad = animation_set[key];
+ if (ad.last_update != animation_set_update_pass) {
+ // Was not updated, update. If the animation is duplicated, the second one will be ignored.
+ if (ad.animation != K.value || ad.animation_library != animation_libraries[i].name) {
+ // Animation changed, update and clear caches.
+ clear_cache_needed = true;
+ ad.animation = K.value;
+ ad.animation_library = animation_libraries[i].name;
+ }
+
+ ad.last_update = animation_set_update_pass;
+ }
+ }
+ }
}
- _ref_anim(p_animation);
- notify_property_list_changed();
- return OK;
+ // Check removed
+ List to_erase;
+ for (const KeyValue &E : animation_set) {
+ if (E.value.last_update != animation_set_update_pass) {
+ // Was not updated, must be erased
+ to_erase.push_back(E.key);
+ clear_cache_needed = true;
+ }
+ }
+
+ while (to_erase.size()) {
+ animation_set.erase(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+
+ if (clear_cache_needed) {
+ // If something was modified or removed, caches need to be cleared
+ clear_caches();
+ }
}
-void AnimationPlayer::remove_animation(const StringName &p_name) {
- ERR_FAIL_COND(!animation_set.has(p_name));
+void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) {
+ _animation_set_cache_update();
- stop();
- _unref_anim(animation_set[p_name].animation);
- animation_set.erase(p_name);
-
- clear_caches();
- notify_property_list_changed();
+ update_configuration_warnings();
}
-void AnimationPlayer::_ref_anim(const Ref &p_anim) {
- Ref(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED);
+void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) {
+ StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name));
+
+ if (!animation_set.has(name)) {
+ return; // No need to update because not the one from the library being used.
+ }
+ _animation_set_cache_update();
+
+ // Erase blends if needed
+ List to_erase;
+ for (const KeyValue &E : blend_times) {
+ BlendKey bk = E.key;
+ if (bk.from == name || bk.to == name) {
+ to_erase.push_back(bk);
+ }
+ }
+
+ while (to_erase.size()) {
+ blend_times.erase(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+
+ update_configuration_warnings();
}
-void AnimationPlayer::_unref_anim(const Ref &p_anim) {
- Ref(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed));
-}
-
-void AnimationPlayer::rename_animation(const StringName &p_name, const StringName &p_new_name) {
- ERR_FAIL_COND(!animation_set.has(p_name));
- ERR_FAIL_COND(String(p_new_name).contains("/") || String(p_new_name).contains(":"));
- ERR_FAIL_COND(animation_set.has(p_new_name));
-
- stop();
- AnimationData ad = animation_set[p_name];
- ad.name = p_new_name;
- animation_set.erase(p_name);
- animation_set[p_new_name] = ad;
-
+void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) {
+ // Rename autoplay or blends if needed.
List to_erase;
Map to_insert;
for (const KeyValue &E : blend_times) {
BlendKey bk = E.key;
BlendKey new_bk = bk;
bool erase = false;
- if (bk.from == p_name) {
- new_bk.from = p_new_name;
+ if (bk.from == p_from_name) {
+ new_bk.from = p_to_name;
erase = true;
}
- if (bk.to == p_name) {
- new_bk.to = p_new_name;
+ if (bk.to == p_from_name) {
+ new_bk.to = p_to_name;
erase = true;
}
@@ -1239,12 +1302,184 @@ void AnimationPlayer::rename_animation(const StringName &p_name, const StringNam
to_insert.erase(to_insert.front());
}
- if (autoplay == p_name) {
- autoplay = p_new_name;
+ if (autoplay == p_from_name) {
+ autoplay = p_to_name;
+ }
+}
+
+void AnimationPlayer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) {
+ StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name));
+ StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name));
+
+ if (!animation_set.has(from_name)) {
+ return; // No need to update because not the one from the library being used.
+ }
+ _animation_set_cache_update();
+
+ _rename_animation(from_name, to_name);
+ update_configuration_warnings();
+}
+
+Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) {
+ ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER);
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
+#endif
+
+ int insert_pos = 0;
+
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ ERR_FAIL_COND_V_MSG(animation_libraries[i].name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name));
+ ERR_FAIL_COND_V_MSG(animation_libraries[i].library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + animation_libraries[i].name.operator String() + "'.");
+
+ if (animation_libraries[i].name.operator String() >= p_name.operator String()) {
+ break;
+ }
+
+ insert_pos++;
}
- clear_caches();
+ AnimationLibraryData ald;
+ ald.name = p_name;
+ ald.library = p_animation_library;
+
+ animation_libraries.insert(insert_pos, ald);
+
+ ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name));
+ ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name));
+ ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed), varray(p_name));
+
+ _animation_set_cache_update();
+
notify_property_list_changed();
+
+ update_configuration_warnings();
+ return OK;
+}
+
+void AnimationPlayer::remove_animation_library(const StringName &p_name) {
+ int at_pos = -1;
+
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ if (animation_libraries[i].name == p_name) {
+ at_pos = i;
+ break;
+ }
+ }
+
+ ERR_FAIL_COND(at_pos == -1);
+
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed));
+
+ stop();
+
+ for (const KeyValue> &K : animation_libraries[at_pos].library->animations) {
+ _unref_anim(K.value);
+ }
+
+ animation_libraries.remove_at(at_pos);
+ _animation_set_cache_update();
+
+ notify_property_list_changed();
+ update_configuration_warnings();
+}
+
+void AnimationPlayer::_ref_anim(const Ref &p_anim) {
+ Ref(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED);
+}
+
+void AnimationPlayer::_unref_anim(const Ref &p_anim) {
+ Ref(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed));
+}
+
+void AnimationPlayer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) {
+ if (p_name == p_new_name) {
+ return;
+ }
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + ".");
+#endif
+
+ bool found = false;
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ ERR_FAIL_COND_MSG(animation_libraries[i].name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name));
+ if (animation_libraries[i].name == p_name) {
+ found = true;
+ animation_libraries[i].name = p_new_name;
+ // rename connections
+ animation_libraries[i].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added));
+ animation_libraries[i].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added));
+ animation_libraries[i].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed));
+
+ animation_libraries[i].library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_new_name));
+ animation_libraries[i].library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_new_name));
+ animation_libraries[i].library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed), varray(p_new_name));
+
+ for (const KeyValue> &K : animation_libraries[i].library->animations) {
+ StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key));
+ StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key));
+ _rename_animation(old_name, new_name);
+ }
+ }
+ }
+
+ ERR_FAIL_COND(!found);
+
+ stop();
+
+ animation_libraries.sort(); // Must keep alphabetical order.
+
+ _animation_set_cache_update(); // Update cache.
+
+ notify_property_list_changed();
+}
+
+bool AnimationPlayer::has_animation_library(const StringName &p_name) const {
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ if (animation_libraries[i].name == p_name) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Ref AnimationPlayer::get_animation_library(const StringName &p_name) const {
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ if (animation_libraries[i].name == p_name) {
+ return animation_libraries[i].library;
+ }
+ }
+ ERR_FAIL_V(Ref());
+}
+
+TypedArray AnimationPlayer::_get_animation_library_list() const {
+ TypedArray ret;
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ ret.push_back(animation_libraries[i].name);
+ }
+ return ret;
+}
+
+void AnimationPlayer::get_animation_library_list(List *p_libraries) const {
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ p_libraries->push_back(animation_libraries[i].name);
+ }
+}
+
+TypedArray AnimationPlayer::get_configuration_warnings() const {
+ TypedArray warnings = Node::get_configuration_warnings();
+
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ for (const KeyValue> &K : animation_libraries[i].library->animations) {
+ if (animation_set.has(K.key) && animation_set[K.key].animation_library != animation_libraries[i].name) {
+ warnings.push_back(vformat(RTR("Animation '%s' in library '%s' is unused because another animation with the same name exists in library '%s'."), K.key, animation_libraries[i].name, animation_set[K.key].animation_library));
+ }
+ }
+ }
+ return warnings;
}
bool AnimationPlayer::has_animation(const StringName &p_name) const {
@@ -1585,7 +1820,16 @@ StringName AnimationPlayer::find_animation(const Ref &p_animation) co
}
}
- return "";
+ return StringName();
+}
+
+StringName AnimationPlayer::find_animation_library(const Ref &p_animation) const {
+ for (const KeyValue &E : animation_set) {
+ if (E.value.animation == p_animation) {
+ return E.value.animation_library;
+ }
+ }
+ return StringName();
}
void AnimationPlayer::set_autoplay(const String &p_name) {
@@ -1764,7 +2008,10 @@ Ref AnimationPlayer::apply_reset(bool p_user_initiated) {
AnimationPlayer *aux_player = memnew(AnimationPlayer);
EditorNode::get_singleton()->add_child(aux_player);
- aux_player->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim);
+ Ref al;
+ al.instantiate();
+ al->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim);
+ aux_player->add_animation_library("default", al);
aux_player->set_assigned_animation(SceneStringNames::get_singleton()->RESET);
// Forcing the use of the original root because the scene where original player belongs may be not the active one
Node *root = get_node(get_root());
@@ -1792,9 +2039,13 @@ bool AnimationPlayer::can_apply_reset() const {
#endif // TOOLS_ENABLED
void AnimationPlayer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationPlayer::add_animation);
- ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationPlayer::remove_animation);
- ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationPlayer::rename_animation);
+ ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationPlayer::add_animation_library);
+ ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationPlayer::remove_animation_library);
+ ClassDB::bind_method(D_METHOD("rename_animation_library", "name", "newname"), &AnimationPlayer::rename_animation_library);
+ ClassDB::bind_method(D_METHOD("has_animation_library", "name"), &AnimationPlayer::has_animation_library);
+ ClassDB::bind_method(D_METHOD("get_animation_library", "name"), &AnimationPlayer::get_animation_library);
+ ClassDB::bind_method(D_METHOD("get_animation_library_list"), &AnimationPlayer::_get_animation_library_list);
+
ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationPlayer::has_animation);
ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation);
ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationPlayer::_get_animation_list);
@@ -1838,6 +2089,7 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::get_root);
ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation);
+ ClassDB::bind_method(D_METHOD("find_animation_library", "animation"), &AnimationPlayer::find_animation_library);
ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches);
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index a68f6b9d5b9..1d450175ad6 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -36,6 +36,7 @@
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
+#include "scene/resources/animation_library.h"
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
@@ -184,9 +185,20 @@ private:
StringName next;
Vector node_cache;
Ref animation;
+ StringName animation_library;
+ uint64_t last_update = 0;
};
Map animation_set;
+
+ struct AnimationLibraryData {
+ StringName name;
+ Ref library;
+ bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); }
+ };
+
+ LocalVector animation_libraries;
+
struct BlendKey {
StringName from;
StringName to;
@@ -261,6 +273,15 @@ private:
bool playing = false;
+ uint64_t animation_set_update_pass = 1;
+ void _animation_set_cache_update();
+ void _animation_added(const StringName &p_name, const StringName &p_library);
+ void _animation_removed(const StringName &p_name, const StringName &p_library);
+ void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library);
+ void _rename_animation(const StringName &p_from_name, const StringName &p_to_name);
+
+ TypedArray _get_animation_library_list() const;
+
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
@@ -272,13 +293,18 @@ protected:
public:
StringName find_animation(const Ref &p_animation) const;
+ StringName find_animation_library(const Ref &p_animation) const;
+
+ Error add_animation_library(const StringName &p_name, const Ref &p_animation_library);
+ void remove_animation_library(const StringName &p_name);
+ void rename_animation_library(const StringName &p_name, const StringName &p_new_name);
+ Ref get_animation_library(const StringName &p_name) const;
+ void get_animation_library_list(List *p_animations) const;
+ bool has_animation_library(const StringName &p_name) const;
- Error add_animation(const StringName &p_name, const Ref &p_animation);
- void remove_animation(const StringName &p_name);
- void rename_animation(const StringName &p_name, const StringName &p_new_name);
- bool has_animation(const StringName &p_name) const;
Ref get_animation(const StringName &p_name) const;
void get_animation_list(List *p_animations) const;
+ bool has_animation(const StringName &p_name) const;
void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, float p_time);
float get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const;
@@ -340,6 +366,8 @@ public:
bool can_apply_reset() const;
#endif
+ TypedArray get_configuration_warnings() const override;
+
AnimationPlayer();
~AnimationPlayer();
};
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 1e8a149e11a..307696c44a2 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -203,16 +203,18 @@ void OptionButton::pressed() {
}
void OptionButton::add_icon_item(const Ref &p_icon, const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_icon_radio_check_item(p_icon, p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
void OptionButton::add_item(const String &p_label, int p_id) {
+ bool first_selectable = !has_selectable_items();
popup->add_radio_check_item(p_label, p_id);
- if (popup->get_item_count() == 1) {
- select(0);
+ if (first_selectable) {
+ select(get_item_count() - 1);
}
}
@@ -280,6 +282,9 @@ bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
+bool OptionButton::is_item_separator(int p_idx) const {
+ return popup->is_item_separator(p_idx);
+}
void OptionButton::set_item_count(int p_count) {
ERR_FAIL_COND(p_count < 0);
@@ -299,12 +304,37 @@ void OptionButton::set_item_count(int p_count) {
notify_property_list_changed();
}
+bool OptionButton::has_selectable_items() const {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return true;
+ }
+ }
+ return false;
+}
+int OptionButton::get_selectable_item(bool p_from_last) const {
+ if (!p_from_last) {
+ for (int i = 0; i < get_item_count(); i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ } else {
+ for (int i = get_item_count() - 1; i >= 0; i++) {
+ if (!is_item_disabled(i) && !is_item_separator(i)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
int OptionButton::get_item_count() const {
return popup->get_item_count();
}
-void OptionButton::add_separator() {
- popup->add_separator();
+void OptionButton::add_separator(const String &p_text) {
+ popup->add_separator(p_text);
}
void OptionButton::clear() {
@@ -407,7 +437,8 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled);
- ClassDB::bind_method(D_METHOD("add_separator"), &OptionButton::add_separator);
+ ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
+ ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));
ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear);
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected);
@@ -420,6 +451,8 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);
+ ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items);
+ ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false));
// "selected" property must come after "item_count", otherwise GH-10213 occurs.
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index 921b76c52a2..78961326265 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -77,12 +77,16 @@ public:
int get_item_index(int p_id) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
+ bool is_item_separator(int p_idx) const;
String get_item_tooltip(int p_idx) const;
+ bool has_selectable_items() const;
+ int get_selectable_item(bool p_from_last = false) const;
+
void set_item_count(int p_count);
int get_item_count() const;
- void add_separator();
+ void add_separator(const String &p_text = "");
void clear();
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index ccd24ed2cf5..24cd485e1b0 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -101,6 +101,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
if (tree->popup_edited_item == this) {
tree->popup_edited_item = nullptr;
+ tree->popup_pressing_edited_item = nullptr;
tree->pressing_for_editor = false;
}
@@ -2670,8 +2671,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int
}
click_handled = true;
- popup_edited_item = p_item;
- popup_edited_item_col = col;
+ popup_pressing_edited_item = p_item;
+ popup_pressing_edited_item_column = col;
pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h));
pressing_for_editor_text = editor_text;
@@ -3206,10 +3207,16 @@ void Tree::gui_input(const Ref &p_event) {
update();
}
- if (pressing_for_editor && popup_edited_item && (popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_RANGE)) {
- //range drag
+ if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
+ /* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */
+ popup_edited_item = popup_pressing_edited_item;
+ popup_edited_item_col = popup_pressing_edited_item_column;
+
+ popup_pressing_edited_item = nullptr;
+ popup_pressing_edited_item_column = -1;
if (!range_drag_enabled) {
+ //range drag
Vector2 cpos = mm->get_position();
if (rtl) {
cpos.x = get_size().width - cpos.x;
@@ -3994,6 +4001,7 @@ void Tree::clear() {
selected_item = nullptr;
edited_item = nullptr;
popup_edited_item = nullptr;
+ popup_pressing_edited_item = nullptr;
update();
};
@@ -4309,12 +4317,16 @@ int Tree::get_pressed_button() const {
return pressed_button;
}
-Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
+Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const {
ERR_FAIL_NULL_V(p_item, Rect2());
ERR_FAIL_COND_V(p_item->tree != this, Rect2());
if (p_column != -1) {
ERR_FAIL_INDEX_V(p_column, columns.size(), Rect2());
}
+ if (p_button != -1) {
+ ERR_FAIL_COND_V(p_column == -1, Rect2()); // pass a column if you want to pass a button
+ ERR_FAIL_INDEX_V(p_button, p_item->cells[p_column].buttons.size(), Rect2());
+ }
int ofs = get_item_offset(p_item);
int height = compute_item_height(p_item);
@@ -4332,6 +4344,19 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const {
}
r.position.x = accum;
r.size.x = get_column_width(p_column);
+ if (p_button != -1) {
+ const TreeItem::Cell &c = p_item->cells[p_column];
+ Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y);
+ for (int j = c.buttons.size() - 1; j >= 0; j--) {
+ Ref b = c.buttons[j].texture;
+ Size2 size = b->get_size() + cache.button_pressed->get_minimum_size();
+ ofst.x -= size.x;
+
+ if (j == p_button) {
+ return Rect2(ofst, size);
+ }
+ }
+ }
}
return r;
@@ -4870,7 +4895,7 @@ void Tree::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column);
ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected);
ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect);
- ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::get_item_rect, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column", "button_index"), &Tree::get_item_rect, DEFVAL(-1), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position);
ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position);
ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 74ad4f94b87..b7044954440 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -379,6 +379,9 @@ private:
TreeItem *selected_item = nullptr;
TreeItem *edited_item = nullptr;
+ TreeItem *popup_pressing_edited_item = nullptr; // Candidate.
+ int popup_pressing_edited_item_column = -1;
+
TreeItem *drop_mode_over = nullptr;
int drop_mode_section = 0;
@@ -673,7 +676,7 @@ public:
Rect2 get_custom_popup_rect() const;
int get_item_offset(TreeItem *p_item) const;
- Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const;
+ Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const;
bool edit_selected();
bool is_editing();
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 5b56d9b11e0..127e734e6cb 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -139,6 +139,7 @@
#include "scene/multiplayer/scene_cache_interface.h"
#include "scene/multiplayer/scene_replication_interface.h"
#include "scene/multiplayer/scene_rpc_interface.h"
+#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_sample.h"
#include "scene/resources/bit_map.h"
#include "scene/resources/box_shape_3d.h"
@@ -834,6 +835,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CompressedTexture2DArray);
GDREGISTER_CLASS(Animation);
+ GDREGISTER_CLASS(AnimationLibrary);
GDREGISTER_CLASS(FontData);
GDREGISTER_CLASS(Font);
GDREGISTER_CLASS(Curve);
diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp
new file mode 100644
index 00000000000..f7b8c6a6488
--- /dev/null
+++ b/scene/resources/animation_library.cpp
@@ -0,0 +1,134 @@
+/*************************************************************************/
+/* animation_library.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "animation_library.h"
+
+Error AnimationLibrary::add_animation(const StringName &p_name, const Ref &p_animation) {
+ ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
+ ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
+
+ if (animations.has(p_name)) {
+ animations.erase(p_name);
+ emit_signal(SNAME("animation_removed"), p_name);
+ }
+
+ animations.insert(p_name, p_animation);
+ emit_signal(SNAME("animation_added"), p_name);
+ notify_property_list_changed();
+ return OK;
+}
+
+void AnimationLibrary::remove_animation(const StringName &p_name) {
+ ERR_FAIL_COND(!animations.has(p_name));
+
+ animations.erase(p_name);
+ emit_signal(SNAME("animation_removed"), p_name);
+ notify_property_list_changed();
+}
+
+void AnimationLibrary::rename_animation(const StringName &p_name, const StringName &p_new_name) {
+ ERR_FAIL_COND(!animations.has(p_name));
+ ERR_FAIL_COND_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), "Invalid animation name: " + String(p_name) + ".");
+ ERR_FAIL_COND(animations.has(p_new_name));
+
+ animations.insert(p_new_name, animations[p_name]);
+ animations.erase(p_name);
+ emit_signal(SNAME("animation_renamed"), p_name, p_new_name);
+}
+
+bool AnimationLibrary::has_animation(const StringName &p_name) const {
+ return animations.has(p_name);
+}
+
+Ref AnimationLibrary::get_animation(const StringName &p_name) const {
+ ERR_FAIL_COND_V(!animations.has(p_name), Ref());
+
+ return animations[p_name];
+}
+
+TypedArray AnimationLibrary::_get_animation_list() const {
+ TypedArray ret;
+ List names;
+ get_animation_list(&names);
+ for (const StringName &K : names) {
+ ret.push_back(K);
+ }
+ return ret;
+}
+
+void AnimationLibrary::get_animation_list(List *p_animations) const {
+ List anims;
+
+ for (const KeyValue> &E : animations) {
+ anims.push_back(E.key);
+ }
+
+ anims.sort_custom();
+
+ for (const StringName &E : anims) {
+ p_animations->push_back(E);
+ }
+}
+
+void AnimationLibrary::_set_data(const Dictionary &p_data) {
+ animations.clear();
+ List keys;
+ p_data.get_key_list(&keys);
+ for (const Variant &K : keys) {
+ add_animation(K, p_data[K]);
+ }
+}
+
+Dictionary AnimationLibrary::_get_data() const {
+ Dictionary ret;
+ for (const KeyValue> &K : animations) {
+ ret[K.key] = K.value;
+ }
+ return ret;
+}
+
+void AnimationLibrary::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationLibrary::add_animation);
+ ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationLibrary::remove_animation);
+ ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationLibrary::rename_animation);
+ ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationLibrary::has_animation);
+ ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationLibrary::get_animation);
+ ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationLibrary::_get_animation_list);
+
+ ClassDB::bind_method(D_METHOD("_set_data", "data"), &AnimationLibrary::_set_data);
+ ClassDB::bind_method(D_METHOD("_get_data"), &AnimationLibrary::_get_data);
+
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data");
+ ADD_SIGNAL(MethodInfo("animation_added", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation")));
+ ADD_SIGNAL(MethodInfo("animation_removed", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation")));
+ ADD_SIGNAL(MethodInfo("animation_renamed", PropertyInfo(Variant::OBJECT, "name", PROPERTY_HINT_RESOURCE_TYPE, "Animation"), PropertyInfo(Variant::OBJECT, "to_name", PROPERTY_HINT_RESOURCE_TYPE, "Animation")));
+}
+AnimationLibrary::AnimationLibrary() {
+}
diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h
new file mode 100644
index 00000000000..69ac5a97d26
--- /dev/null
+++ b/scene/resources/animation_library.h
@@ -0,0 +1,62 @@
+/*************************************************************************/
+/* animation_library.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef ANIMATION_LIBRARY_H
+#define ANIMATION_LIBRARY_H
+
+#include "core/variant/typed_array.h"
+#include "scene/resources/animation.h"
+
+class AnimationLibrary : public Resource {
+ GDCLASS(AnimationLibrary, Resource)
+
+ void _set_data(const Dictionary &p_data);
+ Dictionary _get_data() const;
+
+ TypedArray _get_animation_list() const;
+
+ friend class AnimationPlayer; //for faster access
+ Map> animations;
+
+protected:
+ static void _bind_methods();
+
+public:
+ Error add_animation(const StringName &p_name, const Ref &p_animation);
+ void remove_animation(const StringName &p_name);
+ void rename_animation(const StringName &p_name, const StringName &p_new_name);
+ bool has_animation(const StringName &p_name) const;
+ Ref get_animation(const StringName &p_name) const;
+ void get_animation_list(List *p_animations) const;
+
+ AnimationLibrary();
+};
+
+#endif // ANIMATIONLIBRARY_H
]