|
30 | 30 |
|
31 | 31 | #include "animation_library_editor.h" |
32 | 32 |
|
| 33 | +#include "core/string/print_string.h" |
| 34 | +#include "core/string/ustring.h" |
| 35 | +#include "core/templates/vector.h" |
| 36 | +#include "core/variant/variant.h" |
33 | 37 | #include "editor/editor_node.h" |
| 38 | +#include "editor/editor_paths.h" |
34 | 39 | #include "editor/editor_settings.h" |
35 | 40 | #include "editor/editor_string_names.h" |
36 | 41 | #include "editor/editor_undo_redo_manager.h" |
@@ -518,6 +523,8 @@ void AnimationLibraryEditor::_item_renamed() { |
518 | 523 | if (restore_text) { |
519 | 524 | ti->set_text(0, old_text); |
520 | 525 | } |
| 526 | + |
| 527 | + _save_mixer_lib_folding(ti); |
521 | 528 | } |
522 | 529 |
|
523 | 530 | void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) { |
@@ -670,6 +677,8 @@ void AnimationLibraryEditor::update_tree() { |
670 | 677 |
|
671 | 678 | TreeItem *root = tree->create_item(); |
672 | 679 | List<StringName> libs; |
| 680 | + Vector<uint64_t> collapsed_lib_ids = _load_mixer_libs_folding(); |
| 681 | + |
673 | 682 | mixer->get_animation_library_list(&libs); |
674 | 683 |
|
675 | 684 | for (const StringName &K : libs) { |
@@ -759,12 +768,203 @@ void AnimationLibraryEditor::update_tree() { |
759 | 768 | anitem->set_text(1, anim_path.get_file()); |
760 | 769 | } |
761 | 770 | } |
| 771 | + |
762 | 772 | anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk.")); |
763 | 773 | anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library.")); |
| 774 | + |
| 775 | + for (const uint64_t &lib_id : collapsed_lib_ids) { |
| 776 | + Object *lib_obj = ObjectDB::get_instance(ObjectID(lib_id)); |
| 777 | + AnimationLibrary *cur_lib = Object::cast_to<AnimationLibrary>(lib_obj); |
| 778 | + StringName M = mixer->get_animation_library_name(cur_lib); |
| 779 | + |
| 780 | + if (M == K) { |
| 781 | + libitem->set_collapsed_recursive(true); |
| 782 | + } |
| 783 | + } |
764 | 784 | } |
765 | 785 | } |
766 | 786 | } |
767 | 787 |
|
| 788 | +void AnimationLibraryEditor::_save_mixer_lib_folding(TreeItem *p_item) { |
| 789 | + //Check if ti is a library or animation |
| 790 | + if (p_item->get_parent()->get_parent() != nullptr) { |
| 791 | + return; |
| 792 | + } |
| 793 | + |
| 794 | + Ref<ConfigFile> config; |
| 795 | + config.instantiate(); |
| 796 | + |
| 797 | + String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg"); |
| 798 | + Error err = config->load(path); |
| 799 | + if (err != OK && err != ERR_FILE_NOT_FOUND) { |
| 800 | + ERR_PRINT("Error loading lib_folding.cfg: " + itos(err)); |
| 801 | + } |
| 802 | + |
| 803 | + // Get unique identifier for this scene+mixer combination |
| 804 | + String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text(); |
| 805 | + |
| 806 | + PackedStringArray collapsed_lib_names; |
| 807 | + PackedStringArray collapsed_lib_ids; |
| 808 | + |
| 809 | + if (config->has_section(md)) { |
| 810 | + collapsed_lib_names = String(config->get_value(md, "folding")).split("\n"); |
| 811 | + collapsed_lib_ids = String(config->get_value(md, "id")).split("\n"); |
| 812 | + } |
| 813 | + |
| 814 | + String lib_name = p_item->get_text(0); |
| 815 | + |
| 816 | + // Get library reference and check validity |
| 817 | + Ref<AnimationLibrary> al; |
| 818 | + uint64_t lib_id = 0; |
| 819 | + |
| 820 | + if (mixer->has_animation_library(lib_name)) { |
| 821 | + al = mixer->get_animation_library(lib_name); |
| 822 | + ERR_FAIL_COND(al.is_null()); |
| 823 | + lib_id = uint64_t(al->get_instance_id()); |
| 824 | + } else { |
| 825 | + ERR_PRINT("Library not found: " + lib_name); |
| 826 | + } |
| 827 | + |
| 828 | + int at = collapsed_lib_names.find(lib_name); |
| 829 | + if (p_item->is_collapsed()) { |
| 830 | + if (at != -1) { |
| 831 | + //Entry exists and needs updating |
| 832 | + collapsed_lib_ids.set(at, String::num_int64(lib_id + INT64_MIN)); |
| 833 | + } else { |
| 834 | + //Check if it's a rename |
| 835 | + int id_at = collapsed_lib_ids.find(String::num_int64(lib_id + INT64_MIN)); |
| 836 | + if (id_at != -1) { |
| 837 | + //It's actually a rename |
| 838 | + collapsed_lib_names.set(id_at, lib_name); |
| 839 | + } else { |
| 840 | + //It's a new entry |
| 841 | + collapsed_lib_names.append(lib_name); |
| 842 | + collapsed_lib_ids.append(String::num_int64(lib_id + INT64_MIN)); |
| 843 | + } |
| 844 | + } |
| 845 | + } else { |
| 846 | + if (at != -1) { |
| 847 | + collapsed_lib_names.remove_at(at); |
| 848 | + collapsed_lib_ids.remove_at(at); |
| 849 | + } |
| 850 | + } |
| 851 | + |
| 852 | + //Runtime IDs |
| 853 | + config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())); |
| 854 | + config->set_value(md, "mixer", uint64_t(mixer->get_instance_id())); |
| 855 | + |
| 856 | + //Plan B recovery mechanism |
| 857 | + config->set_value(md, "mixer_signature", _get_mixer_signature()); |
| 858 | + |
| 859 | + //Save folding state as text and runtime ID |
| 860 | + config->set_value(md, "folding", String("\n").join(collapsed_lib_names)); |
| 861 | + config->set_value(md, "id", String("\n").join(collapsed_lib_ids)); |
| 862 | + |
| 863 | + err = config->save(path); |
| 864 | + if (err != OK) { |
| 865 | + ERR_PRINT("Error saving lib_folding.cfg: " + itos(err)); |
| 866 | + } |
| 867 | +} |
| 868 | + |
| 869 | +Vector<uint64_t> AnimationLibraryEditor::_load_mixer_libs_folding() { |
| 870 | + Ref<ConfigFile> config; |
| 871 | + config.instantiate(); |
| 872 | + |
| 873 | + String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join("lib_folding.cfg"); |
| 874 | + Error err = config->load(path); |
| 875 | + if (err != OK && err != ERR_FILE_NOT_FOUND) { |
| 876 | + ERR_PRINT("Error loading lib_folding.cfg: " + itos(err)); |
| 877 | + return Vector<uint64_t>(); |
| 878 | + } |
| 879 | + |
| 880 | + // Get unique identifier for this scene+mixer combination |
| 881 | + String md = (mixer->get_tree()->get_edited_scene_root()->get_scene_file_path() + mixer->get_path()).md5_text(); |
| 882 | + |
| 883 | + Vector<uint64_t> collapsed_lib_ids; |
| 884 | + |
| 885 | + if (config->has_section(md)) { |
| 886 | + _load_config_libs_folding(collapsed_lib_ids, config.ptr(), md); |
| 887 | + |
| 888 | + } else { |
| 889 | + //The scene/mixer combination is no longer valid and we'll try to recover |
| 890 | + uint64_t current_mixer_id = uint64_t(mixer->get_instance_id()); |
| 891 | + String current_mixer_signature = _get_mixer_signature(); |
| 892 | + List<String> sections; |
| 893 | + config->get_sections(§ions); |
| 894 | + |
| 895 | + for (const String §ion : sections) { |
| 896 | + Variant mixer_id = config->get_value(section, "mixer"); |
| 897 | + 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 |
| 898 | + // Found the mixer in a different section! |
| 899 | + _load_config_libs_folding(collapsed_lib_ids, config.ptr(), section); |
| 900 | + |
| 901 | + //Cleanup old entry and copy fold data into new one! |
| 902 | + String collapsed_lib_names_str = String(config->get_value(section, "folding")); |
| 903 | + String collapsed_lib_ids_str = String(config->get_value(section, "id")); |
| 904 | + config->erase_section(section); |
| 905 | + |
| 906 | + config->set_value(md, "root", uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())); |
| 907 | + config->set_value(md, "mixer", uint64_t(mixer->get_instance_id())); |
| 908 | + config->set_value(md, "mixer_signature", _get_mixer_signature()); |
| 909 | + config->set_value(md, "folding", collapsed_lib_names_str); |
| 910 | + config->set_value(md, "id", collapsed_lib_ids_str); |
| 911 | + |
| 912 | + err = config->save(path); |
| 913 | + if (err != OK) { |
| 914 | + ERR_PRINT("Error saving lib_folding.cfg: " + itos(err)); |
| 915 | + } |
| 916 | + break; |
| 917 | + } |
| 918 | + } |
| 919 | + } |
| 920 | + |
| 921 | + return collapsed_lib_ids; |
| 922 | +} |
| 923 | + |
| 924 | +void AnimationLibraryEditor::_load_config_libs_folding(Vector<uint64_t> &p_lib_ids, ConfigFile *p_config, String p_section) { |
| 925 | + if (uint64_t(p_config->get_value(p_section, "root", 0)) != uint64_t(mixer->get_tree()->get_edited_scene_root()->get_instance_id())) { |
| 926 | + // Root changed - tries to match by library names |
| 927 | + PackedStringArray collapsed_lib_names = String(p_config->get_value(p_section, "folding", "")).split("\n"); |
| 928 | + for (const String &lib_name : collapsed_lib_names) { |
| 929 | + if (mixer->has_animation_library(lib_name)) { |
| 930 | + p_lib_ids.append(mixer->get_animation_library(lib_name)->get_instance_id()); |
| 931 | + } else { |
| 932 | + print_line("Can't find ", lib_name, " in mixer"); |
| 933 | + } |
| 934 | + } |
| 935 | + } else { |
| 936 | + // Root same - uses saved instance IDs |
| 937 | + for (const String &saved_id : String(p_config->get_value(p_section, "id")).split("\n")) { |
| 938 | + p_lib_ids.append(uint64_t(saved_id.to_int() - INT64_MIN)); |
| 939 | + } |
| 940 | + } |
| 941 | +} |
| 942 | + |
| 943 | +String AnimationLibraryEditor::_get_mixer_signature() const { |
| 944 | + String signature = String(); |
| 945 | + |
| 946 | + // Get all libraries sorted for consistency |
| 947 | + List<StringName> libs; |
| 948 | + mixer->get_animation_library_list(&libs); |
| 949 | + libs.sort_custom<StringName::AlphCompare>(); |
| 950 | + |
| 951 | + // Add libraries and their animations to signature |
| 952 | + for (const StringName &lib_name : libs) { |
| 953 | + signature += "::" + String(lib_name); |
| 954 | + Ref<AnimationLibrary> lib = mixer->get_animation_library(lib_name); |
| 955 | + if (lib.is_valid()) { |
| 956 | + List<StringName> anims; |
| 957 | + lib->get_animation_list(&anims); |
| 958 | + anims.sort_custom<StringName::AlphCompare>(); |
| 959 | + for (const StringName &anim_name : anims) { |
| 960 | + signature += "," + String(anim_name); |
| 961 | + } |
| 962 | + } |
| 963 | + } |
| 964 | + |
| 965 | + return signature.md5_text(); |
| 966 | +} |
| 967 | + |
768 | 968 | void AnimationLibraryEditor::show_dialog() { |
769 | 969 | update_tree(); |
770 | 970 | popup_centered_ratio(0.5); |
@@ -855,11 +1055,12 @@ AnimationLibraryEditor::AnimationLibraryEditor() { |
855 | 1055 | tree->set_column_custom_minimum_width(1, EDSCALE * 250); |
856 | 1056 | tree->set_column_expand(1, false); |
857 | 1057 | tree->set_hide_root(true); |
858 | | - tree->set_hide_folding(true); |
| 1058 | + tree->set_hide_folding(false); |
859 | 1059 | tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
860 | 1060 |
|
861 | 1061 | tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed)); |
862 | 1062 | tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); |
| 1063 | + tree->connect("item_collapsed", callable_mp(this, &AnimationLibraryEditor::_save_mixer_lib_folding)); |
863 | 1064 |
|
864 | 1065 | file_popup = memnew(PopupMenu); |
865 | 1066 | add_child(file_popup); |
|
0 commit comments