Merge pull request #59980 from reduz/animation-libraries
This commit is contained in:
commit
4ab86c6731
21 changed files with 1742 additions and 394 deletions
70
doc/classes/AnimationLibrary.xml
Normal file
70
doc/classes/AnimationLibrary.xml
Normal 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>
|
|
@ -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">
|
||||||
|
|
|
@ -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="""" />
|
||||||
<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" />
|
||||||
|
|
|
@ -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">
|
||||||
|
|
1
editor/icons/AnimationLibrary.svg
Normal file
1
editor/icons/AnimationLibrary.svg
Normal 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 |
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
689
editor/plugins/animation_library_editor.cpp
Normal file
689
editor/plugins/animation_library_editor.cpp
Normal 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);
|
||||||
|
}
|
119
editor/plugins/animation_library_editor.h
Normal file
119
editor/plugins/animation_library_editor.h
Normal 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
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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_");
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
134
scene/resources/animation_library.cpp
Normal file
134
scene/resources/animation_library.cpp
Normal 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() {
|
||||||
|
}
|
62
scene/resources/animation_library.h
Normal file
62
scene/resources/animation_library.h
Normal 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
|
Loading…
Reference in a new issue