Path: blob/master/editor/scene/texture/texture_region_editor_plugin.cpp
9904 views
/**************************************************************************/1/* texture_region_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 "texture_region_editor_plugin.h"3132#include "core/input/input.h"33#include "core/os/keyboard.h"34#include "editor/editor_node.h"35#include "editor/editor_string_names.h"36#include "editor/editor_undo_redo_manager.h"37#include "editor/settings/editor_settings.h"38#include "editor/themes/editor_scale.h"39#include "scene/2d/sprite_2d.h"40#include "scene/3d/sprite_3d.h"41#include "scene/gui/nine_patch_rect.h"42#include "scene/gui/option_button.h"43#include "scene/gui/panel_container.h"44#include "scene/gui/separator.h"45#include "scene/gui/spin_box.h"46#include "scene/gui/view_panner.h"47#include "scene/resources/atlas_texture.h"48#include "scene/resources/style_box_texture.h"4950Transform2D TextureRegionEditor::_get_offset_transform() const {51Transform2D mtx;52mtx.columns[2] = -draw_ofs * draw_zoom;53mtx.scale_basis(Vector2(draw_zoom, draw_zoom));5455return mtx;56}5758void TextureRegionEditor::_texture_preview_draw() {59const Ref<Texture2D> object_texture = _get_edited_object_texture();60if (object_texture.is_null()) {61return;62}6364Transform2D mtx = _get_offset_transform();6566RS::get_singleton()->canvas_item_add_set_transform(texture_preview->get_canvas_item(), mtx);67texture_preview->draw_rect(Rect2(Point2(), object_texture->get_size()), Color(0.5, 0.5, 0.5, 0.5), false);68texture_preview->draw_texture(object_texture, Point2());69RS::get_singleton()->canvas_item_add_set_transform(texture_preview->get_canvas_item(), Transform2D());70}7172void TextureRegionEditor::_texture_overlay_draw() {73const Ref<Texture2D> object_texture = _get_edited_object_texture();74if (object_texture.is_null()) {75return;76}7778Transform2D mtx = _get_offset_transform();79const Color color = get_theme_color(SNAME("mono_color"), EditorStringName(Editor));8081if (snap_mode == SNAP_GRID) {82const Color grid_color = Color(color.r, color.g, color.b, color.a * 0.15);83Size2 s = texture_overlay->get_size();84int last_cell = 0;8586if (snap_step.x != 0) {87if (snap_separation.x == 0) {88for (int i = 0; i < s.width; i++) {89int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / snap_step.x));90if (i == 0) {91last_cell = cell;92}93if (last_cell != cell) {94texture_overlay->draw_line(Point2(i, 0), Point2(i, s.height), grid_color);95}96last_cell = cell;97}98} else {99for (int i = 0; i < s.width + snap_separation.x; i++) {100int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / (snap_step.x + snap_separation.x)));101if (i == 0) {102last_cell = cell;103}104if (last_cell != cell) {105texture_overlay->draw_rect(Rect2(i - snap_separation.x * draw_zoom, 0, snap_separation.x * draw_zoom, s.height), grid_color);106}107last_cell = cell;108}109}110}111112if (snap_step.y != 0) {113if (snap_separation.y == 0) {114for (int i = 0; i < s.height; i++) {115int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / snap_step.y));116if (i == 0) {117last_cell = cell;118}119if (last_cell != cell) {120texture_overlay->draw_line(Point2(0, i), Point2(s.width, i), grid_color);121}122last_cell = cell;123}124} else {125for (int i = 0; i < s.height + snap_separation.y; i++) {126int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / (snap_step.y + snap_separation.y)));127if (i == 0) {128last_cell = cell;129}130if (last_cell != cell) {131texture_overlay->draw_rect(Rect2(0, i - snap_separation.y * draw_zoom, s.width, snap_separation.y * draw_zoom), grid_color);132}133last_cell = cell;134}135}136}137} else if (snap_mode == SNAP_AUTOSLICE) {138for (const Rect2 &r : autoslice_cache) {139const Vector2 endpoints[4] = {140mtx.basis_xform(r.position),141mtx.basis_xform(r.position + Vector2(r.size.x, 0)),142mtx.basis_xform(r.position + r.size),143mtx.basis_xform(r.position + Vector2(0, r.size.y))144};145for (int i = 0; i < 4; i++) {146int next = (i + 1) % 4;147texture_overlay->draw_line(endpoints[i] - draw_ofs * draw_zoom, endpoints[next] - draw_ofs * draw_zoom, Color(0.3, 0.7, 1, 1), 2);148}149}150}151152Ref<Texture2D> select_handle = get_editor_theme_icon(SNAME("EditorHandle"));153154Rect2 scroll_rect(Point2(), object_texture->get_size());155156const Vector2 raw_endpoints[4] = {157rect.position,158rect.position + Vector2(rect.size.x, 0),159rect.position + rect.size,160rect.position + Vector2(0, rect.size.y)161};162const Vector2 endpoints[4] = {163mtx.basis_xform(raw_endpoints[0]),164mtx.basis_xform(raw_endpoints[1]),165mtx.basis_xform(raw_endpoints[2]),166mtx.basis_xform(raw_endpoints[3])167};168for (int i = 0; i < 4; i++) {169int prev = (i + 3) % 4;170int next = (i + 1) % 4;171172Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();173ofs *= Math::SQRT2 * (select_handle->get_size().width / 2);174175texture_overlay->draw_line(endpoints[i] - draw_ofs * draw_zoom, endpoints[next] - draw_ofs * draw_zoom, color, 2);176177if (snap_mode != SNAP_AUTOSLICE) {178texture_overlay->draw_texture(select_handle, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor() - draw_ofs * draw_zoom);179}180181ofs = (endpoints[next] - endpoints[i]) / 2;182ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);183184if (snap_mode != SNAP_AUTOSLICE) {185texture_overlay->draw_texture(select_handle, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor() - draw_ofs * draw_zoom);186}187188scroll_rect.expand_to(raw_endpoints[i]);189}190191const Size2 scroll_margin = texture_overlay->get_size() / draw_zoom;192scroll_rect.position -= scroll_margin;193scroll_rect.size += scroll_margin * 2;194195updating_scroll = true;196197hscroll->set_min(scroll_rect.position.x);198hscroll->set_max(scroll_rect.position.x + scroll_rect.size.x);199if (Math::abs(scroll_rect.position.x - (scroll_rect.position.x + scroll_rect.size.x)) <= scroll_margin.x) {200hscroll->hide();201} else {202hscroll->show();203hscroll->set_page(scroll_margin.x);204hscroll->set_value(draw_ofs.x);205}206207vscroll->set_min(scroll_rect.position.y);208vscroll->set_max(scroll_rect.position.y + scroll_rect.size.y);209if (Math::abs(scroll_rect.position.y - (scroll_rect.position.y + scroll_rect.size.y)) <= scroll_margin.y) {210vscroll->hide();211draw_ofs.y = scroll_rect.position.y;212} else {213vscroll->show();214vscroll->set_page(scroll_margin.y);215vscroll->set_value(draw_ofs.y);216}217218Size2 hmin = hscroll->get_combined_minimum_size();219Size2 vmin = vscroll->get_combined_minimum_size();220221// Avoid scrollbar overlapping.222hscroll->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, vscroll->is_visible() ? -vmin.width : 0);223vscroll->set_anchor_and_offset(SIDE_BOTTOM, Control::ANCHOR_END, hscroll->is_visible() ? -hmin.height : 0);224225updating_scroll = false;226227if (request_center && hscroll->get_min() < 0) {228hscroll->set_value((hscroll->get_min() + hscroll->get_max() - hscroll->get_page()) / 2);229vscroll->set_value((vscroll->get_min() + vscroll->get_max() - vscroll->get_page()) / 2);230// This ensures that the view is updated correctly.231callable_mp(this, &TextureRegionEditor::_pan_callback).call_deferred(Vector2(1, 0), Ref<InputEvent>());232callable_mp(this, &TextureRegionEditor::_scroll_changed).call_deferred(0.0);233request_center = false;234}235236if (node_ninepatch || res_stylebox.is_valid()) {237float margins[4] = { 0 };238if (node_ninepatch) {239margins[0] = node_ninepatch->get_patch_margin(SIDE_TOP);240margins[1] = node_ninepatch->get_patch_margin(SIDE_BOTTOM);241margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT);242margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT);243} else if (res_stylebox.is_valid()) {244margins[0] = res_stylebox->get_texture_margin(SIDE_TOP);245margins[1] = res_stylebox->get_texture_margin(SIDE_BOTTOM);246margins[2] = res_stylebox->get_texture_margin(SIDE_LEFT);247margins[3] = res_stylebox->get_texture_margin(SIDE_RIGHT);248}249250Vector2 pos[4] = {251mtx.basis_xform(Vector2(0, margins[0])) + Vector2(0, endpoints[0].y - draw_ofs.y * draw_zoom),252-mtx.basis_xform(Vector2(0, margins[1])) + Vector2(0, endpoints[2].y - draw_ofs.y * draw_zoom),253mtx.basis_xform(Vector2(margins[2], 0)) + Vector2(endpoints[0].x - draw_ofs.x * draw_zoom, 0),254-mtx.basis_xform(Vector2(margins[3], 0)) + Vector2(endpoints[2].x - draw_ofs.x * draw_zoom, 0)255};256257_draw_margin_line(pos[0], pos[0] + Vector2(texture_overlay->get_size().x, 0));258_draw_margin_line(pos[1], pos[1] + Vector2(texture_overlay->get_size().x, 0));259_draw_margin_line(pos[2], pos[2] + Vector2(0, texture_overlay->get_size().y));260_draw_margin_line(pos[3], pos[3] + Vector2(0, texture_overlay->get_size().y));261}262}263264void TextureRegionEditor::_draw_margin_line(Vector2 p_from, Vector2 p_to) {265// Margin line is a dashed line with a normalized dash length. This method works266// for both vertical and horizontal lines.267268Vector2 dash_size = (p_to - p_from).normalized() * 10;269const int dash_thickness = Math::round(2 * EDSCALE);270const Color dash_color = get_theme_color(SNAME("mono_color"), EditorStringName(Editor));271const Color dash_bg_color = dash_color.inverted() * Color(1, 1, 1, 0.5);272const int line_threshold = 200;273274// Draw a translucent background line to make the foreground line visible on any background.275texture_overlay->draw_line(p_from, p_to, dash_bg_color, dash_thickness);276277Vector2 dash_start = p_from;278while (dash_start.distance_squared_to(p_to) > line_threshold) {279texture_overlay->draw_line(dash_start, dash_start + dash_size, dash_color, dash_thickness);280281// Skip two size lengths, one for the drawn dash and one for the gap.282dash_start += dash_size * 2;283}284}285286void TextureRegionEditor::_set_grid_parameters_clamping(bool p_enabled) {287sb_off_x->set_allow_lesser(!p_enabled);288sb_off_x->set_allow_greater(!p_enabled);289sb_off_y->set_allow_lesser(!p_enabled);290sb_off_y->set_allow_greater(!p_enabled);291sb_step_x->set_allow_greater(!p_enabled);292sb_step_y->set_allow_greater(!p_enabled);293sb_sep_x->set_allow_greater(!p_enabled);294sb_sep_y->set_allow_greater(!p_enabled);295}296297void TextureRegionEditor::_texture_overlay_input(const Ref<InputEvent> &p_input) {298if (panner->gui_input(p_input, texture_overlay->get_global_rect())) {299return;300}301302Transform2D mtx;303mtx.columns[2] = -draw_ofs * draw_zoom;304mtx.scale_basis(Vector2(draw_zoom, draw_zoom));305306EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();307Ref<InputEventMouseButton> mb = p_input;308if (mb.is_valid()) {309if (mb->get_button_index() == MouseButton::LEFT) {310if (mb->is_pressed() && !panner->is_panning()) {311// Check if we click on any handle first.312{313const real_t handle_radius = 16 * EDSCALE;314const real_t handle_offset = 8 * EDSCALE;315316// Position of selection handles.317const Vector2 endpoints[8] = {318mtx.xform(rect.position) + Vector2(-handle_offset, -handle_offset),319mtx.xform(rect.position + Vector2(rect.size.x / 2, 0)) + Vector2(0, -handle_offset),320mtx.xform(rect.position + Vector2(rect.size.x, 0)) + Vector2(handle_offset, -handle_offset),321mtx.xform(rect.position + Vector2(rect.size.x, rect.size.y / 2)) + Vector2(handle_offset, 0),322mtx.xform(rect.position + rect.size) + Vector2(handle_offset, handle_offset),323mtx.xform(rect.position + Vector2(rect.size.x / 2, rect.size.y)) + Vector2(0, handle_offset),324mtx.xform(rect.position + Vector2(0, rect.size.y)) + Vector2(-handle_offset, handle_offset),325mtx.xform(rect.position + Vector2(0, rect.size.y / 2)) + Vector2(-handle_offset, 0)326};327328drag_from = mtx.affine_inverse().xform(mb->get_position());329if (snap_mode == SNAP_PIXEL) {330drag_from = drag_from.snappedf(1);331} else if (snap_mode == SNAP_GRID) {332drag_from = snap_point(drag_from);333}334drag = true;335336rect_prev = _get_edited_object_region();337338for (int i = 0; i < 8; i++) {339Vector2 tuv = endpoints[i];340if (tuv.distance_to(mb->get_position()) < handle_radius) {341drag_index = i;342}343}344}345346// We didn't hit any handle, try other options.347if (drag_index < 0) {348if (node_ninepatch || res_stylebox.is_valid()) {349// For ninepatchable objects check if we are clicking on margin bars.350351edited_margin = -1;352float margins[4] = { 0 };353if (node_ninepatch) {354margins[0] = node_ninepatch->get_patch_margin(SIDE_TOP);355margins[1] = node_ninepatch->get_patch_margin(SIDE_BOTTOM);356margins[2] = node_ninepatch->get_patch_margin(SIDE_LEFT);357margins[3] = node_ninepatch->get_patch_margin(SIDE_RIGHT);358} else if (res_stylebox.is_valid()) {359margins[0] = res_stylebox->get_texture_margin(SIDE_TOP);360margins[1] = res_stylebox->get_texture_margin(SIDE_BOTTOM);361margins[2] = res_stylebox->get_texture_margin(SIDE_LEFT);362margins[3] = res_stylebox->get_texture_margin(SIDE_RIGHT);363}364365Vector2 pos[4] = {366mtx.basis_xform(rect.position + Vector2(0, margins[0])) - draw_ofs * draw_zoom,367mtx.basis_xform(rect.position + rect.size - Vector2(0, margins[1])) - draw_ofs * draw_zoom,368mtx.basis_xform(rect.position + Vector2(margins[2], 0)) - draw_ofs * draw_zoom,369mtx.basis_xform(rect.position + rect.size - Vector2(margins[3], 0)) - draw_ofs * draw_zoom370};371if (Math::abs(mb->get_position().y - pos[0].y) < 8) {372edited_margin = 0;373prev_margin = margins[0];374} else if (Math::abs(mb->get_position().y - pos[1].y) < 8) {375edited_margin = 1;376prev_margin = margins[1];377} else if (Math::abs(mb->get_position().x - pos[2].x) < 8) {378edited_margin = 2;379prev_margin = margins[2];380} else if (Math::abs(mb->get_position().x - pos[3].x) < 8) {381edited_margin = 3;382prev_margin = margins[3];383}384if (edited_margin >= 0) {385drag_from = mb->get_position();386drag = true;387}388}389390if (edited_margin < 0 && snap_mode == SNAP_AUTOSLICE) {391// We didn't hit anything, but we're in the autoslice mode. Handle it.392393Vector2 point = mtx.affine_inverse().xform(mb->get_position());394for (const Rect2 &E : autoslice_cache) {395if (E.has_point(point)) {396rect = E;397if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && !(Input::get_singleton()->is_key_pressed(Key(Key::SHIFT | Key::ALT)))) {398Rect2 r;399if (node_sprite_2d) {400r = node_sprite_2d->get_region_rect();401} else if (node_sprite_3d) {402r = node_sprite_3d->get_region_rect();403} else if (node_ninepatch) {404r = node_ninepatch->get_region_rect();405} else if (res_stylebox.is_valid()) {406r = res_stylebox->get_region_rect();407} else if (res_atlas_texture.is_valid()) {408r = res_atlas_texture->get_region();409}410rect.expand_to(r.position);411rect.expand_to(r.get_end());412}413414undo_redo->create_action(TTR("Set Region Rect"));415if (node_sprite_2d) {416undo_redo->add_do_method(node_sprite_2d, "set_region_rect", rect);417undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect());418} else if (node_sprite_3d) {419undo_redo->add_do_method(node_sprite_3d, "set_region_rect", rect);420undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect());421} else if (node_ninepatch) {422undo_redo->add_do_method(node_ninepatch, "set_region_rect", rect);423undo_redo->add_undo_method(node_ninepatch, "set_region_rect", node_ninepatch->get_region_rect());424} else if (res_stylebox.is_valid()) {425undo_redo->add_do_method(res_stylebox.ptr(), "set_region_rect", rect);426undo_redo->add_undo_method(res_stylebox.ptr(), "set_region_rect", res_stylebox->get_region_rect());427} else if (res_atlas_texture.is_valid()) {428undo_redo->add_do_method(res_atlas_texture.ptr(), "set_region", rect);429undo_redo->add_undo_method(res_atlas_texture.ptr(), "set_region", res_atlas_texture->get_region());430}431432undo_redo->add_do_method(this, "_update_rect");433undo_redo->add_undo_method(this, "_update_rect");434undo_redo->add_do_method(texture_overlay, "queue_redraw");435undo_redo->add_undo_method(texture_overlay, "queue_redraw");436undo_redo->commit_action();437break;438}439}440} else if (edited_margin < 0) {441// We didn't hit anything and it's not autoslice, which means we try to create a new region.442443if (drag_index == -1) {444creating = true;445rect = Rect2(drag_from, Size2());446}447}448}449450} else if (!mb->is_pressed() && drag) {451if (edited_margin >= 0) {452undo_redo->create_action(TTR("Set Margin"));453static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };454if (node_ninepatch) {455undo_redo->add_do_method(node_ninepatch, "set_patch_margin", side[edited_margin], node_ninepatch->get_patch_margin(side[edited_margin]));456undo_redo->add_undo_method(node_ninepatch, "set_patch_margin", side[edited_margin], prev_margin);457} else if (res_stylebox.is_valid()) {458undo_redo->add_do_method(res_stylebox.ptr(), "set_texture_margin", side[edited_margin], res_stylebox->get_texture_margin(side[edited_margin]));459undo_redo->add_undo_method(res_stylebox.ptr(), "set_texture_margin", side[edited_margin], prev_margin);460res_stylebox->emit_changed();461}462edited_margin = -1;463} else {464undo_redo->create_action(TTR("Set Region Rect"));465if (node_sprite_2d) {466undo_redo->add_do_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect());467undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", rect_prev);468} else if (node_sprite_3d) {469undo_redo->add_do_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect());470undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", rect_prev);471} else if (node_ninepatch) {472undo_redo->add_do_method(node_ninepatch, "set_region_rect", node_ninepatch->get_region_rect());473undo_redo->add_undo_method(node_ninepatch, "set_region_rect", rect_prev);474} else if (res_stylebox.is_valid()) {475undo_redo->add_do_method(res_stylebox.ptr(), "set_region_rect", res_stylebox->get_region_rect());476undo_redo->add_undo_method(res_stylebox.ptr(), "set_region_rect", rect_prev);477} else if (res_atlas_texture.is_valid()) {478undo_redo->add_do_method(res_atlas_texture.ptr(), "set_region", res_atlas_texture->get_region());479undo_redo->add_undo_method(res_atlas_texture.ptr(), "set_region", rect_prev);480}481drag_index = -1;482}483undo_redo->add_do_method(this, "_update_rect");484undo_redo->add_undo_method(this, "_update_rect");485undo_redo->add_do_method(texture_overlay, "queue_redraw");486undo_redo->add_undo_method(texture_overlay, "queue_redraw");487undo_redo->commit_action();488drag = false;489creating = false;490}491492} else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {493if (drag) {494drag = false;495if (edited_margin >= 0) {496static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };497if (node_ninepatch) {498node_ninepatch->set_patch_margin(side[edited_margin], prev_margin);499}500if (res_stylebox.is_valid()) {501res_stylebox->set_texture_margin(side[edited_margin], prev_margin);502}503edited_margin = -1;504} else {505_apply_rect(rect_prev);506rect = rect_prev;507texture_preview->queue_redraw();508texture_overlay->queue_redraw();509drag_index = -1;510}511}512}513}514515Ref<InputEventMouseMotion> mm = p_input;516517if (mm.is_valid()) {518if (drag) {519if (edited_margin >= 0) {520float new_margin = 0;521522if (snap_mode != SNAP_GRID) {523if (edited_margin == 0) {524new_margin = prev_margin + (mm->get_position().y - drag_from.y) / draw_zoom;525} else if (edited_margin == 1) {526new_margin = prev_margin - (mm->get_position().y - drag_from.y) / draw_zoom;527} else if (edited_margin == 2) {528new_margin = prev_margin + (mm->get_position().x - drag_from.x) / draw_zoom;529} else if (edited_margin == 3) {530new_margin = prev_margin - (mm->get_position().x - drag_from.x) / draw_zoom;531} else {532ERR_PRINT("Unexpected edited_margin");533}534535if (snap_mode == SNAP_PIXEL) {536new_margin = Math::round(new_margin);537}538} else {539Vector2 pos_snapped = snap_point(mtx.affine_inverse().xform(mm->get_position()));540Rect2 rect_rounded = Rect2(rect.position.round(), rect.size.round());541542if (edited_margin == 0) {543new_margin = pos_snapped.y - rect_rounded.position.y;544} else if (edited_margin == 1) {545new_margin = rect_rounded.size.y + rect_rounded.position.y - pos_snapped.y;546} else if (edited_margin == 2) {547new_margin = pos_snapped.x - rect_rounded.position.x;548} else if (edited_margin == 3) {549new_margin = rect_rounded.size.x + rect_rounded.position.x - pos_snapped.x;550} else {551ERR_PRINT("Unexpected edited_margin");552}553}554555if (new_margin < 0) {556new_margin = 0;557}558static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT };559if (node_ninepatch) {560node_ninepatch->set_patch_margin(side[edited_margin], new_margin);561}562if (res_stylebox.is_valid()) {563res_stylebox->set_texture_margin(side[edited_margin], new_margin);564}565} else {566Vector2 new_pos = mtx.affine_inverse().xform(mm->get_position());567if (snap_mode == SNAP_PIXEL) {568new_pos = new_pos.snappedf(1);569} else if (snap_mode == SNAP_GRID) {570new_pos = snap_point(new_pos);571}572573if (creating) {574rect = Rect2(drag_from, Size2());575rect.expand_to(new_pos);576_apply_rect(rect);577texture_preview->queue_redraw();578texture_overlay->queue_redraw();579return;580}581582switch (drag_index) {583case 0: {584Vector2 p = rect_prev.get_end();585rect = Rect2(p, Size2());586rect.expand_to(new_pos);587_apply_rect(rect);588} break;589case 1: {590Vector2 p = rect_prev.position + Vector2(0, rect_prev.size.y);591rect = Rect2(p, Size2(rect_prev.size.x, 0));592rect.expand_to(new_pos);593_apply_rect(rect);594} break;595case 2: {596Vector2 p = rect_prev.position + Vector2(0, rect_prev.size.y);597rect = Rect2(p, Size2());598rect.expand_to(new_pos);599_apply_rect(rect);600} break;601case 3: {602Vector2 p = rect_prev.position;603rect = Rect2(p, Size2(0, rect_prev.size.y));604rect.expand_to(new_pos);605_apply_rect(rect);606} break;607case 4: {608Vector2 p = rect_prev.position;609rect = Rect2(p, Size2());610rect.expand_to(new_pos);611_apply_rect(rect);612} break;613case 5: {614Vector2 p = rect_prev.position;615rect = Rect2(p, Size2(rect_prev.size.x, 0));616rect.expand_to(new_pos);617_apply_rect(rect);618} break;619case 6: {620Vector2 p = rect_prev.position + Vector2(rect_prev.size.x, 0);621rect = Rect2(p, Size2());622rect.expand_to(new_pos);623_apply_rect(rect);624} break;625case 7: {626Vector2 p = rect_prev.position + Vector2(rect_prev.size.x, 0);627rect = Rect2(p, Size2(0, rect_prev.size.y));628rect.expand_to(new_pos);629_apply_rect(rect);630} break;631}632}633texture_preview->queue_redraw();634texture_overlay->queue_redraw();635}636}637638Ref<InputEventMagnifyGesture> magnify_gesture = p_input;639if (magnify_gesture.is_valid()) {640_zoom_on_position(draw_zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());641}642643Ref<InputEventPanGesture> pan_gesture = p_input;644if (pan_gesture.is_valid()) {645hscroll->set_value(hscroll->get_value() + hscroll->get_page() * pan_gesture->get_delta().x / 8);646vscroll->set_value(vscroll->get_value() + vscroll->get_page() * pan_gesture->get_delta().y / 8);647}648}649650void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {651p_scroll_vec /= draw_zoom;652hscroll->set_value(hscroll->get_value() - p_scroll_vec.x);653vscroll->set_value(vscroll->get_value() - p_scroll_vec.y);654}655656void TextureRegionEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {657_zoom_on_position(draw_zoom * p_zoom_factor, p_origin);658}659660void TextureRegionEditor::_scroll_changed(float) {661if (updating_scroll) {662return;663}664665draw_ofs.x = hscroll->get_value();666draw_ofs.y = vscroll->get_value();667668texture_preview->queue_redraw();669texture_overlay->queue_redraw();670}671672void TextureRegionEditor::_set_snap_mode(int p_mode) {673snap_mode = (SnapMode)p_mode;674675hb_grid->set_visible(snap_mode == SNAP_GRID);676if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {677_update_autoslice();678}679680texture_overlay->queue_redraw();681}682683void TextureRegionEditor::_set_snap_off_x(float p_val) {684snap_offset.x = p_val;685texture_overlay->queue_redraw();686}687688void TextureRegionEditor::_set_snap_off_y(float p_val) {689snap_offset.y = p_val;690texture_overlay->queue_redraw();691}692693void TextureRegionEditor::_set_snap_step_x(float p_val) {694snap_step.x = p_val;695texture_overlay->queue_redraw();696}697698void TextureRegionEditor::_set_snap_step_y(float p_val) {699snap_step.y = p_val;700texture_overlay->queue_redraw();701}702703void TextureRegionEditor::_set_snap_sep_x(float p_val) {704snap_separation.x = p_val;705texture_overlay->queue_redraw();706}707708void TextureRegionEditor::_set_snap_sep_y(float p_val) {709snap_separation.y = p_val;710texture_overlay->queue_redraw();711}712713void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) {714if (p_zoom < min_draw_zoom || p_zoom > max_draw_zoom) {715return;716}717718float prev_zoom = draw_zoom;719draw_zoom = p_zoom;720Point2 ofs = p_position;721ofs = ofs / prev_zoom - ofs / draw_zoom;722draw_ofs = (draw_ofs + ofs).round();723724texture_preview->queue_redraw();725texture_overlay->queue_redraw();726}727728void TextureRegionEditor::_zoom_in() {729_zoom_on_position(draw_zoom * 1.5, texture_overlay->get_size() / 2.0);730}731732void TextureRegionEditor::_zoom_reset() {733_zoom_on_position(1.0, texture_overlay->get_size() / 2.0);734}735736void TextureRegionEditor::_zoom_out() {737_zoom_on_position(draw_zoom / 1.5, texture_overlay->get_size() / 2.0);738}739740void TextureRegionEditor::_apply_rect(const Rect2 &p_rect) {741if (node_sprite_2d) {742node_sprite_2d->set_region_rect(p_rect);743} else if (node_sprite_3d) {744node_sprite_3d->set_region_rect(p_rect);745} else if (node_ninepatch) {746node_ninepatch->set_region_rect(p_rect);747} else if (res_stylebox.is_valid()) {748res_stylebox->set_region_rect(p_rect);749} else if (res_atlas_texture.is_valid()) {750res_atlas_texture->set_region(p_rect);751}752}753754void TextureRegionEditor::_update_rect() {755rect = _get_edited_object_region();756}757758void TextureRegionEditor::_update_autoslice() {759autoslice_is_dirty = false;760autoslice_cache.clear();761762const Ref<Texture2D> object_texture = _get_edited_object_texture();763if (object_texture.is_null()) {764return;765}766767for (int y = 0; y < object_texture->get_height(); y++) {768for (int x = 0; x < object_texture->get_width(); x++) {769if (object_texture->is_pixel_opaque(x, y)) {770bool found = false;771for (Rect2 &E : autoslice_cache) {772Rect2 grown = E.grow(1.5);773if (grown.has_point(Point2(x, y))) {774E.expand_to(Point2(x, y));775E.expand_to(Point2(x + 1, y + 1));776x = E.position.x + E.size.x - 1;777bool merged = true;778while (merged) {779merged = false;780bool queue_erase = false;781for (List<Rect2>::Element *F = autoslice_cache.front(); F; F = F->next()) {782if (queue_erase) {783autoslice_cache.erase(F->prev());784queue_erase = false;785}786if (F->get() == E) {787continue;788}789if (E.grow(1).intersects(F->get())) {790E.expand_to(F->get().position);791E.expand_to(F->get().position + F->get().size);792if (F->prev()) {793F = F->prev();794autoslice_cache.erase(F->next());795} else {796queue_erase = true;797// Can't delete the first rect in the list.798}799merged = true;800}801}802}803found = true;804break;805}806}807if (!found) {808Rect2 new_rect(x, y, 1, 1);809autoslice_cache.push_back(new_rect);810}811}812}813}814cache_map[object_texture->get_rid()] = autoslice_cache;815}816817void TextureRegionEditor::_notification(int p_what) {818switch (p_what) {819case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {820if (EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning")) {821panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));822panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));823}824} break;825826case NOTIFICATION_ENTER_TREE: {827get_tree()->connect("node_removed", callable_mp(this, &TextureRegionEditor::_node_removed));828829hb_grid->set_visible(snap_mode == SNAP_GRID);830if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {831_update_autoslice();832}833834panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));835panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));836} break;837838case NOTIFICATION_EXIT_TREE: {839get_tree()->disconnect("node_removed", callable_mp(this, &TextureRegionEditor::_node_removed));840} break;841842case NOTIFICATION_THEME_CHANGED: {843texture_preview->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("TextureRegionPreviewBG"), EditorStringName(EditorStyles)));844texture_overlay->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("TextureRegionPreviewFG"), EditorStringName(EditorStyles)));845846zoom_out->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));847zoom_reset->set_button_icon(get_editor_theme_icon(SNAME("ZoomReset")));848zoom_in->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));849} break;850851case NOTIFICATION_VISIBILITY_CHANGED: {852if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) {853_update_autoslice();854}855856if (!is_visible()) {857EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_offset", snap_offset);858EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_step", snap_step);859EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_separation", snap_separation);860EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_mode", snap_mode);861}862} break;863864case NOTIFICATION_WM_WINDOW_FOCUS_IN: {865// This happens when the user leaves the Editor and returns,866// they could have changed the textures, so the cache is cleared.867cache_map.clear();868_edit_region();869} break;870}871}872873void TextureRegionEditor::_node_removed(Node *p_node) {874if (p_node == node_sprite_2d || p_node == node_sprite_3d || p_node == node_ninepatch) {875_clear_edited_object();876hide();877}878}879880void TextureRegionEditor::_clear_edited_object() {881if (node_sprite_2d) {882node_sprite_2d->disconnect(SceneStringName(texture_changed), callable_mp(this, &TextureRegionEditor::_texture_changed));883}884if (node_sprite_3d) {885node_sprite_3d->disconnect(SceneStringName(texture_changed), callable_mp(this, &TextureRegionEditor::_texture_changed));886}887if (node_ninepatch) {888node_ninepatch->disconnect(SceneStringName(texture_changed), callable_mp(this, &TextureRegionEditor::_texture_changed));889}890if (res_stylebox.is_valid()) {891res_stylebox->disconnect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed));892}893if (res_atlas_texture.is_valid()) {894res_atlas_texture->disconnect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed));895}896897node_sprite_2d = nullptr;898node_sprite_3d = nullptr;899node_ninepatch = nullptr;900res_stylebox = Ref<StyleBoxTexture>();901res_atlas_texture = Ref<AtlasTexture>();902}903904void TextureRegionEditor::edit(Object *p_obj) {905_clear_edited_object();906907if (p_obj) {908node_sprite_2d = Object::cast_to<Sprite2D>(p_obj);909node_sprite_3d = Object::cast_to<Sprite3D>(p_obj);910node_ninepatch = Object::cast_to<NinePatchRect>(p_obj);911912bool is_resource = false;913if (Object::cast_to<StyleBoxTexture>(p_obj)) {914res_stylebox = Ref<StyleBoxTexture>(p_obj);915is_resource = true;916}917if (Object::cast_to<AtlasTexture>(p_obj)) {918res_atlas_texture = Ref<AtlasTexture>(p_obj);919is_resource = true;920}921922if (is_resource) {923Object::cast_to<Resource>(p_obj)->connect_changed(callable_mp(this, &TextureRegionEditor::_texture_changed));924} else {925p_obj->connect(SceneStringName(texture_changed), callable_mp(this, &TextureRegionEditor::_texture_changed));926}927_edit_region();928}929930texture_preview->queue_redraw();931texture_overlay->queue_redraw();932popup_centered_ratio(0.5);933request_center = true;934}935936Ref<Texture2D> TextureRegionEditor::_get_edited_object_texture() const {937if (node_sprite_2d) {938return node_sprite_2d->get_texture();939}940if (node_sprite_3d) {941return node_sprite_3d->get_texture();942}943if (node_ninepatch) {944return node_ninepatch->get_texture();945}946if (res_stylebox.is_valid()) {947return res_stylebox->get_texture();948}949if (res_atlas_texture.is_valid()) {950return res_atlas_texture->get_atlas();951}952953return Ref<Texture2D>();954}955956Rect2 TextureRegionEditor::_get_edited_object_region() const {957Rect2 region;958959if (node_sprite_2d) {960region = node_sprite_2d->get_region_rect();961} else if (node_sprite_3d) {962region = node_sprite_3d->get_region_rect();963} else if (node_ninepatch) {964region = node_ninepatch->get_region_rect();965} else if (res_stylebox.is_valid()) {966region = res_stylebox->get_region_rect();967} else if (res_atlas_texture.is_valid()) {968region = res_atlas_texture->get_region();969}970971const Ref<Texture2D> object_texture = _get_edited_object_texture();972if (region == Rect2() && object_texture.is_valid()) {973region = Rect2(Vector2(), object_texture->get_size());974}975976return region;977}978979void TextureRegionEditor::_texture_changed() {980if (!is_visible()) {981return;982}983_edit_region();984}985986void TextureRegionEditor::_edit_region() {987const Ref<Texture2D> object_texture = _get_edited_object_texture();988if (object_texture.is_null()) {989_set_grid_parameters_clamping(false);990_zoom_reset();991hscroll->hide();992vscroll->hide();993texture_preview->queue_redraw();994texture_overlay->queue_redraw();995return;996}997998CanvasItem::TextureFilter filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;999if (node_sprite_2d) {1000filter = node_sprite_2d->get_texture_filter_in_tree();1001} else if (node_sprite_3d) {1002StandardMaterial3D::TextureFilter filter_3d = node_sprite_3d->get_texture_filter();10031004switch (filter_3d) {1005case StandardMaterial3D::TEXTURE_FILTER_NEAREST:1006filter = CanvasItem::TEXTURE_FILTER_NEAREST;1007break;1008case StandardMaterial3D::TEXTURE_FILTER_LINEAR:1009filter = CanvasItem::TEXTURE_FILTER_LINEAR;1010break;1011case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:1012filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;1013break;1014case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:1015filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;1016break;1017case StandardMaterial3D::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC:1018filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC;1019break;1020case StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC:1021filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC;1022break;1023default:1024// fallback to project default1025filter = CanvasItem::TEXTURE_FILTER_PARENT_NODE;1026break;1027}1028} else if (node_ninepatch) {1029filter = node_ninepatch->get_texture_filter_in_tree();1030}10311032// occurs when get_texture_filter_in_tree reaches the scene root1033if (filter == CanvasItem::TEXTURE_FILTER_PARENT_NODE) {1034SubViewport *root = EditorNode::get_singleton()->get_scene_root();10351036if (root != nullptr) {1037Viewport::DefaultCanvasItemTextureFilter filter_default = root->get_default_canvas_item_texture_filter();10381039// depending on default filter, set filter to match, otherwise fall back on nearest w/ mipmaps1040switch (filter_default) {1041case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST:1042filter = CanvasItem::TEXTURE_FILTER_NEAREST;1043break;1044case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR:1045filter = CanvasItem::TEXTURE_FILTER_LINEAR;1046break;1047case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:1048filter = CanvasItem::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;1049break;1050case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:1051default:1052filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;1053break;1054}1055} else {1056filter = CanvasItem::TEXTURE_FILTER_NEAREST_WITH_MIPMAPS;1057}1058}10591060texture_preview->set_texture_filter(filter);1061texture_preview->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_DISABLED);10621063if (cache_map.has(object_texture->get_rid())) {1064autoslice_cache = cache_map[object_texture->get_rid()];1065autoslice_is_dirty = false;1066} else {1067if (is_visible() && snap_mode == SNAP_AUTOSLICE) {1068_update_autoslice();1069} else {1070autoslice_is_dirty = true;1071}1072}10731074// Avoiding clamping with mismatched min/max.1075_set_grid_parameters_clamping(false);1076const Size2 tex_size = object_texture->get_size();1077sb_off_x->set_min(-tex_size.x);1078sb_off_x->set_max(tex_size.x);1079sb_off_y->set_min(-tex_size.y);1080sb_off_y->set_max(tex_size.y);1081sb_step_x->set_max(tex_size.x);1082sb_step_y->set_max(tex_size.y);1083sb_sep_x->set_max(tex_size.x);1084sb_sep_y->set_max(tex_size.y);10851086_set_grid_parameters_clamping(true);1087sb_off_x->set_value(snap_offset.x);1088sb_off_y->set_value(snap_offset.y);1089sb_step_x->set_value(snap_step.x);1090sb_step_y->set_value(snap_step.y);1091sb_sep_x->set_value(snap_separation.x);1092sb_sep_y->set_value(snap_separation.y);10931094_update_rect();1095texture_preview->queue_redraw();1096texture_overlay->queue_redraw();1097}10981099Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const {1100if (snap_mode == SNAP_GRID) {1101p_target.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p_target.x, snap_separation.x);1102p_target.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p_target.y, snap_separation.y);1103}11041105return p_target;1106}11071108void TextureRegionEditor::shortcut_input(const Ref<InputEvent> &p_event) {1109const Ref<InputEventKey> k = p_event;1110if (k.is_valid() && k->is_pressed()) {1111bool handled = false;11121113if (ED_IS_SHORTCUT("ui_undo", p_event)) {1114EditorNode::get_singleton()->undo();1115handled = true;1116}11171118if (ED_IS_SHORTCUT("ui_redo", p_event)) {1119EditorNode::get_singleton()->redo();1120handled = true;1121}11221123if (handled) {1124set_input_as_handled();1125}1126}1127}11281129void TextureRegionEditor::_bind_methods() {1130ClassDB::bind_method(D_METHOD("_update_rect"), &TextureRegionEditor::_update_rect);1131}11321133TextureRegionEditor::TextureRegionEditor() {1134set_title(TTR("Region Editor"));1135set_process_shortcut_input(true);1136set_ok_button_text(TTR("Close"));11371138// A power-of-two value works better as a default grid size.1139snap_offset = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_offset", Vector2());1140snap_step = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_step", Vector2(8, 8));1141snap_separation = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_separation", Vector2());1142snap_mode = (SnapMode)(int)EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_mode", SNAP_NONE);11431144panner.instantiate();1145panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_pan_callback), callable_mp(this, &TextureRegionEditor::_zoom_callback));11461147VBoxContainer *vb = memnew(VBoxContainer);1148add_child(vb);11491150HBoxContainer *hb_tools = memnew(HBoxContainer);1151vb->add_child(hb_tools);1152hb_tools->add_child(memnew(Label(TTR("Snap Mode:"))));11531154snap_mode_button = memnew(OptionButton);1155hb_tools->add_child(snap_mode_button);1156snap_mode_button->set_accessibility_name(TTRC("Snap Mode:"));1157snap_mode_button->add_item(TTR("None"), 0);1158snap_mode_button->add_item(TTR("Pixel Snap"), 1);1159snap_mode_button->add_item(TTR("Grid Snap"), 2);1160snap_mode_button->add_item(TTR("Auto Slice"), 3);1161snap_mode_button->select(snap_mode);1162snap_mode_button->connect(SceneStringName(item_selected), callable_mp(this, &TextureRegionEditor::_set_snap_mode));11631164hb_grid = memnew(HBoxContainer);1165hb_tools->add_child(hb_grid);11661167hb_grid->add_child(memnew(VSeparator));1168hb_grid->add_child(memnew(Label(TTR("Offset:"))));11691170sb_off_x = memnew(SpinBox);1171sb_off_x->set_step(1);1172sb_off_x->set_suffix("px");1173sb_off_x->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_off_x));1174sb_off_x->set_accessibility_name(TTRC("Offset X"));1175hb_grid->add_child(sb_off_x);11761177sb_off_y = memnew(SpinBox);1178sb_off_y->set_step(1);1179sb_off_y->set_suffix("px");1180sb_off_y->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_off_y));1181sb_off_y->set_accessibility_name(TTRC("Offset Y"));1182hb_grid->add_child(sb_off_y);11831184hb_grid->add_child(memnew(VSeparator));1185hb_grid->add_child(memnew(Label(TTR("Step:"))));11861187sb_step_x = memnew(SpinBox);1188sb_step_x->set_min(0);1189sb_step_x->set_step(1);1190sb_step_x->set_suffix("px");1191sb_step_x->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_step_x));1192sb_step_x->set_accessibility_name(TTRC("Step X"));1193hb_grid->add_child(sb_step_x);11941195sb_step_y = memnew(SpinBox);1196sb_step_y->set_min(0);1197sb_step_y->set_step(1);1198sb_step_y->set_suffix("px");1199sb_step_y->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_step_y));1200sb_step_y->set_accessibility_name(TTRC("Step Y"));1201hb_grid->add_child(sb_step_y);12021203hb_grid->add_child(memnew(VSeparator));1204hb_grid->add_child(memnew(Label(TTR("Separation:"))));12051206sb_sep_x = memnew(SpinBox);1207sb_sep_x->set_min(0);1208sb_sep_x->set_step(1);1209sb_sep_x->set_suffix("px");1210sb_sep_x->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_sep_x));1211sb_sep_x->set_accessibility_name(TTRC("Separation X"));1212hb_grid->add_child(sb_sep_x);12131214sb_sep_y = memnew(SpinBox);1215sb_sep_y->set_min(0);1216sb_sep_y->set_step(1);1217sb_sep_y->set_suffix("px");1218sb_sep_y->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_set_snap_sep_y));1219sb_sep_y->set_accessibility_name(TTRC("Separation Y"));1220hb_grid->add_child(sb_sep_y);12211222hb_grid->hide();12231224// Restore grid snap parameters.1225_set_grid_parameters_clamping(false);1226sb_off_x->set_value(snap_offset.x);1227sb_off_y->set_value(snap_offset.y);1228sb_step_x->set_value(snap_step.x);1229sb_step_y->set_value(snap_step.y);1230sb_sep_x->set_value(snap_separation.x);1231sb_sep_y->set_value(snap_separation.y);12321233// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.1234draw_zoom = MAX(1.0f, EDSCALE);1235max_draw_zoom = 128.0f * MAX(1.0f, EDSCALE);1236min_draw_zoom = 0.01f * MAX(1.0f, EDSCALE);12371238texture_preview = memnew(PanelContainer);1239vb->add_child(texture_preview);1240texture_preview->set_v_size_flags(Control::SIZE_EXPAND_FILL);1241texture_preview->set_clip_contents(true);1242texture_preview->connect(SceneStringName(draw), callable_mp(this, &TextureRegionEditor::_texture_preview_draw));12431244texture_overlay = memnew(Panel);1245texture_preview->add_child(texture_overlay);1246texture_overlay->set_focus_mode(Control::FOCUS_CLICK);1247texture_overlay->connect(SceneStringName(draw), callable_mp(this, &TextureRegionEditor::_texture_overlay_draw));1248texture_overlay->connect(SceneStringName(gui_input), callable_mp(this, &TextureRegionEditor::_texture_overlay_input));1249texture_overlay->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));12501251HBoxContainer *zoom_hb = memnew(HBoxContainer);1252texture_overlay->add_child(zoom_hb);1253zoom_hb->set_begin(Point2(5, 5));12541255zoom_out = memnew(Button);1256zoom_out->set_flat(true);1257zoom_out->set_tooltip_text(TTR("Zoom Out"));1258zoom_out->connect(SceneStringName(pressed), callable_mp(this, &TextureRegionEditor::_zoom_out));1259zoom_hb->add_child(zoom_out);12601261zoom_reset = memnew(Button);1262zoom_reset->set_flat(true);1263zoom_reset->set_tooltip_text(TTR("Zoom Reset"));1264zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &TextureRegionEditor::_zoom_reset));1265zoom_hb->add_child(zoom_reset);12661267zoom_in = memnew(Button);1268zoom_in->set_flat(true);1269zoom_in->set_tooltip_text(TTR("Zoom In"));1270zoom_in->connect(SceneStringName(pressed), callable_mp(this, &TextureRegionEditor::_zoom_in));1271zoom_hb->add_child(zoom_in);12721273vscroll = memnew(VScrollBar);1274vscroll->set_anchors_and_offsets_preset(Control::PRESET_RIGHT_WIDE);1275vscroll->set_step(0.001);1276vscroll->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_scroll_changed));1277texture_overlay->add_child(vscroll);12781279hscroll = memnew(HScrollBar);1280hscroll->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_WIDE);1281hscroll->set_step(0.001);1282hscroll->connect(SceneStringName(value_changed), callable_mp(this, &TextureRegionEditor::_scroll_changed));1283texture_overlay->add_child(hscroll);1284}12851286////////////////////////12871288bool EditorInspectorPluginTextureRegion::can_handle(Object *p_object) {1289return Object::cast_to<Sprite2D>(p_object) || Object::cast_to<Sprite3D>(p_object) || Object::cast_to<NinePatchRect>(p_object) || Object::cast_to<StyleBoxTexture>(p_object) || Object::cast_to<AtlasTexture>(p_object);1290}12911292void EditorInspectorPluginTextureRegion::_region_edit(Object *p_object) {1293texture_region_editor->edit(p_object);1294}12951296bool EditorInspectorPluginTextureRegion::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {1297if ((p_type == Variant::RECT2 || p_type == Variant::RECT2I)) {1298if (((Object::cast_to<Sprite2D>(p_object) || Object::cast_to<Sprite3D>(p_object) || Object::cast_to<NinePatchRect>(p_object) || Object::cast_to<StyleBoxTexture>(p_object)) && p_path == "region_rect") || (Object::cast_to<AtlasTexture>(p_object) && p_path == "region")) {1299EditorInspectorActionButton *button = memnew(EditorInspectorActionButton(TTRC("Edit Region"), SNAME("RegionEdit")));1300button->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorPluginTextureRegion::_region_edit).bind(p_object));1301add_property_editor(p_path, button, true);1302}1303}1304return false; //not exclusive1305}13061307EditorInspectorPluginTextureRegion::EditorInspectorPluginTextureRegion() {1308texture_region_editor = memnew(TextureRegionEditor);1309EditorNode::get_singleton()->get_gui_base()->add_child(texture_region_editor);1310}13111312TextureRegionEditorPlugin::TextureRegionEditorPlugin() {1313Ref<EditorInspectorPluginTextureRegion> inspector_plugin;1314inspector_plugin.instantiate();1315add_inspector_plugin(inspector_plugin);1316}131713181319