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/editor_properties.h"
36#include "editor/editor_properties_vector.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_string_names.h"
40#include "editor/gui/editor_spin_slider.h"
41#include "editor/inspector_dock.h"
42#include "scene/gui/button.h"
43#include "scene/resources/packed_scene.h"
44
45bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {
46 String name = p_name;
47
48 if (!name.begins_with("indices")) {
49 return false;
50 }
51
52 int index;
53 if (name.begins_with("metadata/")) {
54 index = name.get_slice("/", 2).to_int();
55 } else {
56 index = name.get_slice("/", 1).to_int();
57 }
58
59 array.set(index, p_value);
60 return true;
61}
62
63bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const {
64 String name = p_name;
65
66 if (!name.begins_with("indices")) {
67 return false;
68 }
69
70 int index;
71 if (name.begins_with("metadata/")) {
72 index = name.get_slice("/", 2).to_int();
73 } else {
74 index = name.get_slice("/", 1).to_int();
75 }
76
77 bool valid;
78 r_ret = array.get(index, &valid);
79
80 if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
81 r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
82 }
83
84 return valid;
85}
86
87void EditorPropertyArrayObject::set_array(const Variant &p_array) {
88 array = p_array;
89}
90
91Variant EditorPropertyArrayObject::get_array() {
92 return array;
93}
94
95EditorPropertyArrayObject::EditorPropertyArrayObject() {
96}
97
98///////////////////
99
100bool 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 int index = name.get_slicec('/', 1).to_int();
115 Variant key = dict.get_key_at_index(index);
116 dict[key] = p_value;
117 return true;
118 }
119
120 return false;
121}
122
123bool EditorPropertyDictionaryObject::_get(const StringName &p_name, Variant &r_ret) const {
124 String name = p_name;
125
126 if (name == "new_item_key") {
127 r_ret = new_item_key;
128 return true;
129 }
130
131 if (name == "new_item_value") {
132 r_ret = new_item_value;
133 return true;
134 }
135
136 if (name.begins_with("indices")) {
137 int index = name.get_slicec('/', 1).to_int();
138 Variant key = dict.get_key_at_index(index);
139 r_ret = dict[key];
140 if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
141 r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
142 }
143
144 return true;
145 }
146
147 return false;
148}
149
150void EditorPropertyDictionaryObject::set_dict(const Dictionary &p_dict) {
151 dict = p_dict;
152}
153
154Dictionary EditorPropertyDictionaryObject::get_dict() {
155 return dict;
156}
157
158void EditorPropertyDictionaryObject::set_new_item_key(const Variant &p_new_item) {
159 new_item_key = p_new_item;
160}
161
162Variant EditorPropertyDictionaryObject::get_new_item_key() {
163 return new_item_key;
164}
165
166void EditorPropertyDictionaryObject::set_new_item_value(const Variant &p_new_item) {
167 new_item_value = p_new_item;
168}
169
170Variant EditorPropertyDictionaryObject::get_new_item_value() {
171 return new_item_value;
172}
173
174EditorPropertyDictionaryObject::EditorPropertyDictionaryObject() {
175}
176
177///////////////////// ARRAY ///////////////////////////
178
179void EditorPropertyArray::initialize_array(Variant &p_array) {
180 if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
181 Array array;
182 StringName subtype_class;
183 Ref<Script> subtype_script;
184 if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
185 if (ClassDB::class_exists(subtype_hint_string)) {
186 subtype_class = subtype_hint_string;
187 }
188 }
189 array.set_typed(subtype, subtype_class, subtype_script);
190 p_array = array;
191 } else {
192 VariantInternal::initialize(&p_array, array_type);
193 }
194}
195
196void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
197 if (!p_property.begins_with("indices")) {
198 return;
199 }
200
201 int index;
202 if (p_property.begins_with("metadata/")) {
203 index = p_property.get_slice("/", 2).to_int();
204 } else {
205 index = p_property.get_slice("/", 1).to_int();
206 }
207
208 Variant array = object->get_array().duplicate();
209 array.set(index, p_value);
210 object->set_array(array);
211 emit_changed(get_edited_property(), array, "", true);
212}
213
214void EditorPropertyArray::_change_type(Object *p_button, int p_index) {
215 Button *button = Object::cast_to<Button>(p_button);
216 changing_type_index = p_index;
217 Rect2 rect = button->get_screen_rect();
218 change_type->reset_size();
219 change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
220 change_type->popup();
221}
222
223void EditorPropertyArray::_change_type_menu(int p_index) {
224 if (p_index == Variant::VARIANT_MAX) {
225 _remove_pressed(changing_type_index);
226 return;
227 }
228
229 Variant value;
230 VariantInternal::initialize(&value, Variant::Type(p_index));
231
232 Variant array = object->get_array().duplicate();
233 array.set(changing_type_index, value);
234
235 emit_changed(get_edited_property(), array, "", true);
236 update_property();
237}
238
239void EditorPropertyArray::_object_id_selected(const StringName &p_property, ObjectID p_id) {
240 emit_signal(SNAME("object_id_selected"), p_property, p_id);
241}
242
243void EditorPropertyArray::update_property() {
244 Variant array = get_edited_property_value();
245
246 String array_type_name = Variant::get_type_name(array_type);
247 if (array_type == Variant::ARRAY && subtype != Variant::NIL) {
248 String type_name;
249 if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
250 type_name = subtype_hint_string;
251 } else {
252 type_name = Variant::get_type_name(subtype);
253 }
254
255 array_type_name = vformat("%s[%s]", array_type_name, type_name);
256 }
257
258 if (array.get_type() == Variant::NIL) {
259 edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
260 edit->set_pressed(false);
261 if (container) {
262 set_bottom_editor(nullptr);
263 memdelete(container);
264 button_add_item = nullptr;
265 container = nullptr;
266 }
267 return;
268 }
269
270 object->set_array(array);
271
272 int size = array.call("size");
273 int max_page = MAX(0, size - 1) / page_length;
274 page_index = MIN(page_index, max_page);
275 int offset = page_index * page_length;
276
277 edit->set_text(vformat(TTR("%s (size %s)"), array_type_name, itos(size)));
278
279 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
280 if (edit->is_pressed() != unfolded) {
281 edit->set_pressed(unfolded);
282 }
283
284 if (unfolded) {
285 updating = true;
286
287 if (!container) {
288 container = memnew(MarginContainer);
289 container->set_theme_type_variation("MarginContainer4px");
290 add_child(container);
291 set_bottom_editor(container);
292
293 VBoxContainer *vbox = memnew(VBoxContainer);
294 container->add_child(vbox);
295
296 HBoxContainer *hbox = memnew(HBoxContainer);
297 vbox->add_child(hbox);
298
299 Label *size_label = memnew(Label(TTR("Size:")));
300 size_label->set_h_size_flags(SIZE_EXPAND_FILL);
301 hbox->add_child(size_label);
302
303 size_slider = memnew(EditorSpinSlider);
304 size_slider->set_step(1);
305 size_slider->set_max(INT32_MAX);
306 size_slider->set_h_size_flags(SIZE_EXPAND_FILL);
307 size_slider->set_read_only(is_read_only());
308 size_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_length_changed));
309 hbox->add_child(size_slider);
310
311 property_vbox = memnew(VBoxContainer);
312 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
313 vbox->add_child(property_vbox);
314
315 button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Element"));
316 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
317 button_add_item->connect(SNAME("pressed"), callable_mp(this, &EditorPropertyArray::_add_element));
318 button_add_item->set_disabled(is_read_only());
319 vbox->add_child(button_add_item);
320
321 paginator = memnew(EditorPaginator);
322 paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed));
323 vbox->add_child(paginator);
324 } else {
325 // Bye bye children of the box.
326 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
327 Node *child = property_vbox->get_child(i);
328 if (child == reorder_selected_element_hbox) {
329 continue; // Don't remove the property that the user is moving.
330 }
331
332 child->queue_free(); // Button still needed after pressed is called.
333 property_vbox->remove_child(child);
334 }
335 }
336
337 size_slider->set_value(size);
338 property_vbox->set_visible(size > 0);
339 button_add_item->set_visible(page_index == max_page);
340 paginator->update(page_index, max_page);
341 paginator->set_visible(max_page > 0);
342
343 int amount = MIN(size - offset, page_length);
344 for (int i = 0; i < amount; i++) {
345 bool reorder_is_from_current_page = reorder_from_index / page_length == page_index;
346 if (reorder_is_from_current_page && i == reorder_from_index % page_length) {
347 // Don't duplicate the property that the user is moving.
348 continue;
349 }
350 if (!reorder_is_from_current_page && i == reorder_to_index % page_length) {
351 // Don't create the property the moving property will take the place of,
352 // e.g. (if page_length == 20) don't create element 20 if dragging an item from
353 // the first page to the second page because element 20 would become element 19.
354 continue;
355 }
356
357 HBoxContainer *hbox = memnew(HBoxContainer);
358 property_vbox->add_child(hbox);
359
360 Button *reorder_button = memnew(Button);
361 reorder_button->set_icon(get_editor_theme_icon(SNAME("TripleBar")));
362 reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE);
363 reorder_button->set_disabled(is_read_only());
364 reorder_button->connect("gui_input", callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input));
365 reorder_button->connect("button_down", callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(i + offset));
366 reorder_button->connect("button_up", callable_mp(this, &EditorPropertyArray::_reorder_button_up));
367 hbox->add_child(reorder_button);
368
369 String prop_name = "indices/" + itos(i + offset);
370
371 EditorProperty *prop = nullptr;
372 Variant value = array.get(i + offset);
373 Variant::Type value_type = value.get_type();
374
375 if (value_type == Variant::NIL && subtype != Variant::NIL) {
376 value_type = subtype;
377 }
378
379 if (value_type == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(value)) {
380 EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
381 editor->setup("Object");
382 prop = editor;
383 } else {
384 prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
385 }
386
387 prop->set_object_and_property(object.ptr(), prop_name);
388 prop->set_label(itos(i + offset));
389 prop->set_selectable(false);
390 prop->set_use_folding(is_using_folding());
391 prop->connect("property_changed", callable_mp(this, &EditorPropertyArray::_property_changed));
392 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyArray::_object_id_selected));
393 prop->set_h_size_flags(SIZE_EXPAND_FILL);
394 prop->set_read_only(is_read_only());
395 hbox->add_child(prop);
396
397 bool is_untyped_array = array.get_type() == Variant::ARRAY && subtype == Variant::NIL;
398
399 if (is_untyped_array) {
400 Button *edit_btn = memnew(Button);
401 edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
402 hbox->add_child(edit_btn);
403 edit_btn->set_disabled(is_read_only());
404 edit_btn->connect("pressed", callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, i + offset));
405 } else {
406 Button *remove_btn = memnew(Button);
407 remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
408 remove_btn->set_disabled(is_read_only());
409 remove_btn->connect("pressed", callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(i + offset));
410 hbox->add_child(remove_btn);
411 }
412
413 prop->update_property();
414 }
415
416 if (reorder_to_index % page_length > 0) {
417 property_vbox->move_child(property_vbox->get_child(0), reorder_to_index % page_length);
418 }
419
420 updating = false;
421
422 } else {
423 if (container) {
424 set_bottom_editor(nullptr);
425 memdelete(container);
426 button_add_item = nullptr;
427 container = nullptr;
428 }
429 }
430}
431
432void EditorPropertyArray::_remove_pressed(int p_index) {
433 Variant array = object->get_array().duplicate();
434 array.call("remove_at", p_index);
435
436 emit_changed(get_edited_property(), array, "", false);
437 update_property();
438}
439
440void EditorPropertyArray::_button_draw() {
441 if (dropping) {
442 Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
443 edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false);
444 }
445}
446
447bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {
448 if (is_read_only()) {
449 return false;
450 }
451
452 String allowed_type = Variant::get_type_name(subtype);
453
454 // When the subtype is of type Object, an additional subtype may be specified in the hint string
455 // (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object".
456 if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {
457 allowed_type = subtype_hint_string;
458 }
459
460 Dictionary drag_data = p_drag_data;
461
462 if (drag_data.has("type") && String(drag_data["type"]) == "files") {
463 Vector<String> files = drag_data["files"];
464
465 for (int i = 0; i < files.size(); i++) {
466 String file = files[i];
467 String ftype = EditorFileSystem::get_singleton()->get_file_type(file);
468
469 for (int j = 0; j < allowed_type.get_slice_count(","); j++) {
470 String at = allowed_type.get_slice(",", j).strip_edges();
471 // Fail if one of the files is not of allowed type.
472 if (!ClassDB::is_parent_class(ftype, at)) {
473 return false;
474 }
475 }
476 }
477
478 // If no files fail, drop is valid.
479 return true;
480 }
481
482 return false;
483}
484
485bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
486 return _is_drop_valid(p_data);
487}
488
489void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
490 ERR_FAIL_COND(!_is_drop_valid(p_data));
491
492 Dictionary drag_data = p_data;
493
494 if (drag_data.has("type") && String(drag_data["type"]) == "files") {
495 Vector<String> files = drag_data["files"];
496
497 Variant array = object->get_array();
498
499 // Handle the case where array is not initialized yet.
500 if (!array.is_array()) {
501 initialize_array(array);
502 } else {
503 array = array.duplicate();
504 }
505
506 // Loop the file array and add to existing array.
507 for (int i = 0; i < files.size(); i++) {
508 String file = files[i];
509
510 Ref<Resource> res = ResourceLoader::load(file);
511 if (res.is_valid()) {
512 array.call("push_back", res);
513 }
514 }
515
516 emit_changed(get_edited_property(), array, "", false);
517 update_property();
518 }
519}
520
521void EditorPropertyArray::_notification(int p_what) {
522 switch (p_what) {
523 case NOTIFICATION_THEME_CHANGED:
524 case NOTIFICATION_ENTER_TREE: {
525 change_type->clear();
526 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
527 if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
528 // These types can't be constructed or serialized properly, so skip them.
529 continue;
530 }
531
532 String type = Variant::get_type_name(Variant::Type(i));
533 change_type->add_icon_item(get_editor_theme_icon(type), type, i);
534 }
535 change_type->add_separator();
536 change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
537
538 if (button_add_item) {
539 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
540 }
541 } break;
542
543 case NOTIFICATION_DRAG_BEGIN: {
544 if (is_visible_in_tree()) {
545 if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {
546 dropping = true;
547 edit->queue_redraw();
548 }
549 }
550 } break;
551
552 case NOTIFICATION_DRAG_END: {
553 if (dropping) {
554 dropping = false;
555 edit->queue_redraw();
556 }
557 } break;
558 }
559}
560
561void EditorPropertyArray::_edit_pressed() {
562 Variant array = get_edited_property_value();
563 if (!array.is_array() && edit->is_pressed()) {
564 initialize_array(array);
565 get_edited_object()->set(get_edited_property(), array);
566 }
567
568 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
569 update_property();
570}
571
572void EditorPropertyArray::_page_changed(int p_page) {
573 if (updating) {
574 return;
575 }
576 page_index = p_page;
577 update_property();
578}
579
580void EditorPropertyArray::_length_changed(double p_page) {
581 if (updating) {
582 return;
583 }
584
585 Variant array = object->get_array().duplicate();
586 array.call("resize", int(p_page));
587
588 emit_changed(get_edited_property(), array, "", false);
589 update_property();
590}
591
592void EditorPropertyArray::_add_element() {
593 _length_changed(double(object->get_array().call("size")) + 1.0);
594}
595
596void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {
597 array_type = p_array_type;
598
599 // The format of p_hint_string is:
600 // subType/subTypeHint:nextSubtype ... etc.
601 if (!p_hint_string.is_empty()) {
602 int hint_subtype_separator = p_hint_string.find(":");
603 if (hint_subtype_separator >= 0) {
604 String subtype_string = p_hint_string.substr(0, hint_subtype_separator);
605 int slash_pos = subtype_string.find("/");
606 if (slash_pos >= 0) {
607 subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1, subtype_string.size() - slash_pos - 1).to_int());
608 subtype_string = subtype_string.substr(0, slash_pos);
609 }
610
611 subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1, p_hint_string.size() - hint_subtype_separator - 1);
612 subtype = Variant::Type(subtype_string.to_int());
613 }
614 }
615}
616
617void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) {
618 if (reorder_from_index < 0 || is_read_only()) {
619 return;
620 }
621
622 Ref<InputEventMouseMotion> mm = p_event;
623 if (mm.is_valid()) {
624 Variant array = object->get_array();
625 int size = array.call("size");
626
627 // Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point.
628 reorder_mouse_y_delta += mm->get_relative().y;
629
630 // Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if
631 // already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta).
632 if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) {
633 reorder_mouse_y_delta = 0.0f;
634 return;
635 }
636
637 float required_y_distance = 20.0f * EDSCALE;
638 if (ABS(reorder_mouse_y_delta) > required_y_distance) {
639 int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1;
640 reorder_mouse_y_delta -= required_y_distance * direction;
641
642 reorder_to_index += direction;
643 if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) {
644 // Automatically move to the next/previous page.
645 _page_changed(page_index + direction);
646 }
647 property_vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length);
648 // Ensure the moving element is visible.
649 InspectorDock::get_inspector_singleton()->ensure_control_visible(reorder_selected_element_hbox);
650 }
651 }
652}
653
654void EditorPropertyArray::_reorder_button_down(int p_index) {
655 if (is_read_only()) {
656 return;
657 }
658
659 reorder_from_index = p_index;
660 reorder_to_index = p_index;
661 reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(property_vbox->get_child(p_index % page_length));
662 reorder_selected_button = Object::cast_to<Button>(reorder_selected_element_hbox->get_child(0));
663 // Ideally it'd to be able to show the mouse but I had issues with
664 // Control's `mouse_exit()`/`mouse_entered()` signals not getting called.
665 Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
666}
667
668void EditorPropertyArray::_reorder_button_up() {
669 if (is_read_only()) {
670 return;
671 }
672
673 if (reorder_from_index != reorder_to_index) {
674 // Move the element.
675 Variant array = object->get_array().duplicate();
676
677 Variant value_to_move = array.get(reorder_from_index);
678 array.call("remove_at", reorder_from_index);
679 array.call("insert", reorder_to_index, value_to_move);
680
681 emit_changed(get_edited_property(), array, "", false);
682 update_property();
683 }
684
685 reorder_from_index = -1;
686 reorder_to_index = -1;
687 reorder_mouse_y_delta = 0.0f;
688
689 Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
690
691 ERR_FAIL_NULL(reorder_selected_button);
692 reorder_selected_button->warp_mouse(reorder_selected_button->get_size() / 2.0f);
693
694 reorder_selected_element_hbox = nullptr;
695 reorder_selected_button = nullptr;
696}
697
698void EditorPropertyArray::_bind_methods() {
699}
700
701EditorPropertyArray::EditorPropertyArray() {
702 object.instantiate();
703 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
704
705 edit = memnew(Button);
706 edit->set_h_size_flags(SIZE_EXPAND_FILL);
707 edit->set_clip_text(true);
708 edit->connect("pressed", callable_mp(this, &EditorPropertyArray::_edit_pressed));
709 edit->set_toggle_mode(true);
710 SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray);
711 edit->connect("draw", callable_mp(this, &EditorPropertyArray::_button_draw));
712 add_child(edit);
713 add_focusable(edit);
714
715 change_type = memnew(PopupMenu);
716 add_child(change_type);
717 change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu));
718 changing_type_index = -1;
719
720 subtype = Variant::NIL;
721 subtype_hint = PROPERTY_HINT_NONE;
722 subtype_hint_string = "";
723}
724
725///////////////////// DICTIONARY ///////////////////////////
726
727void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
728 if (p_property == "new_item_key") {
729 object->set_new_item_key(p_value);
730 } else if (p_property == "new_item_value") {
731 object->set_new_item_value(p_value);
732 } else if (p_property.begins_with("indices")) {
733 int index = p_property.get_slice("/", 1).to_int();
734
735 Dictionary dict = object->get_dict().duplicate();
736 Variant key = dict.get_key_at_index(index);
737 dict[key] = p_value;
738
739 object->set_dict(dict);
740 emit_changed(get_edited_property(), dict, "", true);
741 }
742}
743
744void EditorPropertyDictionary::_change_type(Object *p_button, int p_index) {
745 Button *button = Object::cast_to<Button>(p_button);
746
747 Rect2 rect = button->get_screen_rect();
748 change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), p_index < 0);
749 change_type->reset_size();
750 change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));
751 change_type->popup();
752 changing_type_index = p_index;
753}
754
755void EditorPropertyDictionary::_add_key_value() {
756 // Do not allow nil as valid key. I experienced errors with this
757 if (object->get_new_item_key().get_type() == Variant::NIL) {
758 return;
759 }
760
761 Dictionary dict = object->get_dict().duplicate();
762 dict[object->get_new_item_key()] = object->get_new_item_value();
763 object->set_new_item_key(Variant());
764 object->set_new_item_value(Variant());
765
766 emit_changed(get_edited_property(), dict, "", false);
767 update_property();
768}
769
770void EditorPropertyDictionary::_change_type_menu(int p_index) {
771 if (changing_type_index < 0) {
772 Variant value;
773 VariantInternal::initialize(&value, Variant::Type(p_index));
774 if (changing_type_index == -1) {
775 object->set_new_item_key(value);
776 } else {
777 object->set_new_item_value(value);
778 }
779 update_property();
780 return;
781 }
782
783 Dictionary dict = object->get_dict().duplicate();
784 if (p_index < Variant::VARIANT_MAX) {
785 Variant value;
786 VariantInternal::initialize(&value, Variant::Type(p_index));
787 Variant key = dict.get_key_at_index(changing_type_index);
788 dict[key] = value;
789 } else {
790 Variant key = dict.get_key_at_index(changing_type_index);
791 dict.erase(key);
792 }
793
794 emit_changed(get_edited_property(), dict, "", false);
795 update_property();
796}
797
798void EditorPropertyDictionary::setup(PropertyHint p_hint) {
799 property_hint = p_hint;
800}
801
802void EditorPropertyDictionary::update_property() {
803 Variant updated_val = get_edited_property_value();
804
805 if (updated_val.get_type() == Variant::NIL) {
806 edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property.
807 edit->set_pressed(false);
808 if (container) {
809 set_bottom_editor(nullptr);
810 memdelete(container);
811 button_add_item = nullptr;
812 container = nullptr;
813 }
814 return;
815 }
816
817 Dictionary dict = updated_val;
818 object->set_dict(updated_val);
819
820 edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size()));
821
822 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
823 if (edit->is_pressed() != unfolded) {
824 edit->set_pressed(unfolded);
825 }
826
827 if (unfolded) {
828 updating = true;
829
830 if (!container) {
831 container = memnew(MarginContainer);
832 container->set_theme_type_variation("MarginContainer4px");
833 add_child(container);
834 set_bottom_editor(container);
835
836 VBoxContainer *vbox = memnew(VBoxContainer);
837 container->add_child(vbox);
838
839 property_vbox = memnew(VBoxContainer);
840 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
841 vbox->add_child(property_vbox);
842
843 paginator = memnew(EditorPaginator);
844 paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));
845 vbox->add_child(paginator);
846 } else {
847 // Queue children for deletion, deleting immediately might cause errors.
848 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
849 property_vbox->get_child(i)->queue_free();
850 }
851 }
852
853 int size = dict.size();
854
855 int max_page = MAX(0, size - 1) / page_length;
856 page_index = MIN(page_index, max_page);
857
858 paginator->update(page_index, max_page);
859 paginator->set_visible(max_page > 0);
860
861 int offset = page_index * page_length;
862
863 int amount = MIN(size - offset, page_length);
864 int total_amount = page_index == max_page ? amount + 2 : amount; // For the "Add Key/Value Pair" box on last page.
865
866 VBoxContainer *add_vbox = nullptr;
867 double default_float_step = EDITOR_GET("interface/inspector/default_float_step");
868
869 for (int i = 0; i < total_amount; i++) {
870 String prop_name;
871 Variant key;
872 Variant value;
873
874 if (i < amount) {
875 prop_name = "indices/" + itos(i + offset);
876 key = dict.get_key_at_index(i + offset);
877 value = dict.get_value_at_index(i + offset);
878 } else if (i == amount) {
879 prop_name = "new_item_key";
880 value = object->get_new_item_key();
881 } else if (i == amount + 1) {
882 prop_name = "new_item_value";
883 value = object->get_new_item_value();
884 }
885
886 EditorProperty *prop = nullptr;
887
888 switch (value.get_type()) {
889 case Variant::NIL: {
890 prop = memnew(EditorPropertyNil);
891
892 } break;
893
894 // Atomic types.
895 case Variant::BOOL: {
896 prop = memnew(EditorPropertyCheck);
897
898 } break;
899 case Variant::INT: {
900 EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
901 editor->setup(-100000, 100000, 1, false, true, true);
902 prop = editor;
903
904 } break;
905 case Variant::FLOAT: {
906 EditorPropertyFloat *editor = memnew(EditorPropertyFloat);
907 editor->setup(-100000, 100000, default_float_step, true, false, true, true);
908 prop = editor;
909 } break;
910 case Variant::STRING: {
911 if (i != amount && property_hint == PROPERTY_HINT_MULTILINE_TEXT) {
912 // If this is NOT the new key field and there's a multiline hint,
913 // show the field as multiline
914 prop = memnew(EditorPropertyMultilineText);
915 } else {
916 prop = memnew(EditorPropertyText);
917 }
918
919 } break;
920
921 // Math types.
922 case Variant::VECTOR2: {
923 EditorPropertyVector2 *editor = memnew(EditorPropertyVector2);
924 editor->setup(-100000, 100000, default_float_step, true);
925 prop = editor;
926
927 } break;
928 case Variant::VECTOR2I: {
929 EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i);
930 editor->setup(-100000, 100000);
931 prop = editor;
932
933 } break;
934 case Variant::RECT2: {
935 EditorPropertyRect2 *editor = memnew(EditorPropertyRect2);
936 editor->setup(-100000, 100000, default_float_step, true);
937 prop = editor;
938
939 } break;
940 case Variant::RECT2I: {
941 EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i);
942 editor->setup(-100000, 100000);
943 prop = editor;
944
945 } break;
946 case Variant::VECTOR3: {
947 EditorPropertyVector3 *editor = memnew(EditorPropertyVector3);
948 editor->setup(-100000, 100000, default_float_step, true);
949 prop = editor;
950
951 } break;
952 case Variant::VECTOR3I: {
953 EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i);
954 editor->setup(-100000, 100000);
955 prop = editor;
956
957 } break;
958 case Variant::VECTOR4: {
959 EditorPropertyVector4 *editor = memnew(EditorPropertyVector4);
960 editor->setup(-100000, 100000, default_float_step, true);
961 prop = editor;
962
963 } break;
964 case Variant::VECTOR4I: {
965 EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i);
966 editor->setup(-100000, 100000);
967 prop = editor;
968
969 } break;
970 case Variant::TRANSFORM2D: {
971 EditorPropertyTransform2D *editor = memnew(EditorPropertyTransform2D);
972 editor->setup(-100000, 100000, default_float_step, true);
973 prop = editor;
974
975 } break;
976 case Variant::PLANE: {
977 EditorPropertyPlane *editor = memnew(EditorPropertyPlane);
978 editor->setup(-100000, 100000, default_float_step, true);
979 prop = editor;
980
981 } break;
982 case Variant::QUATERNION: {
983 EditorPropertyQuaternion *editor = memnew(EditorPropertyQuaternion);
984 editor->setup(-100000, 100000, default_float_step, true);
985 prop = editor;
986
987 } break;
988 case Variant::AABB: {
989 EditorPropertyAABB *editor = memnew(EditorPropertyAABB);
990 editor->setup(-100000, 100000, default_float_step, true);
991 prop = editor;
992
993 } break;
994 case Variant::BASIS: {
995 EditorPropertyBasis *editor = memnew(EditorPropertyBasis);
996 editor->setup(-100000, 100000, default_float_step, true);
997 prop = editor;
998
999 } break;
1000 case Variant::TRANSFORM3D: {
1001 EditorPropertyTransform3D *editor = memnew(EditorPropertyTransform3D);
1002 editor->setup(-100000, 100000, default_float_step, true);
1003 prop = editor;
1004
1005 } break;
1006 case Variant::PROJECTION: {
1007 EditorPropertyProjection *editor = memnew(EditorPropertyProjection);
1008 editor->setup(-100000, 100000, default_float_step, true);
1009 prop = editor;
1010
1011 } break;
1012
1013 // Miscellaneous types.
1014 case Variant::COLOR: {
1015 prop = memnew(EditorPropertyColor);
1016
1017 } break;
1018 case Variant::STRING_NAME: {
1019 EditorPropertyText *ept = memnew(EditorPropertyText);
1020 ept->set_string_name(true);
1021 prop = ept;
1022
1023 } break;
1024 case Variant::NODE_PATH: {
1025 prop = memnew(EditorPropertyNodePath);
1026
1027 } break;
1028 case Variant::RID: {
1029 prop = memnew(EditorPropertyRID);
1030
1031 } break;
1032 case Variant::SIGNAL: {
1033 prop = memnew(EditorPropertySignal);
1034
1035 } break;
1036 case Variant::CALLABLE: {
1037 prop = memnew(EditorPropertyCallable);
1038
1039 } break;
1040 case Variant::OBJECT: {
1041 if (Object::cast_to<EncodedObjectAsID>(value)) {
1042 EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);
1043 editor->setup("Object");
1044 prop = editor;
1045
1046 } else {
1047 EditorPropertyResource *editor = memnew(EditorPropertyResource);
1048 editor->setup(object.ptr(), prop_name, "Resource");
1049 editor->set_use_folding(is_using_folding());
1050 prop = editor;
1051 }
1052
1053 } break;
1054 case Variant::DICTIONARY: {
1055 prop = memnew(EditorPropertyDictionary);
1056
1057 } break;
1058 case Variant::ARRAY: {
1059 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1060 editor->setup(Variant::ARRAY);
1061 prop = editor;
1062 } break;
1063
1064 // Arrays.
1065 case Variant::PACKED_BYTE_ARRAY: {
1066 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1067 editor->setup(Variant::PACKED_BYTE_ARRAY);
1068 prop = editor;
1069 } break;
1070 case Variant::PACKED_INT32_ARRAY: {
1071 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1072 editor->setup(Variant::PACKED_INT32_ARRAY);
1073 prop = editor;
1074 } break;
1075 case Variant::PACKED_FLOAT32_ARRAY: {
1076 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1077 editor->setup(Variant::PACKED_FLOAT32_ARRAY);
1078 prop = editor;
1079 } break;
1080 case Variant::PACKED_INT64_ARRAY: {
1081 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1082 editor->setup(Variant::PACKED_INT64_ARRAY);
1083 prop = editor;
1084 } break;
1085 case Variant::PACKED_FLOAT64_ARRAY: {
1086 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1087 editor->setup(Variant::PACKED_FLOAT64_ARRAY);
1088 prop = editor;
1089 } break;
1090 case Variant::PACKED_STRING_ARRAY: {
1091 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1092 editor->setup(Variant::PACKED_STRING_ARRAY);
1093 prop = editor;
1094 } break;
1095 case Variant::PACKED_VECTOR2_ARRAY: {
1096 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1097 editor->setup(Variant::PACKED_VECTOR2_ARRAY);
1098 prop = editor;
1099 } break;
1100 case Variant::PACKED_VECTOR3_ARRAY: {
1101 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1102 editor->setup(Variant::PACKED_VECTOR3_ARRAY);
1103 prop = editor;
1104 } break;
1105 case Variant::PACKED_COLOR_ARRAY: {
1106 EditorPropertyArray *editor = memnew(EditorPropertyArray);
1107 editor->setup(Variant::PACKED_COLOR_ARRAY);
1108 prop = editor;
1109 } break;
1110 default: {
1111 }
1112 }
1113
1114 ERR_FAIL_NULL(prop);
1115
1116 prop->set_read_only(is_read_only());
1117
1118 if (i == amount) {
1119 PanelContainer *pc = memnew(PanelContainer);
1120 property_vbox->add_child(pc);
1121 pc->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("DictionaryAddItem"), EditorStringName(EditorStyles)));
1122
1123 add_vbox = memnew(VBoxContainer);
1124 pc->add_child(add_vbox);
1125 }
1126 prop->set_object_and_property(object.ptr(), prop_name);
1127 int change_index = 0;
1128
1129 if (i < amount) {
1130 String cs = key.get_construct_string();
1131 prop->set_label(key.get_construct_string());
1132 prop->set_tooltip_text(cs);
1133 change_index = i + offset;
1134 } else if (i == amount) {
1135 prop->set_label(TTR("New Key:"));
1136 change_index = -1;
1137 } else if (i == amount + 1) {
1138 prop->set_label(TTR("New Value:"));
1139 change_index = -2;
1140 }
1141
1142 prop->set_selectable(false);
1143 prop->connect("property_changed", callable_mp(this, &EditorPropertyDictionary::_property_changed));
1144 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyDictionary::_object_id_selected));
1145
1146 HBoxContainer *hbox = memnew(HBoxContainer);
1147 if (add_vbox) {
1148 add_vbox->add_child(hbox);
1149 } else {
1150 property_vbox->add_child(hbox);
1151 }
1152 hbox->add_child(prop);
1153 prop->set_h_size_flags(SIZE_EXPAND_FILL);
1154 Button *edit_btn = memnew(Button);
1155 edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
1156 edit_btn->set_disabled(is_read_only());
1157 hbox->add_child(edit_btn);
1158 edit_btn->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, change_index));
1159
1160 prop->update_property();
1161
1162 if (i == amount + 1) {
1163 button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Key/Value Pair"));
1164 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
1165 button_add_item->set_disabled(is_read_only());
1166 button_add_item->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_add_key_value));
1167 add_vbox->add_child(button_add_item);
1168 }
1169 }
1170
1171 updating = false;
1172
1173 } else {
1174 if (container) {
1175 set_bottom_editor(nullptr);
1176 memdelete(container);
1177 button_add_item = nullptr;
1178 container = nullptr;
1179 }
1180 }
1181}
1182
1183void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1184 emit_signal(SNAME("object_id_selected"), p_property, p_id);
1185}
1186
1187void EditorPropertyDictionary::_notification(int p_what) {
1188 switch (p_what) {
1189 case NOTIFICATION_THEME_CHANGED:
1190 case NOTIFICATION_ENTER_TREE: {
1191 change_type->clear();
1192 for (int i = 0; i < Variant::VARIANT_MAX; i++) {
1193 if (i == Variant::CALLABLE || i == Variant::SIGNAL || i == Variant::RID) {
1194 // These types can't be constructed or serialized properly, so skip them.
1195 continue;
1196 }
1197
1198 String type = Variant::get_type_name(Variant::Type(i));
1199 change_type->add_icon_item(get_editor_theme_icon(type), type, i);
1200 }
1201 change_type->add_separator();
1202 change_type->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Remove Item"), Variant::VARIANT_MAX);
1203
1204 if (button_add_item) {
1205 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
1206 }
1207 } break;
1208 }
1209}
1210
1211void EditorPropertyDictionary::_edit_pressed() {
1212 Variant prop_val = get_edited_property_value();
1213 if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1214 VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
1215 get_edited_object()->set(get_edited_property(), prop_val);
1216 }
1217
1218 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1219 update_property();
1220}
1221
1222void EditorPropertyDictionary::_page_changed(int p_page) {
1223 if (updating) {
1224 return;
1225 }
1226 page_index = p_page;
1227 update_property();
1228}
1229
1230void EditorPropertyDictionary::_bind_methods() {
1231}
1232
1233EditorPropertyDictionary::EditorPropertyDictionary() {
1234 object.instantiate();
1235 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1236
1237 edit = memnew(Button);
1238 edit->set_h_size_flags(SIZE_EXPAND_FILL);
1239 edit->set_clip_text(true);
1240 edit->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_edit_pressed));
1241 edit->set_toggle_mode(true);
1242 add_child(edit);
1243 add_focusable(edit);
1244
1245 container = nullptr;
1246 button_add_item = nullptr;
1247 paginator = nullptr;
1248 change_type = memnew(PopupMenu);
1249 add_child(change_type);
1250 change_type->connect("id_pressed", callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
1251 changing_type_index = -1;
1252}
1253
1254///////////////////// LOCALIZABLE STRING ///////////////////////////
1255
1256void EditorPropertyLocalizableString::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
1257 if (p_property.begins_with("indices")) {
1258 int index = p_property.get_slice("/", 1).to_int();
1259
1260 Dictionary dict = object->get_dict().duplicate();
1261 Variant key = dict.get_key_at_index(index);
1262 dict[key] = p_value;
1263
1264 object->set_dict(dict);
1265 emit_changed(get_edited_property(), dict, "", true);
1266 }
1267}
1268
1269void EditorPropertyLocalizableString::_add_locale_popup() {
1270 locale_select->popup_locale_dialog();
1271}
1272
1273void EditorPropertyLocalizableString::_add_locale(const String &p_locale) {
1274 Dictionary dict = object->get_dict().duplicate();
1275 object->set_new_item_key(p_locale);
1276 object->set_new_item_value(String());
1277 dict[object->get_new_item_key()] = object->get_new_item_value();
1278
1279 emit_changed(get_edited_property(), dict, "", false);
1280 update_property();
1281}
1282
1283void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) {
1284 Dictionary dict = object->get_dict().duplicate();
1285
1286 Variant key = dict.get_key_at_index(p_index);
1287 dict.erase(key);
1288
1289 emit_changed(get_edited_property(), dict, "", false);
1290 update_property();
1291}
1292
1293void EditorPropertyLocalizableString::update_property() {
1294 Variant updated_val = get_edited_property_value();
1295
1296 if (updated_val.get_type() == Variant::NIL) {
1297 edit->set_text(TTR("Localizable String (Nil)")); // This provides symmetry with the array property.
1298 edit->set_pressed(false);
1299 if (container) {
1300 set_bottom_editor(nullptr);
1301 memdelete(container);
1302 button_add_item = nullptr;
1303 container = nullptr;
1304 }
1305 return;
1306 }
1307
1308 Dictionary dict = updated_val;
1309 object->set_dict(dict);
1310
1311 edit->set_text(vformat(TTR("Localizable String (size %d)"), dict.size()));
1312
1313 bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
1314 if (edit->is_pressed() != unfolded) {
1315 edit->set_pressed(unfolded);
1316 }
1317
1318 if (unfolded) {
1319 updating = true;
1320
1321 if (!container) {
1322 container = memnew(MarginContainer);
1323 container->set_theme_type_variation("MarginContainer4px");
1324 add_child(container);
1325 set_bottom_editor(container);
1326
1327 VBoxContainer *vbox = memnew(VBoxContainer);
1328 container->add_child(vbox);
1329
1330 property_vbox = memnew(VBoxContainer);
1331 property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1332 vbox->add_child(property_vbox);
1333
1334 paginator = memnew(EditorPaginator);
1335 paginator->connect("page_changed", callable_mp(this, &EditorPropertyLocalizableString::_page_changed));
1336 vbox->add_child(paginator);
1337 } else {
1338 // Queue children for deletion, deleting immediately might cause errors.
1339 for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {
1340 property_vbox->get_child(i)->queue_free();
1341 }
1342 }
1343
1344 int size = dict.size();
1345
1346 int max_page = MAX(0, size - 1) / page_length;
1347 page_index = MIN(page_index, max_page);
1348
1349 paginator->update(page_index, max_page);
1350 paginator->set_visible(max_page > 0);
1351
1352 int offset = page_index * page_length;
1353
1354 int amount = MIN(size - offset, page_length);
1355
1356 for (int i = 0; i < amount; i++) {
1357 String prop_name;
1358 Variant key;
1359 Variant value;
1360
1361 prop_name = "indices/" + itos(i + offset);
1362 key = dict.get_key_at_index(i + offset);
1363 value = dict.get_value_at_index(i + offset);
1364
1365 EditorProperty *prop = memnew(EditorPropertyText);
1366
1367 prop->set_object_and_property(object.ptr(), prop_name);
1368 int remove_index = 0;
1369
1370 String cs = key.get_construct_string();
1371 prop->set_label(cs);
1372 prop->set_tooltip_text(cs);
1373 remove_index = i + offset;
1374
1375 prop->set_selectable(false);
1376 prop->connect("property_changed", callable_mp(this, &EditorPropertyLocalizableString::_property_changed));
1377 prop->connect("object_id_selected", callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected));
1378
1379 HBoxContainer *hbox = memnew(HBoxContainer);
1380 property_vbox->add_child(hbox);
1381 hbox->add_child(prop);
1382 prop->set_h_size_flags(SIZE_EXPAND_FILL);
1383 Button *edit_btn = memnew(Button);
1384 edit_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
1385 hbox->add_child(edit_btn);
1386 edit_btn->connect("pressed", callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index));
1387
1388 prop->update_property();
1389 }
1390
1391 if (page_index == max_page) {
1392 button_add_item = EditorInspector::create_inspector_action_button(TTR("Add Translation"));
1393 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
1394 button_add_item->connect("pressed", callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup));
1395 property_vbox->add_child(button_add_item);
1396 }
1397
1398 updating = false;
1399
1400 } else {
1401 if (container) {
1402 set_bottom_editor(nullptr);
1403 memdelete(container);
1404 button_add_item = nullptr;
1405 container = nullptr;
1406 }
1407 }
1408}
1409
1410void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) {
1411 emit_signal(SNAME("object_id_selected"), p_property, p_id);
1412}
1413
1414void EditorPropertyLocalizableString::_notification(int p_what) {
1415 switch (p_what) {
1416 case NOTIFICATION_THEME_CHANGED:
1417 case NOTIFICATION_ENTER_TREE: {
1418 if (button_add_item) {
1419 button_add_item->set_icon(get_editor_theme_icon(SNAME("Add")));
1420 }
1421 } break;
1422 }
1423}
1424
1425void EditorPropertyLocalizableString::_edit_pressed() {
1426 Variant prop_val = get_edited_property_value();
1427 if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
1428 VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
1429 get_edited_object()->set(get_edited_property(), prop_val);
1430 }
1431
1432 get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());
1433 update_property();
1434}
1435
1436void EditorPropertyLocalizableString::_page_changed(int p_page) {
1437 if (updating) {
1438 return;
1439 }
1440 page_index = p_page;
1441 update_property();
1442}
1443
1444void EditorPropertyLocalizableString::_bind_methods() {
1445}
1446
1447EditorPropertyLocalizableString::EditorPropertyLocalizableString() {
1448 object.instantiate();
1449 page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));
1450
1451 edit = memnew(Button);
1452 edit->set_h_size_flags(SIZE_EXPAND_FILL);
1453 edit->set_clip_text(true);
1454 edit->connect("pressed", callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed));
1455 edit->set_toggle_mode(true);
1456 add_child(edit);
1457 add_focusable(edit);
1458
1459 container = nullptr;
1460 button_add_item = nullptr;
1461 paginator = nullptr;
1462 updating = false;
1463
1464 locale_select = memnew(EditorLocaleDialog);
1465 locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyLocalizableString::_add_locale));
1466 add_child(locale_select);
1467}
1468