Path: blob/master/editor/scene/canvas_item_editor_plugin.cpp
20920 views
/**************************************************************************/1/* canvas_item_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 "canvas_item_editor_plugin.h"3132#include "core/config/project_settings.h"33#include "core/input/input.h"34#include "core/os/keyboard.h"35#include "core/string/translation_server.h"36#include "editor/animation/animation_player_editor_plugin.h"37#include "editor/debugger/editor_debugger_node.h"38#include "editor/docks/scene_tree_dock.h"39#include "editor/editor_main_screen.h"40#include "editor/editor_node.h"41#include "editor/editor_string_names.h"42#include "editor/editor_undo_redo_manager.h"43#include "editor/gui/editor_toaster.h"44#include "editor/gui/editor_zoom_widget.h"45#include "editor/inspector/editor_context_menu_plugin.h"46#include "editor/plugins/editor_plugin_list.h"47#include "editor/run/editor_run_bar.h"48#include "editor/script/script_editor_plugin.h"49#include "editor/settings/editor_settings.h"50#include "editor/themes/editor_scale.h"51#include "editor/themes/editor_theme_manager.h"52#include "editor/translations/editor_translation_preview_button.h"53#include "editor/translations/editor_translation_preview_menu.h"54#include "scene/2d/audio_stream_player_2d.h"55#include "scene/2d/physics/touch_screen_button.h"56#include "scene/2d/polygon_2d.h"57#include "scene/2d/skeleton_2d.h"58#include "scene/2d/sprite_2d.h"59#include "scene/gui/base_button.h"60#include "scene/gui/flow_container.h"61#include "scene/gui/grid_container.h"62#include "scene/gui/separator.h"63#include "scene/gui/split_container.h"64#include "scene/gui/subviewport_container.h"65#include "scene/gui/view_panner.h"66#include "scene/main/canvas_layer.h"67#include "scene/main/timer.h"68#include "scene/main/window.h"69#include "scene/resources/packed_scene.h"70#include "scene/resources/style_box_texture.h"7172#define DRAG_THRESHOLD (8 * EDSCALE)73constexpr real_t SCALE_HANDLE_DISTANCE = 25;74constexpr real_t MOVE_HANDLE_DISTANCE = 25;7576class SnapDialog : public ConfirmationDialog {77GDCLASS(SnapDialog, ConfirmationDialog);7879friend class CanvasItemEditor;8081SpinBox *grid_offset_x;82SpinBox *grid_offset_y;83SpinBox *grid_step_x;84SpinBox *grid_step_y;85SpinBox *primary_grid_step_x;86SpinBox *primary_grid_step_y;87SpinBox *rotation_offset;88SpinBox *rotation_step;89SpinBox *scale_step;9091public:92SnapDialog() {93const int SPIN_BOX_GRID_RANGE = 16384;94const int SPIN_BOX_ROTATION_RANGE = 360;95const real_t SPIN_BOX_SCALE_MIN = 0.01;96const real_t SPIN_BOX_SCALE_MAX = 100;9798Label *label;99VBoxContainer *container;100GridContainer *child_container;101102set_title(TTRC("Configure Snap"));103104container = memnew(VBoxContainer);105add_child(container);106107child_container = memnew(GridContainer);108child_container->set_columns(3);109container->add_child(child_container);110111label = memnew(Label);112label->set_text(TTRC("Grid Offset:"));113child_container->add_child(label);114label->set_h_size_flags(Control::SIZE_EXPAND_FILL);115116grid_offset_x = memnew(SpinBox);117grid_offset_x->set_min(-SPIN_BOX_GRID_RANGE);118grid_offset_x->set_max(SPIN_BOX_GRID_RANGE);119grid_offset_x->set_allow_lesser(true);120grid_offset_x->set_allow_greater(true);121grid_offset_x->set_suffix("px");122grid_offset_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);123grid_offset_x->set_select_all_on_focus(true);124grid_offset_x->set_accessibility_name(TTRC("X Offset"));125child_container->add_child(grid_offset_x);126127grid_offset_y = memnew(SpinBox);128grid_offset_y->set_min(-SPIN_BOX_GRID_RANGE);129grid_offset_y->set_max(SPIN_BOX_GRID_RANGE);130grid_offset_y->set_allow_lesser(true);131grid_offset_y->set_allow_greater(true);132grid_offset_y->set_suffix("px");133grid_offset_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);134grid_offset_y->set_select_all_on_focus(true);135grid_offset_y->set_accessibility_name(TTRC("Y Offset"));136child_container->add_child(grid_offset_y);137138label = memnew(Label);139label->set_text(TTRC("Grid Step:"));140child_container->add_child(label);141label->set_h_size_flags(Control::SIZE_EXPAND_FILL);142143grid_step_x = memnew(SpinBox);144grid_step_x->set_min(1);145grid_step_x->set_max(SPIN_BOX_GRID_RANGE);146grid_step_x->set_allow_greater(true);147grid_step_x->set_suffix("px");148grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);149grid_step_x->set_select_all_on_focus(true);150grid_step_x->set_accessibility_name(TTRC("X Step"));151child_container->add_child(grid_step_x);152153grid_step_y = memnew(SpinBox);154grid_step_y->set_min(1);155grid_step_y->set_max(SPIN_BOX_GRID_RANGE);156grid_step_y->set_allow_greater(true);157grid_step_y->set_suffix("px");158grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);159grid_step_y->set_select_all_on_focus(true);160grid_step_y->set_accessibility_name(TTRC("X Step"));161child_container->add_child(grid_step_y);162163label = memnew(Label);164label->set_text(TTRC("Primary Line Every:"));165label->set_h_size_flags(Control::SIZE_EXPAND_FILL);166child_container->add_child(label);167168primary_grid_step_x = memnew(SpinBox);169primary_grid_step_x->set_min(1);170primary_grid_step_x->set_step(1);171primary_grid_step_x->set_max(SPIN_BOX_GRID_RANGE);172primary_grid_step_x->set_allow_greater(true);173primary_grid_step_x->set_suffix("steps");174primary_grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);175primary_grid_step_x->set_select_all_on_focus(true);176primary_grid_step_x->set_accessibility_name(TTRC("X Primary Step"));177child_container->add_child(primary_grid_step_x);178179primary_grid_step_y = memnew(SpinBox);180primary_grid_step_y->set_min(1);181primary_grid_step_y->set_step(1);182primary_grid_step_y->set_max(SPIN_BOX_GRID_RANGE);183primary_grid_step_y->set_allow_greater(true);184primary_grid_step_y->set_suffix(TTRC("steps")); // TODO: Add suffix auto-translation.185primary_grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);186primary_grid_step_y->set_select_all_on_focus(true);187primary_grid_step_y->set_accessibility_name(TTRC("Y Primary Step"));188child_container->add_child(primary_grid_step_y);189190container->add_child(memnew(HSeparator));191192// We need to create another GridContainer with the same column count,193// so we can put an HSeparator above194child_container = memnew(GridContainer);195child_container->set_columns(2);196container->add_child(child_container);197198label = memnew(Label);199label->set_text(TTRC("Rotation Offset:"));200child_container->add_child(label);201label->set_h_size_flags(Control::SIZE_EXPAND_FILL);202203rotation_offset = memnew(SpinBox);204rotation_offset->set_min(-SPIN_BOX_ROTATION_RANGE);205rotation_offset->set_max(SPIN_BOX_ROTATION_RANGE);206rotation_offset->set_suffix(U"°");207rotation_offset->set_h_size_flags(Control::SIZE_EXPAND_FILL);208rotation_offset->set_select_all_on_focus(true);209rotation_offset->set_accessibility_name(TTRC("Rotation Offset:"));210child_container->add_child(rotation_offset);211212label = memnew(Label);213label->set_text(TTRC("Rotation Step:"));214child_container->add_child(label);215label->set_h_size_flags(Control::SIZE_EXPAND_FILL);216217rotation_step = memnew(SpinBox);218rotation_step->set_min(-SPIN_BOX_ROTATION_RANGE);219rotation_step->set_max(SPIN_BOX_ROTATION_RANGE);220rotation_step->set_suffix(U"°");221rotation_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);222rotation_step->set_select_all_on_focus(true);223rotation_step->set_accessibility_name(TTRC("Rotation Step:"));224child_container->add_child(rotation_step);225226container->add_child(memnew(HSeparator));227228child_container = memnew(GridContainer);229child_container->set_columns(2);230container->add_child(child_container);231label = memnew(Label);232label->set_text(TTRC("Scale Step:"));233child_container->add_child(label);234label->set_h_size_flags(Control::SIZE_EXPAND_FILL);235236scale_step = memnew(SpinBox);237scale_step->set_min(SPIN_BOX_SCALE_MIN);238scale_step->set_max(SPIN_BOX_SCALE_MAX);239scale_step->set_allow_greater(true);240scale_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);241scale_step->set_step(0.01f);242scale_step->set_select_all_on_focus(true);243scale_step->set_accessibility_name(TTRC("Scale Step:"));244child_container->add_child(scale_step);245}246247void set_fields(const Point2 p_grid_offset, const Point2 p_grid_step, const Vector2i p_primary_grid_step, const real_t p_rotation_offset, const real_t p_rotation_step, const real_t p_scale_step) {248grid_offset_x->set_value(p_grid_offset.x);249grid_offset_y->set_value(p_grid_offset.y);250grid_step_x->set_value(p_grid_step.x);251grid_step_y->set_value(p_grid_step.y);252primary_grid_step_x->set_value(p_primary_grid_step.x);253primary_grid_step_y->set_value(p_primary_grid_step.y);254rotation_offset->set_value(Math::rad_to_deg(p_rotation_offset));255rotation_step->set_value(Math::rad_to_deg(p_rotation_step));256scale_step->set_value(p_scale_step);257}258259void get_fields(Point2 &p_grid_offset, Point2 &p_grid_step, Vector2i &p_primary_grid_step, real_t &p_rotation_offset, real_t &p_rotation_step, real_t &p_scale_step) {260p_grid_offset = Point2(grid_offset_x->get_value(), grid_offset_y->get_value());261p_grid_step = Point2(grid_step_x->get_value(), grid_step_y->get_value());262p_primary_grid_step = Vector2i(primary_grid_step_x->get_value(), primary_grid_step_y->get_value());263p_rotation_offset = Math::deg_to_rad(rotation_offset->get_value());264p_rotation_step = Math::deg_to_rad(rotation_step->get_value());265p_scale_step = scale_step->get_value();266}267};268269bool CanvasItemEditor::_is_node_locked(const Node *p_node) const {270return p_node->get_meta("_edit_lock_", false);271}272273bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning) {274if (_is_node_locked(p_node)) {275return false;276}277if (Object::cast_to<Control>(p_node) && Object::cast_to<Container>(p_node->get_parent())) {278if (p_popup_warning) {279EditorToaster::get_singleton()->popup_str(TTR("Children of a container get their position and size determined only by their parent."), EditorToaster::SEVERITY_WARNING);280}281return false;282}283return true;284}285286void CanvasItemEditor::_snap_if_closer_float(287const real_t p_value,288real_t &r_current_snap, SnapTarget &r_current_snap_target,289const real_t p_target_value, const SnapTarget p_snap_target,290const real_t p_radius) {291const real_t radius = p_radius / zoom;292const real_t dist = Math::abs(p_value - p_target_value);293if ((p_radius < 0 || dist < radius) && (r_current_snap_target == SNAP_TARGET_NONE || dist < Math::abs(r_current_snap - p_value))) {294r_current_snap = p_target_value;295r_current_snap_target = p_snap_target;296}297}298299void CanvasItemEditor::_snap_if_closer_point(300Point2 p_value,301Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],302Point2 p_target_value, const SnapTarget p_snap_target,303const real_t rotation,304const real_t p_radius) {305Transform2D rot_trans = Transform2D(rotation, Point2());306p_value = rot_trans.inverse().xform(p_value);307p_target_value = rot_trans.inverse().xform(p_target_value);308r_current_snap = rot_trans.inverse().xform(r_current_snap);309310_snap_if_closer_float(311p_value.x,312r_current_snap.x,313r_current_snap_target[0],314p_target_value.x,315p_snap_target,316p_radius);317318_snap_if_closer_float(319p_value.y,320r_current_snap.y,321r_current_snap_target[1],322p_target_value.y,323p_snap_target,324p_radius);325326r_current_snap = rot_trans.xform(r_current_snap);327}328329void CanvasItemEditor::_snap_other_nodes(330const Point2 p_value,331const Transform2D p_transform_to_snap,332Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],333const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions,334const Node *p_current) {335const CanvasItem *ci = Object::cast_to<CanvasItem>(p_current);336337// Check if the element is in the exception338bool exception = false;339for (const CanvasItem *&E : p_exceptions) {340if (E == p_current) {341exception = true;342break;343}344};345346if (ci && !exception) {347Transform2D ci_transform = ci->get_screen_transform();348if (std::fmod(ci_transform.get_rotation() - p_transform_to_snap.get_rotation(), (real_t)360.0) == 0.0) {349if (ci->_edit_use_rect()) {350Point2 begin = ci_transform.xform(ci->_edit_get_rect().get_position());351Point2 end = ci_transform.xform(ci->_edit_get_rect().get_position() + ci->_edit_get_rect().get_size());352353_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, begin, p_snap_target, ci_transform.get_rotation());354_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, end, p_snap_target, ci_transform.get_rotation());355} else {356Point2 position = ci_transform.xform(Point2());357_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, position, p_snap_target, ci_transform.get_rotation());358}359}360}361for (int i = 0; i < p_current->get_child_count(); i++) {362_snap_other_nodes(p_value, p_transform_to_snap, r_current_snap, r_current_snap_target, p_snap_target, p_exceptions, p_current->get_child(i));363}364}365366Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsigned int p_forced_modes, const CanvasItem *p_self_canvas_item, const List<CanvasItem *> &p_other_nodes_exceptions) {367snap_target[0] = SNAP_TARGET_NONE;368snap_target[1] = SNAP_TARGET_NONE;369370bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);371372// Smart snap using the canvas position373Vector2 output = p_target;374real_t rotation = 0.0;375376if (p_self_canvas_item) {377rotation = p_self_canvas_item->get_screen_transform().get_rotation();378379// Parent sides and center380if ((is_snap_active && snap_node_parent && (p_modes & SNAP_NODE_PARENT)) || (p_forced_modes & SNAP_NODE_PARENT)) {381if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {382Point2 begin = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(0, 0)));383Point2 end = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(1, 1)));384_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);385_snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);386_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);387} else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_self_canvas_item->get_parent())) {388if (parent_ci->_edit_use_rect()) {389Point2 begin = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position());390Point2 end = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size());391_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);392_snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);393_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);394} else {395Point2 position = p_self_canvas_item->get_transform().affine_inverse().xform(Point2());396_snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_PARENT, rotation);397}398}399}400401// Self anchors402if ((is_snap_active && snap_node_anchors && (p_modes & SNAP_NODE_ANCHORS)) || (p_forced_modes & SNAP_NODE_ANCHORS)) {403if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {404Point2 begin = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_LEFT), c->get_anchor(SIDE_TOP))));405Point2 end = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_RIGHT), c->get_anchor(SIDE_BOTTOM))));406_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF_ANCHORS, rotation);407_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF_ANCHORS, rotation);408}409}410411// Self sides412if ((is_snap_active && snap_node_sides && (p_modes & SNAP_NODE_SIDES)) || (p_forced_modes & SNAP_NODE_SIDES)) {413if (p_self_canvas_item->_edit_use_rect()) {414Point2 begin = p_self_canvas_item->get_screen_transform().xform(p_self_canvas_item->_edit_get_rect().get_position());415Point2 end = p_self_canvas_item->get_screen_transform().xform(p_self_canvas_item->_edit_get_rect().get_position() + p_self_canvas_item->_edit_get_rect().get_size());416_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF, rotation);417_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF, rotation);418}419}420421// Self center422if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) {423if (p_self_canvas_item->_edit_use_rect()) {424Point2 center = p_self_canvas_item->get_screen_transform().xform(p_self_canvas_item->_edit_get_rect().get_center());425_snap_if_closer_point(p_target, output, snap_target, center, SNAP_TARGET_SELF, rotation);426} else {427Point2 position = p_self_canvas_item->get_screen_transform().xform(Point2());428_snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_SELF, rotation);429}430}431}432433// Other nodes sides434if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) {435Transform2D to_snap_transform;436List<const CanvasItem *> exceptions = List<const CanvasItem *>();437for (const CanvasItem *E : p_other_nodes_exceptions) {438exceptions.push_back(E);439}440if (p_self_canvas_item) {441exceptions.push_back(p_self_canvas_item);442to_snap_transform = p_self_canvas_item->get_screen_transform();443}444445_snap_other_nodes(446p_target, to_snap_transform,447output, snap_target,448SNAP_TARGET_OTHER_NODE,449exceptions,450get_tree()->get_edited_scene_root());451}452453if (((is_snap_active && snap_guides && (p_modes & SNAP_GUIDES)) || (p_forced_modes & SNAP_GUIDES)) && std::fmod(rotation, (real_t)360.0) == 0.0) {454// Guides.455if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {456Array vguides = scene->get_meta("_edit_vertical_guides_", Array());457for (int i = 0; i < vguides.size(); i++) {458_snap_if_closer_float(p_target.x, output.x, snap_target[0], vguides[i], SNAP_TARGET_GUIDE);459}460461Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());462for (int i = 0; i < hguides.size(); i++) {463_snap_if_closer_float(p_target.y, output.y, snap_target[1], hguides[i], SNAP_TARGET_GUIDE);464}465}466}467468if (((grid_snap_active && (p_modes & SNAP_GRID)) || (p_forced_modes & SNAP_GRID)) && std::fmod(rotation, (real_t)360.0) == 0.0) {469// Grid470Point2 offset = grid_offset;471if (snap_relative) {472List<CanvasItem *> selection = _get_edited_canvas_items();473if (selection.size() == 1 && Object::cast_to<Node2D>(selection.front()->get())) {474offset = Object::cast_to<Node2D>(selection.front()->get())->get_global_position();475} else if (selection.size() > 0) {476offset = _get_encompassing_rect_from_list(selection).position;477}478}479Point2 grid_output;480grid_output.x = Math::snapped(p_target.x - offset.x, grid_step.x * Math::pow(2.0, grid_step_multiplier)) + offset.x;481grid_output.y = Math::snapped(p_target.y - offset.y, grid_step.y * Math::pow(2.0, grid_step_multiplier)) + offset.y;482_snap_if_closer_point(p_target, output, snap_target, grid_output, SNAP_TARGET_GRID, 0.0, -1.0);483}484485if (((snap_pixel && (p_modes & SNAP_PIXEL)) || (p_forced_modes & SNAP_PIXEL)) && rotation == 0.0) {486// Pixel487output = output.snappedf(1);488}489490snap_transform = Transform2D(rotation, output);491492return output;493}494495real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {496if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) && snap_rotation_step != 0) {497if (snap_relative) {498return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);499} else {500return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset;501}502} else {503return p_target;504}505}506507void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) {508ERR_FAIL_COND(p_ev.is_null());509510Ref<InputEventKey> k = p_ev;511512if (!is_visible_in_tree()) {513return;514}515516if (k.is_valid()) {517if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT) {518viewport->queue_redraw();519}520521if (k->is_pressed() && !k->is_command_or_control_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {522if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) {523// Multiply the grid size524grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);525viewport->queue_redraw();526} else if (divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) {527// Divide the grid size528Point2 new_grid_step = grid_step * Math::pow(2.0, grid_step_multiplier - 1);529if (new_grid_step.x >= 1.0 && new_grid_step.y >= 1.0) {530grid_step_multiplier--;531}532viewport->queue_redraw();533}534}535536if (k->is_pressed() && !k->is_echo()) {537if (reset_transform_position_shortcut.is_valid() && reset_transform_position_shortcut->matches_event(p_ev)) {538_reset_transform(TransformType::POSITION);539}540if (reset_transform_rotation_shortcut.is_valid() && reset_transform_rotation_shortcut->matches_event(p_ev)) {541_reset_transform(TransformType::ROTATION);542}543if (reset_transform_scale_shortcut.is_valid() && reset_transform_scale_shortcut->matches_event(p_ev)) {544_reset_transform(TransformType::SCALE);545}546}547}548}549550Object *CanvasItemEditor::_get_editor_data(Object *p_what) {551CanvasItem *ci = Object::cast_to<CanvasItem>(p_what);552if (!ci) {553return nullptr;554}555556return memnew(CanvasItemEditorSelectedItem);557}558559void CanvasItemEditor::_keying_changed() {560AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();561if (te && te->is_visible_in_tree() && te->get_current_animation().is_valid()) {562animation_hb->show();563} else {564animation_hb->hide();565}566}567568Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(const List<CanvasItem *> &p_list) {569ERR_FAIL_COND_V(p_list.is_empty(), Rect2());570571// Handles the first element572CanvasItem *ci = p_list.front()->get();573Rect2 rect = Rect2(ci->get_global_transform_with_canvas().xform(ci->_edit_get_rect().get_center()), Size2());574575// Expand with the other ones576for (CanvasItem *ci2 : p_list) {577Transform2D xform = ci2->get_global_transform_with_canvas();578579Rect2 current_rect = ci2->_edit_get_rect();580rect.expand_to(xform.xform(current_rect.position));581rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0)));582rect.expand_to(xform.xform(current_rect.position + current_rect.size));583rect.expand_to(xform.xform(current_rect.position + Vector2(0, current_rect.size.y)));584}585586return rect;587}588589void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform, bool include_locked_nodes) {590if (!p_node) {591return;592}593if (Object::cast_to<Viewport>(p_node)) {594return;595}596597const CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);598599for (int i = p_node->get_child_count() - 1; i >= 0; i--) {600if (ci && !ci->is_set_as_top_level()) {601_expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * ci->get_transform(), p_canvas_xform);602} else {603const CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);604_expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);605}606}607608if (ci && ci->is_visible_in_tree() && (include_locked_nodes || !_is_node_locked(ci))) {609Transform2D xform = p_canvas_xform;610if (!ci->is_set_as_top_level()) {611xform *= p_parent_xform;612}613xform *= ci->get_transform();614Rect2 rect = ci->_edit_get_rect();615if (r_first) {616r_rect = Rect2(xform.xform(rect.get_center()), Size2());617r_first = false;618}619r_rect.expand_to(xform.xform(rect.position));620r_rect.expand_to(xform.xform(rect.position + Point2(rect.size.x, 0)));621r_rect.expand_to(xform.xform(rect.position + Point2(0, rect.size.y)));622r_rect.expand_to(xform.xform(rect.position + rect.size));623}624}625626Rect2 CanvasItemEditor::_get_encompassing_rect(const Node *p_node) {627Rect2 rect;628bool first = true;629_expand_encompassing_rect_using_children(rect, p_node, first);630631return rect;632}633634void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<_SelectResult> &r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {635if (!p_node) {636return;637}638639CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);640641Transform2D xform = p_canvas_xform;642if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {643xform = cl->get_transform();644} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {645if (!vp->is_visible_subviewport()) {646return;647}648xform = vp->get_popup_base_transform();649if (!vp->get_visible_rect().has_point(xform.affine_inverse().xform(p_pos))) {650return;651}652}653654for (int i = p_node->get_child_count() - 1; i >= 0; i--) {655if (ci) {656if (!ci->is_set_as_top_level()) {657_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), xform);658} else {659_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), xform);660}661} else {662_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), xform);663}664}665666if (ci && ci->is_visible_in_tree()) {667if (!ci->is_set_as_top_level()) {668xform *= p_parent_xform;669}670xform = (xform * ci->get_transform()).affine_inverse();671const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / zoom;672if (ci->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) {673Node2D *node = Object::cast_to<Node2D>(ci);674675_SelectResult res;676res.item = ci;677res.z_index = node ? node->get_z_index() : 0;678res.has_z = node;679r_items.push_back(res);680}681}682}683684void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked) {685Node *scene = EditorNode::get_singleton()->get_edited_scene();686687_find_canvas_items_at_pos(p_pos, scene, r_items);688689//Remove invalid results690for (int i = 0; i < r_items.size(); i++) {691Node *node = r_items[i].item;692693// Make sure the selected node is in the current scene, or editable694if (node && node != get_tree()->get_edited_scene_root()) {695node = scene->get_deepest_editable_node(node);696}697698CanvasItem *ci = Object::cast_to<CanvasItem>(node);699if (!p_allow_locked) {700// Replace the node by the group if grouped701while (node && node != scene->get_parent()) {702CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);703if (ci_tmp && node->has_meta("_edit_group_")) {704ci = ci_tmp;705}706node = node->get_parent();707}708}709710// Check if the canvas item is already in the list (for groups or scenes)711bool duplicate = false;712for (int j = 0; j < i; j++) {713if (r_items[j].item == ci) {714duplicate = true;715break;716}717}718719//Remove the item if invalid720if (!ci || duplicate || (ci != scene && ci->get_owner() != scene && !scene->is_editable_instance(ci->get_owner())) || (!p_allow_locked && _is_node_locked(ci))) {721r_items.remove_at(i);722i--;723} else {724r_items.write[i].item = ci;725}726}727}728729void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {730if (!p_node) {731return;732}733CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);734Node *scene = EditorNode::get_singleton()->get_edited_scene();735736if (p_node != scene && !p_node->get_owner()) {737return;738}739740bool editable = p_node == scene || p_node->get_owner() == scene || p_node == scene->get_deepest_editable_node(p_node);741bool lock_children = p_node->get_meta("_edit_group_", false);742bool locked = _is_node_locked(p_node);743744Transform2D xform = p_canvas_xform;745if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {746xform = cl->get_transform();747} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {748if (!vp->is_visible_subviewport()) {749return;750}751xform = vp->get_popup_base_transform();752if (!vp->get_visible_rect().intersects(xform.affine_inverse().xform(p_rect))) {753return;754}755}756757if (!lock_children || !editable) {758for (int i = p_node->get_child_count() - 1; i >= 0; i--) {759if (ci) {760if (!ci->is_set_as_top_level()) {761_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), xform);762} else {763_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), xform);764}765} else {766CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);767_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : xform);768}769}770}771772if (ci && ci->is_visible_in_tree() && !locked && editable) {773if (!ci->is_set_as_top_level()) {774xform *= p_parent_xform;775}776xform *= ci->get_transform();777778if (ci->_edit_use_rect()) {779Rect2 rect = ci->_edit_get_rect();780if (p_rect.has_point(xform.xform(rect.position)) &&781p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) &&782p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) &&783p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) {784r_items->push_back(ci);785}786} else {787if (p_rect.has_point(xform.xform(Point2()))) {788r_items->push_back(ci);789}790}791}792}793794bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append) {795bool still_selected = true;796const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();797if (p_append && !top_node_list.is_empty()) {798if (editor_selection->is_selected(item)) {799// Already in the selection, remove it from the selected nodes800editor_selection->remove_node(item);801still_selected = false;802803if (top_node_list.size() == 1) {804EditorNode::get_singleton()->push_item(top_node_list.front()->get());805}806} else {807// Add the item to the selection808editor_selection->add_node(item);809}810} else {811if (!editor_selection->is_selected(item)) {812// Select a new one and clear previous selection813editor_selection->clear();814editor_selection->add_node(item);815// Reselect816if (Engine::get_singleton()->is_editor_hint()) {817selected_from_canvas = true;818}819}820}821viewport->queue_redraw();822return still_selected;823}824825List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool p_retrieve_locked, bool p_remove_canvas_item_if_parent_in_selection, bool *r_has_locked_items) const {826List<CanvasItem *> selection;827for (const KeyValue<ObjectID, Object *> &E : editor_selection->get_selection()) {828CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(E.key);829if (ci) {830if (ci->is_visible_in_tree() && (p_retrieve_locked || !_is_node_locked(ci))) {831Viewport *vp = ci->get_viewport();832if (vp && !vp->is_visible_subviewport()) {833continue;834}835CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);836if (se) {837selection.push_back(ci);838}839} else if (r_has_locked_items) {840// CanvasItem is selected, but can't be interacted with.841*r_has_locked_items = true;842}843}844}845846if (p_remove_canvas_item_if_parent_in_selection) {847List<CanvasItem *> filtered_selection;848HashSet<const Node *> nodes_in_selection;849for (CanvasItem *E : selection) {850nodes_in_selection.insert(E);851}852for (CanvasItem *E : selection) {853if (!nodes_in_selection.has(E->get_parent())) {854filtered_selection.push_back(E);855}856}857return filtered_selection;858} else {859return selection;860}861}862863Vector2 CanvasItemEditor::_anchor_to_position(const Control *p_control, Vector2 anchor) {864ERR_FAIL_NULL_V(p_control, Vector2());865866Transform2D parent_transform = p_control->get_transform().affine_inverse();867Rect2 parent_rect = p_control->get_parent_anchorable_rect();868869if (p_control->is_layout_rtl()) {870return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));871} else {872return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));873}874}875876Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 position) {877ERR_FAIL_NULL_V(p_control, Vector2());878879Rect2 parent_rect = p_control->get_parent_anchorable_rect();880881Vector2 output;882if (p_control->is_layout_rtl()) {883output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x;884} else {885output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x;886}887output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y;888return output;889}890891void CanvasItemEditor::_save_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool save_bones) {892original_transform = Transform2D();893bool transform_stored = false;894895for (CanvasItem *ci : p_canvas_items) {896CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);897if (se) {898if (!transform_stored) {899original_transform = ci->get_global_transform();900transform_stored = true;901}902903se->undo_state = ci->_edit_get_state();904se->pre_drag_xform = ci->get_screen_transform();905if (ci->_edit_use_rect()) {906se->pre_drag_rect = ci->_edit_get_rect();907} else {908se->pre_drag_rect = Rect2();909}910}911}912}913914void CanvasItemEditor::_restore_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool restore_bones) {915for (CanvasItem *ci : drag_selection) {916CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);917ci->_edit_set_state(se->undo_state);918}919}920921void CanvasItemEditor::_commit_canvas_item_state(const List<CanvasItem *> &p_canvas_items, const String &action_name, bool commit_bones) {922List<CanvasItem *> modified_canvas_items;923for (CanvasItem *ci : p_canvas_items) {924Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci)->undo_state;925Dictionary new_state = ci->_edit_get_state();926927if (old_state.hash() != new_state.hash()) {928modified_canvas_items.push_back(ci);929}930}931932if (modified_canvas_items.is_empty()) {933return;934}935936EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();937undo_redo->create_action(action_name);938for (CanvasItem *ci : modified_canvas_items) {939CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);940if (se) {941undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());942undo_redo->add_undo_method(ci, "_edit_set_state", se->undo_state);943if (commit_bones) {944for (const Dictionary &F : se->pre_drag_bones_undo_state) {945ci = Object::cast_to<CanvasItem>(ci->get_parent());946undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());947undo_redo->add_undo_method(ci, "_edit_set_state", F);948}949}950}951}952undo_redo->add_do_method(viewport, "queue_redraw");953undo_redo->add_undo_method(viewport, "queue_redraw");954undo_redo->commit_action();955}956957void CanvasItemEditor::_snap_changed() {958static_cast<SnapDialog *>(snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);959960EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_offset", grid_offset);961EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_step", grid_step);962EditorSettings::get_singleton()->set_project_metadata("2d_editor", "primary_grid_step", primary_grid_step);963EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_offset", snap_rotation_offset);964EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_step", snap_rotation_step);965EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_scale_step", snap_scale_step);966967grid_step_multiplier = 0;968viewport->queue_redraw();969}970971void CanvasItemEditor::_selection_result_pressed(int p_result) {972if (selection_results_menu.size() <= p_result) {973return;974}975976CanvasItem *item = selection_results_menu[p_result].item;977978if (item) {979_select_click_on_item(item, Point2(), selection_menu_additive_selection);980}981selection_results_menu.clear();982}983984void CanvasItemEditor::_selection_menu_hide() {985selection_results.clear();986selection_menu->clear();987selection_menu->reset_size();988}989990void CanvasItemEditor::_add_node_pressed(int p_result) {991List<Node *> nodes_to_move;992993switch (p_result) {994case ADD_NODE: {995SceneTreeDock::get_singleton()->open_add_child_dialog();996} break;997case ADD_INSTANCE: {998SceneTreeDock::get_singleton()->open_instance_child_dialog();999} break;1000case ADD_PASTE: {1001nodes_to_move = SceneTreeDock::get_singleton()->paste_nodes();1002[[fallthrough]];1003}1004case ADD_MOVE: {1005nodes_to_move = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();1006if (nodes_to_move.is_empty()) {1007return;1008}10091010EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1011undo_redo->create_action(TTR("Move Node(s) to Position"));1012for (Node *node : nodes_to_move) {1013CanvasItem *ci = Object::cast_to<CanvasItem>(node);1014if (ci) {1015Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();1016undo_redo->add_do_method(ci, "_edit_set_position", xform.xform(node_create_position));1017undo_redo->add_undo_method(ci, "_edit_set_position", ci->_edit_get_position());1018}1019}1020undo_redo->commit_action();1021_reset_create_position();1022} break;1023default: {1024if (p_result >= EditorContextMenuPlugin::BASE_ID) {1025TypedArray<Node> nodes;1026nodes.resize(selection_results.size());10271028int i = 0;1029for (const _SelectResult &result : selection_results) {1030nodes[i] = result.item;1031i++;1032}1033EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, p_result, nodes);1034}1035}1036}1037}10381039void CanvasItemEditor::_adjust_new_node_position(Node *p_node) {1040if (node_create_position == Point2()) {1041return;1042}10431044CanvasItem *c = Object::cast_to<CanvasItem>(p_node);1045if (c) {1046Transform2D xform = c->get_global_transform_with_canvas().affine_inverse() * c->get_transform();1047c->_edit_set_position(xform.xform(node_create_position));1048}10491050callable_mp(this, &CanvasItemEditor::_reset_create_position).call_deferred(); // Defer the call in case more than one node is added.1051}10521053void CanvasItemEditor::_reset_create_position() {1054node_create_position = Point2();1055}10561057bool CanvasItemEditor::_is_grid_visible() const {1058switch (grid_visibility) {1059case GRID_VISIBILITY_SHOW:1060return true;1061case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1062return grid_snap_active;1063case GRID_VISIBILITY_HIDE:1064return false;1065}1066ERR_FAIL_V_MSG(true, "Unexpected grid_visibility value");1067}10681069void CanvasItemEditor::_prepare_grid_menu() {1070for (int i = GRID_VISIBILITY_SHOW; i <= GRID_VISIBILITY_HIDE; i++) {1071grid_menu->set_item_checked(i, i == grid_visibility);1072}1073}10741075void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) {1076switch (p_id) {1077case GRID_VISIBILITY_SHOW:1078case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1079case GRID_VISIBILITY_HIDE:1080grid_visibility = (GridVisibility)p_id;1081viewport->queue_redraw();1082view_menu->get_popup()->hide();1083return;1084}10851086// Toggle grid: go to the least restrictive option possible.1087if (grid_snap_active) {1088switch (grid_visibility) {1089case GRID_VISIBILITY_SHOW:1090case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1091grid_visibility = GRID_VISIBILITY_HIDE;1092break;1093case GRID_VISIBILITY_HIDE:1094grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;1095break;1096}1097} else {1098switch (grid_visibility) {1099case GRID_VISIBILITY_SHOW:1100grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;1101break;1102case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1103case GRID_VISIBILITY_HIDE:1104grid_visibility = GRID_VISIBILITY_SHOW;1105break;1106}1107}1108viewport->queue_redraw();1109}11101111void CanvasItemEditor::_reset_transform(TransformType p_type) {1112List<Node *> selection = editor_selection->get_full_selected_node_list();1113if (selection.is_empty()) {1114return;1115}1116EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1117undo_redo->create_action(TTR("Reset Transform"));1118for (Node *node : selection) {1119Node2D *res_node = Object::cast_to<Node2D>(node);1120if (res_node) {1121switch (p_type) {1122case TransformType::POSITION:1123undo_redo->add_undo_method(res_node, "set_position", res_node->get_position());1124undo_redo->add_do_method(res_node, "set_position", Vector2());1125break;1126case TransformType::ROTATION:1127undo_redo->add_undo_method(res_node, "set_rotation", res_node->get_rotation());1128undo_redo->add_do_method(res_node, "set_rotation", 0);1129break;1130case TransformType::SCALE:1131undo_redo->add_undo_method(res_node, "set_scale", res_node->get_scale());1132undo_redo->add_do_method(res_node, "set_scale", Size2(1, 1));1133break;1134}1135continue;1136}1137Control *res_control = Object::cast_to<Control>(node);1138if (res_control) {1139switch (p_type) {1140case TransformType::POSITION:1141undo_redo->add_undo_method(res_control, "set_position", res_control->get_position());1142undo_redo->add_do_method(res_control, "set_position", Vector2());1143break;1144case TransformType::ROTATION:1145undo_redo->add_undo_method(res_control, "set_rotation", res_control->get_rotation());1146undo_redo->add_do_method(res_control, "set_rotation", 0);1147break;1148case TransformType::SCALE:1149undo_redo->add_undo_method(res_control, "set_scale", res_control->get_scale());1150undo_redo->add_do_method(res_control, "set_scale", Size2(1, 1));1151break;1152}1153}1154}1155undo_redo->commit_action();1156}11571158void CanvasItemEditor::_switch_theme_preview(int p_mode) {1159view_menu->get_popup()->hide();11601161if (theme_preview == p_mode) {1162return;1163}1164theme_preview = (ThemePreviewMode)p_mode;1165EditorSettings::get_singleton()->set_project_metadata("2d_editor", "theme_preview", theme_preview);11661167for (int i = 0; i < THEME_PREVIEW_MAX; i++) {1168theme_menu->set_item_checked(i, i == theme_preview);1169}11701171EditorNode::get_singleton()->update_preview_themes(theme_preview);1172}11731174bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) {1175EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1176Ref<InputEventMouseButton> b = p_event;1177Ref<InputEventMouseMotion> m = p_event;11781179if (drag_type == DRAG_NONE) {1180if (show_guides && show_rulers && EditorNode::get_singleton()->get_edited_scene()) {1181Transform2D xform = viewport_scrollable->get_transform() * transform;1182// Retrieve the guide lists1183Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());1184Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());11851186// Hover over guides1187real_t minimum = 1e20;1188is_hovering_h_guide = false;1189is_hovering_v_guide = false;11901191if (m.is_valid() && m->get_position().x < ruler_width_scaled) {1192// Check if we are hovering an existing horizontal guide1193for (int i = 0; i < hguides.size(); i++) {1194if (Math::abs(xform.xform(Point2(0, hguides[i])).y - m->get_position().y) < MIN(minimum, 8)) {1195is_hovering_h_guide = true;1196is_hovering_v_guide = false;1197break;1198}1199}12001201} else if (m.is_valid() && m->get_position().y < ruler_width_scaled) {1202// Check if we are hovering an existing vertical guide1203for (int i = 0; i < vguides.size(); i++) {1204if (Math::abs(xform.xform(Point2(vguides[i], 0)).x - m->get_position().x) < MIN(minimum, 8)) {1205is_hovering_v_guide = true;1206is_hovering_h_guide = false;1207break;1208}1209}1210}12111212// Start dragging a guide1213if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {1214// Press button1215if (b->get_position().x < ruler_width_scaled && b->get_position().y < ruler_width_scaled) {1216// Drag a new double guide1217drag_type = DRAG_DOUBLE_GUIDE;1218dragged_guide_index = -1;1219return true;1220} else if (b->get_position().x < ruler_width_scaled) {1221// Check if we drag an existing horizontal guide1222dragged_guide_index = -1;1223for (int i = 0; i < hguides.size(); i++) {1224if (Math::abs(xform.xform(Point2(0, hguides[i])).y - b->get_position().y) < MIN(minimum, 8)) {1225dragged_guide_index = i;1226}1227}12281229if (dragged_guide_index >= 0) {1230// Drag an existing horizontal guide1231drag_type = DRAG_H_GUIDE;1232} else {1233// Drag a new vertical guide1234drag_type = DRAG_V_GUIDE;1235}1236return true;1237} else if (b->get_position().y < ruler_width_scaled) {1238// Check if we drag an existing vertical guide1239dragged_guide_index = -1;1240for (int i = 0; i < vguides.size(); i++) {1241if (Math::abs(xform.xform(Point2(vguides[i], 0)).x - b->get_position().x) < MIN(minimum, 8)) {1242dragged_guide_index = i;1243}1244}12451246if (dragged_guide_index >= 0) {1247// Drag an existing vertical guide1248drag_type = DRAG_V_GUIDE;1249} else {1250// Drag a new vertical guide1251drag_type = DRAG_H_GUIDE;1252}1253drag_from = xform.affine_inverse().xform(b->get_position());1254return true;1255}1256}1257}1258}12591260if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE || drag_type == DRAG_H_GUIDE) {1261// Move the guide1262if (m.is_valid()) {1263Transform2D xform = viewport_scrollable->get_transform() * transform;1264drag_to = xform.affine_inverse().xform(m->get_position());12651266dragged_guide_pos = xform.xform(snap_point(drag_to, SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES));1267viewport->queue_redraw();1268return true;1269}12701271// Release confirms the guide move1272if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1273if (show_guides && EditorNode::get_singleton()->get_edited_scene()) {1274Transform2D xform = viewport_scrollable->get_transform() * transform;12751276// Retrieve the guide lists1277Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());1278Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());12791280Point2 edited = snap_point(xform.affine_inverse().xform(b->get_position()), SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES);1281if (drag_type == DRAG_V_GUIDE) {1282Array prev_vguides = vguides.duplicate();1283if (b->get_position().x > ruler_width_scaled) {1284// Adds a new vertical guide1285if (dragged_guide_index >= 0) {1286vguides[dragged_guide_index] = edited.x;1287undo_redo->create_action(TTR("Move Vertical Guide"));1288undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1289undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1290undo_redo->add_undo_method(viewport, "queue_redraw");1291undo_redo->commit_action();1292} else {1293vguides.push_back(edited.x);1294undo_redo->create_action(TTR("Create Vertical Guide"));1295undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1296if (prev_vguides.is_empty()) {1297undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1298} else {1299undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1300}1301undo_redo->add_undo_method(viewport, "queue_redraw");1302undo_redo->commit_action();1303}1304} else {1305if (dragged_guide_index >= 0) {1306vguides.remove_at(dragged_guide_index);1307undo_redo->create_action(TTR("Remove Vertical Guide"));1308if (vguides.is_empty()) {1309undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1310} else {1311undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1312}1313undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1314undo_redo->add_undo_method(viewport, "queue_redraw");1315undo_redo->commit_action();1316}1317}1318} else if (drag_type == DRAG_H_GUIDE) {1319Array prev_hguides = hguides.duplicate();1320if (b->get_position().y > ruler_width_scaled) {1321// Adds a new horizontal guide1322if (dragged_guide_index >= 0) {1323hguides[dragged_guide_index] = edited.y;1324undo_redo->create_action(TTR("Move Horizontal Guide"));1325undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1326undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1327undo_redo->add_undo_method(viewport, "queue_redraw");1328undo_redo->commit_action();1329} else {1330hguides.push_back(edited.y);1331undo_redo->create_action(TTR("Create Horizontal Guide"));1332undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1333if (prev_hguides.is_empty()) {1334undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1335} else {1336undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1337}1338undo_redo->add_undo_method(viewport, "queue_redraw");1339undo_redo->commit_action();1340}1341} else {1342if (dragged_guide_index >= 0) {1343hguides.remove_at(dragged_guide_index);1344undo_redo->create_action(TTR("Remove Horizontal Guide"));1345if (hguides.is_empty()) {1346undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1347} else {1348undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1349}1350undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1351undo_redo->add_undo_method(viewport, "queue_redraw");1352undo_redo->commit_action();1353}1354}1355} else if (drag_type == DRAG_DOUBLE_GUIDE) {1356Array prev_hguides = hguides.duplicate();1357Array prev_vguides = vguides.duplicate();1358if (b->get_position().x > ruler_width_scaled && b->get_position().y > ruler_width_scaled) {1359// Adds a new horizontal guide a new vertical guide1360vguides.push_back(edited.x);1361hguides.push_back(edited.y);1362undo_redo->create_action(TTR("Create Horizontal and Vertical Guides"));1363undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1364undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1365if (prev_vguides.is_empty()) {1366undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1367} else {1368undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1369}1370if (prev_hguides.is_empty()) {1371undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1372} else {1373undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1374}1375undo_redo->add_undo_method(viewport, "queue_redraw");1376undo_redo->commit_action();1377}1378}1379}1380snap_target[0] = SNAP_TARGET_NONE;1381snap_target[1] = SNAP_TARGET_NONE;1382_reset_drag();1383viewport->queue_redraw();1384return true;1385}1386}1387return false;1388}13891390bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {1391panner->set_force_drag(tool == TOOL_PAN);1392bool panner_active = panner->gui_input(p_event, viewport->get_global_rect());1393if (panner->is_panning() != pan_pressed) {1394pan_pressed = panner->is_panning();1395_update_cursor();1396}13971398if (panner_active) {1399return true;1400}14011402Ref<InputEventKey> k = p_event;1403if (k.is_valid()) {1404if (k->is_pressed()) {1405if (ED_IS_SHORTCUT("canvas_item_editor/zoom_3.125_percent", p_event)) {1406_shortcut_zoom_set(1.0 / 32.0);1407} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_6.25_percent", p_event)) {1408_shortcut_zoom_set(1.0 / 16.0);1409} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_12.5_percent", p_event)) {1410_shortcut_zoom_set(1.0 / 8.0);1411} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_25_percent", p_event)) {1412_shortcut_zoom_set(1.0 / 4.0);1413} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_50_percent", p_event)) {1414_shortcut_zoom_set(1.0 / 2.0);1415} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_100_percent", p_event)) {1416_shortcut_zoom_set(1.0);1417} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_200_percent", p_event)) {1418_shortcut_zoom_set(2.0);1419} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_400_percent", p_event)) {1420_shortcut_zoom_set(4.0);1421} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_800_percent", p_event)) {1422_shortcut_zoom_set(8.0);1423} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_1600_percent", p_event)) {1424_shortcut_zoom_set(16.0);1425}1426}1427}14281429return false;1430}14311432void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {1433view_offset.x -= p_scroll_vec.x / zoom;1434view_offset.y -= p_scroll_vec.y / zoom;1435update_viewport();1436}14371438void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {1439Ref<InputEventMouseButton> mb = p_event;1440if (mb.is_valid()) {1441// Special behavior for scroll events, as the zoom_by_increment method can smartly end up on powers of two.1442int increment = p_zoom_factor > 1.0 ? 1 : -1;1443bool by_integer = mb->is_alt_pressed();14441445if (EDITOR_GET("editors/2d/use_integer_zoom_by_default")) {1446by_integer = !by_integer;1447}14481449zoom_widget->set_zoom_by_increments(increment, by_integer);1450} else {1451zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);1452}14531454_zoom_on_position(zoom_widget->get_zoom(), p_origin);1455}14561457bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {1458Ref<InputEventMouseMotion> m = p_event;1459Ref<InputEventMouseButton> b = p_event;1460Ref<InputEventKey> k = p_event;14611462// Drag the pivot (in pivot mode / with V key)1463if (drag_type == DRAG_NONE) {1464bool move_temp_pivot = ((b.is_valid() && b->is_shift_pressed()) || (k.is_valid() && k->is_shift_pressed()));14651466if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1467(k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && (k->get_modifiers_mask().is_empty() || move_temp_pivot))) {1468List<CanvasItem *> selection = _get_edited_canvas_items();14691470// Filters the selection with nodes that allow setting the pivot1471drag_selection = List<CanvasItem *>();1472for (CanvasItem *ci : selection) {1473if (ci->_edit_use_pivot() || move_temp_pivot) {1474drag_selection.push_back(ci);1475}1476}14771478// Start dragging if we still have nodes1479if (drag_selection.size() > 0) {1480Vector2 event_pos = (b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position();14811482if (move_temp_pivot) {1483drag_type = DRAG_TEMP_PIVOT;1484temp_pivot = transform.affine_inverse().xform(event_pos);1485viewport->queue_redraw();1486return true;1487}14881489_save_canvas_item_state(drag_selection);1490drag_from = transform.affine_inverse().xform(event_pos);1491Vector2 new_pos;1492if (drag_selection.size() == 1) {1493new_pos = snap_point(drag_from, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection.front()->get());1494} else {1495new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, nullptr, drag_selection);1496}1497for (CanvasItem *ci : drag_selection) {1498ci->_edit_set_pivot(ci->get_screen_transform().affine_inverse().xform(new_pos));1499}15001501drag_type = DRAG_PIVOT;1502}1503return true;1504}1505}15061507if (drag_type == DRAG_PIVOT) {1508// Move the pivot1509if (m.is_valid()) {1510drag_to = transform.affine_inverse().xform(m->get_position());1511_restore_canvas_item_state(drag_selection);1512Vector2 new_pos;1513if (drag_selection.size() == 1) {1514new_pos = snap_point(drag_to, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection.front()->get());1515} else {1516new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL);1517}1518for (CanvasItem *ci : drag_selection) {1519ci->_edit_set_pivot(ci->get_screen_transform().affine_inverse().xform(new_pos));1520}1521return true;1522}15231524// Confirm the pivot move1525if (drag_selection.size() >= 1 &&1526((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1527(k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V))) {1528_commit_drag();1529snap_target[0] = SNAP_TARGET_NONE;1530snap_target[1] = SNAP_TARGET_NONE;1531return true;1532}15331534// Cancel a drag1535if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1536_restore_canvas_item_state(drag_selection);1537snap_target[0] = SNAP_TARGET_NONE;1538snap_target[1] = SNAP_TARGET_NONE;1539_reset_drag();1540viewport->queue_redraw();1541return true;1542}1543}15441545if (drag_type == DRAG_TEMP_PIVOT) {1546if (m.is_valid()) {1547temp_pivot = transform.affine_inverse().xform(m->get_position());1548viewport->queue_redraw();1549return true;1550}15511552if ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1553(k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V)) {1554drag_type = DRAG_NONE;1555return true;1556}1557}1558return false;1559}15601561bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {1562Ref<InputEventMouseButton> b = p_event;1563Ref<InputEventMouseMotion> m = p_event;15641565// Start rotation1566if (drag_type == DRAG_NONE) {1567if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {1568if ((b->is_command_or_control_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {1569bool has_locked_items = false;1570List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);15711572// Remove not movable nodes1573for (List<CanvasItem *>::Element *E = selection.front(); E;) {1574List<CanvasItem *>::Element *N = E->next();1575if (!_is_node_movable(E->get(), true)) {1576selection.erase(E);1577}1578E = N;1579}15801581drag_selection = selection;1582if (drag_selection.size() > 0) {1583drag_type = DRAG_ROTATE;1584drag_from = transform.affine_inverse().xform(b->get_position());1585CanvasItem *ci = drag_selection.front()->get();1586if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {1587drag_rotation_center = temp_pivot;1588} else if (ci->_edit_use_pivot()) {1589drag_rotation_center = ci->get_screen_transform().xform(ci->_edit_get_pivot());1590} else {1591drag_rotation_center = ci->get_screen_transform().get_origin();1592}1593_save_canvas_item_state(drag_selection);1594return true;1595} else {1596if (has_locked_items) {1597EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);1598}1599return has_locked_items;1600}1601}1602}1603}16041605if (drag_type == DRAG_ROTATE) {1606// Rotate the node1607if (m.is_valid()) {1608_restore_canvas_item_state(drag_selection);1609for (CanvasItem *ci : drag_selection) {1610drag_to = transform.affine_inverse().xform(m->get_position());1611//Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements1612bool opposite = (ci->get_global_transform().get_scale().sign().dot(ci->get_transform().get_scale().sign()) == 0);1613real_t prev_rotation = ci->_edit_get_rotation();1614real_t new_rotation = snap_angle(ci->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), prev_rotation);16151616ci->_edit_set_rotation(new_rotation);1617if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {1618Transform2D xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();1619Vector2 radius = xform.xform(ci->_edit_get_position()) - temp_pivot;1620radius = radius.rotated(new_rotation - prev_rotation);1621ci->_edit_set_position(xform.affine_inverse().xform(temp_pivot + radius));1622}1623viewport->queue_redraw();1624}1625return true;1626}16271628// Confirms the node rotation1629if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1630_commit_drag();1631return true;1632}16331634// Cancel a drag1635if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1636_restore_canvas_item_state(drag_selection);1637_reset_drag();1638viewport->queue_redraw();1639return true;1640}1641}1642return false;1643}16441645bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEvent> &p_event) {1646Ref<InputEventMouseButton> b = p_event;16471648// Open a sub-scene on double-click1649if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) {1650List<CanvasItem *> selection = _get_edited_canvas_items();1651if (selection.size() == 1) {1652CanvasItem *ci = selection.front()->get();1653if (ci->is_instance() && ci != EditorNode::get_singleton()->get_edited_scene()) {1654EditorNode::get_singleton()->load_scene(ci->get_scene_file_path());1655return true;1656}1657}1658}1659return false;1660}16611662bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {1663Ref<InputEventMouseButton> b = p_event;1664Ref<InputEventMouseMotion> m = p_event;16651666// Starts anchor dragging if needed1667if (drag_type == DRAG_NONE) {1668if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {1669List<CanvasItem *> selection = _get_edited_canvas_items();1670if (selection.size() == 1) {1671Control *control = Object::cast_to<Control>(selection.front()->get());1672if (control && _is_node_movable(control)) {1673Vector2 anchor_pos[4];1674anchor_pos[0] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_TOP));1675anchor_pos[1] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_TOP));1676anchor_pos[2] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_BOTTOM));1677anchor_pos[3] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_BOTTOM));16781679Rect2 anchor_rects[4];1680for (int i = 0; i < 4; i++) {1681anchor_pos[i] = (transform * control->get_screen_transform()).xform(_anchor_to_position(control, anchor_pos[i]));1682anchor_rects[i] = Rect2(anchor_pos[i], anchor_handle->get_size());1683if (control->is_layout_rtl()) {1684anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 1 || i == 2), real_t(i <= 1));1685} else {1686anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 0 || i == 3), real_t(i <= 1));1687}1688}16891690const DragType dragger[] = {1691DRAG_ANCHOR_TOP_LEFT,1692DRAG_ANCHOR_TOP_RIGHT,1693DRAG_ANCHOR_BOTTOM_RIGHT,1694DRAG_ANCHOR_BOTTOM_LEFT,1695};16961697for (int i = 0; i < 4; i++) {1698if (anchor_rects[i].has_point(b->get_position())) {1699if ((anchor_pos[0] == anchor_pos[2]) && (anchor_pos[0].distance_to(b->get_position()) < anchor_handle->get_size().length() / 3.0)) {1700drag_type = DRAG_ANCHOR_ALL;1701} else {1702drag_type = dragger[i];1703}1704drag_from = transform.affine_inverse().xform(b->get_position());1705drag_selection = List<CanvasItem *>();1706drag_selection.push_back(control);1707_save_canvas_item_state(drag_selection);1708return true;1709}1710}1711}1712}1713}1714}17151716if (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_TOP_RIGHT || drag_type == DRAG_ANCHOR_BOTTOM_RIGHT || drag_type == DRAG_ANCHOR_BOTTOM_LEFT || drag_type == DRAG_ANCHOR_ALL) {1717// Drag the anchor1718if (m.is_valid()) {1719_restore_canvas_item_state(drag_selection);1720Control *control = Object::cast_to<Control>(drag_selection.front()->get());17211722drag_to = transform.affine_inverse().xform(m->get_position());17231724Transform2D xform = control->get_screen_transform().affine_inverse();17251726Point2 previous_anchor;1727previous_anchor.x = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_BOTTOM_LEFT) ? control->get_anchor(SIDE_LEFT) : control->get_anchor(SIDE_RIGHT);1728previous_anchor.y = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_TOP_RIGHT) ? control->get_anchor(SIDE_TOP) : control->get_anchor(SIDE_BOTTOM);1729previous_anchor = xform.affine_inverse().xform(_anchor_to_position(control, previous_anchor));17301731Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER, control));1732new_anchor = _position_to_anchor(control, new_anchor).snappedf(0.001);17331734bool use_single_axis = m->is_shift_pressed();1735Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from);1736bool use_y = Math::abs(drag_vector.y) > Math::abs(drag_vector.x);17371738switch (drag_type) {1739case DRAG_ANCHOR_TOP_LEFT:1740if (!use_single_axis || !use_y) {1741control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);1742}1743if (!use_single_axis || use_y) {1744control->set_anchor(SIDE_TOP, new_anchor.y, false, false);1745}1746break;1747case DRAG_ANCHOR_TOP_RIGHT:1748if (!use_single_axis || !use_y) {1749control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);1750}1751if (!use_single_axis || use_y) {1752control->set_anchor(SIDE_TOP, new_anchor.y, false, false);1753}1754break;1755case DRAG_ANCHOR_BOTTOM_RIGHT:1756if (!use_single_axis || !use_y) {1757control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);1758}1759if (!use_single_axis || use_y) {1760control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);1761}1762break;1763case DRAG_ANCHOR_BOTTOM_LEFT:1764if (!use_single_axis || !use_y) {1765control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);1766}1767if (!use_single_axis || use_y) {1768control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);1769}1770break;1771case DRAG_ANCHOR_ALL:1772if (!use_single_axis || !use_y) {1773control->set_anchor(SIDE_LEFT, new_anchor.x, false, true);1774control->set_anchor(SIDE_RIGHT, new_anchor.x, false, true);1775}1776if (!use_single_axis || use_y) {1777control->set_anchor(SIDE_TOP, new_anchor.y, false, true);1778control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, true);1779}1780break;1781default:1782break;1783}1784return true;1785}17861787// Confirms new anchor position1788if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1789_commit_drag();1790return true;1791}17921793// Cancel a drag1794if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1795_restore_canvas_item_state(drag_selection);1796snap_target[0] = SNAP_TARGET_NONE;1797snap_target[1] = SNAP_TARGET_NONE;1798_reset_drag();1799viewport->queue_redraw();1800return true;1801}1802}1803return false;1804}18051806bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {1807Ref<InputEventMouseButton> b = p_event;1808Ref<InputEventMouseMotion> m = p_event;18091810// Drag resize handles1811if (drag_type == DRAG_NONE) {1812if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {1813List<CanvasItem *> selection = _get_edited_canvas_items();1814if (selection.size() == 1) {1815CanvasItem *ci = selection.front()->get();1816if (ci->_edit_use_rect() && _is_node_movable(ci)) {1817Rect2 rect = ci->_edit_get_rect();1818Transform2D xform = transform * ci->get_screen_transform();18191820const Vector2 endpoints[4] = {1821xform.xform(rect.position),1822xform.xform(rect.position + Vector2(rect.size.x, 0)),1823xform.xform(rect.position + rect.size),1824xform.xform(rect.position + Vector2(0, rect.size.y))1825};18261827const DragType dragger[] = {1828DRAG_TOP_LEFT,1829DRAG_TOP,1830DRAG_TOP_RIGHT,1831DRAG_RIGHT,1832DRAG_BOTTOM_RIGHT,1833DRAG_BOTTOM,1834DRAG_BOTTOM_LEFT,1835DRAG_LEFT1836};18371838DragType resize_drag = DRAG_NONE;1839real_t radius = (select_handle->get_size().width / 2) * 1.5;18401841for (int i = 0; i < 4; i++) {1842int prev = (i + 3) % 4;1843int next = (i + 1) % 4;18441845Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();1846ofs *= (select_handle->get_size().width / 2);1847ofs += endpoints[i];1848if (ofs.distance_to(b->get_position()) < radius) {1849resize_drag = dragger[i * 2];1850}18511852ofs = (endpoints[i] + endpoints[next]) / 2;1853ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);1854if (ofs.distance_to(b->get_position()) < radius) {1855resize_drag = dragger[i * 2 + 1];1856}1857}18581859if (resize_drag != DRAG_NONE) {1860drag_type = resize_drag;1861drag_from = transform.affine_inverse().xform(b->get_position());1862drag_selection = List<CanvasItem *>();1863drag_selection.push_back(ci);1864_save_canvas_item_state(drag_selection);1865return true;1866}1867}1868}1869}1870}18711872if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||1873drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1874// Resize the node1875if (m.is_valid()) {1876CanvasItem *ci = drag_selection.front()->get();1877CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);1878//Reset state1879ci->_edit_set_state(se->undo_state);18801881bool uniform = m->is_shift_pressed();1882bool symmetric = m->is_alt_pressed();18831884Rect2 local_rect = ci->_edit_get_rect();1885real_t aspect = local_rect.has_area() ? (local_rect.get_size().y / local_rect.get_size().x) : (local_rect.get_size().y + 1.0) / (local_rect.get_size().x + 1.0);1886Point2 current_begin = local_rect.get_position();1887Point2 current_end = local_rect.get_position() + local_rect.get_size();1888Point2 max_begin = (symmetric) ? (current_begin + current_end - ci->_edit_get_minimum_size()) / 2.0 : current_end - ci->_edit_get_minimum_size();1889Point2 min_end = (symmetric) ? (current_begin + current_end + ci->_edit_get_minimum_size()) / 2.0 : current_begin + ci->_edit_get_minimum_size();1890Point2 center = (current_begin + current_end) / 2.0;18911892drag_to = transform.affine_inverse().xform(m->get_position());18931894Transform2D xform = ci->get_screen_transform();18951896Point2 drag_to_snapped_begin;1897Point2 drag_to_snapped_end;18981899drag_to_snapped_end = snap_point(xform.xform(current_end) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, ci);1900drag_to_snapped_begin = snap_point(xform.xform(current_begin) + (drag_to - drag_from), SNAP_NODE_ANCHORS | SNAP_NODE_PARENT | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, ci);19011902Point2 drag_begin = xform.affine_inverse().xform(drag_to_snapped_begin);1903Point2 drag_end = xform.affine_inverse().xform(drag_to_snapped_end);19041905// Horizontal resize1906if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1907current_begin.x = MIN(drag_begin.x, max_begin.x);1908} else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {1909current_end.x = MAX(drag_end.x, min_end.x);1910}19111912// Vertical resize1913if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1914current_begin.y = MIN(drag_begin.y, max_begin.y);1915} else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1916current_end.y = MAX(drag_end.y, min_end.y);1917}19181919// Uniform resize1920if (uniform) {1921if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT) {1922current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);1923} else if (drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM) {1924current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;1925} else {1926if (aspect >= 1.0) {1927if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1928current_begin.y = current_end.y - aspect * (current_end.x - current_begin.x);1929} else {1930current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);1931}1932} else {1933if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1934current_begin.x = current_end.x - (current_end.y - current_begin.y) / aspect;1935} else {1936current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;1937}1938}1939}1940}19411942// Symmetric resize1943if (symmetric) {1944if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1945current_end.x = 2.0 * center.x - current_begin.x;1946} else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {1947current_begin.x = 2.0 * center.x - current_end.x;1948}1949if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1950current_end.y = 2.0 * center.y - current_begin.y;1951} else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1952current_begin.y = 2.0 * center.y - current_end.y;1953}1954}1955ci->_edit_set_rect(Rect2(current_begin, current_end - current_begin));1956return true;1957}19581959// Confirm resize1960if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1961_commit_drag();1962return true;1963}19641965// Cancel a drag1966if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1967_restore_canvas_item_state(drag_selection);1968snap_target[0] = SNAP_TARGET_NONE;1969snap_target[1] = SNAP_TARGET_NONE;1970_reset_drag();1971viewport->queue_redraw();1972return true;1973}1974}1975return false;1976}19771978bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {1979Ref<InputEventMouseButton> b = p_event;1980Ref<InputEventMouseMotion> m = p_event;19811982// Drag resize handles1983if (drag_type == DRAG_NONE) {1984if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() &&1985((tool == TOOL_SELECT && b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {1986bool has_locked_items = false;1987List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);19881989// Remove non-movable nodes.1990for (CanvasItem *ci : selection) {1991if (!_is_node_movable(ci, true)) {1992selection.erase(ci);1993}1994}19951996if (!selection.is_empty()) {1997CanvasItem *ci = selection.front()->get();19981999Transform2D edit_transform;2000if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {2001edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);2002} else {2003edit_transform = ci->_edit_get_transform();2004}20052006Transform2D xform = transform * ci->get_screen_transform();2007Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();2008Transform2D simple_xform;2009if (use_local_space) {2010simple_xform = viewport->get_transform() * unscaled_transform;2011} else {2012Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());2013simple_xform = viewport->get_transform() * translation;2014}20152016drag_type = DRAG_SCALE_BOTH;20172018if (show_transformation_gizmos) {2019Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);2020Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2021if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2022drag_type = DRAG_SCALE_X;2023}2024Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2025if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2026drag_type = DRAG_SCALE_Y;2027}2028}20292030drag_from = transform.affine_inverse().xform(b->get_position());2031drag_selection = selection;2032_save_canvas_item_state(drag_selection);2033return true;2034} else {2035if (has_locked_items) {2036EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);2037}2038return has_locked_items;2039}2040}2041} else if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {2042// Resize the node2043if (m.is_valid()) {2044_restore_canvas_item_state(drag_selection);20452046drag_to = transform.affine_inverse().xform(m->get_position());20472048Size2 scale_max;2049if (drag_type != DRAG_SCALE_BOTH) {2050for (CanvasItem *ci : drag_selection) {2051Size2 scale = ci->_edit_get_scale();20522053if (Math::abs(scale.x) > Math::abs(scale_max.x)) {2054scale_max.x = scale.x;2055}2056if (Math::abs(scale.y) > Math::abs(scale_max.y)) {2057scale_max.y = scale.y;2058}2059}2060}20612062Transform2D edit_transform;2063bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y);2064if (using_temp_pivot) {2065edit_transform = Transform2D(drag_selection.front()->get()->_edit_get_rotation(), temp_pivot);2066} else {2067edit_transform = drag_selection.front()->get()->_edit_get_transform();2068}2069for (CanvasItem *ci : drag_selection) {2070Transform2D parent_xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();2071Transform2D unscaled_transform = (transform * parent_xform * edit_transform).orthonormalized();2072Transform2D simple_xform;20732074if (use_local_space || drag_type == DRAG_SCALE_BOTH) {2075simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;2076} else {2077Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());2078simple_xform = (viewport->get_transform() * translation).affine_inverse() * transform;2079}20802081bool uniform = m->is_shift_pressed();2082bool is_ctrl = m->is_command_or_control_pressed();20832084Point2 drag_from_local = simple_xform.xform(drag_from);2085Point2 drag_to_local = simple_xform.xform(drag_to);2086Point2 offset = drag_to_local - drag_from_local;20872088Transform2D object_transform = ci->_edit_get_transform();2089if (ci->is_class("Node2D")) {2090object_transform.set_skew(ci->get("skew"));2091}20922093Size2 scale = ci->_edit_get_scale();2094Size2 original_scale = scale;2095real_t ratio = scale.y / scale.x;2096if (drag_type == DRAG_SCALE_BOTH) {2097Size2 scale_factor = drag_to_local / drag_from_local;2098if (uniform) {2099scale *= (scale_factor.x + scale_factor.y) / 2.0;2100} else {2101scale *= scale_factor;2102}2103} else {2104Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE;2105Size2 parent_scale = parent_xform.get_scale();2106// Take into account the biggest scale, so all nodes are scaled uniformly.2107scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y) / (scale_max / original_scale);21082109if (drag_type == DRAG_SCALE_X) {2110if (!use_local_space && !uniform) {2111object_transform.set_origin(Vector2(0.0, 0.0));2112object_transform.scale(Size2(scale_factor.x + 1.0, 1.0));2113scale *= object_transform.get_scale();2114} else {2115scale.x += scale_factor.x;2116}2117if (uniform) {2118scale.y = scale.x * ratio;2119}2120} else if (drag_type == DRAG_SCALE_Y) {2121if (!use_local_space && !uniform) {2122object_transform.set_origin(Vector2(0.0, 0.0));2123object_transform.scale(Size2(1.0, -scale_factor.y + 1.0));2124scale *= object_transform.get_scale();2125} else {2126scale.y -= scale_factor.y;2127}2128if (uniform) {2129scale.x = scale.y / ratio;2130}2131}2132}21332134if (snap_scale && !is_ctrl) {2135if (snap_relative) {2136scale.x = original_scale.x * (Math::round((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step);2137scale.y = original_scale.y * (Math::round((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step);2138} else {2139scale.x = Math::round(scale.x / snap_scale_step) * snap_scale_step;2140scale.y = Math::round(scale.y / snap_scale_step) * snap_scale_step;2141}2142}21432144ci->_edit_set_scale(scale);2145if (!use_local_space && !uniform) {2146Node2D *n2d = Object::cast_to<Node2D>(ci);2147if (n2d) {2148n2d->_edit_set_rotation(object_transform.get_rotation());2149n2d->set_skew(object_transform.get_skew());2150}2151}21522153if (using_temp_pivot) {2154Point2 ci_origin = ci->_edit_get_transform().get_origin();2155ci->_edit_set_position(ci_origin + (ci_origin - temp_pivot) * ((scale - original_scale) / original_scale));2156}2157}21582159return true;2160}21612162// Confirm resize2163if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {2164_commit_drag();2165return true;2166}21672168// Cancel a drag2169if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {2170_restore_canvas_item_state(drag_selection);2171_reset_drag();2172viewport->queue_redraw();2173return true;2174}2175}2176return false;2177}21782179bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {2180Ref<InputEventMouseButton> b = p_event;2181Ref<InputEventMouseMotion> m = p_event;2182Ref<InputEventKey> k = p_event;21832184if (drag_type == DRAG_NONE) {2185//Start moving the nodes2186if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {2187if ((tool == TOOL_SELECT && b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {2188bool has_locked_items = false;2189List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);21902191if (selection.size() > 0) {2192drag_selection.clear();2193for (CanvasItem *E : selection) {2194if (_is_node_movable(E, true)) {2195drag_selection.push_back(E);2196}2197}21982199drag_type = DRAG_MOVE;22002201CanvasItem *ci = selection.front()->get();2202Transform2D parent_xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();2203Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized();2204Transform2D simple_xform;2205if (use_local_space) {2206simple_xform = viewport->get_transform() * unscaled_transform;2207} else {2208Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());2209simple_xform = viewport->get_transform() * translation;2210}22112212if (show_transformation_gizmos) {2213Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);2214Rect2 x_handle_rect = Rect2(move_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2215if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2216drag_type = DRAG_MOVE_X;2217}2218Rect2 y_handle_rect = Rect2(-5 * EDSCALE, move_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2219if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2220drag_type = DRAG_MOVE_Y;2221}2222}22232224drag_from = transform.affine_inverse().xform(b->get_position());2225_save_canvas_item_state(drag_selection);22262227return true;2228} else {2229if (has_locked_items) {2230EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);2231}2232return has_locked_items;2233}2234}2235}2236}22372238if (drag_type == DRAG_MOVE || drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {2239// Move the nodes2240if (m.is_valid() && !drag_selection.is_empty()) {2241_restore_canvas_item_state(drag_selection, true);22422243drag_to = transform.affine_inverse().xform(m->get_position());2244Point2 previous_pos;2245if (drag_selection.size() == 1) {2246Transform2D parent_xform = drag_selection.front()->get()->get_screen_transform() * drag_selection.front()->get()->get_transform().affine_inverse();2247previous_pos = parent_xform.xform(drag_selection.front()->get()->_edit_get_position());2248} else {2249previous_pos = _get_encompassing_rect_from_list(drag_selection).position;2250}22512252Point2 drag_delta = drag_to - drag_from;2253if (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {2254const CanvasItem *selected = drag_selection.front()->get();2255Transform2D parent_xform = selected->get_screen_transform() * selected->get_transform().affine_inverse();2256Transform2D unscaled_transform = (transform * parent_xform * selected->_edit_get_transform()).orthonormalized();2257Transform2D simple_xform;2258if (use_local_space) {2259simple_xform = viewport->get_transform() * unscaled_transform;2260} else {2261simple_xform = viewport->get_transform();2262}22632264drag_delta = simple_xform.affine_inverse().basis_xform(drag_delta);2265if (drag_type == DRAG_MOVE_X) {2266drag_delta.y = 0;2267} else {2268drag_delta.x = 0;2269}2270drag_delta = simple_xform.basis_xform(drag_delta);2271}2272Point2 new_pos = snap_point(previous_pos + drag_delta, SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL | SNAP_NODE_PARENT | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES, 0, nullptr, drag_selection);22732274bool single_axis = m->is_shift_pressed();2275if (single_axis) {2276if (Math::abs(new_pos.x - previous_pos.x) > Math::abs(new_pos.y - previous_pos.y)) {2277new_pos.y = previous_pos.y;2278} else {2279new_pos.x = previous_pos.x;2280}2281}22822283for (CanvasItem *ci : drag_selection) {2284Transform2D parent_xform_inv = ci->get_transform() * ci->get_screen_transform().affine_inverse();2285ci->_edit_set_position(ci->_edit_get_position() + parent_xform_inv.basis_xform(new_pos - previous_pos));2286}2287return true;2288}22892290// Confirm the move (only if it was moved)2291if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {2292_commit_drag();2293return true;2294}22952296// Cancel a drag2297if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {2298_restore_canvas_item_state(drag_selection, true);2299snap_target[0] = SNAP_TARGET_NONE;2300snap_target[1] = SNAP_TARGET_NONE;2301_reset_drag();2302viewport->queue_redraw();2303return true;2304}2305}23062307// Move the canvas items with the arrow keys2308if (k.is_valid() && k->is_pressed() && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&2309(k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {2310if (!k->is_echo()) {2311// Start moving the canvas items with the keyboard, if they are movable2312List<CanvasItem *> selection = _get_edited_canvas_items();23132314drag_selection.clear();2315for (CanvasItem *item : selection) {2316if (_is_node_movable(item, true)) {2317drag_selection.push_back(item);2318}2319}23202321drag_type = DRAG_KEY_MOVE;2322drag_from = Vector2();2323drag_to = Vector2();2324_save_canvas_item_state(drag_selection, true);2325}23262327if (drag_selection.size() > 0) {2328_restore_canvas_item_state(drag_selection, true);23292330bool move_local_base = k->is_alt_pressed();2331bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed();23322333Vector2 dir;2334if (k->get_keycode() == Key::UP) {2335dir += Vector2(0, -1);2336} else if (k->get_keycode() == Key::DOWN) {2337dir += Vector2(0, 1);2338} else if (k->get_keycode() == Key::LEFT) {2339dir += Vector2(-1, 0);2340} else if (k->get_keycode() == Key::RIGHT) {2341dir += Vector2(1, 0);2342}2343if (k->is_shift_pressed()) {2344dir *= grid_step * Math::pow(2.0, grid_step_multiplier);2345}23462347drag_to += dir;2348if (k->is_shift_pressed()) {2349drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier));2350}23512352Point2 previous_pos;2353if (drag_selection.size() == 1) {2354Transform2D xform = drag_selection.front()->get()->get_global_transform_with_canvas() * drag_selection.front()->get()->get_transform().affine_inverse();2355previous_pos = xform.xform(drag_selection.front()->get()->_edit_get_position());2356} else {2357previous_pos = _get_encompassing_rect_from_list(drag_selection).position;2358}23592360Point2 new_pos;2361if (drag_selection.size() == 1) {2362Node2D *node_2d = Object::cast_to<Node2D>(drag_selection.front()->get());2363if (node_2d && move_local_base_rotated) {2364Transform2D m2;2365m2.rotate(node_2d->get_rotation());2366new_pos += m2.xform(drag_to);2367} else if (move_local_base) {2368new_pos += drag_to;2369} else {2370new_pos = previous_pos + (drag_to - drag_from);2371}2372} else {2373new_pos = previous_pos + (drag_to - drag_from);2374}23752376for (CanvasItem *ci : drag_selection) {2377Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();2378ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));2379}2380}2381return true;2382}23832384// Confirm canvas items move by arrow keys.2385if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&2386(k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {2387_commit_drag();2388return true;2389}23902391return (k.is_valid() && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)); // Accept the key event in any case2392}23932394bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {2395Ref<InputEventMouseButton> b = p_event;2396Ref<InputEventMouseMotion> m = p_event;2397Ref<InputEventKey> k = p_event;23982399if (drag_type == DRAG_NONE || (drag_type == DRAG_BOX_SELECTION && b.is_valid() && !b->is_pressed())) {2400if (b.is_valid() && b->is_pressed() &&2401((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed()) ||2402(b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) {2403// Popup the selection menu list2404Point2 click = transform.affine_inverse().xform(b->get_position());24052406_get_canvas_items_at_pos(click, selection_results, b->is_alt_pressed());24072408if (selection_results.size() == 1) {2409CanvasItem *item = selection_results[0].item;2410selection_results.clear();24112412_select_click_on_item(item, click, b->is_shift_pressed());24132414return true;2415} else if (!selection_results.is_empty()) {2416// Sorts items according the their z-index2417selection_results.sort();24182419NodePath root_path = get_tree()->get_edited_scene_root()->get_path();2420StringName root_name = root_path.get_name(root_path.get_name_count() - 1);2421int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));24222423for (int i = 0; i < selection_results.size(); i++) {2424CanvasItem *item = selection_results[i].item;24252426Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(item);2427String node_path = "/" + root_name + "/" + String(root_path.rel_path_to(item->get_path()));24282429int locked = 0;2430if (_is_node_locked(item)) {2431locked = 1;2432} else {2433Node *scene = EditorNode::get_singleton()->get_edited_scene();2434Node *node = item;24352436while (node && node != scene->get_parent()) {2437CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);2438if (ci_tmp && node->has_meta("_edit_group_")) {2439locked = 2;2440}2441node = node->get_parent();2442}2443}24442445String suffix;2446if (locked == 1) {2447suffix = " (" + TTR("Locked") + ")";2448} else if (locked == 2) {2449suffix = " (" + TTR("Grouped") + ")";2450}2451selection_menu->add_item((String)item->get_name() + suffix);2452selection_menu->set_item_icon(i, icon);2453selection_menu->set_item_icon_max_width(i, icon_max_width);2454selection_menu->set_item_metadata(i, node_path);2455selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);2456}24572458selection_results_menu = selection_results;2459selection_menu_additive_selection = b->is_shift_pressed();2460selection_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));2461selection_menu->reset_size();2462selection_menu->popup();2463return true;2464}2465}24662467if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {2468add_node_menu->clear();2469add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Add")), TTRC("Add Node Here..."), ADD_NODE);2470add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Instance")), TTRC("Instantiate Scene Here..."), ADD_INSTANCE);2471for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) {2472if (Object::cast_to<CanvasItem>(node)) {2473add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTRC("Paste Node(s) Here"), ADD_PASTE);2474break;2475}2476}2477for (Node *node : EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list()) {2478if (Object::cast_to<CanvasItem>(node)) {2479add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ToolMove")), TTRC("Move Node(s) Here"), ADD_MOVE);2480break;2481}2482}24832484// Context menu plugin receives paths of nodes under cursor. It's a complex operation, so perform it only when necessary.2485if (EditorContextMenuPluginManager::get_singleton()->has_plugins_for_slot(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR)) {2486selection_results.clear();2487_get_canvas_items_at_pos(transform.affine_inverse().xform(viewport->get_local_mouse_position()), selection_results, true);24882489PackedStringArray paths;2490paths.resize(selection_results.size());2491String *paths_write = paths.ptrw();24922493for (int i = 0; i < paths.size(); i++) {2494paths_write[i] = String(selection_results[i].item->get_path());2495}2496EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(add_node_menu, EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, paths);2497}24982499add_node_menu->reset_size();2500add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));2501add_node_menu->popup();2502node_create_position = transform.affine_inverse().xform(b->get_position());2503return true;2504}25052506Point2 click;2507bool can_select = b.is_valid() && b->get_button_index() == MouseButton::LEFT && !panner->is_panning() && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE);2508if (can_select) {2509click = transform.affine_inverse().xform(b->get_position());2510// Allow selecting on release when performed very small box selection (necessary when Shift is pressed, see below).2511can_select = b->is_pressed() || (drag_type == DRAG_BOX_SELECTION && click.distance_to(drag_from) <= DRAG_THRESHOLD);2512}25132514if (can_select) {2515// Single item selection.2516Node *scene = EditorNode::get_singleton()->get_edited_scene();2517if (!scene) {2518return true;2519}25202521// Find the item to select.2522CanvasItem *ci = nullptr;25232524Vector<_SelectResult> selection = Vector<_SelectResult>();2525// Retrieve the canvas items.2526_get_canvas_items_at_pos(click, selection);2527if (!selection.is_empty()) {2528ci = selection[0].item;2529}25302531// Shift also allows forcing box selection when item was clicked.2532if (!ci || (b->is_shift_pressed() && b->is_pressed())) {2533// Start a box selection.2534if (!b->is_shift_pressed()) {2535// Clear the selection if not additive.2536editor_selection->clear();2537viewport->queue_redraw();2538selected_from_canvas = true;2539};25402541if (b->is_pressed()) {2542drag_from = click;2543drag_type = DRAG_BOX_SELECTION;2544box_selecting_to = drag_from;2545return true;2546}2547} else {2548bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed());2549// Start dragging.2550if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE) && b->is_pressed()) {2551// Drag the node(s) if requested.2552drag_start_origin = click;2553drag_type = DRAG_QUEUED;2554} else if (!b->is_pressed()) {2555_reset_drag();2556}2557// Select the item.2558return true;2559}2560}2561}25622563if (drag_type == DRAG_QUEUED) {2564if (b.is_valid() && !b->is_pressed()) {2565_reset_drag();2566return true;2567}2568if (m.is_valid()) {2569Point2 click = transform.affine_inverse().xform(m->get_position());2570bool movement_threshold_passed = drag_start_origin.distance_to(click) > (8 * MAX(1, EDSCALE)) / zoom;2571if (m.is_valid() && movement_threshold_passed) {2572List<CanvasItem *> selection2 = _get_edited_canvas_items();25732574drag_selection.clear();2575for (CanvasItem *E : selection2) {2576if (_is_node_movable(E, true)) {2577drag_selection.push_back(E);2578}2579}25802581if (selection2.size() > 0) {2582drag_type = DRAG_MOVE;2583drag_from = drag_start_origin;2584_save_canvas_item_state(drag_selection);2585}2586return true;2587}2588}2589}25902591if (drag_type == DRAG_BOX_SELECTION) {2592if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {2593// Confirms box selection.2594Node *scene = EditorNode::get_singleton()->get_edited_scene();2595if (scene) {2596List<CanvasItem *> selitems;25972598Point2 bsfrom = drag_from;2599Point2 bsto = box_selecting_to;2600if (bsfrom.x > bsto.x) {2601SWAP(bsfrom.x, bsto.x);2602}2603if (bsfrom.y > bsto.y) {2604SWAP(bsfrom.y, bsto.y);2605}26062607_find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems);2608if (selitems.size() == 1 && editor_selection->get_selection().is_empty()) {2609EditorNode::get_singleton()->push_item(selitems.front()->get());2610}2611for (CanvasItem *E : selitems) {2612editor_selection->add_node(E);2613}2614}26152616_reset_drag();2617viewport->queue_redraw();2618return true;2619}26202621if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {2622// Cancel box selection.2623_reset_drag();2624viewport->queue_redraw();2625return true;2626}26272628if (m.is_valid()) {2629// Update box selection.2630box_selecting_to = transform.affine_inverse().xform(m->get_position());2631viewport->queue_redraw();2632return true;2633}2634}26352636if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true) && drag_type == DRAG_NONE) {2637// Unselect everything2638editor_selection->clear();2639viewport->queue_redraw();2640}2641return false;2642}26432644bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) {2645if (tool != TOOL_RULER) {2646ruler_tool_active = false;2647return false;2648}26492650Ref<InputEventMouseButton> b = p_event;2651Ref<InputEventMouseMotion> m = p_event;26522653Point2 previous_origin = ruler_tool_origin;2654if (!ruler_tool_active) {2655ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset);2656}26572658if (ruler_tool_active && b.is_valid() && b->get_button_index() == MouseButton::RIGHT) {2659ruler_tool_active = false;2660viewport->queue_redraw();2661return true;2662}26632664if (b.is_valid() && b->get_button_index() == MouseButton::LEFT) {2665if (b->is_pressed()) {2666ruler_tool_active = true;2667} else {2668ruler_tool_active = false;2669}26702671viewport->queue_redraw();2672return true;2673}26742675if (m.is_valid() && (ruler_tool_active || (grid_snap_active && previous_origin != ruler_tool_origin))) {2676viewport->queue_redraw();2677return true;2678}26792680return false;2681}26822683bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {2684Ref<InputEventMouseMotion> m = p_event;2685if (m.is_valid()) {2686Point2 click = transform.affine_inverse().xform(m->get_position());26872688// Checks if the hovered items changed, redraw the viewport if so2689Vector<_SelectResult> hovering_results_items;2690_get_canvas_items_at_pos(click, hovering_results_items);2691hovering_results_items.sort();26922693// Compute the nodes names and icon position2694Vector<_HoverResult> hovering_results_tmp;2695for (int i = 0; i < hovering_results_items.size(); i++) {2696CanvasItem *ci = hovering_results_items[i].item;26972698if (ci->_edit_use_rect()) {2699continue;2700}27012702_HoverResult hover_result;2703hover_result.position = ci->get_screen_transform().get_origin();2704hover_result.icon = EditorNode::get_singleton()->get_object_icon(ci);2705hover_result.name = ci->get_name();27062707hovering_results_tmp.push_back(hover_result);2708}27092710// Check if changed, if so, redraw.2711bool changed = false;2712if (hovering_results_tmp.size() == hovering_results.size()) {2713for (int i = 0; i < hovering_results_tmp.size(); i++) {2714_HoverResult a = hovering_results_tmp[i];2715_HoverResult b = hovering_results[i];2716if (a.icon != b.icon || a.name != b.name || a.position != b.position) {2717changed = true;2718break;2719}2720}2721} else {2722changed = true;2723}27242725if (changed) {2726hovering_results = hovering_results_tmp;2727viewport->queue_redraw();2728}27292730return true;2731}27322733return false;2734}27352736void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {2737bool accepted = false;27382739Ref<InputEventMouseButton> mb = p_event;2740bool release_lmb = (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT); // Required to properly release some stuff (e.g. selection box) while panning.27412742if (simple_panning || !pan_pressed || release_lmb) {2743accepted = true;2744if (_gui_input_rulers_and_guides(p_event)) {2745// print_line("Rulers and guides");2746} else if (EditorNode::get_singleton()->get_editor_plugins_over()->forward_gui_input(p_event)) {2747// print_line("Plugin");2748} else if (_gui_input_open_scene_on_double_click(p_event)) {2749// print_line("Open scene on double click");2750} else if (_gui_input_scale(p_event)) {2751// print_line("Set scale");2752} else if (_gui_input_pivot(p_event)) {2753// print_line("Set pivot");2754} else if (_gui_input_resize(p_event)) {2755// print_line("Resize");2756} else if (_gui_input_rotate(p_event)) {2757// print_line("Rotate");2758} else if (_gui_input_move(p_event)) {2759// print_line("Move");2760} else if (_gui_input_anchors(p_event)) {2761// print_line("Anchors");2762} else if (_gui_input_ruler_tool(p_event)) {2763// print_line("Measure");2764} else if (_gui_input_select(p_event)) {2765// print_line("Selection");2766} else {2767// print_line("Not accepted");2768accepted = false;2769}2770}27712772accepted = (_gui_input_zoom_or_pan(p_event, accepted) || accepted);27732774if (accepted) {2775accept_event();2776}27772778// Handles the mouse hovering2779_gui_input_hover(p_event);27802781if (mb.is_valid()) {2782// Update the default cursor.2783_update_cursor();2784}27852786// Grab focus2787if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {2788callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(false);2789}2790}27912792void CanvasItemEditor::_commit_drag() {2793if (!drag_selection.is_empty()) {2794switch (drag_type) {2795// Confirm the pivot move.2796case DRAG_PIVOT: {2797_commit_canvas_item_state(2798drag_selection,2799vformat(2800TTR("Set CanvasItem \"%s\" Pivot Offset to (%d, %d)"),2801drag_selection.front()->get()->get_name(),2802drag_selection.front()->get()->_edit_get_pivot().x,2803drag_selection.front()->get()->_edit_get_pivot().y));2804} break;28052806// Confirm the node rotation.2807case DRAG_ROTATE: {2808if (drag_selection.size() != 1) {2809_commit_canvas_item_state(2810drag_selection,2811vformat(TTR("Rotate %d CanvasItems"), drag_selection.size()),2812true);2813} else {2814_commit_canvas_item_state(2815drag_selection,2816vformat(TTR("Rotate CanvasItem \"%s\" to %d degrees"),2817drag_selection.front()->get()->get_name(),2818Math::rad_to_deg(drag_selection.front()->get()->_edit_get_rotation())),2819true);2820}28212822if (key_auto_insert_button->is_pressed()) {2823_insert_animation_keys(false, true, false, true);2824}2825} break;28262827// Confirm new anchor position.2828case DRAG_ANCHOR_TOP_LEFT:2829case DRAG_ANCHOR_TOP_RIGHT:2830case DRAG_ANCHOR_BOTTOM_RIGHT:2831case DRAG_ANCHOR_BOTTOM_LEFT:2832case DRAG_ANCHOR_ALL: {2833_commit_canvas_item_state(2834drag_selection,2835vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection.front()->get()->get_name()));2836snap_target[0] = SNAP_TARGET_NONE;2837snap_target[1] = SNAP_TARGET_NONE;2838} break;28392840// Confirm resize.2841case DRAG_LEFT:2842case DRAG_RIGHT:2843case DRAG_TOP:2844case DRAG_BOTTOM:2845case DRAG_TOP_LEFT:2846case DRAG_TOP_RIGHT:2847case DRAG_BOTTOM_LEFT:2848case DRAG_BOTTOM_RIGHT: {2849const Node2D *node2d = Object::cast_to<Node2D>(drag_selection.front()->get());2850if (node2d) {2851// Extends from Node2D.2852// Node2D doesn't have an actual stored rect size, unlike Controls.2853_commit_canvas_item_state(2854drag_selection,2855vformat(2856TTR("Scale Node2D \"%s\" to (%s, %s)"),2857drag_selection.front()->get()->get_name(),2858Math::snapped(drag_selection.front()->get()->_edit_get_scale().x, 0.01),2859Math::snapped(drag_selection.front()->get()->_edit_get_scale().y, 0.01)),2860true);2861} else {2862// Extends from Control.2863_commit_canvas_item_state(2864drag_selection,2865vformat(2866TTR("Resize Control \"%s\" to (%d, %d)"),2867drag_selection.front()->get()->get_name(),2868drag_selection.front()->get()->_edit_get_rect().size.x,2869drag_selection.front()->get()->_edit_get_rect().size.y),2870true);2871}28722873if (key_auto_insert_button->is_pressed()) {2874_insert_animation_keys(false, false, true, true);2875}28762877snap_target[0] = SNAP_TARGET_NONE;2878snap_target[1] = SNAP_TARGET_NONE;2879} break;28802881// Confirm resize.2882case DRAG_SCALE_BOTH:2883case DRAG_SCALE_X:2884case DRAG_SCALE_Y: {2885if (drag_selection.size() != 1) {2886_commit_canvas_item_state(2887drag_selection,2888vformat(TTR("Scale %d CanvasItems"), drag_selection.size()),2889true);2890} else {2891_commit_canvas_item_state(2892drag_selection,2893vformat(TTR("Scale CanvasItem \"%s\" to (%s, %s)"),2894drag_selection.front()->get()->get_name(),2895Math::snapped(drag_selection.front()->get()->_edit_get_scale().x, 0.01),2896Math::snapped(drag_selection.front()->get()->_edit_get_scale().y, 0.01)),2897true);2898}2899if (key_auto_insert_button->is_pressed()) {2900_insert_animation_keys(false, false, true, true);2901}2902} break;29032904// Confirm the canvas items move.2905case DRAG_MOVE:2906case DRAG_MOVE_X:2907case DRAG_MOVE_Y: {2908if (transform.affine_inverse().xform(get_viewport()->get_mouse_position()) != drag_from) {2909if (drag_selection.size() != 1) {2910_commit_canvas_item_state(2911drag_selection,2912vformat(TTR("Move %d CanvasItems"), drag_selection.size()),2913true);2914} else {2915_commit_canvas_item_state(2916drag_selection,2917vformat(2918TTR("Move CanvasItem \"%s\" to (%d, %d)"),2919drag_selection.front()->get()->get_name(),2920drag_selection.front()->get()->_edit_get_position().x,2921drag_selection.front()->get()->_edit_get_position().y),2922true);2923}2924}29252926if (key_auto_insert_button->is_pressed()) {2927_insert_animation_keys(true, false, false, true);2928}29292930// Make sure smart snapping lines disappear.2931snap_target[0] = SNAP_TARGET_NONE;2932snap_target[1] = SNAP_TARGET_NONE;2933} break;29342935// Confirm the canvas items move by arrow keys.2936case DRAG_KEY_MOVE: {2937if (tool != TOOL_SELECT && tool != TOOL_MOVE) {2938return;2939}29402941if (drag_selection.size() > 1) {2942_commit_canvas_item_state(2943drag_selection,2944vformat(TTR("Move %d CanvasItems"), drag_selection.size()),2945true);2946} else if (drag_selection.size() == 1) {2947_commit_canvas_item_state(2948drag_selection,2949vformat(TTR("Move CanvasItem \"%s\" to (%d, %d)"),2950drag_selection.front()->get()->get_name(),2951drag_selection.front()->get()->_edit_get_position().x,2952drag_selection.front()->get()->_edit_get_position().y),2953true);2954}2955} break;29562957default:2958break;2959}2960}29612962_reset_drag();2963viewport->queue_redraw();2964_update_cursor();2965}29662967void CanvasItemEditor::_update_cursor() {2968if (cursor_shape_override != CURSOR_ARROW) {2969set_default_cursor_shape(cursor_shape_override);2970return;2971}29722973// Choose the correct default cursor.2974CursorShape c = CURSOR_ARROW;2975switch (tool) {2976case TOOL_MOVE:2977c = CURSOR_MOVE;2978break;2979case TOOL_EDIT_PIVOT:2980c = CURSOR_CROSS;2981break;2982case TOOL_PAN:2983c = CURSOR_DRAG;2984break;2985case TOOL_RULER:2986c = CURSOR_CROSS;2987break;2988default:2989break;2990}2991if (pan_pressed) {2992c = CURSOR_DRAG;2993}2994set_default_cursor_shape(c);2995}29962997void CanvasItemEditor::_update_lock_and_group_button() {2998bool all_locked = true;2999bool all_group = true;3000bool has_canvas_item = false;3001const List<Node *> &selection = editor_selection->get_top_selected_node_list();3002if (selection.is_empty()) {3003all_locked = false;3004all_group = false;3005} else {3006for (Node *E : selection) {3007CanvasItem *item = Object::cast_to<CanvasItem>(E);3008if (item) {3009if (all_locked && !item->has_meta("_edit_lock_")) {3010all_locked = false;3011}3012if (all_group && !item->has_meta("_edit_group_")) {3013all_group = false;3014}3015has_canvas_item = true;3016}3017if (!all_locked && !all_group) {3018break;3019}3020}3021}30223023all_locked = all_locked && has_canvas_item;3024all_group = all_group && has_canvas_item;30253026lock_button->set_visible(!all_locked);3027lock_button->set_disabled(!has_canvas_item);3028unlock_button->set_visible(all_locked);3029unlock_button->set_disabled(!has_canvas_item);3030group_button->set_visible(!all_group);3031group_button->set_disabled(!has_canvas_item);3032ungroup_button->set_visible(all_group);3033ungroup_button->set_disabled(!has_canvas_item);3034}30353036void CanvasItemEditor::set_cursor_shape_override(CursorShape p_shape) {3037if (cursor_shape_override == p_shape) {3038return;3039}3040cursor_shape_override = p_shape;3041_update_cursor();3042}30433044Control::CursorShape CanvasItemEditor::get_cursor_shape(const Point2 &p_pos) const {3045// Compute an eventual rotation of the cursor3046const CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE };3047int rotation_array_index = 0;30483049List<CanvasItem *> selection = _get_edited_canvas_items();3050if (selection.size() == 1) {3051const double angle = Math::fposmod((double)selection.front()->get()->get_global_transform_with_canvas().get_rotation(), Math::PI);3052if (angle > Math::PI * 7.0 / 8.0) {3053rotation_array_index = 0;3054} else if (angle > Math::PI * 5.0 / 8.0) {3055rotation_array_index = 1;3056} else if (angle > Math::PI * 3.0 / 8.0) {3057rotation_array_index = 2;3058} else if (angle > Math::PI * 1.0 / 8.0) {3059rotation_array_index = 3;3060} else {3061rotation_array_index = 0;3062}3063}30643065// Choose the correct cursor3066CursorShape c = get_default_cursor_shape();3067switch (drag_type) {3068case DRAG_LEFT:3069case DRAG_RIGHT:3070c = rotation_array[rotation_array_index];3071break;3072case DRAG_V_GUIDE:3073c = CURSOR_HSIZE;3074break;3075case DRAG_TOP:3076case DRAG_BOTTOM:3077c = rotation_array[(rotation_array_index + 2) % 4];3078break;3079case DRAG_H_GUIDE:3080c = CURSOR_VSIZE;3081break;3082case DRAG_TOP_LEFT:3083case DRAG_BOTTOM_RIGHT:3084c = rotation_array[(rotation_array_index + 3) % 4];3085break;3086case DRAG_DOUBLE_GUIDE:3087c = CURSOR_FDIAGSIZE;3088break;3089case DRAG_TOP_RIGHT:3090case DRAG_BOTTOM_LEFT:3091c = rotation_array[(rotation_array_index + 1) % 4];3092break;3093case DRAG_MOVE:3094c = CURSOR_MOVE;3095break;3096default:3097break;3098}30993100if (is_hovering_h_guide) {3101c = CURSOR_VSIZE;3102} else if (is_hovering_v_guide) {3103c = CURSOR_HSIZE;3104}31053106if (pan_pressed) {3107c = CURSOR_DRAG;3108}3109return c;3110}31113112void CanvasItemEditor::_draw_text_at_position(Point2 p_position, const String &p_string, Side p_side) {3113Color color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3114color.a = 0.8;3115Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));3116int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));3117Size2 text_size = font->get_string_size(p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3118switch (p_side) {3119case SIDE_LEFT:3120p_position += Vector2(-text_size.x - 5, text_size.y / 2);3121break;3122case SIDE_TOP:3123p_position += Vector2(-text_size.x / 2, -5);3124break;3125case SIDE_RIGHT:3126p_position += Vector2(5, text_size.y / 2);3127break;3128case SIDE_BOTTOM:3129p_position += Vector2(-text_size.x / 2, text_size.y + 5);3130break;3131}3132viewport->draw_string(font, p_position, p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);3133}31343135void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position, Side p_side) {3136String str = TranslationServer::get_singleton()->format_number(vformat("%d " + TTR("px"), p_value), _get_locale());3137if (p_value != 0) {3138_draw_text_at_position(p_position, str, p_side);3139}3140}31413142void CanvasItemEditor::_draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side) {3143const String &lang = _get_locale();3144String str = TranslationServer::get_singleton()->format_number(vformat("%.1f ", p_value * 100.0), lang) + TranslationServer::get_singleton()->get_percent_sign(lang);3145if (p_value != 0) {3146_draw_text_at_position(p_position, str, p_side);3147}3148}31493150void CanvasItemEditor::_draw_focus() {3151// Draw the focus around the base viewport3152if (viewport->has_focus()) {3153get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size()));3154}3155}31563157void CanvasItemEditor::_draw_guides() {3158Color guide_color = EDITOR_GET("editors/2d/guides_color");3159Transform2D xform = viewport_scrollable->get_transform() * transform;31603161// Guides already there.3162if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {3163Array vguides = scene->get_meta("_edit_vertical_guides_", Array());3164for (int i = 0; i < vguides.size(); i++) {3165if (drag_type == DRAG_V_GUIDE && i == dragged_guide_index) {3166continue;3167}3168real_t x = xform.xform(Point2(vguides[i], 0)).x;3169viewport->draw_line(Point2(x, 0), Point2(x, viewport->get_size().y), guide_color, Math::round(EDSCALE));3170}31713172Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());3173for (int i = 0; i < hguides.size(); i++) {3174if (drag_type == DRAG_H_GUIDE && i == dragged_guide_index) {3175continue;3176}3177real_t y = xform.xform(Point2(0, hguides[i])).y;3178viewport->draw_line(Point2(0, y), Point2(viewport->get_size().x, y), guide_color, Math::round(EDSCALE));3179}3180}31813182// Dragged guide.3183Color text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3184Color outline_color = text_color.inverted();3185const float outline_size = 2;3186const String &lang = _get_locale();3187if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) {3188String str = TranslationServer::get_singleton()->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x)), lang);3189Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3190int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3191Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3192viewport->draw_string_outline(font, Point2(dragged_guide_pos.x + 10, ruler_width_scaled + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3193viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, ruler_width_scaled + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);3194viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE));3195}3196if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) {3197String str = TranslationServer::get_singleton()->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y)), lang);3198Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3199int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3200Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3201viewport->draw_string_outline(font, Point2(ruler_width_scaled + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3202viewport->draw_string(font, Point2(ruler_width_scaled + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);3203viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE));3204}3205}32063207void CanvasItemEditor::_draw_smart_snapping() {3208Color line_color = EDITOR_GET("editors/2d/smart_snapping_line_color");3209if (snap_target[0] != SNAP_TARGET_NONE && snap_target[0] != SNAP_TARGET_GRID) {3210viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);3211viewport->draw_line(Point2(0, -1.0e+10F), Point2(0, 1.0e+10F), line_color);3212viewport->draw_set_transform_matrix(viewport->get_transform());3213}3214if (snap_target[1] != SNAP_TARGET_NONE && snap_target[1] != SNAP_TARGET_GRID) {3215viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);3216viewport->draw_line(Point2(-1.0e+10F, 0), Point2(1.0e+10F, 0), line_color);3217viewport->draw_set_transform_matrix(viewport->get_transform());3218}3219}32203221void CanvasItemEditor::_draw_rulers() {3222Color bg_color = get_theme_color(SNAME("ruler_color"), EditorStringName(Editor));3223Color graduation_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)).lerp(bg_color, 0.5);3224Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3225font_color.a = 0.9;3226Ref<Font> font = get_theme_font(SNAME("rulers"), EditorStringName(EditorFonts));3227real_t ruler_tick_scale = ruler_width_scaled / 15.0;3228const String lang = _get_locale();32293230// The rule transform3231Transform2D ruler_transform;3232if (grid_snap_active || _is_grid_visible()) {3233List<CanvasItem *> selection = _get_edited_canvas_items();3234if (snap_relative && selection.size() > 0) {3235ruler_transform.translate_local(_get_encompassing_rect_from_list(selection).position);3236ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));3237} else {3238ruler_transform.translate_local(grid_offset);3239ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));3240}3241while ((transform * ruler_transform).get_scale().x < 50.0 * ruler_tick_scale || (transform * ruler_transform).get_scale().y < 50.0 * ruler_tick_scale) {3242ruler_transform.scale_basis(Point2(2, 2));3243}3244} else {3245real_t basic_rule = 100;3246for (int i = 0; basic_rule * zoom > 100 * ruler_tick_scale; i++) {3247basic_rule /= (i % 2) ? 5.0 : 2.0;3248}3249for (int i = 0; basic_rule * zoom < 60 * ruler_tick_scale; i++) {3250basic_rule *= (i % 2) ? 2.0 : 5.0;3251}3252ruler_transform.scale(Size2(basic_rule, basic_rule));3253}32543255// Subdivisions3256int major_subdivision = 2;3257Transform2D major_subdivide;3258major_subdivide.scale(Size2(1.0 / major_subdivision, 1.0 / major_subdivision));32593260int minor_subdivision = 5;3261Transform2D minor_subdivide;3262minor_subdivide.scale(Size2(1.0 / minor_subdivision, 1.0 / minor_subdivision));32633264// First and last graduations to draw (in the ruler space)3265Point2 first = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Point2(ruler_width_scaled, ruler_width_scaled));3266Point2 last = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(viewport->get_size());32673268// Draw top ruler3269viewport->draw_rect(Rect2(Point2(ruler_width_scaled, 0), Size2(viewport->get_size().x, ruler_width_scaled)), bg_color);3270for (int i = Math::ceil(first.x); i < last.x; i++) {3271Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).round();3272if (i % (major_subdivision * minor_subdivision) == 0) {3273viewport->draw_line(Point2(position.x, 0), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3274real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x;3275const String &formatted = TranslationServer::get_singleton()->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val), lang);3276viewport->draw_string(font, Point2(position.x + MAX(Math::round(ruler_font_size / 8.0), 2), font->get_ascent(ruler_font_size) + Math::round(EDSCALE)), formatted, HORIZONTAL_ALIGNMENT_LEFT, -1, ruler_font_size, font_color);3277} else {3278if (i % minor_subdivision == 0) {3279viewport->draw_line(Point2(position.x, ruler_width_scaled * 0.33), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3280} else {3281viewport->draw_line(Point2(position.x, ruler_width_scaled * 0.75), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3282}3283}3284}32853286// Draw left ruler3287viewport->draw_rect(Rect2(Point2(0, ruler_width_scaled), Size2(ruler_width_scaled, viewport->get_size().y)), bg_color);3288for (int i = Math::ceil(first.y); i < last.y; i++) {3289Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).round();3290if (i % (major_subdivision * minor_subdivision) == 0) {3291viewport->draw_line(Point2(0, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3292real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).y;32933294Transform2D text_xform = Transform2D(-Math::PI / 2.0, Point2(font->get_ascent(ruler_font_size) + Math::round(EDSCALE), position.y - 2));3295viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform);3296const String &formatted = TranslationServer::get_singleton()->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val), lang);3297viewport->draw_string(font, Point2(), formatted, HORIZONTAL_ALIGNMENT_LEFT, -1, ruler_font_size, font_color);3298viewport->draw_set_transform_matrix(viewport->get_transform());32993300} else {3301if (i % minor_subdivision == 0) {3302viewport->draw_line(Point2(ruler_width_scaled * 0.33, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3303} else {3304viewport->draw_line(Point2(ruler_width_scaled * 0.75, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3305}3306}3307}33083309// Draw the top left corner3310viewport->draw_rect(Rect2(Point2(), Size2(ruler_width_scaled, ruler_width_scaled)), graduation_color);3311}33123313void CanvasItemEditor::_draw_grid() {3314if (_is_grid_visible()) {3315// Draw the grid3316Vector2 real_grid_offset;3317const List<CanvasItem *> selection = _get_edited_canvas_items();33183319if (snap_relative && selection.size() > 0) {3320const Vector2 topleft = _get_encompassing_rect_from_list(selection).position;3321real_grid_offset.x = std::fmod(topleft.x, grid_step.x * (real_t)Math::pow(2.0, grid_step_multiplier));3322real_grid_offset.y = std::fmod(topleft.y, grid_step.y * (real_t)Math::pow(2.0, grid_step_multiplier));3323} else {3324real_grid_offset = grid_offset;3325}33263327// Draw a "primary" line every several lines to make measurements easier.3328// The step is configurable in the Configure Snap dialog.3329const Color secondary_grid_color = EDITOR_GET("editors/2d/grid_color");3330const Color primary_grid_color =3331Color(secondary_grid_color.r, secondary_grid_color.g, secondary_grid_color.b, secondary_grid_color.a * 2.5);33323333const Size2 viewport_size = viewport->get_size();3334const Transform2D xform = transform.affine_inverse();3335int last_cell = 0;33363337if (grid_step.x != 0) {3338for (int i = 0; i < viewport_size.width; i++) {3339const int cell =3340Math::fast_ftoi(Math::floor((xform.xform(Vector2(i, 0)).x - real_grid_offset.x) / (grid_step.x * Math::pow(2.0, grid_step_multiplier))));33413342if (i == 0) {3343last_cell = cell;3344}33453346if (last_cell != cell) {3347Color grid_color;3348if (primary_grid_step.x <= 1) {3349grid_color = secondary_grid_color;3350} else {3351grid_color = cell % primary_grid_step.x == 0 ? primary_grid_color : secondary_grid_color;3352}33533354viewport->draw_line(Point2(i, 0), Point2(i, viewport_size.height), grid_color, Math::round(EDSCALE));3355}3356last_cell = cell;3357}3358}33593360if (grid_step.y != 0) {3361for (int i = 0; i < viewport_size.height; i++) {3362const int cell =3363Math::fast_ftoi(Math::floor((xform.xform(Vector2(0, i)).y - real_grid_offset.y) / (grid_step.y * Math::pow(2.0, grid_step_multiplier))));33643365if (i == 0) {3366last_cell = cell;3367}33683369if (last_cell != cell) {3370Color grid_color;3371if (primary_grid_step.y <= 1) {3372grid_color = secondary_grid_color;3373} else {3374grid_color = cell % primary_grid_step.y == 0 ? primary_grid_color : secondary_grid_color;3375}33763377viewport->draw_line(Point2(0, i), Point2(viewport_size.width, i), grid_color, Math::round(EDSCALE));3378}3379last_cell = cell;3380}3381}3382}3383}33843385void CanvasItemEditor::_draw_ruler_tool() {3386if (tool != TOOL_RULER) {3387return;3388}33893390const Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));3391if (ruler_tool_active) {3392const String &lang = _get_locale();3393const TranslationServer *ts = TranslationServer::get_singleton();33943395Color ruler_primary_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));3396Color ruler_secondary_color = ruler_primary_color;3397ruler_secondary_color.a = 0.5;33983399Point2 begin = (ruler_tool_origin - view_offset) * zoom;3400Point2 end = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom - view_offset * zoom;3401Point2 corner = Point2(begin.x, end.y);3402Vector2 length_vector = (begin - end).abs() / zoom;34033404const real_t horizontal_angle_rad = length_vector.angle();3405const real_t vertical_angle_rad = Math::PI / 2.0 - horizontal_angle_rad;34063407Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3408int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3409Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3410Color font_secondary_color = font_color;3411font_secondary_color.set_v(font_secondary_color.get_v() > 0.5 ? 0.7 : 0.3);3412Color outline_color = font_color.inverted();3413float text_height = font->get_height(font_size);34143415const float outline_size = 4;3416const float text_width = 76;3417const float angle_text_width = 54;34183419Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);3420text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);3421text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);34223423// Draw lines.3424viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3));34253426bool draw_secondary_lines = !(Math::is_equal_approx(begin.y, corner.y) || Math::is_equal_approx(end.x, corner.x));3427if (draw_secondary_lines) {3428viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE));3429viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE));34303431// Angle arcs.3432int arc_point_count = 8;3433real_t arc_radius_max_length_percent = 0.1;3434real_t ruler_length = length_vector.length() * zoom;3435real_t arc_max_radius = 50.0;3436real_t arc_line_width = 2.0;34373438const Vector2 end_to_begin = (end - begin);34393440real_t arc_1_start_angle = end_to_begin.x < 03441? (end_to_begin.y < 0 ? 3.0 * Math::PI / 2.0 - vertical_angle_rad : Math::PI / 2.0)3442: (end_to_begin.y < 0 ? 3.0 * Math::PI / 2.0 : Math::PI / 2.0 - vertical_angle_rad);3443real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad;3444// Constrain arc to triangle height & max size.3445real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, Math::abs(end_to_begin.y)), arc_max_radius);34463447real_t arc_2_start_angle = end_to_begin.x < 03448? (end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad)3449: (end_to_begin.y < 0 ? Math::PI - horizontal_angle_rad : Math::PI);3450real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad;3451// Constrain arc to triangle width & max size.3452real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, Math::abs(end_to_begin.x)), arc_max_radius);34533454viewport->draw_arc(begin, arc_1_radius, arc_1_start_angle, arc_1_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width));3455viewport->draw_arc(end, arc_2_radius, arc_2_start_angle, arc_2_end_angle, arc_point_count, ruler_primary_color, Math::round(EDSCALE * arc_line_width));3456}34573458// Draw text.3459if (begin.is_equal_approx(end)) {3460viewport->draw_string_outline(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3461viewport->draw_string(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);3462viewport->draw_texture(position_icon, (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);3463return;3464}34653466viewport->draw_string_outline(font, text_pos, ts->format_number(vformat("%.1f px", length_vector.length()), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3467viewport->draw_string(font, text_pos, ts->format_number(vformat("%.1f px", length_vector.length()), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);34683469if (draw_secondary_lines) {3470const int horizontal_angle = std::round(180 * horizontal_angle_rad / Math::PI);3471const int vertical_angle = std::round(180 * vertical_angle_rad / Math::PI);34723473Point2 text_pos2 = text_pos;3474text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);3475viewport->draw_string_outline(font, text_pos2, ts->format_number(vformat("%.1f px", length_vector.y), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3476viewport->draw_string(font, text_pos2, ts->format_number(vformat("%.1f px", length_vector.y), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);34773478Point2 v_angle_text_pos;3479v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);3480v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5);3481viewport->draw_string_outline(font, v_angle_text_pos, ts->format_number(vformat(U"%d°", vertical_angle), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3482viewport->draw_string(font, v_angle_text_pos, ts->format_number(vformat(U"%d°", vertical_angle), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);34833484text_pos2 = text_pos;3485text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2);3486viewport->draw_string_outline(font, text_pos2, ts->format_number(vformat("%.1f px", length_vector.x), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3487viewport->draw_string(font, text_pos2, ts->format_number(vformat("%.1f px", length_vector.x), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);34883489Point2 h_angle_text_pos;3490h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);3491if (begin.y < end.y) {3492h_angle_text_pos.y = end.y + text_height * 1.5;3493if (Math::abs(text_pos2.x - h_angle_text_pos.x) < text_width) {3494int height_multiplier = 1.5 + (int)grid_snap_active;3495h_angle_text_pos.y = MAX(text_pos.y + height_multiplier * text_height, MAX(end.y + text_height * 1.5, text_pos2.y + height_multiplier * text_height));3496}3497} else {3498h_angle_text_pos.y = end.y - text_height * 0.5;3499if (Math::abs(text_pos2.x - h_angle_text_pos.x) < text_width) {3500int height_multiplier = 1 + (int)grid_snap_active;3501h_angle_text_pos.y = MIN(text_pos.y - height_multiplier * text_height, MIN(end.y - text_height * 0.5, text_pos2.y - height_multiplier * text_height));3502}3503}3504viewport->draw_string_outline(font, h_angle_text_pos, ts->format_number(vformat(U"%d°", horizontal_angle), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3505viewport->draw_string(font, h_angle_text_pos, ts->format_number(vformat(U"%d°", horizontal_angle), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);3506}35073508if (grid_snap_active) {3509text_pos = (begin + end) / 2 + Vector2(-text_width / 2, text_height / 2);3510text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);3511text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2);35123513if (draw_secondary_lines) {3514viewport->draw_string_outline(font, text_pos, ts->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length()), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3515viewport->draw_string(font, text_pos, ts->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length()), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);35163517Point2 text_pos2 = text_pos;3518text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);3519viewport->draw_string_outline(font, text_pos2, ts->format_number(vformat("%d " + TTR("units"), std::round(length_vector.y / grid_step.y)), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3520viewport->draw_string(font, text_pos2, ts->format_number(vformat("%d " + TTR("units"), std::round(length_vector.y / grid_step.y)), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);35213522text_pos2 = text_pos;3523text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y + text_height / 2) : MAX(text_pos.y + text_height * 2, end.y + text_height / 2);3524viewport->draw_string_outline(font, text_pos2, ts->format_number(vformat("%d " + TTR("units"), std::round(length_vector.x / grid_step.x)), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3525viewport->draw_string(font, text_pos2, ts->format_number(vformat("%d " + TTR("units"), std::round(length_vector.x / grid_step.x)), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);3526} else {3527viewport->draw_string_outline(font, text_pos, ts->format_number(vformat("%d " + TTR("units"), std::round((length_vector / grid_step).length())), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3528viewport->draw_string(font, text_pos, ts->format_number(vformat("%d " + TTR("units"), std::round((length_vector / grid_step).length())), lang), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);3529}3530}3531} else {3532if (grid_snap_active) {3533viewport->draw_texture(position_icon, (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);3534}3535}3536}35373538void CanvasItemEditor::_draw_control_anchors(Control *control) {3539Transform2D xform = transform * control->get_screen_transform();3540RID ci = viewport->get_canvas_item();3541if (tool == TOOL_SELECT && !Object::cast_to<Container>(control->get_parent())) {3542// Compute the anchors3543real_t anchors_values[4];3544anchors_values[0] = control->get_anchor(SIDE_LEFT);3545anchors_values[1] = control->get_anchor(SIDE_TOP);3546anchors_values[2] = control->get_anchor(SIDE_RIGHT);3547anchors_values[3] = control->get_anchor(SIDE_BOTTOM);35483549Vector2 anchors_pos[4];3550for (int i = 0; i < 4; i++) {3551Vector2 value = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);3552anchors_pos[i] = xform.xform(_anchor_to_position(control, value));3553}35543555// Draw the anchors handles3556Rect2 anchor_rects[4];3557if (control->is_layout_rtl()) {3558anchor_rects[0] = Rect2(anchors_pos[0] - Vector2(0.0, anchor_handle->get_size().y), Point2(-anchor_handle->get_size().x, anchor_handle->get_size().y));3559anchor_rects[1] = Rect2(anchors_pos[1] - anchor_handle->get_size(), anchor_handle->get_size());3560anchor_rects[2] = Rect2(anchors_pos[2] - Vector2(anchor_handle->get_size().x, 0.0), Point2(anchor_handle->get_size().x, -anchor_handle->get_size().y));3561anchor_rects[3] = Rect2(anchors_pos[3], -anchor_handle->get_size());3562} else {3563anchor_rects[0] = Rect2(anchors_pos[0] - anchor_handle->get_size(), anchor_handle->get_size());3564anchor_rects[1] = Rect2(anchors_pos[1] - Vector2(0.0, anchor_handle->get_size().y), Point2(-anchor_handle->get_size().x, anchor_handle->get_size().y));3565anchor_rects[2] = Rect2(anchors_pos[2], -anchor_handle->get_size());3566anchor_rects[3] = Rect2(anchors_pos[3] - Vector2(anchor_handle->get_size().x, 0.0), Point2(anchor_handle->get_size().x, -anchor_handle->get_size().y));3567}35683569for (int i = 0; i < 4; i++) {3570anchor_handle->draw_rect(ci, anchor_rects[i]);3571}3572}3573}35743575void CanvasItemEditor::_draw_control_helpers(Control *control) {3576Transform2D xform = transform * control->get_screen_transform();3577if (tool == TOOL_SELECT && show_helpers && !Object::cast_to<Container>(control->get_parent())) {3578// Draw the helpers3579Color color_base = Color(0.8, 0.8, 0.8, 0.5);35803581// Compute the anchors3582real_t anchors_values[4];3583anchors_values[0] = control->get_anchor(SIDE_LEFT);3584anchors_values[1] = control->get_anchor(SIDE_TOP);3585anchors_values[2] = control->get_anchor(SIDE_RIGHT);3586anchors_values[3] = control->get_anchor(SIDE_BOTTOM);35873588Vector2 anchors[4];3589Vector2 anchors_pos[4];3590for (int i = 0; i < 4; i++) {3591anchors[i] = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);3592anchors_pos[i] = xform.xform(_anchor_to_position(control, anchors[i]));3593}35943595// Get which anchor is dragged3596int dragged_anchor = -1;3597switch (drag_type) {3598case DRAG_ANCHOR_ALL:3599case DRAG_ANCHOR_TOP_LEFT:3600dragged_anchor = 0;3601break;3602case DRAG_ANCHOR_TOP_RIGHT:3603dragged_anchor = 1;3604break;3605case DRAG_ANCHOR_BOTTOM_RIGHT:3606dragged_anchor = 2;3607break;3608case DRAG_ANCHOR_BOTTOM_LEFT:3609dragged_anchor = 3;3610break;3611default:3612break;3613}36143615if (dragged_anchor >= 0) {3616// Draw the 4 lines when dragged3617Color color_snapped = Color(0.64, 0.93, 0.67, 0.5);36183619Vector2 corners_pos[4];3620for (int i = 0; i < 4; i++) {3621corners_pos[i] = xform.xform(_anchor_to_position(control, Vector2((i == 0 || i == 3) ? ANCHOR_BEGIN : ANCHOR_END, (i <= 1) ? ANCHOR_BEGIN : ANCHOR_END)));3622}36233624Vector2 line_starts[4];3625Vector2 line_ends[4];3626for (int i = 0; i < 4; i++) {3627real_t anchor_val = (i >= 2) ? (real_t)ANCHOR_END - anchors_values[i] : anchors_values[i];3628line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val);3629line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val);3630bool anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0;3631viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1);3632}36333634// Display the percentages next to the lines3635real_t percent_val;3636percent_val = anchors_values[(dragged_anchor + 2) % 4] - anchors_values[dragged_anchor];3637percent_val = (dragged_anchor >= 2) ? -percent_val : percent_val;3638_draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 1) % 4]) / 2, (Side)((dragged_anchor + 1) % 4));36393640percent_val = anchors_values[(dragged_anchor + 3) % 4] - anchors_values[(dragged_anchor + 1) % 4];3641percent_val = ((dragged_anchor + 1) % 4 >= 2) ? -percent_val : percent_val;3642_draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 3) % 4]) / 2, (Side)(dragged_anchor));36433644percent_val = anchors_values[(dragged_anchor + 1) % 4];3645percent_val = ((dragged_anchor + 1) % 4 >= 2) ? (real_t)ANCHOR_END - percent_val : percent_val;3646_draw_percentage_at_position(percent_val, (line_starts[dragged_anchor] + anchors_pos[dragged_anchor]) / 2, (Side)(dragged_anchor));36473648percent_val = anchors_values[dragged_anchor];3649percent_val = (dragged_anchor >= 2) ? (real_t)ANCHOR_END - percent_val : percent_val;3650_draw_percentage_at_position(percent_val, (line_ends[(dragged_anchor + 1) % 4] + anchors_pos[dragged_anchor]) / 2, (Side)((dragged_anchor + 1) % 4));3651}36523653// Draw the margin values and the node width/height when dragging control side3654const real_t ratio = 0.33;3655Transform2D parent_transform = xform * control->get_transform().affine_inverse();3656real_t node_pos_in_parent[4];36573658Rect2 parent_rect = control->get_parent_anchorable_rect();36593660node_pos_in_parent[0] = control->get_anchor(SIDE_LEFT) * parent_rect.size.width + control->get_offset(SIDE_LEFT) + parent_rect.position.x;3661node_pos_in_parent[1] = control->get_anchor(SIDE_TOP) * parent_rect.size.height + control->get_offset(SIDE_TOP) + parent_rect.position.y;3662node_pos_in_parent[2] = control->get_anchor(SIDE_RIGHT) * parent_rect.size.width + control->get_offset(SIDE_RIGHT) + parent_rect.position.x;3663node_pos_in_parent[3] = control->get_anchor(SIDE_BOTTOM) * parent_rect.size.height + control->get_offset(SIDE_BOTTOM) + parent_rect.position.y;36643665Point2 start, end;3666switch (drag_type) {3667case DRAG_LEFT:3668case DRAG_TOP_LEFT:3669case DRAG_BOTTOM_LEFT:3670_draw_margin_at_position(control->get_size().width, parent_transform.xform(Vector2((node_pos_in_parent[0] + node_pos_in_parent[2]) / 2, node_pos_in_parent[3])) + Vector2(0, 5), SIDE_BOTTOM);3671[[fallthrough]];3672case DRAG_MOVE:3673start = Vector2(node_pos_in_parent[0], Math::lerp(node_pos_in_parent[1], node_pos_in_parent[3], ratio));3674end = start - Vector2(control->get_offset(SIDE_LEFT), 0);3675_draw_margin_at_position(control->get_offset(SIDE_LEFT), parent_transform.xform((start + end) / 2), SIDE_TOP);3676viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3677break;3678default:3679break;3680}3681switch (drag_type) {3682case DRAG_RIGHT:3683case DRAG_TOP_RIGHT:3684case DRAG_BOTTOM_RIGHT:3685_draw_margin_at_position(control->get_size().width, parent_transform.xform(Vector2((node_pos_in_parent[0] + node_pos_in_parent[2]) / 2, node_pos_in_parent[3])) + Vector2(0, 5), SIDE_BOTTOM);3686[[fallthrough]];3687case DRAG_MOVE:3688start = Vector2(node_pos_in_parent[2], Math::lerp(node_pos_in_parent[3], node_pos_in_parent[1], ratio));3689end = start - Vector2(control->get_offset(SIDE_RIGHT), 0);3690_draw_margin_at_position(control->get_offset(SIDE_RIGHT), parent_transform.xform((start + end) / 2), SIDE_BOTTOM);3691viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3692break;3693default:3694break;3695}3696switch (drag_type) {3697case DRAG_TOP:3698case DRAG_TOP_LEFT:3699case DRAG_TOP_RIGHT:3700_draw_margin_at_position(control->get_size().height, parent_transform.xform(Vector2(node_pos_in_parent[2], (node_pos_in_parent[1] + node_pos_in_parent[3]) / 2)) + Vector2(5, 0), SIDE_RIGHT);3701[[fallthrough]];3702case DRAG_MOVE:3703start = Vector2(Math::lerp(node_pos_in_parent[0], node_pos_in_parent[2], ratio), node_pos_in_parent[1]);3704end = start - Vector2(0, control->get_offset(SIDE_TOP));3705_draw_margin_at_position(control->get_offset(SIDE_TOP), parent_transform.xform((start + end) / 2), SIDE_LEFT);3706viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3707break;3708default:3709break;3710}3711switch (drag_type) {3712case DRAG_BOTTOM:3713case DRAG_BOTTOM_LEFT:3714case DRAG_BOTTOM_RIGHT:3715_draw_margin_at_position(control->get_size().height, parent_transform.xform(Vector2(node_pos_in_parent[2], (node_pos_in_parent[1] + node_pos_in_parent[3]) / 2) + Vector2(5, 0)), SIDE_RIGHT);3716[[fallthrough]];3717case DRAG_MOVE:3718start = Vector2(Math::lerp(node_pos_in_parent[2], node_pos_in_parent[0], ratio), node_pos_in_parent[3]);3719end = start - Vector2(0, control->get_offset(SIDE_BOTTOM));3720_draw_margin_at_position(control->get_offset(SIDE_BOTTOM), parent_transform.xform((start + end) / 2), SIDE_RIGHT);3721viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3722break;3723default:3724break;3725}37263727switch (drag_type) {3728//Draw the ghost rect if the node if rotated/scaled3729case DRAG_LEFT:3730case DRAG_TOP_LEFT:3731case DRAG_TOP:3732case DRAG_TOP_RIGHT:3733case DRAG_RIGHT:3734case DRAG_BOTTOM_RIGHT:3735case DRAG_BOTTOM:3736case DRAG_BOTTOM_LEFT:3737case DRAG_MOVE:3738if (control->get_rotation() != 0.0 || control->get_scale() != Vector2(1, 1)) {3739Rect2 rect = Rect2(Vector2(node_pos_in_parent[0], node_pos_in_parent[1]), control->get_size());3740viewport->draw_rect(parent_transform.xform(rect), color_base, false, Math::round(EDSCALE));3741}3742break;3743default:3744break;3745}3746}3747}37483749void CanvasItemEditor::_draw_selection() {3750Ref<Texture2D> pivot_icon = get_editor_theme_icon(SNAME("EditorPivot"));3751Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));3752Ref<Texture2D> previous_position_icon = get_editor_theme_icon(SNAME("EditorPositionPrevious"));37533754RID vp_ci = viewport->get_canvas_item();3755List<CanvasItem *> selection = _get_edited_canvas_items(true, false);3756bool single = selection.size() == 1;3757bool transform_tool = tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT;37583759for (CanvasItem *E : selection) {3760CanvasItem *ci = Object::cast_to<CanvasItem>(E);3761CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);37623763// Draw the previous position if we are dragging the node3764if (show_helpers &&3765(drag_type == DRAG_MOVE || drag_type == DRAG_ROTATE ||3766drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||3767drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT)) {3768const Transform2D pre_drag_xform = transform * se->pre_drag_xform;3769const Color pre_drag_color = Color(0.4, 0.6, 1, 0.7);37703771if (ci->_edit_use_rect()) {3772Vector2 pre_drag_endpoints[4] = {3773pre_drag_xform.xform(se->pre_drag_rect.position),3774pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)),3775pre_drag_xform.xform(se->pre_drag_rect.position + se->pre_drag_rect.size),3776pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(0, se->pre_drag_rect.size.y))3777};37783779for (int i = 0; i < 4; i++) {3780viewport->draw_line(pre_drag_endpoints[i], pre_drag_endpoints[(i + 1) % 4], pre_drag_color, Math::round(2 * EDSCALE));3781}3782} else {3783viewport->draw_texture(previous_position_icon, (pre_drag_xform.xform(Point2()) - (previous_position_icon->get_size() / 2)).floor());3784}3785}37863787bool item_locked = ci->has_meta("_edit_lock_");3788Transform2D xform = transform * ci->get_screen_transform();37893790// Draw the selected items position / surrounding boxes3791if (ci->_edit_use_rect()) {3792Rect2 rect = ci->_edit_get_rect();3793const Vector2 endpoints[4] = {3794xform.xform(rect.position),3795xform.xform(rect.position + Vector2(rect.size.x, 0)),3796xform.xform(rect.position + rect.size),3797xform.xform(rect.position + Vector2(0, rect.size.y))3798};37993800Color c = Color(1, 0.6, 0.4, 0.7);38013802if (item_locked) {3803c = Color(0.7, 0.7, 0.7, 0.7);3804}38053806for (int i = 0; i < 4; i++) {3807viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, Math::round(2 * EDSCALE));3808}3809} else {3810Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3811Transform2D simple_xform;3812if (use_local_space) {3813simple_xform = viewport->get_transform() * unscaled_transform;3814} else {3815Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());3816simple_xform = viewport->get_transform() * translation;3817}38183819viewport->draw_set_transform_matrix(simple_xform);3820viewport->draw_texture(position_icon, -(position_icon->get_size() / 2));3821viewport->draw_set_transform_matrix(viewport->get_transform());3822}38233824if (single && !item_locked && transform_tool) {3825// Draw the pivot3826if (ci->_edit_use_pivot()) {3827// Draw the node's pivot3828Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3829Transform2D simple_xform;3830if (use_local_space) {3831simple_xform = viewport->get_transform() * unscaled_transform;3832} else {3833Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());3834simple_xform = viewport->get_transform() * translation;3835}38363837viewport->draw_set_transform_matrix(simple_xform);3838viewport->draw_texture(pivot_icon, -(pivot_icon->get_size() / 2).floor());3839viewport->draw_set_transform_matrix(viewport->get_transform());3840}38413842// Draw control-related helpers3843Control *control = Object::cast_to<Control>(ci);3844if (control && _is_node_movable(control)) {3845_draw_control_anchors(control);3846_draw_control_helpers(control);3847}38483849// Draw the resize handles3850if (tool == TOOL_SELECT && ci->_edit_use_rect() && _is_node_movable(ci)) {3851Rect2 rect = ci->_edit_get_rect();3852const Vector2 endpoints[4] = {3853xform.xform(rect.position),3854xform.xform(rect.position + Vector2(rect.size.x, 0)),3855xform.xform(rect.position + rect.size),3856xform.xform(rect.position + Vector2(0, rect.size.y))3857};3858for (int i = 0; i < 4; i++) {3859int prev = (i + 3) % 4;3860int next = (i + 1) % 4;38613862Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();3863ofs *= Math::SQRT2 * (select_handle->get_size().width / 2);38643865select_handle->draw(vp_ci, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor());38663867ofs = (endpoints[i] + endpoints[next]) / 2;3868ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);38693870select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor());3871}3872}3873}3874}38753876// Remove non-movable nodes.3877for (List<CanvasItem *>::Element *E = selection.front(); E;) {3878List<CanvasItem *>::Element *N = E->next();3879if (!_is_node_movable(E->get())) {3880selection.erase(E);3881}3882E = N;3883}38843885if (!selection.is_empty() && transform_tool && show_transformation_gizmos) {3886CanvasItem *ci = selection.front()->get();38873888Transform2D xform = transform * ci->get_screen_transform();3889bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);3890bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);38913892// Draw the move handles.3893if ((tool == TOOL_SELECT && is_alt && !is_ctrl) || tool == TOOL_MOVE) {3894Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3895Transform2D simple_xform;3896if (use_local_space) {3897simple_xform = viewport->get_transform() * unscaled_transform;3898} else {3899Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());3900simple_xform = viewport->get_transform() * translation;3901}39023903Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);3904viewport->draw_set_transform_matrix(simple_xform);39053906Vector<Point2> points = {3907Vector2(move_factor.x * EDSCALE, 5 * EDSCALE),3908Vector2(move_factor.x * EDSCALE, -5 * EDSCALE),3909Vector2((move_factor.x + 10) * EDSCALE, 0)3910};39113912viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));3913viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));39143915points.clear();3916points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE));3917points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE));3918points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE));39193920viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));3921viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));39223923viewport->draw_set_transform_matrix(viewport->get_transform());3924}39253926// Draw the rescale handles.3927if ((tool == TOOL_SELECT && is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {3928Transform2D edit_transform;3929if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {3930edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);3931} else {3932edit_transform = ci->_edit_get_transform();3933}3934Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();3935Transform2D simple_xform;3936if (use_local_space) {3937simple_xform = viewport->get_transform() * unscaled_transform;3938} else {3939Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());3940simple_xform = viewport->get_transform() * translation;3941}39423943Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);3944bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT);3945Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom;39463947if (drag_type == DRAG_SCALE_X) {3948scale_factor.x += offset.x;3949if (uniform) {3950scale_factor.y += offset.x;3951}3952} else if (drag_type == DRAG_SCALE_Y) {3953scale_factor.y += offset.y;3954if (uniform) {3955scale_factor.x += offset.y;3956}3957}39583959viewport->draw_set_transform_matrix(simple_xform);3960Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);3961viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));3962viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));39633964Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);3965viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));3966viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));39673968viewport->draw_set_transform_matrix(viewport->get_transform());3969}3970}39713972if (drag_type == DRAG_BOX_SELECTION) {3973// Draw the dragging box3974Point2 bsfrom = transform.xform(drag_from);3975Point2 bsto = transform.xform(box_selecting_to);39763977viewport->draw_rect(3978Rect2(bsfrom, bsto - bsfrom),3979get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));39803981viewport->draw_rect(3982Rect2(bsfrom, bsto - bsfrom),3983get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),3984false,3985Math::round(EDSCALE));3986}39873988if (drag_type == DRAG_ROTATE) {3989// Draw the line when rotating a node3990viewport->draw_line(3991transform.xform(drag_rotation_center),3992transform.xform(drag_to),3993get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.6),3994Math::round(2 * EDSCALE));3995}39963997if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {3998viewport->draw_texture(pivot_icon, (temp_pivot - view_offset) * zoom - (pivot_icon->get_size() / 2).floor(), get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));3999}4000}40014002void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_color) {4003// Draw a line going through the whole screen from a vector4004RID ci = viewport->get_canvas_item();4005Vector<Point2> points;4006Point2 from = transform.xform(p_from);4007Point2 to = transform.xform(p_to);4008Size2 viewport_size = viewport->get_size();40094010if (to.x == from.x) {4011// Vertical line4012points.push_back(Point2(to.x, 0));4013points.push_back(Point2(to.x, viewport_size.y));4014} else if (to.y == from.y) {4015// Horizontal line4016points.push_back(Point2(0, to.y));4017points.push_back(Point2(viewport_size.x, to.y));4018} else {4019real_t y_for_zero_x = (to.y * from.x - from.y * to.x) / (from.x - to.x);4020real_t x_for_zero_y = (to.x * from.y - from.x * to.y) / (from.y - to.y);4021real_t y_for_viewport_x = ((to.y - from.y) * (viewport_size.x - from.x)) / (to.x - from.x) + from.y;4022real_t x_for_viewport_y = ((to.x - from.x) * (viewport_size.y - from.y)) / (to.y - from.y) + from.x; // faux40234024//bool start_set = false;4025if (y_for_zero_x >= 0 && y_for_zero_x <= viewport_size.y) {4026points.push_back(Point2(0, y_for_zero_x));4027}4028if (x_for_zero_y >= 0 && x_for_zero_y <= viewport_size.x) {4029points.push_back(Point2(x_for_zero_y, 0));4030}4031if (y_for_viewport_x >= 0 && y_for_viewport_x <= viewport_size.y) {4032points.push_back(Point2(viewport_size.x, y_for_viewport_x));4033}4034if (x_for_viewport_y >= 0 && x_for_viewport_y <= viewport_size.x) {4035points.push_back(Point2(x_for_viewport_y, viewport_size.y));4036}4037}4038if (points.size() >= 2) {4039RenderingServer::get_singleton()->canvas_item_add_line(ci, points[0], points[1], p_color);4040}4041}40424043void CanvasItemEditor::_draw_axis() {4044if (show_origin) {4045_draw_straight_line(Point2(), Point2(1, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));4046_draw_straight_line(Point2(), Point2(0, 1), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));4047}40484049if (show_viewport) {4050RID ci = viewport->get_canvas_item();40514052Color area_axis_color = EDITOR_GET("editors/2d/viewport_border_color");40534054Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));40554056Vector2 screen_endpoints[4] = {4057transform.xform(Vector2(0, 0)),4058transform.xform(Vector2(screen_size.width, 0)),4059transform.xform(Vector2(screen_size.width, screen_size.height)),4060transform.xform(Vector2(0, screen_size.height))4061};40624063for (int i = 0; i < 4; i++) {4064RenderingServer::get_singleton()->canvas_item_add_line(ci, screen_endpoints[i], screen_endpoints[(i + 1) % 4], area_axis_color);4065}4066}4067}40684069void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {4070ERR_FAIL_NULL(p_node);40714072Node *scene = EditorNode::get_singleton()->get_edited_scene();4073if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {4074return;4075}4076CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);4077if (ci && !ci->is_visible_in_tree()) {4078return;4079}40804081Transform2D parent_xform = p_parent_xform;4082Transform2D canvas_xform = p_canvas_xform;40834084if (ci && !ci->is_set_as_top_level()) {4085parent_xform = parent_xform * ci->get_transform();4086} else if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {4087parent_xform = Transform2D();4088canvas_xform = cl->get_transform();4089} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {4090if (!vp->is_visible_subviewport()) {4091return;4092}4093parent_xform = Transform2D();4094canvas_xform = vp->get_popup_base_transform();4095}40964097for (int i = p_node->get_child_count() - 1; i >= 0; i--) {4098_draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform);4099}41004101if (show_position_gizmos && ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) {4102Transform2D xform = transform * canvas_xform * parent_xform;41034104// Draw the node's position4105Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPositionUnselected"));4106Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();4107Transform2D simple_xform;4108if (use_local_space) {4109simple_xform = viewport->get_transform() * unscaled_transform;4110} else {4111Transform2D translation = Transform2D(0.0f, unscaled_transform.get_origin());4112simple_xform = viewport->get_transform() * translation;4113}41144115viewport->draw_set_transform_matrix(simple_xform);4116viewport->draw_texture(position_icon, -position_icon->get_size() / 2, Color(1.0, 1.0, 1.0, 0.5));4117viewport->draw_set_transform_matrix(viewport->get_transform());4118}4119}41204121void CanvasItemEditor::_draw_hover() {4122List<Rect2> previous_rects;4123Vector2 icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));41244125for (int i = 0; i < hovering_results.size(); i++) {4126Ref<Texture2D> node_icon = hovering_results[i].icon;4127String node_name = hovering_results[i].name;41284129Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));4130int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));4131Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);4132Size2 item_size = Size2(icon_size.x + 4 + node_name_size.x, MAX(icon_size.y, node_name_size.y - 3));41334134Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(icon_size.x, -icon_size.y) / 4);4135// Rectify the position to avoid overlapping items4136for (const Rect2 &E : previous_rects) {4137if (E.intersects(Rect2(pos, item_size))) {4138pos.y = E.get_position().y - item_size.y;4139}4140}41414142previous_rects.push_back(Rect2(pos, item_size));41434144// Draw icon4145viewport->draw_texture_rect(node_icon, Rect2(pos, icon_size), false, Color(1.0, 1.0, 1.0, 0.5));41464147// Draw name4148viewport->draw_string(font, pos + Point2(icon_size.x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));4149}4150}41514152void CanvasItemEditor::_draw_message() {4153if (drag_type != DRAG_NONE && !drag_selection.is_empty() && drag_selection.front()->get()) {4154Transform2D current_transform = drag_selection.front()->get()->get_global_transform();41554156double snap = EDITOR_GET("interface/inspector/default_float_step");4157int snap_step_decimals = Math::range_step_decimals(snap);4158const String &lang = _get_locale();4159#define FORMAT(value) (TranslationServer::get_singleton()->format_number(String::num(value, snap_step_decimals), lang))41604161switch (drag_type) {4162case DRAG_MOVE:4163case DRAG_MOVE_X:4164case DRAG_MOVE_Y: {4165Vector2 delta = current_transform.get_origin() - original_transform.get_origin();4166if (drag_type == DRAG_MOVE || use_local_space) {4167message = TTR("Moving:") + " (" + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ") px";4168} else if (drag_type == DRAG_MOVE_X) {4169message = TTR("Moving:") + " " + FORMAT(delta.x) + " px";4170} else if (drag_type == DRAG_MOVE_Y) {4171message = TTR("Moving:") + " " + FORMAT(delta.y) + " px";4172}4173} break;41744175case DRAG_ROTATE: {4176real_t delta = Math::rad_to_deg(current_transform.get_rotation() - original_transform.get_rotation());4177message = TTR("Rotating:") + " " + FORMAT(delta) + String::utf8(" °");4178} break;41794180case DRAG_SCALE_X:4181case DRAG_SCALE_Y:4182case DRAG_SCALE_BOTH: {4183Vector2 original_scale = (Math::is_zero_approx(original_transform.get_scale().x) || Math::is_zero_approx(original_transform.get_scale().y)) ? Vector2(CMP_EPSILON, CMP_EPSILON) : original_transform.get_scale();4184Vector2 delta = current_transform.get_scale() / original_scale;4185if (drag_type == DRAG_SCALE_BOTH || !use_local_space) {4186message = TTR("Scaling:") + String::utf8(" ×(") + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ")";4187} else if (drag_type == DRAG_SCALE_X) {4188message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.x);4189} else if (drag_type == DRAG_SCALE_Y) {4190message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.y);4191}4192} break;41934194default:4195break;4196}4197#undef FORMAT4198}41994200if (message.is_empty()) {4201return;4202}42034204Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));4205int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));4206Point2 msgpos = Point2(ruler_width_scaled + 10 * EDSCALE, viewport->get_size().y - 14 * EDSCALE);4207viewport->draw_string(font, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));4208viewport->draw_string(font, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));4209viewport->draw_string(font, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));4210}42114212void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {4213ERR_FAIL_NULL(p_node);42144215Node *scene = EditorNode::get_singleton()->get_edited_scene();4216if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {4217return;4218}4219CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);4220if (ci && !ci->is_visible_in_tree()) {4221return;4222}42234224Transform2D parent_xform = p_parent_xform;4225Transform2D canvas_xform = p_canvas_xform;42264227if (ci && !ci->is_set_as_top_level()) {4228parent_xform = parent_xform * ci->get_transform();4229} else if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {4230parent_xform = Transform2D();4231canvas_xform = cl->get_transform();4232} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {4233if (!vp->is_visible_subviewport()) {4234return;4235}4236parent_xform = Transform2D();4237canvas_xform = vp->get_popup_base_transform();4238}42394240for (int i = p_node->get_child_count() - 1; i >= 0; i--) {4241_draw_locks_and_groups(p_node->get_child(i), parent_xform, canvas_xform);4242}42434244RID viewport_ci = viewport->get_canvas_item();4245if (ci) {4246real_t offset = 0;42474248Ref<Texture2D> lock = get_editor_theme_icon(SNAME("LockViewport"));4249if (show_lock_gizmos && p_node->has_meta("_edit_lock_")) {4250lock->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));4251offset += lock->get_size().x;4252}42534254Ref<Texture2D> group = get_editor_theme_icon(SNAME("GroupViewport"));4255if (show_group_gizmos && ci->has_meta("_edit_group_")) {4256group->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));4257//offset += group->get_size().x;4258}4259}4260}42614262void CanvasItemEditor::_draw_viewport() {4263// Update the transform4264transform = Transform2D();4265transform.scale_basis(Size2(zoom, zoom));4266transform.columns[2] = -view_offset * zoom;4267EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform);42684269_draw_grid();4270_draw_ruler_tool();4271_draw_axis();4272if (EditorNode::get_singleton()->get_edited_scene()) {4273_draw_locks_and_groups(EditorNode::get_singleton()->get_edited_scene());4274_draw_invisible_nodes_positions(EditorNode::get_singleton()->get_edited_scene());4275}4276_draw_selection();42774278RID ci = viewport->get_canvas_item();4279RenderingServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D());42804281EditorNode::get_singleton()->get_editor_plugins_over()->forward_canvas_draw_over_viewport(viewport);4282EditorNode::get_singleton()->get_editor_plugins_force_over()->forward_canvas_force_draw_over_viewport(viewport);42834284if (show_rulers) {4285_draw_rulers();4286}4287if (show_guides) {4288_draw_guides();4289}4290_draw_smart_snapping();4291_draw_focus();4292_draw_hover();4293_draw_message();4294}42954296void CanvasItemEditor::update_viewport() {4297_update_scrollbars();4298viewport->queue_redraw();4299}43004301void CanvasItemEditor::set_current_tool(Tool p_tool) {4302_button_tool_select(p_tool);4303}43044305void CanvasItemEditor::_update_editor_settings() {4306button_center_view->set_button_icon(get_editor_theme_icon(SNAME("CenterView")));4307select_button->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));4308select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));4309list_select_button->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));4310move_button->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));4311scale_button->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));4312rotate_button->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));4313local_space_button->set_button_icon(get_editor_theme_icon(SNAME("Object")));4314smart_snap_button->set_button_icon(get_editor_theme_icon(SNAME("Snap")));4315grid_snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));4316snap_config_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));4317skeleton_menu->set_button_icon(get_editor_theme_icon(SNAME("Bone")));4318pan_button->set_button_icon(get_editor_theme_icon(SNAME("ToolPan")));4319ruler_button->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));4320pivot_button->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));4321select_handle = get_editor_theme_icon(SNAME("EditorHandle"));4322anchor_handle = get_editor_theme_icon(SNAME("EditorControlAnchor"));4323lock_button->set_button_icon(get_editor_theme_icon(SNAME("Lock")));4324unlock_button->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));4325group_button->set_button_icon(get_editor_theme_icon(SNAME("Group")));4326ungroup_button->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));4327key_loc_button->set_button_icon(get_editor_theme_icon(SNAME("KeyPosition")));4328key_rot_button->set_button_icon(get_editor_theme_icon(SNAME("KeyRotation")));4329key_scale_button->set_button_icon(get_editor_theme_icon(SNAME("KeyScale")));4330key_insert_button->set_button_icon(get_editor_theme_icon(SNAME("Key")));4331key_auto_insert_button->set_button_icon(get_editor_theme_icon(SNAME("AutoKey")));4332// Use a different color for the active autokey icon to make them easier4333// to distinguish from the other key icons at the top. On a light theme,4334// the icon will be dark, so we need to lighten it before blending it4335// with the red color.4336const Color key_auto_color = EditorThemeManager::is_dark_icon_and_font() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25);4337key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));4338animation_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));43394340context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));43414342simple_panning = EDITOR_GET("editors/panning/simple_panning");4343panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), simple_panning);4344panner->set_scroll_speed(EDITOR_GET("editors/panning/2d_editor_pan_speed"));4345panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));4346panner->set_zoom_style((ViewPanner::ZoomStyle)EDITOR_GET("editors/panning/zoom_style").operator int());43474348// Compute the ruler width here so we can reuse the result throughout the various draw functions.4349real_t ruler_width_unscaled = EDITOR_GET("editors/2d/ruler_width");4350ruler_font_size = MAX(get_theme_font_size(SNAME("rulers_size"), EditorStringName(EditorFonts)) * ruler_width_unscaled / 15.0, 8);4351ruler_width_scaled = MAX(ruler_width_unscaled * EDSCALE, ruler_font_size * 2.0);43524353grab_distance = EDITOR_GET("editors/polygon_editor/point_grab_radius");43544355resample_delay = EDITOR_GET("editors/2d/auto_resample_delay");4356resample_timer->set_wait_time(resample_delay);4357}43584359void CanvasItemEditor::_project_settings_changed() {4360EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels"));4361}43624363void CanvasItemEditor::_notification(int p_what) {4364switch (p_what) {4365case NOTIFICATION_TRANSLATION_CHANGED: {4366select_button->set_tooltip_text(vformat(TTR("%s+Drag: Rotate selected node around pivot."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("Alt+Drag: Move selected node.") + "\n" + vformat(TTR("%s+Alt+Drag: Scale selected node."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL)) + "\n" + TTR("V: Set selected node's pivot position.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.") + "\n" + TTR("(Available in all modes.)") + "\n" + TTR("RMB: Add node at position clicked."));4367pivot_button->set_tooltip_text(TTR("Click to change object's pivot.") + "\n" + TTR("Shift: Set temporary pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary pivot in the center of the selected nodes."));4368} break;43694370case NOTIFICATION_READY: {4371_update_lock_and_group_button();43724373ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));4374} break;43754376case NOTIFICATION_PROCESS: {4377// Update the viewport if the canvas_item changes4378List<CanvasItem *> selection = _get_edited_canvas_items(true);4379for (CanvasItem *ci : selection) {4380CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);43814382Rect2 rect;4383if (ci->_edit_use_rect()) {4384rect = ci->_edit_get_rect();4385} else {4386rect = Rect2();4387}4388Transform2D xform = ci->get_global_transform();43894390if (rect != se->prev_rect || xform != se->prev_xform) {4391viewport->queue_redraw();4392se->prev_rect = rect;4393se->prev_xform = xform;4394}43954396Control *control = Object::cast_to<Control>(ci);4397if (control) {4398Vector2 pivot = control->get_pivot_offset();4399Vector2 pivot_ratio = control->get_pivot_offset_ratio();44004401real_t anchors[4];4402anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT);4403anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT);4404anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP);4405anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM);44064407if (pivot != se->prev_pivot || pivot_ratio != se->prev_pivot_ratio || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) {4408se->prev_pivot = pivot;4409se->prev_pivot_ratio = pivot_ratio;4410se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT];4411se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT];4412se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP];4413se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM];4414viewport->queue_redraw();4415}4416}4417}44184419// Activate / Deactivate the pivot tool.4420pivot_button->set_disabled(selection.is_empty());44214422// Update the viewport if bones changes4423for (KeyValue<BoneKey, BoneList> &E : bone_list) {4424Object *b = ObjectDB::get_instance(E.key.from);4425if (!b) {4426viewport->queue_redraw();4427break;4428}44294430Node2D *b2 = Object::cast_to<Node2D>(b);4431if (!b2 || !b2->is_inside_tree()) {4432continue;4433}44344435Transform2D global_xform = b2->get_global_transform();44364437if (global_xform != E.value.xform) {4438E.value.xform = global_xform;4439viewport->queue_redraw();4440}44414442Bone2D *bone = Object::cast_to<Bone2D>(b);4443if (bone && bone->get_length() != E.value.length) {4444E.value.length = bone->get_length();4445viewport->queue_redraw();4446}4447}4448} break;44494450case NOTIFICATION_ENTER_TREE: {4451select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));4452select_sb->set_texture_margin_all(4);4453select_sb->set_content_margin_all(4);44544455AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("keying_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));4456AnimationPlayerEditor::get_singleton()->connect("animation_selected", callable_mp(this, &CanvasItemEditor::_keying_changed).unbind(1));4457_keying_changed();4458_update_editor_settings();44594460connect("item_lock_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));4461connect("item_group_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));4462} break;44634464case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {4465if (EditorThemeManager::is_generated_theme_outdated() ||4466EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning") ||4467EditorSettings::get_singleton()->check_changed_settings_in_group("editors/2d") ||4468EditorSettings::get_singleton()->check_changed_settings_in_group("editors/polygon_editor")) {4469_update_editor_settings();4470update_viewport();4471}4472} break;44734474case NOTIFICATION_APPLICATION_FOCUS_OUT:4475case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {4476if (drag_type != DRAG_NONE) {4477_commit_drag();4478}4479} break;4480}4481}44824483void CanvasItemEditor::_selection_changed() {4484_update_lock_and_group_button();4485if (!selected_from_canvas) {4486_reset_drag();4487}4488selected_from_canvas = false;44894490if (temp_pivot != Vector2(Math::INF, Math::INF)) {4491temp_pivot = Vector2(Math::INF, Math::INF);4492viewport->queue_redraw();4493}4494}44954496void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {4497if (!p_canvas_item) {4498return;4499}45004501Array selection = editor_selection->get_selected_nodes();4502if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) {4503_reset_drag();4504}4505}45064507void CanvasItemEditor::_update_scrollbars() {4508updating_scroll = true;45094510// Move the zoom buttons.4511Point2 controls_vb_begin = Point2(5, 5);4512controls_vb_begin += (show_rulers) ? Point2(ruler_width_scaled, ruler_width_scaled) : Point2();4513controls_vb->set_begin(controls_vb_begin);45144515Size2 hmin = h_scroll->get_minimum_size();4516Size2 vmin = v_scroll->get_minimum_size();45174518// Get the visible frame.4519Size2 screen_rect = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));4520Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height));45214522// Calculate scrollable area.4523Rect2 canvas_item_rect = Rect2(Point2(), screen_rect);4524if (EditorNode::get_singleton()->is_inside_tree() && EditorNode::get_singleton()->get_edited_scene()) {4525Rect2 content_rect = _get_encompassing_rect(EditorNode::get_singleton()->get_edited_scene());4526canvas_item_rect.expand_to(content_rect.position);4527canvas_item_rect.expand_to(content_rect.position + content_rect.size);4528}4529canvas_item_rect.size += screen_rect * 2;4530canvas_item_rect.position -= screen_rect;45314532// Updates the scrollbars.4533const Size2 size = viewport->get_size();4534const Point2 begin = canvas_item_rect.position;4535const Point2 end = canvas_item_rect.position + canvas_item_rect.size - local_rect.size / zoom;45364537if (canvas_item_rect.size.height <= (local_rect.size.y / zoom)) {4538v_scroll->hide();4539} else {4540v_scroll->show();4541v_scroll->set_min(MIN(view_offset.y, begin.y));4542v_scroll->set_max(MAX(view_offset.y, end.y) + screen_rect.y);4543v_scroll->set_page(screen_rect.y);4544}45454546if (canvas_item_rect.size.width <= (local_rect.size.x / zoom)) {4547h_scroll->hide();4548} else {4549h_scroll->show();4550h_scroll->set_min(MIN(view_offset.x, begin.x));4551h_scroll->set_max(MAX(view_offset.x, end.x) + screen_rect.x);4552h_scroll->set_page(screen_rect.x);4553}45544555// Move and resize the scrollbars, avoiding overlap.4556if (is_layout_rtl()) {4557v_scroll->set_begin(Point2(0, (show_rulers) ? ruler_width_scaled : 0));4558v_scroll->set_end(Point2(vmin.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));4559} else {4560v_scroll->set_begin(Point2(size.width - vmin.width, (show_rulers) ? ruler_width_scaled : 0));4561v_scroll->set_end(Point2(size.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));4562}4563h_scroll->set_begin(Point2((show_rulers) ? ruler_width_scaled : 0, size.height - hmin.height));4564h_scroll->set_end(Point2(size.width - (v_scroll->is_visible() ? vmin.width : 0), size.height));45654566// Calculate scrollable area.4567v_scroll->set_value(view_offset.y);4568h_scroll->set_value(view_offset.x);45694570previous_update_view_offset = view_offset;4571updating_scroll = false;4572}45734574void CanvasItemEditor::_update_scroll(real_t) {4575if (updating_scroll) {4576return;4577}45784579view_offset.x = h_scroll->get_value();4580view_offset.y = v_scroll->get_value();4581viewport->queue_redraw();4582}45834584void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {4585p_zoom = CLAMP(p_zoom, zoom_widget->get_min_zoom(), zoom_widget->get_max_zoom());45864587if (p_zoom == zoom) {4588return;4589}45904591real_t prev_zoom = zoom;4592zoom = p_zoom;45934594view_offset += p_position / prev_zoom - p_position / zoom;45954596// We want to align in-scene pixels to screen pixels, this prevents blurry rendering4597// of small details (texts, lines).4598// This correction adds a jitter movement when zooming, so we correct only when the4599// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)4600const real_t closest_zoom_factor = Math::round(zoom);4601if (Math::is_zero_approx(zoom - closest_zoom_factor)) {4602// Make sure scene pixel at view_offset is aligned on a screen pixel.4603Vector2 view_offset_int = view_offset.floor();4604Vector2 view_offset_frac = view_offset - view_offset_int;4605view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;4606}46074608zoom_widget->set_zoom(zoom);4609update_viewport();4610if (auto_resampling_enabled) {4611resample_timer->start();4612}4613}46144615void CanvasItemEditor::_update_zoom(real_t p_zoom) {4616_zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);4617}46184619void CanvasItemEditor::_update_oversampling() {4620EditorNode::get_singleton()->get_scene_root()->set_oversampling_override(auto_resampling_enabled ? zoom : 0.0);4621}46224623void CanvasItemEditor::_shortcut_zoom_set(real_t p_zoom) {4624_zoom_on_position(p_zoom * MAX(1, EDSCALE), viewport->get_local_mouse_position());4625}46264627void CanvasItemEditor::_button_toggle_local_space(bool p_status) {4628use_local_space = p_status;4629viewport->queue_redraw();4630}46314632void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) {4633smart_snap_active = p_status;4634viewport->queue_redraw();4635}46364637void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) {4638grid_snap_active = p_status;4639viewport->queue_redraw();4640}46414642void CanvasItemEditor::_button_tool_select(int p_index) {4643if (drag_type != DRAG_NONE) {4644_commit_drag();4645}46464647Button *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };4648for (int i = 0; i < TOOL_MAX; i++) {4649tb[i]->set_pressed(i == p_index);4650}46514652tool = (Tool)p_index;46534654if (p_index == TOOL_EDIT_PIVOT && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {4655// Special action that places temporary rotation pivot in the middle of the selection.4656List<CanvasItem *> selection = _get_edited_canvas_items();4657if (!selection.is_empty()) {4658Vector2 center;4659for (const CanvasItem *ci : selection) {4660center += ci->get_viewport()->get_popup_base_transform().xform(ci->_edit_get_position());4661}4662temp_pivot = center / selection.size();4663}4664}46654666viewport->queue_redraw();4667_update_cursor();4668}46694670void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {4671const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();46724673AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();4674ERR_FAIL_COND_MSG(te->get_current_animation().is_null(), "Cannot insert animation key. No animation selected.");46754676bool is_read_only = te->is_read_only();4677if (is_read_only) {4678te->popup_read_only_dialog();4679return;4680}4681te->make_insert_queue();4682for (const KeyValue<ObjectID, Object *> &E : selection) {4683CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(E.key);4684if (!ci || !ci->is_visible_in_tree()) {4685continue;4686}46874688if (Object::cast_to<Node2D>(ci)) {4689Node2D *n2d = Object::cast_to<Node2D>(ci);46904691if (key_pos && p_location) {4692te->insert_node_value_key(n2d, "position", p_on_existing);4693}4694if (key_rot && p_rotation) {4695te->insert_node_value_key(n2d, "rotation", p_on_existing);4696}4697if (key_scale && p_scale) {4698te->insert_node_value_key(n2d, "scale", p_on_existing);4699}47004701if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {4702//look for an IK chain4703List<Node2D *> ik_chain;47044705Node2D *n = Object::cast_to<Node2D>(n2d->get_parent_item());4706bool has_chain = false;47074708while (n) {4709ik_chain.push_back(n);4710if (n->has_meta("_edit_ik_")) {4711has_chain = true;4712break;4713}47144715if (!n->get_parent_item()) {4716break;4717}4718n = Object::cast_to<Node2D>(n->get_parent_item());4719}47204721if (has_chain && ik_chain.size()) {4722for (Node2D *&F : ik_chain) {4723if (key_pos) {4724te->insert_node_value_key(F, "position", p_on_existing);4725}4726if (key_rot) {4727te->insert_node_value_key(F, "rotation", p_on_existing);4728}4729if (key_scale) {4730te->insert_node_value_key(F, "scale", p_on_existing);4731}4732}4733}4734}47354736} else if (Object::cast_to<Control>(ci)) {4737Control *ctrl = Object::cast_to<Control>(ci);47384739if (key_pos) {4740te->insert_node_value_key(ctrl, "position", p_on_existing);4741}4742if (key_rot) {4743te->insert_node_value_key(ctrl, "rotation", p_on_existing);4744}4745if (key_scale) {4746te->insert_node_value_key(ctrl, "size", p_on_existing);4747}4748}4749}4750te->commit_insert_queue();4751}47524753void CanvasItemEditor::_prepare_view_menu() {4754PopupMenu *popup = view_menu->get_popup();47554756Node *root = EditorNode::get_singleton()->get_edited_scene();4757bool has_guides = root && (root->has_meta("_edit_horizontal_guides_") || root->has_meta("_edit_vertical_guides_"));4758popup->set_item_disabled(popup->get_item_index(CLEAR_GUIDES), !has_guides);4759}47604761void CanvasItemEditor::_popup_callback(int p_op) {4762EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();4763last_option = MenuOption(p_op);4764switch (p_op) {4765case SHOW_ORIGIN: {4766show_origin = !show_origin;4767int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);4768view_menu->get_popup()->set_item_checked(idx, show_origin);4769viewport->queue_redraw();4770} break;4771case SHOW_VIEWPORT: {4772show_viewport = !show_viewport;4773int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);4774view_menu->get_popup()->set_item_checked(idx, show_viewport);4775viewport->queue_redraw();4776} break;4777case SHOW_POSITION_GIZMOS: {4778show_position_gizmos = !show_position_gizmos;4779int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS);4780gizmos_menu->set_item_checked(idx, show_position_gizmos);4781viewport->queue_redraw();4782} break;4783case SHOW_LOCK_GIZMOS: {4784show_lock_gizmos = !show_lock_gizmos;4785int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS);4786gizmos_menu->set_item_checked(idx, show_lock_gizmos);4787viewport->queue_redraw();4788} break;4789case SHOW_GROUP_GIZMOS: {4790show_group_gizmos = !show_group_gizmos;4791int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS);4792gizmos_menu->set_item_checked(idx, show_group_gizmos);4793viewport->queue_redraw();4794} break;4795case SHOW_TRANSFORMATION_GIZMOS: {4796show_transformation_gizmos = !show_transformation_gizmos;4797int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS);4798gizmos_menu->set_item_checked(idx, show_transformation_gizmos);4799viewport->queue_redraw();4800} break;4801case SNAP_USE_NODE_PARENT: {4802snap_node_parent = !snap_node_parent;4803int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);4804smartsnap_config_popup->set_item_checked(idx, snap_node_parent);4805} break;4806case SNAP_USE_NODE_ANCHORS: {4807snap_node_anchors = !snap_node_anchors;4808int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);4809smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);4810} break;4811case SNAP_USE_NODE_SIDES: {4812snap_node_sides = !snap_node_sides;4813int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);4814smartsnap_config_popup->set_item_checked(idx, snap_node_sides);4815} break;4816case SNAP_USE_NODE_CENTER: {4817snap_node_center = !snap_node_center;4818int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);4819smartsnap_config_popup->set_item_checked(idx, snap_node_center);4820} break;4821case SNAP_USE_OTHER_NODES: {4822snap_other_nodes = !snap_other_nodes;4823int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);4824smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);4825} break;4826case SNAP_USE_GUIDES: {4827snap_guides = !snap_guides;4828int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);4829smartsnap_config_popup->set_item_checked(idx, snap_guides);4830} break;4831case SNAP_USE_ROTATION: {4832snap_rotation = !snap_rotation;4833int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);4834snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);4835} break;4836case SNAP_USE_SCALE: {4837snap_scale = !snap_scale;4838int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);4839snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);4840} break;4841case SNAP_RELATIVE: {4842snap_relative = !snap_relative;4843int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);4844snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);4845viewport->queue_redraw();4846} break;4847case SNAP_USE_PIXEL: {4848snap_pixel = !snap_pixel;4849int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);4850snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);4851} break;4852case SNAP_CONFIGURE: {4853static_cast<SnapDialog *>(snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);4854snap_dialog->popup_centered(Size2(320, 160) * EDSCALE);4855} break;4856case SKELETON_SHOW_BONES: {4857List<Node *> selection = editor_selection->get_top_selected_node_list();4858for (Node *E : selection) {4859// Add children nodes so they are processed4860for (int child = 0; child < E->get_child_count(); child++) {4861selection.push_back(E->get_child(child));4862}48634864Bone2D *bone_2d = Object::cast_to<Bone2D>(E);4865if (!bone_2d || !bone_2d->is_inside_tree()) {4866continue;4867}4868bone_2d->_editor_set_show_bone_gizmo(!bone_2d->_editor_get_show_bone_gizmo());4869}4870} break;4871case SHOW_HELPERS: {4872show_helpers = !show_helpers;4873int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);4874view_menu->get_popup()->set_item_checked(idx, show_helpers);4875viewport->queue_redraw();4876} break;4877case SHOW_RULERS: {4878show_rulers = !show_rulers;4879int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);4880view_menu->get_popup()->set_item_checked(idx, show_rulers);4881update_viewport();4882} break;4883case SHOW_GUIDES: {4884show_guides = !show_guides;4885int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);4886view_menu->get_popup()->set_item_checked(idx, show_guides);4887viewport->queue_redraw();4888} break;4889case LOCK_SELECTED: {4890undo_redo->create_action(TTR("Lock Selected"));48914892const List<Node *> &selection = editor_selection->get_top_selected_node_list();4893for (Node *E : selection) {4894CanvasItem *ci = Object::cast_to<CanvasItem>(E);4895if (!ci || !ci->is_inside_tree()) {4896continue;4897}48984899undo_redo->add_do_method(ci, "set_meta", "_edit_lock_", true);4900undo_redo->add_undo_method(ci, "remove_meta", "_edit_lock_");4901undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");4902undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");4903}4904undo_redo->add_do_method(viewport, "queue_redraw");4905undo_redo->add_undo_method(viewport, "queue_redraw");4906undo_redo->commit_action();4907} break;4908case UNLOCK_SELECTED: {4909undo_redo->create_action(TTR("Unlock Selected"));49104911const List<Node *> &selection = editor_selection->get_top_selected_node_list();4912for (Node *E : selection) {4913CanvasItem *ci = Object::cast_to<CanvasItem>(E);4914if (!ci || !ci->is_inside_tree()) {4915continue;4916}49174918undo_redo->add_do_method(ci, "remove_meta", "_edit_lock_");4919undo_redo->add_undo_method(ci, "set_meta", "_edit_lock_", true);4920undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");4921undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");4922}4923undo_redo->add_do_method(viewport, "queue_redraw");4924undo_redo->add_undo_method(viewport, "queue_redraw");4925undo_redo->commit_action();4926} break;4927case GROUP_SELECTED: {4928undo_redo->create_action(TTR("Group Selected"));49294930const List<Node *> &selection = editor_selection->get_top_selected_node_list();4931for (Node *E : selection) {4932CanvasItem *ci = Object::cast_to<CanvasItem>(E);4933if (!ci || !ci->is_inside_tree()) {4934continue;4935}49364937undo_redo->add_do_method(ci, "set_meta", "_edit_group_", true);4938undo_redo->add_undo_method(ci, "remove_meta", "_edit_group_");4939undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");4940undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");4941}4942undo_redo->add_do_method(viewport, "queue_redraw");4943undo_redo->add_undo_method(viewport, "queue_redraw");4944undo_redo->commit_action();4945} break;4946case UNGROUP_SELECTED: {4947undo_redo->create_action(TTR("Ungroup Selected"));49484949const List<Node *> &selection = editor_selection->get_top_selected_node_list();4950for (Node *E : selection) {4951CanvasItem *ci = Object::cast_to<CanvasItem>(E);4952if (!ci || !ci->is_inside_tree()) {4953continue;4954}49554956undo_redo->add_do_method(ci, "remove_meta", "_edit_group_");4957undo_redo->add_undo_method(ci, "set_meta", "_edit_group_", true);4958undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");4959undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");4960}4961undo_redo->add_do_method(viewport, "queue_redraw");4962undo_redo->add_undo_method(viewport, "queue_redraw");4963undo_redo->commit_action();4964} break;49654966case ANIM_INSERT_KEY:4967case ANIM_INSERT_KEY_EXISTING: {4968bool existing = p_op == ANIM_INSERT_KEY_EXISTING;49694970_insert_animation_keys(true, true, true, existing);49714972} break;4973case ANIM_INSERT_POS: {4974key_pos = key_loc_button->is_pressed();4975} break;4976case ANIM_INSERT_ROT: {4977key_rot = key_rot_button->is_pressed();4978} break;4979case ANIM_INSERT_SCALE: {4980key_scale = key_scale_button->is_pressed();4981} break;4982case ANIM_COPY_POSE: {4983pose_clipboard.clear();49844985const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();49864987for (const KeyValue<ObjectID, Object *> &E : selection) {4988CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(E.key);4989if (!ci || !ci->is_visible_in_tree()) {4990continue;4991}49924993if (Object::cast_to<Node2D>(ci)) {4994Node2D *n2d = Object::cast_to<Node2D>(ci);4995PoseClipboard pc;4996pc.pos = n2d->get_position();4997pc.rot = n2d->get_rotation();4998pc.scale = n2d->get_scale();4999pc.id = n2d->get_instance_id();5000pose_clipboard.push_back(pc);5001}5002}50035004} break;5005case ANIM_PASTE_POSE: {5006if (!pose_clipboard.size()) {5007break;5008}50095010undo_redo->create_action(TTR("Paste Pose"));5011for (const PoseClipboard &E : pose_clipboard) {5012Node2D *n2d = ObjectDB::get_instance<Node2D>(E.id);5013if (!n2d) {5014continue;5015}5016undo_redo->add_do_method(n2d, "set_position", E.pos);5017undo_redo->add_do_method(n2d, "set_rotation", E.rot);5018undo_redo->add_do_method(n2d, "set_scale", E.scale);5019undo_redo->add_undo_method(n2d, "set_position", n2d->get_position());5020undo_redo->add_undo_method(n2d, "set_rotation", n2d->get_rotation());5021undo_redo->add_undo_method(n2d, "set_scale", n2d->get_scale());5022}5023undo_redo->commit_action();50245025} break;5026case ANIM_CLEAR_POSE: {5027HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();50285029for (const KeyValue<ObjectID, Object *> &E : selection) {5030CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(E.key);5031if (!ci || !ci->is_visible_in_tree()) {5032continue;5033}50345035if (Object::cast_to<Node2D>(ci)) {5036Node2D *n2d = Object::cast_to<Node2D>(ci);50375038if (key_pos) {5039n2d->set_position(Vector2());5040}5041if (key_rot) {5042n2d->set_rotation(0);5043}5044if (key_scale) {5045n2d->set_scale(Vector2(1, 1));5046}5047} else if (Object::cast_to<Control>(ci)) {5048Control *ctrl = Object::cast_to<Control>(ci);50495050if (key_pos) {5051ctrl->set_position(Point2());5052}5053}5054}50555056} break;5057case CLEAR_GUIDES: {5058Node *const root = EditorNode::get_singleton()->get_edited_scene();50595060if (root && (root->has_meta("_edit_horizontal_guides_") || root->has_meta("_edit_vertical_guides_"))) {5061undo_redo->create_action(TTR("Clear Guides"));5062if (root->has_meta("_edit_horizontal_guides_")) {5063Array hguides = root->get_meta("_edit_horizontal_guides_");50645065undo_redo->add_do_method(root, "remove_meta", "_edit_horizontal_guides_");5066undo_redo->add_undo_method(root, "set_meta", "_edit_horizontal_guides_", hguides);5067}5068if (root->has_meta("_edit_vertical_guides_")) {5069Array vguides = root->get_meta("_edit_vertical_guides_");50705071undo_redo->add_do_method(root, "remove_meta", "_edit_vertical_guides_");5072undo_redo->add_undo_method(root, "set_meta", "_edit_vertical_guides_", vguides);5073}5074undo_redo->add_do_method(viewport, "queue_redraw");5075undo_redo->add_undo_method(viewport, "queue_redraw");5076undo_redo->commit_action();5077}50785079} break;5080case VIEW_CENTER_TO_SELECTION:5081case VIEW_FRAME_TO_SELECTION: {5082_focus_selection(p_op);50835084} break;5085case PREVIEW_CANVAS_SCALE: {5086bool preview = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE));5087preview = !preview;5088RS::get_singleton()->canvas_set_disable_scale(!preview);5089view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE), preview);50905091} break;5092case SKELETON_MAKE_BONES: {5093HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();5094Node *editor_root = get_tree()->get_edited_scene_root();50955096if (!editor_root || selection.is_empty()) {5097return;5098}50995100undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));5101for (const KeyValue<ObjectID, Object *> &E : selection) {5102Node2D *n2d = ObjectDB::get_instance<Node2D>(E.key);5103if (!n2d) {5104continue;5105}51065107Bone2D *new_bone = memnew(Bone2D);5108String new_bone_name = n2d->get_name();5109new_bone_name += "Bone2D";5110new_bone->set_name(new_bone_name);5111new_bone->set_transform(n2d->get_transform());51125113Node *n2d_parent = n2d->get_parent();5114if (!n2d_parent) {5115continue;5116}51175118undo_redo->add_do_method(n2d_parent, "add_child", new_bone);5119undo_redo->add_do_method(n2d_parent, "remove_child", n2d);5120undo_redo->add_do_method(new_bone, "add_child", n2d);5121undo_redo->add_do_method(n2d, "set_transform", Transform2D());5122undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root);5123undo_redo->add_do_reference(new_bone);51245125undo_redo->add_undo_method(new_bone, "remove_child", n2d);5126undo_redo->add_undo_method(n2d_parent, "add_child", n2d);5127undo_redo->add_undo_method(n2d_parent, "remove_child", new_bone);5128undo_redo->add_undo_method(n2d, "set_transform", new_bone->get_transform());5129undo_redo->add_undo_method(this, "_set_owner_for_node_and_children", n2d, editor_root);5130}5131undo_redo->commit_action();51325133} break;5134case AUTO_RESAMPLE_CANVAS_ITEMS: {5135auto_resampling_enabled = !auto_resampling_enabled;5136int idx = view_menu->get_popup()->get_item_index(AUTO_RESAMPLE_CANVAS_ITEMS);5137view_menu->get_popup()->set_item_checked(idx, auto_resampling_enabled);5138if (!resample_timer->is_stopped()) {5139resample_timer->stop();5140}5141_update_oversampling();5142EditorSettings::get_singleton()->set_project_metadata("2d_editor", "auto_resampling_enabled", auto_resampling_enabled);5143} break;5144}5145}51465147void CanvasItemEditor::_set_owner_for_node_and_children(Node *p_node, Node *p_owner) {5148p_node->set_owner(p_owner);5149for (int i = 0; i < p_node->get_child_count(); i++) {5150_set_owner_for_node_and_children(p_node->get_child(i), p_owner);5151}5152}51535154void CanvasItemEditor::_focus_selection(int p_op) {5155Rect2 rect;5156int count = 0;51575158const HashMap<ObjectID, Object *> &selection = editor_selection->get_selection();5159for (const KeyValue<ObjectID, Object *> &E : selection) {5160CanvasItem *ci = ObjectDB::get_instance<CanvasItem>(E.key);5161if (!ci) {5162continue;5163}5164const Transform2D canvas_item_transform = ci->get_global_transform();5165if (!canvas_item_transform.is_finite()) {5166continue;5167}5168Rect2 item_rect;5169if (ci->_edit_use_rect()) {5170item_rect = ci->_edit_get_rect();5171} else {5172item_rect = Rect2();5173}5174Vector2 pos = canvas_item_transform.get_origin();5175const Vector2 scale = canvas_item_transform.get_scale();5176const real_t angle = canvas_item_transform.get_rotation();5177pos = ci->get_viewport()->get_popup_base_transform().xform(pos);51785179Transform2D t(angle, Vector2(0.f, 0.f));5180item_rect = t.xform(item_rect);5181Rect2 canvas_item_rect(pos + scale * item_rect.position, scale * item_rect.size);5182if (count == 0) {5183rect = canvas_item_rect;5184} else {5185rect = rect.merge(canvas_item_rect);5186}5187count++;5188}51895190if (p_op == VIEW_FRAME_TO_SELECTION && rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) {5191real_t scale_x = viewport->get_size().x / rect.size.x;5192real_t scale_y = viewport->get_size().y / rect.size.y;5193zoom = scale_x < scale_y ? scale_x : scale_y;5194zoom *= 0.90;5195zoom_widget->set_zoom(zoom);5196viewport->queue_redraw(); // Redraw to update the global canvas transform after zoom changes.5197callable_mp(this, &CanvasItemEditor::center_at).call_deferred(rect.get_center()); // Defer because the updated transform is needed.5198if (auto_resampling_enabled) {5199resample_timer->start();5200}5201} else {5202center_at(rect.get_center());5203}5204}52055206void CanvasItemEditor::_reset_drag() {5207message = "";5208drag_type = DRAG_NONE;5209drag_selection.clear();5210}52115212void CanvasItemEditor::_bind_methods() {5213ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data);52145215ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);5216ClassDB::bind_method(D_METHOD("center_at", "position"), &CanvasItemEditor::center_at);52175218ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children);52195220ADD_SIGNAL(MethodInfo("item_lock_status_changed"));5221ADD_SIGNAL(MethodInfo("item_group_status_changed"));5222}52235224Dictionary CanvasItemEditor::get_state() const {5225Dictionary state;5226// Take the editor scale into account.5227state["zoom"] = zoom / MAX(1, EDSCALE);5228state["ofs"] = view_offset;5229state["grid_offset"] = grid_offset;5230state["grid_step"] = grid_step;5231state["primary_grid_step"] = primary_grid_step;5232state["snap_rotation_offset"] = snap_rotation_offset;5233state["snap_rotation_step"] = snap_rotation_step;5234state["snap_scale_step"] = snap_scale_step;5235state["smart_snap_active"] = smart_snap_active;5236state["grid_snap_active"] = grid_snap_active;5237state["snap_node_parent"] = snap_node_parent;5238state["snap_node_anchors"] = snap_node_anchors;5239state["snap_node_sides"] = snap_node_sides;5240state["snap_node_center"] = snap_node_center;5241state["snap_other_nodes"] = snap_other_nodes;5242state["snap_guides"] = snap_guides;5243state["grid_visibility"] = grid_visibility;5244state["show_origin"] = show_origin;5245state["show_viewport"] = show_viewport;5246state["show_rulers"] = show_rulers;5247state["show_guides"] = show_guides;5248state["show_helpers"] = show_helpers;5249state["show_zoom_control"] = zoom_widget->is_visible();5250state["show_position_gizmos"] = show_position_gizmos;5251state["show_lock_gizmos"] = show_lock_gizmos;5252state["show_group_gizmos"] = show_group_gizmos;5253state["show_transformation_gizmos"] = show_transformation_gizmos;5254state["snap_rotation"] = snap_rotation;5255state["snap_scale"] = snap_scale;5256state["snap_relative"] = snap_relative;5257state["snap_pixel"] = snap_pixel;5258return state;5259}52605261void CanvasItemEditor::set_state(const Dictionary &p_state) {5262bool update_scrollbars = false;5263Dictionary state = p_state;5264if (state.has("zoom")) {5265// Compensate the editor scale, so that the editor scale can be changed5266// and the zoom level will still be the same (relative to the editor scale).5267zoom = real_t(p_state["zoom"]) * MAX(1, EDSCALE);5268zoom_widget->set_zoom(zoom);5269if (auto_resampling_enabled) {5270resample_timer->start();5271}5272}52735274if (state.has("ofs")) {5275view_offset = p_state["ofs"];5276previous_update_view_offset = view_offset;5277update_scrollbars = true;5278}52795280if (state.has("grid_offset")) {5281grid_offset = state["grid_offset"];5282}52835284if (state.has("grid_step")) {5285grid_step = state["grid_step"];5286}52875288#ifndef DISABLE_DEPRECATED5289if (state.has("primary_grid_steps")) {5290primary_grid_step.x = state["primary_grid_steps"];5291primary_grid_step.y = state["primary_grid_steps"];5292}5293#endif // DISABLE_DEPRECATED52945295if (state.has("primary_grid_step")) {5296primary_grid_step = state["primary_grid_step"];5297}52985299if (state.has("snap_rotation_step")) {5300snap_rotation_step = state["snap_rotation_step"];5301}53025303if (state.has("snap_rotation_offset")) {5304snap_rotation_offset = state["snap_rotation_offset"];5305}53065307if (state.has("snap_scale_step")) {5308snap_scale_step = state["snap_scale_step"];5309}53105311if (state.has("smart_snap_active")) {5312smart_snap_active = state["smart_snap_active"];5313smart_snap_button->set_pressed(smart_snap_active);5314}53155316if (state.has("grid_snap_active")) {5317grid_snap_active = state["grid_snap_active"];5318grid_snap_button->set_pressed(grid_snap_active);5319}53205321if (state.has("snap_node_parent")) {5322snap_node_parent = state["snap_node_parent"];5323int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);5324smartsnap_config_popup->set_item_checked(idx, snap_node_parent);5325}53265327if (state.has("snap_node_anchors")) {5328snap_node_anchors = state["snap_node_anchors"];5329int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);5330smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);5331}53325333if (state.has("snap_node_sides")) {5334snap_node_sides = state["snap_node_sides"];5335int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);5336smartsnap_config_popup->set_item_checked(idx, snap_node_sides);5337}53385339if (state.has("snap_node_center")) {5340snap_node_center = state["snap_node_center"];5341int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);5342smartsnap_config_popup->set_item_checked(idx, snap_node_center);5343}53445345if (state.has("snap_other_nodes")) {5346snap_other_nodes = state["snap_other_nodes"];5347int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);5348smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);5349}53505351if (state.has("snap_guides")) {5352snap_guides = state["snap_guides"];5353int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);5354smartsnap_config_popup->set_item_checked(idx, snap_guides);5355}53565357if (state.has("grid_visibility")) {5358grid_visibility = (GridVisibility)(int)(state["grid_visibility"]);5359}53605361if (state.has("show_origin")) {5362show_origin = state["show_origin"];5363int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);5364view_menu->get_popup()->set_item_checked(idx, show_origin);5365}53665367if (state.has("show_viewport")) {5368show_viewport = state["show_viewport"];5369int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);5370view_menu->get_popup()->set_item_checked(idx, show_viewport);5371}53725373if (state.has("show_rulers")) {5374show_rulers = state["show_rulers"];5375int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);5376view_menu->get_popup()->set_item_checked(idx, show_rulers);5377update_scrollbars = true;5378}53795380if (state.has("show_guides")) {5381show_guides = state["show_guides"];5382int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);5383view_menu->get_popup()->set_item_checked(idx, show_guides);5384}53855386if (state.has("show_helpers")) {5387show_helpers = state["show_helpers"];5388int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);5389view_menu->get_popup()->set_item_checked(idx, show_helpers);5390}53915392if (state.has("show_position_gizmos")) {5393show_position_gizmos = state["show_position_gizmos"];5394int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS);5395gizmos_menu->set_item_checked(idx, show_position_gizmos);5396}53975398if (state.has("show_lock_gizmos")) {5399show_lock_gizmos = state["show_lock_gizmos"];5400int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS);5401gizmos_menu->set_item_checked(idx, show_lock_gizmos);5402}54035404if (state.has("show_group_gizmos")) {5405show_group_gizmos = state["show_group_gizmos"];5406int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS);5407gizmos_menu->set_item_checked(idx, show_group_gizmos);5408}54095410if (state.has("show_transformation_gizmos")) {5411show_transformation_gizmos = state["show_transformation_gizmos"];5412int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS);5413gizmos_menu->set_item_checked(idx, show_transformation_gizmos);5414}54155416if (state.has("show_zoom_control")) {5417// This one is not user-controllable, but instrumentable5418zoom_widget->set_visible(state["show_zoom_control"]);5419}54205421if (state.has("snap_rotation")) {5422snap_rotation = state["snap_rotation"];5423int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);5424snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);5425}54265427if (state.has("snap_scale")) {5428snap_scale = state["snap_scale"];5429int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);5430snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);5431}54325433if (state.has("snap_relative")) {5434snap_relative = state["snap_relative"];5435int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);5436snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);5437}54385439if (state.has("snap_pixel")) {5440snap_pixel = state["snap_pixel"];5441int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);5442snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);5443}54445445if (update_scrollbars) {5446_update_scrollbars();5447}5448viewport->queue_redraw();5449}54505451void CanvasItemEditor::clear() {5452zoom = 1.0 / MAX(1, EDSCALE);5453zoom_widget->set_zoom(zoom);54545455view_offset = Point2(-150 - ruler_width_scaled, -95 - ruler_width_scaled);5456previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen.5457_update_scrollbars();54585459grid_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_offset", Vector2());5460grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_step", Vector2(8, 8));5461primary_grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "primary_grid_step", Vector2i(8, 8));5462snap_rotation_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_step", Math::deg_to_rad(15.0));5463snap_rotation_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_offset", 0.0);5464snap_scale_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_scale_step", 0.1);54655466if (auto_resampling_enabled) {5467if (resample_timer->is_inside_tree()) {5468resample_timer->start();5469} else {5470_update_oversampling();5471}5472}5473}54745475void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) {5476ERR_FAIL_NULL(p_control);5477ERR_FAIL_COND(p_control->get_parent());54785479VSeparator *sep = memnew(VSeparator);5480context_toolbar_hbox->add_child(sep);5481context_toolbar_hbox->add_child(p_control);5482context_toolbar_separators[p_control] = sep;54835484p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItemEditor::_update_context_toolbar));54855486_update_context_toolbar();5487}54885489void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) {5490ERR_FAIL_NULL(p_control);5491ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);54925493p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItemEditor::_update_context_toolbar));54945495VSeparator *sep = context_toolbar_separators[p_control];5496context_toolbar_hbox->remove_child(sep);5497context_toolbar_hbox->remove_child(p_control);5498context_toolbar_separators.erase(p_control);5499memdelete(sep);55005501_update_context_toolbar();5502}55035504void CanvasItemEditor::_update_context_toolbar() {5505bool has_visible = false;5506bool first_visible = false;55075508for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {5509Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));5510if (!child || !context_toolbar_separators.has(child)) {5511continue;5512}5513if (child->is_visible()) {5514first_visible = !has_visible;5515has_visible = true;5516}55175518VSeparator *sep = context_toolbar_separators[child];5519sep->set_visible(!first_visible && child->is_visible());5520}55215522context_toolbar_panel->set_visible(has_visible);5523}55245525void CanvasItemEditor::add_control_to_left_panel(Control *p_control) {5526left_panel_split->add_child(p_control);5527left_panel_split->move_child(p_control, 0);5528}55295530void CanvasItemEditor::add_control_to_right_panel(Control *p_control) {5531right_panel_split->add_child(p_control);5532right_panel_split->move_child(p_control, 1);5533}55345535void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) {5536left_panel_split->remove_child(p_control);5537}55385539void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) {5540right_panel_split->remove_child(p_control);5541}55425543VSplitContainer *CanvasItemEditor::get_bottom_split() {5544return bottom_split;5545}55465547void CanvasItemEditor::focus_selection() {5548_focus_selection(VIEW_CENTER_TO_SELECTION);5549}55505551void CanvasItemEditor::center_at(const Point2 &p_pos) {5552Vector2 offset = viewport->get_size() / 2 - EditorNode::get_singleton()->get_scene_root()->get_global_canvas_transform().xform(p_pos);5553view_offset -= (offset / zoom).round();5554update_viewport();5555}55565557CanvasItemEditor::CanvasItemEditor() {5558snap_target[0] = SNAP_TARGET_NONE;5559snap_target[1] = SNAP_TARGET_NONE;55605561editor_selection = EditorNode::get_singleton()->get_editor_selection();5562editor_selection->add_editor_plugin(this);5563editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));5564editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed));55655566SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position));5567SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));55685569MarginContainer *toolbar_margin = memnew(MarginContainer);5570toolbar_margin->set_theme_type_variation("MainToolBarMargin");5571add_child(toolbar_margin);55725573// A fluid container for all toolbars.5574HFlowContainer *main_flow = memnew(HFlowContainer);5575toolbar_margin->add_child(main_flow);55765577// Main toolbars.5578HBoxContainer *main_menu_hbox = memnew(HBoxContainer);5579main_menu_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5580main_flow->add_child(main_menu_hbox);55815582bottom_split = memnew(VSplitContainer);5583add_child(bottom_split);5584bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);55855586left_panel_split = memnew(HSplitContainer);5587bottom_split->add_child(left_panel_split);5588left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);55895590right_panel_split = memnew(HSplitContainer);5591left_panel_split->add_child(right_panel_split);5592right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);55935594viewport_scrollable = memnew(Control);5595right_panel_split->add_child(viewport_scrollable);5596viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS);5597viewport_scrollable->set_clip_contents(true);5598viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL);5599viewport_scrollable->set_h_size_flags(Control::SIZE_EXPAND_FILL);5600viewport_scrollable->connect(SceneStringName(draw), callable_mp(this, &CanvasItemEditor::_update_scrollbars));56015602SubViewportContainer *scene_tree = memnew(SubViewportContainer);5603viewport_scrollable->add_child(scene_tree);5604scene_tree->set_stretch(true);5605scene_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5606scene_tree->add_child(EditorNode::get_singleton()->get_scene_root());56075608controls_vb = memnew(VBoxContainer);5609controls_vb->set_begin(Point2(5, 5));56105611ED_SHORTCUT("canvas_item_editor/cancel_transform", TTRC("Cancel Transformation"), Key::ESCAPE);56125613// To ensure that scripts can parse the list of shortcuts correctly, we have to define5614// those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget).5615ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_3.125_percent", TTRC("Zoom to 3.125%"),5616{ int32_t(KeyModifierMask::SHIFT | Key::KEY_5), int32_t(KeyModifierMask::SHIFT | Key::KP_5) });56175618ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_6.25_percent", TTRC("Zoom to 6.25%"),5619{ int32_t(KeyModifierMask::SHIFT | Key::KEY_4), int32_t(KeyModifierMask::SHIFT | Key::KP_4) });56205621ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_12.5_percent", TTRC("Zoom to 12.5%"),5622{ int32_t(KeyModifierMask::SHIFT | Key::KEY_3), int32_t(KeyModifierMask::SHIFT | Key::KP_3) });56235624ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_25_percent", TTRC("Zoom to 25%"),5625{ int32_t(KeyModifierMask::SHIFT | Key::KEY_2), int32_t(KeyModifierMask::SHIFT | Key::KP_2) });56265627ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_50_percent", TTRC("Zoom to 50%"),5628{ int32_t(KeyModifierMask::SHIFT | Key::KEY_1), int32_t(KeyModifierMask::SHIFT | Key::KP_1) });56295630ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTRC("Zoom to 100%"),5631{ int32_t(Key::KEY_1), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KEY_0), int32_t(Key::KP_1), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_0) });56325633ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_200_percent", TTRC("Zoom to 200%"),5634{ int32_t(Key::KEY_2), int32_t(Key::KP_2) });56355636ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_400_percent", TTRC("Zoom to 400%"),5637{ int32_t(Key::KEY_3), int32_t(Key::KP_3) });56385639ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_800_percent", TTRC("Zoom to 800%"),5640{ int32_t(Key::KEY_4), int32_t(Key::KP_4) });56415642ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_1600_percent", TTRC("Zoom to 1600%"),5643{ int32_t(Key::KEY_5), int32_t(Key::KP_5) });56445645HBoxContainer *controls_hb = memnew(HBoxContainer);5646controls_vb->add_child(controls_hb);56475648button_center_view = memnew(Button);5649controls_hb->add_child(button_center_view);5650button_center_view->set_flat(true);5651button_center_view->set_tooltip_text(TTR("Center View"));5652button_center_view->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(VIEW_CENTER_TO_SELECTION));56535654zoom_widget = memnew(EditorZoomWidget);5655zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);5656zoom_widget->set_shortcut_context(this);5657controls_hb->add_child(zoom_widget);5658zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));56595660EditorTranslationPreviewButton *translation_preview_button = memnew(EditorTranslationPreviewButton);5661translation_preview_button->set_flat(true);5662translation_preview_button->add_theme_constant_override("outline_size", Math::ceil(2 * EDSCALE));5663translation_preview_button->add_theme_color_override("font_outline_color", Color(0, 0, 0));5664translation_preview_button->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));5665controls_hb->add_child(translation_preview_button);56665667panner.instantiate();5668panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));56695670viewport = memnew(CanvasItemEditorViewport(this));5671viewport_scrollable->add_child(viewport);5672viewport->set_mouse_filter(MOUSE_FILTER_PASS);5673viewport->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5674viewport->set_clip_contents(true);5675viewport->set_focus_mode(FOCUS_ALL);5676viewport->connect(SceneStringName(draw), callable_mp(this, &CanvasItemEditor::_draw_viewport));5677viewport->connect(SceneStringName(gui_input), callable_mp(this, &CanvasItemEditor::_gui_input_viewport));5678viewport->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));56795680h_scroll = memnew(HScrollBar);5681viewport->add_child(h_scroll);5682h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &CanvasItemEditor::_update_scroll));5683h_scroll->hide();56845685v_scroll = memnew(VScrollBar);5686viewport->add_child(v_scroll);5687v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &CanvasItemEditor::_update_scroll));5688v_scroll->hide();56895690viewport->add_child(controls_vb);56915692select_button = memnew(Button);5693select_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5694select_button->set_theme_type_variation(SceneStringName(FlatButton));5695main_menu_hbox->add_child(select_button);5696select_button->set_toggle_mode(true);5697select_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SELECT));5698select_button->set_pressed(true);5699select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTRC("Select Mode"), Key::Q, true));5700select_button->set_shortcut_context(this);5701select_button->set_accessibility_name(TTRC("Select Mode"));57025703main_menu_hbox->add_child(memnew(VSeparator));57045705move_button = memnew(Button);5706move_button->set_theme_type_variation(SceneStringName(FlatButton));5707main_menu_hbox->add_child(move_button);5708move_button->set_toggle_mode(true);5709move_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_MOVE));5710move_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/move_mode", TTRC("Move Mode"), Key::W, true));5711move_button->set_shortcut_context(this);5712move_button->set_tooltip_text(TTRC("Move Mode"));57135714rotate_button = memnew(Button);5715rotate_button->set_theme_type_variation(SceneStringName(FlatButton));5716main_menu_hbox->add_child(rotate_button);5717rotate_button->set_toggle_mode(true);5718rotate_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_ROTATE));5719rotate_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/rotate_mode", TTRC("Rotate Mode"), Key::E, true));5720rotate_button->set_shortcut_context(this);5721rotate_button->set_tooltip_text(TTRC("Rotate Mode"));57225723scale_button = memnew(Button);5724scale_button->set_theme_type_variation(SceneStringName(FlatButton));5725main_menu_hbox->add_child(scale_button);5726scale_button->set_toggle_mode(true);5727scale_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SCALE));5728scale_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/scale_mode", TTRC("Scale Mode"), Key::R, true));5729scale_button->set_shortcut_context(this);5730scale_button->set_tooltip_text(TTRC("Shift: Scale proportionally."));5731scale_button->set_accessibility_name(TTRC("Scale Mode"));57325733main_menu_hbox->add_child(memnew(VSeparator));57345735list_select_button = memnew(Button);5736list_select_button->set_theme_type_variation(SceneStringName(FlatButton));5737main_menu_hbox->add_child(list_select_button);5738list_select_button->set_toggle_mode(true);5739list_select_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_LIST_SELECT));5740list_select_button->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));57415742pivot_button = memnew(Button);5743pivot_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5744pivot_button->set_theme_type_variation(SceneStringName(FlatButton));5745main_menu_hbox->add_child(pivot_button);5746pivot_button->set_toggle_mode(true);5747pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));5748pivot_button->set_accessibility_name(TTRC("Change Pivot"));57495750pan_button = memnew(Button);5751pan_button->set_theme_type_variation(SceneStringName(FlatButton));5752main_menu_hbox->add_child(pan_button);5753pan_button->set_toggle_mode(true);5754pan_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_PAN));5755pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTRC("Pan Mode"), Key::G));5756pan_button->set_shortcut_context(this);5757pan_button->set_tooltip_text(TTRC("You can also use Pan View shortcut (Space by default) to pan in any mode."));5758pan_button->set_accessibility_name(TTRC("Pan View"));57595760ruler_button = memnew(Button);5761ruler_button->set_theme_type_variation(SceneStringName(FlatButton));5762main_menu_hbox->add_child(ruler_button);5763ruler_button->set_toggle_mode(true);5764ruler_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_RULER));5765ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTRC("Ruler Mode"), Key::M));5766ruler_button->set_shortcut_context(this);5767ruler_button->set_tooltip_text(TTRC("Ruler Mode"));57685769main_menu_hbox->add_child(memnew(VSeparator));57705771local_space_button = memnew(Button);5772local_space_button->set_theme_type_variation(SceneStringName(FlatButton));5773main_menu_hbox->add_child(local_space_button);5774local_space_button->set_toggle_mode(true);5775local_space_button->set_pressed_no_signal(true);5776local_space_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_local_space));5777local_space_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_local_space", TTRC("Use Local Space"), Key::T));5778local_space_button->set_shortcut_context(this);5779local_space_button->set_accessibility_name(TTRC("Use Local Space"));57805781smart_snap_button = memnew(Button);5782smart_snap_button->set_theme_type_variation(SceneStringName(FlatButton));5783main_menu_hbox->add_child(smart_snap_button);5784smart_snap_button->set_toggle_mode(true);5785smart_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));5786smart_snap_button->set_tooltip_text(TTRC("Toggle smart snapping."));5787smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTRC("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S));5788smart_snap_button->set_shortcut_context(this);57895790grid_snap_button = memnew(Button);5791grid_snap_button->set_theme_type_variation(SceneStringName(FlatButton));5792main_menu_hbox->add_child(grid_snap_button);5793grid_snap_button->set_toggle_mode(true);5794grid_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));5795grid_snap_button->set_tooltip_text(TTRC("Toggle grid snapping."));5796grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTRC("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G));5797grid_snap_button->set_shortcut_context(this);57985799snap_config_menu = memnew(MenuButton);5800snap_config_menu->set_flat(false);5801snap_config_menu->set_theme_type_variation("FlatMenuButton");5802snap_config_menu->set_shortcut_context(this);5803main_menu_hbox->add_child(snap_config_menu);5804snap_config_menu->set_h_size_flags(SIZE_SHRINK_END);5805snap_config_menu->set_tooltip_text(TTRC("Snapping Options"));5806snap_config_menu->set_switch_on_hover(true);58075808PopupMenu *p = snap_config_menu->get_popup();5809p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5810p->set_hide_on_checkable_item_selection(false);5811p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_rotation_snap", TTRC("Use Rotation Snap")), SNAP_USE_ROTATION);5812p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_scale_snap", TTRC("Use Scale Snap")), SNAP_USE_SCALE);5813p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_relative", TTRC("Snap Relative")), SNAP_RELATIVE);5814p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_pixel_snap", TTRC("Use Pixel Snap")), SNAP_USE_PIXEL);58155816smartsnap_config_popup = memnew(PopupMenu);5817smartsnap_config_popup->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5818smartsnap_config_popup->set_hide_on_checkable_item_selection(false);5819smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_parent", TTRC("Snap to Parent")), SNAP_USE_NODE_PARENT);5820smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_anchors", TTRC("Snap to Node Anchor")), SNAP_USE_NODE_ANCHORS);5821smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_sides", TTRC("Snap to Node Sides")), SNAP_USE_NODE_SIDES);5822smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_center", TTRC("Snap to Node Center")), SNAP_USE_NODE_CENTER);5823smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_other_nodes", TTRC("Snap to Other Nodes")), SNAP_USE_OTHER_NODES);5824smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_guides", TTRC("Snap to Guides")), SNAP_USE_GUIDES);5825p->add_submenu_node_item(TTRC("Smart Snapping"), smartsnap_config_popup);58265827p->add_separator();5828p->add_shortcut(ED_SHORTCUT("canvas_item_editor/configure_snap", TTRC("Configure Snap...")), SNAP_CONFIGURE);58295830main_menu_hbox->add_child(memnew(VSeparator));58315832lock_button = memnew(Button);5833lock_button->set_theme_type_variation(SceneStringName(FlatButton));5834lock_button->set_accessibility_name(TTRC("Lock"));5835main_menu_hbox->add_child(lock_button);58365837lock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED));5838lock_button->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));5839// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5840lock_button->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));58415842unlock_button = memnew(Button);5843unlock_button->set_accessibility_name(TTRC("Unlock"));5844unlock_button->set_theme_type_variation(SceneStringName(FlatButton));5845main_menu_hbox->add_child(unlock_button);5846unlock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED));5847unlock_button->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));5848// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5849unlock_button->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));58505851group_button = memnew(Button);5852group_button->set_accessibility_name(TTRC("Group"));5853group_button->set_theme_type_variation(SceneStringName(FlatButton));5854main_menu_hbox->add_child(group_button);5855group_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED));5856group_button->set_tooltip_text(TTRC("Groups the selected node with its children. This causes the parent to be selected when any child node is clicked in 2D and 3D view."));5857// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5858group_button->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));58595860ungroup_button = memnew(Button);5861ungroup_button->set_accessibility_name(TTRC("Ungroup"));5862ungroup_button->set_theme_type_variation(SceneStringName(FlatButton));5863main_menu_hbox->add_child(ungroup_button);5864ungroup_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED));5865ungroup_button->set_tooltip_text(TTRC("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view."));5866// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5867ungroup_button->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));58685869main_menu_hbox->add_child(memnew(VSeparator));58705871skeleton_menu = memnew(MenuButton);5872skeleton_menu->set_flat(false);5873skeleton_menu->set_theme_type_variation("FlatMenuButton");5874skeleton_menu->set_shortcut_context(this);5875main_menu_hbox->add_child(skeleton_menu);5876skeleton_menu->set_tooltip_text(TTRC("Skeleton Options"));5877skeleton_menu->set_switch_on_hover(true);58785879p = skeleton_menu->get_popup();5880p->set_hide_on_checkable_item_selection(false);5881p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTRC("Show Bones")), SKELETON_SHOW_BONES);5882p->add_separator();5883p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTRC("Make Bone2D Node(s) from Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B), SKELETON_MAKE_BONES);5884p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));58855886main_menu_hbox->add_child(memnew(VSeparator));58875888view_menu = memnew(MenuButton);5889view_menu->set_flat(false);5890view_menu->set_theme_type_variation("FlatMenuButton");5891// TRANSLATORS: Noun, name of the 2D/3D View menus.5892view_menu->set_text(TTRC("View"));5893view_menu->set_switch_on_hover(true);5894view_menu->set_shortcut_context(this);5895main_menu_hbox->add_child(view_menu);58965897p = view_menu->get_popup();5898p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5899p->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_view_menu));5900p->set_hide_on_checkable_item_selection(false);59015902grid_menu = memnew(PopupMenu);5903grid_menu->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_grid_menu));5904grid_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_on_grid_menu_id_pressed));5905grid_menu->add_radio_check_item(TTRC("Show"), GRID_VISIBILITY_SHOW);5906grid_menu->add_radio_check_item(TTRC("Show When Snapping"), GRID_VISIBILITY_SHOW_WHEN_SNAPPING);5907grid_menu->add_radio_check_item(TTRC("Hide"), GRID_VISIBILITY_HIDE);5908grid_menu->add_separator();5909grid_menu->add_shortcut(ED_SHORTCUT("canvas_item_editor/toggle_grid", TTRC("Toggle Grid"), KeyModifierMask::CMD_OR_CTRL | Key::APOSTROPHE));5910p->add_submenu_node_item(TTRC("Grid"), grid_menu);59115912p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTRC("Show Helpers"), Key::H), SHOW_HELPERS);5913p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTRC("Show Rulers")), SHOW_RULERS);5914p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTRC("Show Guides"), Key::Y), SHOW_GUIDES);5915p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTRC("Show Origin")), SHOW_ORIGIN);5916p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTRC("Show Viewport")), SHOW_VIEWPORT);5917p->add_separator();59185919gizmos_menu = memnew(PopupMenu);5920gizmos_menu->set_name("GizmosMenu");5921gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5922gizmos_menu->set_hide_on_checkable_item_selection(false);5923gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_position_gizmos", TTRC("Position")), SHOW_POSITION_GIZMOS);5924gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_lock_gizmos", TTRC("Lock")), SHOW_LOCK_GIZMOS);5925gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_group_gizmos", TTRC("Group")), SHOW_GROUP_GIZMOS);5926gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTRC("Transformation")), SHOW_TRANSFORMATION_GIZMOS);5927p->add_child(gizmos_menu);5928p->add_submenu_item(TTRC("Gizmos"), "GizmosMenu");59295930p->add_separator();5931p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTRC("Center Selection"), Key::F), VIEW_CENTER_TO_SELECTION);5932p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTRC("Frame Selection"), KeyModifierMask::SHIFT | Key::F), VIEW_FRAME_TO_SELECTION);5933p->add_shortcut(ED_SHORTCUT("canvas_item_editor/clear_guides", TTRC("Clear Guides")), CLEAR_GUIDES);59345935p->add_separator();5936auto_resampling_enabled = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "auto_resampling_enabled", true);5937p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/auto_resample_canvas_items", TTRC("Auto Resample CanvasItems")), AUTO_RESAMPLE_CANVAS_ITEMS);5938p->set_item_checked(p->get_item_index(AUTO_RESAMPLE_CANVAS_ITEMS), auto_resampling_enabled);5939p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTRC("Preview Canvas Scale")), PREVIEW_CANVAS_SCALE);59405941theme_menu = memnew(PopupMenu);5942theme_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_switch_theme_preview));5943theme_menu->add_radio_check_item(TTRC("Project theme"), THEME_PREVIEW_PROJECT);5944theme_menu->add_radio_check_item(TTRC("Editor theme"), THEME_PREVIEW_EDITOR);5945theme_menu->add_radio_check_item(TTRC("Default theme"), THEME_PREVIEW_DEFAULT);5946p->add_submenu_node_item(TTRC("Preview Theme"), theme_menu);59475948theme_preview = (ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", THEME_PREVIEW_PROJECT);5949for (int i = 0; i < THEME_PREVIEW_MAX; i++) {5950theme_menu->set_item_checked(i, i == theme_preview);5951}59525953p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));59545955main_menu_hbox->add_child(memnew(VSeparator));59565957// Contextual toolbars.5958context_toolbar_panel = memnew(PanelContainer);5959context_toolbar_hbox = memnew(HBoxContainer);5960context_toolbar_panel->add_child(context_toolbar_hbox);5961main_flow->add_child(context_toolbar_panel);59625963// Animation controls.5964animation_hb = memnew(HBoxContainer);5965add_control_to_menu_panel(animation_hb);5966animation_hb->hide();59675968key_loc_button = memnew(Button);5969key_loc_button->set_theme_type_variation(SceneStringName(FlatButton));5970key_loc_button->set_toggle_mode(true);5971key_loc_button->set_pressed(true);5972key_loc_button->set_focus_mode(FOCUS_ACCESSIBILITY);5973key_loc_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_POS));5974key_loc_button->set_tooltip_text(TTRC("Translation mask for inserting keys."));5975animation_hb->add_child(key_loc_button);59765977key_rot_button = memnew(Button);5978key_rot_button->set_theme_type_variation(SceneStringName(FlatButton));5979key_rot_button->set_toggle_mode(true);5980key_rot_button->set_pressed(true);5981key_rot_button->set_focus_mode(FOCUS_ACCESSIBILITY);5982key_rot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_ROT));5983key_rot_button->set_tooltip_text(TTRC("Rotation mask for inserting keys."));5984animation_hb->add_child(key_rot_button);59855986key_scale_button = memnew(Button);5987key_scale_button->set_theme_type_variation(SceneStringName(FlatButton));5988key_scale_button->set_toggle_mode(true);5989key_scale_button->set_focus_mode(FOCUS_ACCESSIBILITY);5990key_scale_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_SCALE));5991key_scale_button->set_tooltip_text(TTRC("Scale mask for inserting keys."));5992animation_hb->add_child(key_scale_button);59935994key_insert_button = memnew(Button);5995key_insert_button->set_theme_type_variation(SceneStringName(FlatButton));5996key_insert_button->set_focus_mode(FOCUS_ACCESSIBILITY);5997key_insert_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_KEY));5998key_insert_button->set_tooltip_text(TTRC("Insert keys (based on mask)."));5999key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTRC("Insert Key"), Key::INSERT));6000key_insert_button->set_shortcut_context(this);6001animation_hb->add_child(key_insert_button);60026003key_auto_insert_button = memnew(Button);6004key_auto_insert_button->set_theme_type_variation(SceneStringName(FlatButton));6005key_auto_insert_button->set_toggle_mode(true);6006key_auto_insert_button->set_focus_mode(FOCUS_ACCESSIBILITY);6007key_auto_insert_button->set_tooltip_text(TTRC("Auto insert keys when objects are translated, rotated or scaled (based on mask).\nKeys are only added to existing tracks, no new tracks will be created.\nKeys must be inserted manually for the first time."));6008key_auto_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_auto_insert_key", TTRC("Auto Insert Key")));6009key_auto_insert_button->set_accessibility_name(TTRC("Auto Insert Key"));6010key_auto_insert_button->set_shortcut_context(this);6011animation_hb->add_child(key_auto_insert_button);60126013animation_menu = memnew(MenuButton);6014animation_menu->set_flat(false);6015animation_menu->set_theme_type_variation("FlatMenuButton");6016animation_menu->set_shortcut_context(this);6017animation_menu->set_tooltip_text(TTRC("Animation Key and Pose Options"));6018animation_hb->add_child(animation_menu);6019animation_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));6020animation_menu->set_switch_on_hover(true);60216022p = animation_menu->get_popup();60236024p->add_shortcut(ED_GET_SHORTCUT("canvas_item_editor/anim_insert_key"), ANIM_INSERT_KEY);6025p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTRC("Insert Key (Existing Tracks)"), KeyModifierMask::CMD_OR_CTRL + Key::INSERT), ANIM_INSERT_KEY_EXISTING);6026p->add_separator();6027p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_copy_pose", TTRC("Copy Pose")), ANIM_COPY_POSE);6028p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_paste_pose", TTRC("Paste Pose")), ANIM_PASTE_POSE);6029p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_clear_pose", TTRC("Clear Pose"), KeyModifierMask::SHIFT | Key::K), ANIM_CLEAR_POSE);60306031snap_dialog = memnew(SnapDialog);6032snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &CanvasItemEditor::_snap_changed));6033add_child(snap_dialog);60346035select_sb.instantiate();60366037selection_menu = memnew(PopupMenu);6038add_child(selection_menu);6039selection_menu->set_min_size(Vector2(100, 0));6040selection_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);6041selection_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_selection_result_pressed));6042selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED);60436044add_node_menu = memnew(PopupMenu);6045add_child(add_node_menu);6046add_node_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_add_node_pressed));60476048resample_timer = memnew(Timer);6049resample_timer->set_wait_time(resample_delay);6050resample_timer->set_one_shot(true);6051add_child(resample_timer);6052resample_timer->connect("timeout", callable_mp(this, &CanvasItemEditor::_update_oversampling));60536054multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTRC("Multiply grid step by 2"), Key::KP_MULTIPLY);6055divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTRC("Divide grid step by 2"), Key::KP_DIVIDE);6056reset_transform_position_shortcut = ED_SHORTCUT("canvas_item_editor/reset_transform_position", TTRC("Reset Position"), KeyModifierMask::ALT + Key::W);6057reset_transform_rotation_shortcut = ED_SHORTCUT("canvas_item_editor/reset_transform_rotation", TTRC("Reset Rotation"), KeyModifierMask::ALT + Key::E);6058reset_transform_scale_shortcut = ED_SHORTCUT("canvas_item_editor/reset_transform_scale", TTRC("Reset Scale"), KeyModifierMask::ALT + Key::R);60596060skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true);60616062// Store the singleton instance.6063singleton = this;60646065set_process_shortcut_input(true);6066clear(); // Make sure values are initialized.60676068// Update the menus' checkboxes.6069callable_mp(this, &CanvasItemEditor::set_state).call_deferred(get_state());6070}60716072CanvasItemEditor *CanvasItemEditor::singleton = nullptr;60736074void CanvasItemEditorPlugin::edit(Object *p_object) {6075canvas_item_editor->edit(Object::cast_to<CanvasItem>(p_object));6076}60776078bool CanvasItemEditorPlugin::handles(Object *p_object) const {6079return p_object->is_class("CanvasItem");6080}60816082void CanvasItemEditorPlugin::make_visible(bool p_visible) {6083if (p_visible) {6084canvas_item_editor->show();6085canvas_item_editor->set_process(true);6086RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), false);6087RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_ENABLED);60886089} else {6090canvas_item_editor->hide();6091canvas_item_editor->set_process(false);6092RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), true);6093RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_DISABLED);6094}6095}60966097Dictionary CanvasItemEditorPlugin::get_state() const {6098return canvas_item_editor->get_state();6099}61006101void CanvasItemEditorPlugin::set_state(const Dictionary &p_state) {6102canvas_item_editor->set_state(p_state);6103}61046105void CanvasItemEditorPlugin::clear() {6106canvas_item_editor->clear();6107}61086109void CanvasItemEditorPlugin::_notification(int p_what) {6110switch (p_what) {6111case NOTIFICATION_ENTER_TREE: {6112connect("scene_changed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));6113connect("scene_closed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));6114} break;6115}6116}61176118CanvasItemEditorPlugin::CanvasItemEditorPlugin() {6119canvas_item_editor = memnew(CanvasItemEditor);6120canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);6121EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(canvas_item_editor);6122canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);6123canvas_item_editor->hide();6124}61256126void CanvasItemEditorViewport::_on_mouse_exit() {6127if (!texture_node_type_selector->is_visible()) {6128_remove_preview();6129}6130}61316132void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) {6133CheckBox *check = Object::cast_to<CheckBox>(selected);6134String type = check->get_text();6135texture_node_type_selector->set_title(vformat(TTR("Add %s"), type));6136label->set_text(vformat(TTR("Adding %s..."), type));6137}61386139void CanvasItemEditorViewport::_on_change_type_confirmed() {6140if (!button_group->get_pressed_button()) {6141return;6142}61436144CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button());6145default_texture_node_type = check->get_text();6146_perform_drop_data();6147texture_node_type_selector->hide();6148}61496150void CanvasItemEditorViewport::_on_change_type_closed() {6151_remove_preview();6152}61536154void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) const {6155bool add_preview = false;6156for (int i = 0; i < files.size(); i++) {6157Ref<Resource> res = ResourceLoader::load(files[i]);6158ERR_CONTINUE(res.is_null());61596160Ref<Texture2D> texture = res;6161if (texture.is_valid()) {6162Sprite2D *sprite = memnew(Sprite2D);6163sprite->set_texture(texture);6164sprite->set_modulate(Color(1, 1, 1, 0.7f));6165preview_node->add_child(sprite);6166add_preview = true;6167}61686169Ref<PackedScene> scene = res;6170if (scene.is_valid()) {6171Node *instance = scene->instantiate();6172if (instance) {6173preview_node->add_child(instance);6174}6175add_preview = true;6176}61776178Ref<AudioStream> audio = res;6179if (audio.is_valid()) {6180Sprite2D *sprite = memnew(Sprite2D);6181sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D")));6182sprite->set_modulate(Color(1, 1, 1, 0.7f));6183sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE);6184preview_node->add_child(sprite);6185add_preview = true;6186}6187}61886189if (add_preview) {6190EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);6191}6192}61936194void CanvasItemEditorViewport::_remove_preview() {6195if (!canvas_item_editor->message.is_empty()) {6196canvas_item_editor->message = "";6197canvas_item_editor->update_viewport();6198}6199if (preview_node->get_parent()) {6200for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {6201Node *node = preview_node->get_child(i);6202node->queue_free();6203preview_node->remove_child(node);6204}6205EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);62066207label->hide();6208label_desc->hide();6209}6210}62116212bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const {6213if (p_desired_node->get_scene_file_path() == p_target_scene_path) {6214return true;6215}62166217int childCount = p_desired_node->get_child_count();6218for (int i = 0; i < childCount; i++) {6219Node *child = p_desired_node->get_child(i);6220if (_cyclical_dependency_exists(p_target_scene_path, child)) {6221return true;6222}6223}6224return false;6225}62266227void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) {6228// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.6229const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());6230if (!node_name.is_empty()) {6231p_child->set_name(node_name);6232}62336234EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6235Ref<Texture2D> texture = ResourceCache::get_ref(p_path);62366237if (p_parent) {6238undo_redo->add_do_method(p_parent, "add_child", p_child, true);6239undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6240undo_redo->add_do_reference(p_child);6241undo_redo->add_undo_method(p_parent, "remove_child", p_child);6242} else { // If no parent is selected, set as root node of the scene.6243undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child);6244undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6245undo_redo->add_do_reference(p_child);6246undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);6247}62486249if (p_parent) {6250String new_name = p_parent->validate_child_name(p_child);6251EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();6252undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), p_child->get_class(), new_name);6253undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));6254}62556256if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) {6257undo_redo->add_do_property(p_child, "texture_normal", texture);6258} else {6259undo_redo->add_do_property(p_child, "texture", texture);6260}62616262// make visible for certain node type6263if (Object::cast_to<Control>(p_child)) {6264Size2 texture_size = texture->get_size();6265undo_redo->add_do_property(p_child, "size", texture_size);6266} else if (Object::cast_to<Polygon2D>(p_child)) {6267Size2 texture_size = texture->get_size();6268Vector<Vector2> list = {6269Vector2(0, 0),6270Vector2(texture_size.width, 0),6271Vector2(texture_size.width, texture_size.height),6272Vector2(0, texture_size.height)6273};6274undo_redo->add_do_property(p_child, "polygon", list);6275}62766277// Compute the global position6278Transform2D xform = canvas_item_editor->get_canvas_transform();6279Point2 target_position = xform.affine_inverse().xform(p_point);62806281// Adjust position for Control and TouchScreenButton6282if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) {6283target_position -= texture->get_size() / 2;6284}62856286// There's nothing to be used as source position, so snapping will work as absolute if enabled.6287target_position = canvas_item_editor->snap_point(target_position);62886289CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6290Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;62916292undo_redo->add_do_method(p_child, "set_position", local_target_pos);6293}62946295void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {6296AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D);6297child->set_stream(ResourceCache::get_ref(p_path));62986299// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.6300const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());6301if (!node_name.is_empty()) {6302child->set_name(node_name);6303}63046305EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();63066307if (p_parent) {6308undo_redo->add_do_method(p_parent, "add_child", child, true);6309undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6310undo_redo->add_do_reference(child);6311undo_redo->add_undo_method(p_parent, "remove_child", child);6312} else { // If no parent is selected, set as root node of the scene.6313undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);6314undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6315undo_redo->add_do_reference(child);6316undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);6317}63186319if (p_parent) {6320String new_name = p_parent->validate_child_name(child);6321EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();6322undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent), child->get_class(), new_name);6323undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));6324}63256326// Compute the global position6327Transform2D xform = canvas_item_editor->get_canvas_transform();6328Point2 target_position = xform.affine_inverse().xform(p_point);63296330// There's nothing to be used as source position, so snapping will work as absolute if enabled.6331target_position = canvas_item_editor->snap_point(target_position);63326333CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6334Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;63356336undo_redo->add_do_method(child, "set_position", local_target_pos);63376338EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6339undo_redo->add_do_method(editor_selection, "add_node", child);6340}63416342bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {6343Ref<PackedScene> sdata = ResourceLoader::load(p_path);6344if (sdata.is_null()) { // invalid scene6345return false;6346}63476348Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);6349if (!instantiated_scene) { // Error on instantiation.6350return false;6351}63526353Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();63546355if (!edited_scene->get_scene_file_path().is_empty()) { // Cyclic instantiation.6356if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {6357memdelete(instantiated_scene);6358return false;6359}6360}63616362instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));63636364EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6365EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6366undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);6367undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);6368undo_redo->add_do_reference(instantiated_scene);6369undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);6370undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);63716372String new_name = p_parent->validate_child_name(instantiated_scene);6373EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();6374undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name);6375undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name));63766377CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);6378if (instance_ci) {6379Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point);6380target_pos = canvas_item_editor->snap_point(target_pos);63816382CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6383if (parent_ci) {6384target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);6385}6386// Preserve instance position of the original scene.6387target_pos += instance_ci->_edit_get_position();63886389undo_redo->add_do_method(instantiated_scene, "set_position", target_pos);6390}63916392return true;6393}63946395void CanvasItemEditorViewport::_perform_drop_data() {6396ERR_FAIL_COND(selected_files.is_empty());63976398_remove_preview();63996400if (!target_node) {6401// Should already be handled by `can_drop_data`.6402ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root.");64036404const String &path = selected_files[0];6405Ref<Resource> res = ResourceLoader::load(path);6406if (res.is_null()) {6407return;6408}64096410Ref<PackedScene> scene = res;6411if (scene.is_valid()) {6412// Without root node act the same as "Load Inherited Scene".6413Error err = EditorNode::get_singleton()->load_scene(path, false, true);6414if (err != OK) {6415accept->set_text(vformat(TTR("Error instantiating scene from %s."), path.get_file()));6416accept->popup_centered();6417}6418return;6419}6420}64216422PackedStringArray error_files;64236424EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6425undo_redo->create_action_for_history(TTR("Create Node"), EditorNode::get_editor_data().get_current_edited_scene_history_id());6426EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6427undo_redo->add_do_method(editor_selection, "clear");64286429for (int i = 0; i < selected_files.size(); i++) {6430String path = selected_files[i];6431Ref<Resource> res = ResourceLoader::load(path);6432if (res.is_null()) {6433continue;6434}64356436Ref<PackedScene> scene = res;6437if (scene.is_valid()) {6438bool success = _create_instance(target_node, path, drop_pos);6439if (!success) {6440error_files.push_back(path.get_file());6441}6442continue;6443}64446445Ref<Texture2D> texture = res;6446if (texture.is_valid()) {6447Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type));6448_create_texture_node(target_node, child, path, drop_pos);6449undo_redo->add_do_method(editor_selection, "add_node", child);6450}64516452Ref<AudioStream> audio = res;6453if (audio.is_valid()) {6454_create_audio_node(target_node, path, drop_pos);6455}6456}64576458undo_redo->commit_action();64596460if (error_files.size() > 0) {6461accept->set_text(vformat(TTR("Error instantiating scene from %s."), String(", ").join(error_files)));6462accept->popup_centered();6463}6464}64656466bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Variant &p_data) const {6467if (p_point == Vector2(Math::INF, Math::INF)) {6468return false;6469}6470Dictionary d = p_data;6471if (!d.has("type") || (String(d["type"]) != "files")) {6472label->hide();6473return false;6474}64756476Vector<String> files = d["files"];64776478const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();6479if (!edited_scene && files.size() > 1) {6480canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root.");6481canvas_item_editor->update_viewport();6482return false;6483}64846485enum {6486SCENE = 1 << 0,6487TEXTURE = 1 << 1,6488AUDIO = 1 << 2,6489};6490int instantiate_type = 0;64916492for (const String &path : files) {6493const String &res_type = ResourceLoader::get_resource_type(path);6494String error_message;64956496if (ClassDB::is_parent_class(res_type, "PackedScene")) {6497Ref<PackedScene> scn = ResourceLoader::load(path);6498ERR_CONTINUE(scn.is_null());64996500Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);6501if (!instantiated_scene) {6502continue;6503}6504if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {6505error_message = vformat(TTR("Circular dependency found at %s."), path.get_file());6506}6507memdelete(instantiated_scene);6508instantiate_type |= SCENE;6509}6510if (ClassDB::is_parent_class(res_type, "Texture2D")) {6511instantiate_type |= TEXTURE;6512}6513if (ClassDB::is_parent_class(res_type, "AudioStream")) {6514instantiate_type |= AUDIO;6515}65166517if (!error_message.is_empty()) {6518// TRANSLATORS: The placeholder is the error message.6519canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message);6520canvas_item_editor->update_viewport();6521return false;6522}6523}6524if (instantiate_type == 0) {6525return false;6526}65276528if (!preview_node->get_parent()) { // create preview only once6529_create_preview(files);6530}6531ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);65326533const Transform2D trans = canvas_item_editor->get_canvas_transform();6534preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);65356536if (!edited_scene && instantiate_type & SCENE) {6537String scene_file_path = preview_node->get_child(0)->get_scene_file_path();6538// TRANSLATORS: The placeholder is the file path of the scene being instantiated.6539canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path);6540} else {6541double snap = EDITOR_GET("interface/inspector/default_float_step");6542int snap_step_decimals = Math::range_step_decimals(snap);6543const String &lang = _get_locale();6544#define FORMAT(value) (TranslationServer::get_singleton()->format_number(String::num(value, snap_step_decimals), lang))6545Vector2 preview_node_pos = preview_node->get_global_position();6546canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";6547}6548canvas_item_editor->update_viewport();65496550if (instantiate_type & TEXTURE && instantiate_type & AUDIO) {6551// TRANSLATORS: The placeholders are the types of nodes being instantiated.6552label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D"));6553} else {6554String node_type;6555if (instantiate_type & TEXTURE) {6556node_type = default_texture_node_type;6557} else if (instantiate_type & AUDIO) {6558node_type = "AudioStreamPlayer2D";6559}6560if (!node_type.is_empty()) {6561// TRANSLATORS: The placeholder is the type of node being instantiated.6562label->set_text(vformat(TTR("Adding %s..."), node_type));6563}6564}6565label->set_visible(instantiate_type & ~SCENE);65666567String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +6568"\n" + TTR("Hold Shift when dropping to add as child of selected node.") +6569"\n" + TTR("Hold Alt when dropping to add as child of root node.");6570if (instantiate_type & TEXTURE) {6571desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type.");6572}6573label_desc->set_text(desc);6574label_desc->show();65756576return true;6577}65786579void CanvasItemEditorViewport::_show_texture_node_type_selector() {6580_remove_preview();6581List<BaseButton *> btn_list;6582button_group->get_buttons(&btn_list);65836584for (BaseButton *btn : btn_list) {6585CheckBox *check = Object::cast_to<CheckBox>(btn);6586check->set_pressed(check->get_text() == default_texture_node_type);6587}6588texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));6589texture_node_type_selector->popup_centered();6590}65916592bool CanvasItemEditorViewport::_is_any_texture_selected() const {6593for (int i = 0; i < selected_files.size(); ++i) {6594if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) {6595return true;6596}6597}6598return false;6599}66006601void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {6602if (p_point == Vector2(Math::INF, Math::INF)) {6603return;6604}6605bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);6606bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);66076608selected_files.clear();6609Dictionary d = p_data;6610if (d.has("type") && String(d["type"]) == "files") {6611selected_files = d["files"];6612}6613if (selected_files.is_empty()) {6614return;6615}66166617const List<Node *> &selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();6618Node *root_node = EditorNode::get_singleton()->get_edited_scene();6619if (selected_nodes.size() > 0) {6620Node *selected_node = selected_nodes.front()->get();6621if (is_alt) {6622target_node = root_node;6623} else if (is_shift) {6624target_node = selected_node;6625} else { // Default behavior.6626target_node = (selected_node != root_node) ? selected_node->get_parent() : root_node;6627}6628} else {6629if (root_node) {6630target_node = root_node;6631} else {6632target_node = nullptr;6633}6634}66356636drop_pos = p_point;66376638if (is_alt && is_shift && _is_any_texture_selected()) {6639_show_texture_node_type_selector();6640} else {6641_perform_drop_data();6642}6643}66446645void CanvasItemEditorViewport::_update_theme() {6646List<BaseButton *> btn_list;6647button_group->get_buttons(&btn_list);66486649for (BaseButton *btn : btn_list) {6650CheckBox *check = Object::cast_to<CheckBox>(btn);6651check->set_button_icon(get_editor_theme_icon(check->get_text()));6652}66536654label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));6655}66566657void CanvasItemEditorViewport::_notification(int p_what) {6658switch (p_what) {6659case NOTIFICATION_THEME_CHANGED: {6660_update_theme();6661} break;66626663case NOTIFICATION_ENTER_TREE: {6664_update_theme();6665connect(SceneStringName(mouse_exited), callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));6666} break;66676668case NOTIFICATION_EXIT_TREE: {6669disconnect(SceneStringName(mouse_exited), callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));6670} break;66716672case NOTIFICATION_DRAG_END: {6673_remove_preview();6674} break;6675}6676}66776678CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor) {6679default_texture_node_type = "Sprite2D";6680// Node2D6681texture_node_types.push_back("Sprite2D");6682texture_node_types.push_back("PointLight2D");6683texture_node_types.push_back("CPUParticles2D");6684texture_node_types.push_back("GPUParticles2D");6685texture_node_types.push_back("Polygon2D");6686texture_node_types.push_back("TouchScreenButton");6687// Control6688texture_node_types.push_back("TextureRect");6689texture_node_types.push_back("TextureButton");6690texture_node_types.push_back("NinePatchRect");66916692target_node = nullptr;6693canvas_item_editor = p_canvas_item_editor;6694preview_node = memnew(Control);66956696accept = memnew(AcceptDialog);6697EditorNode::get_singleton()->get_gui_base()->add_child(accept);66986699texture_node_type_selector = memnew(AcceptDialog);6700EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector);6701texture_node_type_selector->connect(SceneStringName(confirmed), callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));6702texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));67036704VBoxContainer *vbc = memnew(VBoxContainer);6705texture_node_type_selector->add_child(vbc);6706vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);6707vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);6708vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE);67096710VBoxContainer *btn_group = memnew(VBoxContainer);6711vbc->add_child(btn_group);6712btn_group->set_h_size_flags(SIZE_EXPAND_FILL);67136714button_group.instantiate();6715for (int i = 0; i < texture_node_types.size(); i++) {6716CheckBox *check = memnew(CheckBox);6717check->set_text(texture_node_types[i]);6718check->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);6719check->set_button_group(button_group);6720btn_group->add_child(check);6721check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check));6722}67236724label = memnew(Label);6725label->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1));6726label->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);6727label->hide();6728canvas_item_editor->get_controls_container()->add_child(label);67296730label_desc = memnew(Label);6731label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);6732label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.6f, 0.6f, 0.6f, 1));6733label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));6734label_desc->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);6735label_desc->add_theme_constant_override("line_spacing", 0);6736label_desc->hide();6737canvas_item_editor->get_controls_container()->add_child(label_desc);67386739RS::get_singleton()->canvas_set_disable_scale(true);6740}67416742CanvasItemEditorViewport::~CanvasItemEditorViewport() {6743memdelete(preview_node);6744}674567466747