diff --git a/editor/animation/animation_track_editor.cpp b/editor/animation/animation_track_editor.cpp index 7ca1eba875..a3b14dc83e 100644 --- a/editor/animation/animation_track_editor.cpp +++ b/editor/animation/animation_track_editor.cpp @@ -3911,7 +3911,14 @@ void AnimationTrackEditGroup::_notification(int p_what) { draw_line(Point2(get_size().width - timeline->get_buttons_width(), 0), Point2(get_size().width - timeline->get_buttons_width(), get_size().height), v_line_color, Math::round(EDSCALE)); int ofs = stylebox_header->get_margin(SIDE_LEFT); + bool is_group_folded = editor->get_current_animation()->editor_is_group_folded(node_name); + Ref fold_icon = get_theme_icon(is_group_folded ? SNAME("arrow_collapsed") : SNAME("arrow"), SNAME("Tree")); + Size2 fold_icon_size = fold_icon->get_size(); + draw_texture_rect(fold_icon, Rect2(Point2(ofs, (get_size().height - fold_icon_size.y) / 2 + v_margin_offset).round(), fold_icon_size)); + + ofs += h_separation + fold_icon_size.x; draw_texture_rect(icon, Rect2(Point2(ofs, (get_size().height - icon_size.y) / 2 + v_margin_offset).round(), icon_size)); + ofs += h_separation + icon_size.x; draw_string(font, Point2(ofs, (get_size().height - font->get_height(font_size)) / 2 + font->get_ascent(font_size) + v_margin_offset).round(), node_name, HORIZONTAL_ALIGNMENT_LEFT, timeline->get_name_limit() - ofs, font_size, color); @@ -3938,14 +3945,41 @@ void AnimationTrackEditGroup::gui_input(const Ref &p_event) { Ref mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Point2 pos = mb->get_position(); - Rect2 node_name_rect = Rect2(0, 0, timeline->get_name_limit(), get_size().height); - if (node_name_rect.has_point(pos)) { - EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); - editor_selection->clear(); - Node *n = root->get_node_or_null(node); - if (n) { - editor_selection->add_node(n); + int left_ofs = get_theme_stylebox(SNAME("header"), SNAME("AnimationTrackEditGroup"))->get_margin(SIDE_LEFT); + bool is_group_folded = editor->get_current_animation()->editor_is_group_folded(node_name); + Ref fold_icon = get_theme_icon(is_group_folded ? SNAME("arrow_collapsed") : SNAME("arrow"), SNAME("Tree")); + int fold_icon_width = fold_icon->get_size().width; + Rect2 fold_area_rect = Rect2(0, 0, left_ofs + fold_icon_width, get_size().height); + + if (fold_area_rect.has_point(pos)) { + bool current_group_folded = !editor->get_current_animation()->editor_is_group_folded(node_name); + editor->get_current_animation()->editor_set_group_folded(node_name, current_group_folded); + + if (!editor->get_current_animation()->get_path().is_resource_file()) { + EditorNode::get_editor_folding().save_scene_folding( + EditorNode::get_singleton()->get_edited_scene(), + EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path()); + } else { + EditorNode::get_editor_folding().save_resource_folding( + editor->get_current_animation(), + editor->get_current_animation()->get_path()); + } + + for (AnimationTrackEdit *i : track_edits) { + i->set_visible(!current_group_folded); + } + + queue_redraw(); + } else { + Rect2 node_name_rect = Rect2(0, 0, timeline->get_name_limit(), get_size().height); + if (node_name_rect.has_point(pos)) { + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + editor_selection->clear(); + Node *n = root->get_node_or_null(node); + if (n) { + editor_selection->add_node(n); + } } } } @@ -4079,6 +4113,12 @@ void AnimationTrackEditor::set_animation(const Ref &p_anim, bool p_re } } } + + if (animation->get_path().is_resource_file()) { + EditorNode::get_editor_folding().load_resource_folding( + animation, + animation->get_path()); + } } else { hscroll->hide(); edit->set_disabled(true); @@ -5305,6 +5345,10 @@ void AnimationTrackEditor::_update_tracks() { track_edit->set_in_group(true); group_sort[base_path]->add_child(track_edit); + AnimationTrackEditGroup *g = Object::cast_to(group_sort[base_path]->get_child(0)); + ERR_FAIL_NULL_MSG(g, "The first child of this group's VBoxContainer isn't an AnimationTrackEditGroup. Collapsing this animation group may not work."); + g->track_edits.push_back(track_edit); + } else { track_edit->set_in_group(false); } @@ -5353,6 +5397,13 @@ void AnimationTrackEditor::_update_tracks() { for (VBoxContainer *vb : group_containers) { track_vbox->add_child(vb); + + AnimationTrackEditGroup *g = Object::cast_to(vb->get_child(0)); + if (g) { + for (AnimationTrackEdit *i : g->track_edits) { + i->set_visible(!animation->editor_is_group_folded(g->node_name)); + } + } } } else { @@ -6399,6 +6450,9 @@ void AnimationTrackEditor::_scroll_input(const Ref &p_event) { if (box_selection->is_visible_in_tree()) { // Only if moved. for (int i = 0; i < track_edits.size(); i++) { + if (!track_edits[i]->is_visible_in_tree()) { + continue; // Skip collapsed track edits. + } Rect2 local_rect = box_select_rect; local_rect.position -= track_edits[i]->get_global_position(); track_edits[i]->append_to_selection(local_rect, mb->is_command_or_control_pressed()); diff --git a/editor/animation/animation_track_editor.h b/editor/animation/animation_track_editor.h index d32e33f2ee..f31e14d091 100644 --- a/editor/animation/animation_track_editor.h +++ b/editor/animation/animation_track_editor.h @@ -568,6 +568,8 @@ class AnimationMultiTrackKeyEdit; class AnimationBezierTrackEdit; class AnimationTrackEditGroup : public Control { + friend class AnimationTrackEditor; + GDCLASS(AnimationTrackEditGroup, Control); Ref icon; Vector2 icon_size; @@ -578,7 +580,7 @@ class AnimationTrackEditGroup : public Control { AnimationTrackEditor *editor = nullptr; bool hovered = false; - + LocalVector track_edits; void _zoom_changed(); protected: diff --git a/editor/docks/scene_tree_dock.cpp b/editor/docks/scene_tree_dock.cpp index 37743a5298..98c63f47b9 100644 --- a/editor/docks/scene_tree_dock.cpp +++ b/editor/docks/scene_tree_dock.cpp @@ -2316,6 +2316,32 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap } } } + + // key.get_path() of p_renames is like: + // /root/@EditorNode@18033/@Panel@14/.../Scene/TheOldName + // value of p_renames is like: + // /root/@EditorNode@18033/@Panel@14/.../Scene/TheNewName + for (const KeyValue &rename : *p_renames) { + NodePath old_path = rename.key->get_path(); + NodePath new_path = rename.value; + Vector rel_path = old_path.rel_path_to(new_path).get_names(); + + StringName old_node_name = rename.key->get_name(); + StringName new_node_name = rel_path[rel_path.size() - 1]; + + anim->editor_set_group_folded(new_node_name, anim->editor_is_group_folded(old_node_name)); + anim->editor_set_group_folded(old_node_name, false); + } + + if (!anim->get_path().is_resource_file()) { + EditorNode::get_editor_folding().save_scene_folding( + EditorNode::get_singleton()->get_edited_scene(), + EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path()); + } else { + EditorNode::get_editor_folding().save_resource_folding( + anim, + anim->get_path()); + } } } } diff --git a/editor/settings/editor_folding.cpp b/editor/settings/editor_folding.cpp index f65948c2fc..0d93d6d3be 100644 --- a/editor/settings/editor_folding.cpp +++ b/editor/settings/editor_folding.cpp @@ -34,6 +34,8 @@ #include "core/io/file_access.h" #include "editor/file_system/editor_paths.h" #include "editor/inspector/editor_inspector.h" +#include "scene/animation/animation_mixer.h" +#include "scene/resources/animation.h" Vector EditorFolding::_get_unfolds(const Object *p_object) { Vector sections; @@ -55,6 +57,12 @@ void EditorFolding::save_resource_folding(const Ref &p_resource, const Vector unfolds = _get_unfolds(p_resource.ptr()); config->set_value("folding", "sections_unfolded", unfolds); + Ref as_anim = p_resource; + if (as_anim.is_valid()) { + Vector folded_groups = _get_animation_folds(as_anim.ptr()); + config->set_value("folding", "animation_groups_folded", folded_groups); + } + String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg"; file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file); config->save(file); @@ -86,9 +94,15 @@ void EditorFolding::load_resource_folding(Ref p_resource, const String unfolds = config->get_value("folding", "sections_unfolded"); } _set_unfolds(p_resource.ptr(), unfolds); + + Ref anim = p_resource; + if (anim.is_valid() && config->has_section_key("folding", "animation_groups_folded")) { + Vector folded_groups = config->get_value("folding", "animation_groups_folded"); + _set_animation_folds(anim.ptr(), folded_groups); + } } -void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet> &resources) { +void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet> &resources, HashSet> &animations, Array &anim_groups_folded) { if (p_root != p_node) { if (!p_node->get_owner()) { return; //not owned, bye @@ -108,6 +122,21 @@ void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p p_folds.push_back(unfolds); } + const AnimationMixer *anim_mixer = Object::cast_to(p_node); + if (anim_mixer) { + List anim_names; + anim_mixer->get_animation_list(&anim_names); + for (const StringName &anim_name : anim_names) { + Ref anim = anim_mixer->get_animation(anim_name); + if (anim.is_valid() && !animations.has(anim) && !anim->get_path().is_empty() && !anim->get_path().is_resource_file()) { + Vector anim_folds = _get_animation_folds(anim.ptr()); + anim_groups_folded.push_back(anim->get_path()); + anim_groups_folded.push_back(anim_folds); + animations.insert(anim); + } + } + } + List plist; p_node->get_property_list(&plist); for (const PropertyInfo &E : plist) { @@ -125,7 +154,7 @@ void EditorFolding::_fill_folds(const Node *p_root, const Node *p_node, Array &p } for (int i = 0; i < p_node->get_child_count(); i++) { - _fill_folds(p_root, p_node->get_child(i), p_folds, resource_folds, nodes_folded, resources); + _fill_folds(p_root, p_node->get_child(i), p_folds, resource_folds, nodes_folded, resources, animations, anim_groups_folded); } } @@ -143,11 +172,14 @@ void EditorFolding::save_scene_folding(const Node *p_scene, const String &p_path Array unfolds, res_unfolds; HashSet> resources; Array nodes_folded; - _fill_folds(p_scene, p_scene, unfolds, res_unfolds, nodes_folded, resources); + HashSet> animations; + Array anim_groups_folded; + _fill_folds(p_scene, p_scene, unfolds, res_unfolds, nodes_folded, resources, animations, anim_groups_folded); config->set_value("folding", "node_unfolds", unfolds); config->set_value("folding", "resource_unfolds", res_unfolds); config->set_value("folding", "nodes_folded", nodes_folded); + config->set_value("folding", "animation_groups_folded", anim_groups_folded); String file = p_path.get_file() + "-folding-" + p_path.md5_text() + ".cfg"; file = EditorPaths::get_singleton()->get_project_settings_dir().path_join(file); @@ -178,9 +210,14 @@ void EditorFolding::load_scene_folding(Node *p_scene, const String &p_path) { if (config->has_section_key("folding", "nodes_folded")) { nodes_folded = config->get_value("folding", "nodes_folded"); } + Array animation_groups_folded; + if (config->has_section_key("folding", "animation_groups_folded")) { + animation_groups_folded = config->get_value("folding", "animation_groups_folded"); + } ERR_FAIL_COND(unfolds.size() & 1); ERR_FAIL_COND(res_unfolds.size() & 1); + ERR_FAIL_COND(animation_groups_folded.size() & 1); for (int i = 0; i < unfolds.size(); i += 2) { NodePath path2 = unfolds[i]; @@ -210,6 +247,17 @@ void EditorFolding::load_scene_folding(Node *p_scene, const String &p_path) { node->set_display_folded(true); } } + + for (int i = 0; i < animation_groups_folded.size(); i += 2) { + String path2 = animation_groups_folded[i]; + Ref anim = ResourceCache::get_ref(path2); + if (anim.is_null()) { + continue; + } + + Vector folded_groups = animation_groups_folded[i + 1]; + _set_animation_folds(anim.ptr(), folded_groups); + } } bool EditorFolding::has_folding_data(const String &p_path) { @@ -294,3 +342,24 @@ void EditorFolding::unfold_scene(Node *p_scene) { HashSet> resources; _do_node_unfolds(p_scene, p_scene, resources); } + +Vector EditorFolding::_get_animation_folds(const Animation *p_animation) { + Vector folded_groups; + folded_groups.resize(p_animation->editor_get_folded_groups().size()); + if (folded_groups.size()) { + String *w = folded_groups.ptrw(); + int idx = 0; + for (const StringName &group_name : p_animation->editor_get_folded_groups()) { + w[idx++] = group_name; + } + } + + return folded_groups; +} + +void EditorFolding::_set_animation_folds(Animation *p_animation, const Vector &p_folds) { + p_animation->editor_clear_folded_groups(); + for (const String &group_name : p_folds) { + p_animation->editor_add_folded_group(group_name); + } +} diff --git a/editor/settings/editor_folding.h b/editor/settings/editor_folding.h index cc41dbd71a..fc0a3cdbf7 100644 --- a/editor/settings/editor_folding.h +++ b/editor/settings/editor_folding.h @@ -32,15 +32,20 @@ #include "scene/main/node.h" +class Animation; + class EditorFolding { Vector _get_unfolds(const Object *p_object); void _set_unfolds(Object *p_object, const Vector &p_unfolds); - void _fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet> &resources); + void _fill_folds(const Node *p_root, const Node *p_node, Array &p_folds, Array &resource_folds, Array &nodes_folded, HashSet> &resources, HashSet> &animations, Array &anim_groups_folded); void _do_object_unfolds(Object *p_object, HashSet> &resources); void _do_node_unfolds(Node *p_root, Node *p_node, HashSet> &resources); + Vector _get_animation_folds(const Animation *p_animation); + void _set_animation_folds(Animation *p_animation, const Vector &p_unfolds); + public: void save_resource_folding(const Ref &p_resource, const String &p_path); void load_resource_folding(Ref p_resource, const String &p_path); @@ -48,6 +53,9 @@ public: void save_scene_folding(const Node *p_scene, const String &p_path); void load_scene_folding(Node *p_scene, const String &p_path); + void save_animation_folding(const Ref &p_animation, const String &p_path); + void load_animation_folding(Ref p_animation, const String &p_path); + void unfold_scene(Node *p_scene); bool has_folding_data(const String &p_path); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 71bc1ec6a3..a2725f294e 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -251,6 +251,10 @@ private: LocalVector tracks; +#ifdef TOOLS_ENABLED + HashSet folded_groups; +#endif // TOOLS_ENABLED + template int _insert(double p_time, T &p_keys, const V &p_value); @@ -539,6 +543,21 @@ public: void optimize(real_t p_allowed_velocity_err = 0.01, real_t p_allowed_angular_err = 0.01, int p_precision = 3); void compress(uint32_t p_page_size = 8192, uint32_t p_fps = 120, float p_split_tolerance = 4.0); // 4.0 seems to be the split tolerance sweet spot from many tests. +#ifdef TOOLS_ENABLED + const HashSet &editor_get_folded_groups() const { return folded_groups; } + void editor_clear_folded_groups() { folded_groups.clear(); } + void editor_add_folded_group(const StringName &p_group_name) { folded_groups.insert(p_group_name); } + void editor_remove_folded_group(const StringName &p_group_name) { folded_groups.erase(p_group_name); } + bool editor_is_group_folded(const StringName &p_group_name) const { return folded_groups.has(p_group_name); } + void editor_set_group_folded(const StringName &p_group_name, bool p_folded) { + if (p_folded) { + editor_add_folded_group(p_group_name); + } else { + editor_remove_folded_group(p_group_name); + } + } +#endif // TOOLS_ENABLED + // Helper functions for Rotation. static double interpolate_via_rest(double p_from, double p_to, double p_weight, double p_rest = 0.0); // Deterministic slerp to prevent to cross the inverted rest axis. static Quaternion interpolate_via_rest(const Quaternion &p_from, const Quaternion &p_to, real_t p_weight, const Quaternion &p_rest = Quaternion()); // Deterministic slerp to prevent to cross the inverted rest axis.