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