diff options
author | reduz <reduzio@gmail.com> | 2022-04-07 13:49:28 +0200 |
---|---|---|
committer | reduz <reduzio@gmail.com> | 2022-04-11 12:51:54 +0200 |
commit | 6f401439f849c0c99d8455c607fdef28750ee889 (patch) | |
tree | 3d491feed810e8be2483bce0df2d86626767e7d0 /scene | |
parent | b80a6abaeb8437b75d987952bb829af425f6614e (diff) |
Implement Animation Libraries
* Instead of containing single animations, AnimationPlayer now contains libraries.
* Libraries, in turn, contain the animations.
This paves the way for implementing the possibility of importing scenes as animation libraries, finally allowing to import animations separate from the 3D models.
Missing (will be done on separate PRs):
* Make it possible to import scenes (dae/fbx/gltf) as animation libraries.
* Make it possible for AnimationTree to import animation libraries on its own, so it does not rely on AnimationPlayer for everything.
Diffstat (limited to 'scene')
-rw-r--r-- | scene/animation/animation_player.cpp | 366 | ||||
-rw-r--r-- | scene/animation/animation_player.h | 36 | ||||
-rw-r--r-- | scene/gui/option_button.cpp | 47 | ||||
-rw-r--r-- | scene/gui/option_button.h | 6 | ||||
-rw-r--r-- | scene/gui/tree.cpp | 37 | ||||
-rw-r--r-- | scene/gui/tree.h | 5 | ||||
-rw-r--r-- | scene/register_scene_types.cpp | 2 | ||||
-rw-r--r-- | scene/resources/animation_library.cpp | 134 | ||||
-rw-r--r-- | scene/resources/animation_library.h | 62 |
9 files changed, 619 insertions, 76 deletions
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index 1ab2e2419e..6949e3681c 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -83,8 +83,31 @@ bool AnimationPlayer::_set(const StringName &p_name, const Variant &p_value) { set_current_animation(p_value); } else if (name.begins_with("anims/")) { + // Backwards compatibility with 3.x, add them to "default" library. String which = name.get_slicec('/', 1); - add_animation(which, p_value); + + Ref<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/")) { String which = name.get_slicec('/', 1); @@ -117,9 +140,13 @@ bool AnimationPlayer::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_current_animation(); - } else if (name.begins_with("anims/")) { - String which = name.get_slicec('/', 1); - r_ret = get_animation(which); + } else if (name.begins_with("libraries")) { + Dictionary d; + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + d[animation_libraries[i].name] = animation_libraries[i].library; + } + + r_ret = d; } else if (name.begins_with("next/")) { String which = name.get_slicec('/', 1); @@ -173,8 +200,9 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const { void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const { List<PropertyInfo> anim_names; + anim_names.push_back(PropertyInfo(Variant::DICTIONARY, "libraries")); + 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()) { 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) { -#ifdef DEBUG_ENABLED - ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + "."); -#endif +void AnimationPlayer::_animation_set_cache_update() { + // Relatively fast function to update all animations. - ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER); + animation_set_update_pass++; + bool clear_cache_needed = false; - if (animation_set.has(p_name)) { - _unref_anim(animation_set[p_name].animation); - animation_set[p_name].animation = p_animation; - clear_caches(); - } else { - AnimationData ad; - ad.animation = p_animation; - ad.name = p_name; - animation_set[p_name] = ad; - } + // Update changed and add otherwise + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[i].library->animations) { + StringName key = animation_libraries[i].name == StringName() ? K.key : StringName(String(animation_libraries[i].name) + "/" + String(K.key)); + if (!animation_set.has(key)) { + AnimationData ad; + ad.animation = K.value; + ad.animation_library = animation_libraries[i].name; + ad.name = key; + ad.last_update = animation_set_update_pass; + animation_set.insert(ad.name, ad); + } else { + AnimationData &ad = animation_set[key]; + if (ad.last_update != animation_set_update_pass) { + // Was not updated, update. If the animation is duplicated, the second one will be ignored. + if (ad.animation != K.value || ad.animation_library != animation_libraries[i].name) { + // Animation changed, update and clear caches. + clear_cache_needed = true; + ad.animation = K.value; + ad.animation_library = animation_libraries[i].name; + } - _ref_anim(p_animation); - notify_property_list_changed(); - return OK; -} + ad.last_update = animation_set_update_pass; + } + } + } + } -void AnimationPlayer::remove_animation(const StringName &p_name) { - ERR_FAIL_COND(!animation_set.has(p_name)); + // Check removed + List<StringName> to_erase; + 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; + } + } - stop(); - _unref_anim(animation_set[p_name].animation); - animation_set.erase(p_name); + while (to_erase.size()) { + animation_set.erase(to_erase.front()->get()); + to_erase.pop_front(); + } - clear_caches(); - notify_property_list_changed(); + if (clear_cache_needed) { + // If something was modified or removed, caches need to be cleared + clear_caches(); + } } -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::_animation_added(const StringName &p_name, const StringName &p_library) { + _animation_set_cache_update(); -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)); + update_configuration_warnings(); } -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)); +void AnimationPlayer::_animation_removed(const StringName &p_name, const StringName &p_library) { + StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); - stop(); - AnimationData ad = animation_set[p_name]; - ad.name = p_new_name; - animation_set.erase(p_name); - animation_set[p_new_name] = ad; + 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::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) { + // Rename autoplay or blends if needed. List<BlendKey> to_erase; Map<BlendKey, float> to_insert; for (const KeyValue<BlendKey, float> &E : blend_times) { BlendKey bk = E.key; BlendKey new_bk = bk; bool erase = false; - if (bk.from == p_name) { - new_bk.from = p_new_name; + if (bk.from == p_from_name) { + new_bk.from = p_to_name; erase = true; } - if (bk.to == p_name) { - new_bk.to = p_new_name; + if (bk.to == p_from_name) { + new_bk.to = p_to_name; erase = true; } @@ -1239,12 +1302,184 @@ void AnimationPlayer::rename_animation(const StringName &p_name, const StringNam to_insert.erase(to_insert.front()); } - if (autoplay == p_name) { - autoplay = p_new_name; + if (autoplay == p_from_name) { + autoplay = p_to_name; } +} + +void AnimationPlayer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) { + StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name)); + StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name)); + + if (!animation_set.has(from_name)) { + return; // No need to update because not the one from the library being used. + } + _animation_set_cache_update(); + + _rename_animation(from_name, to_name); + update_configuration_warnings(); +} + +Error AnimationPlayer::add_animation_library(const StringName &p_name, const Ref<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++; + } + + AnimationLibraryData ald; + ald.name = p_name; + ald.library = p_animation_library; + + animation_libraries.insert(insert_pos, ald); + + ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name)); + ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added), varray(p_name)); + ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed), varray(p_name)); + + _animation_set_cache_update(); + + notify_property_list_changed(); + + update_configuration_warnings(); + return OK; +} + +void AnimationPlayer::remove_animation_library(const StringName &p_name) { + int at_pos = -1; + + for (uint32_t i = 0; i < animation_libraries.size(); i++) { + if (animation_libraries[i].name == p_name) { + at_pos = i; + break; + } + } + + ERR_FAIL_COND(at_pos == -1); + + animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationPlayer::_animation_added)); + animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationPlayer::_animation_renamed)); + + stop(); + + for (const KeyValue<StringName, Ref<Animation>> &K : animation_libraries[at_pos].library->animations) { + _unref_anim(K.value); + } + + animation_libraries.remove_at(at_pos); + _animation_set_cache_update(); - clear_caches(); 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 { @@ -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) { @@ -1764,7 +2008,10 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { AnimationPlayer *aux_player = memnew(AnimationPlayer); EditorNode::get_singleton()->add_child(aux_player); - aux_player->add_animation(SceneStringNames::get_singleton()->RESET, reset_anim); + Ref<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); // Forcing the use of the original root because the scene where original player belongs may be not the active one Node *root = get_node(get_root()); @@ -1792,9 +2039,13 @@ bool AnimationPlayer::can_apply_reset() const { #endif // TOOLS_ENABLED void AnimationPlayer::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationPlayer::add_animation); - ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationPlayer::remove_animation); - ClassDB::bind_method(D_METHOD("rename_animation", "name", "newname"), &AnimationPlayer::rename_animation); + ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationPlayer::add_animation_library); + ClassDB::bind_method(D_METHOD("remove_animation_library", "name"), &AnimationPlayer::remove_animation_library); + ClassDB::bind_method(D_METHOD("rename_animation_library", "name", "newname"), &AnimationPlayer::rename_animation_library); + ClassDB::bind_method(D_METHOD("has_animation_library", "name"), &AnimationPlayer::has_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library", "name"), &AnimationPlayer::get_animation_library); + ClassDB::bind_method(D_METHOD("get_animation_library_list"), &AnimationPlayer::_get_animation_library_list); + ClassDB::bind_method(D_METHOD("has_animation", "name"), &AnimationPlayer::has_animation); ClassDB::bind_method(D_METHOD("get_animation", "name"), &AnimationPlayer::get_animation); ClassDB::bind_method(D_METHOD("get_animation_list"), &AnimationPlayer::_get_animation_list); @@ -1838,6 +2089,7 @@ void AnimationPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::get_root); ClassDB::bind_method(D_METHOD("find_animation", "animation"), &AnimationPlayer::find_animation); + ClassDB::bind_method(D_METHOD("find_animation_library", "animation"), &AnimationPlayer::find_animation_library); ClassDB::bind_method(D_METHOD("clear_caches"), &AnimationPlayer::clear_caches); diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h index a68f6b9d5b..1d450175ad 100644 --- a/scene/animation/animation_player.h +++ b/scene/animation/animation_player.h @@ -36,6 +36,7 @@ #include "scene/3d/node_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/resources/animation.h" +#include "scene/resources/animation_library.h" #ifdef TOOLS_ENABLED class AnimatedValuesBackup : public RefCounted { @@ -184,9 +185,20 @@ private: StringName next; Vector<TrackNodeCache *> node_cache; Ref<Animation> animation; + StringName animation_library; + uint64_t last_update = 0; }; 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 { StringName from; StringName to; @@ -261,6 +273,15 @@ private: bool playing = false; + uint64_t animation_set_update_pass = 1; + void _animation_set_cache_update(); + void _animation_added(const StringName &p_name, const StringName &p_library); + void _animation_removed(const StringName &p_name, const StringName &p_library); + void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library); + void _rename_animation(const StringName &p_from_name, const StringName &p_to_name); + + TypedArray<StringName> _get_animation_library_list() const; + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -272,13 +293,18 @@ protected: public: StringName find_animation(const Ref<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; 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); float get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const; @@ -340,6 +366,8 @@ public: bool can_apply_reset() const; #endif + TypedArray<String> get_configuration_warnings() const override; + AnimationPlayer(); ~AnimationPlayer(); }; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 1e8a149e11..307696c44a 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -203,16 +203,18 @@ void OptionButton::pressed() { } void OptionButton::add_icon_item(const Ref<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); - if (popup->get_item_count() == 1) { - select(0); + if (first_selectable) { + select(get_item_count() - 1); } } void OptionButton::add_item(const String &p_label, int p_id) { + bool first_selectable = !has_selectable_items(); popup->add_radio_check_item(p_label, p_id); - if (popup->get_item_count() == 1) { - select(0); + if (first_selectable) { + select(get_item_count() - 1); } } @@ -280,6 +282,9 @@ bool OptionButton::is_item_disabled(int p_idx) const { return popup->is_item_disabled(p_idx); } +bool OptionButton::is_item_separator(int p_idx) const { + return popup->is_item_separator(p_idx); +} void OptionButton::set_item_count(int p_count) { ERR_FAIL_COND(p_count < 0); @@ -299,12 +304,37 @@ void OptionButton::set_item_count(int p_count) { notify_property_list_changed(); } +bool OptionButton::has_selectable_items() const { + for (int i = 0; i < get_item_count(); i++) { + if (!is_item_disabled(i) && !is_item_separator(i)) { + return true; + } + } + return false; +} +int OptionButton::get_selectable_item(bool p_from_last) const { + if (!p_from_last) { + for (int i = 0; i < get_item_count(); i++) { + if (!is_item_disabled(i) && !is_item_separator(i)) { + return i; + } + } + } else { + for (int i = get_item_count() - 1; i >= 0; i++) { + if (!is_item_disabled(i) && !is_item_separator(i)) { + return i; + } + } + } + return -1; +} + int OptionButton::get_item_count() const { return popup->get_item_count(); } -void OptionButton::add_separator() { - popup->add_separator(); +void OptionButton::add_separator(const String &p_text) { + popup->add_separator(p_text); } void OptionButton::clear() { @@ -407,7 +437,8 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata); ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip); ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled); - ClassDB::bind_method(D_METHOD("add_separator"), &OptionButton::add_separator); + ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator); + ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String())); ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear); ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select); ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected); @@ -420,6 +451,8 @@ void OptionButton::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count); ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count); + ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items); + ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false)); // "selected" property must come after "item_count", otherwise GH-10213 occurs. ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 921b76c52a..7896132626 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -77,12 +77,16 @@ public: int get_item_index(int p_id) const; Variant get_item_metadata(int p_idx) const; bool is_item_disabled(int p_idx) const; + bool is_item_separator(int p_idx) const; String get_item_tooltip(int p_idx) const; + bool has_selectable_items() const; + int get_selectable_item(bool p_from_last = false) const; + void set_item_count(int p_count); int get_item_count() const; - void add_separator(); + void add_separator(const String &p_text = ""); void clear(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index ccd24ed2cf..24cd485e1b 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -101,6 +101,7 @@ void TreeItem::_change_tree(Tree *p_tree) { if (tree->popup_edited_item == this) { tree->popup_edited_item = nullptr; + tree->popup_pressing_edited_item = nullptr; tree->pressing_for_editor = false; } @@ -2670,8 +2671,8 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int } click_handled = true; - popup_edited_item = p_item; - popup_edited_item_col = col; + popup_pressing_edited_item = p_item; + popup_pressing_edited_item_column = col; pressing_item_rect = Rect2(get_global_position() + Point2i(col_ofs, _get_title_button_height() + y_ofs) - cache.offset, Size2(col_width, item_h)); pressing_for_editor_text = editor_text; @@ -3206,10 +3207,16 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { update(); } - if (pressing_for_editor && popup_edited_item && (popup_edited_item->get_cell_mode(popup_edited_item_col) == TreeItem::CELL_MODE_RANGE)) { - //range drag + if (pressing_for_editor && popup_pressing_edited_item && (popup_pressing_edited_item->get_cell_mode(popup_pressing_edited_item_column) == TreeItem::CELL_MODE_RANGE)) { + /* This needs to happen now, because the popup can be closed when pressing another item, and must remain the popup edited item until it actually closes */ + popup_edited_item = popup_pressing_edited_item; + popup_edited_item_col = popup_pressing_edited_item_column; + + popup_pressing_edited_item = nullptr; + popup_pressing_edited_item_column = -1; if (!range_drag_enabled) { + //range drag Vector2 cpos = mm->get_position(); if (rtl) { cpos.x = get_size().width - cpos.x; @@ -3994,6 +4001,7 @@ void Tree::clear() { selected_item = nullptr; edited_item = nullptr; popup_edited_item = nullptr; + popup_pressing_edited_item = nullptr; update(); }; @@ -4309,12 +4317,16 @@ int Tree::get_pressed_button() const { return pressed_button; } -Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const { +Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column, int p_button) const { ERR_FAIL_NULL_V(p_item, Rect2()); ERR_FAIL_COND_V(p_item->tree != this, Rect2()); if (p_column != -1) { ERR_FAIL_INDEX_V(p_column, columns.size(), Rect2()); } + if (p_button != -1) { + ERR_FAIL_COND_V(p_column == -1, Rect2()); // pass a column if you want to pass a button + ERR_FAIL_INDEX_V(p_button, p_item->cells[p_column].buttons.size(), Rect2()); + } int ofs = get_item_offset(p_item); int height = compute_item_height(p_item); @@ -4332,6 +4344,19 @@ Rect2 Tree::get_item_rect(TreeItem *p_item, int p_column) const { } r.position.x = accum; r.size.x = get_column_width(p_column); + if (p_button != -1) { + const TreeItem::Cell &c = p_item->cells[p_column]; + Vector2 ofst = Vector2(r.position.x + r.size.x, r.position.y); + for (int j = c.buttons.size() - 1; j >= 0; j--) { + Ref<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; @@ -4870,7 +4895,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column); ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected); ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect); - ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column"), &Tree::get_item_rect, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column", "button_index"), &Tree::get_item_rect, DEFVAL(-1), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position); ClassDB::bind_method(D_METHOD("get_column_at_position", "position"), &Tree::get_column_at_position); ClassDB::bind_method(D_METHOD("get_drop_section_at_position", "position"), &Tree::get_drop_section_at_position); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 74ad4f94b8..b704495444 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -379,6 +379,9 @@ private: TreeItem *selected_item = nullptr; TreeItem *edited_item = nullptr; + TreeItem *popup_pressing_edited_item = nullptr; // Candidate. + int popup_pressing_edited_item_column = -1; + TreeItem *drop_mode_over = nullptr; int drop_mode_section = 0; @@ -673,7 +676,7 @@ public: Rect2 get_custom_popup_rect() const; int get_item_offset(TreeItem *p_item) const; - Rect2 get_item_rect(TreeItem *p_item, int p_column = -1) const; + Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const; bool edit_selected(); bool is_editing(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 5b56d9b11e..127e734e6c 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -139,6 +139,7 @@ #include "scene/multiplayer/scene_cache_interface.h" #include "scene/multiplayer/scene_replication_interface.h" #include "scene/multiplayer/scene_rpc_interface.h" +#include "scene/resources/animation_library.h" #include "scene/resources/audio_stream_sample.h" #include "scene/resources/bit_map.h" #include "scene/resources/box_shape_3d.h" @@ -834,6 +835,7 @@ void register_scene_types() { GDREGISTER_CLASS(CompressedTexture2DArray); GDREGISTER_CLASS(Animation); + GDREGISTER_CLASS(AnimationLibrary); GDREGISTER_CLASS(FontData); GDREGISTER_CLASS(Font); GDREGISTER_CLASS(Curve); diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp new file mode 100644 index 0000000000..f7b8c6a648 --- /dev/null +++ b/scene/resources/animation_library.cpp @@ -0,0 +1,134 @@ +/*************************************************************************/ +/* animation_library.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "animation_library.h" + +Error AnimationLibrary::add_animation(const StringName &p_name, const Ref<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() { +} diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h new file mode 100644 index 0000000000..69ac5a97d2 --- /dev/null +++ b/scene/resources/animation_library.h @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* animation_library.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef ANIMATION_LIBRARY_H +#define ANIMATION_LIBRARY_H + +#include "core/variant/typed_array.h" +#include "scene/resources/animation.h" + +class AnimationLibrary : public Resource { + GDCLASS(AnimationLibrary, Resource) + + void _set_data(const Dictionary &p_data); + Dictionary _get_data() const; + + TypedArray<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 |