Merge pull request #59980 from reduz/animation-libraries

This commit is contained in:
Rémi Verschelde 2022-04-11 14:18:35 +02:00 committed by GitHub
commit 4ab86c6731
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 1742 additions and 394 deletions

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AnimationLibrary" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
</brief_description>
<description>
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_animation">
<return type="int" enum="Error" />
<argument index="0" name="name" type="StringName" />
<argument index="1" name="animation" type="Animation" />
<description>
</description>
</method>
<method name="get_animation" qualifiers="const">
<return type="Animation" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="get_animation_list" qualifiers="const">
<return type="StringName[]" />
<description>
</description>
</method>
<method name="has_animation" qualifiers="const">
<return type="bool" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="remove_animation">
<return type="void" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="rename_animation">
<return type="void" />
<argument index="0" name="name" type="StringName" />
<argument index="1" name="newname" type="StringName" />
<description>
</description>
</method>
</methods>
<members>
<member name="_data" type="Dictionary" setter="_set_data" getter="_get_data" default="{}">
</member>
</members>
<signals>
<signal name="animation_added">
<argument index="0" name="name" type="Animation" />
<description>
</description>
</signal>
<signal name="animation_removed">
<argument index="0" name="name" type="Animation" />
<description>
</description>
</signal>
<signal name="animation_renamed">
<argument index="0" name="name" type="Animation" />
<argument index="1" name="to_name" type="Animation" />
<description>
</description>
</signal>
</signals>
</class>

View file

@ -14,12 +14,11 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link> <link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials> </tutorials>
<methods> <methods>
<method name="add_animation"> <method name="add_animation_library">
<return type="int" enum="Error" /> <return type="int" enum="Error" />
<argument index="0" name="name" type="StringName" /> <argument index="0" name="name" type="StringName" />
<argument index="1" name="animation" type="Animation" /> <argument index="1" name="library" type="AnimationLibrary" />
<description> <description>
Adds [code]animation[/code] to the player accessible with the key [code]name[/code].
</description> </description>
</method> </method>
<method name="advance"> <method name="advance">
@ -63,6 +62,12 @@
Returns the name of [code]animation[/code] or an empty string if not found. Returns the name of [code]animation[/code] or an empty string if not found.
</description> </description>
</method> </method>
<method name="find_animation_library" qualifiers="const">
<return type="StringName" />
<argument index="0" name="animation" type="Animation" />
<description>
</description>
</method>
<method name="get_animation" qualifiers="const"> <method name="get_animation" qualifiers="const">
<return type="Animation" /> <return type="Animation" />
<argument index="0" name="name" type="StringName" /> <argument index="0" name="name" type="StringName" />
@ -70,6 +75,17 @@
Returns the [Animation] with key [code]name[/code] or [code]null[/code] if not found. Returns the [Animation] with key [code]name[/code] or [code]null[/code] if not found.
</description> </description>
</method> </method>
<method name="get_animation_library" qualifiers="const">
<return type="AnimationLibrary" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="get_animation_library_list" qualifiers="const">
<return type="StringName[]" />
<description>
</description>
</method>
<method name="get_animation_list" qualifiers="const"> <method name="get_animation_list" qualifiers="const">
<return type="PackedStringArray" /> <return type="PackedStringArray" />
<description> <description>
@ -103,6 +119,12 @@
Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [code]name[/code]. Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [code]name[/code].
</description> </description>
</method> </method>
<method name="has_animation_library" qualifiers="const">
<return type="bool" />
<argument index="0" name="name" type="StringName" />
<description>
</description>
</method>
<method name="is_playing" qualifiers="const"> <method name="is_playing" qualifiers="const">
<return type="bool" /> <return type="bool" />
<description> <description>
@ -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. [b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow.
</description> </description>
</method> </method>
<method name="remove_animation"> <method name="remove_animation_library">
<return type="void" /> <return type="void" />
<argument index="0" name="name" type="StringName" /> <argument index="0" name="name" type="StringName" />
<description> <description>
Removes the animation with key [code]name[/code].
</description> </description>
</method> </method>
<method name="rename_animation"> <method name="rename_animation_library">
<return type="void" /> <return type="void" />
<argument index="0" name="name" type="StringName" /> <argument index="0" name="name" type="StringName" />
<argument index="1" name="newname" type="StringName" /> <argument index="1" name="newname" type="StringName" />
<description> <description>
Renames an existing animation with key [code]name[/code] to [code]newname[/code].
</description> </description>
</method> </method>
<method name="seek"> <method name="seek">

View file

@ -30,8 +30,9 @@
</method> </method>
<method name="add_separator"> <method name="add_separator">
<return type="void" /> <return type="void" />
<argument index="0" name="text" type="String" default="&quot;&quot;" />
<description> <description>
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.
</description> </description>
</method> </method>
<method name="clear"> <method name="clear">
@ -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. [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.
</description> </description>
</method> </method>
<method name="get_selectable_item" qualifiers="const">
<return type="int" />
<argument index="0" name="from_last" type="bool" default="false" />
<description>
</description>
</method>
<method name="get_selected_id" qualifiers="const"> <method name="get_selected_id" qualifiers="const">
<return type="int" /> <return type="int" />
<description> <description>
@ -101,6 +108,11 @@
Gets the metadata of the selected item. Metadata for items can be set using [method set_item_metadata]. Gets the metadata of the selected item. Metadata for items can be set using [method set_item_metadata].
</description> </description>
</method> </method>
<method name="has_selectable_items" qualifiers="const">
<return type="bool" />
<description>
</description>
</method>
<method name="is_item_disabled" qualifiers="const"> <method name="is_item_disabled" qualifiers="const">
<return type="bool" /> <return type="bool" />
<argument index="0" name="idx" type="int" /> <argument index="0" name="idx" type="int" />
@ -108,6 +120,12 @@
Returns [code]true[/code] if the item at index [code]idx[/code] is disabled. Returns [code]true[/code] if the item at index [code]idx[/code] is disabled.
</description> </description>
</method> </method>
<method name="is_item_separator" qualifiers="const">
<return type="bool" />
<argument index="0" name="idx" type="int" />
<description>
</description>
</method>
<method name="remove_item"> <method name="remove_item">
<return type="void" /> <return type="void" />
<argument index="0" name="idx" type="int" /> <argument index="0" name="idx" type="int" />

View file

@ -179,8 +179,9 @@
<return type="Rect2" /> <return type="Rect2" />
<argument index="0" name="item" type="TreeItem" /> <argument index="0" name="item" type="TreeItem" />
<argument index="1" name="column" type="int" default="-1" /> <argument index="1" name="column" type="int" default="-1" />
<argument index="2" name="button_index" type="int" default="-1" />
<description> <description>
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.
</description> </description>
</method> </method>
<method name="get_item_at_position" qualifiers="const"> <method name="get_item_at_position" qualifiers="const">

View file

@ -0,0 +1 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M14.519 2.006A6 6 0 0 0 8.599 8a6 6 0 0 0 5.92 5.994v-1.01a1 1 0 0 1-.92-.984 1 1 0 0 1 .92-.984V4.984a1 1 0 0 1-.92-.984 1 1 0 0 1 .92-.984Zm-3.432 2.996a1 1 0 0 1 .547.133 1 1 0 0 1 .367 1.365 1 1 0 0 1-1.367.365A1 1 0 0 1 10.27 5.5a1 1 0 0 1 .818-.498ZM11.111 9a1 1 0 0 1 .89.5 1 1 0 0 1-.367 1.365 1 1 0 0 1-1.365-.365 1 1 0 0 1 .365-1.365A1 1 0 0 1 11.111 9Z" style="fill:#e0e0e0;fill-opacity:1"/><path d="M11.094 2.104a6 6 0 0 0-5.92 5.994 6 6 0 0 0 5.92 5.994v-.023a5.795 6.506 0 0 1-2.89-3.104 1 1 0 0 1-1.36-.367 1 1 0 0 1 .365-1.365 1 1 0 0 1 .475-.135 5.795 6.506 0 0 1-.076-.984 5.795 6.506 0 0 1 .082-1.027 1 1 0 0 1-.48-.124 1 1 0 0 1-.366-1.365 1 1 0 0 1 .818-.498 1 1 0 0 1 .547.133 1 1 0 0 1 .004.002 5.795 6.506 0 0 1 2.881-3.076z" style="fill:#e0e0e0;fill-opacity:1"/><path d="M7.616 2.104a6 6 0 0 0-5.92 5.994 6 6 0 0 0 5.92 5.994v-.023a5.795 6.506 0 0 1-2.89-3.104 1 1 0 0 1-1.36-.367 1 1 0 0 1 .366-1.365 1 1 0 0 1 .474-.135 5.795 6.506 0 0 1-.076-.984 5.795 6.506 0 0 1 .082-1.027 1 1 0 0 1-.48-.124 1 1 0 0 1-.366-1.365 1 1 0 0 1 .819-.498 1 1 0 0 1 .547.133 1 1 0 0 1 .003.002 5.795 6.506 0 0 1 2.881-3.076z" style="fill:#e0e0e0;fill-opacity:1"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1801,7 +1801,14 @@ Node *EditorSceneFormatImporterCollada::import_scene(const String &p_path, uint3
name = state.animations[i]->get_name(); name = state.animations[i]->get_name();
} }
ap->add_animation(name, state.animations[i]); Ref<AnimationLibrary> 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); state.scene->add_child(ap, true);
ap->set_owner(state.scene); ap->set_owner(state.scene);

View file

@ -473,7 +473,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, Map<Ref<I
if (_teststr(animname, loop_strings[i])) { if (_teststr(animname, loop_strings[i])) {
anim->set_loop_mode(Animation::LoopMode::LOOP_LINEAR); anim->set_loop_mode(Animation::LoopMode::LOOP_LINEAR);
animname = _fixstr(animname, loop_strings[i]); animname = _fixstr(animname, loop_strings[i]);
ap->rename_animation(E, animname);
Ref<AnimationLibrary> 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<Ref<
Ref<Animation> saved_anim = _save_animation_to_file(anim, save, path, keep_custom); Ref<Animation> saved_anim = _save_animation_to_file(anim, save, path, keep_custom);
if (saved_anim != anim) { if (saved_anim != anim) {
ap->add_animation(name, saved_anim); //replace Ref<AnimationLibrary> 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<Animation> default_anim = anim->get_animation("default"); Ref<Animation> default_anim = anim->get_animation("default");
Ref<AnimationLibrary> al = anim->get_animation_library(anim->find_animation(default_anim));
for (int i = 0; i < p_clips.size(); i += 7) { for (int i = 0; i < p_clips.size(); i += 7) {
String name = p_clips[i]; 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_loop_mode(loop_mode);
new_anim->set_length(to - from); new_anim->set_length(to - from);
anim->add_animation(name, new_anim);
al->add_animation(name, new_anim);
Ref<Animation> saved_anim = _save_animation_to_file(new_anim, save_to_file, save_to_path, keep_current); Ref<Animation> saved_anim = _save_animation_to_file(new_anim, save_to_file, save_to_path, keep_current);
if (saved_anim != new_anim) { 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) { void ResourceImporterScene::_optimize_animations(AnimationPlayer *anim, float p_max_lin_error, float p_max_ang_error, float p_max_angle) {

View file

@ -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<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library);
ERR_FAIL_COND(!al.is_valid());
Ref<Animation> 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<AnimationLibrary> 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<String> 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<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library);
Ref<Animation> 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<String> 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<AnimationLibrary> 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<String> 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<Animation> 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<AnimationLibrary> al = ResourceLoader::load(p_path);
if (al.is_null()) {
error_dialog->set_text(TTR("Invalid AnimationLibrary file."));
error_dialog->popup_centered();
return;
}
TypedArray<StringName> libs = player->call("get_animation_library_list");
for (int i = 0; i < libs.size(); i++) {
const StringName K = libs[i];
Ref<AnimationLibrary> 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<Animation> anim = ResourceLoader::load(p_path);
if (anim.is_null()) {
error_dialog->set_text(TTR("Invalid Animation file."));
error_dialog->popup_centered();
return;
}
Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library);
List<StringName> anims;
al->get_animation_list(&anims);
for (const StringName &K : anims) {
Ref<Animation> 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<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library);
Ref<Animation> 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<AnimationLibrary> 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<AnimationLibrary> 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<String> 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<Animation> 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<AnimationLibrary> al = player->call("get_animation_library", lib_name);
Ref<Animation> 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<StringName> 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<AnimationLibrary> 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<StringName> 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<Animation> 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);
}

View file

@ -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

View file

@ -47,6 +47,8 @@
#include "scene/scene_string_names.h" #include "scene/scene_string_names.h"
#include "servers/rendering_server.h" #include "servers/rendering_server.h"
///////////////////////////////////
void AnimationPlayerEditor::_node_removed(Node *p_node) { void AnimationPlayerEditor::_node_removed(Node *p_node) {
if (player && player == p_node) { if (player && player == p_node) {
player = nullptr; 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"))) #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_NEW_ANIM, "New");
ITEM_ICON(TOOL_LOAD_ANIM, "Load"); ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary");
ITEM_ICON(TOOL_SAVE_ANIM, "Save");
ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save");
ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate"); ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate");
ITEM_ICON(TOOL_RENAME_ANIM, "Rename"); ITEM_ICON(TOOL_RENAME_ANIM, "Rename");
ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend"); ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend");
@ -166,7 +166,7 @@ void AnimationPlayerEditor::_autoplay_pressed() {
if (updating) { if (updating) {
return; return;
} }
if (animation->get_item_count() == 0) { if (animation->has_selectable_items() == 0) {
return; return;
} }
@ -192,10 +192,7 @@ void AnimationPlayerEditor::_autoplay_pressed() {
} }
void AnimationPlayerEditor::_play_pressed() { void AnimationPlayerEditor::_play_pressed() {
String current; String current = _get_current();
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
current = animation->get_item_text(animation->get_selected());
}
if (!current.is_empty()) { if (!current.is_empty()) {
if (current == player->get_assigned_animation()) { if (current == player->get_assigned_animation()) {
@ -209,10 +206,7 @@ void AnimationPlayerEditor::_play_pressed() {
} }
void AnimationPlayerEditor::_play_from_pressed() { void AnimationPlayerEditor::_play_from_pressed() {
String current; String current = _get_current();
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
current = animation->get_item_text(animation->get_selected());
}
if (!current.is_empty()) { if (!current.is_empty()) {
float time = player->get_current_animation_position(); float time = player->get_current_animation_position();
@ -229,12 +223,15 @@ void AnimationPlayerEditor::_play_from_pressed() {
stop->set_pressed(false); stop->set_pressed(false);
} }
void AnimationPlayerEditor::_play_bw_pressed() { String AnimationPlayerEditor::_get_current() const {
String current; 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()); 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.is_empty()) {
if (current == player->get_assigned_animation()) { if (current == player->get_assigned_animation()) {
player->stop(); //so it won't blend with itself player->stop(); //so it won't blend with itself
@ -247,10 +244,7 @@ void AnimationPlayerEditor::_play_bw_pressed() {
} }
void AnimationPlayerEditor::_play_bw_from_pressed() { void AnimationPlayerEditor::_play_bw_from_pressed() {
String current; String current = _get_current();
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
current = animation->get_item_text(animation->get_selected());
}
if (!current.is_empty()) { if (!current.is_empty()) {
float time = player->get_current_animation_position(); 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 // 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 // ui-wise is that it should play/blend the next one if currently playing
String current; String current = _get_current();
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
current = animation->get_item_text(animation->get_selected());
}
if (!current.is_empty()) { if (!current.is_empty()) {
player->set_assigned_animation(current); player->set_assigned_animation(current);
@ -330,6 +321,20 @@ void AnimationPlayerEditor::_animation_new() {
break; break;
} }
List<StringName> 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->set_text(base);
name_dialog->popup_centered(Size2(300, 90)); name_dialog->popup_centered(Size2(300, 90));
name->select_all(); name->select_all();
@ -337,7 +342,7 @@ void AnimationPlayerEditor::_animation_new() {
} }
void AnimationPlayerEditor::_animation_rename() { void AnimationPlayerEditor::_animation_rename() {
if (animation->get_item_count() == 0) { if (!animation->has_selectable_items()) {
return; return;
} }
int selected = animation->get_selected(); int selected = animation->get_selected();
@ -349,84 +354,11 @@ void AnimationPlayerEditor::_animation_rename() {
name_dialog->popup_centered(Size2(300, 90)); name_dialog->popup_centered(Size2(300, 90));
name->select_all(); name->select_all();
name->grab_focus(); name->grab_focus();
} library->hide();
void AnimationPlayerEditor::_animation_load() {
ERR_FAIL_COND(!player);
file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
file->clear_filters();
List<String> 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<Resource> &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<Resource> &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<Resource> &p_resource) {
file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
List<String> 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();
} }
void AnimationPlayerEditor::_animation_remove() { void AnimationPlayerEditor::_animation_remove() {
if (animation->get_item_count() == 0) { if (!animation->has_selectable_items()) {
return; return;
} }
@ -440,6 +372,9 @@ void AnimationPlayerEditor::_animation_remove_confirmed() {
String current = animation->get_item_text(animation->get_selected()); String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current); Ref<Animation> anim = player->get_animation(current);
Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim));
ERR_FAIL_COND(al.is_null());
undo_redo->create_action(TTR("Remove Animation")); undo_redo->create_action(TTR("Remove Animation"));
if (player->get_autoplay() == current) { if (player->get_autoplay() == current) {
undo_redo->add_do_method(player, "set_autoplay", ""); 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. // 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(this, "_animation_player_changed", player);
} }
undo_redo->add_do_method(player, "remove_animation", current); undo_redo->add_do_method(al.ptr(), "remove_animation", current);
undo_redo->add_undo_method(player, "add_animation", current, anim); undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim);
undo_redo->add_do_method(this, "_animation_player_changed", player); undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_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_do_method(this, "_stop_onion_skinning");
undo_redo->add_undo_method(this, "_start_onion_skinning"); undo_redo->add_undo_method(this, "_start_onion_skinning");
} }
@ -498,7 +433,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
return; 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(); name_dialog->hide();
return; return;
} }
@ -514,10 +449,13 @@ void AnimationPlayerEditor::_animation_name_edited() {
String current = animation->get_item_text(animation->get_selected()); String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current); Ref<Animation> anim = player->get_animation(current);
Ref<AnimationLibrary> 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->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_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_undo_method(anim.ptr(), "set_name", current);
undo_redo->add_do_method(this, "_animation_player_changed", player); undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_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<Animation> new_anim = Ref<Animation>(memnew(Animation)); Ref<Animation> new_anim = Ref<Animation>(memnew(Animation));
new_anim->set_name(new_name); new_anim->set_name(new_name);
Ref<AnimationLibrary> 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->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_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_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_do_method(this, "_start_onion_skinning");
undo_redo->add_undo_method(this, "_stop_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(); undo_redo->commit_action();
_select_anim_by_name(new_name); _select_anim_by_name(new_name);
@ -551,9 +509,11 @@ void AnimationPlayerEditor::_animation_name_edited() {
Ref<Animation> new_anim = _animation_clone(anim); Ref<Animation> new_anim = _animation_clone(anim);
new_anim->set_name(new_name); new_anim->set_name(new_name);
Ref<AnimationLibrary> library = player->get_animation_library(player->find_animation_library(anim));
undo_redo->create_action(TTR("Duplicate Animation")); undo_redo->create_action(TTR("Duplicate Animation"));
undo_redo->add_do_method(player, "add_animation", new_name, new_anim); undo_redo->add_do_method(library.ptr(), "add_animation", new_name, new_anim);
undo_redo->add_undo_method(player, "remove_animation", new_name); 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(player, "animation_set_next", new_name, player->animation_get_next(current));
undo_redo->add_do_method(this, "_animation_player_changed", player); undo_redo->add_do_method(this, "_animation_player_changed", player);
undo_redo->add_undo_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) { void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) {
if (animation->get_item_count() == 0) { if (!animation->has_selectable_items()) {
return; return;
} }
@ -588,7 +548,7 @@ void AnimationPlayerEditor::_animation_blend() {
blend_editor.tree->clear(); blend_editor.tree->clear();
if (animation->get_item_count() == 0) { if (!animation->has_selectable_items()) {
return; return;
} }
@ -643,7 +603,7 @@ void AnimationPlayerEditor::_blend_edited() {
return; return;
} }
if (animation->get_item_count() == 0) { if (!animation->has_selectable_items()) {
return; return;
} }
@ -722,16 +682,16 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
} }
void AnimationPlayerEditor::_animation_resource_edit() { void AnimationPlayerEditor::_animation_resource_edit() {
if (animation->get_item_count()) { String current = _get_current();
String current = animation->get_item_text(animation->get_selected()); if (current != String()) {
Ref<Animation> anim = player->get_animation(current); Ref<Animation> anim = player->get_animation(current);
EditorNode::get_singleton()->edit_resource(anim); EditorNode::get_singleton()->edit_resource(anim);
} }
} }
void AnimationPlayerEditor::_animation_edit() { void AnimationPlayerEditor::_animation_edit() {
if (animation->get_item_count()) { String current = _get_current();
String current = animation->get_item_text(animation->get_selected()); if (current != String()) {
Ref<Animation> anim = player->get_animation(current); Ref<Animation> anim = player->get_animation(current);
track_editor->set_animation(anim); 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<Animation> anim = player->get_animation(current);
ERR_FAIL_COND(!Object::cast_to<Resource>(*anim));
RES current_res = RES(Object::cast_to<Resource>(*anim));
_animation_save_in_path(current_res, p_file);
}
}
void AnimationPlayerEditor::_load_animations(Vector<String> p_files) {
ERR_FAIL_COND(!player);
for (int i = 0; i < p_files.size(); i++) {
String file = p_files[i];
Ref<Resource> 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) { void AnimationPlayerEditor::_scale_changed(const String &p_scale) {
player->set_speed_scale(p_scale.to_float()); player->set_speed_scale(p_scale.to_float());
} }
@ -824,49 +739,66 @@ void AnimationPlayerEditor::_update_animation() {
void AnimationPlayerEditor::_update_player() { void AnimationPlayerEditor::_update_player() {
updating = true; updating = true;
List<StringName> animlist;
if (player) {
player->get_animation_list(&animlist);
}
animation->clear(); 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) { if (!player) {
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
return; return;
} }
int active_idx = -1; List<StringName> libraries;
for (const StringName &E : animlist) { if (player) {
animation->add_item(E); player->get_animation_library_list(&libraries);
}
if (player->get_assigned_animation() == E) { int active_idx = -1;
active_idx = animation->get_item_count() - 1; bool no_anims_found = true;
for (const StringName &K : libraries) {
if (K != StringName()) {
animation->add_separator(K);
}
Ref<AnimationLibrary> library = player->get_animation_library(K);
List<StringName> 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(); _update_animation_list_icons();
updating = false; updating = false;
@ -874,16 +806,16 @@ void AnimationPlayerEditor::_update_player() {
animation->select(active_idx); animation->select(active_idx);
autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay()); autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay());
_animation_selected(active_idx); _animation_selected(active_idx);
} else if (animation->has_selectable_items()) {
} else if (animation->get_item_count() > 0) { int item = animation->get_selectable_item();
animation->select(0); animation->select(item);
autoplay->set_pressed(animation->get_item_text(0) == player->get_autoplay()); autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay());
_animation_selected(0); _animation_selected(item);
} else { } else {
_animation_selected(0); _animation_selected(0);
} }
if (animation->get_item_count()) { if (!no_anims_found) {
String current = animation->get_item_text(animation->get_selected()); String current = animation->get_item_text(animation->get_selected());
Ref<Animation> anim = player->get_animation(current); Ref<Animation> anim = player->get_animation(current);
track_editor->set_animation(anim); track_editor->set_animation(anim);
@ -899,6 +831,9 @@ void AnimationPlayerEditor::_update_player() {
void AnimationPlayerEditor::_update_animation_list_icons() { void AnimationPlayerEditor::_update_animation_list_icons() {
for (int i = 0; i < animation->get_item_count(); i++) { for (int i = 0; i < animation->get_item_count(); i++) {
String name = animation->get_item_text(i); String name = animation->get_item_text(i);
if (animation->is_item_disabled(i) || animation->is_item_separator(i)) {
continue;
}
Ref<Texture2D> icon; Ref<Texture2D> icon;
if (name == player->get_autoplay()) { if (name == player->get_autoplay()) {
@ -925,7 +860,7 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
_update_player(); _update_player();
if (onion.enabled) { if (onion.enabled) {
if (animation->get_item_count() > 0) { if (animation->has_selectable_items()) {
_start_onion_skinning(); _start_onion_skinning();
} else { } else {
_stop_onion_skinning(); _stop_onion_skinning();
@ -940,6 +875,8 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
track_editor->show_select_node_warning(true); track_editor->show_select_node_warning(true);
} }
library_editor->set_animation_player(player);
} }
void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) { 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() { void AnimationPlayerEditor::_animation_duplicate() {
if (!animation->get_item_count()) { if (!animation->has_selectable_items()) {
return; return;
} }
@ -1031,29 +968,6 @@ Ref<Animation> AnimationPlayerEditor::_animation_clone(Ref<Animation> p_anim) {
return new_anim; return new_anim;
} }
void AnimationPlayerEditor::_animation_paste(Ref<Animation> 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) { void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) {
if (updating || !player || player->is_playing()) { if (updating || !player || player->is_playing()) {
return; return;
@ -1095,6 +1009,9 @@ void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
if (blend_editor.dialog->is_visible()) { if (blend_editor.dialog->is_visible()) {
_animation_blend(); // Update. _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) { void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
String current; String current = _get_current();
if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) {
current = animation->get_item_text(animation->get_selected());
}
Ref<Animation> anim; Ref<Animation> anim;
if (!current.is_empty()) { if (!current.is_empty()) {
@ -1148,18 +1062,9 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
case TOOL_NEW_ANIM: { case TOOL_NEW_ANIM: {
_animation_new(); _animation_new();
} break; } break;
case TOOL_LOAD_ANIM: { case TOOL_ANIM_LIBRARY: {
_animation_load(); library_editor->set_animation_player(player);
} break; library_editor->show_dialog();
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);
}
} break; } break;
case TOOL_DUPLICATE_ANIM: { case TOOL_DUPLICATE_ANIM: {
_animation_duplicate(); _animation_duplicate();
@ -1173,39 +1078,8 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
case TOOL_REMOVE_ANIM: { case TOOL_REMOVE_ANIM: {
_animation_remove(); _animation_remove();
} break; } 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<Animation> anim2 = player->get_animation(current2);
EditorSettings::get_singleton()->set_resource_clipboard(anim2);
} break;
case TOOL_PASTE_ANIM: {
Ref<Animation> 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<Animation> new_anim = _animation_clone(anim2);
_animation_paste(new_anim);
} break;
case TOOL_PASTE_ANIM_REF: {
Ref<Animation> 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: { 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->set_text(TTR("No animation to edit!"));
error_dialog->popup_centered(); error_dialog->popup_centered();
return; return;
@ -1300,7 +1174,7 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
} }
void AnimationPlayerEditor::_editor_visibility_changed() { void AnimationPlayerEditor::_editor_visibility_changed() {
if (is_visible() && animation->get_item_count() > 0) { if (is_visible() && animation->has_selectable_items()) {
_start_onion_skinning(); _start_onion_skinning();
} }
} }
@ -1536,7 +1410,6 @@ void AnimationPlayerEditor::_pin_pressed() {
void AnimationPlayerEditor::_bind_methods() { void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new); 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_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_remove"), &AnimationPlayerEditor::_animation_remove);
ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend); ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend);
ClassDB::bind_method(D_METHOD("_animation_edit"), &AnimationPlayerEditor::_animation_edit); 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->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_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM);
tool_anim->get_popup()->add_separator(); 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/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY);
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_separator(); 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_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM);
tool_anim->get_popup()->add_separator(); 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_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_separator();
tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM); 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); hb->add_child(tool_anim);
animation = memnew(OptionButton); animation = memnew(OptionButton);
@ -1705,8 +1573,14 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
name_title = memnew(Label(TTR("Animation Name:"))); name_title = memnew(Label(TTR("Animation Name:")));
vb->add_child(name_title); vb->add_child(name_title);
HBoxContainer *name_hb = memnew(HBoxContainer);
name = memnew(LineEdit); 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); name_dialog->register_text_enter(name);
error_dialog = memnew(ConfirmationDialog); error_dialog = memnew(ConfirmationDialog);
@ -1742,8 +1616,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); 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)); 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)); scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
@ -1759,6 +1631,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
_update_player(); _update_player();
library_editor = memnew(AnimationLibraryEditor);
add_child(library_editor);
library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
// Onion skinning. // Onion skinning.
track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed)); track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));

View file

@ -33,6 +33,7 @@
#include "editor/animation_track_editor.h" #include "editor/animation_track_editor.h"
#include "editor/editor_plugin.h" #include "editor/editor_plugin.h"
#include "editor/plugins/animation_library_editor.h"
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "scene/gui/dialogs.h" #include "scene/gui/dialogs.h"
#include "scene/gui/slider.h" #include "scene/gui/slider.h"
@ -40,7 +41,6 @@
#include "scene/gui/texture_button.h" #include "scene/gui/texture_button.h"
#include "scene/gui/tree.h" #include "scene/gui/tree.h"
class EditorFileDialog;
class AnimationPlayerEditorPlugin; class AnimationPlayerEditorPlugin;
class AnimationPlayerEditor : public VBoxContainer { class AnimationPlayerEditor : public VBoxContainer {
@ -51,16 +51,11 @@ class AnimationPlayerEditor : public VBoxContainer {
enum { enum {
TOOL_NEW_ANIM, TOOL_NEW_ANIM,
TOOL_LOAD_ANIM, TOOL_ANIM_LIBRARY,
TOOL_SAVE_ANIM,
TOOL_SAVE_AS_ANIM,
TOOL_DUPLICATE_ANIM, TOOL_DUPLICATE_ANIM,
TOOL_RENAME_ANIM, TOOL_RENAME_ANIM,
TOOL_EDIT_TRANSITIONS, TOOL_EDIT_TRANSITIONS,
TOOL_REMOVE_ANIM, TOOL_REMOVE_ANIM,
TOOL_COPY_ANIM,
TOOL_PASTE_ANIM,
TOOL_PASTE_ANIM_REF,
TOOL_EDIT_RESOURCE TOOL_EDIT_RESOURCE
}; };
@ -103,8 +98,10 @@ class AnimationPlayerEditor : public VBoxContainer {
SpinBox *frame = nullptr; SpinBox *frame = nullptr;
LineEdit *scale = nullptr; LineEdit *scale = nullptr;
LineEdit *name = nullptr; LineEdit *name = nullptr;
OptionButton *library = nullptr;
Label *name_title = nullptr; Label *name_title = nullptr;
UndoRedo *undo_redo = nullptr; UndoRedo *undo_redo = nullptr;
Ref<Texture2D> autoplay_icon; Ref<Texture2D> autoplay_icon;
Ref<Texture2D> reset_icon; Ref<Texture2D> reset_icon;
Ref<ImageTexture> autoplay_reset_icon; Ref<ImageTexture> autoplay_reset_icon;
@ -114,6 +111,8 @@ class AnimationPlayerEditor : public VBoxContainer {
EditorFileDialog *file = nullptr; EditorFileDialog *file = nullptr;
ConfirmationDialog *delete_dialog = nullptr; ConfirmationDialog *delete_dialog = nullptr;
AnimationLibraryEditor *library_editor = nullptr;
struct BlendEditor { struct BlendEditor {
AcceptDialog *dialog = nullptr; AcceptDialog *dialog = nullptr;
Tree *tree = nullptr; Tree *tree = nullptr;
@ -173,11 +172,6 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_new(); void _animation_new();
void _animation_rename(); void _animation_rename();
void _animation_name_edited(); void _animation_name_edited();
void _animation_load();
void _animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path);
void _animation_save(const Ref<Resource> &p_resource);
void _animation_save_as(const Ref<Resource> &p_resource);
void _animation_remove(); void _animation_remove();
void _animation_remove_confirmed(); void _animation_remove_confirmed();
@ -185,11 +179,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _animation_edit(); void _animation_edit();
void _animation_duplicate(); void _animation_duplicate();
Ref<Animation> _animation_clone(const Ref<Animation> p_anim); Ref<Animation> _animation_clone(const Ref<Animation> p_anim);
void _animation_paste(const Ref<Animation> p_anim);
void _animation_resource_edit(); void _animation_resource_edit();
void _scale_changed(const String &p_scale); void _scale_changed(const String &p_scale);
void _save_animation(String p_file);
void _load_animations(Vector<String> p_files);
void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false); 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); void _blend_editor_next_changed(const int p_idx);
@ -219,6 +210,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _stop_onion_skinning(); void _stop_onion_skinning();
void _pin_pressed(); void _pin_pressed();
String _get_current() const;
~AnimationPlayerEditor(); ~AnimationPlayerEditor();

View file

@ -6095,7 +6095,14 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
animation->set_length(length); animation->set_length(length);
ap->add_animation(name, animation); Ref<AnimationLibrary> 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<GLTFState> state) { void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) {

View file

@ -83,8 +83,31 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) {
set_current_animation(p_value); set_current_animation(p_value);
} else if (name.begins_with("anims/")) { } else if (name.begins_with("anims/")) {
// Backwards compatibility with 3.x, add them to "default" library.
String which = name.get_slicec('/', 1); String which = name.get_slicec('/', 1);
add_animation(which, p_value);
Ref<Animation> anim = p_value;
Ref<AnimationLibrary> 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<Variant> keys;
d.get_key_list(&keys);
for (const Variant &K : keys) {
StringName lib_name = K;
Ref<AnimationLibrary> lib = d[lib_name];
add_animation_library(lib_name, lib);
}
} else if (name.begins_with("next/")) { } else if (name.begins_with("next/")) {
String which = name.get_slicec('/', 1); 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(); r_ret = get_current_animation();
} else if (name.begins_with("anims/")) { } else if (name.begins_with("libraries")) {
String which = name.get_slicec('/', 1); Dictionary d;
r_ret = get_animation(which); 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/")) { } else if (name.begins_with("next/")) {
String which = name.get_slicec('/', 1); String which = name.get_slicec('/', 1);
@ -173,8 +200,9 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const {
void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {
List<PropertyInfo> anim_names; List<PropertyInfo> anim_names;
anim_names.push_back(PropertyInfo(Variant::DICTIONARY, "libraries"));
for (const KeyValue<StringName, AnimationData> &E : animation_set) { for (const KeyValue<StringName, AnimationData> &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()) { 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)); 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<Animation> &p_animation) { void AnimationPlayer::_animation_set_cache_update() {
#ifdef DEBUG_ENABLED // Relatively fast function to update all animations.
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
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)) { // Update changed and add otherwise
_unref_anim(animation_set[p_name].animation); for (uint32_t i = 0; i < animation_libraries.size(); i++) {
animation_set[p_name].animation = p_animation; for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) {
clear_caches(); StringName key = animation_libraries[i].name == StringName() ? K.key : StringName(String(animation_libraries[i].name) + "/" + String(K.key));
} else { if (!animation_set.has(key)) {
AnimationData ad; AnimationData ad;
ad.animation = p_animation; ad.animation = K.value;
ad.name = p_name; ad.animation_library = animation_libraries[i].name;
animation_set[p_name] = ad; 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); // Check removed
notify_property_list_changed(); List<StringName> to_erase;
return OK; for (const KeyValue<StringName, AnimationData> &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) { void AnimationPlayer::_animation_added(const StringName &p_name, const StringName &p_library) {
ERR_FAIL_COND(!animation_set.has(p_name)); _animation_set_cache_update();
stop(); update_configuration_warnings();
_unref_anim(animation_set[p_name].animation);
animation_set.erase(p_name);
clear_caches();
notify_property_list_changed();
} }
void AnimationPlayer::_ref_anim(const Ref<Animation> &p_anim) { void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) {
Ref<Animation>(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED); 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<BlendKey> to_erase;
for (const KeyValue<BlendKey, float> &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<Animation> &p_anim) { void AnimationPlayer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) {
Ref<Animation>(p_anim)->disconnect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed)); // Rename autoplay or blends if needed.
}
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;
List<BlendKey> to_erase; List<BlendKey> to_erase;
Map<BlendKey, float> to_insert; Map<BlendKey, float> to_insert;
for (const KeyValue<BlendKey, float> &E : blend_times) { for (const KeyValue<BlendKey, float> &E : blend_times) {
BlendKey bk = E.key; BlendKey bk = E.key;
BlendKey new_bk = bk; BlendKey new_bk = bk;
bool erase = false; bool erase = false;
if (bk.from == p_name) { if (bk.from == p_from_name) {
new_bk.from = p_new_name; new_bk.from = p_to_name;
erase = true; erase = true;
} }
if (bk.to == p_name) { if (bk.to == p_from_name) {
new_bk.to = p_new_name; new_bk.to = p_to_name;
erase = true; erase = true;
} }
@ -1239,12 +1302,184 @@ void AnimationPlayer::rename_animation(const StringName &p_name, const StringNam
to_insert.erase(to_insert.front()); to_insert.erase(to_insert.front());
} }
if (autoplay == p_name) { if (autoplay == p_from_name) {
autoplay = p_new_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<AnimationLibrary> &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(); 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<StringName, Ref<Animation>> &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<Animation> &p_anim) {
Ref<Animation>(p_anim)->connect(SceneStringNames::get_singleton()->tracks_changed, callable_mp(this, &AnimationPlayer::_animation_changed), varray(), CONNECT_REFERENCE_COUNTED);
}
void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) {
Ref<Animation>(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<StringName, Ref<Animation>> &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<AnimationLibrary> 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<AnimationLibrary>());
}
TypedArray<StringName> AnimationPlayer::_get_animation_library_list() const {
TypedArray<StringName> 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<StringName> *p_libraries) const {
for (uint32_t i = 0; i < animation_libraries.size(); i++) {
p_libraries->push_back(animation_libraries[i].name);
}
}
TypedArray<String> AnimationPlayer::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
for (uint32_t i = 0; i < animation_libraries.size(); i++) {
for (const KeyValue<StringName, Ref<Animation>> &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 { bool AnimationPlayer::has_animation(const StringName &p_name) const {
@ -1585,7 +1820,16 @@ StringName AnimationPlayer::find_animation(const Ref<Animation> &p_animation) co
} }
} }
return ""; return StringName();
}
StringName AnimationPlayer::find_animation_library(const Ref<Animation> &p_animation) const {
for (const KeyValue<StringName, AnimationData> &E : animation_set) {
if (E.value.animation == p_animation) {
return E.value.animation_library;
}
}
return StringName();
} }
void AnimationPlayer::set_autoplay(const String &p_name) { void AnimationPlayer::set_autoplay(const String &p_name) {
@ -1764,7 +2008,10 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) {
AnimationPlayer *aux_player = memnew(AnimationPlayer); AnimationPlayer *aux_player = memnew(AnimationPlayer);
EditorNode::get_singleton()->add_child(aux_player); EditorNode::get_singleton()->add_child(aux_player);
aux_player->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); Ref<AnimationLibrary> 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); 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 // 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()); Node *root = get_node(get_root());
@ -1792,9 +2039,13 @@ bool AnimationPlayer::can_apply_reset() const {
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
void AnimationPlayer::_bind_methods() { void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationPlayer::add_animation); ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationPlayer::add_animation_library);
ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationPlayer::remove_animation); ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationPlayer::remove_animation_library);
ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationPlayer::rename_animation); 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("has_animation", "name"), &AnimationPlayer::has_animation);
ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation); ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation);
ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationPlayer::_get_animation_list); 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("get_root"), &AnimationPlayer::get_root);
ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation); 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); ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches);

View file

@ -36,6 +36,7 @@
#include "scene/3d/node_3d.h" #include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h" #include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h" #include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted { class AnimatedValuesBackup : public RefCounted {
@ -184,9 +185,20 @@ private:
StringName next; StringName next;
Vector<TrackNodeCache *> node_cache; Vector<TrackNodeCache *> node_cache;
Ref<Animation> animation; Ref<Animation> animation;
StringName animation_library;
uint64_t last_update = 0;
}; };
Map<StringName, AnimationData> animation_set; Map<StringName, AnimationData> animation_set;
struct AnimationLibraryData {
StringName name;
Ref<AnimationLibrary> library;
bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); }
};
LocalVector<AnimationLibraryData> animation_libraries;
struct BlendKey { struct BlendKey {
StringName from; StringName from;
StringName to; StringName to;
@ -261,6 +273,15 @@ private:
bool playing = false; 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<StringName> _get_animation_library_list() const;
protected: protected:
bool _set(const StringName &p_name, const Variant &p_value); bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const; bool _get(const StringName &p_name, Variant &r_ret) const;
@ -272,13 +293,18 @@ protected:
public: public:
StringName find_animation(const Ref<Animation> &p_animation) const; StringName find_animation(const Ref<Animation> &p_animation) const;
StringName find_animation_library(const Ref<Animation> &p_animation) const;
Error add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &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<AnimationLibrary> get_animation_library(const StringName &p_name) const;
void get_animation_library_list(List<StringName> *p_animations) const;
bool has_animation_library(const StringName &p_name) const;
Error add_animation(const StringName &p_name, const Ref<Animation> &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<Animation> get_animation(const StringName &p_name) const; Ref<Animation> get_animation(const StringName &p_name) const;
void get_animation_list(List<StringName> *p_animations) const; void get_animation_list(List<StringName> *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); 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; float get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const;
@ -340,6 +366,8 @@ public:
bool can_apply_reset() const; bool can_apply_reset() const;
#endif #endif
TypedArray<String> get_configuration_warnings() const override;
AnimationPlayer(); AnimationPlayer();
~AnimationPlayer(); ~AnimationPlayer();
}; };

View file

@ -203,16 +203,18 @@ void OptionButton::pressed() {
} }
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) { void OptionButton::add_icon_item(const Ref<Texture2D> &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); popup->add_icon_radio_check_item(p_icon, p_label, p_id);
if (popup->get_item_count() == 1) { if (first_selectable) {
select(0); select(get_item_count() - 1);
} }
} }
void OptionButton::add_item(const String &p_label, int p_id) { 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); popup->add_radio_check_item(p_label, p_id);
if (popup->get_item_count() == 1) { if (first_selectable) {
select(0); 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); 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) { void OptionButton::set_item_count(int p_count) {
ERR_FAIL_COND(p_count < 0); ERR_FAIL_COND(p_count < 0);
@ -299,12 +304,37 @@ void OptionButton::set_item_count(int p_count) {
notify_property_list_changed(); 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 { int OptionButton::get_item_count() const {
return popup->get_item_count(); return popup->get_item_count();
} }
void OptionButton::add_separator() { void OptionButton::add_separator(const String &p_text) {
popup->add_separator(); popup->add_separator(p_text);
} }
void OptionButton::clear() { 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_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip); 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("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("clear"), &OptionButton::clear);
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select); ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected); 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("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("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. // "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_"); ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");

View file

@ -77,12 +77,16 @@ public:
int get_item_index(int p_id) const; int get_item_index(int p_id) const;
Variant get_item_metadata(int p_idx) const; Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(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; 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); void set_item_count(int p_count);
int get_item_count() const; int get_item_count() const;
void add_separator(); void add_separator(const String &p_text = "");
void clear(); void clear();

View file

@ -101,6 +101,7 @@ void TreeItem::_change_tree(Tree *p_tree) {
if (tree->popup_edited_item == this) { if (tree->popup_edited_item == this) {
tree->popup_edited_item = nullptr; tree->popup_edited_item = nullptr;
tree->popup_pressing_edited_item = nullptr;
tree->pressing_for_editor = false; 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; click_handled = true;
popup_edited_item = p_item; popup_pressing_edited_item = p_item;
popup_edited_item_col = col; 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_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; pressing_for_editor_text = editor_text;
@ -3206,10 +3207,16 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
update(); update();
} }
if (pressing_for_editor && popup_edited_item && (popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_RANGE)) { if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) {
//range drag /* 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) { if (!range_drag_enabled) {
//range drag
Vector2 cpos = mm->get_position(); Vector2 cpos = mm->get_position();
if (rtl) { if (rtl) {
cpos.x = get_size().width - cpos.x; cpos.x = get_size().width - cpos.x;
@ -3994,6 +4001,7 @@ void Tree::clear() {
selected_item = nullptr; selected_item = nullptr;
edited_item = nullptr; edited_item = nullptr;
popup_edited_item = nullptr; popup_edited_item = nullptr;
popup_pressing_edited_item = nullptr;
update(); update();
}; };
@ -4309,12 +4317,16 @@ int Tree::get_pressed_button() const {
return pressed_button; 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_NULL_V(p_item, Rect2());
ERR_FAIL_COND_V(p_item->tree != this, Rect2()); ERR_FAIL_COND_V(p_item->tree != this, Rect2());
if (p_column != -1) { if (p_column != -1) {
ERR_FAIL_INDEX_V(p_column, columns.size(), Rect2()); 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 ofs = get_item_offset(p_item);
int height = compute_item_height(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.position.x = accum;
r.size.x = get_column_width(p_column); 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<Texture2D> 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; 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("get_edited_column"), &Tree::get_edited_column);
ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected); 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_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_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_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); ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position);

View file

@ -379,6 +379,9 @@ private:
TreeItem *selected_item = nullptr; TreeItem *selected_item = nullptr;
TreeItem *edited_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; TreeItem *drop_mode_over = nullptr;
int drop_mode_section = 0; int drop_mode_section = 0;
@ -673,7 +676,7 @@ public:
Rect2 get_custom_popup_rect() const; Rect2 get_custom_popup_rect() const;
int get_item_offset(TreeItem *p_item) 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 edit_selected();
bool is_editing(); bool is_editing();

View file

@ -139,6 +139,7 @@
#include "scene/multiplayer/scene_cache_interface.h" #include "scene/multiplayer/scene_cache_interface.h"
#include "scene/multiplayer/scene_replication_interface.h" #include "scene/multiplayer/scene_replication_interface.h"
#include "scene/multiplayer/scene_rpc_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/audio_stream_sample.h"
#include "scene/resources/bit_map.h" #include "scene/resources/bit_map.h"
#include "scene/resources/box_shape_3d.h" #include "scene/resources/box_shape_3d.h"
@ -834,6 +835,7 @@ void register_scene_types() {
GDREGISTER_CLASS(CompressedTexture2DArray); GDREGISTER_CLASS(CompressedTexture2DArray);
GDREGISTER_CLASS(Animation); GDREGISTER_CLASS(Animation);
GDREGISTER_CLASS(AnimationLibrary);
GDREGISTER_CLASS(FontData); GDREGISTER_CLASS(FontData);
GDREGISTER_CLASS(Font); GDREGISTER_CLASS(Font);
GDREGISTER_CLASS(Curve); GDREGISTER_CLASS(Curve);

View file

@ -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<Animation> &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<Animation> AnimationLibrary::get_animation(const StringName &p_name) const {
ERR_FAIL_COND_V(!animations.has(p_name), Ref<Animation>());
return animations[p_name];
}
TypedArray<StringName> AnimationLibrary::_get_animation_list() const {
TypedArray<StringName> ret;
List<StringName> names;
get_animation_list(&names);
for (const StringName &K : names) {
ret.push_back(K);
}
return ret;
}
void AnimationLibrary::get_animation_list(List<StringName> *p_animations) const {
List<StringName> anims;
for (const KeyValue<StringName, Ref<Animation>> &E : animations) {
anims.push_back(E.key);
}
anims.sort_custom<StringName::AlphCompare>();
for (const StringName &E : anims) {
p_animations->push_back(E);
}
}
void AnimationLibrary::_set_data(const Dictionary &p_data) {
animations.clear();
List<Variant> 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<StringName, Ref<Animation>> &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() {
}

View file

@ -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<StringName> _get_animation_list() const;
friend class AnimationPlayer; //for faster access
Map<StringName, Ref<Animation>> animations;
protected:
static void _bind_methods();
public:
Error add_animation(const StringName &p_name, const Ref<Animation> &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<Animation> get_animation(const StringName &p_name) const;
void get_animation_list(List<StringName> *p_animations) const;
AnimationLibrary();
};
#endif // ANIMATIONLIBRARY_H