diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index 4e8a1bd89b6c..46410b581e81 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -30,7 +30,12 @@ #include "animation_library_editor.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" #include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" @@ -518,6 +523,8 @@ void AnimationLibraryEditor::_item_renamed() { if (restore_text) { ti->set_text(0, old_text); } + + _save_mixer_lib_folding(ti); } void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) { @@ -670,6 +677,8 @@ void AnimationLibraryEditor::update_tree() { TreeItem *root = tree->create_item(); List libs; + Vector collapsed_lib_ids = _load_mixer_libs_folding(); + mixer->get_animation_library_list(&libs); for (const StringName &K : libs) { @@ -759,12 +768,203 @@ void AnimationLibraryEditor::update_tree() { anitem->set_text(1, anim_path.get_file()); } } + anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk.")); anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library.")); + + for (const uint64_t &lib_id : collapsed_lib_ids) { + Object *lib_obj = ObjectDB::get_instance(ObjectID(lib_id)); + AnimationLibrary *cur_lib = Object::cast_to(lib_obj); + StringName M = mixer->get_animation_library_name(cur_lib); + + if (M == K) { + libitem->set_collapsed_recursive(true); + } + } } } } +void AnimationLibraryEditor::_save_mixer_lib_folding(TreeItem *p_item) { + //Check if ti is a library or animation + if (p_item->get_parent()->get_parent() != nullptr) { + return; + } + + Ref config; + config.instantiate(); + + String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg"); + Error err = config->load(path); + if (err != OK && err != ERR_FILE_NOT_FOUND) { + ERR_PRINT("Error loading lib_folding.cfg: " + itos(err)); + } + + // Get unique identifier for this scene+mixer combination + String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text(); + + PackedStringArray collapsed_lib_names; + PackedStringArray collapsed_lib_ids; + + if (config->has_section(md)) { + collapsed_lib_names = String(config->get_value(md, "folding")).split("\n"); + collapsed_lib_ids = String(config->get_value(md, "id")).split("\n"); + } + + String lib_name = p_item->get_text(0); + + // Get library reference and check validity + Ref al; + uint64_t lib_id = 0; + + if (mixer->has_animation_library(lib_name)) { + al = mixer->get_animation_library(lib_name); + ERR_FAIL_COND(al.is_null()); + lib_id = uint64_t(al->get_instance_id()); + } else { + ERR_PRINT("Library not found: " + lib_name); + } + + int at = collapsed_lib_names.find(lib_name); + if (p_item->is_collapsed()) { + if (at != -1) { + //Entry exists and needs updating + collapsed_lib_ids.set(at, String::num_int64(lib_id + INT64_MIN)); + } else { + //Check if it's a rename + int id_at = collapsed_lib_ids.find(String::num_int64(lib_id + INT64_MIN)); + if (id_at != -1) { + //It's actually a rename + collapsed_lib_names.set(id_at, lib_name); + } else { + //It's a new entry + collapsed_lib_names.append(lib_name); + collapsed_lib_ids.append(String::num_int64(lib_id + INT64_MIN)); + } + } + } else { + if (at != -1) { + collapsed_lib_names.remove_at(at); + collapsed_lib_ids.remove_at(at); + } + } + + //Runtime IDs + config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())); + config->set_value(md, "mixer", uint64_t(mixer->get_instance_id())); + + //Plan B recovery mechanism + config->set_value(md, "mixer_signature", _get_mixer_signature()); + + //Save folding state as text and runtime ID + config->set_value(md, "folding", String("\n").join(collapsed_lib_names)); + config->set_value(md, "id", String("\n").join(collapsed_lib_ids)); + + err = config->save(path); + if (err != OK) { + ERR_PRINT("Error saving lib_folding.cfg: " + itos(err)); + } +} + +Vector AnimationLibraryEditor::_load_mixer_libs_folding() { + Ref config; + config.instantiate(); + + String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg"); + Error err = config->load(path); + if (err != OK && err != ERR_FILE_NOT_FOUND) { + ERR_PRINT("Error loading lib_folding.cfg: " + itos(err)); + return Vector(); + } + + // Get unique identifier for this scene+mixer combination + String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text(); + + Vector collapsed_lib_ids; + + if (config->has_section(md)) { + _load_config_libs_folding(collapsed_lib_ids, config.ptr(), md); + + } else { + //The scene/mixer combination is no longer valid and we'll try to recover + uint64_t current_mixer_id = uint64_t(mixer->get_instance_id()); + String current_mixer_signature = _get_mixer_signature(); + List sections; + config->get_sections(§ions); + + for (const String §ion : sections) { + Variant mixer_id = config->get_value(section, "mixer"); + if ((mixer_id.get_type() == Variant::INT && uint64_t(mixer_id) == current_mixer_id) || config->get_value(section, "mixer_signature") == current_mixer_signature) { // Ensure value exists and is correct type + // Found the mixer in a different section! + _load_config_libs_folding(collapsed_lib_ids, config.ptr(), section); + + //Cleanup old entry and copy fold data into new one! + String collapsed_lib_names_str = String(config->get_value(section, "folding")); + String collapsed_lib_ids_str = String(config->get_value(section, "id")); + config->erase_section(section); + + config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())); + config->set_value(md, "mixer", uint64_t(mixer->get_instance_id())); + config->set_value(md, "mixer_signature", _get_mixer_signature()); + config->set_value(md, "folding", collapsed_lib_names_str); + config->set_value(md, "id", collapsed_lib_ids_str); + + err = config->save(path); + if (err != OK) { + ERR_PRINT("Error saving lib_folding.cfg: " + itos(err)); + } + break; + } + } + } + + return collapsed_lib_ids; +} + +void AnimationLibraryEditor::_load_config_libs_folding(Vector &p_lib_ids, ConfigFile *p_config, String p_section) { + if (uint64_t(p_config->get_value(p_section, "root", 0)) != uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())) { + // Root changed - tries to match by library names + PackedStringArray collapsed_lib_names = String(p_config->get_value(p_section, "folding", "")).split("\n"); + for (const String &lib_name : collapsed_lib_names) { + if (mixer->has_animation_library(lib_name)) { + p_lib_ids.append(mixer->get_animation_library(lib_name)->get_instance_id()); + } else { + print_line("Can't find ", lib_name, " in mixer"); + } + } + } else { + // Root same - uses saved instance IDs + for (const String &saved_id : String(p_config->get_value(p_section, "id")).split("\n")) { + p_lib_ids.append(uint64_t(saved_id.to_int() - INT64_MIN)); + } + } +} + +String AnimationLibraryEditor::_get_mixer_signature() const { + String signature = String(); + + // Get all libraries sorted for consistency + List libs; + mixer->get_animation_library_list(&libs); + libs.sort_custom(); + + // Add libraries and their animations to signature + for (const StringName &lib_name : libs) { + signature += "::" + String(lib_name); + Ref lib = mixer->get_animation_library(lib_name); + if (lib.is_valid()) { + List anims; + lib->get_animation_list(&anims); + anims.sort_custom(); + for (const StringName &anim_name : anims) { + signature += "," + String(anim_name); + } + } + } + + return signature.md5_text(); +} + void AnimationLibraryEditor::show_dialog() { update_tree(); popup_centered_ratio(0.5); @@ -855,11 +1055,12 @@ AnimationLibraryEditor::AnimationLibraryEditor() { 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_hide_folding(false); tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed)); tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); + tree->connect("item_collapsed", callable_mp(this, &AnimationLibraryEditor::_save_mixer_lib_folding)); file_popup = memnew(PopupMenu); add_child(file_popup); diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h index beb34c6343a8..c4ad1684a63b 100644 --- a/editor/plugins/animation_library_editor.h +++ b/editor/plugins/animation_library_editor.h @@ -31,6 +31,8 @@ #ifndef ANIMATION_LIBRARY_EDITOR_H #define ANIMATION_LIBRARY_EDITOR_H +#include "core/io/config_file.h" +#include "core/templates/vector.h" #include "editor/animation_track_editor.h" #include "editor/plugins/editor_plugin.h" #include "scene/animation/animation_mixer.h" @@ -103,6 +105,11 @@ class AnimationLibraryEditor : public AcceptDialog { void _load_file(const String &p_path); void _load_files(const PackedStringArray &p_paths); + void _save_mixer_lib_folding(TreeItem *p_item); + Vector _load_mixer_libs_folding(); + void _load_config_libs_folding(Vector &p_lib_ids, ConfigFile *p_config, String p_section); + String _get_mixer_signature() const; + void _item_renamed(); void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button); diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index b40c677f6e80..3a2adead98ff 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -33,6 +33,8 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/string/print_string.h" +#include "core/string/string_name.h" #include "scene/2d/audio_stream_player_2d.h" #include "scene/animation/animation_player.h" #include "scene/audio/audio_stream_player.h" @@ -267,6 +269,16 @@ bool AnimationMixer::has_animation_library(const StringName &p_name) const { return false; } +StringName AnimationMixer::get_animation_library_name(const Ref &p_animation_library) const { + ERR_FAIL_COND_V(p_animation_library.is_null(), StringName()); + for (const AnimationLibraryData &lib : animation_libraries) { + if (lib.library == p_animation_library) { + return lib.name; + } + } + return StringName(); +} + StringName AnimationMixer::find_animation_library(const Ref &p_animation) const { for (const KeyValue &E : animation_set) { if (E.value.animation == p_animation) { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 3769fa268c0d..822a7aef251c 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -409,6 +409,7 @@ class AnimationMixer : public Node { void get_animation_library_list(List *p_animations) const; Ref get_animation_library(const StringName &p_name) const; bool has_animation_library(const StringName &p_name) const; + StringName get_animation_library_name(const Ref &p_animation_library) const; StringName find_animation_library(const Ref &p_animation) const; Error add_animation_library(const StringName &p_name, const Ref &p_animation_library); void remove_animation_library(const StringName &p_name);