1/**************************************************************************/
2/* canvas_item_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "canvas_item_editor_plugin.h"
32
33#include "core/config/project_settings.h"
34#include "core/input/input.h"
35#include "core/os/keyboard.h"
36#include "editor/debugger/editor_debugger_node.h"
37#include "editor/editor_node.h"
38#include "editor/editor_scale.h"
39#include "editor/editor_settings.h"
40#include "editor/editor_string_names.h"
41#include "editor/editor_undo_redo_manager.h"
42#include "editor/gui/editor_run_bar.h"
43#include "editor/gui/editor_toaster.h"
44#include "editor/gui/editor_zoom_widget.h"
45#include "editor/plugins/animation_player_editor_plugin.h"
46#include "editor/plugins/script_editor_plugin.h"
47#include "editor/scene_tree_dock.h"
48#include "scene/2d/polygon_2d.h"
49#include "scene/2d/skeleton_2d.h"
50#include "scene/2d/sprite_2d.h"
51#include "scene/2d/touch_screen_button.h"
52#include "scene/gui/flow_container.h"
53#include "scene/gui/grid_container.h"
54#include "scene/gui/separator.h"
55#include "scene/gui/split_container.h"
56#include "scene/gui/subviewport_container.h"
57#include "scene/gui/view_panner.h"
58#include "scene/main/canvas_layer.h"
59#include "scene/main/window.h"
60#include "scene/resources/packed_scene.h"
61#include "scene/resources/style_box_texture.h"
62
63// Min and Max are power of two in order to play nicely with successive increment.
64// That way, we can naturally reach a 100% zoom from boundaries.
65constexpr real_t MIN_ZOOM = 1. / 128;
66constexpr real_t MAX_ZOOM = 128;
67
68#define RULER_WIDTH (15 * EDSCALE)
69constexpr real_t SCALE_HANDLE_DISTANCE = 25;
70constexpr real_t MOVE_HANDLE_DISTANCE = 25;
71
72class SnapDialog : public ConfirmationDialog {
73 GDCLASS(SnapDialog, ConfirmationDialog);
74
75 friend class CanvasItemEditor;
76
77 SpinBox *grid_offset_x;
78 SpinBox *grid_offset_y;
79 SpinBox *grid_step_x;
80 SpinBox *grid_step_y;
81 SpinBox *primary_grid_step_x;
82 SpinBox *primary_grid_step_y;
83 SpinBox *rotation_offset;
84 SpinBox *rotation_step;
85 SpinBox *scale_step;
86
87public:
88 SnapDialog() {
89 const int SPIN_BOX_GRID_RANGE = 16384;
90 const int SPIN_BOX_ROTATION_RANGE = 360;
91 const real_t SPIN_BOX_SCALE_MIN = 0.01;
92 const real_t SPIN_BOX_SCALE_MAX = 100;
93
94 Label *label;
95 VBoxContainer *container;
96 GridContainer *child_container;
97
98 set_title(TTR("Configure Snap"));
99
100 container = memnew(VBoxContainer);
101 add_child(container);
102
103 child_container = memnew(GridContainer);
104 child_container->set_columns(3);
105 container->add_child(child_container);
106
107 label = memnew(Label);
108 label->set_text(TTR("Grid Offset:"));
109 child_container->add_child(label);
110 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
111
112 grid_offset_x = memnew(SpinBox);
113 grid_offset_x->set_min(-SPIN_BOX_GRID_RANGE);
114 grid_offset_x->set_max(SPIN_BOX_GRID_RANGE);
115 grid_offset_x->set_allow_lesser(true);
116 grid_offset_x->set_allow_greater(true);
117 grid_offset_x->set_suffix("px");
118 grid_offset_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);
119 grid_offset_x->set_select_all_on_focus(true);
120 child_container->add_child(grid_offset_x);
121
122 grid_offset_y = memnew(SpinBox);
123 grid_offset_y->set_min(-SPIN_BOX_GRID_RANGE);
124 grid_offset_y->set_max(SPIN_BOX_GRID_RANGE);
125 grid_offset_y->set_allow_lesser(true);
126 grid_offset_y->set_allow_greater(true);
127 grid_offset_y->set_suffix("px");
128 grid_offset_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);
129 grid_offset_y->set_select_all_on_focus(true);
130 child_container->add_child(grid_offset_y);
131
132 label = memnew(Label);
133 label->set_text(TTR("Grid Step:"));
134 child_container->add_child(label);
135 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
136
137 grid_step_x = memnew(SpinBox);
138 grid_step_x->set_min(1);
139 grid_step_x->set_max(SPIN_BOX_GRID_RANGE);
140 grid_step_x->set_allow_greater(true);
141 grid_step_x->set_suffix("px");
142 grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);
143 grid_step_x->set_select_all_on_focus(true);
144 child_container->add_child(grid_step_x);
145
146 grid_step_y = memnew(SpinBox);
147 grid_step_y->set_min(1);
148 grid_step_y->set_max(SPIN_BOX_GRID_RANGE);
149 grid_step_y->set_allow_greater(true);
150 grid_step_y->set_suffix("px");
151 grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);
152 grid_step_y->set_select_all_on_focus(true);
153 child_container->add_child(grid_step_y);
154
155 label = memnew(Label);
156 label->set_text(TTR("Primary Line Every:"));
157 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
158 child_container->add_child(label);
159
160 primary_grid_step_x = memnew(SpinBox);
161 primary_grid_step_x->set_min(1);
162 primary_grid_step_x->set_step(1);
163 primary_grid_step_x->set_max(SPIN_BOX_GRID_RANGE);
164 primary_grid_step_x->set_allow_greater(true);
165 primary_grid_step_x->set_suffix("steps");
166 primary_grid_step_x->set_h_size_flags(Control::SIZE_EXPAND_FILL);
167 primary_grid_step_x->set_select_all_on_focus(true);
168 child_container->add_child(primary_grid_step_x);
169
170 primary_grid_step_y = memnew(SpinBox);
171 primary_grid_step_y->set_min(1);
172 primary_grid_step_y->set_step(1);
173 primary_grid_step_y->set_max(SPIN_BOX_GRID_RANGE);
174 primary_grid_step_y->set_allow_greater(true);
175 primary_grid_step_y->set_suffix("steps");
176 primary_grid_step_y->set_h_size_flags(Control::SIZE_EXPAND_FILL);
177 primary_grid_step_y->set_select_all_on_focus(true);
178 child_container->add_child(primary_grid_step_y);
179
180 container->add_child(memnew(HSeparator));
181
182 // We need to create another GridContainer with the same column count,
183 // so we can put an HSeparator above
184 child_container = memnew(GridContainer);
185 child_container->set_columns(2);
186 container->add_child(child_container);
187
188 label = memnew(Label);
189 label->set_text(TTR("Rotation Offset:"));
190 child_container->add_child(label);
191 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
192
193 rotation_offset = memnew(SpinBox);
194 rotation_offset->set_min(-SPIN_BOX_ROTATION_RANGE);
195 rotation_offset->set_max(SPIN_BOX_ROTATION_RANGE);
196 rotation_offset->set_suffix("deg");
197 rotation_offset->set_h_size_flags(Control::SIZE_EXPAND_FILL);
198 rotation_offset->set_select_all_on_focus(true);
199 child_container->add_child(rotation_offset);
200
201 label = memnew(Label);
202 label->set_text(TTR("Rotation Step:"));
203 child_container->add_child(label);
204 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
205
206 rotation_step = memnew(SpinBox);
207 rotation_step->set_min(-SPIN_BOX_ROTATION_RANGE);
208 rotation_step->set_max(SPIN_BOX_ROTATION_RANGE);
209 rotation_step->set_suffix("deg");
210 rotation_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);
211 rotation_step->set_select_all_on_focus(true);
212 child_container->add_child(rotation_step);
213
214 container->add_child(memnew(HSeparator));
215
216 child_container = memnew(GridContainer);
217 child_container->set_columns(2);
218 container->add_child(child_container);
219 label = memnew(Label);
220 label->set_text(TTR("Scale Step:"));
221 child_container->add_child(label);
222 label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
223
224 scale_step = memnew(SpinBox);
225 scale_step->set_min(SPIN_BOX_SCALE_MIN);
226 scale_step->set_max(SPIN_BOX_SCALE_MAX);
227 scale_step->set_allow_greater(true);
228 scale_step->set_h_size_flags(Control::SIZE_EXPAND_FILL);
229 scale_step->set_step(0.01f);
230 scale_step->set_select_all_on_focus(true);
231 child_container->add_child(scale_step);
232 }
233
234 void 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) {
235 grid_offset_x->set_value(p_grid_offset.x);
236 grid_offset_y->set_value(p_grid_offset.y);
237 grid_step_x->set_value(p_grid_step.x);
238 grid_step_y->set_value(p_grid_step.y);
239 primary_grid_step_x->set_value(p_primary_grid_step.x);
240 primary_grid_step_y->set_value(p_primary_grid_step.y);
241 rotation_offset->set_value(Math::rad_to_deg(p_rotation_offset));
242 rotation_step->set_value(Math::rad_to_deg(p_rotation_step));
243 scale_step->set_value(p_scale_step);
244 }
245
246 void 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) {
247 p_grid_offset = Point2(grid_offset_x->get_value(), grid_offset_y->get_value());
248 p_grid_step = Point2(grid_step_x->get_value(), grid_step_y->get_value());
249 p_primary_grid_step = Vector2i(primary_grid_step_x->get_value(), primary_grid_step_y->get_value());
250 p_rotation_offset = Math::deg_to_rad(rotation_offset->get_value());
251 p_rotation_step = Math::deg_to_rad(rotation_step->get_value());
252 p_scale_step = scale_step->get_value();
253 }
254};
255
256bool CanvasItemEditor::_is_node_locked(const Node *p_node) const {
257 return p_node->get_meta("_edit_lock_", false);
258}
259
260bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning) {
261 if (_is_node_locked(p_node)) {
262 return false;
263 }
264 if (Object::cast_to<Control>(p_node) && Object::cast_to<Container>(p_node->get_parent())) {
265 if (p_popup_warning) {
266 EditorToaster::get_singleton()->popup_str(TTR("Children of a container get their position and size determined only by their parent."), EditorToaster::SEVERITY_WARNING);
267 }
268 return false;
269 }
270 return true;
271}
272
273void CanvasItemEditor::_snap_if_closer_float(
274 const real_t p_value,
275 real_t &r_current_snap, SnapTarget &r_current_snap_target,
276 const real_t p_target_value, const SnapTarget p_snap_target,
277 const real_t p_radius) {
278 const real_t radius = p_radius / zoom;
279 const real_t dist = Math::abs(p_value - p_target_value);
280 if ((p_radius < 0 || dist < radius) && (r_current_snap_target == SNAP_TARGET_NONE || dist < Math::abs(r_current_snap - p_value))) {
281 r_current_snap = p_target_value;
282 r_current_snap_target = p_snap_target;
283 }
284}
285
286void CanvasItemEditor::_snap_if_closer_point(
287 Point2 p_value,
288 Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],
289 Point2 p_target_value, const SnapTarget p_snap_target,
290 const real_t rotation,
291 const real_t p_radius) {
292 Transform2D rot_trans = Transform2D(rotation, Point2());
293 p_value = rot_trans.inverse().xform(p_value);
294 p_target_value = rot_trans.inverse().xform(p_target_value);
295 r_current_snap = rot_trans.inverse().xform(r_current_snap);
296
297 _snap_if_closer_float(
298 p_value.x,
299 r_current_snap.x,
300 r_current_snap_target[0],
301 p_target_value.x,
302 p_snap_target,
303 p_radius);
304
305 _snap_if_closer_float(
306 p_value.y,
307 r_current_snap.y,
308 r_current_snap_target[1],
309 p_target_value.y,
310 p_snap_target,
311 p_radius);
312
313 r_current_snap = rot_trans.xform(r_current_snap);
314}
315
316void CanvasItemEditor::_snap_other_nodes(
317 const Point2 p_value,
318 const Transform2D p_transform_to_snap,
319 Point2 &r_current_snap, SnapTarget (&r_current_snap_target)[2],
320 const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions,
321 const Node *p_current) {
322 const CanvasItem *ci = Object::cast_to<CanvasItem>(p_current);
323
324 // Check if the element is in the exception
325 bool exception = false;
326 for (const CanvasItem *&E : p_exceptions) {
327 if (E == p_current) {
328 exception = true;
329 break;
330 }
331 };
332
333 if (ci && !exception) {
334 Transform2D ci_transform = ci->get_global_transform_with_canvas();
335 if (fmod(ci_transform.get_rotation() - p_transform_to_snap.get_rotation(), (real_t)360.0) == 0.0) {
336 if (ci->_edit_use_rect()) {
337 Point2 begin = ci_transform.xform(ci->_edit_get_rect().get_position());
338 Point2 end = ci_transform.xform(ci->_edit_get_rect().get_position() + ci->_edit_get_rect().get_size());
339
340 _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, begin, p_snap_target, ci_transform.get_rotation());
341 _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, end, p_snap_target, ci_transform.get_rotation());
342 } else {
343 Point2 position = ci_transform.xform(Point2());
344 _snap_if_closer_point(p_value, r_current_snap, r_current_snap_target, position, p_snap_target, ci_transform.get_rotation());
345 }
346 }
347 }
348 for (int i = 0; i < p_current->get_child_count(); i++) {
349 _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));
350 }
351}
352
353Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsigned int p_forced_modes, const CanvasItem *p_self_canvas_item, List<CanvasItem *> p_other_nodes_exceptions) {
354 snap_target[0] = SNAP_TARGET_NONE;
355 snap_target[1] = SNAP_TARGET_NONE;
356
357 bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
358
359 // Smart snap using the canvas position
360 Vector2 output = p_target;
361 real_t rotation = 0.0;
362
363 if (p_self_canvas_item) {
364 rotation = p_self_canvas_item->get_global_transform_with_canvas().get_rotation();
365
366 // Parent sides and center
367 if ((is_snap_active && snap_node_parent && (p_modes & SNAP_NODE_PARENT)) || (p_forced_modes & SNAP_NODE_PARENT)) {
368 if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {
369 Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(0, 0)));
370 Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(1, 1)));
371 _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);
372 _snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);
373 _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);
374 } else if (const CanvasItem *parent_ci = Object::cast_to<CanvasItem>(p_self_canvas_item->get_parent())) {
375 if (parent_ci->_edit_use_rect()) {
376 Point2 begin = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position());
377 Point2 end = p_self_canvas_item->get_transform().affine_inverse().xform(parent_ci->_edit_get_rect().get_position() + parent_ci->_edit_get_rect().get_size());
378 _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_PARENT, rotation);
379 _snap_if_closer_point(p_target, output, snap_target, (begin + end) / 2.0, SNAP_TARGET_PARENT, rotation);
380 _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_PARENT, rotation);
381 } else {
382 Point2 position = p_self_canvas_item->get_transform().affine_inverse().xform(Point2());
383 _snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_PARENT, rotation);
384 }
385 }
386 }
387
388 // Self anchors
389 if ((is_snap_active && snap_node_anchors && (p_modes & SNAP_NODE_ANCHORS)) || (p_forced_modes & SNAP_NODE_ANCHORS)) {
390 if (const Control *c = Object::cast_to<Control>(p_self_canvas_item)) {
391 Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_LEFT), c->get_anchor(SIDE_TOP))));
392 Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(_anchor_to_position(c, Point2(c->get_anchor(SIDE_RIGHT), c->get_anchor(SIDE_BOTTOM))));
393 _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF_ANCHORS, rotation);
394 _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF_ANCHORS, rotation);
395 }
396 }
397
398 // Self sides
399 if ((is_snap_active && snap_node_sides && (p_modes & SNAP_NODE_SIDES)) || (p_forced_modes & SNAP_NODE_SIDES)) {
400 if (p_self_canvas_item->_edit_use_rect()) {
401 Point2 begin = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position());
402 Point2 end = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position() + p_self_canvas_item->_edit_get_rect().get_size());
403 _snap_if_closer_point(p_target, output, snap_target, begin, SNAP_TARGET_SELF, rotation);
404 _snap_if_closer_point(p_target, output, snap_target, end, SNAP_TARGET_SELF, rotation);
405 }
406 }
407
408 // Self center
409 if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) {
410 if (p_self_canvas_item->_edit_use_rect()) {
411 Point2 center = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_center());
412 _snap_if_closer_point(p_target, output, snap_target, center, SNAP_TARGET_SELF, rotation);
413 } else {
414 Point2 position = p_self_canvas_item->get_global_transform_with_canvas().xform(Point2());
415 _snap_if_closer_point(p_target, output, snap_target, position, SNAP_TARGET_SELF, rotation);
416 }
417 }
418 }
419
420 // Other nodes sides
421 if ((is_snap_active && snap_other_nodes && (p_modes & SNAP_OTHER_NODES)) || (p_forced_modes & SNAP_OTHER_NODES)) {
422 Transform2D to_snap_transform;
423 List<const CanvasItem *> exceptions = List<const CanvasItem *>();
424 for (const CanvasItem *E : p_other_nodes_exceptions) {
425 exceptions.push_back(E);
426 }
427 if (p_self_canvas_item) {
428 exceptions.push_back(p_self_canvas_item);
429 to_snap_transform = p_self_canvas_item->get_global_transform_with_canvas();
430 }
431
432 _snap_other_nodes(
433 p_target, to_snap_transform,
434 output, snap_target,
435 SNAP_TARGET_OTHER_NODE,
436 exceptions,
437 get_tree()->get_edited_scene_root());
438 }
439
440 if (((is_snap_active && snap_guides && (p_modes & SNAP_GUIDES)) || (p_forced_modes & SNAP_GUIDES)) && fmod(rotation, (real_t)360.0) == 0.0) {
441 // Guides.
442 if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {
443 Array vguides = scene->get_meta("_edit_vertical_guides_", Array());
444 for (int i = 0; i < vguides.size(); i++) {
445 _snap_if_closer_float(p_target.x, output.x, snap_target[0], vguides[i], SNAP_TARGET_GUIDE);
446 }
447
448 Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());
449 for (int i = 0; i < hguides.size(); i++) {
450 _snap_if_closer_float(p_target.y, output.y, snap_target[1], hguides[i], SNAP_TARGET_GUIDE);
451 }
452 }
453 }
454
455 if (((grid_snap_active && (p_modes & SNAP_GRID)) || (p_forced_modes & SNAP_GRID)) && fmod(rotation, (real_t)360.0) == 0.0) {
456 // Grid
457 Point2 offset = grid_offset;
458 if (snap_relative) {
459 List<CanvasItem *> selection = _get_edited_canvas_items();
460 if (selection.size() == 1 && Object::cast_to<Node2D>(selection[0])) {
461 offset = Object::cast_to<Node2D>(selection[0])->get_global_position();
462 } else if (selection.size() > 0) {
463 offset = _get_encompassing_rect_from_list(selection).position;
464 }
465 }
466 Point2 grid_output;
467 grid_output.x = Math::snapped(p_target.x - offset.x, grid_step.x * Math::pow(2.0, grid_step_multiplier)) + offset.x;
468 grid_output.y = Math::snapped(p_target.y - offset.y, grid_step.y * Math::pow(2.0, grid_step_multiplier)) + offset.y;
469 _snap_if_closer_point(p_target, output, snap_target, grid_output, SNAP_TARGET_GRID, 0.0, -1.0);
470 }
471
472 if (((snap_pixel && (p_modes & SNAP_PIXEL)) || (p_forced_modes & SNAP_PIXEL)) && rotation == 0.0) {
473 // Pixel
474 output = output.snapped(Size2(1, 1));
475 }
476
477 snap_transform = Transform2D(rotation, output);
478
479 return output;
480}
481
482real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {
483 if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) && snap_rotation_step != 0) {
484 if (snap_relative) {
485 return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);
486 } else {
487 return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset;
488 }
489 } else {
490 return p_target;
491 }
492}
493
494void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
495 ERR_FAIL_COND(p_ev.is_null());
496
497 Ref<InputEventKey> k = p_ev;
498
499 if (!is_visible_in_tree()) {
500 return;
501 }
502
503 if (k.is_valid()) {
504 if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT) {
505 viewport->queue_redraw();
506 }
507
508 if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {
509 if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) {
510 // Multiply the grid size
511 grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);
512 viewport->queue_redraw();
513 } else if (divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) {
514 // Divide the grid size
515 Point2 new_grid_step = grid_step * Math::pow(2.0, grid_step_multiplier - 1);
516 if (new_grid_step.x >= 1.0 && new_grid_step.y >= 1.0) {
517 grid_step_multiplier--;
518 }
519 viewport->queue_redraw();
520 }
521 }
522 }
523}
524
525Object *CanvasItemEditor::_get_editor_data(Object *p_what) {
526 CanvasItem *ci = Object::cast_to<CanvasItem>(p_what);
527 if (!ci) {
528 return nullptr;
529 }
530
531 return memnew(CanvasItemEditorSelectedItem);
532}
533
534void CanvasItemEditor::_keying_changed() {
535 AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
536 if (te && te->is_visible_in_tree() && te->get_current_animation().is_valid()) {
537 animation_hb->show();
538 } else {
539 animation_hb->hide();
540 }
541}
542
543Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_list) {
544 ERR_FAIL_COND_V(p_list.is_empty(), Rect2());
545
546 // Handles the first element
547 CanvasItem *ci = p_list.front()->get();
548 Rect2 rect = Rect2(ci->get_global_transform_with_canvas().xform(ci->_edit_get_rect().get_center()), Size2());
549
550 // Expand with the other ones
551 for (CanvasItem *ci2 : p_list) {
552 Transform2D xform = ci2->get_global_transform_with_canvas();
553
554 Rect2 current_rect = ci2->_edit_get_rect();
555 rect.expand_to(xform.xform(current_rect.position));
556 rect.expand_to(xform.xform(current_rect.position + Vector2(current_rect.size.x, 0)));
557 rect.expand_to(xform.xform(current_rect.position + current_rect.size));
558 rect.expand_to(xform.xform(current_rect.position + Vector2(0, current_rect.size.y)));
559 }
560
561 return rect;
562}
563
564void 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) {
565 if (!p_node) {
566 return;
567 }
568 if (Object::cast_to<Viewport>(p_node)) {
569 return;
570 }
571
572 const CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
573
574 for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
575 if (ci && !ci->is_set_as_top_level()) {
576 _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, p_parent_xform * ci->get_transform(), p_canvas_xform);
577 } else {
578 const CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
579 _expand_encompassing_rect_using_children(r_rect, p_node->get_child(i), r_first, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
580 }
581 }
582
583 if (ci && ci->is_visible_in_tree() && (include_locked_nodes || !_is_node_locked(ci))) {
584 Transform2D xform = p_canvas_xform;
585 if (!ci->is_set_as_top_level()) {
586 xform *= p_parent_xform;
587 }
588 xform *= ci->get_transform();
589 Rect2 rect = ci->_edit_get_rect();
590 if (r_first) {
591 r_rect = Rect2(xform.xform(rect.get_center()), Size2());
592 r_first = false;
593 }
594 r_rect.expand_to(xform.xform(rect.position));
595 r_rect.expand_to(xform.xform(rect.position + Point2(rect.size.x, 0)));
596 r_rect.expand_to(xform.xform(rect.position + Point2(0, rect.size.y)));
597 r_rect.expand_to(xform.xform(rect.position + rect.size));
598 }
599}
600
601Rect2 CanvasItemEditor::_get_encompassing_rect(const Node *p_node) {
602 Rect2 rect;
603 bool first = true;
604 _expand_encompassing_rect_using_children(rect, p_node, first);
605
606 return rect;
607}
608
609void 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) {
610 if (!p_node) {
611 return;
612 }
613 if (Object::cast_to<Viewport>(p_node)) {
614 return;
615 }
616
617 const real_t grab_distance = EDITOR_GET("editors/polygon_editor/point_grab_radius");
618 CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
619
620 for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
621 if (ci) {
622 if (!ci->is_set_as_top_level()) {
623 _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
624 } else {
625 _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
626 }
627 } else {
628 CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
629 _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
630 }
631 }
632
633 if (ci && ci->is_visible_in_tree()) {
634 Transform2D xform = p_canvas_xform;
635 if (!ci->is_set_as_top_level()) {
636 xform *= p_parent_xform;
637 }
638 xform = (xform * ci->get_transform()).affine_inverse();
639 const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length() / zoom;
640 if (ci->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) {
641 Node2D *node = Object::cast_to<Node2D>(ci);
642
643 _SelectResult res;
644 res.item = ci;
645 res.z_index = node ? node->get_z_index() : 0;
646 res.has_z = node;
647 r_items.push_back(res);
648 }
649 }
650}
651
652void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked) {
653 Node *scene = EditorNode::get_singleton()->get_edited_scene();
654
655 _find_canvas_items_at_pos(p_pos, scene, r_items);
656
657 //Remove invalid results
658 for (int i = 0; i < r_items.size(); i++) {
659 Node *node = r_items[i].item;
660
661 // Make sure the selected node is in the current scene, or editable
662 if (node && node != get_tree()->get_edited_scene_root()) {
663 node = scene->get_deepest_editable_node(node);
664 }
665
666 CanvasItem *ci = Object::cast_to<CanvasItem>(node);
667 if (!p_allow_locked) {
668 // Replace the node by the group if grouped
669 while (node && node != scene->get_parent()) {
670 CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);
671 if (ci_tmp && node->has_meta("_edit_group_")) {
672 ci = ci_tmp;
673 }
674 node = node->get_parent();
675 }
676 }
677
678 // Check if the canvas item is already in the list (for groups or scenes)
679 bool duplicate = false;
680 for (int j = 0; j < i; j++) {
681 if (r_items[j].item == ci) {
682 duplicate = true;
683 break;
684 }
685 }
686
687 //Remove the item if invalid
688 if (!ci || duplicate || (ci != scene && ci->get_owner() != scene && !scene->is_editable_instance(ci->get_owner())) || (!p_allow_locked && _is_node_locked(ci))) {
689 r_items.remove_at(i);
690 i--;
691 } else {
692 r_items.write[i].item = ci;
693 }
694 }
695}
696
697void 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) {
698 if (!p_node) {
699 return;
700 }
701 if (Object::cast_to<Viewport>(p_node)) {
702 return;
703 }
704
705 CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
706 Node *scene = EditorNode::get_singleton()->get_edited_scene();
707
708 if (p_node != scene && !p_node->get_owner()) {
709 return;
710 }
711
712 bool editable = p_node == scene || p_node->get_owner() == scene || p_node == scene->get_deepest_editable_node(p_node);
713 bool lock_children = p_node->get_meta("_edit_group_", false);
714 bool locked = _is_node_locked(p_node);
715
716 if (!lock_children || !editable) {
717 for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
718 if (ci) {
719 if (!ci->is_set_as_top_level()) {
720 _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * ci->get_transform(), p_canvas_xform);
721 } else {
722 _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, ci->get_transform(), p_canvas_xform);
723 }
724 } else {
725 CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
726 _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, Transform2D(), cl ? cl->get_transform() : p_canvas_xform);
727 }
728 }
729 }
730
731 if (ci && ci->is_visible_in_tree() && !locked && editable) {
732 Transform2D xform = p_canvas_xform;
733 if (!ci->is_set_as_top_level()) {
734 xform *= p_parent_xform;
735 }
736 xform *= ci->get_transform();
737
738 if (ci->_edit_use_rect()) {
739 Rect2 rect = ci->_edit_get_rect();
740 if (p_rect.has_point(xform.xform(rect.position)) &&
741 p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, 0))) &&
742 p_rect.has_point(xform.xform(rect.position + Vector2(rect.size.x, rect.size.y))) &&
743 p_rect.has_point(xform.xform(rect.position + Vector2(0, rect.size.y)))) {
744 r_items->push_back(ci);
745 }
746 } else {
747 if (p_rect.has_point(xform.xform(Point2()))) {
748 r_items->push_back(ci);
749 }
750 }
751 }
752}
753
754bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append) {
755 bool still_selected = true;
756 if (p_append && !editor_selection->get_selected_node_list().is_empty()) {
757 if (editor_selection->is_selected(item)) {
758 // Already in the selection, remove it from the selected nodes
759 editor_selection->remove_node(item);
760 still_selected = false;
761
762 if (editor_selection->get_selected_node_list().size() == 1) {
763 EditorNode::get_singleton()->push_item(editor_selection->get_selected_node_list()[0]);
764 }
765 } else {
766 // Add the item to the selection
767 editor_selection->add_node(item);
768 }
769 } else {
770 if (!editor_selection->is_selected(item)) {
771 // Select a new one and clear previous selection
772 editor_selection->clear();
773 editor_selection->add_node(item);
774 // Reselect
775 if (Engine::get_singleton()->is_editor_hint()) {
776 selected_from_canvas = true;
777 EditorNode::get_singleton()->edit_node(item);
778 }
779 }
780 }
781 viewport->queue_redraw();
782 return still_selected;
783}
784
785List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) const {
786 List<CanvasItem *> selection;
787 for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) {
788 CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
789 if (ci && ci->is_visible_in_tree() && ci->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(ci))) {
790 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
791 if (se) {
792 selection.push_back(ci);
793 }
794 }
795 }
796
797 if (remove_canvas_item_if_parent_in_selection) {
798 List<CanvasItem *> filtered_selection;
799 for (CanvasItem *E : selection) {
800 if (!selection.find(E->get_parent())) {
801 filtered_selection.push_back(E);
802 }
803 }
804 return filtered_selection;
805 } else {
806 return selection;
807 }
808}
809
810Vector2 CanvasItemEditor::_anchor_to_position(const Control *p_control, Vector2 anchor) {
811 ERR_FAIL_NULL_V(p_control, Vector2());
812
813 Transform2D parent_transform = p_control->get_transform().affine_inverse();
814 Rect2 parent_rect = p_control->get_parent_anchorable_rect();
815
816 if (p_control->is_layout_rtl()) {
817 return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));
818 } else {
819 return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y));
820 }
821}
822
823Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 position) {
824 ERR_FAIL_NULL_V(p_control, Vector2());
825
826 Rect2 parent_rect = p_control->get_parent_anchorable_rect();
827
828 Vector2 output;
829 if (p_control->is_layout_rtl()) {
830 output.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;
831 } else {
832 output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x;
833 }
834 output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y;
835 return output;
836}
837
838void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) {
839 original_transform = Transform2D();
840 bool transform_stored = false;
841
842 for (CanvasItem *ci : p_canvas_items) {
843 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
844 if (se) {
845 if (!transform_stored) {
846 original_transform = ci->get_global_transform();
847 transform_stored = true;
848 }
849
850 se->undo_state = ci->_edit_get_state();
851 se->pre_drag_xform = ci->get_global_transform_with_canvas();
852 if (ci->_edit_use_rect()) {
853 se->pre_drag_rect = ci->_edit_get_rect();
854 } else {
855 se->pre_drag_rect = Rect2();
856 }
857 }
858 }
859}
860
861void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) {
862 for (CanvasItem *ci : drag_selection) {
863 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
864 ci->_edit_set_state(se->undo_state);
865 }
866}
867
868void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones) {
869 List<CanvasItem *> modified_canvas_items;
870 for (CanvasItem *ci : p_canvas_items) {
871 Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci)->undo_state;
872 Dictionary new_state = ci->_edit_get_state();
873
874 if (old_state.hash() != new_state.hash()) {
875 modified_canvas_items.push_back(ci);
876 }
877 }
878
879 if (modified_canvas_items.is_empty()) {
880 return;
881 }
882
883 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
884 undo_redo->create_action(action_name);
885 for (CanvasItem *ci : modified_canvas_items) {
886 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
887 if (se) {
888 undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());
889 undo_redo->add_undo_method(ci, "_edit_set_state", se->undo_state);
890 if (commit_bones) {
891 for (const Dictionary &F : se->pre_drag_bones_undo_state) {
892 ci = Object::cast_to<CanvasItem>(ci->get_parent());
893 undo_redo->add_do_method(ci, "_edit_set_state", ci->_edit_get_state());
894 undo_redo->add_undo_method(ci, "_edit_set_state", F);
895 }
896 }
897 }
898 }
899 undo_redo->add_do_method(viewport, "queue_redraw");
900 undo_redo->add_undo_method(viewport, "queue_redraw");
901 undo_redo->commit_action();
902}
903
904void CanvasItemEditor::_snap_changed() {
905 static_cast<SnapDialog *>(snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);
906
907 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_offset", grid_offset);
908 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "grid_step", grid_step);
909 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "primary_grid_step", primary_grid_step);
910 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_offset", snap_rotation_offset);
911 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_rotation_step", snap_rotation_step);
912 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "snap_scale_step", snap_scale_step);
913
914 grid_step_multiplier = 0;
915 viewport->queue_redraw();
916}
917
918void CanvasItemEditor::_selection_result_pressed(int p_result) {
919 if (selection_results_menu.size() <= p_result) {
920 return;
921 }
922
923 CanvasItem *item = selection_results_menu[p_result].item;
924
925 if (item) {
926 _select_click_on_item(item, Point2(), selection_menu_additive_selection);
927 }
928 selection_results_menu.clear();
929}
930
931void CanvasItemEditor::_selection_menu_hide() {
932 selection_results.clear();
933 selection_menu->clear();
934 selection_menu->reset_size();
935}
936
937void CanvasItemEditor::_add_node_pressed(int p_result) {
938 List<Node *> nodes_to_move;
939
940 switch (p_result) {
941 case ADD_NODE: {
942 SceneTreeDock::get_singleton()->open_add_child_dialog();
943 } break;
944 case ADD_INSTANCE: {
945 SceneTreeDock::get_singleton()->open_instance_child_dialog();
946 } break;
947 case ADD_PASTE: {
948 nodes_to_move = SceneTreeDock::get_singleton()->paste_nodes();
949 [[fallthrough]];
950 }
951 case ADD_MOVE: {
952 nodes_to_move = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
953 if (nodes_to_move.is_empty()) {
954 return;
955 }
956
957 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
958 undo_redo->create_action(TTR("Move Node(s) to Position"));
959 for (Node *node : nodes_to_move) {
960 CanvasItem *ci = Object::cast_to<CanvasItem>(node);
961 if (ci) {
962 Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();
963 undo_redo->add_do_method(ci, "_edit_set_position", xform.xform(node_create_position));
964 undo_redo->add_undo_method(ci, "_edit_set_position", ci->_edit_get_position());
965 }
966 }
967 undo_redo->commit_action();
968 _reset_create_position();
969 } break;
970 }
971}
972
973void CanvasItemEditor::_node_created(Node *p_node) {
974 if (node_create_position == Point2()) {
975 return;
976 }
977
978 CanvasItem *c = Object::cast_to<CanvasItem>(p_node);
979 if (c) {
980 Transform2D xform = c->get_global_transform_with_canvas().affine_inverse() * c->get_transform();
981 c->_edit_set_position(xform.xform(node_create_position));
982 }
983
984 call_deferred(SNAME("_reset_create_position")); // Defer the call in case more than one node is added.
985}
986
987void CanvasItemEditor::_reset_create_position() {
988 node_create_position = Point2();
989}
990
991bool CanvasItemEditor::_is_grid_visible() const {
992 switch (grid_visibility) {
993 case GRID_VISIBILITY_SHOW:
994 return true;
995 case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:
996 return grid_snap_active;
997 case GRID_VISIBILITY_HIDE:
998 return false;
999 }
1000 ERR_FAIL_V_MSG(true, "Unexpected grid_visibility value");
1001}
1002
1003void CanvasItemEditor::_prepare_grid_menu() {
1004 for (int i = GRID_VISIBILITY_SHOW; i <= GRID_VISIBILITY_HIDE; i++) {
1005 grid_menu->set_item_checked(i, i == grid_visibility);
1006 }
1007}
1008
1009void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) {
1010 switch (p_id) {
1011 case GRID_VISIBILITY_SHOW:
1012 case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:
1013 case GRID_VISIBILITY_HIDE:
1014 grid_visibility = (GridVisibility)p_id;
1015 viewport->queue_redraw();
1016 view_menu->get_popup()->hide();
1017 return;
1018 }
1019
1020 // Toggle grid: go to the least restrictive option possible.
1021 if (grid_snap_active) {
1022 switch (grid_visibility) {
1023 case GRID_VISIBILITY_SHOW:
1024 case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:
1025 grid_visibility = GRID_VISIBILITY_HIDE;
1026 break;
1027 case GRID_VISIBILITY_HIDE:
1028 grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;
1029 break;
1030 }
1031 } else {
1032 switch (grid_visibility) {
1033 case GRID_VISIBILITY_SHOW:
1034 grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING;
1035 break;
1036 case GRID_VISIBILITY_SHOW_WHEN_SNAPPING:
1037 case GRID_VISIBILITY_HIDE:
1038 grid_visibility = GRID_VISIBILITY_SHOW;
1039 break;
1040 }
1041 }
1042 viewport->queue_redraw();
1043}
1044
1045void CanvasItemEditor::_switch_theme_preview(int p_mode) {
1046 view_menu->get_popup()->hide();
1047
1048 if (theme_preview == p_mode) {
1049 return;
1050 }
1051 theme_preview = (ThemePreviewMode)p_mode;
1052 EditorSettings::get_singleton()->set_project_metadata("2d_editor", "theme_preview", theme_preview);
1053
1054 for (int i = 0; i < THEME_PREVIEW_MAX; i++) {
1055 theme_menu->set_item_checked(i, i == theme_preview);
1056 }
1057
1058 EditorNode::get_singleton()->update_preview_themes(theme_preview);
1059}
1060
1061bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) {
1062 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1063 Ref<InputEventMouseButton> b = p_event;
1064 Ref<InputEventMouseMotion> m = p_event;
1065
1066 if (drag_type == DRAG_NONE) {
1067 if (show_guides && show_rulers && EditorNode::get_singleton()->get_edited_scene()) {
1068 Transform2D xform = viewport_scrollable->get_transform() * transform;
1069 // Retrieve the guide lists
1070 Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());
1071 Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());
1072
1073 // Hover over guides
1074 real_t minimum = 1e20;
1075 is_hovering_h_guide = false;
1076 is_hovering_v_guide = false;
1077
1078 if (m.is_valid() && m->get_position().x < RULER_WIDTH) {
1079 // Check if we are hovering an existing horizontal guide
1080 for (int i = 0; i < hguides.size(); i++) {
1081 if (ABS(xform.xform(Point2(0, hguides[i])).y - m->get_position().y) < MIN(minimum, 8)) {
1082 is_hovering_h_guide = true;
1083 is_hovering_v_guide = false;
1084 break;
1085 }
1086 }
1087
1088 } else if (m.is_valid() && m->get_position().y < RULER_WIDTH) {
1089 // Check if we are hovering an existing vertical guide
1090 for (int i = 0; i < vguides.size(); i++) {
1091 if (ABS(xform.xform(Point2(vguides[i], 0)).x - m->get_position().x) < MIN(minimum, 8)) {
1092 is_hovering_v_guide = true;
1093 is_hovering_h_guide = false;
1094 break;
1095 }
1096 }
1097 }
1098
1099 // Start dragging a guide
1100 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
1101 // Press button
1102 if (b->get_position().x < RULER_WIDTH && b->get_position().y < RULER_WIDTH) {
1103 // Drag a new double guide
1104 drag_type = DRAG_DOUBLE_GUIDE;
1105 dragged_guide_index = -1;
1106 return true;
1107 } else if (b->get_position().x < RULER_WIDTH) {
1108 // Check if we drag an existing horizontal guide
1109 dragged_guide_index = -1;
1110 for (int i = 0; i < hguides.size(); i++) {
1111 if (ABS(xform.xform(Point2(0, hguides[i])).y - b->get_position().y) < MIN(minimum, 8)) {
1112 dragged_guide_index = i;
1113 }
1114 }
1115
1116 if (dragged_guide_index >= 0) {
1117 // Drag an existing horizontal guide
1118 drag_type = DRAG_H_GUIDE;
1119 } else {
1120 // Drag a new vertical guide
1121 drag_type = DRAG_V_GUIDE;
1122 }
1123 return true;
1124 } else if (b->get_position().y < RULER_WIDTH) {
1125 // Check if we drag an existing vertical guide
1126 dragged_guide_index = -1;
1127 for (int i = 0; i < vguides.size(); i++) {
1128 if (ABS(xform.xform(Point2(vguides[i], 0)).x - b->get_position().x) < MIN(minimum, 8)) {
1129 dragged_guide_index = i;
1130 }
1131 }
1132
1133 if (dragged_guide_index >= 0) {
1134 // Drag an existing vertical guide
1135 drag_type = DRAG_V_GUIDE;
1136 } else {
1137 // Drag a new vertical guide
1138 drag_type = DRAG_H_GUIDE;
1139 }
1140 drag_from = xform.affine_inverse().xform(b->get_position());
1141 return true;
1142 }
1143 }
1144 }
1145 }
1146
1147 if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE || drag_type == DRAG_H_GUIDE) {
1148 // Move the guide
1149 if (m.is_valid()) {
1150 Transform2D xform = viewport_scrollable->get_transform() * transform;
1151 drag_to = xform.affine_inverse().xform(m->get_position());
1152
1153 dragged_guide_pos = xform.xform(snap_point(drag_to, SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES));
1154 viewport->queue_redraw();
1155 return true;
1156 }
1157
1158 // Release confirms the guide move
1159 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {
1160 if (show_guides && EditorNode::get_singleton()->get_edited_scene()) {
1161 Transform2D xform = viewport_scrollable->get_transform() * transform;
1162
1163 // Retrieve the guide lists
1164 Array vguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_vertical_guides_", Array());
1165 Array hguides = EditorNode::get_singleton()->get_edited_scene()->get_meta("_edit_horizontal_guides_", Array());
1166
1167 Point2 edited = snap_point(xform.affine_inverse().xform(b->get_position()), SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES);
1168 if (drag_type == DRAG_V_GUIDE) {
1169 Array prev_vguides = vguides.duplicate();
1170 if (b->get_position().x > RULER_WIDTH) {
1171 // Adds a new vertical guide
1172 if (dragged_guide_index >= 0) {
1173 vguides[dragged_guide_index] = edited.x;
1174 undo_redo->create_action(TTR("Move Vertical Guide"));
1175 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);
1176 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);
1177 undo_redo->add_undo_method(viewport, "queue_redraw");
1178 undo_redo->commit_action();
1179 } else {
1180 vguides.push_back(edited.x);
1181 undo_redo->create_action(TTR("Create Vertical Guide"));
1182 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);
1183 if (prev_vguides.is_empty()) {
1184 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");
1185 } else {
1186 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);
1187 }
1188 undo_redo->add_undo_method(viewport, "queue_redraw");
1189 undo_redo->commit_action();
1190 }
1191 } else {
1192 if (dragged_guide_index >= 0) {
1193 vguides.remove_at(dragged_guide_index);
1194 undo_redo->create_action(TTR("Remove Vertical Guide"));
1195 if (vguides.is_empty()) {
1196 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");
1197 } else {
1198 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);
1199 }
1200 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);
1201 undo_redo->add_undo_method(viewport, "queue_redraw");
1202 undo_redo->commit_action();
1203 }
1204 }
1205 } else if (drag_type == DRAG_H_GUIDE) {
1206 Array prev_hguides = hguides.duplicate();
1207 if (b->get_position().y > RULER_WIDTH) {
1208 // Adds a new horizontal guide
1209 if (dragged_guide_index >= 0) {
1210 hguides[dragged_guide_index] = edited.y;
1211 undo_redo->create_action(TTR("Move Horizontal Guide"));
1212 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);
1213 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);
1214 undo_redo->add_undo_method(viewport, "queue_redraw");
1215 undo_redo->commit_action();
1216 } else {
1217 hguides.push_back(edited.y);
1218 undo_redo->create_action(TTR("Create Horizontal Guide"));
1219 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);
1220 if (prev_hguides.is_empty()) {
1221 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");
1222 } else {
1223 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);
1224 }
1225 undo_redo->add_undo_method(viewport, "queue_redraw");
1226 undo_redo->commit_action();
1227 }
1228 } else {
1229 if (dragged_guide_index >= 0) {
1230 hguides.remove_at(dragged_guide_index);
1231 undo_redo->create_action(TTR("Remove Horizontal Guide"));
1232 if (hguides.is_empty()) {
1233 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");
1234 } else {
1235 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);
1236 }
1237 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);
1238 undo_redo->add_undo_method(viewport, "queue_redraw");
1239 undo_redo->commit_action();
1240 }
1241 }
1242 } else if (drag_type == DRAG_DOUBLE_GUIDE) {
1243 Array prev_hguides = hguides.duplicate();
1244 Array prev_vguides = vguides.duplicate();
1245 if (b->get_position().x > RULER_WIDTH && b->get_position().y > RULER_WIDTH) {
1246 // Adds a new horizontal guide a new vertical guide
1247 vguides.push_back(edited.x);
1248 hguides.push_back(edited.y);
1249 undo_redo->create_action(TTR("Create Horizontal and Vertical Guides"));
1250 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", vguides);
1251 undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", hguides);
1252 if (prev_vguides.is_empty()) {
1253 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_");
1254 } else {
1255 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_vertical_guides_", prev_vguides);
1256 }
1257 if (prev_hguides.is_empty()) {
1258 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_");
1259 } else {
1260 undo_redo->add_undo_method(EditorNode::get_singleton()->get_edited_scene(), "set_meta", "_edit_horizontal_guides_", prev_hguides);
1261 }
1262 undo_redo->add_undo_method(viewport, "queue_redraw");
1263 undo_redo->commit_action();
1264 }
1265 }
1266 }
1267 snap_target[0] = SNAP_TARGET_NONE;
1268 snap_target[1] = SNAP_TARGET_NONE;
1269 _reset_drag();
1270 viewport->queue_redraw();
1271 return true;
1272 }
1273 }
1274 return false;
1275}
1276
1277bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) {
1278 panner->set_force_drag(tool == TOOL_PAN);
1279 bool panner_active = panner->gui_input(p_event, warped_panning ? viewport->get_global_rect() : Rect2());
1280 if (panner->is_panning() != pan_pressed) {
1281 pan_pressed = panner->is_panning();
1282 _update_cursor();
1283 }
1284
1285 if (panner_active) {
1286 return true;
1287 }
1288
1289 Ref<InputEventKey> k = p_event;
1290 if (k.is_valid()) {
1291 if (k->is_pressed()) {
1292 if (ED_GET_SHORTCUT("canvas_item_editor/zoom_3.125_percent")->matches_event(p_event)) {
1293 _shortcut_zoom_set(1.0 / 32.0);
1294 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_6.25_percent")->matches_event(p_event)) {
1295 _shortcut_zoom_set(1.0 / 16.0);
1296 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_12.5_percent")->matches_event(p_event)) {
1297 _shortcut_zoom_set(1.0 / 8.0);
1298 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_25_percent")->matches_event(p_event)) {
1299 _shortcut_zoom_set(1.0 / 4.0);
1300 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_50_percent")->matches_event(p_event)) {
1301 _shortcut_zoom_set(1.0 / 2.0);
1302 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent")->matches_event(p_event)) {
1303 _shortcut_zoom_set(1.0);
1304 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_200_percent")->matches_event(p_event)) {
1305 _shortcut_zoom_set(2.0);
1306 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_400_percent")->matches_event(p_event)) {
1307 _shortcut_zoom_set(4.0);
1308 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_800_percent")->matches_event(p_event)) {
1309 _shortcut_zoom_set(8.0);
1310 } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_1600_percent")->matches_event(p_event)) {
1311 _shortcut_zoom_set(16.0);
1312 }
1313 }
1314 }
1315
1316 return false;
1317}
1318
1319void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
1320 view_offset.x -= p_scroll_vec.x / zoom;
1321 view_offset.y -= p_scroll_vec.y / zoom;
1322 update_viewport();
1323}
1324
1325void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
1326 Ref<InputEventMouseButton> mb = p_event;
1327 if (mb.is_valid()) {
1328 // Special behavior for scroll events, as the zoom_by_increment method can smartly end up on powers of two.
1329 int increment = p_zoom_factor > 1.0 ? 1 : -1;
1330 bool by_integer = mb->is_alt_pressed();
1331
1332 if (EDITOR_GET("editors/2d/use_integer_zoom_by_default")) {
1333 by_integer = !by_integer;
1334 }
1335
1336 zoom_widget->set_zoom_by_increments(increment, by_integer);
1337 } else {
1338 zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);
1339 }
1340
1341 _zoom_on_position(zoom_widget->get_zoom(), p_origin);
1342}
1343
1344bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) {
1345 Ref<InputEventMouseMotion> m = p_event;
1346 Ref<InputEventMouseButton> b = p_event;
1347 Ref<InputEventKey> k = p_event;
1348
1349 // Drag the pivot (in pivot mode / with V key)
1350 if (drag_type == DRAG_NONE) {
1351 if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||
1352 (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && k->get_modifiers_mask().is_empty())) {
1353 List<CanvasItem *> selection = _get_edited_canvas_items();
1354
1355 // Filters the selection with nodes that allow setting the pivot
1356 drag_selection = List<CanvasItem *>();
1357 for (CanvasItem *ci : selection) {
1358 if (ci->_edit_use_pivot()) {
1359 drag_selection.push_back(ci);
1360 }
1361 }
1362
1363 // Start dragging if we still have nodes
1364 if (drag_selection.size() > 0) {
1365 _save_canvas_item_state(drag_selection);
1366 drag_from = transform.affine_inverse().xform((b.is_valid()) ? b->get_position() : viewport->get_local_mouse_position());
1367 Vector2 new_pos;
1368 if (drag_selection.size() == 1) {
1369 new_pos = snap_point(drag_from, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection[0]);
1370 } else {
1371 new_pos = snap_point(drag_from, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, nullptr, drag_selection);
1372 }
1373 for (CanvasItem *ci : drag_selection) {
1374 ci->_edit_set_pivot(ci->get_global_transform_with_canvas().affine_inverse().xform(new_pos));
1375 }
1376
1377 drag_type = DRAG_PIVOT;
1378 }
1379 return true;
1380 }
1381 }
1382
1383 if (drag_type == DRAG_PIVOT) {
1384 // Move the pivot
1385 if (m.is_valid()) {
1386 drag_to = transform.affine_inverse().xform(m->get_position());
1387 _restore_canvas_item_state(drag_selection);
1388 Vector2 new_pos;
1389 if (drag_selection.size() == 1) {
1390 new_pos = snap_point(drag_to, SNAP_NODE_SIDES | SNAP_NODE_CENTER | SNAP_NODE_ANCHORS | SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL, 0, drag_selection[0]);
1391 } else {
1392 new_pos = snap_point(drag_to, SNAP_OTHER_NODES | SNAP_GRID | SNAP_PIXEL);
1393 }
1394 for (CanvasItem *ci : drag_selection) {
1395 ci->_edit_set_pivot(ci->get_global_transform_with_canvas().affine_inverse().xform(new_pos));
1396 }
1397 return true;
1398 }
1399
1400 // Confirm the pivot move
1401 if (drag_selection.size() >= 1 &&
1402 ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) ||
1403 (k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V))) {
1404 _commit_canvas_item_state(
1405 drag_selection,
1406 vformat(
1407 TTR("Set CanvasItem \"%s\" Pivot Offset to (%d, %d)"),
1408 drag_selection[0]->get_name(),
1409 drag_selection[0]->_edit_get_pivot().x,
1410 drag_selection[0]->_edit_get_pivot().y));
1411 _reset_drag();
1412 return true;
1413 }
1414
1415 // Cancel a drag
1416 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
1417 _restore_canvas_item_state(drag_selection);
1418 _reset_drag();
1419 viewport->queue_redraw();
1420 return true;
1421 }
1422 }
1423 return false;
1424}
1425
1426bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) {
1427 Ref<InputEventMouseButton> b = p_event;
1428 Ref<InputEventMouseMotion> m = p_event;
1429
1430 // Start rotation
1431 if (drag_type == DRAG_NONE) {
1432 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
1433 if ((b->is_command_or_control_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) {
1434 List<CanvasItem *> selection = _get_edited_canvas_items();
1435
1436 // Remove not movable nodes
1437 for (CanvasItem *E : selection) {
1438 if (!_is_node_movable(E, true)) {
1439 selection.erase(E);
1440 }
1441 }
1442
1443 drag_selection = selection;
1444 if (drag_selection.size() > 0) {
1445 drag_type = DRAG_ROTATE;
1446 drag_from = transform.affine_inverse().xform(b->get_position());
1447 CanvasItem *ci = drag_selection[0];
1448 if (ci->_edit_use_pivot()) {
1449 drag_rotation_center = ci->get_global_transform_with_canvas().xform(ci->_edit_get_pivot());
1450 } else {
1451 drag_rotation_center = ci->get_global_transform_with_canvas().get_origin();
1452 }
1453 _save_canvas_item_state(drag_selection);
1454 return true;
1455 }
1456 }
1457 }
1458 }
1459
1460 if (drag_type == DRAG_ROTATE) {
1461 // Rotate the node
1462 if (m.is_valid()) {
1463 _restore_canvas_item_state(drag_selection);
1464 for (CanvasItem *ci : drag_selection) {
1465 drag_to = transform.affine_inverse().xform(m->get_position());
1466 //Rotate the opposite way if the canvas item's compounded scale has an uneven number of negative elements
1467 bool opposite = (ci->get_global_transform().get_scale().sign().dot(ci->get_transform().get_scale().sign()) == 0);
1468 ci->_edit_set_rotation(snap_angle(ci->_edit_get_rotation() + (opposite ? -1 : 1) * (drag_from - drag_rotation_center).angle_to(drag_to - drag_rotation_center), ci->_edit_get_rotation()));
1469 viewport->queue_redraw();
1470 }
1471 return true;
1472 }
1473
1474 // Confirms the node rotation
1475 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {
1476 if (drag_selection.size() != 1) {
1477 _commit_canvas_item_state(
1478 drag_selection,
1479 vformat(TTR("Rotate %d CanvasItems"), drag_selection.size()),
1480 true);
1481 } else {
1482 _commit_canvas_item_state(
1483 drag_selection,
1484 vformat(TTR("Rotate CanvasItem \"%s\" to %d degrees"),
1485 drag_selection[0]->get_name(),
1486 Math::rad_to_deg(drag_selection[0]->_edit_get_rotation())),
1487 true);
1488 }
1489
1490 if (key_auto_insert_button->is_pressed()) {
1491 _insert_animation_keys(false, true, false, true);
1492 }
1493
1494 _reset_drag();
1495 return true;
1496 }
1497
1498 // Cancel a drag
1499 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
1500 _restore_canvas_item_state(drag_selection);
1501 _reset_drag();
1502 viewport->queue_redraw();
1503 return true;
1504 }
1505 }
1506 return false;
1507}
1508
1509bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEvent> &p_event) {
1510 Ref<InputEventMouseButton> b = p_event;
1511
1512 // Open a sub-scene on double-click
1513 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) {
1514 List<CanvasItem *> selection = _get_edited_canvas_items();
1515 if (selection.size() == 1) {
1516 CanvasItem *ci = selection[0];
1517 if (!ci->get_scene_file_path().is_empty() && ci != EditorNode::get_singleton()->get_edited_scene()) {
1518 EditorNode::get_singleton()->open_request(ci->get_scene_file_path());
1519 return true;
1520 }
1521 }
1522 }
1523 return false;
1524}
1525
1526bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) {
1527 Ref<InputEventMouseButton> b = p_event;
1528 Ref<InputEventMouseMotion> m = p_event;
1529
1530 // Starts anchor dragging if needed
1531 if (drag_type == DRAG_NONE) {
1532 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {
1533 List<CanvasItem *> selection = _get_edited_canvas_items();
1534 if (selection.size() == 1) {
1535 Control *control = Object::cast_to<Control>(selection[0]);
1536 if (control && _is_node_movable(control)) {
1537 Vector2 anchor_pos[4];
1538 anchor_pos[0] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_TOP));
1539 anchor_pos[1] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_TOP));
1540 anchor_pos[2] = Vector2(control->get_anchor(SIDE_RIGHT), control->get_anchor(SIDE_BOTTOM));
1541 anchor_pos[3] = Vector2(control->get_anchor(SIDE_LEFT), control->get_anchor(SIDE_BOTTOM));
1542
1543 Rect2 anchor_rects[4];
1544 for (int i = 0; i < 4; i++) {
1545 anchor_pos[i] = (transform * control->get_global_transform_with_canvas()).xform(_anchor_to_position(control, anchor_pos[i]));
1546 anchor_rects[i] = Rect2(anchor_pos[i], anchor_handle->get_size());
1547 if (control->is_layout_rtl()) {
1548 anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 1 || i == 2), real_t(i <= 1));
1549 } else {
1550 anchor_rects[i].position -= anchor_handle->get_size() * Vector2(real_t(i == 0 || i == 3), real_t(i <= 1));
1551 }
1552 }
1553
1554 const DragType dragger[] = {
1555 DRAG_ANCHOR_TOP_LEFT,
1556 DRAG_ANCHOR_TOP_RIGHT,
1557 DRAG_ANCHOR_BOTTOM_RIGHT,
1558 DRAG_ANCHOR_BOTTOM_LEFT,
1559 };
1560
1561 for (int i = 0; i < 4; i++) {
1562 if (anchor_rects[i].has_point(b->get_position())) {
1563 if ((anchor_pos[0] == anchor_pos[2]) && (anchor_pos[0].distance_to(b->get_position()) < anchor_handle->get_size().length() / 3.0)) {
1564 drag_type = DRAG_ANCHOR_ALL;
1565 } else {
1566 drag_type = dragger[i];
1567 }
1568 drag_from = transform.affine_inverse().xform(b->get_position());
1569 drag_selection = List<CanvasItem *>();
1570 drag_selection.push_back(control);
1571 _save_canvas_item_state(drag_selection);
1572 return true;
1573 }
1574 }
1575 }
1576 }
1577 }
1578 }
1579
1580 if (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) {
1581 // Drag the anchor
1582 if (m.is_valid()) {
1583 _restore_canvas_item_state(drag_selection);
1584 Control *control = Object::cast_to<Control>(drag_selection[0]);
1585
1586 drag_to = transform.affine_inverse().xform(m->get_position());
1587
1588 Transform2D xform = control->get_global_transform_with_canvas().affine_inverse();
1589
1590 Point2 previous_anchor;
1591 previous_anchor.x = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_BOTTOM_LEFT) ? control->get_anchor(SIDE_LEFT) : control->get_anchor(SIDE_RIGHT);
1592 previous_anchor.y = (drag_type == DRAG_ANCHOR_TOP_LEFT || drag_type == DRAG_ANCHOR_TOP_RIGHT) ? control->get_anchor(SIDE_TOP) : control->get_anchor(SIDE_BOTTOM);
1593 previous_anchor = xform.affine_inverse().xform(_anchor_to_position(control, previous_anchor));
1594
1595 Vector2 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));
1596 new_anchor = _position_to_anchor(control, new_anchor).snapped(Vector2(0.001, 0.001));
1597
1598 bool use_single_axis = m->is_shift_pressed();
1599 Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from);
1600 bool use_y = Math::abs(drag_vector.y) > Math::abs(drag_vector.x);
1601
1602 switch (drag_type) {
1603 case DRAG_ANCHOR_TOP_LEFT:
1604 if (!use_single_axis || !use_y) {
1605 control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);
1606 }
1607 if (!use_single_axis || use_y) {
1608 control->set_anchor(SIDE_TOP, new_anchor.y, false, false);
1609 }
1610 break;
1611 case DRAG_ANCHOR_TOP_RIGHT:
1612 if (!use_single_axis || !use_y) {
1613 control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);
1614 }
1615 if (!use_single_axis || use_y) {
1616 control->set_anchor(SIDE_TOP, new_anchor.y, false, false);
1617 }
1618 break;
1619 case DRAG_ANCHOR_BOTTOM_RIGHT:
1620 if (!use_single_axis || !use_y) {
1621 control->set_anchor(SIDE_RIGHT, new_anchor.x, false, false);
1622 }
1623 if (!use_single_axis || use_y) {
1624 control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);
1625 }
1626 break;
1627 case DRAG_ANCHOR_BOTTOM_LEFT:
1628 if (!use_single_axis || !use_y) {
1629 control->set_anchor(SIDE_LEFT, new_anchor.x, false, false);
1630 }
1631 if (!use_single_axis || use_y) {
1632 control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, false);
1633 }
1634 break;
1635 case DRAG_ANCHOR_ALL:
1636 if (!use_single_axis || !use_y) {
1637 control->set_anchor(SIDE_LEFT, new_anchor.x, false, true);
1638 control->set_anchor(SIDE_RIGHT, new_anchor.x, false, true);
1639 }
1640 if (!use_single_axis || use_y) {
1641 control->set_anchor(SIDE_TOP, new_anchor.y, false, true);
1642 control->set_anchor(SIDE_BOTTOM, new_anchor.y, false, true);
1643 }
1644 break;
1645 default:
1646 break;
1647 }
1648 return true;
1649 }
1650
1651 // Confirms new anchor position
1652 if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {
1653 _commit_canvas_item_state(
1654 drag_selection,
1655 vformat(TTR("Move CanvasItem \"%s\" Anchor"), drag_selection[0]->get_name()));
1656 _reset_drag();
1657 return true;
1658 }
1659
1660 // Cancel a drag
1661 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
1662 _restore_canvas_item_state(drag_selection);
1663 _reset_drag();
1664 viewport->queue_redraw();
1665 return true;
1666 }
1667 }
1668 return false;
1669}
1670
1671bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) {
1672 Ref<InputEventMouseButton> b = p_event;
1673 Ref<InputEventMouseMotion> m = p_event;
1674
1675 // Drag resize handles
1676 if (drag_type == DRAG_NONE) {
1677 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) {
1678 List<CanvasItem *> selection = _get_edited_canvas_items();
1679 if (selection.size() == 1) {
1680 CanvasItem *ci = selection[0];
1681 if (ci->_edit_use_rect() && _is_node_movable(ci)) {
1682 Rect2 rect = ci->_edit_get_rect();
1683 Transform2D xform = transform * ci->get_global_transform_with_canvas();
1684
1685 const Vector2 endpoints[4] = {
1686 xform.xform(rect.position),
1687 xform.xform(rect.position + Vector2(rect.size.x, 0)),
1688 xform.xform(rect.position + rect.size),
1689 xform.xform(rect.position + Vector2(0, rect.size.y))
1690 };
1691
1692 const DragType dragger[] = {
1693 DRAG_TOP_LEFT,
1694 DRAG_TOP,
1695 DRAG_TOP_RIGHT,
1696 DRAG_RIGHT,
1697 DRAG_BOTTOM_RIGHT,
1698 DRAG_BOTTOM,
1699 DRAG_BOTTOM_LEFT,
1700 DRAG_LEFT
1701 };
1702
1703 DragType resize_drag = DRAG_NONE;
1704 real_t radius = (select_handle->get_size().width / 2) * 1.5;
1705
1706 for (int i = 0; i < 4; i++) {
1707 int prev = (i + 3) % 4;
1708 int next = (i + 1) % 4;
1709
1710 Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();
1711 ofs *= (select_handle->get_size().width / 2);
1712 ofs += endpoints[i];
1713 if (ofs.distance_to(b->get_position()) < radius) {
1714 resize_drag = dragger[i * 2];
1715 }
1716
1717 ofs = (endpoints[i] + endpoints[next]) / 2;
1718 ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);
1719 if (ofs.distance_to(b->get_position()) < radius) {
1720 resize_drag = dragger[i * 2 + 1];
1721 }
1722 }
1723
1724 if (resize_drag != DRAG_NONE) {
1725 drag_type = resize_drag;
1726 drag_from = transform.affine_inverse().xform(b->get_position());
1727 drag_selection = List<CanvasItem *>();
1728 drag_selection.push_back(ci);
1729 _save_canvas_item_state(drag_selection);
1730 return true;
1731 }
1732 }
1733 }
1734 }
1735 }
1736
1737 if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||
1738 drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {
1739 // Resize the node
1740 if (m.is_valid()) {
1741 CanvasItem *ci = drag_selection[0];
1742 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
1743 //Reset state
1744 ci->_edit_set_state(se->undo_state);
1745
1746 bool uniform = m->is_shift_pressed();
1747 bool symmetric = m->is_alt_pressed();
1748
1749 Rect2 local_rect = ci->_edit_get_rect();
1750 real_t aspect = local_rect.get_size().y / local_rect.get_size().x;
1751 Point2 current_begin = local_rect.get_position();
1752 Point2 current_end = local_rect.get_position() + local_rect.get_size();
1753 Point2 max_begin = (symmetric) ? (current_begin + current_end - ci->_edit_get_minimum_size()) / 2.0 : current_end - ci->_edit_get_minimum_size();
1754 Point2 min_end = (symmetric) ? (current_begin + current_end + ci->_edit_get_minimum_size()) / 2.0 : current_begin + ci->_edit_get_minimum_size();
1755 Point2 center = (current_begin + current_end) / 2.0;
1756
1757 drag_to = transform.affine_inverse().xform(m->get_position());
1758
1759 Transform2D xform = ci->get_global_transform_with_canvas();
1760
1761 Point2 drag_to_snapped_begin;
1762 Point2 drag_to_snapped_end;
1763
1764 drag_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);
1765 drag_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);
1766
1767 Point2 drag_begin = xform.affine_inverse().xform(drag_to_snapped_begin);
1768 Point2 drag_end = xform.affine_inverse().xform(drag_to_snapped_end);
1769
1770 // Horizontal resize
1771 if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {
1772 current_begin.x = MIN(drag_begin.x, max_begin.x);
1773 } else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {
1774 current_end.x = MAX(drag_end.x, min_end.x);
1775 }
1776
1777 // Vertical resize
1778 if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {
1779 current_begin.y = MIN(drag_begin.y, max_begin.y);
1780 } else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {
1781 current_end.y = MAX(drag_end.y, min_end.y);
1782 }
1783
1784 // Uniform resize
1785 if (uniform) {
1786 if (drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT) {
1787 current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);
1788 } else if (drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM) {
1789 current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;
1790 } else {
1791 if (aspect >= 1.0) {
1792 if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {
1793 current_begin.y = current_end.y - aspect * (current_end.x - current_begin.x);
1794 } else {
1795 current_end.y = current_begin.y + aspect * (current_end.x - current_begin.x);
1796 }
1797 } else {
1798 if (drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {
1799 current_begin.x = current_end.x - (current_end.y - current_begin.y) / aspect;
1800 } else {
1801 current_end.x = current_begin.x + (current_end.y - current_begin.y) / aspect;
1802 }
1803 }
1804 }
1805 }
1806
1807 // Symmetric resize
1808 if (symmetric) {
1809 if (drag_type == DRAG_LEFT || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_BOTTOM_LEFT) {
1810 current_end.x = 2.0 * center.x - current_begin.x;
1811 } else if (drag_type == DRAG_RIGHT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_RIGHT) {
1812 current_begin.x = 2.0 * center.x - current_end.x;
1813 }
1814 if (drag_type == DRAG_TOP || drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT) {
1815 current_end.y = 2.0 * center.y - current_begin.y;
1816 } else if (drag_type == DRAG_BOTTOM || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT) {
1817 current_begin.y = 2.0 * center.y - current_end.y;
1818 }
1819 }
1820 ci->_edit_set_rect(Rect2(current_begin, current_end - current_begin));
1821 return true;
1822 }
1823
1824 // Confirm resize
1825 if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {
1826 const Node2D *node2d = Object::cast_to<Node2D>(drag_selection[0]);
1827 if (node2d) {
1828 // Extends from Node2D.
1829 // Node2D doesn't have an actual stored rect size, unlike Controls.
1830 _commit_canvas_item_state(
1831 drag_selection,
1832 vformat(
1833 TTR("Scale Node2D \"%s\" to (%s, %s)"),
1834 drag_selection[0]->get_name(),
1835 Math::snapped(drag_selection[0]->_edit_get_scale().x, 0.01),
1836 Math::snapped(drag_selection[0]->_edit_get_scale().y, 0.01)),
1837 true);
1838 } else {
1839 // Extends from Control.
1840 _commit_canvas_item_state(
1841 drag_selection,
1842 vformat(
1843 TTR("Resize Control \"%s\" to (%d, %d)"),
1844 drag_selection[0]->get_name(),
1845 drag_selection[0]->_edit_get_rect().size.x,
1846 drag_selection[0]->_edit_get_rect().size.y),
1847 true);
1848 }
1849
1850 if (key_auto_insert_button->is_pressed()) {
1851 _insert_animation_keys(false, false, true, true);
1852 }
1853
1854 snap_target[0] = SNAP_TARGET_NONE;
1855 snap_target[1] = SNAP_TARGET_NONE;
1856 _reset_drag();
1857 viewport->queue_redraw();
1858 return true;
1859 }
1860
1861 // Cancel a drag
1862 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
1863 _restore_canvas_item_state(drag_selection);
1864 snap_target[0] = SNAP_TARGET_NONE;
1865 snap_target[1] = SNAP_TARGET_NONE;
1866 _reset_drag();
1867 viewport->queue_redraw();
1868 return true;
1869 }
1870 }
1871 return false;
1872}
1873
1874bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
1875 Ref<InputEventMouseButton> b = p_event;
1876 Ref<InputEventMouseMotion> m = p_event;
1877
1878 // Drag resize handles
1879 if (drag_type == DRAG_NONE) {
1880 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {
1881 List<CanvasItem *> selection = _get_edited_canvas_items();
1882 if (selection.size() == 1) {
1883 CanvasItem *ci = selection[0];
1884
1885 if (_is_node_movable(ci)) {
1886 Transform2D xform = transform * ci->get_global_transform_with_canvas();
1887 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
1888 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
1889
1890 drag_type = DRAG_SCALE_BOTH;
1891
1892 if (show_transformation_gizmos) {
1893 Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
1894 Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
1895 if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
1896 drag_type = DRAG_SCALE_X;
1897 }
1898 Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
1899 if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
1900 drag_type = DRAG_SCALE_Y;
1901 }
1902 }
1903
1904 drag_from = transform.affine_inverse().xform(b->get_position());
1905 drag_selection = List<CanvasItem *>();
1906 drag_selection.push_back(ci);
1907 _save_canvas_item_state(drag_selection);
1908 return true;
1909 }
1910 }
1911 }
1912 }
1913
1914 if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {
1915 // Resize the node
1916 if (m.is_valid()) {
1917 _restore_canvas_item_state(drag_selection);
1918 CanvasItem *ci = drag_selection[0];
1919
1920 drag_to = transform.affine_inverse().xform(m->get_position());
1921
1922 Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse();
1923 Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized();
1924 Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
1925
1926 bool uniform = m->is_shift_pressed();
1927 bool is_ctrl = m->is_ctrl_pressed();
1928
1929 Point2 drag_from_local = simple_xform.xform(drag_from);
1930 Point2 drag_to_local = simple_xform.xform(drag_to);
1931 Point2 offset = drag_to_local - drag_from_local;
1932
1933 Size2 scale = ci->_edit_get_scale();
1934 Size2 original_scale = scale;
1935 real_t ratio = scale.y / scale.x;
1936 if (drag_type == DRAG_SCALE_BOTH) {
1937 Size2 scale_factor = drag_to_local / drag_from_local;
1938 if (uniform) {
1939 scale *= (scale_factor.x + scale_factor.y) / 2.0;
1940 } else {
1941 scale *= scale_factor;
1942 }
1943 } else {
1944 Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE;
1945 Size2 parent_scale = parent_xform.get_scale();
1946 scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y);
1947
1948 if (drag_type == DRAG_SCALE_X) {
1949 scale.x += scale_factor.x;
1950 if (uniform) {
1951 scale.y = scale.x * ratio;
1952 }
1953 } else if (drag_type == DRAG_SCALE_Y) {
1954 scale.y -= scale_factor.y;
1955 if (uniform) {
1956 scale.x = scale.y / ratio;
1957 }
1958 }
1959 }
1960
1961 if (snap_scale && !is_ctrl) {
1962 if (snap_relative) {
1963 scale.x = original_scale.x * (roundf((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step);
1964 scale.y = original_scale.y * (roundf((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step);
1965 } else {
1966 scale.x = roundf(scale.x / snap_scale_step) * snap_scale_step;
1967 scale.y = roundf(scale.y / snap_scale_step) * snap_scale_step;
1968 }
1969 }
1970
1971 ci->_edit_set_scale(scale);
1972 return true;
1973 }
1974
1975 // Confirm resize
1976 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) {
1977 if (drag_selection.size() != 1) {
1978 _commit_canvas_item_state(
1979 drag_selection,
1980 vformat(TTR("Scale %d CanvasItems"), drag_selection.size()),
1981 true);
1982 } else {
1983 _commit_canvas_item_state(
1984 drag_selection,
1985 vformat(TTR("Scale CanvasItem \"%s\" to (%s, %s)"),
1986 drag_selection[0]->get_name(),
1987 Math::snapped(drag_selection[0]->_edit_get_scale().x, 0.01),
1988 Math::snapped(drag_selection[0]->_edit_get_scale().y, 0.01)),
1989 true);
1990 }
1991 if (key_auto_insert_button->is_pressed()) {
1992 _insert_animation_keys(false, false, true, true);
1993 }
1994
1995 _reset_drag();
1996 viewport->queue_redraw();
1997 return true;
1998 }
1999
2000 // Cancel a drag
2001 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
2002 _restore_canvas_item_state(drag_selection);
2003 _reset_drag();
2004 viewport->queue_redraw();
2005 return true;
2006 }
2007 }
2008 return false;
2009}
2010
2011bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
2012 Ref<InputEventMouseButton> b = p_event;
2013 Ref<InputEventMouseMotion> m = p_event;
2014 Ref<InputEventKey> k = p_event;
2015
2016 if (drag_type == DRAG_NONE) {
2017 //Start moving the nodes
2018 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
2019 if ((b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {
2020 List<CanvasItem *> selection = _get_edited_canvas_items();
2021
2022 drag_selection.clear();
2023 for (int i = 0; i < selection.size(); i++) {
2024 if (_is_node_movable(selection[i], true)) {
2025 drag_selection.push_back(selection[i]);
2026 }
2027 }
2028
2029 if (selection.size() > 0) {
2030 drag_type = DRAG_MOVE;
2031
2032 CanvasItem *ci = selection[0];
2033 Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse();
2034 Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized();
2035 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
2036
2037 if (show_transformation_gizmos) {
2038 Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);
2039 Rect2 x_handle_rect = Rect2(move_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
2040 if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
2041 drag_type = DRAG_MOVE_X;
2042 }
2043 Rect2 y_handle_rect = Rect2(-5 * EDSCALE, move_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
2044 if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
2045 drag_type = DRAG_MOVE_Y;
2046 }
2047 }
2048
2049 drag_from = transform.affine_inverse().xform(b->get_position());
2050 _save_canvas_item_state(drag_selection);
2051 }
2052 return true;
2053 }
2054 }
2055 }
2056
2057 if (drag_type == DRAG_MOVE || drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {
2058 // Move the nodes
2059 if (m.is_valid() && !drag_selection.is_empty()) {
2060 _restore_canvas_item_state(drag_selection, true);
2061
2062 drag_to = transform.affine_inverse().xform(m->get_position());
2063 Point2 previous_pos;
2064 if (drag_selection.size() == 1) {
2065 Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse();
2066 previous_pos = xform.xform(drag_selection[0]->_edit_get_position());
2067 } else {
2068 previous_pos = _get_encompassing_rect_from_list(drag_selection).position;
2069 }
2070
2071 Point2 drag_delta = drag_to - drag_from;
2072 if (drag_selection.size() == 1 && (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y)) {
2073 const CanvasItem *selected = drag_selection.front()->get();
2074 drag_delta = selected->get_transform().affine_inverse().basis_xform(drag_delta);
2075
2076 if (drag_type == DRAG_MOVE_X) {
2077 drag_delta.y = 0;
2078 } else {
2079 drag_delta.x = 0;
2080 }
2081 drag_delta = selected->get_transform().basis_xform(drag_delta);
2082 }
2083 Point2 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);
2084
2085 bool single_axis = m->is_shift_pressed();
2086 if (single_axis) {
2087 if (ABS(new_pos.x - previous_pos.x) > ABS(new_pos.y - previous_pos.y)) {
2088 new_pos.y = previous_pos.y;
2089 } else {
2090 new_pos.x = previous_pos.x;
2091 }
2092 }
2093
2094 for (CanvasItem *ci : drag_selection) {
2095 Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();
2096 ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
2097 }
2098 return true;
2099 }
2100
2101 // Confirm the move (only if it was moved)
2102 if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {
2103 if (transform.affine_inverse().xform(b->get_position()) != drag_from) {
2104 if (drag_selection.size() != 1) {
2105 _commit_canvas_item_state(
2106 drag_selection,
2107 vformat(TTR("Move %d CanvasItems"), drag_selection.size()),
2108 true);
2109 } else {
2110 _commit_canvas_item_state(
2111 drag_selection,
2112 vformat(
2113 TTR("Move CanvasItem \"%s\" to (%d, %d)"),
2114 drag_selection[0]->get_name(),
2115 drag_selection[0]->_edit_get_position().x,
2116 drag_selection[0]->_edit_get_position().y),
2117 true);
2118 }
2119 }
2120
2121 if (key_auto_insert_button->is_pressed()) {
2122 _insert_animation_keys(true, false, false, true);
2123 }
2124
2125 //Make sure smart snapping lines disappear.
2126 snap_target[0] = SNAP_TARGET_NONE;
2127 snap_target[1] = SNAP_TARGET_NONE;
2128
2129 _reset_drag();
2130 viewport->queue_redraw();
2131 return true;
2132 }
2133
2134 // Cancel a drag
2135 if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) {
2136 _restore_canvas_item_state(drag_selection, true);
2137 snap_target[0] = SNAP_TARGET_NONE;
2138 snap_target[1] = SNAP_TARGET_NONE;
2139 _reset_drag();
2140 viewport->queue_redraw();
2141 return true;
2142 }
2143 }
2144
2145 // Move the canvas items with the arrow keys
2146 if (k.is_valid() && k->is_pressed() && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&
2147 (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {
2148 if (!k->is_echo()) {
2149 // Start moving the canvas items with the keyboard, if they are movable
2150 List<CanvasItem *> selection = _get_edited_canvas_items();
2151
2152 drag_selection.clear();
2153 for (CanvasItem *item : selection) {
2154 if (_is_node_movable(item, true)) {
2155 drag_selection.push_back(item);
2156 }
2157 }
2158
2159 drag_type = DRAG_KEY_MOVE;
2160 drag_from = Vector2();
2161 drag_to = Vector2();
2162 _save_canvas_item_state(drag_selection, true);
2163 }
2164
2165 if (drag_selection.size() > 0) {
2166 _restore_canvas_item_state(drag_selection, true);
2167
2168 bool move_local_base = k->is_alt_pressed();
2169 bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed();
2170
2171 Vector2 dir;
2172 if (k->get_keycode() == Key::UP) {
2173 dir += Vector2(0, -1);
2174 } else if (k->get_keycode() == Key::DOWN) {
2175 dir += Vector2(0, 1);
2176 } else if (k->get_keycode() == Key::LEFT) {
2177 dir += Vector2(-1, 0);
2178 } else if (k->get_keycode() == Key::RIGHT) {
2179 dir += Vector2(1, 0);
2180 }
2181 if (k->is_shift_pressed()) {
2182 dir *= grid_step * Math::pow(2.0, grid_step_multiplier);
2183 }
2184
2185 drag_to += dir;
2186 if (k->is_shift_pressed()) {
2187 drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier));
2188 }
2189
2190 Point2 previous_pos;
2191 if (drag_selection.size() == 1) {
2192 Transform2D xform = drag_selection[0]->get_global_transform_with_canvas() * drag_selection[0]->get_transform().affine_inverse();
2193 previous_pos = xform.xform(drag_selection[0]->_edit_get_position());
2194 } else {
2195 previous_pos = _get_encompassing_rect_from_list(drag_selection).position;
2196 }
2197
2198 Point2 new_pos;
2199 if (drag_selection.size() == 1) {
2200 Node2D *node_2d = Object::cast_to<Node2D>(drag_selection[0]);
2201 if (node_2d && move_local_base_rotated) {
2202 Transform2D m2;
2203 m2.rotate(node_2d->get_rotation());
2204 new_pos += m2.xform(drag_to);
2205 } else if (move_local_base) {
2206 new_pos += drag_to;
2207 } else {
2208 new_pos = previous_pos + (drag_to - drag_from);
2209 }
2210 } else {
2211 new_pos = previous_pos + (drag_to - drag_from);
2212 }
2213
2214 for (CanvasItem *ci : drag_selection) {
2215 Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform();
2216 ci->_edit_set_position(ci->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos));
2217 }
2218 }
2219 return true;
2220 }
2221
2222 if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && (tool == TOOL_SELECT || tool == TOOL_MOVE) &&
2223 (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) {
2224 // Confirm canvas items move by arrow keys
2225 if ((!Input::get_singleton()->is_key_pressed(Key::UP)) &&
2226 (!Input::get_singleton()->is_key_pressed(Key::DOWN)) &&
2227 (!Input::get_singleton()->is_key_pressed(Key::LEFT)) &&
2228 (!Input::get_singleton()->is_key_pressed(Key::RIGHT))) {
2229 if (drag_selection.size() > 1) {
2230 _commit_canvas_item_state(
2231 drag_selection,
2232 vformat(TTR("Move %d CanvasItems"), drag_selection.size()),
2233 true);
2234 } else if (drag_selection.size() == 1) {
2235 _commit_canvas_item_state(
2236 drag_selection,
2237 vformat(TTR("Move CanvasItem \"%s\" to (%d, %d)"),
2238 drag_selection[0]->get_name(),
2239 drag_selection[0]->_edit_get_position().x,
2240 drag_selection[0]->_edit_get_position().y),
2241 true);
2242 }
2243 _reset_drag();
2244 }
2245 viewport->queue_redraw();
2246 return true;
2247 }
2248
2249 return (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 case
2250}
2251
2252bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
2253 Ref<InputEventMouseButton> b = p_event;
2254 Ref<InputEventMouseMotion> m = p_event;
2255 Ref<InputEventKey> k = p_event;
2256
2257 if (drag_type == DRAG_NONE) {
2258 if (b.is_valid() && b->is_pressed() &&
2259 ((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) ||
2260 (b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) {
2261 // Popup the selection menu list
2262 Point2 click = transform.affine_inverse().xform(b->get_position());
2263
2264 _get_canvas_items_at_pos(click, selection_results, b->is_alt_pressed() && tool == TOOL_SELECT);
2265
2266 if (selection_results.size() == 1) {
2267 CanvasItem *item = selection_results[0].item;
2268 selection_results.clear();
2269
2270 _select_click_on_item(item, click, b->is_shift_pressed());
2271
2272 return true;
2273 } else if (!selection_results.is_empty()) {
2274 // Sorts items according the their z-index
2275 selection_results.sort();
2276
2277 NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
2278 StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
2279
2280 for (int i = 0; i < selection_results.size(); i++) {
2281 CanvasItem *item = selection_results[i].item;
2282
2283 Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(item, "Node");
2284 String node_path = "/" + root_name + "/" + root_path.rel_path_to(item->get_path());
2285
2286 int locked = 0;
2287 if (_is_node_locked(item)) {
2288 locked = 1;
2289 } else {
2290 Node *scene = EditorNode::get_singleton()->get_edited_scene();
2291 Node *node = item;
2292
2293 while (node && node != scene->get_parent()) {
2294 CanvasItem *ci_tmp = Object::cast_to<CanvasItem>(node);
2295 if (ci_tmp && node->has_meta("_edit_group_")) {
2296 locked = 2;
2297 }
2298 node = node->get_parent();
2299 }
2300 }
2301
2302 String suffix;
2303 if (locked == 1) {
2304 suffix = " (" + TTR("Locked") + ")";
2305 } else if (locked == 2) {
2306 suffix = " (" + TTR("Grouped") + ")";
2307 }
2308 selection_menu->add_item((String)item->get_name() + suffix);
2309 selection_menu->set_item_icon(i, icon);
2310 selection_menu->set_item_metadata(i, node_path);
2311 selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);
2312 }
2313
2314 selection_results_menu = selection_results;
2315 selection_menu_additive_selection = b->is_shift_pressed();
2316 selection_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));
2317 selection_menu->reset_size();
2318 selection_menu->popup();
2319 return true;
2320 }
2321 }
2322
2323 if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
2324 add_node_menu->clear();
2325 add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Add")), TTR("Add Node Here..."), ADD_NODE);
2326 add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("Instance")), TTR("Instantiate Scene Here..."), ADD_INSTANCE);
2327 for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) {
2328 if (Object::cast_to<CanvasItem>(node)) {
2329 add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ActionPaste")), TTR("Paste Node(s) Here"), ADD_PASTE);
2330 break;
2331 }
2332 }
2333 for (Node *node : EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list()) {
2334 if (Object::cast_to<CanvasItem>(node)) {
2335 add_node_menu->add_icon_item(get_editor_theme_icon(SNAME("ToolMove")), TTR("Move Node(s) Here"), ADD_MOVE);
2336 break;
2337 }
2338 }
2339
2340 add_node_menu->reset_size();
2341 add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position()));
2342 add_node_menu->popup();
2343 node_create_position = transform.affine_inverse().xform(b->get_position());
2344 return true;
2345 }
2346
2347 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT && !panner->is_panning()) {
2348 // Single item selection
2349 Point2 click = transform.affine_inverse().xform(b->get_position());
2350
2351 Node *scene = EditorNode::get_singleton()->get_edited_scene();
2352 if (!scene) {
2353 return true;
2354 }
2355
2356 // Find the item to select
2357 CanvasItem *ci = nullptr;
2358
2359 Vector<_SelectResult> selection = Vector<_SelectResult>();
2360 // Retrieve the canvas items
2361 _get_canvas_items_at_pos(click, selection);
2362 if (!selection.is_empty()) {
2363 ci = selection[0].item;
2364 }
2365
2366 if (!ci) {
2367 // Start a box selection
2368 if (!b->is_shift_pressed()) {
2369 // Clear the selection if not additive
2370 editor_selection->clear();
2371 viewport->queue_redraw();
2372 selected_from_canvas = true;
2373 };
2374
2375 drag_from = click;
2376 drag_type = DRAG_BOX_SELECTION;
2377 box_selecting_to = drag_from;
2378 return true;
2379 } else {
2380 bool still_selected = _select_click_on_item(ci, click, b->is_shift_pressed());
2381 // Start dragging
2382 if (still_selected) {
2383 // Drag the node(s) if requested
2384 drag_start_origin = click;
2385 drag_type = DRAG_QUEUED;
2386 }
2387 // Select the item
2388 return true;
2389 }
2390 }
2391 }
2392
2393 if (drag_type == DRAG_QUEUED) {
2394 if (b.is_valid() && !b->is_pressed()) {
2395 _reset_drag();
2396 return true;
2397 }
2398 if (m.is_valid()) {
2399 Point2 click = transform.affine_inverse().xform(m->get_position());
2400 bool movement_threshold_passed = drag_start_origin.distance_to(click) > (8 * MAX(1, EDSCALE)) / zoom;
2401 if (m.is_valid() && movement_threshold_passed) {
2402 List<CanvasItem *> selection2 = _get_edited_canvas_items();
2403
2404 drag_selection.clear();
2405 for (int i = 0; i < selection2.size(); i++) {
2406 if (_is_node_movable(selection2[i], true)) {
2407 drag_selection.push_back(selection2[i]);
2408 }
2409 }
2410
2411 if (selection2.size() > 0) {
2412 drag_type = DRAG_MOVE;
2413 drag_from = drag_start_origin;
2414 _save_canvas_item_state(drag_selection);
2415 }
2416 return true;
2417 }
2418 }
2419 }
2420
2421 if (drag_type == DRAG_BOX_SELECTION) {
2422 if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) {
2423 // Confirms box selection.
2424 Node *scene = EditorNode::get_singleton()->get_edited_scene();
2425 if (scene) {
2426 List<CanvasItem *> selitems;
2427
2428 Point2 bsfrom = drag_from;
2429 Point2 bsto = box_selecting_to;
2430 if (bsfrom.x > bsto.x) {
2431 SWAP(bsfrom.x, bsto.x);
2432 }
2433 if (bsfrom.y > bsto.y) {
2434 SWAP(bsfrom.y, bsto.y);
2435 }
2436
2437 _find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems);
2438 if (selitems.size() == 1 && editor_selection->get_selected_node_list().is_empty()) {
2439 EditorNode::get_singleton()->push_item(selitems[0]);
2440 }
2441 for (CanvasItem *E : selitems) {
2442 editor_selection->add_node(E);
2443 }
2444 }
2445
2446 _reset_drag();
2447 viewport->queue_redraw();
2448 return true;
2449 }
2450
2451 if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) {
2452 // Cancel box selection.
2453 _reset_drag();
2454 viewport->queue_redraw();
2455 return true;
2456 }
2457
2458 if (m.is_valid()) {
2459 // Update box selection.
2460 box_selecting_to = transform.affine_inverse().xform(m->get_position());
2461 viewport->queue_redraw();
2462 return true;
2463 }
2464 }
2465
2466 if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true) && drag_type == DRAG_NONE && tool == TOOL_SELECT) {
2467 // Unselect everything
2468 editor_selection->clear();
2469 viewport->queue_redraw();
2470 }
2471 return false;
2472}
2473
2474bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) {
2475 if (tool != TOOL_RULER) {
2476 ruler_tool_active = false;
2477 return false;
2478 }
2479
2480 Ref<InputEventMouseButton> b = p_event;
2481 Ref<InputEventMouseMotion> m = p_event;
2482
2483 Point2 previous_origin = ruler_tool_origin;
2484 if (!ruler_tool_active) {
2485 ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset);
2486 }
2487
2488 if (b.is_valid() && b->get_button_index() == MouseButton::LEFT) {
2489 if (b->is_pressed()) {
2490 ruler_tool_active = true;
2491 } else {
2492 ruler_tool_active = false;
2493 }
2494
2495 viewport->queue_redraw();
2496 return true;
2497 }
2498
2499 if (m.is_valid() && (ruler_tool_active || (grid_snap_active && previous_origin != ruler_tool_origin))) {
2500 viewport->queue_redraw();
2501 return true;
2502 }
2503
2504 return false;
2505}
2506
2507bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {
2508 Ref<InputEventMouseMotion> m = p_event;
2509 if (m.is_valid()) {
2510 Point2 click = transform.affine_inverse().xform(m->get_position());
2511
2512 // Checks if the hovered items changed, redraw the viewport if so
2513 Vector<_SelectResult> hovering_results_items;
2514 _get_canvas_items_at_pos(click, hovering_results_items);
2515 hovering_results_items.sort();
2516
2517 // Compute the nodes names and icon position
2518 Vector<_HoverResult> hovering_results_tmp;
2519 for (int i = 0; i < hovering_results_items.size(); i++) {
2520 CanvasItem *ci = hovering_results_items[i].item;
2521
2522 if (ci->_edit_use_rect()) {
2523 continue;
2524 }
2525
2526 _HoverResult hover_result;
2527 hover_result.position = ci->get_global_transform_with_canvas().get_origin();
2528 hover_result.icon = EditorNode::get_singleton()->get_object_icon(ci);
2529 hover_result.name = ci->get_name();
2530
2531 hovering_results_tmp.push_back(hover_result);
2532 }
2533
2534 // Check if changed, if so, redraw.
2535 bool changed = false;
2536 if (hovering_results_tmp.size() == hovering_results.size()) {
2537 for (int i = 0; i < hovering_results_tmp.size(); i++) {
2538 _HoverResult a = hovering_results_tmp[i];
2539 _HoverResult b = hovering_results[i];
2540 if (a.icon != b.icon || a.name != b.name || a.position != b.position) {
2541 changed = true;
2542 break;
2543 }
2544 }
2545 } else {
2546 changed = true;
2547 }
2548
2549 if (changed) {
2550 hovering_results = hovering_results_tmp;
2551 viewport->queue_redraw();
2552 }
2553
2554 return true;
2555 }
2556
2557 return false;
2558}
2559
2560void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
2561 bool accepted = false;
2562
2563 Ref<InputEventMouseButton> mb = p_event;
2564 bool 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.
2565
2566 if (EDITOR_GET("editors/panning/simple_panning") || !pan_pressed || release_lmb) {
2567 accepted = true;
2568 if (_gui_input_rulers_and_guides(p_event)) {
2569 // print_line("Rulers and guides");
2570 } else if (EditorNode::get_singleton()->get_editor_plugins_over()->forward_gui_input(p_event)) {
2571 // print_line("Plugin");
2572 } else if (_gui_input_open_scene_on_double_click(p_event)) {
2573 // print_line("Open scene on double click");
2574 } else if (_gui_input_scale(p_event)) {
2575 // print_line("Set scale");
2576 } else if (_gui_input_pivot(p_event)) {
2577 // print_line("Set pivot");
2578 } else if (_gui_input_resize(p_event)) {
2579 // print_line("Resize");
2580 } else if (_gui_input_rotate(p_event)) {
2581 // print_line("Rotate");
2582 } else if (_gui_input_move(p_event)) {
2583 // print_line("Move");
2584 } else if (_gui_input_anchors(p_event)) {
2585 // print_line("Anchors");
2586 } else if (_gui_input_select(p_event)) {
2587 // print_line("Selection");
2588 } else if (_gui_input_ruler_tool(p_event)) {
2589 // print_line("Measure");
2590 } else {
2591 // print_line("Not accepted");
2592 accepted = false;
2593 }
2594 }
2595
2596 accepted = (_gui_input_zoom_or_pan(p_event, accepted) || accepted);
2597
2598 if (accepted) {
2599 accept_event();
2600 }
2601
2602 // Handles the mouse hovering
2603 _gui_input_hover(p_event);
2604
2605 if (mb.is_valid()) {
2606 // Update the default cursor.
2607 _update_cursor();
2608 }
2609
2610 // Grab focus
2611 if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
2612 viewport->call_deferred(SNAME("grab_focus"));
2613 }
2614}
2615
2616void CanvasItemEditor::_update_cursor() {
2617 // Choose the correct default cursor.
2618 CursorShape c = CURSOR_ARROW;
2619 switch (tool) {
2620 case TOOL_MOVE:
2621 c = CURSOR_MOVE;
2622 break;
2623 case TOOL_EDIT_PIVOT:
2624 c = CURSOR_CROSS;
2625 break;
2626 case TOOL_PAN:
2627 c = CURSOR_DRAG;
2628 break;
2629 case TOOL_RULER:
2630 c = CURSOR_CROSS;
2631 break;
2632 default:
2633 break;
2634 }
2635 if (pan_pressed) {
2636 c = CURSOR_DRAG;
2637 }
2638 set_default_cursor_shape(c);
2639}
2640
2641Control::CursorShape CanvasItemEditor::get_cursor_shape(const Point2 &p_pos) const {
2642 // Compute an eventual rotation of the cursor
2643 const CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE };
2644 int rotation_array_index = 0;
2645
2646 List<CanvasItem *> selection = _get_edited_canvas_items();
2647 if (selection.size() == 1) {
2648 const double angle = Math::fposmod((double)selection[0]->get_global_transform_with_canvas().get_rotation(), Math_PI);
2649 if (angle > Math_PI * 7.0 / 8.0) {
2650 rotation_array_index = 0;
2651 } else if (angle > Math_PI * 5.0 / 8.0) {
2652 rotation_array_index = 1;
2653 } else if (angle > Math_PI * 3.0 / 8.0) {
2654 rotation_array_index = 2;
2655 } else if (angle > Math_PI * 1.0 / 8.0) {
2656 rotation_array_index = 3;
2657 } else {
2658 rotation_array_index = 0;
2659 }
2660 }
2661
2662 // Choose the correct cursor
2663 CursorShape c = get_default_cursor_shape();
2664 switch (drag_type) {
2665 case DRAG_LEFT:
2666 case DRAG_RIGHT:
2667 c = rotation_array[rotation_array_index];
2668 break;
2669 case DRAG_V_GUIDE:
2670 c = CURSOR_HSIZE;
2671 break;
2672 case DRAG_TOP:
2673 case DRAG_BOTTOM:
2674 c = rotation_array[(rotation_array_index + 2) % 4];
2675 break;
2676 case DRAG_H_GUIDE:
2677 c = CURSOR_VSIZE;
2678 break;
2679 case DRAG_TOP_LEFT:
2680 case DRAG_BOTTOM_RIGHT:
2681 c = rotation_array[(rotation_array_index + 3) % 4];
2682 break;
2683 case DRAG_DOUBLE_GUIDE:
2684 c = CURSOR_FDIAGSIZE;
2685 break;
2686 case DRAG_TOP_RIGHT:
2687 case DRAG_BOTTOM_LEFT:
2688 c = rotation_array[(rotation_array_index + 1) % 4];
2689 break;
2690 case DRAG_MOVE:
2691 c = CURSOR_MOVE;
2692 break;
2693 default:
2694 break;
2695 }
2696
2697 if (is_hovering_h_guide) {
2698 c = CURSOR_VSIZE;
2699 } else if (is_hovering_v_guide) {
2700 c = CURSOR_HSIZE;
2701 }
2702
2703 if (pan_pressed) {
2704 c = CURSOR_DRAG;
2705 }
2706 return c;
2707}
2708
2709void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) {
2710 Color color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
2711 color.a = 0.8;
2712 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
2713 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
2714 Size2 text_size = font->get_string_size(p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
2715 switch (p_side) {
2716 case SIDE_LEFT:
2717 p_position += Vector2(-text_size.x - 5, text_size.y / 2);
2718 break;
2719 case SIDE_TOP:
2720 p_position += Vector2(-text_size.x / 2, -5);
2721 break;
2722 case SIDE_RIGHT:
2723 p_position += Vector2(5, text_size.y / 2);
2724 break;
2725 case SIDE_BOTTOM:
2726 p_position += Vector2(-text_size.x / 2, text_size.y + 5);
2727 break;
2728 }
2729 viewport->draw_string(font, p_position, p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color);
2730}
2731
2732void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position, Side p_side) {
2733 String str = TS->format_number(vformat("%d " + TTR("px"), p_value));
2734 if (p_value != 0) {
2735 _draw_text_at_position(p_position, str, p_side);
2736 }
2737}
2738
2739void CanvasItemEditor::_draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side) {
2740 String str = TS->format_number(vformat("%.1f ", p_value * 100.0)) + TS->percent_sign();
2741 if (p_value != 0) {
2742 _draw_text_at_position(p_position, str, p_side);
2743 }
2744}
2745
2746void CanvasItemEditor::_draw_focus() {
2747 // Draw the focus around the base viewport
2748 if (viewport->has_focus()) {
2749 get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles))->draw(viewport->get_canvas_item(), Rect2(Point2(), viewport->get_size()));
2750 }
2751}
2752
2753void CanvasItemEditor::_draw_guides() {
2754 Color guide_color = EDITOR_GET("editors/2d/guides_color");
2755 Transform2D xform = viewport_scrollable->get_transform() * transform;
2756
2757 // Guides already there.
2758 if (Node *scene = EditorNode::get_singleton()->get_edited_scene()) {
2759 Array vguides = scene->get_meta("_edit_vertical_guides_", Array());
2760 for (int i = 0; i < vguides.size(); i++) {
2761 if (drag_type == DRAG_V_GUIDE && i == dragged_guide_index) {
2762 continue;
2763 }
2764 real_t x = xform.xform(Point2(vguides[i], 0)).x;
2765 viewport->draw_line(Point2(x, 0), Point2(x, viewport->get_size().y), guide_color, Math::round(EDSCALE));
2766 }
2767
2768 Array hguides = scene->get_meta("_edit_horizontal_guides_", Array());
2769 for (int i = 0; i < hguides.size(); i++) {
2770 if (drag_type == DRAG_H_GUIDE && i == dragged_guide_index) {
2771 continue;
2772 }
2773 real_t y = xform.xform(Point2(0, hguides[i])).y;
2774 viewport->draw_line(Point2(0, y), Point2(viewport->get_size().x, y), guide_color, Math::round(EDSCALE));
2775 }
2776 }
2777
2778 // Dragged guide.
2779 Color text_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
2780 Color outline_color = text_color.inverted();
2781 const float outline_size = 2;
2782 if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) {
2783 String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x)));
2784 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
2785 int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
2786 Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
2787 viewport->draw_string_outline(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
2788 viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
2789 viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE));
2790 }
2791 if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) {
2792 String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y)));
2793 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
2794 int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
2795 Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
2796 viewport->draw_string_outline(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
2797 viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color);
2798 viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE));
2799 }
2800}
2801
2802void CanvasItemEditor::_draw_smart_snapping() {
2803 Color line_color = EDITOR_GET("editors/2d/smart_snapping_line_color");
2804 if (snap_target[0] != SNAP_TARGET_NONE && snap_target[0] != SNAP_TARGET_GRID) {
2805 viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);
2806 viewport->draw_line(Point2(0, -1.0e+10F), Point2(0, 1.0e+10F), line_color);
2807 viewport->draw_set_transform_matrix(viewport->get_transform());
2808 }
2809 if (snap_target[1] != SNAP_TARGET_NONE && snap_target[1] != SNAP_TARGET_GRID) {
2810 viewport->draw_set_transform_matrix(viewport->get_transform() * transform * snap_transform);
2811 viewport->draw_line(Point2(-1.0e+10F, 0), Point2(1.0e+10F, 0), line_color);
2812 viewport->draw_set_transform_matrix(viewport->get_transform());
2813 }
2814}
2815
2816void CanvasItemEditor::_draw_rulers() {
2817 Color bg_color = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor));
2818 Color graduation_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor)).lerp(bg_color, 0.5);
2819 Color font_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
2820 font_color.a = 0.8;
2821 Ref<Font> font = get_theme_font(SNAME("rulers"), EditorStringName(EditorFonts));
2822 int font_size = get_theme_font_size(SNAME("rulers_size"), EditorStringName(EditorFonts));
2823
2824 // The rule transform
2825 Transform2D ruler_transform;
2826 if (grid_snap_active || _is_grid_visible()) {
2827 List<CanvasItem *> selection = _get_edited_canvas_items();
2828 if (snap_relative && selection.size() > 0) {
2829 ruler_transform.translate_local(_get_encompassing_rect_from_list(selection).position);
2830 ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));
2831 } else {
2832 ruler_transform.translate_local(grid_offset);
2833 ruler_transform.scale_basis(grid_step * Math::pow(2.0, grid_step_multiplier));
2834 }
2835 while ((transform * ruler_transform).get_scale().x < 50 || (transform * ruler_transform).get_scale().y < 50) {
2836 ruler_transform.scale_basis(Point2(2, 2));
2837 }
2838 } else {
2839 real_t basic_rule = 100;
2840 for (int i = 0; basic_rule * zoom > 100; i++) {
2841 basic_rule /= (i % 2) ? 5.0 : 2.0;
2842 }
2843 for (int i = 0; basic_rule * zoom < 100; i++) {
2844 basic_rule *= (i % 2) ? 2.0 : 5.0;
2845 }
2846 ruler_transform.scale(Size2(basic_rule, basic_rule));
2847 }
2848
2849 // Subdivisions
2850 int major_subdivision = 2;
2851 Transform2D major_subdivide;
2852 major_subdivide.scale(Size2(1.0 / major_subdivision, 1.0 / major_subdivision));
2853
2854 int minor_subdivision = 5;
2855 Transform2D minor_subdivide;
2856 minor_subdivide.scale(Size2(1.0 / minor_subdivision, 1.0 / minor_subdivision));
2857
2858 // First and last graduations to draw (in the ruler space)
2859 Point2 first = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Point2(RULER_WIDTH, RULER_WIDTH));
2860 Point2 last = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(viewport->get_size());
2861
2862 // Draw top ruler
2863 viewport->draw_rect(Rect2(Point2(RULER_WIDTH, 0), Size2(viewport->get_size().x, RULER_WIDTH)), bg_color);
2864 for (int i = Math::ceil(first.x); i < last.x; i++) {
2865 Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).round();
2866 if (i % (major_subdivision * minor_subdivision) == 0) {
2867 viewport->draw_line(Point2(position.x, 0), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE));
2868 real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x;
2869 viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
2870 } else {
2871 if (i % minor_subdivision == 0) {
2872 viewport->draw_line(Point2(position.x, RULER_WIDTH * 0.33), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE));
2873 } else {
2874 viewport->draw_line(Point2(position.x, RULER_WIDTH * 0.75), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE));
2875 }
2876 }
2877 }
2878
2879 // Draw left ruler
2880 viewport->draw_rect(Rect2(Point2(0, RULER_WIDTH), Size2(RULER_WIDTH, viewport->get_size().y)), bg_color);
2881 for (int i = Math::ceil(first.y); i < last.y; i++) {
2882 Point2 position = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).round();
2883 if (i % (major_subdivision * minor_subdivision) == 0) {
2884 viewport->draw_line(Point2(0, position.y), Point2(RULER_WIDTH, position.y), graduation_color, Math::round(EDSCALE));
2885 real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(0, i)).y;
2886
2887 Transform2D text_xform = Transform2D(-Math_PI / 2.0, Point2(font->get_height(font_size), position.y - 2));
2888 viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform);
2889 viewport->draw_string(font, Point2(), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
2890 viewport->draw_set_transform_matrix(viewport->get_transform());
2891
2892 } else {
2893 if (i % minor_subdivision == 0) {
2894 viewport->draw_line(Point2(RULER_WIDTH * 0.33, position.y), Point2(RULER_WIDTH, position.y), graduation_color, Math::round(EDSCALE));
2895 } else {
2896 viewport->draw_line(Point2(RULER_WIDTH * 0.75, position.y), Point2(RULER_WIDTH, position.y), graduation_color, Math::round(EDSCALE));
2897 }
2898 }
2899 }
2900
2901 // Draw the top left corner
2902 viewport->draw_rect(Rect2(Point2(), Size2(RULER_WIDTH, RULER_WIDTH)), graduation_color);
2903}
2904
2905void CanvasItemEditor::_draw_grid() {
2906 if (_is_grid_visible()) {
2907 // Draw the grid
2908 Vector2 real_grid_offset;
2909 const List<CanvasItem *> selection = _get_edited_canvas_items();
2910
2911 if (snap_relative && selection.size() > 0) {
2912 const Vector2 topleft = _get_encompassing_rect_from_list(selection).position;
2913 real_grid_offset.x = fmod(topleft.x, grid_step.x * (real_t)Math::pow(2.0, grid_step_multiplier));
2914 real_grid_offset.y = fmod(topleft.y, grid_step.y * (real_t)Math::pow(2.0, grid_step_multiplier));
2915 } else {
2916 real_grid_offset = grid_offset;
2917 }
2918
2919 // Draw a "primary" line every several lines to make measurements easier.
2920 // The step is configurable in the Configure Snap dialog.
2921 const Color secondary_grid_color = EDITOR_GET("editors/2d/grid_color");
2922 const Color primary_grid_color =
2923 Color(secondary_grid_color.r, secondary_grid_color.g, secondary_grid_color.b, secondary_grid_color.a * 2.5);
2924
2925 const Size2 viewport_size = viewport->get_size();
2926 const Transform2D xform = transform.affine_inverse();
2927 int last_cell = 0;
2928
2929 if (grid_step.x != 0) {
2930 for (int i = 0; i < viewport_size.width; i++) {
2931 const int cell =
2932 Math::fast_ftoi(Math::floor((xform.xform(Vector2(i, 0)).x - real_grid_offset.x) / (grid_step.x * Math::pow(2.0, grid_step_multiplier))));
2933
2934 if (i == 0) {
2935 last_cell = cell;
2936 }
2937
2938 if (last_cell != cell) {
2939 Color grid_color;
2940 if (primary_grid_step.x <= 1) {
2941 grid_color = secondary_grid_color;
2942 } else {
2943 grid_color = cell % primary_grid_step.x == 0 ? primary_grid_color : secondary_grid_color;
2944 }
2945
2946 viewport->draw_line(Point2(i, 0), Point2(i, viewport_size.height), grid_color, Math::round(EDSCALE));
2947 }
2948 last_cell = cell;
2949 }
2950 }
2951
2952 if (grid_step.y != 0) {
2953 for (int i = 0; i < viewport_size.height; i++) {
2954 const int cell =
2955 Math::fast_ftoi(Math::floor((xform.xform(Vector2(0, i)).y - real_grid_offset.y) / (grid_step.y * Math::pow(2.0, grid_step_multiplier))));
2956
2957 if (i == 0) {
2958 last_cell = cell;
2959 }
2960
2961 if (last_cell != cell) {
2962 Color grid_color;
2963 if (primary_grid_step.y <= 1) {
2964 grid_color = secondary_grid_color;
2965 } else {
2966 grid_color = cell % primary_grid_step.y == 0 ? primary_grid_color : secondary_grid_color;
2967 }
2968
2969 viewport->draw_line(Point2(0, i), Point2(viewport_size.width, i), grid_color, Math::round(EDSCALE));
2970 }
2971 last_cell = cell;
2972 }
2973 }
2974 }
2975}
2976
2977void CanvasItemEditor::_draw_ruler_tool() {
2978 if (tool != TOOL_RULER) {
2979 return;
2980 }
2981
2982 if (ruler_tool_active) {
2983 Color ruler_primary_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
2984 Color ruler_secondary_color = ruler_primary_color;
2985 ruler_secondary_color.a = 0.5;
2986
2987 Point2 begin = (ruler_tool_origin - view_offset) * zoom;
2988 Point2 end = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom - view_offset * zoom;
2989 Point2 corner = Point2(begin.x, end.y);
2990 Vector2 length_vector = (begin - end).abs() / zoom;
2991
2992 const real_t horizontal_angle_rad = length_vector.angle();
2993 const real_t vertical_angle_rad = Math_PI / 2.0 - horizontal_angle_rad;
2994
2995 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
2996 int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
2997 Color font_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
2998 Color font_secondary_color = font_color;
2999 font_secondary_color.set_v(font_secondary_color.get_v() > 0.5 ? 0.7 : 0.3);
3000 Color outline_color = font_color.inverted();
3001 float text_height = font->get_height(font_size);
3002
3003 const float outline_size = 4;
3004 const float text_width = 76;
3005 const float angle_text_width = 54;
3006
3007 Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);
3008 text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
3009 text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);
3010
3011 // Draw lines.
3012 viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3));
3013
3014 bool draw_secondary_lines = !(Math::is_equal_approx(begin.y, corner.y) || Math::is_equal_approx(end.x, corner.x));
3015 if (draw_secondary_lines) {
3016 viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE));
3017 viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE));
3018
3019 // Angle arcs.
3020 int arc_point_count = 8;
3021 real_t arc_radius_max_length_percent = 0.1;
3022 real_t ruler_length = length_vector.length() * zoom;
3023 real_t arc_max_radius = 50.0;
3024 real_t arc_line_width = 2.0;
3025
3026 const Vector2 end_to_begin = (end - begin);
3027
3028 real_t arc_1_start_angle = end_to_begin.x < 0
3029 ? (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 - vertical_angle_rad : Math_PI / 2.0)
3030 : (end_to_begin.y < 0 ? 3.0 * Math_PI / 2.0 : Math_PI / 2.0 - vertical_angle_rad);
3031 real_t arc_1_end_angle = arc_1_start_angle + vertical_angle_rad;
3032 // Constrain arc to triangle height & max size.
3033 real_t arc_1_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.y)), arc_max_radius);
3034
3035 real_t arc_2_start_angle = end_to_begin.x < 0
3036 ? (end_to_begin.y < 0 ? 0.0 : -horizontal_angle_rad)
3037 : (end_to_begin.y < 0 ? Math_PI - horizontal_angle_rad : Math_PI);
3038 real_t arc_2_end_angle = arc_2_start_angle + horizontal_angle_rad;
3039 // Constrain arc to triangle width & max size.
3040 real_t arc_2_radius = MIN(MIN(arc_radius_max_length_percent * ruler_length, ABS(end_to_begin.x)), arc_max_radius);
3041
3042 viewport->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));
3043 viewport->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));
3044 }
3045
3046 // Draw text.
3047 if (begin.is_equal_approx(end)) {
3048 viewport->draw_string_outline(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
3049 viewport->draw_string(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
3050 Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));
3051 viewport->draw_texture(get_editor_theme_icon(SNAME("EditorPosition")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);
3052 return;
3053 }
3054
3055 viewport->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);
3056 viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
3057
3058 if (draw_secondary_lines) {
3059 const int horizontal_angle = round(180 * horizontal_angle_rad / Math_PI);
3060 const int vertical_angle = round(180 * vertical_angle_rad / Math_PI);
3061
3062 Point2 text_pos2 = text_pos;
3063 text_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);
3064 viewport->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);
3065 viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3066
3067 Point2 v_angle_text_pos;
3068 v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);
3069 v_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);
3070 viewport->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);
3071 viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3072
3073 text_pos2 = text_pos;
3074 text_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);
3075 viewport->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);
3076 viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3077
3078 Point2 h_angle_text_pos;
3079 h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width);
3080 if (begin.y < end.y) {
3081 h_angle_text_pos.y = end.y + text_height * 1.5;
3082 if (ABS(text_pos2.x - h_angle_text_pos.x) < text_width) {
3083 int height_multiplier = 1.5 + (int)grid_snap_active;
3084 h_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));
3085 }
3086 } else {
3087 h_angle_text_pos.y = end.y - text_height * 0.5;
3088 if (ABS(text_pos2.x - h_angle_text_pos.x) < text_width) {
3089 int height_multiplier = 1 + (int)grid_snap_active;
3090 h_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));
3091 }
3092 }
3093 viewport->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);
3094 viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3095 }
3096
3097 if (grid_snap_active) {
3098 text_pos = (begin + end) / 2 + Vector2(-text_width / 2, text_height / 2);
3099 text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
3100 text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2);
3101
3102 if (draw_secondary_lines) {
3103 viewport->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);
3104 viewport->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);
3105
3106 Point2 text_pos2 = text_pos;
3107 text_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);
3108 viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
3109 viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3110
3111 text_pos2 = text_pos;
3112 text_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);
3113 viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
3114 viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color);
3115 } else {
3116 viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color);
3117 viewport->draw_string(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
3118 }
3119 }
3120 } else {
3121 if (grid_snap_active) {
3122 Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));
3123 viewport->draw_texture(get_editor_theme_icon(SNAME("EditorPosition")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2);
3124 }
3125 }
3126}
3127
3128void CanvasItemEditor::_draw_control_anchors(Control *control) {
3129 Transform2D xform = transform * control->get_global_transform_with_canvas();
3130 RID ci = viewport->get_canvas_item();
3131 if (tool == TOOL_SELECT && !Object::cast_to<Container>(control->get_parent())) {
3132 // Compute the anchors
3133 real_t anchors_values[4];
3134 anchors_values[0] = control->get_anchor(SIDE_LEFT);
3135 anchors_values[1] = control->get_anchor(SIDE_TOP);
3136 anchors_values[2] = control->get_anchor(SIDE_RIGHT);
3137 anchors_values[3] = control->get_anchor(SIDE_BOTTOM);
3138
3139 Vector2 anchors_pos[4];
3140 for (int i = 0; i < 4; i++) {
3141 Vector2 value = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);
3142 anchors_pos[i] = xform.xform(_anchor_to_position(control, value));
3143 }
3144
3145 // Draw the anchors handles
3146 Rect2 anchor_rects[4];
3147 if (control->is_layout_rtl()) {
3148 anchor_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));
3149 anchor_rects[1] = Rect2(anchors_pos[1] - anchor_handle->get_size(), anchor_handle->get_size());
3150 anchor_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));
3151 anchor_rects[3] = Rect2(anchors_pos[3], -anchor_handle->get_size());
3152 } else {
3153 anchor_rects[0] = Rect2(anchors_pos[0] - anchor_handle->get_size(), anchor_handle->get_size());
3154 anchor_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));
3155 anchor_rects[2] = Rect2(anchors_pos[2], -anchor_handle->get_size());
3156 anchor_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));
3157 }
3158
3159 for (int i = 0; i < 4; i++) {
3160 anchor_handle->draw_rect(ci, anchor_rects[i]);
3161 }
3162 }
3163}
3164
3165void CanvasItemEditor::_draw_control_helpers(Control *control) {
3166 Transform2D xform = transform * control->get_global_transform_with_canvas();
3167 if (tool == TOOL_SELECT && show_helpers && !Object::cast_to<Container>(control->get_parent())) {
3168 // Draw the helpers
3169 Color color_base = Color(0.8, 0.8, 0.8, 0.5);
3170
3171 // Compute the anchors
3172 real_t anchors_values[4];
3173 anchors_values[0] = control->get_anchor(SIDE_LEFT);
3174 anchors_values[1] = control->get_anchor(SIDE_TOP);
3175 anchors_values[2] = control->get_anchor(SIDE_RIGHT);
3176 anchors_values[3] = control->get_anchor(SIDE_BOTTOM);
3177
3178 Vector2 anchors[4];
3179 Vector2 anchors_pos[4];
3180 for (int i = 0; i < 4; i++) {
3181 anchors[i] = Vector2((i % 2 == 0) ? anchors_values[i] : anchors_values[(i + 1) % 4], (i % 2 == 1) ? anchors_values[i] : anchors_values[(i + 1) % 4]);
3182 anchors_pos[i] = xform.xform(_anchor_to_position(control, anchors[i]));
3183 }
3184
3185 // Get which anchor is dragged
3186 int dragged_anchor = -1;
3187 switch (drag_type) {
3188 case DRAG_ANCHOR_ALL:
3189 case DRAG_ANCHOR_TOP_LEFT:
3190 dragged_anchor = 0;
3191 break;
3192 case DRAG_ANCHOR_TOP_RIGHT:
3193 dragged_anchor = 1;
3194 break;
3195 case DRAG_ANCHOR_BOTTOM_RIGHT:
3196 dragged_anchor = 2;
3197 break;
3198 case DRAG_ANCHOR_BOTTOM_LEFT:
3199 dragged_anchor = 3;
3200 break;
3201 default:
3202 break;
3203 }
3204
3205 if (dragged_anchor >= 0) {
3206 // Draw the 4 lines when dragged
3207 Color color_snapped = Color(0.64, 0.93, 0.67, 0.5);
3208
3209 Vector2 corners_pos[4];
3210 for (int i = 0; i < 4; i++) {
3211 corners_pos[i] = xform.xform(_anchor_to_position(control, Vector2((i == 0 || i == 3) ? ANCHOR_BEGIN : ANCHOR_END, (i <= 1) ? ANCHOR_BEGIN : ANCHOR_END)));
3212 }
3213
3214 Vector2 line_starts[4];
3215 Vector2 line_ends[4];
3216 for (int i = 0; i < 4; i++) {
3217 real_t anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i];
3218 line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val);
3219 line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val);
3220 bool anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0;
3221 viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1);
3222 }
3223
3224 // Display the percentages next to the lines
3225 real_t percent_val;
3226 percent_val = anchors_values[(dragged_anchor + 2) % 4] - anchors_values[dragged_anchor];
3227 percent_val = (dragged_anchor >= 2) ? -percent_val : percent_val;
3228 _draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 1) % 4]) / 2, (Side)((dragged_anchor + 1) % 4));
3229
3230 percent_val = anchors_values[(dragged_anchor + 3) % 4] - anchors_values[(dragged_anchor + 1) % 4];
3231 percent_val = ((dragged_anchor + 1) % 4 >= 2) ? -percent_val : percent_val;
3232 _draw_percentage_at_position(percent_val, (anchors_pos[dragged_anchor] + anchors_pos[(dragged_anchor + 3) % 4]) / 2, (Side)(dragged_anchor));
3233
3234 percent_val = anchors_values[(dragged_anchor + 1) % 4];
3235 percent_val = ((dragged_anchor + 1) % 4 >= 2) ? ANCHOR_END - percent_val : percent_val;
3236 _draw_percentage_at_position(percent_val, (line_starts[dragged_anchor] + anchors_pos[dragged_anchor]) / 2, (Side)(dragged_anchor));
3237
3238 percent_val = anchors_values[dragged_anchor];
3239 percent_val = (dragged_anchor >= 2) ? ANCHOR_END - percent_val : percent_val;
3240 _draw_percentage_at_position(percent_val, (line_ends[(dragged_anchor + 1) % 4] + anchors_pos[dragged_anchor]) / 2, (Side)((dragged_anchor + 1) % 4));
3241 }
3242
3243 // Draw the margin values and the node width/height when dragging control side
3244 const real_t ratio = 0.33;
3245 Transform2D parent_transform = xform * control->get_transform().affine_inverse();
3246 real_t node_pos_in_parent[4];
3247
3248 Rect2 parent_rect = control->get_parent_anchorable_rect();
3249
3250 node_pos_in_parent[0] = control->get_anchor(SIDE_LEFT) * parent_rect.size.width + control->get_offset(SIDE_LEFT) + parent_rect.position.x;
3251 node_pos_in_parent[1] = control->get_anchor(SIDE_TOP) * parent_rect.size.height + control->get_offset(SIDE_TOP) + parent_rect.position.y;
3252 node_pos_in_parent[2] = control->get_anchor(SIDE_RIGHT) * parent_rect.size.width + control->get_offset(SIDE_RIGHT) + parent_rect.position.x;
3253 node_pos_in_parent[3] = control->get_anchor(SIDE_BOTTOM) * parent_rect.size.height + control->get_offset(SIDE_BOTTOM) + parent_rect.position.y;
3254
3255 Point2 start, end;
3256 switch (drag_type) {
3257 case DRAG_LEFT:
3258 case DRAG_TOP_LEFT:
3259 case DRAG_BOTTOM_LEFT:
3260 _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);
3261 [[fallthrough]];
3262 case DRAG_MOVE:
3263 start = Vector2(node_pos_in_parent[0], Math::lerp(node_pos_in_parent[1], node_pos_in_parent[3], ratio));
3264 end = start - Vector2(control->get_offset(SIDE_LEFT), 0);
3265 _draw_margin_at_position(control->get_offset(SIDE_LEFT), parent_transform.xform((start + end) / 2), SIDE_TOP);
3266 viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));
3267 break;
3268 default:
3269 break;
3270 }
3271 switch (drag_type) {
3272 case DRAG_RIGHT:
3273 case DRAG_TOP_RIGHT:
3274 case DRAG_BOTTOM_RIGHT:
3275 _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);
3276 [[fallthrough]];
3277 case DRAG_MOVE:
3278 start = Vector2(node_pos_in_parent[2], Math::lerp(node_pos_in_parent[3], node_pos_in_parent[1], ratio));
3279 end = start - Vector2(control->get_offset(SIDE_RIGHT), 0);
3280 _draw_margin_at_position(control->get_offset(SIDE_RIGHT), parent_transform.xform((start + end) / 2), SIDE_BOTTOM);
3281 viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));
3282 break;
3283 default:
3284 break;
3285 }
3286 switch (drag_type) {
3287 case DRAG_TOP:
3288 case DRAG_TOP_LEFT:
3289 case DRAG_TOP_RIGHT:
3290 _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);
3291 [[fallthrough]];
3292 case DRAG_MOVE:
3293 start = Vector2(Math::lerp(node_pos_in_parent[0], node_pos_in_parent[2], ratio), node_pos_in_parent[1]);
3294 end = start - Vector2(0, control->get_offset(SIDE_TOP));
3295 _draw_margin_at_position(control->get_offset(SIDE_TOP), parent_transform.xform((start + end) / 2), SIDE_LEFT);
3296 viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));
3297 break;
3298 default:
3299 break;
3300 }
3301 switch (drag_type) {
3302 case DRAG_BOTTOM:
3303 case DRAG_BOTTOM_LEFT:
3304 case DRAG_BOTTOM_RIGHT:
3305 _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);
3306 [[fallthrough]];
3307 case DRAG_MOVE:
3308 start = Vector2(Math::lerp(node_pos_in_parent[2], node_pos_in_parent[0], ratio), node_pos_in_parent[3]);
3309 end = start - Vector2(0, control->get_offset(SIDE_BOTTOM));
3310 _draw_margin_at_position(control->get_offset(SIDE_BOTTOM), parent_transform.xform((start + end) / 2), SIDE_RIGHT);
3311 viewport->draw_line(parent_transform.xform(start), parent_transform.xform(end), color_base, Math::round(EDSCALE));
3312 break;
3313 default:
3314 break;
3315 }
3316
3317 switch (drag_type) {
3318 //Draw the ghost rect if the node if rotated/scaled
3319 case DRAG_LEFT:
3320 case DRAG_TOP_LEFT:
3321 case DRAG_TOP:
3322 case DRAG_TOP_RIGHT:
3323 case DRAG_RIGHT:
3324 case DRAG_BOTTOM_RIGHT:
3325 case DRAG_BOTTOM:
3326 case DRAG_BOTTOM_LEFT:
3327 case DRAG_MOVE:
3328 if (control->get_rotation() != 0.0 || control->get_scale() != Vector2(1, 1)) {
3329 Rect2 rect = Rect2(Vector2(node_pos_in_parent[0], node_pos_in_parent[1]), control->get_size());
3330 viewport->draw_rect(parent_transform.xform(rect), color_base, false, Math::round(EDSCALE));
3331 }
3332 break;
3333 default:
3334 break;
3335 }
3336 }
3337}
3338
3339void CanvasItemEditor::_draw_selection() {
3340 Ref<Texture2D> pivot_icon = get_editor_theme_icon(SNAME("EditorPivot"));
3341 Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPosition"));
3342 Ref<Texture2D> previous_position_icon = get_editor_theme_icon(SNAME("EditorPositionPrevious"));
3343
3344 RID vp_ci = viewport->get_canvas_item();
3345
3346 List<CanvasItem *> selection = _get_edited_canvas_items(true, false);
3347
3348 bool single = selection.size() == 1;
3349 for (CanvasItem *E : selection) {
3350 CanvasItem *ci = Object::cast_to<CanvasItem>(E);
3351 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
3352
3353 bool item_locked = ci->has_meta("_edit_lock_");
3354
3355 // Draw the previous position if we are dragging the node
3356 if (show_helpers &&
3357 (drag_type == DRAG_MOVE || drag_type == DRAG_ROTATE ||
3358 drag_type == DRAG_LEFT || drag_type == DRAG_RIGHT || drag_type == DRAG_TOP || drag_type == DRAG_BOTTOM ||
3359 drag_type == DRAG_TOP_LEFT || drag_type == DRAG_TOP_RIGHT || drag_type == DRAG_BOTTOM_LEFT || drag_type == DRAG_BOTTOM_RIGHT)) {
3360 const Transform2D pre_drag_xform = transform * se->pre_drag_xform;
3361 const Color pre_drag_color = Color(0.4, 0.6, 1, 0.7);
3362
3363 if (ci->_edit_use_rect()) {
3364 Vector2 pre_drag_endpoints[4] = {
3365 pre_drag_xform.xform(se->pre_drag_rect.position),
3366 pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(se->pre_drag_rect.size.x, 0)),
3367 pre_drag_xform.xform(se->pre_drag_rect.position + se->pre_drag_rect.size),
3368 pre_drag_xform.xform(se->pre_drag_rect.position + Vector2(0, se->pre_drag_rect.size.y))
3369 };
3370
3371 for (int i = 0; i < 4; i++) {
3372 viewport->draw_line(pre_drag_endpoints[i], pre_drag_endpoints[(i + 1) % 4], pre_drag_color, Math::round(2 * EDSCALE));
3373 }
3374 } else {
3375 viewport->draw_texture(previous_position_icon, (pre_drag_xform.xform(Point2()) - (previous_position_icon->get_size() / 2)).floor());
3376 }
3377 }
3378
3379 Transform2D xform = transform * ci->get_global_transform_with_canvas();
3380
3381 // Draw the selected items position / surrounding boxes
3382 if (ci->_edit_use_rect()) {
3383 Rect2 rect = ci->_edit_get_rect();
3384 const Vector2 endpoints[4] = {
3385 xform.xform(rect.position),
3386 xform.xform(rect.position + Vector2(rect.size.x, 0)),
3387 xform.xform(rect.position + rect.size),
3388 xform.xform(rect.position + Vector2(0, rect.size.y))
3389 };
3390
3391 Color c = Color(1, 0.6, 0.4, 0.7);
3392
3393 if (item_locked) {
3394 c = Color(0.7, 0.7, 0.7, 0.7);
3395 }
3396
3397 for (int i = 0; i < 4; i++) {
3398 viewport->draw_line(endpoints[i], endpoints[(i + 1) % 4], c, Math::round(2 * EDSCALE));
3399 }
3400 } else {
3401 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
3402 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
3403 viewport->draw_set_transform_matrix(simple_xform);
3404 viewport->draw_texture(position_icon, -(position_icon->get_size() / 2));
3405 viewport->draw_set_transform_matrix(viewport->get_transform());
3406 }
3407
3408 if (single && !item_locked && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT)) { //kind of sucks
3409 // Draw the pivot
3410 if (ci->_edit_use_pivot()) {
3411 // Draw the node's pivot
3412 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
3413 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
3414
3415 viewport->draw_set_transform_matrix(simple_xform);
3416 viewport->draw_texture(pivot_icon, -(pivot_icon->get_size() / 2).floor());
3417 viewport->draw_set_transform_matrix(viewport->get_transform());
3418 }
3419
3420 // Draw control-related helpers
3421 Control *control = Object::cast_to<Control>(ci);
3422 if (control && _is_node_movable(control)) {
3423 _draw_control_anchors(control);
3424 _draw_control_helpers(control);
3425 }
3426
3427 // Draw the resize handles
3428 if (tool == TOOL_SELECT && ci->_edit_use_rect() && _is_node_movable(ci)) {
3429 Rect2 rect = ci->_edit_get_rect();
3430 const Vector2 endpoints[4] = {
3431 xform.xform(rect.position),
3432 xform.xform(rect.position + Vector2(rect.size.x, 0)),
3433 xform.xform(rect.position + rect.size),
3434 xform.xform(rect.position + Vector2(0, rect.size.y))
3435 };
3436 for (int i = 0; i < 4; i++) {
3437 int prev = (i + 3) % 4;
3438 int next = (i + 1) % 4;
3439
3440 Vector2 ofs = ((endpoints[i] - endpoints[prev]).normalized() + ((endpoints[i] - endpoints[next]).normalized())).normalized();
3441 ofs *= Math_SQRT2 * (select_handle->get_size().width / 2);
3442
3443 select_handle->draw(vp_ci, (endpoints[i] + ofs - (select_handle->get_size() / 2)).floor());
3444
3445 ofs = (endpoints[i] + endpoints[next]) / 2;
3446 ofs += (endpoints[next] - endpoints[i]).orthogonal().normalized() * (select_handle->get_size().width / 2);
3447
3448 select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor());
3449 }
3450 }
3451
3452 // Draw the move handles
3453 bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
3454 bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
3455 if (tool == TOOL_MOVE && show_transformation_gizmos) {
3456 if (_is_node_movable(ci)) {
3457 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
3458 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
3459
3460 Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);
3461 viewport->draw_set_transform_matrix(simple_xform);
3462
3463 Vector<Point2> points = {
3464 Vector2(move_factor.x * EDSCALE, 5 * EDSCALE),
3465 Vector2(move_factor.x * EDSCALE, -5 * EDSCALE),
3466 Vector2((move_factor.x + 10) * EDSCALE, 0)
3467 };
3468
3469 viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
3470 viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
3471
3472 points.clear();
3473 points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE));
3474 points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE));
3475 points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE));
3476
3477 viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
3478 viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
3479
3480 viewport->draw_set_transform_matrix(viewport->get_transform());
3481 }
3482 }
3483
3484 // Draw the rescale handles
3485 if (show_transformation_gizmos && ((is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y)) {
3486 if (_is_node_movable(ci)) {
3487 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
3488 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
3489
3490 Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
3491 bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT);
3492 Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom;
3493
3494 if (drag_type == DRAG_SCALE_X) {
3495 scale_factor.x += offset.x;
3496 if (uniform) {
3497 scale_factor.y += offset.x;
3498 }
3499 } else if (drag_type == DRAG_SCALE_Y) {
3500 scale_factor.y += offset.y;
3501 if (uniform) {
3502 scale_factor.x += offset.y;
3503 }
3504 }
3505
3506 viewport->draw_set_transform_matrix(simple_xform);
3507 Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
3508 viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
3509 viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
3510
3511 Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
3512 viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
3513 viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
3514
3515 viewport->draw_set_transform_matrix(viewport->get_transform());
3516 }
3517 }
3518 }
3519 }
3520
3521 if (drag_type == DRAG_BOX_SELECTION) {
3522 // Draw the dragging box
3523 Point2 bsfrom = transform.xform(drag_from);
3524 Point2 bsto = transform.xform(box_selecting_to);
3525
3526 viewport->draw_rect(
3527 Rect2(bsfrom, bsto - bsfrom),
3528 get_theme_color(SNAME("box_selection_fill_color"), EditorStringName(Editor)));
3529
3530 viewport->draw_rect(
3531 Rect2(bsfrom, bsto - bsfrom),
3532 get_theme_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor)),
3533 false,
3534 Math::round(EDSCALE));
3535 }
3536
3537 if (drag_type == DRAG_ROTATE) {
3538 // Draw the line when rotating a node
3539 viewport->draw_line(
3540 transform.xform(drag_rotation_center),
3541 transform.xform(drag_to),
3542 get_theme_color(SNAME("accent_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.6),
3543 Math::round(2 * EDSCALE));
3544 }
3545}
3546
3547void CanvasItemEditor::_draw_straight_line(Point2 p_from, Point2 p_to, Color p_color) {
3548 // Draw a line going through the whole screen from a vector
3549 RID ci = viewport->get_canvas_item();
3550 Vector<Point2> points;
3551 Point2 from = transform.xform(p_from);
3552 Point2 to = transform.xform(p_to);
3553 Size2 viewport_size = viewport->get_size();
3554
3555 if (to.x == from.x) {
3556 // Vertical line
3557 points.push_back(Point2(to.x, 0));
3558 points.push_back(Point2(to.x, viewport_size.y));
3559 } else if (to.y == from.y) {
3560 // Horizontal line
3561 points.push_back(Point2(0, to.y));
3562 points.push_back(Point2(viewport_size.x, to.y));
3563 } else {
3564 real_t y_for_zero_x = (to.y * from.x - from.y * to.x) / (from.x - to.x);
3565 real_t x_for_zero_y = (to.x * from.y - from.x * to.y) / (from.y - to.y);
3566 real_t y_for_viewport_x = ((to.y - from.y) * (viewport_size.x - from.x)) / (to.x - from.x) + from.y;
3567 real_t x_for_viewport_y = ((to.x - from.x) * (viewport_size.y - from.y)) / (to.y - from.y) + from.x; // faux
3568
3569 //bool start_set = false;
3570 if (y_for_zero_x >= 0 && y_for_zero_x <= viewport_size.y) {
3571 points.push_back(Point2(0, y_for_zero_x));
3572 }
3573 if (x_for_zero_y >= 0 && x_for_zero_y <= viewport_size.x) {
3574 points.push_back(Point2(x_for_zero_y, 0));
3575 }
3576 if (y_for_viewport_x >= 0 && y_for_viewport_x <= viewport_size.y) {
3577 points.push_back(Point2(viewport_size.x, y_for_viewport_x));
3578 }
3579 if (x_for_viewport_y >= 0 && x_for_viewport_y <= viewport_size.x) {
3580 points.push_back(Point2(x_for_viewport_y, viewport_size.y));
3581 }
3582 }
3583 if (points.size() >= 2) {
3584 RenderingServer::get_singleton()->canvas_item_add_line(ci, points[0], points[1], p_color);
3585 }
3586}
3587
3588void CanvasItemEditor::_draw_axis() {
3589 if (show_origin) {
3590 _draw_straight_line(Point2(), Point2(1, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));
3591 _draw_straight_line(Point2(), Point2(0, 1), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.75));
3592 }
3593
3594 if (show_viewport) {
3595 RID ci = viewport->get_canvas_item();
3596
3597 Color area_axis_color = EDITOR_GET("editors/2d/viewport_border_color");
3598
3599 Size2 screen_size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
3600
3601 Vector2 screen_endpoints[4] = {
3602 transform.xform(Vector2(0, 0)),
3603 transform.xform(Vector2(screen_size.width, 0)),
3604 transform.xform(Vector2(screen_size.width, screen_size.height)),
3605 transform.xform(Vector2(0, screen_size.height))
3606 };
3607
3608 for (int i = 0; i < 4; i++) {
3609 RenderingServer::get_singleton()->canvas_item_add_line(ci, screen_endpoints[i], screen_endpoints[(i + 1) % 4], area_axis_color);
3610 }
3611 }
3612}
3613
3614void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
3615 ERR_FAIL_NULL(p_node);
3616
3617 Node *scene = EditorNode::get_singleton()->get_edited_scene();
3618 if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {
3619 return;
3620 }
3621 CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
3622 if (ci && !ci->is_visible_in_tree()) {
3623 return;
3624 }
3625
3626 Transform2D parent_xform = p_parent_xform;
3627 Transform2D canvas_xform = p_canvas_xform;
3628
3629 if (ci && !ci->is_set_as_top_level()) {
3630 parent_xform = parent_xform * ci->get_transform();
3631 } else {
3632 CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
3633 parent_xform = Transform2D();
3634 canvas_xform = cl ? cl->get_transform() : p_canvas_xform;
3635 }
3636
3637 for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
3638 _draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform);
3639 }
3640
3641 if (ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) {
3642 Transform2D xform = transform * canvas_xform * parent_xform;
3643
3644 // Draw the node's position
3645 Ref<Texture2D> position_icon = get_editor_theme_icon(SNAME("EditorPositionUnselected"));
3646 Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
3647 Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
3648 viewport->draw_set_transform_matrix(simple_xform);
3649 viewport->draw_texture(position_icon, -position_icon->get_size() / 2, Color(1.0, 1.0, 1.0, 0.5));
3650 viewport->draw_set_transform_matrix(viewport->get_transform());
3651 }
3652}
3653
3654void CanvasItemEditor::_draw_hover() {
3655 List<Rect2> previous_rects;
3656
3657 for (int i = 0; i < hovering_results.size(); i++) {
3658 Ref<Texture2D> node_icon = hovering_results[i].icon;
3659 String node_name = hovering_results[i].name;
3660
3661 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
3662 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
3663 Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
3664 Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3));
3665
3666 Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
3667 // Rectify the position to avoid overlapping items
3668 for (const Rect2 &E : previous_rects) {
3669 if (E.intersects(Rect2(pos, item_size))) {
3670 pos.y = E.get_position().y - item_size.y;
3671 }
3672 }
3673
3674 previous_rects.push_back(Rect2(pos, item_size));
3675
3676 // Draw icon
3677 viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5));
3678
3679 // Draw name
3680 viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
3681 }
3682}
3683
3684void CanvasItemEditor::_draw_transform_message() {
3685 if (drag_type == DRAG_NONE || drag_selection.is_empty() || !drag_selection.front()->get()) {
3686 return;
3687 }
3688 String transform_message;
3689 Transform2D current_transform = drag_selection.front()->get()->get_global_transform();
3690
3691 double snap = EDITOR_GET("interface/inspector/default_float_step");
3692 int snap_step_decimals = Math::range_step_decimals(snap);
3693#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals)))
3694
3695 switch (drag_type) {
3696 case DRAG_MOVE:
3697 case DRAG_MOVE_X:
3698 case DRAG_MOVE_Y: {
3699 Vector2 delta = current_transform.get_origin() - original_transform.get_origin();
3700 if (drag_type == DRAG_MOVE) {
3701 transform_message = TTR("Moving:") + " (" + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ") px";
3702 } else if (drag_type == DRAG_MOVE_X) {
3703 transform_message = TTR("Moving:") + " " + FORMAT(delta.x) + " px";
3704 } else if (drag_type == DRAG_MOVE_Y) {
3705 transform_message = TTR("Moving:") + " " + FORMAT(delta.y) + " px";
3706 }
3707 } break;
3708
3709 case DRAG_ROTATE: {
3710 real_t delta = Math::rad_to_deg(current_transform.get_rotation() - original_transform.get_rotation());
3711 transform_message = TTR("Rotating:") + " " + FORMAT(delta) + String::utf8(" °");
3712 } break;
3713
3714 case DRAG_SCALE_X:
3715 case DRAG_SCALE_Y:
3716 case DRAG_SCALE_BOTH: {
3717 Vector2 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();
3718 Vector2 delta = current_transform.get_scale() / original_scale;
3719 if (drag_type == DRAG_SCALE_BOTH) {
3720 transform_message = TTR("Scaling:") + String::utf8(" ×(") + FORMAT(delta.x) + ", " + FORMAT(delta.y) + ")";
3721 } else if (drag_type == DRAG_SCALE_X) {
3722 transform_message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.x);
3723 } else if (drag_type == DRAG_SCALE_Y) {
3724 transform_message = TTR("Scaling:") + String::utf8(" ×") + FORMAT(delta.y);
3725 }
3726 } break;
3727
3728 default:
3729 break;
3730 }
3731#undef FORMAT
3732
3733 if (transform_message.is_empty()) {
3734 return;
3735 }
3736
3737 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
3738 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
3739 Point2 msgpos = Point2(RULER_WIDTH + 5 * EDSCALE, viewport->get_size().y - 20 * EDSCALE);
3740 viewport->draw_string(font, msgpos + Point2(1, 1), transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));
3741 viewport->draw_string(font, msgpos + Point2(-1, -1), transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8));
3742 viewport->draw_string(font, msgpos, transform_message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1));
3743}
3744
3745void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) {
3746 ERR_FAIL_NULL(p_node);
3747
3748 Node *scene = EditorNode::get_singleton()->get_edited_scene();
3749 if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) {
3750 return;
3751 }
3752 CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
3753 if (ci && !ci->is_visible_in_tree()) {
3754 return;
3755 }
3756
3757 Transform2D parent_xform = p_parent_xform;
3758 Transform2D canvas_xform = p_canvas_xform;
3759
3760 if (ci && !ci->is_set_as_top_level()) {
3761 parent_xform = parent_xform * ci->get_transform();
3762 } else {
3763 CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node);
3764 parent_xform = Transform2D();
3765 canvas_xform = cl ? cl->get_transform() : p_canvas_xform;
3766 }
3767
3768 for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
3769 _draw_locks_and_groups(p_node->get_child(i), parent_xform, canvas_xform);
3770 }
3771
3772 RID viewport_ci = viewport->get_canvas_item();
3773 if (ci) {
3774 real_t offset = 0;
3775
3776 Ref<Texture2D> lock = get_editor_theme_icon(SNAME("LockViewport"));
3777 if (p_node->has_meta("_edit_lock_") && show_edit_locks) {
3778 lock->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));
3779 offset += lock->get_size().x;
3780 }
3781
3782 Ref<Texture2D> group = get_editor_theme_icon(SNAME("GroupViewport"));
3783 if (ci->has_meta("_edit_group_") && show_edit_locks) {
3784 group->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0));
3785 //offset += group->get_size().x;
3786 }
3787 }
3788}
3789
3790void CanvasItemEditor::_draw_viewport() {
3791 // Update the transform
3792 transform = Transform2D();
3793 transform.scale_basis(Size2(zoom, zoom));
3794 transform.columns[2] = -view_offset * zoom;
3795 EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform);
3796
3797 // hide/show buttons depending on the selection
3798 bool all_locked = true;
3799 bool all_group = true;
3800 List<Node *> selection = editor_selection->get_selected_node_list();
3801 if (selection.is_empty()) {
3802 all_locked = false;
3803 all_group = false;
3804 } else {
3805 for (Node *E : selection) {
3806 if (Object::cast_to<CanvasItem>(E) && !Object::cast_to<CanvasItem>(E)->has_meta("_edit_lock_")) {
3807 all_locked = false;
3808 break;
3809 }
3810 }
3811 for (Node *E : selection) {
3812 if (Object::cast_to<CanvasItem>(E) && !Object::cast_to<CanvasItem>(E)->has_meta("_edit_group_")) {
3813 all_group = false;
3814 break;
3815 }
3816 }
3817 }
3818
3819 lock_button->set_visible(!all_locked);
3820 lock_button->set_disabled(selection.is_empty());
3821 unlock_button->set_visible(all_locked);
3822 group_button->set_visible(!all_group);
3823 group_button->set_disabled(selection.is_empty());
3824 ungroup_button->set_visible(all_group);
3825
3826 _draw_grid();
3827 _draw_ruler_tool();
3828 _draw_axis();
3829 if (EditorNode::get_singleton()->get_edited_scene()) {
3830 _draw_locks_and_groups(EditorNode::get_singleton()->get_edited_scene());
3831 _draw_invisible_nodes_positions(EditorNode::get_singleton()->get_edited_scene());
3832 }
3833 _draw_selection();
3834
3835 RID ci = viewport->get_canvas_item();
3836 RenderingServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D());
3837
3838 EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over();
3839 if (!over_plugin_list->is_empty()) {
3840 over_plugin_list->forward_canvas_draw_over_viewport(viewport);
3841 }
3842 EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over();
3843 if (!force_over_plugin_list->is_empty()) {
3844 force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport);
3845 }
3846
3847 if (show_rulers) {
3848 _draw_rulers();
3849 }
3850 if (show_guides) {
3851 _draw_guides();
3852 }
3853 _draw_smart_snapping();
3854 _draw_focus();
3855 _draw_hover();
3856 _draw_transform_message();
3857}
3858
3859void CanvasItemEditor::update_viewport() {
3860 _update_scrollbars();
3861 viewport->queue_redraw();
3862}
3863
3864void CanvasItemEditor::set_current_tool(Tool p_tool) {
3865 _button_tool_select(p_tool);
3866}
3867
3868void CanvasItemEditor::_update_editor_settings() {
3869 button_center_view->set_icon(get_editor_theme_icon(SNAME("CenterView")));
3870 select_button->set_icon(get_editor_theme_icon(SNAME("ToolSelect")));
3871 select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));
3872 list_select_button->set_icon(get_editor_theme_icon(SNAME("ListSelect")));
3873 move_button->set_icon(get_editor_theme_icon(SNAME("ToolMove")));
3874 scale_button->set_icon(get_editor_theme_icon(SNAME("ToolScale")));
3875 rotate_button->set_icon(get_editor_theme_icon(SNAME("ToolRotate")));
3876 smart_snap_button->set_icon(get_editor_theme_icon(SNAME("Snap")));
3877 grid_snap_button->set_icon(get_editor_theme_icon(SNAME("SnapGrid")));
3878 snap_config_menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
3879 skeleton_menu->set_icon(get_editor_theme_icon(SNAME("Bone")));
3880 override_camera_button->set_icon(get_editor_theme_icon(SNAME("Camera2D")));
3881 pan_button->set_icon(get_editor_theme_icon(SNAME("ToolPan")));
3882 ruler_button->set_icon(get_editor_theme_icon(SNAME("Ruler")));
3883 pivot_button->set_icon(get_editor_theme_icon(SNAME("EditPivot")));
3884 select_handle = get_editor_theme_icon(SNAME("EditorHandle"));
3885 anchor_handle = get_editor_theme_icon(SNAME("EditorControlAnchor"));
3886 lock_button->set_icon(get_editor_theme_icon(SNAME("Lock")));
3887 unlock_button->set_icon(get_editor_theme_icon(SNAME("Unlock")));
3888 group_button->set_icon(get_editor_theme_icon(SNAME("Group")));
3889 ungroup_button->set_icon(get_editor_theme_icon(SNAME("Ungroup")));
3890 key_loc_button->set_icon(get_editor_theme_icon(SNAME("KeyPosition")));
3891 key_rot_button->set_icon(get_editor_theme_icon(SNAME("KeyRotation")));
3892 key_scale_button->set_icon(get_editor_theme_icon(SNAME("KeyScale")));
3893 key_insert_button->set_icon(get_editor_theme_icon(SNAME("Key")));
3894 key_auto_insert_button->set_icon(get_editor_theme_icon(SNAME("AutoKey")));
3895 // Use a different color for the active autokey icon to make them easier
3896 // to distinguish from the other key icons at the top. On a light theme,
3897 // the icon will be dark, so we need to lighten it before blending it
3898 // with the red color.
3899 const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25);
3900 key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55));
3901 animation_menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
3902
3903 context_toolbar_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("ContextualToolbar"), EditorStringName(EditorStyles)));
3904
3905 panner->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")));
3906 panner->set_scroll_speed(EDITOR_GET("editors/panning/2d_editor_pan_speed"));
3907 warped_panning = bool(EDITOR_GET("editors/panning/warped_mouse_panning"));
3908}
3909
3910void CanvasItemEditor::_notification(int p_what) {
3911 switch (p_what) {
3912 case NOTIFICATION_PHYSICS_PROCESS: {
3913 EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels"));
3914
3915 int nb_having_pivot = 0;
3916
3917 // Update the viewport if the canvas_item changes
3918 List<CanvasItem *> selection = _get_edited_canvas_items(true);
3919 for (CanvasItem *ci : selection) {
3920 CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
3921
3922 Rect2 rect;
3923 if (ci->_edit_use_rect()) {
3924 rect = ci->_edit_get_rect();
3925 } else {
3926 rect = Rect2();
3927 }
3928 Transform2D xform = ci->get_transform();
3929
3930 if (rect != se->prev_rect || xform != se->prev_xform) {
3931 viewport->queue_redraw();
3932 se->prev_rect = rect;
3933 se->prev_xform = xform;
3934 }
3935
3936 Control *control = Object::cast_to<Control>(ci);
3937 if (control) {
3938 real_t anchors[4];
3939 Vector2 pivot;
3940
3941 pivot = control->get_pivot_offset();
3942 anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT);
3943 anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT);
3944 anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP);
3945 anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM);
3946
3947 if (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]) {
3948 se->prev_pivot = pivot;
3949 se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT];
3950 se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT];
3951 se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP];
3952 se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM];
3953 viewport->queue_redraw();
3954 }
3955 }
3956
3957 if (ci->_edit_use_pivot()) {
3958 nb_having_pivot++;
3959 }
3960 }
3961
3962 // Activate / Deactivate the pivot tool
3963 pivot_button->set_disabled(nb_having_pivot == 0);
3964
3965 // Update the viewport if bones changes
3966 for (KeyValue<BoneKey, BoneList> &E : bone_list) {
3967 Object *b = ObjectDB::get_instance(E.key.from);
3968 if (!b) {
3969 viewport->queue_redraw();
3970 break;
3971 }
3972
3973 Node2D *b2 = Object::cast_to<Node2D>(b);
3974 if (!b2 || !b2->is_inside_tree()) {
3975 continue;
3976 }
3977
3978 Transform2D global_xform = b2->get_global_transform();
3979
3980 if (global_xform != E.value.xform) {
3981 E.value.xform = global_xform;
3982 viewport->queue_redraw();
3983 }
3984
3985 Bone2D *bone = Object::cast_to<Bone2D>(b);
3986 if (bone && bone->get_length() != E.value.length) {
3987 E.value.length = bone->get_length();
3988 viewport->queue_redraw();
3989 }
3990 }
3991 } break;
3992
3993 case NOTIFICATION_ENTER_TREE: {
3994 select_sb->set_texture(get_editor_theme_icon(SNAME("EditorRect2D")));
3995 select_sb->set_texture_margin_all(4);
3996 select_sb->set_content_margin_all(4);
3997
3998 AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("keying_changed", callable_mp(this, &CanvasItemEditor::_keying_changed));
3999 AnimationPlayerEditor::get_singleton()->connect("animation_selected", callable_mp(this, &CanvasItemEditor::_keying_changed).unbind(1));
4000 _keying_changed();
4001 _update_editor_settings();
4002 } break;
4003
4004 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
4005 _update_editor_settings();
4006 } break;
4007
4008 case NOTIFICATION_VISIBILITY_CHANGED: {
4009 if (!is_visible() && override_camera_button->is_pressed()) {
4010 EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
4011
4012 debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
4013 override_camera_button->set_pressed(false);
4014 }
4015 } break;
4016 }
4017}
4018
4019void CanvasItemEditor::_selection_changed() {
4020 if (!selected_from_canvas) {
4021 _reset_drag();
4022 }
4023 selected_from_canvas = false;
4024}
4025
4026void CanvasItemEditor::edit(CanvasItem *p_canvas_item) {
4027 if (!p_canvas_item) {
4028 return;
4029 }
4030
4031 Array selection = editor_selection->get_selected_nodes();
4032 if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) {
4033 _reset_drag();
4034 }
4035}
4036
4037void CanvasItemEditor::_update_scrollbars() {
4038 updating_scroll = true;
4039
4040 // Move the zoom buttons.
4041 Point2 controls_vb_begin = Point2(5, 5);
4042 controls_vb_begin += (show_rulers) ? Point2(RULER_WIDTH, RULER_WIDTH) : Point2();
4043 controls_vb->set_begin(controls_vb_begin);
4044
4045 Size2 hmin = h_scroll->get_minimum_size();
4046 Size2 vmin = v_scroll->get_minimum_size();
4047
4048 // Get the visible frame.
4049 Size2 screen_rect = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
4050 Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height));
4051
4052 // Calculate scrollable area.
4053 Rect2 canvas_item_rect = Rect2(Point2(), screen_rect);
4054 if (EditorNode::get_singleton()->is_inside_tree() && EditorNode::get_singleton()->get_edited_scene()) {
4055 Rect2 content_rect = _get_encompassing_rect(EditorNode::get_singleton()->get_edited_scene());
4056 canvas_item_rect.expand_to(content_rect.position);
4057 canvas_item_rect.expand_to(content_rect.position + content_rect.size);
4058 }
4059 canvas_item_rect.size += screen_rect * 2;
4060 canvas_item_rect.position -= screen_rect;
4061
4062 // Updates the scrollbars.
4063 const Size2 size = viewport->get_size();
4064 const Point2 begin = canvas_item_rect.position;
4065 const Point2 end = canvas_item_rect.position + canvas_item_rect.size - local_rect.size / zoom;
4066
4067 if (canvas_item_rect.size.height <= (local_rect.size.y / zoom)) {
4068 v_scroll->hide();
4069 } else {
4070 v_scroll->show();
4071 v_scroll->set_min(MIN(view_offset.y, begin.y));
4072 v_scroll->set_max(MAX(view_offset.y, end.y) + screen_rect.y);
4073 v_scroll->set_page(screen_rect.y);
4074 }
4075
4076 if (canvas_item_rect.size.width <= (local_rect.size.x / zoom)) {
4077 h_scroll->hide();
4078 } else {
4079 h_scroll->show();
4080 h_scroll->set_min(MIN(view_offset.x, begin.x));
4081 h_scroll->set_max(MAX(view_offset.x, end.x) + screen_rect.x);
4082 h_scroll->set_page(screen_rect.x);
4083 }
4084
4085 // Move and resize the scrollbars, avoiding overlap.
4086 if (is_layout_rtl()) {
4087 v_scroll->set_begin(Point2(0, (show_rulers) ? RULER_WIDTH : 0));
4088 v_scroll->set_end(Point2(vmin.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));
4089 } else {
4090 v_scroll->set_begin(Point2(size.width - vmin.width, (show_rulers) ? RULER_WIDTH : 0));
4091 v_scroll->set_end(Point2(size.width, size.height - (h_scroll->is_visible() ? hmin.height : 0)));
4092 }
4093 h_scroll->set_begin(Point2((show_rulers) ? RULER_WIDTH : 0, size.height - hmin.height));
4094 h_scroll->set_end(Point2(size.width - (v_scroll->is_visible() ? vmin.width : 0), size.height));
4095
4096 // Calculate scrollable area.
4097 v_scroll->set_value(view_offset.y);
4098 h_scroll->set_value(view_offset.x);
4099
4100 previous_update_view_offset = view_offset;
4101 updating_scroll = false;
4102}
4103
4104void CanvasItemEditor::_update_scroll(real_t) {
4105 if (updating_scroll) {
4106 return;
4107 }
4108
4109 view_offset.x = h_scroll->get_value();
4110 view_offset.y = v_scroll->get_value();
4111 viewport->queue_redraw();
4112}
4113
4114void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) {
4115 p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM);
4116
4117 if (p_zoom == zoom) {
4118 zoom_widget->set_zoom(p_zoom);
4119 return;
4120 }
4121
4122 real_t prev_zoom = zoom;
4123 zoom = p_zoom;
4124
4125 view_offset += p_position / prev_zoom - p_position / zoom;
4126
4127 // We want to align in-scene pixels to screen pixels, this prevents blurry rendering
4128 // in small details (texts, lines).
4129 // This correction adds a jitter movement when zooming, so we correct only when the
4130 // zoom factor is an integer. (in the other cases, all pixels won't be aligned anyway)
4131 const real_t closest_zoom_factor = Math::round(zoom);
4132 if (Math::is_zero_approx(zoom - closest_zoom_factor)) {
4133 // make sure scene pixel at view_offset is aligned on a screen pixel
4134 Vector2 view_offset_int = view_offset.floor();
4135 Vector2 view_offset_frac = view_offset - view_offset_int;
4136 view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor;
4137 }
4138
4139 zoom_widget->set_zoom(zoom);
4140 update_viewport();
4141}
4142
4143void CanvasItemEditor::_update_zoom(real_t p_zoom) {
4144 _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0);
4145}
4146
4147void CanvasItemEditor::_shortcut_zoom_set(real_t p_zoom) {
4148 _zoom_on_position(p_zoom * MAX(1, EDSCALE), viewport->get_local_mouse_position());
4149}
4150
4151void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) {
4152 smart_snap_active = p_status;
4153 viewport->queue_redraw();
4154}
4155
4156void CanvasItemEditor::_button_toggle_grid_snap(bool p_status) {
4157 grid_snap_active = p_status;
4158 viewport->queue_redraw();
4159}
4160
4161void CanvasItemEditor::_button_override_camera(bool p_pressed) {
4162 EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton();
4163
4164 if (p_pressed) {
4165 debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_2D);
4166 } else {
4167 debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE);
4168 }
4169}
4170
4171void CanvasItemEditor::_button_tool_select(int p_index) {
4172 Button *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };
4173 for (int i = 0; i < TOOL_MAX; i++) {
4174 tb[i]->set_pressed(i == p_index);
4175 }
4176
4177 tool = (Tool)p_index;
4178
4179 viewport->queue_redraw();
4180 _update_cursor();
4181}
4182
4183void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {
4184 const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
4185
4186 AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor();
4187 ERR_FAIL_COND_MSG(!te->get_current_animation().is_valid(), "Cannot insert animation key. No animation selected.");
4188
4189 te->make_insert_queue();
4190 for (const KeyValue<Node *, Object *> &E : selection) {
4191 CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
4192 if (!ci || !ci->is_visible_in_tree()) {
4193 continue;
4194 }
4195
4196 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4197 continue;
4198 }
4199
4200 if (Object::cast_to<Node2D>(ci)) {
4201 Node2D *n2d = Object::cast_to<Node2D>(ci);
4202
4203 if (key_pos && p_location) {
4204 te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing);
4205 }
4206 if (key_rot && p_rotation) {
4207 te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing);
4208 }
4209 if (key_scale && p_scale) {
4210 te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing);
4211 }
4212
4213 if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) {
4214 //look for an IK chain
4215 List<Node2D *> ik_chain;
4216
4217 Node2D *n = Object::cast_to<Node2D>(n2d->get_parent_item());
4218 bool has_chain = false;
4219
4220 while (n) {
4221 ik_chain.push_back(n);
4222 if (n->has_meta("_edit_ik_")) {
4223 has_chain = true;
4224 break;
4225 }
4226
4227 if (!n->get_parent_item()) {
4228 break;
4229 }
4230 n = Object::cast_to<Node2D>(n->get_parent_item());
4231 }
4232
4233 if (has_chain && ik_chain.size()) {
4234 for (Node2D *&F : ik_chain) {
4235 if (key_pos) {
4236 te->insert_node_value_key(F, "position", F->get_position(), p_on_existing);
4237 }
4238 if (key_rot) {
4239 te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing);
4240 }
4241 if (key_scale) {
4242 te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing);
4243 }
4244 }
4245 }
4246 }
4247
4248 } else if (Object::cast_to<Control>(ci)) {
4249 Control *ctrl = Object::cast_to<Control>(ci);
4250
4251 if (key_pos) {
4252 te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing);
4253 }
4254 if (key_rot) {
4255 te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing);
4256 }
4257 if (key_scale) {
4258 te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing);
4259 }
4260 }
4261 }
4262 te->commit_insert_queue();
4263}
4264
4265void CanvasItemEditor::_update_override_camera_button(bool p_game_running) {
4266 if (p_game_running) {
4267 override_camera_button->set_disabled(false);
4268 override_camera_button->set_tooltip_text(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera."));
4269 } else {
4270 override_camera_button->set_disabled(true);
4271 override_camera_button->set_pressed(false);
4272 override_camera_button->set_tooltip_text(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature."));
4273 }
4274}
4275
4276void CanvasItemEditor::_popup_callback(int p_op) {
4277 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4278 last_option = MenuOption(p_op);
4279 switch (p_op) {
4280 case SHOW_ORIGIN: {
4281 show_origin = !show_origin;
4282 int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);
4283 view_menu->get_popup()->set_item_checked(idx, show_origin);
4284 viewport->queue_redraw();
4285 } break;
4286 case SHOW_VIEWPORT: {
4287 show_viewport = !show_viewport;
4288 int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);
4289 view_menu->get_popup()->set_item_checked(idx, show_viewport);
4290 viewport->queue_redraw();
4291 } break;
4292 case SHOW_EDIT_LOCKS: {
4293 show_edit_locks = !show_edit_locks;
4294 int idx = view_menu->get_popup()->get_item_index(SHOW_EDIT_LOCKS);
4295 view_menu->get_popup()->set_item_checked(idx, show_edit_locks);
4296 viewport->queue_redraw();
4297 } break;
4298 case SHOW_TRANSFORMATION_GIZMOS: {
4299 show_transformation_gizmos = !show_transformation_gizmos;
4300 int idx = view_menu->get_popup()->get_item_index(SHOW_TRANSFORMATION_GIZMOS);
4301 view_menu->get_popup()->set_item_checked(idx, show_transformation_gizmos);
4302 viewport->queue_redraw();
4303 } break;
4304 case SNAP_USE_NODE_PARENT: {
4305 snap_node_parent = !snap_node_parent;
4306 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);
4307 smartsnap_config_popup->set_item_checked(idx, snap_node_parent);
4308 } break;
4309 case SNAP_USE_NODE_ANCHORS: {
4310 snap_node_anchors = !snap_node_anchors;
4311 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);
4312 smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);
4313 } break;
4314 case SNAP_USE_NODE_SIDES: {
4315 snap_node_sides = !snap_node_sides;
4316 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);
4317 smartsnap_config_popup->set_item_checked(idx, snap_node_sides);
4318 } break;
4319 case SNAP_USE_NODE_CENTER: {
4320 snap_node_center = !snap_node_center;
4321 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);
4322 smartsnap_config_popup->set_item_checked(idx, snap_node_center);
4323 } break;
4324 case SNAP_USE_OTHER_NODES: {
4325 snap_other_nodes = !snap_other_nodes;
4326 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);
4327 smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);
4328 } break;
4329 case SNAP_USE_GUIDES: {
4330 snap_guides = !snap_guides;
4331 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);
4332 smartsnap_config_popup->set_item_checked(idx, snap_guides);
4333 } break;
4334 case SNAP_USE_ROTATION: {
4335 snap_rotation = !snap_rotation;
4336 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);
4337 snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);
4338 } break;
4339 case SNAP_USE_SCALE: {
4340 snap_scale = !snap_scale;
4341 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);
4342 snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);
4343 } break;
4344 case SNAP_RELATIVE: {
4345 snap_relative = !snap_relative;
4346 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);
4347 snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);
4348 viewport->queue_redraw();
4349 } break;
4350 case SNAP_USE_PIXEL: {
4351 snap_pixel = !snap_pixel;
4352 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);
4353 snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);
4354 } break;
4355 case SNAP_CONFIGURE: {
4356 static_cast<SnapDialog *>(snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_step, snap_rotation_offset, snap_rotation_step, snap_scale_step);
4357 snap_dialog->popup_centered(Size2(320, 160) * EDSCALE);
4358 } break;
4359 case SKELETON_SHOW_BONES: {
4360 List<Node *> selection = editor_selection->get_selected_node_list();
4361 for (Node *E : selection) {
4362 // Add children nodes so they are processed
4363 for (int child = 0; child < E->get_child_count(); child++) {
4364 selection.push_back(E->get_child(child));
4365 }
4366
4367 Bone2D *bone_2d = Object::cast_to<Bone2D>(E);
4368 if (!bone_2d || !bone_2d->is_inside_tree()) {
4369 continue;
4370 }
4371 bone_2d->_editor_set_show_bone_gizmo(!bone_2d->_editor_get_show_bone_gizmo());
4372 }
4373 } break;
4374 case SHOW_HELPERS: {
4375 show_helpers = !show_helpers;
4376 int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);
4377 view_menu->get_popup()->set_item_checked(idx, show_helpers);
4378 viewport->queue_redraw();
4379 } break;
4380 case SHOW_RULERS: {
4381 show_rulers = !show_rulers;
4382 int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);
4383 view_menu->get_popup()->set_item_checked(idx, show_rulers);
4384 update_viewport();
4385 } break;
4386 case SHOW_GUIDES: {
4387 show_guides = !show_guides;
4388 int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);
4389 view_menu->get_popup()->set_item_checked(idx, show_guides);
4390 viewport->queue_redraw();
4391 } break;
4392 case LOCK_SELECTED: {
4393 undo_redo->create_action(TTR("Lock Selected"));
4394
4395 List<Node *> selection = editor_selection->get_selected_node_list();
4396 for (Node *E : selection) {
4397 CanvasItem *ci = Object::cast_to<CanvasItem>(E);
4398 if (!ci || !ci->is_inside_tree()) {
4399 continue;
4400 }
4401 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4402 continue;
4403 }
4404
4405 undo_redo->add_do_method(ci, "set_meta", "_edit_lock_", true);
4406 undo_redo->add_undo_method(ci, "remove_meta", "_edit_lock_");
4407 undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
4408 undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
4409 }
4410 undo_redo->add_do_method(viewport, "queue_redraw");
4411 undo_redo->add_undo_method(viewport, "queue_redraw");
4412 undo_redo->commit_action();
4413 } break;
4414 case UNLOCK_SELECTED: {
4415 undo_redo->create_action(TTR("Unlock Selected"));
4416
4417 List<Node *> selection = editor_selection->get_selected_node_list();
4418 for (Node *E : selection) {
4419 CanvasItem *ci = Object::cast_to<CanvasItem>(E);
4420 if (!ci || !ci->is_inside_tree()) {
4421 continue;
4422 }
4423 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4424 continue;
4425 }
4426
4427 undo_redo->add_do_method(ci, "remove_meta", "_edit_lock_");
4428 undo_redo->add_undo_method(ci, "set_meta", "_edit_lock_", true);
4429 undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed");
4430 undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed");
4431 }
4432 undo_redo->add_do_method(viewport, "queue_redraw");
4433 undo_redo->add_undo_method(viewport, "queue_redraw");
4434 undo_redo->commit_action();
4435 } break;
4436 case GROUP_SELECTED: {
4437 undo_redo->create_action(TTR("Group Selected"));
4438
4439 List<Node *> selection = editor_selection->get_selected_node_list();
4440 for (Node *E : selection) {
4441 CanvasItem *ci = Object::cast_to<CanvasItem>(E);
4442 if (!ci || !ci->is_inside_tree()) {
4443 continue;
4444 }
4445 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4446 continue;
4447 }
4448
4449 undo_redo->add_do_method(ci, "set_meta", "_edit_group_", true);
4450 undo_redo->add_undo_method(ci, "remove_meta", "_edit_group_");
4451 undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
4452 undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
4453 }
4454 undo_redo->add_do_method(viewport, "queue_redraw");
4455 undo_redo->add_undo_method(viewport, "queue_redraw");
4456 undo_redo->commit_action();
4457 } break;
4458 case UNGROUP_SELECTED: {
4459 undo_redo->create_action(TTR("Ungroup Selected"));
4460
4461 List<Node *> selection = editor_selection->get_selected_node_list();
4462 for (Node *E : selection) {
4463 CanvasItem *ci = Object::cast_to<CanvasItem>(E);
4464 if (!ci || !ci->is_inside_tree()) {
4465 continue;
4466 }
4467 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4468 continue;
4469 }
4470
4471 undo_redo->add_do_method(ci, "remove_meta", "_edit_group_");
4472 undo_redo->add_undo_method(ci, "set_meta", "_edit_group_", true);
4473 undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed");
4474 undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed");
4475 }
4476 undo_redo->add_do_method(viewport, "queue_redraw");
4477 undo_redo->add_undo_method(viewport, "queue_redraw");
4478 undo_redo->commit_action();
4479 } break;
4480
4481 case ANIM_INSERT_KEY:
4482 case ANIM_INSERT_KEY_EXISTING: {
4483 bool existing = p_op == ANIM_INSERT_KEY_EXISTING;
4484
4485 _insert_animation_keys(true, true, true, existing);
4486
4487 } break;
4488 case ANIM_INSERT_POS: {
4489 key_pos = key_loc_button->is_pressed();
4490 } break;
4491 case ANIM_INSERT_ROT: {
4492 key_rot = key_rot_button->is_pressed();
4493 } break;
4494 case ANIM_INSERT_SCALE: {
4495 key_scale = key_scale_button->is_pressed();
4496 } break;
4497 case ANIM_COPY_POSE: {
4498 pose_clipboard.clear();
4499
4500 const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
4501
4502 for (const KeyValue<Node *, Object *> &E : selection) {
4503 CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
4504 if (!ci || !ci->is_visible_in_tree()) {
4505 continue;
4506 }
4507
4508 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4509 continue;
4510 }
4511
4512 if (Object::cast_to<Node2D>(ci)) {
4513 Node2D *n2d = Object::cast_to<Node2D>(ci);
4514 PoseClipboard pc;
4515 pc.pos = n2d->get_position();
4516 pc.rot = n2d->get_rotation();
4517 pc.scale = n2d->get_scale();
4518 pc.id = n2d->get_instance_id();
4519 pose_clipboard.push_back(pc);
4520 }
4521 }
4522
4523 } break;
4524 case ANIM_PASTE_POSE: {
4525 if (!pose_clipboard.size()) {
4526 break;
4527 }
4528
4529 undo_redo->create_action(TTR("Paste Pose"));
4530 for (const PoseClipboard &E : pose_clipboard) {
4531 Node2D *n2d = Object::cast_to<Node2D>(ObjectDB::get_instance(E.id));
4532 if (!n2d) {
4533 continue;
4534 }
4535 undo_redo->add_do_method(n2d, "set_position", E.pos);
4536 undo_redo->add_do_method(n2d, "set_rotation", E.rot);
4537 undo_redo->add_do_method(n2d, "set_scale", E.scale);
4538 undo_redo->add_undo_method(n2d, "set_position", n2d->get_position());
4539 undo_redo->add_undo_method(n2d, "set_rotation", n2d->get_rotation());
4540 undo_redo->add_undo_method(n2d, "set_scale", n2d->get_scale());
4541 }
4542 undo_redo->commit_action();
4543
4544 } break;
4545 case ANIM_CLEAR_POSE: {
4546 HashMap<Node *, Object *> &selection = editor_selection->get_selection();
4547
4548 for (const KeyValue<Node *, Object *> &E : selection) {
4549 CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
4550 if (!ci || !ci->is_visible_in_tree()) {
4551 continue;
4552 }
4553
4554 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4555 continue;
4556 }
4557
4558 if (Object::cast_to<Node2D>(ci)) {
4559 Node2D *n2d = Object::cast_to<Node2D>(ci);
4560
4561 if (key_pos) {
4562 n2d->set_position(Vector2());
4563 }
4564 if (key_rot) {
4565 n2d->set_rotation(0);
4566 }
4567 if (key_scale) {
4568 n2d->set_scale(Vector2(1, 1));
4569 }
4570 } else if (Object::cast_to<Control>(ci)) {
4571 Control *ctrl = Object::cast_to<Control>(ci);
4572
4573 if (key_pos) {
4574 ctrl->set_position(Point2());
4575 }
4576 }
4577 }
4578
4579 } break;
4580 case CLEAR_GUIDES: {
4581 Node *const root = EditorNode::get_singleton()->get_edited_scene();
4582
4583 if (root && (root->has_meta("_edit_horizontal_guides_") || root->has_meta("_edit_vertical_guides_"))) {
4584 undo_redo->create_action(TTR("Clear Guides"));
4585 if (root->has_meta("_edit_horizontal_guides_")) {
4586 Array hguides = root->get_meta("_edit_horizontal_guides_");
4587
4588 undo_redo->add_do_method(root, "remove_meta", "_edit_horizontal_guides_");
4589 undo_redo->add_undo_method(root, "set_meta", "_edit_horizontal_guides_", hguides);
4590 }
4591 if (root->has_meta("_edit_vertical_guides_")) {
4592 Array vguides = root->get_meta("_edit_vertical_guides_");
4593
4594 undo_redo->add_do_method(root, "remove_meta", "_edit_vertical_guides_");
4595 undo_redo->add_undo_method(root, "set_meta", "_edit_vertical_guides_", vguides);
4596 }
4597 undo_redo->add_do_method(viewport, "queue_redraw");
4598 undo_redo->add_undo_method(viewport, "queue_redraw");
4599 undo_redo->commit_action();
4600 }
4601
4602 } break;
4603 case VIEW_CENTER_TO_SELECTION:
4604 case VIEW_FRAME_TO_SELECTION: {
4605 _focus_selection(p_op);
4606
4607 } break;
4608 case PREVIEW_CANVAS_SCALE: {
4609 bool preview = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE));
4610 preview = !preview;
4611 RS::get_singleton()->canvas_set_disable_scale(!preview);
4612 view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(PREVIEW_CANVAS_SCALE), preview);
4613
4614 } break;
4615 case SKELETON_MAKE_BONES: {
4616 HashMap<Node *, Object *> &selection = editor_selection->get_selection();
4617 Node *editor_root = get_tree()->get_edited_scene_root();
4618
4619 if (!editor_root || selection.is_empty()) {
4620 return;
4621 }
4622
4623 undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)"));
4624 for (const KeyValue<Node *, Object *> &E : selection) {
4625 Node2D *n2d = Object::cast_to<Node2D>(E.key);
4626 if (!n2d) {
4627 continue;
4628 }
4629
4630 Bone2D *new_bone = memnew(Bone2D);
4631 String new_bone_name = n2d->get_name();
4632 new_bone_name += "Bone2D";
4633 new_bone->set_name(new_bone_name);
4634 new_bone->set_transform(n2d->get_transform());
4635
4636 Node *n2d_parent = n2d->get_parent();
4637 if (!n2d_parent) {
4638 continue;
4639 }
4640
4641 undo_redo->add_do_method(n2d_parent, "add_child", new_bone);
4642 undo_redo->add_do_method(n2d_parent, "remove_child", n2d);
4643 undo_redo->add_do_method(new_bone, "add_child", n2d);
4644 undo_redo->add_do_method(n2d, "set_transform", Transform2D());
4645 undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root);
4646
4647 undo_redo->add_undo_method(new_bone, "remove_child", n2d);
4648 undo_redo->add_undo_method(n2d_parent, "add_child", n2d);
4649 undo_redo->add_undo_method(n2d, "set_transform", new_bone->get_transform());
4650 undo_redo->add_undo_method(new_bone, "queue_free");
4651 undo_redo->add_undo_method(this, "_set_owner_for_node_and_children", n2d, editor_root);
4652 }
4653 undo_redo->commit_action();
4654
4655 } break;
4656 }
4657}
4658
4659void CanvasItemEditor::_set_owner_for_node_and_children(Node *p_node, Node *p_owner) {
4660 p_node->set_owner(p_owner);
4661 for (int i = 0; i < p_node->get_child_count(); i++) {
4662 _set_owner_for_node_and_children(p_node->get_child(i), p_owner);
4663 }
4664}
4665
4666void CanvasItemEditor::_focus_selection(int p_op) {
4667 Vector2 center(0.f, 0.f);
4668 Rect2 rect;
4669 int count = 0;
4670
4671 const HashMap<Node *, Object *> &selection = editor_selection->get_selection();
4672 for (const KeyValue<Node *, Object *> &E : selection) {
4673 CanvasItem *ci = Object::cast_to<CanvasItem>(E.key);
4674 if (!ci) {
4675 continue;
4676 }
4677 if (ci->get_viewport() != EditorNode::get_singleton()->get_scene_root()) {
4678 continue;
4679 }
4680
4681 // counting invisible items, for now
4682 //if (!ci->is_visible_in_tree()) continue;
4683 ++count;
4684
4685 Rect2 item_rect;
4686 if (ci->_edit_use_rect()) {
4687 item_rect = ci->_edit_get_rect();
4688 } else {
4689 item_rect = Rect2();
4690 }
4691
4692 Vector2 pos = ci->get_global_transform().get_origin();
4693 Vector2 scale = ci->get_global_transform().get_scale();
4694 real_t angle = ci->get_global_transform().get_rotation();
4695
4696 Transform2D t(angle, Vector2(0.f, 0.f));
4697 item_rect = t.xform(item_rect);
4698 Rect2 canvas_item_rect(pos + scale * item_rect.position, scale * item_rect.size);
4699 if (count == 1) {
4700 rect = canvas_item_rect;
4701 } else {
4702 rect = rect.merge(canvas_item_rect);
4703 }
4704 }
4705
4706 if (p_op == VIEW_FRAME_TO_SELECTION && rect.size.x > CMP_EPSILON && rect.size.y > CMP_EPSILON) {
4707 real_t scale_x = viewport->get_size().x / rect.size.x;
4708 real_t scale_y = viewport->get_size().y / rect.size.y;
4709 zoom = scale_x < scale_y ? scale_x : scale_y;
4710 zoom *= 0.90;
4711 zoom_widget->set_zoom(zoom);
4712 viewport->queue_redraw(); // Redraw to update the global canvas transform after zoom changes.
4713 call_deferred(SNAME("center_at"), rect.get_center()); // Defer because the updated transform is needed.
4714 } else {
4715 center_at(rect.get_center());
4716 }
4717}
4718
4719void CanvasItemEditor::_reset_drag() {
4720 drag_type = DRAG_NONE;
4721 drag_selection.clear();
4722}
4723
4724void CanvasItemEditor::_bind_methods() {
4725 ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data);
4726
4727 ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);
4728 ClassDB::bind_method(D_METHOD("center_at", "position"), &CanvasItemEditor::center_at);
4729
4730 ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children);
4731
4732 ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
4733 ADD_SIGNAL(MethodInfo("item_group_status_changed"));
4734}
4735
4736Dictionary CanvasItemEditor::get_state() const {
4737 Dictionary state;
4738 // Take the editor scale into account.
4739 state["zoom"] = zoom / MAX(1, EDSCALE);
4740 state["ofs"] = view_offset;
4741 state["grid_offset"] = grid_offset;
4742 state["grid_step"] = grid_step;
4743 state["primary_grid_step"] = primary_grid_step;
4744 state["snap_rotation_offset"] = snap_rotation_offset;
4745 state["snap_rotation_step"] = snap_rotation_step;
4746 state["snap_scale_step"] = snap_scale_step;
4747 state["smart_snap_active"] = smart_snap_active;
4748 state["grid_snap_active"] = grid_snap_active;
4749 state["snap_node_parent"] = snap_node_parent;
4750 state["snap_node_anchors"] = snap_node_anchors;
4751 state["snap_node_sides"] = snap_node_sides;
4752 state["snap_node_center"] = snap_node_center;
4753 state["snap_other_nodes"] = snap_other_nodes;
4754 state["snap_guides"] = snap_guides;
4755 state["grid_visibility"] = grid_visibility;
4756 state["show_origin"] = show_origin;
4757 state["show_viewport"] = show_viewport;
4758 state["show_rulers"] = show_rulers;
4759 state["show_guides"] = show_guides;
4760 state["show_helpers"] = show_helpers;
4761 state["show_zoom_control"] = zoom_widget->is_visible();
4762 state["show_edit_locks"] = show_edit_locks;
4763 state["show_transformation_gizmos"] = show_transformation_gizmos;
4764 state["snap_rotation"] = snap_rotation;
4765 state["snap_scale"] = snap_scale;
4766 state["snap_relative"] = snap_relative;
4767 state["snap_pixel"] = snap_pixel;
4768 return state;
4769}
4770
4771void CanvasItemEditor::set_state(const Dictionary &p_state) {
4772 bool update_scrollbars = false;
4773 Dictionary state = p_state;
4774 if (state.has("zoom")) {
4775 // Compensate the editor scale, so that the editor scale can be changed
4776 // and the zoom level will still be the same (relative to the editor scale).
4777 zoom = real_t(p_state["zoom"]) * MAX(1, EDSCALE);
4778 zoom_widget->set_zoom(zoom);
4779 }
4780
4781 if (state.has("ofs")) {
4782 view_offset = p_state["ofs"];
4783 previous_update_view_offset = view_offset;
4784 update_scrollbars = true;
4785 }
4786
4787 if (state.has("grid_offset")) {
4788 grid_offset = state["grid_offset"];
4789 }
4790
4791 if (state.has("grid_step")) {
4792 grid_step = state["grid_step"];
4793 }
4794
4795#ifndef DISABLE_DEPRECATED
4796 if (state.has("primary_grid_steps")) {
4797 primary_grid_step.x = state["primary_grid_steps"];
4798 primary_grid_step.y = state["primary_grid_steps"];
4799 }
4800#endif // DISABLE_DEPRECATED
4801
4802 if (state.has("primary_grid_step")) {
4803 primary_grid_step = state["primary_grid_step"];
4804 }
4805
4806 if (state.has("snap_rotation_step")) {
4807 snap_rotation_step = state["snap_rotation_step"];
4808 }
4809
4810 if (state.has("snap_rotation_offset")) {
4811 snap_rotation_offset = state["snap_rotation_offset"];
4812 }
4813
4814 if (state.has("snap_scale_step")) {
4815 snap_scale_step = state["snap_scale_step"];
4816 }
4817
4818 if (state.has("smart_snap_active")) {
4819 smart_snap_active = state["smart_snap_active"];
4820 smart_snap_button->set_pressed(smart_snap_active);
4821 }
4822
4823 if (state.has("grid_snap_active")) {
4824 grid_snap_active = state["grid_snap_active"];
4825 grid_snap_button->set_pressed(grid_snap_active);
4826 }
4827
4828 if (state.has("snap_node_parent")) {
4829 snap_node_parent = state["snap_node_parent"];
4830 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_PARENT);
4831 smartsnap_config_popup->set_item_checked(idx, snap_node_parent);
4832 }
4833
4834 if (state.has("snap_node_anchors")) {
4835 snap_node_anchors = state["snap_node_anchors"];
4836 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_ANCHORS);
4837 smartsnap_config_popup->set_item_checked(idx, snap_node_anchors);
4838 }
4839
4840 if (state.has("snap_node_sides")) {
4841 snap_node_sides = state["snap_node_sides"];
4842 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_SIDES);
4843 smartsnap_config_popup->set_item_checked(idx, snap_node_sides);
4844 }
4845
4846 if (state.has("snap_node_center")) {
4847 snap_node_center = state["snap_node_center"];
4848 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_NODE_CENTER);
4849 smartsnap_config_popup->set_item_checked(idx, snap_node_center);
4850 }
4851
4852 if (state.has("snap_other_nodes")) {
4853 snap_other_nodes = state["snap_other_nodes"];
4854 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_OTHER_NODES);
4855 smartsnap_config_popup->set_item_checked(idx, snap_other_nodes);
4856 }
4857
4858 if (state.has("snap_guides")) {
4859 snap_guides = state["snap_guides"];
4860 int idx = smartsnap_config_popup->get_item_index(SNAP_USE_GUIDES);
4861 smartsnap_config_popup->set_item_checked(idx, snap_guides);
4862 }
4863
4864 if (state.has("grid_visibility")) {
4865 grid_visibility = (GridVisibility)(int)(state["grid_visibility"]);
4866 }
4867
4868 if (state.has("show_origin")) {
4869 show_origin = state["show_origin"];
4870 int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN);
4871 view_menu->get_popup()->set_item_checked(idx, show_origin);
4872 }
4873
4874 if (state.has("show_viewport")) {
4875 show_viewport = state["show_viewport"];
4876 int idx = view_menu->get_popup()->get_item_index(SHOW_VIEWPORT);
4877 view_menu->get_popup()->set_item_checked(idx, show_viewport);
4878 }
4879
4880 if (state.has("show_rulers")) {
4881 show_rulers = state["show_rulers"];
4882 int idx = view_menu->get_popup()->get_item_index(SHOW_RULERS);
4883 view_menu->get_popup()->set_item_checked(idx, show_rulers);
4884 update_scrollbars = true;
4885 }
4886
4887 if (state.has("show_guides")) {
4888 show_guides = state["show_guides"];
4889 int idx = view_menu->get_popup()->get_item_index(SHOW_GUIDES);
4890 view_menu->get_popup()->set_item_checked(idx, show_guides);
4891 }
4892
4893 if (state.has("show_helpers")) {
4894 show_helpers = state["show_helpers"];
4895 int idx = view_menu->get_popup()->get_item_index(SHOW_HELPERS);
4896 view_menu->get_popup()->set_item_checked(idx, show_helpers);
4897 }
4898
4899 if (state.has("show_edit_locks")) {
4900 show_edit_locks = state["show_edit_locks"];
4901 int idx = view_menu->get_popup()->get_item_index(SHOW_EDIT_LOCKS);
4902 view_menu->get_popup()->set_item_checked(idx, show_edit_locks);
4903 }
4904
4905 if (state.has("show_transformation_gizmos")) {
4906 show_transformation_gizmos = state["show_transformation_gizmos"];
4907 int idx = view_menu->get_popup()->get_item_index(SHOW_TRANSFORMATION_GIZMOS);
4908 view_menu->get_popup()->set_item_checked(idx, show_transformation_gizmos);
4909 }
4910
4911 if (state.has("show_zoom_control")) {
4912 // This one is not user-controllable, but instrumentable
4913 zoom_widget->set_visible(state["show_zoom_control"]);
4914 }
4915
4916 if (state.has("snap_rotation")) {
4917 snap_rotation = state["snap_rotation"];
4918 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_ROTATION);
4919 snap_config_menu->get_popup()->set_item_checked(idx, snap_rotation);
4920 }
4921
4922 if (state.has("snap_scale")) {
4923 snap_scale = state["snap_scale"];
4924 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_SCALE);
4925 snap_config_menu->get_popup()->set_item_checked(idx, snap_scale);
4926 }
4927
4928 if (state.has("snap_relative")) {
4929 snap_relative = state["snap_relative"];
4930 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_RELATIVE);
4931 snap_config_menu->get_popup()->set_item_checked(idx, snap_relative);
4932 }
4933
4934 if (state.has("snap_pixel")) {
4935 snap_pixel = state["snap_pixel"];
4936 int idx = snap_config_menu->get_popup()->get_item_index(SNAP_USE_PIXEL);
4937 snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel);
4938 }
4939
4940 if (update_scrollbars) {
4941 _update_scrollbars();
4942 }
4943 viewport->queue_redraw();
4944}
4945
4946void CanvasItemEditor::clear() {
4947 zoom = 1.0 / MAX(1, EDSCALE);
4948 zoom_widget->set_zoom(zoom);
4949
4950 view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH);
4951 previous_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.
4952 _update_scrollbars();
4953
4954 grid_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_offset", Vector2());
4955 grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "grid_step", Vector2(8, 8));
4956 primary_grid_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "primary_grid_step", Vector2i(8, 8));
4957 snap_rotation_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_step", Math::deg_to_rad(15.0));
4958 snap_rotation_offset = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_rotation_offset", 0.0);
4959 snap_scale_step = EditorSettings::get_singleton()->get_project_metadata("2d_editor", "snap_scale_step", 0.1);
4960}
4961
4962void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) {
4963 ERR_FAIL_NULL(p_control);
4964 ERR_FAIL_COND(p_control->get_parent());
4965
4966 VSeparator *sep = memnew(VSeparator);
4967 context_toolbar_hbox->add_child(sep);
4968 context_toolbar_hbox->add_child(p_control);
4969 context_toolbar_separators[p_control] = sep;
4970
4971 p_control->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_update_context_toolbar));
4972
4973 _update_context_toolbar();
4974}
4975
4976void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) {
4977 ERR_FAIL_NULL(p_control);
4978 ERR_FAIL_COND(p_control->get_parent() != context_toolbar_hbox);
4979
4980 p_control->disconnect("visibility_changed", callable_mp(this, &CanvasItemEditor::_update_context_toolbar));
4981
4982 context_toolbar_hbox->remove_child(context_toolbar_separators[p_control]);
4983 context_toolbar_hbox->remove_child(p_control);
4984 context_toolbar_separators.erase(p_control);
4985
4986 _update_context_toolbar();
4987}
4988
4989void CanvasItemEditor::_update_context_toolbar() {
4990 bool has_visible = false;
4991 bool first_visible = false;
4992
4993 for (int i = 0; i < context_toolbar_hbox->get_child_count(); i++) {
4994 Control *child = Object::cast_to<Control>(context_toolbar_hbox->get_child(i));
4995 if (!child || !context_toolbar_separators.has(child)) {
4996 continue;
4997 }
4998 if (child->is_visible()) {
4999 first_visible = !has_visible;
5000 has_visible = true;
5001 }
5002
5003 VSeparator *sep = context_toolbar_separators[child];
5004 sep->set_visible(!first_visible && child->is_visible());
5005 }
5006
5007 context_toolbar_panel->set_visible(has_visible);
5008}
5009
5010void CanvasItemEditor::add_control_to_left_panel(Control *p_control) {
5011 left_panel_split->add_child(p_control);
5012 left_panel_split->move_child(p_control, 0);
5013}
5014
5015void CanvasItemEditor::add_control_to_right_panel(Control *p_control) {
5016 right_panel_split->add_child(p_control);
5017 right_panel_split->move_child(p_control, 1);
5018}
5019
5020void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) {
5021 left_panel_split->remove_child(p_control);
5022}
5023
5024void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) {
5025 right_panel_split->remove_child(p_control);
5026}
5027
5028VSplitContainer *CanvasItemEditor::get_bottom_split() {
5029 return bottom_split;
5030}
5031
5032void CanvasItemEditor::focus_selection() {
5033 _focus_selection(VIEW_CENTER_TO_SELECTION);
5034}
5035
5036void CanvasItemEditor::center_at(const Point2 &p_pos) {
5037 Vector2 offset = viewport->get_size() / 2 - EditorNode::get_singleton()->get_scene_root()->get_global_canvas_transform().xform(p_pos);
5038 view_offset -= (offset / zoom).round();
5039 update_viewport();
5040}
5041
5042CanvasItemEditor::CanvasItemEditor() {
5043 snap_target[0] = SNAP_TARGET_NONE;
5044 snap_target[1] = SNAP_TARGET_NONE;
5045
5046 editor_selection = EditorNode::get_singleton()->get_editor_selection();
5047 editor_selection->add_editor_plugin(this);
5048 editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw));
5049 editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed));
5050
5051 SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created));
5052 SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position));
5053
5054 EditorRunBar::get_singleton()->call_deferred(SNAME("connect"), "play_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(true));
5055 EditorRunBar::get_singleton()->call_deferred(SNAME("connect"), "stop_pressed", callable_mp(this, &CanvasItemEditor::_update_override_camera_button).bind(false));
5056
5057 // Add some margin to the sides for better aesthetics.
5058 // This prevents the first button's hover/pressed effect from "touching" the panel's border,
5059 // which looks ugly.
5060 MarginContainer *toolbar_margin = memnew(MarginContainer);
5061 toolbar_margin->add_theme_constant_override("margin_left", 4 * EDSCALE);
5062 toolbar_margin->add_theme_constant_override("margin_right", 4 * EDSCALE);
5063 add_child(toolbar_margin);
5064
5065 // A fluid container for all toolbars.
5066 HFlowContainer *main_flow = memnew(HFlowContainer);
5067 toolbar_margin->add_child(main_flow);
5068
5069 // Main toolbars.
5070 HBoxContainer *main_menu_hbox = memnew(HBoxContainer);
5071 main_menu_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5072 main_flow->add_child(main_menu_hbox);
5073
5074 bottom_split = memnew(VSplitContainer);
5075 add_child(bottom_split);
5076 bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5077
5078 left_panel_split = memnew(HSplitContainer);
5079 bottom_split->add_child(left_panel_split);
5080 left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5081
5082 right_panel_split = memnew(HSplitContainer);
5083 left_panel_split->add_child(right_panel_split);
5084 right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5085
5086 viewport_scrollable = memnew(Control);
5087 right_panel_split->add_child(viewport_scrollable);
5088 viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS);
5089 viewport_scrollable->set_clip_contents(true);
5090 viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5091 viewport_scrollable->set_h_size_flags(Control::SIZE_EXPAND_FILL);
5092 viewport_scrollable->connect("draw", callable_mp(this, &CanvasItemEditor::_update_scrollbars));
5093
5094 SubViewportContainer *scene_tree = memnew(SubViewportContainer);
5095 viewport_scrollable->add_child(scene_tree);
5096 scene_tree->set_stretch(true);
5097 scene_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5098 scene_tree->add_child(EditorNode::get_singleton()->get_scene_root());
5099
5100 controls_vb = memnew(VBoxContainer);
5101 controls_vb->set_begin(Point2(5, 5));
5102
5103 // To ensure that scripts can parse the list of shortcuts correctly, we have to define
5104 // those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget).
5105 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"),
5106 { int32_t(KeyModifierMask::SHIFT | Key::KEY_5), int32_t(KeyModifierMask::SHIFT | Key::KP_5) });
5107
5108 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"),
5109 { int32_t(KeyModifierMask::SHIFT | Key::KEY_4), int32_t(KeyModifierMask::SHIFT | Key::KP_4) });
5110
5111 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"),
5112 { int32_t(KeyModifierMask::SHIFT | Key::KEY_3), int32_t(KeyModifierMask::SHIFT | Key::KP_3) });
5113
5114 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"),
5115 { int32_t(KeyModifierMask::SHIFT | Key::KEY_2), int32_t(KeyModifierMask::SHIFT | Key::KP_2) });
5116
5117 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"),
5118 { int32_t(KeyModifierMask::SHIFT | Key::KEY_1), int32_t(KeyModifierMask::SHIFT | Key::KP_1) });
5119
5120 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"),
5121 { 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) });
5122
5123 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"),
5124 { int32_t(Key::KEY_2), int32_t(Key::KP_2) });
5125
5126 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"),
5127 { int32_t(Key::KEY_3), int32_t(Key::KP_3) });
5128
5129 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"),
5130 { int32_t(Key::KEY_4), int32_t(Key::KP_4) });
5131
5132 ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"),
5133 { int32_t(Key::KEY_5), int32_t(Key::KP_5) });
5134
5135 HBoxContainer *controls_hb = memnew(HBoxContainer);
5136 controls_vb->add_child(controls_hb);
5137
5138 button_center_view = memnew(Button);
5139 controls_hb->add_child(button_center_view);
5140 button_center_view->set_flat(true);
5141 button_center_view->set_tooltip_text(TTR("Center View"));
5142 button_center_view->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(VIEW_CENTER_TO_SELECTION));
5143
5144 zoom_widget = memnew(EditorZoomWidget);
5145 controls_hb->add_child(zoom_widget);
5146 zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE);
5147 zoom_widget->set_shortcut_context(this);
5148 zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));
5149
5150 panner.instantiate();
5151 panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));
5152
5153 viewport = memnew(CanvasItemEditorViewport(this));
5154 viewport_scrollable->add_child(viewport);
5155 viewport->set_mouse_filter(MOUSE_FILTER_PASS);
5156 viewport->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5157 viewport->set_clip_contents(true);
5158 viewport->set_focus_mode(FOCUS_ALL);
5159 viewport->connect("draw", callable_mp(this, &CanvasItemEditor::_draw_viewport));
5160 viewport->connect("gui_input", callable_mp(this, &CanvasItemEditor::_gui_input_viewport));
5161 viewport->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key));
5162
5163 h_scroll = memnew(HScrollBar);
5164 viewport->add_child(h_scroll);
5165 h_scroll->connect("value_changed", callable_mp(this, &CanvasItemEditor::_update_scroll));
5166 h_scroll->hide();
5167
5168 v_scroll = memnew(VScrollBar);
5169 viewport->add_child(v_scroll);
5170 v_scroll->connect("value_changed", callable_mp(this, &CanvasItemEditor::_update_scroll));
5171 v_scroll->hide();
5172
5173 viewport->add_child(controls_vb);
5174
5175 select_button = memnew(Button);
5176 select_button->set_flat(true);
5177 main_menu_hbox->add_child(select_button);
5178 select_button->set_toggle_mode(true);
5179 select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SELECT));
5180 select_button->set_pressed(true);
5181 select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTR("Select Mode"), Key::Q));
5182 select_button->set_shortcut_context(this);
5183 select_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("RMB: Add node at position clicked."));
5184
5185 main_menu_hbox->add_child(memnew(VSeparator));
5186
5187 move_button = memnew(Button);
5188 move_button->set_flat(true);
5189 main_menu_hbox->add_child(move_button);
5190 move_button->set_toggle_mode(true);
5191 move_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_MOVE));
5192 move_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/move_mode", TTR("Move Mode"), Key::W));
5193 move_button->set_shortcut_context(this);
5194 move_button->set_tooltip_text(TTR("Move Mode"));
5195
5196 rotate_button = memnew(Button);
5197 rotate_button->set_flat(true);
5198 main_menu_hbox->add_child(rotate_button);
5199 rotate_button->set_toggle_mode(true);
5200 rotate_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_ROTATE));
5201 rotate_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/rotate_mode", TTR("Rotate Mode"), Key::E));
5202 rotate_button->set_shortcut_context(this);
5203 rotate_button->set_tooltip_text(TTR("Rotate Mode"));
5204
5205 scale_button = memnew(Button);
5206 scale_button->set_flat(true);
5207 main_menu_hbox->add_child(scale_button);
5208 scale_button->set_toggle_mode(true);
5209 scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SCALE));
5210 scale_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/scale_mode", TTR("Scale Mode"), Key::S));
5211 scale_button->set_shortcut_context(this);
5212 scale_button->set_tooltip_text(TTR("Shift: Scale proportionally."));
5213
5214 main_menu_hbox->add_child(memnew(VSeparator));
5215
5216 list_select_button = memnew(Button);
5217 list_select_button->set_flat(true);
5218 main_menu_hbox->add_child(list_select_button);
5219 list_select_button->set_toggle_mode(true);
5220 list_select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_LIST_SELECT));
5221 list_select_button->set_tooltip_text(TTR("Show list of selectable nodes at position clicked."));
5222
5223 pivot_button = memnew(Button);
5224 pivot_button->set_flat(true);
5225 main_menu_hbox->add_child(pivot_button);
5226 pivot_button->set_toggle_mode(true);
5227 pivot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));
5228 pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot."));
5229
5230 pan_button = memnew(Button);
5231 pan_button->set_flat(true);
5232 main_menu_hbox->add_child(pan_button);
5233 pan_button->set_toggle_mode(true);
5234 pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_PAN));
5235 pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), Key::G));
5236 pan_button->set_shortcut_context(this);
5237 pan_button->set_tooltip_text(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode."));
5238
5239 ruler_button = memnew(Button);
5240 ruler_button->set_flat(true);
5241 main_menu_hbox->add_child(ruler_button);
5242 ruler_button->set_toggle_mode(true);
5243 ruler_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_RULER));
5244 ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTR("Ruler Mode"), Key::R));
5245 ruler_button->set_shortcut_context(this);
5246 ruler_button->set_tooltip_text(TTR("Ruler Mode"));
5247
5248 main_menu_hbox->add_child(memnew(VSeparator));
5249
5250 smart_snap_button = memnew(Button);
5251 smart_snap_button->set_flat(true);
5252 main_menu_hbox->add_child(smart_snap_button);
5253 smart_snap_button->set_toggle_mode(true);
5254 smart_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_smart_snap));
5255 smart_snap_button->set_tooltip_text(TTR("Toggle smart snapping."));
5256 smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S));
5257 smart_snap_button->set_shortcut_context(this);
5258
5259 grid_snap_button = memnew(Button);
5260 grid_snap_button->set_flat(true);
5261 main_menu_hbox->add_child(grid_snap_button);
5262 grid_snap_button->set_toggle_mode(true);
5263 grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap));
5264 grid_snap_button->set_tooltip_text(TTR("Toggle grid snapping."));
5265 grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G));
5266 grid_snap_button->set_shortcut_context(this);
5267
5268 snap_config_menu = memnew(MenuButton);
5269 snap_config_menu->set_shortcut_context(this);
5270 main_menu_hbox->add_child(snap_config_menu);
5271 snap_config_menu->set_h_size_flags(SIZE_SHRINK_END);
5272 snap_config_menu->set_tooltip_text(TTR("Snapping Options"));
5273 snap_config_menu->set_switch_on_hover(true);
5274
5275 PopupMenu *p = snap_config_menu->get_popup();
5276 p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
5277 p->set_hide_on_checkable_item_selection(false);
5278 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_rotation_snap", TTR("Use Rotation Snap")), SNAP_USE_ROTATION);
5279 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_scale_snap", TTR("Use Scale Snap")), SNAP_USE_SCALE);
5280 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_relative", TTR("Snap Relative")), SNAP_RELATIVE);
5281 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/use_pixel_snap", TTR("Use Pixel Snap")), SNAP_USE_PIXEL);
5282 p->add_submenu_item(TTR("Smart Snapping"), "SmartSnapping");
5283
5284 p->add_separator();
5285 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/configure_snap", TTR("Configure Snap...")), SNAP_CONFIGURE);
5286
5287 smartsnap_config_popup = memnew(PopupMenu);
5288 p->add_child(smartsnap_config_popup);
5289 smartsnap_config_popup->set_name("SmartSnapping");
5290 smartsnap_config_popup->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
5291 smartsnap_config_popup->set_hide_on_checkable_item_selection(false);
5292 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_parent", TTR("Snap to Parent")), SNAP_USE_NODE_PARENT);
5293 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_anchors", TTR("Snap to Node Anchor")), SNAP_USE_NODE_ANCHORS);
5294 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_sides", TTR("Snap to Node Sides")), SNAP_USE_NODE_SIDES);
5295 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_node_center", TTR("Snap to Node Center")), SNAP_USE_NODE_CENTER);
5296 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_other_nodes", TTR("Snap to Other Nodes")), SNAP_USE_OTHER_NODES);
5297 smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_guides", TTR("Snap to Guides")), SNAP_USE_GUIDES);
5298
5299 main_menu_hbox->add_child(memnew(VSeparator));
5300
5301 lock_button = memnew(Button);
5302 lock_button->set_flat(true);
5303 main_menu_hbox->add_child(lock_button);
5304
5305 lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(LOCK_SELECTED));
5306 lock_button->set_tooltip_text(TTR("Lock selected node, preventing selection and movement."));
5307 // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
5308 lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L));
5309
5310 unlock_button = memnew(Button);
5311 unlock_button->set_flat(true);
5312 main_menu_hbox->add_child(unlock_button);
5313 unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNLOCK_SELECTED));
5314 unlock_button->set_tooltip_text(TTR("Unlock selected node, allowing selection and movement."));
5315 // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
5316 unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L));
5317
5318 group_button = memnew(Button);
5319 group_button->set_flat(true);
5320 main_menu_hbox->add_child(group_button);
5321 group_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(GROUP_SELECTED));
5322 group_button->set_tooltip_text(TTR("Make selected node's children not selectable."));
5323 // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
5324 group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::G));
5325
5326 ungroup_button = memnew(Button);
5327 ungroup_button->set_flat(true);
5328 main_menu_hbox->add_child(ungroup_button);
5329 ungroup_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(UNGROUP_SELECTED));
5330 ungroup_button->set_tooltip_text(TTR("Make selected node's children selectable."));
5331 // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused.
5332 ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G));
5333
5334 main_menu_hbox->add_child(memnew(VSeparator));
5335
5336 skeleton_menu = memnew(MenuButton);
5337 skeleton_menu->set_shortcut_context(this);
5338 main_menu_hbox->add_child(skeleton_menu);
5339 skeleton_menu->set_tooltip_text(TTR("Skeleton Options"));
5340 skeleton_menu->set_switch_on_hover(true);
5341
5342 p = skeleton_menu->get_popup();
5343 p->set_hide_on_checkable_item_selection(false);
5344 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES);
5345 p->add_separator();
5346 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B), SKELETON_MAKE_BONES);
5347 p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
5348
5349 main_menu_hbox->add_child(memnew(VSeparator));
5350
5351 override_camera_button = memnew(Button);
5352 override_camera_button->set_flat(true);
5353 main_menu_hbox->add_child(override_camera_button);
5354 override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera));
5355 override_camera_button->set_toggle_mode(true);
5356 override_camera_button->set_disabled(true);
5357 _update_override_camera_button(false);
5358
5359 main_menu_hbox->add_child(memnew(VSeparator));
5360
5361 view_menu = memnew(MenuButton);
5362 // TRANSLATORS: Noun, name of the 2D/3D View menus.
5363 view_menu->set_text(TTR("View"));
5364 view_menu->set_switch_on_hover(true);
5365 view_menu->set_shortcut_context(this);
5366 main_menu_hbox->add_child(view_menu);
5367 view_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
5368
5369 p = view_menu->get_popup();
5370 p->set_hide_on_checkable_item_selection(false);
5371
5372 grid_menu = memnew(PopupMenu);
5373 grid_menu->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_grid_menu));
5374 grid_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_on_grid_menu_id_pressed));
5375 grid_menu->set_name("GridMenu");
5376 grid_menu->add_radio_check_item(TTR("Show"), GRID_VISIBILITY_SHOW);
5377 grid_menu->add_radio_check_item(TTR("Show When Snapping"), GRID_VISIBILITY_SHOW_WHEN_SNAPPING);
5378 grid_menu->add_radio_check_item(TTR("Hide"), GRID_VISIBILITY_HIDE);
5379 grid_menu->add_separator();
5380 grid_menu->add_shortcut(ED_SHORTCUT("canvas_item_editor/toggle_grid", TTR("Toggle Grid"), KeyModifierMask::CMD_OR_CTRL | Key::APOSTROPHE));
5381 p->add_child(grid_menu);
5382 p->add_submenu_item(TTR("Grid"), "GridMenu");
5383
5384 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), Key::H), SHOW_HELPERS);
5385 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS);
5386 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), Key::Y), SHOW_GUIDES);
5387 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTR("Show Origin")), SHOW_ORIGIN);
5388 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTR("Show Viewport")), SHOW_VIEWPORT);
5389 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_edit_locks", TTR("Show Group And Lock Icons")), SHOW_EDIT_LOCKS);
5390 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTR("Show Transformation Gizmos")), SHOW_TRANSFORMATION_GIZMOS);
5391
5392 p->add_separator();
5393 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTR("Center Selection"), Key::F), VIEW_CENTER_TO_SELECTION);
5394 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTR("Frame Selection"), KeyModifierMask::SHIFT | Key::F), VIEW_FRAME_TO_SELECTION);
5395 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/clear_guides", TTR("Clear Guides")), CLEAR_GUIDES);
5396 p->add_separator();
5397 p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale")), PREVIEW_CANVAS_SCALE);
5398
5399 theme_menu = memnew(PopupMenu);
5400 theme_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_switch_theme_preview));
5401 theme_menu->set_name("ThemeMenu");
5402 theme_menu->add_radio_check_item(TTR("Project theme"), THEME_PREVIEW_PROJECT);
5403 theme_menu->add_radio_check_item(TTR("Editor theme"), THEME_PREVIEW_EDITOR);
5404 theme_menu->add_radio_check_item(TTR("Default theme"), THEME_PREVIEW_DEFAULT);
5405 p->add_child(theme_menu);
5406 p->add_submenu_item(TTR("Preview Theme"), "ThemeMenu");
5407
5408 theme_preview = (ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", THEME_PREVIEW_PROJECT);
5409 for (int i = 0; i < THEME_PREVIEW_MAX; i++) {
5410 theme_menu->set_item_checked(i, i == theme_preview);
5411 }
5412
5413 main_menu_hbox->add_child(memnew(VSeparator));
5414
5415 // Contextual toolbars.
5416 context_toolbar_panel = memnew(PanelContainer);
5417 context_toolbar_hbox = memnew(HBoxContainer);
5418 context_toolbar_panel->add_child(context_toolbar_hbox);
5419 main_flow->add_child(context_toolbar_panel);
5420
5421 // Animation controls.
5422 animation_hb = memnew(HBoxContainer);
5423 add_control_to_menu_panel(animation_hb);
5424 animation_hb->hide();
5425
5426 key_loc_button = memnew(Button);
5427 key_loc_button->set_flat(true);
5428 key_loc_button->set_toggle_mode(true);
5429 key_loc_button->set_pressed(true);
5430 key_loc_button->set_focus_mode(FOCUS_NONE);
5431 key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_POS));
5432 key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys."));
5433 animation_hb->add_child(key_loc_button);
5434
5435 key_rot_button = memnew(Button);
5436 key_rot_button->set_flat(true);
5437 key_rot_button->set_toggle_mode(true);
5438 key_rot_button->set_pressed(true);
5439 key_rot_button->set_focus_mode(FOCUS_NONE);
5440 key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_ROT));
5441 key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys."));
5442 animation_hb->add_child(key_rot_button);
5443
5444 key_scale_button = memnew(Button);
5445 key_scale_button->set_flat(true);
5446 key_scale_button->set_toggle_mode(true);
5447 key_scale_button->set_focus_mode(FOCUS_NONE);
5448 key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_SCALE));
5449 key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys."));
5450 animation_hb->add_child(key_scale_button);
5451
5452 key_insert_button = memnew(Button);
5453 key_insert_button->set_flat(true);
5454 key_insert_button->set_focus_mode(FOCUS_NONE);
5455 key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback).bind(ANIM_INSERT_KEY));
5456 key_insert_button->set_tooltip_text(TTR("Insert keys (based on mask)."));
5457 key_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTR("Insert Key"), Key::INSERT));
5458 key_insert_button->set_shortcut_context(this);
5459 animation_hb->add_child(key_insert_button);
5460
5461 key_auto_insert_button = memnew(Button);
5462 key_auto_insert_button->set_flat(true);
5463 key_auto_insert_button->set_toggle_mode(true);
5464 key_auto_insert_button->set_focus_mode(FOCUS_NONE);
5465 key_auto_insert_button->set_tooltip_text(TTR("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."));
5466 key_auto_insert_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/anim_auto_insert_key", TTR("Auto Insert Key")));
5467 key_auto_insert_button->set_shortcut_context(this);
5468 animation_hb->add_child(key_auto_insert_button);
5469
5470 animation_menu = memnew(MenuButton);
5471 animation_menu->set_shortcut_context(this);
5472 animation_menu->set_tooltip_text(TTR("Animation Key and Pose Options"));
5473 animation_hb->add_child(animation_menu);
5474 animation_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback));
5475 animation_menu->set_switch_on_hover(true);
5476
5477 p = animation_menu->get_popup();
5478
5479 p->add_shortcut(ED_GET_SHORTCUT("canvas_item_editor/anim_insert_key"), ANIM_INSERT_KEY);
5480 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTR("Insert Key (Existing Tracks)"), KeyModifierMask::CMD_OR_CTRL + Key::INSERT), ANIM_INSERT_KEY_EXISTING);
5481 p->add_separator();
5482 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_copy_pose", TTR("Copy Pose")), ANIM_COPY_POSE);
5483 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_paste_pose", TTR("Paste Pose")), ANIM_PASTE_POSE);
5484 p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_clear_pose", TTR("Clear Pose"), KeyModifierMask::SHIFT | Key::K), ANIM_CLEAR_POSE);
5485
5486 snap_dialog = memnew(SnapDialog);
5487 snap_dialog->connect("confirmed", callable_mp(this, &CanvasItemEditor::_snap_changed));
5488 add_child(snap_dialog);
5489
5490 select_sb = Ref<StyleBoxTexture>(memnew(StyleBoxTexture));
5491
5492 selection_menu = memnew(PopupMenu);
5493 add_child(selection_menu);
5494 selection_menu->set_min_size(Vector2(100, 0));
5495 selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed));
5496 selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED);
5497
5498 add_node_menu = memnew(PopupMenu);
5499 add_child(add_node_menu);
5500 add_node_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_add_node_pressed));
5501
5502 multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), Key::KP_MULTIPLY);
5503 divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE);
5504
5505 skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true);
5506
5507 // Store the singleton instance.
5508 singleton = this;
5509
5510 set_process_shortcut_input(true);
5511 clear(); // Make sure values are initialized.
5512
5513 // Update the menus' checkboxes.
5514 callable_mp(this, &CanvasItemEditor::set_state).bind(get_state()).call_deferred();
5515}
5516
5517CanvasItemEditor *CanvasItemEditor::singleton = nullptr;
5518
5519void CanvasItemEditorPlugin::edit(Object *p_object) {
5520 canvas_item_editor->edit(Object::cast_to<CanvasItem>(p_object));
5521}
5522
5523bool CanvasItemEditorPlugin::handles(Object *p_object) const {
5524 return p_object->is_class("CanvasItem");
5525}
5526
5527void CanvasItemEditorPlugin::make_visible(bool p_visible) {
5528 if (p_visible) {
5529 canvas_item_editor->show();
5530 canvas_item_editor->set_physics_process(true);
5531 RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), false);
5532 RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_ENABLED);
5533
5534 } else {
5535 canvas_item_editor->hide();
5536 canvas_item_editor->set_physics_process(false);
5537 RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), true);
5538 RenderingServer::get_singleton()->viewport_set_environment_mode(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), RS::VIEWPORT_ENVIRONMENT_DISABLED);
5539 }
5540}
5541
5542Dictionary CanvasItemEditorPlugin::get_state() const {
5543 return canvas_item_editor->get_state();
5544}
5545
5546void CanvasItemEditorPlugin::set_state(const Dictionary &p_state) {
5547 canvas_item_editor->set_state(p_state);
5548}
5549
5550void CanvasItemEditorPlugin::clear() {
5551 canvas_item_editor->clear();
5552}
5553
5554void CanvasItemEditorPlugin::_notification(int p_what) {
5555 switch (p_what) {
5556 case NOTIFICATION_ENTER_TREE: {
5557 connect("scene_changed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));
5558 connect("scene_closed", callable_mp((CanvasItem *)canvas_item_editor->get_viewport_control(), &CanvasItem::queue_redraw).unbind(1));
5559 } break;
5560 }
5561}
5562
5563CanvasItemEditorPlugin::CanvasItemEditorPlugin() {
5564 canvas_item_editor = memnew(CanvasItemEditor);
5565 canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
5566 EditorNode::get_singleton()->get_main_screen_control()->add_child(canvas_item_editor);
5567 canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
5568 canvas_item_editor->hide();
5569}
5570
5571CanvasItemEditorPlugin::~CanvasItemEditorPlugin() {
5572}
5573
5574void CanvasItemEditorViewport::_on_mouse_exit() {
5575 if (!selector->is_visible()) {
5576 _remove_preview();
5577 }
5578}
5579
5580void CanvasItemEditorViewport::_on_select_type(Object *selected) {
5581 CheckBox *check = Object::cast_to<CheckBox>(selected);
5582 String type = check->get_text();
5583 selector->set_title(vformat(TTR("Add %s"), type));
5584 label->set_text(vformat(TTR("Adding %s..."), type));
5585}
5586
5587void CanvasItemEditorViewport::_on_change_type_confirmed() {
5588 if (!button_group->get_pressed_button()) {
5589 return;
5590 }
5591
5592 CheckBox *check = Object::cast_to<CheckBox>(button_group->get_pressed_button());
5593 default_texture_node_type = check->get_text();
5594 _perform_drop_data();
5595 selector->hide();
5596}
5597
5598void CanvasItemEditorViewport::_on_change_type_closed() {
5599 _remove_preview();
5600}
5601
5602void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) const {
5603 bool add_preview = false;
5604 for (int i = 0; i < files.size(); i++) {
5605 String path = files[i];
5606 Ref<Resource> res = ResourceLoader::load(path);
5607 ERR_FAIL_COND(res.is_null());
5608 Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res));
5609 Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
5610 if (texture != nullptr || scene != nullptr) {
5611 bool root_node_selected = EditorNode::get_singleton()->get_editor_selection()->is_selected(EditorNode::get_singleton()->get_edited_scene());
5612 String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + TTR("Hold Ctrl when dropping to add as child of selected node.");
5613 if (!root_node_selected) {
5614 desc += "\n" + TTR("Hold Shift when dropping to add as sibling of selected node.");
5615 }
5616 if (texture != nullptr) {
5617 Sprite2D *sprite = memnew(Sprite2D);
5618 sprite->set_texture(texture);
5619 sprite->set_modulate(Color(1, 1, 1, 0.7f));
5620 preview_node->add_child(sprite);
5621 label->show();
5622 label_desc->show();
5623 desc += "\n" + TTR("Hold Alt when dropping to add as a different node type.");
5624 label_desc->set_text(desc);
5625 } else {
5626 if (scene.is_valid()) {
5627 Node *instance = scene->instantiate();
5628 if (instance) {
5629 preview_node->add_child(instance);
5630 label_desc->show();
5631 label_desc->set_text(desc);
5632 }
5633 }
5634 }
5635 add_preview = true;
5636 }
5637 }
5638
5639 if (add_preview) {
5640 EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);
5641 }
5642}
5643
5644void CanvasItemEditorViewport::_remove_preview() {
5645 if (preview_node->get_parent()) {
5646 for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {
5647 Node *node = preview_node->get_child(i);
5648 node->queue_free();
5649 preview_node->remove_child(node);
5650 }
5651 EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);
5652
5653 label->hide();
5654 label_desc->hide();
5655 }
5656}
5657
5658bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) {
5659 if (p_desired_node->get_scene_file_path() == p_target_scene_path) {
5660 return true;
5661 }
5662
5663 int childCount = p_desired_node->get_child_count();
5664 for (int i = 0; i < childCount; i++) {
5665 Node *child = p_desired_node->get_child(i);
5666 if (_cyclical_dependency_exists(p_target_scene_path, child)) {
5667 return true;
5668 }
5669 }
5670 return false;
5671}
5672
5673void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &path, const Point2 &p_point) {
5674 // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others.
5675 String name = path.get_file().get_basename();
5676 child->set_name(Node::adjust_name_casing(name));
5677
5678 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5679 Ref<Texture2D> texture = ResourceCache::get_ref(path);
5680
5681 if (parent) {
5682 undo_redo->add_do_method(parent, "add_child", child, true);
5683 undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
5684 undo_redo->add_do_reference(child);
5685 undo_redo->add_undo_method(parent, "remove_child", child);
5686 } else { // If no parent is selected, set as root node of the scene.
5687 undo_redo->add_do_method(EditorNode::get_singleton(), "set_edited_scene", child);
5688 undo_redo->add_do_method(child, "set_owner", EditorNode::get_singleton()->get_edited_scene());
5689 undo_redo->add_do_reference(child);
5690 undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr);
5691 }
5692
5693 if (parent) {
5694 String new_name = parent->validate_child_name(child);
5695 EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
5696 undo_redo->add_do_method(ed, "live_debug_create_node", EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent), child->get_class(), new_name);
5697 undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(EditorNode::get_singleton()->get_edited_scene()->get_path_to(parent)) + "/" + new_name));
5698 }
5699
5700 if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) {
5701 undo_redo->add_do_property(child, "texture_normal", texture);
5702 } else {
5703 undo_redo->add_do_property(child, "texture", texture);
5704 }
5705
5706 // make visible for certain node type
5707 if (Object::cast_to<Control>(child)) {
5708 Size2 texture_size = texture->get_size();
5709 undo_redo->add_do_property(child, "size", texture_size);
5710 } else if (Object::cast_to<Polygon2D>(child)) {
5711 Size2 texture_size = texture->get_size();
5712 Vector<Vector2> list = {
5713 Vector2(0, 0),
5714 Vector2(texture_size.width, 0),
5715 Vector2(texture_size.width, texture_size.height),
5716 Vector2(0, texture_size.height)
5717 };
5718 undo_redo->add_do_property(child, "polygon", list);
5719 }
5720
5721 // Compute the global position
5722 Transform2D xform = canvas_item_editor->get_canvas_transform();
5723 Point2 target_position = xform.affine_inverse().xform(p_point);
5724
5725 // Adjust position for Control and TouchScreenButton
5726 if (Object::cast_to<Control>(child) || Object::cast_to<TouchScreenButton>(child)) {
5727 target_position -= texture->get_size() / 2;
5728 }
5729
5730 // there's nothing to be used as source position so snapping will work as absolute if enabled
5731 target_position = canvas_item_editor->snap_point(target_position);
5732 undo_redo->add_do_method(child, "set_global_position", target_position);
5733}
5734
5735bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, const Point2 &p_point) {
5736 Ref<PackedScene> sdata = ResourceLoader::load(path);
5737 if (!sdata.is_valid()) { // invalid scene
5738 return false;
5739 }
5740
5741 Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
5742 if (!instantiated_scene) { // Error on instantiation.
5743 return false;
5744 }
5745
5746 Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
5747
5748 if (!edited_scene->get_scene_file_path().is_empty()) { // Cyclic instantiation.
5749 if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
5750 memdelete(instantiated_scene);
5751 return false;
5752 }
5753 }
5754
5755 instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path));
5756
5757 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5758 undo_redo->add_do_method(parent, "add_child", instantiated_scene, true);
5759 undo_redo->add_do_method(instantiated_scene, "set_owner", edited_scene);
5760 undo_redo->add_do_reference(instantiated_scene);
5761 undo_redo->add_undo_method(parent, "remove_child", instantiated_scene);
5762
5763 String new_name = parent->validate_child_name(instantiated_scene);
5764 EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
5765 undo_redo->add_do_method(ed, "live_debug_instantiate_node", edited_scene->get_path_to(parent), path, new_name);
5766 undo_redo->add_undo_method(ed, "live_debug_remove_node", NodePath(String(edited_scene->get_path_to(parent)) + "/" + new_name));
5767
5768 CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene);
5769 if (instance_ci) {
5770 Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point);
5771 target_pos = canvas_item_editor->snap_point(target_pos);
5772
5773 CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent);
5774 if (parent_ci) {
5775 target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos);
5776 }
5777 // Preserve instance position of the original scene.
5778 target_pos += instance_ci->_edit_get_position();
5779
5780 undo_redo->add_do_method(instantiated_scene, "set_position", target_pos);
5781 }
5782
5783 return true;
5784}
5785
5786void CanvasItemEditorViewport::_perform_drop_data() {
5787 _remove_preview();
5788
5789 // Without root dropping multiple files is not allowed
5790 if (!target_node && selected_files.size() > 1) {
5791 accept->set_text(TTR("Cannot instantiate multiple nodes without root."));
5792 accept->popup_centered();
5793 return;
5794 }
5795
5796 Vector<String> error_files;
5797
5798 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
5799 undo_redo->create_action(TTR("Create Node"));
5800
5801 for (int i = 0; i < selected_files.size(); i++) {
5802 String path = selected_files[i];
5803 Ref<Resource> res = ResourceLoader::load(path);
5804 if (res.is_null()) {
5805 continue;
5806 }
5807 Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
5808 if (scene != nullptr && scene.is_valid()) {
5809 if (!target_node) {
5810 // Without root node act the same as "Load Inherited Scene"
5811 Error err = EditorNode::get_singleton()->load_scene(path, false, true);
5812 if (err != OK) {
5813 error_files.push_back(path);
5814 }
5815 } else {
5816 bool success = _create_instance(target_node, path, drop_pos);
5817 if (!success) {
5818 error_files.push_back(path);
5819 }
5820 }
5821 } else {
5822 Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res));
5823 if (texture != nullptr && texture.is_valid()) {
5824 Node *child = Object::cast_to<Node>(ClassDB::instantiate(default_texture_node_type));
5825 _create_nodes(target_node, child, path, drop_pos);
5826 }
5827 }
5828 }
5829
5830 undo_redo->commit_action();
5831
5832 if (error_files.size() > 0) {
5833 String files_str;
5834 for (int i = 0; i < error_files.size(); i++) {
5835 files_str += error_files[i].get_file().get_basename() + ",";
5836 }
5837 files_str = files_str.substr(0, files_str.length() - 1);
5838 accept->set_text(vformat(TTR("Error instantiating scene from %s"), files_str.get_data()));
5839 accept->popup_centered();
5840 }
5841}
5842
5843bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
5844 Dictionary d = p_data;
5845 if (d.has("type")) {
5846 if (String(d["type"]) == "files") {
5847 Vector<String> files = d["files"];
5848 bool can_instantiate = false;
5849
5850 List<String> scene_extensions;
5851 ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions);
5852 List<String> texture_extensions;
5853 ResourceLoader::get_recognized_extensions_for_type("Texture2D", &texture_extensions);
5854
5855 for (int i = 0; i < files.size(); i++) {
5856 String extension = files[i].get_extension().to_lower();
5857
5858 // Check if dragged files with texture or scene extension can be created at least once.
5859 if (texture_extensions.find(extension) || scene_extensions.find(extension)) {
5860 Ref<Resource> res = ResourceLoader::load(files[i]);
5861 if (res.is_null()) {
5862 continue;
5863 }
5864 Ref<PackedScene> scn = res;
5865 if (scn.is_valid()) {
5866 Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
5867 if (!instantiated_scene) {
5868 continue;
5869 }
5870 memdelete(instantiated_scene);
5871 }
5872 can_instantiate = true;
5873 break;
5874 }
5875 }
5876 if (can_instantiate) {
5877 if (!preview_node->get_parent()) { // create preview only once
5878 _create_preview(files);
5879 }
5880 Transform2D trans = canvas_item_editor->get_canvas_transform();
5881 preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x);
5882 label->set_text(vformat(TTR("Adding %s..."), default_texture_node_type));
5883 }
5884 return can_instantiate;
5885 }
5886 }
5887 label->hide();
5888 return false;
5889}
5890
5891void CanvasItemEditorViewport::_show_resource_type_selector() {
5892 _remove_preview();
5893 List<BaseButton *> btn_list;
5894 button_group->get_buttons(&btn_list);
5895
5896 for (int i = 0; i < btn_list.size(); i++) {
5897 CheckBox *check = Object::cast_to<CheckBox>(btn_list[i]);
5898 check->set_pressed(check->get_text() == default_texture_node_type);
5899 }
5900 selector->set_title(vformat(TTR("Add %s"), default_texture_node_type));
5901 selector->popup_centered();
5902}
5903
5904bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
5905 for (int i = 0; i < selected_files.size(); ++i) {
5906 if (ResourceLoader::load(selected_files[i])->get_class() != "PackedScene") {
5907 return false;
5908 }
5909 }
5910
5911 return true;
5912}
5913
5914void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
5915 bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);
5916 bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);
5917 bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
5918
5919 selected_files.clear();
5920 Dictionary d = p_data;
5921 if (d.has("type") && String(d["type"]) == "files") {
5922 selected_files = d["files"];
5923 }
5924 if (selected_files.size() == 0) {
5925 return;
5926 }
5927
5928 List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list();
5929 Node *root_node = EditorNode::get_singleton()->get_edited_scene();
5930 if (selected_nodes.size() > 0) {
5931 Node *selected_node = selected_nodes[0];
5932 target_node = root_node;
5933 if (is_ctrl) {
5934 target_node = selected_node;
5935 } else if (is_shift && selected_node != root_node) {
5936 target_node = selected_node->get_parent();
5937 }
5938 } else {
5939 if (root_node) {
5940 target_node = root_node;
5941 } else {
5942 target_node = nullptr;
5943 }
5944 }
5945
5946 drop_pos = p_point;
5947
5948 if (is_alt && !_only_packed_scenes_selected()) {
5949 _show_resource_type_selector();
5950 } else {
5951 _perform_drop_data();
5952 }
5953}
5954
5955void CanvasItemEditorViewport::_update_theme() {
5956 List<BaseButton *> btn_list;
5957 button_group->get_buttons(&btn_list);
5958
5959 for (int i = 0; i < btn_list.size(); i++) {
5960 CheckBox *check = Object::cast_to<CheckBox>(btn_list[i]);
5961 check->set_icon(get_editor_theme_icon(check->get_text()));
5962 }
5963
5964 label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
5965}
5966
5967void CanvasItemEditorViewport::_notification(int p_what) {
5968 switch (p_what) {
5969 case NOTIFICATION_THEME_CHANGED: {
5970 _update_theme();
5971 } break;
5972
5973 case NOTIFICATION_ENTER_TREE: {
5974 _update_theme();
5975 connect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));
5976 } break;
5977
5978 case NOTIFICATION_EXIT_TREE: {
5979 disconnect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit));
5980 } break;
5981 }
5982}
5983
5984CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor) {
5985 default_texture_node_type = "Sprite2D";
5986 // Node2D
5987 texture_node_types.push_back("Sprite2D");
5988 texture_node_types.push_back("PointLight2D");
5989 texture_node_types.push_back("CPUParticles2D");
5990 texture_node_types.push_back("GPUParticles2D");
5991 texture_node_types.push_back("Polygon2D");
5992 texture_node_types.push_back("TouchScreenButton");
5993 // Control
5994 texture_node_types.push_back("TextureRect");
5995 texture_node_types.push_back("TextureButton");
5996 texture_node_types.push_back("NinePatchRect");
5997
5998 target_node = nullptr;
5999 canvas_item_editor = p_canvas_item_editor;
6000 preview_node = memnew(Control);
6001
6002 accept = memnew(AcceptDialog);
6003 EditorNode::get_singleton()->get_gui_base()->add_child(accept);
6004
6005 selector = memnew(AcceptDialog);
6006 EditorNode::get_singleton()->get_gui_base()->add_child(selector);
6007 selector->set_title(TTR("Change Default Type"));
6008 selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed));
6009 selector->connect("canceled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed));
6010
6011 VBoxContainer *vbc = memnew(VBoxContainer);
6012 selector->add_child(vbc);
6013 vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
6014 vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
6015 vbc->set_custom_minimum_size(Size2(240, 260) * EDSCALE);
6016
6017 btn_group = memnew(VBoxContainer);
6018 vbc->add_child(btn_group);
6019 btn_group->set_h_size_flags(SIZE_EXPAND_FILL);
6020
6021 button_group.instantiate();
6022 for (int i = 0; i < texture_node_types.size(); i++) {
6023 CheckBox *check = memnew(CheckBox);
6024 btn_group->add_child(check);
6025 check->set_text(texture_node_types[i]);
6026 check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check));
6027 check->set_button_group(button_group);
6028 }
6029
6030 label = memnew(Label);
6031 label->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1));
6032 label->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);
6033 label->hide();
6034 canvas_item_editor->get_controls_container()->add_child(label);
6035
6036 label_desc = memnew(Label);
6037 label_desc->add_theme_color_override("font_color", Color(0.6f, 0.6f, 0.6f, 1));
6038 label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));
6039 label_desc->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);
6040 label_desc->add_theme_constant_override("line_spacing", 0);
6041 label_desc->hide();
6042 canvas_item_editor->get_controls_container()->add_child(label_desc);
6043
6044 RS::get_singleton()->canvas_set_disable_scale(true);
6045}
6046
6047CanvasItemEditorViewport::~CanvasItemEditorViewport() {
6048 memdelete(preview_node);
6049}
6050