From a7abbe5e9e2cc0319e10bb1f201dc360f660e427 Mon Sep 17 00:00:00 2001 From: passivestar <60579014+passivestar@users.noreply.github.com> Date: Fri, 23 Aug 2024 23:04:03 +0400 Subject: [PATCH] Allow jumping to previous/next keyframe in animation player --- editor/animation_track_editor.cpp | 4 + editor/animation_track_editor.h | 1 + .../animation_player_editor_plugin.cpp | 127 ++++++++++++++---- .../plugins/animation_player_editor_plugin.h | 1 + 4 files changed, 104 insertions(+), 29 deletions(-) diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 1f0ad134513..1b3d06c3c66 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -4580,6 +4580,10 @@ bool AnimationTrackEditor::is_snap_enabled() const { return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); } +bool AnimationTrackEditor::is_bezier_editor_active() const { + return bezier_edit->is_visible(); +} + bool AnimationTrackEditor::can_add_reset_key() const { for (const KeyValue &E : selection) { const Animation::TrackType track_type = animation->track_get_type(E.key.track); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 59ee6535ac2..e2bb308709a 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -729,6 +729,7 @@ public: bool is_key_clipboard_active() const; bool is_moving_selection() const; bool is_snap_enabled() const; + bool is_bezier_editor_active() const; bool can_add_reset_key() const; float get_moving_selection_offset() const; float snap_time(float p_value, bool p_relative = false); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 660e4647a19..b1c83739112 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -224,6 +224,69 @@ void AnimationPlayerEditor::_autoplay_pressed() { } } +void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) { + if (_get_current().is_empty()) { + return; + } + + Ref anim = player->get_animation(player->get_assigned_animation()); + + double current_time = player->get_current_animation_position(); + // Offset the time to avoid finding the same keyframe with Animation::track_find_key(). + double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2); + double current_time_offset = current_time + (p_backward ? -time_offset : time_offset); + + float nearest_key_time = p_backward ? 0 : anim->get_length(); + int track_count = anim->get_track_count(); + bool bezier_active = track_editor->is_bezier_editor_active(); + + Node *root = get_tree()->get_edited_scene_root(); + EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection(); + + Vector selected_tracks; + for (int i = 0; i < track_count; ++i) { + if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) { + selected_tracks.push_back(i); + } + } + + // Find the nearest keyframe in selection if the scene has selected nodes + // or the nearest keyframe in the entire animation otherwise. + if (selected_tracks.size() > 0) { + for (int track : selected_tracks) { + if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) { + continue; + } + int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward); + if (key == -1) { + continue; + } + double key_time = anim->track_get_key_time(track, key); + if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) { + nearest_key_time = key_time; + } + } + } else { + for (int track = 0; track < track_count; ++track) { + if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) { + continue; + } + int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward); + if (key == -1) { + continue; + } + double key_time = anim->track_get_key_time(track, key); + if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) { + nearest_key_time = key_time; + } + } + } + + player->seek_internal(nearest_key_time, true, true, true); + frame->set_value(nearest_key_time); + track_editor->set_anim_pos(nearest_key_time); +} + void AnimationPlayerEditor::_play_pressed() { String current = _get_current(); @@ -1505,30 +1568,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref k = p_ev; - if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) { - switch (k->get_keycode()) { - case Key::A: { - if (!k->is_shift_pressed()) { - _play_bw_from_pressed(); - } else { - _play_bw_pressed(); - } - accept_event(); - } break; - case Key::S: { - _stop_pressed(); - accept_event(); - } break; - case Key::D: { - if (!k->is_shift_pressed()) { - _play_from_pressed(); - } else { - _play_pressed(); - } - accept_event(); - } break; - default: - break; + if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) { + if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) { + _stop_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) { + _play_from_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) { + _play_bw_from_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) { + _play_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) { + _play_bw_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) { + _go_to_nearest_keyframe(false); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) { + _go_to_nearest_keyframe(true); + accept_event(); } } } @@ -1902,27 +1963,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug play_bw_from = memnew(Button); play_bw_from->set_theme_type_variation("FlatButton"); - play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)")); + play_bw_from->set_tooltip_text(TTR("Play Animation Backwards")); hb->add_child(play_bw_from); play_bw = memnew(Button); play_bw->set_theme_type_variation("FlatButton"); - play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)")); + play_bw->set_tooltip_text(TTR("Play Animation Backwards from End")); hb->add_child(play_bw); stop = memnew(Button); stop->set_theme_type_variation("FlatButton"); + stop->set_tooltip_text(TTR("Pause/Stop Animation")); hb->add_child(stop); - stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); play = memnew(Button); play->set_theme_type_variation("FlatButton"); - play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)")); + play->set_tooltip_text(TTR("Play Animation from Start")); hb->add_child(play); play_from = memnew(Button); play_from->set_theme_type_variation("FlatButton"); - play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)")); + play_from->set_tooltip_text(TTR("Play Animation")); hb->add_child(play_from); frame = memnew(SpinBox); @@ -2138,6 +2199,14 @@ void fragment() { } )"); RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid()); + + ED_SHORTCUT("animation_editor/stop_animation", TTR("Pause/Stop Animation"), Key::S); + ED_SHORTCUT("animation_editor/play_animation", TTR("Play Animation"), Key::D); + ED_SHORTCUT("animation_editor/play_animation_backwards", TTR("Play Animation Backwards"), Key::A); + ED_SHORTCUT("animation_editor/play_animation_from_start", TTR("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D); + ED_SHORTCUT("animation_editor/play_animation_from_end", TTR("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A); + ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTR("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D); + ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTR("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A); } AnimationPlayerEditor::~AnimationPlayerEditor() { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 4a3b1f37ab7..53e573d74fd 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -178,6 +178,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _select_anim_by_name(const String &p_anim); float _get_editor_step() const; + void _go_to_nearest_keyframe(bool p_backward); void _play_pressed(); void _play_from_pressed(); void _play_bw_pressed();