Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_properties_array_dict.cpp
20838 views
1
/**************************************************************************/
2
/* editor_properties_array_dict.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "editor_properties_array_dict.h"
32
33
#include "core/input/input.h"
34
#include "core/io/marshalls.h"
35
#include "editor/docks/inspector_dock.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_string_names.h"
38
#include "editor/file_system/editor_file_system.h"
39
#include "editor/gui/editor_spin_slider.h"
40
#include "editor/gui/editor_variant_type_selectors.h"
41
#include "editor/inspector/editor_properties.h"
42
#include "editor/inspector/editor_properties_vector.h"
43
#include "editor/settings/editor_settings.h"
44
#include "editor/themes/editor_scale.h"
45
#include "scene/gui/button.h"
46
#include "scene/gui/margin_container.h"
47
48
bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {
49
String name = p_name;
50
51
if (!name.begins_with("indices")) {
52
return false;
53
}
54
55
int index;
56
if (name.begins_with("metadata/")) {
57
index = name.get_slicec('/', 2).to_int();
58
} else {
59
index = name.get_slicec('/', 1).to_int();
60
}
61
62
array.set(index, p_value);
63
return true;
64
}
65
66
bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const {
67
String name = p_name;
68
69
if (!name.begins_with("indices")) {
70
return false;
71
}
72
73
int index;
74
if (name.begins_with("metadata/")) {
75
index = name.get_slicec('/', 2).to_int();
76
} else {
77
index = name.get_slicec('/', 1).to_int();
78
}
79
80
bool valid;
81
r_ret = array.get(index, &valid);
82
83
if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
84
r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
85
}
86
87
return valid;
88
}
89
90
void EditorPropertyArrayObject::set_array(const Variant &p_array) {
91
array = p_array;
92
}
93
94
Variant EditorPropertyArrayObject::get_array() {
95
return array;
96
}
97
98
///////////////////
99
100
bool EditorPropertyDictionaryObject::_set(const StringName &p_name, const Variant &p_value) {
101
String name = p_name;
102
103
if (name == "new_item_key") {
104
new_item_key = p_value;
105
return true;
106
}
107
108
if (name == "new_item_value") {
109
new_item_value = p_value;
110
return true;
111
}
112
113
if (name.begins_with("indices")) {
114
dict = dict.duplicate();
115
int index = name.get_slicec('/', 1).to_int();
116
Variant key = dict.get_key_at_index(index);
117
dict[key] = p_value;
118
return true;
119
}
120
121
return false;
122
}
123
124
bool EditorPropertyDictionaryObject::_get(const StringName &p_name, Variant &r_ret) const {
125
if (!get_by_property_name(p_name, r_ret)) {
126
return false;
127
}
128
if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
129
r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
130
}
131
return true;
132
}
133
134
bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, Variant &r_ret) const {
135
String name = p_name;
136
137
if (name == "new_item_key") {
138
r_ret = new_item_key;
139
return true;
140
}
141
142
if (name == "new_item_value") {
143
r_ret = new_item_value;
144
return true;
145
}
146
147
if (name == "new_item_key_name") {
148
r_ret = TTR("New Key:");
149
return true;
150
}
151
152
if (name == "new_item_value_name") {
153
r_ret = TTR("New Value:");
154
return true;
155
}
156
157
if (name.begins_with("indices")) {
158
int index = name.get_slicec('/', 1).to_int();
159
Variant key = dict.get_key_at_index(index);
160
r_ret = dict[key];
161
return true;
162
}
163
164
if (name.begins_with("keys")) {
165
int index = name.get_slicec('/', 1).to_int();
166
Variant key = dict.get_key_at_index(index);
167
r_ret = key;
168
return true;
169
}
170
171
return false;
172
}
173
174
void EditorPropertyDictionaryObject::set_dict(const Dictionary &p_dict) {
175
dict = p_dict;
176
}
177
178
Dictionary EditorPropertyDictionaryObject::get_dict() {
179
return dict;
180
}
181
182
void EditorPropertyDictionaryObject::set_new_item_key(const Variant &p_new_item) {
183
new_item_key = p_new_item;
184
}
185
186
Variant EditorPropertyDictionaryObject::get_new_item_key() {
187
return new_item_key;
188
}
189
190
void EditorPropertyDictionaryObject::set_new_item_value(const Variant &p_new_item) {
191
new_item_value = p_new_item;
192
}
193
194
Variant EditorPropertyDictionaryObject::get_new_item_value() {
195
return new_item_value;
196
}
197
198
String EditorPropertyDictionaryObject::get_property_name_for_index(int p_index) {
199
switch (p_index) {
200
case NEW_KEY_INDEX:
201
return "new_item_key";
202
case NEW_VALUE_INDEX:
203
return "new_item_value";
204
default:
205
return "indices/" + itos(p_index);
206
}
207
}
208
209
String EditorPropertyDictionaryObject::get_key_name_for_index(int p_index) {
210
switch (p_index) {
211
case NEW_KEY_INDEX:
212
return "new_item_key_name";
213
case NEW_VALUE_INDEX:
214
return "new_item_value_name";
215
default:
216
return "keys/" + itos(p_index);
217
}
218
}
219
220
String EditorPropertyDictionaryObject::get_label_for_index(int p_index) {
221
switch (p_index) {
222
case NEW_KEY_INDEX:
223
return TTR("New Key:");
224
break;
225
case NEW_VALUE_INDEX:
226
return TTR("New Value:");
227
break;
228
default:
229
return dict.get_key_at_index(p_index).get_construct_string();
230
break;
231
}
232
}
233
234
///////////////////// ARRAY ///////////////////////////
235
236
void EditorPropertyArray::initialize_array(Variant &p_array) {
237
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
238
Array array;
239
StringName subtype_class;
240
Ref<Script> subtype_script;
241
if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
242
if (ClassDB::class_exists(subtype_hint_string)) {
243
subtype_class = subtype_hint_string;
244
}
245
}
246
array.set_typed(subtype, subtype_class, subtype_script);
247
p_array = array;
248
} else {
249
VariantInternal::initialize(&p_array, array_type);
250
}
251
}
252
253
void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
254
if (!p_property.begins_with("indices")) {
255
return;
256
}
257
258
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
259
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
260
}
261
262
int index = p_property.get_slicec('/', 1).to_int();
263
264
Variant array = object->get_array().duplicate();
265
array.set(index, p_value);
266
emit_changed(get_edited_property(), array, p_name, p_changing);
267
if (p_changing) {
268
object->set_array(array);
269
}
270
}
271
272
void EditorPropertyArray::_change_type(Object *p_button, int p_slot_index) {
273
Button *button = Object::cast_to<Button>(p_button);
274
changing_type_index = p_slot_index;
275
Rect2 rect = button->get_screen_rect();
276
change_type->reset_size();
277
change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
278
change_type->popup();
279
}
280
281
void EditorPropertyArray::_change_type_menu(int p_index) {
282
if (p_index == Variant::VARIANT_MAX) {
283
_remove_pressed(changing_type_index);
284
return;
285
}
286
287
ERR_FAIL_COND_MSG(
288
changing_type_index == EditorPropertyArrayObject::NOT_CHANGING_TYPE,
289
"Tried to change type of an array item, but no item was selected.");
290
291
Variant value;
292
VariantInternal::initialize(&value, Variant::Type(p_index));
293
294
Variant array = object->get_array().duplicate();
295
array.set(slots[changing_type_index].index, value);
296
297
emit_changed(get_edited_property(), array);
298
}
299
300
void EditorPropertyArray::_object_id_selected(const StringName &p_property, ObjectID p_id) {
301
emit_signal(SNAME("object_id_selected"), p_property, p_id);
302
}
303
304
void EditorPropertyArray::_resource_selected(const String &p_path, Ref<Resource> p_resource) {
305
emit_signal(SNAME("resource_selected"), get_edited_property(), p_resource);
306
}
307
308
void EditorPropertyArray::_create_new_property_slot() {
309
int idx = slots.size();
310
HBoxContainer *hbox = memnew(HBoxContainer);
311
312
EditorProperty *prop = memnew(EditorPropertyNil);
313
314
Button *reorder_button = memnew(Button);
315
reorder_button->set_accessibility_name(TTRC("Reorder"));
316
reorder_button->set_button_icon(get_editor_theme_icon(SNAME("TripleBar")));
317
reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE);
318
reorder_button->set_disabled(is_read_only());
319
reorder_button->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
320
reorder_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input));
321
reorder_button->connect(SNAME("button_up"), callable_mp(this, &EditorPropertyArray::_reorder_button_up));
322
reorder_button->connect(SNAME("button_down"), callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(idx));
323
324
hbox->add_child(prop);
325
326
bool is_untyped_array = object->get_array().get_type() == Variant::ARRAY && subtype == Variant::NIL;
327
328
Button *edit_btn = nullptr;
329
Button *remove_btn = nullptr;
330
if (is_untyped_array) {
331
edit_btn = memnew(Button);
332
edit_btn->set_accessibility_name(TTRC("Edit"));
333
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
334
edit_btn->set_disabled(is_read_only());
335
edit_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
336
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, idx));
337
} else {
338
remove_btn = memnew(Button);
339
remove_btn->set_accessibility_name(TTRC("Remove"));
340
remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
341
remove_btn->set_disabled(is_read_only());
342
remove_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
343
remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(idx));
344
}
345
property_vbox->add_child(hbox);
346
347
Slot slot;
348
slot.prop = prop;
349
slot.object = object;
350
slot.container = hbox;
351
slot.reorder_button = reorder_button;
352
slot.edit_button = edit_btn;
353
slot.remove_button = remove_btn;
354
slot.set_index(idx + page_index * page_length);
355
slots.push_back(slot);
356
}
357
358
void EditorPropertyArray::set_preview_value(bool p_preview_value) {
359
preview_value = p_preview_value;
360
}
361
362
void EditorPropertyArray::update_property() {
363
Variant array = get_edited_property_value();
364
365
String array_type_name = Variant::get_type_name(array_type);
366
String array_sub_type_name;
367
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
368
String type_name;
369
if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
370
type_name = subtype_hint_string;
371
} else {
372
type_name = Variant::get_type_name(subtype);
373
}
374
375
if (preview_value) {
376
array_sub_type_name = vformat("[%s] ", type_name);
377
} else {
378
array_type_name = vformat("%s[%s]", array_type_name, type_name);
379
}
380
}
381
382
if (!array.is_array()) {
383
if (preview_value) {
384
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
385
edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));
386
edit->set_text(array_type_name);
387
} else {
388
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
389
edit->set_button_icon(Ref<Texture2D>());
390
edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
391
}
392
edit->set_pressed(false);
393
if (container) {
394
set_bottom_editor(nullptr);
395
memdelete(container);
396
button_add_item = nullptr;
397
container = nullptr;
398
slots.clear();
399
}
400
return;
401
}
402
403
object->set_array(array);
404
405
int size = array.call("size");
406
int max_page = MAX(0, size - 1) / page_length;
407
if (page_index > max_page) {
408
_page_changed(max_page);
409
}
410
411
if (preview_value) {
412
String ctr_str = array.get_construct_string().trim_prefix(array_type_name + "(").trim_suffix(")").remove_char('\n');
413
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
414
int type_end = ctr_str.find("](");
415
if (type_end > 0) {
416
ctr_str = ctr_str.substr(type_end + 2);
417
}
418
}
419
420
edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
421
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
422
edit->set_button_icon(get_editor_theme_icon(array_type_name));
423
edit->set_text(vformat("%s%s", array_sub_type_name, ctr_str));
424
edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), array_type_name, array_sub_type_name, size));
425
} else {
426
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
427
edit->set_button_icon(Ref<Texture2D>());
428
edit->set_text(vformat(TTR("%s (size %d)"), array_type_name, size));
429
}
430
431
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
432
if (edit->is_pressed() != unfolded) {
433
edit->set_pressed(unfolded);
434
}
435
436
if (unfolded) {
437
updating = true;
438
439
if (!container) {
440
container = memnew(PanelContainer);
441
add_child(container);
442
set_bottom_editor(container);
443
444
VBoxContainer *vbox = memnew(VBoxContainer);
445
vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
446
container->add_child(vbox);
447
448
HBoxContainer *hbox = memnew(HBoxContainer);
449
vbox->add_child(hbox);
450
451
Label *size_label = memnew(Label(TTR("Size:")));
452
size_label->set_h_size_flags(SIZE_EXPAND_FILL);
453
hbox->add_child(size_label);
454
455
size_slider = memnew(EditorSpinSlider);
456
size_slider->set_step(1);
457
size_slider->set_max(INT32_MAX);
458
size_slider->set_editing_integer(true);
459
size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
460
size_slider->set_read_only(is_read_only());
461
size_slider->set_accessibility_name(TTRC("Size"));
462
size_slider->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyArray::_length_changed));
463
hbox->add_child(size_slider);
464
465
property_vbox = memnew(VBoxContainer);
466
property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
467
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
468
vbox->add_child(property_vbox);
469
470
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Element"), SNAME("Add")));
471
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_add_element));
472
button_add_item->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_add_item_draw));
473
SET_DRAG_FORWARDING_CD(button_add_item, EditorPropertyArray);
474
button_add_item->set_disabled(is_read_only());
475
button_add_item->set_accessibility_name(TTRC("Add"));
476
vbox->add_child(button_add_item);
477
478
paginator = memnew(EditorPaginator);
479
paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed));
480
vbox->add_child(paginator);
481
482
for (int i = 0; i < page_length; i++) {
483
_create_new_property_slot();
484
}
485
}
486
487
size_slider->set_value(size);
488
property_vbox->set_visible(size > 0);
489
button_add_item->set_visible(page_index == max_page);
490
paginator->update(page_index, max_page);
491
paginator->set_visible(max_page > 0);
492
493
for (Slot &slot : slots) {
494
bool slot_visible = &slot != &reorder_slot && slot.index < size;
495
slot.container->set_visible(slot_visible);
496
// If not visible no need to update it
497
if (!slot_visible) {
498
continue;
499
}
500
501
int idx = slot.index;
502
Variant::Type value_type = subtype;
503
504
if (value_type == Variant::NIL) {
505
value_type = array.get(idx).get_type();
506
}
507
508
// Check if the editor property needs to be updated.
509
bool value_as_id = Object::cast_to<EncodedObjectAsID>(array.get(idx));
510
if (value_type != slot.type || (value_type == Variant::OBJECT && (value_as_id != slot.as_id))) {
511
slot.as_id = value_as_id;
512
slot.type = value_type;
513
EditorProperty *new_prop = nullptr;
514
if (value_type == Variant::OBJECT && value_as_id) {
515
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
516
editor->setup("Object");
517
new_prop = editor;
518
} else {
519
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
520
}
521
new_prop->set_selectable(false);
522
new_prop->set_use_folding(is_using_folding());
523
new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyArray::_property_changed));
524
new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyArray::_object_id_selected));
525
if (value_type == Variant::OBJECT) {
526
new_prop->connect("resource_selected", callable_mp(this, &EditorPropertyArray::_resource_selected), CONNECT_DEFERRED);
527
}
528
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
529
new_prop->set_read_only(is_read_only());
530
531
if (slot.reorder_button) {
532
new_prop->add_inline_control(slot.reorder_button, INLINE_CONTROL_LEFT);
533
slot.reorder_button->get_parent()->move_child(slot.reorder_button, 0);
534
}
535
if (slot.edit_button) {
536
new_prop->add_inline_control(slot.edit_button, INLINE_CONTROL_RIGHT);
537
} else if (slot.remove_button) {
538
new_prop->add_inline_control(slot.remove_button, INLINE_CONTROL_RIGHT);
539
}
540
541
slot.prop->add_sibling(new_prop, false);
542
slot.prop->queue_free();
543
slot.prop = new_prop;
544
slot.set_index(idx);
545
}
546
if (slot.index == changing_type_index) {
547
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
548
changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;
549
}
550
slot.prop->update_property();
551
}
552
553
updating = false;
554
555
} else {
556
if (container) {
557
set_bottom_editor(nullptr);
558
memdelete(container);
559
button_add_item = nullptr;
560
container = nullptr;
561
slots.clear();
562
}
563
}
564
}
565
566
void EditorPropertyArray::_remove_pressed(int p_slot_index) {
567
Variant array = object->get_array().duplicate();
568
array.call("remove_at", slots[p_slot_index].index);
569
570
emit_changed(get_edited_property(), array);
571
}
572
573
void EditorPropertyArray::_button_draw() {
574
if (dropping) {
575
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
576
edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false);
577
}
578
}
579
580
void EditorPropertyArray::_button_add_item_draw() {
581
if (dropping) {
582
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
583
button_add_item->draw_rect(Rect2(Point2(), button_add_item->get_size()), color, false);
584
}
585
}
586
587
bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {
588
if (is_read_only()) {
589
return false;
590
}
591
592
String allowed_type = Variant::get_type_name(subtype);
593
594
// When the subtype is of type Object, an additional subtype may be specified in the hint string
595
// (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object".
596
if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
597
allowed_type = subtype_hint_string;
598
}
599
600
Dictionary drag_data = p_drag_data;
601
const String drop_type = drag_data.get("type", "");
602
603
if (drop_type == "files") {
604
PackedStringArray files = drag_data["files"];
605
606
for (const String &file : files) {
607
int idx_in_dir;
608
EditorFileSystemDirectory const *dir = EditorFileSystem::get_singleton()->find_file(file, &idx_in_dir);
609
if (!dir) {
610
return false;
611
}
612
StringName ftype = dir->get_file_type(idx_in_dir);
613
String script_class = dir->get_file_resource_script_class(idx_in_dir);
614
615
for (String at : allowed_type.split(",", false)) {
616
at = at.strip_edges();
617
// Fail if one of the files is not of allowed type.
618
if (!ClassDB::is_parent_class(ftype, at) && !EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {
619
return false;
620
}
621
}
622
}
623
624
// If no files fail, drop is valid.
625
return true;
626
}
627
628
if (drop_type == "resource") {
629
Ref<Resource> res = drag_data["resource"];
630
if (res.is_null()) {
631
return false;
632
}
633
634
String res_type = res->get_class();
635
StringName script_class;
636
if (res->get_script()) {
637
script_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());
638
}
639
640
for (String at : allowed_type.split(",", false)) {
641
at = at.strip_edges();
642
if (ClassDB::is_parent_class(res_type, at) || EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {
643
return true;
644
}
645
}
646
647
return false;
648
}
649
650
if (drop_type == "nodes") {
651
Array node_paths = drag_data["nodes"];
652
653
PackedStringArray allowed_subtype_array;
654
if (allowed_type == "NodePath") {
655
if (subtype_hint_string == "NodePath") {
656
return true;
657
} else {
658
for (String ast : subtype_hint_string.split(",", false)) {
659
ast = ast.strip_edges();
660
allowed_subtype_array.append(ast);
661
}
662
}
663
}
664
665
bool is_drop_allowed = true;
666
667
for (int i = 0; i < node_paths.size(); i++) {
668
const Node *dropped_node = get_node_or_null(node_paths[i]);
669
ERR_FAIL_NULL_V_MSG(dropped_node, false, "Could not get the dropped node by its path.");
670
671
if (allowed_type != "NodePath") {
672
if (!ClassDB::is_parent_class(dropped_node->get_class_name(), allowed_type) &&
673
!EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, allowed_type)) {
674
// Fail if one of the nodes is not of allowed type.
675
return false;
676
}
677
}
678
679
// The array of NodePaths is restricted to specific types using @export_node_path().
680
if (allowed_type == "NodePath" && subtype_hint_string != "NodePath") {
681
if (!allowed_subtype_array.has(dropped_node->get_class_name())) {
682
// The dropped node type was not found in the allowed subtype array, we must check if it inherits one of them.
683
for (const String &ast : allowed_subtype_array) {
684
if (ClassDB::is_parent_class(dropped_node->get_class_name(), ast) ||
685
EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, ast)) {
686
is_drop_allowed = true;
687
break;
688
} else {
689
is_drop_allowed = false;
690
}
691
}
692
if (!is_drop_allowed) {
693
break;
694
}
695
}
696
}
697
}
698
699
return is_drop_allowed;
700
}
701
702
return false;
703
}
704
705
bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
706
return _is_drop_valid(p_data);
707
}
708
709
void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
710
Dictionary drag_data = p_data;
711
const String drop_type = drag_data.get("type", "");
712
Variant array = object->get_array();
713
714
// Handle the case where array is not initialized yet.
715
if (!array.is_array()) {
716
initialize_array(array);
717
} else {
718
array = array.duplicate();
719
}
720
721
if (drop_type == "files") {
722
PackedStringArray files = drag_data["files"];
723
724
// Loop the file array and add to existing array.
725
for (int i = 0; i < files.size(); i++) {
726
const String &file = files[i];
727
728
Ref<Resource> res = ResourceLoader::load(file);
729
if (res.is_valid()) {
730
array.call("push_back", res);
731
}
732
}
733
734
emit_changed(get_edited_property(), array);
735
}
736
737
if (drop_type == "resource") {
738
Ref<Resource> res = drag_data["resource"];
739
740
if (res.is_valid()) {
741
array.call("push_back", res);
742
743
emit_changed(get_edited_property(), array);
744
}
745
}
746
747
if (drop_type == "nodes") {
748
Array node_paths = drag_data["nodes"];
749
Node *base_node = get_base_node();
750
751
for (int i = 0; i < node_paths.size(); i++) {
752
const NodePath &path = node_paths[i];
753
754
if (subtype == Variant::OBJECT) {
755
array.call("push_back", get_node(path));
756
} else if (subtype == Variant::NODE_PATH) {
757
array.call("push_back", base_node->get_path().rel_path_to(path));
758
}
759
}
760
761
emit_changed(get_edited_property(), array);
762
}
763
}
764
765
Node *EditorPropertyArray::get_base_node() {
766
Node *base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
767
768
if (!base_node) {
769
base_node = get_tree()->get_edited_scene_root();
770
}
771
772
return base_node;
773
}
774
775
void EditorPropertyArray::_notification(int p_what) {
776
switch (p_what) {
777
case NOTIFICATION_DRAG_BEGIN: {
778
if (is_visible_in_tree()) {
779
if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {
780
dropping = true;
781
edit->queue_redraw();
782
if (button_add_item) {
783
button_add_item->queue_redraw();
784
}
785
}
786
}
787
} break;
788
789
case NOTIFICATION_DRAG_END: {
790
if (dropping) {
791
dropping = false;
792
edit->queue_redraw();
793
if (button_add_item) {
794
button_add_item->queue_redraw();
795
}
796
}
797
} break;
798
}
799
}
800
801
void EditorPropertyArray::_edit_pressed() {
802
Variant array = get_edited_property_value();
803
if (!array.is_array() && edit->is_pressed()) {
804
initialize_array(array);
805
emit_changed(get_edited_property(), array);
806
}
807
808
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
809
update_property();
810
}
811
812
void EditorPropertyArray::_page_changed(int p_page) {
813
if (updating) {
814
return;
815
}
816
page_index = p_page;
817
int i = p_page * page_length;
818
819
if (reorder_slot.index < 0) {
820
for (Slot &slot : slots) {
821
slot.set_index(i);
822
i++;
823
}
824
} else {
825
int reorder_from_page = reorder_slot.index / page_length;
826
if (reorder_from_page < p_page) {
827
i++;
828
}
829
for (Slot &slot : slots) {
830
if (slot.index != reorder_slot.index) {
831
slot.set_index(i);
832
i++;
833
} else if (i == reorder_slot.index) {
834
i++;
835
}
836
}
837
}
838
update_property();
839
}
840
841
void EditorPropertyArray::_length_changed(double p_page) {
842
if (updating) {
843
return;
844
}
845
846
Variant array = object->get_array().duplicate();
847
array.call("resize", int(p_page));
848
849
emit_changed(get_edited_property(), array);
850
}
851
852
void EditorPropertyArray::_add_element() {
853
_length_changed(double(object->get_array().call("size")) + 1.0);
854
}
855
856
void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {
857
array_type = p_array_type;
858
859
// The format of p_hint_string is:
860
// subType/subTypeHint:nextSubtype ... etc.
861
if (!p_hint_string.is_empty()) {
862
int hint_subtype_separator = p_hint_string.find_char(':');
863
if (hint_subtype_separator >= 0) {
864
String subtype_string = p_hint_string.substr(0, hint_subtype_separator);
865
int slash_pos = subtype_string.find_char('/');
866
if (slash_pos >= 0) {
867
subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1).to_int());
868
subtype_string = subtype_string.substr(0, slash_pos);
869
}
870
871
subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1);
872
subtype = Variant::Type(subtype_string.to_int());
873
} else {
874
subtype = Variant::get_type_by_name(p_hint_string);
875
876
if (subtype == Variant::VARIANT_MAX) {
877
subtype = Variant::OBJECT;
878
subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
879
subtype_hint_string = p_hint_string;
880
}
881
}
882
}
883
}
884
885
void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) {
886
if (reorder_slot.index < 0 || is_read_only()) {
887
return;
888
}
889
890
Ref<InputEventMouseMotion> mm = p_event;
891
if (mm.is_valid()) {
892
Variant array = object->get_array();
893
int size = array.call("size");
894
895
// Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point.
896
reorder_mouse_y_delta += mm->get_relative().y;
897
898
// Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if
899
// already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta).
900
if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) {
901
reorder_mouse_y_delta = 0.0f;
902
return;
903
}
904
905
float required_y_distance = 20.0f * EDSCALE;
906
if (Math::abs(reorder_mouse_y_delta) > required_y_distance) {
907
int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1;
908
reorder_mouse_y_delta -= required_y_distance * direction;
909
910
reorder_to_index += direction;
911
912
property_vbox->move_child(reorder_slot.container, reorder_to_index % page_length);
913
914
if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) {
915
// Automatically move to the next/previous page.
916
_page_changed(page_index + direction);
917
}
918
// Ensure the moving element is visible in the root inspector.
919
EditorInspector *parent_inspector = get_parent_inspector();
920
if (parent_inspector) {
921
// Defer to prevent moving elements from not displaying properly, especially near borders.
922
callable_mp((ScrollContainer *)parent_inspector->get_root_inspector(), &ScrollContainer::ensure_control_visible).call_deferred(reorder_slot.container);
923
}
924
}
925
}
926
}
927
928
void EditorPropertyArray::_reorder_button_down(int p_slot_index) {
929
if (is_read_only()) {
930
return;
931
}
932
reorder_slot = slots[p_slot_index];
933
reorder_to_index = reorder_slot.index;
934
// Ideally it'd to be able to show the mouse but I had issues with
935
// Control's `mouse_exit()`/`mouse_entered()` signals not getting called.
936
Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);
937
}
938
939
void EditorPropertyArray::_reorder_button_up() {
940
if (is_read_only()) {
941
return;
942
}
943
944
if (reorder_slot.index != reorder_to_index) {
945
// Move the element.
946
Variant array = object->get_array().duplicate();
947
948
property_vbox->move_child(reorder_slot.container, reorder_slot.index % page_length);
949
Variant value_to_move = array.get(reorder_slot.index);
950
array.call("remove_at", reorder_slot.index);
951
array.call("insert", reorder_to_index, value_to_move);
952
953
slots[reorder_to_index % page_length].reorder_button->grab_focus();
954
emit_changed(get_edited_property(), array);
955
}
956
957
Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);
958
959
ERR_FAIL_NULL(reorder_slot.reorder_button);
960
reorder_slot.reorder_button->warp_mouse(reorder_slot.reorder_button->get_size() / 2.0f);
961
reorder_to_index = -1;
962
reorder_mouse_y_delta = 0.0f;
963
reorder_slot = Slot();
964
_page_changed(page_index);
965
}
966
967
bool EditorPropertyArray::is_colored(ColorationMode p_mode) {
968
return p_mode == COLORATION_CONTAINER_RESOURCE;
969
}
970
971
EditorPropertyArray::EditorPropertyArray() {
972
object.instantiate();
973
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
974
975
edit = memnew(Button);
976
edit->set_accessibility_name(TTRC("Edit"));
977
edit->set_h_size_flags(SIZE_EXPAND_FILL);
978
edit->set_clip_text(true);
979
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
980
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_edit_pressed));
981
edit->set_toggle_mode(true);
982
SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray);
983
edit->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_draw));
984
add_child(edit);
985
add_focusable(edit);
986
987
change_type = memnew(EditorVariantTypePopupMenu(true));
988
add_child(change_type);
989
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyArray::_change_type_menu));
990
changing_type_index = -1;
991
992
subtype = Variant::NIL;
993
subtype_hint = PROPERTY_HINT_NONE;
994
subtype_hint_string = "";
995
has_borders = true;
996
}
997
998
///////////////////// DICTIONARY ///////////////////////////
999
1000
void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
1001
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
1002
Dictionary dict;
1003
StringName key_subtype_class;
1004
Ref<Script> key_subtype_script;
1005
if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
1006
key_subtype_class = key_subtype_hint_string;
1007
}
1008
StringName value_subtype_class;
1009
Ref<Script> value_subtype_script;
1010
if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
1011
value_subtype_class = value_subtype_hint_string;
1012
}
1013
dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
1014
p_dictionary = dict;
1015
} else {
1016
VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
1017
}
1018
}
1019
1020
void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
1021
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
1022
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
1023
}
1024
1025
object->set(p_property, p_value);
1026
bool new_item_or_key = !p_property.begins_with("indices");
1027
emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing || new_item_or_key);
1028
if (new_item_or_key) {
1029
update_property();
1030
}
1031
}
1032
1033
void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) {
1034
Button *button = Object::cast_to<Button>(p_button);
1035
int index = slots[p_slot_index].index;
1036
Rect2 rect = button->get_screen_rect();
1037
change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), index < 0);
1038
change_type->reset_size();
1039
change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
1040
change_type->popup();
1041
changing_type_index = index;
1042
}
1043
1044
void EditorPropertyDictionary::_add_key_value() {
1045
// Do not allow nil as valid key. I experienced errors with this
1046
if (object->get_new_item_key().get_type() == Variant::NIL) {
1047
return;
1048
}
1049
1050
Dictionary dict = object->get_dict().duplicate();
1051
Variant new_key = object->get_new_item_key();
1052
Variant new_value = object->get_new_item_value();
1053
dict[new_key] = new_value;
1054
1055
Variant::Type type = new_key.get_type();
1056
new_key.zero();
1057
VariantInternal::initialize(&new_key, type);
1058
object->set_new_item_key(new_key);
1059
1060
type = new_value.get_type();
1061
new_value.zero();
1062
VariantInternal::initialize(&new_value, type);
1063
object->set_new_item_value(new_value);
1064
1065
object->set_dict(dict);
1066
slots[(dict.size() - 1) % page_length].update_prop_or_index();
1067
emit_changed(get_edited_property(), dict);
1068
}
1069
1070
void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
1071
HBoxContainer *hbox = memnew(HBoxContainer);
1072
1073
EditorProperty *prop_key = nullptr;
1074
if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1075
prop_key = memnew(EditorPropertyNil);
1076
}
1077
1078
EditorProperty *prop = memnew(EditorPropertyNil);
1079
prop->set_h_size_flags(SIZE_EXPAND_FILL);
1080
hbox->add_child(prop);
1081
if (prop_key) {
1082
prop->add_inline_control(prop_key, INLINE_CONTROL_LEFT);
1083
}
1084
1085
bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
1086
bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
1087
1088
Button *edit_btn = nullptr;
1089
Button *remove_btn = nullptr;
1090
if (is_untyped_dict) {
1091
edit_btn = memnew(Button);
1092
edit_btn->set_accessibility_name(TTRC("Edit"));
1093
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
1094
edit_btn->set_disabled(is_read_only());
1095
edit_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
1096
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
1097
} else if (p_idx >= 0) {
1098
remove_btn = memnew(Button);
1099
remove_btn->set_accessibility_name(TTRC("Remove"));
1100
remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
1101
remove_btn->set_disabled(is_read_only());
1102
remove_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
1103
remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
1104
}
1105
1106
if (add_panel) {
1107
add_panel->get_child(0)->add_child(hbox);
1108
} else {
1109
property_vbox->add_child(hbox);
1110
}
1111
1112
Slot slot;
1113
slot.prop = prop;
1114
slot.prop_key = prop_key;
1115
slot.object = object;
1116
slot.container = hbox;
1117
slot.edit_button = edit_btn;
1118
slot.remove_button = remove_btn;
1119
int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0);
1120
slot.set_index(index);
1121
slots.push_back(slot);
1122
}
1123
1124
void EditorPropertyDictionary::_change_type_menu(int p_index) {
1125
ERR_FAIL_COND_MSG(
1126
changing_type_index == EditorPropertyDictionaryObject::NOT_CHANGING_TYPE,
1127
"Tried to change the type of a dict key or value, but nothing was selected.");
1128
1129
Variant value;
1130
switch (changing_type_index) {
1131
case EditorPropertyDictionaryObject::NEW_KEY_INDEX:
1132
case EditorPropertyDictionaryObject::NEW_VALUE_INDEX:
1133
VariantInternal::initialize(&value, Variant::Type(p_index));
1134
if (changing_type_index == EditorPropertyDictionaryObject::NEW_KEY_INDEX) {
1135
object->set_new_item_key(value);
1136
} else {
1137
object->set_new_item_value(value);
1138
}
1139
update_property();
1140
break;
1141
1142
default:
1143
Dictionary dict = object->get_dict().duplicate();
1144
Variant key = dict.get_key_at_index(changing_type_index);
1145
if (p_index < Variant::VARIANT_MAX) {
1146
VariantInternal::initialize(&value, Variant::Type(p_index));
1147
dict[key] = value;
1148
} else {
1149
dict.erase(key);
1150
object->set_dict(dict);
1151
for (Slot &slot : slots) {
1152
slot.update_prop_or_index();
1153
}
1154
}
1155
1156
emit_changed(get_edited_property(), dict);
1157
}
1158
}
1159
1160
void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
1161
PackedStringArray types = p_hint_string.split(";");
1162
if (types.size() > 0 && !types[0].is_empty()) {
1163
String key = types[0];
1164
int hint_key_subtype_separator = key.find_char(':');
1165
if (hint_key_subtype_separator >= 0) {
1166
String key_subtype_string = key.substr(0, hint_key_subtype_separator);
1167
int slash_pos = key_subtype_string.find_char('/');
1168
if (slash_pos >= 0) {
1169
key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1).to_int());
1170
key_subtype_string = key_subtype_string.substr(0, slash_pos);
1171
}
1172
1173
key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1);
1174
key_subtype = Variant::Type(key_subtype_string.to_int());
1175
} else {
1176
key_subtype = Variant::get_type_by_name(key);
1177
1178
if (key_subtype == Variant::VARIANT_MAX) {
1179
key_subtype = Variant::OBJECT;
1180
key_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
1181
key_subtype_hint_string = key;
1182
}
1183
}
1184
1185
Variant new_key = object->get_new_item_key();
1186
VariantInternal::initialize(&new_key, key_subtype);
1187
object->set_new_item_key(new_key);
1188
}
1189
1190
if (types.size() > 1 && !types[1].is_empty()) {
1191
String value = types[1];
1192
int hint_value_subtype_separator = value.find_char(':');
1193
if (hint_value_subtype_separator >= 0) {
1194
String value_subtype_string = value.substr(0, hint_value_subtype_separator);
1195
int slash_pos = value_subtype_string.find_char('/');
1196
if (slash_pos >= 0) {
1197
value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1).to_int());
1198
value_subtype_string = value_subtype_string.substr(0, slash_pos);
1199
}
1200
1201
value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1);
1202
value_subtype = Variant::Type(value_subtype_string.to_int());
1203
} else {
1204
value_subtype = Variant::get_type_by_name(value);
1205
1206
if (value_subtype == Variant::VARIANT_MAX) {
1207
value_subtype = Variant::OBJECT;
1208
value_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
1209
value_subtype_hint_string = value;
1210
}
1211
}
1212
1213
Variant new_value = object->get_new_item_value();
1214
VariantInternal::initialize(&new_value, value_subtype);
1215
object->set_new_item_value(new_value);
1216
}
1217
}
1218
1219
void EditorPropertyDictionary::set_preview_value(bool p_preview_value) {
1220
preview_value = p_preview_value;
1221
}
1222
1223
void EditorPropertyDictionary::update_property() {
1224
Variant updated_val = get_edited_property_value();
1225
1226
String dict_type_name = "Dictionary";
1227
String dict_sub_type_name;
1228
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
1229
String key_subtype_name = "Variant";
1230
if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
1231
key_subtype_name = key_subtype_hint_string;
1232
} else if (key_subtype != Variant::NIL) {
1233
key_subtype_name = Variant::get_type_name(key_subtype);
1234
}
1235
String value_subtype_name = "Variant";
1236
if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
1237
value_subtype_name = value_subtype_hint_string;
1238
} else if (value_subtype != Variant::NIL) {
1239
value_subtype_name = Variant::get_type_name(value_subtype);
1240
}
1241
if (preview_value) {
1242
dict_sub_type_name = vformat("[%s, %s] ", key_subtype_name, value_subtype_name);
1243
} else {
1244
dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
1245
}
1246
}
1247
1248
if (updated_val.get_type() != Variant::DICTIONARY) {
1249
if (preview_value) {
1250
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
1251
edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));
1252
edit->set_text(dict_type_name);
1253
} else {
1254
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1255
edit->set_button_icon(Ref<Texture2D>());
1256
edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name));
1257
}
1258
edit->set_pressed(false);
1259
if (container) {
1260
set_bottom_editor(nullptr);
1261
memdelete(container);
1262
button_add_item = nullptr;
1263
container = nullptr;
1264
add_panel = nullptr;
1265
slots.clear();
1266
}
1267
return;
1268
}
1269
1270
Dictionary dict = updated_val;
1271
object->set_dict(updated_val);
1272
1273
if (preview_value) {
1274
String ctr_str = updated_val.get_construct_string().remove_char('\n');
1275
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
1276
int type_end = ctr_str.find("](");
1277
if (type_end > 0) {
1278
ctr_str = ctr_str.substr(type_end + 2).trim_suffix(")");
1279
}
1280
}
1281
1282
edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1283
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
1284
edit->set_button_icon(get_editor_theme_icon(dict_type_name));
1285
edit->set_text(vformat("%s%s", dict_sub_type_name, ctr_str));
1286
edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), dict_type_name, dict_sub_type_name, dict.size()));
1287
} else {
1288
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1289
edit->set_button_icon(Ref<Texture2D>());
1290
edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
1291
}
1292
1293
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
1294
if (edit->is_pressed() != unfolded) {
1295
edit->set_pressed(unfolded);
1296
}
1297
1298
if (unfolded) {
1299
updating = true;
1300
1301
if (!container) {
1302
container = memnew(PanelContainer);
1303
add_child(container);
1304
set_bottom_editor(container);
1305
1306
VBoxContainer *vbox = memnew(VBoxContainer);
1307
vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
1308
container->add_child(vbox);
1309
1310
property_vbox = memnew(VBoxContainer);
1311
property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
1312
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1313
vbox->add_child(property_vbox);
1314
1315
paginator = memnew(EditorPaginator);
1316
paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));
1317
vbox->add_child(paginator);
1318
1319
for (int i = 0; i < page_length; i++) {
1320
_create_new_property_slot(slots.size());
1321
}
1322
1323
add_panel = memnew(PanelContainer);
1324
property_vbox->add_child(add_panel);
1325
add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));
1326
VBoxContainer *add_vbox = memnew(VBoxContainer);
1327
add_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
1328
add_panel->add_child(add_vbox);
1329
1330
_create_new_property_slot(EditorPropertyDictionaryObject::NEW_KEY_INDEX);
1331
_create_new_property_slot(EditorPropertyDictionaryObject::NEW_VALUE_INDEX);
1332
1333
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Key/Value Pair"), SNAME("Add")));
1334
button_add_item->set_disabled(is_read_only());
1335
button_add_item->set_accessibility_name(TTRC("Add"));
1336
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_add_key_value));
1337
add_vbox->add_child(button_add_item);
1338
}
1339
1340
int size = dict.size();
1341
1342
int max_page = MAX(0, size - 1) / page_length;
1343
if (page_index > max_page) {
1344
_page_changed(max_page);
1345
}
1346
1347
paginator->update(page_index, max_page);
1348
paginator->set_visible(max_page > 0);
1349
1350
add_panel->set_visible(page_index == max_page);
1351
1352
for (Slot &slot : slots) {
1353
bool slot_visible = slot.index < size;
1354
slot.container->set_visible(slot_visible);
1355
// If not visible no need to update it.
1356
if (!slot_visible) {
1357
continue;
1358
}
1359
1360
// Check if the editor property key needs to be updated.
1361
if (slot.prop_key) {
1362
Variant key;
1363
object->get_by_property_name(slot.key_name, key);
1364
Variant::Type key_type = key.get_type();
1365
1366
bool key_as_id = Object::cast_to<EncodedObjectAsID>(key);
1367
if (key_type != slot.key_type || (key_type == Variant::OBJECT && key_as_id != slot.key_as_id)) {
1368
slot.key_as_id = key_as_id;
1369
slot.key_type = key_type;
1370
EditorProperty *new_prop = nullptr;
1371
if (key_type == Variant::OBJECT && key_as_id) {
1372
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
1373
editor->setup("Object");
1374
new_prop = editor;
1375
} else {
1376
new_prop = EditorInspector::instantiate_property_editor(this, key_type, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE);
1377
}
1378
new_prop->set_read_only(true);
1379
new_prop->set_selectable(false);
1380
new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
1381
new_prop->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1382
new_prop->set_draw_background(false);
1383
new_prop->set_use_folding(is_using_folding());
1384
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
1385
new_prop->set_draw_label(false);
1386
EditorPropertyArray *arr_prop = Object::cast_to<EditorPropertyArray>(new_prop);
1387
if (arr_prop) {
1388
arr_prop->set_preview_value(true);
1389
}
1390
EditorPropertyDictionary *dict_prop = Object::cast_to<EditorPropertyDictionary>(new_prop);
1391
if (dict_prop) {
1392
dict_prop->set_preview_value(true);
1393
}
1394
slot.set_key_prop(new_prop);
1395
if (slot.prop) {
1396
slot.prop->add_inline_control(new_prop, INLINE_CONTROL_LEFT);
1397
}
1398
}
1399
}
1400
1401
Variant value;
1402
object->get_by_property_name(slot.prop_name, value);
1403
1404
Variant::Type value_type;
1405
1406
if (dict.is_typed_value() && value_subtype != Variant::NIL && slot.prop_key) {
1407
value_type = value_subtype;
1408
} else {
1409
value_type = value.get_type();
1410
}
1411
1412
// Check if the editor property needs to be updated.
1413
bool value_as_id = Object::cast_to<EncodedObjectAsID>(value);
1414
if (value_type != slot.type || (value_type == Variant::OBJECT && value_as_id != slot.as_id)) {
1415
slot.as_id = value_as_id;
1416
slot.type = value_type;
1417
EditorProperty *new_prop = nullptr;
1418
if (value_type == Variant::OBJECT && value_as_id) {
1419
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
1420
editor->setup("Object");
1421
new_prop = editor;
1422
} else {
1423
bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
1424
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
1425
use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
1426
}
1427
new_prop->set_selectable(false);
1428
new_prop->set_use_folding(is_using_folding());
1429
new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed));
1430
new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
1431
if (value_type == Variant::OBJECT) {
1432
new_prop->connect("resource_selected", callable_mp(this, &EditorPropertyDictionary::_resource_selected), CONNECT_DEFERRED);
1433
}
1434
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
1435
if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1436
new_prop->set_label(" ");
1437
new_prop->set_label_overlayed(true);
1438
}
1439
new_prop->set_read_only(is_read_only());
1440
if (slot.remove_button) {
1441
new_prop->add_inline_control(slot.remove_button, INLINE_CONTROL_RIGHT);
1442
}
1443
if (slot.edit_button) {
1444
new_prop->add_inline_control(slot.edit_button, INLINE_CONTROL_RIGHT);
1445
}
1446
if (slot.prop_key) {
1447
new_prop->add_inline_control(slot.prop_key, INLINE_CONTROL_LEFT);
1448
}
1449
1450
slot.set_prop(new_prop);
1451
1452
} else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1453
Variant key = dict.get_key_at_index(slot.index);
1454
String cs = key.get_construct_string();
1455
slot.prop->set_tooltip_text(cs);
1456
}
1457
1458
// We need to grab the focus of the property that is being changed, even if the type didn't actually changed.
1459
// Otherwise, focus will stay on the change type button, which is not very user friendly.
1460
if (changing_type_index == slot.index) {
1461
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
1462
changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again.
1463
}
1464
1465
slot.prop->update_property();
1466
if (slot.prop_key) {
1467
slot.prop_key->update_property();
1468
}
1469
}
1470
updating = false;
1471
1472
} else {
1473
if (container) {
1474
set_bottom_editor(nullptr);
1475
memdelete(container);
1476
button_add_item = nullptr;
1477
container = nullptr;
1478
add_panel = nullptr;
1479
slots.clear();
1480
}
1481
}
1482
}
1483
1484
void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
1485
Dictionary dict = object->get_dict().duplicate();
1486
int index = slots[p_slot_index].index;
1487
dict.erase(dict.get_key_at_index(index));
1488
1489
emit_changed(get_edited_property(), dict);
1490
}
1491
1492
void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1493
emit_signal(SNAME("object_id_selected"), p_property, p_id);
1494
}
1495
1496
void EditorPropertyDictionary::_resource_selected(const String &p_path, Ref<Resource> p_resource) {
1497
emit_signal(SNAME("resource_selected"), get_edited_property(), p_resource);
1498
}
1499
1500
void EditorPropertyDictionary::_notification(int p_what) {
1501
switch (p_what) {
1502
case NOTIFICATION_THEME_CHANGED: {
1503
if (button_add_item) {
1504
add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));
1505
}
1506
} break;
1507
}
1508
}
1509
1510
void EditorPropertyDictionary::_edit_pressed() {
1511
Variant prop_val = get_edited_property_value();
1512
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1513
initialize_dictionary(prop_val);
1514
emit_changed(get_edited_property(), prop_val);
1515
}
1516
1517
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1518
update_property();
1519
}
1520
1521
void EditorPropertyDictionary::_page_changed(int p_page) {
1522
page_index = p_page;
1523
int i = p_page * page_length;
1524
for (Slot &slot : slots) {
1525
if (slot.index > -1) {
1526
slot.set_index(i);
1527
i++;
1528
}
1529
}
1530
if (updating) {
1531
return;
1532
}
1533
update_property();
1534
}
1535
1536
bool EditorPropertyDictionary::is_colored(ColorationMode p_mode) {
1537
return p_mode == COLORATION_CONTAINER_RESOURCE;
1538
}
1539
1540
EditorPropertyDictionary::EditorPropertyDictionary() {
1541
object.instantiate();
1542
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1543
1544
edit = memnew(Button);
1545
edit->set_accessibility_name(TTRC("Edit"));
1546
edit->set_h_size_flags(SIZE_EXPAND_FILL);
1547
edit->set_clip_text(true);
1548
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
1549
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_edit_pressed));
1550
edit->set_toggle_mode(true);
1551
add_child(edit);
1552
add_focusable(edit);
1553
1554
container = nullptr;
1555
button_add_item = nullptr;
1556
paginator = nullptr;
1557
change_type = memnew(EditorVariantTypePopupMenu(true));
1558
add_child(change_type);
1559
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
1560
changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE;
1561
has_borders = true;
1562
1563
key_subtype = Variant::NIL;
1564
key_subtype_hint = PROPERTY_HINT_NONE;
1565
key_subtype_hint_string = "";
1566
1567
value_subtype = Variant::NIL;
1568
value_subtype_hint = PROPERTY_HINT_NONE;
1569
value_subtype_hint_string = "";
1570
}
1571
1572
///////////////////// LOCALIZABLE STRING ///////////////////////////
1573
1574
void EditorPropertyLocalizableString::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
1575
if (p_property.begins_with("indices")) {
1576
int index = p_property.get_slicec('/', 1).to_int();
1577
1578
Dictionary dict = object->get_dict().duplicate();
1579
Variant key = dict.get_key_at_index(index);
1580
dict[key] = p_value;
1581
1582
object->set_dict(dict);
1583
emit_changed(get_edited_property(), dict, "", true);
1584
}
1585
}
1586
1587
void EditorPropertyLocalizableString::_add_locale_popup() {
1588
locale_select->popup_locale_dialog();
1589
}
1590
1591
void EditorPropertyLocalizableString::_add_locale(const String &p_locale) {
1592
Dictionary dict = object->get_dict().duplicate();
1593
object->set_new_item_key(p_locale);
1594
object->set_new_item_value(String());
1595
dict[object->get_new_item_key()] = object->get_new_item_value();
1596
1597
emit_changed(get_edited_property(), dict, "", false);
1598
update_property();
1599
}
1600
1601
void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) {
1602
Dictionary dict = object->get_dict().duplicate();
1603
1604
Variant key = dict.get_key_at_index(p_index);
1605
dict.erase(key);
1606
1607
emit_changed(get_edited_property(), dict, "", false);
1608
update_property();
1609
}
1610
1611
void EditorPropertyLocalizableString::update_property() {
1612
Variant updated_val = get_edited_property_value();
1613
1614
if (updated_val.get_type() == Variant::NIL) {
1615
edit->set_text(TTR("Localizable String (Nil)")); // This provides symmetry with the array property.
1616
edit->set_pressed(false);
1617
if (container) {
1618
set_bottom_editor(nullptr);
1619
memdelete(container);
1620
button_add_item = nullptr;
1621
container = nullptr;
1622
}
1623
return;
1624
}
1625
1626
Dictionary dict = updated_val;
1627
object->set_dict(dict);
1628
1629
edit->set_text(vformat(TTR("Localizable String (size %d)"), dict.size()));
1630
1631
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
1632
if (edit->is_pressed() != unfolded) {
1633
edit->set_pressed(unfolded);
1634
}
1635
1636
if (unfolded) {
1637
updating = true;
1638
1639
if (!container) {
1640
container = memnew(MarginContainer);
1641
container->set_theme_type_variation("MarginContainer4px");
1642
add_child(container);
1643
set_bottom_editor(container);
1644
1645
VBoxContainer *vbox = memnew(VBoxContainer);
1646
vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
1647
container->add_child(vbox);
1648
1649
property_vbox = memnew(VBoxContainer);
1650
property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));
1651
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1652
vbox->add_child(property_vbox);
1653
1654
paginator = memnew(EditorPaginator);
1655
paginator->connect("page_changed", callable_mp(this, &EditorPropertyLocalizableString::_page_changed));
1656
vbox->add_child(paginator);
1657
} else {
1658
// Queue children for deletion, deleting immediately might cause errors.
1659
for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
1660
property_vbox->get_child(i)->queue_free();
1661
}
1662
}
1663
1664
int size = dict.size();
1665
1666
int max_page = MAX(0, size - 1) / page_length;
1667
page_index = MIN(page_index, max_page);
1668
1669
paginator->update(page_index, max_page);
1670
paginator->set_visible(max_page > 0);
1671
1672
int offset = page_index * page_length;
1673
1674
int amount = MIN(size - offset, page_length);
1675
1676
for (int i = 0; i < amount; i++) {
1677
String prop_name;
1678
Variant key;
1679
Variant value;
1680
1681
prop_name = "indices/" + itos(i + offset);
1682
key = dict.get_key_at_index(i + offset);
1683
value = dict.get_value_at_index(i + offset);
1684
1685
EditorProperty *prop = memnew(EditorPropertyText);
1686
1687
prop->set_object_and_property(object.ptr(), prop_name);
1688
int remove_index = 0;
1689
1690
String cs = key.get_construct_string();
1691
prop->set_label(cs);
1692
prop->set_tooltip_text(cs);
1693
remove_index = i + offset;
1694
1695
prop->set_selectable(false);
1696
prop->connect("property_changed", callable_mp(this, &EditorPropertyLocalizableString::_property_changed));
1697
prop->connect("object_id_selected", callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected));
1698
1699
HBoxContainer *hbox = memnew(HBoxContainer);
1700
property_vbox->add_child(hbox);
1701
hbox->add_child(prop);
1702
prop->set_h_size_flags(SIZE_EXPAND_FILL);
1703
Button *edit_btn = memnew(Button);
1704
edit_btn->set_accessibility_name(TTRC("Remove Translation"));
1705
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
1706
hbox->add_child(edit_btn);
1707
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index));
1708
1709
prop->update_property();
1710
}
1711
1712
if (page_index == max_page) {
1713
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Translation"), SNAME("Add")));
1714
button_add_item->set_accessibility_name(TTRC("Add Translation"));
1715
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup));
1716
property_vbox->add_child(button_add_item);
1717
}
1718
1719
updating = false;
1720
1721
} else {
1722
if (container) {
1723
set_bottom_editor(nullptr);
1724
memdelete(container);
1725
button_add_item = nullptr;
1726
container = nullptr;
1727
}
1728
}
1729
}
1730
1731
void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1732
emit_signal(SNAME("object_id_selected"), p_property, p_id);
1733
}
1734
1735
void EditorPropertyLocalizableString::_edit_pressed() {
1736
Variant prop_val = get_edited_property_value();
1737
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1738
VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
1739
get_edited_object()->set(get_edited_property(), prop_val);
1740
}
1741
1742
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1743
update_property();
1744
}
1745
1746
void EditorPropertyLocalizableString::_page_changed(int p_page) {
1747
if (updating) {
1748
return;
1749
}
1750
page_index = p_page;
1751
update_property();
1752
}
1753
1754
EditorPropertyLocalizableString::EditorPropertyLocalizableString() {
1755
object.instantiate();
1756
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1757
1758
edit = memnew(Button);
1759
edit->set_accessibility_name(TTRC("Edit"));
1760
edit->set_h_size_flags(SIZE_EXPAND_FILL);
1761
edit->set_clip_text(true);
1762
edit->set_theme_type_variation(SNAME("EditorInspectorButton"));
1763
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed));
1764
edit->set_toggle_mode(true);
1765
add_child(edit);
1766
add_focusable(edit);
1767
1768
container = nullptr;
1769
button_add_item = nullptr;
1770
paginator = nullptr;
1771
updating = false;
1772
1773
locale_select = memnew(EditorLocaleDialog);
1774
locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyLocalizableString::_add_locale));
1775
add_child(locale_select);
1776
}
1777
1778