1/**************************************************************************/
2/* editor_inspector.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_inspector.h"
32
33#include "core/os/keyboard.h"
34#include "editor/doc_tools.h"
35#include "editor/editor_feature_profile.h"
36#include "editor/editor_node.h"
37#include "editor/editor_property_name_processor.h"
38#include "editor/editor_scale.h"
39#include "editor/editor_settings.h"
40#include "editor/editor_string_names.h"
41#include "editor/editor_undo_redo_manager.h"
42#include "editor/gui/editor_validation_panel.h"
43#include "editor/inspector_dock.h"
44#include "editor/plugins/script_editor_plugin.h"
45#include "multi_node_edit.h"
46#include "scene/gui/spin_box.h"
47#include "scene/gui/texture_rect.h"
48#include "scene/property_utils.h"
49#include "scene/resources/packed_scene.h"
50#include "scene/resources/style_box_flat.h"
51
52bool EditorInspector::_property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) {
53 if (p_property_path.findn(p_filter) != -1) {
54 return true;
55 }
56
57 const Vector<String> prop_sections = p_property_path.split("/");
58 for (int i = 0; i < prop_sections.size(); i++) {
59 if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(prop_sections[i], p_style))) {
60 return true;
61 }
62 }
63 return false;
64}
65
66Size2 EditorProperty::get_minimum_size() const {
67 Size2 ms;
68 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
69 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
70 ms.height = font->get_height(font_size) + 4 * EDSCALE;
71
72 for (int i = 0; i < get_child_count(); i++) {
73 Control *c = Object::cast_to<Control>(get_child(i));
74 if (!c) {
75 continue;
76 }
77 if (c->is_set_as_top_level()) {
78 continue;
79 }
80 if (!c->is_visible()) {
81 continue;
82 }
83 if (c == bottom_editor) {
84 continue;
85 }
86
87 Size2 minsize = c->get_combined_minimum_size();
88 ms.width = MAX(ms.width, minsize.width);
89 ms.height = MAX(ms.height, minsize.height);
90 }
91
92 if (keying) {
93 Ref<Texture2D> key = get_editor_theme_icon(SNAME("Key"));
94 ms.width += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
95 }
96
97 if (deletable) {
98 Ref<Texture2D> key = get_editor_theme_icon(SNAME("Close"));
99 ms.width += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
100 }
101
102 if (checkable) {
103 Ref<Texture2D> check = get_theme_icon(SNAME("checked"), SNAME("CheckBox"));
104 ms.width += check->get_width() + get_theme_constant(SNAME("h_separation"), SNAME("CheckBox")) + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
105 }
106
107 if (bottom_editor != nullptr && bottom_editor->is_visible()) {
108 ms.height += get_theme_constant(SNAME("v_separation"));
109 Size2 bems = bottom_editor->get_combined_minimum_size();
110 //bems.width += get_constant("item_margin", "Tree");
111 ms.height += bems.height;
112 ms.width = MAX(ms.width, bems.width);
113 }
114
115 return ms;
116}
117
118void EditorProperty::emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field, bool p_changing) {
119 Variant args[4] = { p_property, p_value, p_field, p_changing };
120 const Variant *argptrs[4] = { &args[0], &args[1], &args[2], &args[3] };
121
122 cache[p_property] = p_value;
123 emit_signalp(SNAME("property_changed"), (const Variant **)argptrs, 4);
124}
125
126void EditorProperty::_notification(int p_what) {
127 switch (p_what) {
128 case NOTIFICATION_SORT_CHILDREN: {
129 Size2 size = get_size();
130 Rect2 rect;
131 Rect2 bottom_rect;
132
133 right_child_rect = Rect2();
134 bottom_child_rect = Rect2();
135
136 {
137 int child_room = size.width * (1.0 - split_ratio);
138 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
139 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
140 int height = font->get_height(font_size) + 4 * EDSCALE;
141 bool no_children = true;
142
143 //compute room needed
144 for (int i = 0; i < get_child_count(); i++) {
145 Control *c = Object::cast_to<Control>(get_child(i));
146 if (!c) {
147 continue;
148 }
149 if (c->is_set_as_top_level()) {
150 continue;
151 }
152 if (c == bottom_editor) {
153 continue;
154 }
155
156 Size2 minsize = c->get_combined_minimum_size();
157 child_room = MAX(child_room, minsize.width);
158 height = MAX(height, minsize.height);
159 no_children = false;
160 }
161
162 if (no_children) {
163 text_size = size.width;
164 rect = Rect2(size.width - 1, 0, 1, height);
165 } else {
166 text_size = MAX(0, size.width - (child_room + 4 * EDSCALE));
167 if (is_layout_rtl()) {
168 rect = Rect2(1, 0, child_room, height);
169 } else {
170 rect = Rect2(size.width - child_room, 0, child_room, height);
171 }
172 }
173
174 if (bottom_editor) {
175 int m = 0; //get_constant("item_margin", "Tree");
176
177 bottom_rect = Rect2(m, rect.size.height + get_theme_constant(SNAME("v_separation")), size.width - m, bottom_editor->get_combined_minimum_size().height);
178 }
179
180 if (keying) {
181 Ref<Texture2D> key;
182
183 if (use_keying_next()) {
184 key = get_editor_theme_icon(SNAME("KeyNext"));
185 } else {
186 key = get_editor_theme_icon(SNAME("Key"));
187 }
188
189 rect.size.x -= key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
190 if (is_layout_rtl()) {
191 rect.position.x += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
192 }
193
194 if (no_children) {
195 text_size -= key->get_width() + 4 * EDSCALE;
196 }
197 }
198
199 if (deletable) {
200 Ref<Texture2D> close;
201
202 close = get_editor_theme_icon(SNAME("Close"));
203
204 rect.size.x -= close->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
205
206 if (is_layout_rtl()) {
207 rect.position.x += close->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
208 }
209
210 if (no_children) {
211 text_size -= close->get_width() + 4 * EDSCALE;
212 }
213 }
214 }
215
216 //set children
217 for (int i = 0; i < get_child_count(); i++) {
218 Control *c = Object::cast_to<Control>(get_child(i));
219 if (!c) {
220 continue;
221 }
222 if (c->is_set_as_top_level()) {
223 continue;
224 }
225 if (c == bottom_editor) {
226 continue;
227 }
228
229 fit_child_in_rect(c, rect);
230 right_child_rect = rect;
231 }
232
233 if (bottom_editor) {
234 fit_child_in_rect(bottom_editor, bottom_rect);
235 bottom_child_rect = bottom_rect;
236 }
237
238 queue_redraw(); //need to redraw text
239 } break;
240
241 case NOTIFICATION_DRAW: {
242 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
243 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
244 bool rtl = is_layout_rtl();
245
246 Size2 size = get_size();
247 if (bottom_editor) {
248 size.height = bottom_editor->get_offset(SIDE_TOP) - get_theme_constant(SNAME("v_separation"));
249 } else if (label_reference) {
250 size.height = label_reference->get_size().height;
251 }
252
253 Ref<StyleBox> sb = get_theme_stylebox(selected ? SNAME("bg_selected") : SNAME("bg"));
254 draw_style_box(sb, Rect2(Vector2(), size));
255
256 Ref<StyleBox> bg_stylebox = get_theme_stylebox(SNAME("child_bg"));
257 if (draw_top_bg && right_child_rect != Rect2()) {
258 draw_style_box(bg_stylebox, right_child_rect);
259 }
260 if (bottom_child_rect != Rect2()) {
261 draw_style_box(bg_stylebox, bottom_child_rect);
262 }
263
264 Color color;
265 if (draw_warning || draw_prop_warning) {
266 color = get_theme_color(is_read_only() ? SNAME("readonly_warning_color") : SNAME("warning_color"));
267 } else {
268 color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color"));
269 }
270 if (label.contains(".")) {
271 // FIXME: Move this to the project settings editor, as this is only used
272 // for project settings feature tag overrides.
273 color.a = 0.5;
274 }
275
276 int ofs = get_theme_constant(SNAME("font_offset"));
277 int text_limit = text_size - ofs;
278
279 if (checkable) {
280 Ref<Texture2D> checkbox;
281 if (checked) {
282 checkbox = get_editor_theme_icon(SNAME("GuiChecked"));
283 } else {
284 checkbox = get_editor_theme_icon(SNAME("GuiUnchecked"));
285 }
286
287 Color color2(1, 1, 1);
288 if (check_hover) {
289 color2.r *= 1.2;
290 color2.g *= 1.2;
291 color2.b *= 1.2;
292 }
293 check_rect = Rect2(ofs, ((size.height - checkbox->get_height()) / 2), checkbox->get_width(), checkbox->get_height());
294 if (rtl) {
295 draw_texture(checkbox, Vector2(size.width - check_rect.position.x - checkbox->get_width(), check_rect.position.y), color2);
296 } else {
297 draw_texture(checkbox, check_rect.position, color2);
298 }
299 int check_ofs = get_theme_constant(SNAME("hseparator"), SNAME("Tree")) + checkbox->get_width() + get_theme_constant(SNAME("h_separation"), SNAME("CheckBox"));
300 ofs += check_ofs;
301 text_limit -= check_ofs;
302 } else {
303 check_rect = Rect2();
304 }
305
306 if (can_revert && !is_read_only()) {
307 Ref<Texture2D> reload_icon = get_editor_theme_icon(SNAME("ReloadSmall"));
308 text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2;
309 revert_rect = Rect2(ofs + text_limit, (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height());
310
311 Color color2(1, 1, 1);
312 if (revert_hover) {
313 color2.r *= 1.2;
314 color2.g *= 1.2;
315 color2.b *= 1.2;
316 }
317 if (rtl) {
318 draw_texture(reload_icon, Vector2(size.width - revert_rect.position.x - reload_icon->get_width(), revert_rect.position.y), color2);
319 } else {
320 draw_texture(reload_icon, revert_rect.position, color2);
321 }
322 } else {
323 revert_rect = Rect2();
324 }
325
326 if (!pin_hidden && pinned) {
327 Ref<Texture2D> pinned_icon = get_editor_theme_icon(SNAME("Pin"));
328 int margin_w = get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2;
329 int total_icon_w = margin_w + pinned_icon->get_width();
330 int text_w = font->get_string_size(label, rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT, text_limit - total_icon_w, font_size).x;
331 int y = (size.height - pinned_icon->get_height()) / 2;
332 if (rtl) {
333 draw_texture(pinned_icon, Vector2(size.width - ofs - text_w - total_icon_w, y), color);
334 } else {
335 draw_texture(pinned_icon, Vector2(ofs + text_w + margin_w, y), color);
336 }
337 text_limit -= total_icon_w;
338 }
339
340 int v_ofs = (size.height - font->get_height(font_size)) / 2;
341 if (rtl) {
342 draw_string(font, Point2(size.width - ofs - text_limit, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_RIGHT, text_limit, font_size, color);
343 } else {
344 draw_string(font, Point2(ofs, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_LEFT, text_limit, font_size, color);
345 }
346
347 ofs = size.width;
348
349 if (keying) {
350 Ref<Texture2D> key;
351
352 if (use_keying_next()) {
353 key = get_editor_theme_icon(SNAME("KeyNext"));
354 } else {
355 key = get_editor_theme_icon(SNAME("Key"));
356 }
357
358 ofs -= key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
359
360 Color color2(1, 1, 1);
361 if (keying_hover) {
362 color2.r *= 1.2;
363 color2.g *= 1.2;
364 color2.b *= 1.2;
365 }
366 keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height());
367 if (rtl) {
368 draw_texture(key, Vector2(size.width - keying_rect.position.x - key->get_width(), keying_rect.position.y), color2);
369 } else {
370 draw_texture(key, keying_rect.position, color2);
371 }
372
373 } else {
374 keying_rect = Rect2();
375 }
376
377 if (deletable) {
378 Ref<Texture2D> close;
379
380 close = get_editor_theme_icon(SNAME("Close"));
381
382 ofs -= close->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree"));
383
384 Color color2(1, 1, 1);
385 if (delete_hover) {
386 color2.r *= 1.2;
387 color2.g *= 1.2;
388 color2.b *= 1.2;
389 }
390 delete_rect = Rect2(ofs, ((size.height - close->get_height()) / 2), close->get_width(), close->get_height());
391 if (rtl) {
392 draw_texture(close, Vector2(size.width - delete_rect.position.x - close->get_width(), delete_rect.position.y), color2);
393 } else {
394 draw_texture(close, delete_rect.position, color2);
395 }
396 } else {
397 delete_rect = Rect2();
398 }
399 } break;
400 }
401}
402
403void EditorProperty::set_label(const String &p_label) {
404 label = p_label;
405 queue_redraw();
406}
407
408String EditorProperty::get_label() const {
409 return label;
410}
411
412Object *EditorProperty::get_edited_object() {
413 return object;
414}
415
416StringName EditorProperty::get_edited_property() const {
417 return property;
418}
419
420EditorInspector *EditorProperty::get_parent_inspector() const {
421 Node *parent = get_parent();
422 while (parent) {
423 EditorInspector *ei = Object::cast_to<EditorInspector>(parent);
424 if (ei) {
425 return ei;
426 }
427 parent = parent->get_parent();
428 }
429 ERR_FAIL_V_MSG(nullptr, "EditorProperty is outside inspector.");
430}
431
432void EditorProperty::set_doc_path(const String &p_doc_path) {
433 doc_path = p_doc_path;
434}
435
436void EditorProperty::update_property() {
437 GDVIRTUAL_CALL(_update_property);
438}
439
440void EditorProperty::_set_read_only(bool p_read_only) {
441}
442
443void EditorProperty::set_read_only(bool p_read_only) {
444 read_only = p_read_only;
445 if (GDVIRTUAL_CALL(_set_read_only, p_read_only)) {
446 return;
447 }
448 _set_read_only(p_read_only);
449}
450
451bool EditorProperty::is_read_only() const {
452 return read_only;
453}
454
455Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid) {
456 if (p_object->property_can_revert(p_property)) {
457 if (r_is_valid) {
458 *r_is_valid = true;
459 }
460 return p_object->property_get_revert(p_property);
461 }
462
463 return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid);
464}
465
466bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) {
467 bool is_valid_revert = false;
468 Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert);
469 if (!is_valid_revert) {
470 return false;
471 }
472 Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property);
473 return PropertyUtils::is_property_value_different(current_value, revert_value);
474}
475
476StringName EditorProperty::_get_revert_property() const {
477 return property;
478}
479
480void EditorProperty::update_editor_property_status() {
481 if (property == StringName()) {
482 return; //no property, so nothing to do
483 }
484
485 bool new_pinned = false;
486 if (can_pin) {
487 Node *node = Object::cast_to<Node>(object);
488 CRASH_COND(!node);
489 new_pinned = node->is_property_pinned(property);
490 }
491
492 bool new_warning = false;
493 if (object->has_method("_get_property_warning")) {
494 new_warning = !String(object->call("_get_property_warning", property)).is_empty();
495 }
496
497 Variant current = object->get(_get_revert_property());
498 bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, &current) && !is_read_only();
499
500 bool new_checked = checked;
501 if (checkable) { // for properties like theme overrides.
502 bool valid = false;
503 Variant value = object->get(property, &valid);
504 if (valid) {
505 new_checked = value.get_type() != Variant::NIL;
506 }
507 }
508
509 if (new_can_revert != can_revert || new_pinned != pinned || new_checked != checked || new_warning != draw_prop_warning) {
510 if (new_can_revert != can_revert) {
511 emit_signal(SNAME("property_can_revert_changed"), property, new_can_revert);
512 }
513 draw_prop_warning = new_warning;
514 can_revert = new_can_revert;
515 pinned = new_pinned;
516 checked = new_checked;
517 queue_redraw();
518 }
519}
520
521bool EditorProperty::use_keying_next() const {
522 List<PropertyInfo> plist;
523 object->get_property_list(&plist, true);
524
525 for (List<PropertyInfo>::Element *I = plist.front(); I; I = I->next()) {
526 PropertyInfo &p = I->get();
527
528 if (p.name == property) {
529 return (p.usage & PROPERTY_USAGE_KEYING_INCREMENTS);
530 }
531 }
532
533 return false;
534}
535
536void EditorProperty::set_checkable(bool p_checkable) {
537 checkable = p_checkable;
538 queue_redraw();
539 queue_sort();
540}
541
542bool EditorProperty::is_checkable() const {
543 return checkable;
544}
545
546void EditorProperty::set_checked(bool p_checked) {
547 checked = p_checked;
548 queue_redraw();
549}
550
551bool EditorProperty::is_checked() const {
552 return checked;
553}
554
555void EditorProperty::set_draw_warning(bool p_draw_warning) {
556 draw_warning = p_draw_warning;
557 queue_redraw();
558}
559
560void EditorProperty::set_keying(bool p_keying) {
561 keying = p_keying;
562 queue_redraw();
563 queue_sort();
564}
565
566void EditorProperty::set_deletable(bool p_deletable) {
567 deletable = p_deletable;
568 queue_redraw();
569 queue_sort();
570}
571
572bool EditorProperty::is_deletable() const {
573 return deletable;
574}
575
576bool EditorProperty::is_keying() const {
577 return keying;
578}
579
580bool EditorProperty::is_draw_warning() const {
581 return draw_warning;
582}
583
584void EditorProperty::_focusable_focused(int p_index) {
585 if (!selectable) {
586 return;
587 }
588 bool already_selected = selected;
589 selected = true;
590 selected_focusable = p_index;
591 queue_redraw();
592 if (!already_selected && selected) {
593 emit_signal(SNAME("selected"), property, selected_focusable);
594 }
595}
596
597void EditorProperty::add_focusable(Control *p_control) {
598 p_control->connect("focus_entered", callable_mp(this, &EditorProperty::_focusable_focused).bind(focusables.size()));
599 focusables.push_back(p_control);
600}
601
602void EditorProperty::select(int p_focusable) {
603 bool already_selected = selected;
604 if (!selectable) {
605 return;
606 }
607
608 if (p_focusable >= 0) {
609 ERR_FAIL_INDEX(p_focusable, focusables.size());
610 focusables[p_focusable]->grab_focus();
611 } else {
612 selected = true;
613 queue_redraw();
614 }
615
616 if (!already_selected && selected) {
617 emit_signal(SNAME("selected"), property, selected_focusable);
618 }
619}
620
621void EditorProperty::deselect() {
622 selected = false;
623 selected_focusable = -1;
624 queue_redraw();
625}
626
627bool EditorProperty::is_selected() const {
628 return selected;
629}
630
631void EditorProperty::gui_input(const Ref<InputEvent> &p_event) {
632 ERR_FAIL_COND(p_event.is_null());
633
634 if (property == StringName()) {
635 return;
636 }
637
638 Ref<InputEventMouse> me = p_event;
639
640 if (me.is_valid()) {
641 Vector2 mpos = me->get_position();
642 if (is_layout_rtl()) {
643 mpos.x = get_size().x - mpos.x;
644 }
645 bool button_left = me->get_button_mask().has_flag(MouseButtonMask::LEFT);
646
647 bool new_keying_hover = keying_rect.has_point(mpos) && !button_left;
648 if (new_keying_hover != keying_hover) {
649 keying_hover = new_keying_hover;
650 queue_redraw();
651 }
652
653 bool new_delete_hover = delete_rect.has_point(mpos) && !button_left;
654 if (new_delete_hover != delete_hover) {
655 delete_hover = new_delete_hover;
656 queue_redraw();
657 }
658
659 bool new_revert_hover = revert_rect.has_point(mpos) && !button_left;
660 if (new_revert_hover != revert_hover) {
661 revert_hover = new_revert_hover;
662 queue_redraw();
663 }
664
665 bool new_check_hover = check_rect.has_point(mpos) && !button_left;
666 if (new_check_hover != check_hover) {
667 check_hover = new_check_hover;
668 queue_redraw();
669 }
670 }
671
672 Ref<InputEventMouseButton> mb = p_event;
673
674 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
675 Vector2 mpos = mb->get_position();
676 if (is_layout_rtl()) {
677 mpos.x = get_size().x - mpos.x;
678 }
679
680 select();
681
682 if (keying_rect.has_point(mpos)) {
683 accept_event();
684 emit_signal(SNAME("property_keyed"), property, use_keying_next());
685
686 if (use_keying_next()) {
687 if (property == "frame_coords" && (object->is_class("Sprite2D") || object->is_class("Sprite3D"))) {
688 Vector2i new_coords = object->get(property);
689 new_coords.x++;
690 if (new_coords.x >= int64_t(object->get("hframes"))) {
691 new_coords.x = 0;
692 new_coords.y++;
693 }
694 if (new_coords.x < int64_t(object->get("hframes")) && new_coords.y < int64_t(object->get("vframes"))) {
695 call_deferred(SNAME("emit_changed"), property, new_coords, "", false);
696 }
697 } else {
698 if (int64_t(object->get(property)) + 1 < (int64_t(object->get("hframes")) * int64_t(object->get("vframes")))) {
699 call_deferred(SNAME("emit_changed"), property, object->get(property).operator int64_t() + 1, "", false);
700 }
701 }
702
703 call_deferred(SNAME("update_property"));
704 }
705 }
706 if (delete_rect.has_point(mpos)) {
707 accept_event();
708 emit_signal(SNAME("property_deleted"), property);
709 }
710
711 if (revert_rect.has_point(mpos)) {
712 accept_event();
713 get_viewport()->gui_release_focus();
714 bool is_valid_revert = false;
715 Variant revert_value = EditorPropertyRevert::get_property_revert_value(object, property, &is_valid_revert);
716 ERR_FAIL_COND(!is_valid_revert);
717 emit_changed(_get_revert_property(), revert_value);
718 update_property();
719 }
720
721 if (check_rect.has_point(mpos)) {
722 accept_event();
723 checked = !checked;
724 queue_redraw();
725 emit_signal(SNAME("property_checked"), property, checked);
726 }
727 } else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
728 accept_event();
729 _update_popup();
730 menu->set_position(get_screen_position() + get_local_mouse_position());
731 menu->reset_size();
732 menu->popup();
733 select();
734 return;
735 }
736}
737
738void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) {
739 if (!selected || !p_event->is_pressed()) {
740 return;
741 }
742
743 const Ref<InputEventKey> k = p_event;
744
745 if (k.is_valid() && k->is_pressed()) {
746 if (ED_IS_SHORTCUT("property_editor/copy_value", p_event)) {
747 menu_option(MENU_COPY_VALUE);
748 accept_event();
749 } else if (ED_IS_SHORTCUT("property_editor/paste_value", p_event) && !is_read_only()) {
750 menu_option(MENU_PASTE_VALUE);
751 accept_event();
752 } else if (ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) {
753 menu_option(MENU_COPY_PROPERTY_PATH);
754 accept_event();
755 }
756 }
757}
758
759const Color *EditorProperty::_get_property_colors() {
760 static Color c[4];
761 c[0] = get_theme_color(SNAME("property_color_x"), EditorStringName(Editor));
762 c[1] = get_theme_color(SNAME("property_color_y"), EditorStringName(Editor));
763 c[2] = get_theme_color(SNAME("property_color_z"), EditorStringName(Editor));
764 c[3] = get_theme_color(SNAME("property_color_w"), EditorStringName(Editor));
765 return c;
766}
767
768void EditorProperty::set_label_reference(Control *p_control) {
769 label_reference = p_control;
770}
771
772void EditorProperty::set_bottom_editor(Control *p_control) {
773 bottom_editor = p_control;
774}
775
776Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
777 return object->get(p_prop, &r_valid);
778}
779
780bool EditorProperty::is_cache_valid() const {
781 if (object) {
782 for (const KeyValue<StringName, Variant> &E : cache) {
783 bool valid;
784 Variant value = _get_cache_value(E.key, valid);
785 if (!valid || value != E.value) {
786 return false;
787 }
788 }
789 }
790 return true;
791}
792void EditorProperty::update_cache() {
793 cache.clear();
794 if (object && property != StringName()) {
795 bool valid;
796 Variant value = _get_cache_value(property, valid);
797 if (valid) {
798 cache[property] = value;
799 }
800 }
801}
802Variant EditorProperty::get_drag_data(const Point2 &p_point) {
803 if (property == StringName()) {
804 return Variant();
805 }
806
807 Dictionary dp;
808 dp["type"] = "obj_property";
809 dp["object"] = object;
810 dp["property"] = property;
811 dp["value"] = object->get(property);
812
813 Label *drag_label = memnew(Label);
814 drag_label->set_text(property);
815 set_drag_preview(drag_label);
816 return dp;
817}
818
819void EditorProperty::set_use_folding(bool p_use_folding) {
820 use_folding = p_use_folding;
821}
822
823bool EditorProperty::is_using_folding() const {
824 return use_folding;
825}
826
827void EditorProperty::expand_all_folding() {
828}
829
830void EditorProperty::collapse_all_folding() {
831}
832
833void EditorProperty::expand_revertable() {
834}
835
836void EditorProperty::set_selectable(bool p_selectable) {
837 selectable = p_selectable;
838}
839
840bool EditorProperty::is_selectable() const {
841 return selectable;
842}
843
844void EditorProperty::set_name_split_ratio(float p_ratio) {
845 split_ratio = p_ratio;
846}
847
848float EditorProperty::get_name_split_ratio() const {
849 return split_ratio;
850}
851
852void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) {
853 object = p_object;
854 property = p_property;
855 _update_pin_flags();
856}
857
858static bool _is_value_potential_override(Node *p_node, const String &p_property) {
859 // Consider a value is potentially overriding another if either of the following is true:
860 // a) The node is foreign (inheriting or an instance), so the original value may come from another scene.
861 // b) The node belongs to the scene, but the original value comes from somewhere but the builtin class (i.e., a script).
862 Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
863 Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, edited_scene);
864 if (states_stack.size()) {
865 return true;
866 } else {
867 bool is_valid_default = false;
868 bool is_class_default = false;
869 PropertyUtils::get_property_default_value(p_node, p_property, &is_valid_default, &states_stack, false, nullptr, &is_class_default);
870 return !is_class_default;
871 }
872}
873
874void EditorProperty::_update_pin_flags() {
875 can_pin = false;
876 pin_hidden = true;
877 if (read_only) {
878 return;
879 }
880 if (Node *node = Object::cast_to<Node>(object)) {
881 // Avoid errors down the road by ignoring nodes which are not part of a scene
882 if (!node->get_owner()) {
883 bool is_scene_root = false;
884 for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); ++i) {
885 if (EditorNode::get_editor_data().get_edited_scene_root(i) == node) {
886 is_scene_root = true;
887 break;
888 }
889 }
890 if (!is_scene_root) {
891 return;
892 }
893 }
894 if (!_is_value_potential_override(node, property)) {
895 return;
896 }
897 pin_hidden = false;
898 {
899 HashSet<StringName> storable_properties;
900 node->get_storable_properties(storable_properties);
901 if (storable_properties.has(node->get_property_store_alias(property))) {
902 can_pin = true;
903 }
904 }
905 }
906}
907
908static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) {
909 // `p_text` is expected to be something like this:
910 // `item_name|Item description.`.
911 // Note that the description can be empty or contain `|`.
912 PackedStringArray slices = p_text.split("|", true, 1);
913 if (slices.size() < 2) {
914 return nullptr; // Use default tooltip instead.
915 }
916
917 String item_name = slices[0].strip_edges();
918 String item_descr = slices[1].strip_edges();
919
920 String text;
921 if (!p_item_type.is_empty()) {
922 text = p_item_type + " ";
923 }
924 text += "[u][b]" + item_name + "[/b][/u]\n";
925 if (item_descr.is_empty()) {
926 text += "[i]" + TTR("No description.") + "[/i]";
927 } else {
928 text += item_descr;
929 }
930 if (!p_warning.is_empty()) {
931 text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]";
932 }
933
934 EditorHelpBit *help_bit = memnew(EditorHelpBit);
935 help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
936 help_bit->set_text(text);
937
938 return help_bit;
939}
940
941Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
942 String warn;
943 Color warn_color;
944 if (object->has_method("_get_property_warning")) {
945 warn = object->call("_get_property_warning", property);
946 warn_color = get_theme_color(SNAME("warning_color"));
947 }
948 return make_help_bit(TTR("Property:"), p_text, warn, warn_color);
949}
950
951void EditorProperty::menu_option(int p_option) {
952 switch (p_option) {
953 case MENU_COPY_VALUE: {
954 InspectorDock::get_inspector_singleton()->set_property_clipboard(object->get(property));
955 } break;
956 case MENU_PASTE_VALUE: {
957 emit_changed(property, InspectorDock::get_inspector_singleton()->get_property_clipboard());
958 } break;
959 case MENU_COPY_PROPERTY_PATH: {
960 DisplayServer::get_singleton()->clipboard_set(property_path);
961 } break;
962 case MENU_PIN_VALUE: {
963 emit_signal(SNAME("property_pinned"), property, !pinned);
964 queue_redraw();
965 } break;
966 case MENU_OPEN_DOCUMENTATION: {
967 ScriptEditor::get_singleton()->goto_help(doc_path);
968 EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
969 } break;
970 }
971}
972
973void EditorProperty::_bind_methods() {
974 ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label);
975 ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label);
976
977 ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only);
978 ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only);
979
980 ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable);
981 ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable);
982
983 ClassDB::bind_method(D_METHOD("set_checked", "checked"), &EditorProperty::set_checked);
984 ClassDB::bind_method(D_METHOD("is_checked"), &EditorProperty::is_checked);
985
986 ClassDB::bind_method(D_METHOD("set_draw_warning", "draw_warning"), &EditorProperty::set_draw_warning);
987 ClassDB::bind_method(D_METHOD("is_draw_warning"), &EditorProperty::is_draw_warning);
988
989 ClassDB::bind_method(D_METHOD("set_keying", "keying"), &EditorProperty::set_keying);
990 ClassDB::bind_method(D_METHOD("is_keying"), &EditorProperty::is_keying);
991
992 ClassDB::bind_method(D_METHOD("set_deletable", "deletable"), &EditorProperty::set_deletable);
993 ClassDB::bind_method(D_METHOD("is_deletable"), &EditorProperty::is_deletable);
994
995 ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property);
996 ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object);
997
998 ClassDB::bind_method(D_METHOD("update_property"), &EditorProperty::update_property);
999
1000 ClassDB::bind_method(D_METHOD("add_focusable", "control"), &EditorProperty::add_focusable);
1001 ClassDB::bind_method(D_METHOD("set_bottom_editor", "editor"), &EditorProperty::set_bottom_editor);
1002
1003 ClassDB::bind_method(D_METHOD("emit_changed", "property", "value", "field", "changing"), &EditorProperty::emit_changed, DEFVAL(StringName()), DEFVAL(false));
1004
1005 ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label");
1006 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");
1007 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable");
1008 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked");
1009 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning");
1010 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying");
1011 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deletable"), "set_deletable", "is_deletable");
1012
1013 ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING_NAME, "field"), PropertyInfo(Variant::BOOL, "changing")));
1014 ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::PACKED_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value")));
1015 ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING_NAME, "property")));
1016 ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING_NAME, "property")));
1017 ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
1018 ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "checked")));
1019 ADD_SIGNAL(MethodInfo("property_pinned", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "pinned")));
1020 ADD_SIGNAL(MethodInfo("property_can_revert_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "can_revert")));
1021 ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
1022 ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::INT, "id")));
1023 ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx")));
1024
1025 GDVIRTUAL_BIND(_update_property)
1026 GDVIRTUAL_BIND(_set_read_only, "read_only")
1027
1028 ClassDB::bind_method(D_METHOD("_update_editor_property_status"), &EditorProperty::update_editor_property_status);
1029}
1030
1031EditorProperty::EditorProperty() {
1032 object = nullptr;
1033 split_ratio = 0.5;
1034 text_size = 0;
1035 property_usage = 0;
1036 selected_focusable = -1;
1037 label_reference = nullptr;
1038 bottom_editor = nullptr;
1039 menu = nullptr;
1040 set_process_shortcut_input(true);
1041}
1042
1043void EditorProperty::_update_popup() {
1044 if (menu) {
1045 menu->clear();
1046 } else {
1047 menu = memnew(PopupMenu);
1048 add_child(menu);
1049 menu->connect("id_pressed", callable_mp(this, &EditorProperty::menu_option));
1050 }
1051 menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionCopy")), ED_GET_SHORTCUT("property_editor/copy_value"), MENU_COPY_VALUE);
1052 menu->add_icon_shortcut(get_editor_theme_icon(SNAME("ActionPaste")), ED_GET_SHORTCUT("property_editor/paste_value"), MENU_PASTE_VALUE);
1053 menu->add_icon_shortcut(get_editor_theme_icon(SNAME("CopyNodePath")), ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH);
1054 menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
1055 if (!pin_hidden) {
1056 menu->add_separator();
1057 if (can_pin) {
1058 menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin")), TTR("Pin Value"), MENU_PIN_VALUE);
1059 menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned);
1060 } else {
1061 menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin")), vformat(TTR("Pin Value [Disabled because '%s' is editor-only]"), property), MENU_PIN_VALUE);
1062 menu->set_item_disabled(menu->get_item_index(MENU_PIN_VALUE), true);
1063 }
1064 menu->set_item_tooltip(menu->get_item_index(MENU_PIN_VALUE), TTR("Pinning a value forces it to be saved even if it's equal to the default."));
1065 }
1066
1067 if (!doc_path.is_empty()) {
1068 menu->add_separator();
1069 menu->add_icon_item(get_editor_theme_icon(SNAME("Help")), TTR("Open Documentation"), MENU_OPEN_DOCUMENTATION);
1070 }
1071}
1072
1073////////////////////////////////////////////////
1074////////////////////////////////////////////////
1075
1076void EditorInspectorPlugin::add_custom_control(Control *control) {
1077 AddedEditor ae;
1078 ae.property_editor = control;
1079 added_editors.push_back(ae);
1080}
1081
1082void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end) {
1083 AddedEditor ae;
1084 ae.properties.push_back(p_for_property);
1085 ae.property_editor = p_prop;
1086 ae.add_to_end = p_add_to_end;
1087 added_editors.push_back(ae);
1088}
1089
1090void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop) {
1091 AddedEditor ae;
1092 ae.properties = p_properties;
1093 ae.property_editor = p_prop;
1094 ae.label = p_label;
1095 added_editors.push_back(ae);
1096}
1097
1098bool EditorInspectorPlugin::can_handle(Object *p_object) {
1099 bool success = false;
1100 GDVIRTUAL_CALL(_can_handle, p_object, success);
1101 return success;
1102}
1103
1104void EditorInspectorPlugin::parse_begin(Object *p_object) {
1105 GDVIRTUAL_CALL(_parse_begin, p_object);
1106}
1107
1108void EditorInspectorPlugin::parse_category(Object *p_object, const String &p_category) {
1109 GDVIRTUAL_CALL(_parse_category, p_object, p_category);
1110}
1111
1112void EditorInspectorPlugin::parse_group(Object *p_object, const String &p_group) {
1113 GDVIRTUAL_CALL(_parse_group, p_object, p_group);
1114}
1115
1116bool EditorInspectorPlugin::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) {
1117 bool ret = false;
1118 GDVIRTUAL_CALL(_parse_property, p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide, ret);
1119 return ret;
1120}
1121
1122void EditorInspectorPlugin::parse_end(Object *p_object) {
1123 GDVIRTUAL_CALL(_parse_end, p_object);
1124}
1125
1126void EditorInspectorPlugin::_bind_methods() {
1127 ClassDB::bind_method(D_METHOD("add_custom_control", "control"), &EditorInspectorPlugin::add_custom_control);
1128 ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end"), &EditorInspectorPlugin::add_property_editor, DEFVAL(false));
1129 ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties);
1130
1131 GDVIRTUAL_BIND(_can_handle, "object")
1132 GDVIRTUAL_BIND(_parse_begin, "object")
1133 GDVIRTUAL_BIND(_parse_category, "object", "category")
1134 GDVIRTUAL_BIND(_parse_group, "object", "group")
1135 GDVIRTUAL_BIND(_parse_property, "object", "type", "name", "hint_type", "hint_string", "usage_flags", "wide");
1136 GDVIRTUAL_BIND(_parse_end, "object")
1137}
1138
1139////////////////////////////////////////////////
1140////////////////////////////////////////////////
1141
1142void EditorInspectorCategory::_notification(int p_what) {
1143 switch (p_what) {
1144 case NOTIFICATION_ENTER_TREE:
1145 case NOTIFICATION_THEME_CHANGED: {
1146 menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));
1147 } break;
1148 case NOTIFICATION_DRAW: {
1149 Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
1150
1151 draw_style_box(sb, Rect2(Vector2(), get_size()));
1152
1153 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
1154 int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
1155
1156 int hs = get_theme_constant(SNAME("h_separation"), SNAME("Tree"));
1157 int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
1158
1159 int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
1160 if (icon.is_valid()) {
1161 w += hs + icon_size;
1162 }
1163
1164 int ofs = (get_size().width - w) / 2;
1165
1166 if (icon.is_valid()) {
1167 Size2 rect_size = Size2(icon_size, icon_size);
1168 Point2 rect_pos = Point2(ofs, (get_size().height - icon_size) / 2).floor();
1169 draw_texture_rect(icon, Rect2(rect_pos, rect_size));
1170
1171 ofs += hs + icon_size;
1172 }
1173
1174 Color color = get_theme_color(SNAME("font_color"), SNAME("Tree"));
1175 draw_string(font, Point2(ofs, font->get_ascent(font_size) + (get_size().height - font->get_height(font_size)) / 2).floor(), label, HORIZONTAL_ALIGNMENT_LEFT, get_size().width, font_size, color);
1176 } break;
1177 }
1178}
1179
1180Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
1181 return make_help_bit(TTR("Class:"), p_text, String(), Color());
1182}
1183
1184Size2 EditorInspectorCategory::get_minimum_size() const {
1185 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
1186 int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
1187
1188 Size2 ms;
1189 ms.height = font->get_height(font_size);
1190 if (icon.is_valid()) {
1191 ms.height = MAX(icon->get_height(), ms.height);
1192 }
1193 ms.height += get_theme_constant(SNAME("v_separation"), SNAME("Tree"));
1194
1195 return ms;
1196}
1197
1198void EditorInspectorCategory::_handle_menu_option(int p_option) {
1199 switch (p_option) {
1200 case MENU_OPEN_DOCS:
1201 ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);
1202 EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
1203 break;
1204 }
1205}
1206
1207void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
1208 if (doc_class_name.is_empty()) {
1209 return;
1210 }
1211
1212 const Ref<InputEventMouseButton> &mb_event = p_event;
1213 if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) {
1214 return;
1215 }
1216
1217 menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
1218
1219 menu->set_position(get_screen_position() + mb_event->get_position());
1220 menu->reset_size();
1221 menu->popup();
1222}
1223
1224EditorInspectorCategory::EditorInspectorCategory() {
1225 menu = memnew(PopupMenu);
1226 menu->connect("id_pressed", callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
1227 menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS);
1228 add_child(menu);
1229}
1230
1231////////////////////////////////////////////////
1232////////////////////////////////////////////////
1233
1234void EditorInspectorSection::_test_unfold() {
1235 if (!vbox_added) {
1236 add_child(vbox);
1237 move_child(vbox, 0);
1238 vbox_added = true;
1239 }
1240}
1241
1242Ref<Texture2D> EditorInspectorSection::_get_arrow() {
1243 Ref<Texture2D> arrow;
1244 if (foldable) {
1245 if (object->editor_is_section_unfolded(section)) {
1246 arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
1247 } else {
1248 if (is_layout_rtl()) {
1249 arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));
1250 } else {
1251 arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));
1252 }
1253 }
1254 }
1255 return arrow;
1256}
1257
1258int EditorInspectorSection::_get_header_height() {
1259 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
1260 int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
1261
1262 int header_height = font->get_height(font_size);
1263 Ref<Texture2D> arrow = _get_arrow();
1264 if (arrow.is_valid()) {
1265 header_height = MAX(header_height, arrow->get_height());
1266 }
1267 header_height += get_theme_constant(SNAME("v_separation"), SNAME("Tree"));
1268
1269 return header_height;
1270}
1271
1272void EditorInspectorSection::_notification(int p_what) {
1273 switch (p_what) {
1274 case NOTIFICATION_THEME_CHANGED: {
1275 update_minimum_size();
1276 } break;
1277
1278 case NOTIFICATION_SORT_CHILDREN: {
1279 if (!vbox_added) {
1280 return;
1281 }
1282
1283 int inspector_margin = get_theme_constant(SNAME("inspector_margin"), EditorStringName(Editor));
1284 int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection"));
1285 if (indent_depth > 0 && section_indent_size > 0) {
1286 inspector_margin += indent_depth * section_indent_size;
1287 }
1288 Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection"));
1289 if (indent_depth > 0 && section_indent_style.is_valid()) {
1290 inspector_margin += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT);
1291 }
1292
1293 Size2 size = get_size() - Vector2(inspector_margin, 0);
1294 int header_height = _get_header_height();
1295 Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height);
1296 for (int i = 0; i < get_child_count(); i++) {
1297 Control *c = Object::cast_to<Control>(get_child(i));
1298 if (!c) {
1299 continue;
1300 }
1301 if (c->is_set_as_top_level()) {
1302 continue;
1303 }
1304
1305 fit_child_in_rect(c, Rect2(offset, size));
1306 }
1307 } break;
1308
1309 case NOTIFICATION_DRAW: {
1310 int section_indent = 0;
1311 int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection"));
1312 if (indent_depth > 0 && section_indent_size > 0) {
1313 section_indent = indent_depth * section_indent_size;
1314 }
1315 Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection"));
1316 if (indent_depth > 0 && section_indent_style.is_valid()) {
1317 section_indent += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT);
1318 }
1319
1320 int header_width = get_size().width - section_indent;
1321 int header_offset_x = 0.0;
1322 bool rtl = is_layout_rtl();
1323 if (!rtl) {
1324 header_offset_x += section_indent;
1325 }
1326
1327 // Draw header area.
1328 int header_height = _get_header_height();
1329 Rect2 header_rect = Rect2(Vector2(header_offset_x, 0.0), Vector2(header_width, header_height));
1330 Color c = bg_color;
1331 c.a *= 0.4;
1332 if (foldable && header_rect.has_point(get_local_mouse_position())) {
1333 c = c.lightened(Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) ? -0.05 : 0.2);
1334 }
1335 draw_rect(header_rect, c);
1336
1337 // Draw header title, folding arrow and count of revertable properties.
1338 {
1339 int separation = Math::round(2 * EDSCALE);
1340
1341 int margin_start = section_indent + separation;
1342 int margin_end = separation;
1343
1344 // - Arrow.
1345 Ref<Texture2D> arrow = _get_arrow();
1346 if (arrow.is_valid()) {
1347 Point2 arrow_position;
1348 if (rtl) {
1349 arrow_position.x = get_size().width - (margin_start + arrow->get_width());
1350 } else {
1351 arrow_position.x = margin_start;
1352 }
1353 arrow_position.y = (header_height - arrow->get_height()) / 2;
1354 draw_texture(arrow, arrow_position);
1355 margin_start += arrow->get_width();
1356 }
1357
1358 int available = get_size().width - (margin_start + margin_end);
1359
1360 // - Count of revertable properties.
1361 String num_revertable_str;
1362 int num_revertable_width = 0;
1363
1364 bool folded = foldable && !object->editor_is_section_unfolded(section);
1365
1366 Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
1367 int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
1368 Color font_color = get_theme_color(SNAME("font_color"), EditorStringName(Editor));
1369
1370 if (folded && revertable_properties.size()) {
1371 int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, available, font_size, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS).x;
1372
1373 Ref<Font> light_font = get_theme_font(SNAME("main"), EditorStringName(EditorFonts));
1374 int light_font_size = get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));
1375 Color light_font_color = get_theme_color(SNAME("disabled_font_color"), EditorStringName(Editor));
1376
1377 // Can we fit the long version of the revertable count text?
1378 num_revertable_str = vformat(TTRN("(%d change)", "(%d changes)", revertable_properties.size()), revertable_properties.size());
1379 num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x;
1380 if (label_width + separation + num_revertable_width > available) {
1381 // We'll have to use the short version.
1382 num_revertable_str = vformat("(%d)", revertable_properties.size());
1383 num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x;
1384 }
1385
1386 Point2 text_offset = Point2(
1387 margin_end,
1388 light_font->get_ascent(light_font_size) + (header_height - light_font->get_height(light_font_size)) / 2);
1389 if (!rtl) {
1390 text_offset.x = get_size().width - (text_offset.x + num_revertable_width);
1391 }
1392 draw_string(light_font, text_offset, num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, light_font_color, TextServer::JUSTIFICATION_NONE);
1393 margin_end += num_revertable_width + separation;
1394 available -= num_revertable_width + separation;
1395 }
1396
1397 // - Label.
1398 Point2 text_offset = Point2(
1399 margin_start,
1400 font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2);
1401 if (rtl) {
1402 text_offset.x = margin_end;
1403 }
1404 HorizontalAlignment text_align = rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT;
1405 draw_string(font, text_offset, label, text_align, available, font_size, font_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
1406 }
1407
1408 // Draw dropping highlight.
1409 if (dropping && !vbox->is_visible_in_tree()) {
1410 Color accent_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1411 draw_rect(Rect2(Point2(), get_size()), accent_color, false);
1412 }
1413
1414 // Draw section indentation.
1415 if (section_indent_style.is_valid() && section_indent > 0) {
1416 Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height));
1417 if (rtl) {
1418 indent_rect.position.x = get_size().width - section_indent + section_indent_style->get_margin(SIDE_RIGHT);
1419 } else {
1420 indent_rect.position.x = section_indent_style->get_margin(SIDE_LEFT);
1421 }
1422 draw_style_box(section_indent_style, indent_rect);
1423 }
1424 } break;
1425
1426 case NOTIFICATION_DRAG_BEGIN: {
1427 dropping_for_unfold = true;
1428 } break;
1429
1430 case NOTIFICATION_DRAG_END: {
1431 dropping_for_unfold = false;
1432 } break;
1433
1434 case NOTIFICATION_MOUSE_ENTER: {
1435 if (dropping || dropping_for_unfold) {
1436 dropping_unfold_timer->start();
1437 }
1438 queue_redraw();
1439 } break;
1440
1441 case NOTIFICATION_MOUSE_EXIT: {
1442 if (dropping || dropping_for_unfold) {
1443 dropping_unfold_timer->stop();
1444 }
1445 queue_redraw();
1446 } break;
1447 }
1448}
1449
1450Size2 EditorInspectorSection::get_minimum_size() const {
1451 Size2 ms;
1452 for (int i = 0; i < get_child_count(); i++) {
1453 Control *c = Object::cast_to<Control>(get_child(i));
1454 if (!c) {
1455 continue;
1456 }
1457 if (c->is_set_as_top_level()) {
1458 continue;
1459 }
1460 if (!c->is_visible()) {
1461 continue;
1462 }
1463 Size2 minsize = c->get_combined_minimum_size();
1464 ms.width = MAX(ms.width, minsize.width);
1465 ms.height = MAX(ms.height, minsize.height);
1466 }
1467
1468 Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree"));
1469 int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree"));
1470 ms.height += font->get_height(font_size) + get_theme_constant(SNAME("v_separation"), SNAME("Tree"));
1471 ms.width += get_theme_constant(SNAME("inspector_margin"), EditorStringName(Editor));
1472
1473 int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection"));
1474 if (indent_depth > 0 && section_indent_size > 0) {
1475 ms.width += indent_depth * section_indent_size;
1476 }
1477 Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection"));
1478 if (indent_depth > 0 && section_indent_style.is_valid()) {
1479 ms.width += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT);
1480 }
1481
1482 return ms;
1483}
1484
1485void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth) {
1486 section = p_section;
1487 label = p_label;
1488 object = p_object;
1489 bg_color = p_bg_color;
1490 foldable = p_foldable;
1491 indent_depth = p_indent_depth;
1492
1493 if (!foldable && !vbox_added) {
1494 add_child(vbox);
1495 move_child(vbox, 0);
1496 vbox_added = true;
1497 }
1498
1499 if (foldable) {
1500 _test_unfold();
1501 if (object->editor_is_section_unfolded(section)) {
1502 vbox->show();
1503 } else {
1504 vbox->hide();
1505 }
1506 }
1507}
1508
1509void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) {
1510 ERR_FAIL_COND(p_event.is_null());
1511
1512 if (!foldable) {
1513 return;
1514 }
1515
1516 Ref<InputEventMouseButton> mb = p_event;
1517 if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
1518 if (object->editor_is_section_unfolded(section)) {
1519 int header_height = _get_header_height();
1520
1521 if (mb->get_position().y >= header_height) {
1522 return;
1523 }
1524 }
1525
1526 accept_event();
1527
1528 bool should_unfold = !object->editor_is_section_unfolded(section);
1529 if (should_unfold) {
1530 unfold();
1531 } else {
1532 fold();
1533 }
1534 } else if (mb.is_valid() && !mb->is_pressed()) {
1535 queue_redraw();
1536 }
1537}
1538
1539VBoxContainer *EditorInspectorSection::get_vbox() {
1540 return vbox;
1541}
1542
1543void EditorInspectorSection::unfold() {
1544 if (!foldable) {
1545 return;
1546 }
1547
1548 _test_unfold();
1549
1550 object->editor_set_section_unfold(section, true);
1551 vbox->show();
1552 queue_redraw();
1553}
1554
1555void EditorInspectorSection::fold() {
1556 if (!foldable) {
1557 return;
1558 }
1559
1560 if (!vbox_added) {
1561 return;
1562 }
1563
1564 object->editor_set_section_unfold(section, false);
1565 vbox->hide();
1566 queue_redraw();
1567}
1568
1569void EditorInspectorSection::set_bg_color(const Color &p_bg_color) {
1570 bg_color = p_bg_color;
1571 queue_redraw();
1572}
1573
1574bool EditorInspectorSection::has_revertable_properties() const {
1575 return !revertable_properties.is_empty();
1576}
1577
1578void EditorInspectorSection::property_can_revert_changed(const String &p_path, bool p_can_revert) {
1579 bool had_revertable_properties = has_revertable_properties();
1580 if (p_can_revert) {
1581 revertable_properties.insert(p_path);
1582 } else {
1583 revertable_properties.erase(p_path);
1584 }
1585 if (has_revertable_properties() != had_revertable_properties) {
1586 queue_redraw();
1587 }
1588}
1589
1590void EditorInspectorSection::_bind_methods() {
1591 ClassDB::bind_method(D_METHOD("setup", "section", "label", "object", "bg_color", "foldable"), &EditorInspectorSection::setup);
1592 ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox);
1593 ClassDB::bind_method(D_METHOD("unfold"), &EditorInspectorSection::unfold);
1594 ClassDB::bind_method(D_METHOD("fold"), &EditorInspectorSection::fold);
1595}
1596
1597EditorInspectorSection::EditorInspectorSection() {
1598 vbox = memnew(VBoxContainer);
1599
1600 dropping_unfold_timer = memnew(Timer);
1601 dropping_unfold_timer->set_wait_time(0.6);
1602 dropping_unfold_timer->set_one_shot(true);
1603 add_child(dropping_unfold_timer);
1604 dropping_unfold_timer->connect("timeout", callable_mp(this, &EditorInspectorSection::unfold));
1605}
1606
1607EditorInspectorSection::~EditorInspectorSection() {
1608 if (!vbox_added) {
1609 memdelete(vbox);
1610 }
1611}
1612
1613////////////////////////////////////////////////
1614////////////////////////////////////////////////
1615
1616int EditorInspectorArray::_get_array_count() {
1617 if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
1618 List<PropertyInfo> object_property_list;
1619 object->get_property_list(&object_property_list);
1620 return _extract_properties_as_array(object_property_list).size();
1621 } else if (mode == MODE_USE_COUNT_PROPERTY) {
1622 bool valid;
1623 int count_val = object->get(count_property, &valid);
1624 ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property));
1625 return count_val;
1626 }
1627 return 0;
1628}
1629
1630void EditorInspectorArray::_add_button_pressed() {
1631 _move_element(-1, -1);
1632}
1633
1634void EditorInspectorArray::_paginator_page_changed(int p_page) {
1635 emit_signal("page_change_request", p_page);
1636}
1637
1638void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) {
1639 switch (p_id) {
1640 case OPTION_MOVE_UP:
1641 if (popup_array_index_pressed > 0) {
1642 _move_element(popup_array_index_pressed, popup_array_index_pressed - 1);
1643 }
1644 break;
1645 case OPTION_MOVE_DOWN:
1646 if (popup_array_index_pressed < count - 1) {
1647 _move_element(popup_array_index_pressed, popup_array_index_pressed + 2);
1648 }
1649 break;
1650 case OPTION_NEW_BEFORE:
1651 _move_element(-1, popup_array_index_pressed);
1652 break;
1653 case OPTION_NEW_AFTER:
1654 _move_element(-1, popup_array_index_pressed + 1);
1655 break;
1656 case OPTION_REMOVE:
1657 _move_element(popup_array_index_pressed, -1);
1658 break;
1659 case OPTION_CLEAR_ARRAY:
1660 _clear_array();
1661 break;
1662 case OPTION_RESIZE_ARRAY:
1663 new_size_spin_box->set_value(count);
1664 resize_dialog->get_ok_button()->set_disabled(true);
1665 resize_dialog->popup_centered(Size2(250, 0) * EDSCALE);
1666 new_size_spin_box->get_line_edit()->grab_focus();
1667 new_size_spin_box->get_line_edit()->select_all();
1668 break;
1669 default:
1670 break;
1671 }
1672}
1673
1674void EditorInspectorArray::_control_dropping_draw() {
1675 int drop_position = _drop_position();
1676
1677 if (dropping && drop_position >= 0) {
1678 Vector2 from;
1679 Vector2 to;
1680 if (drop_position < elements_vbox->get_child_count()) {
1681 Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform();
1682 from = xform.xform(Vector2());
1683 to = xform.xform(Vector2(elements_vbox->get_size().x, 0));
1684 } else {
1685 Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1));
1686 Transform2D xform = child->get_transform();
1687 from = xform.xform(Vector2(0, child->get_size().y));
1688 to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y));
1689 }
1690 Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1691 control_dropping->draw_line(from, to, color, 2);
1692 }
1693}
1694
1695void EditorInspectorArray::_vbox_visibility_changed() {
1696 control_dropping->set_visible(vbox->is_visible_in_tree());
1697}
1698
1699void EditorInspectorArray::_panel_draw(int p_index) {
1700 ERR_FAIL_INDEX(p_index, (int)array_elements.size());
1701
1702 Ref<StyleBox> style = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles));
1703 if (!style.is_valid()) {
1704 return;
1705 }
1706 if (array_elements[p_index].panel->has_focus()) {
1707 array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size()));
1708 }
1709}
1710
1711void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) {
1712 ERR_FAIL_INDEX(p_index, (int)array_elements.size());
1713
1714 if (read_only) {
1715 return;
1716 }
1717
1718 Ref<InputEventKey> key_ref = p_event;
1719 if (key_ref.is_valid()) {
1720 const InputEventKey &key = **key_ref;
1721
1722 if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == Key::KEY_DELETE) {
1723 _move_element(begin_array_index + p_index, -1);
1724 array_elements[p_index].panel->accept_event();
1725 }
1726 }
1727
1728 Ref<InputEventMouseButton> mb = p_event;
1729 if (mb.is_valid()) {
1730 if (movable && mb->get_button_index() == MouseButton::RIGHT) {
1731 array_elements[p_index].panel->accept_event();
1732 popup_array_index_pressed = begin_array_index + p_index;
1733 rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);
1734 rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);
1735 rmb_popup->set_position(get_screen_position() + mb->get_position());
1736 rmb_popup->reset_size();
1737 rmb_popup->popup();
1738 }
1739 }
1740}
1741
1742void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) {
1743 String action_name;
1744 if (p_element_index < 0) {
1745 action_name = vformat(TTR("Add element to property array with prefix %s."), array_element_prefix);
1746 } else if (p_to_pos < 0) {
1747 action_name = vformat(TTR("Remove element %d from property array with prefix %s."), p_element_index, array_element_prefix);
1748 } else {
1749 action_name = vformat(TTR("Move element %d to position %d in property array with prefix %s."), p_element_index, p_to_pos, array_element_prefix);
1750 }
1751 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1752 undo_redo->create_action(action_name);
1753 if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
1754 // Call the function.
1755 Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());
1756 if (move_function.is_valid()) {
1757 Variant args[] = { undo_redo, object, array_element_prefix, p_element_index, p_to_pos };
1758 const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
1759 Variant return_value;
1760 Callable::CallError call_error;
1761 move_function.callp(args_p, 5, return_value, call_error);
1762 } else {
1763 WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
1764 }
1765 } else if (mode == MODE_USE_COUNT_PROPERTY) {
1766 ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count);
1767
1768 if (!swap_method.is_empty()) {
1769 ERR_FAIL_COND(!object->has_method(swap_method));
1770
1771 // Swap method was provided, use it.
1772 if (p_element_index < 0) {
1773 // Add an element at position
1774 undo_redo->add_do_property(object, count_property, count + 1);
1775 if (p_to_pos >= 0) {
1776 for (int i = count; i > p_to_pos; i--) {
1777 undo_redo->add_do_method(object, swap_method, i, i - 1);
1778 }
1779 for (int i = p_to_pos; i < count; i++) {
1780 undo_redo->add_undo_method(object, swap_method, i, i + 1);
1781 }
1782 }
1783 undo_redo->add_undo_property(object, count_property, count);
1784
1785 } else if (p_to_pos < 0) {
1786 if (count > 0) {
1787 // Remove element at position
1788 undo_redo->add_undo_property(object, count_property, count);
1789
1790 List<PropertyInfo> object_property_list;
1791 object->get_property_list(&object_property_list);
1792
1793 for (int i = p_element_index; i < count - 1; i++) {
1794 undo_redo->add_do_method(object, swap_method, i, i + 1);
1795 }
1796
1797 for (int i = count; i > p_element_index; i--) {
1798 undo_redo->add_undo_method(object, swap_method, i, i - 1);
1799 }
1800
1801 String erase_prefix = String(array_element_prefix) + itos(p_element_index);
1802
1803 for (const PropertyInfo &E : object_property_list) {
1804 if (E.name.begins_with(erase_prefix)) {
1805 undo_redo->add_undo_property(object, E.name, object->get(E.name));
1806 }
1807 }
1808
1809 undo_redo->add_do_property(object, count_property, count - 1);
1810 }
1811 } else {
1812 if (p_to_pos > p_element_index) {
1813 p_to_pos--;
1814 }
1815
1816 if (p_to_pos < p_element_index) {
1817 for (int i = p_element_index; i > p_to_pos; i--) {
1818 undo_redo->add_do_method(object, swap_method, i, i - 1);
1819 }
1820 for (int i = p_to_pos; i < p_element_index; i++) {
1821 undo_redo->add_undo_method(object, swap_method, i, i + 1);
1822 }
1823 } else if (p_to_pos > p_element_index) {
1824 for (int i = p_element_index; i < p_to_pos; i++) {
1825 undo_redo->add_do_method(object, swap_method, i, i + 1);
1826 }
1827
1828 for (int i = p_to_pos; i > p_element_index; i--) {
1829 undo_redo->add_undo_method(object, swap_method, i, i - 1);
1830 }
1831 }
1832 }
1833 } else {
1834 // Use standard properties.
1835 List<PropertyInfo> object_property_list;
1836 object->get_property_list(&object_property_list);
1837
1838 Array properties_as_array = _extract_properties_as_array(object_property_list);
1839 properties_as_array.resize(count);
1840
1841 // For undoing things
1842 undo_redo->add_undo_property(object, count_property, properties_as_array.size());
1843 for (int i = 0; i < (int)properties_as_array.size(); i++) {
1844 Dictionary d = Dictionary(properties_as_array[i]);
1845 Array keys = d.keys();
1846 for (int j = 0; j < keys.size(); j++) {
1847 String key = keys[j];
1848 undo_redo->add_undo_property(object, vformat(key, i), d[key]);
1849 }
1850 }
1851
1852 if (p_element_index < 0) {
1853 // Add an element.
1854 properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary());
1855 } else if (p_to_pos < 0) {
1856 // Delete the element.
1857 properties_as_array.remove_at(p_element_index);
1858 } else {
1859 // Move the element.
1860 properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate());
1861 properties_as_array.remove_at(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index);
1862 }
1863
1864 // Change the array size then set the properties.
1865 undo_redo->add_do_property(object, count_property, properties_as_array.size());
1866 for (int i = 0; i < (int)properties_as_array.size(); i++) {
1867 Dictionary d = properties_as_array[i];
1868 Array keys = d.keys();
1869 for (int j = 0; j < keys.size(); j++) {
1870 String key = keys[j];
1871 undo_redo->add_do_property(object, vformat(key, i), d[key]);
1872 }
1873 }
1874 }
1875 }
1876 undo_redo->commit_action();
1877
1878 // Handle page change and update counts.
1879 if (p_element_index < 0) {
1880 int added_index = p_to_pos < 0 ? count : p_to_pos;
1881 emit_signal(SNAME("page_change_request"), added_index / page_length);
1882 count += 1;
1883 } else if (p_to_pos < 0) {
1884 count -= 1;
1885 if (page == max_page && (MAX(0, count - 1) / page_length != max_page)) {
1886 emit_signal(SNAME("page_change_request"), max_page - 1);
1887 }
1888 } else if (p_to_pos == begin_array_index - 1) {
1889 emit_signal(SNAME("page_change_request"), page - 1);
1890 } else if (p_to_pos > end_array_index) {
1891 emit_signal(SNAME("page_change_request"), page + 1);
1892 }
1893 begin_array_index = page * page_length;
1894 end_array_index = MIN(count, (page + 1) * page_length);
1895 max_page = MAX(0, count - 1) / page_length;
1896}
1897
1898void EditorInspectorArray::_clear_array() {
1899 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1900 undo_redo->create_action(vformat(TTR("Clear Property Array with Prefix %s"), array_element_prefix));
1901 if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
1902 for (int i = count - 1; i >= 0; i--) {
1903 // Call the function.
1904 Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());
1905 if (move_function.is_valid()) {
1906 Variant args[] = { undo_redo, object, array_element_prefix, i, -1 };
1907 const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
1908 Variant return_value;
1909 Callable::CallError call_error;
1910 move_function.callp(args_p, 5, return_value, call_error);
1911 } else {
1912 WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
1913 }
1914 }
1915 } else if (mode == MODE_USE_COUNT_PROPERTY) {
1916 List<PropertyInfo> object_property_list;
1917 object->get_property_list(&object_property_list);
1918
1919 Array properties_as_array = _extract_properties_as_array(object_property_list);
1920 properties_as_array.resize(count);
1921
1922 // For undoing things
1923 undo_redo->add_undo_property(object, count_property, count);
1924 for (int i = 0; i < (int)properties_as_array.size(); i++) {
1925 Dictionary d = Dictionary(properties_as_array[i]);
1926 Array keys = d.keys();
1927 for (int j = 0; j < keys.size(); j++) {
1928 String key = keys[j];
1929 undo_redo->add_undo_property(object, vformat(key, i), d[key]);
1930 }
1931 }
1932
1933 // Change the array size then set the properties.
1934 undo_redo->add_do_property(object, count_property, 0);
1935 }
1936 undo_redo->commit_action();
1937
1938 // Handle page change and update counts.
1939 emit_signal(SNAME("page_change_request"), 0);
1940 count = 0;
1941 begin_array_index = 0;
1942 end_array_index = 0;
1943 max_page = 0;
1944}
1945
1946void EditorInspectorArray::_resize_array(int p_size) {
1947 ERR_FAIL_COND(p_size < 0);
1948 if (p_size == count) {
1949 return;
1950 }
1951
1952 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
1953 undo_redo->create_action(vformat(TTR("Resize Property Array with Prefix %s"), array_element_prefix));
1954 if (p_size > count) {
1955 if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
1956 for (int i = count; i < p_size; i++) {
1957 // Call the function.
1958 Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());
1959 if (move_function.is_valid()) {
1960 Variant args[] = { undo_redo, object, array_element_prefix, -1, -1 };
1961 const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
1962 Variant return_value;
1963 Callable::CallError call_error;
1964 move_function.callp(args_p, 5, return_value, call_error);
1965 } else {
1966 WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
1967 }
1968 }
1969 } else if (mode == MODE_USE_COUNT_PROPERTY) {
1970 undo_redo->add_undo_property(object, count_property, count);
1971 undo_redo->add_do_property(object, count_property, p_size);
1972 }
1973 } else {
1974 if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {
1975 for (int i = count - 1; i > p_size - 1; i--) {
1976 // Call the function.
1977 Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());
1978 if (move_function.is_valid()) {
1979 Variant args[] = { undo_redo, object, array_element_prefix, i, -1 };
1980 const Variant *args_p[] = { &args[0], &args[1], &args[2], &args[3], &args[4] };
1981 Variant return_value;
1982 Callable::CallError call_error;
1983 move_function.callp(args_p, 5, return_value, call_error);
1984 } else {
1985 WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));
1986 }
1987 }
1988 } else if (mode == MODE_USE_COUNT_PROPERTY) {
1989 List<PropertyInfo> object_property_list;
1990 object->get_property_list(&object_property_list);
1991
1992 Array properties_as_array = _extract_properties_as_array(object_property_list);
1993 properties_as_array.resize(count);
1994
1995 // For undoing things
1996 undo_redo->add_undo_property(object, count_property, count);
1997 for (int i = count - 1; i > p_size - 1; i--) {
1998 Dictionary d = Dictionary(properties_as_array[i]);
1999 Array keys = d.keys();
2000 for (int j = 0; j < keys.size(); j++) {
2001 String key = keys[j];
2002 undo_redo->add_undo_property(object, vformat(key, i), d[key]);
2003 }
2004 }
2005
2006 // Change the array size then set the properties.
2007 undo_redo->add_do_property(object, count_property, p_size);
2008 }
2009 }
2010 undo_redo->commit_action();
2011
2012 // Handle page change and update counts.
2013 emit_signal(SNAME("page_change_request"), 0);
2014 /*
2015 count = 0;
2016 begin_array_index = 0;
2017 end_array_index = 0;
2018 max_page = 0;
2019 */
2020}
2021
2022Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) {
2023 Array output;
2024
2025 for (const PropertyInfo &pi : p_list) {
2026 if (pi.name.begins_with(array_element_prefix)) {
2027 String str = pi.name.trim_prefix(array_element_prefix);
2028
2029 int to_char_index = 0;
2030 while (to_char_index < str.length()) {
2031 if (!is_digit(str[to_char_index])) {
2032 break;
2033 }
2034 to_char_index++;
2035 }
2036 if (to_char_index > 0) {
2037 int array_index = str.left(to_char_index).to_int();
2038 Error error = OK;
2039 if (array_index >= output.size()) {
2040 error = output.resize(array_index + 1);
2041 }
2042 if (error == OK) {
2043 String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index);
2044 Dictionary dict = output[array_index];
2045 dict[format_string] = object->get(pi.name);
2046 output[array_index] = dict;
2047 } else {
2048 WARN_PRINT(vformat("Array element %s has an index too high. Array allocation failed.", pi.name));
2049 }
2050 }
2051 }
2052 }
2053 return output;
2054}
2055
2056int EditorInspectorArray::_drop_position() const {
2057 for (int i = 0; i < (int)array_elements.size(); i++) {
2058 const ArrayElement &ae = array_elements[i];
2059
2060 Size2 size = ae.panel->get_size();
2061 Vector2 mp = ae.panel->get_local_mouse_position();
2062
2063 if (Rect2(Vector2(), size).has_point(mp)) {
2064 if (mp.y < size.y / 2) {
2065 return i;
2066 } else {
2067 return i + 1;
2068 }
2069 }
2070 }
2071 return -1;
2072}
2073
2074void EditorInspectorArray::_resize_dialog_confirmed() {
2075 if (int(new_size_spin_box->get_value()) == count) {
2076 return;
2077 }
2078
2079 resize_dialog->hide();
2080 _resize_array(int(new_size_spin_box->get_value()));
2081}
2082
2083void EditorInspectorArray::_new_size_spin_box_value_changed(float p_value) {
2084 resize_dialog->get_ok_button()->set_disabled(int(p_value) == count);
2085}
2086
2087void EditorInspectorArray::_new_size_spin_box_text_submitted(String p_text) {
2088 _resize_dialog_confirmed();
2089}
2090
2091void EditorInspectorArray::_setup() {
2092 // Setup counts.
2093 count = _get_array_count();
2094 begin_array_index = page * page_length;
2095 end_array_index = MIN(count, (page + 1) * page_length);
2096 max_page = MAX(0, count - 1) / page_length;
2097 array_elements.resize(MAX(0, end_array_index - begin_array_index));
2098 if (page < 0 || page > max_page) {
2099 WARN_PRINT(vformat("Invalid page number %d", page));
2100 page = CLAMP(page, 0, max_page);
2101 }
2102
2103 Ref<Font> numbers_font;
2104 int numbers_min_w = 0;
2105
2106 if (numbered) {
2107 numbers_font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
2108 int digits_found = count;
2109 String test;
2110 while (digits_found) {
2111 test += "8";
2112 digits_found /= 10;
2113 }
2114 numbers_min_w = numbers_font->get_string_size(test).width;
2115 }
2116
2117 for (int i = 0; i < (int)array_elements.size(); i++) {
2118 ArrayElement &ae = array_elements[i];
2119
2120 // Panel and its hbox.
2121 ae.panel = memnew(PanelContainer);
2122 ae.panel->set_focus_mode(FOCUS_ALL);
2123 ae.panel->set_mouse_filter(MOUSE_FILTER_PASS);
2124 SET_DRAG_FORWARDING_GCD(ae.panel, EditorInspectorArray);
2125
2126 int element_position = begin_array_index + i;
2127 ae.panel->set_meta("index", element_position);
2128 ae.panel->set_tooltip_text(vformat(TTR("Element %d: %s%d*"), element_position, array_element_prefix, element_position));
2129 ae.panel->connect("focus_entered", callable_mp((CanvasItem *)ae.panel, &PanelContainer::queue_redraw));
2130 ae.panel->connect("focus_exited", callable_mp((CanvasItem *)ae.panel, &PanelContainer::queue_redraw));
2131 ae.panel->connect("draw", callable_mp(this, &EditorInspectorArray::_panel_draw).bind(i));
2132 ae.panel->connect("gui_input", callable_mp(this, &EditorInspectorArray::_panel_gui_input).bind(i));
2133 ae.panel->add_theme_style_override(SNAME("panel"), i % 2 ? odd_style : even_style);
2134 elements_vbox->add_child(ae.panel);
2135
2136 ae.margin = memnew(MarginContainer);
2137 ae.margin->set_mouse_filter(MOUSE_FILTER_PASS);
2138 if (is_inside_tree()) {
2139 Size2 min_size = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles))->get_minimum_size();
2140 ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
2141 ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
2142 ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
2143 ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
2144 }
2145 ae.panel->add_child(ae.margin);
2146
2147 ae.hbox = memnew(HBoxContainer);
2148 ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL);
2149 ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL);
2150 ae.margin->add_child(ae.hbox);
2151
2152 // Move button.
2153 if (movable) {
2154 VBoxContainer *move_vbox = memnew(VBoxContainer);
2155 move_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
2156 move_vbox->set_alignment(BoxContainer::ALIGNMENT_CENTER);
2157 ae.hbox->add_child(move_vbox);
2158
2159 if (element_position > 0) {
2160 ae.move_up = memnew(Button);
2161 ae.move_up->set_icon(get_editor_theme_icon(SNAME("MoveUp")));
2162 ae.move_up->connect("pressed", callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position - 1));
2163 move_vbox->add_child(ae.move_up);
2164 }
2165
2166 ae.move_texture_rect = memnew(TextureRect);
2167 ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
2168 ae.move_texture_rect->set_default_cursor_shape(Control::CURSOR_MOVE);
2169
2170 if (is_inside_tree()) {
2171 ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar")));
2172 }
2173 move_vbox->add_child(ae.move_texture_rect);
2174
2175 if (element_position < _get_array_count() - 1) {
2176 ae.move_down = memnew(Button);
2177 ae.move_down->set_icon(get_editor_theme_icon(SNAME("MoveDown")));
2178 ae.move_down->connect("pressed", callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position + 2));
2179 move_vbox->add_child(ae.move_down);
2180 }
2181 }
2182
2183 if (numbered) {
2184 ae.number = memnew(Label);
2185 ae.number->add_theme_font_override("font", numbers_font);
2186 ae.number->set_custom_minimum_size(Size2(numbers_min_w, 0));
2187 ae.number->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
2188 ae.number->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
2189 ae.number->set_text(itos(element_position));
2190 ae.hbox->add_child(ae.number);
2191 }
2192
2193 // Right vbox.
2194 ae.vbox = memnew(VBoxContainer);
2195 ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL);
2196 ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL);
2197 ae.hbox->add_child(ae.vbox);
2198
2199 ae.erase = memnew(Button);
2200 ae.erase->set_icon(get_editor_theme_icon(SNAME("Remove")));
2201 ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER);
2202 ae.erase->connect("pressed", callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position));
2203 ae.hbox->add_child(ae.erase);
2204 }
2205
2206 // Hide/show the add button.
2207 add_button->set_visible(page == max_page);
2208
2209 // Add paginator if there's more than 1 page.
2210 if (max_page > 0) {
2211 EditorPaginator *paginator = memnew(EditorPaginator);
2212 paginator->update(page, max_page);
2213 paginator->connect("page_changed", callable_mp(this, &EditorInspectorArray::_paginator_page_changed));
2214 vbox->add_child(paginator);
2215 }
2216}
2217
2218void EditorInspectorArray::_remove_item(int p_index) {
2219 _move_element(p_index, -1);
2220}
2221
2222Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
2223 if (!movable) {
2224 return Variant();
2225 }
2226 int index = p_from->get_meta("index");
2227 Dictionary dict;
2228 dict["type"] = "property_array_element";
2229 dict["property_array_prefix"] = array_element_prefix;
2230 dict["index"] = index;
2231
2232 return dict;
2233}
2234
2235void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
2236 Dictionary dict = p_data;
2237
2238 int to_drop = dict["index"];
2239 int drop_position = _drop_position();
2240 if (drop_position < 0) {
2241 return;
2242 }
2243 _move_element(to_drop, begin_array_index + drop_position);
2244}
2245
2246bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
2247 if (!movable || read_only) {
2248 return false;
2249 }
2250 // First, update drawing.
2251 control_dropping->queue_redraw();
2252
2253 if (p_data.get_type() != Variant::DICTIONARY) {
2254 return false;
2255 }
2256 Dictionary dict = p_data;
2257 int drop_position = _drop_position();
2258 if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) {
2259 return false;
2260 }
2261
2262 // Check in dropping at the given index does indeed move the item.
2263 int moved_array_index = (int)dict["index"];
2264 int drop_array_index = begin_array_index + drop_position;
2265
2266 return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index;
2267}
2268
2269void EditorInspectorArray::_notification(int p_what) {
2270 switch (p_what) {
2271 case NOTIFICATION_ENTER_TREE:
2272 case NOTIFICATION_THEME_CHANGED: {
2273 Color color = get_theme_color(SNAME("dark_color_1"), EditorStringName(Editor));
2274 odd_style->set_bg_color(color.darkened(-0.08));
2275 even_style->set_bg_color(color.darkened(0.08));
2276
2277 for (ArrayElement &ae : array_elements) {
2278 if (ae.move_texture_rect) {
2279 ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar")));
2280 }
2281 if (ae.move_up) {
2282 ae.move_up->set_icon(get_editor_theme_icon(SNAME("MoveUp")));
2283 }
2284 if (ae.move_down) {
2285 ae.move_down->set_icon(get_editor_theme_icon(SNAME("MoveDown")));
2286 }
2287 Size2 min_size = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles))->get_minimum_size();
2288 ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);
2289 ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);
2290 ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);
2291 ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);
2292
2293 if (ae.erase) {
2294 ae.erase->set_icon(get_editor_theme_icon(SNAME("Remove")));
2295 }
2296 }
2297
2298 add_button->set_icon(get_editor_theme_icon(SNAME("Add")));
2299 update_minimum_size();
2300 } break;
2301
2302 case NOTIFICATION_DRAG_BEGIN: {
2303 Dictionary dict = get_viewport()->gui_get_drag_data();
2304 if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) {
2305 dropping = true;
2306 control_dropping->queue_redraw();
2307 }
2308 } break;
2309
2310 case NOTIFICATION_DRAG_END: {
2311 if (dropping) {
2312 dropping = false;
2313 control_dropping->queue_redraw();
2314 }
2315 } break;
2316 }
2317}
2318
2319void EditorInspectorArray::_bind_methods() {
2320 ADD_SIGNAL(MethodInfo("page_change_request"));
2321}
2322
2323void EditorInspectorArray::setup_with_move_element_function(Object *p_object, String p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text) {
2324 count_property = "";
2325 mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION;
2326 array_element_prefix = p_array_element_prefix;
2327 page = p_page;
2328 movable = p_movable;
2329 page_length = p_page_length;
2330 numbered = p_numbered;
2331
2332 EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable, 0);
2333
2334 _setup();
2335}
2336
2337void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) {
2338 count_property = p_count_property;
2339 mode = MODE_USE_COUNT_PROPERTY;
2340 array_element_prefix = p_array_element_prefix;
2341 page = p_page;
2342 movable = p_movable;
2343 page_length = p_page_length;
2344 numbered = p_numbered;
2345 swap_method = p_swap_method;
2346
2347 add_button->set_text(p_add_item_text);
2348 EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable, 0);
2349
2350 _setup();
2351}
2352
2353VBoxContainer *EditorInspectorArray::get_vbox(int p_index) {
2354 if (p_index >= begin_array_index && p_index < end_array_index) {
2355 return array_elements[p_index - begin_array_index].vbox;
2356 } else if (p_index < 0) {
2357 return vbox;
2358 } else {
2359 return nullptr;
2360 }
2361}
2362
2363EditorInspectorArray::EditorInspectorArray(bool p_read_only) {
2364 read_only = p_read_only;
2365
2366 set_mouse_filter(Control::MOUSE_FILTER_STOP);
2367
2368 odd_style.instantiate();
2369 even_style.instantiate();
2370
2371 rmb_popup = memnew(PopupMenu);
2372 rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP);
2373 rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN);
2374 rmb_popup->add_separator();
2375 rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE);
2376 rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER);
2377 rmb_popup->add_separator();
2378 rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE);
2379 rmb_popup->add_separator();
2380 rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY);
2381 rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY);
2382 rmb_popup->connect("id_pressed", callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed));
2383 add_child(rmb_popup);
2384
2385 elements_vbox = memnew(VBoxContainer);
2386 elements_vbox->add_theme_constant_override("separation", 0);
2387 vbox->add_child(elements_vbox);
2388
2389 add_button = EditorInspector::create_inspector_action_button(TTR("Add Element"));
2390 add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed));
2391 add_button->set_disabled(read_only);
2392 vbox->add_child(add_button);
2393
2394 control_dropping = memnew(Control);
2395 control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw));
2396 control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
2397 add_child(control_dropping);
2398
2399 resize_dialog = memnew(AcceptDialog);
2400 resize_dialog->set_title(TTRC("Resize Array"));
2401 resize_dialog->add_cancel_button();
2402 resize_dialog->connect("confirmed", callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed));
2403 add_child(resize_dialog);
2404
2405 VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer);
2406 resize_dialog->add_child(resize_dialog_vbox);
2407
2408 new_size_spin_box = memnew(SpinBox);
2409 new_size_spin_box->set_max(16384);
2410 new_size_spin_box->connect("value_changed", callable_mp(this, &EditorInspectorArray::_new_size_spin_box_value_changed));
2411 new_size_spin_box->get_line_edit()->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_new_size_spin_box_text_submitted));
2412 new_size_spin_box->set_editable(!read_only);
2413 resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_spin_box);
2414
2415 vbox->connect("visibility_changed", callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed));
2416}
2417
2418////////////////////////////////////////////////
2419////////////////////////////////////////////////
2420
2421void EditorPaginator::_first_page_button_pressed() {
2422 emit_signal("page_changed", 0);
2423}
2424
2425void EditorPaginator::_prev_page_button_pressed() {
2426 emit_signal("page_changed", MAX(0, page - 1));
2427}
2428
2429void EditorPaginator::_page_line_edit_text_submitted(String p_text) {
2430 if (p_text.is_valid_int()) {
2431 int new_page = p_text.to_int() - 1;
2432 new_page = MIN(MAX(0, new_page), max_page);
2433 page_line_edit->set_text(Variant(new_page));
2434 emit_signal("page_changed", new_page);
2435 } else {
2436 page_line_edit->set_text(Variant(page));
2437 }
2438}
2439
2440void EditorPaginator::_next_page_button_pressed() {
2441 emit_signal("page_changed", MIN(max_page, page + 1));
2442}
2443
2444void EditorPaginator::_last_page_button_pressed() {
2445 emit_signal("page_changed", max_page);
2446}
2447
2448void EditorPaginator::update(int p_page, int p_max_page) {
2449 page = p_page;
2450 max_page = p_max_page;
2451
2452 // Update buttons.
2453 first_page_button->set_disabled(page == 0);
2454 prev_page_button->set_disabled(page == 0);
2455 next_page_button->set_disabled(page == max_page);
2456 last_page_button->set_disabled(page == max_page);
2457
2458 // Update page number and page count.
2459 page_line_edit->set_text(vformat("%d", page + 1));
2460 page_count_label->set_text(vformat("/ %d", max_page + 1));
2461}
2462
2463void EditorPaginator::_notification(int p_what) {
2464 switch (p_what) {
2465 case NOTIFICATION_ENTER_TREE:
2466 case NOTIFICATION_THEME_CHANGED: {
2467 first_page_button->set_icon(get_editor_theme_icon(SNAME("PageFirst")));
2468 prev_page_button->set_icon(get_editor_theme_icon(SNAME("PagePrevious")));
2469 next_page_button->set_icon(get_editor_theme_icon(SNAME("PageNext")));
2470 last_page_button->set_icon(get_editor_theme_icon(SNAME("PageLast")));
2471 } break;
2472 }
2473}
2474
2475void EditorPaginator::_bind_methods() {
2476 ADD_SIGNAL(MethodInfo("page_changed", PropertyInfo(Variant::INT, "page")));
2477}
2478
2479EditorPaginator::EditorPaginator() {
2480 set_h_size_flags(SIZE_EXPAND_FILL);
2481 set_alignment(ALIGNMENT_CENTER);
2482
2483 first_page_button = memnew(Button);
2484 first_page_button->set_flat(true);
2485 first_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_first_page_button_pressed));
2486 add_child(first_page_button);
2487
2488 prev_page_button = memnew(Button);
2489 prev_page_button->set_flat(true);
2490 prev_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_prev_page_button_pressed));
2491 add_child(prev_page_button);
2492
2493 page_line_edit = memnew(LineEdit);
2494 page_line_edit->connect("text_submitted", callable_mp(this, &EditorPaginator::_page_line_edit_text_submitted));
2495 page_line_edit->add_theme_constant_override("minimum_character_width", 2);
2496 add_child(page_line_edit);
2497
2498 page_count_label = memnew(Label);
2499 add_child(page_count_label);
2500
2501 next_page_button = memnew(Button);
2502 next_page_button->set_flat(true);
2503 next_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_next_page_button_pressed));
2504 add_child(next_page_button);
2505
2506 last_page_button = memnew(Button);
2507 last_page_button->set_flat(true);
2508 last_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_last_page_button_pressed));
2509 add_child(last_page_button);
2510}
2511
2512////////////////////////////////////////////////
2513////////////////////////////////////////////////
2514
2515Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS];
2516int EditorInspector::inspector_plugin_count = 0;
2517
2518EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {
2519 for (int i = inspector_plugin_count - 1; i >= 0; i--) {
2520 inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);
2521 if (inspector_plugins[i]->added_editors.size()) {
2522 for (int j = 1; j < inspector_plugins[i]->added_editors.size(); j++) { //only keep first one
2523 memdelete(inspector_plugins[i]->added_editors[j].property_editor);
2524 }
2525
2526 EditorProperty *prop = Object::cast_to<EditorProperty>(inspector_plugins[i]->added_editors[0].property_editor);
2527 if (prop) {
2528 inspector_plugins[i]->added_editors.clear();
2529 return prop;
2530 } else {
2531 memdelete(inspector_plugins[i]->added_editors[0].property_editor);
2532 inspector_plugins[i]->added_editors.clear();
2533 }
2534 }
2535 }
2536 return nullptr;
2537}
2538
2539void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {
2540 ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS);
2541
2542 for (int i = 0; i < inspector_plugin_count; i++) {
2543 if (inspector_plugins[i] == p_plugin) {
2544 return; //already exists
2545 }
2546 }
2547 inspector_plugins[inspector_plugin_count++] = p_plugin;
2548}
2549
2550void EditorInspector::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {
2551 ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS);
2552
2553 int idx = -1;
2554 for (int i = 0; i < inspector_plugin_count; i++) {
2555 if (inspector_plugins[i] == p_plugin) {
2556 idx = i;
2557 break;
2558 }
2559 }
2560
2561 ERR_FAIL_COND_MSG(idx == -1, "Trying to remove nonexistent inspector plugin.");
2562 for (int i = idx; i < inspector_plugin_count - 1; i++) {
2563 inspector_plugins[i] = inspector_plugins[i + 1];
2564 }
2565 inspector_plugins[inspector_plugin_count - 1] = Ref<EditorInspectorPlugin>();
2566
2567 inspector_plugin_count--;
2568}
2569
2570void EditorInspector::cleanup_plugins() {
2571 for (int i = 0; i < inspector_plugin_count; i++) {
2572 inspector_plugins[i].unref();
2573 }
2574 inspector_plugin_count = 0;
2575}
2576
2577Button *EditorInspector::create_inspector_action_button(const String &p_text) {
2578 Button *button = memnew(Button);
2579 button->set_text(p_text);
2580 button->set_theme_type_variation(SNAME("InspectorActionButton"));
2581 button->set_h_size_flags(SIZE_SHRINK_CENTER);
2582 return button;
2583}
2584
2585bool EditorInspector::is_main_editor_inspector() const {
2586 return InspectorDock::get_singleton() && InspectorDock::get_inspector_singleton() == this;
2587}
2588
2589String EditorInspector::get_selected_path() const {
2590 return property_selected;
2591}
2592
2593void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped) {
2594 for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
2595 EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);
2596 current_vbox->add_child(F.property_editor);
2597
2598 if (ep) {
2599 ep->object = object;
2600 ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed).bind(false));
2601 ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
2602 ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
2603 ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
2604 ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
2605 ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
2606 ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
2607 ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
2608 ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
2609 ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
2610
2611 if (F.properties.size()) {
2612 if (F.properties.size() == 1) {
2613 //since it's one, associate:
2614 ep->property = F.properties[0];
2615 ep->property_path = property_prefix + F.properties[0];
2616 ep->property_usage = 0;
2617 }
2618
2619 if (!F.label.is_empty()) {
2620 ep->set_label(F.label);
2621 }
2622
2623 for (int i = 0; i < F.properties.size(); i++) {
2624 String prop = F.properties[i];
2625
2626 if (!editor_property_map.has(prop)) {
2627 editor_property_map[prop] = List<EditorProperty *>();
2628 }
2629 editor_property_map[prop].push_back(ep);
2630 }
2631 }
2632
2633 if (p_section) {
2634 ep->connect("property_can_revert_changed", callable_mp(p_section, &EditorInspectorSection::property_can_revert_changed));
2635 }
2636
2637 ep->set_read_only(read_only);
2638 ep->update_property();
2639 ep->_update_pin_flags();
2640 ep->update_editor_property_status();
2641 ep->set_deletable(deletable_properties);
2642 ep->update_cache();
2643 }
2644 }
2645 ped->added_editors.clear();
2646}
2647
2648bool EditorInspector::_is_property_disabled_by_feature_profile(const StringName &p_property) {
2649 Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
2650 if (profile.is_null()) {
2651 return false;
2652 }
2653
2654 StringName class_name = object->get_class();
2655
2656 while (class_name != StringName()) {
2657 if (profile->is_class_property_disabled(class_name, p_property)) {
2658 return true;
2659 }
2660 if (profile->is_class_disabled(class_name)) {
2661 //won't see properties of a disabled class
2662 return true;
2663 }
2664 class_name = ClassDB::get_parent_class(class_name);
2665 }
2666
2667 return false;
2668}
2669
2670void EditorInspector::update_tree() {
2671 // Store currently selected and focused elements to restore after the update.
2672 // TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit.
2673 StringName current_selected = property_selected;
2674 int current_focusable = -1;
2675
2676 if (property_focusable != -1) {
2677 // Check that focusable is actually focusable.
2678 bool restore_focus = false;
2679 Control *focused = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
2680 if (focused) {
2681 Node *parent = focused->get_parent();
2682 while (parent) {
2683 EditorInspector *inspector = Object::cast_to<EditorInspector>(parent);
2684 if (inspector) {
2685 restore_focus = inspector == this; // May be owned by another inspector.
2686 break; // Exit after the first inspector is found, since there may be nested ones.
2687 }
2688 parent = parent->get_parent();
2689 }
2690 }
2691
2692 if (restore_focus) {
2693 current_focusable = property_focusable;
2694 }
2695 }
2696
2697 // Only hide plugins if we are not editing any object.
2698 // This should be handled outside of the update_tree call anyway (see EditorInspector::edit), but might as well keep it safe.
2699 _clear(!object);
2700
2701 if (!object) {
2702 return;
2703 }
2704
2705 List<Ref<EditorInspectorPlugin>> valid_plugins;
2706
2707 for (int i = inspector_plugin_count - 1; i >= 0; i--) { //start by last, so lastly added can override newly added
2708 if (!inspector_plugins[i]->can_handle(object)) {
2709 continue;
2710 }
2711 valid_plugins.push_back(inspector_plugins[i]);
2712 }
2713
2714 // Decide if properties should be drawn with the warning color (yellow),
2715 // or if the whole object should be considered read-only.
2716 bool draw_warning = false;
2717 bool all_read_only = false;
2718 if (is_inside_tree()) {
2719 if (object->has_method("_is_read_only")) {
2720 all_read_only = object->call("_is_read_only");
2721 }
2722
2723 Node *nod = Object::cast_to<Node>(object);
2724 Node *es = EditorNode::get_singleton()->get_edited_scene();
2725 if (nod && es != nod && nod->get_owner() != es) {
2726 // Draw in warning color edited nodes that are not in the currently edited scene,
2727 // as changes may be lost in the future.
2728 draw_warning = true;
2729 } else {
2730 if (!all_read_only) {
2731 Resource *res = Object::cast_to<Resource>(object);
2732 if (res) {
2733 all_read_only = EditorNode::get_singleton()->is_resource_read_only(res);
2734 }
2735 }
2736 }
2737 }
2738
2739 String filter = search_box ? search_box->get_text() : "";
2740 String group;
2741 String group_base;
2742 String subgroup;
2743 String subgroup_base;
2744 int section_depth = 0;
2745 VBoxContainer *category_vbox = nullptr;
2746
2747 List<PropertyInfo> plist;
2748 object->get_property_list(&plist, true);
2749
2750 HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
2751 HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix;
2752
2753 Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
2754
2755 // Get the lists of editors to add the beginning.
2756 for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
2757 ped->parse_begin(object);
2758 _parse_added_editors(main_vbox, nullptr, ped);
2759 }
2760
2761 StringName doc_name;
2762
2763 // Get the lists of editors for properties.
2764 for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) {
2765 PropertyInfo &p = E_property->get();
2766
2767 if (p.usage & PROPERTY_USAGE_SUBGROUP) {
2768 // Setup a property sub-group.
2769 subgroup = p.name;
2770
2771 Vector<String> hint_parts = p.hint_string.split(",");
2772 subgroup_base = hint_parts[0];
2773 if (hint_parts.size() > 1) {
2774 section_depth = hint_parts[1].to_int();
2775 } else {
2776 section_depth = 0;
2777 }
2778
2779 continue;
2780
2781 } else if (p.usage & PROPERTY_USAGE_GROUP) {
2782 // Setup a property group.
2783 group = p.name;
2784
2785 Vector<String> hint_parts = p.hint_string.split(",");
2786 group_base = hint_parts[0];
2787 if (hint_parts.size() > 1) {
2788 section_depth = hint_parts[1].to_int();
2789 } else {
2790 section_depth = 0;
2791 }
2792
2793 subgroup = "";
2794 subgroup_base = "";
2795
2796 continue;
2797
2798 } else if (p.usage & PROPERTY_USAGE_CATEGORY) {
2799 // Setup a property category.
2800 group = "";
2801 group_base = "";
2802 subgroup = "";
2803 subgroup_base = "";
2804 section_depth = 0;
2805
2806 if (!show_categories) {
2807 continue;
2808 }
2809
2810 // Hide the "MultiNodeEdit" category for MultiNodeEdit.
2811 if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") {
2812 continue;
2813 }
2814
2815 // Iterate over remaining properties. If no properties in category, skip the category.
2816 List<PropertyInfo>::Element *N = E_property->next();
2817 bool valid = true;
2818 while (N) {
2819 if (!N->get().name.begins_with("metadata/_") && N->get().usage & PROPERTY_USAGE_EDITOR &&
2820 (!filter.is_empty() || !restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
2821 break;
2822 }
2823 if (N->get().usage & PROPERTY_USAGE_CATEGORY) {
2824 valid = false;
2825 break;
2826 }
2827 N = N->next();
2828 }
2829 if (!valid) {
2830 continue; // Empty, ignore it.
2831 }
2832
2833 // Create an EditorInspectorCategory and add it to the inspector.
2834 EditorInspectorCategory *category = memnew(EditorInspectorCategory);
2835 main_vbox->add_child(category);
2836 category_vbox = nullptr; //reset
2837
2838 // `hint_script` should contain a native class name or a script path.
2839 // Otherwise the category was probably added via `@export_category` or `_get_property_list()`.
2840 if (p.hint_string.is_empty()) {
2841 category->label = p.name;
2842 category->set_tooltip_text(p.name);
2843 continue; // Do not add an icon, do not change the current class (`doc_name`).
2844 }
2845
2846 String type = p.name;
2847 String label = p.name;
2848 doc_name = p.name;
2849
2850 // Use category's owner script to update some of its information.
2851 if (!EditorNode::get_editor_data().is_type_recognized(type) && p.hint_string.length() && ResourceLoader::exists(p.hint_string)) {
2852 Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");
2853 if (scr.is_valid()) {
2854 StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());
2855
2856 // Update the docs reference and the label based on the script.
2857 Vector<DocData::ClassDoc> docs = scr->get_documentation();
2858 if (!docs.is_empty()) {
2859 // The documentation of a GDScript's main class is at the end of the array.
2860 // Hacky because this isn't necessarily always guaranteed.
2861 doc_name = docs[docs.size() - 1].name;
2862 }
2863 if (script_name != StringName()) {
2864 label = script_name;
2865 }
2866
2867 // Find the icon corresponding to the script.
2868 if (script_name != StringName()) {
2869 category->icon = EditorNode::get_singleton()->get_class_icon(script_name);
2870 } else {
2871 category->icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
2872 }
2873 }
2874 }
2875
2876 if (category->icon.is_null() && !type.is_empty()) {
2877 category->icon = EditorNode::get_singleton()->get_class_icon(type);
2878 }
2879
2880 // Set the category label.
2881 category->label = label;
2882 category->doc_class_name = doc_name;
2883
2884 if (use_doc_hints) {
2885 String descr = "";
2886 // Sets the category tooltip to show documentation.
2887 if (!class_descr_cache.has(doc_name)) {
2888 DocTools *dd = EditorHelp::get_doc_data();
2889 HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
2890 if (E) {
2891 descr = E->value.brief_description;
2892 }
2893 if (ClassDB::class_exists(doc_name)) {
2894 descr = DTR(descr); // Do not translate the class description of scripts.
2895 class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts.
2896 }
2897 } else {
2898 descr = class_descr_cache[doc_name];
2899 }
2900
2901 // `|` separator used in `make_help_bit()` for formatting.
2902 category->set_tooltip_text(p.name + "|" + descr);
2903 }
2904
2905 // Add editors at the start of a category.
2906 for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
2907 ped->parse_category(object, p.name);
2908 _parse_added_editors(main_vbox, nullptr, ped);
2909 }
2910
2911 continue;
2912
2913 } else if (p.name.begins_with("metadata/_") || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) ||
2914 (filter.is_empty() && restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {
2915 // Ignore properties that are not supposed to be in the inspector.
2916 continue;
2917 }
2918
2919 if (p.name == "script") {
2920 // Script should go into its own category.
2921 category_vbox = nullptr;
2922 }
2923
2924 if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) {
2925 // Do not show this property in low end gfx.
2926 continue;
2927 }
2928
2929 if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) {
2930 // Hide script variables from inspector if required.
2931 continue;
2932 }
2933
2934 if (p.name.begins_with("metadata/") && bool(object->call("_hide_metadata_from_inspector"))) {
2935 // Hide metadata from inspector if required.
2936 continue;
2937 }
2938
2939 // Get the path for property.
2940 String path = p.name;
2941
2942 // First check if we have an array that fits the prefix.
2943 String array_prefix = "";
2944 int array_index = -1;
2945 for (KeyValue<String, EditorInspectorArray *> &E : editor_inspector_array_per_prefix) {
2946 if (p.name.begins_with(E.key) && E.key.length() > array_prefix.length()) {
2947 array_prefix = E.key;
2948 }
2949 }
2950
2951 if (!array_prefix.is_empty()) {
2952 // If we have an array element, find the according index in array.
2953 String str = p.name.trim_prefix(array_prefix);
2954 int to_char_index = 0;
2955 while (to_char_index < str.length()) {
2956 if (!is_digit(str[to_char_index])) {
2957 break;
2958 }
2959 to_char_index++;
2960 }
2961 if (to_char_index > 0) {
2962 array_index = str.left(to_char_index).to_int();
2963 } else {
2964 array_prefix = "";
2965 }
2966 }
2967
2968 if (!array_prefix.is_empty()) {
2969 path = path.trim_prefix(array_prefix);
2970 int char_index = path.find("/");
2971 if (char_index >= 0) {
2972 path = path.right(-char_index - 1);
2973 } else {
2974 path = vformat(TTR("Element %s"), array_index);
2975 }
2976 } else {
2977 // Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string.
2978 if (!subgroup.is_empty() && !subgroup_base.is_empty()) {
2979 if (path.begins_with(subgroup_base)) {
2980 path = path.trim_prefix(subgroup_base);
2981 } else if (subgroup_base.begins_with(path)) {
2982 // Keep it, this is used pretty often.
2983 } else {
2984 subgroup = ""; // The prefix changed, we are no longer in the subgroup.
2985 }
2986 }
2987
2988 // Check if we exit or not a group. If there is a prefix, remove it from the property label string.
2989 if (!group.is_empty() && !group_base.is_empty() && subgroup.is_empty()) {
2990 if (path.begins_with(group_base)) {
2991 path = path.trim_prefix(group_base);
2992 } else if (group_base.begins_with(path)) {
2993 // Keep it, this is used pretty often.
2994 } else {
2995 group = ""; // The prefix changed, we are no longer in the group.
2996 subgroup = "";
2997 }
2998 }
2999
3000 // Add the group and subgroup to the path.
3001 if (!subgroup.is_empty()) {
3002 path = subgroup + "/" + path;
3003 }
3004 if (!group.is_empty()) {
3005 path = group + "/" + path;
3006 }
3007 }
3008
3009 // Get the property label's string.
3010 String name_override = (path.contains("/")) ? path.substr(path.rfind("/") + 1) : path;
3011 String feature_tag;
3012 {
3013 const int dot = name_override.find(".");
3014 if (dot != -1) {
3015 feature_tag = name_override.substr(dot);
3016 name_override = name_override.substr(0, dot);
3017 }
3018 }
3019
3020 // Don't localize script variables.
3021 EditorPropertyNameProcessor::Style name_style = property_name_style;
3022 if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {
3023 name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED;
3024 }
3025 const String property_label_string = EditorPropertyNameProcessor::get_singleton()->process_name(name_override, name_style) + feature_tag;
3026
3027 // Remove the property from the path.
3028 int idx = path.rfind("/");
3029 if (idx > -1) {
3030 path = path.left(idx);
3031 } else {
3032 path = "";
3033 }
3034
3035 // Ignore properties that do not fit the filter.
3036 if (use_filter && !filter.is_empty()) {
3037 const String property_path = property_prefix + (path.is_empty() ? "" : path + "/") + name_override;
3038 if (!_property_path_matches(property_path, filter, property_name_style)) {
3039 continue;
3040 }
3041 }
3042
3043 // Recreate the category vbox if it was reset.
3044 if (category_vbox == nullptr) {
3045 category_vbox = memnew(VBoxContainer);
3046 main_vbox->add_child(category_vbox);
3047 }
3048
3049 // Find the correct section/vbox to add the property editor to.
3050 VBoxContainer *root_vbox = array_prefix.is_empty() ? main_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index);
3051 if (!root_vbox) {
3052 continue;
3053 }
3054
3055 if (!vbox_per_path.has(root_vbox)) {
3056 vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>();
3057 vbox_per_path[root_vbox][""] = root_vbox;
3058 }
3059
3060 VBoxContainer *current_vbox = root_vbox;
3061 String acc_path = "";
3062 int level = 1;
3063
3064 Vector<String> components = path.split("/");
3065 for (int i = 0; i < components.size(); i++) {
3066 String component = components[i];
3067 acc_path += (i > 0) ? "/" + component : component;
3068
3069 if (!vbox_per_path[root_vbox].has(acc_path)) {
3070 // If the section does not exists, create it.
3071 EditorInspectorSection *section = memnew(EditorInspectorSection);
3072 current_vbox->add_child(section);
3073 sections.push_back(section);
3074
3075 String label;
3076 String tooltip;
3077
3078 // Don't localize groups for script variables.
3079 EditorPropertyNameProcessor::Style section_name_style = property_name_style;
3080 if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {
3081 section_name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED;
3082 }
3083
3084 // Only process group label if this is not the group or subgroup.
3085 if ((i == 0 && component == group) || (i == 1 && component == subgroup)) {
3086 if (section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {
3087 label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component);
3088 tooltip = component;
3089 } else {
3090 label = component;
3091 tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component);
3092 }
3093 } else {
3094 label = EditorPropertyNameProcessor::get_singleton()->process_name(component, section_name_style);
3095 tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(component, EditorPropertyNameProcessor::get_tooltip_style(section_name_style));
3096 }
3097
3098 Color c = sscolor;
3099 c.a /= level;
3100 section->setup(acc_path, label, object, c, use_folding, section_depth);
3101 section->set_tooltip_text(tooltip);
3102
3103 // Add editors at the start of a group.
3104 for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
3105 ped->parse_group(object, path);
3106 _parse_added_editors(section->get_vbox(), section, ped);
3107 }
3108
3109 vbox_per_path[root_vbox][acc_path] = section->get_vbox();
3110 }
3111
3112 current_vbox = vbox_per_path[root_vbox][acc_path];
3113 level = (MIN(level + 1, 4));
3114 }
3115
3116 // If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly).
3117 if (current_vbox == main_vbox) {
3118 current_vbox = category_vbox;
3119 }
3120
3121 // Check if the property is an array counter, if so create a dedicated array editor for the array.
3122 if (p.usage & PROPERTY_USAGE_ARRAY) {
3123 EditorInspectorArray *editor_inspector_array = nullptr;
3124 StringName array_element_prefix;
3125 Color c = sscolor;
3126 c.a /= level;
3127
3128 Vector<String> class_name_components = String(p.class_name).split(",");
3129
3130 int page_size = 5;
3131 bool movable = true;
3132 bool numbered = false;
3133 bool foldable = use_folding;
3134 String add_button_text = TTR("Add Element");
3135 String swap_method;
3136 for (int i = (p.type == Variant::NIL ? 1 : 2); i < class_name_components.size(); i++) {
3137 if (class_name_components[i].begins_with("page_size") && class_name_components[i].get_slice_count("=") == 2) {
3138 page_size = class_name_components[i].get_slice("=", 1).to_int();
3139 } else if (class_name_components[i].begins_with("add_button_text") && class_name_components[i].get_slice_count("=") == 2) {
3140 add_button_text = class_name_components[i].get_slice("=", 1).strip_edges();
3141 } else if (class_name_components[i] == "static") {
3142 movable = false;
3143 } else if (class_name_components[i] == "numbered") {
3144 numbered = true;
3145 } else if (class_name_components[i] == "unfoldable") {
3146 foldable = false;
3147 } else if (class_name_components[i].begins_with("swap_method") && class_name_components[i].get_slice_count("=") == 2) {
3148 swap_method = class_name_components[i].get_slice("=", 1).strip_edges();
3149 }
3150 }
3151
3152 if (p.type == Variant::NIL) {
3153 // Setup the array to use a method to create/move/delete elements.
3154 array_element_prefix = class_name_components[0];
3155 editor_inspector_array = memnew(EditorInspectorArray(all_read_only));
3156
3157 String array_label = path.contains("/") ? path.substr(path.rfind("/") + 1) : path;
3158 array_label = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string, property_name_style);
3159 int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
3160 editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding);
3161 editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix));
3162 } else if (p.type == Variant::INT) {
3163 // Setup the array to use the count property and built-in functions to create/move/delete elements.
3164 if (class_name_components.size() >= 2) {
3165 array_element_prefix = class_name_components[1];
3166 editor_inspector_array = memnew(EditorInspectorArray(all_read_only));
3167 int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
3168
3169 editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, numbered, page_size, add_button_text, swap_method);
3170 editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix));
3171 }
3172 }
3173
3174 if (editor_inspector_array) {
3175 current_vbox->add_child(editor_inspector_array);
3176 editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array;
3177 }
3178
3179 continue;
3180 }
3181
3182 // Checkable and checked properties.
3183 bool checkable = false;
3184 bool checked = false;
3185 if (p.usage & PROPERTY_USAGE_CHECKABLE) {
3186 checkable = true;
3187 checked = p.usage & PROPERTY_USAGE_CHECKED;
3188 }
3189
3190 bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only;
3191
3192 // Mark properties that would require an editor restart (mostly when editing editor settings).
3193 if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) {
3194 restart_request_props.insert(p.name);
3195 }
3196
3197 PropertyDocInfo doc_info;
3198
3199 if (use_doc_hints) {
3200 // Build the doc hint, to use as tooltip.
3201
3202 // Get the class name.
3203 StringName classname = doc_name;
3204 if (!object_class.is_empty()) {
3205 classname = object_class;
3206 } else if (Object::cast_to<MultiNodeEdit>(object)) {
3207 classname = Object::cast_to<MultiNodeEdit>(object)->get_edited_class_name();
3208 } else if (classname == "") {
3209 classname = object->get_class_name();
3210 Resource *res = Object::cast_to<Resource>(object);
3211 if (res && !res->get_script().is_null()) {
3212 // Grab the script of this resource to get the evaluated script class.
3213 Ref<Script> scr = res->get_script();
3214 if (scr.is_valid()) {
3215 Vector<DocData::ClassDoc> docs = scr->get_documentation();
3216 if (!docs.is_empty()) {
3217 // The documentation of a GDScript's main class is at the end of the array.
3218 // Hacky because this isn't necessarily always guaranteed.
3219 classname = docs[docs.size() - 1].name;
3220 }
3221 }
3222 }
3223 }
3224
3225 StringName propname = property_prefix + p.name;
3226 bool found = false;
3227
3228 // Small hack for theme_overrides. They are listed under Control, but come from another class.
3229 if (classname == "Control" && p.name.begins_with("theme_override_")) {
3230 classname = get_edited_object()->get_class();
3231 }
3232
3233 // Search for the property description in the cache.
3234 HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname);
3235 if (E) {
3236 HashMap<StringName, PropertyDocInfo>::Iterator F = E->value.find(propname);
3237 if (F) {
3238 found = true;
3239 doc_info = F->value;
3240 }
3241 }
3242
3243 if (!found) {
3244 bool is_native_class = ClassDB::class_exists(classname);
3245
3246 // Build the property description String and add it to the cache.
3247 DocTools *dd = EditorHelp::get_doc_data();
3248 HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);
3249 while (F && doc_info.description.is_empty()) {
3250 for (int i = 0; i < F->value.properties.size(); i++) {
3251 if (F->value.properties[i].name == propname.operator String()) {
3252 doc_info.description = F->value.properties[i].description;
3253 if (is_native_class) {
3254 doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts.
3255 }
3256
3257 const Vector<String> class_enum = F->value.properties[i].enumeration.split(".");
3258 const String class_name = class_enum[0];
3259 const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
3260 if (!enum_name.is_empty()) {
3261 HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_name);
3262 if (enum_class) {
3263 for (DocData::ConstantDoc val : enum_class->value.constants) {
3264 // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
3265 if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
3266 const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
3267 // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
3268 String desc = val.description;
3269 if (is_native_class) {
3270 desc = DTR(desc); // Do not translate the enum value description of scripts.
3271 }
3272 desc = desc.trim_prefix("\n");
3273 doc_info.description += vformat(
3274 "\n[b]%s:[/b] %s",
3275 enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
3276 desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc);
3277 }
3278 }
3279 }
3280 }
3281
3282 doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
3283 break;
3284 }
3285 }
3286
3287 Vector<String> slices = propname.operator String().split("/");
3288 if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
3289 for (int i = 0; i < F->value.theme_properties.size(); i++) {
3290 if (F->value.theme_properties[i].name == slices[1]) {
3291 doc_info.description = F->value.theme_properties[i].description;
3292 if (is_native_class) {
3293 doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts.
3294 }
3295 doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
3296 break;
3297 }
3298 }
3299 }
3300
3301 if (!F->value.inherits.is_empty()) {
3302 F = dd->class_list.find(F->value.inherits);
3303 } else {
3304 break;
3305 }
3306 }
3307
3308 if (is_native_class) {
3309 doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
3310 }
3311 }
3312 }
3313
3314 Vector<EditorInspectorPlugin::AddedEditor> editors;
3315 Vector<EditorInspectorPlugin::AddedEditor> late_editors;
3316
3317 // Search for the inspector plugin that will handle the properties. Then add the correct property editor to it.
3318 for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
3319 bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors);
3320
3321 for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
3322 if (F.add_to_end) {
3323 late_editors.push_back(F);
3324 } else {
3325 editors.push_back(F);
3326 }
3327 }
3328
3329 ped->added_editors.clear();
3330
3331 if (exclusive) {
3332 break;
3333 }
3334 }
3335
3336 editors.append_array(late_editors);
3337
3338 for (int i = 0; i < editors.size(); i++) {
3339 EditorProperty *ep = Object::cast_to<EditorProperty>(editors[i].property_editor);
3340 const Vector<String> &properties = editors[i].properties;
3341
3342 if (ep) {
3343 // Set all this before the control gets the ENTER_TREE notification.
3344 ep->object = object;
3345
3346 if (properties.size()) {
3347 if (properties.size() == 1) {
3348 //since it's one, associate:
3349 ep->property = properties[0];
3350 ep->property_path = property_prefix + properties[0];
3351 ep->property_usage = p.usage;
3352 //and set label?
3353 }
3354 if (!editors[i].label.is_empty()) {
3355 ep->set_label(editors[i].label);
3356 } else {
3357 // Use the existing one.
3358 ep->set_label(property_label_string);
3359 }
3360
3361 for (int j = 0; j < properties.size(); j++) {
3362 String prop = properties[j];
3363
3364 if (!editor_property_map.has(prop)) {
3365 editor_property_map[prop] = List<EditorProperty *>();
3366 }
3367 editor_property_map[prop].push_back(ep);
3368 }
3369 }
3370
3371 EditorInspectorSection *section = Object::cast_to<EditorInspectorSection>(current_vbox->get_parent());
3372 if (section) {
3373 ep->connect("property_can_revert_changed", callable_mp(section, &EditorInspectorSection::property_can_revert_changed));
3374 }
3375
3376 ep->set_draw_warning(draw_warning);
3377 ep->set_use_folding(use_folding);
3378 ep->set_checkable(checkable);
3379 ep->set_checked(checked);
3380 ep->set_keying(keying);
3381 ep->set_read_only(property_read_only || all_read_only);
3382 ep->set_deletable(deletable_properties || p.name.begins_with("metadata/"));
3383 }
3384
3385 current_vbox->add_child(editors[i].property_editor);
3386
3387 if (ep) {
3388 // Eventually, set other properties/signals after the property editor got added to the tree.
3389 bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED);
3390 ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed).bind(update_all));
3391 ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
3392 ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
3393 ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
3394 ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
3395 ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
3396 ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
3397 ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
3398 ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
3399 ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
3400 // `|` separator used in `make_help_bit()` for formatting.
3401 ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description);
3402 ep->set_doc_path(doc_info.path);
3403 ep->update_property();
3404 ep->_update_pin_flags();
3405 ep->update_editor_property_status();
3406 ep->update_cache();
3407
3408 if (current_selected && ep->property == current_selected) {
3409 ep->select(current_focusable);
3410 }
3411 }
3412 }
3413 }
3414
3415 if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {
3416 // Add 4px of spacing between the "Add Metadata" button and the content above it.
3417 Control *spacer = memnew(Control);
3418 spacer->set_custom_minimum_size(Size2(0, 4) * EDSCALE);
3419 main_vbox->add_child(spacer);
3420
3421 Button *add_md = EditorInspector::create_inspector_action_button(TTR("Add Metadata"));
3422 add_md->set_icon(get_editor_theme_icon(SNAME("Add")));
3423 add_md->connect(SNAME("pressed"), callable_mp(this, &EditorInspector::_show_add_meta_dialog));
3424 main_vbox->add_child(add_md);
3425 if (all_read_only) {
3426 add_md->set_disabled(true);
3427 }
3428 }
3429
3430 // Get the lists of to add at the end.
3431 for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
3432 ped->parse_end(object);
3433 _parse_added_editors(main_vbox, nullptr, ped);
3434 }
3435
3436 if (is_main_editor_inspector()) {
3437 // Updating inspector might invalidate some editing owners.
3438 EditorNode::get_singleton()->hide_unused_editors();
3439 }
3440}
3441
3442void EditorInspector::update_property(const String &p_prop) {
3443 if (!editor_property_map.has(p_prop)) {
3444 return;
3445 }
3446
3447 for (EditorProperty *E : editor_property_map[p_prop]) {
3448 E->update_property();
3449 E->update_editor_property_status();
3450 E->update_cache();
3451 }
3452}
3453
3454void EditorInspector::_clear(bool p_hide_plugins) {
3455 while (main_vbox->get_child_count()) {
3456 memdelete(main_vbox->get_child(0));
3457 }
3458
3459 property_selected = StringName();
3460 property_focusable = -1;
3461 editor_property_map.clear();
3462 sections.clear();
3463 pending.clear();
3464 restart_request_props.clear();
3465
3466 if (p_hide_plugins && is_main_editor_inspector()) {
3467 EditorNode::get_singleton()->hide_unused_editors(this);
3468 }
3469}
3470
3471Object *EditorInspector::get_edited_object() {
3472 return object;
3473}
3474
3475void EditorInspector::edit(Object *p_object) {
3476 if (object == p_object) {
3477 return;
3478 }
3479
3480 if (object) {
3481 _clear();
3482 object->disconnect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
3483 }
3484 per_array_page.clear();
3485
3486 object = p_object;
3487
3488 if (object) {
3489 update_scroll_request = 0; //reset
3490 if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else
3491 update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accommodated
3492 }
3493 object->connect("property_list_changed", callable_mp(this, &EditorInspector::_changed_callback));
3494 update_tree();
3495 }
3496 emit_signal(SNAME("edited_object_changed"));
3497}
3498
3499void EditorInspector::set_keying(bool p_active) {
3500 if (keying == p_active) {
3501 return;
3502 }
3503 keying = p_active;
3504 _keying_changed();
3505}
3506
3507void EditorInspector::_keying_changed() {
3508 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
3509 for (EditorProperty *E : F.value) {
3510 if (E) {
3511 E->set_keying(keying);
3512 }
3513 }
3514 }
3515}
3516
3517void EditorInspector::set_read_only(bool p_read_only) {
3518 if (p_read_only == read_only) {
3519 return;
3520 }
3521 read_only = p_read_only;
3522 update_tree();
3523}
3524
3525EditorPropertyNameProcessor::Style EditorInspector::get_property_name_style() const {
3526 return property_name_style;
3527}
3528
3529void EditorInspector::set_property_name_style(EditorPropertyNameProcessor::Style p_style) {
3530 if (property_name_style == p_style) {
3531 return;
3532 }
3533 property_name_style = p_style;
3534 update_tree();
3535}
3536
3537void EditorInspector::set_use_settings_name_style(bool p_enable) {
3538 if (use_settings_name_style == p_enable) {
3539 return;
3540 }
3541 use_settings_name_style = p_enable;
3542 if (use_settings_name_style) {
3543 set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style());
3544 }
3545}
3546
3547void EditorInspector::set_autoclear(bool p_enable) {
3548 autoclear = p_enable;
3549}
3550
3551void EditorInspector::set_show_categories(bool p_show) {
3552 show_categories = p_show;
3553 update_tree();
3554}
3555
3556void EditorInspector::set_use_doc_hints(bool p_enable) {
3557 use_doc_hints = p_enable;
3558 update_tree();
3559}
3560
3561void EditorInspector::set_hide_script(bool p_hide) {
3562 hide_script = p_hide;
3563 update_tree();
3564}
3565
3566void EditorInspector::set_hide_metadata(bool p_hide) {
3567 hide_metadata = p_hide;
3568 update_tree();
3569}
3570
3571void EditorInspector::set_use_filter(bool p_use) {
3572 use_filter = p_use;
3573 update_tree();
3574}
3575
3576void EditorInspector::register_text_enter(Node *p_line_edit) {
3577 search_box = Object::cast_to<LineEdit>(p_line_edit);
3578 if (search_box) {
3579 search_box->connect("text_changed", callable_mp(this, &EditorInspector::_filter_changed));
3580 }
3581}
3582
3583void EditorInspector::_filter_changed(const String &p_text) {
3584 update_tree();
3585}
3586
3587void EditorInspector::set_use_folding(bool p_use_folding, bool p_update_tree) {
3588 use_folding = p_use_folding;
3589
3590 if (p_update_tree) {
3591 update_tree();
3592 }
3593}
3594
3595bool EditorInspector::is_using_folding() {
3596 return use_folding;
3597}
3598
3599void EditorInspector::collapse_all_folding() {
3600 for (EditorInspectorSection *E : sections) {
3601 E->fold();
3602 }
3603
3604 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
3605 for (EditorProperty *E : F.value) {
3606 E->collapse_all_folding();
3607 }
3608 }
3609}
3610
3611void EditorInspector::expand_all_folding() {
3612 for (EditorInspectorSection *E : sections) {
3613 E->unfold();
3614 }
3615 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
3616 for (EditorProperty *E : F.value) {
3617 E->expand_all_folding();
3618 }
3619 }
3620}
3621
3622void EditorInspector::expand_revertable() {
3623 HashSet<EditorInspectorSection *> sections_to_unfold[2];
3624 for (EditorInspectorSection *E : sections) {
3625 if (E->has_revertable_properties()) {
3626 sections_to_unfold[0].insert(E);
3627 }
3628 }
3629
3630 // Climb up the hierarchy doing double buffering with the sets.
3631 int a = 0;
3632 int b = 1;
3633 while (sections_to_unfold[a].size()) {
3634 for (EditorInspectorSection *E : sections_to_unfold[a]) {
3635 E->unfold();
3636
3637 Node *n = E->get_parent();
3638 while (n) {
3639 if (Object::cast_to<EditorInspector>(n)) {
3640 break;
3641 }
3642 if (Object::cast_to<EditorInspectorSection>(n) && !sections_to_unfold[a].has((EditorInspectorSection *)n)) {
3643 sections_to_unfold[b].insert((EditorInspectorSection *)n);
3644 }
3645 n = n->get_parent();
3646 }
3647 }
3648
3649 sections_to_unfold[a].clear();
3650 SWAP(a, b);
3651 }
3652
3653 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
3654 for (EditorProperty *E : F.value) {
3655 E->expand_revertable();
3656 }
3657 }
3658}
3659
3660void EditorInspector::set_scroll_offset(int p_offset) {
3661 set_v_scroll(p_offset);
3662}
3663
3664int EditorInspector::get_scroll_offset() const {
3665 return get_v_scroll();
3666}
3667
3668void EditorInspector::set_use_wide_editors(bool p_enable) {
3669 wide_editors = p_enable;
3670}
3671
3672void EditorInspector::_update_inspector_bg() {
3673 if (sub_inspector) {
3674 int count_subinspectors = 0;
3675 Node *n = get_parent();
3676 while (n) {
3677 EditorInspector *ei = Object::cast_to<EditorInspector>(n);
3678 if (ei && ei->sub_inspector) {
3679 count_subinspectors++;
3680 }
3681 n = n->get_parent();
3682 }
3683 count_subinspectors = MIN(15, count_subinspectors);
3684 add_theme_style_override("panel", get_theme_stylebox("sub_inspector_bg" + itos(count_subinspectors), EditorStringName(Editor)));
3685 } else {
3686 add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
3687 }
3688}
3689void EditorInspector::set_sub_inspector(bool p_enable) {
3690 sub_inspector = p_enable;
3691 if (!is_inside_tree()) {
3692 return;
3693 }
3694
3695 _update_inspector_bg();
3696}
3697
3698void EditorInspector::set_use_deletable_properties(bool p_enabled) {
3699 deletable_properties = p_enabled;
3700}
3701
3702void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) {
3703 int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0;
3704 int new_page = MAX(0, p_new_page);
3705 if (new_page != prev_page) {
3706 per_array_page[p_array_prefix] = new_page;
3707 update_tree_pending = true;
3708 }
3709}
3710
3711void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) {
3712 if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore
3713 return;
3714 }
3715
3716 if (changing) {
3717 return;
3718 }
3719
3720 if (p_property.is_empty()) {
3721 update_tree_pending = true;
3722 } else {
3723 pending.insert(p_property);
3724 }
3725}
3726
3727void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field) {
3728 if (autoclear && editor_property_map.has(p_name)) {
3729 for (EditorProperty *E : editor_property_map[p_name]) {
3730 if (E->is_checkable()) {
3731 E->set_checked(true);
3732 }
3733 }
3734 }
3735
3736 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3737 if (bool(object->call("_dont_undo_redo"))) {
3738 object->set(p_name, p_value);
3739 if (p_refresh_all) {
3740 _edit_request_change(object, "");
3741 } else {
3742 _edit_request_change(object, p_name);
3743 }
3744
3745 emit_signal(_prop_edited, p_name);
3746
3747 } else if (Object::cast_to<MultiNodeEdit>(object)) {
3748 Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field);
3749 _edit_request_change(object, p_name);
3750 emit_signal(_prop_edited, p_name);
3751 } else {
3752 undo_redo->create_action(vformat(TTR("Set %s"), p_name), UndoRedo::MERGE_ENDS);
3753 undo_redo->add_do_property(object, p_name, p_value);
3754 bool valid = false;
3755 Variant value = object->get(p_name, &valid);
3756 if (valid) {
3757 undo_redo->add_undo_property(object, p_name, value);
3758 }
3759
3760 List<StringName> linked_properties;
3761 ClassDB::get_linked_properties_info(object->get_class_name(), p_name, &linked_properties);
3762
3763 for (const StringName &linked_prop : linked_properties) {
3764 valid = false;
3765 Variant undo_value = object->get(linked_prop, &valid);
3766 if (valid) {
3767 undo_redo->add_undo_property(object, linked_prop, undo_value);
3768 }
3769 }
3770
3771 PackedStringArray linked_properties_dynamic = object->call("_get_linked_undo_properties", p_name, p_value);
3772 for (int i = 0; i < linked_properties_dynamic.size(); i++) {
3773 valid = false;
3774 Variant undo_value = object->get(linked_properties_dynamic[i], &valid);
3775 if (valid) {
3776 undo_redo->add_undo_property(object, linked_properties_dynamic[i], undo_value);
3777 }
3778 }
3779
3780 Variant v_undo_redo = undo_redo;
3781 Variant v_object = object;
3782 Variant v_name = p_name;
3783 const Vector<Callable> &callbacks = EditorNode::get_editor_data().get_undo_redo_inspector_hook_callback();
3784 for (int i = 0; i < callbacks.size(); i++) {
3785 const Callable &callback = callbacks[i];
3786
3787 const Variant *p_arguments[] = { &v_undo_redo, &v_object, &v_name, &p_value };
3788 Variant return_value;
3789 Callable::CallError call_error;
3790
3791 callback.callp(p_arguments, 4, return_value, call_error);
3792 if (call_error.error != Callable::CallError::CALL_OK) {
3793 ERR_PRINT("Invalid UndoRedo callback.");
3794 }
3795 }
3796
3797 if (p_refresh_all) {
3798 undo_redo->add_do_method(this, "_edit_request_change", object, "");
3799 undo_redo->add_undo_method(this, "_edit_request_change", object, "");
3800 } else {
3801 undo_redo->add_do_method(this, "_edit_request_change", object, p_name);
3802 undo_redo->add_undo_method(this, "_edit_request_change", object, p_name);
3803 }
3804
3805 Resource *r = Object::cast_to<Resource>(object);
3806 if (r) {
3807 if (String(p_name) == "resource_local_to_scene") {
3808 bool prev = object->get(p_name);
3809 bool next = p_value;
3810 if (next) {
3811 undo_redo->add_do_method(r, "setup_local_to_scene");
3812 }
3813 if (prev) {
3814 undo_redo->add_undo_method(r, "setup_local_to_scene");
3815 }
3816 }
3817 }
3818 undo_redo->add_do_method(this, "emit_signal", _prop_edited, p_name);
3819 undo_redo->add_undo_method(this, "emit_signal", _prop_edited, p_name);
3820 undo_redo->commit_action();
3821 }
3822
3823 if (editor_property_map.has(p_name)) {
3824 for (EditorProperty *E : editor_property_map[p_name]) {
3825 E->update_editor_property_status();
3826 }
3827 }
3828}
3829
3830void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) {
3831 // The "changing" variable must be true for properties that trigger events as typing occurs,
3832 // like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc.
3833 if (p_changing) {
3834 this->changing++;
3835 }
3836
3837 _edit_set(p_path, p_value, p_update_all, p_name);
3838
3839 if (p_changing) {
3840 this->changing--;
3841 }
3842
3843 if (restart_request_props.has(p_path)) {
3844 emit_signal(SNAME("restart_requested"));
3845 }
3846}
3847
3848void EditorInspector::_multiple_properties_changed(Vector<String> p_paths, Array p_values, bool p_changing) {
3849 ERR_FAIL_COND(p_paths.size() == 0 || p_values.size() == 0);
3850 ERR_FAIL_COND(p_paths.size() != p_values.size());
3851 String names;
3852 for (int i = 0; i < p_paths.size(); i++) {
3853 if (i > 0) {
3854 names += ",";
3855 }
3856 names += p_paths[i];
3857 }
3858 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3859 // TRANSLATORS: This is describing a change to multiple properties at once. The parameter is a list of property names.
3860 undo_redo->create_action(vformat(TTR("Set Multiple: %s"), names), UndoRedo::MERGE_ENDS);
3861 for (int i = 0; i < p_paths.size(); i++) {
3862 _edit_set(p_paths[i], p_values[i], false, "");
3863 if (restart_request_props.has(p_paths[i])) {
3864 emit_signal(SNAME("restart_requested"));
3865 }
3866 }
3867 if (p_changing) {
3868 changing++;
3869 }
3870 undo_redo->commit_action();
3871 if (p_changing) {
3872 changing--;
3873 }
3874}
3875
3876void EditorInspector::_property_keyed(const String &p_path, bool p_advance) {
3877 if (!object) {
3878 return;
3879 }
3880
3881 // The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it.
3882 const Variant args[3] = { p_path, object->get(p_path), p_advance };
3883 const Variant *argp[3] = { &args[0], &args[1], &args[2] };
3884 emit_signalp(SNAME("property_keyed"), argp, 3);
3885}
3886
3887void EditorInspector::_property_deleted(const String &p_path) {
3888 if (!object) {
3889 return;
3890 }
3891
3892 if (p_path.begins_with("metadata/")) {
3893 String name = p_path.replace_first("metadata/", "");
3894 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3895 undo_redo->create_action(vformat(TTR("Remove metadata %s"), name));
3896 undo_redo->add_do_method(object, "remove_meta", name);
3897 undo_redo->add_undo_method(object, "set_meta", name, object->get_meta(name));
3898 undo_redo->commit_action();
3899 }
3900
3901 emit_signal(SNAME("property_deleted"), p_path);
3902}
3903
3904void EditorInspector::_property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance) {
3905 if (!object) {
3906 return;
3907 }
3908
3909 // The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it.
3910 const Variant args[3] = { p_path, p_value, p_advance };
3911 const Variant *argp[3] = { &args[0], &args[1], &args[2] };
3912 emit_signalp(SNAME("property_keyed"), argp, 3);
3913}
3914
3915void EditorInspector::_property_checked(const String &p_path, bool p_checked) {
3916 if (!object) {
3917 return;
3918 }
3919
3920 //property checked
3921 if (autoclear) {
3922 if (!p_checked) {
3923 object->set(p_path, Variant());
3924 } else {
3925 Variant to_create;
3926 List<PropertyInfo> pinfo;
3927 object->get_property_list(&pinfo);
3928 for (const PropertyInfo &E : pinfo) {
3929 if (E.name == p_path) {
3930 Callable::CallError ce;
3931 Variant::construct(E.type, to_create, nullptr, 0, ce);
3932 break;
3933 }
3934 }
3935 object->set(p_path, to_create);
3936 }
3937
3938 if (editor_property_map.has(p_path)) {
3939 for (EditorProperty *E : editor_property_map[p_path]) {
3940 E->set_checked(p_checked);
3941 E->update_property();
3942 E->update_editor_property_status();
3943 E->update_cache();
3944 }
3945 }
3946
3947 } else {
3948 emit_signal(SNAME("property_toggled"), p_path, p_checked);
3949 }
3950}
3951
3952void EditorInspector::_property_pinned(const String &p_path, bool p_pinned) {
3953 if (!object) {
3954 return;
3955 }
3956
3957 Node *node = Object::cast_to<Node>(object);
3958 ERR_FAIL_NULL(node);
3959
3960 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
3961 undo_redo->create_action(vformat(p_pinned ? TTR("Pinned %s") : TTR("Unpinned %s"), p_path));
3962 undo_redo->add_do_method(node, "_set_property_pinned", p_path, p_pinned);
3963 undo_redo->add_undo_method(node, "_set_property_pinned", p_path, !p_pinned);
3964 if (editor_property_map.has(p_path)) {
3965 for (List<EditorProperty *>::Element *E = editor_property_map[p_path].front(); E; E = E->next()) {
3966 undo_redo->add_do_method(E->get(), "_update_editor_property_status");
3967 undo_redo->add_undo_method(E->get(), "_update_editor_property_status");
3968 }
3969 }
3970 undo_redo->commit_action();
3971}
3972
3973void EditorInspector::_property_selected(const String &p_path, int p_focusable) {
3974 property_selected = p_path;
3975 property_focusable = p_focusable;
3976 // Deselect the others.
3977 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
3978 if (F.key == property_selected) {
3979 continue;
3980 }
3981 for (EditorProperty *E : F.value) {
3982 if (E->is_selected()) {
3983 E->deselect();
3984 }
3985 }
3986 }
3987
3988 emit_signal(SNAME("property_selected"), p_path);
3989}
3990
3991void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) {
3992 emit_signal(SNAME("object_id_selected"), p_id);
3993}
3994
3995void EditorInspector::_resource_selected(const String &p_path, Ref<Resource> p_resource) {
3996 emit_signal(SNAME("resource_selected"), p_resource, p_path);
3997}
3998
3999void EditorInspector::_node_removed(Node *p_node) {
4000 if (p_node == object) {
4001 edit(nullptr);
4002 }
4003}
4004
4005void EditorInspector::_notification(int p_what) {
4006 switch (p_what) {
4007 case NOTIFICATION_READY: {
4008 EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &EditorInspector::_feature_profile_changed));
4009 set_process(is_visible_in_tree());
4010 _update_inspector_bg();
4011 } break;
4012
4013 case NOTIFICATION_ENTER_TREE: {
4014 if (!sub_inspector) {
4015 get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
4016 }
4017 } break;
4018
4019 case NOTIFICATION_PREDELETE: {
4020 edit(nullptr); //just in case
4021 } break;
4022
4023 case NOTIFICATION_EXIT_TREE: {
4024 if (!sub_inspector) {
4025 get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed));
4026 }
4027 edit(nullptr);
4028 } break;
4029
4030 case NOTIFICATION_VISIBILITY_CHANGED: {
4031 set_process(is_visible_in_tree());
4032 } break;
4033
4034 case NOTIFICATION_PROCESS: {
4035 if (update_scroll_request >= 0) {
4036 get_v_scroll_bar()->call_deferred(SNAME("set_value"), update_scroll_request);
4037 update_scroll_request = -1;
4038 }
4039 if (update_tree_pending) {
4040 refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));
4041 } else if (refresh_countdown > 0) {
4042 refresh_countdown -= get_process_delta_time();
4043 if (refresh_countdown <= 0) {
4044 for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {
4045 for (EditorProperty *E : F.value) {
4046 if (E && !E->is_cache_valid()) {
4047 E->update_property();
4048 E->update_editor_property_status();
4049 E->update_cache();
4050 }
4051 }
4052 }
4053 refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));
4054 }
4055 }
4056
4057 changing++;
4058
4059 if (update_tree_pending) {
4060 update_tree();
4061 update_tree_pending = false;
4062 pending.clear();
4063
4064 } else {
4065 while (pending.size()) {
4066 StringName prop = *pending.begin();
4067 if (editor_property_map.has(prop)) {
4068 for (EditorProperty *E : editor_property_map[prop]) {
4069 E->update_property();
4070 E->update_editor_property_status();
4071 E->update_cache();
4072 }
4073 }
4074 pending.remove(pending.begin());
4075 }
4076 }
4077
4078 changing--;
4079 } break;
4080
4081 case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
4082 _update_inspector_bg();
4083
4084 bool needs_update = false;
4085
4086 if (use_settings_name_style && EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) {
4087 EditorPropertyNameProcessor::Style style = EditorPropertyNameProcessor::get_settings_style();
4088 if (property_name_style != style) {
4089 property_name_style = style;
4090 needs_update = true;
4091 }
4092 }
4093 if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/inspector")) {
4094 needs_update = true;
4095 }
4096
4097 if (needs_update) {
4098 update_tree();
4099 }
4100 } break;
4101 }
4102}
4103
4104void EditorInspector::_changed_callback() {
4105 //this is called when property change is notified via notify_property_list_changed()
4106 if (object != nullptr) {
4107 _edit_request_change(object, String());
4108 }
4109}
4110
4111void EditorInspector::_vscroll_changed(double p_offset) {
4112 if (update_scroll_request >= 0) { //waiting, do nothing
4113 return;
4114 }
4115
4116 if (object) {
4117 scroll_cache[object->get_instance_id()] = p_offset;
4118 }
4119}
4120
4121void EditorInspector::set_property_prefix(const String &p_prefix) {
4122 property_prefix = p_prefix;
4123}
4124
4125String EditorInspector::get_property_prefix() const {
4126 return property_prefix;
4127}
4128
4129void EditorInspector::set_object_class(const String &p_class) {
4130 object_class = p_class;
4131}
4132
4133String EditorInspector::get_object_class() const {
4134 return object_class;
4135}
4136
4137void EditorInspector::_feature_profile_changed() {
4138 update_tree();
4139}
4140
4141void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) {
4142 restrict_to_basic = p_restrict;
4143 update_tree();
4144}
4145
4146void EditorInspector::set_property_clipboard(const Variant &p_value) {
4147 property_clipboard = p_value;
4148}
4149
4150Variant EditorInspector::get_property_clipboard() const {
4151 return property_clipboard;
4152}
4153
4154void EditorInspector::_add_meta_confirm() {
4155 String name = add_meta_name->get_text();
4156
4157 object->editor_set_section_unfold("metadata", true); // Ensure metadata is unfolded when adding a new metadata.
4158
4159 Variant defval;
4160 Callable::CallError ce;
4161 Variant::construct(Variant::Type(add_meta_type->get_selected_id()), defval, nullptr, 0, ce);
4162 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
4163 undo_redo->create_action(vformat(TTR("Add metadata %s"), name));
4164 undo_redo->add_do_method(object, "set_meta", name, defval);
4165 undo_redo->add_undo_method(object, "remove_meta", name);
4166 undo_redo->commit_action();
4167}
4168
4169void EditorInspector::_check_meta_name() {
4170 const String meta_name = add_meta_name->get_text();
4171
4172 if (meta_name.is_empty()) {
4173 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name can't be empty."), EditorValidationPanel::MSG_ERROR);
4174 } else if (!meta_name.is_valid_identifier()) {
4175 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name must be a valid identifier."), EditorValidationPanel::MSG_ERROR);
4176 } else if (object->has_meta(meta_name)) {
4177 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, vformat(TTR("Metadata with name \"%s\" already exists."), meta_name), EditorValidationPanel::MSG_ERROR);
4178 } else if (meta_name[0] == '_') {
4179 validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Names starting with _ are reserved for editor-only metadata."), EditorValidationPanel::MSG_ERROR);
4180 }
4181}
4182
4183void EditorInspector::_show_add_meta_dialog() {
4184 if (!add_meta_dialog) {
4185 add_meta_dialog = memnew(ConfirmationDialog);
4186
4187 VBoxContainer *vbc = memnew(VBoxContainer);
4188 add_meta_dialog->add_child(vbc);
4189
4190 HBoxContainer *hbc = memnew(HBoxContainer);
4191 vbc->add_child(hbc);
4192 hbc->add_child(memnew(Label(TTR("Name:"))));
4193
4194 add_meta_name = memnew(LineEdit);
4195 add_meta_name->set_custom_minimum_size(Size2(200 * EDSCALE, 1));
4196 hbc->add_child(add_meta_name);
4197 hbc->add_child(memnew(Label(TTR("Type:"))));
4198
4199 add_meta_type = memnew(OptionButton);
4200 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
4201 if (i == Variant::NIL || i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) {
4202 continue; //not editable by inspector.
4203 }
4204 String type = i == Variant::OBJECT ? String("Resource") : Variant::get_type_name(Variant::Type(i));
4205
4206 add_meta_type->add_icon_item(get_editor_theme_icon(type), type, i);
4207 }
4208 hbc->add_child(add_meta_type);
4209
4210 Control *spacing = memnew(Control);
4211 vbc->add_child(spacing);
4212 spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
4213
4214 add_meta_dialog->set_ok_button_text(TTR("Add"));
4215 add_child(add_meta_dialog);
4216 add_meta_dialog->register_text_enter(add_meta_name);
4217 add_meta_dialog->connect("confirmed", callable_mp(this, &EditorInspector::_add_meta_confirm));
4218
4219 validation_panel = memnew(EditorValidationPanel);
4220 vbc->add_child(validation_panel);
4221 validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Metadata name is valid."));
4222 validation_panel->set_update_callback(callable_mp(this, &EditorInspector::_check_meta_name));
4223 validation_panel->set_accept_button(add_meta_dialog->get_ok_button());
4224
4225 add_meta_name->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
4226 }
4227
4228 Node *node = Object::cast_to<Node>(object);
4229 if (node) {
4230 add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), node->get_name()));
4231 } else {
4232 // This should normally be reached when the object is derived from Resource.
4233 add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), object->get_class()));
4234 }
4235
4236 add_meta_dialog->popup_centered();
4237 add_meta_name->grab_focus();
4238 add_meta_name->set_text("");
4239 validation_panel->update();
4240}
4241
4242void EditorInspector::_bind_methods() {
4243 ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
4244 ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path);
4245
4246 ADD_SIGNAL(MethodInfo("property_selected", PropertyInfo(Variant::STRING, "property")));
4247 ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "advance")));
4248 ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING, "property")));
4249 ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::STRING, "path")));
4250 ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::INT, "id")));
4251 ADD_SIGNAL(MethodInfo("property_edited", PropertyInfo(Variant::STRING, "property")));
4252 ADD_SIGNAL(MethodInfo("property_toggled", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::BOOL, "checked")));
4253 ADD_SIGNAL(MethodInfo("edited_object_changed"));
4254 ADD_SIGNAL(MethodInfo("restart_requested"));
4255}
4256
4257EditorInspector::EditorInspector() {
4258 object = nullptr;
4259 main_vbox = memnew(VBoxContainer);
4260 main_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
4261 main_vbox->add_theme_constant_override("separation", 0);
4262 add_child(main_vbox);
4263 set_horizontal_scroll_mode(SCROLL_MODE_DISABLED);
4264
4265 changing = 0;
4266 search_box = nullptr;
4267 _prop_edited = "property_edited";
4268 set_process(false);
4269 property_focusable = -1;
4270 property_clipboard = Variant();
4271
4272 get_v_scroll_bar()->connect("value_changed", callable_mp(this, &EditorInspector::_vscroll_changed));
4273 update_scroll_request = -1;
4274 if (EditorSettings::get_singleton()) {
4275 refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));
4276 } else {
4277 //used when class is created by the docgen to dump default values of everything bindable, editorsettings may not be created
4278 refresh_countdown = 0.33;
4279 }
4280
4281 ED_SHORTCUT("property_editor/copy_value", TTR("Copy Value"), KeyModifierMask::CMD_OR_CTRL | Key::C);
4282 ED_SHORTCUT("property_editor/paste_value", TTR("Paste Value"), KeyModifierMask::CMD_OR_CTRL | Key::V);
4283 ED_SHORTCUT("property_editor/copy_property_path", TTR("Copy Property Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);
4284
4285 // `use_settings_name_style` is true by default, set the name style accordingly.
4286 set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style());
4287}
4288