Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_properties_array_dict.cpp
9903 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::_create_new_property_slot() {
305
int idx = slots.size();
306
HBoxContainer *hbox = memnew(HBoxContainer);
307
308
Button *reorder_button = memnew(Button);
309
reorder_button->set_accessibility_name(TTRC("Reorder"));
310
reorder_button->set_button_icon(get_editor_theme_icon(SNAME("TripleBar")));
311
reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE);
312
reorder_button->set_disabled(is_read_only());
313
reorder_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input));
314
reorder_button->connect(SNAME("button_up"), callable_mp(this, &EditorPropertyArray::_reorder_button_up));
315
reorder_button->connect(SNAME("button_down"), callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(idx));
316
317
hbox->add_child(reorder_button);
318
EditorProperty *prop = memnew(EditorPropertyNil);
319
hbox->add_child(prop);
320
321
bool is_untyped_array = object->get_array().get_type() == Variant::ARRAY && subtype == Variant::NIL;
322
323
if (is_untyped_array) {
324
Button *edit_btn = memnew(Button);
325
edit_btn->set_accessibility_name(TTRC("Edit"));
326
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
327
edit_btn->set_disabled(is_read_only());
328
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, idx));
329
hbox->add_child(edit_btn);
330
} else {
331
Button *remove_btn = memnew(Button);
332
remove_btn->set_accessibility_name(TTRC("Remove"));
333
remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
334
remove_btn->set_disabled(is_read_only());
335
remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(idx));
336
hbox->add_child(remove_btn);
337
}
338
property_vbox->add_child(hbox);
339
340
Slot slot;
341
slot.prop = prop;
342
slot.object = object;
343
slot.container = hbox;
344
slot.reorder_button = reorder_button;
345
slot.set_index(idx + page_index * page_length);
346
slots.push_back(slot);
347
}
348
349
void EditorPropertyArray::set_preview_value(bool p_preview_value) {
350
preview_value = p_preview_value;
351
}
352
353
void EditorPropertyArray::update_property() {
354
Variant array = get_edited_property_value();
355
356
String array_type_name = Variant::get_type_name(array_type);
357
String array_sub_type_name;
358
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
359
String type_name;
360
if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
361
type_name = subtype_hint_string;
362
} else {
363
type_name = Variant::get_type_name(subtype);
364
}
365
366
if (preview_value) {
367
array_sub_type_name = vformat("[%s] ", type_name);
368
} else {
369
array_type_name = vformat("%s[%s]", array_type_name, type_name);
370
}
371
}
372
373
if (!array.is_array()) {
374
if (preview_value) {
375
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
376
edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));
377
edit->set_text(array_type_name);
378
} else {
379
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
380
edit->set_button_icon(Ref<Texture2D>());
381
edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
382
}
383
edit->set_pressed(false);
384
if (container) {
385
set_bottom_editor(nullptr);
386
memdelete(container);
387
button_add_item = nullptr;
388
container = nullptr;
389
slots.clear();
390
}
391
return;
392
}
393
394
object->set_array(array);
395
396
int size = array.call("size");
397
int max_page = MAX(0, size - 1) / page_length;
398
if (page_index > max_page) {
399
_page_changed(max_page);
400
}
401
402
if (preview_value) {
403
String ctr_str = array.get_construct_string().trim_prefix(array_type_name + "(").trim_suffix(")").remove_char('\n');
404
if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
405
int type_end = ctr_str.find("](");
406
if (type_end > 0) {
407
ctr_str = ctr_str.substr(type_end + 2);
408
}
409
}
410
411
edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
412
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
413
edit->set_button_icon(get_editor_theme_icon(array_type_name));
414
edit->set_text(vformat("%s%s", array_sub_type_name, ctr_str));
415
edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), array_type_name, array_sub_type_name, size));
416
} else {
417
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
418
edit->set_button_icon(Ref<Texture2D>());
419
edit->set_text(vformat(TTR("%s (size %d)"), array_type_name, size));
420
}
421
422
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
423
if (edit->is_pressed() != unfolded) {
424
edit->set_pressed(unfolded);
425
}
426
427
if (unfolded) {
428
updating = true;
429
430
if (!container) {
431
container = memnew(PanelContainer);
432
add_child(container);
433
set_bottom_editor(container);
434
435
VBoxContainer *vbox = memnew(VBoxContainer);
436
container->add_child(vbox);
437
438
HBoxContainer *hbox = memnew(HBoxContainer);
439
vbox->add_child(hbox);
440
441
Label *size_label = memnew(Label(TTR("Size:")));
442
size_label->set_h_size_flags(SIZE_EXPAND_FILL);
443
hbox->add_child(size_label);
444
445
size_slider = memnew(EditorSpinSlider);
446
size_slider->set_step(1);
447
size_slider->set_max(INT32_MAX);
448
size_slider->set_editing_integer(true);
449
size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
450
size_slider->set_read_only(is_read_only());
451
size_slider->set_accessibility_name(TTRC("Size"));
452
size_slider->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyArray::_length_changed));
453
hbox->add_child(size_slider);
454
455
property_vbox = memnew(VBoxContainer);
456
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
457
vbox->add_child(property_vbox);
458
459
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Element"), SNAME("Add")));
460
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_add_element));
461
button_add_item->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_add_item_draw));
462
SET_DRAG_FORWARDING_CD(button_add_item, EditorPropertyArray);
463
button_add_item->set_disabled(is_read_only());
464
button_add_item->set_accessibility_name(TTRC("Add"));
465
vbox->add_child(button_add_item);
466
467
paginator = memnew(EditorPaginator);
468
paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed));
469
vbox->add_child(paginator);
470
471
for (int i = 0; i < page_length; i++) {
472
_create_new_property_slot();
473
}
474
}
475
476
size_slider->set_value(size);
477
property_vbox->set_visible(size > 0);
478
button_add_item->set_visible(page_index == max_page);
479
paginator->update(page_index, max_page);
480
paginator->set_visible(max_page > 0);
481
482
for (Slot &slot : slots) {
483
bool slot_visible = &slot != &reorder_slot && slot.index < size;
484
slot.container->set_visible(slot_visible);
485
// If not visible no need to update it
486
if (!slot_visible) {
487
continue;
488
}
489
490
int idx = slot.index;
491
Variant::Type value_type = subtype;
492
493
if (value_type == Variant::NIL) {
494
value_type = array.get(idx).get_type();
495
}
496
497
// Check if the editor property needs to be updated.
498
bool value_as_id = Object::cast_to<EncodedObjectAsID>(array.get(idx));
499
if (value_type != slot.type || (value_type == Variant::OBJECT && (value_as_id != slot.as_id))) {
500
slot.as_id = value_as_id;
501
slot.type = value_type;
502
EditorProperty *new_prop = nullptr;
503
if (value_type == Variant::OBJECT && value_as_id) {
504
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
505
editor->setup("Object");
506
new_prop = editor;
507
} else {
508
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
509
}
510
new_prop->set_selectable(false);
511
new_prop->set_use_folding(is_using_folding());
512
new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyArray::_property_changed));
513
new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyArray::_object_id_selected));
514
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
515
new_prop->set_read_only(is_read_only());
516
slot.prop->add_sibling(new_prop, false);
517
slot.prop->queue_free();
518
slot.prop = new_prop;
519
slot.set_index(idx);
520
}
521
if (slot.index == changing_type_index) {
522
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
523
changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;
524
}
525
slot.prop->update_property();
526
}
527
528
updating = false;
529
530
} else {
531
if (container) {
532
set_bottom_editor(nullptr);
533
memdelete(container);
534
button_add_item = nullptr;
535
container = nullptr;
536
slots.clear();
537
}
538
}
539
}
540
541
void EditorPropertyArray::_remove_pressed(int p_slot_index) {
542
Variant array = object->get_array().duplicate();
543
array.call("remove_at", slots[p_slot_index].index);
544
545
emit_changed(get_edited_property(), array);
546
}
547
548
void EditorPropertyArray::_button_draw() {
549
if (dropping) {
550
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
551
edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false);
552
}
553
}
554
555
void EditorPropertyArray::_button_add_item_draw() {
556
if (dropping) {
557
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
558
button_add_item->draw_rect(Rect2(Point2(), button_add_item->get_size()), color, false);
559
}
560
}
561
562
bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {
563
if (is_read_only()) {
564
return false;
565
}
566
567
String allowed_type = Variant::get_type_name(subtype);
568
569
// When the subtype is of type Object, an additional subtype may be specified in the hint string
570
// (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object".
571
if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
572
allowed_type = subtype_hint_string;
573
}
574
575
Dictionary drag_data = p_drag_data;
576
const String drop_type = drag_data.get("type", "");
577
578
if (drop_type == "files") {
579
PackedStringArray files = drag_data["files"];
580
581
for (const String &file : files) {
582
int idx_in_dir;
583
EditorFileSystemDirectory const *dir = EditorFileSystem::get_singleton()->find_file(file, &idx_in_dir);
584
if (!dir) {
585
return false;
586
}
587
StringName ftype = dir->get_file_type(idx_in_dir);
588
String script_class = dir->get_file_resource_script_class(idx_in_dir);
589
590
for (String at : allowed_type.split(",", false)) {
591
at = at.strip_edges();
592
// Fail if one of the files is not of allowed type.
593
if (!ClassDB::is_parent_class(ftype, at) && !EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {
594
return false;
595
}
596
}
597
}
598
599
// If no files fail, drop is valid.
600
return true;
601
}
602
603
if (drop_type == "resource") {
604
Ref<Resource> res = drag_data["resource"];
605
if (res.is_null()) {
606
return false;
607
}
608
609
String res_type = res->get_class();
610
StringName script_class;
611
if (res->get_script()) {
612
script_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());
613
}
614
615
for (String at : allowed_type.split(",", false)) {
616
at = at.strip_edges();
617
if (ClassDB::is_parent_class(res_type, at) || EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {
618
return true;
619
}
620
}
621
622
return false;
623
}
624
625
if (drop_type == "nodes") {
626
Array node_paths = drag_data["nodes"];
627
628
PackedStringArray allowed_subtype_array;
629
if (allowed_type == "NodePath") {
630
if (subtype_hint_string == "NodePath") {
631
return true;
632
} else {
633
for (String ast : subtype_hint_string.split(",", false)) {
634
ast = ast.strip_edges();
635
allowed_subtype_array.append(ast);
636
}
637
}
638
}
639
640
bool is_drop_allowed = true;
641
642
for (int i = 0; i < node_paths.size(); i++) {
643
const Node *dropped_node = get_node_or_null(node_paths[i]);
644
ERR_FAIL_NULL_V_MSG(dropped_node, false, "Could not get the dropped node by its path.");
645
646
if (allowed_type != "NodePath") {
647
if (!ClassDB::is_parent_class(dropped_node->get_class_name(), allowed_type) &&
648
!EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, allowed_type)) {
649
// Fail if one of the nodes is not of allowed type.
650
return false;
651
}
652
}
653
654
// The array of NodePaths is restricted to specific types using @export_node_path().
655
if (allowed_type == "NodePath" && subtype_hint_string != "NodePath") {
656
if (!allowed_subtype_array.has(dropped_node->get_class_name())) {
657
// The dropped node type was not found in the allowed subtype array, we must check if it inherits one of them.
658
for (const String &ast : allowed_subtype_array) {
659
if (ClassDB::is_parent_class(dropped_node->get_class_name(), ast) ||
660
EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, ast)) {
661
is_drop_allowed = true;
662
break;
663
} else {
664
is_drop_allowed = false;
665
}
666
}
667
if (!is_drop_allowed) {
668
break;
669
}
670
}
671
}
672
}
673
674
return is_drop_allowed;
675
}
676
677
return false;
678
}
679
680
bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
681
return _is_drop_valid(p_data);
682
}
683
684
void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
685
Dictionary drag_data = p_data;
686
const String drop_type = drag_data.get("type", "");
687
Variant array = object->get_array();
688
689
// Handle the case where array is not initialized yet.
690
if (!array.is_array()) {
691
initialize_array(array);
692
} else {
693
array = array.duplicate();
694
}
695
696
if (drop_type == "files") {
697
PackedStringArray files = drag_data["files"];
698
699
// Loop the file array and add to existing array.
700
for (int i = 0; i < files.size(); i++) {
701
const String &file = files[i];
702
703
Ref<Resource> res = ResourceLoader::load(file);
704
if (res.is_valid()) {
705
array.call("push_back", res);
706
}
707
}
708
709
emit_changed(get_edited_property(), array);
710
}
711
712
if (drop_type == "resource") {
713
Ref<Resource> res = drag_data["resource"];
714
715
if (res.is_valid()) {
716
array.call("push_back", res);
717
718
emit_changed(get_edited_property(), array);
719
}
720
}
721
722
if (drop_type == "nodes") {
723
Array node_paths = drag_data["nodes"];
724
Node *base_node = get_base_node();
725
726
for (int i = 0; i < node_paths.size(); i++) {
727
const NodePath &path = node_paths[i];
728
729
if (subtype == Variant::OBJECT) {
730
array.call("push_back", get_node(path));
731
} else if (subtype == Variant::NODE_PATH) {
732
array.call("push_back", base_node->get_path().rel_path_to(path));
733
}
734
}
735
736
emit_changed(get_edited_property(), array);
737
}
738
}
739
740
Node *EditorPropertyArray::get_base_node() {
741
Node *base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());
742
743
if (!base_node) {
744
base_node = get_tree()->get_edited_scene_root();
745
}
746
747
return base_node;
748
}
749
750
void EditorPropertyArray::_notification(int p_what) {
751
switch (p_what) {
752
case NOTIFICATION_DRAG_BEGIN: {
753
if (is_visible_in_tree()) {
754
if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {
755
dropping = true;
756
edit->queue_redraw();
757
if (button_add_item) {
758
button_add_item->queue_redraw();
759
}
760
}
761
}
762
} break;
763
764
case NOTIFICATION_DRAG_END: {
765
if (dropping) {
766
dropping = false;
767
edit->queue_redraw();
768
if (button_add_item) {
769
button_add_item->queue_redraw();
770
}
771
}
772
} break;
773
}
774
}
775
776
void EditorPropertyArray::_edit_pressed() {
777
Variant array = get_edited_property_value();
778
if (!array.is_array() && edit->is_pressed()) {
779
initialize_array(array);
780
emit_changed(get_edited_property(), array);
781
}
782
783
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
784
update_property();
785
}
786
787
void EditorPropertyArray::_page_changed(int p_page) {
788
if (updating) {
789
return;
790
}
791
page_index = p_page;
792
int i = p_page * page_length;
793
794
if (reorder_slot.index < 0) {
795
for (Slot &slot : slots) {
796
slot.set_index(i);
797
i++;
798
}
799
} else {
800
int reorder_from_page = reorder_slot.index / page_length;
801
if (reorder_from_page < p_page) {
802
i++;
803
}
804
for (Slot &slot : slots) {
805
if (slot.index != reorder_slot.index) {
806
slot.set_index(i);
807
i++;
808
} else if (i == reorder_slot.index) {
809
i++;
810
}
811
}
812
}
813
update_property();
814
}
815
816
void EditorPropertyArray::_length_changed(double p_page) {
817
if (updating) {
818
return;
819
}
820
821
Variant array = object->get_array().duplicate();
822
array.call("resize", int(p_page));
823
824
emit_changed(get_edited_property(), array);
825
}
826
827
void EditorPropertyArray::_add_element() {
828
_length_changed(double(object->get_array().call("size")) + 1.0);
829
}
830
831
void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {
832
array_type = p_array_type;
833
834
// The format of p_hint_string is:
835
// subType/subTypeHint:nextSubtype ... etc.
836
if (!p_hint_string.is_empty()) {
837
int hint_subtype_separator = p_hint_string.find_char(':');
838
if (hint_subtype_separator >= 0) {
839
String subtype_string = p_hint_string.substr(0, hint_subtype_separator);
840
int slash_pos = subtype_string.find_char('/');
841
if (slash_pos >= 0) {
842
subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1).to_int());
843
subtype_string = subtype_string.substr(0, slash_pos);
844
}
845
846
subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1);
847
subtype = Variant::Type(subtype_string.to_int());
848
} else {
849
subtype = Variant::get_type_by_name(p_hint_string);
850
851
if (subtype == Variant::VARIANT_MAX) {
852
subtype = Variant::OBJECT;
853
subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
854
subtype_hint_string = p_hint_string;
855
}
856
}
857
}
858
}
859
860
void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) {
861
if (reorder_slot.index < 0 || is_read_only()) {
862
return;
863
}
864
865
Ref<InputEventMouseMotion> mm = p_event;
866
if (mm.is_valid()) {
867
Variant array = object->get_array();
868
int size = array.call("size");
869
870
// Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point.
871
reorder_mouse_y_delta += mm->get_relative().y;
872
873
// Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if
874
// already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta).
875
if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) {
876
reorder_mouse_y_delta = 0.0f;
877
return;
878
}
879
880
float required_y_distance = 20.0f * EDSCALE;
881
if (Math::abs(reorder_mouse_y_delta) > required_y_distance) {
882
int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1;
883
reorder_mouse_y_delta -= required_y_distance * direction;
884
885
reorder_to_index += direction;
886
887
property_vbox->move_child(reorder_slot.container, reorder_to_index % page_length);
888
889
if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) {
890
// Automatically move to the next/previous page.
891
_page_changed(page_index + direction);
892
}
893
// Ensure the moving element is visible in the root inspector.
894
EditorInspector *parent_inspector = get_parent_inspector();
895
if (parent_inspector) {
896
// Defer to prevent moving elements from not displaying properly, especially near borders.
897
callable_mp((ScrollContainer *)parent_inspector->get_root_inspector(), &ScrollContainer::ensure_control_visible).call_deferred(reorder_slot.container);
898
}
899
}
900
}
901
}
902
903
void EditorPropertyArray::_reorder_button_down(int p_slot_index) {
904
if (is_read_only()) {
905
return;
906
}
907
reorder_slot = slots[p_slot_index];
908
reorder_to_index = reorder_slot.index;
909
// Ideally it'd to be able to show the mouse but I had issues with
910
// Control's `mouse_exit()`/`mouse_entered()` signals not getting called.
911
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
912
}
913
914
void EditorPropertyArray::_reorder_button_up() {
915
if (is_read_only()) {
916
return;
917
}
918
919
if (reorder_slot.index != reorder_to_index) {
920
// Move the element.
921
Variant array = object->get_array().duplicate();
922
923
property_vbox->move_child(reorder_slot.container, reorder_slot.index % page_length);
924
Variant value_to_move = array.get(reorder_slot.index);
925
array.call("remove_at", reorder_slot.index);
926
array.call("insert", reorder_to_index, value_to_move);
927
928
slots[reorder_to_index % page_length].reorder_button->grab_focus();
929
emit_changed(get_edited_property(), array);
930
}
931
932
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
933
934
ERR_FAIL_NULL(reorder_slot.reorder_button);
935
reorder_slot.reorder_button->warp_mouse(reorder_slot.reorder_button->get_size() / 2.0f);
936
reorder_to_index = -1;
937
reorder_mouse_y_delta = 0.0f;
938
reorder_slot = Slot();
939
_page_changed(page_index);
940
}
941
942
bool EditorPropertyArray::is_colored(ColorationMode p_mode) {
943
return p_mode == COLORATION_CONTAINER_RESOURCE;
944
}
945
946
EditorPropertyArray::EditorPropertyArray() {
947
object.instantiate();
948
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
949
950
edit = memnew(Button);
951
edit->set_accessibility_name(TTRC("Edit"));
952
edit->set_h_size_flags(SIZE_EXPAND_FILL);
953
edit->set_clip_text(true);
954
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_edit_pressed));
955
edit->set_toggle_mode(true);
956
SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray);
957
edit->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_draw));
958
add_child(edit);
959
add_focusable(edit);
960
961
change_type = memnew(EditorVariantTypePopupMenu(true));
962
add_child(change_type);
963
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyArray::_change_type_menu));
964
changing_type_index = -1;
965
966
subtype = Variant::NIL;
967
subtype_hint = PROPERTY_HINT_NONE;
968
subtype_hint_string = "";
969
has_borders = true;
970
}
971
972
///////////////////// DICTIONARY ///////////////////////////
973
974
void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
975
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
976
Dictionary dict;
977
StringName key_subtype_class;
978
Ref<Script> key_subtype_script;
979
if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
980
key_subtype_class = key_subtype_hint_string;
981
}
982
StringName value_subtype_class;
983
Ref<Script> value_subtype_script;
984
if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
985
value_subtype_class = value_subtype_hint_string;
986
}
987
dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
988
p_dictionary = dict;
989
} else {
990
VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
991
}
992
}
993
994
void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
995
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
996
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
997
}
998
999
object->set(p_property, p_value);
1000
bool new_item_or_key = !p_property.begins_with("indices");
1001
emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing || new_item_or_key);
1002
if (new_item_or_key) {
1003
update_property();
1004
}
1005
}
1006
1007
void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) {
1008
Button *button = Object::cast_to<Button>(p_button);
1009
int index = slots[p_slot_index].index;
1010
Rect2 rect = button->get_screen_rect();
1011
change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), index < 0);
1012
change_type->reset_size();
1013
change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
1014
change_type->popup();
1015
changing_type_index = index;
1016
}
1017
1018
void EditorPropertyDictionary::_add_key_value() {
1019
// Do not allow nil as valid key. I experienced errors with this
1020
if (object->get_new_item_key().get_type() == Variant::NIL) {
1021
return;
1022
}
1023
1024
Dictionary dict = object->get_dict().duplicate();
1025
Variant new_key = object->get_new_item_key();
1026
Variant new_value = object->get_new_item_value();
1027
dict[new_key] = new_value;
1028
1029
Variant::Type type = new_key.get_type();
1030
new_key.zero();
1031
VariantInternal::initialize(&new_key, type);
1032
object->set_new_item_key(new_key);
1033
1034
type = new_value.get_type();
1035
new_value.zero();
1036
VariantInternal::initialize(&new_value, type);
1037
object->set_new_item_value(new_value);
1038
1039
object->set_dict(dict);
1040
slots[(dict.size() - 1) % page_length].update_prop_or_index();
1041
emit_changed(get_edited_property(), dict);
1042
}
1043
1044
void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
1045
HBoxContainer *hbox = memnew(HBoxContainer);
1046
1047
EditorProperty *prop_key = nullptr;
1048
if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1049
prop_key = memnew(EditorPropertyNil);
1050
hbox->add_child(prop_key);
1051
}
1052
1053
EditorProperty *prop = memnew(EditorPropertyNil);
1054
prop->set_h_size_flags(SIZE_EXPAND_FILL);
1055
if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1056
prop->set_draw_label(false);
1057
}
1058
hbox->add_child(prop);
1059
1060
bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
1061
bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
1062
1063
if (is_untyped_dict) {
1064
Button *edit_btn = memnew(Button);
1065
edit_btn->set_accessibility_name(TTRC("Edit"));
1066
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
1067
edit_btn->set_disabled(is_read_only());
1068
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
1069
hbox->add_child(edit_btn);
1070
} else if (p_idx >= 0) {
1071
Button *remove_btn = memnew(Button);
1072
remove_btn->set_accessibility_name(TTRC("Remove"));
1073
remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
1074
remove_btn->set_disabled(is_read_only());
1075
remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
1076
hbox->add_child(remove_btn);
1077
}
1078
1079
if (add_panel) {
1080
add_panel->get_child(0)->add_child(hbox);
1081
} else {
1082
property_vbox->add_child(hbox);
1083
}
1084
1085
Slot slot;
1086
slot.prop = prop;
1087
slot.prop_key = prop_key;
1088
slot.object = object;
1089
slot.container = hbox;
1090
int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0);
1091
slot.set_index(index);
1092
slots.push_back(slot);
1093
}
1094
1095
void EditorPropertyDictionary::_change_type_menu(int p_index) {
1096
ERR_FAIL_COND_MSG(
1097
changing_type_index == EditorPropertyDictionaryObject::NOT_CHANGING_TYPE,
1098
"Tried to change the type of a dict key or value, but nothing was selected.");
1099
1100
Variant value;
1101
switch (changing_type_index) {
1102
case EditorPropertyDictionaryObject::NEW_KEY_INDEX:
1103
case EditorPropertyDictionaryObject::NEW_VALUE_INDEX:
1104
VariantInternal::initialize(&value, Variant::Type(p_index));
1105
if (changing_type_index == EditorPropertyDictionaryObject::NEW_KEY_INDEX) {
1106
object->set_new_item_key(value);
1107
} else {
1108
object->set_new_item_value(value);
1109
}
1110
update_property();
1111
break;
1112
1113
default:
1114
Dictionary dict = object->get_dict().duplicate();
1115
Variant key = dict.get_key_at_index(changing_type_index);
1116
if (p_index < Variant::VARIANT_MAX) {
1117
VariantInternal::initialize(&value, Variant::Type(p_index));
1118
dict[key] = value;
1119
} else {
1120
dict.erase(key);
1121
object->set_dict(dict);
1122
for (Slot &slot : slots) {
1123
slot.update_prop_or_index();
1124
}
1125
}
1126
1127
emit_changed(get_edited_property(), dict);
1128
}
1129
}
1130
1131
void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
1132
PackedStringArray types = p_hint_string.split(";");
1133
if (types.size() > 0 && !types[0].is_empty()) {
1134
String key = types[0];
1135
int hint_key_subtype_separator = key.find_char(':');
1136
if (hint_key_subtype_separator >= 0) {
1137
String key_subtype_string = key.substr(0, hint_key_subtype_separator);
1138
int slash_pos = key_subtype_string.find_char('/');
1139
if (slash_pos >= 0) {
1140
key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1).to_int());
1141
key_subtype_string = key_subtype_string.substr(0, slash_pos);
1142
}
1143
1144
key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1);
1145
key_subtype = Variant::Type(key_subtype_string.to_int());
1146
} else {
1147
key_subtype = Variant::get_type_by_name(key);
1148
1149
if (key_subtype == Variant::VARIANT_MAX) {
1150
key_subtype = Variant::OBJECT;
1151
key_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
1152
key_subtype_hint_string = key;
1153
}
1154
}
1155
1156
Variant new_key = object->get_new_item_key();
1157
VariantInternal::initialize(&new_key, key_subtype);
1158
object->set_new_item_key(new_key);
1159
}
1160
1161
if (types.size() > 1 && !types[1].is_empty()) {
1162
String value = types[1];
1163
int hint_value_subtype_separator = value.find_char(':');
1164
if (hint_value_subtype_separator >= 0) {
1165
String value_subtype_string = value.substr(0, hint_value_subtype_separator);
1166
int slash_pos = value_subtype_string.find_char('/');
1167
if (slash_pos >= 0) {
1168
value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1).to_int());
1169
value_subtype_string = value_subtype_string.substr(0, slash_pos);
1170
}
1171
1172
value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1);
1173
value_subtype = Variant::Type(value_subtype_string.to_int());
1174
} else {
1175
value_subtype = Variant::get_type_by_name(value);
1176
1177
if (value_subtype == Variant::VARIANT_MAX) {
1178
value_subtype = Variant::OBJECT;
1179
value_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;
1180
value_subtype_hint_string = value;
1181
}
1182
}
1183
1184
Variant new_value = object->get_new_item_value();
1185
VariantInternal::initialize(&new_value, value_subtype);
1186
object->set_new_item_value(new_value);
1187
}
1188
}
1189
1190
void EditorPropertyDictionary::set_preview_value(bool p_preview_value) {
1191
preview_value = p_preview_value;
1192
}
1193
1194
void EditorPropertyDictionary::update_property() {
1195
Variant updated_val = get_edited_property_value();
1196
1197
String dict_type_name = "Dictionary";
1198
String dict_sub_type_name;
1199
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
1200
String key_subtype_name = "Variant";
1201
if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
1202
key_subtype_name = key_subtype_hint_string;
1203
} else if (key_subtype != Variant::NIL) {
1204
key_subtype_name = Variant::get_type_name(key_subtype);
1205
}
1206
String value_subtype_name = "Variant";
1207
if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
1208
value_subtype_name = value_subtype_hint_string;
1209
} else if (value_subtype != Variant::NIL) {
1210
value_subtype_name = Variant::get_type_name(value_subtype);
1211
}
1212
if (preview_value) {
1213
dict_sub_type_name = vformat("[%s, %s] ", key_subtype_name, value_subtype_name);
1214
} else {
1215
dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
1216
}
1217
}
1218
1219
if (updated_val.get_type() != Variant::DICTIONARY) {
1220
if (preview_value) {
1221
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
1222
edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));
1223
edit->set_text(dict_type_name);
1224
} else {
1225
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1226
edit->set_button_icon(Ref<Texture2D>());
1227
edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name));
1228
}
1229
edit->set_pressed(false);
1230
if (container) {
1231
set_bottom_editor(nullptr);
1232
memdelete(container);
1233
button_add_item = nullptr;
1234
container = nullptr;
1235
add_panel = nullptr;
1236
slots.clear();
1237
}
1238
return;
1239
}
1240
1241
Dictionary dict = updated_val;
1242
object->set_dict(updated_val);
1243
1244
if (preview_value) {
1245
String ctr_str = updated_val.get_construct_string().remove_char('\n');
1246
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
1247
int type_end = ctr_str.find("](");
1248
if (type_end > 0) {
1249
ctr_str = ctr_str.substr(type_end + 2).trim_suffix(")");
1250
}
1251
}
1252
1253
edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1254
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
1255
edit->set_button_icon(get_editor_theme_icon(dict_type_name));
1256
edit->set_text(vformat("%s%s", dict_sub_type_name, ctr_str));
1257
edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), dict_type_name, dict_sub_type_name, dict.size()));
1258
} else {
1259
edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
1260
edit->set_button_icon(Ref<Texture2D>());
1261
edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
1262
}
1263
1264
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
1265
if (edit->is_pressed() != unfolded) {
1266
edit->set_pressed(unfolded);
1267
}
1268
1269
if (unfolded) {
1270
updating = true;
1271
1272
if (!container) {
1273
container = memnew(PanelContainer);
1274
add_child(container);
1275
set_bottom_editor(container);
1276
1277
VBoxContainer *vbox = memnew(VBoxContainer);
1278
container->add_child(vbox);
1279
1280
property_vbox = memnew(VBoxContainer);
1281
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1282
vbox->add_child(property_vbox);
1283
1284
paginator = memnew(EditorPaginator);
1285
paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));
1286
vbox->add_child(paginator);
1287
1288
for (int i = 0; i < page_length; i++) {
1289
_create_new_property_slot(slots.size());
1290
}
1291
1292
add_panel = memnew(PanelContainer);
1293
property_vbox->add_child(add_panel);
1294
add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));
1295
VBoxContainer *add_vbox = memnew(VBoxContainer);
1296
add_panel->add_child(add_vbox);
1297
1298
_create_new_property_slot(EditorPropertyDictionaryObject::NEW_KEY_INDEX);
1299
_create_new_property_slot(EditorPropertyDictionaryObject::NEW_VALUE_INDEX);
1300
1301
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Key/Value Pair"), SNAME("Add")));
1302
button_add_item->set_disabled(is_read_only());
1303
button_add_item->set_accessibility_name(TTRC("Add"));
1304
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_add_key_value));
1305
add_vbox->add_child(button_add_item);
1306
}
1307
1308
int size = dict.size();
1309
1310
int max_page = MAX(0, size - 1) / page_length;
1311
if (page_index > max_page) {
1312
_page_changed(max_page);
1313
}
1314
1315
paginator->update(page_index, max_page);
1316
paginator->set_visible(max_page > 0);
1317
1318
add_panel->set_visible(page_index == max_page);
1319
1320
for (Slot &slot : slots) {
1321
bool slot_visible = slot.index < size;
1322
slot.container->set_visible(slot_visible);
1323
// If not visible no need to update it.
1324
if (!slot_visible) {
1325
continue;
1326
}
1327
1328
// Check if the editor property key needs to be updated.
1329
if (slot.prop_key) {
1330
Variant key;
1331
object->get_by_property_name(slot.key_name, key);
1332
Variant::Type key_type = key.get_type();
1333
1334
bool key_as_id = Object::cast_to<EncodedObjectAsID>(key);
1335
if (key_type != slot.key_type || (key_type == Variant::OBJECT && key_as_id != slot.key_as_id)) {
1336
slot.key_as_id = key_as_id;
1337
slot.key_type = key_type;
1338
EditorProperty *new_prop = nullptr;
1339
if (key_type == Variant::OBJECT && key_as_id) {
1340
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
1341
editor->setup("Object");
1342
new_prop = editor;
1343
} else {
1344
new_prop = EditorInspector::instantiate_property_editor(this, key_type, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE);
1345
}
1346
new_prop->set_read_only(true);
1347
new_prop->set_selectable(false);
1348
new_prop->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
1349
new_prop->set_draw_background(false);
1350
new_prop->set_use_folding(is_using_folding());
1351
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
1352
new_prop->set_draw_label(false);
1353
EditorPropertyArray *arr_prop = Object::cast_to<EditorPropertyArray>(new_prop);
1354
if (arr_prop) {
1355
arr_prop->set_preview_value(true);
1356
}
1357
EditorPropertyDictionary *dict_prop = Object::cast_to<EditorPropertyDictionary>(new_prop);
1358
if (dict_prop) {
1359
dict_prop->set_preview_value(true);
1360
}
1361
slot.set_key_prop(new_prop);
1362
}
1363
}
1364
1365
Variant value;
1366
object->get_by_property_name(slot.prop_name, value);
1367
1368
Variant::Type value_type;
1369
1370
if (dict.is_typed_value() && value_subtype != Variant::NIL && slot.prop_key) {
1371
value_type = value_subtype;
1372
} else {
1373
value_type = value.get_type();
1374
}
1375
1376
// Check if the editor property needs to be updated.
1377
bool value_as_id = Object::cast_to<EncodedObjectAsID>(value);
1378
if (value_type != slot.type || (value_type == Variant::OBJECT && value_as_id != slot.as_id)) {
1379
slot.as_id = value_as_id;
1380
slot.type = value_type;
1381
EditorProperty *new_prop = nullptr;
1382
if (value_type == Variant::OBJECT && value_as_id) {
1383
EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
1384
editor->setup("Object");
1385
new_prop = editor;
1386
} else {
1387
bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
1388
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
1389
use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
1390
}
1391
new_prop->set_selectable(false);
1392
new_prop->set_use_folding(is_using_folding());
1393
new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed));
1394
new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
1395
new_prop->set_h_size_flags(SIZE_EXPAND_FILL);
1396
if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1397
new_prop->set_draw_label(false);
1398
}
1399
new_prop->set_read_only(is_read_only());
1400
slot.set_prop(new_prop);
1401
} else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {
1402
Variant key = dict.get_key_at_index(slot.index);
1403
String cs = key.get_construct_string();
1404
slot.prop->set_tooltip_text(cs);
1405
}
1406
1407
// We need to grab the focus of the property that is being changed, even if the type didn't actually changed.
1408
// Otherwise, focus will stay on the change type button, which is not very user friendly.
1409
if (changing_type_index == slot.index) {
1410
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
1411
changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again.
1412
}
1413
1414
slot.prop->update_property();
1415
if (slot.prop_key) {
1416
slot.prop_key->update_property();
1417
}
1418
}
1419
updating = false;
1420
1421
} else {
1422
if (container) {
1423
set_bottom_editor(nullptr);
1424
memdelete(container);
1425
button_add_item = nullptr;
1426
container = nullptr;
1427
add_panel = nullptr;
1428
slots.clear();
1429
}
1430
}
1431
}
1432
1433
void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
1434
Dictionary dict = object->get_dict().duplicate();
1435
int index = slots[p_slot_index].index;
1436
dict.erase(dict.get_key_at_index(index));
1437
1438
emit_changed(get_edited_property(), dict);
1439
}
1440
1441
void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1442
emit_signal(SNAME("object_id_selected"), p_property, p_id);
1443
}
1444
1445
void EditorPropertyDictionary::_notification(int p_what) {
1446
switch (p_what) {
1447
case NOTIFICATION_THEME_CHANGED: {
1448
if (button_add_item) {
1449
add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));
1450
}
1451
} break;
1452
}
1453
}
1454
1455
void EditorPropertyDictionary::_edit_pressed() {
1456
Variant prop_val = get_edited_property_value();
1457
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1458
initialize_dictionary(prop_val);
1459
emit_changed(get_edited_property(), prop_val);
1460
}
1461
1462
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1463
update_property();
1464
}
1465
1466
void EditorPropertyDictionary::_page_changed(int p_page) {
1467
page_index = p_page;
1468
int i = p_page * page_length;
1469
for (Slot &slot : slots) {
1470
if (slot.index > -1) {
1471
slot.set_index(i);
1472
i++;
1473
}
1474
}
1475
if (updating) {
1476
return;
1477
}
1478
update_property();
1479
}
1480
1481
bool EditorPropertyDictionary::is_colored(ColorationMode p_mode) {
1482
return p_mode == COLORATION_CONTAINER_RESOURCE;
1483
}
1484
1485
EditorPropertyDictionary::EditorPropertyDictionary() {
1486
object.instantiate();
1487
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1488
1489
edit = memnew(Button);
1490
edit->set_accessibility_name(TTRC("Edit"));
1491
edit->set_h_size_flags(SIZE_EXPAND_FILL);
1492
edit->set_clip_text(true);
1493
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_edit_pressed));
1494
edit->set_toggle_mode(true);
1495
add_child(edit);
1496
add_focusable(edit);
1497
1498
container = nullptr;
1499
button_add_item = nullptr;
1500
paginator = nullptr;
1501
change_type = memnew(EditorVariantTypePopupMenu(true));
1502
add_child(change_type);
1503
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
1504
changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE;
1505
has_borders = true;
1506
1507
key_subtype = Variant::NIL;
1508
key_subtype_hint = PROPERTY_HINT_NONE;
1509
key_subtype_hint_string = "";
1510
1511
value_subtype = Variant::NIL;
1512
value_subtype_hint = PROPERTY_HINT_NONE;
1513
value_subtype_hint_string = "";
1514
}
1515
1516
///////////////////// LOCALIZABLE STRING ///////////////////////////
1517
1518
void EditorPropertyLocalizableString::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {
1519
if (p_property.begins_with("indices")) {
1520
int index = p_property.get_slicec('/', 1).to_int();
1521
1522
Dictionary dict = object->get_dict().duplicate();
1523
Variant key = dict.get_key_at_index(index);
1524
dict[key] = p_value;
1525
1526
object->set_dict(dict);
1527
emit_changed(get_edited_property(), dict, "", true);
1528
}
1529
}
1530
1531
void EditorPropertyLocalizableString::_add_locale_popup() {
1532
locale_select->popup_locale_dialog();
1533
}
1534
1535
void EditorPropertyLocalizableString::_add_locale(const String &p_locale) {
1536
Dictionary dict = object->get_dict().duplicate();
1537
object->set_new_item_key(p_locale);
1538
object->set_new_item_value(String());
1539
dict[object->get_new_item_key()] = object->get_new_item_value();
1540
1541
emit_changed(get_edited_property(), dict, "", false);
1542
update_property();
1543
}
1544
1545
void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) {
1546
Dictionary dict = object->get_dict().duplicate();
1547
1548
Variant key = dict.get_key_at_index(p_index);
1549
dict.erase(key);
1550
1551
emit_changed(get_edited_property(), dict, "", false);
1552
update_property();
1553
}
1554
1555
void EditorPropertyLocalizableString::update_property() {
1556
Variant updated_val = get_edited_property_value();
1557
1558
if (updated_val.get_type() == Variant::NIL) {
1559
edit->set_text(TTR("Localizable String (Nil)")); // This provides symmetry with the array property.
1560
edit->set_pressed(false);
1561
if (container) {
1562
set_bottom_editor(nullptr);
1563
memdelete(container);
1564
button_add_item = nullptr;
1565
container = nullptr;
1566
}
1567
return;
1568
}
1569
1570
Dictionary dict = updated_val;
1571
object->set_dict(dict);
1572
1573
edit->set_text(vformat(TTR("Localizable String (size %d)"), dict.size()));
1574
1575
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
1576
if (edit->is_pressed() != unfolded) {
1577
edit->set_pressed(unfolded);
1578
}
1579
1580
if (unfolded) {
1581
updating = true;
1582
1583
if (!container) {
1584
container = memnew(MarginContainer);
1585
container->set_theme_type_variation("MarginContainer4px");
1586
add_child(container);
1587
set_bottom_editor(container);
1588
1589
VBoxContainer *vbox = memnew(VBoxContainer);
1590
container->add_child(vbox);
1591
1592
property_vbox = memnew(VBoxContainer);
1593
property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1594
vbox->add_child(property_vbox);
1595
1596
paginator = memnew(EditorPaginator);
1597
paginator->connect("page_changed", callable_mp(this, &EditorPropertyLocalizableString::_page_changed));
1598
vbox->add_child(paginator);
1599
} else {
1600
// Queue children for deletion, deleting immediately might cause errors.
1601
for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
1602
property_vbox->get_child(i)->queue_free();
1603
}
1604
}
1605
1606
int size = dict.size();
1607
1608
int max_page = MAX(0, size - 1) / page_length;
1609
page_index = MIN(page_index, max_page);
1610
1611
paginator->update(page_index, max_page);
1612
paginator->set_visible(max_page > 0);
1613
1614
int offset = page_index * page_length;
1615
1616
int amount = MIN(size - offset, page_length);
1617
1618
for (int i = 0; i < amount; i++) {
1619
String prop_name;
1620
Variant key;
1621
Variant value;
1622
1623
prop_name = "indices/" + itos(i + offset);
1624
key = dict.get_key_at_index(i + offset);
1625
value = dict.get_value_at_index(i + offset);
1626
1627
EditorProperty *prop = memnew(EditorPropertyText);
1628
1629
prop->set_object_and_property(object.ptr(), prop_name);
1630
int remove_index = 0;
1631
1632
String cs = key.get_construct_string();
1633
prop->set_label(cs);
1634
prop->set_tooltip_text(cs);
1635
remove_index = i + offset;
1636
1637
prop->set_selectable(false);
1638
prop->connect("property_changed", callable_mp(this, &EditorPropertyLocalizableString::_property_changed));
1639
prop->connect("object_id_selected", callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected));
1640
1641
HBoxContainer *hbox = memnew(HBoxContainer);
1642
property_vbox->add_child(hbox);
1643
hbox->add_child(prop);
1644
prop->set_h_size_flags(SIZE_EXPAND_FILL);
1645
Button *edit_btn = memnew(Button);
1646
edit_btn->set_accessibility_name(TTRC("Remove Translation"));
1647
edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));
1648
hbox->add_child(edit_btn);
1649
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index));
1650
1651
prop->update_property();
1652
}
1653
1654
if (page_index == max_page) {
1655
button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Translation"), SNAME("Add")));
1656
button_add_item->set_accessibility_name(TTRC("Add Translation"));
1657
button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup));
1658
property_vbox->add_child(button_add_item);
1659
}
1660
1661
updating = false;
1662
1663
} else {
1664
if (container) {
1665
set_bottom_editor(nullptr);
1666
memdelete(container);
1667
button_add_item = nullptr;
1668
container = nullptr;
1669
}
1670
}
1671
}
1672
1673
void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1674
emit_signal(SNAME("object_id_selected"), p_property, p_id);
1675
}
1676
1677
void EditorPropertyLocalizableString::_edit_pressed() {
1678
Variant prop_val = get_edited_property_value();
1679
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1680
VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
1681
get_edited_object()->set(get_edited_property(), prop_val);
1682
}
1683
1684
get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1685
update_property();
1686
}
1687
1688
void EditorPropertyLocalizableString::_page_changed(int p_page) {
1689
if (updating) {
1690
return;
1691
}
1692
page_index = p_page;
1693
update_property();
1694
}
1695
1696
EditorPropertyLocalizableString::EditorPropertyLocalizableString() {
1697
object.instantiate();
1698
page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1699
1700
edit = memnew(Button);
1701
edit->set_accessibility_name(TTRC("Edit"));
1702
edit->set_h_size_flags(SIZE_EXPAND_FILL);
1703
edit->set_clip_text(true);
1704
edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed));
1705
edit->set_toggle_mode(true);
1706
add_child(edit);
1707
add_focusable(edit);
1708
1709
container = nullptr;
1710
button_add_item = nullptr;
1711
paginator = nullptr;
1712
updating = false;
1713
1714
locale_select = memnew(EditorLocaleDialog);
1715
locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyLocalizableString::_add_locale));
1716
add_child(locale_select);
1717
}
1718
1719