diff options
Diffstat (limited to 'editor/animation_track_editor.cpp')
-rw-r--r-- | editor/animation_track_editor.cpp | 203 |
1 files changed, 174 insertions, 29 deletions
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 6f1439a91f..bec95d40c6 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1305,7 +1305,11 @@ void AnimationTimelineEdit::_zoom_changed(double) { } float AnimationTimelineEdit::get_zoom_scale() const { - float zv = zoom->get_max() - zoom->get_value(); + return _get_zoom_scale(zoom->get_value()); +} + +float AnimationTimelineEdit::_get_zoom_scale(double p_zoom_value) const { + float zv = zoom->get_max() - p_zoom_value; if (zv < 1) { zv = 1.0 - zv; return Math::pow(1.0f + zv, 8.0f) * 100; @@ -1633,6 +1637,68 @@ void AnimationTimelineEdit::set_zoom(Range *p_zoom) { zoom->connect("value_changed", callable_mp(this, &AnimationTimelineEdit::_zoom_changed)); } +void AnimationTimelineEdit::auto_fit() { + if (!animation.is_valid()) { + return; + } + + float anim_end = animation->get_length(); + float anim_start = 0; + + // Search for keyframe outside animation boundaries to include keyframes before animation start and after animation length. + int track_count = animation->get_track_count(); + for (int track = 0; track < track_count; ++track) { + for (int i = 0; i < animation->track_get_key_count(track); i++) { + float key_time = animation->track_get_key_time(track, i); + if (key_time > anim_end) { + anim_end = key_time; + } + if (key_time < anim_start) { + anim_start = key_time; + } + } + } + + float anim_length = anim_end - anim_start; + int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit(); + + // I want a little buffer at the end... (5% looks nice and we should keep some space for the bezier handles) + timeline_width_pixels *= 0.95; + + // The technique is to reuse the _get_zoom_scale function directly to be sure that the auto_fit is always calculated + // the same way as the zoom slider. It's a little bit more calculation then doing the inverse of get_zoom_scale but + // it's really easier to understand and should always be accurate. + float new_zoom = zoom->get_max(); + while (true) { + double test_zoom_scale = _get_zoom_scale(new_zoom); + + if (anim_length * test_zoom_scale <= timeline_width_pixels) { + // It fits... + break; + } + + new_zoom -= zoom->get_step(); + + if (new_zoom <= zoom->get_min()) { + new_zoom = zoom->get_min(); + break; + } + } + + // Horizontal scroll to get_min which should include keyframes that are before the animation start. + hscroll->set_value(hscroll->get_min()); + // Set the zoom value... the signal value_changed will be emitted and the timeline will be refreshed correctly! + zoom->set_value(new_zoom); + // The new zoom value must be applied correctly so the scrollbar are updated before we move the scrollbar to + // the beginning of the animation, hence the call deferred. + callable_mp(this, &AnimationTimelineEdit::_scroll_to_start).call_deferred(); +} + +void AnimationTimelineEdit::_scroll_to_start() { + // Horizontal scroll to get_min which should include keyframes that are before the animation start. + hscroll->set_value(hscroll->get_min()); +} + void AnimationTimelineEdit::set_track_edit(AnimationTrackEdit *p_track_edit) { track_edit = p_track_edit; } @@ -2001,13 +2067,13 @@ void AnimationTrackEdit::_notification(int p_what) { for (int i = 0; i < animation->track_get_key_count(track); i++) { float offset = animation->track_get_key_time(track, i) - timeline->get_value(); if (editor->is_key_selected(track, i) && editor->is_moving_selection()) { - offset = editor->snap_time(offset + editor->get_moving_selection_offset(), true); + offset = offset + editor->get_moving_selection_offset(); } offset = offset * scale + limit; if (i < animation->track_get_key_count(track) - 1) { float offset_n = animation->track_get_key_time(track, i + 1) - timeline->get_value(); if (editor->is_key_selected(track, i + 1) && editor->is_moving_selection()) { - offset_n = editor->snap_time(offset_n + editor->get_moving_selection_offset()); + offset_n = offset_n + editor->get_moving_selection_offset(); } offset_n = offset_n * scale + limit; @@ -2688,7 +2754,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (p_event->is_pressed()) { if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) { if (!read_only) { - emit_signal(SNAME("duplicate_request"), -1.0); + emit_signal(SNAME("duplicate_request"), -1.0, false); } accept_event(); } @@ -2707,7 +2773,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) { if (!read_only) { - emit_signal(SNAME("paste_request"), -1.0); + emit_signal(SNAME("paste_request"), -1.0, false); } accept_event(); } @@ -2908,8 +2974,10 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && moving_selection_attempt) { if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { moving_selection_attempt = false; - if (moving_selection) { - emit_signal(SNAME("move_selection_commit")); + if (moving_selection && moving_selection_effective) { + if (abs(editor->get_moving_selection_offset()) > CMP_EPSILON) { + emit_signal(SNAME("move_selection_commit")); + } } else if (select_single_attempt != -1) { emit_signal(SNAME("select_key"), select_single_attempt, true); } @@ -2982,8 +3050,18 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { emit_signal(SNAME("move_selection_begin")); } - float new_ofs = (mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale(); - emit_signal(SNAME("move_selection"), new_ofs - moving_selection_from_ofs); + float moving_begin_time = ((moving_selection_mouse_begin_x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + float new_time = ((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); + float delta = new_time - moving_begin_time; + float snapped_time = editor->snap_time(moving_selection_pivot + delta); + + float offset = 0.0; + if (abs(editor->get_moving_selection_offset()) > CMP_EPSILON || (snapped_time > moving_selection_pivot && delta > CMP_EPSILON) || (snapped_time < moving_selection_pivot && delta < -CMP_EPSILON)) { + offset = snapped_time - moving_selection_pivot; + moving_selection_effective = true; + } + + emit_signal(SNAME("move_selection"), offset); } } @@ -3026,12 +3104,16 @@ bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggre if (editor->is_key_selected(track, key_idx)) { if (p_deselectable) { emit_signal(SNAME("deselect_key"), key_idx); + moving_selection_pivot = 0.0f; + moving_selection_mouse_begin_x = 0.0f; } } else { emit_signal(SNAME("select_key"), key_idx, false); moving_selection_attempt = true; + moving_selection_effective = false; select_single_attempt = -1; - moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale(); + moving_selection_pivot = animation->track_get_key_time(track, key_idx); + moving_selection_mouse_begin_x = p_pos.x; } } else { if (!editor->is_key_selected(track, key_idx)) { @@ -3042,12 +3124,15 @@ bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggre } moving_selection_attempt = true; - moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale(); + moving_selection_effective = false; + moving_selection_pivot = animation->track_get_key_time(track, key_idx); + moving_selection_mouse_begin_x = p_pos.x; } if (read_only) { moving_selection_attempt = false; - moving_selection_from_ofs = 0.0f; + moving_selection_pivot = 0.0f; + moving_selection_mouse_begin_x = 0.0f; } return true; } @@ -3182,7 +3267,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) { emit_signal(SNAME("insert_key"), insert_at_pos); } break; case MENU_KEY_DUPLICATE: { - emit_signal(SNAME("duplicate_request"), insert_at_pos); + emit_signal(SNAME("duplicate_request"), insert_at_pos, true); } break; case MENU_KEY_CUT: { emit_signal(SNAME("cut_request")); @@ -3191,7 +3276,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) { emit_signal(SNAME("copy_request")); } break; case MENU_KEY_PASTE: { - emit_signal(SNAME("paste_request"), insert_at_pos); + emit_signal(SNAME("paste_request"), insert_at_pos, true); } break; case MENU_KEY_ADD_RESET: { emit_signal(SNAME("create_reset_request")); @@ -3265,11 +3350,11 @@ void AnimationTrackEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("move_selection_commit")); ADD_SIGNAL(MethodInfo("move_selection_cancel")); - ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset"))); + ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid"))); ADD_SIGNAL(MethodInfo("create_reset_request")); ADD_SIGNAL(MethodInfo("copy_request")); ADD_SIGNAL(MethodInfo("cut_request")); - ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset"))); + ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset"), PropertyInfo(Variant::BOOL, "is_offset_valid"))); ADD_SIGNAL(MethodInfo("delete_request")); } @@ -3446,6 +3531,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re step->set_read_only(false); snap->set_disabled(false); snap_mode->set_disabled(false); + auto_fit->set_disabled(false); + auto_fit_bezier->set_disabled(false); imported_anim_warning->hide(); for (int i = 0; i < animation->get_track_count(); i++) { @@ -3466,6 +3553,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re snap->set_disabled(true); snap_mode->set_disabled(true); bezier_edit_icon->set_disabled(true); + auto_fit->set_disabled(true); + auto_fit_bezier->set_disabled(true); } } @@ -4763,6 +4852,8 @@ void AnimationTrackEditor::_notification(int p_what) { inactive_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree"))); edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload"))); + auto_fit->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFit"))); + auto_fit_bezier->set_icon(get_editor_theme_icon(SNAME("AnimationAutoFitBezier"))); } break; case NOTIFICATION_READY: { @@ -4915,12 +5006,17 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { } void AnimationTrackEditor::_add_track(int p_type) { - if (!root) { + AnimationPlayer *ap = AnimationPlayerEditor::get_singleton()->get_player(); + if (!ap) { + ERR_FAIL_EDMSG("No AnimationPlayer is currently being edited."); + } + Node *root_node = ap->get_node_or_null(ap->get_root_node()); + if (!root_node) { EditorNode::get_singleton()->show_warning(TTR("Not possible to add a new track without a root")); return; } adding_track_type = p_type; - pick_track->popup_scenetree_dialog(); + pick_track->popup_scenetree_dialog(nullptr, root_node); pick_track->get_filter_line_edit()->clear(); pick_track->get_filter_line_edit()->grab_focus(); } @@ -5086,6 +5182,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Position Key")); undo_redo->add_do_method(animation.ptr(), "position_track_insert_key", p_track, p_ofs, pos); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5106,6 +5203,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Rotation Key")); undo_redo->add_do_method(animation.ptr(), "rotation_track_insert_key", p_track, p_ofs, rot); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5124,6 +5222,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Scale Key")); undo_redo->add_do_method(animation.ptr(), "scale_track_insert_key", p_track, p_ofs, base->get_scale()); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5169,6 +5268,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); @@ -5181,6 +5281,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); } break; @@ -5189,6 +5290,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { undo_redo->create_action(TTR("Add Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs); undo_redo->commit_action(); } break; @@ -5229,6 +5331,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Method Track Key")); undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d); + undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", insert_key_from_track_call_track, insert_key_from_track_call_ofs); undo_redo->commit_action(); @@ -5427,7 +5530,7 @@ void AnimationTrackEditor::_move_selection_commit() { } // 2 - Remove overlapped keys. for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - float newtime = snap_time(E->get().pos + motion); + float newtime = E->get().pos + motion; int idx = animation->track_find_key(E->key().track, newtime, Animation::FIND_MODE_APPROX); if (idx == -1) { continue; @@ -5452,13 +5555,13 @@ void AnimationTrackEditor::_move_selection_commit() { // 3 - Move the keys (Reinsert them). for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = snap_time(E->get().pos + motion); + float newpos = E->get().pos + motion; undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->key().track, newpos, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); } // 4 - (Undo) Remove inserted keys. for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = snap_time(E->get().pos + motion); + float newpos = E->get().pos + motion; undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos); } @@ -5478,7 +5581,7 @@ void AnimationTrackEditor::_move_selection_commit() { // 7 - Reselect. for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) { float oldpos = E->get().pos; - float newpos = snap_time(oldpos + motion); + float newpos = oldpos + motion; undo_redo->add_do_method(this, "_select_at_anim", animation, E->key().track, newpos); undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, oldpos); @@ -5617,6 +5720,8 @@ void AnimationTrackEditor::_cancel_bezier_edit() { bezier_edit->hide(); scroll->show(); bezier_edit_icon->set_pressed(false); + auto_fit->show(); + auto_fit_bezier->hide(); } void AnimationTrackEditor::_bezier_edit(int p_for_track) { @@ -5625,6 +5730,8 @@ void AnimationTrackEditor::_bezier_edit(int p_for_track) { bezier_edit->set_animation_and_track(animation, p_for_track, read_only); scroll->hide(); bezier_edit->show(); + auto_fit->hide(); + auto_fit_bezier->show(); // Search everything within the track and curve - edit it. } @@ -5635,7 +5742,7 @@ void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim, p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode); } -void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) { +void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track) { if (selection.size() && animation.is_valid()) { int top_track = 0x7FFFFFFF; float top_time = 1e10; @@ -5688,7 +5795,13 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) { const SelectedKey &sk = E->key(); float t = animation->track_get_key_time(sk.track, sk.key); - float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position(); + float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); + + if (p_ofs_valid) { + if (snap->is_pressed() && step->get_value() != 0) { + insert_pos = snap_time(insert_pos); + } + } float dst_time = t + (insert_pos - top_time); int dst_track = sk.track + (start_track - top_track); @@ -5795,7 +5908,7 @@ void AnimationTrackEditor::_set_key_clipboard(int p_top_track, float p_top_time, } } -void AnimationTrackEditor::_anim_paste_keys(float p_ofs, int p_track) { +void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track) { if (is_key_clipboard_active() && animation.is_valid()) { int start_track = p_track; if (p_track == -1) { // Pasting from shortcut or Edit menu. @@ -5830,7 +5943,13 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, int p_track) { for (int i = 0; i < key_clipboard.keys.size(); i++) { const KeyClipboard::Key key = key_clipboard.keys[i]; - float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position(); + float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); + + if (p_ofs_valid) { + if (snap->is_pressed() && step->get_value() != 0) { + insert_pos = snap_time(insert_pos); + } + } float dst_time = key.time + insert_pos; int dst_track = key.track + start_track; @@ -6427,10 +6546,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { case EDIT_DUPLICATE_SELECTED_KEYS: { if (bezier_edit->is_visible()) { - bezier_edit->duplicate_selected_keys(-1.0); + bezier_edit->duplicate_selected_keys(-1.0, false); break; } - _anim_duplicate_keys(-1.0, -1.0); + _anim_duplicate_keys(-1.0, false, -1.0); } break; case EDIT_CUT_KEYS: { if (bezier_edit->is_visible()) { @@ -6447,7 +6566,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { _anim_copy_keys(false); } break; case EDIT_PASTE_KEYS: { - _anim_paste_keys(-1.0, -1.0); + _anim_paste_keys(-1.0, false, -1.0); } break; case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: { if (moving_selection || selection.is_empty()) { @@ -6865,6 +6984,18 @@ bool AnimationTrackEditor::is_grouping_tracks() { return !view_group->is_pressed(); } +void AnimationTrackEditor::_auto_fit() { + timeline->auto_fit(); +} + +void AnimationTrackEditor::_auto_fit_bezier() { + timeline->auto_fit(); + + if (bezier_edit->is_visible()) { + bezier_edit->auto_fit_vertically(); + } +} + void AnimationTrackEditor::_selection_changed() { if (selected_filter->is_pressed()) { _update_tracks(); // Needs updatin. @@ -7179,6 +7310,19 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_child(zoom); timeline->set_zoom(zoom); + auto_fit = memnew(Button); + auto_fit->set_flat(true); + auto_fit->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit)); + auto_fit->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + bottom_hb->add_child(auto_fit); + + auto_fit_bezier = memnew(Button); + auto_fit_bezier->set_flat(true); + auto_fit_bezier->set_visible(false); + auto_fit_bezier->connect("pressed", callable_mp(this, &AnimationTrackEditor::_auto_fit_bezier)); + auto_fit_bezier->set_shortcut(ED_SHORTCUT("animation_editor/auto_fit", TTR("Fit to panel"), KeyModifierMask::ALT | Key::F)); + bottom_hb->add_child(auto_fit_bezier); + edit = memnew(MenuButton); edit->set_shortcut_context(this); edit->set_text(TTR("Edit")); @@ -7430,6 +7574,7 @@ AnimationTrackEditor::AnimationTrackEditor() { track_copy_vbox->add_child(select_all_button); track_copy_select = memnew(Tree); + track_copy_select->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); track_copy_select->set_h_size_flags(SIZE_EXPAND_FILL); track_copy_select->set_v_size_flags(SIZE_EXPAND_FILL); track_copy_select->set_hide_root(true); |