Add solo/hide/lock/delete buttons to node groups in bezier track editor

Co-authored-by: Kasper Arnklit Frandsen <kasper.arnklit@gmail.com>
This commit is contained in:
Mikael Hermansson 2025-09-24 18:00:28 +02:00
parent f5918a9d35
commit ebf6016267
3 changed files with 297 additions and 78 deletions

View file

@ -332,6 +332,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
RBMap<int, Color> subtrack_colors; RBMap<int, Color> subtrack_colors;
Color selected_track_color; Color selected_track_color;
subtracks.clear(); subtracks.clear();
node_icons.clear();
subtrack_icons.clear(); subtrack_icons.clear();
// Marker sections. // Marker sections.
@ -405,6 +406,15 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
track_indices[base_path] = indices; track_indices[base_path] = indices;
} }
const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));
Ref<Texture2D> remove = get_editor_theme_icon(SNAME("Remove"));
Ref<Texture2D> visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));
Ref<Texture2D> visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden"));
Ref<Texture2D> lock = get_editor_theme_icon(SNAME("Lock"));
Ref<Texture2D> unlock = get_editor_theme_icon(SNAME("Unlock"));
Ref<Texture2D> solo = get_editor_theme_icon(SNAME("AudioBusSolo"));
for (const KeyValue<String, Vector<int>> &E : track_indices) { for (const KeyValue<String, Vector<int>> &E : track_indices) {
String base_path = E.key; String base_path = E.key;
@ -444,25 +454,64 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
string_pos = string_pos.floor(); string_pos = string_pos.floor();
text_buf.draw(get_canvas_item(), string_pos, color); 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<int, Rect2> 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; vofs += h + v_separation;
track_v_scroll_max += h + v_separation; track_v_scroll_max += h + v_separation;
} }
} }
const Color dc = get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));
Ref<Texture2D> remove = get_editor_theme_icon(SNAME("Remove"));
float remove_hpos = limit - h_separation - remove->get_width(); float remove_hpos = limit - h_separation - remove->get_width();
Ref<Texture2D> lock = get_editor_theme_icon(SNAME("Lock"));
Ref<Texture2D> unlock = get_editor_theme_icon(SNAME("Unlock"));
float lock_hpos = remove_hpos - h_separation - lock->get_width(); float lock_hpos = remove_hpos - h_separation - lock->get_width();
Ref<Texture2D> visibility_visible = get_editor_theme_icon(SNAME("GuiVisibilityVisible"));
Ref<Texture2D> visibility_hidden = get_editor_theme_icon(SNAME("GuiVisibilityHidden"));
float visibility_hpos = lock_hpos - h_separation - visibility_visible->get_width(); float visibility_hpos = lock_hpos - h_separation - visibility_visible->get_width();
Ref<Texture2D> solo = get_editor_theme_icon(SNAME("AudioBusSolo"));
float solo_hpos = visibility_hpos - h_separation - solo->get_width(); 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; 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); Vector2 string_pos = Point2(margin + h_separation, vofs);
text_buf.draw(get_canvas_item(), string_pos, cc); 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, vofs, remove->get_width(), remove->get_height());
Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2.0, remove->get_width(), remove->get_height()); static const Color texture_modulate = Color(1, 1, 1, .75);
if (read_only) { if (read_only) {
draw_texture(remove, remove_rect.position, dc); draw_texture(remove, remove_rect.position, dc);
} else { } 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)) { if (locked_tracks.has(current_track)) {
draw_texture(lock, lock_rect.position); draw_texture(lock, lock_rect.position, texture_modulate);
} else { } 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)) { if (hidden_tracks.has(current_track)) {
draw_texture(visibility_hidden, visible_rect.position); draw_texture(visibility_hidden, visible_rect.position, texture_modulate);
} else { } 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()); Rect2 solo_rect = Rect2(solo_hpos, vofs, solo->get_width(), solo->get_height());
draw_texture(solo, solo_rect.position); draw_texture(solo, solo_rect.position, texture_modulate);
RBMap<int, Rect2> track_icons; RBMap<int, Rect2> track_icons;
track_icons[REMOVE_ICON] = remove_rect; 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 edited curve.
_draw_track(selected_track, selected_track_color); _draw_track(selected_track, selected_track_color);
} }
@ -1062,9 +1112,7 @@ void AnimationBezierTrackEdit::_zoom_changed() {
} }
void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) {
if (locked_tracks.has(p_track)) { _unlock_track(p_track);
locked_tracks.erase(p_track);
}
Vector<int> updated_locked_tracks; Vector<int> updated_locked_tracks;
for (const int &E : 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) { void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) {
if (hidden_tracks.has(p_track)) { _show_track(p_track);
hidden_tracks.erase(p_track);
}
Vector<int> updated_hidden_tracks; Vector<int> updated_hidden_tracks;
for (const int &E : 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 { String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const {
return Control::get_tooltip(p_pos); return Control::get_tooltip(p_pos);
} }
@ -1340,8 +1422,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
for (const KeyValue<int, RBMap<int, Rect2>> &E : subtrack_icons) { for (const KeyValue<int, RBMap<int, Rect2>> &E : subtrack_icons) {
int track = E.key; int track = E.key;
RBMap<int, Rect2> track_icons = E.value; for (const KeyValue<int, Rect2> &I : E.value) {
for (const KeyValue<int, Rect2> &I : track_icons) {
if (I.value.has_point(mb->get_position())) { if (I.value.has_point(mb->get_position())) {
if (I.key == REMOVE_ICON) { if (I.key == REMOVE_ICON) {
if (!read_only) { if (!read_only) {
@ -1356,6 +1437,14 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track); 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)); 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) { for (int i = 0; i < animation->track_get_key_count(track); ++i) {
undo_redo->add_undo_method( undo_redo->add_undo_method(
this, this,
@ -1371,75 +1460,199 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
undo_redo->commit_action(); 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; return;
} else if (I.key == LOCK_ICON) { } else if (I.key == LOCK_ICON) {
if (locked_tracks.has(track)) { if (!_unlock_track(track)) {
locked_tracks.erase(track); _lock_track(track);
} else { }
locked_tracks.insert(track); queue_redraw();
if (selected_track == track) { return;
for (int i = 0; i < animation->get_track_count(); ++i) { } else if (I.key == VISIBILITY_ICON) {
if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { if (!_show_track(track)) {
set_animation_and_track(animation, i, read_only); _hide_track(track);
break; }
} 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(); queue_redraw();
return; return;
} else if (I.key == VISIBILITY_ICON) { }
if (hidden_tracks.has(track)) { return;
hidden_tracks.erase(track); }
} else { }
hidden_tracks.insert(track); }
if (selected_track == track) {
for (int i = 0; i < animation->get_track_count(); ++i) { for (const KeyValue<String, RBMap<int, Rect2>> &E : node_icons) {
if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { for (const KeyValue<int, Rect2> &I : E.value) {
set_animation_and_track(animation, i, read_only); if (I.value.has_point(mb->get_position())) {
break; 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<int> visible_tracks;
for (int i = 0; i < animation->get_track_count(); ++i) { for (int i = 0; i < animation->get_track_count(); ++i) {
if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { if (animation->track_get_path(i).get_concatenated_names() == E.key) {
visible_tracks.push_back(i); if (unlock_tracks) {
_unlock_track(i);
} else {
_lock_track(i);
}
} }
} }
if (visible_tracks.size() == 1) { queue_redraw();
solo_track = visible_tracks[0]; return;
} else { } else if (I.key == VISIBILITY_ICON) {
solo_track = -1; 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(); queue_redraw();
return; return;
} else if (I.key == SOLO_ICON) { } else if (I.key == SOLO_ICON) {
if (solo_track == track) { bool show_other_tracks = true;
solo_track = -1;
hidden_tracks.clear(); for (int i = 0; i < animation->get_track_count(); ++i) {
} else { if (animation->track_get_path(i).get_concatenated_names() != E.key) {
if (hidden_tracks.has(track)) { if (animation->track_get_type(i) == Animation::TYPE_BEZIER && !hidden_tracks.has(i)) {
hidden_tracks.erase(track); show_other_tracks = false;
} break;
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);
}
} }
} }
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(); queue_redraw();
return; 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("_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_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("_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)); 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"))); ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));

View file

@ -60,7 +60,7 @@ class AnimationBezierTrackEdit : public Control {
Ref<Animation> animation; Ref<Animation> animation;
bool read_only = false; bool read_only = false;
int selected_track = 0; int selected_track = -1;
Vector<Rect2> view_rects; Vector<Rect2> view_rects;
@ -77,10 +77,10 @@ class AnimationBezierTrackEdit : public Control {
VISIBILITY_ICON VISIBILITY_ICON
}; };
RBMap<String, RBMap<int, Rect2>> node_icons;
RBMap<int, RBMap<int, Rect2>> subtrack_icons; RBMap<int, RBMap<int, Rect2>> subtrack_icons;
HashSet<int> locked_tracks; HashSet<int> locked_tracks;
HashSet<int> hidden_tracks; HashSet<int> hidden_tracks;
int solo_track = -1;
bool is_filtered = false; bool is_filtered = false;
float track_v_scroll = 0; float track_v_scroll = 0;
@ -95,6 +95,10 @@ class AnimationBezierTrackEdit : public Control {
void _update_locked_tracks_after(int p_track); void _update_locked_tracks_after(int p_track);
void _update_hidden_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<InputEvent> &p_event) override; virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _menu_selected(int p_index); void _menu_selected(int p_index);

View file

@ -2155,7 +2155,7 @@ void AnimationTrackEdit::_notification(int p_what) {
} break; } break;
case NOTIFICATION_DRAW: { case NOTIFICATION_DRAW: {
if (animation.is_null()) { if (animation.is_null() || animation->get_track_count() == 0) {
return; return;
} }
ERR_FAIL_INDEX(track, animation->get_track_count()); ERR_FAIL_INDEX(track, animation->get_track_count());