mirror of
https://github.com/godotengine/godot.git
synced 2024-11-22 04:06:14 +00:00
Merge pull request #87250 from CookieBadger/animation-copy-paste-keyframe
Implement consistent functionality for select, copy, paste, and duplicate in AnimationPlayer
This commit is contained in:
commit
75255bd15c
@ -834,16 +834,22 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
|
||||
if (p_event->is_pressed()) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/duplicate_selected_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
duplicate_selection();
|
||||
duplicate_selected_keys(-1.0);
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
if (ED_GET_SHORTCUT("animation_editor/copy_selected_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
copy_selected_keys();
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
|
||||
if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/paste_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
delete_selection();
|
||||
paste_keys(-1.0);
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
@ -946,11 +952,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
if (!read_only) {
|
||||
Vector2 popup_pos = get_screen_position() + mb->get_position();
|
||||
|
||||
bool selected = _try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), false);
|
||||
|
||||
menu->clear();
|
||||
menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
|
||||
if (selection.size()) {
|
||||
if (selected || selection.size()) {
|
||||
menu->add_separator();
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Selected Key(s)"), MENU_KEY_COPY);
|
||||
}
|
||||
|
||||
if (editor->is_key_clipboard_active()) {
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Key(s)"), MENU_KEY_PASTE);
|
||||
}
|
||||
|
||||
if (selected || selection.size()) {
|
||||
menu->add_separator();
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
|
||||
menu->add_separator();
|
||||
@ -1092,50 +1108,16 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < edit_points.size(); i++) {
|
||||
//first check point
|
||||
//command makes it ignore the main point, so control point editors can be force-edited
|
||||
//path 2D editing in the 3D and 2D editors works the same way
|
||||
if (!mb->is_command_or_control_pressed()) {
|
||||
if (edit_points[i].point_rect.has_point(mb->get_position())) {
|
||||
IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);
|
||||
if (mb->is_shift_pressed()) {
|
||||
//add to selection
|
||||
if (selection.has(pair)) {
|
||||
selection.erase(pair);
|
||||
} else {
|
||||
selection.insert(pair);
|
||||
}
|
||||
queue_redraw();
|
||||
select_single_attempt = IntPair(-1, -1);
|
||||
} else if (selection.has(pair)) {
|
||||
moving_selection_attempt = true;
|
||||
moving_selection = false;
|
||||
moving_selection_from_key = pair.second;
|
||||
moving_selection_from_track = pair.first;
|
||||
moving_handle_track = pair.first;
|
||||
moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);
|
||||
moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);
|
||||
moving_selection_offset = Vector2();
|
||||
select_single_attempt = pair;
|
||||
queue_redraw();
|
||||
} else {
|
||||
moving_selection_attempt = true;
|
||||
moving_selection = true;
|
||||
moving_selection_from_key = pair.second;
|
||||
moving_selection_from_track = pair.first;
|
||||
moving_selection_offset = Vector2();
|
||||
moving_handle_track = pair.first;
|
||||
moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);
|
||||
moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);
|
||||
selection.clear();
|
||||
selection.insert(pair);
|
||||
set_animation_and_track(animation, pair.first, read_only);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// First, check keyframe.
|
||||
// Command/Control makes it ignore the keyframe, so control point editors can be force-edited.
|
||||
if (!mb->is_command_or_control_pressed()) {
|
||||
if (_try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Second, check handles.
|
||||
for (int i = 0; i < edit_points.size(); i++) {
|
||||
if (!read_only) {
|
||||
if (edit_points[i].in_rect.has_point(mb->get_position())) {
|
||||
moving_handle = -1;
|
||||
@ -1494,6 +1476,52 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationBezierTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {
|
||||
for (int i = 0; i < edit_points.size(); i++) {
|
||||
// Path 2D editing in the 3D and 2D editors works the same way. (?)
|
||||
if (edit_points[i].point_rect.has_point(p_pos)) {
|
||||
IntPair pair = IntPair(edit_points[i].track, edit_points[i].key);
|
||||
if (p_aggregate) {
|
||||
// Add to selection.
|
||||
if (selection.has(pair)) {
|
||||
if (p_deselectable) {
|
||||
selection.erase(pair);
|
||||
}
|
||||
} else {
|
||||
selection.insert(pair);
|
||||
}
|
||||
queue_redraw();
|
||||
select_single_attempt = IntPair(-1, -1);
|
||||
} else {
|
||||
if (p_deselectable) {
|
||||
moving_selection_attempt = true;
|
||||
moving_selection_from_key = pair.second;
|
||||
moving_selection_from_track = pair.first;
|
||||
moving_selection_offset = Vector2();
|
||||
moving_handle_track = pair.first;
|
||||
moving_handle_left = animation->bezier_track_get_key_in_handle(pair.first, pair.second);
|
||||
moving_handle_right = animation->bezier_track_get_key_out_handle(pair.first, pair.second);
|
||||
|
||||
if (selection.has(pair)) {
|
||||
select_single_attempt = pair;
|
||||
moving_selection = false;
|
||||
} else {
|
||||
moving_selection = true;
|
||||
}
|
||||
}
|
||||
|
||||
set_animation_and_track(animation, pair.first, read_only);
|
||||
if (p_deselectable || !selection.has(pair)) {
|
||||
selection.clear();
|
||||
selection.insert(pair);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
if (mm.is_valid()) {
|
||||
@ -1526,29 +1554,37 @@ void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_ori
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
Array AnimationBezierTrackEdit::make_default_bezier_key(float p_value) {
|
||||
Array new_point;
|
||||
new_point.resize(5);
|
||||
|
||||
new_point[0] = p_value;
|
||||
new_point[1] = -0.25;
|
||||
new_point[2] = 0;
|
||||
new_point[3] = 0.25;
|
||||
new_point[4] = 0;
|
||||
|
||||
return new_point;
|
||||
}
|
||||
|
||||
float AnimationBezierTrackEdit::get_bezier_key_value(Array p_bezier_key_array) {
|
||||
return p_bezier_key_array[0];
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::_menu_selected(int p_index) {
|
||||
int limit = timeline->get_name_limit();
|
||||
|
||||
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) {
|
||||
case MENU_KEY_INSERT: {
|
||||
if (animation->get_track_count() > 0) {
|
||||
Array new_point;
|
||||
new_point.resize(5);
|
||||
|
||||
float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
|
||||
|
||||
new_point[0] = h;
|
||||
new_point[1] = -0.25;
|
||||
new_point[2] = 0;
|
||||
new_point[3] = 0.25;
|
||||
new_point[4] = 0;
|
||||
|
||||
int limit = timeline->get_name_limit();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Array new_point = make_default_bezier_key(h);
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Add Bezier Point"));
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
|
||||
@ -1558,11 +1594,17 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
|
||||
}
|
||||
} break;
|
||||
case MENU_KEY_DUPLICATE: {
|
||||
duplicate_selection();
|
||||
duplicate_selected_keys(time);
|
||||
} break;
|
||||
case MENU_KEY_DELETE: {
|
||||
delete_selection();
|
||||
} break;
|
||||
case MENU_KEY_COPY: {
|
||||
copy_selected_keys();
|
||||
} break;
|
||||
case MENU_KEY_PASTE: {
|
||||
paste_keys(time);
|
||||
} break;
|
||||
case MENU_KEY_SET_HANDLE_FREE: {
|
||||
_change_selected_keys_handle_mode(Animation::HANDLE_MODE_FREE);
|
||||
} break;
|
||||
@ -1584,7 +1626,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::duplicate_selection() {
|
||||
void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs) {
|
||||
if (selection.size() == 0) {
|
||||
return;
|
||||
}
|
||||
@ -1604,7 +1646,8 @@ void AnimationBezierTrackEdit::duplicate_selection() {
|
||||
|
||||
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 dst_time = t + (timeline->get_play_position() - top_time);
|
||||
real_t insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
|
||||
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);
|
||||
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second));
|
||||
@ -1620,25 +1663,117 @@ void AnimationBezierTrackEdit::duplicate_selection() {
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->commit_action();
|
||||
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
|
||||
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
|
||||
|
||||
//reselect duplicated
|
||||
|
||||
selection.clear();
|
||||
// Reselect duplicated.
|
||||
for (const Pair<int, real_t> &E : new_selection_values) {
|
||||
int track = E.first;
|
||||
real_t time = E.second;
|
||||
|
||||
int existing_idx = animation->track_find_key(track, time, Animation::FIND_MODE_APPROX);
|
||||
|
||||
if (existing_idx == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
selection.insert(IntPair(track, existing_idx));
|
||||
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
|
||||
}
|
||||
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
|
||||
real_t time = animation->track_get_key_time(E->get().first, E->get().second);
|
||||
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, time);
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
undo_redo->add_do_method(this, "queue_redraw");
|
||||
undo_redo->add_undo_method(this, "queue_redraw");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::copy_selected_keys() {
|
||||
if (selection.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float top_time = 1e10;
|
||||
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
|
||||
float t = animation->track_get_key_time(E->get().first, E->get().second);
|
||||
if (t < top_time) {
|
||||
top_time = t;
|
||||
}
|
||||
}
|
||||
|
||||
RBMap<AnimationTrackEditor::SelectedKey, AnimationTrackEditor::KeyInfo> keys;
|
||||
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
|
||||
AnimationTrackEditor::SelectedKey sk;
|
||||
AnimationTrackEditor::KeyInfo ki;
|
||||
sk.track = E->get().first;
|
||||
sk.key = E->get().second;
|
||||
ki.pos = animation->track_get_key_time(E->get().first, E->get().second);
|
||||
keys.insert(sk, ki);
|
||||
}
|
||||
editor->_set_key_clipboard(selected_track, top_time, keys);
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::paste_keys(real_t p_ofs) {
|
||||
if (editor->is_key_clipboard_active() && animation.is_valid() && (selected_track >= 0 && selected_track < animation->get_track_count())) {
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Animation Paste Keys"));
|
||||
|
||||
bool same_track = true;
|
||||
bool all_compatible = true;
|
||||
|
||||
for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {
|
||||
const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];
|
||||
|
||||
if (key.track != 0) {
|
||||
same_track = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!editor->_is_track_compatible(selected_track, key.value.get_type(), key.track_type)) {
|
||||
all_compatible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!all_compatible, "Paste failed: Not all animation keys were compatible with their target tracks");
|
||||
if (!same_track) {
|
||||
WARN_PRINT("Pasted animation keys from multiple tracks into single Bezier track");
|
||||
}
|
||||
|
||||
List<Pair<int, float>> new_selection_values;
|
||||
for (int i = 0; i < editor->key_clipboard.keys.size(); i++) {
|
||||
const AnimationTrackEditor::KeyClipboard::Key key = editor->key_clipboard.keys[i];
|
||||
|
||||
float insert_pos = p_ofs >= 0 ? p_ofs : timeline->get_play_position();
|
||||
float dst_time = key.time + insert_pos;
|
||||
|
||||
int existing_idx = animation->track_find_key(selected_track, dst_time, Animation::FIND_MODE_APPROX);
|
||||
|
||||
Variant value = key.value;
|
||||
if (key.track_type != Animation::TYPE_BEZIER) {
|
||||
value = make_default_bezier_key(key.value);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, dst_time, value, key.transition);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, dst_time);
|
||||
|
||||
Pair<int, float> p;
|
||||
p.first = selected_track;
|
||||
p.second = dst_time;
|
||||
new_selection_values.push_back(p);
|
||||
|
||||
if (existing_idx != -1) {
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", selected_track, dst_time, animation->track_get_key_value(selected_track, existing_idx), animation->track_get_key_transition(selected_track, existing_idx));
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
|
||||
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
|
||||
|
||||
// Reselect pasted.
|
||||
for (const Pair<int, float> &E : new_selection_values) {
|
||||
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
|
||||
}
|
||||
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
|
||||
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, animation->track_get_key_time(E->get().first, E->get().second));
|
||||
}
|
||||
|
||||
undo_redo->commit_action();
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationBezierTrackEdit::delete_selection() {
|
||||
|
@ -42,6 +42,8 @@ class AnimationBezierTrackEdit : public Control {
|
||||
enum {
|
||||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_COPY,
|
||||
MENU_KEY_PASTE,
|
||||
MENU_KEY_DELETE,
|
||||
MENU_KEY_SET_HANDLE_FREE,
|
||||
MENU_KEY_SET_HANDLE_LINEAR,
|
||||
@ -139,6 +141,7 @@ class AnimationBezierTrackEdit : public Control {
|
||||
void _clear_selection();
|
||||
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
|
||||
void _select_at_anim(const Ref<Animation> &p_anim, int p_track, real_t p_pos);
|
||||
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
|
||||
void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false);
|
||||
|
||||
Vector2 menu_insert_key;
|
||||
@ -190,6 +193,9 @@ protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static Array make_default_bezier_key(float p_value);
|
||||
static float get_bezier_key_value(Array p_bezier_key_array);
|
||||
|
||||
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
Ref<Animation> get_animation() const;
|
||||
@ -205,7 +211,9 @@ public:
|
||||
void set_play_position(real_t p_pos);
|
||||
void update_play_position();
|
||||
|
||||
void duplicate_selection();
|
||||
void duplicate_selected_keys(real_t p_ofs);
|
||||
void copy_selected_keys();
|
||||
void paste_keys(real_t p_ofs);
|
||||
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);
|
||||
|
@ -2686,16 +2686,22 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
if (p_event->is_pressed()) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/duplicate_selected_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
emit_signal(SNAME("duplicate_request"));
|
||||
emit_signal(SNAME("duplicate_request"), -1.0);
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
if (ED_GET_SHORTCUT("animation_editor/copy_selected_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
emit_signal(SNAME("copy_request"));
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
|
||||
if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->matches_event(p_event)) {
|
||||
if (ED_GET_SHORTCUT("animation_editor/paste_keys")->matches_event(p_event)) {
|
||||
if (!read_only) {
|
||||
emit_signal(SNAME("duplicate_transpose_request"));
|
||||
emit_signal(SNAME("paste_request"), -1.0);
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
@ -2822,71 +2828,8 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check keyframes.
|
||||
|
||||
if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
|
||||
|
||||
float scale = timeline->get_zoom_scale();
|
||||
int limit = timeline->get_name_limit();
|
||||
int limit_end = get_size().width - timeline->get_buttons_width();
|
||||
// Left Border including space occupied by keyframes on t=0.
|
||||
int limit_start_hitbox = limit - type_icon->get_width();
|
||||
|
||||
if (pos.x >= limit_start_hitbox && pos.x <= limit_end) {
|
||||
int key_idx = -1;
|
||||
float key_distance = 1e20;
|
||||
|
||||
// Select should happen in the opposite order of drawing for more accurate overlap select.
|
||||
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
|
||||
Rect2 rect = get_key_rect(i, scale);
|
||||
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
|
||||
offset = offset * scale + limit;
|
||||
rect.position.x += offset;
|
||||
|
||||
if (rect.has_point(pos)) {
|
||||
if (is_key_selectable_by_distance()) {
|
||||
float distance = ABS(offset - pos.x);
|
||||
if (key_idx == -1 || distance < key_distance) {
|
||||
key_idx = i;
|
||||
key_distance = distance;
|
||||
}
|
||||
} else {
|
||||
// First one does it.
|
||||
key_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key_idx != -1) {
|
||||
if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
|
||||
if (editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("deselect_key"), key_idx);
|
||||
} else {
|
||||
emit_signal(SNAME("select_key"), key_idx, false);
|
||||
moving_selection_attempt = true;
|
||||
select_single_attempt = -1;
|
||||
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
} else {
|
||||
if (!editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("select_key"), key_idx, true);
|
||||
select_single_attempt = -1;
|
||||
} else {
|
||||
select_single_attempt = key_idx;
|
||||
}
|
||||
|
||||
moving_selection_attempt = true;
|
||||
moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
moving_selection_attempt = false;
|
||||
moving_selection_from_ofs = 0.0f;
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
}
|
||||
if (_try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), true)) {
|
||||
accept_event();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2902,12 +2845,19 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
|
||||
}
|
||||
|
||||
bool selected = _try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), false);
|
||||
|
||||
menu->clear();
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key"), MENU_KEY_INSERT);
|
||||
if (editor->is_selection_active()) {
|
||||
if (selected || editor->is_selection_active()) {
|
||||
menu->add_separator();
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
|
||||
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionCopy")), TTR("Copy Key(s)"), MENU_KEY_COPY);
|
||||
}
|
||||
if (editor->is_key_clipboard_active()) {
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Key(s)"), MENU_KEY_PASTE);
|
||||
}
|
||||
if (selected || editor->is_selection_active()) {
|
||||
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
|
||||
if (!player->has_animation(SceneStringNames::get_singleton()->RESET) || animation != player->get_animation(SceneStringNames::get_singleton()->RESET)) {
|
||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Reload")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
|
||||
@ -3030,6 +2980,75 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationTrackEdit::_try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable) {
|
||||
if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible.
|
||||
float scale = timeline->get_zoom_scale();
|
||||
int limit = timeline->get_name_limit();
|
||||
int limit_end = get_size().width - timeline->get_buttons_width();
|
||||
// Left Border including space occupied by keyframes on t=0.
|
||||
int limit_start_hitbox = limit - type_icon->get_width();
|
||||
|
||||
if (p_pos.x >= limit_start_hitbox && p_pos.x <= limit_end) {
|
||||
int key_idx = -1;
|
||||
float key_distance = 1e20;
|
||||
|
||||
// Select should happen in the opposite order of drawing for more accurate overlap select.
|
||||
for (int i = animation->track_get_key_count(track) - 1; i >= 0; i--) {
|
||||
Rect2 rect = get_key_rect(i, scale);
|
||||
float offset = animation->track_get_key_time(track, i) - timeline->get_value();
|
||||
offset = offset * scale + limit;
|
||||
rect.position.x += offset;
|
||||
|
||||
if (rect.has_point(p_pos)) {
|
||||
if (is_key_selectable_by_distance()) {
|
||||
float distance = ABS(offset - p_pos.x);
|
||||
if (key_idx == -1 || distance < key_distance) {
|
||||
key_idx = i;
|
||||
key_distance = distance;
|
||||
}
|
||||
} else {
|
||||
// First one does it.
|
||||
key_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key_idx != -1) {
|
||||
if (p_aggregate) {
|
||||
if (editor->is_key_selected(track, key_idx)) {
|
||||
if (p_deselectable) {
|
||||
emit_signal(SNAME("deselect_key"), key_idx);
|
||||
}
|
||||
} else {
|
||||
emit_signal(SNAME("select_key"), key_idx, false);
|
||||
moving_selection_attempt = true;
|
||||
select_single_attempt = -1;
|
||||
moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
} else {
|
||||
if (!editor->is_key_selected(track, key_idx)) {
|
||||
emit_signal(SNAME("select_key"), key_idx, true);
|
||||
select_single_attempt = -1;
|
||||
} else {
|
||||
select_single_attempt = key_idx;
|
||||
}
|
||||
|
||||
moving_selection_attempt = true;
|
||||
moving_selection_from_ofs = (p_pos.x - limit) / timeline->get_zoom_scale();
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
moving_selection_attempt = false;
|
||||
moving_selection_from_ofs = 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) {
|
||||
if (!clicking_on_name) {
|
||||
return Variant();
|
||||
@ -3156,7 +3175,14 @@ 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"));
|
||||
emit_signal(SNAME("duplicate_request"), insert_at_pos);
|
||||
} break;
|
||||
case MENU_KEY_COPY: {
|
||||
emit_signal(SNAME("copy_request"));
|
||||
} break;
|
||||
|
||||
case MENU_KEY_PASTE: {
|
||||
emit_signal(SNAME("paste_request"), insert_at_pos);
|
||||
} break;
|
||||
case MENU_KEY_ADD_RESET: {
|
||||
emit_signal(SNAME("create_reset_request"));
|
||||
@ -3230,9 +3256,10 @@ void AnimationTrackEdit::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("move_selection_commit"));
|
||||
ADD_SIGNAL(MethodInfo("move_selection_cancel"));
|
||||
|
||||
ADD_SIGNAL(MethodInfo("duplicate_request"));
|
||||
ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset")));
|
||||
ADD_SIGNAL(MethodInfo("create_reset_request"));
|
||||
ADD_SIGNAL(MethodInfo("duplicate_transpose_request"));
|
||||
ADD_SIGNAL(MethodInfo("copy_request"));
|
||||
ADD_SIGNAL(MethodInfo("paste_request", PropertyInfo(Variant::FLOAT, "offset")));
|
||||
ADD_SIGNAL(MethodInfo("delete_request"));
|
||||
}
|
||||
|
||||
@ -4389,6 +4416,10 @@ bool AnimationTrackEditor::is_selection_active() const {
|
||||
return selection.size();
|
||||
}
|
||||
|
||||
bool AnimationTrackEditor::is_key_clipboard_active() const {
|
||||
return key_clipboard.keys.size();
|
||||
}
|
||||
|
||||
bool AnimationTrackEditor::is_snap_enabled() const {
|
||||
return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
|
||||
}
|
||||
@ -4576,8 +4607,9 @@ void AnimationTrackEditor::_update_tracks() {
|
||||
track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit));
|
||||
track_edit->connect("move_selection_cancel", callable_mp(this, &AnimationTrackEditor::_move_selection_cancel));
|
||||
|
||||
track_edit->connect("duplicate_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DUPLICATE_SELECTION), CONNECT_DEFERRED);
|
||||
track_edit->connect("duplicate_transpose_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DUPLICATE_TRANSPOSED), CONNECT_DEFERRED);
|
||||
track_edit->connect("duplicate_request", callable_mp(this, &AnimationTrackEditor::_anim_duplicate_keys).bind(i), CONNECT_DEFERRED);
|
||||
track_edit->connect("copy_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_COPY_KEYS), CONNECT_DEFERRED);
|
||||
track_edit->connect("paste_request", callable_mp(this, &AnimationTrackEditor::_anim_paste_keys).bind(i), CONNECT_DEFERRED);
|
||||
track_edit->connect("create_reset_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_ADD_RESET_KEY), CONNECT_DEFERRED);
|
||||
track_edit->connect("delete_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DELETE_SELECTION), CONNECT_DEFERRED);
|
||||
}
|
||||
@ -5483,7 +5515,7 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) {
|
||||
if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work.
|
||||
track_edits[track_edits.size() - 1]->grab_focus();
|
||||
}
|
||||
} else {
|
||||
} else if (!mb->is_command_or_control_pressed() && !mb->is_shift_pressed()) {
|
||||
_clear_selection(true); // Clear it.
|
||||
}
|
||||
|
||||
@ -5584,9 +5616,8 @@ 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(bool transpose) {
|
||||
// Duplicait!
|
||||
if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) {
|
||||
void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) {
|
||||
if (selection.size() && animation.is_valid()) {
|
||||
int top_track = 0x7FFFFFFF;
|
||||
float top_time = 1e10;
|
||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
||||
@ -5602,9 +5633,32 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
||||
}
|
||||
ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
|
||||
|
||||
//
|
||||
int start_track = p_track;
|
||||
if (p_track == -1) { // Duplicating from shortcut or Edit menu.
|
||||
bool is_valid_track_selected = _get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count();
|
||||
start_track = is_valid_track_selected ? _get_track_selected() : top_track;
|
||||
}
|
||||
|
||||
int start_track = transpose ? _get_track_selected() : top_track;
|
||||
bool all_compatible = true;
|
||||
|
||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
||||
const SelectedKey &sk = E->key();
|
||||
int dst_track = sk.track + (start_track - top_track);
|
||||
|
||||
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
|
||||
all_compatible = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Variant::Type value_type = animation->track_get_key_value(sk.track, sk.key).get_type();
|
||||
Animation::TrackType track_type = animation->track_get_type(sk.track);
|
||||
if (!_is_track_compatible(dst_track, value_type, track_type)) {
|
||||
all_compatible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!all_compatible, "Duplicate failed: Not all animation keys were compatible with their target tracks");
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Animation Duplicate Keys"));
|
||||
@ -5615,21 +5669,27 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
||||
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 dst_time = t + (timeline->get_play_position() - top_time);
|
||||
float dst_time = t + (insert_pos - top_time);
|
||||
int dst_track = sk.track + (start_track - top_track);
|
||||
|
||||
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (animation->track_get_type(dst_track) != animation->track_get_type(sk.track)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int existing_idx = animation->track_find_key(dst_track, dst_time, Animation::FIND_MODE_APPROX);
|
||||
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key));
|
||||
Variant value = animation->track_get_key_value(sk.track, sk.key);
|
||||
bool key_is_bezier = animation->track_get_type(sk.track) == Animation::TYPE_BEZIER;
|
||||
bool track_is_bezier = animation->track_get_type(dst_track) == Animation::TYPE_BEZIER;
|
||||
if (key_is_bezier && !track_is_bezier) {
|
||||
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
|
||||
} else if (!key_is_bezier && track_is_bezier) {
|
||||
value = AnimationBezierTrackEdit::make_default_bezier_key(value);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, animation->track_get_key_transition(E->key().track, E->key().key));
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
|
||||
|
||||
Pair<int, float> p;
|
||||
@ -5660,6 +5720,183 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_anim_copy_keys() {
|
||||
if (is_selection_active() && animation.is_valid()) {
|
||||
int top_track = 0x7FFFFFFF;
|
||||
float top_time = 1e10;
|
||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
||||
const SelectedKey &sk = E->key();
|
||||
|
||||
float t = animation->track_get_key_time(sk.track, sk.key);
|
||||
if (t < top_time) {
|
||||
top_time = t;
|
||||
}
|
||||
if (sk.track < top_track) {
|
||||
top_track = sk.track;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
|
||||
|
||||
_set_key_clipboard(top_track, top_time, selection);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_set_key_clipboard(int p_top_track, float p_top_time, RBMap<SelectedKey, KeyInfo> &p_keys) {
|
||||
key_clipboard.keys.clear();
|
||||
key_clipboard.top_track = p_top_track;
|
||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = p_keys.back(); E; E = E->prev()) {
|
||||
KeyClipboard::Key k;
|
||||
k.value = animation->track_get_key_value(E->key().track, E->key().key);
|
||||
k.transition = animation->track_get_key_transition(E->key().track, E->key().key);
|
||||
k.time = E->value().pos - p_top_time;
|
||||
k.track = E->key().track - p_top_track;
|
||||
k.track_type = animation->track_get_type(E->key().track);
|
||||
|
||||
key_clipboard.keys.push_back(k);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_anim_paste_keys(float p_ofs, 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.
|
||||
bool is_valid_track_selected = _get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count();
|
||||
start_track = is_valid_track_selected ? _get_track_selected() : key_clipboard.top_track;
|
||||
}
|
||||
|
||||
bool all_compatible = true;
|
||||
|
||||
for (int i = 0; i < key_clipboard.keys.size(); i++) {
|
||||
const KeyClipboard::Key key = key_clipboard.keys[i];
|
||||
|
||||
int dst_track = key.track + start_track;
|
||||
|
||||
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
|
||||
all_compatible = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!_is_track_compatible(dst_track, key.value.get_type(), key.track_type)) {
|
||||
all_compatible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_MSG(!all_compatible, "Paste failed: Not all animation keys were compatible with their target tracks");
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Animation Paste Keys"));
|
||||
List<Pair<int, float>> new_selection_values;
|
||||
|
||||
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 dst_time = key.time + insert_pos;
|
||||
int dst_track = key.track + start_track;
|
||||
|
||||
int existing_idx = animation->track_find_key(dst_track, dst_time, Animation::FIND_MODE_APPROX);
|
||||
|
||||
Variant value = key.value;
|
||||
bool key_is_bezier = key.track_type == Animation::TYPE_BEZIER;
|
||||
bool track_is_bezier = animation->track_get_type(dst_track) == Animation::TYPE_BEZIER;
|
||||
if (key_is_bezier && !track_is_bezier) {
|
||||
value = AnimationBezierTrackEdit::get_bezier_key_value(value);
|
||||
} else if (!key_is_bezier && track_is_bezier) {
|
||||
value = AnimationBezierTrackEdit::make_default_bezier_key(value);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, value, key.transition);
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
|
||||
|
||||
Pair<int, float> p;
|
||||
p.first = dst_track;
|
||||
p.second = dst_time;
|
||||
new_selection_values.push_back(p);
|
||||
|
||||
if (existing_idx != -1) {
|
||||
undo_redo->add_undo_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(dst_track, existing_idx), animation->track_get_key_transition(dst_track, existing_idx));
|
||||
}
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
|
||||
undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
|
||||
|
||||
// Reselect pasted.
|
||||
for (const Pair<int, float> &E : new_selection_values) {
|
||||
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
|
||||
}
|
||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
||||
undo_redo->add_undo_method(this, "_select_at_anim", animation, E->key().track, E->get().pos);
|
||||
}
|
||||
|
||||
undo_redo->add_do_method(this, "_redraw_tracks");
|
||||
undo_redo->add_undo_method(this, "_redraw_tracks");
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimationTrackEditor::_is_track_compatible(int p_target_track_idx, Variant::Type p_source_value_type, Animation::TrackType p_source_track_type) {
|
||||
if (animation.is_valid()) {
|
||||
Animation::TrackType target_track_type = animation->track_get_type(p_target_track_idx);
|
||||
bool track_types_equal = target_track_type == p_source_track_type;
|
||||
bool is_source_vector3_type = p_source_track_type == Animation::TYPE_POSITION_3D || p_source_track_type == Animation::TYPE_SCALE_3D || p_source_track_type == Animation::TYPE_ROTATION_3D;
|
||||
bool is_source_bezier = p_source_track_type == Animation::TYPE_BEZIER;
|
||||
switch (target_track_type) {
|
||||
case Animation::TYPE_POSITION_3D:
|
||||
case Animation::TYPE_SCALE_3D:
|
||||
return p_source_value_type == Variant::VECTOR3;
|
||||
case Animation::TYPE_ROTATION_3D:
|
||||
return p_source_value_type == Variant::QUATERNION;
|
||||
case Animation::TYPE_BEZIER:
|
||||
return track_types_equal || p_source_value_type == Variant::FLOAT;
|
||||
case Animation::TYPE_VALUE:
|
||||
if (track_types_equal || is_source_vector3_type || is_source_bezier) {
|
||||
bool path_valid = false;
|
||||
Variant::Type property_type = Variant::NIL;
|
||||
|
||||
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
|
||||
if (ape) {
|
||||
AnimationPlayer *ap = ape->get_player();
|
||||
if (ap) {
|
||||
NodePath npath = animation->track_get_path(p_target_track_idx);
|
||||
Node *a_ap_root_node = ap->get_node(ap->get_root_node());
|
||||
Node *nd = nullptr;
|
||||
// We must test that we have a valid a_ap_root_node before trying to access its content to init the nd Node.
|
||||
if (a_ap_root_node) {
|
||||
nd = a_ap_root_node->get_node(NodePath(npath.get_concatenated_names()));
|
||||
}
|
||||
if (nd) {
|
||||
StringName prop = npath.get_concatenated_subnames();
|
||||
PropertyInfo prop_info;
|
||||
path_valid = ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
|
||||
property_type = prop_info.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path_valid) {
|
||||
if (is_source_bezier)
|
||||
p_source_value_type = Variant::FLOAT;
|
||||
return property_type == p_source_value_type;
|
||||
} else {
|
||||
if (animation->track_get_key_count(p_target_track_idx) > 0) {
|
||||
Variant::Type first_key_type = animation->track_get_key_value(p_target_track_idx, 0).get_type();
|
||||
return first_key_type == p_source_value_type;
|
||||
}
|
||||
return true; // Type is Undefined.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
default: // Works for TYPE_ANIMATION; TYPE_AUDIO; TYPE_CALL_METHOD; BLEND_SHAPE.
|
||||
return track_types_equal;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationTrackEditor::_edit_menu_about_to_popup() {
|
||||
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
|
||||
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
|
||||
@ -6084,19 +6321,22 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
|
||||
|
||||
} break;
|
||||
|
||||
case EDIT_DUPLICATE_SELECTION: {
|
||||
case EDIT_DUPLICATE_SELECTED_KEYS: {
|
||||
if (bezier_edit->is_visible()) {
|
||||
bezier_edit->duplicate_selection();
|
||||
bezier_edit->duplicate_selected_keys(-1.0);
|
||||
break;
|
||||
}
|
||||
_anim_duplicate_keys(false);
|
||||
_anim_duplicate_keys(-1.0, -1.0);
|
||||
} break;
|
||||
case EDIT_DUPLICATE_TRANSPOSED: {
|
||||
case EDIT_COPY_KEYS: {
|
||||
if (bezier_edit->is_visible()) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("This option does not work for Bezier editing, as it's only a single track."));
|
||||
bezier_edit->copy_selected_keys();
|
||||
break;
|
||||
}
|
||||
_anim_duplicate_keys(true);
|
||||
_anim_copy_keys();
|
||||
} break;
|
||||
case EDIT_PASTE_KEYS: {
|
||||
_anim_paste_keys(-1.0, -1.0);
|
||||
} break;
|
||||
case EDIT_ADD_RESET_KEY: {
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
@ -6740,8 +6980,9 @@ AnimationTrackEditor::AnimationTrackEditor() {
|
||||
edit->get_popup()->add_separator();
|
||||
edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION);
|
||||
edit->get_popup()->add_separator();
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_SELECTION);
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection_transposed", TTR("Duplicate Transposed"), KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_TRANSPOSED);
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selected_keys", TTR("Duplicate Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::D), EDIT_DUPLICATE_SELECTED_KEYS);
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/copy_selected_keys", TTR("Copy Selected Keys"), KeyModifierMask::CMD_OR_CTRL | Key::C), EDIT_COPY_KEYS);
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/paste_keys", TTR("Paste Keys"), KeyModifierMask::CMD_OR_CTRL | Key::V), EDIT_PASTE_KEYS);
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTR("Add RESET Value(s)")));
|
||||
edit->get_popup()->add_separator();
|
||||
edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/delete_selection", TTR("Delete Selection"), Key::KEY_DELETE), EDIT_DELETE_SELECTION);
|
||||
|
@ -230,6 +230,8 @@ class AnimationTrackEdit : public Control {
|
||||
MENU_LOOP_CLAMP,
|
||||
MENU_KEY_INSERT,
|
||||
MENU_KEY_DUPLICATE,
|
||||
MENU_KEY_COPY,
|
||||
MENU_KEY_PASTE,
|
||||
MENU_KEY_ADD_RESET,
|
||||
MENU_KEY_DELETE,
|
||||
MENU_USE_BLEND_ENABLED,
|
||||
@ -275,6 +277,7 @@ class AnimationTrackEdit : public Control {
|
||||
void _path_submitted(const String &p_text);
|
||||
void _play_position_draw();
|
||||
bool _is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const;
|
||||
bool _try_select_at_ui_pos(const Point2 &p_pos, bool p_aggregate, bool p_deselectable);
|
||||
|
||||
Ref<Texture2D> _get_key_type_icon() const;
|
||||
|
||||
@ -376,6 +379,7 @@ public:
|
||||
class AnimationTrackEditor : public VBoxContainer {
|
||||
GDCLASS(AnimationTrackEditor, VBoxContainer);
|
||||
friend class AnimationTimelineEdit;
|
||||
friend class AnimationBezierTrackEdit;
|
||||
|
||||
Ref<Animation> animation;
|
||||
bool read_only = false;
|
||||
@ -571,7 +575,13 @@ class AnimationTrackEditor : public VBoxContainer {
|
||||
|
||||
void _cleanup_animation(Ref<Animation> p_animation);
|
||||
|
||||
void _anim_duplicate_keys(bool transpose);
|
||||
void _anim_duplicate_keys(float p_ofs, int p_track);
|
||||
|
||||
void _anim_copy_keys();
|
||||
|
||||
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 _view_group_toggle();
|
||||
Button *view_group = nullptr;
|
||||
@ -601,8 +611,23 @@ class AnimationTrackEditor : public VBoxContainer {
|
||||
Vector<Key> keys;
|
||||
};
|
||||
|
||||
Vector<TrackClipboard> track_clipboard;
|
||||
struct KeyClipboard {
|
||||
int top_track;
|
||||
|
||||
struct Key {
|
||||
Animation::TrackType track_type;
|
||||
int track;
|
||||
float time = 0;
|
||||
float transition = 0;
|
||||
Variant value;
|
||||
};
|
||||
Vector<Key> keys;
|
||||
};
|
||||
|
||||
Vector<TrackClipboard> track_clipboard;
|
||||
KeyClipboard key_clipboard;
|
||||
|
||||
void _set_key_clipboard(int p_top_track, float p_top_time, RBMap<SelectedKey, KeyInfo> &p_keymap);
|
||||
void _insert_animation_key(NodePath p_path, const Variant &p_value);
|
||||
|
||||
void _pick_track_filter_text_changed(const String &p_newtext);
|
||||
@ -623,13 +648,14 @@ public:
|
||||
EDIT_COPY_TRACKS,
|
||||
EDIT_COPY_TRACKS_CONFIRM,
|
||||
EDIT_PASTE_TRACKS,
|
||||
EDIT_COPY_KEYS,
|
||||
EDIT_PASTE_KEYS,
|
||||
EDIT_SCALE_SELECTION,
|
||||
EDIT_SCALE_FROM_CURSOR,
|
||||
EDIT_SCALE_CONFIRM,
|
||||
EDIT_EASE_SELECTION,
|
||||
EDIT_EASE_CONFIRM,
|
||||
EDIT_DUPLICATE_SELECTION,
|
||||
EDIT_DUPLICATE_TRANSPOSED,
|
||||
EDIT_DUPLICATE_SELECTED_KEYS,
|
||||
EDIT_ADD_RESET_KEY,
|
||||
EDIT_DELETE_SELECTION,
|
||||
EDIT_GOTO_NEXT_STEP,
|
||||
@ -673,6 +699,7 @@ public:
|
||||
|
||||
bool is_key_selected(int p_track, int p_key) const;
|
||||
bool is_selection_active() const;
|
||||
bool is_key_clipboard_active() const;
|
||||
bool is_moving_selection() const;
|
||||
bool is_snap_enabled() const;
|
||||
float get_moving_selection_offset() const;
|
||||
|
Loading…
Reference in New Issue
Block a user