implement consistent select, copy, paste, duplicate in animation player
This commit is contained in:
parent
26b1fd0d84
commit
a5cb760d90
4 changed files with 595 additions and 184 deletions
|
@ -834,16 +834,22 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p_event->is_pressed()) {
|
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) {
|
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();
|
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) {
|
if (!read_only) {
|
||||||
delete_selection();
|
paste_keys(-1.0);
|
||||||
}
|
}
|
||||||
accept_event();
|
accept_event();
|
||||||
}
|
}
|
||||||
|
@ -946,11 +952,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
if (!read_only) {
|
if (!read_only) {
|
||||||
Vector2 popup_pos = get_screen_position() + mb->get_position();
|
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->clear();
|
||||||
menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
|
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_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("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_separator();
|
||||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
|
menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
|
||||||
menu->add_separator();
|
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 keyframe.
|
||||||
//first check point
|
// Command/Control makes it ignore the keyframe, so control point editors can be force-edited.
|
||||||
//command makes it ignore the main point, so control point editors can be force-edited
|
if (!mb->is_command_or_control_pressed()) {
|
||||||
//path 2D editing in the 3D and 2D editors works the same way
|
if (_try_select_at_ui_pos(mb->get_position(), mb->is_shift_pressed(), true)) {
|
||||||
if (!mb->is_command_or_control_pressed()) {
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, check handles.
|
||||||
|
for (int i = 0; i < edit_points.size(); i++) {
|
||||||
if (!read_only) {
|
if (!read_only) {
|
||||||
if (edit_points[i].in_rect.has_point(mb->get_position())) {
|
if (edit_points[i].in_rect.has_point(mb->get_position())) {
|
||||||
moving_handle = -1;
|
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) {
|
void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
|
||||||
Ref<InputEventMouseMotion> mm = p_event;
|
Ref<InputEventMouseMotion> mm = p_event;
|
||||||
if (mm.is_valid()) {
|
if (mm.is_valid()) {
|
||||||
|
@ -1526,29 +1554,37 @@ void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_ori
|
||||||
queue_redraw();
|
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) {
|
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) {
|
switch (p_index) {
|
||||||
case MENU_KEY_INSERT: {
|
case MENU_KEY_INSERT: {
|
||||||
if (animation->get_track_count() > 0) {
|
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;
|
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);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -1558,11 +1594,17 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case MENU_KEY_DUPLICATE: {
|
case MENU_KEY_DUPLICATE: {
|
||||||
duplicate_selection();
|
duplicate_selected_keys(time);
|
||||||
} break;
|
} break;
|
||||||
case MENU_KEY_DELETE: {
|
case MENU_KEY_DELETE: {
|
||||||
delete_selection();
|
delete_selection();
|
||||||
} break;
|
} break;
|
||||||
|
case MENU_KEY_COPY: {
|
||||||
|
copy_selected_keys();
|
||||||
|
} break;
|
||||||
|
case MENU_KEY_PASTE: {
|
||||||
|
paste_keys(time);
|
||||||
|
} 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);
|
||||||
} break;
|
} 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) {
|
if (selection.size() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1604,7 +1646,8 @@ void AnimationBezierTrackEdit::duplicate_selection() {
|
||||||
|
|
||||||
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 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);
|
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));
|
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
|
// Reselect duplicated.
|
||||||
|
|
||||||
selection.clear();
|
|
||||||
for (const Pair<int, real_t> &E : new_selection_values) {
|
for (const Pair<int, real_t> &E : new_selection_values) {
|
||||||
int track = E.first;
|
undo_redo->add_do_method(this, "_select_at_anim", animation, E.first, E.second);
|
||||||
real_t time = E.second;
|
}
|
||||||
|
for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
|
||||||
int existing_idx = animation->track_find_key(track, time, Animation::FIND_MODE_APPROX);
|
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);
|
||||||
if (existing_idx == -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
selection.insert(IntPair(track, existing_idx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
void AnimationBezierTrackEdit::delete_selection() {
|
||||||
|
|
|
@ -42,6 +42,8 @@ class AnimationBezierTrackEdit : public Control {
|
||||||
enum {
|
enum {
|
||||||
MENU_KEY_INSERT,
|
MENU_KEY_INSERT,
|
||||||
MENU_KEY_DUPLICATE,
|
MENU_KEY_DUPLICATE,
|
||||||
|
MENU_KEY_COPY,
|
||||||
|
MENU_KEY_PASTE,
|
||||||
MENU_KEY_DELETE,
|
MENU_KEY_DELETE,
|
||||||
MENU_KEY_SET_HANDLE_FREE,
|
MENU_KEY_SET_HANDLE_FREE,
|
||||||
MENU_KEY_SET_HANDLE_LINEAR,
|
MENU_KEY_SET_HANDLE_LINEAR,
|
||||||
|
@ -139,6 +141,7 @@ class AnimationBezierTrackEdit : public Control {
|
||||||
void _clear_selection();
|
void _clear_selection();
|
||||||
void _clear_selection_for_anim(const Ref<Animation> &p_anim);
|
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);
|
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);
|
void _change_selected_keys_handle_mode(Animation::HandleMode p_mode, bool p_auto = false);
|
||||||
|
|
||||||
Vector2 menu_insert_key;
|
Vector2 menu_insert_key;
|
||||||
|
@ -190,6 +193,9 @@ protected:
|
||||||
void _notification(int p_what);
|
void _notification(int p_what);
|
||||||
|
|
||||||
public:
|
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;
|
virtual String get_tooltip(const Point2 &p_pos) const override;
|
||||||
|
|
||||||
Ref<Animation> get_animation() const;
|
Ref<Animation> get_animation() const;
|
||||||
|
@ -205,7 +211,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_selection();
|
void duplicate_selected_keys(real_t p_ofs);
|
||||||
|
void copy_selected_keys();
|
||||||
|
void paste_keys(real_t p_ofs);
|
||||||
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);
|
||||||
|
|
|
@ -2685,16 +2685,22 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
ERR_FAIL_COND(p_event.is_null());
|
ERR_FAIL_COND(p_event.is_null());
|
||||||
|
|
||||||
if (p_event->is_pressed()) {
|
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) {
|
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();
|
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) {
|
if (!read_only) {
|
||||||
emit_signal(SNAME("duplicate_transpose_request"));
|
emit_signal(SNAME("paste_request"), -1.0);
|
||||||
}
|
}
|
||||||
accept_event();
|
accept_event();
|
||||||
}
|
}
|
||||||
|
@ -2821,71 +2827,8 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check keyframes.
|
if (_try_select_at_ui_pos(pos, mb->is_command_or_control_pressed() || mb->is_shift_pressed(), true)) {
|
||||||
|
accept_event();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2901,12 +2844,19 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
|
||||||
menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
|
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->clear();
|
||||||
menu->add_icon_item(get_editor_theme_icon(SNAME("Key")), TTR("Insert Key"), MENU_KEY_INSERT);
|
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_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("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();
|
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
|
||||||
if (!player->has_animation(SceneStringNames::get_singleton()->RESET) || animation != player->get_animation(SceneStringNames::get_singleton()->RESET)) {
|
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);
|
menu->add_icon_item(get_editor_theme_icon(SNAME("Reload")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
|
||||||
|
@ -3029,6 +2979,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) {
|
Variant AnimationTrackEdit::get_drag_data(const Point2 &p_point) {
|
||||||
if (!clicking_on_name) {
|
if (!clicking_on_name) {
|
||||||
return Variant();
|
return Variant();
|
||||||
|
@ -3154,7 +3173,14 @@ 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"));
|
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;
|
} break;
|
||||||
case MENU_KEY_ADD_RESET: {
|
case MENU_KEY_ADD_RESET: {
|
||||||
emit_signal(SNAME("create_reset_request"));
|
emit_signal(SNAME("create_reset_request"));
|
||||||
|
@ -3228,9 +3254,10 @@ 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"));
|
ADD_SIGNAL(MethodInfo("duplicate_request", PropertyInfo(Variant::FLOAT, "offset")));
|
||||||
ADD_SIGNAL(MethodInfo("create_reset_request"));
|
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"));
|
ADD_SIGNAL(MethodInfo("delete_request"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4383,6 +4410,10 @@ bool AnimationTrackEditor::is_selection_active() const {
|
||||||
return selection.size();
|
return selection.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AnimationTrackEditor::is_key_clipboard_active() const {
|
||||||
|
return key_clipboard.keys.size();
|
||||||
|
}
|
||||||
|
|
||||||
bool AnimationTrackEditor::is_snap_enabled() const {
|
bool AnimationTrackEditor::is_snap_enabled() const {
|
||||||
return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
|
return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
|
||||||
}
|
}
|
||||||
|
@ -4570,8 +4601,9 @@ void AnimationTrackEditor::_update_tracks() {
|
||||||
track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit));
|
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("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_request", callable_mp(this, &AnimationTrackEditor::_anim_duplicate_keys).bind(i), CONNECT_DEFERRED);
|
||||||
track_edit->connect("duplicate_transpose_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DUPLICATE_TRANSPOSED), 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("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);
|
track_edit->connect("delete_request", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_DELETE_SELECTION), CONNECT_DEFERRED);
|
||||||
}
|
}
|
||||||
|
@ -5477,7 +5509,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.
|
if (_get_track_selected() == -1 && track_edits.size() > 0) { // Minimal hack to make shortcuts work.
|
||||||
track_edits[track_edits.size() - 1]->grab_focus();
|
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.
|
_clear_selection(true); // Clear it.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5578,9 +5610,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);
|
p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, int p_track) {
|
||||||
// Duplicait!
|
if (selection.size() && animation.is_valid()) {
|
||||||
if (selection.size() && animation.is_valid() && (!transpose || (_get_track_selected() >= 0 && _get_track_selected() < animation->get_track_count()))) {
|
|
||||||
int top_track = 0x7FFFFFFF;
|
int top_track = 0x7FFFFFFF;
|
||||||
float top_time = 1e10;
|
float top_time = 1e10;
|
||||||
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
for (RBMap<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {
|
||||||
|
@ -5596,9 +5627,32 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
||||||
}
|
}
|
||||||
ERR_FAIL_COND(top_track == 0x7FFFFFFF || top_time == 1e10);
|
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();
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||||
undo_redo->create_action(TTR("Animation Duplicate Keys"));
|
undo_redo->create_action(TTR("Animation Duplicate Keys"));
|
||||||
|
@ -5609,21 +5663,27 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {
|
||||||
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 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);
|
int dst_track = sk.track + (start_track - top_track);
|
||||||
|
|
||||||
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
|
if (dst_track < 0 || dst_track >= animation->get_track_count()) {
|
||||||
continue;
|
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);
|
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);
|
undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);
|
||||||
|
|
||||||
Pair<int, float> p;
|
Pair<int, float> p;
|
||||||
|
@ -5654,6 +5714,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() {
|
void AnimationTrackEditor::_edit_menu_about_to_popup() {
|
||||||
AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
|
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());
|
edit->get_popup()->set_item_disabled(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), !player->can_apply_reset());
|
||||||
|
@ -6078,19 +6315,22 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case EDIT_DUPLICATE_SELECTION: {
|
case EDIT_DUPLICATE_SELECTED_KEYS: {
|
||||||
if (bezier_edit->is_visible()) {
|
if (bezier_edit->is_visible()) {
|
||||||
bezier_edit->duplicate_selection();
|
bezier_edit->duplicate_selected_keys(-1.0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_anim_duplicate_keys(false);
|
_anim_duplicate_keys(-1.0, -1.0);
|
||||||
} break;
|
} break;
|
||||||
case EDIT_DUPLICATE_TRANSPOSED: {
|
case EDIT_COPY_KEYS: {
|
||||||
if (bezier_edit->is_visible()) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
_anim_duplicate_keys(true);
|
_anim_copy_keys();
|
||||||
|
} break;
|
||||||
|
case EDIT_PASTE_KEYS: {
|
||||||
|
_anim_paste_keys(-1.0, -1.0);
|
||||||
} break;
|
} break;
|
||||||
case EDIT_ADD_RESET_KEY: {
|
case EDIT_ADD_RESET_KEY: {
|
||||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||||
|
@ -6734,8 +6974,9 @@ AnimationTrackEditor::AnimationTrackEditor() {
|
||||||
edit->get_popup()->add_separator();
|
edit->get_popup()->add_separator();
|
||||||
edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION);
|
edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION);
|
||||||
edit->get_popup()->add_separator();
|
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_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/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/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_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTR("Add RESET Value(s)")));
|
||||||
edit->get_popup()->add_separator();
|
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);
|
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_LOOP_CLAMP,
|
||||||
MENU_KEY_INSERT,
|
MENU_KEY_INSERT,
|
||||||
MENU_KEY_DUPLICATE,
|
MENU_KEY_DUPLICATE,
|
||||||
|
MENU_KEY_COPY,
|
||||||
|
MENU_KEY_PASTE,
|
||||||
MENU_KEY_ADD_RESET,
|
MENU_KEY_ADD_RESET,
|
||||||
MENU_KEY_DELETE,
|
MENU_KEY_DELETE,
|
||||||
MENU_USE_BLEND_ENABLED,
|
MENU_USE_BLEND_ENABLED,
|
||||||
|
@ -275,6 +277,7 @@ class AnimationTrackEdit : public Control {
|
||||||
void _path_submitted(const String &p_text);
|
void _path_submitted(const String &p_text);
|
||||||
void _play_position_draw();
|
void _play_position_draw();
|
||||||
bool _is_value_key_valid(const Variant &p_key_value, Variant::Type &r_valid_type) const;
|
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;
|
Ref<Texture2D> _get_key_type_icon() const;
|
||||||
|
|
||||||
|
@ -375,6 +378,7 @@ public:
|
||||||
class AnimationTrackEditor : public VBoxContainer {
|
class AnimationTrackEditor : public VBoxContainer {
|
||||||
GDCLASS(AnimationTrackEditor, VBoxContainer);
|
GDCLASS(AnimationTrackEditor, VBoxContainer);
|
||||||
friend class AnimationTimelineEdit;
|
friend class AnimationTimelineEdit;
|
||||||
|
friend class AnimationBezierTrackEdit;
|
||||||
|
|
||||||
Ref<Animation> animation;
|
Ref<Animation> animation;
|
||||||
bool read_only = false;
|
bool read_only = false;
|
||||||
|
@ -570,7 +574,13 @@ class AnimationTrackEditor : public VBoxContainer {
|
||||||
|
|
||||||
void _cleanup_animation(Ref<Animation> p_animation);
|
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();
|
void _view_group_toggle();
|
||||||
Button *view_group = nullptr;
|
Button *view_group = nullptr;
|
||||||
|
@ -600,8 +610,23 @@ class AnimationTrackEditor : public VBoxContainer {
|
||||||
Vector<Key> keys;
|
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 _insert_animation_key(NodePath p_path, const Variant &p_value);
|
||||||
|
|
||||||
void _pick_track_filter_text_changed(const String &p_newtext);
|
void _pick_track_filter_text_changed(const String &p_newtext);
|
||||||
|
@ -622,13 +647,14 @@ public:
|
||||||
EDIT_COPY_TRACKS,
|
EDIT_COPY_TRACKS,
|
||||||
EDIT_COPY_TRACKS_CONFIRM,
|
EDIT_COPY_TRACKS_CONFIRM,
|
||||||
EDIT_PASTE_TRACKS,
|
EDIT_PASTE_TRACKS,
|
||||||
|
EDIT_COPY_KEYS,
|
||||||
|
EDIT_PASTE_KEYS,
|
||||||
EDIT_SCALE_SELECTION,
|
EDIT_SCALE_SELECTION,
|
||||||
EDIT_SCALE_FROM_CURSOR,
|
EDIT_SCALE_FROM_CURSOR,
|
||||||
EDIT_SCALE_CONFIRM,
|
EDIT_SCALE_CONFIRM,
|
||||||
EDIT_EASE_SELECTION,
|
EDIT_EASE_SELECTION,
|
||||||
EDIT_EASE_CONFIRM,
|
EDIT_EASE_CONFIRM,
|
||||||
EDIT_DUPLICATE_SELECTION,
|
EDIT_DUPLICATE_SELECTED_KEYS,
|
||||||
EDIT_DUPLICATE_TRANSPOSED,
|
|
||||||
EDIT_ADD_RESET_KEY,
|
EDIT_ADD_RESET_KEY,
|
||||||
EDIT_DELETE_SELECTION,
|
EDIT_DELETE_SELECTION,
|
||||||
EDIT_GOTO_NEXT_STEP,
|
EDIT_GOTO_NEXT_STEP,
|
||||||
|
@ -672,6 +698,7 @@ public:
|
||||||
|
|
||||||
bool is_key_selected(int p_track, int p_key) const;
|
bool is_key_selected(int p_track, int p_key) const;
|
||||||
bool is_selection_active() const;
|
bool is_selection_active() const;
|
||||||
|
bool is_key_clipboard_active() const;
|
||||||
bool is_moving_selection() const;
|
bool is_moving_selection() const;
|
||||||
bool is_snap_enabled() const;
|
bool is_snap_enabled() const;
|
||||||
float get_moving_selection_offset() const;
|
float get_moving_selection_offset() const;
|
||||||
|
|
Loading…
Reference in a new issue