Path: blob/master/editor/scene/sprite_frames_editor_plugin.cpp
9898 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();1613hide();1614return;1615}16161617frames = p_frames;1618read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames);16191620if (!p_frames->has_animation(edited_anim)) {1621List<StringName> anim_names;1622frames->get_animation_list(&anim_names);1623anim_names.sort_custom<StringName::AlphCompare>();1624if (anim_names.size()) {1625edited_anim = anim_names.front()->get();1626} else {1627edited_anim = StringName();1628}1629}16301631_update_library();1632// Clear zoom and split sheet texture1633split_sheet_preview->set_texture(Ref<Texture2D>());1634_zoom_reset();16351636add_anim->set_disabled(read_only);1637duplicate_anim->set_disabled(read_only);1638delete_anim->set_disabled(read_only);1639anim_speed->set_editable(!read_only);1640anim_loop->set_disabled(read_only);1641load->set_disabled(read_only);1642load_sheet->set_disabled(read_only);1643copy->set_disabled(read_only);1644paste->set_disabled(read_only);1645empty_before->set_disabled(read_only);1646empty_after->set_disabled(read_only);1647move_up->set_disabled(read_only);1648move_down->set_disabled(read_only);1649delete_frame->set_disabled(read_only);16501651_fetch_sprite_node(); // Fetch node after set frames.1652}16531654Ref<SpriteFrames> SpriteFramesEditor::get_sprite_frames() const {1655return frames;1656}16571658Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {1659if (read_only) {1660return false;1661}16621663if (!frames->has_animation(edited_anim)) {1664return false;1665}16661667int idx = -1;1668if (p_point == Vector2(Math::INF, Math::INF)) {1669if (frame_list->is_anything_selected()) {1670idx = frame_list->get_selected_items()[0];1671}1672} else {1673idx = frame_list->get_item_at_position(p_point, true);1674}16751676if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) {1677return Variant();1678}16791680Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx);16811682if (frame.is_null()) {1683return Variant();1684}16851686Dictionary drag_data = EditorNode::get_singleton()->drag_resource(frame, p_from);1687drag_data["frame"] = idx; // store the frame, in case we want to reorder frames inside 'drop_data_fw'1688return drag_data;1689}16901691bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {1692if (read_only) {1693return false;1694}16951696Dictionary d = p_data;16971698if (!d.has("type")) {1699return false;1700}17011702// reordering frames1703if (d.has("from") && (Object *)(d["from"]) == frame_list) {1704return true;1705}17061707if (String(d["type"]) == "resource" && d.has("resource")) {1708Ref<Resource> r = d["resource"];17091710Ref<Texture2D> texture = r;17111712if (texture.is_valid()) {1713return true;1714}1715}17161717if (String(d["type"]) == "files") {1718Vector<String> files = d["files"];17191720if (files.is_empty()) {1721return false;1722}17231724for (int i = 0; i < files.size(); i++) {1725const String &f = files[i];1726String ftype = EditorFileSystem::get_singleton()->get_file_type(f);17271728if (!ClassDB::is_parent_class(ftype, "Texture2D")) {1729return false;1730}1731}17321733return true;1734}1735return false;1736}17371738void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {1739if (!can_drop_data_fw(p_point, p_data, p_from)) {1740return;1741}17421743Dictionary d = p_data;17441745if (!d.has("type")) {1746return;1747}17481749int at_pos = -1;1750if (p_point == Vector2(Math::INF, Math::INF)) {1751if (frame_list->is_anything_selected()) {1752at_pos = frame_list->get_selected_items()[0];1753}1754} else {1755at_pos = frame_list->get_item_at_position(p_point, true);1756}17571758if (String(d["type"]) == "resource" && d.has("resource")) {1759Ref<Resource> r = d["resource"];17601761Ref<Texture2D> texture = r;17621763if (texture.is_valid()) {1764bool reorder = false;1765if (d.has("from") && (Object *)(d["from"]) == frame_list) {1766reorder = true;1767}17681769EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1770if (reorder) { //drop is from reordering frames1771int from_frame = -1;1772float duration = 1.0;1773if (d.has("frame")) {1774from_frame = d["frame"];1775duration = frames->get_frame_duration(edited_anim, from_frame);1776}17771778undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1779undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame);1780undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos);1781undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos);1782undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame);1783undo_redo->add_do_method(this, "_update_library");1784undo_redo->add_undo_method(this, "_update_library");1785undo_redo->commit_action();1786} else {1787undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1788undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos);1789undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos);1790undo_redo->add_do_method(this, "_update_library");1791undo_redo->add_undo_method(this, "_update_library");1792undo_redo->commit_action();1793}1794}1795}17961797if (String(d["type"]) == "files") {1798Vector<String> files = d["files"];17991800if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {1801_prepare_sprite_sheet(files[0]);1802} else {1803_file_load_request(files, at_pos);1804}1805}1806}18071808void SpriteFramesEditor::_update_stop_icon() {1809bool is_playing = false;1810if (animated_sprite) {1811is_playing = animated_sprite->call("is_playing");1812}1813if (is_playing) {1814stop->set_button_icon(pause_icon);1815} else {1816stop->set_button_icon(stop_icon);1817}1818}18191820void SpriteFramesEditor::_remove_sprite_node() {1821if (!animated_sprite) {1822return;1823}1824if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {1825animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));1826}1827if (animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {1828animated_sprite->disconnect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation));1829}1830if (animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {1831animated_sprite->disconnect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));1832}1833animated_sprite = nullptr;1834}18351836void SpriteFramesEditor::_fetch_sprite_node() {1837Node *selected = nullptr;1838EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();1839const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();1840if (top_node_list.size() == 1) {1841selected = top_node_list.front()->get();1842}18431844bool show_node_edit = false;1845AnimatedSprite2D *as2d = Object::cast_to<AnimatedSprite2D>(selected);1846AnimatedSprite3D *as3d = Object::cast_to<AnimatedSprite3D>(selected);1847if (as2d || as3d) {1848if (frames != selected->call("get_sprite_frames")) {1849_remove_sprite_node();1850} else {1851animated_sprite = selected;1852if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {1853animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));1854}1855if (!animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {1856animated_sprite->connect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED);1857}1858if (!animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {1859animated_sprite->connect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));1860}1861show_node_edit = true;1862}1863} else {1864_remove_sprite_node();1865}18661867if (show_node_edit) {1868_sync_animation();1869autoplay_container->show();1870playback_container->show();1871} else {1872_update_library(); // To init autoplay icon.1873autoplay_container->hide();1874playback_container->hide();1875}1876}18771878void SpriteFramesEditor::_play_pressed() {1879if (animated_sprite) {1880animated_sprite->call("stop");1881animated_sprite->call("play", animated_sprite->call("get_animation"));1882}1883_update_stop_icon();1884}18851886void SpriteFramesEditor::_play_from_pressed() {1887if (animated_sprite) {1888animated_sprite->call("play", animated_sprite->call("get_animation"));1889}1890_update_stop_icon();1891}18921893void SpriteFramesEditor::_play_bw_pressed() {1894if (animated_sprite) {1895animated_sprite->call("stop");1896animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));1897}1898_update_stop_icon();1899}19001901void SpriteFramesEditor::_play_bw_from_pressed() {1902if (animated_sprite) {1903animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));1904}1905_update_stop_icon();1906}19071908void SpriteFramesEditor::_stop_pressed() {1909if (animated_sprite) {1910if (animated_sprite->call("is_playing")) {1911animated_sprite->call("pause");1912} else {1913animated_sprite->call("stop");1914}1915}1916_update_stop_icon();1917}19181919void SpriteFramesEditor::_autoplay_pressed() {1920if (updating) {1921return;1922}19231924if (animated_sprite) {1925EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1926undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, animated_sprite);1927String current = animated_sprite->call("get_animation");1928String current_auto = animated_sprite->call("get_autoplay");1929if (current == current_auto) {1930//unset1931undo_redo->add_do_method(animated_sprite, "set_autoplay", "");1932undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);1933} else {1934//set1935undo_redo->add_do_method(animated_sprite, "set_autoplay", current);1936undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);1937}1938undo_redo->add_do_method(this, "_update_library");1939undo_redo->add_undo_method(this, "_update_library");1940undo_redo->commit_action();1941}19421943_update_library();1944}19451946void SpriteFramesEditor::_bind_methods() {1947ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false));1948ClassDB::bind_method(D_METHOD("_select_animation", "name", "update_node"), &SpriteFramesEditor::_select_animation, DEFVAL(true));1949}19501951void SpriteFramesEditor::_node_removed(Node *p_node) {1952if (animated_sprite) {1953if (animated_sprite != p_node) {1954return;1955}1956_remove_sprite_node();1957}1958}19591960SpriteFramesEditor::SpriteFramesEditor() {1961VBoxContainer *vbc_animlist = memnew(VBoxContainer);1962add_child(vbc_animlist);1963vbc_animlist->set_custom_minimum_size(Size2(150, 0) * EDSCALE);19641965VBoxContainer *sub_vb = memnew(VBoxContainer);1966vbc_animlist->add_margin_child(TTR("Animations:"), sub_vb, true);1967sub_vb->set_v_size_flags(SIZE_EXPAND_FILL);19681969HBoxContainer *hbc_animlist = memnew(HBoxContainer);1970sub_vb->add_child(hbc_animlist);19711972add_anim = memnew(Button);1973add_anim->set_theme_type_variation(SceneStringName(FlatButton));1974add_anim->set_accessibility_name(TTRC("Add Animation"));1975hbc_animlist->add_child(add_anim);1976add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));19771978duplicate_anim = memnew(Button);1979duplicate_anim->set_theme_type_variation(SceneStringName(FlatButton));1980duplicate_anim->set_accessibility_name(TTRC("Duplicate Animation"));1981hbc_animlist->add_child(duplicate_anim);1982duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));19831984delete_anim = memnew(Button);1985delete_anim->set_theme_type_variation(SceneStringName(FlatButton));1986delete_anim->set_accessibility_name(TTRC("Delete Animation"));1987hbc_animlist->add_child(delete_anim);1988delete_anim->set_disabled(true);1989delete_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_remove));19901991autoplay_container = memnew(HBoxContainer);1992hbc_animlist->add_child(autoplay_container);19931994autoplay_container->add_child(memnew(VSeparator));19951996autoplay = memnew(Button);1997autoplay->set_theme_type_variation(SceneStringName(FlatButton));1998autoplay->set_tooltip_text(TTR("Autoplay on Load"));1999autoplay_container->add_child(autoplay);20002001hbc_animlist->add_child(memnew(VSeparator));20022003anim_loop = memnew(Button);2004anim_loop->set_toggle_mode(true);2005anim_loop->set_theme_type_variation(SceneStringName(FlatButton));2006anim_loop->set_tooltip_text(TTR("Animation Looping"));2007anim_loop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_loop_changed));2008hbc_animlist->add_child(anim_loop);20092010anim_speed = memnew(SpinBox);2011anim_speed->set_suffix(TTR("FPS"));2012anim_speed->set_min(0);2013anim_speed->set_max(120);2014anim_speed->set_step(0.01);2015anim_speed->set_custom_arrow_step(1);2016anim_speed->set_tooltip_text(TTR("Animation Speed"));2017anim_speed->get_line_edit()->set_expand_to_text_length_enabled(true);2018anim_speed->get_line_edit()->connect(SceneStringName(resized), callable_mp(this, &SpriteFramesEditor::_animation_speed_resized));2019anim_speed->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_animation_speed_changed));2020hbc_animlist->add_child(anim_speed);20212022anim_search_box = memnew(LineEdit);2023sub_vb->add_child(anim_search_box);2024anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL);2025anim_search_box->set_placeholder(TTR("Filter Animations"));2026anim_search_box->set_clear_button_enabled(true);2027anim_search_box->connect(SceneStringName(text_changed), callable_mp(this, &SpriteFramesEditor::_animation_search_text_changed));20282029animations = memnew(Tree);2030sub_vb->add_child(animations);2031animations->set_v_size_flags(SIZE_EXPAND_FILL);2032animations->set_hide_root(true);2033// HACK: The cell_selected signal is emitted before the FPS spinbox loses focus and applies the change.2034animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected), CONNECT_DEFERRED);2035animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited));2036animations->set_theme_type_variation("TreeSecondary");2037animations->set_allow_reselect(true);20382039add_anim->set_shortcut_context(animations);2040add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTRC("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));2041duplicate_anim->set_shortcut_context(animations);2042duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTRC("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));2043delete_anim->set_shortcut_context(animations);2044delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTRC("Delete Animation"), Key::KEY_DELETE));20452046missing_anim_label = memnew(Label);2047missing_anim_label->set_focus_mode(FOCUS_ACCESSIBILITY);2048missing_anim_label->set_text(TTR("This resource does not have any animations."));2049missing_anim_label->set_h_size_flags(SIZE_EXPAND_FILL);2050missing_anim_label->set_v_size_flags(SIZE_EXPAND_FILL);2051missing_anim_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);2052missing_anim_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);2053missing_anim_label->hide();2054add_child(missing_anim_label);20552056anim_frames_vb = memnew(VBoxContainer);2057add_child(anim_frames_vb);2058anim_frames_vb->set_h_size_flags(SIZE_EXPAND_FILL);2059anim_frames_vb->hide();20602061sub_vb = memnew(VBoxContainer);2062anim_frames_vb->add_margin_child(TTR("Animation Frames:"), sub_vb, true);20632064HFlowContainer *hfc = memnew(HFlowContainer);2065sub_vb->add_child(hfc);20662067playback_container = memnew(HBoxContainer);2068playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);2069hfc->add_child(playback_container);20702071play_bw_from = memnew(Button);2072play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));2073play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)"));2074playback_container->add_child(play_bw_from);20752076play_bw = memnew(Button);2077play_bw->set_theme_type_variation(SceneStringName(FlatButton));2078play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)"));2079playback_container->add_child(play_bw);20802081stop = memnew(Button);2082stop->set_theme_type_variation(SceneStringName(FlatButton));2083stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)"));2084playback_container->add_child(stop);20852086play = memnew(Button);2087play->set_theme_type_variation(SceneStringName(FlatButton));2088play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)"));2089playback_container->add_child(play);20902091play_from = memnew(Button);2092play_from->set_theme_type_variation(SceneStringName(FlatButton));2093play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)"));2094playback_container->add_child(play_from);20952096hfc->add_child(memnew(VSeparator));20972098autoplay->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_autoplay_pressed));2099autoplay->set_toggle_mode(true);2100play->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_pressed));2101play_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_from_pressed));2102play_bw->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_pressed));2103play_bw_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed));2104stop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_stop_pressed));21052106HBoxContainer *hbc_actions = memnew(HBoxContainer);2107hfc->add_child(hbc_actions);21082109load = memnew(Button);2110load->set_accessibility_name(TTRC("Load"));2111load->set_theme_type_variation(SceneStringName(FlatButton));2112hbc_actions->add_child(load);21132114load_sheet = memnew(Button);2115load_sheet->set_accessibility_name(TTRC("Load Sheet"));2116load_sheet->set_theme_type_variation(SceneStringName(FlatButton));2117hbc_actions->add_child(load_sheet);21182119hbc_actions->add_child(memnew(VSeparator));21202121copy = memnew(Button);2122copy->set_accessibility_name(TTRC("Copy"));2123copy->set_theme_type_variation(SceneStringName(FlatButton));2124hbc_actions->add_child(copy);21252126paste = memnew(Button);2127paste->set_accessibility_name(TTRC("Paste"));2128paste->set_theme_type_variation(SceneStringName(FlatButton));2129hbc_actions->add_child(paste);21302131hbc_actions->add_child(memnew(VSeparator));21322133empty_before = memnew(Button);2134empty_before->set_accessibility_name(TTRC("Empty Before"));2135empty_before->set_theme_type_variation(SceneStringName(FlatButton));2136hbc_actions->add_child(empty_before);21372138empty_after = memnew(Button);2139empty_after->set_accessibility_name(TTRC("Empty After"));2140empty_after->set_theme_type_variation(SceneStringName(FlatButton));2141hbc_actions->add_child(empty_after);21422143hbc_actions->add_child(memnew(VSeparator));21442145move_up = memnew(Button);2146move_up->set_accessibility_name(TTRC("Move Up"));2147move_up->set_theme_type_variation(SceneStringName(FlatButton));2148hbc_actions->add_child(move_up);21492150move_down = memnew(Button);2151move_down->set_accessibility_name(TTRC("Move Down"));2152move_down->set_theme_type_variation(SceneStringName(FlatButton));2153hbc_actions->add_child(move_down);21542155delete_frame = memnew(Button);2156delete_frame->set_accessibility_name(TTRC("Delete Frame"));2157delete_frame->set_theme_type_variation(SceneStringName(FlatButton));2158hbc_actions->add_child(delete_frame);21592160hbc_actions->add_child(memnew(VSeparator));21612162HBoxContainer *hbc_frame_duration = memnew(HBoxContainer);2163hfc->add_child(hbc_frame_duration);21642165Label *label = memnew(Label);2166label->set_text(TTR("Frame Duration:"));2167hbc_frame_duration->add_child(label);21682169frame_duration = memnew(SpinBox);2170frame_duration->set_prefix(String::utf8("×"));2171frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div.2172frame_duration->set_max(10);2173frame_duration->set_step(0.01);2174frame_duration->set_custom_arrow_step(0.1);2175frame_duration->set_allow_lesser(false);2176frame_duration->set_allow_greater(true);2177frame_duration->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_frame_duration_changed));2178frame_duration->set_accessibility_name(TTRC("Frame Duration:"));2179hbc_frame_duration->add_child(frame_duration);21802181// Wide empty separation control. (like BoxContainer::add_spacer())2182Control *c = memnew(Control);2183c->set_mouse_filter(MOUSE_FILTER_PASS);2184c->set_h_size_flags(SIZE_EXPAND_FILL);2185hfc->add_child(c);21862187HBoxContainer *hbc_zoom = memnew(HBoxContainer);2188hfc->add_child(hbc_zoom);21892190zoom_out = memnew(Button);2191zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_out));2192zoom_out->set_flat(true);2193zoom_out->set_tooltip_text(TTRC("Zoom Out"));2194hbc_zoom->add_child(zoom_out);21952196zoom_reset = memnew(Button);2197zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_reset));2198zoom_reset->set_flat(true);2199zoom_reset->set_tooltip_text(TTRC("Zoom Reset"));2200hbc_zoom->add_child(zoom_reset);22012202zoom_in = memnew(Button);2203zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_in));2204zoom_in->set_flat(true);2205zoom_in->set_tooltip_text(TTRC("Zoom In"));2206hbc_zoom->add_child(zoom_in);22072208file = memnew(EditorFileDialog);2209file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));2210add_child(file);22112212frame_list = memnew(ItemList);2213frame_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);2214frame_list->set_v_size_flags(SIZE_EXPAND_FILL);2215frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);2216frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2217frame_list->set_select_mode(ItemList::SELECT_MULTI);22182219frame_list->set_max_columns(0);2220frame_list->set_max_text_lines(2);2221SET_DRAG_FORWARDING_GCD(frame_list, SpriteFramesEditor);2222frame_list->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input));2223// HACK: The item_selected signal is emitted before the Frame Duration spinbox loses focus and applies the change.2224frame_list->connect("multi_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected), CONNECT_DEFERRED);22252226sub_vb->add_child(frame_list);22272228dialog = memnew(AcceptDialog);2229add_child(dialog);22302231load->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_load_pressed));2232load_sheet->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet));2233delete_frame->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_delete_pressed));2234copy->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_copy_pressed));2235paste->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_paste_pressed));2236empty_before->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty_pressed));2237empty_after->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty2_pressed));2238move_up->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_up_pressed));2239move_down->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_down_pressed));22402241load->set_shortcut_context(frame_list);2242load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTRC("Add frame from file"), KeyModifierMask::CMD_OR_CTRL | Key::O));2243load_sheet->set_shortcut_context(frame_list);2244load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTRC("Add frames from sprite sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));2245delete_frame->set_shortcut_context(frame_list);2246delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTRC("Delete Frame"), Key::KEY_DELETE));2247copy->set_shortcut_context(frame_list);2248copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTRC("Copy Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::C));2249paste->set_shortcut_context(frame_list);2250paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTRC("Paste Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::V));2251empty_before->set_shortcut_context(frame_list);2252empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTRC("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));2253empty_after->set_shortcut_context(frame_list);2254empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTRC("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));2255move_up->set_shortcut_context(frame_list);2256move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTRC("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));2257move_down->set_shortcut_context(frame_list);2258move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTRC("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));22592260zoom_out->set_shortcut_context(frame_list);2261zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTRC("Zoom Out"),2262{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));2263zoom_in->set_shortcut_context(frame_list);2264zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTRC("Zoom In"),2265{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));22662267loading_scene = false;22682269updating = false;22702271edited_anim = SceneStringName(default_);22722273delete_dialog = memnew(ConfirmationDialog);2274add_child(delete_dialog);2275delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_animation_remove_confirmed));22762277split_sheet_dialog = memnew(ConfirmationDialog);2278add_child(split_sheet_dialog);2279split_sheet_dialog->set_title(TTR("Select Frames"));2280split_sheet_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));22812282HBoxContainer *split_sheet_hb = memnew(HBoxContainer);2283split_sheet_dialog->add_child(split_sheet_hb);2284split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);2285split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);22862287VBoxContainer *split_sheet_vb = memnew(VBoxContainer);2288split_sheet_hb->add_child(split_sheet_vb);2289split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);2290split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);22912292HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);22932294split_sheet_menu_hb->add_child(memnew(Label(TTR("Frame Order"))));22952296split_sheet_order = memnew(OptionButton);2297split_sheet_order->add_item(TTR("As Selected"), FRAME_ORDER_SELECTION);2298split_sheet_order->add_separator(TTR("By Row"));2299split_sheet_order->add_item(TTR("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);2300split_sheet_order->add_item(TTR("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);2301split_sheet_order->add_item(TTR("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);2302split_sheet_order->add_item(TTR("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);2303split_sheet_order->add_separator(TTR("By Column"));2304split_sheet_order->add_item(TTR("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);2305split_sheet_order->add_item(TTR("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);2306split_sheet_order->add_item(TTR("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);2307split_sheet_order->add_item(TTR("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);2308split_sheet_order->connect(SceneStringName(item_selected), callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));2309split_sheet_menu_hb->add_child(split_sheet_order);23102311Button *select_all = memnew(Button);2312select_all->set_text(TTR("Select All"));2313select_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));2314split_sheet_menu_hb->add_child(select_all);23152316Button *clear_all = memnew(Button);2317clear_all->set_text(TTR("Select None"));2318clear_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));2319split_sheet_menu_hb->add_child(clear_all);23202321split_sheet_menu_hb->add_spacer();23222323toggle_settings_button = memnew(Button);2324toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);2325toggle_settings_button->set_theme_type_variation(SceneStringName(FlatButton));2326toggle_settings_button->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));2327toggle_settings_button->set_tooltip_text(TTR("Toggle Settings Panel"));2328split_sheet_menu_hb->add_child(toggle_settings_button);23292330split_sheet_vb->add_child(split_sheet_menu_hb);23312332PanelContainer *split_sheet_panel = memnew(PanelContainer);2333split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);2334split_sheet_panel->set_v_size_flags(SIZE_EXPAND_FILL);2335split_sheet_vb->add_child(split_sheet_panel);23362337split_sheet_preview = memnew(TextureRect);2338split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);2339split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2340split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS);2341split_sheet_preview->connect(SceneStringName(draw), callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));2342split_sheet_preview->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));23432344split_sheet_scroll = memnew(ScrollContainer);2345split_sheet_scroll->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));2346split_sheet_panel->add_child(split_sheet_scroll);2347CenterContainer *cc = memnew(CenterContainer);2348cc->add_child(split_sheet_preview);2349cc->set_h_size_flags(SIZE_EXPAND_FILL);2350cc->set_v_size_flags(SIZE_EXPAND_FILL);2351split_sheet_scroll->add_child(cc);23522353MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer);2354split_sheet_panel->add_child(split_sheet_zoom_margin);2355split_sheet_zoom_margin->set_h_size_flags(0);2356split_sheet_zoom_margin->set_v_size_flags(0);2357split_sheet_zoom_margin->add_theme_constant_override("margin_top", 5);2358split_sheet_zoom_margin->add_theme_constant_override("margin_left", 5);2359HBoxContainer *split_sheet_zoom_hb = memnew(HBoxContainer);2360split_sheet_zoom_margin->add_child(split_sheet_zoom_hb);23612362split_sheet_zoom_out = memnew(Button);2363split_sheet_zoom_out->set_theme_type_variation(SceneStringName(FlatButton));2364split_sheet_zoom_out->set_focus_mode(FOCUS_ACCESSIBILITY);2365split_sheet_zoom_out->set_tooltip_text(TTR("Zoom Out"));2366split_sheet_zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out));2367split_sheet_zoom_hb->add_child(split_sheet_zoom_out);23682369split_sheet_zoom_reset = memnew(Button);2370split_sheet_zoom_reset->set_theme_type_variation(SceneStringName(FlatButton));2371split_sheet_zoom_reset->set_focus_mode(FOCUS_ACCESSIBILITY);2372split_sheet_zoom_reset->set_tooltip_text(TTR("Zoom Reset"));2373split_sheet_zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));2374split_sheet_zoom_hb->add_child(split_sheet_zoom_reset);23752376split_sheet_zoom_in = memnew(Button);2377split_sheet_zoom_in->set_theme_type_variation(SceneStringName(FlatButton));2378split_sheet_zoom_in->set_focus_mode(FOCUS_ACCESSIBILITY);2379split_sheet_zoom_in->set_tooltip_text(TTR("Zoom In"));2380split_sheet_zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));2381split_sheet_zoom_hb->add_child(split_sheet_zoom_in);23822383split_sheet_settings_vb = memnew(VBoxContainer);2384split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);23852386HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);2387split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);23882389Label *split_sheet_h_label = memnew(Label(TTR("Horizontal")));2390split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);2391split_sheet_h_hb->add_child(split_sheet_h_label);23922393split_sheet_h = memnew(SpinBox);2394split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);2395split_sheet_h->set_min(1);2396split_sheet_h->set_max(128);2397split_sheet_h->set_step(1);2398split_sheet_h->set_select_all_on_focus(true);2399split_sheet_h->set_accessibility_name(TTRC("Horizontal"));2400split_sheet_h_hb->add_child(split_sheet_h);2401split_sheet_h->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2402split_sheet_settings_vb->add_child(split_sheet_h_hb);24032404HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);2405split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);24062407Label *split_sheet_v_label = memnew(Label(TTR("Vertical")));2408split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);2409split_sheet_v_hb->add_child(split_sheet_v_label);24102411split_sheet_v = memnew(SpinBox);2412split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);2413split_sheet_v->set_min(1);2414split_sheet_v->set_max(128);2415split_sheet_v->set_step(1);2416split_sheet_v->set_select_all_on_focus(true);2417split_sheet_v->set_accessibility_name(TTRC("Vertical"));2418split_sheet_v_hb->add_child(split_sheet_v);2419split_sheet_v->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2420split_sheet_settings_vb->add_child(split_sheet_v_hb);24212422HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);2423split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);24242425Label *split_sheet_size_label = memnew(Label(TTR("Size")));2426split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);2427split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2428split_sheet_size_hb->add_child(split_sheet_size_label);24292430VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);2431split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);2432split_sheet_size_x = memnew(SpinBox);2433split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);2434split_sheet_size_x->set_min(1);2435split_sheet_size_x->set_step(1);2436split_sheet_size_x->set_suffix("px");2437split_sheet_size_x->set_select_all_on_focus(true);2438split_sheet_size_x->set_accessibility_name(TTRC("X Size"));2439split_sheet_size_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2440split_sheet_size_vb->add_child(split_sheet_size_x);2441split_sheet_size_y = memnew(SpinBox);2442split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);2443split_sheet_size_y->set_min(1);2444split_sheet_size_y->set_step(1);2445split_sheet_size_y->set_suffix("px");2446split_sheet_size_y->set_select_all_on_focus(true);2447split_sheet_size_y->set_accessibility_name(TTRC("Y Size"));2448split_sheet_size_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2449split_sheet_size_vb->add_child(split_sheet_size_y);2450split_sheet_size_hb->add_child(split_sheet_size_vb);2451split_sheet_settings_vb->add_child(split_sheet_size_hb);24522453HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);2454split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);24552456Label *split_sheet_sep_label = memnew(Label(TTR("Separation")));2457split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);2458split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2459split_sheet_sep_hb->add_child(split_sheet_sep_label);24602461VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);2462split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);2463split_sheet_sep_x = memnew(SpinBox);2464split_sheet_sep_x->set_min(0);2465split_sheet_sep_x->set_step(1);2466split_sheet_sep_x->set_suffix("px");2467split_sheet_sep_x->set_select_all_on_focus(true);2468split_sheet_sep_x->set_accessibility_name(TTRC("X Separation"));2469split_sheet_sep_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2470split_sheet_sep_vb->add_child(split_sheet_sep_x);2471split_sheet_sep_y = memnew(SpinBox);2472split_sheet_sep_y->set_min(0);2473split_sheet_sep_y->set_step(1);2474split_sheet_sep_y->set_suffix("px");2475split_sheet_sep_y->set_select_all_on_focus(true);2476split_sheet_sep_y->set_accessibility_name(TTRC("Y Separation"));2477split_sheet_sep_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2478split_sheet_sep_vb->add_child(split_sheet_sep_y);2479split_sheet_sep_hb->add_child(split_sheet_sep_vb);2480split_sheet_settings_vb->add_child(split_sheet_sep_hb);24812482HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);2483split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);24842485Label *split_sheet_offset_label = memnew(Label(TTR("Offset")));2486split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);2487split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2488split_sheet_offset_hb->add_child(split_sheet_offset_label);24892490VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);2491split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);2492split_sheet_offset_x = memnew(SpinBox);2493split_sheet_offset_x->set_min(0);2494split_sheet_offset_x->set_step(1);2495split_sheet_offset_x->set_suffix("px");2496split_sheet_offset_x->set_select_all_on_focus(true);2497split_sheet_offset_x->set_accessibility_name(TTRC("X Offset"));2498split_sheet_offset_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2499split_sheet_offset_vb->add_child(split_sheet_offset_x);2500split_sheet_offset_y = memnew(SpinBox);2501split_sheet_offset_y->set_min(0);2502split_sheet_offset_y->set_step(1);2503split_sheet_offset_y->set_suffix("px");2504split_sheet_offset_y->set_select_all_on_focus(true);2505split_sheet_offset_y->set_accessibility_name(TTRC("Y Offset"));2506split_sheet_offset_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2507split_sheet_offset_vb->add_child(split_sheet_offset_y);2508split_sheet_offset_hb->add_child(split_sheet_offset_vb);2509split_sheet_settings_vb->add_child(split_sheet_offset_hb);25102511Button *auto_slice = memnew(Button);2512auto_slice->set_text(TTR("Auto Slice"));2513auto_slice->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_auto_slice_sprite_sheet));2514split_sheet_settings_vb->add_child(auto_slice);25152516split_sheet_hb->add_child(split_sheet_settings_vb);25172518file_split_sheet = memnew(EditorFileDialog);2519file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet"));2520file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);2521add_child(file_split_sheet);2522file_split_sheet->connect("file_selected", callable_mp(this, &SpriteFramesEditor::_prepare_sprite_sheet));25232524// Config scale.2525scale_ratio = 1.2f;2526thumbnail_default_size = 96 * MAX(1, EDSCALE);2527thumbnail_zoom = MAX(1.0f, EDSCALE);2528max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE);2529min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE);2530// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.2531sheet_zoom = MAX(1.0f, EDSCALE);2532max_sheet_zoom = 128.0f * MAX(1.0f, EDSCALE);2533min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE);2534_zoom_reset();25352536// Ensure the anim search box is wide enough by default.2537// Not by setting its minimum size so it can still be shrunk if desired.2538set_split_offset(56 * EDSCALE);2539}25402541void SpriteFramesEditorPlugin::edit(Object *p_object) {2542Ref<SpriteFrames> s;2543AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object);2544if (animated_sprite) {2545s = animated_sprite->get_sprite_frames();2546} else {2547AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2548if (animated_sprite_3d) {2549s = animated_sprite_3d->get_sprite_frames();2550} else {2551s = p_object;2552}2553}25542555frames_editor->edit(s);2556}25572558bool SpriteFramesEditorPlugin::handles(Object *p_object) const {2559AnimatedSprite2D *animated_sprite_2d = Object::cast_to<AnimatedSprite2D>(p_object);2560if (animated_sprite_2d && *animated_sprite_2d->get_sprite_frames()) {2561return true;2562}2563AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2564if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) {2565return true;2566}2567SpriteFrames *frames = Object::cast_to<SpriteFrames>(p_object);2568if (frames && (frames_editor->get_sprite_frames().is_null() || frames_editor->get_sprite_frames() == frames)) {2569return true;2570}2571return false;2572}25732574void SpriteFramesEditorPlugin::make_visible(bool p_visible) {2575if (p_visible) {2576button->show();2577EditorNode::get_bottom_panel()->make_item_visible(frames_editor);2578} else {2579button->hide();2580if (frames_editor->is_visible_in_tree()) {2581EditorNode::get_bottom_panel()->hide_bottom_panel();2582}2583}2584}25852586SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {2587frames_editor = memnew(SpriteFramesEditor);2588frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);2589button = 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")));2590button->hide();2591}259225932594