Path: blob/master/editor/scene/sprite_frames_editor_plugin.cpp
10277 views
/**************************************************************************/1/* sprite_frames_editor_plugin.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "sprite_frames_editor_plugin.h"3132#include "core/io/resource_loader.h"33#include "core/os/keyboard.h"34#include "editor/docks/filesystem_dock.h"35#include "editor/docks/scene_tree_dock.h"36#include "editor/editor_node.h"37#include "editor/editor_string_names.h"38#include "editor/editor_undo_redo_manager.h"39#include "editor/file_system/editor_file_system.h"40#include "editor/gui/editor_bottom_panel.h"41#include "editor/gui/editor_file_dialog.h"42#include "editor/settings/editor_command_palette.h"43#include "editor/settings/editor_settings.h"44#include "editor/themes/editor_scale.h"45#include "scene/2d/animated_sprite_2d.h"46#include "scene/3d/sprite_3d.h"47#include "scene/gui/center_container.h"48#include "scene/gui/flow_container.h"49#include "scene/gui/margin_container.h"50#include "scene/gui/option_button.h"51#include "scene/gui/panel_container.h"52#include "scene/gui/separator.h"53#include "scene/resources/atlas_texture.h"5455static void _draw_shadowed_line(Control *p_control, const Point2 &p_from, const Size2 &p_size, const Size2 &p_shadow_offset, Color p_color, Color p_shadow_color) {56p_control->draw_line(p_from, p_from + p_size, p_color);57p_control->draw_line(p_from + p_shadow_offset, p_from + p_size + p_shadow_offset, p_shadow_color);58}5960void SpriteFramesEditor::_open_sprite_sheet() {61file_split_sheet->clear_filters();62List<String> extensions;63ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);64for (const String &extension : extensions) {65file_split_sheet->add_filter("*." + extension);66}6768file_split_sheet->popup_file_dialog();69}7071int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) {72const Size2i offset = _get_offset();73const Size2i frame_size = _get_frame_size();74const Size2i separation = _get_separation();75const Size2i block_size = frame_size + separation;76const Point2i position = p_position / sheet_zoom - offset;7778if (position.x < 0 || position.y < 0) {79return -1; // Out of bounds.80}8182if (position.x % block_size.x >= frame_size.x || position.y % block_size.y >= frame_size.y) {83return -1; // Gap between frames.84}8586const Point2i frame = position / block_size;87const Size2i frame_count = _get_frame_count();88if (frame.x >= frame_count.x || frame.y >= frame_count.y) {89return -1; // Out of bounds.90}9192return frame_count.x * frame.y + frame.x;93}9495void SpriteFramesEditor::_sheet_preview_draw() {96const Size2i frame_count = _get_frame_count();97const Size2i separation = _get_separation();9899const Size2 draw_offset = Size2(_get_offset()) * sheet_zoom;100const Size2 draw_sep = Size2(separation) * sheet_zoom;101const Size2 draw_frame_size = Size2(_get_frame_size()) * sheet_zoom;102const Size2 draw_size = draw_frame_size * frame_count + draw_sep * (frame_count - Size2i(1, 1));103104const Color line_color = Color(1, 1, 1, 0.3);105const Color shadow_color = Color(0, 0, 0, 0.3);106107// Vertical lines.108_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);109for (int i = 0; i < frame_count.x - 1; i++) {110const Point2 start = draw_offset + Vector2(i * draw_sep.x + (i + 1) * draw_frame_size.x, 0);111if (separation.x == 0) {112_draw_shadowed_line(split_sheet_preview, start, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);113} else {114const Size2 size = Size2(draw_sep.x, draw_size.y);115split_sheet_preview->draw_rect(Rect2(start, size), line_color);116}117}118_draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(draw_size.x, 0), Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);119120// Horizontal lines.121_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);122for (int i = 0; i < frame_count.y - 1; i++) {123const Point2 start = draw_offset + Vector2(0, i * draw_sep.y + (i + 1) * draw_frame_size.y);124if (separation.y == 0) {125_draw_shadowed_line(split_sheet_preview, start, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);126} else {127const Size2 size = Size2(draw_size.x, draw_sep.y);128split_sheet_preview->draw_rect(Rect2(start, size), line_color);129}130}131_draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(0, draw_size.y), Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);132133if (frames_selected.is_empty()) {134split_sheet_dialog->get_ok_button()->set_disabled(true);135split_sheet_dialog->set_ok_button_text(TTR("No Frames Selected"));136return;137}138139Color accent = get_theme_color("accent_color", EditorStringName(Editor));140141_sheet_sort_frames();142143Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));144int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));145146for (int i = 0; i < frames_ordered.size(); ++i) {147const int idx = frames_ordered[i].second;148149const int x = idx % frame_count.x;150const int y = idx / frame_count.x;151const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep);152split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 0.35), true);153split_sheet_preview->draw_rect(Rect2(pos, draw_frame_size), Color(0, 0, 0, 1), false);154split_sheet_preview->draw_rect(Rect2(pos + Size2(1, 1), draw_frame_size - Size2(2, 2)), Color(0, 0, 0, 1), false);155split_sheet_preview->draw_rect(Rect2(pos + Size2(2, 2), draw_frame_size - Size2(4, 4)), accent, false);156split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false);157split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false);158split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false);159160const String text = itos(i);161const Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);162163// Stop rendering text if too large.164if (string_size.x + 6 < draw_frame_size.x && string_size.y / 2 + 10 < draw_frame_size.y) {165split_sheet_preview->draw_string_outline(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, 1, Color(0, 0, 0, 1));166split_sheet_preview->draw_string(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, Color(1, 1, 1));167}168}169170split_sheet_dialog->get_ok_button()->set_disabled(false);171split_sheet_dialog->set_ok_button_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size()));172}173174void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {175const Ref<InputEventMouseButton> mb = p_event;176if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {177const int idx = _sheet_preview_position_to_frame_index(mb->get_position());178179if (idx != -1) {180if (mb->is_shift_pressed() && last_frame_selected >= 0) {181// Select multiple frames.182const int from = last_frame_selected;183const int to = idx;184185const int diff = Math::abs(to - from);186const int dir = SIGN(to - from);187188for (int i = 0; i <= diff; i++) {189const int this_idx = from + i * dir;190191// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.192frames_toggled_by_mouse_hover.insert(this_idx);193194if (mb->is_command_or_control_pressed()) {195frames_selected.erase(this_idx);196} else if (!frames_selected.has(this_idx)) {197frames_selected.insert(this_idx, selected_count);198selected_count++;199}200}201} else {202// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.203frames_toggled_by_mouse_hover.insert(idx);204205if (frames_selected.has(idx)) {206frames_selected.erase(idx);207} else {208frames_selected.insert(idx, selected_count);209selected_count++;210}211}212}213214if (last_frame_selected != idx || idx != -1) {215last_frame_selected = idx;216frames_need_sort = true;217split_sheet_preview->queue_redraw();218}219}220221if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {222frames_toggled_by_mouse_hover.clear();223}224225const Ref<InputEventMouseMotion> mm = p_event;226if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {227// Select by holding down the mouse button on frames.228const int idx = _sheet_preview_position_to_frame_index(mm->get_position());229230if (idx != -1 && !frames_toggled_by_mouse_hover.has(idx)) {231// Only allow toggling each tile once per mouse hold.232// Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor.233// The mouse button must be released before it can be toggled again.234frames_toggled_by_mouse_hover.insert(idx);235236if (frames_selected.has(idx)) {237frames_selected.erase(idx);238} else {239frames_selected.insert(idx, selected_count);240selected_count++;241}242243last_frame_selected = idx;244frames_need_sort = true;245split_sheet_preview->queue_redraw();246}247}248249if (frames_selected.is_empty()) {250selected_count = 0;251}252}253254void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {255const Ref<InputEventMouseButton> mb = p_event;256257if (mb.is_valid()) {258// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer259// to allow performing this action anywhere, even if the cursor isn't260// hovering the texture in the workspace.261// keep CTRL and not CMD_OR_CTRL as CTRL is expected even on MacOS.262if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {263_sheet_zoom_on_position(scale_ratio, mb->get_position());264// Don't scroll up after zooming in.265split_sheet_scroll->accept_event();266} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {267_sheet_zoom_on_position(1 / scale_ratio, mb->get_position());268// Don't scroll down after zooming out.269split_sheet_scroll->accept_event();270}271}272273const Ref<InputEventMouseMotion> mm = p_event;274if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {275const Vector2 dragged = Input::get_singleton()->warp_mouse_motion(mm, split_sheet_scroll->get_global_rect());276split_sheet_scroll->set_h_scroll(split_sheet_scroll->get_h_scroll() - dragged.x);277split_sheet_scroll->set_v_scroll(split_sheet_scroll->get_v_scroll() - dragged.y);278}279}280281void SpriteFramesEditor::_sheet_add_frames() {282const Size2i frame_count = _get_frame_count();283const Size2i frame_size = _get_frame_size();284const Size2i offset = _get_offset();285const Size2i separation = _get_separation();286287EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();288undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());289int fc = frames->get_frame_count(edited_anim);290291_sheet_sort_frames();292293for (const Pair<int, int> &pair : frames_ordered) {294const int idx = pair.second;295296const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x);297298Ref<AtlasTexture> at;299at.instantiate();300at->set_atlas(split_sheet_preview->get_texture());301at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size));302303undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, at, 1.0, -1);304undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, fc);305}306307undo_redo->add_do_method(this, "_update_library");308undo_redo->add_undo_method(this, "_update_library");309undo_redo->commit_action();310}311312void SpriteFramesEditor::_sheet_zoom_on_position(float p_zoom, const Vector2 &p_position) {313const float old_zoom = sheet_zoom;314sheet_zoom = CLAMP(sheet_zoom * p_zoom, min_sheet_zoom, max_sheet_zoom);315316const Size2 texture_size = split_sheet_preview->get_texture()->get_size();317split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);318319Vector2 offset = Vector2(split_sheet_scroll->get_h_scroll(), split_sheet_scroll->get_v_scroll());320offset = (offset + p_position) / old_zoom * sheet_zoom - p_position;321split_sheet_scroll->set_h_scroll(offset.x);322split_sheet_scroll->set_v_scroll(offset.y);323}324325void SpriteFramesEditor::_sheet_zoom_in() {326_sheet_zoom_on_position(scale_ratio, Vector2());327}328329void SpriteFramesEditor::_sheet_zoom_out() {330_sheet_zoom_on_position(1 / scale_ratio, Vector2());331}332333void SpriteFramesEditor::_sheet_zoom_reset() {334// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.335sheet_zoom = MAX(1.0f, EDSCALE);336Size2 texture_size = split_sheet_preview->get_texture()->get_size();337split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);338}339340void SpriteFramesEditor::_sheet_order_selected(int p_option) {341frames_need_sort = true;342split_sheet_preview->queue_redraw();343}344345void SpriteFramesEditor::_sheet_select_all_frames() {346for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) {347if (!frames_selected.has(i)) {348frames_selected.insert(i, selected_count);349selected_count++;350frames_need_sort = true;351}352}353354split_sheet_preview->queue_redraw();355}356357void SpriteFramesEditor::_sheet_clear_all_frames() {358frames_selected.clear();359selected_count = 0;360361split_sheet_preview->queue_redraw();362}363364void SpriteFramesEditor::_sheet_sort_frames() {365if (!frames_need_sort) {366return;367}368frames_need_sort = false;369frames_ordered.resize(frames_selected.size());370if (frames_selected.is_empty()) {371return;372}373374const Size2i frame_count = _get_frame_count();375const int frame_order = split_sheet_order->get_selected_id();376int index = 0;377378// Fill based on order.379for (const KeyValue<int, int> &from_pair : frames_selected) {380const int idx = from_pair.key;381382const int selection_order = from_pair.value;383384// Default to using selection order.385int order_by = selection_order;386387// Extract coordinates for sorting.388const int pos_frame_x = idx % frame_count.x;389const int pos_frame_y = idx / frame_count.x;390391const int neg_frame_x = frame_count.x - (pos_frame_x + 1);392const int neg_frame_y = frame_count.y - (pos_frame_y + 1);393394switch (frame_order) {395case FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM: {396order_by = frame_count.x * pos_frame_y + pos_frame_x;397} break;398399case FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP: {400order_by = frame_count.x * neg_frame_y + pos_frame_x;401} break;402403case FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM: {404order_by = frame_count.x * pos_frame_y + neg_frame_x;405} break;406407case FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP: {408order_by = frame_count.x * neg_frame_y + neg_frame_x;409} break;410411case FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT: {412order_by = pos_frame_y + frame_count.y * pos_frame_x;413} break;414415case FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT: {416order_by = pos_frame_y + frame_count.y * neg_frame_x;417} break;418419case FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT: {420order_by = neg_frame_y + frame_count.y * pos_frame_x;421} break;422423case FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT: {424order_by = neg_frame_y + frame_count.y * neg_frame_x;425} break;426}427428// Assign in vector.429frames_ordered.set(index, Pair<int, int>(order_by, idx));430index++;431}432433// Sort frames.434frames_ordered.sort_custom<PairSort<int, int>>();435}436437void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) {438if (updating_split_settings) {439return;440}441updating_split_settings = true;442443if (p_dominant_param != PARAM_USE_CURRENT) {444dominant_param = p_dominant_param;445}446447const Size2i texture_size = split_sheet_preview->get_texture()->get_size();448const Size2i size = texture_size - _get_offset();449450switch (dominant_param) {451case PARAM_SIZE: {452const Size2i frame_size = _get_frame_size();453454const Size2i offset_max = texture_size - frame_size;455split_sheet_offset_x->set_max(offset_max.x);456split_sheet_offset_y->set_max(offset_max.y);457458const Size2i sep_max = size - frame_size * 2;459split_sheet_sep_x->set_max(sep_max.x);460split_sheet_sep_y->set_max(sep_max.y);461462const Size2i separation = _get_separation();463const Size2i count = (size + separation) / (frame_size + separation);464split_sheet_h->set_value(count.x);465split_sheet_v->set_value(count.y);466} break;467468case PARAM_FRAME_COUNT: {469const Size2i count = _get_frame_count();470471const Size2i offset_max = texture_size - count;472split_sheet_offset_x->set_max(offset_max.x);473split_sheet_offset_y->set_max(offset_max.y);474475const Size2i gap_count = count - Size2i(1, 1);476split_sheet_sep_x->set_max(gap_count.x == 0 ? size.x : (size.x - count.x) / gap_count.x);477split_sheet_sep_y->set_max(gap_count.y == 0 ? size.y : (size.y - count.y) / gap_count.y);478479const Size2i separation = _get_separation();480const Size2i frame_size = (size - separation * gap_count) / count;481split_sheet_size_x->set_value(frame_size.x);482split_sheet_size_y->set_value(frame_size.y);483} break;484}485486updating_split_settings = false;487488frames_selected.clear();489selected_count = 0;490last_frame_selected = -1;491split_sheet_preview->queue_redraw();492}493494void SpriteFramesEditor::_toggle_show_settings() {495split_sheet_settings_vb->set_visible(!split_sheet_settings_vb->is_visible());496497_update_show_settings();498}499500void SpriteFramesEditor::_update_show_settings() {501if (is_layout_rtl()) {502toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Back") : SNAME("Forward")));503} else {504toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Forward") : SNAME("Back")));505}506}507508void SpriteFramesEditor::_auto_slice_sprite_sheet() {509if (updating_split_settings) {510return;511}512updating_split_settings = true;513514const Size2i size = split_sheet_preview->get_texture()->get_size();515516const Size2i split_sheet = _estimate_sprite_sheet_size(split_sheet_preview->get_texture());517split_sheet_h->set_value(split_sheet.x);518split_sheet_v->set_value(split_sheet.y);519split_sheet_size_x->set_value(size.x / split_sheet.x);520split_sheet_size_y->set_value(size.y / split_sheet.y);521split_sheet_sep_x->set_value(0);522split_sheet_sep_y->set_value(0);523split_sheet_offset_x->set_value(0);524split_sheet_offset_y->set_value(0);525526updating_split_settings = false;527528frames_selected.clear();529selected_count = 0;530last_frame_selected = -1;531split_sheet_preview->queue_redraw();532}533534bool SpriteFramesEditor::_matches_background_color(const Color &p_background_color, const Color &p_pixel_color) {535if ((p_background_color.a == 0 && p_pixel_color.a == 0) || p_background_color.is_equal_approx(p_pixel_color)) {536return true;537}538539Color d = p_background_color - p_pixel_color;540// 0.04f is the threshold for how much a colour can deviate from background colour and still be considered a match. Arrived at through experimentation, can be tweaked.541return (d.r * d.r) + (d.g * d.g) + (d.b * d.b) + (d.a * d.a) < 0.04f;542}543544Size2i SpriteFramesEditor::_estimate_sprite_sheet_size(const Ref<Texture2D> p_texture) {545Ref<Image> image = p_texture->get_image();546if (image->is_compressed()) {547image = image->duplicate();548ERR_FAIL_COND_V(image->decompress() != OK, p_texture->get_size());549}550Size2i size = image->get_size();551552Color assumed_background_color = image->get_pixel(0, 0);553Size2i sheet_size;554555bool previous_line_background = true;556for (int x = 0; x < size.x; x++) {557int y = 0;558while (y < size.y && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {559y++;560}561bool current_line_background = (y == size.y);562if (previous_line_background && !current_line_background) {563sheet_size.x++;564}565previous_line_background = current_line_background;566}567568previous_line_background = true;569for (int y = 0; y < size.y; y++) {570int x = 0;571while (x < size.x && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {572x++;573}574bool current_line_background = (x == size.x);575if (previous_line_background && !current_line_background) {576sheet_size.y++;577}578previous_line_background = current_line_background;579}580581if (sheet_size == Size2i(0, 0) || sheet_size == Size2i(1, 1)) {582sheet_size = Size2i(4, 4);583}584585return sheet_size;586}587588void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {589Ref<Texture2D> texture = ResourceLoader::load(p_file);590if (texture.is_null()) {591EditorNode::get_singleton()->show_warning(TTR("Unable to load images"));592ERR_FAIL_COND(texture.is_null());593}594frames_selected.clear();595selected_count = 0;596last_frame_selected = -1;597598bool new_texture = texture != split_sheet_preview->get_texture();599split_sheet_preview->set_texture(texture);600if (new_texture) {601// Reset spin max.602const Size2i size = texture->get_size();603split_sheet_size_x->set_max(size.x);604split_sheet_size_y->set_max(size.y);605split_sheet_sep_x->set_max(size.x);606split_sheet_sep_y->set_max(size.y);607split_sheet_offset_x->set_max(size.x);608split_sheet_offset_y->set_max(size.y);609610if (size != previous_texture_size) {611// Different texture, reset to 4x4.612dominant_param = PARAM_FRAME_COUNT;613updating_split_settings = true;614const Size2i split_sheet = Size2i(4, 4);615split_sheet_h->set_value(split_sheet.x);616split_sheet_v->set_value(split_sheet.y);617split_sheet_size_x->set_value(size.x / split_sheet.x);618split_sheet_size_y->set_value(size.y / split_sheet.y);619split_sheet_sep_x->set_value(0);620split_sheet_sep_y->set_value(0);621split_sheet_offset_x->set_value(0);622split_sheet_offset_y->set_value(0);623updating_split_settings = false;624}625previous_texture_size = size;626627// Reset zoom.628_sheet_zoom_reset();629}630631split_sheet_dialog->popup_centered_ratio(0.65);632}633634void SpriteFramesEditor::_notification(int p_what) {635switch (p_what) {636case NOTIFICATION_ENTER_TREE: {637get_tree()->connect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));638639[[fallthrough]];640}641case NOTIFICATION_THEME_CHANGED: {642autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));643stop_icon = get_editor_theme_icon(SNAME("Stop"));644pause_icon = get_editor_theme_icon(SNAME("Pause"));645_update_stop_icon();646647autoplay->set_button_icon(get_editor_theme_icon(SNAME("AutoPlay")));648anim_loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));649play->set_button_icon(get_editor_theme_icon(SNAME("PlayStart")));650play_from->set_button_icon(get_editor_theme_icon(SNAME("Play")));651play_bw->set_button_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));652play_bw_from->set_button_icon(get_editor_theme_icon(SNAME("PlayBackwards")));653654load->set_button_icon(get_editor_theme_icon(SNAME("Load")));655load_sheet->set_button_icon(get_editor_theme_icon(SNAME("SpriteSheet")));656copy->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));657paste->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));658empty_before->set_button_icon(get_editor_theme_icon(SNAME("InsertBefore")));659empty_after->set_button_icon(get_editor_theme_icon(SNAME("InsertAfter")));660move_up->set_button_icon(get_editor_theme_icon(SNAME("MoveLeft")));661move_down->set_button_icon(get_editor_theme_icon(SNAME("MoveRight")));662delete_frame->set_button_icon(get_editor_theme_icon(SNAME("Remove")));663zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));664zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));665zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));666add_anim->set_button_icon(get_editor_theme_icon(SNAME("New")));667duplicate_anim->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));668delete_anim->set_button_icon(get_editor_theme_icon(SNAME("Remove")));669anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));670split_sheet_zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));671split_sheet_zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));672split_sheet_zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));673split_sheet_scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));674675_update_show_settings();676} break;677678case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:679case NOTIFICATION_TRANSLATION_CHANGED: {680_update_show_settings();681} break;682683case NOTIFICATION_READY: {684add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.685} break;686687case NOTIFICATION_EXIT_TREE: {688get_tree()->disconnect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));689} break;690}691}692693void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_at_pos) {694ERR_FAIL_COND(!frames->has_animation(edited_anim));695696List<Ref<Texture2D>> resources;697698for (int i = 0; i < p_path.size(); i++) {699Ref<Texture2D> resource;700resource = ResourceLoader::load(p_path[i]);701702if (resource.is_null()) {703dialog->set_text(TTR("ERROR: Couldn't load frame resource!"));704dialog->set_title(TTR("Error!"));705706//dialog->get_cancel()->set_text("Close");707dialog->set_ok_button_text(TTR("Close"));708dialog->popup_centered();709return; ///beh should show an error i guess710}711712resources.push_back(resource);713}714715if (resources.is_empty()) {716return;717}718719EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();720undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());721int fc = frames->get_frame_count(edited_anim);722723int count = 0;724725for (const Ref<Texture2D> &E : resources) {726undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count);727undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos);728count++;729}730undo_redo->add_do_method(this, "_update_library");731undo_redo->add_undo_method(this, "_update_library");732733undo_redo->commit_action();734}735736Size2i SpriteFramesEditor::_get_frame_count() const {737return Size2i(split_sheet_h->get_value(), split_sheet_v->get_value());738}739740Size2i SpriteFramesEditor::_get_frame_size() const {741return Size2i(split_sheet_size_x->get_value(), split_sheet_size_y->get_value());742}743744Size2i SpriteFramesEditor::_get_offset() const {745return Size2i(split_sheet_offset_x->get_value(), split_sheet_offset_y->get_value());746}747748Size2i SpriteFramesEditor::_get_separation() const {749return Size2i(split_sheet_sep_x->get_value(), split_sheet_sep_y->get_value());750}751752void SpriteFramesEditor::_load_pressed() {753ERR_FAIL_COND(!frames->has_animation(edited_anim));754loading_scene = false;755756file->clear_filters();757List<String> extensions;758ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);759for (const String &extension : extensions) {760file->add_filter("*." + extension);761}762763file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);764file->popup_file_dialog();765}766767void SpriteFramesEditor::_paste_pressed() {768ERR_FAIL_COND(!frames->has_animation(edited_anim));769770Ref<ClipboardSpriteFrames> clipboard_frames = EditorSettings::get_singleton()->get_resource_clipboard();771if (clipboard_frames.is_valid()) {772_paste_frame_array(clipboard_frames);773return;774}775776Ref<Texture2D> texture = EditorSettings::get_singleton()->get_resource_clipboard();777if (texture.is_valid()) {778_paste_texture(texture);779return;780}781}782783void SpriteFramesEditor::_paste_frame_array(const Ref<ClipboardSpriteFrames> &p_clipboard_frames) {784if (p_clipboard_frames->frames.is_empty()) {785return;786}787788Ref<Texture2D> texture;789float duration = 1.0;790791EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();792undo_redo->create_action(TTR("Paste Frame(s)"), UndoRedo::MERGE_DISABLE, frames.ptr());793794int undo_index = frames->get_frame_count(edited_anim);795796for (int index = 0; index < p_clipboard_frames->frames.size(); index++) {797const ClipboardSpriteFrames::Frame &frame = p_clipboard_frames->frames[index];798texture = frame.texture;799duration = frame.duration;800801undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration);802undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);803}804805undo_redo->add_do_method(this, "_update_library");806undo_redo->add_undo_method(this, "_update_library");807undo_redo->commit_action();808}809810void SpriteFramesEditor::_paste_texture(const Ref<Texture2D> &p_texture) {811float duration = 1.0;812813EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();814undo_redo->create_action(TTR("Paste Texture"), UndoRedo::MERGE_DISABLE, frames.ptr());815816int undo_index = frames->get_frame_count(edited_anim);817818undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, p_texture, duration);819undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);820821undo_redo->add_do_method(this, "_update_library");822undo_redo->add_undo_method(this, "_update_library");823undo_redo->commit_action();824}825826void SpriteFramesEditor::_copy_pressed() {827ERR_FAIL_COND(!frames->has_animation(edited_anim));828829Vector<int> selected_items = frame_list->get_selected_items();830831if (selected_items.is_empty()) {832return;833}834835Ref<ClipboardSpriteFrames> clipboard_frames = memnew(ClipboardSpriteFrames);836837for (const int &frame_index : selected_items) {838Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, frame_index);839if (texture.is_null()) {840continue;841}842843ClipboardSpriteFrames::Frame frame;844frame.texture = texture;845frame.duration = frames->get_frame_duration(edited_anim, frame_index);846847clipboard_frames->frames.push_back(frame);848}849EditorSettings::get_singleton()->set_resource_clipboard(clipboard_frames);850}851852void SpriteFramesEditor::_empty_pressed() {853ERR_FAIL_COND(!frames->has_animation(edited_anim));854855int from = -1;856Vector<int> selected_items = frame_list->get_selected_items();857858if (!selected_items.is_empty()) {859from = selected_items[0];860selection.clear();861selection.push_back(from + 1);862} else {863from = frames->get_frame_count(edited_anim);864}865866Ref<Texture2D> texture;867868EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();869undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());870undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from);871undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from);872undo_redo->add_do_method(this, "_update_library");873undo_redo->add_undo_method(this, "_update_library");874undo_redo->commit_action();875}876877void SpriteFramesEditor::_empty2_pressed() {878ERR_FAIL_COND(!frames->has_animation(edited_anim));879880int from = -1;881Vector<int> selected_items = frame_list->get_selected_items();882883if (!selected_items.is_empty()) {884from = selected_items[selected_items.size() - 1];885selection.clear();886selection.push_back(from);887} else {888from = frames->get_frame_count(edited_anim);889}890891Ref<Texture2D> texture;892893EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();894undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());895undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from + 1);896undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from + 1);897undo_redo->add_do_method(this, "_update_library");898undo_redo->add_undo_method(this, "_update_library");899undo_redo->commit_action();900}901902void SpriteFramesEditor::_up_pressed() {903ERR_FAIL_COND(!frames->has_animation(edited_anim));904905Vector<int> selected_items = frame_list->get_selected_items();906907int nb_selected_items = selected_items.size();908if (nb_selected_items <= 0) {909return;910}911912int first_selected_frame_index = selected_items[0];913if (first_selected_frame_index < 1) {914return;915}916917EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();918undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());919920int last_overwritten_frame = -1;921922for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {923int to_move = selected_items[selected_index];924int new_index = to_move - 1;925selected_items.set(selected_index, new_index);926927undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));928undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));929930bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == to_move + 1;931if (last_overwritten_frame == -1) {932last_overwritten_frame = new_index;933}934935if (!is_next_item_in_selection) {936undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, last_overwritten_frame), frames->get_frame_duration(edited_anim, last_overwritten_frame));937undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, to_move, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));938last_overwritten_frame = -1;939}940}941selection = selected_items;942943undo_redo->add_do_method(this, "_update_library");944undo_redo->add_undo_method(this, "_update_library");945undo_redo->commit_action();946}947948void SpriteFramesEditor::_down_pressed() {949ERR_FAIL_COND(!frames->has_animation(edited_anim));950951Vector<int> selected_items = frame_list->get_selected_items();952953int nb_selected_items = selected_items.size();954if (nb_selected_items <= 0) {955return;956}957958int last_selected_frame_index = selected_items[nb_selected_items - 1];959if (last_selected_frame_index >= frames->get_frame_count(edited_anim) - 1) {960return;961}962963EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();964undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());965966int first_moved_frame = -1;967968for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {969int to_move = selected_items[selected_index];970int new_index = to_move + 1;971selected_items.set(selected_index, new_index);972973undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, to_move), frames->get_frame_duration(edited_anim, to_move));974undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, new_index, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));975976bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == new_index;977if (first_moved_frame == -1) {978first_moved_frame = to_move;979}980981if (!is_next_item_in_selection) {982undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, first_moved_frame, frames->get_frame_texture(edited_anim, new_index), frames->get_frame_duration(edited_anim, new_index));983undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, first_moved_frame, frames->get_frame_texture(edited_anim, first_moved_frame), frames->get_frame_duration(edited_anim, first_moved_frame));984first_moved_frame = -1;985}986}987selection = selected_items;988989undo_redo->add_do_method(this, "_update_library");990undo_redo->add_undo_method(this, "_update_library");991undo_redo->commit_action();992}993994void SpriteFramesEditor::_delete_pressed() {995ERR_FAIL_COND(!frames->has_animation(edited_anim));996997Vector<int> selected_items = frame_list->get_selected_items();998999int nb_selected_items = selected_items.size();1000if (nb_selected_items <= 0) {1001return;1002}10031004EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1005undo_redo->create_action(TTR("Delete Resource"), UndoRedo::MERGE_DISABLE, frames.ptr());1006for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {1007int to_delete = selected_items[selected_index];1008undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, to_delete - selected_index);1009undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, frames->get_frame_texture(edited_anim, to_delete), frames->get_frame_duration(edited_anim, to_delete), to_delete);1010}10111012undo_redo->add_do_method(this, "_update_library");1013undo_redo->add_undo_method(this, "_update_library");1014undo_redo->commit_action();1015}10161017void SpriteFramesEditor::_animation_selected() {1018if (updating) {1019return;1020}10211022TreeItem *selected = animations->get_selected();1023ERR_FAIL_NULL(selected);1024edited_anim = selected->get_text(0);10251026if (animated_sprite) {1027sprite_node_updating = true;1028animated_sprite->call("set_animation", edited_anim);1029sprite_node_updating = false;1030}10311032_update_library(true);1033}10341035void SpriteFramesEditor::_sync_animation() {1036if (!animated_sprite || sprite_node_updating) {1037return;1038}1039_select_animation(animated_sprite->call("get_animation"), false);1040_update_stop_icon();1041}10421043void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) {1044if (frames.is_null() || !frames->has_animation(p_name)) {1045return;1046}1047edited_anim = p_name;10481049if (animated_sprite) {1050if (p_update_node) {1051animated_sprite->call("set_animation", edited_anim);1052}1053}10541055_update_library();1056}10571058static void _find_anim_sprites(Node *p_node, List<Node *> *r_nodes, Ref<SpriteFrames> p_sfames) {1059Node *edited = EditorNode::get_singleton()->get_edited_scene();1060if (!edited) {1061return;1062}1063if (p_node != edited && p_node->get_owner() != edited) {1064return;1065}10661067{1068AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node);1069if (as && as->get_sprite_frames() == p_sfames) {1070r_nodes->push_back(p_node);1071}1072}10731074{1075AnimatedSprite3D *as = Object::cast_to<AnimatedSprite3D>(p_node);1076if (as && as->get_sprite_frames() == p_sfames) {1077r_nodes->push_back(p_node);1078}1079}10801081for (int i = 0; i < p_node->get_child_count(); i++) {1082_find_anim_sprites(p_node->get_child(i), r_nodes, p_sfames);1083}1084}10851086void SpriteFramesEditor::_animation_name_edited() {1087if (updating) {1088return;1089}10901091if (!frames->has_animation(edited_anim)) {1092return;1093}10941095TreeItem *edited = animations->get_edited();1096if (!edited) {1097return;1098}10991100String new_name = edited->get_text(0);11011102if (new_name == String(edited_anim)) {1103return;1104}11051106if (new_name.is_empty()) {1107new_name = "new_animation";1108}11091110new_name = new_name.replace_char('/', '_').replace_char(',', ' ');11111112String name = new_name;1113int counter = 0;1114while (frames->has_animation(name)) {1115if (name == String(edited_anim)) {1116edited->set_text(0, name); // The name didn't change, just updated the column text to name.1117return;1118}1119counter++;1120name = new_name + "_" + itos(counter);1121}1122edited->set_text(0, name);11231124EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1125undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());1126undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name);1127undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim);1128_rename_node_animation(undo_redo, false, edited_anim, name, name);1129_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);1130undo_redo->add_do_method(this, "_select_animation", name);1131undo_redo->add_undo_method(this, "_select_animation", edited_anim);1132undo_redo->add_do_method(this, "_update_library");1133undo_redo->add_undo_method(this, "_update_library");1134undo_redo->commit_action();11351136animations->grab_focus();1137}11381139void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay) {1140List<Node *> nodes;1141_find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames));11421143if (is_undo) {1144for (Node *E : nodes) {1145String current_name = E->call("get_animation");1146if (current_name == p_filter) {1147undo_redo->force_fixed_history(); // Fixes corner-case when editing SpriteFrames stored as separate file.1148undo_redo->add_undo_method(E, "set_animation", p_new_animation);1149}1150String autoplay_name = E->call("get_autoplay");1151if (autoplay_name == p_filter) {1152undo_redo->force_fixed_history();1153undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay);1154}1155}1156} else {1157for (Node *E : nodes) {1158String current_name = E->call("get_animation");1159if (current_name == p_filter) {1160undo_redo->force_fixed_history();1161undo_redo->add_do_method(E, "set_animation", p_new_animation);1162}1163String autoplay_name = E->call("get_autoplay");1164if (autoplay_name == p_filter) {1165undo_redo->force_fixed_history();1166undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay);1167}1168}1169}1170}11711172void SpriteFramesEditor::_animation_add() {1173String name = "new_animation";1174int counter = 0;1175while (frames->has_animation(name)) {1176counter++;1177name = vformat("new_animation_%d", counter);1178}11791180EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1181undo_redo->create_action(TTR("Add Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());1182undo_redo->add_do_method(frames.ptr(), "add_animation", name);1183undo_redo->add_undo_method(frames.ptr(), "remove_animation", name);1184undo_redo->add_do_method(this, "_select_animation", name);1185undo_redo->add_undo_method(this, "_select_animation", edited_anim);1186undo_redo->add_do_method(this, "_update_library");1187undo_redo->add_undo_method(this, "_update_library");1188undo_redo->commit_action();11891190animations->grab_focus();1191}11921193void SpriteFramesEditor::_animation_duplicate() {1194if (updating) {1195return;1196}11971198if (!frames->has_animation(edited_anim)) {1199return;1200}12011202int counter = 1;1203String new_name = edited_anim;1204PackedStringArray name_component = new_name.rsplit("_", true, 1);1205String base_name = name_component[0];1206if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) {1207counter = name_component[1].to_int();1208}1209new_name = base_name + "_" + itos(counter);1210while (frames->has_animation(new_name)) {1211counter++;1212new_name = base_name + "_" + itos(counter);1213}12141215EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1216undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());1217undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);1218undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);1219undo_redo->add_do_method(this, "_select_animation", new_name);1220undo_redo->add_undo_method(this, "_select_animation", edited_anim);1221undo_redo->add_do_method(this, "_update_library");1222undo_redo->add_undo_method(this, "_update_library");1223undo_redo->commit_action();12241225animations->grab_focus();1226}12271228void SpriteFramesEditor::_animation_remove() {1229if (updating) {1230return;1231}12321233if (!frames->has_animation(edited_anim)) {1234return;1235}12361237delete_dialog->set_text(TTR("Delete Animation?"));1238delete_dialog->popup_centered();1239}12401241void SpriteFramesEditor::_animation_remove_confirmed() {1242StringName new_edited;1243List<StringName> anim_names;1244frames->get_animation_list(&anim_names);1245anim_names.sort_custom<StringName::AlphCompare>();1246if (anim_names.size() >= 2) {1247if (edited_anim == anim_names.get(0)) {1248new_edited = anim_names.get(1);1249} else {1250new_edited = anim_names.get(0);1251}1252} else {1253new_edited = StringName();1254}12551256EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1257undo_redo->create_action(TTR("Remove Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());1258_rename_node_animation(undo_redo, false, edited_anim, new_edited, "");1259undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim);1260undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim);1261_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);1262undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));1263undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));1264int fc = frames->get_frame_count(edited_anim);1265for (int i = 0; i < fc; i++) {1266Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);1267float duration = frames->get_frame_duration(edited_anim, i);1268undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration);1269}1270undo_redo->add_do_method(this, "_select_animation", new_edited);1271undo_redo->add_undo_method(this, "_select_animation", edited_anim);1272undo_redo->add_do_method(this, "_update_library");1273undo_redo->add_undo_method(this, "_update_library");1274undo_redo->commit_action();1275}12761277void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) {1278_update_library();1279}12801281void SpriteFramesEditor::_animation_loop_changed() {1282if (updating) {1283return;1284}12851286EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1287undo_redo->create_action(TTR("Change Animation Loop"), UndoRedo::MERGE_DISABLE, frames.ptr());1288undo_redo->add_do_method(frames.ptr(), "set_animation_loop", edited_anim, anim_loop->is_pressed());1289undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));1290undo_redo->add_do_method(this, "_update_library", true);1291undo_redo->add_undo_method(this, "_update_library", true);1292undo_redo->commit_action();1293}12941295void SpriteFramesEditor::_animation_speed_resized() {1296anim_speed->update_minimum_size();1297}12981299void SpriteFramesEditor::_animation_speed_changed(double p_value) {1300if (frames.is_null()) {1301return;1302}13031304if (updating) {1305return;1306}13071308EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1309undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS, frames.ptr());1310undo_redo->add_do_method(frames.ptr(), "set_animation_speed", edited_anim, p_value);1311undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));1312undo_redo->add_do_method(this, "_update_library", true);1313undo_redo->add_undo_method(this, "_update_library", true);1314undo_redo->commit_action();1315}13161317void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) {1318const Ref<InputEventMouseButton> mb = p_event;13191320if (mb.is_valid()) {1321if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {1322_zoom_in();1323// Don't scroll up after zooming in.1324accept_event();1325} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {1326_zoom_out();1327// Don't scroll down after zooming out.1328accept_event();1329} else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {1330Point2 pos = mb->get_position();1331right_clicked_frame = frame_list->get_item_at_position(pos, true);1332if (right_clicked_frame != -1) {1333Ref<Texture2D> tex = frames->get_frame_texture(edited_anim, right_clicked_frame);1334if (tex.is_null()) {1335return;1336}1337if (!menu) {1338menu = memnew(PopupMenu);1339add_child(menu);1340menu->connect(SceneStringName(id_pressed), callable_mp(this, &SpriteFramesEditor::_menu_selected));1341menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTRC("Show in FileSystem"), MENU_SHOW_IN_FILESYSTEM);1342}13431344menu->set_position(get_screen_position() + get_local_mouse_position());1345menu->popup();1346}1347}1348}1349}13501351void SpriteFramesEditor::_menu_selected(int p_id) {1352switch (p_id) {1353case MENU_SHOW_IN_FILESYSTEM: {1354Ref<Texture2D> frame_texture = frames->get_frame_texture(edited_anim, right_clicked_frame);1355ERR_FAIL_COND(frame_texture.is_null());1356String path = frame_texture->get_path();1357// Check if the file is an atlas resource, if it is find the source texture.1358Ref<AtlasTexture> at = frame_texture;1359while (at.is_valid() && at->get_atlas().is_valid()) {1360path = at->get_atlas()->get_path();1361at = at->get_atlas();1362}1363FileSystemDock::get_singleton()->navigate_to_path(path);1364} break;1365}1366}13671368void SpriteFramesEditor::_frame_list_item_selected(int p_index, bool p_selected) {1369if (updating) {1370return;1371}13721373selection = frame_list->get_selected_items();1374if (selection.is_empty() || !p_selected) {1375return;1376}13771378updating = true;1379frame_duration->set_value(frames->get_frame_duration(edited_anim, selection[0]));1380updating = false;1381}13821383void SpriteFramesEditor::_frame_duration_changed(double p_value) {1384if (frames.is_null()) {1385return;1386}13871388if (updating) {1389return;1390}13911392if (selection.is_empty()) {1393return;1394}13951396EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1397undo_redo->create_action(TTR("Set Frame Duration"), UndoRedo::MERGE_ENDS, frames.ptr());13981399for (const int &index : selection) {1400Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, index);1401float old_duration = frames->get_frame_duration(edited_anim, index);14021403undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, index, texture, p_value);1404undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, index, texture, old_duration);1405}14061407undo_redo->add_do_method(this, "_update_library");1408undo_redo->add_undo_method(this, "_update_library");1409undo_redo->commit_action();1410}14111412void SpriteFramesEditor::_zoom_in() {1413// Do not zoom in or out with no visible frames1414if (frames->get_frame_count(edited_anim) <= 0) {1415return;1416}1417if (thumbnail_zoom < max_thumbnail_zoom) {1418thumbnail_zoom *= scale_ratio;1419int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);1420frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);1421frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));1422}1423}14241425void SpriteFramesEditor::_zoom_out() {1426// Do not zoom in or out with no visible frames1427if (frames->get_frame_count(edited_anim) <= 0) {1428return;1429}1430if (thumbnail_zoom > min_thumbnail_zoom) {1431thumbnail_zoom /= scale_ratio;1432int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);1433frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);1434frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));1435}1436}14371438void SpriteFramesEditor::_zoom_reset() {1439thumbnail_zoom = MAX(1.0f, EDSCALE);1440frame_list->set_fixed_column_width(thumbnail_default_size * 3 / 2);1441frame_list->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));1442}14431444void SpriteFramesEditor::_update_library(bool p_skip_selector) {1445if (!p_skip_selector) {1446animations_dirty = true;1447}14481449if (pending_update) {1450return;1451}1452pending_update = true;1453callable_mp(this, &SpriteFramesEditor::_update_library_impl).call_deferred();1454}14551456void SpriteFramesEditor::_update_library_impl() {1457pending_update = false;14581459if (frames.is_null()) {1460return;1461}14621463updating = true;14641465frame_duration->set_value_no_signal(1.0); // Default.14661467if (animations_dirty) {1468animations_dirty = false;1469animations->clear();14701471TreeItem *anim_root = animations->create_item();14721473List<StringName> anim_names;1474frames->get_animation_list(&anim_names);1475anim_names.sort_custom<StringName::AlphCompare>();1476if (!anim_names.size()) {1477missing_anim_label->show();1478anim_frames_vb->hide();1479return;1480}1481missing_anim_label->hide();1482anim_frames_vb->show();1483bool searching = anim_search_box->get_text().size();1484String searched_string = searching ? anim_search_box->get_text().to_lower() : String();14851486TreeItem *selected = nullptr;1487for (const StringName &E : anim_names) {1488String name = E;1489if (searching && !name.to_lower().contains(searched_string)) {1490continue;1491}1492TreeItem *it = animations->create_item(anim_root);1493it->set_metadata(0, name);1494it->set_text(0, name);1495it->set_editable(0, true);1496if (animated_sprite) {1497if (name == String(animated_sprite->call("get_autoplay"))) {1498it->set_icon(0, autoplay_icon);1499}1500}1501if (E == edited_anim) {1502it->select(0);1503selected = it;1504}1505}1506if (selected) {1507animations->scroll_to_item(selected);1508}1509}15101511if (animated_sprite) {1512String autoplay_name = animated_sprite->call("get_autoplay");1513if (autoplay_name.is_empty()) {1514autoplay->set_pressed_no_signal(false);1515} else {1516autoplay->set_pressed_no_signal(String(edited_anim) == autoplay_name);1517}1518}15191520frame_list->clear();15211522if (!frames->has_animation(edited_anim)) {1523updating = false;1524return;1525}15261527int anim_frame_count = frames->get_frame_count(edited_anim);1528if (anim_frame_count == 0) {1529selection.clear();1530}15311532for (int index = 0; index < selection.size(); index++) {1533int sel = selection[index];1534if (sel == -1) {1535selection.remove_at(index);1536index--;1537}1538if (sel >= anim_frame_count) {1539selection.set(index, anim_frame_count - 1);1540// Since selection is ordered, if we get a frame that is outside of the range1541// we can clip all the other one.1542selection.resize(index + 1);1543break;1544}1545}15461547if (selection.is_empty() && frames->get_frame_count(edited_anim)) {1548selection.push_back(0);1549}15501551bool is_first_selection = true;1552for (int i = 0; i < frames->get_frame_count(edited_anim); i++) {1553String name = itos(i);1554Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);1555float duration = frames->get_frame_duration(edited_anim, i);15561557if (texture.is_null()) {1558texture = empty_icon;1559name += ": " + TTR("(empty)");1560} else if (!texture->get_name().is_empty()) {1561name += ": " + texture->get_name();1562}15631564if (duration != 1.0f) {1565name += String::utf8(" [× ") + String::num(duration, 2) + "]";1566}15671568frame_list->add_item(name, texture);1569if (texture.is_valid()) {1570String tooltip = texture->get_path();15711572// Frame is often saved as an AtlasTexture subresource within a scene/resource file,1573// thus its path might be not what the user is looking for. So we're also showing1574// subsequent source texture paths.1575String prefix = U"┖╴";1576Ref<AtlasTexture> at = texture;1577while (at.is_valid() && at->get_atlas().is_valid()) {1578tooltip += "\n" + prefix + at->get_atlas()->get_path();1579prefix = " " + prefix;1580at = at->get_atlas();1581}15821583frame_list->set_item_tooltip(-1, tooltip);1584}1585if (selection.has(i)) {1586frame_list->select(frame_list->get_item_count() - 1, is_first_selection);1587if (is_first_selection) {1588frame_duration->set_value_no_signal(frames->get_frame_duration(edited_anim, i));1589}1590is_first_selection = false;1591}1592}15931594anim_speed->set_value_no_signal(frames->get_animation_speed(edited_anim));1595anim_loop->set_pressed_no_signal(frames->get_animation_loop(edited_anim));15961597updating = false;1598}15991600void SpriteFramesEditor::_edit() {1601if (!animated_sprite) {1602return;1603}1604edit(animated_sprite->call("get_sprite_frames"));1605}16061607void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {1608_update_stop_icon();16091610if (p_frames.is_null()) {1611frames.unref();1612_remove_sprite_node();1613return;1614}16151616frames = p_frames;1617read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames);16181619if (!p_frames->has_animation(edited_anim)) {1620List<StringName> anim_names;1621frames->get_animation_list(&anim_names);1622anim_names.sort_custom<StringName::AlphCompare>();1623if (anim_names.size()) {1624edited_anim = anim_names.front()->get();1625} else {1626edited_anim = StringName();1627}1628}16291630_update_library();1631// Clear zoom and split sheet texture1632split_sheet_preview->set_texture(Ref<Texture2D>());1633_zoom_reset();16341635add_anim->set_disabled(read_only);1636duplicate_anim->set_disabled(read_only);1637delete_anim->set_disabled(read_only);1638anim_speed->set_editable(!read_only);1639anim_loop->set_disabled(read_only);1640load->set_disabled(read_only);1641load_sheet->set_disabled(read_only);1642copy->set_disabled(read_only);1643paste->set_disabled(read_only);1644empty_before->set_disabled(read_only);1645empty_after->set_disabled(read_only);1646move_up->set_disabled(read_only);1647move_down->set_disabled(read_only);1648delete_frame->set_disabled(read_only);16491650_fetch_sprite_node(); // Fetch node after set frames.1651}16521653Ref<SpriteFrames> SpriteFramesEditor::get_sprite_frames() const {1654return frames;1655}16561657Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {1658if (read_only) {1659return false;1660}16611662if (!frames->has_animation(edited_anim)) {1663return false;1664}16651666int idx = -1;1667if (p_point == Vector2(Math::INF, Math::INF)) {1668if (frame_list->is_anything_selected()) {1669idx = frame_list->get_selected_items()[0];1670}1671} else {1672idx = frame_list->get_item_at_position(p_point, true);1673}16741675if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) {1676return Variant();1677}16781679Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx);16801681if (frame.is_null()) {1682return Variant();1683}16841685Dictionary drag_data = EditorNode::get_singleton()->drag_resource(frame, p_from);1686drag_data["frame"] = idx; // store the frame, in case we want to reorder frames inside 'drop_data_fw'1687return drag_data;1688}16891690bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {1691if (read_only) {1692return false;1693}16941695Dictionary d = p_data;16961697if (!d.has("type")) {1698return false;1699}17001701// reordering frames1702if (d.has("from") && (Object *)(d["from"]) == frame_list) {1703return true;1704}17051706if (String(d["type"]) == "resource" && d.has("resource")) {1707Ref<Resource> r = d["resource"];17081709Ref<Texture2D> texture = r;17101711if (texture.is_valid()) {1712return true;1713}1714}17151716if (String(d["type"]) == "files") {1717Vector<String> files = d["files"];17181719if (files.is_empty()) {1720return false;1721}17221723for (int i = 0; i < files.size(); i++) {1724const String &f = files[i];1725String ftype = EditorFileSystem::get_singleton()->get_file_type(f);17261727if (!ClassDB::is_parent_class(ftype, "Texture2D")) {1728return false;1729}1730}17311732return true;1733}1734return false;1735}17361737void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {1738if (!can_drop_data_fw(p_point, p_data, p_from)) {1739return;1740}17411742Dictionary d = p_data;17431744if (!d.has("type")) {1745return;1746}17471748int at_pos = -1;1749if (p_point == Vector2(Math::INF, Math::INF)) {1750if (frame_list->is_anything_selected()) {1751at_pos = frame_list->get_selected_items()[0];1752}1753} else {1754at_pos = frame_list->get_item_at_position(p_point, true);1755}17561757if (String(d["type"]) == "resource" && d.has("resource")) {1758Ref<Resource> r = d["resource"];17591760Ref<Texture2D> texture = r;17611762if (texture.is_valid()) {1763bool reorder = false;1764if (d.has("from") && (Object *)(d["from"]) == frame_list) {1765reorder = true;1766}17671768EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1769if (reorder) { //drop is from reordering frames1770int from_frame = -1;1771float duration = 1.0;1772if (d.has("frame")) {1773from_frame = d["frame"];1774duration = frames->get_frame_duration(edited_anim, from_frame);1775}17761777undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1778undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame);1779undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos);1780undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos);1781undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame);1782undo_redo->add_do_method(this, "_update_library");1783undo_redo->add_undo_method(this, "_update_library");1784undo_redo->commit_action();1785} else {1786undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1787undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos);1788undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos);1789undo_redo->add_do_method(this, "_update_library");1790undo_redo->add_undo_method(this, "_update_library");1791undo_redo->commit_action();1792}1793}1794}17951796if (String(d["type"]) == "files") {1797Vector<String> files = d["files"];17981799if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {1800_prepare_sprite_sheet(files[0]);1801} else {1802_file_load_request(files, at_pos);1803}1804}1805}18061807void SpriteFramesEditor::_update_stop_icon() {1808bool is_playing = false;1809if (animated_sprite) {1810is_playing = animated_sprite->call("is_playing");1811}1812if (is_playing) {1813stop->set_button_icon(pause_icon);1814} else {1815stop->set_button_icon(stop_icon);1816}1817}18181819void SpriteFramesEditor::_remove_sprite_node() {1820if (!animated_sprite) {1821return;1822}1823if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {1824animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));1825}1826if (animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {1827animated_sprite->disconnect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation));1828}1829if (animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {1830animated_sprite->disconnect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));1831}1832animated_sprite = nullptr;1833}18341835void SpriteFramesEditor::_fetch_sprite_node() {1836Node *selected = nullptr;1837EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();1838const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();1839if (top_node_list.size() == 1) {1840selected = top_node_list.front()->get();1841}18421843bool show_node_edit = false;1844AnimatedSprite2D *as2d = Object::cast_to<AnimatedSprite2D>(selected);1845AnimatedSprite3D *as3d = Object::cast_to<AnimatedSprite3D>(selected);1846if (as2d || as3d) {1847if (frames != selected->call("get_sprite_frames")) {1848_remove_sprite_node();1849} else {1850animated_sprite = selected;1851if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {1852animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));1853}1854if (!animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {1855animated_sprite->connect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED);1856}1857if (!animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {1858animated_sprite->connect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));1859}1860show_node_edit = true;1861}1862} else {1863_remove_sprite_node();1864}18651866if (show_node_edit) {1867_sync_animation();1868autoplay_container->show();1869playback_container->show();1870} else {1871_update_library(); // To init autoplay icon.1872autoplay_container->hide();1873playback_container->hide();1874}1875}18761877void SpriteFramesEditor::_play_pressed() {1878if (animated_sprite) {1879animated_sprite->call("stop");1880animated_sprite->call("play", animated_sprite->call("get_animation"));1881}1882_update_stop_icon();1883}18841885void SpriteFramesEditor::_play_from_pressed() {1886if (animated_sprite) {1887animated_sprite->call("play", animated_sprite->call("get_animation"));1888}1889_update_stop_icon();1890}18911892void SpriteFramesEditor::_play_bw_pressed() {1893if (animated_sprite) {1894animated_sprite->call("stop");1895animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));1896}1897_update_stop_icon();1898}18991900void SpriteFramesEditor::_play_bw_from_pressed() {1901if (animated_sprite) {1902animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));1903}1904_update_stop_icon();1905}19061907void SpriteFramesEditor::_stop_pressed() {1908if (animated_sprite) {1909if (animated_sprite->call("is_playing")) {1910animated_sprite->call("pause");1911} else {1912animated_sprite->call("stop");1913}1914}1915_update_stop_icon();1916}19171918void SpriteFramesEditor::_autoplay_pressed() {1919if (updating) {1920return;1921}19221923if (animated_sprite) {1924EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1925undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, animated_sprite);1926String current = animated_sprite->call("get_animation");1927String current_auto = animated_sprite->call("get_autoplay");1928if (current == current_auto) {1929//unset1930undo_redo->add_do_method(animated_sprite, "set_autoplay", "");1931undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);1932} else {1933//set1934undo_redo->add_do_method(animated_sprite, "set_autoplay", current);1935undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);1936}1937undo_redo->add_do_method(this, "_update_library");1938undo_redo->add_undo_method(this, "_update_library");1939undo_redo->commit_action();1940}19411942_update_library();1943}19441945void SpriteFramesEditor::_bind_methods() {1946ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false));1947ClassDB::bind_method(D_METHOD("_select_animation", "name", "update_node"), &SpriteFramesEditor::_select_animation, DEFVAL(true));1948}19491950void SpriteFramesEditor::_node_removed(Node *p_node) {1951if (animated_sprite) {1952if (animated_sprite != p_node) {1953return;1954}1955_remove_sprite_node();1956}1957}19581959SpriteFramesEditor::SpriteFramesEditor() {1960VBoxContainer *vbc_animlist = memnew(VBoxContainer);1961add_child(vbc_animlist);1962vbc_animlist->set_custom_minimum_size(Size2(150, 0) * EDSCALE);19631964VBoxContainer *sub_vb = memnew(VBoxContainer);1965vbc_animlist->add_margin_child(TTR("Animations:"), sub_vb, true);1966sub_vb->set_v_size_flags(SIZE_EXPAND_FILL);19671968HBoxContainer *hbc_animlist = memnew(HBoxContainer);1969sub_vb->add_child(hbc_animlist);19701971add_anim = memnew(Button);1972add_anim->set_theme_type_variation(SceneStringName(FlatButton));1973add_anim->set_accessibility_name(TTRC("Add Animation"));1974hbc_animlist->add_child(add_anim);1975add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));19761977duplicate_anim = memnew(Button);1978duplicate_anim->set_theme_type_variation(SceneStringName(FlatButton));1979duplicate_anim->set_accessibility_name(TTRC("Duplicate Animation"));1980hbc_animlist->add_child(duplicate_anim);1981duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));19821983delete_anim = memnew(Button);1984delete_anim->set_theme_type_variation(SceneStringName(FlatButton));1985delete_anim->set_accessibility_name(TTRC("Delete Animation"));1986hbc_animlist->add_child(delete_anim);1987delete_anim->set_disabled(true);1988delete_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_remove));19891990autoplay_container = memnew(HBoxContainer);1991hbc_animlist->add_child(autoplay_container);19921993autoplay_container->add_child(memnew(VSeparator));19941995autoplay = memnew(Button);1996autoplay->set_theme_type_variation(SceneStringName(FlatButton));1997autoplay->set_tooltip_text(TTR("Autoplay on Load"));1998autoplay_container->add_child(autoplay);19992000hbc_animlist->add_child(memnew(VSeparator));20012002anim_loop = memnew(Button);2003anim_loop->set_toggle_mode(true);2004anim_loop->set_theme_type_variation(SceneStringName(FlatButton));2005anim_loop->set_tooltip_text(TTR("Animation Looping"));2006anim_loop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_loop_changed));2007hbc_animlist->add_child(anim_loop);20082009anim_speed = memnew(SpinBox);2010anim_speed->set_suffix(TTR("FPS"));2011anim_speed->set_min(0);2012anim_speed->set_max(120);2013anim_speed->set_step(0.01);2014anim_speed->set_custom_arrow_step(1);2015anim_speed->set_tooltip_text(TTR("Animation Speed"));2016anim_speed->get_line_edit()->set_expand_to_text_length_enabled(true);2017anim_speed->get_line_edit()->connect(SceneStringName(resized), callable_mp(this, &SpriteFramesEditor::_animation_speed_resized));2018anim_speed->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_animation_speed_changed));2019hbc_animlist->add_child(anim_speed);20202021anim_search_box = memnew(LineEdit);2022sub_vb->add_child(anim_search_box);2023anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL);2024anim_search_box->set_placeholder(TTR("Filter Animations"));2025anim_search_box->set_clear_button_enabled(true);2026anim_search_box->connect(SceneStringName(text_changed), callable_mp(this, &SpriteFramesEditor::_animation_search_text_changed));20272028animations = memnew(Tree);2029sub_vb->add_child(animations);2030animations->set_v_size_flags(SIZE_EXPAND_FILL);2031animations->set_hide_root(true);2032// HACK: The cell_selected signal is emitted before the FPS spinbox loses focus and applies the change.2033animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected), CONNECT_DEFERRED);2034animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited));2035animations->set_theme_type_variation("TreeSecondary");2036animations->set_allow_reselect(true);20372038add_anim->set_shortcut_context(animations);2039add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTRC("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));2040duplicate_anim->set_shortcut_context(animations);2041duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTRC("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));2042delete_anim->set_shortcut_context(animations);2043delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTRC("Delete Animation"), Key::KEY_DELETE));20442045missing_anim_label = memnew(Label);2046missing_anim_label->set_focus_mode(FOCUS_ACCESSIBILITY);2047missing_anim_label->set_text(TTR("This resource does not have any animations."));2048missing_anim_label->set_h_size_flags(SIZE_EXPAND_FILL);2049missing_anim_label->set_v_size_flags(SIZE_EXPAND_FILL);2050missing_anim_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);2051missing_anim_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);2052missing_anim_label->hide();2053add_child(missing_anim_label);20542055anim_frames_vb = memnew(VBoxContainer);2056add_child(anim_frames_vb);2057anim_frames_vb->set_h_size_flags(SIZE_EXPAND_FILL);2058anim_frames_vb->hide();20592060sub_vb = memnew(VBoxContainer);2061anim_frames_vb->add_margin_child(TTR("Animation Frames:"), sub_vb, true);20622063HFlowContainer *hfc = memnew(HFlowContainer);2064sub_vb->add_child(hfc);20652066playback_container = memnew(HBoxContainer);2067playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);2068hfc->add_child(playback_container);20692070play_bw_from = memnew(Button);2071play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));2072play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));2073playback_container->add_child(play_bw_from);20742075play_bw = memnew(Button);2076play_bw->set_theme_type_variation(SceneStringName(FlatButton));2077play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));2078playback_container->add_child(play_bw);20792080stop = memnew(Button);2081stop->set_theme_type_variation(SceneStringName(FlatButton));2082stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));2083playback_container->add_child(stop);20842085play = memnew(Button);2086play->set_theme_type_variation(SceneStringName(FlatButton));2087play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));2088playback_container->add_child(play);20892090play_from = memnew(Button);2091play_from->set_theme_type_variation(SceneStringName(FlatButton));2092play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));2093playback_container->add_child(play_from);20942095hfc->add_child(memnew(VSeparator));20962097autoplay->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_autoplay_pressed));2098autoplay->set_toggle_mode(true);2099play->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_pressed));2100play_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_from_pressed));2101play_bw->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_pressed));2102play_bw_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed));2103stop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_stop_pressed));21042105HBoxContainer *hbc_actions = memnew(HBoxContainer);2106hfc->add_child(hbc_actions);21072108load = memnew(Button);2109load->set_accessibility_name(TTRC("Load"));2110load->set_theme_type_variation(SceneStringName(FlatButton));2111hbc_actions->add_child(load);21122113load_sheet = memnew(Button);2114load_sheet->set_accessibility_name(TTRC("Load Sheet"));2115load_sheet->set_theme_type_variation(SceneStringName(FlatButton));2116hbc_actions->add_child(load_sheet);21172118hbc_actions->add_child(memnew(VSeparator));21192120copy = memnew(Button);2121copy->set_accessibility_name(TTRC("Copy"));2122copy->set_theme_type_variation(SceneStringName(FlatButton));2123hbc_actions->add_child(copy);21242125paste = memnew(Button);2126paste->set_accessibility_name(TTRC("Paste"));2127paste->set_theme_type_variation(SceneStringName(FlatButton));2128hbc_actions->add_child(paste);21292130hbc_actions->add_child(memnew(VSeparator));21312132empty_before = memnew(Button);2133empty_before->set_accessibility_name(TTRC("Empty Before"));2134empty_before->set_theme_type_variation(SceneStringName(FlatButton));2135hbc_actions->add_child(empty_before);21362137empty_after = memnew(Button);2138empty_after->set_accessibility_name(TTRC("Empty After"));2139empty_after->set_theme_type_variation(SceneStringName(FlatButton));2140hbc_actions->add_child(empty_after);21412142hbc_actions->add_child(memnew(VSeparator));21432144move_up = memnew(Button);2145move_up->set_accessibility_name(TTRC("Move Up"));2146move_up->set_theme_type_variation(SceneStringName(FlatButton));2147hbc_actions->add_child(move_up);21482149move_down = memnew(Button);2150move_down->set_accessibility_name(TTRC("Move Down"));2151move_down->set_theme_type_variation(SceneStringName(FlatButton));2152hbc_actions->add_child(move_down);21532154delete_frame = memnew(Button);2155delete_frame->set_accessibility_name(TTRC("Delete Frame"));2156delete_frame->set_theme_type_variation(SceneStringName(FlatButton));2157hbc_actions->add_child(delete_frame);21582159hbc_actions->add_child(memnew(VSeparator));21602161HBoxContainer *hbc_frame_duration = memnew(HBoxContainer);2162hfc->add_child(hbc_frame_duration);21632164Label *label = memnew(Label);2165label->set_text(TTR("Frame Duration:"));2166hbc_frame_duration->add_child(label);21672168frame_duration = memnew(SpinBox);2169frame_duration->set_prefix(String::utf8("×"));2170frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div.2171frame_duration->set_max(10);2172frame_duration->set_step(0.01);2173frame_duration->set_custom_arrow_step(0.1);2174frame_duration->set_allow_lesser(false);2175frame_duration->set_allow_greater(true);2176frame_duration->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_frame_duration_changed));2177frame_duration->set_accessibility_name(TTRC("Frame Duration:"));2178hbc_frame_duration->add_child(frame_duration);21792180// Wide empty separation control. (like BoxContainer::add_spacer())2181Control *c = memnew(Control);2182c->set_mouse_filter(MOUSE_FILTER_PASS);2183c->set_h_size_flags(SIZE_EXPAND_FILL);2184hfc->add_child(c);21852186HBoxContainer *hbc_zoom = memnew(HBoxContainer);2187hfc->add_child(hbc_zoom);21882189zoom_out = memnew(Button);2190zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_out));2191zoom_out->set_flat(true);2192zoom_out->set_tooltip_text(TTRC("Zoom Out"));2193hbc_zoom->add_child(zoom_out);21942195zoom_reset = memnew(Button);2196zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_reset));2197zoom_reset->set_flat(true);2198zoom_reset->set_tooltip_text(TTRC("Zoom Reset"));2199hbc_zoom->add_child(zoom_reset);22002201zoom_in = memnew(Button);2202zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_in));2203zoom_in->set_flat(true);2204zoom_in->set_tooltip_text(TTRC("Zoom In"));2205hbc_zoom->add_child(zoom_in);22062207file = memnew(EditorFileDialog);2208file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));2209add_child(file);22102211frame_list = memnew(ItemList);2212frame_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);2213frame_list->set_v_size_flags(SIZE_EXPAND_FILL);2214frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);2215frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2216frame_list->set_select_mode(ItemList::SELECT_MULTI);22172218frame_list->set_max_columns(0);2219frame_list->set_max_text_lines(2);2220SET_DRAG_FORWARDING_GCD(frame_list, SpriteFramesEditor);2221frame_list->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input));2222// HACK: The item_selected signal is emitted before the Frame Duration spinbox loses focus and applies the change.2223frame_list->connect("multi_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected), CONNECT_DEFERRED);22242225sub_vb->add_child(frame_list);22262227dialog = memnew(AcceptDialog);2228add_child(dialog);22292230load->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_load_pressed));2231load_sheet->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet));2232delete_frame->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_delete_pressed));2233copy->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_copy_pressed));2234paste->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_paste_pressed));2235empty_before->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty_pressed));2236empty_after->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty2_pressed));2237move_up->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_up_pressed));2238move_down->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_down_pressed));22392240load->set_shortcut_context(frame_list);2241load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTRC("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O));2242load_sheet->set_shortcut_context(frame_list);2243load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTRC("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));2244delete_frame->set_shortcut_context(frame_list);2245delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTRC("Delete Frame"), Key::KEY_DELETE));2246copy->set_shortcut_context(frame_list);2247copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTRC("Copy Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::C));2248paste->set_shortcut_context(frame_list);2249paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTRC("Paste Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::V));2250empty_before->set_shortcut_context(frame_list);2251empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTRC("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));2252empty_after->set_shortcut_context(frame_list);2253empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTRC("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));2254move_up->set_shortcut_context(frame_list);2255move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTRC("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));2256move_down->set_shortcut_context(frame_list);2257move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTRC("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));22582259zoom_out->set_shortcut_context(frame_list);2260zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTRC("Zoom Out"),2261{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));2262zoom_in->set_shortcut_context(frame_list);2263zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTRC("Zoom In"),2264{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));22652266loading_scene = false;22672268updating = false;22692270edited_anim = SceneStringName(default_);22712272delete_dialog = memnew(ConfirmationDialog);2273add_child(delete_dialog);2274delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_animation_remove_confirmed));22752276split_sheet_dialog = memnew(ConfirmationDialog);2277add_child(split_sheet_dialog);2278split_sheet_dialog->set_title(TTR("Select Frames"));2279split_sheet_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));22802281HBoxContainer *split_sheet_hb = memnew(HBoxContainer);2282split_sheet_dialog->add_child(split_sheet_hb);2283split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);2284split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);22852286VBoxContainer *split_sheet_vb = memnew(VBoxContainer);2287split_sheet_hb->add_child(split_sheet_vb);2288split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);2289split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);22902291HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);22922293split_sheet_menu_hb->add_child(memnew(Label(TTR("Frame Order"))));22942295split_sheet_order = memnew(OptionButton);2296split_sheet_order->add_item(TTR("As Selected"), FRAME_ORDER_SELECTION);2297split_sheet_order->add_separator(TTR("By Row"));2298split_sheet_order->add_item(TTR("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);2299split_sheet_order->add_item(TTR("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);2300split_sheet_order->add_item(TTR("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);2301split_sheet_order->add_item(TTR("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);2302split_sheet_order->add_separator(TTR("By Column"));2303split_sheet_order->add_item(TTR("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);2304split_sheet_order->add_item(TTR("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);2305split_sheet_order->add_item(TTR("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);2306split_sheet_order->add_item(TTR("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);2307split_sheet_order->connect(SceneStringName(item_selected), callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));2308split_sheet_menu_hb->add_child(split_sheet_order);23092310Button *select_all = memnew(Button);2311select_all->set_text(TTR("Select All"));2312select_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));2313split_sheet_menu_hb->add_child(select_all);23142315Button *clear_all = memnew(Button);2316clear_all->set_text(TTR("Select None"));2317clear_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));2318split_sheet_menu_hb->add_child(clear_all);23192320split_sheet_menu_hb->add_spacer();23212322toggle_settings_button = memnew(Button);2323toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);2324toggle_settings_button->set_theme_type_variation(SceneStringName(FlatButton));2325toggle_settings_button->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));2326toggle_settings_button->set_tooltip_text(TTR("Toggle Settings Panel"));2327split_sheet_menu_hb->add_child(toggle_settings_button);23282329split_sheet_vb->add_child(split_sheet_menu_hb);23302331PanelContainer *split_sheet_panel = memnew(PanelContainer);2332split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);2333split_sheet_panel->set_v_size_flags(SIZE_EXPAND_FILL);2334split_sheet_vb->add_child(split_sheet_panel);23352336split_sheet_preview = memnew(TextureRect);2337split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);2338split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2339split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS);2340split_sheet_preview->connect(SceneStringName(draw), callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));2341split_sheet_preview->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));23422343split_sheet_scroll = memnew(ScrollContainer);2344split_sheet_scroll->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));2345split_sheet_panel->add_child(split_sheet_scroll);2346CenterContainer *cc = memnew(CenterContainer);2347cc->add_child(split_sheet_preview);2348cc->set_h_size_flags(SIZE_EXPAND_FILL);2349cc->set_v_size_flags(SIZE_EXPAND_FILL);2350split_sheet_scroll->add_child(cc);23512352MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer);2353split_sheet_panel->add_child(split_sheet_zoom_margin);2354split_sheet_zoom_margin->set_h_size_flags(0);2355split_sheet_zoom_margin->set_v_size_flags(0);2356split_sheet_zoom_margin->add_theme_constant_override("margin_top", 5);2357split_sheet_zoom_margin->add_theme_constant_override("margin_left", 5);2358HBoxContainer *split_sheet_zoom_hb = memnew(HBoxContainer);2359split_sheet_zoom_margin->add_child(split_sheet_zoom_hb);23602361split_sheet_zoom_out = memnew(Button);2362split_sheet_zoom_out->set_theme_type_variation(SceneStringName(FlatButton));2363split_sheet_zoom_out->set_focus_mode(FOCUS_ACCESSIBILITY);2364split_sheet_zoom_out->set_tooltip_text(TTR("Zoom Out"));2365split_sheet_zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out));2366split_sheet_zoom_hb->add_child(split_sheet_zoom_out);23672368split_sheet_zoom_reset = memnew(Button);2369split_sheet_zoom_reset->set_theme_type_variation(SceneStringName(FlatButton));2370split_sheet_zoom_reset->set_focus_mode(FOCUS_ACCESSIBILITY);2371split_sheet_zoom_reset->set_tooltip_text(TTR("Zoom Reset"));2372split_sheet_zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));2373split_sheet_zoom_hb->add_child(split_sheet_zoom_reset);23742375split_sheet_zoom_in = memnew(Button);2376split_sheet_zoom_in->set_theme_type_variation(SceneStringName(FlatButton));2377split_sheet_zoom_in->set_focus_mode(FOCUS_ACCESSIBILITY);2378split_sheet_zoom_in->set_tooltip_text(TTR("Zoom In"));2379split_sheet_zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));2380split_sheet_zoom_hb->add_child(split_sheet_zoom_in);23812382split_sheet_settings_vb = memnew(VBoxContainer);2383split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);23842385HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);2386split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);23872388Label *split_sheet_h_label = memnew(Label(TTR("Horizontal")));2389split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);2390split_sheet_h_hb->add_child(split_sheet_h_label);23912392split_sheet_h = memnew(SpinBox);2393split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);2394split_sheet_h->set_min(1);2395split_sheet_h->set_max(128);2396split_sheet_h->set_step(1);2397split_sheet_h->set_select_all_on_focus(true);2398split_sheet_h->set_accessibility_name(TTRC("Horizontal"));2399split_sheet_h_hb->add_child(split_sheet_h);2400split_sheet_h->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2401split_sheet_settings_vb->add_child(split_sheet_h_hb);24022403HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);2404split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);24052406Label *split_sheet_v_label = memnew(Label(TTR("Vertical")));2407split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);2408split_sheet_v_hb->add_child(split_sheet_v_label);24092410split_sheet_v = memnew(SpinBox);2411split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);2412split_sheet_v->set_min(1);2413split_sheet_v->set_max(128);2414split_sheet_v->set_step(1);2415split_sheet_v->set_select_all_on_focus(true);2416split_sheet_v->set_accessibility_name(TTRC("Vertical"));2417split_sheet_v_hb->add_child(split_sheet_v);2418split_sheet_v->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2419split_sheet_settings_vb->add_child(split_sheet_v_hb);24202421HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);2422split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);24232424Label *split_sheet_size_label = memnew(Label(TTR("Size")));2425split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);2426split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2427split_sheet_size_hb->add_child(split_sheet_size_label);24282429VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);2430split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);2431split_sheet_size_x = memnew(SpinBox);2432split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);2433split_sheet_size_x->set_min(1);2434split_sheet_size_x->set_step(1);2435split_sheet_size_x->set_suffix("px");2436split_sheet_size_x->set_select_all_on_focus(true);2437split_sheet_size_x->set_accessibility_name(TTRC("X Size"));2438split_sheet_size_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2439split_sheet_size_vb->add_child(split_sheet_size_x);2440split_sheet_size_y = memnew(SpinBox);2441split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);2442split_sheet_size_y->set_min(1);2443split_sheet_size_y->set_step(1);2444split_sheet_size_y->set_suffix("px");2445split_sheet_size_y->set_select_all_on_focus(true);2446split_sheet_size_y->set_accessibility_name(TTRC("Y Size"));2447split_sheet_size_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2448split_sheet_size_vb->add_child(split_sheet_size_y);2449split_sheet_size_hb->add_child(split_sheet_size_vb);2450split_sheet_settings_vb->add_child(split_sheet_size_hb);24512452HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);2453split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);24542455Label *split_sheet_sep_label = memnew(Label(TTR("Separation")));2456split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);2457split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2458split_sheet_sep_hb->add_child(split_sheet_sep_label);24592460VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);2461split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);2462split_sheet_sep_x = memnew(SpinBox);2463split_sheet_sep_x->set_min(0);2464split_sheet_sep_x->set_step(1);2465split_sheet_sep_x->set_suffix("px");2466split_sheet_sep_x->set_select_all_on_focus(true);2467split_sheet_sep_x->set_accessibility_name(TTRC("X Separation"));2468split_sheet_sep_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2469split_sheet_sep_vb->add_child(split_sheet_sep_x);2470split_sheet_sep_y = memnew(SpinBox);2471split_sheet_sep_y->set_min(0);2472split_sheet_sep_y->set_step(1);2473split_sheet_sep_y->set_suffix("px");2474split_sheet_sep_y->set_select_all_on_focus(true);2475split_sheet_sep_y->set_accessibility_name(TTRC("Y Separation"));2476split_sheet_sep_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2477split_sheet_sep_vb->add_child(split_sheet_sep_y);2478split_sheet_sep_hb->add_child(split_sheet_sep_vb);2479split_sheet_settings_vb->add_child(split_sheet_sep_hb);24802481HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);2482split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);24832484Label *split_sheet_offset_label = memnew(Label(TTR("Offset")));2485split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);2486split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2487split_sheet_offset_hb->add_child(split_sheet_offset_label);24882489VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);2490split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);2491split_sheet_offset_x = memnew(SpinBox);2492split_sheet_offset_x->set_min(0);2493split_sheet_offset_x->set_step(1);2494split_sheet_offset_x->set_suffix("px");2495split_sheet_offset_x->set_select_all_on_focus(true);2496split_sheet_offset_x->set_accessibility_name(TTRC("X Offset"));2497split_sheet_offset_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2498split_sheet_offset_vb->add_child(split_sheet_offset_x);2499split_sheet_offset_y = memnew(SpinBox);2500split_sheet_offset_y->set_min(0);2501split_sheet_offset_y->set_step(1);2502split_sheet_offset_y->set_suffix("px");2503split_sheet_offset_y->set_select_all_on_focus(true);2504split_sheet_offset_y->set_accessibility_name(TTRC("Y Offset"));2505split_sheet_offset_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2506split_sheet_offset_vb->add_child(split_sheet_offset_y);2507split_sheet_offset_hb->add_child(split_sheet_offset_vb);2508split_sheet_settings_vb->add_child(split_sheet_offset_hb);25092510Button *auto_slice = memnew(Button);2511auto_slice->set_text(TTR("Auto Slice"));2512auto_slice->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_auto_slice_sprite_sheet));2513split_sheet_settings_vb->add_child(auto_slice);25142515split_sheet_hb->add_child(split_sheet_settings_vb);25162517file_split_sheet = memnew(EditorFileDialog);2518file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet"));2519file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);2520add_child(file_split_sheet);2521file_split_sheet->connect("file_selected", callable_mp(this, &SpriteFramesEditor::_prepare_sprite_sheet));25222523// Config scale.2524scale_ratio = 1.2f;2525thumbnail_default_size = 96 * MAX(1, EDSCALE);2526thumbnail_zoom = MAX(1.0f, EDSCALE);2527max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE);2528min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE);2529// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.2530sheet_zoom = MAX(1.0f, EDSCALE);2531max_sheet_zoom = 128.0f * MAX(1.0f, EDSCALE);2532min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE);2533_zoom_reset();25342535// Ensure the anim search box is wide enough by default.2536// Not by setting its minimum size so it can still be shrunk if desired.2537set_split_offset(56 * EDSCALE);2538}25392540void SpriteFramesEditorPlugin::edit(Object *p_object) {2541Ref<SpriteFrames> s;2542AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object);2543if (animated_sprite) {2544s = animated_sprite->get_sprite_frames();2545} else {2546AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2547if (animated_sprite_3d) {2548s = animated_sprite_3d->get_sprite_frames();2549} else {2550s = p_object;2551}2552}25532554frames_editor->edit(s);2555}25562557bool SpriteFramesEditorPlugin::handles(Object *p_object) const {2558AnimatedSprite2D *animated_sprite_2d = Object::cast_to<AnimatedSprite2D>(p_object);2559if (animated_sprite_2d && *animated_sprite_2d->get_sprite_frames()) {2560return true;2561}2562AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2563if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) {2564return true;2565}2566SpriteFrames *frames = Object::cast_to<SpriteFrames>(p_object);2567if (frames && (frames_editor->get_sprite_frames().is_null() || frames_editor->get_sprite_frames() == frames)) {2568return true;2569}2570return false;2571}25722573void SpriteFramesEditorPlugin::make_visible(bool p_visible) {2574if (p_visible) {2575button->show();2576EditorNode::get_bottom_panel()->make_item_visible(frames_editor);2577} else {2578button->hide();2579if (frames_editor->is_visible_in_tree()) {2580EditorNode::get_bottom_panel()->hide_bottom_panel();2581}2582}2583}25842585SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {2586frames_editor = memnew(SpriteFramesEditor);2587frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);2588button = 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")));2589button->hide();2590}259125922593