Path: blob/master/editor/scene/canvas_item_editor_plugin.cpp
9896 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 "editor/animation/animation_player_editor_plugin.h"36#include "editor/debugger/editor_debugger_node.h"37#include "editor/docks/scene_tree_dock.h"38#include "editor/editor_main_screen.h"39#include "editor/editor_node.h"40#include "editor/editor_string_names.h"41#include "editor/editor_undo_redo_manager.h"42#include "editor/gui/editor_toaster.h"43#include "editor/gui/editor_zoom_widget.h"44#include "editor/inspector/editor_context_menu_plugin.h"45#include "editor/run/editor_run_bar.h"46#include "editor/script/script_editor_plugin.h"47#include "editor/settings/editor_settings.h"48#include "editor/themes/editor_scale.h"49#include "editor/themes/editor_theme_manager.h"50#include "editor/translations/editor_translation_preview_button.h"51#include "editor/translations/editor_translation_preview_menu.h"52#include "scene/2d/audio_stream_player_2d.h"53#include "scene/2d/physics/touch_screen_button.h"54#include "scene/2d/polygon_2d.h"55#include "scene/2d/skeleton_2d.h"56#include "scene/2d/sprite_2d.h"57#include "scene/gui/base_button.h"58#include "scene/gui/flow_container.h"59#include "scene/gui/grid_container.h"60#include "scene/gui/separator.h"61#include "scene/gui/split_container.h"62#include "scene/gui/subviewport_container.h"63#include "scene/gui/view_panner.h"64#include "scene/main/canvas_layer.h"65#include "scene/main/window.h"66#include "scene/resources/packed_scene.h"67#include "scene/resources/style_box_texture.h"6869#define DRAG_THRESHOLD (8 * EDSCALE)70constexpr real_t SCALE_HANDLE_DISTANCE = 25;71constexpr real_t MOVE_HANDLE_DISTANCE = 25;7273class SnapDialog : public ConfirmationDialog {74GDCLASS(SnapDialog, ConfirmationDialog);7576friend class CanvasItemEditor;7778SpinBox *grid_offset_x;79SpinBox *grid_offset_y;80SpinBox *grid_step_x;81SpinBox *grid_step_y;82SpinBox *primary_grid_step_x;83SpinBox *primary_grid_step_y;84SpinBox *rotation_offset;85SpinBox *rotation_step;86SpinBox *scale_step;8788public:89SnapDialog() {90const int SPIN_BOX_GRID_RANGE = 16384;91const int SPIN_BOX_ROTATION_RANGE = 360;92const real_t SPIN_BOX_SCALE_MIN = 0.01;93const real_t SPIN_BOX_SCALE_MAX = 100;9495Label *label;96VBoxContainer *container;97GridContainer *child_container;9899set_title(TTRC("Configure Snap"));100101container = memnew(VBoxContainer);102add_child(container);103104child_container = memnew(GridContainer);105child_container->set_columns(3);106container->add_child(child_container);107108label = memnew(Label);109label->set_text(TTRC("Grid Offset:"));110child_container->add_child(label);111label->set_h_size_flags(Control::SIZE_EXPAND_FILL);112113grid_offset_x = memnew(SpinBox);114grid_offset_x->set_min(-SPIN_BOX_GRID_RANGE);115grid_offset_x->set_max(SPIN_BOX_GRID_RANGE);116grid_offset_x->set_allow_lesser(true);117grid_offset_x->set_allow_greater(true);118grid_offset_x->set_suffix("px");119grid_offset_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);120grid_offset_x->set_select_all_on_focus(true);121grid_offset_x->set_accessibility_name(TTRC("X Offset"));122child_container->add_child(grid_offset_x);123124grid_offset_y = memnew(SpinBox);125grid_offset_y->set_min(-SPIN_BOX_GRID_RANGE);126grid_offset_y->set_max(SPIN_BOX_GRID_RANGE);127grid_offset_y->set_allow_lesser(true);128grid_offset_y->set_allow_greater(true);129grid_offset_y->set_suffix("px");130grid_offset_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);131grid_offset_y->set_select_all_on_focus(true);132grid_offset_y->set_accessibility_name(TTRC("Y Offset"));133child_container->add_child(grid_offset_y);134135label = memnew(Label);136label->set_text(TTRC("Grid Step:"));137child_container->add_child(label);138label->set_h_size_flags(Control::SIZE_EXPAND_FILL);139140grid_step_x = memnew(SpinBox);141grid_step_x->set_min(1);142grid_step_x->set_max(SPIN_BOX_GRID_RANGE);143grid_step_x->set_allow_greater(true);144grid_step_x->set_suffix("px");145grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);146grid_step_x->set_select_all_on_focus(true);147grid_step_x->set_accessibility_name(TTRC("X Step"));148child_container->add_child(grid_step_x);149150grid_step_y = memnew(SpinBox);151grid_step_y->set_min(1);152grid_step_y->set_max(SPIN_BOX_GRID_RANGE);153grid_step_y->set_allow_greater(true);154grid_step_y->set_suffix("px");155grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);156grid_step_y->set_select_all_on_focus(true);157grid_step_y->set_accessibility_name(TTRC("X Step"));158child_container->add_child(grid_step_y);159160label = memnew(Label);161label->set_text(TTRC("Primary Line Every:"));162label->set_h_size_flags(Control::SIZE_EXPAND_FILL);163child_container->add_child(label);164165primary_grid_step_x = memnew(SpinBox);166primary_grid_step_x->set_min(1);167primary_grid_step_x->set_step(1);168primary_grid_step_x->set_max(SPIN_BOX_GRID_RANGE);169primary_grid_step_x->set_allow_greater(true);170primary_grid_step_x->set_suffix("steps");171primary_grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);172primary_grid_step_x->set_select_all_on_focus(true);173primary_grid_step_x->set_accessibility_name(TTRC("X Primary Step"));174child_container->add_child(primary_grid_step_x);175176primary_grid_step_y = memnew(SpinBox);177primary_grid_step_y->set_min(1);178primary_grid_step_y->set_step(1);179primary_grid_step_y->set_max(SPIN_BOX_GRID_RANGE);180primary_grid_step_y->set_allow_greater(true);181primary_grid_step_y->set_suffix(TTRC("steps")); // TODO: Add suffix auto-translation.182primary_grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);183primary_grid_step_y->set_select_all_on_focus(true);184primary_grid_step_y->set_accessibility_name(TTRC("Y Primary Step"));185child_container->add_child(primary_grid_step_y);186187container->add_child(memnew(HSeparator));188189// We need to create another GridContainer with the same column count,190// so we can put an HSeparator above191child_container = memnew(GridContainer);192child_container->set_columns(2);193container->add_child(child_container);194195label = memnew(Label);196label->set_text(TTRC("Rotation Offset:"));197child_container->add_child(label);198label->set_h_size_flags(Control::SIZE_EXPAND_FILL);199200rotation_offset = memnew(SpinBox);201rotation_offset->set_min(-SPIN_BOX_ROTATION_RANGE);202rotation_offset->set_max(SPIN_BOX_ROTATION_RANGE);203rotation_offset->set_suffix(U"°");204rotation_offset->set_h_size_flags(Control::SIZE_EXPAND_FILL);205rotation_offset->set_select_all_on_focus(true);206rotation_offset->set_accessibility_name(TTRC("Rotation Offset:"));207child_container->add_child(rotation_offset);208209label = memnew(Label);210label->set_text(TTRC("Rotation Step:"));211child_container->add_child(label);212label->set_h_size_flags(Control::SIZE_EXPAND_FILL);213214rotation_step = memnew(SpinBox);215rotation_step->set_min(-SPIN_BOX_ROTATION_RANGE);216rotation_step->set_max(SPIN_BOX_ROTATION_RANGE);217rotation_step->set_suffix(U"°");218rotation_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);219rotation_step->set_select_all_on_focus(true);220rotation_step->set_accessibility_name(TTRC("Rotation Step:"));221child_container->add_child(rotation_step);222223container->add_child(memnew(HSeparator));224225child_container = memnew(GridContainer);226child_container->set_columns(2);227container->add_child(child_container);228label = memnew(Label);229label->set_text(TTRC("Scale Step:"));230child_container->add_child(label);231label->set_h_size_flags(Control::SIZE_EXPAND_FILL);232233scale_step = memnew(SpinBox);234scale_step->set_min(SPIN_BOX_SCALE_MIN);235scale_step->set_max(SPIN_BOX_SCALE_MAX);236scale_step->set_allow_greater(true);237scale_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);238scale_step->set_step(0.01f);239scale_step->set_select_all_on_focus(true);240scale_step->set_accessibility_name(TTRC("Scale Step:"));241child_container->add_child(scale_step);242}243244void 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) {245grid_offset_x->set_value(p_grid_offset.x);246grid_offset_y->set_value(p_grid_offset.y);247grid_step_x->set_value(p_grid_step.x);248grid_step_y->set_value(p_grid_step.y);249primary_grid_step_x->set_value(p_primary_grid_step.x);250primary_grid_step_y->set_value(p_primary_grid_step.y);251rotation_offset->set_value(Math::rad_to_deg(p_rotation_offset));252rotation_step->set_value(Math::rad_to_deg(p_rotation_step));253scale_step->set_value(p_scale_step);254}255256void 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) {257p_grid_offset = Point2(grid_offset_x->get_value(), grid_offset_y->get_value());258p_grid_step = Point2(grid_step_x->get_value(), grid_step_y->get_value());259p_primary_grid_step = Vector2i(primary_grid_step_x->get_value(), primary_grid_step_y->get_value());260p_rotation_offset = Math::deg_to_rad(rotation_offset->get_value());261p_rotation_step = Math::deg_to_rad(rotation_step->get_value());262p_scale_step = scale_step->get_value();263}264};265266bool CanvasItemEditor::_is_node_locked(const Node *p_node) const {267return p_node->get_meta("_edit_lock_", false);268}269270bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning) {271if (_is_node_locked(p_node)) {272return false;273}274if (Object::cast_to<Control>(p_node) && Object::cast_to<Container>(p_node->get_parent())) {275if (p_popup_warning) {276EditorToaster::get_singleton()->popup_str(TTR("Children of a container get their position and size determined only by their parent."), EditorToaster::SEVERITY_WARNING);277}278return false;279}280return true;281}282283void CanvasItemEditor::_snap_if_closer_float(284const real_t p_value,285real_t &r_current_snap, SnapTarget &r_current_snap_target,286const real_t p_target_value, const SnapTarget p_snap_target,287const real_t p_radius) {288const real_t radius = p_radius / zoom;289const real_t dist = Math::abs(p_value - p_target_value);290if ((p_radius < 0 || dist < radius) && (r_current_snap_target == SNAP_TARGET_NONE || dist < Math::abs(r_current_snap - p_value))) {291r_current_snap = p_target_value;292r_current_snap_target = p_snap_target;293}294}295296void CanvasItemEditor::_snap_if_closer_point(297Point2 p_value,298Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],299Point2 p_target_value, const SnapTarget p_snap_target,300const real_t rotation,301const real_t p_radius) {302Transform2D rot_trans = Transform2D(rotation, Point2());303p_value = rot_trans.inverse().xform(p_value);304p_target_value = rot_trans.inverse().xform(p_target_value);305r_current_snap = rot_trans.inverse().xform(r_current_snap);306307_snap_if_closer_float(308p_value.x,309r_current_snap.x,310r_current_snap_target[0],311p_target_value.x,312p_snap_target,313p_radius);314315_snap_if_closer_float(316p_value.y,317r_current_snap.y,318r_current_snap_target[1],319p_target_value.y,320p_snap_target,321p_radius);322323r_current_snap = rot_trans.xform(r_current_snap);324}325326void CanvasItemEditor::_snap_other_nodes(327const Point2 p_value,328const Transform2D p_transform_to_snap,329Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],330const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions,331const Node *p_current) {332const CanvasItem *ci = Object::cast_to<CanvasItem>(p_current);333334// Check if the element is in the exception335bool exception = false;336for (const CanvasItem *&E : p_exceptions) {337if (E == p_current) {338exception = true;339break;340}341};342343if (ci && !exception) {344Transform2D ci_transform = ci->get_screen_transform();345if (std::fmod(ci_transform.get_rotation() - p_transform_to_snap.get_rotation(), (real_t)360.0) == 0.0) {346if (ci->_edit_use_rect()) {347Point2 begin = ci_transform.xform(ci->_edit_get_rect().get_position());348Point2 end = ci_transform.xform(ci->_edit_get_rect().get_position() + ci->_edit_get_rect().get_size());349350_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, begin, p_snap_target, ci_transform.get_rotation());351_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, end, p_snap_target, ci_transform.get_rotation());352} else {353Point2 position = ci_transform.xform(Point2());354_snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, position, p_snap_target, ci_transform.get_rotation());355}356}357}358for (int i = 0; i < p_current->get_child_count(); i++) {359_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));360}361}362363Point2 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) {364snap_target[0] = SNAP_TARGET_NONE;365snap_target[1] = SNAP_TARGET_NONE;366367bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);368369// Smart snap using the canvas position370Vector2 output = p_target;371real_t rotation = 0.0;372373if (p_self_canvas_item) {374rotation = p_self_canvas_item->get_screen_transform().get_rotation();375376// Parent sides and center377if ((is_snap_active && snap_node_parent && (p_modes & SNAP_NODE_PARENT)) || (p_forced_modes & SNAP_NODE_PARENT)) {378if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {379Point2 begin = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(0, 0)));380Point2 end = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(1, 1)));381_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);382_snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);383_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);384} else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_self_canvas_item->get_parent())) {385if (parent_ci->_edit_use_rect()) {386Point2 begin = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position());387Point2 end = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size());388_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);389_snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);390_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);391} else {392Point2 position = p_self_canvas_item->get_transform().affine_inverse().xform(Point2());393_snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_PARENT, rotation);394}395}396}397398// Self anchors399if ((is_snap_active && snap_node_anchors && (p_modes & SNAP_NODE_ANCHORS)) || (p_forced_modes & SNAP_NODE_ANCHORS)) {400if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {401Point2 begin = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_LEFT), c->get_anchor(SIDE_TOP))));402Point2 end = p_self_canvas_item->get_screen_transform().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_RIGHT), c->get_anchor(SIDE_BOTTOM))));403_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF_ANCHORS, rotation);404_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF_ANCHORS, rotation);405}406}407408// Self sides409if ((is_snap_active && snap_node_sides && (p_modes & SNAP_NODE_SIDES)) || (p_forced_modes & SNAP_NODE_SIDES)) {410if (p_self_canvas_item->_edit_use_rect()) {411Point2 begin = p_self_canvas_item->get_screen_transform().xform(p_self_canvas_item->_edit_get_rect().get_position());412Point2 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());413_snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF, rotation);414_snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF, rotation);415}416}417418// Self center419if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) {420if (p_self_canvas_item->_edit_use_rect()) {421Point2 center = p_self_canvas_item->get_screen_transform().xform(p_self_canvas_item->_edit_get_rect().get_center());422_snap_if_closer_point(p_target, output, snap_target, center, SNAP_TARGET_SELF, rotation);423} else {424Point2 position = p_self_canvas_item->get_screen_transform().xform(Point2());425_snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_SELF, rotation);426}427}428}429430// Other nodes sides431if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) {432Transform2D to_snap_transform;433List<const CanvasItem *> exceptions = List<const CanvasItem *>();434for (const CanvasItem *E : p_other_nodes_exceptions) {435exceptions.push_back(E);436}437if (p_self_canvas_item) {438exceptions.push_back(p_self_canvas_item);439to_snap_transform = p_self_canvas_item->get_screen_transform();440}441442_snap_other_nodes(443p_target, to_snap_transform,444output, snap_target,445SNAP_TARGET_OTHER_NODE,446exceptions,447get_tree()->get_edited_scene_root());448}449450if (((is_snap_active && snap_guides && (p_modes & SNAP_GUIDES)) || (p_forced_modes & SNAP_GUIDES)) && std::fmod(rotation, (real_t)360.0) == 0.0) {451// Guides.452if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {453Array vguides = scene->get_meta("_edit_vertical_guides_", Array());454for (int i = 0; i < vguides.size(); i++) {455_snap_if_closer_float(p_target.x, output.x, snap_target[0], vguides[i], SNAP_TARGET_GUIDE);456}457458Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());459for (int i = 0; i < hguides.size(); i++) {460_snap_if_closer_float(p_target.y, output.y, snap_target[1], hguides[i], SNAP_TARGET_GUIDE);461}462}463}464465if (((grid_snap_active && (p_modes & SNAP_GRID)) || (p_forced_modes & SNAP_GRID)) && std::fmod(rotation, (real_t)360.0) == 0.0) {466// Grid467Point2 offset = grid_offset;468if (snap_relative) {469List<CanvasItem *> selection = _get_edited_canvas_items();470if (selection.size() == 1 && Object::cast_to<Node2D>(selection.front()->get())) {471offset = Object::cast_to<Node2D>(selection.front()->get())->get_global_position();472} else if (selection.size() > 0) {473offset = _get_encompassing_rect_from_list(selection).position;474}475}476Point2 grid_output;477grid_output.x = Math::snapped(p_target.x - offset.x, grid_step.x * Math::pow(2.0, grid_step_multiplier)) + offset.x;478grid_output.y = Math::snapped(p_target.y - offset.y, grid_step.y * Math::pow(2.0, grid_step_multiplier)) + offset.y;479_snap_if_closer_point(p_target, output, snap_target, grid_output, SNAP_TARGET_GRID, 0.0, -1.0);480}481482if (((snap_pixel && (p_modes & SNAP_PIXEL)) || (p_forced_modes & SNAP_PIXEL)) && rotation == 0.0) {483// Pixel484output = output.snappedf(1);485}486487snap_transform = Transform2D(rotation, output);488489return output;490}491492real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {493if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) && snap_rotation_step != 0) {494if (snap_relative) {495return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);496} else {497return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset;498}499} else {500return p_target;501}502}503504void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) {505ERR_FAIL_COND(p_ev.is_null());506507Ref<InputEventKey> k = p_ev;508509if (!is_visible_in_tree()) {510return;511}512513if (k.is_valid()) {514if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT) {515viewport->queue_redraw();516}517518if (k->is_pressed() && !k->is_command_or_control_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {519if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) {520// Multiply the grid size521grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);522viewport->queue_redraw();523} else if (divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) {524// Divide the grid size525Point2 new_grid_step = grid_step * Math::pow(2.0, grid_step_multiplier - 1);526if (new_grid_step.x >= 1.0 && new_grid_step.y >= 1.0) {527grid_step_multiplier--;528}529viewport->queue_redraw();530}531}532}533}534535Object *CanvasItemEditor::_get_editor_data(Object *p_what) {536CanvasItem *ci = Object::cast_to<CanvasItem>(p_what);537if (!ci) {538return nullptr;539}540541return memnew(CanvasItemEditorSelectedItem);542}543544void CanvasItemEditor::_keying_changed() {545AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();546if (te && te->is_visible_in_tree() && te->get_current_animation().is_valid()) {547animation_hb->show();548} else {549animation_hb->hide();550}551}552553Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(const List<CanvasItem *> &p_list) {554ERR_FAIL_COND_V(p_list.is_empty(), Rect2());555556// Handles the first element557CanvasItem *ci = p_list.front()->get();558Rect2 rect = Rect2(ci->get_global_transform_with_canvas().xform(ci->_edit_get_rect().get_center()), Size2());559560// Expand with the other ones561for (CanvasItem *ci2 : p_list) {562Transform2D xform = ci2->get_global_transform_with_canvas();563564Rect2 current_rect = ci2->_edit_get_rect();565rect.expand_to(xform.xform(current_rect.position));566rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0)));567rect.expand_to(xform.xform(current_rect.position + current_rect.size));568rect.expand_to(xform.xform(current_rect.position + Vector2(0, current_rect.size.y)));569}570571return rect;572}573574void 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) {575if (!p_node) {576return;577}578if (Object::cast_to<Viewport>(p_node)) {579return;580}581582const CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);583584for (int i = p_node->get_child_count() - 1; i >= 0; i--) {585if (ci && !ci->is_set_as_top_level()) {586_expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * ci->get_transform(), p_canvas_xform);587} else {588const CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);589_expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);590}591}592593if (ci && ci->is_visible_in_tree() && (include_locked_nodes || !_is_node_locked(ci))) {594Transform2D xform = p_canvas_xform;595if (!ci->is_set_as_top_level()) {596xform *= p_parent_xform;597}598xform *= ci->get_transform();599Rect2 rect = ci->_edit_get_rect();600if (r_first) {601r_rect = Rect2(xform.xform(rect.get_center()), Size2());602r_first = false;603}604r_rect.expand_to(xform.xform(rect.position));605r_rect.expand_to(xform.xform(rect.position + Point2(rect.size.x, 0)));606r_rect.expand_to(xform.xform(rect.position + Point2(0, rect.size.y)));607r_rect.expand_to(xform.xform(rect.position + rect.size));608}609}610611Rect2 CanvasItemEditor::_get_encompassing_rect(const Node *p_node) {612Rect2 rect;613bool first = true;614_expand_encompassing_rect_using_children(rect, p_node, first);615616return rect;617}618619void 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) {620if (!p_node) {621return;622}623624const real_t grab_distance = EDITOR_GET("editors/polygon_editor/point_grab_radius");625CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);626627Transform2D xform = p_canvas_xform;628if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {629xform = cl->get_transform();630} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {631if (!vp->is_visible_subviewport()) {632return;633}634xform = vp->get_popup_base_transform();635if (!vp->get_visible_rect().has_point(xform.affine_inverse().xform(p_pos))) {636return;637}638}639640for (int i = p_node->get_child_count() - 1; i >= 0; i--) {641if (ci) {642if (!ci->is_set_as_top_level()) {643_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), xform);644} else {645_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), xform);646}647} else {648_find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), xform);649}650}651652if (ci && ci->is_visible_in_tree()) {653if (!ci->is_set_as_top_level()) {654xform *= p_parent_xform;655}656xform = (xform * ci->get_transform()).affine_inverse();657const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / zoom;658if (ci->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) {659Node2D *node = Object::cast_to<Node2D>(ci);660661_SelectResult res;662res.item = ci;663res.z_index = node ? node->get_z_index() : 0;664res.has_z = node;665r_items.push_back(res);666}667}668}669670void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked) {671Node *scene = EditorNode::get_singleton()->get_edited_scene();672673_find_canvas_items_at_pos(p_pos, scene, r_items);674675//Remove invalid results676for (int i = 0; i < r_items.size(); i++) {677Node *node = r_items[i].item;678679// Make sure the selected node is in the current scene, or editable680if (node && node != get_tree()->get_edited_scene_root()) {681node = scene->get_deepest_editable_node(node);682}683684CanvasItem *ci = Object::cast_to<CanvasItem>(node);685if (!p_allow_locked) {686// Replace the node by the group if grouped687while (node && node != scene->get_parent()) {688CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);689if (ci_tmp && node->has_meta("_edit_group_")) {690ci = ci_tmp;691}692node = node->get_parent();693}694}695696// Check if the canvas item is already in the list (for groups or scenes)697bool duplicate = false;698for (int j = 0; j < i; j++) {699if (r_items[j].item == ci) {700duplicate = true;701break;702}703}704705//Remove the item if invalid706if (!ci || duplicate || (ci != scene && ci->get_owner() != scene && !scene->is_editable_instance(ci->get_owner())) || (!p_allow_locked && _is_node_locked(ci))) {707r_items.remove_at(i);708i--;709} else {710r_items.write[i].item = ci;711}712}713}714715void 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) {716if (!p_node) {717return;718}719CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);720Node *scene = EditorNode::get_singleton()->get_edited_scene();721722if (p_node != scene && !p_node->get_owner()) {723return;724}725726bool editable = p_node == scene || p_node->get_owner() == scene || p_node == scene->get_deepest_editable_node(p_node);727bool lock_children = p_node->get_meta("_edit_group_", false);728bool locked = _is_node_locked(p_node);729730Transform2D xform = p_canvas_xform;731if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {732xform = cl->get_transform();733} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {734if (!vp->is_visible_subviewport()) {735return;736}737xform = vp->get_popup_base_transform();738if (!vp->get_visible_rect().intersects(xform.affine_inverse().xform(p_rect))) {739return;740}741}742743if (!lock_children || !editable) {744for (int i = p_node->get_child_count() - 1; i >= 0; i--) {745if (ci) {746if (!ci->is_set_as_top_level()) {747_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), xform);748} else {749_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), xform);750}751} else {752CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);753_find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : xform);754}755}756}757758if (ci && ci->is_visible_in_tree() && !locked && editable) {759if (!ci->is_set_as_top_level()) {760xform *= p_parent_xform;761}762xform *= ci->get_transform();763764if (ci->_edit_use_rect()) {765Rect2 rect = ci->_edit_get_rect();766if (p_rect.has_point(xform.xform(rect.position)) &&767p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) &&768p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) &&769p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) {770r_items->push_back(ci);771}772} else {773if (p_rect.has_point(xform.xform(Point2()))) {774r_items->push_back(ci);775}776}777}778}779780bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append) {781bool still_selected = true;782const List<Node *> &top_node_list = editor_selection->get_top_selected_node_list();783if (p_append && !top_node_list.is_empty()) {784if (editor_selection->is_selected(item)) {785// Already in the selection, remove it from the selected nodes786editor_selection->remove_node(item);787still_selected = false;788789if (top_node_list.size() == 1) {790EditorNode::get_singleton()->push_item(top_node_list.front()->get());791}792} else {793// Add the item to the selection794editor_selection->add_node(item);795}796} else {797if (!editor_selection->is_selected(item)) {798// Select a new one and clear previous selection799editor_selection->clear();800editor_selection->add_node(item);801// Reselect802if (Engine::get_singleton()->is_editor_hint()) {803selected_from_canvas = true;804}805}806}807viewport->queue_redraw();808return still_selected;809}810811List<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 {812List<CanvasItem *> selection;813for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) {814CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);815if (ci) {816if (ci->is_visible_in_tree() && (p_retrieve_locked || !_is_node_locked(ci))) {817Viewport *vp = ci->get_viewport();818if (vp && !vp->is_visible_subviewport()) {819continue;820}821CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);822if (se) {823selection.push_back(ci);824}825} else if (r_has_locked_items) {826// CanvasItem is selected, but can't be interacted with.827*r_has_locked_items = true;828}829}830}831832if (p_remove_canvas_item_if_parent_in_selection) {833List<CanvasItem *> filtered_selection;834for (CanvasItem *E : selection) {835if (!selection.find(E->get_parent())) {836filtered_selection.push_back(E);837}838}839return filtered_selection;840} else {841return selection;842}843}844845Vector2 CanvasItemEditor::_anchor_to_position(const Control *p_control, Vector2 anchor) {846ERR_FAIL_NULL_V(p_control, Vector2());847848Transform2D parent_transform = p_control->get_transform().affine_inverse();849Rect2 parent_rect = p_control->get_parent_anchorable_rect();850851if (p_control->is_layout_rtl()) {852return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));853} else {854return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));855}856}857858Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 position) {859ERR_FAIL_NULL_V(p_control, Vector2());860861Rect2 parent_rect = p_control->get_parent_anchorable_rect();862863Vector2 output;864if (p_control->is_layout_rtl()) {865output.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;866} else {867output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x;868}869output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y;870return output;871}872873void CanvasItemEditor::_save_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool save_bones) {874original_transform = Transform2D();875bool transform_stored = false;876877for (CanvasItem *ci : p_canvas_items) {878CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);879if (se) {880if (!transform_stored) {881original_transform = ci->get_global_transform();882transform_stored = true;883}884885se->undo_state = ci->_edit_get_state();886se->pre_drag_xform = ci->get_screen_transform();887if (ci->_edit_use_rect()) {888se->pre_drag_rect = ci->_edit_get_rect();889} else {890se->pre_drag_rect = Rect2();891}892}893}894}895896void CanvasItemEditor::_restore_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool restore_bones) {897for (CanvasItem *ci : drag_selection) {898CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);899ci->_edit_set_state(se->undo_state);900}901}902903void CanvasItemEditor::_commit_canvas_item_state(const List<CanvasItem *> &p_canvas_items, const String &action_name, bool commit_bones) {904List<CanvasItem *> modified_canvas_items;905for (CanvasItem *ci : p_canvas_items) {906Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci)->undo_state;907Dictionary new_state = ci->_edit_get_state();908909if (old_state.hash() != new_state.hash()) {910modified_canvas_items.push_back(ci);911}912}913914if (modified_canvas_items.is_empty()) {915return;916}917918EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();919undo_redo->create_action(action_name);920for (CanvasItem *ci : modified_canvas_items) {921CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);922if (se) {923undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());924undo_redo->add_undo_method(ci, "_edit_set_state", se->undo_state);925if (commit_bones) {926for (const Dictionary &F : se->pre_drag_bones_undo_state) {927ci = Object::cast_to<CanvasItem>(ci->get_parent());928undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());929undo_redo->add_undo_method(ci, "_edit_set_state", F);930}931}932}933}934undo_redo->add_do_method(viewport, "queue_redraw");935undo_redo->add_undo_method(viewport, "queue_redraw");936undo_redo->commit_action();937}938939void CanvasItemEditor::_snap_changed() {940static_cast<SnapDialog *>(snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);941942EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_offset", grid_offset);943EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_step", grid_step);944EditorSettings::get_singleton()->set_project_metadata("2d_editor", "primary_grid_step", primary_grid_step);945EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_offset", snap_rotation_offset);946EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_step", snap_rotation_step);947EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_scale_step", snap_scale_step);948949grid_step_multiplier = 0;950viewport->queue_redraw();951}952953void CanvasItemEditor::_selection_result_pressed(int p_result) {954if (selection_results_menu.size() <= p_result) {955return;956}957958CanvasItem *item = selection_results_menu[p_result].item;959960if (item) {961_select_click_on_item(item, Point2(), selection_menu_additive_selection);962}963selection_results_menu.clear();964}965966void CanvasItemEditor::_selection_menu_hide() {967selection_results.clear();968selection_menu->clear();969selection_menu->reset_size();970}971972void CanvasItemEditor::_add_node_pressed(int p_result) {973List<Node *> nodes_to_move;974975switch (p_result) {976case ADD_NODE: {977SceneTreeDock::get_singleton()->open_add_child_dialog();978} break;979case ADD_INSTANCE: {980SceneTreeDock::get_singleton()->open_instance_child_dialog();981} break;982case ADD_PASTE: {983nodes_to_move = SceneTreeDock::get_singleton()->paste_nodes();984[[fallthrough]];985}986case ADD_MOVE: {987nodes_to_move = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();988if (nodes_to_move.is_empty()) {989return;990}991992EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();993undo_redo->create_action(TTR("Move Node(s) to Position"));994for (Node *node : nodes_to_move) {995CanvasItem *ci = Object::cast_to<CanvasItem>(node);996if (ci) {997Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();998undo_redo->add_do_method(ci, "_edit_set_position", xform.xform(node_create_position));999undo_redo->add_undo_method(ci, "_edit_set_position", ci->_edit_get_position());1000}1001}1002undo_redo->commit_action();1003_reset_create_position();1004} break;1005default: {1006if (p_result >= EditorContextMenuPlugin::BASE_ID) {1007TypedArray<Node> nodes;1008nodes.resize(selection_results.size());10091010int i = 0;1011for (const _SelectResult &result : selection_results) {1012nodes[i] = result.item;1013i++;1014}1015EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, p_result, nodes);1016}1017}1018}1019}10201021void CanvasItemEditor::_adjust_new_node_position(Node *p_node) {1022if (node_create_position == Point2()) {1023return;1024}10251026CanvasItem *c = Object::cast_to<CanvasItem>(p_node);1027if (c) {1028Transform2D xform = c->get_global_transform_with_canvas().affine_inverse() * c->get_transform();1029c->_edit_set_position(xform.xform(node_create_position));1030}10311032callable_mp(this, &CanvasItemEditor::_reset_create_position).call_deferred(); // Defer the call in case more than one node is added.1033}10341035void CanvasItemEditor::_reset_create_position() {1036node_create_position = Point2();1037}10381039bool CanvasItemEditor::_is_grid_visible() const {1040switch (grid_visibility) {1041case GRID_VISIBILITY_SHOW:1042return true;1043case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1044return grid_snap_active;1045case GRID_VISIBILITY_HIDE:1046return false;1047}1048ERR_FAIL_V_MSG(true, "Unexpected grid_visibility value");1049}10501051void CanvasItemEditor::_prepare_grid_menu() {1052for (int i = GRID_VISIBILITY_SHOW; i <= GRID_VISIBILITY_HIDE; i++) {1053grid_menu->set_item_checked(i, i == grid_visibility);1054}1055}10561057void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) {1058switch (p_id) {1059case GRID_VISIBILITY_SHOW:1060case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1061case GRID_VISIBILITY_HIDE:1062grid_visibility = (GridVisibility)p_id;1063viewport->queue_redraw();1064view_menu->get_popup()->hide();1065return;1066}10671068// Toggle grid: go to the least restrictive option possible.1069if (grid_snap_active) {1070switch (grid_visibility) {1071case GRID_VISIBILITY_SHOW:1072case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1073grid_visibility = GRID_VISIBILITY_HIDE;1074break;1075case GRID_VISIBILITY_HIDE:1076grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;1077break;1078}1079} else {1080switch (grid_visibility) {1081case GRID_VISIBILITY_SHOW:1082grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;1083break;1084case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:1085case GRID_VISIBILITY_HIDE:1086grid_visibility = GRID_VISIBILITY_SHOW;1087break;1088}1089}1090viewport->queue_redraw();1091}10921093void CanvasItemEditor::_switch_theme_preview(int p_mode) {1094view_menu->get_popup()->hide();10951096if (theme_preview == p_mode) {1097return;1098}1099theme_preview = (ThemePreviewMode)p_mode;1100EditorSettings::get_singleton()->set_project_metadata("2d_editor", "theme_preview", theme_preview);11011102for (int i = 0; i < THEME_PREVIEW_MAX; i++) {1103theme_menu->set_item_checked(i, i == theme_preview);1104}11051106EditorNode::get_singleton()->update_preview_themes(theme_preview);1107}11081109bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) {1110EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();1111Ref<InputEventMouseButton> b = p_event;1112Ref<InputEventMouseMotion> m = p_event;11131114if (drag_type == DRAG_NONE) {1115if (show_guides && show_rulers && EditorNode::get_singleton()->get_edited_scene()) {1116Transform2D xform = viewport_scrollable->get_transform() * transform;1117// Retrieve the guide lists1118Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());1119Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());11201121// Hover over guides1122real_t minimum = 1e20;1123is_hovering_h_guide = false;1124is_hovering_v_guide = false;11251126if (m.is_valid() && m->get_position().x < ruler_width_scaled) {1127// Check if we are hovering an existing horizontal guide1128for (int i = 0; i < hguides.size(); i++) {1129if (Math::abs(xform.xform(Point2(0, hguides[i])).y - m->get_position().y) < MIN(minimum, 8)) {1130is_hovering_h_guide = true;1131is_hovering_v_guide = false;1132break;1133}1134}11351136} else if (m.is_valid() && m->get_position().y < ruler_width_scaled) {1137// Check if we are hovering an existing vertical guide1138for (int i = 0; i < vguides.size(); i++) {1139if (Math::abs(xform.xform(Point2(vguides[i], 0)).x - m->get_position().x) < MIN(minimum, 8)) {1140is_hovering_v_guide = true;1141is_hovering_h_guide = false;1142break;1143}1144}1145}11461147// Start dragging a guide1148if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {1149// Press button1150if (b->get_position().x < ruler_width_scaled && b->get_position().y < ruler_width_scaled) {1151// Drag a new double guide1152drag_type = DRAG_DOUBLE_GUIDE;1153dragged_guide_index = -1;1154return true;1155} else if (b->get_position().x < ruler_width_scaled) {1156// Check if we drag an existing horizontal guide1157dragged_guide_index = -1;1158for (int i = 0; i < hguides.size(); i++) {1159if (Math::abs(xform.xform(Point2(0, hguides[i])).y - b->get_position().y) < MIN(minimum, 8)) {1160dragged_guide_index = i;1161}1162}11631164if (dragged_guide_index >= 0) {1165// Drag an existing horizontal guide1166drag_type = DRAG_H_GUIDE;1167} else {1168// Drag a new vertical guide1169drag_type = DRAG_V_GUIDE;1170}1171return true;1172} else if (b->get_position().y < ruler_width_scaled) {1173// Check if we drag an existing vertical guide1174dragged_guide_index = -1;1175for (int i = 0; i < vguides.size(); i++) {1176if (Math::abs(xform.xform(Point2(vguides[i], 0)).x - b->get_position().x) < MIN(minimum, 8)) {1177dragged_guide_index = i;1178}1179}11801181if (dragged_guide_index >= 0) {1182// Drag an existing vertical guide1183drag_type = DRAG_V_GUIDE;1184} else {1185// Drag a new vertical guide1186drag_type = DRAG_H_GUIDE;1187}1188drag_from = xform.affine_inverse().xform(b->get_position());1189return true;1190}1191}1192}1193}11941195if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE || drag_type == DRAG_H_GUIDE) {1196// Move the guide1197if (m.is_valid()) {1198Transform2D xform = viewport_scrollable->get_transform() * transform;1199drag_to = xform.affine_inverse().xform(m->get_position());12001201dragged_guide_pos = xform.xform(snap_point(drag_to, SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES));1202viewport->queue_redraw();1203return true;1204}12051206// Release confirms the guide move1207if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1208if (show_guides && EditorNode::get_singleton()->get_edited_scene()) {1209Transform2D xform = viewport_scrollable->get_transform() * transform;12101211// Retrieve the guide lists1212Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());1213Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());12141215Point2 edited = snap_point(xform.affine_inverse().xform(b->get_position()), SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES);1216if (drag_type == DRAG_V_GUIDE) {1217Array prev_vguides = vguides.duplicate();1218if (b->get_position().x > ruler_width_scaled) {1219// Adds a new vertical guide1220if (dragged_guide_index >= 0) {1221vguides[dragged_guide_index] = edited.x;1222undo_redo->create_action(TTR("Move Vertical Guide"));1223undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1224undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1225undo_redo->add_undo_method(viewport, "queue_redraw");1226undo_redo->commit_action();1227} else {1228vguides.push_back(edited.x);1229undo_redo->create_action(TTR("Create Vertical Guide"));1230undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1231if (prev_vguides.is_empty()) {1232undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1233} else {1234undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1235}1236undo_redo->add_undo_method(viewport, "queue_redraw");1237undo_redo->commit_action();1238}1239} else {1240if (dragged_guide_index >= 0) {1241vguides.remove_at(dragged_guide_index);1242undo_redo->create_action(TTR("Remove Vertical Guide"));1243if (vguides.is_empty()) {1244undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1245} else {1246undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1247}1248undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1249undo_redo->add_undo_method(viewport, "queue_redraw");1250undo_redo->commit_action();1251}1252}1253} else if (drag_type == DRAG_H_GUIDE) {1254Array prev_hguides = hguides.duplicate();1255if (b->get_position().y > ruler_width_scaled) {1256// Adds a new horizontal guide1257if (dragged_guide_index >= 0) {1258hguides[dragged_guide_index] = edited.y;1259undo_redo->create_action(TTR("Move Horizontal Guide"));1260undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1261undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1262undo_redo->add_undo_method(viewport, "queue_redraw");1263undo_redo->commit_action();1264} else {1265hguides.push_back(edited.y);1266undo_redo->create_action(TTR("Create Horizontal Guide"));1267undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1268if (prev_hguides.is_empty()) {1269undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1270} else {1271undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1272}1273undo_redo->add_undo_method(viewport, "queue_redraw");1274undo_redo->commit_action();1275}1276} else {1277if (dragged_guide_index >= 0) {1278hguides.remove_at(dragged_guide_index);1279undo_redo->create_action(TTR("Remove Horizontal Guide"));1280if (hguides.is_empty()) {1281undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1282} else {1283undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1284}1285undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1286undo_redo->add_undo_method(viewport, "queue_redraw");1287undo_redo->commit_action();1288}1289}1290} else if (drag_type == DRAG_DOUBLE_GUIDE) {1291Array prev_hguides = hguides.duplicate();1292Array prev_vguides = vguides.duplicate();1293if (b->get_position().x > ruler_width_scaled && b->get_position().y > ruler_width_scaled) {1294// Adds a new horizontal guide a new vertical guide1295vguides.push_back(edited.x);1296hguides.push_back(edited.y);1297undo_redo->create_action(TTR("Create Horizontal and Vertical Guides"));1298undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);1299undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);1300if (prev_vguides.is_empty()) {1301undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");1302} else {1303undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);1304}1305if (prev_hguides.is_empty()) {1306undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");1307} else {1308undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);1309}1310undo_redo->add_undo_method(viewport, "queue_redraw");1311undo_redo->commit_action();1312}1313}1314}1315snap_target[0] = SNAP_TARGET_NONE;1316snap_target[1] = SNAP_TARGET_NONE;1317_reset_drag();1318viewport->queue_redraw();1319return true;1320}1321}1322return false;1323}13241325bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {1326panner->set_force_drag(tool == TOOL_PAN);1327bool panner_active = panner->gui_input(p_event, viewport->get_global_rect());1328if (panner->is_panning() != pan_pressed) {1329pan_pressed = panner->is_panning();1330_update_cursor();1331}13321333if (panner_active) {1334return true;1335}13361337Ref<InputEventKey> k = p_event;1338if (k.is_valid()) {1339if (k->is_pressed()) {1340if (ED_IS_SHORTCUT("canvas_item_editor/zoom_3.125_percent", p_event)) {1341_shortcut_zoom_set(1.0 / 32.0);1342} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_6.25_percent", p_event)) {1343_shortcut_zoom_set(1.0 / 16.0);1344} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_12.5_percent", p_event)) {1345_shortcut_zoom_set(1.0 / 8.0);1346} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_25_percent", p_event)) {1347_shortcut_zoom_set(1.0 / 4.0);1348} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_50_percent", p_event)) {1349_shortcut_zoom_set(1.0 / 2.0);1350} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_100_percent", p_event)) {1351_shortcut_zoom_set(1.0);1352} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_200_percent", p_event)) {1353_shortcut_zoom_set(2.0);1354} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_400_percent", p_event)) {1355_shortcut_zoom_set(4.0);1356} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_800_percent", p_event)) {1357_shortcut_zoom_set(8.0);1358} else if (ED_IS_SHORTCUT("canvas_item_editor/zoom_1600_percent", p_event)) {1359_shortcut_zoom_set(16.0);1360}1361}1362}13631364return false;1365}13661367void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {1368view_offset.x -= p_scroll_vec.x / zoom;1369view_offset.y -= p_scroll_vec.y / zoom;1370update_viewport();1371}13721373void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {1374Ref<InputEventMouseButton> mb = p_event;1375if (mb.is_valid()) {1376// Special behavior for scroll events, as the zoom_by_increment method can smartly end up on powers of two.1377int increment = p_zoom_factor > 1.0 ? 1 : -1;1378bool by_integer = mb->is_alt_pressed();13791380if (EDITOR_GET("editors/2d/use_integer_zoom_by_default")) {1381by_integer = !by_integer;1382}13831384zoom_widget->set_zoom_by_increments(increment, by_integer);1385} else {1386zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);1387}13881389_zoom_on_position(zoom_widget->get_zoom(), p_origin);1390}13911392bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {1393Ref<InputEventMouseMotion> m = p_event;1394Ref<InputEventMouseButton> b = p_event;1395Ref<InputEventKey> k = p_event;13961397// Drag the pivot (in pivot mode / with V key)1398if (drag_type == DRAG_NONE) {1399bool move_temp_pivot = ((b.is_valid() && b->is_shift_pressed()) || (k.is_valid() && k->is_shift_pressed()));14001401if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1402(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))) {1403List<CanvasItem *> selection = _get_edited_canvas_items();14041405// Filters the selection with nodes that allow setting the pivot1406drag_selection = List<CanvasItem *>();1407for (CanvasItem *ci : selection) {1408if (ci->_edit_use_pivot() || move_temp_pivot) {1409drag_selection.push_back(ci);1410}1411}14121413// Start dragging if we still have nodes1414if (drag_selection.size() > 0) {1415Vector2 event_pos = (b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position();14161417if (move_temp_pivot) {1418drag_type = DRAG_TEMP_PIVOT;1419temp_pivot = transform.affine_inverse().xform(event_pos);1420viewport->queue_redraw();1421return true;1422}14231424_save_canvas_item_state(drag_selection);1425drag_from = transform.affine_inverse().xform(event_pos);1426Vector2 new_pos;1427if (drag_selection.size() == 1) {1428new_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());1429} else {1430new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, nullptr, drag_selection);1431}1432for (CanvasItem *ci : drag_selection) {1433ci->_edit_set_pivot(ci->get_screen_transform().affine_inverse().xform(new_pos));1434}14351436drag_type = DRAG_PIVOT;1437}1438return true;1439}1440}14411442if (drag_type == DRAG_PIVOT) {1443// Move the pivot1444if (m.is_valid()) {1445drag_to = transform.affine_inverse().xform(m->get_position());1446_restore_canvas_item_state(drag_selection);1447Vector2 new_pos;1448if (drag_selection.size() == 1) {1449new_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());1450} else {1451new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL);1452}1453for (CanvasItem *ci : drag_selection) {1454ci->_edit_set_pivot(ci->get_screen_transform().affine_inverse().xform(new_pos));1455}1456return true;1457}14581459// Confirm the pivot move1460if (drag_selection.size() >= 1 &&1461((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1462(k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V))) {1463_commit_canvas_item_state(1464drag_selection,1465vformat(1466TTR("Set CanvasItem \"%s\" Pivot Offset to (%d, %d)"),1467drag_selection.front()->get()->get_name(),1468drag_selection.front()->get()->_edit_get_pivot().x,1469drag_selection.front()->get()->_edit_get_pivot().y));1470_reset_drag();1471return true;1472}14731474// Cancel a drag1475if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1476_restore_canvas_item_state(drag_selection);1477_reset_drag();1478viewport->queue_redraw();1479return true;1480}1481}14821483if (drag_type == DRAG_TEMP_PIVOT) {1484if (m.is_valid()) {1485temp_pivot = transform.affine_inverse().xform(m->get_position());1486viewport->queue_redraw();1487return true;1488}14891490if ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||1491(k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V)) {1492drag_type = DRAG_NONE;1493return true;1494}1495}1496return false;1497}14981499bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {1500Ref<InputEventMouseButton> b = p_event;1501Ref<InputEventMouseMotion> m = p_event;15021503// Start rotation1504if (drag_type == DRAG_NONE) {1505if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {1506if ((b->is_command_or_control_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {1507bool has_locked_items = false;1508List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);15091510// Remove not movable nodes1511for (List<CanvasItem *>::Element *E = selection.front(); E;) {1512List<CanvasItem *>::Element *N = E->next();1513if (!_is_node_movable(E->get(), true)) {1514selection.erase(E);1515}1516E = N;1517}15181519drag_selection = selection;1520if (drag_selection.size() > 0) {1521drag_type = DRAG_ROTATE;1522drag_from = transform.affine_inverse().xform(b->get_position());1523CanvasItem *ci = drag_selection.front()->get();1524if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {1525drag_rotation_center = temp_pivot;1526} else if (ci->_edit_use_pivot()) {1527drag_rotation_center = ci->get_screen_transform().xform(ci->_edit_get_pivot());1528} else {1529drag_rotation_center = ci->get_screen_transform().get_origin();1530}1531_save_canvas_item_state(drag_selection);1532return true;1533} else {1534if (has_locked_items) {1535EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);1536}1537return has_locked_items;1538}1539}1540}1541}15421543if (drag_type == DRAG_ROTATE) {1544// Rotate the node1545if (m.is_valid()) {1546_restore_canvas_item_state(drag_selection);1547for (CanvasItem *ci : drag_selection) {1548drag_to = transform.affine_inverse().xform(m->get_position());1549//Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements1550bool opposite = (ci->get_global_transform().get_scale().sign().dot(ci->get_transform().get_scale().sign()) == 0);1551real_t prev_rotation = ci->_edit_get_rotation();1552real_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);15531554ci->_edit_set_rotation(new_rotation);1555if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {1556Transform2D xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();1557Vector2 radius = xform.xform(ci->_edit_get_position()) - temp_pivot;1558radius = radius.rotated(new_rotation - prev_rotation);1559ci->_edit_set_position(xform.affine_inverse().xform(temp_pivot + radius));1560}1561viewport->queue_redraw();1562}1563return true;1564}15651566// Confirms the node rotation1567if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1568if (drag_selection.size() != 1) {1569_commit_canvas_item_state(1570drag_selection,1571vformat(TTR("Rotate %d CanvasItems"), drag_selection.size()),1572true);1573} else {1574_commit_canvas_item_state(1575drag_selection,1576vformat(TTR("Rotate CanvasItem \"%s\" to %d degrees"),1577drag_selection.front()->get()->get_name(),1578Math::rad_to_deg(drag_selection.front()->get()->_edit_get_rotation())),1579true);1580}15811582if (key_auto_insert_button->is_pressed()) {1583_insert_animation_keys(false, true, false, true);1584}15851586_reset_drag();1587return true;1588}15891590// Cancel a drag1591if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1592_restore_canvas_item_state(drag_selection);1593_reset_drag();1594viewport->queue_redraw();1595return true;1596}1597}1598return false;1599}16001601bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEvent> &p_event) {1602Ref<InputEventMouseButton> b = p_event;16031604// Open a sub-scene on double-click1605if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) {1606List<CanvasItem *> selection = _get_edited_canvas_items();1607if (selection.size() == 1) {1608CanvasItem *ci = selection.front()->get();1609if (!ci->get_scene_file_path().is_empty() && ci != EditorNode::get_singleton()->get_edited_scene()) {1610EditorNode::get_singleton()->load_scene(ci->get_scene_file_path());1611return true;1612}1613}1614}1615return false;1616}16171618bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {1619Ref<InputEventMouseButton> b = p_event;1620Ref<InputEventMouseMotion> m = p_event;16211622// Starts anchor dragging if needed1623if (drag_type == DRAG_NONE) {1624if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {1625List<CanvasItem *> selection = _get_edited_canvas_items();1626if (selection.size() == 1) {1627Control *control = Object::cast_to<Control>(selection.front()->get());1628if (control && _is_node_movable(control)) {1629Vector2 anchor_pos[4];1630anchor_pos[0] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_TOP));1631anchor_pos[1] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_TOP));1632anchor_pos[2] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_BOTTOM));1633anchor_pos[3] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_BOTTOM));16341635Rect2 anchor_rects[4];1636for (int i = 0; i < 4; i++) {1637anchor_pos[i] = (transform * control->get_screen_transform()).xform(_anchor_to_position(control, anchor_pos[i]));1638anchor_rects[i] = Rect2(anchor_pos[i], anchor_handle->get_size());1639if (control->is_layout_rtl()) {1640anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 1 || i == 2), real_t(i <= 1));1641} else {1642anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 0 || i == 3), real_t(i <= 1));1643}1644}16451646const DragType dragger[] = {1647DRAG_ANCHOR_TOP_LEFT,1648DRAG_ANCHOR_TOP_RIGHT,1649DRAG_ANCHOR_BOTTOM_RIGHT,1650DRAG_ANCHOR_BOTTOM_LEFT,1651};16521653for (int i = 0; i < 4; i++) {1654if (anchor_rects[i].has_point(b->get_position())) {1655if ((anchor_pos[0] == anchor_pos[2]) && (anchor_pos[0].distance_to(b->get_position()) < anchor_handle->get_size().length() / 3.0)) {1656drag_type = DRAG_ANCHOR_ALL;1657} else {1658drag_type = dragger[i];1659}1660drag_from = transform.affine_inverse().xform(b->get_position());1661drag_selection = List<CanvasItem *>();1662drag_selection.push_back(control);1663_save_canvas_item_state(drag_selection);1664return true;1665}1666}1667}1668}1669}1670}16711672if (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) {1673// Drag the anchor1674if (m.is_valid()) {1675_restore_canvas_item_state(drag_selection);1676Control *control = Object::cast_to<Control>(drag_selection.front()->get());16771678drag_to = transform.affine_inverse().xform(m->get_position());16791680Transform2D xform = control->get_screen_transform().affine_inverse();16811682Point2 previous_anchor;1683previous_anchor.x = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_BOTTOM_LEFT) ? control->get_anchor(SIDE_LEFT) : control->get_anchor(SIDE_RIGHT);1684previous_anchor.y = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_TOP_RIGHT) ? control->get_anchor(SIDE_TOP) : control->get_anchor(SIDE_BOTTOM);1685previous_anchor = xform.affine_inverse().xform(_anchor_to_position(control, previous_anchor));16861687Vector2 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));1688new_anchor = _position_to_anchor(control, new_anchor).snappedf(0.001);16891690bool use_single_axis = m->is_shift_pressed();1691Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from);1692bool use_y = Math::abs(drag_vector.y) > Math::abs(drag_vector.x);16931694switch (drag_type) {1695case DRAG_ANCHOR_TOP_LEFT:1696if (!use_single_axis || !use_y) {1697control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);1698}1699if (!use_single_axis || use_y) {1700control->set_anchor(SIDE_TOP, new_anchor.y, false, false);1701}1702break;1703case DRAG_ANCHOR_TOP_RIGHT:1704if (!use_single_axis || !use_y) {1705control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);1706}1707if (!use_single_axis || use_y) {1708control->set_anchor(SIDE_TOP, new_anchor.y, false, false);1709}1710break;1711case DRAG_ANCHOR_BOTTOM_RIGHT:1712if (!use_single_axis || !use_y) {1713control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);1714}1715if (!use_single_axis || use_y) {1716control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);1717}1718break;1719case DRAG_ANCHOR_BOTTOM_LEFT:1720if (!use_single_axis || !use_y) {1721control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);1722}1723if (!use_single_axis || use_y) {1724control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);1725}1726break;1727case DRAG_ANCHOR_ALL:1728if (!use_single_axis || !use_y) {1729control->set_anchor(SIDE_LEFT, new_anchor.x, false, true);1730control->set_anchor(SIDE_RIGHT, new_anchor.x, false, true);1731}1732if (!use_single_axis || use_y) {1733control->set_anchor(SIDE_TOP, new_anchor.y, false, true);1734control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, true);1735}1736break;1737default:1738break;1739}1740return true;1741}17421743// Confirms new anchor position1744if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1745_commit_canvas_item_state(1746drag_selection,1747vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection.front()->get()->get_name()));1748snap_target[0] = SNAP_TARGET_NONE;1749snap_target[1] = SNAP_TARGET_NONE;1750_reset_drag();1751viewport->queue_redraw();1752return true;1753}17541755// Cancel a drag1756if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1757_restore_canvas_item_state(drag_selection);1758snap_target[0] = SNAP_TARGET_NONE;1759snap_target[1] = SNAP_TARGET_NONE;1760_reset_drag();1761viewport->queue_redraw();1762return true;1763}1764}1765return false;1766}17671768bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {1769Ref<InputEventMouseButton> b = p_event;1770Ref<InputEventMouseMotion> m = p_event;17711772// Drag resize handles1773if (drag_type == DRAG_NONE) {1774if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {1775List<CanvasItem *> selection = _get_edited_canvas_items();1776if (selection.size() == 1) {1777CanvasItem *ci = selection.front()->get();1778if (ci->_edit_use_rect() && _is_node_movable(ci)) {1779Rect2 rect = ci->_edit_get_rect();1780Transform2D xform = transform * ci->get_screen_transform();17811782const Vector2 endpoints[4] = {1783xform.xform(rect.position),1784xform.xform(rect.position + Vector2(rect.size.x, 0)),1785xform.xform(rect.position + rect.size),1786xform.xform(rect.position + Vector2(0, rect.size.y))1787};17881789const DragType dragger[] = {1790DRAG_TOP_LEFT,1791DRAG_TOP,1792DRAG_TOP_RIGHT,1793DRAG_RIGHT,1794DRAG_BOTTOM_RIGHT,1795DRAG_BOTTOM,1796DRAG_BOTTOM_LEFT,1797DRAG_LEFT1798};17991800DragType resize_drag = DRAG_NONE;1801real_t radius = (select_handle->get_size().width / 2) * 1.5;18021803for (int i = 0; i < 4; i++) {1804int prev = (i + 3) % 4;1805int next = (i + 1) % 4;18061807Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();1808ofs *= (select_handle->get_size().width / 2);1809ofs += endpoints[i];1810if (ofs.distance_to(b->get_position()) < radius) {1811resize_drag = dragger[i * 2];1812}18131814ofs = (endpoints[i] + endpoints[next]) / 2;1815ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);1816if (ofs.distance_to(b->get_position()) < radius) {1817resize_drag = dragger[i * 2 + 1];1818}1819}18201821if (resize_drag != DRAG_NONE) {1822drag_type = resize_drag;1823drag_from = transform.affine_inverse().xform(b->get_position());1824drag_selection = List<CanvasItem *>();1825drag_selection.push_back(ci);1826_save_canvas_item_state(drag_selection);1827return true;1828}1829}1830}1831}1832}18331834if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||1835drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1836// Resize the node1837if (m.is_valid()) {1838CanvasItem *ci = drag_selection.front()->get();1839CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);1840//Reset state1841ci->_edit_set_state(se->undo_state);18421843bool uniform = m->is_shift_pressed();1844bool symmetric = m->is_alt_pressed();18451846Rect2 local_rect = ci->_edit_get_rect();1847real_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);1848Point2 current_begin = local_rect.get_position();1849Point2 current_end = local_rect.get_position() + local_rect.get_size();1850Point2 max_begin = (symmetric) ? (current_begin + current_end - ci->_edit_get_minimum_size()) / 2.0 : current_end - ci->_edit_get_minimum_size();1851Point2 min_end = (symmetric) ? (current_begin + current_end + ci->_edit_get_minimum_size()) / 2.0 : current_begin + ci->_edit_get_minimum_size();1852Point2 center = (current_begin + current_end) / 2.0;18531854drag_to = transform.affine_inverse().xform(m->get_position());18551856Transform2D xform = ci->get_screen_transform();18571858Point2 drag_to_snapped_begin;1859Point2 drag_to_snapped_end;18601861drag_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);1862drag_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);18631864Point2 drag_begin = xform.affine_inverse().xform(drag_to_snapped_begin);1865Point2 drag_end = xform.affine_inverse().xform(drag_to_snapped_end);18661867// Horizontal resize1868if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1869current_begin.x = MIN(drag_begin.x, max_begin.x);1870} else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {1871current_end.x = MAX(drag_end.x, min_end.x);1872}18731874// Vertical resize1875if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1876current_begin.y = MIN(drag_begin.y, max_begin.y);1877} else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1878current_end.y = MAX(drag_end.y, min_end.y);1879}18801881// Uniform resize1882if (uniform) {1883if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT) {1884current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);1885} else if (drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM) {1886current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;1887} else {1888if (aspect >= 1.0) {1889if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1890current_begin.y = current_end.y - aspect * (current_end.x - current_begin.x);1891} else {1892current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);1893}1894} else {1895if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1896current_begin.x = current_end.x - (current_end.y - current_begin.y) / aspect;1897} else {1898current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;1899}1900}1901}1902}19031904// Symmetric resize1905if (symmetric) {1906if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {1907current_end.x = 2.0 * center.x - current_begin.x;1908} else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {1909current_begin.x = 2.0 * center.x - current_end.x;1910}1911if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {1912current_end.y = 2.0 * center.y - current_begin.y;1913} else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {1914current_begin.y = 2.0 * center.y - current_end.y;1915}1916}1917ci->_edit_set_rect(Rect2(current_begin, current_end - current_begin));1918return true;1919}19201921// Confirm resize1922if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {1923const Node2D *node2d = Object::cast_to<Node2D>(drag_selection.front()->get());1924if (node2d) {1925// Extends from Node2D.1926// Node2D doesn't have an actual stored rect size, unlike Controls.1927_commit_canvas_item_state(1928drag_selection,1929vformat(1930TTR("Scale Node2D \"%s\" to (%s, %s)"),1931drag_selection.front()->get()->get_name(),1932Math::snapped(drag_selection.front()->get()->_edit_get_scale().x, 0.01),1933Math::snapped(drag_selection.front()->get()->_edit_get_scale().y, 0.01)),1934true);1935} else {1936// Extends from Control.1937_commit_canvas_item_state(1938drag_selection,1939vformat(1940TTR("Resize Control \"%s\" to (%d, %d)"),1941drag_selection.front()->get()->get_name(),1942drag_selection.front()->get()->_edit_get_rect().size.x,1943drag_selection.front()->get()->_edit_get_rect().size.y),1944true);1945}19461947if (key_auto_insert_button->is_pressed()) {1948_insert_animation_keys(false, false, true, true);1949}19501951snap_target[0] = SNAP_TARGET_NONE;1952snap_target[1] = SNAP_TARGET_NONE;1953_reset_drag();1954viewport->queue_redraw();1955return true;1956}19571958// Cancel a drag1959if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {1960_restore_canvas_item_state(drag_selection);1961snap_target[0] = SNAP_TARGET_NONE;1962snap_target[1] = SNAP_TARGET_NONE;1963_reset_drag();1964viewport->queue_redraw();1965return true;1966}1967}1968return false;1969}19701971bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {1972Ref<InputEventMouseButton> b = p_event;1973Ref<InputEventMouseMotion> m = p_event;19741975// Drag resize handles1976if (drag_type == DRAG_NONE) {1977if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() &&1978((tool == TOOL_SELECT && b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {1979bool has_locked_items = false;1980List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);19811982// Remove non-movable nodes.1983for (CanvasItem *ci : selection) {1984if (!_is_node_movable(ci, true)) {1985selection.erase(ci);1986}1987}19881989if (!selection.is_empty()) {1990CanvasItem *ci = selection.front()->get();19911992Transform2D edit_transform;1993if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {1994edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);1995} else {1996edit_transform = ci->_edit_get_transform();1997}19981999Transform2D xform = transform * ci->get_screen_transform();2000Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();2001Transform2D simple_xform = viewport->get_transform() * unscaled_transform;20022003drag_type = DRAG_SCALE_BOTH;20042005if (show_transformation_gizmos) {2006Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);2007Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2008if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2009drag_type = DRAG_SCALE_X;2010}2011Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2012if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2013drag_type = DRAG_SCALE_Y;2014}2015}20162017drag_from = transform.affine_inverse().xform(b->get_position());2018drag_selection = selection;2019_save_canvas_item_state(drag_selection);2020return true;2021} else {2022if (has_locked_items) {2023EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);2024}2025return has_locked_items;2026}2027}2028} else if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {2029// Resize the node2030if (m.is_valid()) {2031_restore_canvas_item_state(drag_selection);20322033drag_to = transform.affine_inverse().xform(m->get_position());20342035Size2 scale_max;2036if (drag_type != DRAG_SCALE_BOTH) {2037for (CanvasItem *ci : drag_selection) {2038Size2 scale = ci->_edit_get_scale();20392040if (Math::abs(scale.x) > Math::abs(scale_max.x)) {2041scale_max.x = scale.x;2042}2043if (Math::abs(scale.y) > Math::abs(scale_max.y)) {2044scale_max.y = scale.y;2045}2046}2047}20482049Transform2D edit_transform;2050bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y);2051if (using_temp_pivot) {2052edit_transform = Transform2D(drag_selection.front()->get()->_edit_get_rotation(), temp_pivot);2053} else {2054edit_transform = drag_selection.front()->get()->_edit_get_transform();2055}2056for (CanvasItem *ci : drag_selection) {2057Transform2D parent_xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();2058Transform2D unscaled_transform = (transform * parent_xform * edit_transform).orthonormalized();2059Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;20602061bool uniform = m->is_shift_pressed();2062bool is_ctrl = m->is_command_or_control_pressed();20632064Point2 drag_from_local = simple_xform.xform(drag_from);2065Point2 drag_to_local = simple_xform.xform(drag_to);2066Point2 offset = drag_to_local - drag_from_local;20672068Size2 scale = ci->_edit_get_scale();2069Size2 original_scale = scale;2070real_t ratio = scale.y / scale.x;2071if (drag_type == DRAG_SCALE_BOTH) {2072Size2 scale_factor = drag_to_local / drag_from_local;2073if (uniform) {2074scale *= (scale_factor.x + scale_factor.y) / 2.0;2075} else {2076scale *= scale_factor;2077}2078} else {2079Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE;2080Size2 parent_scale = parent_xform.get_scale();2081// Take into account the biggest scale, so all nodes are scaled uniformly.2082scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y) / (scale_max / original_scale);20832084if (drag_type == DRAG_SCALE_X) {2085scale.x += scale_factor.x;2086if (uniform) {2087scale.y = scale.x * ratio;2088}2089} else if (drag_type == DRAG_SCALE_Y) {2090scale.y -= scale_factor.y;2091if (uniform) {2092scale.x = scale.y / ratio;2093}2094}2095}20962097if (snap_scale && !is_ctrl) {2098if (snap_relative) {2099scale.x = original_scale.x * (Math::round((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step);2100scale.y = original_scale.y * (Math::round((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step);2101} else {2102scale.x = Math::round(scale.x / snap_scale_step) * snap_scale_step;2103scale.y = Math::round(scale.y / snap_scale_step) * snap_scale_step;2104}2105}21062107ci->_edit_set_scale(scale);21082109if (using_temp_pivot) {2110Point2 ci_origin = ci->_edit_get_transform().get_origin();2111ci->_edit_set_position(ci_origin + (ci_origin - temp_pivot) * ((scale - original_scale) / original_scale));2112}2113}21142115return true;2116}21172118// Confirm resize2119if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {2120if (drag_selection.size() != 1) {2121_commit_canvas_item_state(2122drag_selection,2123vformat(TTR("Scale %d CanvasItems"), drag_selection.size()),2124true);2125} else {2126_commit_canvas_item_state(2127drag_selection,2128vformat(TTR("Scale CanvasItem \"%s\" to (%s, %s)"),2129drag_selection.front()->get()->get_name(),2130Math::snapped(drag_selection.front()->get()->_edit_get_scale().x, 0.01),2131Math::snapped(drag_selection.front()->get()->_edit_get_scale().y, 0.01)),2132true);2133}2134if (key_auto_insert_button->is_pressed()) {2135_insert_animation_keys(false, false, true, true);2136}21372138_reset_drag();2139viewport->queue_redraw();2140return true;2141}21422143// Cancel a drag2144if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {2145_restore_canvas_item_state(drag_selection);2146_reset_drag();2147viewport->queue_redraw();2148return true;2149}2150}2151return false;2152}21532154bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {2155Ref<InputEventMouseButton> b = p_event;2156Ref<InputEventMouseMotion> m = p_event;2157Ref<InputEventKey> k = p_event;21582159if (drag_type == DRAG_NONE) {2160//Start moving the nodes2161if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {2162if ((tool == TOOL_SELECT && b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {2163bool has_locked_items = false;2164List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);21652166if (selection.size() > 0) {2167drag_selection.clear();2168for (CanvasItem *E : selection) {2169if (_is_node_movable(E, true)) {2170drag_selection.push_back(E);2171}2172}21732174drag_type = DRAG_MOVE;21752176CanvasItem *ci = selection.front()->get();2177Transform2D parent_xform = ci->get_screen_transform() * ci->get_transform().affine_inverse();2178Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized();2179Transform2D simple_xform = viewport->get_transform() * unscaled_transform;21802181if (show_transformation_gizmos) {2182Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);2183Rect2 x_handle_rect = Rect2(move_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2184if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2185drag_type = DRAG_MOVE_X;2186}2187Rect2 y_handle_rect = Rect2(-5 * EDSCALE, move_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);2188if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {2189drag_type = DRAG_MOVE_Y;2190}2191}21922193drag_from = transform.affine_inverse().xform(b->get_position());2194_save_canvas_item_state(drag_selection);21952196return true;2197} else {2198if (has_locked_items) {2199EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);2200}2201return has_locked_items;2202}2203}2204}2205}22062207if (drag_type == DRAG_MOVE || drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {2208// Move the nodes2209if (m.is_valid() && !drag_selection.is_empty()) {2210_restore_canvas_item_state(drag_selection, true);22112212drag_to = transform.affine_inverse().xform(m->get_position());2213Point2 previous_pos;2214if (drag_selection.size() == 1) {2215Transform2D parent_xform = drag_selection.front()->get()->get_screen_transform() * drag_selection.front()->get()->get_transform().affine_inverse();2216previous_pos = parent_xform.xform(drag_selection.front()->get()->_edit_get_position());2217} else {2218previous_pos = _get_encompassing_rect_from_list(drag_selection).position;2219}22202221Point2 drag_delta = drag_to - drag_from;2222if (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {2223const CanvasItem *selected = drag_selection.front()->get();2224Transform2D parent_xform = selected->get_screen_transform() * selected->get_transform().affine_inverse();2225Transform2D unscaled_transform = (transform * parent_xform * selected->_edit_get_transform()).orthonormalized();2226Transform2D simple_xform = viewport->get_transform() * unscaled_transform;22272228drag_delta = simple_xform.affine_inverse().basis_xform(drag_delta);2229if (drag_type == DRAG_MOVE_X) {2230drag_delta.y = 0;2231} else {2232drag_delta.x = 0;2233}2234drag_delta = simple_xform.basis_xform(drag_delta);2235}2236Point2 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);22372238bool single_axis = m->is_shift_pressed();2239if (single_axis) {2240if (Math::abs(new_pos.x - previous_pos.x) > Math::abs(new_pos.y - previous_pos.y)) {2241new_pos.y = previous_pos.y;2242} else {2243new_pos.x = previous_pos.x;2244}2245}22462247for (CanvasItem *ci : drag_selection) {2248Transform2D parent_xform_inv = ci->get_transform() * ci->get_screen_transform().affine_inverse();2249ci->_edit_set_position(ci->_edit_get_position() + parent_xform_inv.basis_xform(new_pos - previous_pos));2250}2251return true;2252}22532254// Confirm the move (only if it was moved)2255if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {2256if (transform.affine_inverse().xform(b->get_position()) != drag_from) {2257if (drag_selection.size() != 1) {2258_commit_canvas_item_state(2259drag_selection,2260vformat(TTR("Move %d CanvasItems"), drag_selection.size()),2261true);2262} else {2263_commit_canvas_item_state(2264drag_selection,2265vformat(2266TTR("Move CanvasItem \"%s\" to (%d, %d)"),2267drag_selection.front()->get()->get_name(),2268drag_selection.front()->get()->_edit_get_position().x,2269drag_selection.front()->get()->_edit_get_position().y),2270true);2271}2272}22732274if (key_auto_insert_button->is_pressed()) {2275_insert_animation_keys(true, false, false, true);2276}22772278//Make sure smart snapping lines disappear.2279snap_target[0] = SNAP_TARGET_NONE;2280snap_target[1] = SNAP_TARGET_NONE;22812282_reset_drag();2283viewport->queue_redraw();2284return true;2285}22862287// Cancel a drag2288if (ED_IS_SHORTCUT("canvas_item_editor/cancel_transform", p_event) || (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed())) {2289_restore_canvas_item_state(drag_selection, true);2290snap_target[0] = SNAP_TARGET_NONE;2291snap_target[1] = SNAP_TARGET_NONE;2292_reset_drag();2293viewport->queue_redraw();2294return true;2295}2296}22972298// Move the canvas items with the arrow keys2299if (k.is_valid() && k->is_pressed() && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&2300(k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {2301if (!k->is_echo()) {2302// Start moving the canvas items with the keyboard, if they are movable2303List<CanvasItem *> selection = _get_edited_canvas_items();23042305drag_selection.clear();2306for (CanvasItem *item : selection) {2307if (_is_node_movable(item, true)) {2308drag_selection.push_back(item);2309}2310}23112312drag_type = DRAG_KEY_MOVE;2313drag_from = Vector2();2314drag_to = Vector2();2315_save_canvas_item_state(drag_selection, true);2316}23172318if (drag_selection.size() > 0) {2319_restore_canvas_item_state(drag_selection, true);23202321bool move_local_base = k->is_alt_pressed();2322bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed();23232324Vector2 dir;2325if (k->get_keycode() == Key::UP) {2326dir += Vector2(0, -1);2327} else if (k->get_keycode() == Key::DOWN) {2328dir += Vector2(0, 1);2329} else if (k->get_keycode() == Key::LEFT) {2330dir += Vector2(-1, 0);2331} else if (k->get_keycode() == Key::RIGHT) {2332dir += Vector2(1, 0);2333}2334if (k->is_shift_pressed()) {2335dir *= grid_step * Math::pow(2.0, grid_step_multiplier);2336}23372338drag_to += dir;2339if (k->is_shift_pressed()) {2340drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier));2341}23422343Point2 previous_pos;2344if (drag_selection.size() == 1) {2345Transform2D xform = drag_selection.front()->get()->get_global_transform_with_canvas() * drag_selection.front()->get()->get_transform().affine_inverse();2346previous_pos = xform.xform(drag_selection.front()->get()->_edit_get_position());2347} else {2348previous_pos = _get_encompassing_rect_from_list(drag_selection).position;2349}23502351Point2 new_pos;2352if (drag_selection.size() == 1) {2353Node2D *node_2d = Object::cast_to<Node2D>(drag_selection.front()->get());2354if (node_2d && move_local_base_rotated) {2355Transform2D m2;2356m2.rotate(node_2d->get_rotation());2357new_pos += m2.xform(drag_to);2358} else if (move_local_base) {2359new_pos += drag_to;2360} else {2361new_pos = previous_pos + (drag_to - drag_from);2362}2363} else {2364new_pos = previous_pos + (drag_to - drag_from);2365}23662367for (CanvasItem *ci : drag_selection) {2368Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();2369ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));2370}2371}2372return true;2373}23742375if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&2376(k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {2377// Confirm canvas items move by arrow keys2378if ((!Input::get_singleton()->is_key_pressed(Key::UP)) &&2379(!Input::get_singleton()->is_key_pressed(Key::DOWN)) &&2380(!Input::get_singleton()->is_key_pressed(Key::LEFT)) &&2381(!Input::get_singleton()->is_key_pressed(Key::RIGHT))) {2382if (drag_selection.size() > 1) {2383_commit_canvas_item_state(2384drag_selection,2385vformat(TTR("Move %d CanvasItems"), drag_selection.size()),2386true);2387} else if (drag_selection.size() == 1) {2388_commit_canvas_item_state(2389drag_selection,2390vformat(TTR("Move CanvasItem \"%s\" to (%d, %d)"),2391drag_selection.front()->get()->get_name(),2392drag_selection.front()->get()->_edit_get_position().x,2393drag_selection.front()->get()->_edit_get_position().y),2394true);2395}2396_reset_drag();2397}2398viewport->queue_redraw();2399return true;2400}24012402return (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 case2403}24042405bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {2406Ref<InputEventMouseButton> b = p_event;2407Ref<InputEventMouseMotion> m = p_event;2408Ref<InputEventKey> k = p_event;24092410if (drag_type == DRAG_NONE || (drag_type == DRAG_BOX_SELECTION && b.is_valid() && !b->is_pressed())) {2411if (b.is_valid() && b->is_pressed() &&2412((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed()) ||2413(b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) {2414// Popup the selection menu list2415Point2 click = transform.affine_inverse().xform(b->get_position());24162417_get_canvas_items_at_pos(click, selection_results, b->is_alt_pressed());24182419if (selection_results.size() == 1) {2420CanvasItem *item = selection_results[0].item;2421selection_results.clear();24222423_select_click_on_item(item, click, b->is_shift_pressed());24242425return true;2426} else if (!selection_results.is_empty()) {2427// Sorts items according the their z-index2428selection_results.sort();24292430NodePath root_path = get_tree()->get_edited_scene_root()->get_path();2431StringName root_name = root_path.get_name(root_path.get_name_count() - 1);2432int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));24332434for (int i = 0; i < selection_results.size(); i++) {2435CanvasItem *item = selection_results[i].item;24362437Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(item, "Node");2438String node_path = "/" + root_name + "/" + String(root_path.rel_path_to(item->get_path()));24392440int locked = 0;2441if (_is_node_locked(item)) {2442locked = 1;2443} else {2444Node *scene = EditorNode::get_singleton()->get_edited_scene();2445Node *node = item;24462447while (node && node != scene->get_parent()) {2448CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);2449if (ci_tmp && node->has_meta("_edit_group_")) {2450locked = 2;2451}2452node = node->get_parent();2453}2454}24552456String suffix;2457if (locked == 1) {2458suffix = " (" + TTR("Locked") + ")";2459} else if (locked == 2) {2460suffix = " (" + TTR("Grouped") + ")";2461}2462selection_menu->add_item((String)item->get_name() + suffix);2463selection_menu->set_item_icon(i, icon);2464selection_menu->set_item_icon_max_width(i, icon_max_width);2465selection_menu->set_item_metadata(i, node_path);2466selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);2467}24682469selection_results_menu = selection_results;2470selection_menu_additive_selection = b->is_shift_pressed();2471selection_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));2472selection_menu->reset_size();2473selection_menu->popup();2474return true;2475}2476}24772478if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {2479add_node_menu->clear();2480add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Add")), TTRC("Add Node Here..."), ADD_NODE);2481add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Instance")), TTRC("Instantiate Scene Here..."), ADD_INSTANCE);2482for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) {2483if (Object::cast_to<CanvasItem>(node)) {2484add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTRC("Paste Node(s) Here"), ADD_PASTE);2485break;2486}2487}2488for (Node *node : EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list()) {2489if (Object::cast_to<CanvasItem>(node)) {2490add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ToolMove")), TTRC("Move Node(s) Here"), ADD_MOVE);2491break;2492}2493}24942495// Context menu plugin receives paths of nodes under cursor. It's a complex operation, so perform it only when necessary.2496if (EditorContextMenuPluginManager::get_singleton()->has_plugins_for_slot(EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR)) {2497selection_results.clear();2498_get_canvas_items_at_pos(transform.affine_inverse().xform(viewport->get_local_mouse_position()), selection_results, true);24992500PackedStringArray paths;2501paths.resize(selection_results.size());2502String *paths_write = paths.ptrw();25032504for (int i = 0; i < paths.size(); i++) {2505paths_write[i] = String(selection_results[i].item->get_path());2506}2507EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(add_node_menu, EditorContextMenuPlugin::CONTEXT_SLOT_2D_EDITOR, paths);2508}25092510add_node_menu->reset_size();2511add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));2512add_node_menu->popup();2513node_create_position = transform.affine_inverse().xform(b->get_position());2514return true;2515}25162517Point2 click;2518bool 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);2519if (can_select) {2520click = transform.affine_inverse().xform(b->get_position());2521// Allow selecting on release when performed very small box selection (necessary when Shift is pressed, see below).2522can_select = b->is_pressed() || (drag_type == DRAG_BOX_SELECTION && click.distance_to(drag_from) <= DRAG_THRESHOLD);2523}25242525if (can_select) {2526// Single item selection.2527Node *scene = EditorNode::get_singleton()->get_edited_scene();2528if (!scene) {2529return true;2530}25312532// Find the item to select.2533CanvasItem *ci = nullptr;25342535Vector<_SelectResult> selection = Vector<_SelectResult>();2536// Retrieve the canvas items.2537_get_canvas_items_at_pos(click, selection);2538if (!selection.is_empty()) {2539ci = selection[0].item;2540}25412542// Shift also allows forcing box selection when item was clicked.2543if (!ci || (b->is_shift_pressed() && b->is_pressed())) {2544// Start a box selection.2545if (!b->is_shift_pressed()) {2546// Clear the selection if not additive.2547editor_selection->clear();2548viewport->queue_redraw();2549selected_from_canvas = true;2550};25512552if (b->is_pressed()) {2553drag_from = click;2554drag_type = DRAG_BOX_SELECTION;2555box_selecting_to = drag_from;2556return true;2557}2558} else {2559bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed());2560// Start dragging.2561if (still_selected && (tool == TOOL_SELECT || tool == TOOL_MOVE) && b->is_pressed()) {2562// Drag the node(s) if requested.2563drag_start_origin = click;2564drag_type = DRAG_QUEUED;2565} else if (!b->is_pressed()) {2566_reset_drag();2567}2568// Select the item.2569return true;2570}2571}2572}25732574if (drag_type == DRAG_QUEUED) {2575if (b.is_valid() && !b->is_pressed()) {2576_reset_drag();2577return true;2578}2579if (m.is_valid()) {2580Point2 click = transform.affine_inverse().xform(m->get_position());2581bool movement_threshold_passed = drag_start_origin.distance_to(click) > (8 * MAX(1, EDSCALE)) / zoom;2582if (m.is_valid() && movement_threshold_passed) {2583List<CanvasItem *> selection2 = _get_edited_canvas_items();25842585drag_selection.clear();2586for (CanvasItem *E : selection2) {2587if (_is_node_movable(E, true)) {2588drag_selection.push_back(E);2589}2590}25912592if (selection2.size() > 0) {2593drag_type = DRAG_MOVE;2594drag_from = drag_start_origin;2595_save_canvas_item_state(drag_selection);2596}2597return true;2598}2599}2600}26012602if (drag_type == DRAG_BOX_SELECTION) {2603if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {2604// Confirms box selection.2605Node *scene = EditorNode::get_singleton()->get_edited_scene();2606if (scene) {2607List<CanvasItem *> selitems;26082609Point2 bsfrom = drag_from;2610Point2 bsto = box_selecting_to;2611if (bsfrom.x > bsto.x) {2612SWAP(bsfrom.x, bsto.x);2613}2614if (bsfrom.y > bsto.y) {2615SWAP(bsfrom.y, bsto.y);2616}26172618_find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems);2619if (selitems.size() == 1 && editor_selection->get_top_selected_node_list().is_empty()) {2620EditorNode::get_singleton()->push_item(selitems.front()->get());2621}2622for (CanvasItem *E : selitems) {2623editor_selection->add_node(E);2624}2625}26262627_reset_drag();2628viewport->queue_redraw();2629return true;2630}26312632if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {2633// Cancel box selection.2634_reset_drag();2635viewport->queue_redraw();2636return true;2637}26382639if (m.is_valid()) {2640// Update box selection.2641box_selecting_to = transform.affine_inverse().xform(m->get_position());2642viewport->queue_redraw();2643return true;2644}2645}26462647if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true) && drag_type == DRAG_NONE) {2648// Unselect everything2649editor_selection->clear();2650viewport->queue_redraw();2651}2652return false;2653}26542655bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) {2656if (tool != TOOL_RULER) {2657ruler_tool_active = false;2658return false;2659}26602661Ref<InputEventMouseButton> b = p_event;2662Ref<InputEventMouseMotion> m = p_event;26632664Point2 previous_origin = ruler_tool_origin;2665if (!ruler_tool_active) {2666ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset);2667}26682669if (ruler_tool_active && b.is_valid() && b->get_button_index() == MouseButton::RIGHT) {2670ruler_tool_active = false;2671viewport->queue_redraw();2672return true;2673}26742675if (b.is_valid() && b->get_button_index() == MouseButton::LEFT) {2676if (b->is_pressed()) {2677ruler_tool_active = true;2678} else {2679ruler_tool_active = false;2680}26812682viewport->queue_redraw();2683return true;2684}26852686if (m.is_valid() && (ruler_tool_active || (grid_snap_active && previous_origin != ruler_tool_origin))) {2687viewport->queue_redraw();2688return true;2689}26902691return false;2692}26932694bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {2695Ref<InputEventMouseMotion> m = p_event;2696if (m.is_valid()) {2697Point2 click = transform.affine_inverse().xform(m->get_position());26982699// Checks if the hovered items changed, redraw the viewport if so2700Vector<_SelectResult> hovering_results_items;2701_get_canvas_items_at_pos(click, hovering_results_items);2702hovering_results_items.sort();27032704// Compute the nodes names and icon position2705Vector<_HoverResult> hovering_results_tmp;2706for (int i = 0; i < hovering_results_items.size(); i++) {2707CanvasItem *ci = hovering_results_items[i].item;27082709if (ci->_edit_use_rect()) {2710continue;2711}27122713_HoverResult hover_result;2714hover_result.position = ci->get_screen_transform().get_origin();2715hover_result.icon = EditorNode::get_singleton()->get_object_icon(ci);2716hover_result.name = ci->get_name();27172718hovering_results_tmp.push_back(hover_result);2719}27202721// Check if changed, if so, redraw.2722bool changed = false;2723if (hovering_results_tmp.size() == hovering_results.size()) {2724for (int i = 0; i < hovering_results_tmp.size(); i++) {2725_HoverResult a = hovering_results_tmp[i];2726_HoverResult b = hovering_results[i];2727if (a.icon != b.icon || a.name != b.name || a.position != b.position) {2728changed = true;2729break;2730}2731}2732} else {2733changed = true;2734}27352736if (changed) {2737hovering_results = hovering_results_tmp;2738viewport->queue_redraw();2739}27402741return true;2742}27432744return false;2745}27462747void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {2748bool accepted = false;27492750Ref<InputEventMouseButton> mb = p_event;2751bool 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.27522753if (EDITOR_GET("editors/panning/simple_panning") || !pan_pressed || release_lmb) {2754accepted = true;2755if (_gui_input_rulers_and_guides(p_event)) {2756// print_line("Rulers and guides");2757} else if (EditorNode::get_singleton()->get_editor_plugins_over()->forward_gui_input(p_event)) {2758// print_line("Plugin");2759} else if (_gui_input_open_scene_on_double_click(p_event)) {2760// print_line("Open scene on double click");2761} else if (_gui_input_scale(p_event)) {2762// print_line("Set scale");2763} else if (_gui_input_pivot(p_event)) {2764// print_line("Set pivot");2765} else if (_gui_input_resize(p_event)) {2766// print_line("Resize");2767} else if (_gui_input_rotate(p_event)) {2768// print_line("Rotate");2769} else if (_gui_input_move(p_event)) {2770// print_line("Move");2771} else if (_gui_input_anchors(p_event)) {2772// print_line("Anchors");2773} else if (_gui_input_ruler_tool(p_event)) {2774// print_line("Measure");2775} else if (_gui_input_select(p_event)) {2776// print_line("Selection");2777} else {2778// print_line("Not accepted");2779accepted = false;2780}2781}27822783accepted = (_gui_input_zoom_or_pan(p_event, accepted) || accepted);27842785if (accepted) {2786accept_event();2787}27882789// Handles the mouse hovering2790_gui_input_hover(p_event);27912792if (mb.is_valid()) {2793// Update the default cursor.2794_update_cursor();2795}27962797// Grab focus2798if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {2799callable_mp((Control *)viewport, &Control::grab_focus).call_deferred();2800}2801}28022803void CanvasItemEditor::_update_cursor() {2804if (cursor_shape_override != CURSOR_ARROW) {2805set_default_cursor_shape(cursor_shape_override);2806return;2807}28082809// Choose the correct default cursor.2810CursorShape c = CURSOR_ARROW;2811switch (tool) {2812case TOOL_MOVE:2813c = CURSOR_MOVE;2814break;2815case TOOL_EDIT_PIVOT:2816c = CURSOR_CROSS;2817break;2818case TOOL_PAN:2819c = CURSOR_DRAG;2820break;2821case TOOL_RULER:2822c = CURSOR_CROSS;2823break;2824default:2825break;2826}2827if (pan_pressed) {2828c = CURSOR_DRAG;2829}2830set_default_cursor_shape(c);2831}28322833void CanvasItemEditor::_update_lock_and_group_button() {2834bool all_locked = true;2835bool all_group = true;2836bool has_canvas_item = false;2837List<Node *> selection = editor_selection->get_top_selected_node_list();2838if (selection.is_empty()) {2839all_locked = false;2840all_group = false;2841} else {2842for (Node *E : selection) {2843CanvasItem *item = Object::cast_to<CanvasItem>(E);2844if (item) {2845if (all_locked && !item->has_meta("_edit_lock_")) {2846all_locked = false;2847}2848if (all_group && !item->has_meta("_edit_group_")) {2849all_group = false;2850}2851has_canvas_item = true;2852}2853if (!all_locked && !all_group) {2854break;2855}2856}2857}28582859all_locked = all_locked && has_canvas_item;2860all_group = all_group && has_canvas_item;28612862lock_button->set_visible(!all_locked);2863lock_button->set_disabled(!has_canvas_item);2864unlock_button->set_visible(all_locked);2865unlock_button->set_disabled(!has_canvas_item);2866group_button->set_visible(!all_group);2867group_button->set_disabled(!has_canvas_item);2868ungroup_button->set_visible(all_group);2869ungroup_button->set_disabled(!has_canvas_item);2870}28712872void CanvasItemEditor::set_cursor_shape_override(CursorShape p_shape) {2873if (cursor_shape_override == p_shape) {2874return;2875}2876cursor_shape_override = p_shape;2877_update_cursor();2878}28792880Control::CursorShape CanvasItemEditor::get_cursor_shape(const Point2 &p_pos) const {2881// Compute an eventual rotation of the cursor2882const CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE };2883int rotation_array_index = 0;28842885List<CanvasItem *> selection = _get_edited_canvas_items();2886if (selection.size() == 1) {2887const double angle = Math::fposmod((double)selection.front()->get()->get_global_transform_with_canvas().get_rotation(), Math::PI);2888if (angle > Math::PI * 7.0 / 8.0) {2889rotation_array_index = 0;2890} else if (angle > Math::PI * 5.0 / 8.0) {2891rotation_array_index = 1;2892} else if (angle > Math::PI * 3.0 / 8.0) {2893rotation_array_index = 2;2894} else if (angle > Math::PI * 1.0 / 8.0) {2895rotation_array_index = 3;2896} else {2897rotation_array_index = 0;2898}2899}29002901// Choose the correct cursor2902CursorShape c = get_default_cursor_shape();2903switch (drag_type) {2904case DRAG_LEFT:2905case DRAG_RIGHT:2906c = rotation_array[rotation_array_index];2907break;2908case DRAG_V_GUIDE:2909c = CURSOR_HSIZE;2910break;2911case DRAG_TOP:2912case DRAG_BOTTOM:2913c = rotation_array[(rotation_array_index + 2) % 4];2914break;2915case DRAG_H_GUIDE:2916c = CURSOR_VSIZE;2917break;2918case DRAG_TOP_LEFT:2919case DRAG_BOTTOM_RIGHT:2920c = rotation_array[(rotation_array_index + 3) % 4];2921break;2922case DRAG_DOUBLE_GUIDE:2923c = CURSOR_FDIAGSIZE;2924break;2925case DRAG_TOP_RIGHT:2926case DRAG_BOTTOM_LEFT:2927c = rotation_array[(rotation_array_index + 1) % 4];2928break;2929case DRAG_MOVE:2930c = CURSOR_MOVE;2931break;2932default:2933break;2934}29352936if (is_hovering_h_guide) {2937c = CURSOR_VSIZE;2938} else if (is_hovering_v_guide) {2939c = CURSOR_HSIZE;2940}29412942if (pan_pressed) {2943c = CURSOR_DRAG;2944}2945return c;2946}29472948void CanvasItemEditor::_draw_text_at_position(Point2 p_position, const String &p_string, Side p_side) {2949Color color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));2950color.a = 0.8;2951Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));2952int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));2953Size2 text_size = font->get_string_size(p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);2954switch (p_side) {2955case SIDE_LEFT:2956p_position += Vector2(-text_size.x - 5, text_size.y / 2);2957break;2958case SIDE_TOP:2959p_position += Vector2(-text_size.x / 2, -5);2960break;2961case SIDE_RIGHT:2962p_position += Vector2(5, text_size.y / 2);2963break;2964case SIDE_BOTTOM:2965p_position += Vector2(-text_size.x / 2, text_size.y + 5);2966break;2967}2968viewport->draw_string(font, p_position, p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);2969}29702971void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position, Side p_side) {2972String str = TS->format_number(vformat("%d " + TTR("px"), p_value));2973if (p_value != 0) {2974_draw_text_at_position(p_position, str, p_side);2975}2976}29772978void CanvasItemEditor::_draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side) {2979String str = TS->format_number(vformat("%.1f ", p_value * 100.0)) + TS->percent_sign();2980if (p_value != 0) {2981_draw_text_at_position(p_position, str, p_side);2982}2983}29842985void CanvasItemEditor::_draw_focus() {2986// Draw the focus around the base viewport2987if (viewport->has_focus()) {2988get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size()));2989}2990}29912992void CanvasItemEditor::_draw_guides() {2993Color guide_color = EDITOR_GET("editors/2d/guides_color");2994Transform2D xform = viewport_scrollable->get_transform() * transform;29952996// Guides already there.2997if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {2998Array vguides = scene->get_meta("_edit_vertical_guides_", Array());2999for (int i = 0; i < vguides.size(); i++) {3000if (drag_type == DRAG_V_GUIDE && i == dragged_guide_index) {3001continue;3002}3003real_t x = xform.xform(Point2(vguides[i], 0)).x;3004viewport->draw_line(Point2(x, 0), Point2(x, viewport->get_size().y), guide_color, Math::round(EDSCALE));3005}30063007Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());3008for (int i = 0; i < hguides.size(); i++) {3009if (drag_type == DRAG_H_GUIDE && i == dragged_guide_index) {3010continue;3011}3012real_t y = xform.xform(Point2(0, hguides[i])).y;3013viewport->draw_line(Point2(0, y), Point2(viewport->get_size().x, y), guide_color, Math::round(EDSCALE));3014}3015}30163017// Dragged guide.3018Color text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3019Color outline_color = text_color.inverted();3020const float outline_size = 2;3021if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) {3022String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x)));3023Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3024int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3025Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3026viewport->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);3027viewport->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);3028viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE));3029}3030if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) {3031String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y)));3032Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3033int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3034Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3035viewport->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);3036viewport->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);3037viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE));3038}3039}30403041void CanvasItemEditor::_draw_smart_snapping() {3042Color line_color = EDITOR_GET("editors/2d/smart_snapping_line_color");3043if (snap_target[0] != SNAP_TARGET_NONE && snap_target[0] != SNAP_TARGET_GRID) {3044viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);3045viewport->draw_line(Point2(0, -1.0e+10F), Point2(0, 1.0e+10F), line_color);3046viewport->draw_set_transform_matrix(viewport->get_transform());3047}3048if (snap_target[1] != SNAP_TARGET_NONE && snap_target[1] != SNAP_TARGET_GRID) {3049viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);3050viewport->draw_line(Point2(-1.0e+10F, 0), Point2(1.0e+10F, 0), line_color);3051viewport->draw_set_transform_matrix(viewport->get_transform());3052}3053}30543055void CanvasItemEditor::_draw_rulers() {3056Color bg_color = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor));3057Color graduation_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)).lerp(bg_color, 0.5);3058Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3059font_color.a = 0.8;3060Ref<Font> font = get_theme_font(SNAME("rulers"), EditorStringName(EditorFonts));3061real_t ruler_tick_scale = ruler_width_scaled / 15.0;30623063// The rule transform3064Transform2D ruler_transform;3065if (grid_snap_active || _is_grid_visible()) {3066List<CanvasItem *> selection = _get_edited_canvas_items();3067if (snap_relative && selection.size() > 0) {3068ruler_transform.translate_local(_get_encompassing_rect_from_list(selection).position);3069ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));3070} else {3071ruler_transform.translate_local(grid_offset);3072ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));3073}3074while ((transform * ruler_transform).get_scale().x < 50.0 * ruler_tick_scale || (transform * ruler_transform).get_scale().y < 50.0 * ruler_tick_scale) {3075ruler_transform.scale_basis(Point2(2, 2));3076}3077} else {3078real_t basic_rule = 100;3079for (int i = 0; basic_rule * zoom > 100 * ruler_tick_scale; i++) {3080basic_rule /= (i % 2) ? 5.0 : 2.0;3081}3082for (int i = 0; basic_rule * zoom < 60 * ruler_tick_scale; i++) {3083basic_rule *= (i % 2) ? 2.0 : 5.0;3084}3085ruler_transform.scale(Size2(basic_rule, basic_rule));3086}30873088// Subdivisions3089int major_subdivision = 2;3090Transform2D major_subdivide;3091major_subdivide.scale(Size2(1.0 / major_subdivision, 1.0 / major_subdivision));30923093int minor_subdivision = 5;3094Transform2D minor_subdivide;3095minor_subdivide.scale(Size2(1.0 / minor_subdivision, 1.0 / minor_subdivision));30963097// First and last graduations to draw (in the ruler space)3098Point2 first = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Point2(ruler_width_scaled, ruler_width_scaled));3099Point2 last = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(viewport->get_size());31003101// Draw top ruler3102viewport->draw_rect(Rect2(Point2(ruler_width_scaled, 0), Size2(viewport->get_size().x, ruler_width_scaled)), bg_color);3103for (int i = Math::ceil(first.x); i < last.x; i++) {3104Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).round();3105if (i % (major_subdivision * minor_subdivision) == 0) {3106viewport->draw_line(Point2(position.x, 0), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3107real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x;3108viewport->draw_string(font, Point2(position.x + MAX(Math::round(ruler_font_size / 8.0), 2), font->get_ascent(ruler_font_size) + Math::round(EDSCALE)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, ruler_font_size, font_color);3109} else {3110if (i % minor_subdivision == 0) {3111viewport->draw_line(Point2(position.x, ruler_width_scaled * 0.33), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3112} else {3113viewport->draw_line(Point2(position.x, ruler_width_scaled * 0.75), Point2(position.x, ruler_width_scaled), graduation_color, Math::round(EDSCALE));3114}3115}3116}31173118// Draw left ruler3119viewport->draw_rect(Rect2(Point2(0, ruler_width_scaled), Size2(ruler_width_scaled, viewport->get_size().y)), bg_color);3120for (int i = Math::ceil(first.y); i < last.y; i++) {3121Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).round();3122if (i % (major_subdivision * minor_subdivision) == 0) {3123viewport->draw_line(Point2(0, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3124real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).y;31253126Transform2D text_xform = Transform2D(-Math::PI / 2.0, Point2(font->get_ascent(ruler_font_size) + Math::round(EDSCALE), position.y - 2));3127viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform);3128viewport->draw_string(font, Point2(), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, ruler_font_size, font_color);3129viewport->draw_set_transform_matrix(viewport->get_transform());31303131} else {3132if (i % minor_subdivision == 0) {3133viewport->draw_line(Point2(ruler_width_scaled * 0.33, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3134} else {3135viewport->draw_line(Point2(ruler_width_scaled * 0.75, position.y), Point2(ruler_width_scaled, position.y), graduation_color, Math::round(EDSCALE));3136}3137}3138}31393140// Draw the top left corner3141viewport->draw_rect(Rect2(Point2(), Size2(ruler_width_scaled, ruler_width_scaled)), graduation_color);3142}31433144void CanvasItemEditor::_draw_grid() {3145if (_is_grid_visible()) {3146// Draw the grid3147Vector2 real_grid_offset;3148const List<CanvasItem *> selection = _get_edited_canvas_items();31493150if (snap_relative && selection.size() > 0) {3151const Vector2 topleft = _get_encompassing_rect_from_list(selection).position;3152real_grid_offset.x = std::fmod(topleft.x, grid_step.x * (real_t)Math::pow(2.0, grid_step_multiplier));3153real_grid_offset.y = std::fmod(topleft.y, grid_step.y * (real_t)Math::pow(2.0, grid_step_multiplier));3154} else {3155real_grid_offset = grid_offset;3156}31573158// Draw a "primary" line every several lines to make measurements easier.3159// The step is configurable in the Configure Snap dialog.3160const Color secondary_grid_color = EDITOR_GET("editors/2d/grid_color");3161const Color primary_grid_color =3162Color(secondary_grid_color.r, secondary_grid_color.g, secondary_grid_color.b, secondary_grid_color.a * 2.5);31633164const Size2 viewport_size = viewport->get_size();3165const Transform2D xform = transform.affine_inverse();3166int last_cell = 0;31673168if (grid_step.x != 0) {3169for (int i = 0; i < viewport_size.width; i++) {3170const int cell =3171Math::fast_ftoi(Math::floor((xform.xform(Vector2(i, 0)).x - real_grid_offset.x) / (grid_step.x * Math::pow(2.0, grid_step_multiplier))));31723173if (i == 0) {3174last_cell = cell;3175}31763177if (last_cell != cell) {3178Color grid_color;3179if (primary_grid_step.x <= 1) {3180grid_color = secondary_grid_color;3181} else {3182grid_color = cell % primary_grid_step.x == 0 ? primary_grid_color : secondary_grid_color;3183}31843185viewport->draw_line(Point2(i, 0), Point2(i, viewport_size.height), grid_color, Math::round(EDSCALE));3186}3187last_cell = cell;3188}3189}31903191if (grid_step.y != 0) {3192for (int i = 0; i < viewport_size.height; i++) {3193const int cell =3194Math::fast_ftoi(Math::floor((xform.xform(Vector2(0, i)).y - real_grid_offset.y) / (grid_step.y * Math::pow(2.0, grid_step_multiplier))));31953196if (i == 0) {3197last_cell = cell;3198}31993200if (last_cell != cell) {3201Color grid_color;3202if (primary_grid_step.y <= 1) {3203grid_color = secondary_grid_color;3204} else {3205grid_color = cell % primary_grid_step.y == 0 ? primary_grid_color : secondary_grid_color;3206}32073208viewport->draw_line(Point2(0, i), Point2(viewport_size.width, i), grid_color, Math::round(EDSCALE));3209}3210last_cell = cell;3211}3212}3213}3214}32153216void CanvasItemEditor::_draw_ruler_tool() {3217if (tool != TOOL_RULER) {3218return;3219}32203221const Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));3222if (ruler_tool_active) {3223Color ruler_primary_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));3224Color ruler_secondary_color = ruler_primary_color;3225ruler_secondary_color.a = 0.5;32263227Point2 begin = (ruler_tool_origin - view_offset) * zoom;3228Point2 end = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom - view_offset * zoom;3229Point2 corner = Point2(begin.x, end.y);3230Vector2 length_vector = (begin - end).abs() / zoom;32313232const real_t horizontal_angle_rad = length_vector.angle();3233const real_t vertical_angle_rad = Math::PI / 2.0 - horizontal_angle_rad;32343235Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3236int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3237Color font_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3238Color font_secondary_color = font_color;3239font_secondary_color.set_v(font_secondary_color.get_v() > 0.5 ? 0.7 : 0.3);3240Color outline_color = font_color.inverted();3241float text_height = font->get_height(font_size);32423243const float outline_size = 4;3244const float text_width = 76;3245const float angle_text_width = 54;32463247Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);3248text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);3249text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);32503251// Draw lines.3252viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3));32533254bool draw_secondary_lines = !(Math::is_equal_approx(begin.y, corner.y) || Math::is_equal_approx(end.x, corner.x));3255if (draw_secondary_lines) {3256viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE));3257viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE));32583259// Angle arcs.3260int arc_point_count = 8;3261real_t arc_radius_max_length_percent = 0.1;3262real_t ruler_length = length_vector.length() * zoom;3263real_t arc_max_radius = 50.0;3264real_t arc_line_width = 2.0;32653266const Vector2 end_to_begin = (end - begin);32673268real_t arc_1_start_angle = end_to_begin.x < 03269? (end_to_begin.y < 0 ? 3.0 * Math::PI / 2.0 - vertical_angle_rad : Math::PI / 2.0)3270: (end_to_begin.y < 0 ? 3.0 * Math::PI / 2.0 : Math::PI / 2.0 - vertical_angle_rad);3271real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad;3272// Constrain arc to triangle height & max size.3273real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, Math::abs(end_to_begin.y)), arc_max_radius);32743275real_t arc_2_start_angle = end_to_begin.x < 03276? (end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad)3277: (end_to_begin.y < 0 ? Math::PI - horizontal_angle_rad : Math::PI);3278real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad;3279// Constrain arc to triangle width & max size.3280real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, Math::abs(end_to_begin.x)), arc_max_radius);32813282viewport->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));3283viewport->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));3284}32853286// Draw text.3287if (begin.is_equal_approx(end)) {3288viewport->draw_string_outline(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3289viewport->draw_string(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);3290viewport->draw_texture(position_icon, (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);3291return;3292}32933294viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3295viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);32963297if (draw_secondary_lines) {3298const int horizontal_angle = std::round(180 * horizontal_angle_rad / Math::PI);3299const int vertical_angle = std::round(180 * vertical_angle_rad / Math::PI);33003301Point2 text_pos2 = text_pos;3302text_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);3303viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3304viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);33053306Point2 v_angle_text_pos;3307v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);3308v_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);3309viewport->draw_string_outline(font, v_angle_text_pos, TS->format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3310viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);33113312text_pos2 = text_pos;3313text_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);3314viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3315viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);33163317Point2 h_angle_text_pos;3318h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);3319if (begin.y < end.y) {3320h_angle_text_pos.y = end.y + text_height * 1.5;3321if (Math::abs(text_pos2.x - h_angle_text_pos.x) < text_width) {3322int height_multiplier = 1.5 + (int)grid_snap_active;3323h_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));3324}3325} else {3326h_angle_text_pos.y = end.y - text_height * 0.5;3327if (Math::abs(text_pos2.x - h_angle_text_pos.x) < text_width) {3328int height_multiplier = 1 + (int)grid_snap_active;3329h_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));3330}3331}3332viewport->draw_string_outline(font, h_angle_text_pos, TS->format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3333viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);3334}33353336if (grid_snap_active) {3337text_pos = (begin + end) / 2 + Vector2(-text_width / 2, text_height / 2);3338text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);3339text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2);33403341if (draw_secondary_lines) {3342viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3343viewport->draw_string(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);33443345Point2 text_pos2 = text_pos;3346text_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);3347viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), std::round(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3348viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), std::round(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);33493350text_pos2 = text_pos;3351text_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);3352viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), std::round(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3353viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), std::round(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);3354} else {3355viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), std::round((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);3356viewport->draw_string(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), std::round((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);3357}3358}3359} else {3360if (grid_snap_active) {3361viewport->draw_texture(position_icon, (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);3362}3363}3364}33653366void CanvasItemEditor::_draw_control_anchors(Control *control) {3367Transform2D xform = transform * control->get_screen_transform();3368RID ci = viewport->get_canvas_item();3369if (tool == TOOL_SELECT && !Object::cast_to<Container>(control->get_parent())) {3370// Compute the anchors3371real_t anchors_values[4];3372anchors_values[0] = control->get_anchor(SIDE_LEFT);3373anchors_values[1] = control->get_anchor(SIDE_TOP);3374anchors_values[2] = control->get_anchor(SIDE_RIGHT);3375anchors_values[3] = control->get_anchor(SIDE_BOTTOM);33763377Vector2 anchors_pos[4];3378for (int i = 0; i < 4; i++) {3379Vector2 value = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);3380anchors_pos[i] = xform.xform(_anchor_to_position(control, value));3381}33823383// Draw the anchors handles3384Rect2 anchor_rects[4];3385if (control->is_layout_rtl()) {3386anchor_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));3387anchor_rects[1] = Rect2(anchors_pos[1] - anchor_handle->get_size(), anchor_handle->get_size());3388anchor_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));3389anchor_rects[3] = Rect2(anchors_pos[3], -anchor_handle->get_size());3390} else {3391anchor_rects[0] = Rect2(anchors_pos[0] - anchor_handle->get_size(), anchor_handle->get_size());3392anchor_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));3393anchor_rects[2] = Rect2(anchors_pos[2], -anchor_handle->get_size());3394anchor_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));3395}33963397for (int i = 0; i < 4; i++) {3398anchor_handle->draw_rect(ci, anchor_rects[i]);3399}3400}3401}34023403void CanvasItemEditor::_draw_control_helpers(Control *control) {3404Transform2D xform = transform * control->get_screen_transform();3405if (tool == TOOL_SELECT && show_helpers && !Object::cast_to<Container>(control->get_parent())) {3406// Draw the helpers3407Color color_base = Color(0.8, 0.8, 0.8, 0.5);34083409// Compute the anchors3410real_t anchors_values[4];3411anchors_values[0] = control->get_anchor(SIDE_LEFT);3412anchors_values[1] = control->get_anchor(SIDE_TOP);3413anchors_values[2] = control->get_anchor(SIDE_RIGHT);3414anchors_values[3] = control->get_anchor(SIDE_BOTTOM);34153416Vector2 anchors[4];3417Vector2 anchors_pos[4];3418for (int i = 0; i < 4; i++) {3419anchors[i] = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);3420anchors_pos[i] = xform.xform(_anchor_to_position(control, anchors[i]));3421}34223423// Get which anchor is dragged3424int dragged_anchor = -1;3425switch (drag_type) {3426case DRAG_ANCHOR_ALL:3427case DRAG_ANCHOR_TOP_LEFT:3428dragged_anchor = 0;3429break;3430case DRAG_ANCHOR_TOP_RIGHT:3431dragged_anchor = 1;3432break;3433case DRAG_ANCHOR_BOTTOM_RIGHT:3434dragged_anchor = 2;3435break;3436case DRAG_ANCHOR_BOTTOM_LEFT:3437dragged_anchor = 3;3438break;3439default:3440break;3441}34423443if (dragged_anchor >= 0) {3444// Draw the 4 lines when dragged3445Color color_snapped = Color(0.64, 0.93, 0.67, 0.5);34463447Vector2 corners_pos[4];3448for (int i = 0; i < 4; i++) {3449corners_pos[i] = xform.xform(_anchor_to_position(control, Vector2((i == 0 || i == 3) ? ANCHOR_BEGIN : ANCHOR_END, (i <= 1) ? ANCHOR_BEGIN : ANCHOR_END)));3450}34513452Vector2 line_starts[4];3453Vector2 line_ends[4];3454for (int i = 0; i < 4; i++) {3455real_t anchor_val = (i >= 2) ? (real_t)ANCHOR_END - anchors_values[i] : anchors_values[i];3456line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val);3457line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val);3458bool anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0;3459viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1);3460}34613462// Display the percentages next to the lines3463real_t percent_val;3464percent_val = anchors_values[(dragged_anchor + 2) % 4] - anchors_values[dragged_anchor];3465percent_val = (dragged_anchor >= 2) ? -percent_val : percent_val;3466_draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 1) % 4]) / 2, (Side)((dragged_anchor + 1) % 4));34673468percent_val = anchors_values[(dragged_anchor + 3) % 4] - anchors_values[(dragged_anchor + 1) % 4];3469percent_val = ((dragged_anchor + 1) % 4 >= 2) ? -percent_val : percent_val;3470_draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 3) % 4]) / 2, (Side)(dragged_anchor));34713472percent_val = anchors_values[(dragged_anchor + 1) % 4];3473percent_val = ((dragged_anchor + 1) % 4 >= 2) ? (real_t)ANCHOR_END - percent_val : percent_val;3474_draw_percentage_at_position(percent_val, (line_starts[dragged_anchor] + anchors_pos[dragged_anchor]) / 2, (Side)(dragged_anchor));34753476percent_val = anchors_values[dragged_anchor];3477percent_val = (dragged_anchor >= 2) ? (real_t)ANCHOR_END - percent_val : percent_val;3478_draw_percentage_at_position(percent_val, (line_ends[(dragged_anchor + 1) % 4] + anchors_pos[dragged_anchor]) / 2, (Side)((dragged_anchor + 1) % 4));3479}34803481// Draw the margin values and the node width/height when dragging control side3482const real_t ratio = 0.33;3483Transform2D parent_transform = xform * control->get_transform().affine_inverse();3484real_t node_pos_in_parent[4];34853486Rect2 parent_rect = control->get_parent_anchorable_rect();34873488node_pos_in_parent[0] = control->get_anchor(SIDE_LEFT) * parent_rect.size.width + control->get_offset(SIDE_LEFT) + parent_rect.position.x;3489node_pos_in_parent[1] = control->get_anchor(SIDE_TOP) * parent_rect.size.height + control->get_offset(SIDE_TOP) + parent_rect.position.y;3490node_pos_in_parent[2] = control->get_anchor(SIDE_RIGHT) * parent_rect.size.width + control->get_offset(SIDE_RIGHT) + parent_rect.position.x;3491node_pos_in_parent[3] = control->get_anchor(SIDE_BOTTOM) * parent_rect.size.height + control->get_offset(SIDE_BOTTOM) + parent_rect.position.y;34923493Point2 start, end;3494switch (drag_type) {3495case DRAG_LEFT:3496case DRAG_TOP_LEFT:3497case DRAG_BOTTOM_LEFT:3498_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);3499[[fallthrough]];3500case DRAG_MOVE:3501start = Vector2(node_pos_in_parent[0], Math::lerp(node_pos_in_parent[1], node_pos_in_parent[3], ratio));3502end = start - Vector2(control->get_offset(SIDE_LEFT), 0);3503_draw_margin_at_position(control->get_offset(SIDE_LEFT), parent_transform.xform((start + end) / 2), SIDE_TOP);3504viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3505break;3506default:3507break;3508}3509switch (drag_type) {3510case DRAG_RIGHT:3511case DRAG_TOP_RIGHT:3512case DRAG_BOTTOM_RIGHT:3513_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);3514[[fallthrough]];3515case DRAG_MOVE:3516start = Vector2(node_pos_in_parent[2], Math::lerp(node_pos_in_parent[3], node_pos_in_parent[1], ratio));3517end = start - Vector2(control->get_offset(SIDE_RIGHT), 0);3518_draw_margin_at_position(control->get_offset(SIDE_RIGHT), parent_transform.xform((start + end) / 2), SIDE_BOTTOM);3519viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3520break;3521default:3522break;3523}3524switch (drag_type) {3525case DRAG_TOP:3526case DRAG_TOP_LEFT:3527case DRAG_TOP_RIGHT:3528_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);3529[[fallthrough]];3530case DRAG_MOVE:3531start = Vector2(Math::lerp(node_pos_in_parent[0], node_pos_in_parent[2], ratio), node_pos_in_parent[1]);3532end = start - Vector2(0, control->get_offset(SIDE_TOP));3533_draw_margin_at_position(control->get_offset(SIDE_TOP), parent_transform.xform((start + end) / 2), SIDE_LEFT);3534viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3535break;3536default:3537break;3538}3539switch (drag_type) {3540case DRAG_BOTTOM:3541case DRAG_BOTTOM_LEFT:3542case DRAG_BOTTOM_RIGHT:3543_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);3544[[fallthrough]];3545case DRAG_MOVE:3546start = Vector2(Math::lerp(node_pos_in_parent[2], node_pos_in_parent[0], ratio), node_pos_in_parent[3]);3547end = start - Vector2(0, control->get_offset(SIDE_BOTTOM));3548_draw_margin_at_position(control->get_offset(SIDE_BOTTOM), parent_transform.xform((start + end) / 2), SIDE_RIGHT);3549viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));3550break;3551default:3552break;3553}35543555switch (drag_type) {3556//Draw the ghost rect if the node if rotated/scaled3557case DRAG_LEFT:3558case DRAG_TOP_LEFT:3559case DRAG_TOP:3560case DRAG_TOP_RIGHT:3561case DRAG_RIGHT:3562case DRAG_BOTTOM_RIGHT:3563case DRAG_BOTTOM:3564case DRAG_BOTTOM_LEFT:3565case DRAG_MOVE:3566if (control->get_rotation() != 0.0 || control->get_scale() != Vector2(1, 1)) {3567Rect2 rect = Rect2(Vector2(node_pos_in_parent[0], node_pos_in_parent[1]), control->get_size());3568viewport->draw_rect(parent_transform.xform(rect), color_base, false, Math::round(EDSCALE));3569}3570break;3571default:3572break;3573}3574}3575}35763577void CanvasItemEditor::_draw_selection() {3578Ref<Texture2D> pivot_icon = get_editor_theme_icon(SNAME("EditorPivot"));3579Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));3580Ref<Texture2D> previous_position_icon = get_editor_theme_icon(SNAME("EditorPositionPrevious"));35813582RID vp_ci = viewport->get_canvas_item();3583List<CanvasItem *> selection = _get_edited_canvas_items(true, false);3584bool single = selection.size() == 1;3585bool transform_tool = tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT;35863587for (CanvasItem *E : selection) {3588CanvasItem *ci = Object::cast_to<CanvasItem>(E);3589CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);35903591// Draw the previous position if we are dragging the node3592if (show_helpers &&3593(drag_type == DRAG_MOVE || drag_type == DRAG_ROTATE ||3594drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||3595drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT)) {3596const Transform2D pre_drag_xform = transform * se->pre_drag_xform;3597const Color pre_drag_color = Color(0.4, 0.6, 1, 0.7);35983599if (ci->_edit_use_rect()) {3600Vector2 pre_drag_endpoints[4] = {3601pre_drag_xform.xform(se->pre_drag_rect.position),3602pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)),3603pre_drag_xform.xform(se->pre_drag_rect.position + se->pre_drag_rect.size),3604pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(0, se->pre_drag_rect.size.y))3605};36063607for (int i = 0; i < 4; i++) {3608viewport->draw_line(pre_drag_endpoints[i], pre_drag_endpoints[(i + 1) % 4], pre_drag_color, Math::round(2 * EDSCALE));3609}3610} else {3611viewport->draw_texture(previous_position_icon, (pre_drag_xform.xform(Point2()) - (previous_position_icon->get_size() / 2)).floor());3612}3613}36143615bool item_locked = ci->has_meta("_edit_lock_");3616Transform2D xform = transform * ci->get_screen_transform();36173618// Draw the selected items position / surrounding boxes3619if (ci->_edit_use_rect()) {3620Rect2 rect = ci->_edit_get_rect();3621const Vector2 endpoints[4] = {3622xform.xform(rect.position),3623xform.xform(rect.position + Vector2(rect.size.x, 0)),3624xform.xform(rect.position + rect.size),3625xform.xform(rect.position + Vector2(0, rect.size.y))3626};36273628Color c = Color(1, 0.6, 0.4, 0.7);36293630if (item_locked) {3631c = Color(0.7, 0.7, 0.7, 0.7);3632}36333634for (int i = 0; i < 4; i++) {3635viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, Math::round(2 * EDSCALE));3636}3637} else {3638Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3639Transform2D simple_xform = viewport->get_transform() * unscaled_transform;3640viewport->draw_set_transform_matrix(simple_xform);3641viewport->draw_texture(position_icon, -(position_icon->get_size() / 2));3642viewport->draw_set_transform_matrix(viewport->get_transform());3643}36443645if (single && !item_locked && transform_tool) {3646// Draw the pivot3647if (ci->_edit_use_pivot()) {3648// Draw the node's pivot3649Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3650Transform2D simple_xform = viewport->get_transform() * unscaled_transform;36513652viewport->draw_set_transform_matrix(simple_xform);3653viewport->draw_texture(pivot_icon, -(pivot_icon->get_size() / 2).floor());3654viewport->draw_set_transform_matrix(viewport->get_transform());3655}36563657// Draw control-related helpers3658Control *control = Object::cast_to<Control>(ci);3659if (control && _is_node_movable(control)) {3660_draw_control_anchors(control);3661_draw_control_helpers(control);3662}36633664// Draw the resize handles3665if (tool == TOOL_SELECT && ci->_edit_use_rect() && _is_node_movable(ci)) {3666Rect2 rect = ci->_edit_get_rect();3667const Vector2 endpoints[4] = {3668xform.xform(rect.position),3669xform.xform(rect.position + Vector2(rect.size.x, 0)),3670xform.xform(rect.position + rect.size),3671xform.xform(rect.position + Vector2(0, rect.size.y))3672};3673for (int i = 0; i < 4; i++) {3674int prev = (i + 3) % 4;3675int next = (i + 1) % 4;36763677Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();3678ofs *= Math::SQRT2 * (select_handle->get_size().width / 2);36793680select_handle->draw(vp_ci, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor());36813682ofs = (endpoints[i] + endpoints[next]) / 2;3683ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);36843685select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor());3686}3687}3688}3689}36903691// Remove non-movable nodes.3692for (List<CanvasItem *>::Element *E = selection.front(); E;) {3693List<CanvasItem *>::Element *N = E->next();3694if (!_is_node_movable(E->get())) {3695selection.erase(E);3696}3697E = N;3698}36993700if (!selection.is_empty() && transform_tool && show_transformation_gizmos) {3701CanvasItem *ci = selection.front()->get();37023703Transform2D xform = transform * ci->get_screen_transform();3704bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);3705bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);37063707// Draw the move handles.3708if ((tool == TOOL_SELECT && is_alt && !is_ctrl) || tool == TOOL_MOVE) {3709Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3710Transform2D simple_xform = viewport->get_transform() * unscaled_transform;37113712Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);3713viewport->draw_set_transform_matrix(simple_xform);37143715Vector<Point2> points = {3716Vector2(move_factor.x * EDSCALE, 5 * EDSCALE),3717Vector2(move_factor.x * EDSCALE, -5 * EDSCALE),3718Vector2((move_factor.x + 10) * EDSCALE, 0)3719};37203721viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));3722viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));37233724points.clear();3725points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE));3726points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE));3727points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE));37283729viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));3730viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));37313732viewport->draw_set_transform_matrix(viewport->get_transform());3733}37343735// Draw the rescale handles.3736if ((tool == TOOL_SELECT && is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {3737Transform2D edit_transform;3738if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {3739edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);3740} else {3741edit_transform = ci->_edit_get_transform();3742}3743Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();3744Transform2D simple_xform = viewport->get_transform() * unscaled_transform;37453746Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);3747bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT);3748Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom;37493750if (drag_type == DRAG_SCALE_X) {3751scale_factor.x += offset.x;3752if (uniform) {3753scale_factor.y += offset.x;3754}3755} else if (drag_type == DRAG_SCALE_Y) {3756scale_factor.y += offset.y;3757if (uniform) {3758scale_factor.x += offset.y;3759}3760}37613762viewport->draw_set_transform_matrix(simple_xform);3763Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);3764viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));3765viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));37663767Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);3768viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));3769viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));37703771viewport->draw_set_transform_matrix(viewport->get_transform());3772}3773}37743775if (drag_type == DRAG_BOX_SELECTION) {3776// Draw the dragging box3777Point2 bsfrom = transform.xform(drag_from);3778Point2 bsto = transform.xform(box_selecting_to);37793780viewport->draw_rect(3781Rect2(bsfrom, bsto - bsfrom),3782get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));37833784viewport->draw_rect(3785Rect2(bsfrom, bsto - bsfrom),3786get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),3787false,3788Math::round(EDSCALE));3789}37903791if (drag_type == DRAG_ROTATE) {3792// Draw the line when rotating a node3793viewport->draw_line(3794transform.xform(drag_rotation_center),3795transform.xform(drag_to),3796get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.6),3797Math::round(2 * EDSCALE));3798}37993800if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {3801viewport->draw_texture(pivot_icon, (temp_pivot - view_offset) * zoom - (pivot_icon->get_size() / 2).floor(), get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));3802}3803}38043805void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_color) {3806// Draw a line going through the whole screen from a vector3807RID ci = viewport->get_canvas_item();3808Vector<Point2> points;3809Point2 from = transform.xform(p_from);3810Point2 to = transform.xform(p_to);3811Size2 viewport_size = viewport->get_size();38123813if (to.x == from.x) {3814// Vertical line3815points.push_back(Point2(to.x, 0));3816points.push_back(Point2(to.x, viewport_size.y));3817} else if (to.y == from.y) {3818// Horizontal line3819points.push_back(Point2(0, to.y));3820points.push_back(Point2(viewport_size.x, to.y));3821} else {3822real_t y_for_zero_x = (to.y * from.x - from.y * to.x) / (from.x - to.x);3823real_t x_for_zero_y = (to.x * from.y - from.x * to.y) / (from.y - to.y);3824real_t y_for_viewport_x = ((to.y - from.y) * (viewport_size.x - from.x)) / (to.x - from.x) + from.y;3825real_t x_for_viewport_y = ((to.x - from.x) * (viewport_size.y - from.y)) / (to.y - from.y) + from.x; // faux38263827//bool start_set = false;3828if (y_for_zero_x >= 0 && y_for_zero_x <= viewport_size.y) {3829points.push_back(Point2(0, y_for_zero_x));3830}3831if (x_for_zero_y >= 0 && x_for_zero_y <= viewport_size.x) {3832points.push_back(Point2(x_for_zero_y, 0));3833}3834if (y_for_viewport_x >= 0 && y_for_viewport_x <= viewport_size.y) {3835points.push_back(Point2(viewport_size.x, y_for_viewport_x));3836}3837if (x_for_viewport_y >= 0 && x_for_viewport_y <= viewport_size.x) {3838points.push_back(Point2(x_for_viewport_y, viewport_size.y));3839}3840}3841if (points.size() >= 2) {3842RenderingServer::get_singleton()->canvas_item_add_line(ci, points[0], points[1], p_color);3843}3844}38453846void CanvasItemEditor::_draw_axis() {3847if (show_origin) {3848_draw_straight_line(Point2(), Point2(1, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));3849_draw_straight_line(Point2(), Point2(0, 1), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));3850}38513852if (show_viewport) {3853RID ci = viewport->get_canvas_item();38543855Color area_axis_color = EDITOR_GET("editors/2d/viewport_border_color");38563857Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));38583859Vector2 screen_endpoints[4] = {3860transform.xform(Vector2(0, 0)),3861transform.xform(Vector2(screen_size.width, 0)),3862transform.xform(Vector2(screen_size.width, screen_size.height)),3863transform.xform(Vector2(0, screen_size.height))3864};38653866for (int i = 0; i < 4; i++) {3867RenderingServer::get_singleton()->canvas_item_add_line(ci, screen_endpoints[i], screen_endpoints[(i + 1) % 4], area_axis_color);3868}3869}3870}38713872void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {3873ERR_FAIL_NULL(p_node);38743875Node *scene = EditorNode::get_singleton()->get_edited_scene();3876if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {3877return;3878}3879CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);3880if (ci && !ci->is_visible_in_tree()) {3881return;3882}38833884Transform2D parent_xform = p_parent_xform;3885Transform2D canvas_xform = p_canvas_xform;38863887if (ci && !ci->is_set_as_top_level()) {3888parent_xform = parent_xform * ci->get_transform();3889} else if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {3890parent_xform = Transform2D();3891canvas_xform = cl->get_transform();3892} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {3893if (!vp->is_visible_subviewport()) {3894return;3895}3896parent_xform = Transform2D();3897canvas_xform = vp->get_popup_base_transform();3898}38993900for (int i = p_node->get_child_count() - 1; i >= 0; i--) {3901_draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform);3902}39033904if (show_position_gizmos && ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) {3905Transform2D xform = transform * canvas_xform * parent_xform;39063907// Draw the node's position3908Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPositionUnselected"));3909Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();3910Transform2D simple_xform = viewport->get_transform() * unscaled_transform;3911viewport->draw_set_transform_matrix(simple_xform);3912viewport->draw_texture(position_icon, -position_icon->get_size() / 2, Color(1.0, 1.0, 1.0, 0.5));3913viewport->draw_set_transform_matrix(viewport->get_transform());3914}3915}39163917void CanvasItemEditor::_draw_hover() {3918List<Rect2> previous_rects;3919Vector2 icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));39203921for (int i = 0; i < hovering_results.size(); i++) {3922Ref<Texture2D> node_icon = hovering_results[i].icon;3923String node_name = hovering_results[i].name;39243925Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));3926int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));3927Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);3928Size2 item_size = Size2(icon_size.x + 4 + node_name_size.x, MAX(icon_size.y, node_name_size.y - 3));39293930Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(icon_size.x, -icon_size.y) / 4);3931// Rectify the position to avoid overlapping items3932for (const Rect2 &E : previous_rects) {3933if (E.intersects(Rect2(pos, item_size))) {3934pos.y = E.get_position().y - item_size.y;3935}3936}39373938previous_rects.push_back(Rect2(pos, item_size));39393940// Draw icon3941viewport->draw_texture_rect(node_icon, Rect2(pos, icon_size), false, Color(1.0, 1.0, 1.0, 0.5));39423943// Draw name3944viewport->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));3945}3946}39473948void CanvasItemEditor::_draw_message() {3949if (drag_type != DRAG_NONE && !drag_selection.is_empty() && drag_selection.front()->get()) {3950Transform2D current_transform = drag_selection.front()->get()->get_global_transform();39513952double snap = EDITOR_GET("interface/inspector/default_float_step");3953int snap_step_decimals = Math::range_step_decimals(snap);3954#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals)))39553956switch (drag_type) {3957case DRAG_MOVE:3958case DRAG_MOVE_X:3959case DRAG_MOVE_Y: {3960Vector2 delta = current_transform.get_origin() - original_transform.get_origin();3961if (drag_type == DRAG_MOVE) {3962message = TTR("Moving:") + " (" + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ") px";3963} else if (drag_type == DRAG_MOVE_X) {3964message = TTR("Moving:") + " " + FORMAT(delta.x) + " px";3965} else if (drag_type == DRAG_MOVE_Y) {3966message = TTR("Moving:") + " " + FORMAT(delta.y) + " px";3967}3968} break;39693970case DRAG_ROTATE: {3971real_t delta = Math::rad_to_deg(current_transform.get_rotation() - original_transform.get_rotation());3972message = TTR("Rotating:") + " " + FORMAT(delta) + String::utf8(" °");3973} break;39743975case DRAG_SCALE_X:3976case DRAG_SCALE_Y:3977case DRAG_SCALE_BOTH: {3978Vector2 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();3979Vector2 delta = current_transform.get_scale() / original_scale;3980if (drag_type == DRAG_SCALE_BOTH) {3981message = TTR("Scaling:") + String::utf8(" ×(") + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ")";3982} else if (drag_type == DRAG_SCALE_X) {3983message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.x);3984} else if (drag_type == DRAG_SCALE_Y) {3985message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.y);3986}3987} break;39883989default:3990break;3991}3992#undef FORMAT3993}39943995if (message.is_empty()) {3996return;3997}39983999Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));4000int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));4001Point2 msgpos = Point2(ruler_width_scaled + 10 * EDSCALE, viewport->get_size().y - 14 * EDSCALE);4002viewport->draw_string(font, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));4003viewport->draw_string(font, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));4004viewport->draw_string(font, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));4005}40064007void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {4008ERR_FAIL_NULL(p_node);40094010Node *scene = EditorNode::get_singleton()->get_edited_scene();4011if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {4012return;4013}4014CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);4015if (ci && !ci->is_visible_in_tree()) {4016return;4017}40184019Transform2D parent_xform = p_parent_xform;4020Transform2D canvas_xform = p_canvas_xform;40214022if (ci && !ci->is_set_as_top_level()) {4023parent_xform = parent_xform * ci->get_transform();4024} else if (CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node)) {4025parent_xform = Transform2D();4026canvas_xform = cl->get_transform();4027} else if (Viewport *vp = Object::cast_to<Viewport>(p_node)) {4028if (!vp->is_visible_subviewport()) {4029return;4030}4031parent_xform = Transform2D();4032canvas_xform = vp->get_popup_base_transform();4033}40344035for (int i = p_node->get_child_count() - 1; i >= 0; i--) {4036_draw_locks_and_groups(p_node->get_child(i), parent_xform, canvas_xform);4037}40384039RID viewport_ci = viewport->get_canvas_item();4040if (ci) {4041real_t offset = 0;40424043Ref<Texture2D> lock = get_editor_theme_icon(SNAME("LockViewport"));4044if (show_lock_gizmos && p_node->has_meta("_edit_lock_")) {4045lock->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));4046offset += lock->get_size().x;4047}40484049Ref<Texture2D> group = get_editor_theme_icon(SNAME("GroupViewport"));4050if (show_group_gizmos && ci->has_meta("_edit_group_")) {4051group->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));4052//offset += group->get_size().x;4053}4054}4055}40564057void CanvasItemEditor::_draw_viewport() {4058// Update the transform4059transform = Transform2D();4060transform.scale_basis(Size2(zoom, zoom));4061transform.columns[2] = -view_offset * zoom;4062EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform);40634064_draw_grid();4065_draw_ruler_tool();4066_draw_axis();4067if (EditorNode::get_singleton()->get_edited_scene()) {4068_draw_locks_and_groups(EditorNode::get_singleton()->get_edited_scene());4069_draw_invisible_nodes_positions(EditorNode::get_singleton()->get_edited_scene());4070}4071_draw_selection();40724073RID ci = viewport->get_canvas_item();4074RenderingServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D());40754076EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over();4077if (!over_plugin_list->is_empty()) {4078over_plugin_list->forward_canvas_draw_over_viewport(viewport);4079}4080EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over();4081if (!force_over_plugin_list->is_empty()) {4082force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport);4083}40844085if (show_rulers) {4086_draw_rulers();4087}4088if (show_guides) {4089_draw_guides();4090}4091_draw_smart_snapping();4092_draw_focus();4093_draw_hover();4094_draw_message();4095}40964097void CanvasItemEditor::update_viewport() {4098_update_scrollbars();4099viewport->queue_redraw();4100}41014102void CanvasItemEditor::set_current_tool(Tool p_tool) {4103_button_tool_select(p_tool);4104}41054106void CanvasItemEditor::_update_editor_settings() {4107button_center_view->set_button_icon(get_editor_theme_icon(SNAME("CenterView")));4108select_button->set_button_icon(get_editor_theme_icon(SNAME("ToolSelect")));4109select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));4110list_select_button->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));4111move_button->set_button_icon(get_editor_theme_icon(SNAME("ToolMove")));4112scale_button->set_button_icon(get_editor_theme_icon(SNAME("ToolScale")));4113rotate_button->set_button_icon(get_editor_theme_icon(SNAME("ToolRotate")));4114smart_snap_button->set_button_icon(get_editor_theme_icon(SNAME("Snap")));4115grid_snap_button->set_button_icon(get_editor_theme_icon(SNAME("SnapGrid")));4116snap_config_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));4117skeleton_menu->set_button_icon(get_editor_theme_icon(SNAME("Bone")));4118pan_button->set_button_icon(get_editor_theme_icon(SNAME("ToolPan")));4119ruler_button->set_button_icon(get_editor_theme_icon(SNAME("Ruler")));4120pivot_button->set_button_icon(get_editor_theme_icon(SNAME("EditPivot")));4121select_handle = get_editor_theme_icon(SNAME("EditorHandle"));4122anchor_handle = get_editor_theme_icon(SNAME("EditorControlAnchor"));4123lock_button->set_button_icon(get_editor_theme_icon(SNAME("Lock")));4124unlock_button->set_button_icon(get_editor_theme_icon(SNAME("Unlock")));4125group_button->set_button_icon(get_editor_theme_icon(SNAME("Group")));4126ungroup_button->set_button_icon(get_editor_theme_icon(SNAME("Ungroup")));4127key_loc_button->set_button_icon(get_editor_theme_icon(SNAME("KeyPosition")));4128key_rot_button->set_button_icon(get_editor_theme_icon(SNAME("KeyRotation")));4129key_scale_button->set_button_icon(get_editor_theme_icon(SNAME("KeyScale")));4130key_insert_button->set_button_icon(get_editor_theme_icon(SNAME("Key")));4131key_auto_insert_button->set_button_icon(get_editor_theme_icon(SNAME("AutoKey")));4132// Use a different color for the active autokey icon to make them easier4133// to distinguish from the other key icons at the top. On a light theme,4134// the icon will be dark, so we need to lighten it before blending it4135// with the red color.4136const Color key_auto_color = EditorThemeManager::is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25);4137key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));4138animation_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));41394140context_toolbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));41414142panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));4143panner->set_scroll_speed(EDITOR_GET("editors/panning/2d_editor_pan_speed"));4144panner->setup_warped_panning(get_viewport(), EDITOR_GET("editors/panning/warped_mouse_panning"));4145panner->set_zoom_style((ViewPanner::ZoomStyle)EDITOR_GET("editors/panning/zoom_style").operator int());41464147// Compute the ruler width here so we can reuse the result throughout the various draw functions.4148real_t ruler_width_unscaled = EDITOR_GET("editors/2d/ruler_width");4149ruler_font_size = MAX(get_theme_font_size(SNAME("rulers_size"), EditorStringName(EditorFonts)) * ruler_width_unscaled / 15.0, 8);4150ruler_width_scaled = MAX(ruler_width_unscaled * EDSCALE, ruler_font_size * 2.0);4151}41524153void CanvasItemEditor::_project_settings_changed() {4154EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels"));4155}41564157void CanvasItemEditor::_notification(int p_what) {4158switch (p_what) {4159case NOTIFICATION_TRANSLATION_CHANGED: {4160select_button->set_tooltip_text(keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+Drag: Move selected node.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Alt+Drag: Scale selected node.") + "\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."));4161pivot_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."));4162} break;41634164case NOTIFICATION_READY: {4165_update_lock_and_group_button();41664167SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));4168ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &CanvasItemEditor::_project_settings_changed));4169} break;41704171case NOTIFICATION_ACCESSIBILITY_UPDATE: {4172RID ae = get_accessibility_element();4173ERR_FAIL_COND(ae.is_null());41744175//TODO4176DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);4177DisplayServer::get_singleton()->accessibility_update_set_value(ae, TTR(vformat("The %s is not accessible at this time.", "Canvas item editor")));4178} break;41794180case NOTIFICATION_PROCESS: {4181// Update the viewport if the canvas_item changes4182List<CanvasItem *> selection = _get_edited_canvas_items(true);4183for (CanvasItem *ci : selection) {4184CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);41854186Rect2 rect;4187if (ci->_edit_use_rect()) {4188rect = ci->_edit_get_rect();4189} else {4190rect = Rect2();4191}4192Transform2D xform = ci->get_global_transform();41934194if (rect != se->prev_rect || xform != se->prev_xform) {4195viewport->queue_redraw();4196se->prev_rect = rect;4197se->prev_xform = xform;4198}41994200Control *control = Object::cast_to<Control>(ci);4201if (control) {4202real_t anchors[4];4203Vector2 pivot;42044205pivot = control->get_pivot_offset();4206anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT);4207anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT);4208anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP);4209anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM);42104211if (pivot != se->prev_pivot || 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]) {4212se->prev_pivot = pivot;4213se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT];4214se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT];4215se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP];4216se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM];4217viewport->queue_redraw();4218}4219}4220}42214222// Activate / Deactivate the pivot tool.4223pivot_button->set_disabled(selection.is_empty());42244225// Update the viewport if bones changes4226for (KeyValue<BoneKey, BoneList> &E : bone_list) {4227Object *b = ObjectDB::get_instance(E.key.from);4228if (!b) {4229viewport->queue_redraw();4230break;4231}42324233Node2D *b2 = Object::cast_to<Node2D>(b);4234if (!b2 || !b2->is_inside_tree()) {4235continue;4236}42374238Transform2D global_xform = b2->get_global_transform();42394240if (global_xform != E.value.xform) {4241E.value.xform = global_xform;4242viewport->queue_redraw();4243}42444245Bone2D *bone = Object::cast_to<Bone2D>(b);4246if (bone && bone->get_length() != E.value.length) {4247E.value.length = bone->get_length();4248viewport->queue_redraw();4249}4250}4251} break;42524253case NOTIFICATION_ENTER_TREE: {4254select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));4255select_sb->set_texture_margin_all(4);4256select_sb->set_content_margin_all(4);42574258AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("keying_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));4259AnimationPlayerEditor::get_singleton()->connect("animation_selected", callable_mp(this, &CanvasItemEditor::_keying_changed).unbind(1));4260_keying_changed();4261_update_editor_settings();42624263connect("item_lock_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));4264connect("item_group_status_changed", callable_mp(this, &CanvasItemEditor::_update_lock_and_group_button));4265} break;42664267case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {4268if (EditorThemeManager::is_generated_theme_outdated() ||4269EditorSettings::get_singleton()->check_changed_settings_in_group("editors/panning") ||4270EditorSettings::get_singleton()->check_changed_settings_in_group("editors/2d")) {4271_update_editor_settings();4272update_viewport();4273}4274} break;42754276case NOTIFICATION_APPLICATION_FOCUS_OUT:4277case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {4278if (drag_type != DRAG_NONE) {4279_reset_drag();4280viewport->queue_redraw();4281}4282} break;4283}4284}42854286void CanvasItemEditor::_selection_changed() {4287_update_lock_and_group_button();4288if (!selected_from_canvas) {4289_reset_drag();4290}4291selected_from_canvas = false;42924293if (temp_pivot != Vector2(Math::INF, Math::INF)) {4294temp_pivot = Vector2(Math::INF, Math::INF);4295viewport->queue_redraw();4296}4297}42984299void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {4300if (!p_canvas_item) {4301return;4302}43034304Array selection = editor_selection->get_selected_nodes();4305if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) {4306_reset_drag();4307}4308}43094310void CanvasItemEditor::_update_scrollbars() {4311updating_scroll = true;43124313// Move the zoom buttons.4314Point2 controls_vb_begin = Point2(5, 5);4315controls_vb_begin += (show_rulers) ? Point2(ruler_width_scaled, ruler_width_scaled) : Point2();4316controls_vb->set_begin(controls_vb_begin);43174318Size2 hmin = h_scroll->get_minimum_size();4319Size2 vmin = v_scroll->get_minimum_size();43204321// Get the visible frame.4322Size2 screen_rect = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));4323Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height));43244325// Calculate scrollable area.4326Rect2 canvas_item_rect = Rect2(Point2(), screen_rect);4327if (EditorNode::get_singleton()->is_inside_tree() && EditorNode::get_singleton()->get_edited_scene()) {4328Rect2 content_rect = _get_encompassing_rect(EditorNode::get_singleton()->get_edited_scene());4329canvas_item_rect.expand_to(content_rect.position);4330canvas_item_rect.expand_to(content_rect.position + content_rect.size);4331}4332canvas_item_rect.size += screen_rect * 2;4333canvas_item_rect.position -= screen_rect;43344335// Updates the scrollbars.4336const Size2 size = viewport->get_size();4337const Point2 begin = canvas_item_rect.position;4338const Point2 end = canvas_item_rect.position + canvas_item_rect.size - local_rect.size / zoom;43394340if (canvas_item_rect.size.height <= (local_rect.size.y / zoom)) {4341v_scroll->hide();4342} else {4343v_scroll->show();4344v_scroll->set_min(MIN(view_offset.y, begin.y));4345v_scroll->set_max(MAX(view_offset.y, end.y) + screen_rect.y);4346v_scroll->set_page(screen_rect.y);4347}43484349if (canvas_item_rect.size.width <= (local_rect.size.x / zoom)) {4350h_scroll->hide();4351} else {4352h_scroll->show();4353h_scroll->set_min(MIN(view_offset.x, begin.x));4354h_scroll->set_max(MAX(view_offset.x, end.x) + screen_rect.x);4355h_scroll->set_page(screen_rect.x);4356}43574358// Move and resize the scrollbars, avoiding overlap.4359if (is_layout_rtl()) {4360v_scroll->set_begin(Point2(0, (show_rulers) ? ruler_width_scaled : 0));4361v_scroll->set_end(Point2(vmin.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));4362} else {4363v_scroll->set_begin(Point2(size.width - vmin.width, (show_rulers) ? ruler_width_scaled : 0));4364v_scroll->set_end(Point2(size.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));4365}4366h_scroll->set_begin(Point2((show_rulers) ? ruler_width_scaled : 0, size.height - hmin.height));4367h_scroll->set_end(Point2(size.width - (v_scroll->is_visible() ? vmin.width : 0), size.height));43684369// Calculate scrollable area.4370v_scroll->set_value(view_offset.y);4371h_scroll->set_value(view_offset.x);43724373previous_update_view_offset = view_offset;4374updating_scroll = false;4375}43764377void CanvasItemEditor::_update_scroll(real_t) {4378if (updating_scroll) {4379return;4380}43814382view_offset.x = h_scroll->get_value();4383view_offset.y = v_scroll->get_value();4384viewport->queue_redraw();4385}43864387void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {4388p_zoom = CLAMP(p_zoom, zoom_widget->get_min_zoom(), zoom_widget->get_max_zoom());43894390if (p_zoom == zoom) {4391return;4392}43934394real_t prev_zoom = zoom;4395zoom = p_zoom;43964397view_offset += p_position / prev_zoom - p_position / zoom;43984399// We want to align in-scene pixels to screen pixels, this prevents blurry rendering4400// of small details (texts, lines).4401// This correction adds a jitter movement when zooming, so we correct only when the4402// zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)4403const real_t closest_zoom_factor = Math::round(zoom);4404if (Math::is_zero_approx(zoom - closest_zoom_factor)) {4405// Make sure scene pixel at view_offset is aligned on a screen pixel.4406Vector2 view_offset_int = view_offset.floor();4407Vector2 view_offset_frac = view_offset - view_offset_int;4408view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;4409}44104411zoom_widget->set_zoom(zoom);4412update_viewport();4413}44144415void CanvasItemEditor::_update_zoom(real_t p_zoom) {4416_zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);4417}44184419void CanvasItemEditor::_shortcut_zoom_set(real_t p_zoom) {4420_zoom_on_position(p_zoom * MAX(1, EDSCALE), viewport->get_local_mouse_position());4421}44224423void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) {4424smart_snap_active = p_status;4425viewport->queue_redraw();4426}44274428void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) {4429grid_snap_active = p_status;4430viewport->queue_redraw();4431}44324433void CanvasItemEditor::_button_tool_select(int p_index) {4434Button *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };4435for (int i = 0; i < TOOL_MAX; i++) {4436tb[i]->set_pressed(i == p_index);4437}44384439tool = (Tool)p_index;44404441if (p_index == TOOL_EDIT_PIVOT && Input::get_singleton()->is_key_pressed(Key::SHIFT)) {4442// Special action that places temporary rotation pivot in the middle of the selection.4443List<CanvasItem *> selection = _get_edited_canvas_items();4444if (!selection.is_empty()) {4445Vector2 center;4446for (const CanvasItem *ci : selection) {4447center += ci->get_viewport()->get_popup_base_transform().xform(ci->_edit_get_position());4448}4449temp_pivot = center / selection.size();4450}4451}44524453viewport->queue_redraw();4454_update_cursor();4455}44564457void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {4458const HashMap<Node *, Object *> &selection = editor_selection->get_selection();44594460AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();4461ERR_FAIL_COND_MSG(te->get_current_animation().is_null(), "Cannot insert animation key. No animation selected.");44624463te->make_insert_queue();4464for (const KeyValue<Node *, Object *> &E : selection) {4465CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);4466if (!ci || !ci->is_visible_in_tree()) {4467continue;4468}44694470if (Object::cast_to<Node2D>(ci)) {4471Node2D *n2d = Object::cast_to<Node2D>(ci);44724473if (key_pos && p_location) {4474te->insert_node_value_key(n2d, "position", p_on_existing);4475}4476if (key_rot && p_rotation) {4477te->insert_node_value_key(n2d, "rotation", p_on_existing);4478}4479if (key_scale && p_scale) {4480te->insert_node_value_key(n2d, "scale", p_on_existing);4481}44824483if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {4484//look for an IK chain4485List<Node2D *> ik_chain;44864487Node2D *n = Object::cast_to<Node2D>(n2d->get_parent_item());4488bool has_chain = false;44894490while (n) {4491ik_chain.push_back(n);4492if (n->has_meta("_edit_ik_")) {4493has_chain = true;4494break;4495}44964497if (!n->get_parent_item()) {4498break;4499}4500n = Object::cast_to<Node2D>(n->get_parent_item());4501}45024503if (has_chain && ik_chain.size()) {4504for (Node2D *&F : ik_chain) {4505if (key_pos) {4506te->insert_node_value_key(F, "position", p_on_existing);4507}4508if (key_rot) {4509te->insert_node_value_key(F, "rotation", p_on_existing);4510}4511if (key_scale) {4512te->insert_node_value_key(F, "scale", p_on_existing);4513}4514}4515}4516}45174518} else if (Object::cast_to<Control>(ci)) {4519Control *ctrl = Object::cast_to<Control>(ci);45204521if (key_pos) {4522te->insert_node_value_key(ctrl, "position", p_on_existing);4523}4524if (key_rot) {4525te->insert_node_value_key(ctrl, "rotation", p_on_existing);4526}4527if (key_scale) {4528te->insert_node_value_key(ctrl, "size", p_on_existing);4529}4530}4531}4532te->commit_insert_queue();4533}45344535void CanvasItemEditor::_prepare_view_menu() {4536PopupMenu *popup = view_menu->get_popup();45374538Node *root = EditorNode::get_singleton()->get_edited_scene();4539bool has_guides = root && (root->has_meta("_edit_horizontal_guides_") || root->has_meta("_edit_vertical_guides_"));4540popup->set_item_disabled(popup->get_item_index(CLEAR_GUIDES), !has_guides);4541}45424543void CanvasItemEditor::_popup_callback(int p_op) {4544EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();4545last_option = MenuOption(p_op);4546switch (p_op) {4547case SHOW_ORIGIN: {4548show_origin = !show_origin;4549int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);4550view_menu->get_popup()->set_item_checked(idx, show_origin);4551viewport->queue_redraw();4552} break;4553case SHOW_VIEWPORT: {4554show_viewport = !show_viewport;4555int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);4556view_menu->get_popup()->set_item_checked(idx, show_viewport);4557viewport->queue_redraw();4558} break;4559case SHOW_POSITION_GIZMOS: {4560show_position_gizmos = !show_position_gizmos;4561int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS);4562gizmos_menu->set_item_checked(idx, show_position_gizmos);4563viewport->queue_redraw();4564} break;4565case SHOW_LOCK_GIZMOS: {4566show_lock_gizmos = !show_lock_gizmos;4567int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS);4568gizmos_menu->set_item_checked(idx, show_lock_gizmos);4569viewport->queue_redraw();4570} break;4571case SHOW_GROUP_GIZMOS: {4572show_group_gizmos = !show_group_gizmos;4573int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS);4574gizmos_menu->set_item_checked(idx, show_group_gizmos);4575viewport->queue_redraw();4576} break;4577case SHOW_TRANSFORMATION_GIZMOS: {4578show_transformation_gizmos = !show_transformation_gizmos;4579int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS);4580gizmos_menu->set_item_checked(idx, show_transformation_gizmos);4581viewport->queue_redraw();4582} break;4583case SNAP_USE_NODE_PARENT: {4584snap_node_parent = !snap_node_parent;4585int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);4586smartsnap_config_popup->set_item_checked(idx, snap_node_parent);4587} break;4588case SNAP_USE_NODE_ANCHORS: {4589snap_node_anchors = !snap_node_anchors;4590int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);4591smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);4592} break;4593case SNAP_USE_NODE_SIDES: {4594snap_node_sides = !snap_node_sides;4595int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);4596smartsnap_config_popup->set_item_checked(idx, snap_node_sides);4597} break;4598case SNAP_USE_NODE_CENTER: {4599snap_node_center = !snap_node_center;4600int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);4601smartsnap_config_popup->set_item_checked(idx, snap_node_center);4602} break;4603case SNAP_USE_OTHER_NODES: {4604snap_other_nodes = !snap_other_nodes;4605int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);4606smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);4607} break;4608case SNAP_USE_GUIDES: {4609snap_guides = !snap_guides;4610int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);4611smartsnap_config_popup->set_item_checked(idx, snap_guides);4612} break;4613case SNAP_USE_ROTATION: {4614snap_rotation = !snap_rotation;4615int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);4616snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);4617} break;4618case SNAP_USE_SCALE: {4619snap_scale = !snap_scale;4620int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);4621snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);4622} break;4623case SNAP_RELATIVE: {4624snap_relative = !snap_relative;4625int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);4626snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);4627viewport->queue_redraw();4628} break;4629case SNAP_USE_PIXEL: {4630snap_pixel = !snap_pixel;4631int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);4632snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);4633} break;4634case SNAP_CONFIGURE: {4635static_cast<SnapDialog *>(snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);4636snap_dialog->popup_centered(Size2(320, 160) * EDSCALE);4637} break;4638case SKELETON_SHOW_BONES: {4639List<Node *> selection = editor_selection->get_top_selected_node_list();4640for (Node *E : selection) {4641// Add children nodes so they are processed4642for (int child = 0; child < E->get_child_count(); child++) {4643selection.push_back(E->get_child(child));4644}46454646Bone2D *bone_2d = Object::cast_to<Bone2D>(E);4647if (!bone_2d || !bone_2d->is_inside_tree()) {4648continue;4649}4650bone_2d->_editor_set_show_bone_gizmo(!bone_2d->_editor_get_show_bone_gizmo());4651}4652} break;4653case SHOW_HELPERS: {4654show_helpers = !show_helpers;4655int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);4656view_menu->get_popup()->set_item_checked(idx, show_helpers);4657viewport->queue_redraw();4658} break;4659case SHOW_RULERS: {4660show_rulers = !show_rulers;4661int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);4662view_menu->get_popup()->set_item_checked(idx, show_rulers);4663update_viewport();4664} break;4665case SHOW_GUIDES: {4666show_guides = !show_guides;4667int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);4668view_menu->get_popup()->set_item_checked(idx, show_guides);4669viewport->queue_redraw();4670} break;4671case LOCK_SELECTED: {4672undo_redo->create_action(TTR("Lock Selected"));46734674List<Node *> selection = editor_selection->get_top_selected_node_list();4675for (Node *E : selection) {4676CanvasItem *ci = Object::cast_to<CanvasItem>(E);4677if (!ci || !ci->is_inside_tree()) {4678continue;4679}46804681undo_redo->add_do_method(ci, "set_meta", "_edit_lock_", true);4682undo_redo->add_undo_method(ci, "remove_meta", "_edit_lock_");4683undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");4684undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");4685}4686undo_redo->add_do_method(viewport, "queue_redraw");4687undo_redo->add_undo_method(viewport, "queue_redraw");4688undo_redo->commit_action();4689} break;4690case UNLOCK_SELECTED: {4691undo_redo->create_action(TTR("Unlock Selected"));46924693List<Node *> selection = editor_selection->get_top_selected_node_list();4694for (Node *E : selection) {4695CanvasItem *ci = Object::cast_to<CanvasItem>(E);4696if (!ci || !ci->is_inside_tree()) {4697continue;4698}46994700undo_redo->add_do_method(ci, "remove_meta", "_edit_lock_");4701undo_redo->add_undo_method(ci, "set_meta", "_edit_lock_", true);4702undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");4703undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");4704}4705undo_redo->add_do_method(viewport, "queue_redraw");4706undo_redo->add_undo_method(viewport, "queue_redraw");4707undo_redo->commit_action();4708} break;4709case GROUP_SELECTED: {4710undo_redo->create_action(TTR("Group Selected"));47114712List<Node *> selection = editor_selection->get_top_selected_node_list();4713for (Node *E : selection) {4714CanvasItem *ci = Object::cast_to<CanvasItem>(E);4715if (!ci || !ci->is_inside_tree()) {4716continue;4717}47184719undo_redo->add_do_method(ci, "set_meta", "_edit_group_", true);4720undo_redo->add_undo_method(ci, "remove_meta", "_edit_group_");4721undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");4722undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");4723}4724undo_redo->add_do_method(viewport, "queue_redraw");4725undo_redo->add_undo_method(viewport, "queue_redraw");4726undo_redo->commit_action();4727} break;4728case UNGROUP_SELECTED: {4729undo_redo->create_action(TTR("Ungroup Selected"));47304731List<Node *> selection = editor_selection->get_top_selected_node_list();4732for (Node *E : selection) {4733CanvasItem *ci = Object::cast_to<CanvasItem>(E);4734if (!ci || !ci->is_inside_tree()) {4735continue;4736}47374738undo_redo->add_do_method(ci, "remove_meta", "_edit_group_");4739undo_redo->add_undo_method(ci, "set_meta", "_edit_group_", true);4740undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");4741undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");4742}4743undo_redo->add_do_method(viewport, "queue_redraw");4744undo_redo->add_undo_method(viewport, "queue_redraw");4745undo_redo->commit_action();4746} break;47474748case ANIM_INSERT_KEY:4749case ANIM_INSERT_KEY_EXISTING: {4750bool existing = p_op == ANIM_INSERT_KEY_EXISTING;47514752_insert_animation_keys(true, true, true, existing);47534754} break;4755case ANIM_INSERT_POS: {4756key_pos = key_loc_button->is_pressed();4757} break;4758case ANIM_INSERT_ROT: {4759key_rot = key_rot_button->is_pressed();4760} break;4761case ANIM_INSERT_SCALE: {4762key_scale = key_scale_button->is_pressed();4763} break;4764case ANIM_COPY_POSE: {4765pose_clipboard.clear();47664767const HashMap<Node *, Object *> &selection = editor_selection->get_selection();47684769for (const KeyValue<Node *, Object *> &E : selection) {4770CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);4771if (!ci || !ci->is_visible_in_tree()) {4772continue;4773}47744775if (Object::cast_to<Node2D>(ci)) {4776Node2D *n2d = Object::cast_to<Node2D>(ci);4777PoseClipboard pc;4778pc.pos = n2d->get_position();4779pc.rot = n2d->get_rotation();4780pc.scale = n2d->get_scale();4781pc.id = n2d->get_instance_id();4782pose_clipboard.push_back(pc);4783}4784}47854786} break;4787case ANIM_PASTE_POSE: {4788if (!pose_clipboard.size()) {4789break;4790}47914792undo_redo->create_action(TTR("Paste Pose"));4793for (const PoseClipboard &E : pose_clipboard) {4794Node2D *n2d = ObjectDB::get_instance<Node2D>(E.id);4795if (!n2d) {4796continue;4797}4798undo_redo->add_do_method(n2d, "set_position", E.pos);4799undo_redo->add_do_method(n2d, "set_rotation", E.rot);4800undo_redo->add_do_method(n2d, "set_scale", E.scale);4801undo_redo->add_undo_method(n2d, "set_position", n2d->get_position());4802undo_redo->add_undo_method(n2d, "set_rotation", n2d->get_rotation());4803undo_redo->add_undo_method(n2d, "set_scale", n2d->get_scale());4804}4805undo_redo->commit_action();48064807} break;4808case ANIM_CLEAR_POSE: {4809HashMap<Node *, Object *> &selection = editor_selection->get_selection();48104811for (const KeyValue<Node *, Object *> &E : selection) {4812CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);4813if (!ci || !ci->is_visible_in_tree()) {4814continue;4815}48164817if (Object::cast_to<Node2D>(ci)) {4818Node2D *n2d = Object::cast_to<Node2D>(ci);48194820if (key_pos) {4821n2d->set_position(Vector2());4822}4823if (key_rot) {4824n2d->set_rotation(0);4825}4826if (key_scale) {4827n2d->set_scale(Vector2(1, 1));4828}4829} else if (Object::cast_to<Control>(ci)) {4830Control *ctrl = Object::cast_to<Control>(ci);48314832if (key_pos) {4833ctrl->set_position(Point2());4834}4835}4836}48374838} break;4839case CLEAR_GUIDES: {4840Node *const root = EditorNode::get_singleton()->get_edited_scene();48414842if (root && (root->has_meta("_edit_horizontal_guides_") || root->has_meta("_edit_vertical_guides_"))) {4843undo_redo->create_action(TTR("Clear Guides"));4844if (root->has_meta("_edit_horizontal_guides_")) {4845Array hguides = root->get_meta("_edit_horizontal_guides_");48464847undo_redo->add_do_method(root, "remove_meta", "_edit_horizontal_guides_");4848undo_redo->add_undo_method(root, "set_meta", "_edit_horizontal_guides_", hguides);4849}4850if (root->has_meta("_edit_vertical_guides_")) {4851Array vguides = root->get_meta("_edit_vertical_guides_");48524853undo_redo->add_do_method(root, "remove_meta", "_edit_vertical_guides_");4854undo_redo->add_undo_method(root, "set_meta", "_edit_vertical_guides_", vguides);4855}4856undo_redo->add_do_method(viewport, "queue_redraw");4857undo_redo->add_undo_method(viewport, "queue_redraw");4858undo_redo->commit_action();4859}48604861} break;4862case VIEW_CENTER_TO_SELECTION:4863case VIEW_FRAME_TO_SELECTION: {4864_focus_selection(p_op);48654866} break;4867case PREVIEW_CANVAS_SCALE: {4868bool preview = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE));4869preview = !preview;4870RS::get_singleton()->canvas_set_disable_scale(!preview);4871view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE), preview);48724873} break;4874case SKELETON_MAKE_BONES: {4875HashMap<Node *, Object *> &selection = editor_selection->get_selection();4876Node *editor_root = get_tree()->get_edited_scene_root();48774878if (!editor_root || selection.is_empty()) {4879return;4880}48814882undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));4883for (const KeyValue<Node *, Object *> &E : selection) {4884Node2D *n2d = Object::cast_to<Node2D>(E.key);4885if (!n2d) {4886continue;4887}48884889Bone2D *new_bone = memnew(Bone2D);4890String new_bone_name = n2d->get_name();4891new_bone_name += "Bone2D";4892new_bone->set_name(new_bone_name);4893new_bone->set_transform(n2d->get_transform());48944895Node *n2d_parent = n2d->get_parent();4896if (!n2d_parent) {4897continue;4898}48994900undo_redo->add_do_method(n2d_parent, "add_child", new_bone);4901undo_redo->add_do_method(n2d_parent, "remove_child", n2d);4902undo_redo->add_do_method(new_bone, "add_child", n2d);4903undo_redo->add_do_method(n2d, "set_transform", Transform2D());4904undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root);4905undo_redo->add_do_reference(new_bone);49064907undo_redo->add_undo_method(new_bone, "remove_child", n2d);4908undo_redo->add_undo_method(n2d_parent, "add_child", n2d);4909undo_redo->add_undo_method(n2d_parent, "remove_child", new_bone);4910undo_redo->add_undo_method(n2d, "set_transform", new_bone->get_transform());4911undo_redo->add_undo_method(this, "_set_owner_for_node_and_children", n2d, editor_root);4912}4913undo_redo->commit_action();49144915} break;4916}4917}49184919void CanvasItemEditor::_set_owner_for_node_and_children(Node *p_node, Node *p_owner) {4920p_node->set_owner(p_owner);4921for (int i = 0; i < p_node->get_child_count(); i++) {4922_set_owner_for_node_and_children(p_node->get_child(i), p_owner);4923}4924}49254926void CanvasItemEditor::_focus_selection(int p_op) {4927Rect2 rect;4928int count = 0;49294930const HashMap<Node *, Object *> &selection = editor_selection->get_selection();4931for (const KeyValue<Node *, Object *> &E : selection) {4932CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);4933if (!ci) {4934continue;4935}4936const Transform2D canvas_item_transform = ci->get_global_transform();4937if (!canvas_item_transform.is_finite()) {4938continue;4939}4940Rect2 item_rect;4941if (ci->_edit_use_rect()) {4942item_rect = ci->_edit_get_rect();4943} else {4944item_rect = Rect2();4945}4946Vector2 pos = canvas_item_transform.get_origin();4947const Vector2 scale = canvas_item_transform.get_scale();4948const real_t angle = canvas_item_transform.get_rotation();4949pos = ci->get_viewport()->get_popup_base_transform().xform(pos);49504951Transform2D t(angle, Vector2(0.f, 0.f));4952item_rect = t.xform(item_rect);4953Rect2 canvas_item_rect(pos + scale * item_rect.position, scale * item_rect.size);4954if (count == 0) {4955rect = canvas_item_rect;4956} else {4957rect = rect.merge(canvas_item_rect);4958}4959count++;4960}49614962if (p_op == VIEW_FRAME_TO_SELECTION && rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) {4963real_t scale_x = viewport->get_size().x / rect.size.x;4964real_t scale_y = viewport->get_size().y / rect.size.y;4965zoom = scale_x < scale_y ? scale_x : scale_y;4966zoom *= 0.90;4967zoom_widget->set_zoom(zoom);4968viewport->queue_redraw(); // Redraw to update the global canvas transform after zoom changes.4969callable_mp(this, &CanvasItemEditor::center_at).call_deferred(rect.get_center()); // Defer because the updated transform is needed.4970} else {4971center_at(rect.get_center());4972}4973}49744975void CanvasItemEditor::_reset_drag() {4976message = "";4977drag_type = DRAG_NONE;4978drag_selection.clear();4979}49804981void CanvasItemEditor::_bind_methods() {4982ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data);49834984ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);4985ClassDB::bind_method(D_METHOD("center_at", "position"), &CanvasItemEditor::center_at);49864987ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children);49884989ADD_SIGNAL(MethodInfo("item_lock_status_changed"));4990ADD_SIGNAL(MethodInfo("item_group_status_changed"));4991}49924993Dictionary CanvasItemEditor::get_state() const {4994Dictionary state;4995// Take the editor scale into account.4996state["zoom"] = zoom / MAX(1, EDSCALE);4997state["ofs"] = view_offset;4998state["grid_offset"] = grid_offset;4999state["grid_step"] = grid_step;5000state["primary_grid_step"] = primary_grid_step;5001state["snap_rotation_offset"] = snap_rotation_offset;5002state["snap_rotation_step"] = snap_rotation_step;5003state["snap_scale_step"] = snap_scale_step;5004state["smart_snap_active"] = smart_snap_active;5005state["grid_snap_active"] = grid_snap_active;5006state["snap_node_parent"] = snap_node_parent;5007state["snap_node_anchors"] = snap_node_anchors;5008state["snap_node_sides"] = snap_node_sides;5009state["snap_node_center"] = snap_node_center;5010state["snap_other_nodes"] = snap_other_nodes;5011state["snap_guides"] = snap_guides;5012state["grid_visibility"] = grid_visibility;5013state["show_origin"] = show_origin;5014state["show_viewport"] = show_viewport;5015state["show_rulers"] = show_rulers;5016state["show_guides"] = show_guides;5017state["show_helpers"] = show_helpers;5018state["show_zoom_control"] = zoom_widget->is_visible();5019state["show_position_gizmos"] = show_position_gizmos;5020state["show_lock_gizmos"] = show_lock_gizmos;5021state["show_group_gizmos"] = show_group_gizmos;5022state["show_transformation_gizmos"] = show_transformation_gizmos;5023state["snap_rotation"] = snap_rotation;5024state["snap_scale"] = snap_scale;5025state["snap_relative"] = snap_relative;5026state["snap_pixel"] = snap_pixel;5027return state;5028}50295030void CanvasItemEditor::set_state(const Dictionary &p_state) {5031bool update_scrollbars = false;5032Dictionary state = p_state;5033if (state.has("zoom")) {5034// Compensate the editor scale, so that the editor scale can be changed5035// and the zoom level will still be the same (relative to the editor scale).5036zoom = real_t(p_state["zoom"]) * MAX(1, EDSCALE);5037zoom_widget->set_zoom(zoom);5038}50395040if (state.has("ofs")) {5041view_offset = p_state["ofs"];5042previous_update_view_offset = view_offset;5043update_scrollbars = true;5044}50455046if (state.has("grid_offset")) {5047grid_offset = state["grid_offset"];5048}50495050if (state.has("grid_step")) {5051grid_step = state["grid_step"];5052}50535054#ifndef DISABLE_DEPRECATED5055if (state.has("primary_grid_steps")) {5056primary_grid_step.x = state["primary_grid_steps"];5057primary_grid_step.y = state["primary_grid_steps"];5058}5059#endif // DISABLE_DEPRECATED50605061if (state.has("primary_grid_step")) {5062primary_grid_step = state["primary_grid_step"];5063}50645065if (state.has("snap_rotation_step")) {5066snap_rotation_step = state["snap_rotation_step"];5067}50685069if (state.has("snap_rotation_offset")) {5070snap_rotation_offset = state["snap_rotation_offset"];5071}50725073if (state.has("snap_scale_step")) {5074snap_scale_step = state["snap_scale_step"];5075}50765077if (state.has("smart_snap_active")) {5078smart_snap_active = state["smart_snap_active"];5079smart_snap_button->set_pressed(smart_snap_active);5080}50815082if (state.has("grid_snap_active")) {5083grid_snap_active = state["grid_snap_active"];5084grid_snap_button->set_pressed(grid_snap_active);5085}50865087if (state.has("snap_node_parent")) {5088snap_node_parent = state["snap_node_parent"];5089int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);5090smartsnap_config_popup->set_item_checked(idx, snap_node_parent);5091}50925093if (state.has("snap_node_anchors")) {5094snap_node_anchors = state["snap_node_anchors"];5095int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);5096smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);5097}50985099if (state.has("snap_node_sides")) {5100snap_node_sides = state["snap_node_sides"];5101int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);5102smartsnap_config_popup->set_item_checked(idx, snap_node_sides);5103}51045105if (state.has("snap_node_center")) {5106snap_node_center = state["snap_node_center"];5107int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);5108smartsnap_config_popup->set_item_checked(idx, snap_node_center);5109}51105111if (state.has("snap_other_nodes")) {5112snap_other_nodes = state["snap_other_nodes"];5113int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);5114smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);5115}51165117if (state.has("snap_guides")) {5118snap_guides = state["snap_guides"];5119int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);5120smartsnap_config_popup->set_item_checked(idx, snap_guides);5121}51225123if (state.has("grid_visibility")) {5124grid_visibility = (GridVisibility)(int)(state["grid_visibility"]);5125}51265127if (state.has("show_origin")) {5128show_origin = state["show_origin"];5129int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);5130view_menu->get_popup()->set_item_checked(idx, show_origin);5131}51325133if (state.has("show_viewport")) {5134show_viewport = state["show_viewport"];5135int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);5136view_menu->get_popup()->set_item_checked(idx, show_viewport);5137}51385139if (state.has("show_rulers")) {5140show_rulers = state["show_rulers"];5141int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);5142view_menu->get_popup()->set_item_checked(idx, show_rulers);5143update_scrollbars = true;5144}51455146if (state.has("show_guides")) {5147show_guides = state["show_guides"];5148int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);5149view_menu->get_popup()->set_item_checked(idx, show_guides);5150}51515152if (state.has("show_helpers")) {5153show_helpers = state["show_helpers"];5154int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);5155view_menu->get_popup()->set_item_checked(idx, show_helpers);5156}51575158if (state.has("show_position_gizmos")) {5159show_position_gizmos = state["show_position_gizmos"];5160int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS);5161gizmos_menu->set_item_checked(idx, show_position_gizmos);5162}51635164if (state.has("show_lock_gizmos")) {5165show_lock_gizmos = state["show_lock_gizmos"];5166int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS);5167gizmos_menu->set_item_checked(idx, show_lock_gizmos);5168}51695170if (state.has("show_group_gizmos")) {5171show_group_gizmos = state["show_group_gizmos"];5172int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS);5173gizmos_menu->set_item_checked(idx, show_group_gizmos);5174}51755176if (state.has("show_transformation_gizmos")) {5177show_transformation_gizmos = state["show_transformation_gizmos"];5178int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS);5179gizmos_menu->set_item_checked(idx, show_transformation_gizmos);5180}51815182if (state.has("show_zoom_control")) {5183// This one is not user-controllable, but instrumentable5184zoom_widget->set_visible(state["show_zoom_control"]);5185}51865187if (state.has("snap_rotation")) {5188snap_rotation = state["snap_rotation"];5189int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);5190snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);5191}51925193if (state.has("snap_scale")) {5194snap_scale = state["snap_scale"];5195int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);5196snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);5197}51985199if (state.has("snap_relative")) {5200snap_relative = state["snap_relative"];5201int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);5202snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);5203}52045205if (state.has("snap_pixel")) {5206snap_pixel = state["snap_pixel"];5207int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);5208snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);5209}52105211if (update_scrollbars) {5212_update_scrollbars();5213}5214viewport->queue_redraw();5215}52165217void CanvasItemEditor::clear() {5218zoom = 1.0 / MAX(1, EDSCALE);5219zoom_widget->set_zoom(zoom);52205221view_offset = Point2(-150 - ruler_width_scaled, -95 - ruler_width_scaled);5222previous_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.5223_update_scrollbars();52245225grid_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_offset", Vector2());5226grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_step", Vector2(8, 8));5227primary_grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "primary_grid_step", Vector2i(8, 8));5228snap_rotation_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_step", Math::deg_to_rad(15.0));5229snap_rotation_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_offset", 0.0);5230snap_scale_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_scale_step", 0.1);5231}52325233void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) {5234ERR_FAIL_NULL(p_control);5235ERR_FAIL_COND(p_control->get_parent());52365237VSeparator *sep = memnew(VSeparator);5238context_toolbar_hbox->add_child(sep);5239context_toolbar_hbox->add_child(p_control);5240context_toolbar_separators[p_control] = sep;52415242p_control->connect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItemEditor::_update_context_toolbar));52435244_update_context_toolbar();5245}52465247void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) {5248ERR_FAIL_NULL(p_control);5249ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);52505251p_control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &CanvasItemEditor::_update_context_toolbar));52525253VSeparator *sep = context_toolbar_separators[p_control];5254context_toolbar_hbox->remove_child(sep);5255context_toolbar_hbox->remove_child(p_control);5256context_toolbar_separators.erase(p_control);5257memdelete(sep);52585259_update_context_toolbar();5260}52615262void CanvasItemEditor::_update_context_toolbar() {5263bool has_visible = false;5264bool first_visible = false;52655266for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {5267Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));5268if (!child || !context_toolbar_separators.has(child)) {5269continue;5270}5271if (child->is_visible()) {5272first_visible = !has_visible;5273has_visible = true;5274}52755276VSeparator *sep = context_toolbar_separators[child];5277sep->set_visible(!first_visible && child->is_visible());5278}52795280context_toolbar_panel->set_visible(has_visible);5281}52825283void CanvasItemEditor::add_control_to_left_panel(Control *p_control) {5284left_panel_split->add_child(p_control);5285left_panel_split->move_child(p_control, 0);5286}52875288void CanvasItemEditor::add_control_to_right_panel(Control *p_control) {5289right_panel_split->add_child(p_control);5290right_panel_split->move_child(p_control, 1);5291}52925293void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) {5294left_panel_split->remove_child(p_control);5295}52965297void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) {5298right_panel_split->remove_child(p_control);5299}53005301VSplitContainer *CanvasItemEditor::get_bottom_split() {5302return bottom_split;5303}53045305void CanvasItemEditor::focus_selection() {5306_focus_selection(VIEW_CENTER_TO_SELECTION);5307}53085309void CanvasItemEditor::center_at(const Point2 &p_pos) {5310Vector2 offset = viewport->get_size() / 2 - EditorNode::get_singleton()->get_scene_root()->get_global_canvas_transform().xform(p_pos);5311view_offset -= (offset / zoom).round();5312update_viewport();5313}53145315CanvasItemEditor::CanvasItemEditor() {5316snap_target[0] = SNAP_TARGET_NONE;5317snap_target[1] = SNAP_TARGET_NONE;53185319editor_selection = EditorNode::get_singleton()->get_editor_selection();5320editor_selection->add_editor_plugin(this);5321editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));5322editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed));53235324SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_adjust_new_node_position));5325SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));53265327// Add some margin to the sides for better aesthetics.5328// This prevents the first button's hover/pressed effect from "touching" the panel's border,5329// which looks ugly.5330MarginContainer *toolbar_margin = memnew(MarginContainer);5331toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);5332toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);5333add_child(toolbar_margin);53345335// A fluid container for all toolbars.5336HFlowContainer *main_flow = memnew(HFlowContainer);5337toolbar_margin->add_child(main_flow);53385339// Main toolbars.5340HBoxContainer *main_menu_hbox = memnew(HBoxContainer);5341main_menu_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5342main_flow->add_child(main_menu_hbox);53435344bottom_split = memnew(VSplitContainer);5345add_child(bottom_split);5346bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);53475348left_panel_split = memnew(HSplitContainer);5349bottom_split->add_child(left_panel_split);5350left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);53515352right_panel_split = memnew(HSplitContainer);5353left_panel_split->add_child(right_panel_split);5354right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);53555356viewport_scrollable = memnew(Control);5357right_panel_split->add_child(viewport_scrollable);5358viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS);5359viewport_scrollable->set_clip_contents(true);5360viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL);5361viewport_scrollable->set_h_size_flags(Control::SIZE_EXPAND_FILL);5362viewport_scrollable->connect(SceneStringName(draw), callable_mp(this, &CanvasItemEditor::_update_scrollbars));53635364SubViewportContainer *scene_tree = memnew(SubViewportContainer);5365viewport_scrollable->add_child(scene_tree);5366scene_tree->set_stretch(true);5367scene_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5368scene_tree->add_child(EditorNode::get_singleton()->get_scene_root());53695370controls_vb = memnew(VBoxContainer);5371controls_vb->set_begin(Point2(5, 5));53725373ED_SHORTCUT("canvas_item_editor/cancel_transform", TTRC("Cancel Transformation"), Key::ESCAPE);53745375// To ensure that scripts can parse the list of shortcuts correctly, we have to define5376// those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget).5377ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_3.125_percent", TTRC("Zoom to 3.125%"),5378{ int32_t(KeyModifierMask::SHIFT | Key::KEY_5), int32_t(KeyModifierMask::SHIFT | Key::KP_5) });53795380ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_6.25_percent", TTRC("Zoom to 6.25%"),5381{ int32_t(KeyModifierMask::SHIFT | Key::KEY_4), int32_t(KeyModifierMask::SHIFT | Key::KP_4) });53825383ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_12.5_percent", TTRC("Zoom to 12.5%"),5384{ int32_t(KeyModifierMask::SHIFT | Key::KEY_3), int32_t(KeyModifierMask::SHIFT | Key::KP_3) });53855386ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_25_percent", TTRC("Zoom to 25%"),5387{ int32_t(KeyModifierMask::SHIFT | Key::KEY_2), int32_t(KeyModifierMask::SHIFT | Key::KP_2) });53885389ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_50_percent", TTRC("Zoom to 50%"),5390{ int32_t(KeyModifierMask::SHIFT | Key::KEY_1), int32_t(KeyModifierMask::SHIFT | Key::KP_1) });53915392ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTRC("Zoom to 100%"),5393{ 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) });53945395ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_200_percent", TTRC("Zoom to 200%"),5396{ int32_t(Key::KEY_2), int32_t(Key::KP_2) });53975398ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_400_percent", TTRC("Zoom to 400%"),5399{ int32_t(Key::KEY_3), int32_t(Key::KP_3) });54005401ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_800_percent", TTRC("Zoom to 800%"),5402{ int32_t(Key::KEY_4), int32_t(Key::KP_4) });54035404ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_1600_percent", TTRC("Zoom to 1600%"),5405{ int32_t(Key::KEY_5), int32_t(Key::KP_5) });54065407HBoxContainer *controls_hb = memnew(HBoxContainer);5408controls_vb->add_child(controls_hb);54095410button_center_view = memnew(Button);5411controls_hb->add_child(button_center_view);5412button_center_view->set_flat(true);5413button_center_view->set_tooltip_text(TTR("Center View"));5414button_center_view->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(VIEW_CENTER_TO_SELECTION));54155416zoom_widget = memnew(EditorZoomWidget);5417zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);5418zoom_widget->set_shortcut_context(this);5419controls_hb->add_child(zoom_widget);5420zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));54215422EditorTranslationPreviewButton *translation_preview_button = memnew(EditorTranslationPreviewButton);5423translation_preview_button->set_flat(true);5424translation_preview_button->add_theme_constant_override("outline_size", Math::ceil(2 * EDSCALE));5425translation_preview_button->add_theme_color_override("font_outline_color", Color(0, 0, 0));5426translation_preview_button->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));5427controls_hb->add_child(translation_preview_button);54285429panner.instantiate();5430panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));54315432viewport = memnew(CanvasItemEditorViewport(this));5433viewport_scrollable->add_child(viewport);5434viewport->set_mouse_filter(MOUSE_FILTER_PASS);5435viewport->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5436viewport->set_clip_contents(true);5437viewport->set_focus_mode(FOCUS_ALL);5438viewport->connect(SceneStringName(draw), callable_mp(this, &CanvasItemEditor::_draw_viewport));5439viewport->connect(SceneStringName(gui_input), callable_mp(this, &CanvasItemEditor::_gui_input_viewport));5440viewport->connect(SceneStringName(focus_exited), callable_mp(panner.ptr(), &ViewPanner::release_pan_key));54415442h_scroll = memnew(HScrollBar);5443viewport->add_child(h_scroll);5444h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &CanvasItemEditor::_update_scroll));5445h_scroll->hide();54465447v_scroll = memnew(VScrollBar);5448viewport->add_child(v_scroll);5449v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &CanvasItemEditor::_update_scroll));5450v_scroll->hide();54515452viewport->add_child(controls_vb);54535454select_button = memnew(Button);5455select_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5456select_button->set_theme_type_variation(SceneStringName(FlatButton));5457main_menu_hbox->add_child(select_button);5458select_button->set_toggle_mode(true);5459select_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SELECT));5460select_button->set_pressed(true);5461select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTRC("Select Mode"), Key::Q, true));5462select_button->set_shortcut_context(this);5463select_button->set_accessibility_name(TTRC("Select Mode"));54645465main_menu_hbox->add_child(memnew(VSeparator));54665467move_button = memnew(Button);5468move_button->set_theme_type_variation(SceneStringName(FlatButton));5469main_menu_hbox->add_child(move_button);5470move_button->set_toggle_mode(true);5471move_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_MOVE));5472move_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/move_mode", TTRC("Move Mode"), Key::W, true));5473move_button->set_shortcut_context(this);5474move_button->set_tooltip_text(TTRC("Move Mode"));54755476rotate_button = memnew(Button);5477rotate_button->set_theme_type_variation(SceneStringName(FlatButton));5478main_menu_hbox->add_child(rotate_button);5479rotate_button->set_toggle_mode(true);5480rotate_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_ROTATE));5481rotate_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/rotate_mode", TTRC("Rotate Mode"), Key::E, true));5482rotate_button->set_shortcut_context(this);5483rotate_button->set_tooltip_text(TTRC("Rotate Mode"));54845485scale_button = memnew(Button);5486scale_button->set_theme_type_variation(SceneStringName(FlatButton));5487main_menu_hbox->add_child(scale_button);5488scale_button->set_toggle_mode(true);5489scale_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SCALE));5490scale_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/scale_mode", TTRC("Scale Mode"), Key::R, true));5491scale_button->set_shortcut_context(this);5492scale_button->set_tooltip_text(TTRC("Shift: Scale proportionally."));5493scale_button->set_accessibility_name(TTRC("Scale Mode"));54945495main_menu_hbox->add_child(memnew(VSeparator));54965497list_select_button = memnew(Button);5498list_select_button->set_theme_type_variation(SceneStringName(FlatButton));5499main_menu_hbox->add_child(list_select_button);5500list_select_button->set_toggle_mode(true);5501list_select_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_LIST_SELECT));5502list_select_button->set_tooltip_text(TTRC("Show list of selectable nodes at position clicked."));55035504pivot_button = memnew(Button);5505pivot_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5506pivot_button->set_theme_type_variation(SceneStringName(FlatButton));5507main_menu_hbox->add_child(pivot_button);5508pivot_button->set_toggle_mode(true);5509pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));5510pivot_button->set_accessibility_name(TTRC("Change Pivot"));55115512pan_button = memnew(Button);5513pan_button->set_theme_type_variation(SceneStringName(FlatButton));5514main_menu_hbox->add_child(pan_button);5515pan_button->set_toggle_mode(true);5516pan_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_PAN));5517pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTRC("Pan Mode"), Key::G));5518pan_button->set_shortcut_context(this);5519pan_button->set_tooltip_text(TTRC("You can also use Pan View shortcut (Space by default) to pan in any mode."));5520pan_button->set_accessibility_name(TTRC("Pan View"));55215522ruler_button = memnew(Button);5523ruler_button->set_theme_type_variation(SceneStringName(FlatButton));5524main_menu_hbox->add_child(ruler_button);5525ruler_button->set_toggle_mode(true);5526ruler_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_RULER));5527ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTRC("Ruler Mode"), Key::M));5528ruler_button->set_shortcut_context(this);5529ruler_button->set_tooltip_text(TTRC("Ruler Mode"));55305531main_menu_hbox->add_child(memnew(VSeparator));55325533smart_snap_button = memnew(Button);5534smart_snap_button->set_theme_type_variation(SceneStringName(FlatButton));5535main_menu_hbox->add_child(smart_snap_button);5536smart_snap_button->set_toggle_mode(true);5537smart_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));5538smart_snap_button->set_tooltip_text(TTRC("Toggle smart snapping."));5539smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTRC("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S));5540smart_snap_button->set_shortcut_context(this);55415542grid_snap_button = memnew(Button);5543grid_snap_button->set_theme_type_variation(SceneStringName(FlatButton));5544main_menu_hbox->add_child(grid_snap_button);5545grid_snap_button->set_toggle_mode(true);5546grid_snap_button->connect(SceneStringName(toggled), callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));5547grid_snap_button->set_tooltip_text(TTRC("Toggle grid snapping."));5548grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTRC("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G));5549grid_snap_button->set_shortcut_context(this);55505551snap_config_menu = memnew(MenuButton);5552snap_config_menu->set_flat(false);5553snap_config_menu->set_theme_type_variation("FlatMenuButton");5554snap_config_menu->set_shortcut_context(this);5555main_menu_hbox->add_child(snap_config_menu);5556snap_config_menu->set_h_size_flags(SIZE_SHRINK_END);5557snap_config_menu->set_tooltip_text(TTRC("Snapping Options"));5558snap_config_menu->set_switch_on_hover(true);55595560PopupMenu *p = snap_config_menu->get_popup();5561p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5562p->set_hide_on_checkable_item_selection(false);5563p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_rotation_snap", TTRC("Use Rotation Snap")), SNAP_USE_ROTATION);5564p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_scale_snap", TTRC("Use Scale Snap")), SNAP_USE_SCALE);5565p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_relative", TTRC("Snap Relative")), SNAP_RELATIVE);5566p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_pixel_snap", TTRC("Use Pixel Snap")), SNAP_USE_PIXEL);55675568smartsnap_config_popup = memnew(PopupMenu);5569smartsnap_config_popup->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5570smartsnap_config_popup->set_hide_on_checkable_item_selection(false);5571smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_parent", TTRC("Snap to Parent")), SNAP_USE_NODE_PARENT);5572smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_anchors", TTRC("Snap to Node Anchor")), SNAP_USE_NODE_ANCHORS);5573smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_sides", TTRC("Snap to Node Sides")), SNAP_USE_NODE_SIDES);5574smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_center", TTRC("Snap to Node Center")), SNAP_USE_NODE_CENTER);5575smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_other_nodes", TTRC("Snap to Other Nodes")), SNAP_USE_OTHER_NODES);5576smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_guides", TTRC("Snap to Guides")), SNAP_USE_GUIDES);5577p->add_submenu_node_item(TTRC("Smart Snapping"), smartsnap_config_popup);55785579p->add_separator();5580p->add_shortcut(ED_SHORTCUT("canvas_item_editor/configure_snap", TTRC("Configure Snap...")), SNAP_CONFIGURE);55815582main_menu_hbox->add_child(memnew(VSeparator));55835584lock_button = memnew(Button);5585lock_button->set_theme_type_variation(SceneStringName(FlatButton));5586lock_button->set_accessibility_name(TTRC("Lock"));5587main_menu_hbox->add_child(lock_button);55885589lock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED));5590lock_button->set_tooltip_text(TTRC("Lock selected node, preventing selection and movement."));5591// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5592lock_button->set_shortcut(ED_GET_SHORTCUT("editor/lock_selected_nodes"));55935594unlock_button = memnew(Button);5595unlock_button->set_accessibility_name(TTRC("Unlock"));5596unlock_button->set_theme_type_variation(SceneStringName(FlatButton));5597main_menu_hbox->add_child(unlock_button);5598unlock_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED));5599unlock_button->set_tooltip_text(TTRC("Unlock selected node, allowing selection and movement."));5600// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5601unlock_button->set_shortcut(ED_GET_SHORTCUT("editor/unlock_selected_nodes"));56025603group_button = memnew(Button);5604group_button->set_accessibility_name(TTRC("Group"));5605group_button->set_theme_type_variation(SceneStringName(FlatButton));5606main_menu_hbox->add_child(group_button);5607group_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED));5608group_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."));5609// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5610group_button->set_shortcut(ED_GET_SHORTCUT("editor/group_selected_nodes"));56115612ungroup_button = memnew(Button);5613ungroup_button->set_accessibility_name(TTRC("Ungroup"));5614ungroup_button->set_theme_type_variation(SceneStringName(FlatButton));5615main_menu_hbox->add_child(ungroup_button);5616ungroup_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED));5617ungroup_button->set_tooltip_text(TTRC("Ungroups the selected node from its children. Child nodes will be individual items in 2D and 3D view."));5618// Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.5619ungroup_button->set_shortcut(ED_GET_SHORTCUT("editor/ungroup_selected_nodes"));56205621main_menu_hbox->add_child(memnew(VSeparator));56225623skeleton_menu = memnew(MenuButton);5624skeleton_menu->set_flat(false);5625skeleton_menu->set_theme_type_variation("FlatMenuButton");5626skeleton_menu->set_shortcut_context(this);5627main_menu_hbox->add_child(skeleton_menu);5628skeleton_menu->set_tooltip_text(TTRC("Skeleton Options"));5629skeleton_menu->set_switch_on_hover(true);56305631p = skeleton_menu->get_popup();5632p->set_hide_on_checkable_item_selection(false);5633p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTRC("Show Bones")), SKELETON_SHOW_BONES);5634p->add_separator();5635p->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);5636p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));56375638main_menu_hbox->add_child(memnew(VSeparator));56395640view_menu = memnew(MenuButton);5641view_menu->set_flat(false);5642view_menu->set_theme_type_variation("FlatMenuButton");5643// TRANSLATORS: Noun, name of the 2D/3D View menus.5644view_menu->set_text(TTRC("View"));5645view_menu->set_switch_on_hover(true);5646view_menu->set_shortcut_context(this);5647main_menu_hbox->add_child(view_menu);56485649p = view_menu->get_popup();5650p->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5651p->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_view_menu));5652p->set_hide_on_checkable_item_selection(false);56535654grid_menu = memnew(PopupMenu);5655grid_menu->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_grid_menu));5656grid_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_on_grid_menu_id_pressed));5657grid_menu->add_radio_check_item(TTRC("Show"), GRID_VISIBILITY_SHOW);5658grid_menu->add_radio_check_item(TTRC("Show When Snapping"), GRID_VISIBILITY_SHOW_WHEN_SNAPPING);5659grid_menu->add_radio_check_item(TTRC("Hide"), GRID_VISIBILITY_HIDE);5660grid_menu->add_separator();5661grid_menu->add_shortcut(ED_SHORTCUT("canvas_item_editor/toggle_grid", TTRC("Toggle Grid"), KeyModifierMask::CMD_OR_CTRL | Key::APOSTROPHE));5662p->add_submenu_node_item(TTRC("Grid"), grid_menu);56635664p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTRC("Show Helpers"), Key::H), SHOW_HELPERS);5665p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTRC("Show Rulers")), SHOW_RULERS);5666p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTRC("Show Guides"), Key::Y), SHOW_GUIDES);5667p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTRC("Show Origin")), SHOW_ORIGIN);5668p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTRC("Show Viewport")), SHOW_VIEWPORT);5669p->add_separator();56705671gizmos_menu = memnew(PopupMenu);5672gizmos_menu->set_name("GizmosMenu");5673gizmos_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5674gizmos_menu->set_hide_on_checkable_item_selection(false);5675gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_position_gizmos", TTRC("Position")), SHOW_POSITION_GIZMOS);5676gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_lock_gizmos", TTRC("Lock")), SHOW_LOCK_GIZMOS);5677gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_group_gizmos", TTRC("Group")), SHOW_GROUP_GIZMOS);5678gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTRC("Transformation")), SHOW_TRANSFORMATION_GIZMOS);5679p->add_child(gizmos_menu);5680p->add_submenu_item(TTRC("Gizmos"), "GizmosMenu");56815682p->add_separator();5683p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTRC("Center Selection"), Key::F), VIEW_CENTER_TO_SELECTION);5684p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTRC("Frame Selection"), KeyModifierMask::SHIFT | Key::F), VIEW_FRAME_TO_SELECTION);5685p->add_shortcut(ED_SHORTCUT("canvas_item_editor/clear_guides", TTRC("Clear Guides")), CLEAR_GUIDES);5686p->add_separator();5687p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTRC("Preview Canvas Scale")), PREVIEW_CANVAS_SCALE);56885689theme_menu = memnew(PopupMenu);5690theme_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_switch_theme_preview));5691theme_menu->add_radio_check_item(TTRC("Project theme"), THEME_PREVIEW_PROJECT);5692theme_menu->add_radio_check_item(TTRC("Editor theme"), THEME_PREVIEW_EDITOR);5693theme_menu->add_radio_check_item(TTRC("Default theme"), THEME_PREVIEW_DEFAULT);5694p->add_submenu_node_item(TTRC("Preview Theme"), theme_menu);56955696theme_preview = (ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", THEME_PREVIEW_PROJECT);5697for (int i = 0; i < THEME_PREVIEW_MAX; i++) {5698theme_menu->set_item_checked(i, i == theme_preview);5699}57005701p->add_submenu_node_item(TTRC("Preview Translation"), memnew(EditorTranslationPreviewMenu));57025703main_menu_hbox->add_child(memnew(VSeparator));57045705// Contextual toolbars.5706context_toolbar_panel = memnew(PanelContainer);5707context_toolbar_hbox = memnew(HBoxContainer);5708context_toolbar_panel->add_child(context_toolbar_hbox);5709main_flow->add_child(context_toolbar_panel);57105711// Animation controls.5712animation_hb = memnew(HBoxContainer);5713add_control_to_menu_panel(animation_hb);5714animation_hb->hide();57155716key_loc_button = memnew(Button);5717key_loc_button->set_theme_type_variation(SceneStringName(FlatButton));5718key_loc_button->set_toggle_mode(true);5719key_loc_button->set_pressed(true);5720key_loc_button->set_focus_mode(FOCUS_ACCESSIBILITY);5721key_loc_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_POS));5722key_loc_button->set_tooltip_text(TTRC("Translation mask for inserting keys."));5723animation_hb->add_child(key_loc_button);57245725key_rot_button = memnew(Button);5726key_rot_button->set_theme_type_variation(SceneStringName(FlatButton));5727key_rot_button->set_toggle_mode(true);5728key_rot_button->set_pressed(true);5729key_rot_button->set_focus_mode(FOCUS_ACCESSIBILITY);5730key_rot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_ROT));5731key_rot_button->set_tooltip_text(TTRC("Rotation mask for inserting keys."));5732animation_hb->add_child(key_rot_button);57335734key_scale_button = memnew(Button);5735key_scale_button->set_theme_type_variation(SceneStringName(FlatButton));5736key_scale_button->set_toggle_mode(true);5737key_scale_button->set_focus_mode(FOCUS_ACCESSIBILITY);5738key_scale_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_SCALE));5739key_scale_button->set_tooltip_text(TTRC("Scale mask for inserting keys."));5740animation_hb->add_child(key_scale_button);57415742key_insert_button = memnew(Button);5743key_insert_button->set_theme_type_variation(SceneStringName(FlatButton));5744key_insert_button->set_focus_mode(FOCUS_ACCESSIBILITY);5745key_insert_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_KEY));5746key_insert_button->set_tooltip_text(TTRC("Insert keys (based on mask)."));5747key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTRC("Insert Key"), Key::INSERT));5748key_insert_button->set_shortcut_context(this);5749animation_hb->add_child(key_insert_button);57505751key_auto_insert_button = memnew(Button);5752key_auto_insert_button->set_theme_type_variation(SceneStringName(FlatButton));5753key_auto_insert_button->set_toggle_mode(true);5754key_auto_insert_button->set_focus_mode(FOCUS_ACCESSIBILITY);5755key_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."));5756key_auto_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_auto_insert_key", TTRC("Auto Insert Key")));5757key_auto_insert_button->set_accessibility_name(TTRC("Auto Insert Key"));5758key_auto_insert_button->set_shortcut_context(this);5759animation_hb->add_child(key_auto_insert_button);57605761animation_menu = memnew(MenuButton);5762animation_menu->set_flat(false);5763animation_menu->set_theme_type_variation("FlatMenuButton");5764animation_menu->set_shortcut_context(this);5765animation_menu->set_tooltip_text(TTRC("Animation Key and Pose Options"));5766animation_hb->add_child(animation_menu);5767animation_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_popup_callback));5768animation_menu->set_switch_on_hover(true);57695770p = animation_menu->get_popup();57715772p->add_shortcut(ED_GET_SHORTCUT("canvas_item_editor/anim_insert_key"), ANIM_INSERT_KEY);5773p->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);5774p->add_separator();5775p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_copy_pose", TTRC("Copy Pose")), ANIM_COPY_POSE);5776p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_paste_pose", TTRC("Paste Pose")), ANIM_PASTE_POSE);5777p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_clear_pose", TTRC("Clear Pose"), KeyModifierMask::SHIFT | Key::K), ANIM_CLEAR_POSE);57785779snap_dialog = memnew(SnapDialog);5780snap_dialog->connect(SceneStringName(confirmed), callable_mp(this, &CanvasItemEditor::_snap_changed));5781add_child(snap_dialog);57825783select_sb.instantiate();57845785selection_menu = memnew(PopupMenu);5786add_child(selection_menu);5787selection_menu->set_min_size(Vector2(100, 0));5788selection_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);5789selection_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_selection_result_pressed));5790selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED);57915792add_node_menu = memnew(PopupMenu);5793add_child(add_node_menu);5794add_node_menu->connect(SceneStringName(id_pressed), callable_mp(this, &CanvasItemEditor::_add_node_pressed));57955796multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTRC("Multiply grid step by 2"), Key::KP_MULTIPLY);5797divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTRC("Divide grid step by 2"), Key::KP_DIVIDE);57985799skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true);58005801// Store the singleton instance.5802singleton = this;58035804set_process_shortcut_input(true);5805clear(); // Make sure values are initialized.58065807// Update the menus' checkboxes.5808callable_mp(this, &CanvasItemEditor::set_state).call_deferred(get_state());5809}58105811CanvasItemEditor *CanvasItemEditor::singleton = nullptr;58125813void CanvasItemEditorPlugin::edit(Object *p_object) {5814canvas_item_editor->edit(Object::cast_to<CanvasItem>(p_object));5815}58165817bool CanvasItemEditorPlugin::handles(Object *p_object) const {5818return p_object->is_class("CanvasItem");5819}58205821void CanvasItemEditorPlugin::make_visible(bool p_visible) {5822if (p_visible) {5823canvas_item_editor->show();5824canvas_item_editor->set_process(true);5825RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), false);5826RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_ENABLED);58275828} else {5829canvas_item_editor->hide();5830canvas_item_editor->set_process(false);5831RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), true);5832RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_DISABLED);5833}5834}58355836Dictionary CanvasItemEditorPlugin::get_state() const {5837return canvas_item_editor->get_state();5838}58395840void CanvasItemEditorPlugin::set_state(const Dictionary &p_state) {5841canvas_item_editor->set_state(p_state);5842}58435844void CanvasItemEditorPlugin::clear() {5845canvas_item_editor->clear();5846}58475848void CanvasItemEditorPlugin::_notification(int p_what) {5849switch (p_what) {5850case NOTIFICATION_ENTER_TREE: {5851connect("scene_changed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));5852connect("scene_closed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));5853} break;5854}5855}58565857CanvasItemEditorPlugin::CanvasItemEditorPlugin() {5858canvas_item_editor = memnew(CanvasItemEditor);5859canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);5860EditorNode::get_singleton()->get_editor_main_screen()->get_control()->add_child(canvas_item_editor);5861canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);5862canvas_item_editor->hide();5863}58645865void CanvasItemEditorViewport::_on_mouse_exit() {5866if (!texture_node_type_selector->is_visible()) {5867_remove_preview();5868}5869}58705871void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) {5872CheckBox *check = Object::cast_to<CheckBox>(selected);5873String type = check->get_text();5874texture_node_type_selector->set_title(vformat(TTR("Add %s"), type));5875label->set_text(vformat(TTR("Adding %s..."), type));5876}58775878void CanvasItemEditorViewport::_on_change_type_confirmed() {5879if (!button_group->get_pressed_button()) {5880return;5881}58825883CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button());5884default_texture_node_type = check->get_text();5885_perform_drop_data();5886texture_node_type_selector->hide();5887}58885889void CanvasItemEditorViewport::_on_change_type_closed() {5890_remove_preview();5891}58925893void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) const {5894bool add_preview = false;5895for (int i = 0; i < files.size(); i++) {5896Ref<Resource> res = ResourceLoader::load(files[i]);5897ERR_CONTINUE(res.is_null());58985899Ref<Texture2D> texture = res;5900if (texture.is_valid()) {5901Sprite2D *sprite = memnew(Sprite2D);5902sprite->set_texture(texture);5903sprite->set_modulate(Color(1, 1, 1, 0.7f));5904preview_node->add_child(sprite);5905add_preview = true;5906}59075908Ref<PackedScene> scene = res;5909if (scene.is_valid()) {5910Node *instance = scene->instantiate();5911if (instance) {5912preview_node->add_child(instance);5913}5914add_preview = true;5915}59165917Ref<AudioStream> audio = res;5918if (audio.is_valid()) {5919Sprite2D *sprite = memnew(Sprite2D);5920sprite->set_texture(get_editor_theme_icon(SNAME("AudioStreamPlayer2D")));5921sprite->set_modulate(Color(1, 1, 1, 0.7f));5922sprite->set_position(Vector2(0, -sprite->get_texture()->get_size().height) * EDSCALE);5923preview_node->add_child(sprite);5924add_preview = true;5925}5926}59275928if (add_preview) {5929EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);5930}5931}59325933void CanvasItemEditorViewport::_remove_preview() {5934if (!canvas_item_editor->message.is_empty()) {5935canvas_item_editor->message = "";5936canvas_item_editor->update_viewport();5937}5938if (preview_node->get_parent()) {5939for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {5940Node *node = preview_node->get_child(i);5941node->queue_free();5942preview_node->remove_child(node);5943}5944EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);59455946label->hide();5947label_desc->hide();5948}5949}59505951bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) const {5952if (p_desired_node->get_scene_file_path() == p_target_scene_path) {5953return true;5954}59555956int childCount = p_desired_node->get_child_count();5957for (int i = 0; i < childCount; i++) {5958Node *child = p_desired_node->get_child(i);5959if (_cyclical_dependency_exists(p_target_scene_path, child)) {5960return true;5961}5962}5963return false;5964}59655966void CanvasItemEditorViewport::_create_texture_node(Node *p_parent, Node *p_child, const String &p_path, const Point2 &p_point) {5967// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.5968const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());5969if (!node_name.is_empty()) {5970p_child->set_name(node_name);5971}59725973EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5974Ref<Texture2D> texture = ResourceCache::get_ref(p_path);59755976if (p_parent) {5977undo_redo->add_do_method(p_parent, "add_child", p_child, true);5978undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());5979undo_redo->add_do_reference(p_child);5980undo_redo->add_undo_method(p_parent, "remove_child", p_child);5981} else { // If no parent is selected, set as root node of the scene.5982undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", p_child);5983undo_redo->add_do_method(p_child, "set_owner", EditorNode::get_singleton()->get_edited_scene());5984undo_redo->add_do_reference(p_child);5985undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);5986}59875988if (p_parent) {5989String new_name = p_parent->validate_child_name(p_child);5990EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();5991undo_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);5992undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));5993}59945995if (Object::cast_to<TouchScreenButton>(p_child) || Object::cast_to<TextureButton>(p_child)) {5996undo_redo->add_do_property(p_child, "texture_normal", texture);5997} else {5998undo_redo->add_do_property(p_child, "texture", texture);5999}60006001// make visible for certain node type6002if (Object::cast_to<Control>(p_child)) {6003Size2 texture_size = texture->get_size();6004undo_redo->add_do_property(p_child, "size", texture_size);6005} else if (Object::cast_to<Polygon2D>(p_child)) {6006Size2 texture_size = texture->get_size();6007Vector<Vector2> list = {6008Vector2(0, 0),6009Vector2(texture_size.width, 0),6010Vector2(texture_size.width, texture_size.height),6011Vector2(0, texture_size.height)6012};6013undo_redo->add_do_property(p_child, "polygon", list);6014}60156016// Compute the global position6017Transform2D xform = canvas_item_editor->get_canvas_transform();6018Point2 target_position = xform.affine_inverse().xform(p_point);60196020// Adjust position for Control and TouchScreenButton6021if (Object::cast_to<Control>(p_child) || Object::cast_to<TouchScreenButton>(p_child)) {6022target_position -= texture->get_size() / 2;6023}60246025// There's nothing to be used as source position, so snapping will work as absolute if enabled.6026target_position = canvas_item_editor->snap_point(target_position);60276028CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6029Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;60306031undo_redo->add_do_method(p_child, "set_position", local_target_pos);6032}60336034void CanvasItemEditorViewport::_create_audio_node(Node *p_parent, const String &p_path, const Point2 &p_point) {6035AudioStreamPlayer2D *child = memnew(AudioStreamPlayer2D);6036child->set_stream(ResourceCache::get_ref(p_path));60376038// Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.6039const String &node_name = Node::adjust_name_casing(p_path.get_file().get_basename());6040if (!node_name.is_empty()) {6041child->set_name(node_name);6042}60436044EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();60456046if (p_parent) {6047undo_redo->add_do_method(p_parent, "add_child", child, true);6048undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6049undo_redo->add_do_reference(child);6050undo_redo->add_undo_method(p_parent, "remove_child", child);6051} else { // If no parent is selected, set as root node of the scene.6052undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);6053undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());6054undo_redo->add_do_reference(child);6055undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);6056}60576058if (p_parent) {6059String new_name = p_parent->validate_child_name(child);6060EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();6061undo_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);6062undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(p_parent)) + "/" + new_name));6063}60646065// Compute the global position6066Transform2D xform = canvas_item_editor->get_canvas_transform();6067Point2 target_position = xform.affine_inverse().xform(p_point);60686069// There's nothing to be used as source position, so snapping will work as absolute if enabled.6070target_position = canvas_item_editor->snap_point(target_position);60716072CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6073Point2 local_target_pos = parent_ci ? parent_ci->get_global_transform().affine_inverse().xform(target_position) : target_position;60746075undo_redo->add_do_method(child, "set_position", local_target_pos);60766077EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6078undo_redo->add_do_method(editor_selection, "add_node", child);6079}60806081bool CanvasItemEditorViewport::_create_instance(Node *p_parent, const String &p_path, const Point2 &p_point) {6082Ref<PackedScene> sdata = ResourceLoader::load(p_path);6083if (sdata.is_null()) { // invalid scene6084return false;6085}60866087Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);6088if (!instantiated_scene) { // Error on instantiation.6089return false;6090}60916092Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();60936094if (!edited_scene->get_scene_file_path().is_empty()) { // Cyclic instantiation.6095if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {6096memdelete(instantiated_scene);6097return false;6098}6099}61006101instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path));61026103EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6104EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6105undo_redo->add_do_method(p_parent, "add_child", instantiated_scene, true);6106undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);6107undo_redo->add_do_reference(instantiated_scene);6108undo_redo->add_undo_method(p_parent, "remove_child", instantiated_scene);6109undo_redo->add_do_method(editor_selection, "add_node", instantiated_scene);61106111String new_name = p_parent->validate_child_name(instantiated_scene);6112EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();6113undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(p_parent), p_path, new_name);6114undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(p_parent)) + "/" + new_name));61156116CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);6117if (instance_ci) {6118Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point);6119target_pos = canvas_item_editor->snap_point(target_pos);61206121CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_parent);6122if (parent_ci) {6123target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);6124}6125// Preserve instance position of the original scene.6126target_pos += instance_ci->_edit_get_position();61276128undo_redo->add_do_method(instantiated_scene, "set_position", target_pos);6129}61306131return true;6132}61336134void CanvasItemEditorViewport::_perform_drop_data() {6135ERR_FAIL_COND(selected_files.is_empty());61366137_remove_preview();61386139if (!target_node) {6140// Should already be handled by `can_drop_data`.6141ERR_FAIL_COND_MSG(selected_files.size() > 1, "Can't instantiate multiple nodes without root.");61426143const String &path = selected_files[0];6144Ref<Resource> res = ResourceLoader::load(path);6145if (res.is_null()) {6146return;6147}61486149Ref<PackedScene> scene = res;6150if (scene.is_valid()) {6151// Without root node act the same as "Load Inherited Scene".6152Error err = EditorNode::get_singleton()->load_scene(path, false, true);6153if (err != OK) {6154accept->set_text(vformat(TTR("Error instantiating scene from %s."), path.get_file()));6155accept->popup_centered();6156}6157return;6158}6159}61606161PackedStringArray error_files;61626163EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();6164undo_redo->create_action_for_history(TTR("Create Node"), EditorNode::get_editor_data().get_current_edited_scene_history_id());6165EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();6166undo_redo->add_do_method(editor_selection, "clear");61676168for (int i = 0; i < selected_files.size(); i++) {6169String path = selected_files[i];6170Ref<Resource> res = ResourceLoader::load(path);6171if (res.is_null()) {6172continue;6173}61746175Ref<PackedScene> scene = res;6176if (scene.is_valid()) {6177bool success = _create_instance(target_node, path, drop_pos);6178if (!success) {6179error_files.push_back(path.get_file());6180}6181continue;6182}61836184Ref<Texture2D> texture = res;6185if (texture.is_valid()) {6186Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type));6187_create_texture_node(target_node, child, path, drop_pos);6188undo_redo->add_do_method(editor_selection, "add_node", child);6189}61906191Ref<AudioStream> audio = res;6192if (audio.is_valid()) {6193_create_audio_node(target_node, path, drop_pos);6194}6195}61966197undo_redo->commit_action();61986199if (error_files.size() > 0) {6200accept->set_text(vformat(TTR("Error instantiating scene from %s."), String(", ").join(error_files)));6201accept->popup_centered();6202}6203}62046205bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Variant &p_data) const {6206if (p_point == Vector2(Math::INF, Math::INF)) {6207return false;6208}6209Dictionary d = p_data;6210if (!d.has("type") || (String(d["type"]) != "files")) {6211label->hide();6212return false;6213}62146215Vector<String> files = d["files"];62166217const Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();6218if (!edited_scene && files.size() > 1) {6219canvas_item_editor->message = TTR("Can't instantiate multiple nodes without root.");6220canvas_item_editor->update_viewport();6221return false;6222}62236224enum {6225SCENE = 1 << 0,6226TEXTURE = 1 << 1,6227AUDIO = 1 << 2,6228};6229int instantiate_type = 0;62306231for (const String &path : files) {6232const String &res_type = ResourceLoader::get_resource_type(path);6233String error_message;62346235if (ClassDB::is_parent_class(res_type, "PackedScene")) {6236Ref<PackedScene> scn = ResourceLoader::load(path);6237ERR_CONTINUE(scn.is_null());62386239Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);6240if (!instantiated_scene) {6241continue;6242}6243if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {6244error_message = vformat(TTR("Circular dependency found at %s."), path.get_file());6245}6246memdelete(instantiated_scene);6247instantiate_type |= SCENE;6248}6249if (ClassDB::is_parent_class(res_type, "Texture2D")) {6250instantiate_type |= TEXTURE;6251}6252if (ClassDB::is_parent_class(res_type, "AudioStream")) {6253instantiate_type |= AUDIO;6254}62556256if (!error_message.is_empty()) {6257// TRANSLATORS: The placeholder is the error message.6258canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message);6259canvas_item_editor->update_viewport();6260return false;6261}6262}6263if (instantiate_type == 0) {6264return false;6265}62666267if (!preview_node->get_parent()) { // create preview only once6268_create_preview(files);6269}6270ERR_FAIL_COND_V(preview_node->get_child_count() == 0, false);62716272const Transform2D trans = canvas_item_editor->get_canvas_transform();6273preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);62746275if (!edited_scene && instantiate_type & SCENE) {6276String scene_file_path = preview_node->get_child(0)->get_scene_file_path();6277// TRANSLATORS: The placeholder is the file path of the scene being instantiated.6278canvas_item_editor->message = vformat(TTR("Creating inherited scene from: %s"), scene_file_path);6279} else {6280double snap = EDITOR_GET("interface/inspector/default_float_step");6281int snap_step_decimals = Math::range_step_decimals(snap);6282#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals)))6283Vector2 preview_node_pos = preview_node->get_global_position();6284canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px";6285}6286canvas_item_editor->update_viewport();62876288if (instantiate_type & TEXTURE && instantiate_type & AUDIO) {6289// TRANSLATORS: The placeholders are the types of nodes being instantiated.6290label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D"));6291} else {6292String node_type;6293if (instantiate_type & TEXTURE) {6294node_type = default_texture_node_type;6295} else if (instantiate_type & AUDIO) {6296node_type = "AudioStreamPlayer2D";6297}6298if (!node_type.is_empty()) {6299// TRANSLATORS: The placeholder is the type of node being instantiated.6300label->set_text(vformat(TTR("Adding %s..."), node_type));6301}6302}6303label->set_visible(instantiate_type & ~SCENE);63046305String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +6306"\n" + TTR("Hold Shift when dropping to add as child of selected node.") +6307"\n" + TTR("Hold Alt when dropping to add as child of root node.");6308if (instantiate_type & TEXTURE) {6309desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type.");6310}6311label_desc->set_text(desc);6312label_desc->show();63136314return true;6315}63166317void CanvasItemEditorViewport::_show_texture_node_type_selector() {6318_remove_preview();6319List<BaseButton *> btn_list;6320button_group->get_buttons(&btn_list);63216322for (BaseButton *btn : btn_list) {6323CheckBox *check = Object::cast_to<CheckBox>(btn);6324check->set_pressed(check->get_text() == default_texture_node_type);6325}6326texture_node_type_selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));6327texture_node_type_selector->popup_centered();6328}63296330bool CanvasItemEditorViewport::_is_any_texture_selected() const {6331for (int i = 0; i < selected_files.size(); ++i) {6332if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(selected_files[i]), "Texture2D")) {6333return true;6334}6335}6336return false;6337}63386339void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {6340if (p_point == Vector2(Math::INF, Math::INF)) {6341return;6342}6343bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);6344bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);63456346selected_files.clear();6347Dictionary d = p_data;6348if (d.has("type") && String(d["type"]) == "files") {6349selected_files = d["files"];6350}6351if (selected_files.is_empty()) {6352return;6353}63546355List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_top_selected_node_list();6356Node *root_node = EditorNode::get_singleton()->get_edited_scene();6357if (selected_nodes.size() > 0) {6358Node *selected_node = selected_nodes.front()->get();6359if (is_alt) {6360target_node = root_node;6361} else if (is_shift) {6362target_node = selected_node;6363} else { // Default behavior.6364target_node = (selected_node != root_node) ? selected_node->get_parent() : root_node;6365}6366} else {6367if (root_node) {6368target_node = root_node;6369} else {6370target_node = nullptr;6371}6372}63736374drop_pos = p_point;63756376if (is_alt && is_shift && _is_any_texture_selected()) {6377_show_texture_node_type_selector();6378} else {6379_perform_drop_data();6380}6381}63826383void CanvasItemEditorViewport::_update_theme() {6384List<BaseButton *> btn_list;6385button_group->get_buttons(&btn_list);63866387for (BaseButton *btn : btn_list) {6388CheckBox *check = Object::cast_to<CheckBox>(btn);6389check->set_button_icon(get_editor_theme_icon(check->get_text()));6390}63916392label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));6393}63946395void CanvasItemEditorViewport::_notification(int p_what) {6396switch (p_what) {6397case NOTIFICATION_THEME_CHANGED: {6398_update_theme();6399} break;64006401case NOTIFICATION_ENTER_TREE: {6402_update_theme();6403connect(SceneStringName(mouse_exited), callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));6404} break;64056406case NOTIFICATION_EXIT_TREE: {6407disconnect(SceneStringName(mouse_exited), callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));6408} break;64096410case NOTIFICATION_DRAG_END: {6411_remove_preview();6412} break;6413}6414}64156416CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor) {6417default_texture_node_type = "Sprite2D";6418// Node2D6419texture_node_types.push_back("Sprite2D");6420texture_node_types.push_back("PointLight2D");6421texture_node_types.push_back("CPUParticles2D");6422texture_node_types.push_back("GPUParticles2D");6423texture_node_types.push_back("Polygon2D");6424texture_node_types.push_back("TouchScreenButton");6425// Control6426texture_node_types.push_back("TextureRect");6427texture_node_types.push_back("TextureButton");6428texture_node_types.push_back("NinePatchRect");64296430target_node = nullptr;6431canvas_item_editor = p_canvas_item_editor;6432preview_node = memnew(Control);64336434accept = memnew(AcceptDialog);6435EditorNode::get_singleton()->get_gui_base()->add_child(accept);64366437texture_node_type_selector = memnew(AcceptDialog);6438EditorNode::get_singleton()->get_gui_base()->add_child(texture_node_type_selector);6439texture_node_type_selector->connect(SceneStringName(confirmed), callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));6440texture_node_type_selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));64416442VBoxContainer *vbc = memnew(VBoxContainer);6443texture_node_type_selector->add_child(vbc);6444vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);6445vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);6446vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE);64476448VBoxContainer *btn_group = memnew(VBoxContainer);6449vbc->add_child(btn_group);6450btn_group->set_h_size_flags(SIZE_EXPAND_FILL);64516452button_group.instantiate();6453for (int i = 0; i < texture_node_types.size(); i++) {6454CheckBox *check = memnew(CheckBox);6455check->set_text(texture_node_types[i]);6456check->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);6457check->set_button_group(button_group);6458btn_group->add_child(check);6459check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check));6460}64616462label = memnew(Label);6463label->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1));6464label->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);6465label->hide();6466canvas_item_editor->get_controls_container()->add_child(label);64676468label_desc = memnew(Label);6469label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);6470label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.6f, 0.6f, 0.6f, 1));6471label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));6472label_desc->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);6473label_desc->add_theme_constant_override("line_spacing", 0);6474label_desc->hide();6475canvas_item_editor->get_controls_container()->add_child(label_desc);64766477RS::get_singleton()->canvas_set_disable_scale(true);6478}64796480CanvasItemEditorViewport::~CanvasItemEditorViewport() {6481memdelete(preview_node);6482}648364846485