Browse Source

Merge pull request #88445 from CookieBadger/animation-paste-snap-fix

Fix various bugs in Animation key right click actions
Rémi Verschelde 1 year ago
parent
commit
b0505b580d

+ 28 - 12
editor/animation_bezier_editor.cpp

@@ -34,6 +34,7 @@
 #include "editor/editor_settings.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_string_names.h"
 #include "editor/editor_string_names.h"
 #include "editor/editor_undo_redo_manager.h"
 #include "editor/editor_undo_redo_manager.h"
+#include "editor/gui/editor_spin_slider.h"
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_scale.h"
 #include "scene/gui/view_panner.h"
 #include "scene/gui/view_panner.h"
 #include "scene/resources/text_line.h"
 #include "scene/resources/text_line.h"
@@ -900,7 +901,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	if (p_event->is_pressed()) {
 	if (p_event->is_pressed()) {
 		if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
 		if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
 			if (!read_only) {
 			if (!read_only) {
-				duplicate_selected_keys(-1.0);
+				duplicate_selected_keys(-1.0, false);
 			}
 			}
 			accept_event();
 			accept_event();
 		}
 		}
@@ -918,7 +919,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 		}
 		}
 		if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
 		if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
 			if (!read_only) {
 			if (!read_only) {
-				paste_keys(-1.0);
+				paste_keys(-1.0, false);
 			}
 			}
 			accept_event();
 			accept_event();
 		}
 		}
@@ -1642,25 +1643,28 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
 
 
 	real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
 	real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
 
 
-	while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
-		time += 0.001;
-	}
-
 	switch (p_index) {
 	switch (p_index) {
 		case MENU_KEY_INSERT: {
 		case MENU_KEY_INSERT: {
 			if (animation->get_track_count() > 0) {
 			if (animation->get_track_count() > 0) {
+				if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+					time = editor->snap_time(time);
+				}
+				while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
+					time += 0.001;
+				}
 				float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
 				float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
 				Array new_point = make_default_bezier_key(h);
 				Array new_point = make_default_bezier_key(h);
 				EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 				EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 				undo_redo->create_action(TTR("Add Bezier Point"));
 				undo_redo->create_action(TTR("Add Bezier Point"));
 				undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
 				undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
+				undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
 				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 				undo_redo->commit_action();
 				undo_redo->commit_action();
 				queue_redraw();
 				queue_redraw();
 			}
 			}
 		} break;
 		} break;
 		case MENU_KEY_DUPLICATE: {
 		case MENU_KEY_DUPLICATE: {
-			duplicate_selected_keys(time);
+			duplicate_selected_keys(time, true);
 		} break;
 		} break;
 		case MENU_KEY_DELETE: {
 		case MENU_KEY_DELETE: {
 			delete_selection();
 			delete_selection();
@@ -1672,7 +1676,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
 			copy_selected_keys(false);
 			copy_selected_keys(false);
 		} break;
 		} break;
 		case MENU_KEY_PASTE: {
 		case MENU_KEY_PASTE: {
-			paste_keys(time);
+			paste_keys(time, true);
 		} break;
 		} break;
 		case MENU_KEY_SET_HANDLE_FREE: {
 		case MENU_KEY_SET_HANDLE_FREE: {
 			_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);
 			_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);
@@ -1695,7 +1699,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
 	}
 	}
 }
 }
 
 
-void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) {
+void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid) {
 	if (selection.size() == 0) {
 	if (selection.size() == 0) {
 		return;
 		return;
 	}
 	}
@@ -1715,7 +1719,14 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) {
 
 
 	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
 	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
 		real_t t = animation->track_get_key_time(E->get().first, E->get().second);
 		real_t t = animation->track_get_key_time(E->get().first, E->get().second);
-		real_t insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
+		real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position();
+
+		if (p_ofs_valid) {
+			if (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+				insert_pos = editor->snap_time(insert_pos);
+			}
+		}
+
 		real_t dst_time = t + (insert_pos - top_time);
 		real_t dst_time = t + (insert_pos - top_time);
 		int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);
 		int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);
 
 
@@ -1793,7 +1804,7 @@ void AnimationBezierTrackEdit::copy_selected_keys(bool p_cut) {
 	}
 	}
 }
 }
 
 
-void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) {
+void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) {
 	if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {
 	if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {
 		EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 		EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 		undo_redo->create_action(TTR("Animation Paste Keys"));
 		undo_redo->create_action(TTR("Animation Paste Keys"));
@@ -1824,7 +1835,12 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) {
 		for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {
 		for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {
 			const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];
 			const AnimationTrackEditor::KeyClipboard::Key key = editor->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 (editor->snap->is_pressed() && editor->step->get_value() != 0) {
+					insert_pos = editor->snap_time(insert_pos);
+				}
+			}
 			float dst_time = key.time + insert_pos;
 			float dst_time = key.time + insert_pos;
 
 
 			int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);
 			int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);

+ 2 - 2
editor/animation_bezier_editor.h

@@ -216,9 +216,9 @@ public:
 	void set_play_position(real_t p_pos);
 	void set_play_position(real_t p_pos);
 	void update_play_position();
 	void update_play_position();
 
 
-	void duplicate_selected_keys(real_t p_ofs);
+	void duplicate_selected_keys(real_t p_ofs, bool p_ofs_valid);
 	void copy_selected_keys(bool p_cut);
 	void copy_selected_keys(bool p_cut);
-	void paste_keys(real_t p_ofs);
+	void paste_keys(real_t p_ofs, bool p_ofs_valid);
 	void delete_selection();
 	void delete_selection();
 
 
 	void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
 	void _bezier_track_insert_key(int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);

+ 32 - 13
editor/animation_track_editor.cpp

@@ -2754,7 +2754,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	if (p_event->is_pressed()) {
 	if (p_event->is_pressed()) {
 		if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
 		if (ED_IS_SHORTCUT("animation_editor/duplicate_selected_keys", p_event)) {
 			if (!read_only) {
 			if (!read_only) {
-				emit_signal(SNAME("duplicate_request"), -1.0);
+				emit_signal(SNAME("duplicate_request"), -1.0, false);
 			}
 			}
 			accept_event();
 			accept_event();
 		}
 		}
@@ -2773,7 +2773,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 		if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
 		if (ED_IS_SHORTCUT("animation_editor/paste_keys", p_event)) {
 			if (!read_only) {
 			if (!read_only) {
-				emit_signal(SNAME("paste_request"), -1.0);
+				emit_signal(SNAME("paste_request"), -1.0, false);
 			}
 			}
 			accept_event();
 			accept_event();
 		}
 		}
@@ -3248,7 +3248,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
 			emit_signal(SNAME("insert_key"), insert_at_pos);
 			emit_signal(SNAME("insert_key"), insert_at_pos);
 		} break;
 		} break;
 		case MENU_KEY_DUPLICATE: {
 		case MENU_KEY_DUPLICATE: {
-			emit_signal(SNAME("duplicate_request"), insert_at_pos);
+			emit_signal(SNAME("duplicate_request"), insert_at_pos, true);
 		} break;
 		} break;
 		case MENU_KEY_CUT: {
 		case MENU_KEY_CUT: {
 			emit_signal(SNAME("cut_request"));
 			emit_signal(SNAME("cut_request"));
@@ -3257,7 +3257,7 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
 			emit_signal(SNAME("copy_request"));
 			emit_signal(SNAME("copy_request"));
 		} break;
 		} break;
 		case MENU_KEY_PASTE: {
 		case MENU_KEY_PASTE: {
-			emit_signal(SNAME("paste_request"), insert_at_pos);
+			emit_signal(SNAME("paste_request"), insert_at_pos, true);
 		} break;
 		} break;
 		case MENU_KEY_ADD_RESET: {
 		case MENU_KEY_ADD_RESET: {
 			emit_signal(SNAME("create_reset_request"));
 			emit_signal(SNAME("create_reset_request"));
@@ -3331,11 +3331,11 @@ void AnimationTrackEdit::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("move_selection_commit"));
 	ADD_SIGNAL(MethodInfo("move_selection_commit"));
 	ADD_SIGNAL(MethodInfo("move_selection_cancel"));
 	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("create_reset_request"));
 	ADD_SIGNAL(MethodInfo("copy_request"));
 	ADD_SIGNAL(MethodInfo("copy_request"));
 	ADD_SIGNAL(MethodInfo("cut_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"));
 	ADD_SIGNAL(MethodInfo("delete_request"));
 }
 }
 
 
@@ -5158,6 +5158,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Position Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
@@ -5178,6 +5179,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Rotation Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
@@ -5196,6 +5198,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Scale Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
@@ -5241,6 +5244,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Track Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
@@ -5253,6 +5257,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Track Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 		} break;
 		} break;
@@ -5261,6 +5266,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {
 
 
 			undo_redo->create_action(TTR("Add Track Key"));
 			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_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->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 		} break;
 		} break;
@@ -5301,6 +5307,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) {
 			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 			undo_redo->create_action(TTR("Add Method Track Key"));
 			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_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->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();
 			undo_redo->commit_action();
 
 
@@ -5711,7 +5718,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);
 	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()) {
 	if (selection.size() && animation.is_valid()) {
 		int top_track = 0x7FFFFFFF;
 		int top_track = 0x7FFFFFFF;
 		float top_time = 1e10;
 		float top_time = 1e10;
@@ -5764,7 +5771,13 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) {
 			const SelectedKey &sk = E->key();
 			const SelectedKey &sk = E->key();
 
 
 			float t = animation->track_get_key_time(sk.track, sk.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);
 			float dst_time = t + (insert_pos - top_time);
 			int dst_track = sk.track + (start_track - top_track);
 			int dst_track = sk.track + (start_track - top_track);
@@ -5871,7 +5884,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()) {
 	if (is_key_clipboard_active() && animation.is_valid()) {
 		int start_track = p_track;
 		int start_track = p_track;
 		if (p_track == -1) { // Pasting from shortcut or Edit menu.
 		if (p_track == -1) { // Pasting from shortcut or Edit menu.
@@ -5906,7 +5919,13 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, int p_track) {
 		for (int i = 0; i < key_clipboard.keys.size(); i++) {
 		for (int i = 0; i < key_clipboard.keys.size(); i++) {
 			const KeyClipboard::Key key = key_clipboard.keys[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;
 			float dst_time = key.time + insert_pos;
 			int dst_track = key.track + start_track;
 			int dst_track = key.track + start_track;
@@ -6503,10 +6522,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 
 
 		case EDIT_DUPLICATE_SELECTED_KEYS: {
 		case EDIT_DUPLICATE_SELECTED_KEYS: {
 			if (bezier_edit->is_visible()) {
 			if (bezier_edit->is_visible()) {
-				bezier_edit->duplicate_selected_keys(-1.0);
+				bezier_edit->duplicate_selected_keys(-1.0, false);
 				break;
 				break;
 			}
 			}
-			_anim_duplicate_keys(-1.0, -1.0);
+			_anim_duplicate_keys(-1.0, false, -1.0);
 		} break;
 		} break;
 		case EDIT_CUT_KEYS: {
 		case EDIT_CUT_KEYS: {
 			if (bezier_edit->is_visible()) {
 			if (bezier_edit->is_visible()) {
@@ -6523,7 +6542,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 			_anim_copy_keys(false);
 			_anim_copy_keys(false);
 		} break;
 		} break;
 		case EDIT_PASTE_KEYS: {
 		case EDIT_PASTE_KEYS: {
-			_anim_paste_keys(-1.0, -1.0);
+			_anim_paste_keys(-1.0, false, -1.0);
 		} break;
 		} break;
 		case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: {
 		case EDIT_MOVE_FIRST_SELECTED_KEY_TO_CURSOR: {
 			if (moving_selection || selection.is_empty()) {
 			if (moving_selection || selection.is_empty()) {

+ 2 - 2
editor/animation_track_editor.h

@@ -585,13 +585,13 @@ class AnimationTrackEditor : public VBoxContainer {
 
 
 	void _cleanup_animation(Ref<Animation> p_animation);
 	void _cleanup_animation(Ref<Animation> p_animation);
 
 
-	void _anim_duplicate_keys(float p_ofs, int p_track);
+	void _anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track);
 
 
 	void _anim_copy_keys(bool p_cut);
 	void _anim_copy_keys(bool p_cut);
 
 
 	bool _is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type);
 	bool _is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type);
 
 
-	void _anim_paste_keys(float p_ofs, int p_track);
+	void _anim_paste_keys(float p_ofs, bool p_ofs_valid, int p_track);
 
 
 	void _view_group_toggle();
 	void _view_group_toggle();
 	Button *view_group = nullptr;
 	Button *view_group = nullptr;