Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_properties.cpp
20897 views
1
/**************************************************************************/
2
/* editor_properties.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 "editor_properties.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/input/input_map.h"
35
#include "core/string/translation_server.h"
36
#include "editor/docks/inspector_dock.h"
37
#include "editor/docks/scene_tree_dock.h"
38
#include "editor/editor_node.h"
39
#include "editor/editor_string_names.h"
40
#include "editor/gui/create_dialog.h"
41
#include "editor/gui/editor_file_dialog.h"
42
#include "editor/gui/editor_spin_slider.h"
43
#include "editor/gui/editor_variant_type_selectors.h"
44
#include "editor/inspector/editor_properties_array_dict.h"
45
#include "editor/inspector/editor_properties_vector.h"
46
#include "editor/inspector/editor_resource_picker.h"
47
#include "editor/inspector/property_selector.h"
48
#include "editor/scene/scene_tree_editor.h"
49
#include "editor/script/syntax_highlighters.h"
50
#include "editor/settings/editor_settings.h"
51
#include "editor/settings/project_settings_editor.h"
52
#include "editor/themes/editor_scale.h"
53
#include "scene/2d/gpu_particles_2d.h"
54
#include "scene/3d/fog_volume.h"
55
#include "scene/3d/gpu_particles_3d.h"
56
#include "scene/gui/color_picker.h"
57
#include "scene/gui/grid_container.h"
58
#include "scene/gui/text_edit.h"
59
#include "scene/main/window.h"
60
#include "scene/resources/font.h"
61
#include "scene/resources/mesh.h"
62
#include "scene/resources/sky.h"
63
#include "scene/resources/visual_shader_nodes.h"
64
65
///////////////////// NIL /////////////////////////
66
67
void EditorPropertyNil::update_property() {
68
}
69
70
EditorPropertyNil::EditorPropertyNil() {
71
Label *prop_label = memnew(Label);
72
prop_label->set_text("<null>");
73
add_child(prop_label);
74
}
75
76
//////////////////// VARIANT ///////////////////////
77
78
void EditorPropertyVariant::_change_type(int p_to_type) {
79
new_type = Variant::Type(p_to_type);
80
81
Variant zero;
82
Callable::CallError ce;
83
Variant::construct(new_type, zero, nullptr, 0, ce);
84
emit_changed(get_edited_property(), zero);
85
}
86
87
void EditorPropertyVariant::_popup_edit_menu() {
88
if (change_type == nullptr) {
89
change_type = memnew(EditorVariantTypePopupMenu(false));
90
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyVariant::_change_type));
91
content->add_child(change_type);
92
}
93
94
Rect2 rect = edit_button->get_screen_rect();
95
change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
96
change_type->popup();
97
}
98
99
void EditorPropertyVariant::_set_read_only(bool p_read_only) {
100
edit_button->set_disabled(p_read_only);
101
if (sub_property) {
102
sub_property->set_read_only(p_read_only);
103
}
104
}
105
106
void EditorPropertyVariant::_notification(int p_what) {
107
if (p_what == NOTIFICATION_THEME_CHANGED) {
108
edit_button->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
109
}
110
}
111
112
void EditorPropertyVariant::update_property() {
113
const Variant &value = get_edited_property_value();
114
if (new_type == Variant::VARIANT_MAX) {
115
new_type = value.get_type();
116
}
117
118
if (new_type != current_type) {
119
current_type = new_type;
120
121
if (sub_property) {
122
memdelete(sub_property);
123
sub_property = nullptr;
124
}
125
126
if (current_type == Variant::OBJECT) {
127
sub_property = EditorInspector::instantiate_property_editor(nullptr, current_type, "", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static(), PROPERTY_USAGE_NONE);
128
} else {
129
sub_property = EditorInspector::instantiate_property_editor(nullptr, current_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
130
}
131
ERR_FAIL_NULL(sub_property);
132
133
sub_property->set_object_and_property(get_edited_object(), get_edited_property());
134
sub_property->set_name_split_ratio(0);
135
sub_property->set_selectable(false);
136
sub_property->set_use_folding(is_using_folding());
137
sub_property->set_read_only(is_read_only());
138
sub_property->set_h_size_flags(SIZE_EXPAND_FILL);
139
sub_property->connect(SNAME("property_changed"), callable_mp((EditorProperty *)this, &EditorProperty::emit_changed));
140
content->add_child(sub_property);
141
content->move_child(sub_property, 0);
142
sub_property->update_property();
143
} else if (sub_property) {
144
sub_property->update_property();
145
}
146
new_type = Variant::VARIANT_MAX;
147
}
148
149
EditorPropertyVariant::EditorPropertyVariant() {
150
content = memnew(HBoxContainer);
151
add_child(content);
152
153
edit_button = memnew(Button);
154
edit_button->set_flat(true);
155
edit_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
156
edit_button->set_accessibility_name(TTRC("Edit"));
157
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyVariant::_popup_edit_menu));
158
content->add_child(edit_button);
159
}
160
161
///////////////////// TEXT /////////////////////////
162
163
void EditorPropertyText::_notification(int p_what) {
164
switch (p_what) {
165
case NOTIFICATION_THEME_CHANGED: {
166
_update_theme();
167
} break;
168
}
169
}
170
171
void EditorPropertyText::_set_read_only(bool p_read_only) {
172
text->set_editable(!p_read_only);
173
}
174
175
void EditorPropertyText::_update_theme() {
176
Ref<Font> font;
177
int font_size;
178
179
if (monospaced) {
180
font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts));
181
font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts));
182
} else {
183
font = get_theme_font(SceneStringName(font), SNAME("LineEdit"));
184
font_size = get_theme_font_size(SceneStringName(font_size), SNAME("LineEdit"));
185
}
186
187
text->add_theme_font_override(SceneStringName(font), font);
188
text->add_theme_font_size_override(SceneStringName(font_size), font_size);
189
}
190
191
void EditorPropertyText::_text_submitted(const String &p_string) {
192
if (updating) {
193
return;
194
}
195
196
if (text->has_focus()) {
197
_text_changed(p_string);
198
}
199
}
200
201
void EditorPropertyText::_text_changed(const String &p_string) {
202
if (updating) {
203
return;
204
}
205
206
// Set tooltip so that the full text is displayed in a tooltip if hovered.
207
// This is useful when using a narrow inspector, as the text can be trimmed otherwise.
208
if (text->is_secret()) {
209
text->set_tooltip_text(get_tooltip_string(text->get_placeholder()));
210
} else {
211
text->set_tooltip_text(get_tooltip_string(text->get_text()));
212
}
213
214
if (string_name) {
215
emit_changed(get_edited_property(), StringName(p_string));
216
} else {
217
emit_changed(get_edited_property(), p_string);
218
}
219
}
220
221
void EditorPropertyText::update_property() {
222
String s = get_edited_property_value();
223
updating = true;
224
if (text->get_text() != s) {
225
int caret = text->get_caret_column();
226
text->set_text(s);
227
if (text->is_secret()) {
228
text->set_tooltip_text(get_tooltip_string(text->get_placeholder()));
229
} else {
230
text->set_tooltip_text(get_tooltip_string(s));
231
}
232
text->set_caret_column(caret);
233
}
234
text->set_editable(!is_read_only());
235
updating = false;
236
}
237
238
void EditorPropertyText::set_string_name(bool p_enabled) {
239
string_name = p_enabled;
240
if (p_enabled) {
241
Label *prefix = memnew(Label("&"));
242
prefix->set_tooltip_text("StringName");
243
prefix->set_mouse_filter(MOUSE_FILTER_STOP);
244
text->get_parent()->add_child(prefix);
245
text->get_parent()->move_child(prefix, 0);
246
}
247
}
248
249
void EditorPropertyText::set_secret(bool p_enabled) {
250
text->set_secret(p_enabled);
251
}
252
253
void EditorPropertyText::set_placeholder(const String &p_string) {
254
text->set_placeholder(p_string);
255
}
256
257
void EditorPropertyText::set_monospaced(bool p_monospaced) {
258
if (p_monospaced == monospaced) {
259
return;
260
}
261
monospaced = p_monospaced;
262
_update_theme();
263
}
264
265
EditorPropertyText::EditorPropertyText() {
266
HBoxContainer *hb = memnew(HBoxContainer);
267
add_child(hb);
268
269
text = memnew(LineEdit);
270
text->set_h_size_flags(SIZE_EXPAND_FILL);
271
text->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Prevents translating placeholder.
272
hb->add_child(text);
273
add_focusable(text);
274
text->connect(SceneStringName(text_changed), callable_mp(this, &EditorPropertyText::_text_changed));
275
text->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPropertyText::_text_submitted));
276
}
277
278
///////////////////// MULTILINE TEXT /////////////////////////
279
280
void EditorPropertyMultilineText::_set_read_only(bool p_read_only) {
281
text->set_editable(!p_read_only);
282
open_big_text->set_disabled(p_read_only);
283
}
284
285
void EditorPropertyMultilineText::_big_text_changed() {
286
text->set_text(big_text->get_text());
287
// Set tooltip so that the full text is displayed in a tooltip if hovered.
288
// This is useful when using a narrow inspector, as the text can be trimmed otherwise.
289
text->set_tooltip_text(get_tooltip_string(big_text->get_text()));
290
emit_changed(get_edited_property(), big_text->get_text(), "", true);
291
}
292
293
void EditorPropertyMultilineText::_text_changed() {
294
text->set_tooltip_text(get_tooltip_string(text->get_text()));
295
emit_changed(get_edited_property(), text->get_text(), "", true);
296
}
297
298
void EditorPropertyMultilineText::_open_big_text() {
299
if (!big_text_dialog) {
300
big_text = memnew(TextEdit);
301
if (expression) {
302
big_text->set_syntax_highlighter(text->get_syntax_highlighter());
303
}
304
big_text->connect(SceneStringName(text_changed), callable_mp(this, &EditorPropertyMultilineText::_big_text_changed));
305
big_text->set_line_wrapping_mode(wrap_lines
306
? TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY
307
: TextEdit::LineWrappingMode::LINE_WRAPPING_NONE);
308
big_text_dialog = memnew(AcceptDialog);
309
big_text_dialog->add_child(big_text);
310
big_text_dialog->set_title(TTR("Edit Text:"));
311
add_child(big_text_dialog);
312
}
313
314
big_text_dialog->popup_centered_clamped(Size2(1000, 900) * EDSCALE, 0.8);
315
big_text->set_text(text->get_text());
316
big_text->grab_focus();
317
318
_update_theme();
319
}
320
321
void EditorPropertyMultilineText::update_property() {
322
String t = get_edited_property_value();
323
if (text->get_text() != t) {
324
text->set_text(t);
325
text->set_tooltip_text(get_tooltip_string(t));
326
if (big_text && big_text->is_visible_in_tree()) {
327
big_text->set_text(t);
328
}
329
}
330
}
331
332
void EditorPropertyMultilineText::_update_theme() {
333
Ref<Texture2D> df = get_editor_theme_icon(SNAME("DistractionFree"));
334
open_big_text->set_button_icon(df);
335
336
Ref<Font> font;
337
int font_size;
338
if (expression) {
339
font = get_theme_font(SNAME("expression"), EditorStringName(EditorFonts));
340
font_size = get_theme_font_size(SNAME("expression_size"), EditorStringName(EditorFonts));
341
} else {
342
// Non expression.
343
if (monospaced) {
344
font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts));
345
font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts));
346
} else {
347
font = get_theme_font(SceneStringName(font), SNAME("TextEdit"));
348
font_size = get_theme_font_size(SceneStringName(font_size), SNAME("TextEdit"));
349
}
350
}
351
text->add_theme_font_override(SceneStringName(font), font);
352
text->add_theme_font_size_override(SceneStringName(font_size), font_size);
353
text->set_line_wrapping_mode(wrap_lines
354
? TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY
355
: TextEdit::LineWrappingMode::LINE_WRAPPING_NONE);
356
if (big_text) {
357
big_text->add_theme_font_override(SceneStringName(font), font);
358
big_text->add_theme_font_size_override(SceneStringName(font_size), font_size);
359
big_text->set_line_wrapping_mode(wrap_lines
360
? TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY
361
: TextEdit::LineWrappingMode::LINE_WRAPPING_NONE);
362
}
363
364
text->set_custom_minimum_size(Vector2(0, font->get_height(font_size) * 6));
365
}
366
367
void EditorPropertyMultilineText::_notification(int p_what) {
368
switch (p_what) {
369
case NOTIFICATION_THEME_CHANGED: {
370
_update_theme();
371
} break;
372
}
373
}
374
375
void EditorPropertyMultilineText::EditorPropertyMultilineText::set_monospaced(bool p_monospaced) {
376
if (p_monospaced == monospaced) {
377
return;
378
}
379
monospaced = p_monospaced;
380
_update_theme();
381
}
382
383
bool EditorPropertyMultilineText::EditorPropertyMultilineText::get_monospaced() {
384
return monospaced;
385
}
386
387
void EditorPropertyMultilineText::EditorPropertyMultilineText::set_wrap_lines(bool p_wrap_lines) {
388
if (p_wrap_lines == wrap_lines) {
389
return;
390
}
391
wrap_lines = p_wrap_lines;
392
_update_theme();
393
}
394
395
bool EditorPropertyMultilineText::EditorPropertyMultilineText::get_wrap_lines() {
396
return wrap_lines;
397
}
398
399
EditorPropertyMultilineText::EditorPropertyMultilineText(bool p_expression) :
400
expression(p_expression) {
401
HBoxContainer *hb = memnew(HBoxContainer);
402
hb->add_theme_constant_override("separation", 0);
403
add_child(hb);
404
set_bottom_editor(hb);
405
text = memnew(TextEdit);
406
text->connect(SceneStringName(text_changed), callable_mp(this, &EditorPropertyMultilineText::_text_changed));
407
text->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY);
408
add_focusable(text);
409
hb->add_child(text);
410
text->set_h_size_flags(SIZE_EXPAND_FILL);
411
open_big_text = memnew(Button);
412
open_big_text->set_accessibility_name(TTRC("Open Text Edit Dialog"));
413
open_big_text->set_flat(true);
414
open_big_text->set_theme_type_variation(SNAME("EditorInspectorButton"));
415
open_big_text->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyMultilineText::_open_big_text));
416
hb->add_child(open_big_text);
417
big_text_dialog = nullptr;
418
big_text = nullptr;
419
420
if (expression) {
421
Ref<EditorStandardSyntaxHighlighter> highlighter;
422
highlighter.instantiate();
423
text->set_syntax_highlighter(highlighter);
424
}
425
}
426
427
///////////////////// TEXT ENUM /////////////////////////
428
429
void EditorPropertyTextEnum::_set_read_only(bool p_read_only) {
430
option_button->set_disabled(p_read_only);
431
edit_button->set_disabled(p_read_only);
432
}
433
434
void EditorPropertyTextEnum::_emit_changed_value(const String &p_string) {
435
if (string_name) {
436
emit_changed(get_edited_property(), StringName(p_string));
437
} else {
438
emit_changed(get_edited_property(), p_string);
439
}
440
}
441
442
void EditorPropertyTextEnum::_option_selected(int p_which) {
443
_emit_changed_value(option_button->get_item_metadata(p_which));
444
}
445
446
void EditorPropertyTextEnum::_edit_custom_value() {
447
default_layout->hide();
448
edit_custom_layout->show();
449
custom_value_edit->grab_focus(true);
450
}
451
452
void EditorPropertyTextEnum::_custom_value_submitted(const String &p_value) {
453
edit_custom_layout->hide();
454
default_layout->show();
455
456
_emit_changed_value(p_value.strip_edges());
457
}
458
459
void EditorPropertyTextEnum::_custom_value_accepted() {
460
String new_value = custom_value_edit->get_text().strip_edges();
461
_custom_value_submitted(new_value);
462
}
463
464
void EditorPropertyTextEnum::_custom_value_canceled() {
465
custom_value_edit->set_text(get_edited_property_value());
466
467
edit_custom_layout->hide();
468
default_layout->show();
469
}
470
471
void EditorPropertyTextEnum::update_property() {
472
String current_value = get_edited_property_value();
473
int default_option = options.find(current_value);
474
475
// The list can change in the loose mode.
476
if (loose_mode) {
477
custom_value_edit->set_text(current_value);
478
option_button->clear();
479
480
// Manually entered value.
481
if (default_option < 0 && !current_value.is_empty()) {
482
option_button->add_item(current_value, options.size() + 1001);
483
option_button->set_item_metadata(-1, current_value);
484
option_button->select(0);
485
486
option_button->add_separator();
487
}
488
489
// Add an explicit empty value for clearing the property.
490
option_button->add_item("", options.size() + 1000);
491
option_button->set_item_metadata(-1, String());
492
493
for (int i = 0; i < options.size(); i++) {
494
option_button->add_item(option_names[i], i);
495
option_button->set_item_metadata(-1, options[i]);
496
if (options[i] == current_value) {
497
option_button->select(option_button->get_item_count() - 1);
498
}
499
}
500
} else {
501
option_button->select(default_option);
502
if (default_option < 0) {
503
option_button->set_text(current_value);
504
}
505
}
506
}
507
508
void EditorPropertyTextEnum::setup(const Vector<String> &p_options, const Vector<String> &p_option_names, bool p_string_name, bool p_loose_mode) {
509
ERR_FAIL_COND(!p_option_names.is_empty() && p_option_names.size() != p_options.size());
510
511
string_name = p_string_name;
512
loose_mode = p_loose_mode;
513
514
options = p_options;
515
if (p_option_names.is_empty()) {
516
option_names = p_options;
517
} else {
518
option_names = p_option_names;
519
}
520
521
if (loose_mode) {
522
// Add an explicit empty value for clearing the property in the loose mode.
523
option_button->add_item("", options.size() + 1000);
524
option_button->set_item_metadata(-1, String());
525
}
526
527
for (int i = 0; i < options.size(); i++) {
528
option_button->add_item(option_names[i], i);
529
option_button->set_item_metadata(-1, options[i]);
530
}
531
532
if (loose_mode) {
533
edit_button->show();
534
}
535
}
536
537
void EditorPropertyTextEnum::_notification(int p_what) {
538
switch (p_what) {
539
case NOTIFICATION_THEME_CHANGED: {
540
edit_button->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
541
accept_button->set_button_icon(get_editor_theme_icon(SNAME("ImportCheck")));
542
cancel_button->set_button_icon(get_editor_theme_icon(SNAME("ImportFail")));
543
} break;
544
}
545
}
546
547
EditorPropertyTextEnum::EditorPropertyTextEnum() {
548
HBoxContainer *hb = memnew(HBoxContainer);
549
add_child(hb);
550
551
default_layout = memnew(HBoxContainer);
552
default_layout->set_h_size_flags(SIZE_EXPAND_FILL);
553
hb->add_child(default_layout);
554
555
edit_custom_layout = memnew(HBoxContainer);
556
edit_custom_layout->set_h_size_flags(SIZE_EXPAND_FILL);
557
edit_custom_layout->hide();
558
hb->add_child(edit_custom_layout);
559
560
option_button = memnew(OptionButton);
561
option_button->set_accessibility_name(TTRC("Enum Options"));
562
option_button->set_h_size_flags(SIZE_EXPAND_FILL);
563
option_button->set_clip_text(true);
564
option_button->set_flat(true);
565
option_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
566
option_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
567
default_layout->add_child(option_button);
568
option_button->connect(SceneStringName(item_selected), callable_mp(this, &EditorPropertyTextEnum::_option_selected));
569
570
edit_button = memnew(Button);
571
edit_button->set_accessibility_name(TTRC("Edit"));
572
edit_button->set_flat(true);
573
edit_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
574
edit_button->hide();
575
default_layout->add_child(edit_button);
576
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyTextEnum::_edit_custom_value));
577
578
custom_value_edit = memnew(LineEdit);
579
custom_value_edit->set_accessibility_name(TTRC("Custom Value"));
580
custom_value_edit->set_h_size_flags(SIZE_EXPAND_FILL);
581
edit_custom_layout->add_child(custom_value_edit);
582
custom_value_edit->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPropertyTextEnum::_custom_value_submitted));
583
584
accept_button = memnew(Button);
585
accept_button->set_accessibility_name(TTRC("Accept Custom Value Edit"));
586
accept_button->set_flat(true);
587
accept_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
588
edit_custom_layout->add_child(accept_button);
589
accept_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyTextEnum::_custom_value_accepted));
590
591
cancel_button = memnew(Button);
592
cancel_button->set_accessibility_name(TTRC("Cancel Custom Value Edit"));
593
cancel_button->set_flat(true);
594
cancel_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
595
edit_custom_layout->add_child(cancel_button);
596
cancel_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyTextEnum::_custom_value_canceled));
597
598
add_focusable(option_button);
599
add_focusable(edit_button);
600
add_focusable(custom_value_edit);
601
add_focusable(accept_button);
602
add_focusable(cancel_button);
603
}
604
605
//////////////////// LOCALE ////////////////////////
606
607
void EditorPropertyLocale::_locale_selected(const String &p_locale) {
608
emit_changed(get_edited_property(), p_locale);
609
update_property();
610
}
611
612
void EditorPropertyLocale::_locale_pressed() {
613
if (!dialog) {
614
dialog = memnew(EditorLocaleDialog);
615
dialog->connect("locale_selected", callable_mp(this, &EditorPropertyLocale::_locale_selected));
616
add_child(dialog);
617
}
618
619
String locale_code = get_edited_property_value();
620
dialog->set_locale(locale_code);
621
dialog->popup_locale_dialog();
622
}
623
624
void EditorPropertyLocale::update_property() {
625
String locale_code = get_edited_property_value();
626
locale->set_text(locale_code);
627
locale->set_tooltip_text(locale_code);
628
}
629
630
void EditorPropertyLocale::setup(const String &p_hint_text) {
631
}
632
633
void EditorPropertyLocale::_notification(int p_what) {
634
switch (p_what) {
635
case NOTIFICATION_THEME_CHANGED: {
636
locale_edit->set_button_icon(get_editor_theme_icon(SNAME("Translation")));
637
} break;
638
}
639
}
640
641
void EditorPropertyLocale::_locale_focus_exited() {
642
_locale_selected(locale->get_text());
643
}
644
645
EditorPropertyLocale::EditorPropertyLocale() {
646
HBoxContainer *locale_hb = memnew(HBoxContainer);
647
add_child(locale_hb);
648
locale = memnew(LineEdit);
649
locale->set_accessibility_name(TTRC("Locale"));
650
locale_hb->add_child(locale);
651
locale->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPropertyLocale::_locale_selected));
652
locale->connect(SceneStringName(focus_exited), callable_mp(this, &EditorPropertyLocale::_locale_focus_exited));
653
locale->set_h_size_flags(SIZE_EXPAND_FILL);
654
655
locale_edit = memnew(Button);
656
locale_edit->set_accessibility_name(TTRC("Edit"));
657
locale_edit->set_clip_text(true);
658
locale_edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
659
locale_hb->add_child(locale_edit);
660
add_focusable(locale);
661
dialog = nullptr;
662
locale_edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocale::_locale_pressed));
663
}
664
665
///////////////////// PATH /////////////////////////
666
667
void EditorPropertyPath::_set_read_only(bool p_read_only) {
668
path->set_editable(!p_read_only);
669
path_edit->set_disabled(p_read_only);
670
}
671
672
void EditorPropertyPath::_path_selected(const String &p_path) {
673
String full_path = p_path;
674
675
if (enable_uid) {
676
const ResourceUID::ID id = ResourceLoader::get_resource_uid(full_path);
677
if (id != ResourceUID::INVALID_ID) {
678
full_path = ResourceUID::get_singleton()->id_to_text(id);
679
}
680
}
681
682
emit_changed(get_edited_property(), full_path);
683
update_property();
684
}
685
686
String EditorPropertyPath::_get_path_text(bool p_allow_uid) {
687
String full_path = get_edited_property_value();
688
if (!p_allow_uid && full_path.begins_with("uid://")) {
689
full_path = ResourceUID::uid_to_path(full_path);
690
}
691
692
return full_path;
693
}
694
695
void EditorPropertyPath::_path_pressed() {
696
if (!dialog) {
697
dialog = memnew(EditorFileDialog);
698
dialog->connect("file_selected", callable_mp(this, &EditorPropertyPath::_path_selected));
699
dialog->connect("dir_selected", callable_mp(this, &EditorPropertyPath::_path_selected));
700
add_child(dialog);
701
}
702
703
String full_path = _get_path_text();
704
705
dialog->clear_filters();
706
707
if (global) {
708
dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
709
} else {
710
dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
711
}
712
713
if (folder) {
714
dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
715
dialog->set_current_dir(full_path);
716
} else {
717
dialog->set_file_mode(save_mode ? EditorFileDialog::FILE_MODE_SAVE_FILE : EditorFileDialog::FILE_MODE_OPEN_FILE);
718
for (int i = 0; i < extensions.size(); i++) {
719
String e = extensions[i].strip_edges();
720
if (!e.is_empty()) {
721
dialog->add_filter(extensions[i].strip_edges());
722
}
723
}
724
dialog->set_current_path(full_path);
725
}
726
727
dialog->popup_file_dialog();
728
}
729
730
void EditorPropertyPath::update_property() {
731
String full_path = _get_path_text(display_uid);
732
path->set_text(full_path);
733
path->set_tooltip_text(full_path);
734
735
toggle_uid->set_visible(get_edited_property_value().operator String().begins_with("uid://"));
736
}
737
738
void EditorPropertyPath::setup(const Vector<String> &p_extensions, bool p_folder, bool p_global, bool p_enable_uid) {
739
extensions = p_extensions;
740
folder = p_folder;
741
global = p_global;
742
enable_uid = p_enable_uid;
743
}
744
745
void EditorPropertyPath::set_save_mode() {
746
save_mode = true;
747
}
748
749
void EditorPropertyPath::_notification(int p_what) {
750
switch (p_what) {
751
case NOTIFICATION_THEME_CHANGED: {
752
if (folder) {
753
path_edit->set_button_icon(get_editor_theme_icon(SNAME("FolderBrowse")));
754
} else {
755
path_edit->set_button_icon(get_editor_theme_icon(SNAME("FileBrowse")));
756
}
757
_update_uid_icon();
758
} break;
759
}
760
}
761
762
void EditorPropertyPath::_path_focus_exited() {
763
_path_selected(path->get_text());
764
}
765
766
void EditorPropertyPath::_toggle_uid_display() {
767
display_uid = !display_uid;
768
_update_uid_icon();
769
update_property();
770
}
771
772
void EditorPropertyPath::_update_uid_icon() {
773
toggle_uid->set_button_icon(get_editor_theme_icon(display_uid ? SNAME("UID") : SNAME("NodePath")));
774
}
775
776
void EditorPropertyPath::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
777
const Dictionary drag_data = p_data;
778
if (!drag_data.has("type")) {
779
return;
780
}
781
if (String(drag_data["type"]) != "files") {
782
return;
783
}
784
const Vector<String> filesPaths = drag_data["files"];
785
if (filesPaths.is_empty()) {
786
return;
787
}
788
789
_path_selected(filesPaths[0]);
790
}
791
792
bool EditorPropertyPath::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
793
const Dictionary drag_data = p_data;
794
if (!drag_data.has("type")) {
795
return false;
796
}
797
if (String(drag_data["type"]) != "files") {
798
return false;
799
}
800
const Vector<String> filesPaths = drag_data["files"];
801
if (filesPaths.is_empty()) {
802
return false;
803
}
804
805
for (const String &extension : extensions) {
806
if (filesPaths[0].ends_with(extension.substr(1))) {
807
return true;
808
}
809
}
810
811
return false;
812
}
813
814
EditorPropertyPath::EditorPropertyPath() {
815
HBoxContainer *path_hb = memnew(HBoxContainer);
816
add_child(path_hb);
817
path = memnew(LineEdit);
818
path->set_accessibility_name(TTRC("Path"));
819
SET_DRAG_FORWARDING_CDU(path, EditorPropertyPath);
820
path->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
821
path_hb->add_child(path);
822
path->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPropertyPath::_path_selected));
823
path->connect(SceneStringName(focus_exited), callable_mp(this, &EditorPropertyPath::_path_focus_exited));
824
path->set_h_size_flags(SIZE_EXPAND_FILL);
825
826
toggle_uid = memnew(Button);
827
toggle_uid->set_accessibility_name(TTRC("Toggle Display UID"));
828
toggle_uid->set_tooltip_text(TTRC("Toggles displaying between path and UID.\nThe UID is the actual value of this property."));
829
toggle_uid->set_pressed(false);
830
toggle_uid->set_theme_type_variation(SNAME("EditorInspectorButton"));
831
path_hb->add_child(toggle_uid);
832
add_focusable(toggle_uid);
833
toggle_uid->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyPath::_toggle_uid_display));
834
835
path_edit = memnew(Button);
836
path_edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
837
path_edit->set_accessibility_name(TTRC("Edit"));
838
path_hb->add_child(path_edit);
839
add_focusable(path);
840
path_edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyPath::_path_pressed));
841
}
842
843
///////////////////// CLASS NAME /////////////////////////
844
845
void EditorPropertyClassName::_set_read_only(bool p_read_only) {
846
property->set_disabled(p_read_only);
847
}
848
849
void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) {
850
base_type = p_base_type;
851
dialog->set_base_type(base_type);
852
selected_type = p_selected_type;
853
property->set_text(selected_type);
854
}
855
856
void EditorPropertyClassName::update_property() {
857
String s = get_edited_property_value();
858
property->set_text(s);
859
selected_type = s;
860
}
861
862
void EditorPropertyClassName::_property_selected() {
863
dialog->popup_create(true, true, get_edited_property_value(), get_edited_property());
864
}
865
866
void EditorPropertyClassName::_dialog_created() {
867
selected_type = dialog->get_selected_type();
868
emit_changed(get_edited_property(), selected_type);
869
update_property();
870
}
871
872
EditorPropertyClassName::EditorPropertyClassName() {
873
property = memnew(Button);
874
property->set_clip_text(true);
875
property->set_theme_type_variation(SNAME("EditorInspectorButton"));
876
add_child(property);
877
add_focusable(property);
878
property->set_text(selected_type);
879
property->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyClassName::_property_selected));
880
dialog = memnew(CreateDialog);
881
dialog->set_base_type(base_type);
882
dialog->connect("create", callable_mp(this, &EditorPropertyClassName::_dialog_created));
883
add_child(dialog);
884
}
885
886
///////////////////// CHECK /////////////////////////
887
888
void EditorPropertyCheck::_set_read_only(bool p_read_only) {
889
checkbox->set_disabled(p_read_only);
890
}
891
892
void EditorPropertyCheck::_checkbox_pressed() {
893
emit_changed(get_edited_property(), checkbox->is_pressed());
894
}
895
896
void EditorPropertyCheck::update_property() {
897
bool c = get_edited_property_value();
898
checkbox->set_pressed(c);
899
checkbox->set_disabled(is_read_only());
900
}
901
902
EditorPropertyCheck::EditorPropertyCheck() {
903
checkbox = memnew(CheckBox);
904
checkbox->set_text(TTR("On"));
905
add_child(checkbox);
906
add_focusable(checkbox);
907
checkbox->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyCheck::_checkbox_pressed));
908
}
909
910
///////////////////// ENUM /////////////////////////
911
912
void EditorPropertyEnum::_set_read_only(bool p_read_only) {
913
options->set_disabled(p_read_only);
914
}
915
916
void EditorPropertyEnum::_option_selected(int p_which) {
917
int64_t val = options->get_item_metadata(p_which);
918
emit_changed(get_edited_property(), val);
919
}
920
921
void EditorPropertyEnum::update_property() {
922
Variant current = get_edited_property_value();
923
if (current.get_type() == Variant::NIL) {
924
options->select(-1);
925
options->set_text("<null>");
926
return;
927
}
928
929
int64_t which = current;
930
for (int i = 0; i < options->get_item_count(); i++) {
931
if (which == (int64_t)options->get_item_metadata(i)) {
932
options->select(i);
933
return;
934
}
935
}
936
options->select(-1);
937
options->set_text(itos(which));
938
}
939
940
void EditorPropertyEnum::setup(const Vector<String> &p_options) {
941
options->clear();
942
HashMap<int64_t, Vector<String>> items;
943
int64_t current_val = 0;
944
for (const String &option : p_options) {
945
if (option.get_slice_count(":") != 1) {
946
current_val = option.get_slicec(':', 1).to_int();
947
}
948
items[current_val].push_back(option.get_slicec(':', 0));
949
current_val += 1;
950
}
951
952
for (const KeyValue<int64_t, Vector<String>> &K : items) {
953
options->add_item(String(", ").join(K.value));
954
options->set_item_metadata(-1, K.key);
955
}
956
}
957
958
void EditorPropertyEnum::set_option_button_clip(bool p_enable) {
959
options->set_clip_text(p_enable);
960
}
961
962
OptionButton *EditorPropertyEnum::get_option_button() {
963
return options;
964
}
965
966
EditorPropertyEnum::EditorPropertyEnum() {
967
options = memnew(OptionButton);
968
options->set_clip_text(true);
969
options->set_flat(true);
970
options->set_theme_type_variation(SNAME("EditorInspectorButton"));
971
options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
972
add_child(options);
973
add_focusable(options);
974
options->connect(SceneStringName(item_selected), callable_mp(this, &EditorPropertyEnum::_option_selected));
975
}
976
977
///////////////////// FLAGS /////////////////////////
978
979
void EditorPropertyFlags::_set_read_only(bool p_read_only) {
980
for (CheckBox *check : flags) {
981
check->set_disabled(p_read_only);
982
}
983
}
984
985
void EditorPropertyFlags::_flag_toggled(int p_index) {
986
uint32_t value = get_edited_property_value();
987
if (flags[p_index]->is_pressed()) {
988
value |= flag_values[p_index];
989
} else {
990
value &= ~flag_values[p_index];
991
}
992
993
emit_changed(get_edited_property(), value);
994
}
995
996
void EditorPropertyFlags::update_property() {
997
uint32_t value = get_edited_property_value();
998
999
for (int i = 0; i < flags.size(); i++) {
1000
flags[i]->set_pressed((value & flag_values[i]) == flag_values[i]);
1001
}
1002
}
1003
1004
void EditorPropertyFlags::setup(const Vector<String> &p_options) {
1005
ERR_FAIL_COND(flags.size());
1006
1007
bool first = true;
1008
uint32_t current_val;
1009
for (int i = 0; i < p_options.size(); i++) {
1010
// An empty option is not considered a "flag".
1011
String option = p_options[i].strip_edges();
1012
if (option.is_empty()) {
1013
continue;
1014
}
1015
const int flag_index = flags.size(); // Index of the next element (added by the code below).
1016
1017
// Value for a flag can be explicitly overridden.
1018
Vector<String> text_split = option.split(":");
1019
if (text_split.size() != 1) {
1020
current_val = text_split[1].to_int();
1021
// Skip entries like "None:0" (it's not an actual flag).
1022
if (current_val == 0) {
1023
continue;
1024
}
1025
} else {
1026
current_val = 1u << i;
1027
}
1028
flag_values.push_back(current_val);
1029
1030
// Create a CheckBox for the current flag.
1031
CheckBox *cb = memnew(CheckBox);
1032
cb->set_text(text_split[0]);
1033
cb->set_clip_text(true);
1034
cb->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyFlags::_flag_toggled).bind(flag_index));
1035
add_focusable(cb);
1036
vbox->add_child(cb);
1037
flags.push_back(cb);
1038
1039
// Can't use `i == 0` because we want to find the first none-empty option.
1040
if (first) {
1041
set_label_reference(cb);
1042
first = false;
1043
}
1044
}
1045
}
1046
1047
EditorPropertyFlags::EditorPropertyFlags() {
1048
vbox = memnew(VBoxContainer);
1049
vbox->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1050
add_child(vbox);
1051
}
1052
1053
///////////////////// LAYERS /////////////////////////
1054
1055
void EditorPropertyLayersGrid::_rename_pressed(int p_menu) {
1056
// Show rename popup for active layer.
1057
ERR_FAIL_INDEX(renamed_layer_index, names.size());
1058
String name = names[renamed_layer_index];
1059
rename_dialog->set_title(vformat(TTR("Renaming Layer %d:"), renamed_layer_index + 1));
1060
rename_dialog_text->set_text(name);
1061
// Indicate that leaving it blank reverts back to "Layer [Number]".
1062
rename_dialog_text->set_placeholder(vformat(TTR("Layer %d"), renamed_layer_index + 1));
1063
rename_dialog_text->select(0, name.length());
1064
rename_dialog->popup_centered(Size2(300, 80) * EDSCALE);
1065
rename_dialog_text->grab_focus();
1066
}
1067
1068
void EditorPropertyLayersGrid::_rename_operation_confirm() {
1069
String new_name = rename_dialog_text->get_text().strip_edges();
1070
if (new_name.contains_char('/') || new_name.contains_char('\\') || new_name.contains_char(':')) {
1071
EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters."));
1072
return;
1073
}
1074
1075
names.set(renamed_layer_index, new_name);
1076
tooltips.set(renamed_layer_index, new_name + "\n" + vformat(TTR("Bit %d, Value %d"), renamed_layer_index, 1u << renamed_layer_index));
1077
emit_signal(SNAME("rename_confirmed"), renamed_layer_index, new_name);
1078
}
1079
1080
EditorPropertyLayersGrid::EditorPropertyLayersGrid() {
1081
rename_dialog = memnew(ConfirmationDialog);
1082
VBoxContainer *rename_dialog_vb = memnew(VBoxContainer);
1083
rename_dialog->add_child(rename_dialog_vb);
1084
rename_dialog_text = memnew(LineEdit);
1085
rename_dialog_vb->add_margin_child(TTR("Name:"), rename_dialog_text);
1086
rename_dialog->set_ok_button_text(TTR("Rename"));
1087
add_child(rename_dialog);
1088
rename_dialog->register_text_enter(rename_dialog_text);
1089
rename_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorPropertyLayersGrid::_rename_operation_confirm));
1090
layer_rename = memnew(PopupMenu);
1091
layer_rename->add_item(TTR("Rename Layer"), 0);
1092
add_child(layer_rename);
1093
layer_rename->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyLayersGrid::_rename_pressed));
1094
}
1095
1096
Size2 EditorPropertyLayersGrid::get_grid_size() const {
1097
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1098
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1099
return Vector2(0, font->get_height(font_size) * 3);
1100
}
1101
1102
void EditorPropertyLayersGrid::set_read_only(bool p_read_only) {
1103
read_only = p_read_only;
1104
}
1105
1106
Size2 EditorPropertyLayersGrid::get_minimum_size() const {
1107
Size2 min_size = get_grid_size();
1108
1109
// Add extra rows when expanded.
1110
if (expanded) {
1111
const int bsize = (min_size.height * 80 / 100) / 2;
1112
for (int i = 0; i < expansion_rows; ++i) {
1113
min_size.y += 2 * (bsize + 1) + 3;
1114
}
1115
}
1116
1117
return min_size;
1118
}
1119
1120
String EditorPropertyLayersGrid::get_tooltip(const Point2 &p_pos) const {
1121
for (int i = 0; i < flag_rects.size(); i++) {
1122
if (i < tooltips.size() && flag_rects[i].has_point(p_pos)) {
1123
return tooltips[i];
1124
}
1125
}
1126
return String();
1127
}
1128
1129
void EditorPropertyLayersGrid::_update_hovered(const Vector2 &p_position) {
1130
bool expand_was_hovered = expand_hovered;
1131
expand_hovered = expand_rect.has_point(p_position);
1132
if (expand_hovered != expand_was_hovered) {
1133
queue_redraw();
1134
}
1135
1136
if (!expand_hovered) {
1137
for (int i = 0; i < flag_rects.size(); i++) {
1138
if (flag_rects[i].has_point(p_position)) {
1139
// Used to highlight the hovered flag in the layers grid.
1140
hovered_index = i;
1141
queue_redraw();
1142
return;
1143
}
1144
}
1145
}
1146
1147
// Remove highlight when no square is hovered.
1148
if (hovered_index != HOVERED_INDEX_NONE) {
1149
hovered_index = HOVERED_INDEX_NONE;
1150
queue_redraw();
1151
}
1152
}
1153
1154
void EditorPropertyLayersGrid::_on_hover_exit() {
1155
if (expand_hovered) {
1156
expand_hovered = false;
1157
queue_redraw();
1158
}
1159
if (hovered_index != HOVERED_INDEX_NONE) {
1160
hovered_index = HOVERED_INDEX_NONE;
1161
queue_redraw();
1162
}
1163
if (dragging) {
1164
dragging = false;
1165
}
1166
}
1167
1168
void EditorPropertyLayersGrid::_update_flag(bool p_replace) {
1169
if (hovered_index != HOVERED_INDEX_NONE) {
1170
// Toggle the flag.
1171
// We base our choice on the hovered flag, so that it always matches the hovered flag.
1172
if (p_replace) {
1173
// Replace all flags with the hovered flag ("solo mode"),
1174
// instead of toggling the hovered flags while preserving other flags' state.
1175
if (value == 1u << hovered_index) {
1176
// If the flag is already enabled, enable all other items and disable the current flag.
1177
// This allows for quicker toggling.
1178
value = ~value;
1179
} else {
1180
value = 1u << hovered_index;
1181
}
1182
} else {
1183
value ^= 1u << hovered_index;
1184
}
1185
1186
emit_signal(SNAME("flag_changed"), value);
1187
queue_redraw();
1188
} else if (expand_hovered) {
1189
expanded = !expanded;
1190
update_minimum_size();
1191
queue_redraw();
1192
}
1193
}
1194
1195
void EditorPropertyLayersGrid::gui_input(const Ref<InputEvent> &p_ev) {
1196
if (read_only) {
1197
return;
1198
}
1199
const Ref<InputEventMouseMotion> mm = p_ev;
1200
if (mm.is_valid()) {
1201
_update_hovered(mm->get_position());
1202
if (dragging && hovered_index != HOVERED_INDEX_NONE && dragging_value_to_set != bool(value & (1u << hovered_index))) {
1203
value ^= 1u << hovered_index;
1204
emit_signal(SNAME("flag_changed"), value);
1205
queue_redraw();
1206
}
1207
return;
1208
}
1209
1210
const Ref<InputEventMouseButton> mb = p_ev;
1211
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
1212
_update_hovered(mb->get_position());
1213
bool replace_mode = mb->is_command_or_control_pressed();
1214
_update_flag(replace_mode);
1215
if (!replace_mode && hovered_index != HOVERED_INDEX_NONE) {
1216
dragging = true;
1217
dragging_value_to_set = bool(value & (1u << hovered_index));
1218
}
1219
}
1220
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed()) {
1221
dragging = false;
1222
}
1223
if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
1224
if (hovered_index != HOVERED_INDEX_NONE) {
1225
renamed_layer_index = hovered_index;
1226
layer_rename->set_position(get_screen_position() + mb->get_position());
1227
layer_rename->reset_size();
1228
layer_rename->popup();
1229
}
1230
}
1231
}
1232
1233
void EditorPropertyLayersGrid::_notification(int p_what) {
1234
switch (p_what) {
1235
case NOTIFICATION_DRAW: {
1236
Size2 grid_size = get_grid_size();
1237
grid_size.x = get_size().x;
1238
1239
flag_rects.clear();
1240
1241
int prev_expansion_rows = expansion_rows;
1242
expansion_rows = 0;
1243
1244
const int bsize = (grid_size.height * 80 / 100) / 2;
1245
const int h = bsize * 2 + 1;
1246
1247
Color color = get_theme_color(read_only ? SNAME("highlight_disabled_color") : SNAME("highlight_color"), EditorStringName(Editor));
1248
1249
Color text_color = get_theme_color(read_only ? SNAME("font_disabled_color") : SceneStringName(font_color), EditorStringName(Editor));
1250
text_color.a *= 0.5;
1251
1252
Color text_color_on = get_theme_color(read_only ? SNAME("font_disabled_color") : SNAME("font_hover_color"), EditorStringName(Editor));
1253
text_color_on.a *= 0.7;
1254
1255
const int vofs = (grid_size.height - h) / 2;
1256
1257
uint32_t layer_index = 0;
1258
1259
Point2 arrow_pos;
1260
1261
Point2 block_ofs(4, vofs);
1262
1263
while (true) {
1264
Point2 ofs = block_ofs;
1265
1266
for (int i = 0; i < 2; i++) {
1267
for (int j = 0; j < layer_group_size; j++) {
1268
const bool on = value & (1u << layer_index);
1269
Rect2 rect2 = Rect2(ofs, Size2(bsize, bsize));
1270
1271
color.a = on ? 0.6 : 0.2;
1272
if (layer_index == hovered_index) {
1273
// Add visual feedback when hovering a flag.
1274
color.a += 0.15;
1275
}
1276
1277
draw_rect(rect2, color);
1278
flag_rects.push_back(rect2);
1279
1280
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1281
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1282
Vector2 offset;
1283
offset.y = rect2.size.y * 0.75;
1284
1285
draw_string(font, rect2.position + offset, itos(layer_index + 1), HORIZONTAL_ALIGNMENT_CENTER, rect2.size.x, font_size, on ? text_color_on : text_color);
1286
1287
ofs.x += bsize + 1;
1288
1289
++layer_index;
1290
}
1291
1292
ofs.x = block_ofs.x;
1293
ofs.y += bsize + 1;
1294
}
1295
1296
if (layer_index >= layer_count) {
1297
if (!flag_rects.is_empty() && (expansion_rows == 0)) {
1298
const Rect2 &last_rect = flag_rects[flag_rects.size() - 1];
1299
arrow_pos = last_rect.get_end();
1300
}
1301
break;
1302
}
1303
1304
int block_size_x = layer_group_size * (bsize + 1);
1305
block_ofs.x += block_size_x + 3;
1306
1307
if (block_ofs.x + block_size_x + 12 > grid_size.width) {
1308
// Keep last valid cell position for the expansion icon.
1309
if (!flag_rects.is_empty() && (expansion_rows == 0)) {
1310
const Rect2 &last_rect = flag_rects[flag_rects.size() - 1];
1311
arrow_pos = last_rect.get_end();
1312
}
1313
++expansion_rows;
1314
1315
if (expanded) {
1316
// Expand grid to next line.
1317
block_ofs.x = 4;
1318
block_ofs.y += 2 * (bsize + 1) + 3;
1319
} else {
1320
// Skip remaining blocks.
1321
break;
1322
}
1323
}
1324
}
1325
1326
if ((expansion_rows != prev_expansion_rows) && expanded) {
1327
update_minimum_size();
1328
}
1329
1330
if ((expansion_rows == 0) && (layer_index == layer_count)) {
1331
// Whole grid was drawn, no need for expansion icon.
1332
break;
1333
}
1334
1335
Ref<Texture2D> arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
1336
ERR_FAIL_COND(arrow.is_null());
1337
1338
Color arrow_color = get_theme_color(SNAME("highlight_color"), EditorStringName(Editor));
1339
arrow_color.a = expand_hovered ? 1.0 : 0.6;
1340
1341
arrow_pos.x += 2.0;
1342
arrow_pos.y -= arrow->get_height();
1343
1344
Rect2 arrow_draw_rect(arrow_pos, arrow->get_size());
1345
expand_rect = arrow_draw_rect;
1346
if (expanded) {
1347
arrow_draw_rect.size.y *= -1.0; // Flip arrow vertically when expanded.
1348
}
1349
1350
RID ci = get_canvas_item();
1351
arrow->draw_rect(ci, arrow_draw_rect, false, arrow_color);
1352
1353
} break;
1354
1355
case NOTIFICATION_MOUSE_EXIT: {
1356
_on_hover_exit();
1357
} break;
1358
}
1359
}
1360
1361
void EditorPropertyLayersGrid::set_flag(uint32_t p_flag) {
1362
value = p_flag;
1363
queue_redraw();
1364
}
1365
1366
void EditorPropertyLayersGrid::_bind_methods() {
1367
ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag")));
1368
ADD_SIGNAL(MethodInfo("rename_confirmed", PropertyInfo(Variant::INT, "layer_id"), PropertyInfo(Variant::STRING, "new_name")));
1369
}
1370
1371
void EditorPropertyLayers::_notification(int p_what) {
1372
switch (p_what) {
1373
case NOTIFICATION_THEME_CHANGED: {
1374
button->set_texture_normal(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
1375
button->set_texture_pressed(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
1376
button->set_texture_disabled(get_editor_theme_icon(SNAME("GuiTabMenu")));
1377
} break;
1378
}
1379
}
1380
1381
void EditorPropertyLayers::_set_read_only(bool p_read_only) {
1382
button->set_disabled(p_read_only);
1383
grid->set_read_only(p_read_only);
1384
}
1385
1386
void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
1387
emit_changed(get_edited_property(), p_grid);
1388
}
1389
1390
void EditorPropertyLayers::update_property() {
1391
uint32_t value = get_edited_property_value();
1392
1393
grid->set_flag(value);
1394
}
1395
1396
void EditorPropertyLayers::setup(LayerType p_layer_type) {
1397
layer_type = p_layer_type;
1398
int layer_group_size = 0;
1399
int layer_count = 0;
1400
switch (p_layer_type) {
1401
case LAYER_RENDER_2D: {
1402
basename = "layer_names/2d_render";
1403
layer_group_size = 5;
1404
layer_count = 20;
1405
} break;
1406
1407
case LAYER_PHYSICS_2D: {
1408
basename = "layer_names/2d_physics";
1409
layer_group_size = 4;
1410
layer_count = 32;
1411
} break;
1412
1413
case LAYER_NAVIGATION_2D: {
1414
basename = "layer_names/2d_navigation";
1415
layer_group_size = 4;
1416
layer_count = 32;
1417
} break;
1418
1419
case LAYER_RENDER_3D: {
1420
basename = "layer_names/3d_render";
1421
layer_group_size = 5;
1422
layer_count = 20;
1423
} break;
1424
1425
case LAYER_PHYSICS_3D: {
1426
basename = "layer_names/3d_physics";
1427
layer_group_size = 4;
1428
layer_count = 32;
1429
} break;
1430
1431
case LAYER_NAVIGATION_3D: {
1432
basename = "layer_names/3d_navigation";
1433
layer_group_size = 4;
1434
layer_count = 32;
1435
} break;
1436
1437
case LAYER_AVOIDANCE: {
1438
basename = "layer_names/avoidance";
1439
layer_group_size = 4;
1440
layer_count = 32;
1441
} break;
1442
}
1443
1444
Vector<String> names;
1445
Vector<String> tooltips;
1446
for (int i = 0; i < layer_count; i++) {
1447
String name;
1448
1449
if (ProjectSettings::get_singleton()->has_setting(basename + vformat("/layer_%d", i + 1))) {
1450
name = GLOBAL_GET(basename + vformat("/layer_%d", i + 1));
1451
}
1452
1453
if (name.is_empty()) {
1454
name = vformat(TTR("Layer %d"), i + 1);
1455
}
1456
1457
names.push_back(name);
1458
tooltips.push_back(name + "\n" + vformat(TTR("Bit %d, value %d"), i, 1u << i));
1459
}
1460
1461
grid->names = names;
1462
grid->tooltips = tooltips;
1463
grid->layer_group_size = layer_group_size;
1464
grid->layer_count = layer_count;
1465
}
1466
1467
void EditorPropertyLayers::set_layer_name(int p_index, const String &p_name) {
1468
const String property_name = basename + vformat("/layer_%d", p_index + 1);
1469
if (ProjectSettings::get_singleton()->has_setting(property_name)) {
1470
ProjectSettings::get_singleton()->set(property_name, p_name);
1471
ProjectSettings::get_singleton()->save();
1472
}
1473
}
1474
1475
String EditorPropertyLayers::get_layer_name(int p_index) const {
1476
const String property_name = basename + vformat("/layer_%d", p_index + 1);
1477
if (ProjectSettings::get_singleton()->has_setting(property_name)) {
1478
return GLOBAL_GET(property_name);
1479
}
1480
return String();
1481
}
1482
1483
void EditorPropertyLayers::_button_pressed() {
1484
int layer_count = grid->layer_count;
1485
layers->clear();
1486
for (int i = 0; i < layer_count; i++) {
1487
const String name = get_layer_name(i);
1488
if (name.is_empty()) {
1489
continue;
1490
}
1491
layers->add_check_item(name, i);
1492
int idx = layers->get_item_index(i);
1493
layers->set_item_checked(idx, grid->value & (1u << i));
1494
}
1495
1496
if (layers->get_item_count() == 0) {
1497
layers->add_item(TTR("No Named Layers"));
1498
layers->set_item_disabled(0, true);
1499
}
1500
layers->add_separator();
1501
layers->add_icon_item(get_editor_theme_icon("Edit"), TTR("Edit Layer Names"), grid->layer_count);
1502
1503
Rect2 gp = button->get_screen_rect();
1504
layers->reset_size();
1505
Vector2 popup_pos = gp.position - Vector2(layers->get_contents_minimum_size().x, 0);
1506
layers->set_position(popup_pos);
1507
layers->popup();
1508
}
1509
1510
void EditorPropertyLayers::_menu_pressed(int p_menu) {
1511
if (uint32_t(p_menu) == grid->layer_count) {
1512
ProjectSettingsEditor::get_singleton()->popup_project_settings(true);
1513
ProjectSettingsEditor::get_singleton()->set_general_page(basename);
1514
} else {
1515
grid->value ^= 1u << p_menu;
1516
grid->queue_redraw();
1517
layers->set_item_checked(layers->get_item_index(p_menu), grid->value & (1u << p_menu));
1518
_grid_changed(grid->value);
1519
}
1520
}
1521
1522
void EditorPropertyLayers::_refresh_names() {
1523
setup(layer_type);
1524
}
1525
1526
EditorPropertyLayers::EditorPropertyLayers() {
1527
HBoxContainer *hb = memnew(HBoxContainer);
1528
hb->set_clip_contents(true);
1529
add_child(hb);
1530
grid = memnew(EditorPropertyLayersGrid);
1531
grid->connect("flag_changed", callable_mp(this, &EditorPropertyLayers::_grid_changed));
1532
grid->connect("rename_confirmed", callable_mp(this, &EditorPropertyLayers::set_layer_name));
1533
grid->set_h_size_flags(SIZE_EXPAND_FILL);
1534
hb->add_child(grid);
1535
1536
button = memnew(TextureButton);
1537
button->set_accessibility_name(TTRC("Layers"));
1538
button->set_stretch_mode(TextureButton::STRETCH_KEEP_CENTERED);
1539
button->set_toggle_mode(true);
1540
button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLayers::_button_pressed));
1541
hb->add_child(button);
1542
1543
set_bottom_editor(hb);
1544
1545
layers = memnew(PopupMenu);
1546
add_child(layers);
1547
layers->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1548
layers->set_hide_on_checkable_item_selection(false);
1549
layers->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyLayers::_menu_pressed));
1550
layers->connect("popup_hide", callable_mp((BaseButton *)button, &BaseButton::set_pressed).bind(false));
1551
ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorPropertyLayers::_refresh_names));
1552
}
1553
1554
///////////////////// INT /////////////////////////
1555
1556
void EditorPropertyInteger::_set_read_only(bool p_read_only) {
1557
spin->set_read_only(p_read_only);
1558
}
1559
1560
void EditorPropertyInteger::_value_changed(int64_t val) {
1561
emit_changed(get_edited_property(), val);
1562
}
1563
1564
void EditorPropertyInteger::update_property() {
1565
int64_t val = get_edited_property_display_value();
1566
spin->set_value_no_signal(val);
1567
#ifdef DEBUG_ENABLED
1568
// If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem.
1569
if (val != (int64_t)(double)(val)) {
1570
WARN_PRINT("Cannot reliably represent '" + itos(val) + "' in the inspector, value is too large.");
1571
}
1572
#endif
1573
}
1574
1575
void EditorPropertyInteger::setup(const EditorPropertyRangeHint &p_range_hint) {
1576
spin->set_min(p_range_hint.min);
1577
spin->set_max(p_range_hint.max);
1578
spin->set_step(Math::round(p_range_hint.step));
1579
if (p_range_hint.hide_control) {
1580
spin->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
1581
} else {
1582
spin->set_control_state(p_range_hint.prefer_slider ? EditorSpinSlider::CONTROL_STATE_PREFER_SLIDER : EditorSpinSlider::CONTROL_STATE_DEFAULT);
1583
}
1584
spin->set_allow_greater(p_range_hint.or_greater);
1585
spin->set_allow_lesser(p_range_hint.or_less);
1586
spin->set_suffix(p_range_hint.suffix);
1587
}
1588
1589
EditorPropertyInteger::EditorPropertyInteger() {
1590
spin = memnew(EditorSpinSlider);
1591
spin->set_flat(true);
1592
spin->set_editing_integer(true);
1593
add_child(spin);
1594
add_focusable(spin);
1595
spin->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyInteger::_value_changed));
1596
}
1597
1598
///////////////////// OBJECT ID /////////////////////////
1599
1600
void EditorPropertyObjectID::_set_read_only(bool p_read_only) {
1601
edit->set_disabled(p_read_only);
1602
}
1603
1604
void EditorPropertyObjectID::_edit_pressed() {
1605
emit_signal(SNAME("object_id_selected"), get_edited_property(), get_edited_property_value());
1606
}
1607
1608
void EditorPropertyObjectID::update_property() {
1609
String type = base_type;
1610
if (type.is_empty()) {
1611
type = "Object";
1612
}
1613
1614
ObjectID id = get_edited_property_value();
1615
if (id.is_valid()) {
1616
edit->set_text(type + " ID: " + uitos(id));
1617
edit->set_tooltip_text(type + " ID: " + uitos(id));
1618
edit->set_disabled(false);
1619
edit->set_button_icon(EditorNode::get_singleton()->get_class_icon(type));
1620
} else {
1621
edit->set_text(TTR("<empty>"));
1622
edit->set_tooltip_text("");
1623
edit->set_disabled(true);
1624
edit->set_button_icon(Ref<Texture2D>());
1625
}
1626
}
1627
1628
void EditorPropertyObjectID::setup(const String &p_base_type) {
1629
base_type = p_base_type;
1630
}
1631
1632
EditorPropertyObjectID::EditorPropertyObjectID() {
1633
edit = memnew(Button);
1634
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
1635
edit->set_accessibility_name(TTRC("Edit"));
1636
add_child(edit);
1637
add_focusable(edit);
1638
edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1639
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyObjectID::_edit_pressed));
1640
}
1641
1642
///////////////////// SIGNAL /////////////////////////
1643
1644
void EditorPropertySignal::_edit_pressed() {
1645
Signal signal = get_edited_property_value();
1646
emit_signal(SNAME("object_id_selected"), get_edited_property(), signal.get_object_id());
1647
}
1648
1649
void EditorPropertySignal::update_property() {
1650
String type = base_type;
1651
1652
Signal signal = get_edited_property_value();
1653
1654
edit->set_text("Signal: " + signal.get_name());
1655
edit->set_disabled(false);
1656
edit->set_button_icon(get_editor_theme_icon(SNAME("Signals")));
1657
}
1658
1659
EditorPropertySignal::EditorPropertySignal() {
1660
edit = memnew(Button);
1661
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
1662
edit->set_accessibility_name(TTRC("Edit"));
1663
add_child(edit);
1664
add_focusable(edit);
1665
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertySignal::_edit_pressed));
1666
}
1667
1668
///////////////////// CALLABLE /////////////////////////
1669
1670
void EditorPropertyCallable::update_property() {
1671
String type = base_type;
1672
1673
Callable callable = get_edited_property_value();
1674
1675
edit->set_text("Callable");
1676
edit->set_disabled(true);
1677
edit->set_button_icon(get_editor_theme_icon(SNAME("Callable")));
1678
}
1679
1680
EditorPropertyCallable::EditorPropertyCallable() {
1681
edit = memnew(Button);
1682
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
1683
edit->set_accessibility_name(TTRC("Edit"));
1684
add_child(edit);
1685
add_focusable(edit);
1686
}
1687
1688
///////////////////// FLOAT /////////////////////////
1689
1690
void EditorPropertyFloat::_set_read_only(bool p_read_only) {
1691
spin->set_read_only(p_read_only);
1692
}
1693
1694
void EditorPropertyFloat::_value_changed(double val) {
1695
if (radians_as_degrees) {
1696
val = Math::deg_to_rad(val);
1697
}
1698
emit_changed(get_edited_property(), val);
1699
}
1700
1701
void EditorPropertyFloat::update_property() {
1702
double val = get_edited_property_value();
1703
if (radians_as_degrees) {
1704
val = Math::rad_to_deg(val);
1705
}
1706
spin->set_value_no_signal(val);
1707
}
1708
1709
void EditorPropertyFloat::setup(const EditorPropertyRangeHint &p_range_hint) {
1710
radians_as_degrees = p_range_hint.radians_as_degrees;
1711
spin->set_min(p_range_hint.min);
1712
spin->set_max(p_range_hint.max);
1713
spin->set_step(p_range_hint.step);
1714
if (p_range_hint.hide_control) {
1715
spin->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
1716
}
1717
spin->set_exp_ratio(p_range_hint.exp_range);
1718
spin->set_allow_greater(p_range_hint.or_greater);
1719
spin->set_allow_lesser(p_range_hint.or_less);
1720
spin->set_suffix(p_range_hint.suffix);
1721
}
1722
1723
EditorPropertyFloat::EditorPropertyFloat() {
1724
spin = memnew(EditorSpinSlider);
1725
spin->set_flat(true);
1726
add_child(spin);
1727
add_focusable(spin);
1728
spin->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyFloat::_value_changed));
1729
}
1730
1731
///////////////////// EASING /////////////////////////
1732
1733
void EditorPropertyEasing::_set_read_only(bool p_read_only) {
1734
spin->set_read_only(p_read_only);
1735
}
1736
1737
void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {
1738
if (is_read_only()) {
1739
return;
1740
}
1741
const Ref<InputEventMouseButton> mb = p_ev;
1742
if (mb.is_valid()) {
1743
if (mb->is_double_click() && mb->get_button_index() == MouseButton::LEFT) {
1744
_setup_spin();
1745
}
1746
1747
if (mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
1748
preset->set_position(easing_draw->get_screen_position() + mb->get_position());
1749
preset->reset_size();
1750
preset->popup();
1751
1752
// Ensure the easing doesn't appear as being dragged
1753
dragging = false;
1754
easing_draw->queue_redraw();
1755
}
1756
1757
if (mb->get_button_index() == MouseButton::LEFT) {
1758
dragging = mb->is_pressed();
1759
// Update to display the correct dragging color
1760
easing_draw->queue_redraw();
1761
}
1762
}
1763
1764
const Ref<InputEventMouseMotion> mm = p_ev;
1765
1766
if (dragging && mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
1767
float rel = mm->get_relative().x;
1768
if (rel == 0) {
1769
return;
1770
}
1771
1772
if (flip) {
1773
rel = -rel;
1774
}
1775
1776
float val = get_edited_property_value();
1777
bool sg = val < 0;
1778
val = Math::abs(val);
1779
1780
val = Math::log(val) / Math::log((float)2.0);
1781
// Logarithmic space.
1782
val += rel * 0.05;
1783
1784
val = Math::pow(2.0f, val);
1785
if (sg) {
1786
val = -val;
1787
}
1788
1789
// 0 is a singularity, but both positive and negative values
1790
// are otherwise allowed. Enforce 0+ as workaround.
1791
if (Math::is_zero_approx(val)) {
1792
val = 0.00001;
1793
}
1794
1795
// Limit to a reasonable value to prevent the curve going into infinity,
1796
// which can cause crashes and other issues.
1797
val = CLAMP(val, -1'000'000, 1'000'000);
1798
1799
emit_changed(get_edited_property(), val);
1800
}
1801
}
1802
1803
void EditorPropertyEasing::_draw_easing() {
1804
RID ci = easing_draw->get_canvas_item();
1805
1806
Size2 s = easing_draw->get_size();
1807
1808
const int point_count = 48;
1809
1810
const float exp = get_edited_property_value();
1811
1812
const Ref<Font> f = get_theme_font(SceneStringName(font), SNAME("Label"));
1813
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1814
const Color font_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
1815
Color line_color;
1816
if (dragging) {
1817
line_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1818
} else {
1819
line_color = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit")) * Color(1, 1, 1, 0.9);
1820
}
1821
1822
Vector<Point2> points;
1823
for (int i = 0; i <= point_count; i++) {
1824
float ifl = i / float(point_count);
1825
1826
const float h = 1.0 - Math::ease(ifl, exp);
1827
1828
if (flip) {
1829
ifl = 1.0 - ifl;
1830
}
1831
1832
points.push_back(Point2(ifl * s.width, h * s.height));
1833
}
1834
1835
easing_draw->draw_polyline(points, line_color, 1.0, true);
1836
// Draw more decimals for small numbers since higher precision is usually required for fine adjustments.
1837
int decimals;
1838
if (Math::abs(exp) < 0.1 - CMP_EPSILON) {
1839
decimals = 4;
1840
} else if (Math::abs(exp) < 1 - CMP_EPSILON) {
1841
decimals = 3;
1842
} else if (Math::abs(exp) < 10 - CMP_EPSILON) {
1843
decimals = 2;
1844
} else {
1845
decimals = 1;
1846
}
1847
1848
const String &formatted = TranslationServer::get_singleton()->format_number(rtos(exp).pad_decimals(decimals), _get_locale());
1849
f->draw_string(ci, Point2(10, 10 + f->get_ascent(font_size)), formatted, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color);
1850
}
1851
1852
void EditorPropertyEasing::update_property() {
1853
float val = get_edited_property_value();
1854
spin->set_value_no_signal(val);
1855
1856
easing_draw->queue_redraw();
1857
}
1858
1859
void EditorPropertyEasing::_set_preset(int p_preset) {
1860
static const float preset_value[EASING_MAX] = { 0.0, 1.0, 2.0, 0.5, -2.0, -0.5 };
1861
1862
emit_changed(get_edited_property(), preset_value[p_preset]);
1863
}
1864
1865
void EditorPropertyEasing::_setup_spin() {
1866
spin->setup_and_show();
1867
spin->get_line_edit()->set_text(TranslationServer::get_singleton()->format_number(rtos(get_edited_property_value()), _get_locale()));
1868
spin->show();
1869
}
1870
1871
void EditorPropertyEasing::_spin_value_changed(double p_value) {
1872
// Limit to a reasonable value to prevent the curve going into infinity,
1873
// which can cause crashes and other issues.
1874
p_value = CLAMP(p_value, -1'000'000, 1'000'000);
1875
1876
if (positive_only) {
1877
// Force a positive or zero value if a negative value was manually entered by double-clicking.
1878
p_value = MAX(0.0, p_value);
1879
}
1880
1881
emit_changed(get_edited_property(), p_value);
1882
_spin_focus_exited();
1883
}
1884
1885
void EditorPropertyEasing::_spin_focus_exited() {
1886
spin->hide();
1887
// Ensure the easing doesn't appear as being dragged
1888
dragging = false;
1889
easing_draw->queue_redraw();
1890
}
1891
1892
void EditorPropertyEasing::setup(bool p_positive_only, bool p_flip) {
1893
flip = p_flip;
1894
positive_only = p_positive_only;
1895
1896
// Names need translation context, so they are set in NOTIFICATION_TRANSLATION_CHANGED.
1897
preset->add_item("", EASING_LINEAR);
1898
preset->add_item("", EASING_IN);
1899
preset->add_item("", EASING_OUT);
1900
preset->add_item("", EASING_ZERO);
1901
if (!positive_only) {
1902
preset->add_item("", EASING_IN_OUT);
1903
preset->add_item("", EASING_OUT_IN);
1904
}
1905
}
1906
1907
void EditorPropertyEasing::_notification(int p_what) {
1908
switch (p_what) {
1909
case NOTIFICATION_THEME_CHANGED: {
1910
preset->set_item_icon(preset->get_item_index(EASING_LINEAR), get_editor_theme_icon(SNAME("CurveLinear")));
1911
preset->set_item_icon(preset->get_item_index(EASING_IN), get_editor_theme_icon(SNAME("CurveIn")));
1912
preset->set_item_icon(preset->get_item_index(EASING_OUT), get_editor_theme_icon(SNAME("CurveOut")));
1913
preset->set_item_icon(preset->get_item_index(EASING_ZERO), get_editor_theme_icon(SNAME("CurveConstant")));
1914
if (!positive_only) {
1915
preset->set_item_icon(preset->get_item_index(EASING_IN_OUT), get_editor_theme_icon(SNAME("CurveInOut")));
1916
preset->set_item_icon(preset->get_item_index(EASING_OUT_IN), get_editor_theme_icon(SNAME("CurveOutIn")));
1917
}
1918
easing_draw->set_custom_minimum_size(Size2(0, get_theme_font(SceneStringName(font), SNAME("Label"))->get_height(get_theme_font_size(SceneStringName(font_size), SNAME("Label"))) * 2));
1919
} break;
1920
1921
case NOTIFICATION_TRANSLATION_CHANGED: {
1922
preset->set_item_text(preset->get_item_index(EASING_LINEAR), TTR("Linear", "Ease Type"));
1923
preset->set_item_text(preset->get_item_index(EASING_IN), TTR("Ease In", "Ease Type"));
1924
preset->set_item_text(preset->get_item_index(EASING_OUT), TTR("Ease Out", "Ease Type"));
1925
preset->set_item_text(preset->get_item_index(EASING_ZERO), TTR("Zero", "Ease Type"));
1926
if (!positive_only) {
1927
preset->set_item_text(preset->get_item_index(EASING_IN_OUT), TTR("Ease In-Out", "Ease Type"));
1928
preset->set_item_text(preset->get_item_index(EASING_OUT_IN), TTR("Ease Out-In", "Ease Type"));
1929
}
1930
} break;
1931
}
1932
}
1933
1934
EditorPropertyEasing::EditorPropertyEasing() {
1935
easing_draw = memnew(Control);
1936
easing_draw->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyEasing::_draw_easing));
1937
easing_draw->connect(SceneStringName(gui_input), callable_mp(this, &EditorPropertyEasing::_drag_easing));
1938
easing_draw->set_default_cursor_shape(Control::CURSOR_MOVE);
1939
add_child(easing_draw);
1940
1941
preset = memnew(PopupMenu);
1942
preset->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1943
add_child(preset);
1944
preset->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyEasing::_set_preset));
1945
1946
spin = memnew(EditorSpinSlider);
1947
spin->set_flat(true);
1948
spin->set_min(-100);
1949
spin->set_max(100);
1950
spin->set_step(0);
1951
spin->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
1952
spin->set_allow_lesser(true);
1953
spin->set_allow_greater(true);
1954
spin->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyEasing::_spin_value_changed));
1955
spin->get_line_edit()->connect(SceneStringName(focus_exited), callable_mp(this, &EditorPropertyEasing::_spin_focus_exited));
1956
spin->hide();
1957
add_child(spin);
1958
}
1959
1960
///////////////////// RECT2 /////////////////////////
1961
1962
void EditorPropertyRect2::_set_read_only(bool p_read_only) {
1963
for (int i = 0; i < 4; i++) {
1964
spin[i]->set_read_only(p_read_only);
1965
}
1966
}
1967
1968
void EditorPropertyRect2::_value_changed(double val, const String &p_name) {
1969
Rect2 r2;
1970
r2.position.x = spin[0]->get_value();
1971
r2.position.y = spin[1]->get_value();
1972
r2.size.x = spin[2]->get_value();
1973
r2.size.y = spin[3]->get_value();
1974
emit_changed(get_edited_property(), r2, p_name);
1975
}
1976
1977
void EditorPropertyRect2::update_property() {
1978
Rect2 val = get_edited_property_value();
1979
spin[0]->set_value_no_signal(val.position.x);
1980
spin[1]->set_value_no_signal(val.position.y);
1981
spin[2]->set_value_no_signal(val.size.x);
1982
spin[3]->set_value_no_signal(val.size.y);
1983
}
1984
1985
void EditorPropertyRect2::_notification(int p_what) {
1986
switch (p_what) {
1987
case NOTIFICATION_THEME_CHANGED: {
1988
const Color *colors = _get_property_colors();
1989
for (int i = 0; i < 4; i++) {
1990
spin[i]->add_theme_color_override("label_color", colors[i % 2]);
1991
}
1992
} break;
1993
}
1994
}
1995
1996
void EditorPropertyRect2::setup(const EditorPropertyRangeHint &p_range_hint) {
1997
for (int i = 0; i < 4; i++) {
1998
spin[i]->set_min(p_range_hint.min);
1999
spin[i]->set_max(p_range_hint.max);
2000
spin[i]->set_step(p_range_hint.step);
2001
if (p_range_hint.hide_control) {
2002
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2003
}
2004
spin[i]->set_allow_greater(true);
2005
spin[i]->set_allow_lesser(true);
2006
spin[i]->set_suffix(p_range_hint.suffix);
2007
}
2008
}
2009
2010
EditorPropertyRect2::EditorPropertyRect2(bool p_force_wide) {
2011
bool horizontal = p_force_wide || bool(EDITOR_GET("interface/inspector/horizontal_vector_types_editing"));
2012
bool grid = false;
2013
BoxContainer *bc;
2014
2015
if (p_force_wide) {
2016
bc = memnew(HBoxContainer);
2017
add_child(bc);
2018
} else if (horizontal) {
2019
bc = memnew(VBoxContainer);
2020
add_child(bc);
2021
set_bottom_editor(bc);
2022
2023
bc->add_child(memnew(HBoxContainer));
2024
bc->add_child(memnew(HBoxContainer));
2025
grid = true;
2026
} else {
2027
bc = memnew(VBoxContainer);
2028
add_child(bc);
2029
}
2030
2031
static const char *desc[4] = { "x", "y", "w", "h" };
2032
for (int i = 0; i < 4; i++) {
2033
spin[i] = memnew(EditorSpinSlider);
2034
spin[i]->set_label(desc[i]);
2035
spin[i]->set_accessibility_name(desc[i]);
2036
spin[i]->set_flat(true);
2037
2038
if (grid) {
2039
bc->get_child(i / 2)->add_child(spin[i]);
2040
} else {
2041
bc->add_child(spin[i]);
2042
}
2043
2044
add_focusable(spin[i]);
2045
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyRect2::_value_changed).bind(desc[i]));
2046
if (horizontal) {
2047
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2048
}
2049
}
2050
2051
if (!horizontal) {
2052
set_label_reference(spin[0]); //show text and buttons around this
2053
}
2054
}
2055
2056
///////////////////// RECT2i /////////////////////////
2057
2058
void EditorPropertyRect2i::_set_read_only(bool p_read_only) {
2059
for (int i = 0; i < 4; i++) {
2060
spin[i]->set_read_only(p_read_only);
2061
}
2062
}
2063
2064
void EditorPropertyRect2i::_value_changed(double val, const String &p_name) {
2065
Rect2i r2;
2066
r2.position.x = spin[0]->get_value();
2067
r2.position.y = spin[1]->get_value();
2068
r2.size.x = spin[2]->get_value();
2069
r2.size.y = spin[3]->get_value();
2070
emit_changed(get_edited_property(), r2, p_name);
2071
}
2072
2073
void EditorPropertyRect2i::update_property() {
2074
Rect2i val = get_edited_property_value();
2075
spin[0]->set_value_no_signal(val.position.x);
2076
spin[1]->set_value_no_signal(val.position.y);
2077
spin[2]->set_value_no_signal(val.size.x);
2078
spin[3]->set_value_no_signal(val.size.y);
2079
}
2080
2081
void EditorPropertyRect2i::_notification(int p_what) {
2082
switch (p_what) {
2083
case NOTIFICATION_THEME_CHANGED: {
2084
const Color *colors = _get_property_colors();
2085
for (int i = 0; i < 4; i++) {
2086
spin[i]->add_theme_color_override("label_color", colors[i % 2]);
2087
}
2088
} break;
2089
}
2090
}
2091
2092
void EditorPropertyRect2i::setup(const EditorPropertyRangeHint &p_range_hint) {
2093
for (int i = 0; i < 4; i++) {
2094
spin[i]->set_min(p_range_hint.min);
2095
spin[i]->set_max(p_range_hint.max);
2096
spin[i]->set_step(1);
2097
spin[i]->set_allow_greater(true);
2098
spin[i]->set_allow_lesser(true);
2099
spin[i]->set_suffix(p_range_hint.suffix);
2100
spin[i]->set_editing_integer(true);
2101
}
2102
}
2103
2104
EditorPropertyRect2i::EditorPropertyRect2i(bool p_force_wide) {
2105
bool horizontal = p_force_wide || bool(EDITOR_GET("interface/inspector/horizontal_vector_types_editing"));
2106
bool grid = false;
2107
BoxContainer *bc;
2108
2109
if (p_force_wide) {
2110
bc = memnew(HBoxContainer);
2111
add_child(bc);
2112
} else if (horizontal) {
2113
bc = memnew(VBoxContainer);
2114
add_child(bc);
2115
set_bottom_editor(bc);
2116
2117
bc->add_child(memnew(HBoxContainer));
2118
bc->add_child(memnew(HBoxContainer));
2119
grid = true;
2120
} else {
2121
bc = memnew(VBoxContainer);
2122
add_child(bc);
2123
}
2124
2125
static const char *desc[4] = { "x", "y", "w", "h" };
2126
for (int i = 0; i < 4; i++) {
2127
spin[i] = memnew(EditorSpinSlider);
2128
spin[i]->set_label(desc[i]);
2129
spin[i]->set_accessibility_name(desc[i]);
2130
spin[i]->set_flat(true);
2131
2132
if (grid) {
2133
bc->get_child(i / 2)->add_child(spin[i]);
2134
} else {
2135
bc->add_child(spin[i]);
2136
}
2137
2138
add_focusable(spin[i]);
2139
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyRect2i::_value_changed).bind(desc[i]));
2140
if (horizontal) {
2141
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2142
}
2143
}
2144
2145
if (!horizontal) {
2146
set_label_reference(spin[0]); //show text and buttons around this
2147
}
2148
}
2149
2150
///////////////////// PLANE /////////////////////////
2151
2152
void EditorPropertyPlane::_set_read_only(bool p_read_only) {
2153
for (int i = 0; i < 4; i++) {
2154
spin[i]->set_read_only(p_read_only);
2155
}
2156
}
2157
2158
void EditorPropertyPlane::_value_changed(double val, const String &p_name) {
2159
Plane p;
2160
p.normal.x = spin[0]->get_value();
2161
p.normal.y = spin[1]->get_value();
2162
p.normal.z = spin[2]->get_value();
2163
p.d = spin[3]->get_value();
2164
emit_changed(get_edited_property(), p, p_name);
2165
}
2166
2167
void EditorPropertyPlane::update_property() {
2168
Plane val = get_edited_property_value();
2169
spin[0]->set_value_no_signal(val.normal.x);
2170
spin[1]->set_value_no_signal(val.normal.y);
2171
spin[2]->set_value_no_signal(val.normal.z);
2172
spin[3]->set_value_no_signal(val.d);
2173
}
2174
2175
void EditorPropertyPlane::_notification(int p_what) {
2176
switch (p_what) {
2177
case NOTIFICATION_THEME_CHANGED: {
2178
const Color *colors = _get_property_colors();
2179
for (int i = 0; i < 4; i++) {
2180
spin[i]->add_theme_color_override("label_color", colors[i]);
2181
}
2182
} break;
2183
}
2184
}
2185
2186
void EditorPropertyPlane::setup(const EditorPropertyRangeHint &p_range_hint) {
2187
for (int i = 0; i < 4; i++) {
2188
spin[i]->set_min(p_range_hint.min);
2189
spin[i]->set_max(p_range_hint.max);
2190
spin[i]->set_step(p_range_hint.step);
2191
if (p_range_hint.hide_control) {
2192
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2193
}
2194
spin[i]->set_allow_greater(true);
2195
spin[i]->set_allow_lesser(true);
2196
}
2197
spin[3]->set_suffix(p_range_hint.suffix);
2198
}
2199
2200
EditorPropertyPlane::EditorPropertyPlane(bool p_force_wide) {
2201
bool horizontal = p_force_wide || bool(EDITOR_GET("interface/inspector/horizontal_vector_types_editing"));
2202
2203
BoxContainer *bc;
2204
2205
if (p_force_wide) {
2206
bc = memnew(HBoxContainer);
2207
add_child(bc);
2208
} else if (horizontal) {
2209
bc = memnew(HBoxContainer);
2210
add_child(bc);
2211
set_bottom_editor(bc);
2212
} else {
2213
bc = memnew(VBoxContainer);
2214
add_child(bc);
2215
}
2216
2217
static const char *desc[4] = { "x", "y", "z", "d" };
2218
for (int i = 0; i < 4; i++) {
2219
spin[i] = memnew(EditorSpinSlider);
2220
spin[i]->set_flat(true);
2221
spin[i]->set_label(desc[i]);
2222
spin[i]->set_accessibility_name(desc[i]);
2223
bc->add_child(spin[i]);
2224
add_focusable(spin[i]);
2225
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyPlane::_value_changed).bind(desc[i]));
2226
if (horizontal) {
2227
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2228
}
2229
}
2230
2231
if (!horizontal) {
2232
set_label_reference(spin[0]); //show text and buttons around this
2233
}
2234
}
2235
2236
///////////////////// QUATERNION /////////////////////////
2237
2238
void EditorPropertyQuaternion::_set_read_only(bool p_read_only) {
2239
for (int i = 0; i < 4; i++) {
2240
spin[i]->set_read_only(p_read_only);
2241
}
2242
for (int i = 0; i < 3; i++) {
2243
euler[i]->set_read_only(p_read_only);
2244
}
2245
}
2246
2247
void EditorPropertyQuaternion::_edit_custom_value() {
2248
if (edit_button->is_pressed()) {
2249
edit_custom_bc->show();
2250
for (int i = 0; i < 3; i++) {
2251
euler[i]->grab_focus();
2252
}
2253
} else {
2254
edit_custom_bc->hide();
2255
for (int i = 0; i < 4; i++) {
2256
spin[i]->grab_focus();
2257
}
2258
}
2259
update_property();
2260
}
2261
2262
void EditorPropertyQuaternion::_custom_value_changed(double val) {
2263
edit_euler.x = euler[0]->get_value();
2264
edit_euler.y = euler[1]->get_value();
2265
edit_euler.z = euler[2]->get_value();
2266
2267
Vector3 v;
2268
v.x = Math::deg_to_rad(edit_euler.x);
2269
v.y = Math::deg_to_rad(edit_euler.y);
2270
v.z = Math::deg_to_rad(edit_euler.z);
2271
2272
Quaternion temp_q = Quaternion::from_euler(v);
2273
spin[0]->set_value_no_signal(temp_q.x);
2274
spin[1]->set_value_no_signal(temp_q.y);
2275
spin[2]->set_value_no_signal(temp_q.z);
2276
spin[3]->set_value_no_signal(temp_q.w);
2277
_value_changed(-1, "");
2278
}
2279
2280
void EditorPropertyQuaternion::_value_changed(double val, const String &p_name) {
2281
Quaternion p;
2282
p.x = spin[0]->get_value();
2283
p.y = spin[1]->get_value();
2284
p.z = spin[2]->get_value();
2285
p.w = spin[3]->get_value();
2286
2287
emit_changed(get_edited_property(), p, p_name);
2288
}
2289
2290
bool EditorPropertyQuaternion::is_grabbing_euler() {
2291
bool is_grabbing = false;
2292
for (int i = 0; i < 3; i++) {
2293
is_grabbing |= euler[i]->is_grabbing();
2294
}
2295
return is_grabbing;
2296
}
2297
2298
void EditorPropertyQuaternion::update_property() {
2299
Quaternion val = get_edited_property_value();
2300
spin[0]->set_value_no_signal(val.x);
2301
spin[1]->set_value_no_signal(val.y);
2302
spin[2]->set_value_no_signal(val.z);
2303
spin[3]->set_value_no_signal(val.w);
2304
if (!is_grabbing_euler()) {
2305
Vector3 v = val.normalized().get_euler();
2306
edit_euler.x = Math::rad_to_deg(v.x);
2307
edit_euler.y = Math::rad_to_deg(v.y);
2308
edit_euler.z = Math::rad_to_deg(v.z);
2309
euler[0]->set_value_no_signal(edit_euler.x);
2310
euler[1]->set_value_no_signal(edit_euler.y);
2311
euler[2]->set_value_no_signal(edit_euler.z);
2312
}
2313
}
2314
2315
void EditorPropertyQuaternion::_warning_pressed() {
2316
warning_dialog->popup_centered();
2317
}
2318
2319
void EditorPropertyQuaternion::_notification(int p_what) {
2320
switch (p_what) {
2321
case NOTIFICATION_THEME_CHANGED: {
2322
const Color *colors = _get_property_colors();
2323
for (int i = 0; i < 4; i++) {
2324
spin[i]->add_theme_color_override("label_color", colors[i]);
2325
}
2326
for (int i = 0; i < 3; i++) {
2327
euler[i]->add_theme_color_override("label_color", colors[i]);
2328
}
2329
edit_button->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
2330
euler_label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("property_color"), SNAME("EditorProperty")));
2331
warning->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
2332
warning->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
2333
} break;
2334
}
2335
}
2336
2337
void EditorPropertyQuaternion::setup(const EditorPropertyRangeHint &p_range_hint, bool p_hide_editor) {
2338
for (int i = 0; i < 4; i++) {
2339
spin[i]->set_min(p_range_hint.min);
2340
spin[i]->set_max(p_range_hint.max);
2341
spin[i]->set_step(p_range_hint.step);
2342
if (p_range_hint.hide_control) {
2343
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2344
}
2345
spin[i]->set_allow_greater(true);
2346
spin[i]->set_allow_lesser(true);
2347
// Quaternion is inherently unitless, however someone may want to use it as
2348
// a generic way to store 4 values, so we'll still respect the suffix.
2349
spin[i]->set_suffix(p_range_hint.suffix);
2350
}
2351
2352
for (int i = 0; i < 3; i++) {
2353
euler[i]->set_min(-360);
2354
euler[i]->set_max(360);
2355
euler[i]->set_step(0.1);
2356
euler[i]->set_allow_greater(true);
2357
euler[i]->set_allow_lesser(true);
2358
euler[i]->set_suffix(U"\u00B0");
2359
}
2360
2361
if (p_hide_editor) {
2362
edit_button->hide();
2363
}
2364
}
2365
2366
EditorPropertyQuaternion::EditorPropertyQuaternion() {
2367
bool horizontal = EDITOR_GET("interface/inspector/horizontal_vector_types_editing");
2368
2369
VBoxContainer *bc = memnew(VBoxContainer);
2370
edit_custom_bc = memnew(VBoxContainer);
2371
BoxContainer *edit_custom_layout;
2372
if (horizontal) {
2373
default_layout = memnew(HBoxContainer);
2374
edit_custom_layout = memnew(HBoxContainer);
2375
set_bottom_editor(bc);
2376
} else {
2377
default_layout = memnew(VBoxContainer);
2378
edit_custom_layout = memnew(VBoxContainer);
2379
}
2380
edit_custom_bc->hide();
2381
add_child(bc);
2382
edit_custom_bc->set_h_size_flags(SIZE_EXPAND_FILL);
2383
default_layout->set_h_size_flags(SIZE_EXPAND_FILL);
2384
edit_custom_layout->set_h_size_flags(SIZE_EXPAND_FILL);
2385
bc->add_child(default_layout);
2386
bc->add_child(edit_custom_bc);
2387
2388
static const char *desc[4] = { "x", "y", "z", "w" };
2389
for (int i = 0; i < 4; i++) {
2390
spin[i] = memnew(EditorSpinSlider);
2391
spin[i]->set_flat(true);
2392
spin[i]->set_label(desc[i]);
2393
spin[i]->set_accessibility_name(desc[i]);
2394
default_layout->add_child(spin[i]);
2395
add_focusable(spin[i]);
2396
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyQuaternion::_value_changed).bind(desc[i]));
2397
if (horizontal) {
2398
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2399
}
2400
}
2401
2402
warning = memnew(Button);
2403
warning->set_text(TTR("Temporary Euler may be changed implicitly!"));
2404
warning->set_clip_text(true);
2405
warning->set_theme_type_variation(SNAME("EditorInspectorButton"));
2406
warning->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyQuaternion::_warning_pressed));
2407
warning_dialog = memnew(AcceptDialog);
2408
add_child(warning_dialog);
2409
warning_dialog->set_text(TTR("Temporary Euler will not be stored in the object with the original value. Instead, it will be stored as Quaternion with irreversible conversion.\nThis is due to the fact that the result of Euler->Quaternion can be determined uniquely, but the result of Quaternion->Euler can be multi-existent."));
2410
2411
euler_label = memnew(Label);
2412
euler_label->set_text(TTR("Temporary Euler"));
2413
2414
edit_custom_bc->add_child(warning);
2415
edit_custom_bc->add_child(edit_custom_layout);
2416
edit_custom_layout->add_child(euler_label);
2417
2418
for (int i = 0; i < 3; i++) {
2419
euler[i] = memnew(EditorSpinSlider);
2420
euler[i]->set_flat(true);
2421
euler[i]->set_label(desc[i]);
2422
euler[i]->set_accessibility_name(vformat(TTR("Temporary Euler %s"), desc[i]));
2423
edit_custom_layout->add_child(euler[i]);
2424
add_focusable(euler[i]);
2425
euler[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyQuaternion::_custom_value_changed));
2426
if (horizontal) {
2427
euler[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2428
}
2429
}
2430
2431
edit_button = memnew(Button);
2432
edit_button->set_accessibility_name(TTRC("Edit"));
2433
edit_button->set_flat(true);
2434
edit_button->set_toggle_mode(true);
2435
edit_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
2436
default_layout->add_child(edit_button);
2437
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyQuaternion::_edit_custom_value));
2438
2439
add_focusable(edit_button);
2440
2441
if (!horizontal) {
2442
set_label_reference(spin[0]); //show text and buttons around this
2443
}
2444
}
2445
2446
///////////////////// AABB /////////////////////////
2447
2448
void EditorPropertyAABB::_set_read_only(bool p_read_only) {
2449
for (int i = 0; i < 6; i++) {
2450
spin[i]->set_read_only(p_read_only);
2451
}
2452
}
2453
2454
void EditorPropertyAABB::_value_changed(double val, const String &p_name) {
2455
AABB p;
2456
p.position.x = spin[0]->get_value();
2457
p.position.y = spin[1]->get_value();
2458
p.position.z = spin[2]->get_value();
2459
p.size.x = spin[3]->get_value();
2460
p.size.y = spin[4]->get_value();
2461
p.size.z = spin[5]->get_value();
2462
emit_changed(get_edited_property(), p, p_name);
2463
}
2464
2465
void EditorPropertyAABB::update_property() {
2466
AABB val = get_edited_property_value();
2467
spin[0]->set_value_no_signal(val.position.x);
2468
spin[1]->set_value_no_signal(val.position.y);
2469
spin[2]->set_value_no_signal(val.position.z);
2470
spin[3]->set_value_no_signal(val.size.x);
2471
spin[4]->set_value_no_signal(val.size.y);
2472
spin[5]->set_value_no_signal(val.size.z);
2473
}
2474
2475
void EditorPropertyAABB::_notification(int p_what) {
2476
switch (p_what) {
2477
case NOTIFICATION_THEME_CHANGED: {
2478
const Color *colors = _get_property_colors();
2479
for (int i = 0; i < 6; i++) {
2480
spin[i]->add_theme_color_override("label_color", colors[i % 3]);
2481
}
2482
} break;
2483
}
2484
}
2485
2486
void EditorPropertyAABB::setup(const EditorPropertyRangeHint &p_range_hint) {
2487
for (int i = 0; i < 6; i++) {
2488
spin[i]->set_min(p_range_hint.min);
2489
spin[i]->set_max(p_range_hint.max);
2490
spin[i]->set_step(p_range_hint.step);
2491
if (p_range_hint.hide_control) {
2492
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2493
}
2494
spin[i]->set_allow_greater(true);
2495
spin[i]->set_allow_lesser(true);
2496
spin[i]->set_suffix(p_range_hint.suffix);
2497
}
2498
}
2499
2500
EditorPropertyAABB::EditorPropertyAABB() {
2501
GridContainer *g = memnew(GridContainer);
2502
g->set_columns(3);
2503
add_child(g);
2504
2505
static const char *desc[6] = { "x", "y", "z", "w", "h", "d" };
2506
for (int i = 0; i < 6; i++) {
2507
spin[i] = memnew(EditorSpinSlider);
2508
spin[i]->set_label(desc[i]);
2509
spin[i]->set_accessibility_name(desc[i]);
2510
spin[i]->set_flat(true);
2511
2512
g->add_child(spin[i]);
2513
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2514
add_focusable(spin[i]);
2515
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyAABB::_value_changed).bind(desc[i]));
2516
}
2517
set_bottom_editor(g);
2518
}
2519
2520
///////////////////// TRANSFORM2D /////////////////////////
2521
2522
void EditorPropertyTransform2D::_set_read_only(bool p_read_only) {
2523
for (int i = 0; i < 6; i++) {
2524
spin[i]->set_read_only(p_read_only);
2525
}
2526
}
2527
2528
void EditorPropertyTransform2D::_value_changed(double val, const String &p_name) {
2529
Transform2D p;
2530
p[0][0] = spin[0]->get_value();
2531
p[1][0] = spin[1]->get_value();
2532
p[2][0] = spin[2]->get_value();
2533
p[0][1] = spin[3]->get_value();
2534
p[1][1] = spin[4]->get_value();
2535
p[2][1] = spin[5]->get_value();
2536
2537
emit_changed(get_edited_property(), p, p_name);
2538
}
2539
2540
void EditorPropertyTransform2D::update_property() {
2541
Transform2D val = get_edited_property_value();
2542
spin[0]->set_value_no_signal(val[0][0]);
2543
spin[1]->set_value_no_signal(val[1][0]);
2544
spin[2]->set_value_no_signal(val[2][0]);
2545
spin[3]->set_value_no_signal(val[0][1]);
2546
spin[4]->set_value_no_signal(val[1][1]);
2547
spin[5]->set_value_no_signal(val[2][1]);
2548
}
2549
2550
void EditorPropertyTransform2D::_notification(int p_what) {
2551
switch (p_what) {
2552
case NOTIFICATION_THEME_CHANGED: {
2553
const Color *colors = _get_property_colors();
2554
for (int i = 0; i < 6; i++) {
2555
// For Transform2D, use the 4th color (cyan) for the origin vector.
2556
if (i % 3 == 2) {
2557
spin[i]->add_theme_color_override("label_color", colors[3]);
2558
} else {
2559
spin[i]->add_theme_color_override("label_color", colors[i % 3]);
2560
}
2561
}
2562
} break;
2563
}
2564
}
2565
2566
void EditorPropertyTransform2D::setup(const EditorPropertyRangeHint &p_range_hint) {
2567
for (int i = 0; i < 6; i++) {
2568
spin[i]->set_min(p_range_hint.min);
2569
spin[i]->set_max(p_range_hint.max);
2570
spin[i]->set_step(p_range_hint.step);
2571
if (p_range_hint.hide_control) {
2572
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2573
}
2574
spin[i]->set_allow_greater(true);
2575
spin[i]->set_allow_lesser(true);
2576
if (i % 3 == 2) {
2577
spin[i]->set_suffix(p_range_hint.suffix);
2578
}
2579
}
2580
}
2581
2582
EditorPropertyTransform2D::EditorPropertyTransform2D(bool p_include_origin) {
2583
GridContainer *g = memnew(GridContainer);
2584
g->set_columns(p_include_origin ? 3 : 2);
2585
add_child(g);
2586
2587
static const char *desc[6] = { "xx", "xy", "xo", "yx", "yy", "yo" };
2588
for (int i = 0; i < 6; i++) {
2589
spin[i] = memnew(EditorSpinSlider);
2590
spin[i]->set_label(desc[i]);
2591
spin[i]->set_accessibility_name(desc[i]);
2592
spin[i]->set_flat(true);
2593
if (p_include_origin || i % 3 != 2) {
2594
g->add_child(spin[i]);
2595
}
2596
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2597
add_focusable(spin[i]);
2598
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyTransform2D::_value_changed).bind(desc[i]));
2599
}
2600
set_bottom_editor(g);
2601
}
2602
2603
///////////////////// BASIS /////////////////////////
2604
2605
void EditorPropertyBasis::_set_read_only(bool p_read_only) {
2606
for (int i = 0; i < 9; i++) {
2607
spin[i]->set_read_only(p_read_only);
2608
}
2609
}
2610
2611
void EditorPropertyBasis::_value_changed(double val, const String &p_name) {
2612
Basis p;
2613
p[0][0] = spin[0]->get_value();
2614
p[0][1] = spin[1]->get_value();
2615
p[0][2] = spin[2]->get_value();
2616
p[1][0] = spin[3]->get_value();
2617
p[1][1] = spin[4]->get_value();
2618
p[1][2] = spin[5]->get_value();
2619
p[2][0] = spin[6]->get_value();
2620
p[2][1] = spin[7]->get_value();
2621
p[2][2] = spin[8]->get_value();
2622
2623
emit_changed(get_edited_property(), p, p_name);
2624
}
2625
2626
void EditorPropertyBasis::update_property() {
2627
Basis val = get_edited_property_value();
2628
spin[0]->set_value_no_signal(val[0][0]);
2629
spin[1]->set_value_no_signal(val[0][1]);
2630
spin[2]->set_value_no_signal(val[0][2]);
2631
spin[3]->set_value_no_signal(val[1][0]);
2632
spin[4]->set_value_no_signal(val[1][1]);
2633
spin[5]->set_value_no_signal(val[1][2]);
2634
spin[6]->set_value_no_signal(val[2][0]);
2635
spin[7]->set_value_no_signal(val[2][1]);
2636
spin[8]->set_value_no_signal(val[2][2]);
2637
}
2638
2639
void EditorPropertyBasis::_notification(int p_what) {
2640
switch (p_what) {
2641
case NOTIFICATION_THEME_CHANGED: {
2642
const Color *colors = _get_property_colors();
2643
for (int i = 0; i < 9; i++) {
2644
spin[i]->add_theme_color_override("label_color", colors[i % 3]);
2645
}
2646
} break;
2647
}
2648
}
2649
2650
void EditorPropertyBasis::setup(const EditorPropertyRangeHint &p_range_hint) {
2651
for (int i = 0; i < 9; i++) {
2652
spin[i]->set_min(p_range_hint.min);
2653
spin[i]->set_max(p_range_hint.max);
2654
spin[i]->set_step(p_range_hint.step);
2655
if (p_range_hint.hide_control) {
2656
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2657
}
2658
spin[i]->set_allow_greater(true);
2659
spin[i]->set_allow_lesser(true);
2660
// Basis is inherently unitless, however someone may want to use it as
2661
// a generic way to store 9 values, so we'll still respect the suffix.
2662
spin[i]->set_suffix(p_range_hint.suffix);
2663
}
2664
}
2665
2666
EditorPropertyBasis::EditorPropertyBasis() {
2667
GridContainer *g = memnew(GridContainer);
2668
g->set_columns(3);
2669
add_child(g);
2670
2671
static const char *desc[9] = { "xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz" };
2672
for (int i = 0; i < 9; i++) {
2673
spin[i] = memnew(EditorSpinSlider);
2674
spin[i]->set_label(desc[i]);
2675
spin[i]->set_accessibility_name(desc[i]);
2676
spin[i]->set_flat(true);
2677
g->add_child(spin[i]);
2678
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2679
add_focusable(spin[i]);
2680
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyBasis::_value_changed).bind(desc[i]));
2681
}
2682
set_bottom_editor(g);
2683
}
2684
2685
///////////////////// TRANSFORM3D /////////////////////////
2686
2687
void EditorPropertyTransform3D::_set_read_only(bool p_read_only) {
2688
for (int i = 0; i < 12; i++) {
2689
spin[i]->set_read_only(p_read_only);
2690
}
2691
}
2692
2693
void EditorPropertyTransform3D::_value_changed(double val, const String &p_name) {
2694
Transform3D p;
2695
p.basis[0][0] = spin[0]->get_value();
2696
p.basis[0][1] = spin[1]->get_value();
2697
p.basis[0][2] = spin[2]->get_value();
2698
p.origin[0] = spin[3]->get_value();
2699
p.basis[1][0] = spin[4]->get_value();
2700
p.basis[1][1] = spin[5]->get_value();
2701
p.basis[1][2] = spin[6]->get_value();
2702
p.origin[1] = spin[7]->get_value();
2703
p.basis[2][0] = spin[8]->get_value();
2704
p.basis[2][1] = spin[9]->get_value();
2705
p.basis[2][2] = spin[10]->get_value();
2706
p.origin[2] = spin[11]->get_value();
2707
2708
emit_changed(get_edited_property(), p, p_name);
2709
}
2710
2711
void EditorPropertyTransform3D::update_property() {
2712
update_using_transform(get_edited_property_value());
2713
}
2714
2715
void EditorPropertyTransform3D::update_using_transform(Transform3D p_transform) {
2716
spin[0]->set_value_no_signal(p_transform.basis[0][0]);
2717
spin[1]->set_value_no_signal(p_transform.basis[0][1]);
2718
spin[2]->set_value_no_signal(p_transform.basis[0][2]);
2719
spin[3]->set_value_no_signal(p_transform.origin[0]);
2720
spin[4]->set_value_no_signal(p_transform.basis[1][0]);
2721
spin[5]->set_value_no_signal(p_transform.basis[1][1]);
2722
spin[6]->set_value_no_signal(p_transform.basis[1][2]);
2723
spin[7]->set_value_no_signal(p_transform.origin[1]);
2724
spin[8]->set_value_no_signal(p_transform.basis[2][0]);
2725
spin[9]->set_value_no_signal(p_transform.basis[2][1]);
2726
spin[10]->set_value_no_signal(p_transform.basis[2][2]);
2727
spin[11]->set_value_no_signal(p_transform.origin[2]);
2728
}
2729
2730
void EditorPropertyTransform3D::_notification(int p_what) {
2731
switch (p_what) {
2732
case NOTIFICATION_THEME_CHANGED: {
2733
const Color *colors = _get_property_colors();
2734
for (int i = 0; i < 12; i++) {
2735
spin[i]->add_theme_color_override("label_color", colors[i % 4]);
2736
}
2737
} break;
2738
}
2739
}
2740
2741
void EditorPropertyTransform3D::setup(const EditorPropertyRangeHint &p_range_hint) {
2742
for (int i = 0; i < 12; i++) {
2743
spin[i]->set_min(p_range_hint.min);
2744
spin[i]->set_max(p_range_hint.max);
2745
spin[i]->set_step(p_range_hint.step);
2746
if (p_range_hint.hide_control) {
2747
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2748
}
2749
spin[i]->set_allow_greater(true);
2750
spin[i]->set_allow_lesser(true);
2751
if (i % 4 == 3) {
2752
spin[i]->set_suffix(p_range_hint.suffix);
2753
}
2754
}
2755
}
2756
2757
EditorPropertyTransform3D::EditorPropertyTransform3D() {
2758
GridContainer *g = memnew(GridContainer);
2759
g->set_columns(4);
2760
add_child(g);
2761
2762
static const char *desc[12] = { "xx", "xy", "xz", "xo", "yx", "yy", "yz", "yo", "zx", "zy", "zz", "zo" };
2763
for (int i = 0; i < 12; i++) {
2764
spin[i] = memnew(EditorSpinSlider);
2765
spin[i]->set_label(desc[i]);
2766
spin[i]->set_accessibility_name(desc[i]);
2767
spin[i]->set_flat(true);
2768
g->add_child(spin[i]);
2769
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2770
add_focusable(spin[i]);
2771
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyTransform3D::_value_changed).bind(desc[i]));
2772
}
2773
set_bottom_editor(g);
2774
}
2775
2776
///////////////////// PROJECTION /////////////////////////
2777
2778
void EditorPropertyProjection::_set_read_only(bool p_read_only) {
2779
for (int i = 0; i < 12; i++) {
2780
spin[i]->set_read_only(p_read_only);
2781
}
2782
}
2783
2784
void EditorPropertyProjection::_value_changed(double val, const String &p_name) {
2785
Projection p;
2786
p.columns[0][0] = spin[0]->get_value();
2787
p.columns[0][1] = spin[1]->get_value();
2788
p.columns[0][2] = spin[2]->get_value();
2789
p.columns[0][3] = spin[3]->get_value();
2790
p.columns[1][0] = spin[4]->get_value();
2791
p.columns[1][1] = spin[5]->get_value();
2792
p.columns[1][2] = spin[6]->get_value();
2793
p.columns[1][3] = spin[7]->get_value();
2794
p.columns[2][0] = spin[8]->get_value();
2795
p.columns[2][1] = spin[9]->get_value();
2796
p.columns[2][2] = spin[10]->get_value();
2797
p.columns[2][3] = spin[11]->get_value();
2798
p.columns[3][0] = spin[12]->get_value();
2799
p.columns[3][1] = spin[13]->get_value();
2800
p.columns[3][2] = spin[14]->get_value();
2801
p.columns[3][3] = spin[15]->get_value();
2802
2803
emit_changed(get_edited_property(), p, p_name);
2804
}
2805
2806
void EditorPropertyProjection::update_property() {
2807
update_using_transform(get_edited_property_value());
2808
}
2809
2810
void EditorPropertyProjection::update_using_transform(Projection p_transform) {
2811
spin[0]->set_value_no_signal(p_transform.columns[0][0]);
2812
spin[1]->set_value_no_signal(p_transform.columns[0][1]);
2813
spin[2]->set_value_no_signal(p_transform.columns[0][2]);
2814
spin[3]->set_value_no_signal(p_transform.columns[0][3]);
2815
spin[4]->set_value_no_signal(p_transform.columns[1][0]);
2816
spin[5]->set_value_no_signal(p_transform.columns[1][1]);
2817
spin[6]->set_value_no_signal(p_transform.columns[1][2]);
2818
spin[7]->set_value_no_signal(p_transform.columns[1][3]);
2819
spin[8]->set_value_no_signal(p_transform.columns[2][0]);
2820
spin[9]->set_value_no_signal(p_transform.columns[2][1]);
2821
spin[10]->set_value_no_signal(p_transform.columns[2][2]);
2822
spin[11]->set_value_no_signal(p_transform.columns[2][3]);
2823
spin[12]->set_value_no_signal(p_transform.columns[3][0]);
2824
spin[13]->set_value_no_signal(p_transform.columns[3][1]);
2825
spin[14]->set_value_no_signal(p_transform.columns[3][2]);
2826
spin[15]->set_value_no_signal(p_transform.columns[3][3]);
2827
}
2828
2829
void EditorPropertyProjection::_notification(int p_what) {
2830
switch (p_what) {
2831
case NOTIFICATION_THEME_CHANGED: {
2832
const Color *colors = _get_property_colors();
2833
for (int i = 0; i < 16; i++) {
2834
spin[i]->add_theme_color_override("label_color", colors[i % 4]);
2835
}
2836
} break;
2837
}
2838
}
2839
2840
void EditorPropertyProjection::setup(const EditorPropertyRangeHint &p_range_hint) {
2841
for (int i = 0; i < 16; i++) {
2842
spin[i]->set_min(p_range_hint.min);
2843
spin[i]->set_max(p_range_hint.max);
2844
spin[i]->set_step(p_range_hint.step);
2845
if (p_range_hint.hide_control) {
2846
spin[i]->set_control_state(EditorSpinSlider::CONTROL_STATE_HIDE);
2847
}
2848
spin[i]->set_allow_greater(true);
2849
spin[i]->set_allow_lesser(true);
2850
if (i % 4 == 3) {
2851
spin[i]->set_suffix(p_range_hint.suffix);
2852
}
2853
}
2854
}
2855
2856
EditorPropertyProjection::EditorPropertyProjection() {
2857
GridContainer *g = memnew(GridContainer);
2858
g->set_columns(4);
2859
add_child(g);
2860
2861
static const char *desc[16] = { "xx", "xy", "xz", "xw", "yx", "yy", "yz", "yw", "zx", "zy", "zz", "zw", "wx", "wy", "wz", "ww" };
2862
for (int i = 0; i < 16; i++) {
2863
spin[i] = memnew(EditorSpinSlider);
2864
spin[i]->set_label(desc[i]);
2865
spin[i]->set_accessibility_name(desc[i]);
2866
spin[i]->set_flat(true);
2867
g->add_child(spin[i]);
2868
spin[i]->set_h_size_flags(SIZE_EXPAND_FILL);
2869
add_focusable(spin[i]);
2870
spin[i]->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyProjection::_value_changed).bind(desc[i]));
2871
}
2872
set_bottom_editor(g);
2873
}
2874
////////////// COLOR PICKER //////////////////////
2875
2876
void EditorPropertyColor::_set_read_only(bool p_read_only) {
2877
picker->set_disabled(p_read_only);
2878
}
2879
2880
void EditorPropertyColor::_color_changed(const Color &p_color) {
2881
if (!live_changes_enabled) {
2882
return;
2883
}
2884
2885
// Cancel the color change if the current color is identical to the new one.
2886
if (((Color)get_edited_property_value()).is_equal_approx(p_color)) {
2887
return;
2888
}
2889
2890
// Preview color change, bypassing undo/redo.
2891
get_edited_object()->set(get_edited_property(), p_color);
2892
}
2893
2894
void EditorPropertyColor::_picker_created() {
2895
picker->get_popup()->connect("about_to_popup", callable_mp(this, &EditorPropertyColor::_popup_opening));
2896
picker->connect("popup_closed", callable_mp(this, &EditorPropertyColor::_popup_closed), CONNECT_DEFERRED);
2897
}
2898
2899
void EditorPropertyColor::_popup_opening() {
2900
if (EditorNode::get_singleton()) {
2901
EditorNode::get_singleton()->setup_color_picker(picker->get_picker());
2902
}
2903
last_color = picker->get_pick_color();
2904
was_checked = !is_checkable() || is_checked();
2905
}
2906
2907
void EditorPropertyColor::_popup_closed() {
2908
get_edited_object()->set(get_edited_property(), was_checked ? Variant(last_color) : Variant());
2909
if (!picker->get_pick_color().is_equal_approx(last_color)) {
2910
emit_changed(get_edited_property(), picker->get_pick_color(), "", false);
2911
}
2912
}
2913
2914
void EditorPropertyColor::update_property() {
2915
picker->set_pick_color(get_edited_property_display_value());
2916
const Color color = picker->get_pick_color();
2917
2918
// Add a tooltip to display each channel's values without having to click the ColorPickerButton
2919
if (picker->is_editing_alpha()) {
2920
picker->set_tooltip_text(vformat(
2921
"R: %s\nG: %s\nB: %s\nA: %s",
2922
rtos(color.r).pad_decimals(2),
2923
rtos(color.g).pad_decimals(2),
2924
rtos(color.b).pad_decimals(2),
2925
rtos(color.a).pad_decimals(2)));
2926
} else {
2927
picker->set_tooltip_text(vformat(
2928
"R: %s\nG: %s\nB: %s",
2929
rtos(color.r).pad_decimals(2),
2930
rtos(color.g).pad_decimals(2),
2931
rtos(color.b).pad_decimals(2)));
2932
}
2933
}
2934
2935
void EditorPropertyColor::setup(bool p_show_alpha) {
2936
picker->set_edit_alpha(p_show_alpha);
2937
}
2938
2939
void EditorPropertyColor::set_live_changes_enabled(bool p_enabled) {
2940
live_changes_enabled = p_enabled;
2941
}
2942
2943
EditorPropertyColor::EditorPropertyColor() {
2944
picker = memnew(ColorPickerButton);
2945
add_child(picker);
2946
picker->set_flat(true);
2947
picker->set_theme_type_variation(SNAME("EditorInspectorButton"));
2948
picker->connect("color_changed", callable_mp(this, &EditorPropertyColor::_color_changed));
2949
picker->connect("picker_created", callable_mp(this, &EditorPropertyColor::_picker_created), CONNECT_ONE_SHOT);
2950
}
2951
2952
////////////// NODE PATH //////////////////////
2953
2954
void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
2955
assign->set_disabled(p_read_only);
2956
menu->set_disabled(p_read_only);
2957
}
2958
2959
Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
2960
if (p_prop == get_edited_property()) {
2961
r_valid = true;
2962
return const_cast<EditorPropertyNodePath *>(this)->get_edited_object()->get(get_edited_property(), &r_valid);
2963
}
2964
return Variant();
2965
}
2966
2967
void EditorPropertyNodePath::_node_selected(const NodePath &p_path, bool p_absolute) {
2968
NodePath path = p_path;
2969
Node *base_node = get_base_node();
2970
2971
if (!base_node && Object::cast_to<RefCounted>(get_edited_object())) {
2972
Node *to_node = get_node(p_path);
2973
ERR_FAIL_NULL(to_node);
2974
path = get_tree()->get_edited_scene_root()->get_path_to(to_node);
2975
}
2976
2977
if (p_absolute && base_node) { // for AnimationTrackKeyEdit
2978
path = base_node->get_path().rel_path_to(p_path);
2979
}
2980
2981
if (editing_node) {
2982
if (!base_node) {
2983
emit_changed(get_edited_property(), get_tree()->get_edited_scene_root()->get_node(path));
2984
} else {
2985
emit_changed(get_edited_property(), base_node->get_node(path));
2986
}
2987
} else {
2988
emit_changed(get_edited_property(), path);
2989
}
2990
update_property();
2991
}
2992
2993
void EditorPropertyNodePath::_node_assign() {
2994
if (!scene_tree) {
2995
scene_tree = memnew(SceneTreeDialog);
2996
scene_tree->get_scene_tree()->set_show_enabled_subscene(true);
2997
scene_tree->set_valid_types(valid_types);
2998
add_child(scene_tree);
2999
scene_tree->connect("selected", callable_mp(this, &EditorPropertyNodePath::_node_selected).bind(true));
3000
}
3001
3002
Variant val = get_edited_property_value();
3003
Node *n = nullptr;
3004
if (val.get_type() == Variant::Type::NODE_PATH) {
3005
Node *base_node = get_base_node();
3006
n = base_node == nullptr ? nullptr : base_node->get_node_or_null(val);
3007
} else {
3008
n = Object::cast_to<Node>(val);
3009
}
3010
scene_tree->popup_scenetree_dialog(n, get_base_node());
3011
}
3012
3013
void EditorPropertyNodePath::_assign_draw() {
3014
if (dropping) {
3015
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
3016
assign->draw_rect(Rect2(Point2(), assign->get_size()), color, false);
3017
}
3018
}
3019
3020
void EditorPropertyNodePath::_update_menu() {
3021
const NodePath &np = _get_node_path();
3022
3023
menu->get_popup()->set_item_disabled(ACTION_CLEAR, np.is_empty());
3024
menu->get_popup()->set_item_disabled(ACTION_COPY, np.is_empty());
3025
3026
Node *edited_node = Object::cast_to<Node>(get_edited_object());
3027
menu->get_popup()->set_item_disabled(ACTION_SELECT, !edited_node || !edited_node->has_node(np));
3028
}
3029
3030
void EditorPropertyNodePath::_menu_option(int p_idx) {
3031
switch (p_idx) {
3032
case ACTION_CLEAR: {
3033
if (editing_node) {
3034
emit_changed(get_edited_property(), Variant());
3035
} else {
3036
emit_changed(get_edited_property(), NodePath());
3037
}
3038
update_property();
3039
} break;
3040
3041
case ACTION_COPY: {
3042
DisplayServer::get_singleton()->clipboard_set(String(_get_node_path()));
3043
} break;
3044
3045
case ACTION_EDIT: {
3046
assign->hide();
3047
menu->hide();
3048
3049
const NodePath &np = _get_node_path();
3050
edit->set_text(String(np));
3051
edit->show();
3052
callable_mp((Control *)edit, &Control::grab_focus).call_deferred(false);
3053
} break;
3054
3055
case ACTION_SELECT: {
3056
const Node *edited_node = get_base_node();
3057
ERR_FAIL_NULL(edited_node);
3058
3059
const NodePath &np = _get_node_path();
3060
Node *target_node = edited_node->get_node_or_null(np);
3061
ERR_FAIL_NULL(target_node);
3062
3063
SceneTreeDock::get_singleton()->set_selected(target_node);
3064
} break;
3065
}
3066
}
3067
3068
void EditorPropertyNodePath::_accept_text() {
3069
_text_submitted(edit->get_text());
3070
}
3071
3072
void EditorPropertyNodePath::_text_submitted(const String &p_text) {
3073
NodePath np = p_text;
3074
_node_selected(np, false);
3075
edit->hide();
3076
assign->show();
3077
menu->show();
3078
}
3079
3080
const NodePath EditorPropertyNodePath::_get_node_path() const {
3081
const Node *base_node = const_cast<EditorPropertyNodePath *>(this)->get_base_node();
3082
3083
Variant val = get_edited_property_value();
3084
Node *n = Object::cast_to<Node>(val);
3085
if (n) {
3086
if (!n->is_inside_tree()) {
3087
return NodePath();
3088
}
3089
if (base_node) {
3090
return base_node->get_path_to(n);
3091
} else {
3092
return get_tree()->get_edited_scene_root()->get_path_to(n);
3093
}
3094
} else {
3095
return val;
3096
}
3097
}
3098
3099
bool EditorPropertyNodePath::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
3100
return !is_read_only() && is_drop_valid(p_data);
3101
}
3102
3103
void EditorPropertyNodePath::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
3104
ERR_FAIL_COND(!is_drop_valid(p_data));
3105
Dictionary data_dict = p_data;
3106
Array nodes = data_dict["nodes"];
3107
Node *node = get_tree()->get_edited_scene_root()->get_node(nodes[0]);
3108
3109
if (node) {
3110
_node_selected(node->get_path());
3111
}
3112
}
3113
3114
bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const {
3115
if (!p_drag_data.has("type") || p_drag_data["type"] != "nodes") {
3116
return false;
3117
}
3118
Array nodes = p_drag_data["nodes"];
3119
if (nodes.size() != 1) {
3120
return false;
3121
}
3122
3123
Object *data_root = p_drag_data.get("scene_root", (Object *)nullptr);
3124
if (data_root && get_tree()->get_edited_scene_root() != data_root) {
3125
return false;
3126
}
3127
3128
Node *dropped_node = get_tree()->get_edited_scene_root()->get_node(nodes[0]);
3129
ERR_FAIL_NULL_V(dropped_node, false);
3130
3131
if (valid_types.is_empty()) {
3132
// No type requirements specified so any type is valid.
3133
return true;
3134
}
3135
3136
for (const StringName &E : valid_types) {
3137
if (dropped_node->is_class(E) ||
3138
EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, E)) {
3139
return true;
3140
} else {
3141
Ref<Script> dropped_node_script = dropped_node->get_script();
3142
while (dropped_node_script.is_valid()) {
3143
if (dropped_node_script->get_path() == E) {
3144
return true;
3145
}
3146
dropped_node_script = dropped_node_script->get_base_script();
3147
}
3148
}
3149
}
3150
3151
return false;
3152
}
3153
3154
void EditorPropertyNodePath::update_property() {
3155
const Node *base_node = get_base_node();
3156
const NodePath &p = _get_node_path();
3157
assign->set_tooltip_text(String(p));
3158
3159
if (p.is_empty()) {
3160
assign->set_button_icon(Ref<Texture2D>());
3161
assign->set_text(TTR("Assign..."));
3162
assign->set_flat(false);
3163
return;
3164
}
3165
assign->set_flat(true);
3166
3167
if (!base_node || !base_node->has_node(p)) {
3168
assign->set_button_icon(Ref<Texture2D>());
3169
assign->set_text(String(p));
3170
return;
3171
}
3172
3173
const Node *target_node = base_node->get_node(p);
3174
ERR_FAIL_NULL(target_node);
3175
3176
String new_text = target_node->get_name();
3177
if (new_text.contains_char('@')) {
3178
assign->set_button_icon(Ref<Texture2D>());
3179
assign->set_text(String(p));
3180
return;
3181
}
3182
3183
if (p.get_subname_count() > 0) {
3184
new_text += ":" + p.get_concatenated_subnames();
3185
}
3186
assign->set_text(new_text);
3187
assign->set_button_icon(EditorNode::get_singleton()->get_object_icon(target_node));
3188
}
3189
3190
void EditorPropertyNodePath::setup(const Vector<StringName> &p_valid_types, bool p_use_path_from_scene_root, bool p_editing_node) {
3191
valid_types = p_valid_types;
3192
editing_node = p_editing_node;
3193
use_path_from_scene_root = p_use_path_from_scene_root;
3194
}
3195
3196
void EditorPropertyNodePath::_notification(int p_what) {
3197
switch (p_what) {
3198
case NOTIFICATION_THEME_CHANGED: {
3199
menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
3200
menu->get_popup()->set_item_icon(ACTION_CLEAR, get_editor_theme_icon(SNAME("Clear")));
3201
menu->get_popup()->set_item_icon(ACTION_COPY, get_editor_theme_icon(SNAME("ActionCopy")));
3202
menu->get_popup()->set_item_icon(ACTION_EDIT, get_editor_theme_icon(SNAME("Edit")));
3203
menu->get_popup()->set_item_icon(ACTION_SELECT, get_editor_theme_icon(SNAME("ExternalLink")));
3204
3205
// Use a constant width for the icon to avoid sizing issues or blurry icons.
3206
assign->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
3207
} break;
3208
3209
case NOTIFICATION_DRAG_BEGIN: {
3210
if (!is_read_only() && is_drop_valid(get_viewport()->gui_get_drag_data())) {
3211
dropping = true;
3212
assign->queue_redraw();
3213
}
3214
} break;
3215
3216
case NOTIFICATION_DRAG_END: {
3217
if (dropping) {
3218
dropping = false;
3219
assign->queue_redraw();
3220
}
3221
} break;
3222
}
3223
}
3224
3225
Node *EditorPropertyNodePath::get_base_node() {
3226
Node *base_node = Object::cast_to<Node>(get_edited_object());
3227
3228
// For proxy objects, specifies the node to which the path is relative.
3229
if (!base_node && get_edited_object()->has_meta("__base_node_relative")) {
3230
base_node = Object::cast_to<Node>(get_edited_object()->get_meta("__base_node_relative"));
3231
}
3232
3233
if (!base_node) {
3234
base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
3235
}
3236
if (!base_node) {
3237
// Try a base node within history.
3238
if (EditorNode::get_singleton()->get_editor_selection_history()->get_path_size() > 0) {
3239
Object *base = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_path_object(0));
3240
if (base) {
3241
base_node = Object::cast_to<Node>(base);
3242
}
3243
}
3244
}
3245
3246
if (!use_path_from_scene_root) {
3247
return base_node;
3248
}
3249
3250
if (get_edited_object()->has_method("get_root_path")) {
3251
return Object::cast_to<Node>(get_edited_object()->call("get_root_path"));
3252
}
3253
3254
if (!base_node) {
3255
return nullptr; // Editing external resources.
3256
}
3257
3258
if (base_node->is_instance()) {
3259
return base_node; // Known scene root.
3260
}
3261
3262
base_node = base_node->get_owner();
3263
if (base_node) {
3264
return base_node; // Node in known scene.
3265
}
3266
3267
return get_tree()->get_edited_scene_root(); // Treat as a node in the main scene.
3268
}
3269
3270
EditorPropertyNodePath::EditorPropertyNodePath() {
3271
HBoxContainer *hbc = memnew(HBoxContainer);
3272
hbc->add_theme_constant_override("separation", 0);
3273
add_child(hbc);
3274
assign = memnew(Button);
3275
assign->set_accessibility_name(TTRC("Assign Node"));
3276
assign->set_flat(true);
3277
assign->set_theme_type_variation(SNAME("EditorInspectorButton"));
3278
assign->set_h_size_flags(SIZE_EXPAND_FILL);
3279
assign->set_clip_text(true);
3280
assign->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
3281
assign->set_expand_icon(true);
3282
assign->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyNodePath::_node_assign));
3283
assign->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyNodePath::_assign_draw));
3284
SET_DRAG_FORWARDING_CD(assign, EditorPropertyNodePath);
3285
hbc->add_child(assign);
3286
3287
menu = memnew(MenuButton);
3288
menu->set_flat(true);
3289
menu->connect(SNAME("about_to_popup"), callable_mp(this, &EditorPropertyNodePath::_update_menu));
3290
hbc->add_child(menu);
3291
3292
menu->get_popup()->add_item(TTR("Clear"), ACTION_CLEAR);
3293
menu->get_popup()->add_item(TTR("Copy as Text"), ACTION_COPY);
3294
menu->get_popup()->add_item(TTR("Edit"), ACTION_EDIT);
3295
menu->get_popup()->add_item(TTR("Show Node in Tree"), ACTION_SELECT);
3296
menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyNodePath::_menu_option));
3297
3298
edit = memnew(LineEdit);
3299
edit->set_accessibility_name(TTRC("Node Path"));
3300
edit->set_h_size_flags(SIZE_EXPAND_FILL);
3301
edit->hide();
3302
edit->connect(SceneStringName(focus_exited), callable_mp(this, &EditorPropertyNodePath::_accept_text));
3303
edit->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPropertyNodePath::_text_submitted));
3304
hbc->add_child(edit);
3305
}
3306
3307
///////////////////// RID /////////////////////////
3308
3309
void EditorPropertyRID::update_property() {
3310
RID rid = get_edited_property_value();
3311
if (rid.is_valid()) {
3312
uint64_t id = rid.get_id();
3313
label->set_text("RID: " + uitos(id));
3314
} else {
3315
label->set_text(TTR("Invalid RID"));
3316
}
3317
}
3318
3319
EditorPropertyRID::EditorPropertyRID() {
3320
label = memnew(Label);
3321
add_child(label);
3322
}
3323
3324
////////////// RESOURCE //////////////////////
3325
3326
void EditorPropertyResource::_set_read_only(bool p_read_only) {
3327
resource_picker->set_editable(!p_read_only);
3328
}
3329
3330
void EditorPropertyResource::_resource_selected(const Ref<Resource> &p_resource, bool p_inspect) {
3331
if (p_resource->is_built_in() && !p_resource->get_path().is_empty()) {
3332
String parent = p_resource->get_path().get_slice("::", 0);
3333
List<String> extensions;
3334
ResourceLoader::get_recognized_extensions_for_type("PackedScene", &extensions);
3335
3336
if (p_inspect) {
3337
if (extensions.find(parent.get_extension()) && (!EditorNode::get_singleton()->get_edited_scene() || EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path() != parent)) {
3338
// If the resource belongs to another (non-imported) scene, edit it in that scene instead.
3339
if (!FileAccess::exists(parent + ".import")) {
3340
callable_mp(EditorNode::get_singleton(), &EditorNode::edit_foreign_resource).call_deferred(p_resource);
3341
return;
3342
}
3343
}
3344
}
3345
}
3346
3347
if (!p_inspect && use_sub_inspector) {
3348
bool unfold = !get_edited_object()->editor_is_section_unfolded(get_edited_property());
3349
get_edited_object()->editor_set_section_unfold(get_edited_property(), unfold);
3350
update_property();
3351
} else if (!is_checkable() || is_checked()) {
3352
emit_signal(SNAME("resource_selected"), get_edited_property(), p_resource);
3353
}
3354
}
3355
3356
void EditorPropertyResource::_resource_changed(const Ref<Resource> &p_resource) {
3357
Resource *r = Object::cast_to<Resource>(get_edited_object());
3358
if (r) {
3359
// Check for recursive setting of resource
3360
HashSet<Resource *> resources_found;
3361
resources_found.insert(r);
3362
bool found = EditorNode::find_recursive_resources(p_resource, resources_found);
3363
if (found) {
3364
callable_mp(EditorNode::get_singleton(), &EditorNode::show_warning).call_deferred(TTR("Recursion detected, unable to assign resource to property."), TTR("Warning!"));
3365
emit_changed(get_edited_property(), Ref<Resource>());
3366
update_property();
3367
return;
3368
}
3369
}
3370
3371
if (p_resource.is_valid() && p_resource->is_local_to_scene()) {
3372
// Attempting to configure the local scene.
3373
Node *local_scene = _get_base_node();
3374
if (local_scene) {
3375
HashMap<Ref<Resource>, Ref<Resource>> remap;
3376
p_resource->configure_for_local_scene(local_scene, remap);
3377
} else {
3378
WARN_PRINT("You are attempting to assign a local-to-scene resource outside the scene.");
3379
}
3380
}
3381
3382
// The bool is_script applies only to an object's main script.
3383
// Changing the value of Script-type exported variables of the main script should not trigger saving/reloading properties.
3384
bool is_script = false;
3385
Ref<Script> s = p_resource;
3386
if (get_edited_object() && s.is_valid() && get_edited_property() == CoreStringName(script)) {
3387
is_script = true;
3388
InspectorDock::get_singleton()->store_script_properties(get_edited_object());
3389
s->call("set_instance_base_type", get_edited_object()->get_class());
3390
}
3391
3392
// Prevent the creation of invalid ViewportTextures when possible.
3393
Ref<ViewportTexture> vpt = p_resource;
3394
if (vpt.is_valid()) {
3395
r = Object::cast_to<Resource>(get_edited_object());
3396
if (Object::cast_to<VisualShaderNodeTexture>(r)) {
3397
EditorNode::get_singleton()->show_warning(TTR("Can't create a ViewportTexture in a Texture2D node because the texture will not be bound to a scene.\nUse a Texture2DParameter node instead and set the texture in the \"Shader Parameters\" tab."));
3398
emit_changed(get_edited_property(), Ref<Resource>());
3399
update_property();
3400
return;
3401
}
3402
3403
if (r && r->get_path().is_resource_file()) {
3404
EditorNode::get_singleton()->show_warning(TTR("Can't create a ViewportTexture on resources saved as a file.\nResource needs to belong to a scene."));
3405
emit_changed(get_edited_property(), Ref<Resource>());
3406
update_property();
3407
return;
3408
}
3409
3410
if (r && !r->is_local_to_scene()) {
3411
EditorNode::get_singleton()->show_warning(TTR("Can't create a ViewportTexture on this resource because it's not set as local to scene.\nPlease switch on the 'local to scene' property on it (and all resources containing it up to a node)."));
3412
emit_changed(get_edited_property(), Ref<Resource>());
3413
update_property();
3414
return;
3415
}
3416
}
3417
3418
emit_changed(get_edited_property(), p_resource);
3419
update_property();
3420
3421
if (is_script) {
3422
// Restore properties if script was changed.
3423
InspectorDock::get_singleton()->apply_script_properties(get_edited_object());
3424
}
3425
3426
// Automatically suggest setting up the path for a ViewportTexture.
3427
if (vpt.is_valid() && vpt->get_viewport_path_in_scene().is_empty()) {
3428
if (!scene_tree) {
3429
scene_tree = memnew(SceneTreeDialog);
3430
scene_tree->set_title(TTR("Pick a Viewport"));
3431
3432
Vector<StringName> valid_types;
3433
valid_types.push_back("Viewport");
3434
scene_tree->set_valid_types(valid_types);
3435
scene_tree->get_scene_tree()->set_show_enabled_subscene(true);
3436
3437
add_child(scene_tree);
3438
scene_tree->connect("selected", callable_mp(this, &EditorPropertyResource::_viewport_selected));
3439
}
3440
scene_tree->popup_scenetree_dialog();
3441
}
3442
}
3443
3444
void EditorPropertyResource::_sub_inspector_property_keyed(const String &p_property, const Variant &p_value, bool p_advance) {
3445
// The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it.
3446
const Variant args[3] = { String(get_edited_property()) + ":" + p_property, p_value, p_advance };
3447
const Variant *argp[3] = { &args[0], &args[1], &args[2] };
3448
emit_signalp(SNAME("property_keyed_with_value"), argp, 3);
3449
}
3450
3451
void EditorPropertyResource::_sub_inspector_resource_selected(const Ref<Resource> &p_resource, const String &p_property) {
3452
emit_signal(SNAME("resource_selected"), String(get_edited_property()) + ":" + p_property, p_resource);
3453
}
3454
3455
void EditorPropertyResource::_sub_inspector_object_id_selected(int p_id) {
3456
emit_signal(SNAME("object_id_selected"), get_edited_property(), p_id);
3457
}
3458
3459
void EditorPropertyResource::_open_editor_pressed() {
3460
Ref<Resource> res = get_edited_property_value();
3461
if (res.is_valid()) {
3462
EditorNode::get_singleton()->edit_item(res.ptr(), this);
3463
}
3464
}
3465
3466
void EditorPropertyResource::_update_preferred_shader() {
3467
Node *parent = get_parent();
3468
EditorProperty *parent_property = nullptr;
3469
3470
while (parent && !parent_property) {
3471
parent_property = Object::cast_to<EditorProperty>(parent);
3472
parent = parent->get_parent();
3473
}
3474
3475
if (parent_property) {
3476
EditorShaderPicker *shader_picker = Object::cast_to<EditorShaderPicker>(resource_picker);
3477
Object *ed_object = parent_property->get_edited_object();
3478
const StringName &ed_property = parent_property->get_edited_property();
3479
3480
// Set preferred shader based on edited parent type.
3481
if ((Object::cast_to<GPUParticles2D>(ed_object) || Object::cast_to<GPUParticles3D>(ed_object)) && ed_property == SNAME("process_material")) {
3482
shader_picker->set_preferred_mode(Shader::MODE_PARTICLES);
3483
} else if (Object::cast_to<FogVolume>(ed_object)) {
3484
shader_picker->set_preferred_mode(Shader::MODE_FOG);
3485
} else if (Object::cast_to<CanvasItem>(ed_object)) {
3486
shader_picker->set_preferred_mode(Shader::MODE_CANVAS_ITEM);
3487
} else if (Object::cast_to<Node3D>(ed_object) || Object::cast_to<Mesh>(ed_object)) {
3488
shader_picker->set_preferred_mode(Shader::MODE_SPATIAL);
3489
} else if (Object::cast_to<Sky>(ed_object)) {
3490
shader_picker->set_preferred_mode(Shader::MODE_SKY);
3491
}
3492
}
3493
}
3494
3495
bool EditorPropertyResource::_should_stop_editing() const {
3496
return !resource_picker->is_toggle_pressed();
3497
}
3498
3499
Node *EditorPropertyResource::_get_base_node() {
3500
Node *base_node = Object::cast_to<Node>(get_edited_object());
3501
3502
if (!base_node) {
3503
base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
3504
}
3505
3506
if (!base_node) {
3507
// Try a base node within history.
3508
if (EditorNode::get_singleton()->get_editor_selection_history()->get_path_size() > 0) {
3509
Object *base = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_path_object(0));
3510
if (base) {
3511
base_node = Object::cast_to<Node>(base);
3512
}
3513
}
3514
}
3515
3516
if (!base_node) {
3517
return nullptr; // Editing external resources.
3518
}
3519
3520
if (!base_node->get_scene_file_path().is_empty()) {
3521
return base_node; // Known scene root.
3522
}
3523
3524
base_node = base_node->get_owner();
3525
if (base_node) {
3526
return base_node; // Node in known scene.
3527
}
3528
3529
return get_tree()->get_edited_scene_root(); // Treat as a node in the main scene.
3530
}
3531
3532
void EditorPropertyResource::_viewport_selected(const NodePath &p_path) {
3533
Node *to_node = get_node(p_path);
3534
if (!Object::cast_to<Viewport>(to_node)) {
3535
EditorNode::get_singleton()->show_warning(TTR("Selected node is not a Viewport!"));
3536
return;
3537
}
3538
3539
Ref<ViewportTexture> vt = get_edited_property_value();
3540
ERR_FAIL_COND(vt.is_null());
3541
3542
Node *local_scene = _get_base_node();
3543
ERR_FAIL_NULL(local_scene);
3544
vt->set_viewport_path_in_scene(local_scene->get_path_to(to_node));
3545
3546
emit_changed(get_edited_property(), vt);
3547
update_property();
3548
}
3549
3550
void EditorPropertyResource::setup(Object *p_object, const String &p_path, const String &p_base_type) {
3551
if (resource_picker) {
3552
memdelete(resource_picker);
3553
resource_picker = nullptr;
3554
}
3555
3556
if (p_path == "script" && p_base_type == "Script" && Object::cast_to<Node>(p_object)) {
3557
EditorScriptPicker *script_picker = memnew(EditorScriptPicker);
3558
script_picker->set_script_owner(Object::cast_to<Node>(p_object));
3559
resource_picker = script_picker;
3560
} else if (p_path == "shader" && p_base_type == "Shader" && Object::cast_to<ShaderMaterial>(p_object)) {
3561
EditorShaderPicker *shader_picker = memnew(EditorShaderPicker);
3562
shader_picker->set_edited_material(Object::cast_to<ShaderMaterial>(p_object));
3563
resource_picker = shader_picker;
3564
connect(SceneStringName(ready), callable_mp(this, &EditorPropertyResource::_update_preferred_shader));
3565
} else if (ClassDB::is_parent_class(p_base_type, "AudioStream")) {
3566
EditorAudioStreamPicker *astream_picker = memnew(EditorAudioStreamPicker);
3567
resource_picker = astream_picker;
3568
} else {
3569
resource_picker = memnew(EditorResourcePicker);
3570
}
3571
3572
resource_picker->set_base_type(p_base_type);
3573
resource_picker->set_resource_owner(p_object);
3574
resource_picker->set_property_path(p_path);
3575
resource_picker->set_editable(true);
3576
resource_picker->set_h_size_flags(SIZE_EXPAND_FILL);
3577
add_child(resource_picker);
3578
3579
resource_picker->connect("resource_selected", callable_mp(this, &EditorPropertyResource::_resource_selected));
3580
resource_picker->connect("resource_changed", callable_mp(this, &EditorPropertyResource::_resource_changed));
3581
3582
for (int i = 0; i < resource_picker->get_child_count(); i++) {
3583
Button *b = Object::cast_to<Button>(resource_picker->get_child(i));
3584
if (b) {
3585
add_focusable(b);
3586
}
3587
}
3588
}
3589
3590
void EditorPropertyResource::update_property() {
3591
Ref<Resource> res = get_edited_property_display_value();
3592
3593
if (use_sub_inspector) {
3594
if (res.is_valid() != resource_picker->is_toggle_mode()) {
3595
resource_picker->set_toggle_mode(res.is_valid());
3596
}
3597
3598
if (res.is_valid() && get_edited_object()->editor_is_section_unfolded(get_edited_property())) {
3599
if (!sub_inspector) {
3600
sub_inspector = memnew(EditorInspector);
3601
sub_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
3602
sub_inspector->set_use_doc_hints(true);
3603
3604
EditorInspector *parent_inspector = get_parent_inspector();
3605
if (parent_inspector) {
3606
sub_inspector->set_root_inspector(parent_inspector->get_root_inspector());
3607
sub_inspector->register_text_enter(parent_inspector->search_box);
3608
}
3609
3610
sub_inspector->set_property_name_style(InspectorDock::get_singleton()->get_property_name_style());
3611
3612
sub_inspector->connect("property_keyed", callable_mp(this, &EditorPropertyResource::_sub_inspector_property_keyed));
3613
sub_inspector->connect("resource_selected", callable_mp(this, &EditorPropertyResource::_sub_inspector_resource_selected));
3614
sub_inspector->connect("object_id_selected", callable_mp(this, &EditorPropertyResource::_sub_inspector_object_id_selected));
3615
sub_inspector->set_keying(is_keying());
3616
sub_inspector->set_read_only(is_read_only());
3617
sub_inspector->set_use_folding(is_using_folding());
3618
3619
sub_inspector->set_draw_focus_border(false);
3620
sub_inspector->set_focus_mode(FocusMode::FOCUS_NONE);
3621
3622
sub_inspector->set_use_filter(use_filter);
3623
3624
add_child(sub_inspector);
3625
set_bottom_editor(sub_inspector);
3626
3627
resource_picker->set_toggle_pressed(true);
3628
3629
Array editor_list;
3630
for (int i = 0; i < EditorNode::get_editor_data().get_editor_plugin_count(); i++) {
3631
EditorPlugin *ep = EditorNode::get_editor_data().get_editor_plugin(i);
3632
if (ep->handles(res.ptr())) {
3633
editor_list.push_back(ep);
3634
}
3635
}
3636
3637
if (!editor_list.is_empty()) {
3638
// Open editor directly.
3639
_open_editor_pressed();
3640
opened_editor = true;
3641
}
3642
}
3643
3644
sub_inspector->set_read_only(is_checkable() && !is_checked());
3645
3646
if (res.ptr() != sub_inspector->get_edited_object()) {
3647
sub_inspector->edit(res.ptr());
3648
_update_property_bg();
3649
}
3650
3651
} else if (sub_inspector) {
3652
set_bottom_editor(nullptr);
3653
memdelete(sub_inspector);
3654
sub_inspector = nullptr;
3655
3656
if (opened_editor) {
3657
EditorNode::get_singleton()->hide_unused_editors();
3658
opened_editor = false;
3659
}
3660
}
3661
}
3662
3663
resource_picker->set_edited_resource_no_check(res);
3664
const Ref<Resource> &real_res = get_edited_property_value();
3665
resource_picker->set_force_allow_unique(real_res.is_null() && res.is_valid());
3666
}
3667
3668
void EditorPropertyResource::collapse_all_folding() {
3669
if (sub_inspector) {
3670
sub_inspector->collapse_all_folding();
3671
}
3672
}
3673
3674
void EditorPropertyResource::expand_all_folding() {
3675
if (sub_inspector) {
3676
sub_inspector->expand_all_folding();
3677
}
3678
}
3679
3680
void EditorPropertyResource::expand_revertable() {
3681
if (sub_inspector) {
3682
sub_inspector->expand_revertable();
3683
}
3684
}
3685
3686
void EditorPropertyResource::set_use_sub_inspector(bool p_enable) {
3687
use_sub_inspector = p_enable;
3688
}
3689
3690
void EditorPropertyResource::set_use_filter(bool p_use) {
3691
use_filter = p_use;
3692
if (sub_inspector) {
3693
update_property();
3694
}
3695
}
3696
3697
void EditorPropertyResource::fold_resource() {
3698
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
3699
if (unfolded) {
3700
resource_picker->set_toggle_pressed(false);
3701
get_edited_object()->editor_set_section_unfold(get_edited_property(), false);
3702
update_property();
3703
}
3704
}
3705
3706
bool EditorPropertyResource::is_colored(ColorationMode p_mode) {
3707
switch (p_mode) {
3708
case COLORATION_CONTAINER_RESOURCE:
3709
return sub_inspector != nullptr;
3710
case COLORATION_RESOURCE:
3711
return true;
3712
case COLORATION_EXTERNAL:
3713
if (sub_inspector) {
3714
Resource *edited_resource = Object::cast_to<Resource>(sub_inspector->get_edited_object());
3715
return edited_resource && !edited_resource->is_built_in();
3716
}
3717
break;
3718
}
3719
return false;
3720
}
3721
3722
void EditorPropertyResource::_notification(int p_what) {
3723
switch (p_what) {
3724
case NOTIFICATION_EXIT_TREE: {
3725
const EditorInspector *ei = get_parent_inspector();
3726
const EditorInspector *main_ei = InspectorDock::get_inspector_singleton();
3727
if (ei && main_ei && ei != main_ei && !main_ei->is_ancestor_of(ei)) {
3728
fold_resource();
3729
}
3730
} break;
3731
}
3732
}
3733
3734
void EditorPropertyResource::_bind_methods() {
3735
ClassDB::bind_method(D_METHOD("_should_stop_editing"), &EditorPropertyResource::_should_stop_editing);
3736
}
3737
3738
EditorPropertyResource::EditorPropertyResource() {
3739
use_sub_inspector = bool(EDITOR_GET("interface/inspector/open_resources_in_current_inspector"));
3740
has_borders = true;
3741
}
3742
3743
////////////// DEFAULT PLUGIN //////////////////////
3744
3745
bool EditorInspectorDefaultPlugin::can_handle(Object *p_object) {
3746
return true; // Can handle everything.
3747
}
3748
3749
bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
3750
Control *editor = EditorInspectorDefaultPlugin::get_editor_for_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);
3751
if (editor) {
3752
add_property_editor(p_path, editor);
3753
}
3754
return false;
3755
}
3756
3757
static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const String &p_hint_text, double p_default_step, bool is_int = false) {
3758
EditorPropertyRangeHint hint;
3759
hint.step = p_default_step;
3760
if (is_int) {
3761
hint.hide_control = false; // Always show controls for ints, unless specified in hint range.
3762
}
3763
Vector<String> slices = p_hint_text.split(",");
3764
if (p_hint == PROPERTY_HINT_RANGE) {
3765
ERR_FAIL_COND_V_MSG(slices.size() < 2, hint,
3766
vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": Missing required min and/or max values.", p_hint_text));
3767
3768
hint.or_greater = false; // If using ranged, assume false by default.
3769
hint.or_less = false;
3770
3771
hint.min = slices[0].to_float();
3772
hint.max = slices[1].to_float();
3773
3774
if (slices.size() >= 3 && slices[2].is_valid_float()) {
3775
// Step is optional, could be something else if not a number.
3776
hint.step = slices[2].to_float();
3777
}
3778
hint.hide_control = false;
3779
for (int i = 2; i < slices.size(); i++) {
3780
String slice = slices[i].strip_edges();
3781
if (slice == "or_greater") {
3782
hint.or_greater = true;
3783
} else if (slice == "or_less") {
3784
hint.or_less = true;
3785
} else if (slice == "prefer_slider") {
3786
hint.prefer_slider = true;
3787
} else if (slice == "hide_control") {
3788
hint.hide_control = true;
3789
#ifndef DISABLE_DEPRECATED
3790
} else if (slice == "hide_slider") {
3791
hint.hide_control = true;
3792
#endif
3793
} else if (slice == "exp") {
3794
hint.exp_range = true;
3795
}
3796
}
3797
}
3798
bool degrees = false;
3799
for (int i = 0; i < slices.size(); i++) {
3800
String slice = slices[i].strip_edges();
3801
if (slice == "radians_as_degrees"
3802
#ifndef DISABLE_DEPRECATED
3803
|| slice == "radians"
3804
#endif // DISABLE_DEPRECATED
3805
) {
3806
hint.radians_as_degrees = true;
3807
} else if (slice == "degrees") {
3808
degrees = true;
3809
} else if (slice.begins_with("suffix:")) {
3810
hint.suffix = " " + slice.replace_first("suffix:", "").strip_edges();
3811
}
3812
}
3813
3814
if ((hint.radians_as_degrees || degrees) && hint.suffix.is_empty()) {
3815
hint.suffix = U"\u00B0";
3816
}
3817
3818
ERR_FAIL_COND_V_MSG(hint.step == 0, hint,
3819
vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": Step cannot be 0.", p_hint_text));
3820
3821
return hint;
3822
}
3823
3824
static EditorProperty *get_input_action_editor(const String &p_hint_text, bool is_string_name) {
3825
// TODO: Should probably use a better editor GUI with a search bar.
3826
// Said GUI could also handle showing builtin options, requiring 1 less hint.
3827
EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);
3828
Vector<String> options;
3829
Vector<String> builtin_options;
3830
List<PropertyInfo> pinfo;
3831
ProjectSettings::get_singleton()->get_property_list(&pinfo);
3832
Vector<String> hints = p_hint_text.remove_char(' ').split(",", false);
3833
3834
HashMap<String, List<Ref<InputEvent>>> builtins(InputMap::get_singleton()->get_builtins());
3835
bool show_builtin = hints.has("show_builtin");
3836
3837
for (const PropertyInfo &pi : pinfo) {
3838
if (!pi.name.begins_with("input/")) {
3839
continue;
3840
}
3841
3842
const String action_name = pi.name.get_slicec('/', 1);
3843
if (builtins.has(action_name)) {
3844
if (show_builtin) {
3845
builtin_options.append(action_name);
3846
}
3847
} else {
3848
options.append(action_name);
3849
}
3850
}
3851
options.append_array(builtin_options);
3852
editor->setup(options, Vector<String>(), is_string_name, hints.has("loose_mode"));
3853
return editor;
3854
}
3855
3856
EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {
3857
double default_float_step = EDITOR_GET("interface/inspector/default_float_step");
3858
3859
switch (p_type) {
3860
// atomic types
3861
case Variant::NIL: {
3862
if (p_usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
3863
return memnew(EditorPropertyVariant);
3864
} else {
3865
return memnew(EditorPropertyNil);
3866
}
3867
} break;
3868
case Variant::BOOL: {
3869
EditorPropertyCheck *editor = memnew(EditorPropertyCheck);
3870
return editor;
3871
} break;
3872
case Variant::INT: {
3873
if (p_hint == PROPERTY_HINT_ENUM) {
3874
EditorPropertyEnum *editor = memnew(EditorPropertyEnum);
3875
Vector<String> options = p_hint_text.split(",");
3876
editor->setup(options);
3877
return editor;
3878
3879
} else if (p_hint == PROPERTY_HINT_FLAGS) {
3880
EditorPropertyFlags *editor = memnew(EditorPropertyFlags);
3881
Vector<String> options = p_hint_text.split(",");
3882
editor->setup(options);
3883
return editor;
3884
3885
} else if (p_hint == PROPERTY_HINT_LAYERS_2D_PHYSICS ||
3886
p_hint == PROPERTY_HINT_LAYERS_2D_RENDER ||
3887
p_hint == PROPERTY_HINT_LAYERS_2D_NAVIGATION ||
3888
p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS ||
3889
p_hint == PROPERTY_HINT_LAYERS_3D_RENDER ||
3890
p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION ||
3891
p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE) {
3892
EditorPropertyLayers::LayerType lt = EditorPropertyLayers::LAYER_RENDER_2D;
3893
switch (p_hint) {
3894
case PROPERTY_HINT_LAYERS_2D_RENDER:
3895
lt = EditorPropertyLayers::LAYER_RENDER_2D;
3896
break;
3897
case PROPERTY_HINT_LAYERS_2D_PHYSICS:
3898
lt = EditorPropertyLayers::LAYER_PHYSICS_2D;
3899
break;
3900
case PROPERTY_HINT_LAYERS_2D_NAVIGATION:
3901
lt = EditorPropertyLayers::LAYER_NAVIGATION_2D;
3902
break;
3903
case PROPERTY_HINT_LAYERS_3D_RENDER:
3904
lt = EditorPropertyLayers::LAYER_RENDER_3D;
3905
break;
3906
case PROPERTY_HINT_LAYERS_3D_PHYSICS:
3907
lt = EditorPropertyLayers::LAYER_PHYSICS_3D;
3908
break;
3909
case PROPERTY_HINT_LAYERS_3D_NAVIGATION:
3910
lt = EditorPropertyLayers::LAYER_NAVIGATION_3D;
3911
break;
3912
case PROPERTY_HINT_LAYERS_AVOIDANCE:
3913
lt = EditorPropertyLayers::LAYER_AVOIDANCE;
3914
break;
3915
default: {
3916
} //compiler could be smarter here and realize this can't happen
3917
}
3918
EditorPropertyLayers *editor = memnew(EditorPropertyLayers);
3919
editor->setup(lt);
3920
return editor;
3921
} else if (p_hint == PROPERTY_HINT_OBJECT_ID) {
3922
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
3923
editor->setup(p_hint_text);
3924
return editor;
3925
3926
} else {
3927
EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
3928
editor->setup(_parse_range_hint(p_hint, p_hint_text, 1, true));
3929
return editor;
3930
}
3931
} break;
3932
case Variant::FLOAT: {
3933
if (p_hint == PROPERTY_HINT_EXP_EASING) {
3934
EditorPropertyEasing *editor = memnew(EditorPropertyEasing);
3935
bool positive_only = false;
3936
bool flip = false;
3937
const Vector<String> hints = p_hint_text.split(",");
3938
for (int i = 0; i < hints.size(); i++) {
3939
const String hint = hints[i].strip_edges();
3940
if (hint == "attenuation") {
3941
flip = true;
3942
}
3943
if (hint == "positive_only") {
3944
positive_only = true;
3945
}
3946
}
3947
3948
editor->setup(positive_only, flip);
3949
return editor;
3950
3951
} else {
3952
EditorPropertyFloat *editor = memnew(EditorPropertyFloat);
3953
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
3954
return editor;
3955
}
3956
} break;
3957
case Variant::STRING: {
3958
if (p_hint == PROPERTY_HINT_ENUM || p_hint == PROPERTY_HINT_ENUM_SUGGESTION) {
3959
EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);
3960
Vector<String> options;
3961
Vector<String> option_names;
3962
if (p_hint_text.begins_with(";")) {
3963
// This is not supported officially. Only for `interface/editor/editor_language`.
3964
for (const String &option : p_hint_text.split(";", false)) {
3965
options.append(option.get_slicec('/', 0));
3966
option_names.append(option.get_slicec('/', 1));
3967
}
3968
} else {
3969
options = p_hint_text.split(",", false);
3970
}
3971
editor->setup(options, option_names, false, (p_hint == PROPERTY_HINT_ENUM_SUGGESTION));
3972
return editor;
3973
} else if (p_hint == PROPERTY_HINT_INPUT_NAME) {
3974
return get_input_action_editor(p_hint_text, false);
3975
} else if (p_hint == PROPERTY_HINT_MULTILINE_TEXT) {
3976
Vector<String> options = p_hint_text.split(",", false);
3977
EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText(false));
3978
if (options.has("monospace")) {
3979
editor->set_monospaced(true);
3980
}
3981
if (options.has("no_wrap")) {
3982
editor->set_wrap_lines(false);
3983
}
3984
return editor;
3985
} else if (p_hint == PROPERTY_HINT_EXPRESSION) {
3986
EditorPropertyMultilineText *editor = memnew(EditorPropertyMultilineText(true));
3987
return editor;
3988
} else if (p_hint == PROPERTY_HINT_TYPE_STRING) {
3989
EditorPropertyClassName *editor = memnew(EditorPropertyClassName);
3990
editor->setup(p_hint_text, p_hint_text);
3991
return editor;
3992
} else if (p_hint == PROPERTY_HINT_LOCALE_ID) {
3993
EditorPropertyLocale *editor = memnew(EditorPropertyLocale);
3994
editor->setup(p_hint_text);
3995
return editor;
3996
} else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE || p_hint == PROPERTY_HINT_FILE_PATH) {
3997
Vector<String> extensions = p_hint_text.split(",");
3998
bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE || p_hint == PROPERTY_HINT_GLOBAL_SAVE_FILE;
3999
bool folder = p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_GLOBAL_DIR;
4000
bool save = p_hint == PROPERTY_HINT_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_SAVE_FILE;
4001
bool enable_uid = p_hint == PROPERTY_HINT_FILE;
4002
EditorPropertyPath *editor = memnew(EditorPropertyPath);
4003
editor->setup(extensions, folder, global, enable_uid);
4004
if (save) {
4005
editor->set_save_mode();
4006
}
4007
return editor;
4008
} else {
4009
EditorPropertyText *editor = memnew(EditorPropertyText);
4010
4011
Vector<String> hints = p_hint_text.split(",");
4012
if (hints.has("monospace")) {
4013
editor->set_monospaced(true);
4014
}
4015
4016
if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {
4017
editor->set_placeholder(p_hint_text);
4018
} else if (p_hint == PROPERTY_HINT_PASSWORD) {
4019
editor->set_secret(true);
4020
editor->set_placeholder(p_hint_text);
4021
}
4022
return editor;
4023
}
4024
} break;
4025
4026
// math types
4027
4028
case Variant::VECTOR2: {
4029
EditorPropertyVector2 *editor = memnew(EditorPropertyVector2(p_wide));
4030
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step), p_hint == PROPERTY_HINT_LINK);
4031
return editor;
4032
4033
} break;
4034
case Variant::VECTOR2I: {
4035
EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i(p_wide));
4036
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true);
4037
hint.step = Math::round(hint.step);
4038
editor->setup(hint, p_hint == PROPERTY_HINT_LINK, true);
4039
return editor;
4040
4041
} break;
4042
case Variant::RECT2: {
4043
EditorPropertyRect2 *editor = memnew(EditorPropertyRect2(p_wide));
4044
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4045
return editor;
4046
} break;
4047
case Variant::RECT2I: {
4048
EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i(p_wide));
4049
editor->setup(_parse_range_hint(p_hint, p_hint_text, 1, true));
4050
return editor;
4051
} break;
4052
case Variant::VECTOR3: {
4053
EditorPropertyVector3 *editor = memnew(EditorPropertyVector3(p_wide));
4054
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step), p_hint == PROPERTY_HINT_LINK);
4055
return editor;
4056
4057
} break;
4058
case Variant::VECTOR3I: {
4059
EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i(p_wide));
4060
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true);
4061
hint.step = Math::round(hint.step);
4062
editor->setup(hint, p_hint == PROPERTY_HINT_LINK, true);
4063
return editor;
4064
4065
} break;
4066
case Variant::VECTOR4: {
4067
EditorPropertyVector4 *editor = memnew(EditorPropertyVector4);
4068
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step), p_hint == PROPERTY_HINT_LINK);
4069
return editor;
4070
4071
} break;
4072
case Variant::VECTOR4I: {
4073
EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i);
4074
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true);
4075
hint.step = Math::round(hint.step);
4076
editor->setup(hint, p_hint == PROPERTY_HINT_LINK, true);
4077
return editor;
4078
4079
} break;
4080
case Variant::TRANSFORM2D: {
4081
EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D);
4082
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4083
return editor;
4084
} break;
4085
case Variant::PLANE: {
4086
EditorPropertyPlane *editor = memnew(EditorPropertyPlane(p_wide));
4087
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4088
return editor;
4089
} break;
4090
case Variant::QUATERNION: {
4091
EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion);
4092
// Quaternions are almost never used for human-readable values that need stepifying,
4093
// so we should be more precise with their step, as much as the float precision allows.
4094
#ifdef REAL_T_IS_DOUBLE
4095
constexpr double QUATERNION_STEP = 1e-14;
4096
#else
4097
constexpr double QUATERNION_STEP = 1e-6;
4098
#endif
4099
editor->setup(_parse_range_hint(p_hint, p_hint_text, QUATERNION_STEP), p_hint == PROPERTY_HINT_HIDE_QUATERNION_EDIT);
4100
return editor;
4101
} break;
4102
case Variant::AABB: {
4103
EditorPropertyAABB *editor = memnew(EditorPropertyAABB);
4104
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4105
return editor;
4106
} break;
4107
case Variant::BASIS: {
4108
EditorPropertyBasis *editor = memnew(EditorPropertyBasis);
4109
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4110
return editor;
4111
} break;
4112
case Variant::TRANSFORM3D: {
4113
EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D);
4114
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4115
return editor;
4116
4117
} break;
4118
case Variant::PROJECTION: {
4119
EditorPropertyProjection *editor = memnew(EditorPropertyProjection);
4120
editor->setup(_parse_range_hint(p_hint, p_hint_text, default_float_step));
4121
return editor;
4122
4123
} break;
4124
4125
// misc types
4126
case Variant::COLOR: {
4127
EditorPropertyColor *editor = memnew(EditorPropertyColor);
4128
editor->setup(p_hint != PROPERTY_HINT_COLOR_NO_ALPHA);
4129
return editor;
4130
} break;
4131
case Variant::STRING_NAME: {
4132
if (p_hint == PROPERTY_HINT_ENUM || p_hint == PROPERTY_HINT_ENUM_SUGGESTION) {
4133
EditorPropertyTextEnum *editor = memnew(EditorPropertyTextEnum);
4134
Vector<String> options = p_hint_text.split(",", false);
4135
editor->setup(options, Vector<String>(), true, (p_hint == PROPERTY_HINT_ENUM_SUGGESTION));
4136
return editor;
4137
} else if (p_hint == PROPERTY_HINT_INPUT_NAME) {
4138
return get_input_action_editor(p_hint_text, true);
4139
} else {
4140
EditorPropertyText *editor = memnew(EditorPropertyText);
4141
if (p_hint == PROPERTY_HINT_PLACEHOLDER_TEXT) {
4142
editor->set_placeholder(p_hint_text);
4143
} else if (p_hint == PROPERTY_HINT_PASSWORD) {
4144
editor->set_secret(true);
4145
editor->set_placeholder(p_hint_text);
4146
}
4147
editor->set_string_name(true);
4148
return editor;
4149
}
4150
} break;
4151
case Variant::NODE_PATH: {
4152
EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath);
4153
if (p_hint == PROPERTY_HINT_NODE_PATH_VALID_TYPES && !p_hint_text.is_empty()) {
4154
Vector<String> types = p_hint_text.split(",", false);
4155
Vector<StringName> sn = Variant(types); //convert via variant
4156
editor->setup(sn, (p_usage & PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT));
4157
}
4158
return editor;
4159
4160
} break;
4161
case Variant::RID: {
4162
EditorPropertyRID *editor = memnew(EditorPropertyRID);
4163
return editor;
4164
} break;
4165
case Variant::OBJECT: {
4166
if (p_hint == PROPERTY_HINT_NODE_TYPE) {
4167
EditorPropertyNodePath *editor = memnew(EditorPropertyNodePath);
4168
Vector<String> types = p_hint_text.split(",", false);
4169
Vector<StringName> sn = Variant(types); //convert via variant
4170
editor->setup(sn, false, true);
4171
return editor;
4172
} else {
4173
EditorPropertyResource *editor = memnew(EditorPropertyResource);
4174
editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource");
4175
4176
if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) {
4177
const PackedStringArray open_in_new_inspector = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector");
4178
4179
for (const String &type : open_in_new_inspector) {
4180
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
4181
const String inherits = p_hint_text.get_slicec(',', j);
4182
if (ClassDB::is_parent_class(inherits, type)) {
4183
editor->set_use_sub_inspector(false);
4184
}
4185
}
4186
}
4187
}
4188
4189
return editor;
4190
}
4191
4192
} break;
4193
case Variant::CALLABLE: {
4194
EditorPropertyCallable *editor = memnew(EditorPropertyCallable);
4195
return editor;
4196
} break;
4197
case Variant::SIGNAL: {
4198
EditorPropertySignal *editor = memnew(EditorPropertySignal);
4199
return editor;
4200
} break;
4201
case Variant::DICTIONARY: {
4202
if (p_hint == PROPERTY_HINT_LOCALIZABLE_STRING) {
4203
EditorPropertyLocalizableString *editor = memnew(EditorPropertyLocalizableString);
4204
return editor;
4205
} else {
4206
EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
4207
editor->setup(p_hint, p_hint_text);
4208
return editor;
4209
}
4210
} break;
4211
case Variant::ARRAY: {
4212
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4213
editor->setup(Variant::ARRAY, p_hint_text);
4214
return editor;
4215
} break;
4216
case Variant::PACKED_BYTE_ARRAY: {
4217
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4218
editor->setup(Variant::PACKED_BYTE_ARRAY, p_hint_text);
4219
return editor;
4220
} break;
4221
case Variant::PACKED_INT32_ARRAY: {
4222
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4223
editor->setup(Variant::PACKED_INT32_ARRAY, p_hint_text);
4224
return editor;
4225
} break;
4226
case Variant::PACKED_INT64_ARRAY: {
4227
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4228
editor->setup(Variant::PACKED_INT64_ARRAY, p_hint_text);
4229
return editor;
4230
} break;
4231
case Variant::PACKED_FLOAT32_ARRAY: {
4232
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4233
editor->setup(Variant::PACKED_FLOAT32_ARRAY, p_hint_text);
4234
return editor;
4235
} break;
4236
case Variant::PACKED_FLOAT64_ARRAY: {
4237
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4238
editor->setup(Variant::PACKED_FLOAT64_ARRAY, p_hint_text);
4239
return editor;
4240
} break;
4241
case Variant::PACKED_STRING_ARRAY: {
4242
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4243
editor->setup(Variant::PACKED_STRING_ARRAY, p_hint_text);
4244
return editor;
4245
} break;
4246
case Variant::PACKED_VECTOR2_ARRAY: {
4247
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4248
editor->setup(Variant::PACKED_VECTOR2_ARRAY, p_hint_text);
4249
return editor;
4250
} break;
4251
case Variant::PACKED_VECTOR3_ARRAY: {
4252
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4253
editor->setup(Variant::PACKED_VECTOR3_ARRAY, p_hint_text);
4254
return editor;
4255
} break;
4256
case Variant::PACKED_COLOR_ARRAY: {
4257
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4258
editor->setup(Variant::PACKED_COLOR_ARRAY, p_hint_text);
4259
return editor;
4260
} break;
4261
case Variant::PACKED_VECTOR4_ARRAY: {
4262
EditorPropertyArray *editor = memnew(EditorPropertyArray);
4263
editor->setup(Variant::PACKED_VECTOR4_ARRAY, p_hint_text);
4264
return editor;
4265
} break;
4266
default: {
4267
}
4268
}
4269
4270
return nullptr;
4271
}
4272
4273