Path: blob/master/editor/scene/sprite_frames_editor_plugin.cpp
21017 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/input/input.h"33#include "core/io/resource_loader.h"34#include "core/os/keyboard.h"35#include "core/string/translation_server.h"36#include "editor/docks/editor_dock_manager.h"37#include "editor/docks/filesystem_dock.h"38#include "editor/docks/scene_tree_dock.h"39#include "editor/editor_node.h"40#include "editor/editor_string_names.h"41#include "editor/editor_undo_redo_manager.h"42#include "editor/file_system/editor_file_system.h"43#include "editor/gui/editor_file_dialog.h"44#include "editor/settings/editor_command_palette.h"45#include "editor/settings/editor_settings.h"46#include "editor/themes/editor_scale.h"47#include "scene/2d/animated_sprite_2d.h"48#include "scene/3d/sprite_3d.h"49#include "scene/gui/center_container.h"50#include "scene/gui/flow_container.h"51#include "scene/gui/margin_container.h"52#include "scene/gui/option_button.h"53#include "scene/gui/panel_container.h"54#include "scene/gui/separator.h"55#include "scene/gui/split_container.h"56#include "scene/resources/atlas_texture.h"5758static 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) {59p_control->draw_line(p_from, p_from + p_size, p_color);60p_control->draw_line(p_from + p_shadow_offset, p_from + p_size + p_shadow_offset, p_shadow_color);61}6263void SpriteFramesEditor::_open_sprite_sheet() {64file_split_sheet->clear_filters();65List<String> extensions;66ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);67for (const String &extension : extensions) {68file_split_sheet->add_filter("*." + extension);69}7071file_split_sheet->popup_file_dialog();72}7374int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) {75const Size2i offset = _get_offset();76const Size2i frame_size = _get_frame_size();77const Size2i separation = _get_separation();78const Size2i block_size = frame_size + separation;79const Point2i position = p_position / sheet_zoom - offset;8081if (position.x < 0 || position.y < 0) {82return -1; // Out of bounds.83}8485if (position.x % block_size.x >= frame_size.x || position.y % block_size.y >= frame_size.y) {86return -1; // Gap between frames.87}8889const Point2i frame = position / block_size;90const Size2i frame_count = _get_frame_count();91if (frame.x >= frame_count.x || frame.y >= frame_count.y) {92return -1; // Out of bounds.93}9495return frame_count.x * frame.y + frame.x;96}9798void SpriteFramesEditor::_sheet_preview_draw() {99const Size2i frame_count = _get_frame_count();100const Size2i separation = _get_separation();101102const Size2 draw_offset = Size2(_get_offset()) * sheet_zoom;103const Size2 draw_sep = Size2(separation) * sheet_zoom;104const Size2 draw_frame_size = Size2(_get_frame_size()) * sheet_zoom;105const Size2 draw_size = draw_frame_size * frame_count + draw_sep * (frame_count - Size2i(1, 1));106107const Color line_color = Color(1, 1, 1, 0.3);108const Color shadow_color = Color(0, 0, 0, 0.3);109110// Vertical lines.111_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);112for (int i = 0; i < frame_count.x - 1; i++) {113const Point2 start = draw_offset + Vector2(i * draw_sep.x + (i + 1) * draw_frame_size.x, 0);114if (separation.x == 0) {115_draw_shadowed_line(split_sheet_preview, start, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color);116} else {117const Size2 size = Size2(draw_sep.x, draw_size.y);118split_sheet_preview->draw_rect(Rect2(start, size), line_color);119}120}121_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);122123// Horizontal lines.124_draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);125for (int i = 0; i < frame_count.y - 1; i++) {126const Point2 start = draw_offset + Vector2(0, i * draw_sep.y + (i + 1) * draw_frame_size.y);127if (separation.y == 0) {128_draw_shadowed_line(split_sheet_preview, start, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color);129} else {130const Size2 size = Size2(draw_size.x, draw_sep.y);131split_sheet_preview->draw_rect(Rect2(start, size), line_color);132}133}134_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);135136if (frames_selected.is_empty()) {137split_sheet_dialog->get_ok_button()->set_disabled(true);138split_sheet_dialog->set_ok_button_text(TTRC("No Frames Selected"));139return;140}141142Color accent = get_theme_color("accent_color", EditorStringName(Editor));143144_sheet_sort_frames();145146Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));147int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));148149for (int i = 0; i < frames_ordered.size(); ++i) {150const int idx = frames_ordered[i].second;151152const int x = idx % frame_count.x;153const int y = idx / frame_count.x;154const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep);155split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 0.35), true);156split_sheet_preview->draw_rect(Rect2(pos, draw_frame_size), Color(0, 0, 0, 1), false);157split_sheet_preview->draw_rect(Rect2(pos + Size2(1, 1), draw_frame_size - Size2(2, 2)), Color(0, 0, 0, 1), false);158split_sheet_preview->draw_rect(Rect2(pos + Size2(2, 2), draw_frame_size - Size2(4, 4)), accent, false);159split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false);160split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false);161split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false);162163const String text = itos(i);164const Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);165166// Stop rendering text if too large.167if (string_size.x + 6 < draw_frame_size.x && string_size.y / 2 + 10 < draw_frame_size.y) {168split_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));169split_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));170}171}172173split_sheet_dialog->get_ok_button()->set_disabled(false);174split_sheet_dialog->set_ok_button_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size()));175}176177void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {178const Ref<InputEventMouseButton> mb = p_event;179if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {180const int idx = _sheet_preview_position_to_frame_index(mb->get_position());181182if (idx != -1) {183if (mb->is_shift_pressed() && last_frame_selected >= 0) {184// Select multiple frames.185const int from = last_frame_selected;186const int to = idx;187188const int diff = Math::abs(to - from);189const int dir = SIGN(to - from);190191for (int i = 0; i <= diff; i++) {192const int this_idx = from + i * dir;193194// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.195frames_toggled_by_mouse_hover.insert(this_idx);196197if (mb->is_command_or_control_pressed()) {198frames_selected.erase(this_idx);199} else if (!frames_selected.has(this_idx)) {200frames_selected.insert(this_idx, selected_count);201selected_count++;202}203}204} else {205// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.206frames_toggled_by_mouse_hover.insert(idx);207208if (frames_selected.has(idx)) {209frames_selected.erase(idx);210} else {211frames_selected.insert(idx, selected_count);212selected_count++;213}214}215}216217if (last_frame_selected != idx || idx != -1) {218last_frame_selected = idx;219frames_need_sort = true;220split_sheet_preview->queue_redraw();221}222}223224if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {225frames_toggled_by_mouse_hover.clear();226}227228const Ref<InputEventMouseMotion> mm = p_event;229if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {230// Select by holding down the mouse button on frames.231const int idx = _sheet_preview_position_to_frame_index(mm->get_position());232233if (idx != -1 && !frames_toggled_by_mouse_hover.has(idx)) {234// Only allow toggling each tile once per mouse hold.235// Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor.236// The mouse button must be released before it can be toggled again.237frames_toggled_by_mouse_hover.insert(idx);238239if (frames_selected.has(idx)) {240frames_selected.erase(idx);241} else {242frames_selected.insert(idx, selected_count);243selected_count++;244}245246last_frame_selected = idx;247frames_need_sort = true;248split_sheet_preview->queue_redraw();249}250}251252if (frames_selected.is_empty()) {253selected_count = 0;254}255}256257void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {258const Ref<InputEventMouseButton> mb = p_event;259260if (mb.is_valid()) {261// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer262// to allow performing this action anywhere, even if the cursor isn't263// hovering the texture in the workspace.264// keep CTRL and not CMD_OR_CTRL as CTRL is expected even on MacOS.265if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {266_sheet_zoom_on_position(scale_ratio, mb->get_position());267// Don't scroll up after zooming in.268split_sheet_scroll->accept_event();269} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {270_sheet_zoom_on_position(1 / scale_ratio, mb->get_position());271// Don't scroll down after zooming out.272split_sheet_scroll->accept_event();273}274}275276const Ref<InputEventMouseMotion> mm = p_event;277if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::MIDDLE)) {278const Vector2 dragged = Input::get_singleton()->warp_mouse_motion(mm, split_sheet_scroll->get_global_rect());279split_sheet_scroll->set_h_scroll(split_sheet_scroll->get_h_scroll() - dragged.x);280split_sheet_scroll->set_v_scroll(split_sheet_scroll->get_v_scroll() - dragged.y);281}282}283284void SpriteFramesEditor::_sheet_add_frames() {285const Size2i frame_count = _get_frame_count();286const Size2i frame_size = _get_frame_size();287const Size2i offset = _get_offset();288const Size2i separation = _get_separation();289290EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();291undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());292int fc = frames->get_frame_count(edited_anim);293294_sheet_sort_frames();295296for (const Pair<int, int> &pair : frames_ordered) {297const int idx = pair.second;298299const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x);300301Ref<AtlasTexture> at;302at.instantiate();303at->set_atlas(split_sheet_preview->get_texture());304at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size));305306undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, at, 1.0, -1);307undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, fc);308}309310undo_redo->add_do_method(this, "_update_library");311undo_redo->add_undo_method(this, "_update_library");312undo_redo->commit_action();313}314315void SpriteFramesEditor::_sheet_update_zoom_label() {316String zoom_text;317// The zoom level displayed is relative to the editor scale318// (like in most image editors). Its lower bound is clamped to 1 as some people319// lower the editor scale to increase the available real estate,320// even if their display doesn't have a particularly low DPI.321TranslationServer *translation_server = TranslationServer::get_singleton();322String locale = translation_server->get_tool_locale();323if (sheet_zoom >= 10) {324zoom_text = translation_server->format_number(rtos(Math::round((sheet_zoom / MAX(1, EDSCALE)) * 100)), locale);325} else {326// 2 decimal places if the zoom is below 10%, 1 decimal place if it's below 1000%.327zoom_text = translation_server->format_number(rtos(Math::snapped((sheet_zoom / MAX(1, EDSCALE)) * 100, (sheet_zoom >= 0.1) ? 0.1 : 0.01)), locale);328}329zoom_text += " " + translation_server->get_percent_sign(locale);330split_sheet_zoom_reset->set_text(zoom_text);331}332333void SpriteFramesEditor::_sheet_zoom_on_position(float p_zoom, const Vector2 &p_position) {334const float old_zoom = sheet_zoom;335sheet_zoom = CLAMP(sheet_zoom * p_zoom, min_sheet_zoom, max_sheet_zoom);336337const Size2 texture_size = split_sheet_preview->get_texture()->get_size();338split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);339340Vector2 offset = Vector2(split_sheet_scroll->get_h_scroll(), split_sheet_scroll->get_v_scroll());341offset = (offset + p_position) / old_zoom * sheet_zoom - p_position;342split_sheet_scroll->set_h_scroll(offset.x);343split_sheet_scroll->set_v_scroll(offset.y);344345_sheet_update_zoom_label();346}347348void SpriteFramesEditor::_sheet_zoom_in() {349_sheet_zoom_on_position(scale_ratio, Vector2());350}351352void SpriteFramesEditor::_sheet_zoom_out() {353_sheet_zoom_on_position(1 / scale_ratio, Vector2());354}355356void SpriteFramesEditor::_sheet_zoom_reset() {357// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.358sheet_zoom = MAX(1.0f, EDSCALE);359Size2 texture_size = split_sheet_preview->get_texture()->get_size();360split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);361362_sheet_update_zoom_label();363}364365void SpriteFramesEditor::_sheet_zoom_fit() {366const float margin_percentage = 0.1f;367const float max_margin = 64.0f;368const Size2 margin = (margin_percentage * split_sheet_scroll->get_size()).minf(max_margin);369const Size2 display_area_size = split_sheet_scroll->get_size() - margin;370const Size2 texture_size = split_sheet_preview->get_texture()->get_size();371const Vector2 texture_ratio = display_area_size / texture_size;372float texture_fit_zoom = MIN(texture_ratio.x, texture_ratio.y);373374// Quantize the zoom level to avoid subpixel rendering375if (texture_fit_zoom > 1.0) {376texture_fit_zoom = Math::floor(texture_fit_zoom);377} else if (!Math::is_zero_approx(texture_fit_zoom)) {378texture_fit_zoom = 1.0f / Math::ceil(1.0f / texture_fit_zoom);379}380381sheet_zoom = CLAMP(texture_fit_zoom, min_sheet_zoom, max_sheet_zoom);382split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);383384_sheet_update_zoom_label();385}386387void SpriteFramesEditor::_sheet_order_selected(int p_option) {388frames_need_sort = true;389split_sheet_preview->queue_redraw();390}391392void SpriteFramesEditor::_sheet_select_all_frames() {393for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) {394if (!frames_selected.has(i)) {395frames_selected.insert(i, selected_count);396selected_count++;397frames_need_sort = true;398}399}400401split_sheet_preview->queue_redraw();402}403404void SpriteFramesEditor::_sheet_clear_all_frames() {405frames_selected.clear();406selected_count = 0;407408split_sheet_preview->queue_redraw();409}410411void SpriteFramesEditor::_sheet_sort_frames() {412if (!frames_need_sort) {413return;414}415frames_need_sort = false;416frames_ordered.resize(frames_selected.size());417if (frames_selected.is_empty()) {418return;419}420421const Size2i frame_count = _get_frame_count();422const int frame_order = split_sheet_order->get_selected_id();423int index = 0;424425// Fill based on order.426for (const KeyValue<int, int> &from_pair : frames_selected) {427const int idx = from_pair.key;428429const int selection_order = from_pair.value;430431// Default to using selection order.432int order_by = selection_order;433434// Extract coordinates for sorting.435const int pos_frame_x = idx % frame_count.x;436const int pos_frame_y = idx / frame_count.x;437438const int neg_frame_x = frame_count.x - (pos_frame_x + 1);439const int neg_frame_y = frame_count.y - (pos_frame_y + 1);440441switch (frame_order) {442case FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM: {443order_by = frame_count.x * pos_frame_y + pos_frame_x;444} break;445446case FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP: {447order_by = frame_count.x * neg_frame_y + pos_frame_x;448} break;449450case FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM: {451order_by = frame_count.x * pos_frame_y + neg_frame_x;452} break;453454case FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP: {455order_by = frame_count.x * neg_frame_y + neg_frame_x;456} break;457458case FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT: {459order_by = pos_frame_y + frame_count.y * pos_frame_x;460} break;461462case FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT: {463order_by = pos_frame_y + frame_count.y * neg_frame_x;464} break;465466case FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT: {467order_by = neg_frame_y + frame_count.y * pos_frame_x;468} break;469470case FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT: {471order_by = neg_frame_y + frame_count.y * neg_frame_x;472} break;473}474475// Assign in vector.476frames_ordered.set(index, Pair<int, int>(order_by, idx));477index++;478}479480// Sort frames.481frames_ordered.sort_custom<PairSort<int, int>>();482}483484void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) {485if (updating_split_settings) {486return;487}488updating_split_settings = true;489490if (p_dominant_param != PARAM_USE_CURRENT) {491dominant_param = p_dominant_param;492}493494const Size2i texture_size = split_sheet_preview->get_texture()->get_size();495const Size2i size = texture_size - _get_offset();496497switch (dominant_param) {498case PARAM_SIZE: {499const Size2i frame_size = _get_frame_size();500501const Size2i offset_max = texture_size - frame_size;502split_sheet_offset_x->set_max(offset_max.x);503split_sheet_offset_y->set_max(offset_max.y);504505const Size2i sep_max = size - frame_size * 2;506split_sheet_sep_x->set_max(sep_max.x);507split_sheet_sep_y->set_max(sep_max.y);508509const Size2i separation = _get_separation();510const Size2i count = (size + separation) / (frame_size + separation);511split_sheet_h->set_value(count.x);512split_sheet_v->set_value(count.y);513} break;514515case PARAM_FRAME_COUNT: {516const Size2i count = _get_frame_count();517518const Size2i offset_max = texture_size - count;519split_sheet_offset_x->set_max(offset_max.x);520split_sheet_offset_y->set_max(offset_max.y);521522const Size2i gap_count = count - Size2i(1, 1);523split_sheet_sep_x->set_max(gap_count.x == 0 ? size.x : (size.x - count.x) / gap_count.x);524split_sheet_sep_y->set_max(gap_count.y == 0 ? size.y : (size.y - count.y) / gap_count.y);525526const Size2i separation = _get_separation();527const Size2i frame_size = (size - separation * gap_count) / count;528split_sheet_size_x->set_value(frame_size.x);529split_sheet_size_y->set_value(frame_size.y);530} break;531}532533updating_split_settings = false;534535frames_selected.clear();536selected_count = 0;537last_frame_selected = -1;538split_sheet_preview->queue_redraw();539}540541void SpriteFramesEditor::_toggle_show_settings() {542split_sheet_settings_vb->set_visible(!split_sheet_settings_vb->is_visible());543544_update_show_settings();545}546547void SpriteFramesEditor::_update_show_settings() {548if (is_layout_rtl()) {549toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Back") : SNAME("Forward")));550} else {551toggle_settings_button->set_button_icon(get_editor_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Forward") : SNAME("Back")));552}553}554555void SpriteFramesEditor::_auto_slice_sprite_sheet() {556if (updating_split_settings) {557return;558}559updating_split_settings = true;560561const Size2i size = split_sheet_preview->get_texture()->get_size();562563const Size2i split_sheet = _estimate_sprite_sheet_size(split_sheet_preview->get_texture());564split_sheet_h->set_value(split_sheet.x);565split_sheet_v->set_value(split_sheet.y);566split_sheet_size_x->set_value(size.x / split_sheet.x);567split_sheet_size_y->set_value(size.y / split_sheet.y);568split_sheet_sep_x->set_value(0);569split_sheet_sep_y->set_value(0);570split_sheet_offset_x->set_value(0);571split_sheet_offset_y->set_value(0);572573updating_split_settings = false;574575frames_selected.clear();576selected_count = 0;577last_frame_selected = -1;578split_sheet_preview->queue_redraw();579}580581bool SpriteFramesEditor::_matches_background_color(const Color &p_background_color, const Color &p_pixel_color) {582if ((p_background_color.a == 0 && p_pixel_color.a == 0) || p_background_color.is_equal_approx(p_pixel_color)) {583return true;584}585586Color d = p_background_color - p_pixel_color;587// 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.588return (d.r * d.r) + (d.g * d.g) + (d.b * d.b) + (d.a * d.a) < 0.04f;589}590591Size2i SpriteFramesEditor::_estimate_sprite_sheet_size(const Ref<Texture2D> p_texture) {592Ref<Image> image = p_texture->get_image();593if (image->is_compressed()) {594image = image->duplicate();595ERR_FAIL_COND_V(image->decompress() != OK, p_texture->get_size());596}597Size2i size = image->get_size();598599Color assumed_background_color = image->get_pixel(0, 0);600Size2i sheet_size;601602bool previous_line_background = true;603for (int x = 0; x < size.x; x++) {604int y = 0;605while (y < size.y && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {606y++;607}608bool current_line_background = (y == size.y);609if (previous_line_background && !current_line_background) {610sheet_size.x++;611}612previous_line_background = current_line_background;613}614615previous_line_background = true;616for (int y = 0; y < size.y; y++) {617int x = 0;618while (x < size.x && _matches_background_color(assumed_background_color, image->get_pixel(x, y))) {619x++;620}621bool current_line_background = (x == size.x);622if (previous_line_background && !current_line_background) {623sheet_size.y++;624}625previous_line_background = current_line_background;626}627628if (sheet_size == Size2i(0, 0) || sheet_size == Size2i(1, 1)) {629sheet_size = Size2i(4, 4);630}631632return sheet_size;633}634635void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {636Ref<Texture2D> texture = ResourceLoader::load(p_file);637if (texture.is_null()) {638EditorNode::get_singleton()->show_warning(TTR("Unable to load images"));639ERR_FAIL_COND(texture.is_null());640}641frames_selected.clear();642selected_count = 0;643last_frame_selected = -1;644645bool first_open = split_sheet_preview->get_texture().is_null();646bool new_texture = texture != split_sheet_preview->get_texture();647split_sheet_preview->set_texture(texture);648if (new_texture) {649// Reset spin max.650const Size2i size = texture->get_size();651split_sheet_size_x->set_max(size.x);652split_sheet_size_y->set_max(size.y);653split_sheet_sep_x->set_max(size.x);654split_sheet_sep_y->set_max(size.y);655split_sheet_offset_x->set_max(size.x);656split_sheet_offset_y->set_max(size.y);657658if (size != previous_texture_size) {659// Different texture, reset to 4x4.660dominant_param = PARAM_FRAME_COUNT;661updating_split_settings = true;662const Size2i split_sheet = Size2i(4, 4);663split_sheet_h->set_value(split_sheet.x);664split_sheet_v->set_value(split_sheet.y);665split_sheet_size_x->set_value(size.x / split_sheet.x);666split_sheet_size_y->set_value(size.y / split_sheet.y);667split_sheet_sep_x->set_value(0);668split_sheet_sep_y->set_value(0);669split_sheet_offset_x->set_value(0);670split_sheet_offset_y->set_value(0);671updating_split_settings = false;672}673previous_texture_size = size;674675// Reset zoom.676if (first_open) {677_sheet_zoom_reset();678split_sheet_dialog->connect(SceneStringName(focus_entered), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_fit), CONNECT_ONE_SHOT);679} else {680_sheet_zoom_fit();681}682}683684split_sheet_dialog->popup_centered_ratio(0.65);685}686687void SpriteFramesEditor::_notification(int p_what) {688switch (p_what) {689case NOTIFICATION_ENTER_TREE: {690get_tree()->connect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));691692[[fallthrough]];693}694case NOTIFICATION_THEME_CHANGED: {695autoplay_icon = get_editor_theme_icon(SNAME("AutoPlay"));696stop_icon = get_editor_theme_icon(SNAME("Stop"));697pause_icon = get_editor_theme_icon(SNAME("Pause"));698_update_stop_icon();699700autoplay->set_button_icon(get_editor_theme_icon(SNAME("AutoPlay")));701anim_loop->set_button_icon(get_editor_theme_icon(SNAME("Loop")));702play->set_button_icon(get_editor_theme_icon(SNAME("PlayStart")));703play_from->set_button_icon(get_editor_theme_icon(SNAME("Play")));704play_bw->set_button_icon(get_editor_theme_icon(SNAME("PlayStartBackwards")));705play_bw_from->set_button_icon(get_editor_theme_icon(SNAME("PlayBackwards")));706707load->set_button_icon(get_editor_theme_icon(SNAME("Load")));708load_sheet->set_button_icon(get_editor_theme_icon(SNAME("SpriteSheet")));709copy->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));710paste->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));711empty_before->set_button_icon(get_editor_theme_icon(SNAME("InsertBefore")));712empty_after->set_button_icon(get_editor_theme_icon(SNAME("InsertAfter")));713move_up->set_button_icon(get_editor_theme_icon(SNAME("MoveLeft")));714move_down->set_button_icon(get_editor_theme_icon(SNAME("MoveRight")));715delete_frame->set_button_icon(get_editor_theme_icon(SNAME("Remove")));716zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));717zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));718zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));719add_anim->set_button_icon(get_editor_theme_icon(SNAME("New")));720duplicate_anim->set_button_icon(get_editor_theme_icon(SNAME("Duplicate")));721cut_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionCut")));722copy_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));723paste_anim->set_button_icon(get_editor_theme_icon(SNAME("ActionPaste")));724delete_anim->set_button_icon(get_editor_theme_icon(SNAME("Remove")));725anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));726split_sheet_zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));727split_sheet_zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));728split_sheet_zoom_fit->set_button_icon(get_editor_theme_icon(SNAME("DistractionFree")));729split_sheet_scroll->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));730731_update_show_settings();732} break;733734case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {735_update_show_settings();736} break;737738case NOTIFICATION_TRANSLATION_CHANGED: {739_update_show_settings();740anim_speed->set_suffix(TTR("FPS"));741742// Similar to `_update_library_impl()`, but only updates text for "empty" items.743if (frames.is_valid()) {744for (int i = 0; i < frames->get_frame_count(edited_anim); i++) {745Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);746if (texture.is_null()) {747String name = itos(i);748float duration = frames->get_frame_duration(edited_anim, i);749texture = empty_icon;750name += ": " + TTR("(empty)");751if (duration != 1.0f) {752name += String::utf8(" [× ") + String::num(duration, 2) + "]";753}754frame_list->set_item_text(i, name);755}756}757}758} break;759760case NOTIFICATION_READY: {761add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up.762} break;763764case NOTIFICATION_EXIT_TREE: {765get_tree()->disconnect("node_removed", callable_mp(this, &SpriteFramesEditor::_node_removed));766} break;767}768}769770void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_at_pos) {771ERR_FAIL_COND(!frames->has_animation(edited_anim));772773List<Ref<Texture2D>> resources;774775for (int i = 0; i < p_path.size(); i++) {776Ref<Texture2D> resource;777resource = ResourceLoader::load(p_path[i]);778779if (resource.is_null()) {780dialog->set_text(TTRC("ERROR: Couldn't load frame resource!"));781dialog->set_title(TTRC("Error!"));782783//dialog->get_cancel()->set_text("Close");784dialog->set_ok_button_text(TTRC("Close"));785dialog->popup_centered();786return; ///beh should show an error i guess787}788789resources.push_back(resource);790}791792if (resources.is_empty()) {793return;794}795796EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();797undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());798int fc = frames->get_frame_count(edited_anim);799800int count = 0;801802for (const Ref<Texture2D> &E : resources) {803undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, E, 1.0, p_at_pos == -1 ? -1 : p_at_pos + count);804undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, p_at_pos == -1 ? fc : p_at_pos);805count++;806}807undo_redo->add_do_method(this, "_update_library");808undo_redo->add_undo_method(this, "_update_library");809810undo_redo->commit_action();811}812813Size2i SpriteFramesEditor::_get_frame_count() const {814return Size2i(split_sheet_h->get_value(), split_sheet_v->get_value());815}816817Size2i SpriteFramesEditor::_get_frame_size() const {818return Size2i(split_sheet_size_x->get_value(), split_sheet_size_y->get_value());819}820821Size2i SpriteFramesEditor::_get_offset() const {822return Size2i(split_sheet_offset_x->get_value(), split_sheet_offset_y->get_value());823}824825Size2i SpriteFramesEditor::_get_separation() const {826return Size2i(split_sheet_sep_x->get_value(), split_sheet_sep_y->get_value());827}828829void SpriteFramesEditor::_load_pressed() {830ERR_FAIL_COND(!frames->has_animation(edited_anim));831loading_scene = false;832833file->clear_filters();834List<String> extensions;835ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions);836for (const String &extension : extensions) {837file->add_filter("*." + extension);838}839840file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);841file->popup_file_dialog();842}843844void SpriteFramesEditor::_paste_pressed() {845ERR_FAIL_COND(!frames->has_animation(edited_anim));846847Ref<ClipboardSpriteFrames> clipboard_frames = EditorSettings::get_singleton()->get_resource_clipboard();848if (clipboard_frames.is_valid()) {849_paste_frame_array(clipboard_frames);850return;851}852853Ref<Texture2D> texture = EditorSettings::get_singleton()->get_resource_clipboard();854if (texture.is_valid()) {855_paste_texture(texture);856return;857}858}859860void SpriteFramesEditor::_paste_frame_array(const Ref<ClipboardSpriteFrames> &p_clipboard_frames) {861if (p_clipboard_frames->frames.is_empty()) {862return;863}864865Ref<Texture2D> texture;866float duration = 1.0;867868EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();869undo_redo->create_action(TTR("Paste Frame(s)"), UndoRedo::MERGE_DISABLE, frames.ptr());870871int undo_index = frames->get_frame_count(edited_anim);872873for (int index = 0; index < p_clipboard_frames->frames.size(); index++) {874const ClipboardSpriteFrames::Frame &frame = p_clipboard_frames->frames[index];875texture = frame.texture;876duration = frame.duration;877878undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration);879undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);880}881882undo_redo->add_do_method(this, "_update_library");883undo_redo->add_undo_method(this, "_update_library");884undo_redo->commit_action();885}886887void SpriteFramesEditor::_paste_texture(const Ref<Texture2D> &p_texture) {888float duration = 1.0;889890EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();891undo_redo->create_action(TTR("Paste Texture"), UndoRedo::MERGE_DISABLE, frames.ptr());892893int undo_index = frames->get_frame_count(edited_anim);894895undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, p_texture, duration);896undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, undo_index);897898undo_redo->add_do_method(this, "_update_library");899undo_redo->add_undo_method(this, "_update_library");900undo_redo->commit_action();901}902903void SpriteFramesEditor::_copy_pressed() {904ERR_FAIL_COND(!frames->has_animation(edited_anim));905906Vector<int> selected_items = frame_list->get_selected_items();907908if (selected_items.is_empty()) {909return;910}911912Ref<ClipboardSpriteFrames> clipboard_frames = memnew(ClipboardSpriteFrames);913914for (const int &frame_index : selected_items) {915Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, frame_index);916if (texture.is_null()) {917continue;918}919920ClipboardSpriteFrames::Frame frame;921frame.texture = texture;922frame.duration = frames->get_frame_duration(edited_anim, frame_index);923924clipboard_frames->frames.push_back(frame);925}926EditorSettings::get_singleton()->set_resource_clipboard(clipboard_frames);927}928929void SpriteFramesEditor::_empty_pressed() {930ERR_FAIL_COND(!frames->has_animation(edited_anim));931932int from = -1;933Vector<int> selected_items = frame_list->get_selected_items();934935if (!selected_items.is_empty()) {936from = selected_items[0];937selection.clear();938selection.push_back(from + 1);939} else {940from = frames->get_frame_count(edited_anim);941}942943Ref<Texture2D> texture;944945EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();946undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());947undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from);948undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from);949undo_redo->add_do_method(this, "_update_library");950undo_redo->add_undo_method(this, "_update_library");951undo_redo->commit_action();952}953954void SpriteFramesEditor::_empty2_pressed() {955ERR_FAIL_COND(!frames->has_animation(edited_anim));956957int from = -1;958Vector<int> selected_items = frame_list->get_selected_items();959960if (!selected_items.is_empty()) {961from = selected_items[selected_items.size() - 1];962selection.clear();963selection.push_back(from);964} else {965from = frames->get_frame_count(edited_anim);966}967968Ref<Texture2D> texture;969970EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();971undo_redo->create_action(TTR("Add Empty"), UndoRedo::MERGE_DISABLE, frames.ptr());972undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, from + 1);973undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, from + 1);974undo_redo->add_do_method(this, "_update_library");975undo_redo->add_undo_method(this, "_update_library");976undo_redo->commit_action();977}978979void SpriteFramesEditor::_up_pressed() {980ERR_FAIL_COND(!frames->has_animation(edited_anim));981982Vector<int> selected_items = frame_list->get_selected_items();983984int nb_selected_items = selected_items.size();985if (nb_selected_items <= 0) {986return;987}988989int first_selected_frame_index = selected_items[0];990if (first_selected_frame_index < 1) {991return;992}993994EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();995undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());996997int last_overwritten_frame = -1;998999for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {1000int to_move = selected_items[selected_index];1001int new_index = to_move - 1;1002selected_items.set(selected_index, new_index);10031004undo_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));1005undo_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));10061007bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == to_move + 1;1008if (last_overwritten_frame == -1) {1009last_overwritten_frame = new_index;1010}10111012if (!is_next_item_in_selection) {1013undo_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));1014undo_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));1015last_overwritten_frame = -1;1016}1017}1018selection = selected_items;10191020undo_redo->add_do_method(this, "_update_library");1021undo_redo->add_undo_method(this, "_update_library");1022undo_redo->commit_action();1023}10241025void SpriteFramesEditor::_down_pressed() {1026ERR_FAIL_COND(!frames->has_animation(edited_anim));10271028Vector<int> selected_items = frame_list->get_selected_items();10291030int nb_selected_items = selected_items.size();1031if (nb_selected_items <= 0) {1032return;1033}10341035int last_selected_frame_index = selected_items[nb_selected_items - 1];1036if (last_selected_frame_index >= frames->get_frame_count(edited_anim) - 1) {1037return;1038}10391040EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1041undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());10421043int first_moved_frame = -1;10441045for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {1046int to_move = selected_items[selected_index];1047int new_index = to_move + 1;1048selected_items.set(selected_index, new_index);10491050undo_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));1051undo_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));10521053bool is_next_item_in_selection = selected_index + 1 < nb_selected_items && selected_items[selected_index + 1] == new_index;1054if (first_moved_frame == -1) {1055first_moved_frame = to_move;1056}10571058if (!is_next_item_in_selection) {1059undo_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));1060undo_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));1061first_moved_frame = -1;1062}1063}1064selection = selected_items;10651066undo_redo->add_do_method(this, "_update_library");1067undo_redo->add_undo_method(this, "_update_library");1068undo_redo->commit_action();1069}10701071void SpriteFramesEditor::_delete_pressed() {1072ERR_FAIL_COND(!frames->has_animation(edited_anim));10731074Vector<int> selected_items = frame_list->get_selected_items();10751076int nb_selected_items = selected_items.size();1077if (nb_selected_items <= 0) {1078return;1079}10801081EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1082undo_redo->create_action(TTR("Delete Resource"), UndoRedo::MERGE_DISABLE, frames.ptr());1083for (int selected_index = 0; selected_index < nb_selected_items; selected_index++) {1084int to_delete = selected_items[selected_index];1085undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, to_delete - selected_index);1086undo_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);1087}10881089undo_redo->add_do_method(this, "_update_library");1090undo_redo->add_undo_method(this, "_update_library");1091undo_redo->commit_action();1092}10931094void SpriteFramesEditor::_animation_selected() {1095if (updating) {1096return;1097}10981099TreeItem *selected = animations->get_selected();1100ERR_FAIL_NULL(selected);1101edited_anim = selected->get_text(0);11021103if (animated_sprite) {1104sprite_node_updating = true;1105animated_sprite->call("set_animation", edited_anim);1106sprite_node_updating = false;1107}11081109_update_library(true);1110}11111112void SpriteFramesEditor::_sync_animation() {1113if (!animated_sprite || sprite_node_updating) {1114return;1115}1116_select_animation(animated_sprite->call("get_animation"), false);1117_update_stop_icon();1118}11191120void SpriteFramesEditor::_select_animation(const String &p_name, bool p_update_node) {1121if (frames.is_null() || !frames->has_animation(p_name)) {1122return;1123}1124edited_anim = p_name;11251126if (animated_sprite) {1127if (p_update_node) {1128animated_sprite->call("set_animation", edited_anim);1129}1130}11311132_update_library();1133}11341135static void _find_anim_sprites(Node *p_node, List<Node *> *r_nodes, Ref<SpriteFrames> p_sfames) {1136Node *edited = EditorNode::get_singleton()->get_edited_scene();1137if (!edited) {1138return;1139}1140if (p_node != edited && p_node->get_owner() != edited) {1141return;1142}11431144{1145AnimatedSprite2D *as = Object::cast_to<AnimatedSprite2D>(p_node);1146if (as && as->get_sprite_frames() == p_sfames) {1147r_nodes->push_back(p_node);1148}1149}11501151{1152AnimatedSprite3D *as = Object::cast_to<AnimatedSprite3D>(p_node);1153if (as && as->get_sprite_frames() == p_sfames) {1154r_nodes->push_back(p_node);1155}1156}11571158for (int i = 0; i < p_node->get_child_count(); i++) {1159_find_anim_sprites(p_node->get_child(i), r_nodes, p_sfames);1160}1161}11621163void SpriteFramesEditor::_animation_name_edited() {1164if (updating) {1165return;1166}11671168if (!frames->has_animation(edited_anim)) {1169return;1170}11711172TreeItem *edited = animations->get_edited();1173if (!edited) {1174return;1175}11761177String new_name = edited->get_text(0);11781179if (new_name == String(edited_anim)) {1180return;1181}11821183if (new_name.is_empty()) {1184new_name = "new_animation";1185}11861187new_name = new_name.replace_char('/', '_').replace_char(',', ' ');11881189String name = new_name;1190int counter = 0;1191while (frames->has_animation(name)) {1192if (name == String(edited_anim)) {1193edited->set_text(0, name); // The name didn't change, just updated the column text to name.1194return;1195}1196counter++;1197name = new_name + "_" + itos(counter);1198}1199edited->set_text(0, name);12001201EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1202undo_redo->create_action(TTR("Rename Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());1203undo_redo->add_do_method(frames.ptr(), "rename_animation", edited_anim, name);1204undo_redo->add_undo_method(frames.ptr(), "rename_animation", name, edited_anim);1205_rename_node_animation(undo_redo, false, edited_anim, name, name);1206_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);1207undo_redo->add_do_method(this, "_select_animation", name);1208undo_redo->add_undo_method(this, "_select_animation", edited_anim);1209undo_redo->add_do_method(this, "_update_library");1210undo_redo->add_undo_method(this, "_update_library");1211undo_redo->commit_action();12121213animations->grab_focus();1214}12151216void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo, bool is_undo, const String &p_filter, const String &p_new_animation, const String &p_new_autoplay) {1217List<Node *> nodes;1218_find_anim_sprites(EditorNode::get_singleton()->get_edited_scene(), &nodes, Ref<SpriteFrames>(frames));12191220if (is_undo) {1221for (Node *E : nodes) {1222String current_name = E->call("get_animation");1223if (current_name == p_filter) {1224undo_redo->force_fixed_history(); // Fixes corner-case when editing SpriteFrames stored as separate file.1225undo_redo->add_undo_method(E, "set_animation", p_new_animation);1226}1227String autoplay_name = E->call("get_autoplay");1228if (autoplay_name == p_filter) {1229undo_redo->force_fixed_history();1230undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay);1231}1232}1233} else {1234for (Node *E : nodes) {1235String current_name = E->call("get_animation");1236if (current_name == p_filter) {1237undo_redo->force_fixed_history();1238undo_redo->add_do_method(E, "set_animation", p_new_animation);1239}1240String autoplay_name = E->call("get_autoplay");1241if (autoplay_name == p_filter) {1242undo_redo->force_fixed_history();1243undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay);1244}1245}1246}1247}12481249void SpriteFramesEditor::_animation_add() {1250String name = "new_animation";1251int counter = 0;1252while (frames->has_animation(name)) {1253counter++;1254name = vformat("new_animation_%d", counter);1255}12561257EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1258undo_redo->create_action(TTR("Add Animation"), UndoRedo::MERGE_DISABLE, frames.ptr());1259undo_redo->add_do_method(frames.ptr(), "add_animation", name);1260undo_redo->add_undo_method(frames.ptr(), "remove_animation", name);1261undo_redo->add_do_method(this, "_select_animation", name);1262undo_redo->add_undo_method(this, "_select_animation", edited_anim);1263undo_redo->add_do_method(this, "_update_library");1264undo_redo->add_undo_method(this, "_update_library");1265undo_redo->commit_action();12661267animations->grab_focus();1268}12691270void SpriteFramesEditor::_animation_duplicate() {1271if (updating) {1272return;1273}12741275if (!frames->has_animation(edited_anim)) {1276return;1277}12781279String new_name = _generate_unique_animation_name(edited_anim);1280EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1281undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());1282undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name);1283undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);1284undo_redo->add_do_method(this, "_select_animation", new_name);1285undo_redo->add_undo_method(this, "_select_animation", edited_anim);1286undo_redo->add_do_method(this, "_update_library");1287undo_redo->add_undo_method(this, "_update_library");1288undo_redo->commit_action();12891290animations->grab_focus();1291}12921293void SpriteFramesEditor::_animation_cut() {1294if (!frames->has_animation(edited_anim)) {1295return;1296}12971298// Copy animation to clipboard.1299Ref<ClipboardAnimation> clipboard_anim = ClipboardAnimation::from_sprite_frames(frames, edited_anim);1300EditorSettings::get_singleton()->set_resource_clipboard(clipboard_anim);13011302// Remove animation with undo/redo (no confirmation dialog).1303_animation_remove_undo_redo(TTR("Cut Animation"), &clipboard_anim->frames);1304}13051306void SpriteFramesEditor::_animation_copy() {1307if (!frames->has_animation(edited_anim)) {1308return;1309}13101311Ref<ClipboardAnimation> clipboard_anim = ClipboardAnimation::from_sprite_frames(frames, edited_anim);1312EditorSettings::get_singleton()->set_resource_clipboard(clipboard_anim);1313}13141315void SpriteFramesEditor::_animation_paste() {1316if (updating) {1317return;1318}13191320Ref<ClipboardAnimation> clipboard_anim = EditorSettings::get_singleton()->get_resource_clipboard();1321if (clipboard_anim.is_null()) {1322return;1323}13241325String new_name = _generate_unique_animation_name(clipboard_anim->name);1326EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1327undo_redo->create_action(TTR("Paste Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());1328undo_redo->add_do_method(frames.ptr(), "add_animation", new_name);1329undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name);1330undo_redo->add_do_method(frames.ptr(), "set_animation_speed", new_name, clipboard_anim->speed);1331undo_redo->add_do_method(frames.ptr(), "set_animation_loop", new_name, clipboard_anim->loop);13321333for (ClipboardSpriteFrames::Frame &frame : clipboard_anim->frames) {1334undo_redo->add_do_method(frames.ptr(), "add_frame", new_name, frame.texture, frame.duration);1335}13361337undo_redo->add_do_method(this, "_select_animation", new_name);1338undo_redo->add_undo_method(this, "_select_animation", edited_anim);1339undo_redo->add_do_method(this, "_update_library");1340undo_redo->add_undo_method(this, "_update_library");1341undo_redo->commit_action();1342}13431344void SpriteFramesEditor::_animation_remove() {1345if (updating) {1346return;1347}13481349if (!frames->has_animation(edited_anim)) {1350return;1351}13521353delete_dialog->set_text(TTRC("Delete Animation?"));1354delete_dialog->popup_centered();1355}13561357void SpriteFramesEditor::_animation_remove_confirmed() {1358_animation_remove_undo_redo(TTR("Remove Animation"), nullptr);1359}13601361void SpriteFramesEditor::_animation_search_text_changed(const String &p_text) {1362_update_library();1363}13641365void SpriteFramesEditor::_animation_loop_changed() {1366if (updating) {1367return;1368}13691370EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1371undo_redo->create_action(TTR("Change Animation Loop"), UndoRedo::MERGE_DISABLE, frames.ptr());1372undo_redo->add_do_method(frames.ptr(), "set_animation_loop", edited_anim, anim_loop->is_pressed());1373undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));1374undo_redo->add_do_method(this, "_update_library", true);1375undo_redo->add_undo_method(this, "_update_library", true);1376undo_redo->commit_action();1377}13781379void SpriteFramesEditor::_animation_speed_resized() {1380anim_speed->update_minimum_size();1381}13821383void SpriteFramesEditor::_animation_speed_changed(double p_value) {1384if (frames.is_null()) {1385return;1386}13871388if (updating) {1389return;1390}13911392EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1393undo_redo->create_action(TTR("Change Animation FPS"), UndoRedo::MERGE_ENDS, frames.ptr());1394undo_redo->add_do_method(frames.ptr(), "set_animation_speed", edited_anim, p_value);1395undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));1396undo_redo->add_do_method(this, "_update_library", true);1397undo_redo->add_undo_method(this, "_update_library", true);1398undo_redo->commit_action();1399}14001401void SpriteFramesEditor::_animation_remove_undo_redo(const StringName &p_action_name, const Vector<ClipboardSpriteFrames::Frame> *p_frames) {1402StringName new_edited = _find_next_animation();1403int frame_count = frames->get_frame_count(edited_anim);1404EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1405undo_redo->create_action(p_action_name, UndoRedo::MERGE_DISABLE, frames.ptr());1406_rename_node_animation(undo_redo, false, edited_anim, new_edited, "");1407undo_redo->add_do_method(frames.ptr(), "remove_animation", edited_anim);1408undo_redo->add_undo_method(frames.ptr(), "add_animation", edited_anim);1409_rename_node_animation(undo_redo, true, edited_anim, edited_anim, edited_anim);1410undo_redo->add_undo_method(frames.ptr(), "set_animation_speed", edited_anim, frames->get_animation_speed(edited_anim));1411undo_redo->add_undo_method(frames.ptr(), "set_animation_loop", edited_anim, frames->get_animation_loop(edited_anim));1412for (int i = 0; i < frame_count; i++) {1413Ref<Texture2D> texture;1414float duration;1415if (p_frames) {1416texture = (*p_frames)[i].texture;1417duration = (*p_frames)[i].duration;1418} else {1419texture = frames->get_frame_texture(edited_anim, i);1420duration = frames->get_frame_duration(edited_anim, i);1421}1422undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration);1423}1424undo_redo->add_do_method(this, "_select_animation", new_edited);1425undo_redo->add_undo_method(this, "_select_animation", edited_anim);1426undo_redo->add_do_method(this, "_update_library");1427undo_redo->add_undo_method(this, "_update_library");1428undo_redo->commit_action();1429}14301431StringName SpriteFramesEditor::_find_next_animation() {1432List<StringName> anim_names;1433frames->get_animation_list(&anim_names);1434anim_names.sort_custom<StringName::AlphCompare>();1435if (anim_names.size() >= 2) {1436if (edited_anim == anim_names.get(0)) {1437return anim_names.get(1);1438} else {1439return anim_names.get(0);1440}1441} else {1442return StringName();1443}1444}14451446String SpriteFramesEditor::_generate_unique_animation_name(const String &p_base_name) const {1447if (!frames->has_animation(p_base_name)) {1448return p_base_name;1449}14501451int count = 2;1452String new_name = p_base_name;1453PackedStringArray split = new_name.split("_");1454int last_index = split.size() - 1;1455if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) {1456count = split[last_index].to_int();1457split.remove_at(last_index);1458new_name = String("_").join(split);1459}1460while (true) {1461String attempt = new_name;1462attempt += vformat("_%d", count);1463if (frames->has_animation(attempt)) {1464count++;1465continue;1466}1467new_name = attempt;1468break;1469}1470return new_name;1471}14721473void SpriteFramesEditor::_frame_list_gui_input(const Ref<InputEvent> &p_event) {1474const Ref<InputEventMouseButton> mb = p_event;14751476if (mb.is_valid()) {1477if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {1478_zoom_in();1479// Don't scroll up after zooming in.1480accept_event();1481} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) {1482_zoom_out();1483// Don't scroll down after zooming out.1484accept_event();1485} else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {1486Point2 pos = mb->get_position();1487right_clicked_frame = frame_list->get_item_at_position(pos, true);1488if (right_clicked_frame != -1) {1489Ref<Texture2D> tex = frames->get_frame_texture(edited_anim, right_clicked_frame);1490if (tex.is_null()) {1491return;1492}1493if (!menu) {1494menu = memnew(PopupMenu);1495add_child(menu);1496menu->connect(SceneStringName(id_pressed), callable_mp(this, &SpriteFramesEditor::_menu_selected));1497menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTRC("Show in FileSystem"), MENU_SHOW_IN_FILESYSTEM);1498}14991500menu->set_position(get_screen_position() + get_local_mouse_position());1501menu->popup();1502}1503}1504}1505}15061507void SpriteFramesEditor::_menu_selected(int p_id) {1508switch (p_id) {1509case MENU_SHOW_IN_FILESYSTEM: {1510Ref<Texture2D> frame_texture = frames->get_frame_texture(edited_anim, right_clicked_frame);1511ERR_FAIL_COND(frame_texture.is_null());1512String path = frame_texture->get_path();1513// Check if the file is an atlas resource, if it is find the source texture.1514Ref<AtlasTexture> at = frame_texture;1515while (at.is_valid() && at->get_atlas().is_valid()) {1516path = at->get_atlas()->get_path();1517at = at->get_atlas();1518}1519FileSystemDock::get_singleton()->navigate_to_path(path);1520} break;1521}1522}15231524void SpriteFramesEditor::_frame_list_item_selected(int p_index, bool p_selected) {1525if (updating) {1526return;1527}15281529selection = frame_list->get_selected_items();1530if (selection.is_empty() || !p_selected) {1531return;1532}15331534updating = true;1535frame_duration->set_value(frames->get_frame_duration(edited_anim, selection[0]));1536updating = false;1537}15381539void SpriteFramesEditor::_frame_duration_changed(double p_value) {1540if (frames.is_null()) {1541return;1542}15431544if (updating) {1545return;1546}15471548if (selection.is_empty()) {1549return;1550}15511552EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1553undo_redo->create_action(TTR("Set Frame Duration"), UndoRedo::MERGE_ENDS, frames.ptr());15541555for (const int &index : selection) {1556Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, index);1557float old_duration = frames->get_frame_duration(edited_anim, index);15581559undo_redo->add_do_method(frames.ptr(), "set_frame", edited_anim, index, texture, p_value);1560undo_redo->add_undo_method(frames.ptr(), "set_frame", edited_anim, index, texture, old_duration);1561}15621563undo_redo->add_do_method(this, "_update_library");1564undo_redo->add_undo_method(this, "_update_library");1565undo_redo->commit_action();1566}15671568void SpriteFramesEditor::_zoom_in() {1569// Do not zoom in or out with no visible frames1570if (frames->get_frame_count(edited_anim) <= 0) {1571return;1572}1573if (thumbnail_zoom < max_thumbnail_zoom) {1574thumbnail_zoom *= scale_ratio;1575int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);1576frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);1577frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));1578}1579}15801581void SpriteFramesEditor::_zoom_out() {1582// Do not zoom in or out with no visible frames1583if (frames->get_frame_count(edited_anim) <= 0) {1584return;1585}1586if (thumbnail_zoom > min_thumbnail_zoom) {1587thumbnail_zoom /= scale_ratio;1588int thumbnail_size = (int)(thumbnail_default_size * thumbnail_zoom);1589frame_list->set_fixed_column_width(thumbnail_size * 3 / 2);1590frame_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size));1591}1592}15931594void SpriteFramesEditor::_zoom_reset() {1595thumbnail_zoom = MAX(1.0f, EDSCALE);1596frame_list->set_fixed_column_width(thumbnail_default_size * 3 / 2);1597frame_list->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size));1598}15991600void SpriteFramesEditor::_update_library(bool p_skip_selector) {1601if (!p_skip_selector) {1602animations_dirty = true;1603}16041605if (pending_update) {1606return;1607}1608pending_update = true;1609callable_mp(this, &SpriteFramesEditor::_update_library_impl).call_deferred();1610}16111612void SpriteFramesEditor::_update_library_impl() {1613pending_update = false;16141615if (frames.is_null()) {1616return;1617}16181619updating = true;16201621frame_duration->set_value_no_signal(1.0); // Default.16221623if (animations_dirty) {1624animations_dirty = false;1625animations->clear();16261627TreeItem *anim_root = animations->create_item();16281629List<StringName> anim_names;1630frames->get_animation_list(&anim_names);1631anim_names.sort_custom<StringName::AlphCompare>();1632if (!anim_names.size()) {1633missing_anim_label->show();1634anim_frames_vb->hide();1635updating = false;1636return;1637}1638missing_anim_label->hide();1639anim_frames_vb->show();1640bool searching = anim_search_box->get_text().size();1641String searched_string = searching ? anim_search_box->get_text().to_lower() : String();16421643TreeItem *selected = nullptr;1644for (const StringName &E : anim_names) {1645String name = E;1646if (searching && !name.to_lower().contains(searched_string)) {1647continue;1648}1649TreeItem *it = animations->create_item(anim_root);1650it->set_metadata(0, name);1651it->set_text(0, name);1652it->set_editable(0, true);1653if (animated_sprite) {1654if (name == String(animated_sprite->call("get_autoplay"))) {1655it->set_icon(0, autoplay_icon);1656}1657}1658if (E == edited_anim) {1659it->select(0);1660selected = it;1661}1662}1663if (selected) {1664animations->scroll_to_item(selected);1665}1666}16671668if (animated_sprite) {1669String autoplay_name = animated_sprite->call("get_autoplay");1670if (autoplay_name.is_empty()) {1671autoplay->set_pressed_no_signal(false);1672} else {1673autoplay->set_pressed_no_signal(String(edited_anim) == autoplay_name);1674}1675}16761677frame_list->clear();16781679if (!frames->has_animation(edited_anim)) {1680updating = false;1681return;1682}16831684int anim_frame_count = frames->get_frame_count(edited_anim);1685if (anim_frame_count == 0) {1686selection.clear();1687}16881689for (int index = 0; index < selection.size(); index++) {1690int sel = selection[index];1691if (sel == -1) {1692selection.remove_at(index);1693index--;1694}1695if (sel >= anim_frame_count) {1696selection.set(index, anim_frame_count - 1);1697// Since selection is ordered, if we get a frame that is outside of the range1698// we can clip all the other one.1699selection.resize(index + 1);1700break;1701}1702}17031704if (selection.is_empty() && frames->get_frame_count(edited_anim)) {1705selection.push_back(0);1706}17071708bool is_first_selection = true;1709// NOTE: When the language is changed, the text of the items is updated in `NOTIFICATION_TRANSLATION_CHANGED`.1710// If there are changes related to the items and their text in the loop below, the code in `NOTIFICATION_TRANSLATION_CHANGED` must also be changed.1711for (int i = 0; i < frames->get_frame_count(edited_anim); i++) {1712String name = itos(i);1713Ref<Texture2D> texture = frames->get_frame_texture(edited_anim, i);1714float duration = frames->get_frame_duration(edited_anim, i);17151716if (texture.is_null()) {1717texture = empty_icon;1718name += ": " + TTR("(empty)");1719} else if (!texture->get_name().is_empty()) {1720name += ": " + texture->get_name();1721}17221723if (duration != 1.0f) {1724name += String::utf8(" [× ") + String::num(duration, 2) + "]";1725}17261727frame_list->add_item(name, texture);1728if (texture.is_valid()) {1729String tooltip = texture->get_path();17301731// Frame is often saved as an AtlasTexture subresource within a scene/resource file,1732// thus its path might be not what the user is looking for. So we're also showing1733// subsequent source texture paths.1734String prefix = U"┖╴";1735Ref<AtlasTexture> at = texture;1736while (at.is_valid() && at->get_atlas().is_valid()) {1737tooltip += "\n" + prefix + at->get_atlas()->get_path();1738prefix = " " + prefix;1739at = at->get_atlas();1740}17411742frame_list->set_item_tooltip(-1, tooltip);1743}1744if (selection.has(i)) {1745frame_list->select(frame_list->get_item_count() - 1, is_first_selection);1746if (is_first_selection) {1747frame_duration->set_value_no_signal(frames->get_frame_duration(edited_anim, i));1748}1749is_first_selection = false;1750}1751}17521753anim_speed->set_value_no_signal(frames->get_animation_speed(edited_anim));1754anim_loop->set_pressed_no_signal(frames->get_animation_loop(edited_anim));17551756updating = false;1757}17581759void SpriteFramesEditor::_edit() {1760if (!animated_sprite) {1761return;1762}1763edit(animated_sprite->call("get_sprite_frames"));1764}17651766void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) {1767_update_stop_icon();17681769if (p_frames.is_null()) {1770frames.unref();1771_remove_sprite_node();1772return;1773}17741775frames = p_frames;1776read_only = EditorNode::get_singleton()->is_resource_read_only(p_frames);17771778if (!p_frames->has_animation(edited_anim)) {1779List<StringName> anim_names;1780frames->get_animation_list(&anim_names);1781anim_names.sort_custom<StringName::AlphCompare>();1782if (anim_names.size()) {1783edited_anim = anim_names.front()->get();1784} else {1785edited_anim = StringName();1786}1787}17881789_update_library();1790// Clear zoom and split sheet texture1791split_sheet_preview->set_texture(Ref<Texture2D>());1792_zoom_reset();17931794add_anim->set_disabled(read_only);1795duplicate_anim->set_disabled(read_only);1796cut_anim->set_disabled(read_only);1797copy_anim->set_disabled(read_only);1798paste_anim->set_disabled(read_only);1799delete_anim->set_disabled(read_only);1800anim_speed->set_editable(!read_only);1801anim_loop->set_disabled(read_only);1802load->set_disabled(read_only);1803load_sheet->set_disabled(read_only);1804copy->set_disabled(read_only);1805paste->set_disabled(read_only);1806empty_before->set_disabled(read_only);1807empty_after->set_disabled(read_only);1808move_up->set_disabled(read_only);1809move_down->set_disabled(read_only);1810delete_frame->set_disabled(read_only);18111812_fetch_sprite_node(); // Fetch node after set frames.1813}18141815Ref<SpriteFrames> SpriteFramesEditor::get_sprite_frames() const {1816return frames;1817}18181819Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {1820if (read_only) {1821return false;1822}18231824if (!frames->has_animation(edited_anim)) {1825return false;1826}18271828int idx = -1;1829if (p_point == Vector2(Math::INF, Math::INF)) {1830if (frame_list->is_anything_selected()) {1831idx = frame_list->get_selected_items()[0];1832}1833} else {1834idx = frame_list->get_item_at_position(p_point, true);1835}18361837if (idx < 0 || idx >= frames->get_frame_count(edited_anim)) {1838return Variant();1839}18401841Ref<Resource> frame = frames->get_frame_texture(edited_anim, idx);18421843if (frame.is_null()) {1844return Variant();1845}18461847Dictionary drag_data = EditorNode::get_singleton()->drag_resource(frame, p_from);1848drag_data["frame"] = idx; // store the frame, in case we want to reorder frames inside 'drop_data_fw'1849return drag_data;1850}18511852bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {1853if (read_only) {1854return false;1855}18561857Dictionary d = p_data;18581859if (!d.has("type")) {1860return false;1861}18621863// reordering frames1864if (d.has("from") && (Object *)(d["from"]) == frame_list) {1865return true;1866}18671868if (String(d["type"]) == "resource" && d.has("resource")) {1869Ref<Resource> r = d["resource"];18701871Ref<Texture2D> texture = r;18721873if (texture.is_valid()) {1874return true;1875}1876}18771878if (String(d["type"]) == "files") {1879Vector<String> files = d["files"];18801881if (files.is_empty()) {1882return false;1883}18841885for (int i = 0; i < files.size(); i++) {1886const String &f = files[i];1887String ftype = EditorFileSystem::get_singleton()->get_file_type(f);18881889if (!ClassDB::is_parent_class(ftype, "Texture2D")) {1890return false;1891}1892}18931894return true;1895}1896return false;1897}18981899void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {1900if (!can_drop_data_fw(p_point, p_data, p_from)) {1901return;1902}19031904Dictionary d = p_data;19051906if (!d.has("type")) {1907return;1908}19091910int at_pos = -1;1911if (p_point == Vector2(Math::INF, Math::INF)) {1912if (frame_list->is_anything_selected()) {1913at_pos = frame_list->get_selected_items()[0];1914}1915} else {1916at_pos = frame_list->get_item_at_position(p_point, true);1917}19181919if (String(d["type"]) == "resource" && d.has("resource")) {1920Ref<Resource> r = d["resource"];19211922Ref<Texture2D> texture = r;19231924if (texture.is_valid()) {1925bool reorder = false;1926if (d.has("from") && (Object *)(d["from"]) == frame_list) {1927reorder = true;1928}19291930EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1931if (reorder) { //drop is from reordering frames1932int from_frame = -1;1933float duration = 1.0;1934if (d.has("frame")) {1935from_frame = d["frame"];1936duration = frames->get_frame_duration(edited_anim, from_frame);1937}19381939undo_redo->create_action(TTR("Move Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1940undo_redo->add_do_method(frames.ptr(), "remove_frame", edited_anim, from_frame == -1 ? frames->get_frame_count(edited_anim) : from_frame);1941undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, duration, at_pos == -1 ? -1 : at_pos);1942undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) - 1 : at_pos);1943undo_redo->add_undo_method(frames.ptr(), "add_frame", edited_anim, texture, duration, from_frame);1944undo_redo->add_do_method(this, "_update_library");1945undo_redo->add_undo_method(this, "_update_library");1946undo_redo->commit_action();1947} else {1948undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, frames.ptr());1949undo_redo->add_do_method(frames.ptr(), "add_frame", edited_anim, texture, 1.0, at_pos == -1 ? -1 : at_pos);1950undo_redo->add_undo_method(frames.ptr(), "remove_frame", edited_anim, at_pos == -1 ? frames->get_frame_count(edited_anim) : at_pos);1951undo_redo->add_do_method(this, "_update_library");1952undo_redo->add_undo_method(this, "_update_library");1953undo_redo->commit_action();1954}1955}1956}19571958if (String(d["type"]) == "files") {1959Vector<String> files = d["files"];19601961if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {1962_prepare_sprite_sheet(files[0]);1963} else {1964_file_load_request(files, at_pos);1965}1966}1967}19681969void SpriteFramesEditor::_update_stop_icon() {1970bool is_playing = false;1971if (animated_sprite) {1972is_playing = animated_sprite->call("is_playing");1973}1974if (is_playing) {1975stop->set_button_icon(pause_icon);1976} else {1977stop->set_button_icon(stop_icon);1978}1979}19801981void SpriteFramesEditor::_remove_sprite_node() {1982if (!animated_sprite) {1983return;1984}1985if (animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {1986animated_sprite->disconnect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));1987}1988if (animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {1989animated_sprite->disconnect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation));1990}1991if (animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {1992animated_sprite->disconnect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));1993}1994animated_sprite = nullptr;1995}19961997void SpriteFramesEditor::_fetch_sprite_node() {1998Node *selected = nullptr;1999EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();2000const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();2001if (top_node_list.size() == 1) {2002selected = top_node_list.front()->get();2003}20042005bool show_node_edit = false;2006AnimatedSprite2D *as2d = Object::cast_to<AnimatedSprite2D>(selected);2007AnimatedSprite3D *as3d = Object::cast_to<AnimatedSprite3D>(selected);2008if (as2d || as3d) {2009if (frames != selected->call("get_sprite_frames")) {2010_remove_sprite_node();2011} else {2012animated_sprite = selected;2013if (!animated_sprite->is_connected("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit))) {2014animated_sprite->connect("sprite_frames_changed", callable_mp(this, &SpriteFramesEditor::_edit));2015}2016if (!animated_sprite->is_connected(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation))) {2017animated_sprite->connect(SceneStringName(animation_changed), callable_mp(this, &SpriteFramesEditor::_sync_animation), CONNECT_DEFERRED);2018}2019if (!animated_sprite->is_connected(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon))) {2020animated_sprite->connect(SceneStringName(animation_finished), callable_mp(this, &SpriteFramesEditor::_update_stop_icon));2021}2022show_node_edit = true;2023}2024} else {2025_remove_sprite_node();2026}20272028if (show_node_edit) {2029_sync_animation();2030autoplay_container->show();2031playback_container->show();2032} else {2033_update_library(); // To init autoplay icon.2034autoplay_container->hide();2035playback_container->hide();2036}2037}20382039void SpriteFramesEditor::_play_pressed() {2040if (animated_sprite) {2041animated_sprite->call("stop");2042animated_sprite->call("play", animated_sprite->call("get_animation"));2043}2044_update_stop_icon();2045}20462047void SpriteFramesEditor::_play_from_pressed() {2048if (animated_sprite) {2049animated_sprite->call("play", animated_sprite->call("get_animation"));2050}2051_update_stop_icon();2052}20532054void SpriteFramesEditor::_play_bw_pressed() {2055if (animated_sprite) {2056animated_sprite->call("stop");2057animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));2058}2059_update_stop_icon();2060}20612062void SpriteFramesEditor::_play_bw_from_pressed() {2063if (animated_sprite) {2064animated_sprite->call("play_backwards", animated_sprite->call("get_animation"));2065}2066_update_stop_icon();2067}20682069void SpriteFramesEditor::_stop_pressed() {2070if (animated_sprite) {2071if (animated_sprite->call("is_playing")) {2072animated_sprite->call("pause");2073} else {2074animated_sprite->call("stop");2075}2076}2077_update_stop_icon();2078}20792080void SpriteFramesEditor::_autoplay_pressed() {2081if (updating) {2082return;2083}20842085if (animated_sprite) {2086EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2087undo_redo->create_action(TTR("Toggle Autoplay"), UndoRedo::MERGE_DISABLE, animated_sprite);2088String current = animated_sprite->call("get_animation");2089String current_auto = animated_sprite->call("get_autoplay");2090if (current == current_auto) {2091//unset2092undo_redo->add_do_method(animated_sprite, "set_autoplay", "");2093undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);2094} else {2095//set2096undo_redo->add_do_method(animated_sprite, "set_autoplay", current);2097undo_redo->add_undo_method(animated_sprite, "set_autoplay", current_auto);2098}2099undo_redo->add_do_method(this, "_update_library");2100undo_redo->add_undo_method(this, "_update_library");2101undo_redo->commit_action();2102}21032104_update_library();2105}21062107void SpriteFramesEditor::_bind_methods() {2108ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false));2109ClassDB::bind_method(D_METHOD("_select_animation", "name", "update_node"), &SpriteFramesEditor::_select_animation, DEFVAL(true));2110}21112112void SpriteFramesEditor::_node_removed(Node *p_node) {2113if (animated_sprite) {2114if (animated_sprite != p_node) {2115return;2116}2117_remove_sprite_node();2118}2119}21202121SpriteFramesEditor::SpriteFramesEditor() {2122set_name(TTRC("SpriteFrames"));2123set_icon_name("SpriteFrames");2124set_dock_shortcut(ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_sprite_frames_bottom_panel", TTRC("Open SpriteFrames Dock")));2125set_default_slot(EditorDock::DOCK_SLOT_BOTTOM);2126set_available_layouts(EditorDock::DOCK_LAYOUT_HORIZONTAL | EditorDock::DOCK_LAYOUT_FLOATING);2127set_global(false);2128set_transient(true);21292130HSplitContainer *main_split = memnew(HSplitContainer);2131add_child(main_split);21322133VBoxContainer *vbc_animlist = memnew(VBoxContainer);2134main_split->add_child(vbc_animlist);2135vbc_animlist->set_custom_minimum_size(Size2(150 * EDSCALE, 0));21362137VBoxContainer *sub_vb = memnew(VBoxContainer);2138vbc_animlist->add_margin_child(TTRC("Animations:"), sub_vb, true);2139sub_vb->set_v_size_flags(SIZE_EXPAND_FILL);21402141HBoxContainer *hbc_animlist = memnew(HBoxContainer);2142sub_vb->add_child(hbc_animlist);21432144add_anim = memnew(Button);2145add_anim->set_theme_type_variation(SceneStringName(FlatButton));2146add_anim->set_accessibility_name(TTRC("Add Animation"));2147hbc_animlist->add_child(add_anim);2148add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add));21492150duplicate_anim = memnew(Button);2151duplicate_anim->set_theme_type_variation(SceneStringName(FlatButton));2152duplicate_anim->set_accessibility_name(TTRC("Duplicate Animation"));2153hbc_animlist->add_child(duplicate_anim);2154duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate));2155duplicate_anim->set_visible(false);21562157cut_anim = memnew(Button);2158cut_anim->set_theme_type_variation(SceneStringName(FlatButton));2159cut_anim->set_accessibility_name(TTRC("Cut Animation"));2160hbc_animlist->add_child(cut_anim);2161cut_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_cut));21622163copy_anim = memnew(Button);2164copy_anim->set_theme_type_variation(SceneStringName(FlatButton));2165copy_anim->set_accessibility_name(TTRC("Copy Animation"));2166hbc_animlist->add_child(copy_anim);2167copy_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_copy));21682169paste_anim = memnew(Button);2170paste_anim->set_theme_type_variation(SceneStringName(FlatButton));2171paste_anim->set_accessibility_name(TTRC("Paste Animation"));2172hbc_animlist->add_child(paste_anim);2173paste_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_paste));21742175delete_anim = memnew(Button);2176delete_anim->set_theme_type_variation(SceneStringName(FlatButton));2177delete_anim->set_accessibility_name(TTRC("Delete Animation"));2178hbc_animlist->add_child(delete_anim);2179delete_anim->set_disabled(true);2180delete_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_remove));21812182autoplay_container = memnew(HBoxContainer);2183hbc_animlist->add_child(autoplay_container);21842185autoplay_container->add_child(memnew(VSeparator));21862187autoplay = memnew(Button);2188autoplay->set_theme_type_variation(SceneStringName(FlatButton));2189autoplay->set_tooltip_text(TTRC("Autoplay on Load"));2190autoplay_container->add_child(autoplay);21912192hbc_animlist->add_child(memnew(VSeparator));21932194anim_loop = memnew(Button);2195anim_loop->set_toggle_mode(true);2196anim_loop->set_theme_type_variation(SceneStringName(FlatButton));2197anim_loop->set_tooltip_text(TTRC("Animation Looping"));2198anim_loop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_loop_changed));2199hbc_animlist->add_child(anim_loop);22002201anim_speed = memnew(SpinBox);2202anim_speed->set_suffix(TTR("FPS"));2203anim_speed->set_min(0);2204anim_speed->set_max(120);2205anim_speed->set_step(0.01);2206anim_speed->set_custom_arrow_step(1);2207anim_speed->set_tooltip_text(TTRC("Animation Speed"));2208anim_speed->get_line_edit()->set_expand_to_text_length_enabled(true);2209anim_speed->get_line_edit()->connect(SceneStringName(resized), callable_mp(this, &SpriteFramesEditor::_animation_speed_resized));2210anim_speed->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_animation_speed_changed));2211hbc_animlist->add_child(anim_speed);22122213anim_search_box = memnew(LineEdit);2214sub_vb->add_child(anim_search_box);2215anim_search_box->set_h_size_flags(SIZE_EXPAND_FILL);2216anim_search_box->set_placeholder(TTRC("Filter Animations"));2217anim_search_box->set_clear_button_enabled(true);2218anim_search_box->connect(SceneStringName(text_changed), callable_mp(this, &SpriteFramesEditor::_animation_search_text_changed));22192220animations = memnew(Tree);2221sub_vb->add_child(animations);2222animations->set_v_size_flags(SIZE_EXPAND_FILL);2223animations->set_hide_root(true);2224animations->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);2225// HACK: The cell_selected signal is emitted before the FPS spinbox loses focus and applies the change.2226animations->connect("cell_selected", callable_mp(this, &SpriteFramesEditor::_animation_selected), CONNECT_DEFERRED);2227animations->connect("item_edited", callable_mp(this, &SpriteFramesEditor::_animation_name_edited));2228animations->set_theme_type_variation("TreeSecondary");2229animations->set_allow_reselect(true);22302231add_anim->set_shortcut_context(animations);2232add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTRC("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N));2233duplicate_anim->set_shortcut_context(animations);2234duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTRC("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D));2235cut_anim->set_shortcut_context(animations);2236cut_anim->set_shortcut(ED_SHORTCUT("sprite_frames/cut_animation", TTRC("Cut Animation"), KeyModifierMask::CMD_OR_CTRL | Key::X));2237copy_anim->set_shortcut_context(animations);2238copy_anim->set_shortcut(ED_SHORTCUT("sprite_frames/copy_animation", TTRC("Copy Animation"), KeyModifierMask::CMD_OR_CTRL | Key::C));2239paste_anim->set_shortcut_context(animations);2240paste_anim->set_shortcut(ED_SHORTCUT("sprite_frames/paste_animation", TTRC("Paste Animation"), KeyModifierMask::CMD_OR_CTRL | Key::V));2241delete_anim->set_shortcut_context(animations);2242delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTRC("Delete Animation"), Key::KEY_DELETE));22432244missing_anim_label = memnew(Label);2245missing_anim_label->set_focus_mode(FOCUS_ACCESSIBILITY);2246missing_anim_label->set_text(TTRC("This resource does not have any animations."));2247missing_anim_label->set_h_size_flags(SIZE_EXPAND_FILL);2248missing_anim_label->set_v_size_flags(SIZE_EXPAND_FILL);2249missing_anim_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);2250missing_anim_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);2251missing_anim_label->hide();2252main_split->add_child(missing_anim_label);22532254anim_frames_vb = memnew(VBoxContainer);2255main_split->add_child(anim_frames_vb);2256anim_frames_vb->set_h_size_flags(SIZE_EXPAND_FILL);2257anim_frames_vb->hide();22582259sub_vb = memnew(VBoxContainer);2260anim_frames_vb->add_margin_child(TTRC("Animation Frames:"), sub_vb, true);22612262HFlowContainer *hfc = memnew(HFlowContainer);2263sub_vb->add_child(hfc);22642265playback_container = memnew(HBoxContainer);2266playback_container->set_layout_direction(LAYOUT_DIRECTION_LTR);2267hfc->add_child(playback_container);22682269play_bw_from = memnew(Button);2270play_bw_from->set_theme_type_variation(SceneStringName(FlatButton));2271playback_container->add_child(play_bw_from);22722273play_bw = memnew(Button);2274play_bw->set_theme_type_variation(SceneStringName(FlatButton));2275playback_container->add_child(play_bw);22762277stop = memnew(Button);2278stop->set_theme_type_variation(SceneStringName(FlatButton));2279playback_container->add_child(stop);22802281play = memnew(Button);2282play->set_theme_type_variation(SceneStringName(FlatButton));2283playback_container->add_child(play);22842285play_from = memnew(Button);2286play_from->set_theme_type_variation(SceneStringName(FlatButton));2287playback_container->add_child(play_from);22882289hfc->add_child(memnew(VSeparator));22902291autoplay->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_autoplay_pressed));2292autoplay->set_toggle_mode(true);2293play->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_pressed));2294play_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_from_pressed));2295play_bw->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_pressed));2296play_bw_from->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_play_bw_from_pressed));2297stop->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_stop_pressed));22982299HBoxContainer *hbc_actions = memnew(HBoxContainer);2300hfc->add_child(hbc_actions);23012302load = memnew(Button);2303load->set_accessibility_name(TTRC("Load"));2304load->set_theme_type_variation(SceneStringName(FlatButton));2305hbc_actions->add_child(load);23062307load_sheet = memnew(Button);2308load_sheet->set_accessibility_name(TTRC("Load Sheet"));2309load_sheet->set_theme_type_variation(SceneStringName(FlatButton));2310hbc_actions->add_child(load_sheet);23112312hbc_actions->add_child(memnew(VSeparator));23132314copy = memnew(Button);2315copy->set_accessibility_name(TTRC("Copy"));2316copy->set_theme_type_variation(SceneStringName(FlatButton));2317hbc_actions->add_child(copy);23182319paste = memnew(Button);2320paste->set_accessibility_name(TTRC("Paste"));2321paste->set_theme_type_variation(SceneStringName(FlatButton));2322hbc_actions->add_child(paste);23232324hbc_actions->add_child(memnew(VSeparator));23252326empty_before = memnew(Button);2327empty_before->set_accessibility_name(TTRC("Empty Before"));2328empty_before->set_theme_type_variation(SceneStringName(FlatButton));2329hbc_actions->add_child(empty_before);23302331empty_after = memnew(Button);2332empty_after->set_accessibility_name(TTRC("Empty After"));2333empty_after->set_theme_type_variation(SceneStringName(FlatButton));2334hbc_actions->add_child(empty_after);23352336hbc_actions->add_child(memnew(VSeparator));23372338move_up = memnew(Button);2339move_up->set_accessibility_name(TTRC("Move Up"));2340move_up->set_theme_type_variation(SceneStringName(FlatButton));2341hbc_actions->add_child(move_up);23422343move_down = memnew(Button);2344move_down->set_accessibility_name(TTRC("Move Down"));2345move_down->set_theme_type_variation(SceneStringName(FlatButton));2346hbc_actions->add_child(move_down);23472348delete_frame = memnew(Button);2349delete_frame->set_accessibility_name(TTRC("Delete Frame"));2350delete_frame->set_theme_type_variation(SceneStringName(FlatButton));2351hbc_actions->add_child(delete_frame);23522353hbc_actions->add_child(memnew(VSeparator));23542355HBoxContainer *hbc_frame_duration = memnew(HBoxContainer);2356hfc->add_child(hbc_frame_duration);23572358Label *label = memnew(Label);2359label->set_text(TTRC("Frame Duration:"));2360hbc_frame_duration->add_child(label);23612362frame_duration = memnew(SpinBox);2363frame_duration->set_prefix(String::utf8("×"));2364frame_duration->set_min(SPRITE_FRAME_MINIMUM_DURATION); // Avoid zero div.2365frame_duration->set_max(10);2366frame_duration->set_step(0.01);2367frame_duration->set_custom_arrow_step(0.1);2368frame_duration->set_allow_lesser(false);2369frame_duration->set_allow_greater(true);2370frame_duration->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_frame_duration_changed));2371frame_duration->set_accessibility_name(TTRC("Frame Duration:"));2372hbc_frame_duration->add_child(frame_duration);23732374// Wide empty separation control. (like BoxContainer::add_spacer())2375Control *c = memnew(Control);2376c->set_mouse_filter(MOUSE_FILTER_PASS);2377c->set_h_size_flags(SIZE_EXPAND_FILL);2378hfc->add_child(c);23792380HBoxContainer *hbc_zoom = memnew(HBoxContainer);2381hfc->add_child(hbc_zoom);23822383zoom_out = memnew(Button);2384zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_out));2385zoom_out->set_flat(true);2386zoom_out->set_tooltip_text(TTRC("Zoom Out"));2387hbc_zoom->add_child(zoom_out);23882389zoom_reset = memnew(Button);2390zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_reset));2391zoom_reset->set_flat(true);2392zoom_reset->set_tooltip_text(TTRC("Zoom Reset"));2393hbc_zoom->add_child(zoom_reset);23942395zoom_in = memnew(Button);2396zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_zoom_in));2397zoom_in->set_flat(true);2398zoom_in->set_tooltip_text(TTRC("Zoom In"));2399hbc_zoom->add_child(zoom_in);24002401file = memnew(EditorFileDialog);2402file->connect("files_selected", callable_mp(this, &SpriteFramesEditor::_file_load_request).bind(-1));2403add_child(file);24042405frame_list = memnew(ItemList);2406frame_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);2407frame_list->set_v_size_flags(SIZE_EXPAND_FILL);2408frame_list->set_icon_mode(ItemList::ICON_MODE_TOP);2409frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2410frame_list->set_select_mode(ItemList::SELECT_MULTI);2411frame_list->set_theme_type_variation("ItemListSecondary");24122413frame_list->set_max_columns(0);2414frame_list->set_max_text_lines(2);2415SET_DRAG_FORWARDING_GCD(frame_list, SpriteFramesEditor);2416frame_list->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_frame_list_gui_input));2417// HACK: The item_selected signal is emitted before the Frame Duration spinbox loses focus and applies the change.2418frame_list->connect("multi_selected", callable_mp(this, &SpriteFramesEditor::_frame_list_item_selected), CONNECT_DEFERRED);24192420sub_vb->add_child(frame_list);24212422dialog = memnew(AcceptDialog);2423add_child(dialog);24242425load->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_load_pressed));2426load_sheet->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_open_sprite_sheet));2427delete_frame->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_delete_pressed));2428copy->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_copy_pressed));2429paste->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_paste_pressed));2430empty_before->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty_pressed));2431empty_after->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_empty2_pressed));2432move_up->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_up_pressed));2433move_down->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_down_pressed));24342435play_bw_from->set_shortcut(ED_SHORTCUT("sprite_frames/play_animation_backwards", TTRC("Play Animation Backwards"), Key::A));2436play_bw->set_shortcut(ED_SHORTCUT("sprite_frames/play_animation_from_end", TTRC("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A));2437stop->set_shortcut(ED_SHORTCUT("sprite_frames/stop_animation", TTRC("Pause/Stop Animation"), Key::S));2438play->set_shortcut(ED_SHORTCUT("sprite_frames/play_animation_from_start", TTRC("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D));2439play_from->set_shortcut(ED_SHORTCUT("sprite_frames/play_animation", TTRC("Play Animation"), Key::D));2440load->set_shortcut_context(frame_list);2441load->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_file", TTRC("Add Frame from File"), KeyModifierMask::CMD_OR_CTRL | Key::O));2442load_sheet->set_shortcut_context(frame_list);2443load_sheet->set_shortcut(ED_SHORTCUT("sprite_frames/load_from_sheet", TTRC("Add Frames from Sprite Sheet"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::O));2444delete_frame->set_shortcut_context(frame_list);2445delete_frame->set_shortcut(ED_SHORTCUT("sprite_frames/delete", TTRC("Delete Frame"), Key::KEY_DELETE));2446copy->set_shortcut_context(frame_list);2447copy->set_shortcut(ED_SHORTCUT("sprite_frames/copy", TTRC("Copy Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::C));2448paste->set_shortcut_context(frame_list);2449paste->set_shortcut(ED_SHORTCUT("sprite_frames/paste", TTRC("Paste Frame(s)"), KeyModifierMask::CMD_OR_CTRL | Key::V));2450empty_before->set_shortcut_context(frame_list);2451empty_before->set_shortcut(ED_SHORTCUT("sprite_frames/empty_before", TTRC("Insert Empty (Before Selected)"), KeyModifierMask::ALT | Key::LEFT));2452empty_after->set_shortcut_context(frame_list);2453empty_after->set_shortcut(ED_SHORTCUT("sprite_frames/empty_after", TTRC("Insert Empty (After Selected)"), KeyModifierMask::ALT | Key::RIGHT));2454move_up->set_shortcut_context(frame_list);2455move_up->set_shortcut(ED_SHORTCUT("sprite_frames/move_left", TTRC("Move Frame Left"), KeyModifierMask::CMD_OR_CTRL | Key::LEFT));2456move_down->set_shortcut_context(frame_list);2457move_down->set_shortcut(ED_SHORTCUT("sprite_frames/move_right", TTRC("Move Frame Right"), KeyModifierMask::CMD_OR_CTRL | Key::RIGHT));24582459zoom_out->set_shortcut_context(frame_list);2460zoom_out->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_out", TTRC("Zoom Out"),2461{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));2462zoom_in->set_shortcut_context(frame_list);2463zoom_in->set_shortcut(ED_SHORTCUT_ARRAY("sprite_frames/zoom_in", TTRC("Zoom In"),2464{ int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));24652466loading_scene = false;24672468updating = false;24692470edited_anim = SceneStringName(default_);24712472delete_dialog = memnew(ConfirmationDialog);2473add_child(delete_dialog);2474delete_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_animation_remove_confirmed));24752476split_sheet_dialog = memnew(ConfirmationDialog);2477split_sheet_dialog->set_flag(Window::FLAG_MAXIMIZE_DISABLED, false);2478add_child(split_sheet_dialog);2479split_sheet_dialog->set_title(TTRC("Select Frames"));2480split_sheet_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));24812482HBoxContainer *split_sheet_hb = memnew(HBoxContainer);2483split_sheet_dialog->add_child(split_sheet_hb);2484split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);2485split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);24862487VBoxContainer *split_sheet_vb = memnew(VBoxContainer);2488split_sheet_hb->add_child(split_sheet_vb);2489split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);2490split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);24912492HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);24932494split_sheet_menu_hb->add_child(memnew(Label(TTRC("Frame Order"))));24952496split_sheet_order = memnew(OptionButton);2497split_sheet_order->add_item(TTRC("As Selected"), FRAME_ORDER_SELECTION);2498split_sheet_order->add_separator(TTRC("By Row"));2499split_sheet_order->add_item(TTRC("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);2500split_sheet_order->add_item(TTRC("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);2501split_sheet_order->add_item(TTRC("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);2502split_sheet_order->add_item(TTRC("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);2503split_sheet_order->add_separator(TTRC("By Column"));2504split_sheet_order->add_item(TTRC("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);2505split_sheet_order->add_item(TTRC("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);2506split_sheet_order->add_item(TTRC("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);2507split_sheet_order->add_item(TTRC("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);2508split_sheet_order->connect(SceneStringName(item_selected), callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));2509split_sheet_menu_hb->add_child(split_sheet_order);25102511Button *select_all = memnew(Button);2512select_all->set_text(TTRC("Select All"));2513select_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));2514split_sheet_menu_hb->add_child(select_all);25152516Button *clear_all = memnew(Button);2517clear_all->set_text(TTRC("Select None"));2518clear_all->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));2519split_sheet_menu_hb->add_child(clear_all);25202521split_sheet_menu_hb->add_spacer();25222523toggle_settings_button = memnew(Button);2524toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);2525toggle_settings_button->set_theme_type_variation(SceneStringName(FlatButton));2526toggle_settings_button->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));2527toggle_settings_button->set_tooltip_text(TTRC("Toggle Settings Panel"));2528split_sheet_menu_hb->add_child(toggle_settings_button);25292530split_sheet_vb->add_child(split_sheet_menu_hb);25312532PanelContainer *split_sheet_panel = memnew(PanelContainer);2533split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);2534split_sheet_panel->set_v_size_flags(SIZE_EXPAND_FILL);2535split_sheet_vb->add_child(split_sheet_panel);25362537split_sheet_preview = memnew(TextureRect);2538split_sheet_preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);2539split_sheet_preview->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);2540split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS);2541split_sheet_preview->connect(SceneStringName(draw), callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw));2542split_sheet_preview->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_preview_input));25432544split_sheet_scroll = memnew(ScrollContainer);2545split_sheet_scroll->connect(SceneStringName(gui_input), callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input));2546split_sheet_panel->add_child(split_sheet_scroll);2547CenterContainer *cc = memnew(CenterContainer);2548cc->add_child(split_sheet_preview);2549cc->set_h_size_flags(SIZE_EXPAND_FILL);2550cc->set_v_size_flags(SIZE_EXPAND_FILL);2551split_sheet_scroll->add_child(cc);25522553MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer);2554split_sheet_panel->add_child(split_sheet_zoom_margin);2555split_sheet_zoom_margin->set_h_size_flags(0);2556split_sheet_zoom_margin->set_v_size_flags(0);2557split_sheet_zoom_margin->add_theme_constant_override("margin_top", 5);2558split_sheet_zoom_margin->add_theme_constant_override("margin_left", 5);2559HBoxContainer *split_sheet_zoom_hb = memnew(HBoxContainer);2560split_sheet_zoom_margin->add_child(split_sheet_zoom_hb);25612562split_sheet_zoom_out = memnew(Button);2563split_sheet_zoom_out->set_theme_type_variation(SceneStringName(FlatButton));2564split_sheet_zoom_out->set_focus_mode(FOCUS_ACCESSIBILITY);2565split_sheet_zoom_out->set_tooltip_text(TTRC("Zoom Out"));2566split_sheet_zoom_out->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out));2567split_sheet_zoom_hb->add_child(split_sheet_zoom_out);25682569split_sheet_zoom_reset = memnew(Button);2570split_sheet_zoom_reset->set_theme_type_variation(SceneStringName(FlatButton));2571split_sheet_zoom_reset->set_focus_mode(FOCUS_ACCESSIBILITY);2572split_sheet_zoom_reset->set_tooltip_text(TTRC("Zoom Reset"));2573split_sheet_zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset));2574split_sheet_zoom_hb->add_child(split_sheet_zoom_reset);25752576split_sheet_zoom_in = memnew(Button);2577split_sheet_zoom_in->set_theme_type_variation(SceneStringName(FlatButton));2578split_sheet_zoom_in->set_focus_mode(FOCUS_ACCESSIBILITY);2579split_sheet_zoom_in->set_tooltip_text(TTRC("Zoom In"));2580split_sheet_zoom_in->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));2581split_sheet_zoom_hb->add_child(split_sheet_zoom_in);25822583split_sheet_zoom_fit = memnew(Button);2584split_sheet_zoom_fit->set_theme_type_variation(SceneStringName(FlatButton));2585split_sheet_zoom_fit->set_focus_mode(FOCUS_ACCESSIBILITY);2586split_sheet_zoom_fit->set_tooltip_text(TTRC("Zoom to Fit"));2587split_sheet_zoom_fit->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_sheet_zoom_fit));2588split_sheet_zoom_hb->add_child(split_sheet_zoom_fit);25892590split_sheet_settings_vb = memnew(VBoxContainer);2591split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);25922593HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);2594split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);25952596Label *split_sheet_h_label = memnew(Label(TTRC("Horizontal")));2597split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);2598split_sheet_h_hb->add_child(split_sheet_h_label);25992600split_sheet_h = memnew(SpinBox);2601split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);2602split_sheet_h->set_min(1);2603split_sheet_h->set_max(128);2604split_sheet_h->set_step(1);2605split_sheet_h->set_select_all_on_focus(true);2606split_sheet_h->set_accessibility_name(TTRC("Horizontal"));2607split_sheet_h_hb->add_child(split_sheet_h);2608split_sheet_h->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2609split_sheet_settings_vb->add_child(split_sheet_h_hb);26102611HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);2612split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);26132614Label *split_sheet_v_label = memnew(Label(TTRC("Vertical")));2615split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);2616split_sheet_v_hb->add_child(split_sheet_v_label);26172618split_sheet_v = memnew(SpinBox);2619split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);2620split_sheet_v->set_min(1);2621split_sheet_v->set_max(128);2622split_sheet_v->set_step(1);2623split_sheet_v->set_select_all_on_focus(true);2624split_sheet_v->set_accessibility_name(TTRC("Vertical"));2625split_sheet_v_hb->add_child(split_sheet_v);2626split_sheet_v->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));2627split_sheet_settings_vb->add_child(split_sheet_v_hb);26282629HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);2630split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);26312632Label *split_sheet_size_label = memnew(Label(TTRC("Size")));2633split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);2634split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2635split_sheet_size_hb->add_child(split_sheet_size_label);26362637VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);2638split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);2639split_sheet_size_x = memnew(SpinBox);2640split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);2641split_sheet_size_x->set_min(1);2642split_sheet_size_x->set_step(1);2643split_sheet_size_x->set_suffix("px");2644split_sheet_size_x->set_select_all_on_focus(true);2645split_sheet_size_x->set_accessibility_name(TTRC("X Size"));2646split_sheet_size_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2647split_sheet_size_vb->add_child(split_sheet_size_x);2648split_sheet_size_y = memnew(SpinBox);2649split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);2650split_sheet_size_y->set_min(1);2651split_sheet_size_y->set_step(1);2652split_sheet_size_y->set_suffix("px");2653split_sheet_size_y->set_select_all_on_focus(true);2654split_sheet_size_y->set_accessibility_name(TTRC("Y Size"));2655split_sheet_size_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));2656split_sheet_size_vb->add_child(split_sheet_size_y);2657split_sheet_size_hb->add_child(split_sheet_size_vb);2658split_sheet_settings_vb->add_child(split_sheet_size_hb);26592660HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);2661split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);26622663Label *split_sheet_sep_label = memnew(Label(TTRC("Separation")));2664split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);2665split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2666split_sheet_sep_hb->add_child(split_sheet_sep_label);26672668VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);2669split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);2670split_sheet_sep_x = memnew(SpinBox);2671split_sheet_sep_x->set_min(0);2672split_sheet_sep_x->set_step(1);2673split_sheet_sep_x->set_suffix("px");2674split_sheet_sep_x->set_select_all_on_focus(true);2675split_sheet_sep_x->set_accessibility_name(TTRC("X Separation"));2676split_sheet_sep_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2677split_sheet_sep_vb->add_child(split_sheet_sep_x);2678split_sheet_sep_y = memnew(SpinBox);2679split_sheet_sep_y->set_min(0);2680split_sheet_sep_y->set_step(1);2681split_sheet_sep_y->set_suffix("px");2682split_sheet_sep_y->set_select_all_on_focus(true);2683split_sheet_sep_y->set_accessibility_name(TTRC("Y Separation"));2684split_sheet_sep_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2685split_sheet_sep_vb->add_child(split_sheet_sep_y);2686split_sheet_sep_hb->add_child(split_sheet_sep_vb);2687split_sheet_settings_vb->add_child(split_sheet_sep_hb);26882689HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);2690split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);26912692Label *split_sheet_offset_label = memnew(Label(TTRC("Offset")));2693split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);2694split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);2695split_sheet_offset_hb->add_child(split_sheet_offset_label);26962697VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);2698split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);2699split_sheet_offset_x = memnew(SpinBox);2700split_sheet_offset_x->set_min(0);2701split_sheet_offset_x->set_step(1);2702split_sheet_offset_x->set_suffix("px");2703split_sheet_offset_x->set_select_all_on_focus(true);2704split_sheet_offset_x->set_accessibility_name(TTRC("X Offset"));2705split_sheet_offset_x->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2706split_sheet_offset_vb->add_child(split_sheet_offset_x);2707split_sheet_offset_y = memnew(SpinBox);2708split_sheet_offset_y->set_min(0);2709split_sheet_offset_y->set_step(1);2710split_sheet_offset_y->set_suffix("px");2711split_sheet_offset_y->set_select_all_on_focus(true);2712split_sheet_offset_y->set_accessibility_name(TTRC("Y Offset"));2713split_sheet_offset_y->connect(SceneStringName(value_changed), callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));2714split_sheet_offset_vb->add_child(split_sheet_offset_y);2715split_sheet_offset_hb->add_child(split_sheet_offset_vb);2716split_sheet_settings_vb->add_child(split_sheet_offset_hb);27172718Button *auto_slice = memnew(Button);2719auto_slice->set_text(TTRC("Auto Slice"));2720auto_slice->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_auto_slice_sprite_sheet));2721split_sheet_settings_vb->add_child(auto_slice);27222723split_sheet_hb->add_child(split_sheet_settings_vb);27242725file_split_sheet = memnew(EditorFileDialog);2726file_split_sheet->set_title(TTRC("Create Frames from Sprite Sheet"));2727file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);2728add_child(file_split_sheet);2729// Deferred so file dialog is hidden when sprite sheet dialog popups. Otherwise, after allowing2730// sprite sheet dialog to be maximized, it would complain about already having exclusive child window.2731file_split_sheet->connect("file_selected", callable_mp(this, &SpriteFramesEditor::_prepare_sprite_sheet), CONNECT_DEFERRED);27322733// Config scale.2734scale_ratio = 1.2f;2735thumbnail_default_size = 96 * MAX(1, EDSCALE);2736thumbnail_zoom = MAX(1.0f, EDSCALE);2737max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE);2738min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE);2739// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.2740sheet_zoom = MAX(1.0f, EDSCALE);2741max_sheet_zoom = 128.0f * MAX(1.0f, EDSCALE);2742min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE);2743_zoom_reset();27442745// Ensure the anim search box is wide enough by default.2746// Not by setting its minimum size so it can still be shrunk if desired.2747main_split->set_split_offset(56 * EDSCALE);2748}27492750void SpriteFramesEditorPlugin::edit(Object *p_object) {2751Ref<SpriteFrames> s;2752AnimatedSprite2D *animated_sprite = Object::cast_to<AnimatedSprite2D>(p_object);2753if (animated_sprite) {2754s = animated_sprite->get_sprite_frames();2755} else {2756AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2757if (animated_sprite_3d) {2758s = animated_sprite_3d->get_sprite_frames();2759} else {2760s = p_object;2761}2762}27632764frames_editor->edit(s);2765}27662767bool SpriteFramesEditorPlugin::handles(Object *p_object) const {2768AnimatedSprite2D *animated_sprite_2d = Object::cast_to<AnimatedSprite2D>(p_object);2769if (animated_sprite_2d && *animated_sprite_2d->get_sprite_frames()) {2770return true;2771}2772AnimatedSprite3D *animated_sprite_3d = Object::cast_to<AnimatedSprite3D>(p_object);2773if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) {2774return true;2775}2776SpriteFrames *frames = Object::cast_to<SpriteFrames>(p_object);2777if (frames && (frames_editor->get_sprite_frames().is_null() || frames_editor->get_sprite_frames() == frames)) {2778return true;2779}2780return false;2781}27822783void SpriteFramesEditorPlugin::make_visible(bool p_visible) {2784if (p_visible) {2785frames_editor->make_visible();2786} else {2787frames_editor->close();2788}2789}27902791SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() {2792frames_editor = memnew(SpriteFramesEditor);2793frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE);2794EditorDockManager::get_singleton()->add_dock(frames_editor);2795frames_editor->close();2796}27972798Ref<ClipboardAnimation> ClipboardAnimation::from_sprite_frames(const Ref<SpriteFrames> &p_frames, const String &p_anim) {2799Ref<ClipboardAnimation> clipboard_anim;2800clipboard_anim.instantiate();2801clipboard_anim->name = p_anim;2802clipboard_anim->speed = p_frames->get_animation_speed(p_anim);2803clipboard_anim->loop = p_frames->get_animation_loop(p_anim);28042805int frame_count = p_frames->get_frame_count(p_anim);2806for (int i = 0; i < frame_count; ++i) {2807ClipboardSpriteFrames::Frame frame;2808frame.texture = p_frames->get_frame_texture(p_anim, i);2809frame.duration = p_frames->get_frame_duration(p_anim, i);2810clipboard_anim->frames.push_back(frame);2811}2812return clipboard_anim;2813}281428152816