Skip to content

Commit 9597771

Browse files
committed
Merge pull request #107887 from DexterFstone/add-copy-paste-animation-sprite-frames
Add ability to copy and paste animations in SpriteFrames
2 parents e5e2c06 + 7d8370a commit 9597771

File tree

4 files changed

+197
-49
lines changed

4 files changed

+197
-49
lines changed

editor/scene/sprite_frames_editor_plugin.cpp

Lines changed: 171 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,9 @@ void SpriteFramesEditor::_notification(int p_what) {
666666
zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
667667
add_anim->set_button_icon(get_editor_theme_icon(SNAME("New")));
668668
duplicate_anim->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));
669+
cut_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionCut")));
670+
copy_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
671+
paste_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));
669672
delete_anim->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
670673
anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
671674
split_sheet_zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
@@ -1221,19 +1224,7 @@ void SpriteFramesEditor::_animation_duplicate() {
12211224
return;
12221225
}
12231226

1224-
int counter = 1;
1225-
String new_name = edited_anim;
1226-
PackedStringArray name_component = new_name.rsplit("_", true, 1);
1227-
String base_name = name_component[0];
1228-
if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) {
1229-
counter = name_component[1].to_int();
1230-
}
1231-
new_name = base_name + "_" + itos(counter);
1232-
while (frames->has_animation(new_name)) {
1233-
counter++;
1234-
new_name = base_name + "_" + itos(counter);
1235-
}
1236-
1227+
String new_name = _generate_unique_animation_name(edited_anim);
12371228
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
12381229
undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
12391230
undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);
@@ -1247,55 +1238,74 @@ void SpriteFramesEditor::_animation_duplicate() {
12471238
animations->grab_focus();
12481239
}
12491240

1250-
void SpriteFramesEditor::_animation_remove() {
1251-
if (updating) {
1241+
void SpriteFramesEditor::_animation_cut() {
1242+
if (!frames->has_animation(edited_anim)) {
12521243
return;
12531244
}
12541245

1246+
// Copy animation to clipboard.
1247+
Ref<ClipboardAnimation> clipboard_anim = ClipboardAnimation::from_sprite_frames(frames, edited_anim);
1248+
EditorSettings::get_singleton()->set_resource_clipboard(clipboard_anim);
1249+
1250+
// Remove animation with undo/redo (no confirmation dialog).
1251+
_animation_remove_undo_redo(TTR("Cut Animation"), &clipboard_anim->frames);
1252+
}
1253+
1254+
void SpriteFramesEditor::_animation_copy() {
12551255
if (!frames->has_animation(edited_anim)) {
12561256
return;
12571257
}
12581258

1259-
delete_dialog->set_text(TTRC("Delete Animation?"));
1260-
delete_dialog->popup_centered();
1259+
Ref<ClipboardAnimation> clipboard_anim = ClipboardAnimation::from_sprite_frames(frames, edited_anim);
1260+
EditorSettings::get_singleton()->set_resource_clipboard(clipboard_anim);
12611261
}
12621262

1263-
void SpriteFramesEditor::_animation_remove_confirmed() {
1264-
StringName new_edited;
1265-
List<StringName> anim_names;
1266-
frames->get_animation_list(&anim_names);
1267-
anim_names.sort_custom<StringName::AlphCompare>();
1268-
if (anim_names.size() >= 2) {
1269-
if (edited_anim == anim_names.get(0)) {
1270-
new_edited = anim_names.get(1);
1271-
} else {
1272-
new_edited = anim_names.get(0);
1273-
}
1274-
} else {
1275-
new_edited = StringName();
1263+
void SpriteFramesEditor::_animation_paste() {
1264+
if (updating) {
1265+
return;
1266+
}
1267+
1268+
Ref<ClipboardAnimation> clipboard_anim = EditorSettings::get_singleton()->get_resource_clipboard();
1269+
if (clipboard_anim.is_null()) {
1270+
return;
12761271
}
12771272

1273+
String new_name = _generate_unique_animation_name(clipboard_anim->name);
12781274
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1279-
undo_redo->create_action(TTR("Remove Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());
1280-
_rename_node_animation(undo_redo, false, edited_anim, new_edited, "");
1281-
undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim);
1282-
undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim);
1283-
_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);
1284-
undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));
1285-
undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));
1286-
int fc = frames->get_frame_count(edited_anim);
1287-
for (int i = 0; i < fc; i++) {
1288-
Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);
1289-
float duration = frames->get_frame_duration(edited_anim, i);
1290-
undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration);
1275+
undo_redo->create_action(TTR("Paste Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
1276+
undo_redo->add_do_method(frames.ptr(), "add_animation", new_name);
1277+
undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);
1278+
undo_redo->add_do_method(frames.ptr(), "set_animation_speed", new_name, clipboard_anim->speed);
1279+
undo_redo->add_do_method(frames.ptr(), "set_animation_loop", new_name, clipboard_anim->loop);
1280+
1281+
for (ClipboardSpriteFrames::Frame &frame : clipboard_anim->frames) {
1282+
undo_redo->add_do_method(frames.ptr(), "add_frame", new_name, frame.texture, frame.duration);
12911283
}
1292-
undo_redo->add_do_method(this, "_select_animation", new_edited);
1284+
1285+
undo_redo->add_do_method(this, "_select_animation", new_name);
12931286
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
12941287
undo_redo->add_do_method(this, "_update_library");
12951288
undo_redo->add_undo_method(this, "_update_library");
12961289
undo_redo->commit_action();
12971290
}
12981291

1292+
void SpriteFramesEditor::_animation_remove() {
1293+
if (updating) {
1294+
return;
1295+
}
1296+
1297+
if (!frames->has_animation(edited_anim)) {
1298+
return;
1299+
}
1300+
1301+
delete_dialog->set_text(TTRC("Delete Animation?"));
1302+
delete_dialog->popup_centered();
1303+
}
1304+
1305+
void SpriteFramesEditor::_animation_remove_confirmed() {
1306+
_animation_remove_undo_redo(TTR("Remove Animation"), nullptr);
1307+
}
1308+
12991309
void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) {
13001310
_update_library();
13011311
}
@@ -1336,6 +1346,78 @@ void SpriteFramesEditor::_animation_speed_changed(double p_value) {
13361346
undo_redo->commit_action();
13371347
}
13381348

1349+
void SpriteFramesEditor::_animation_remove_undo_redo(const StringName &p_action_name, const Vector<ClipboardSpriteFrames::Frame> *p_frames) {
1350+
StringName new_edited = _find_next_animation();
1351+
int frame_count = frames->get_frame_count(edited_anim);
1352+
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1353+
undo_redo->create_action(p_action_name, UndoRedo::MERGE_DISABLE, frames.ptr());
1354+
_rename_node_animation(undo_redo, false, edited_anim, new_edited, "");
1355+
undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim);
1356+
undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim);
1357+
_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);
1358+
undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));
1359+
undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));
1360+
for (int i = 0; i < frame_count; i++) {
1361+
Ref<Texture2D> texture;
1362+
float duration;
1363+
if (p_frames) {
1364+
texture = (*p_frames)[i].texture;
1365+
duration = (*p_frames)[i].duration;
1366+
} else {
1367+
texture = frames->get_frame_texture(edited_anim, i);
1368+
duration = frames->get_frame_duration(edited_anim, i);
1369+
}
1370+
undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration);
1371+
}
1372+
undo_redo->add_do_method(this, "_select_animation", new_edited);
1373+
undo_redo->add_undo_method(this, "_select_animation", edited_anim);
1374+
undo_redo->add_do_method(this, "_update_library");
1375+
undo_redo->add_undo_method(this, "_update_library");
1376+
undo_redo->commit_action();
1377+
}
1378+
1379+
StringName SpriteFramesEditor::_find_next_animation() {
1380+
List<StringName> anim_names;
1381+
frames->get_animation_list(&anim_names);
1382+
anim_names.sort_custom<StringName::AlphCompare>();
1383+
if (anim_names.size() >= 2) {
1384+
if (edited_anim == anim_names.get(0)) {
1385+
return anim_names.get(1);
1386+
} else {
1387+
return anim_names.get(0);
1388+
}
1389+
} else {
1390+
return StringName();
1391+
}
1392+
}
1393+
1394+
String SpriteFramesEditor::_generate_unique_animation_name(const String &p_base_name) const {
1395+
if (!frames->has_animation(p_base_name)) {
1396+
return p_base_name;
1397+
}
1398+
1399+
int count = 2;
1400+
String new_name = p_base_name;
1401+
PackedStringArray split = new_name.split("_");
1402+
int last_index = split.size() - 1;
1403+
if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) {
1404+
count = split[last_index].to_int();
1405+
split.remove_at(last_index);
1406+
new_name = String("_").join(split);
1407+
}
1408+
while (true) {
1409+
String attempt = new_name;
1410+
attempt += vformat("_%d", count);
1411+
if (frames->has_animation(attempt)) {
1412+
count++;
1413+
continue;
1414+
}
1415+
new_name = attempt;
1416+
break;
1417+
}
1418+
return new_name;
1419+
}
1420+
13391421
void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) {
13401422
const Ref<InputEventMouseButton> mb = p_event;
13411423

@@ -1498,6 +1580,7 @@ void SpriteFramesEditor::_update_library_impl() {
14981580
if (!anim_names.size()) {
14991581
missing_anim_label->show();
15001582
anim_frames_vb->hide();
1583+
updating = false;
15011584
return;
15021585
}
15031586
missing_anim_label->hide();
@@ -1658,6 +1741,9 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {
16581741

16591742
add_anim->set_disabled(read_only);
16601743
duplicate_anim->set_disabled(read_only);
1744+
cut_anim->set_disabled(read_only);
1745+
copy_anim->set_disabled(read_only);
1746+
paste_anim->set_disabled(read_only);
16611747
delete_anim->set_disabled(read_only);
16621748
anim_speed->set_editable(!read_only);
16631749
anim_loop->set_disabled(read_only);
@@ -2003,6 +2089,25 @@ SpriteFramesEditor::SpriteFramesEditor() {
20032089
duplicate_anim->set_accessibility_name(TTRC("Duplicate Animation"));
20042090
hbc_animlist->add_child(duplicate_anim);
20052091
duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));
2092+
duplicate_anim->set_visible(false);
2093+
2094+
cut_anim = memnew(Button);
2095+
cut_anim->set_theme_type_variation(SceneStringName(FlatButton));
2096+
cut_anim->set_accessibility_name(TTRC("Cut Animation"));
2097+
hbc_animlist->add_child(cut_anim);
2098+
cut_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_cut));
2099+
2100+
copy_anim = memnew(Button);
2101+
copy_anim->set_theme_type_variation(SceneStringName(FlatButton));
2102+
copy_anim->set_accessibility_name(TTRC("Copy Animation"));
2103+
hbc_animlist->add_child(copy_anim);
2104+
copy_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_copy));
2105+
2106+
paste_anim = memnew(Button);
2107+
paste_anim->set_theme_type_variation(SceneStringName(FlatButton));
2108+
paste_anim->set_accessibility_name(TTRC("Paste Animation"));
2109+
hbc_animlist->add_child(paste_anim);
2110+
paste_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_paste));
20062111

20072112
delete_anim = memnew(Button);
20082113
delete_anim->set_theme_type_variation(SceneStringName(FlatButton));
@@ -2064,6 +2169,12 @@ SpriteFramesEditor::SpriteFramesEditor() {
20642169
add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTRC("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));
20652170
duplicate_anim->set_shortcut_context(animations);
20662171
duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTRC("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));
2172+
cut_anim->set_shortcut_context(animations);
2173+
cut_anim->set_shortcut(ED_SHORTCUT("sprite_frames/cut_animation", TTRC("Cut Animation"), KeyModifierMask::CMD_OR_CTRL | Key::X));
2174+
copy_anim->set_shortcut_context(animations);
2175+
copy_anim->set_shortcut(ED_SHORTCUT("sprite_frames/copy_animation", TTRC("Copy Animation"), KeyModifierMask::CMD_OR_CTRL | Key::C));
2176+
paste_anim->set_shortcut_context(animations);
2177+
paste_anim->set_shortcut(ED_SHORTCUT("sprite_frames/paste_animation", TTRC("Paste Animation"), KeyModifierMask::CMD_OR_CTRL | Key::V));
20672178
delete_anim->set_shortcut_context(animations);
20682179
delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTRC("Delete Animation"), Key::KEY_DELETE));
20692180

@@ -2613,3 +2724,20 @@ SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {
26132724
button = EditorNode::get_bottom_panel()->add_item(TTRC("SpriteFrames"), frames_editor, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_sprite_frames_bottom_panel", TTRC("Toggle SpriteFrames Bottom Panel")));
26142725
button->hide();
26152726
}
2727+
2728+
Ref<ClipboardAnimation> ClipboardAnimation::from_sprite_frames(const Ref<SpriteFrames> &p_frames, const String &p_anim) {
2729+
Ref<ClipboardAnimation> clipboard_anim;
2730+
clipboard_anim.instantiate();
2731+
clipboard_anim->name = p_anim;
2732+
clipboard_anim->speed = p_frames->get_animation_speed(p_anim);
2733+
clipboard_anim->loop = p_frames->get_animation_loop(p_anim);
2734+
2735+
int frame_count = p_frames->get_frame_count(p_anim);
2736+
for (int i = 0; i < frame_count; ++i) {
2737+
ClipboardSpriteFrames::Frame frame;
2738+
frame.texture = p_frames->get_frame_texture(p_anim, i);
2739+
frame.duration = p_frames->get_frame_duration(p_anim, i);
2740+
clipboard_anim->frames.push_back(frame);
2741+
}
2742+
return clipboard_anim;
2743+
}

editor/scene/sprite_frames_editor_plugin.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ class ClipboardSpriteFrames : public Resource {
5757
Vector<Frame> frames;
5858
};
5959

60+
class ClipboardAnimation : public Resource {
61+
GDCLASS(ClipboardAnimation, Resource);
62+
63+
public:
64+
String name;
65+
Vector<ClipboardSpriteFrames::Frame> frames;
66+
float speed = 1.0f;
67+
bool loop = false;
68+
69+
static Ref<ClipboardAnimation> from_sprite_frames(const Ref<SpriteFrames> &p_frames, const String &p_anim);
70+
};
71+
6072
class SpriteFramesEditor : public HSplitContainer {
6173
GDCLASS(SpriteFramesEditor, HSplitContainer);
6274

@@ -125,6 +137,9 @@ class SpriteFramesEditor : public HSplitContainer {
125137

126138
Button *add_anim = nullptr;
127139
Button *duplicate_anim = nullptr;
140+
Button *cut_anim = nullptr;
141+
Button *copy_anim = nullptr;
142+
Button *paste_anim = nullptr;
128143
Button *delete_anim = nullptr;
129144
SpinBox *anim_speed = nullptr;
130145
Button *anim_loop = nullptr;
@@ -217,12 +232,19 @@ class SpriteFramesEditor : public HSplitContainer {
217232
void _animation_name_edited();
218233
void _animation_add();
219234
void _animation_duplicate();
235+
void _animation_cut();
236+
void _animation_copy();
237+
void _animation_paste();
220238
void _animation_remove();
221239
void _animation_remove_confirmed();
222240
void _animation_search_text_changed(const String &p_text);
223241
void _animation_loop_changed();
224242
void _animation_speed_resized();
225243
void _animation_speed_changed(double p_value);
244+
void _animation_remove_undo_redo(const StringName &p_action_name, const Vector<ClipboardSpriteFrames::Frame> *p_frames = nullptr);
245+
246+
StringName _find_next_animation();
247+
String _generate_unique_animation_name(const String &p_base_name) const;
226248

227249
void _frame_list_gui_input(const Ref<InputEvent> &p_event);
228250
void _frame_list_item_selected(int p_index, bool p_selected);

scene/2d/animated_sprite_2d.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) {
565565
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
566566
}
567567

568-
int frame_count = frames->get_frame_count(animation);
569-
if (animation == StringName() || frame_count == 0) {
568+
if (animation == StringName() || frames->get_frame_count(animation) == 0) {
570569
stop();
571570
return;
572571
} else if (!frames->get_animation_names().has(animation)) {
@@ -576,7 +575,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) {
576575
}
577576

578577
if (std::signbit(get_playing_speed())) {
579-
set_frame_and_progress(frame_count - 1, 1.0);
578+
set_frame_and_progress(frames->get_frame_count(animation) - 1, 1.0);
580579
} else {
581580
set_frame_and_progress(0, 0.0);
582581
}

scene/3d/sprite_3d.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1431,8 +1431,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
14311431
ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name));
14321432
}
14331433

1434-
int frame_count = frames->get_frame_count(animation);
1435-
if (animation == StringName() || frame_count == 0) {
1434+
if (animation == StringName() || frames->get_frame_count(animation) == 0) {
14361435
stop();
14371436
return;
14381437
} else if (!frames->get_animation_names().has(animation)) {
@@ -1442,7 +1441,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) {
14421441
}
14431442

14441443
if (std::signbit(get_playing_speed())) {
1445-
set_frame_and_progress(frame_count - 1, 1.0);
1444+
set_frame_and_progress(frames->get_frame_count(animation) - 1, 1.0);
14461445
} else {
14471446
set_frame_and_progress(0, 0.0);
14481447
}

0 commit comments

Comments
 (0)