From ebf6016267e4383438f56e001cbe1cfa978d2491 Mon Sep 17 00:00:00 2001 From: Mikael Hermansson Date: Wed, 24 Sep 2025 18:00:28 +0200 Subject: [PATCH] Add solo/hide/lock/delete buttons to node groups in bezier track editor Co-authored-by: Kasper Arnklit Frandsen --- editor/animation/animation_bezier_editor.cpp | 365 +++++++++++++++---- editor/animation/animation_bezier_editor.h | 8 +- editor/animation/animation_track_editor.cpp | 2 +- 3 files changed, 297 insertions(+), 78 deletions(-) diff --git a/editor/animation/animation_bezier_editor.cpp b/editor/animation/animation_bezier_editor.cpp index 673c12eec46..b1eced534a2 100644 --- a/editor/animation/animation_bezier_editor.cpp +++ b/editor/animation/animation_bezier_editor.cpp @@ -332,6 +332,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { RBMap subtrack_colors; Color selected_track_color; subtracks.clear(); + node_icons.clear(); subtrack_icons.clear(); // Marker sections. @@ -405,6 +406,15 @@ void AnimationBezierTrackEdit::_notification(int p_what) { track_indices[base_path] = indices; } + const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)); + + Ref remove = get_editor_theme_icon(SNAME("Remove")); + Ref visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible")); + Ref visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden")); + Ref lock = get_editor_theme_icon(SNAME("Lock")); + Ref unlock = get_editor_theme_icon(SNAME("Unlock")); + Ref solo = get_editor_theme_icon(SNAME("AudioBusSolo")); + for (const KeyValue> &E : track_indices) { String base_path = E.key; @@ -444,25 +454,64 @@ void AnimationBezierTrackEdit::_notification(int p_what) { string_pos = string_pos.floor(); text_buf.draw(get_canvas_item(), string_pos, color); + Rect2 remove_rect(limit - h_separation - remove->get_width(), vofs, remove->get_width(), remove->get_height()); + if (read_only) { + draw_texture(remove, remove_rect.position, dc); + } else { + draw_texture(remove, remove_rect.position); + } + + bool all_tracks_locked = true; + for (int track : tracks) { + if (!locked_tracks.has(track)) { + all_tracks_locked = false; + break; + } + } + + Rect2 lock_rect(remove_rect.position.x - h_separation - lock->get_width(), vofs, lock->get_width(), lock->get_height()); + + if (all_tracks_locked) { + draw_texture(lock, lock_rect.position); + } else { + draw_texture(unlock, lock_rect.position); + } + + bool all_tracks_hidden = true; + for (int track : tracks) { + if (!hidden_tracks.has(track)) { + all_tracks_hidden = false; + break; + } + } + + Rect2 visibility_rect(lock_rect.position.x - h_separation - visibility_visible->get_width(), vofs, visibility_visible->get_width(), visibility_visible->get_height()); + + if (all_tracks_hidden) { + draw_texture(visibility_hidden, visibility_rect.position); + } else { + draw_texture(visibility_visible, visibility_rect.position); + } + + Rect2 solo_rect(visibility_rect.position.x - h_separation - solo->get_width(), vofs, solo->get_width(), solo->get_height()); + draw_texture(solo, solo_rect.position); + + RBMap icon_rects; + icon_rects[REMOVE_ICON] = remove_rect; + icon_rects[LOCK_ICON] = lock_rect; + icon_rects[VISIBILITY_ICON] = visibility_rect; + icon_rects[SOLO_ICON] = solo_rect; + + node_icons[base_path.trim_suffix(":")] = icon_rects; + vofs += h + v_separation; track_v_scroll_max += h + v_separation; } } - const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)); - - Ref remove = get_editor_theme_icon(SNAME("Remove")); float remove_hpos = limit - h_separation - remove->get_width(); - - Ref lock = get_editor_theme_icon(SNAME("Lock")); - Ref unlock = get_editor_theme_icon(SNAME("Unlock")); float lock_hpos = remove_hpos - h_separation - lock->get_width(); - - Ref visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible")); - Ref visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden")); float visibility_hpos = lock_hpos - h_separation - visibility_visible->get_width(); - - Ref solo = get_editor_theme_icon(SNAME("AudioBusSolo")); float solo_hpos = visibility_hpos - h_separation - solo->get_width(); float buttons_width = remove->get_width() + lock->get_width() + visibility_visible->get_width() + solo->get_width() + h_separation * 3; @@ -521,30 +570,31 @@ void AnimationBezierTrackEdit::_notification(int p_what) { Vector2 string_pos = Point2(margin + h_separation, vofs); text_buf.draw(get_canvas_item(), string_pos, cc); - float icon_start_height = vofs + rect.size.y / 2.0; - Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2.0, remove->get_width(), remove->get_height()); + Rect2 remove_rect = Rect2(remove_hpos, vofs, remove->get_width(), remove->get_height()); + static const Color texture_modulate = Color(1, 1, 1, .75); + if (read_only) { draw_texture(remove, remove_rect.position, dc); } else { - draw_texture(remove, remove_rect.position); + draw_texture(remove, remove_rect.position, texture_modulate); } - Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2.0, lock->get_width(), lock->get_height()); + Rect2 lock_rect = Rect2(lock_hpos, vofs, lock->get_width(), lock->get_height()); if (locked_tracks.has(current_track)) { - draw_texture(lock, lock_rect.position); + draw_texture(lock, lock_rect.position, texture_modulate); } else { - draw_texture(unlock, lock_rect.position); + draw_texture(unlock, lock_rect.position, texture_modulate); } - Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visibility_visible->get_height() / 2.0, visibility_visible->get_width(), visibility_visible->get_height()); + Rect2 visible_rect = Rect2(visibility_hpos, vofs, visibility_visible->get_width(), visibility_visible->get_height()); if (hidden_tracks.has(current_track)) { - draw_texture(visibility_hidden, visible_rect.position); + draw_texture(visibility_hidden, visible_rect.position, texture_modulate); } else { - draw_texture(visibility_visible, visible_rect.position); + draw_texture(visibility_visible, visible_rect.position, texture_modulate); } - Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2.0, solo->get_width(), solo->get_height()); - draw_texture(solo, solo_rect.position); + Rect2 solo_rect = Rect2(solo_hpos, vofs, solo->get_width(), solo->get_height()); + draw_texture(solo, solo_rect.position, texture_modulate); RBMap track_icons; track_icons[REMOVE_ICON] = remove_rect; @@ -622,7 +672,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { } } - if (track_count > 0 && !hidden_tracks.has(selected_track)) { + if (selected_track >= 0 && track_count > 0 && !hidden_tracks.has(selected_track)) { // Draw edited curve. _draw_track(selected_track, selected_track_color); } @@ -1062,9 +1112,7 @@ void AnimationBezierTrackEdit::_zoom_changed() { } void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { - if (locked_tracks.has(p_track)) { - locked_tracks.erase(p_track); - } + _unlock_track(p_track); Vector updated_locked_tracks; for (const int &E : locked_tracks) { @@ -1081,9 +1129,7 @@ void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { } void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) { - if (hidden_tracks.has(p_track)) { - hidden_tracks.erase(p_track); - } + _show_track(p_track); Vector updated_hidden_tracks; for (const int &E : hidden_tracks) { @@ -1099,6 +1145,42 @@ void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) { } } +bool AnimationBezierTrackEdit::_lock_track(int p_track) { + locked_tracks.insert(p_track); + if (selected_track == p_track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i, read_only); + return true; + } + } + } + + return false; +} + +bool AnimationBezierTrackEdit::_unlock_track(int p_track) { + return locked_tracks.erase(p_track); +} + +bool AnimationBezierTrackEdit::_hide_track(int p_track) { + hidden_tracks.insert(p_track); + if (selected_track == p_track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i, read_only); + return true; + } + } + } + + return false; +} + +bool AnimationBezierTrackEdit::_show_track(int p_track) { + return hidden_tracks.erase(p_track); +} + String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } @@ -1340,8 +1422,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { for (const KeyValue> &E : subtrack_icons) { int track = E.key; - RBMap track_icons = E.value; - for (const KeyValue &I : track_icons) { + for (const KeyValue &I : E.value) { if (I.value.has_point(mb->get_position())) { if (I.key == REMOVE_ICON) { if (!read_only) { @@ -1356,6 +1437,14 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track); undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track)); + if (locked_tracks.has(track)) { + undo_redo->add_undo_method(this, "_lock_track", track); + } + + if (hidden_tracks.has(track)) { + undo_redo->add_undo_method(this, "_hide_track", track); + } + for (int i = 0; i < animation->track_get_key_count(track); ++i) { undo_redo->add_undo_method( this, @@ -1371,75 +1460,199 @@ void AnimationBezierTrackEdit::gui_input(const Ref &p_event) { undo_redo->commit_action(); - selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1); + for (selected_track = MIN(selected_track, animation->get_track_count() - 1); selected_track >= 0; --selected_track) { + if (animation->track_get_type(selected_track) == Animation::TYPE_BEZIER) { + break; + } + } } return; } else if (I.key == LOCK_ICON) { - if (locked_tracks.has(track)) { - locked_tracks.erase(track); - } else { - locked_tracks.insert(track); - if (selected_track == track) { - for (int i = 0; i < animation->get_track_count(); ++i) { - if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { - set_animation_and_track(animation, i, read_only); - break; - } + if (!_unlock_track(track)) { + _lock_track(track); + } + queue_redraw(); + return; + } else if (I.key == VISIBILITY_ICON) { + if (!_show_track(track)) { + _hide_track(track); + } + queue_redraw(); + return; + } else if (I.key == SOLO_ICON) { + bool show_other_tracks = true; + for (int i = 0; i < animation->get_track_count(); ++i) { + if (i != track && animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) { + show_other_tracks = false; + break; + } + } + + if (_show_track(track)) { + show_other_tracks = false; + } + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (i != track) { + if (show_other_tracks) { + _show_track(i); + } else { + _hide_track(i); } } } queue_redraw(); return; - } else if (I.key == VISIBILITY_ICON) { - if (hidden_tracks.has(track)) { - hidden_tracks.erase(track); - } else { - hidden_tracks.insert(track); - if (selected_track == track) { - for (int i = 0; i < animation->get_track_count(); ++i) { - if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { - set_animation_and_track(animation, i, read_only); - break; - } + } + return; + } + } + } + + for (const KeyValue> &E : node_icons) { + for (const KeyValue &I : E.value) { + if (I.value.has_point(mb->get_position())) { + if (I.key == REMOVE_ICON) { + if (!read_only) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action("Remove Bezier Track", UndoRedo::MERGE_DISABLE, animation.ptr(), true); + + for (int i = animation->get_track_count() - 1; i >= 0; --i) { + if (animation->track_get_path(i).get_concatenated_names() != E.key) { + continue; + } + + if (animation->track_get_type(i) != Animation::TYPE_BEZIER) { + continue; + } + + undo_redo->add_do_method(this, "_update_locked_tracks_after", i); + undo_redo->add_do_method(this, "_update_hidden_tracks_after", i); + undo_redo->add_do_method(animation.ptr(), "remove_track", i); + + for (int j = animation->track_get_key_count(i) - 1; j >= 0; --j) { + undo_redo->add_undo_method( + this, + "_bezier_track_insert_key_at_anim", + animation, + i, + animation->track_get_key_time(i, j), + animation->bezier_track_get_key_value(i, j), + animation->bezier_track_get_key_in_handle(i, j), + animation->bezier_track_get_key_out_handle(i, j), + animation->bezier_track_get_key_handle_mode(i, j)); + } + + if (hidden_tracks.has(i)) { + undo_redo->add_undo_method(this, "_hide_track", i); + } + + if (locked_tracks.has(i)) { + undo_redo->add_undo_method(this, "_lock_track", i); + } + + undo_redo->add_undo_method(animation.ptr(), "track_set_path", i, animation->track_get_path(i)); + undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, i); + } + + undo_redo->commit_action(); + + for (selected_track = MIN(selected_track, animation->get_track_count() - 1); selected_track >= 0; --selected_track) { + if (animation->track_get_type(selected_track) == Animation::TYPE_BEZIER) { + break; + } + } + } + return; + } else if (I.key == LOCK_ICON) { + bool unlock_tracks = true; + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !locked_tracks.has(i)) { + unlock_tracks = false; + break; } } } - Vector visible_tracks; for (int i = 0; i < animation->get_track_count(); ++i) { - if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { - visible_tracks.push_back(i); + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (unlock_tracks) { + _unlock_track(i); + } else { + _lock_track(i); + } } } - if (visible_tracks.size() == 1) { - solo_track = visible_tracks[0]; - } else { - solo_track = -1; + queue_redraw(); + return; + } else if (I.key == VISIBILITY_ICON) { + bool show_tracks = true; + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) { + show_tracks = false; + break; + } + } + } + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (show_tracks) { + _show_track(i); + } else { + _hide_track(i); + } + } } queue_redraw(); return; } else if (I.key == SOLO_ICON) { - if (solo_track == track) { - solo_track = -1; + bool show_other_tracks = true; - hidden_tracks.clear(); - } else { - if (hidden_tracks.has(track)) { - hidden_tracks.erase(track); - } - for (int i = 0; i < animation->get_track_count(); ++i) { - if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { - if (i != track && !hidden_tracks.has(i)) { - hidden_tracks.insert(i); - } + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() != E.key) { + if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) { + show_other_tracks = false; + break; } } - - set_animation_and_track(animation, track, read_only); - solo_track = track; } + + bool show_own_tracks = true; + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) { + show_own_tracks = false; + break; + } + } + } + + if (show_own_tracks) { + show_other_tracks = false; + } + + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_path(i).get_concatenated_names() == E.key) { + if (show_own_tracks) { + _show_track(i); + } + } else { + if (show_other_tracks) { + _show_track(i); + } else { + _hide_track(i); + } + } + } + queue_redraw(); return; } @@ -2506,6 +2719,8 @@ void AnimationBezierTrackEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim); ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after); ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after); + ClassDB::bind_method(D_METHOD("_lock_track"), &AnimationBezierTrackEdit::_lock_track); + ClassDB::bind_method(D_METHOD("_hide_track"), &AnimationBezierTrackEdit::_hide_track); ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim, DEFVAL(Animation::HANDLE_SET_MODE_NONE)); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track"))); diff --git a/editor/animation/animation_bezier_editor.h b/editor/animation/animation_bezier_editor.h index 06e1795bae0..692b2e9338a 100644 --- a/editor/animation/animation_bezier_editor.h +++ b/editor/animation/animation_bezier_editor.h @@ -60,7 +60,7 @@ class AnimationBezierTrackEdit : public Control { Ref animation; bool read_only = false; - int selected_track = 0; + int selected_track = -1; Vector view_rects; @@ -77,10 +77,10 @@ class AnimationBezierTrackEdit : public Control { VISIBILITY_ICON }; + RBMap> node_icons; RBMap> subtrack_icons; HashSet locked_tracks; HashSet hidden_tracks; - int solo_track = -1; bool is_filtered = false; float track_v_scroll = 0; @@ -95,6 +95,10 @@ class AnimationBezierTrackEdit : public Control { void _update_locked_tracks_after(int p_track); void _update_hidden_tracks_after(int p_track); + bool _lock_track(int p_track); + bool _unlock_track(int p_track); + bool _hide_track(int p_track); + bool _show_track(int p_track); virtual void gui_input(const Ref &p_event) override; void _menu_selected(int p_index); diff --git a/editor/animation/animation_track_editor.cpp b/editor/animation/animation_track_editor.cpp index 142d5cf233a..ee3a4200dd5 100644 --- a/editor/animation/animation_track_editor.cpp +++ b/editor/animation/animation_track_editor.cpp @@ -2155,7 +2155,7 @@ void AnimationTrackEdit::_notification(int p_what) { } break; case NOTIFICATION_DRAW: { - if (animation.is_null()) { + if (animation.is_null() || animation->get_track_count() == 0) { return; } ERR_FAIL_INDEX(track, animation->get_track_count());