@@ -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+
12991309void 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+
13391421void 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+ }
0 commit comments