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