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