Path: blob/master/editor/inspector/editor_properties_array_dict.cpp
20838 views
/**************************************************************************/1/* editor_properties_array_dict.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_properties_array_dict.h"3132#include "core/input/input.h"33#include "core/io/marshalls.h"34#include "editor/docks/inspector_dock.h"35#include "editor/editor_node.h"36#include "editor/editor_string_names.h"37#include "editor/file_system/editor_file_system.h"38#include "editor/gui/editor_spin_slider.h"39#include "editor/gui/editor_variant_type_selectors.h"40#include "editor/inspector/editor_properties.h"41#include "editor/inspector/editor_properties_vector.h"42#include "editor/settings/editor_settings.h"43#include "editor/themes/editor_scale.h"44#include "scene/gui/button.h"45#include "scene/gui/margin_container.h"4647bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {48String name = p_name;4950if (!name.begins_with("indices")) {51return false;52}5354int index;55if (name.begins_with("metadata/")) {56index = name.get_slicec('/', 2).to_int();57} else {58index = name.get_slicec('/', 1).to_int();59}6061array.set(index, p_value);62return true;63}6465bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const {66String name = p_name;6768if (!name.begins_with("indices")) {69return false;70}7172int index;73if (name.begins_with("metadata/")) {74index = name.get_slicec('/', 2).to_int();75} else {76index = name.get_slicec('/', 1).to_int();77}7879bool valid;80r_ret = array.get(index, &valid);8182if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {83r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();84}8586return valid;87}8889void EditorPropertyArrayObject::set_array(const Variant &p_array) {90array = p_array;91}9293Variant EditorPropertyArrayObject::get_array() {94return array;95}9697///////////////////9899bool EditorPropertyDictionaryObject::_set(const StringName &p_name, const Variant &p_value) {100String name = p_name;101102if (name == "new_item_key") {103new_item_key = p_value;104return true;105}106107if (name == "new_item_value") {108new_item_value = p_value;109return true;110}111112if (name.begins_with("indices")) {113dict = dict.duplicate();114int index = name.get_slicec('/', 1).to_int();115Variant key = dict.get_key_at_index(index);116dict[key] = p_value;117return true;118}119120return false;121}122123bool EditorPropertyDictionaryObject::_get(const StringName &p_name, Variant &r_ret) const {124if (!get_by_property_name(p_name, r_ret)) {125return false;126}127if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {128r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();129}130return true;131}132133bool EditorPropertyDictionaryObject::get_by_property_name(const String &p_name, Variant &r_ret) const {134String name = p_name;135136if (name == "new_item_key") {137r_ret = new_item_key;138return true;139}140141if (name == "new_item_value") {142r_ret = new_item_value;143return true;144}145146if (name == "new_item_key_name") {147r_ret = TTR("New Key:");148return true;149}150151if (name == "new_item_value_name") {152r_ret = TTR("New Value:");153return true;154}155156if (name.begins_with("indices")) {157int index = name.get_slicec('/', 1).to_int();158Variant key = dict.get_key_at_index(index);159r_ret = dict[key];160return true;161}162163if (name.begins_with("keys")) {164int index = name.get_slicec('/', 1).to_int();165Variant key = dict.get_key_at_index(index);166r_ret = key;167return true;168}169170return false;171}172173void EditorPropertyDictionaryObject::set_dict(const Dictionary &p_dict) {174dict = p_dict;175}176177Dictionary EditorPropertyDictionaryObject::get_dict() {178return dict;179}180181void EditorPropertyDictionaryObject::set_new_item_key(const Variant &p_new_item) {182new_item_key = p_new_item;183}184185Variant EditorPropertyDictionaryObject::get_new_item_key() {186return new_item_key;187}188189void EditorPropertyDictionaryObject::set_new_item_value(const Variant &p_new_item) {190new_item_value = p_new_item;191}192193Variant EditorPropertyDictionaryObject::get_new_item_value() {194return new_item_value;195}196197String EditorPropertyDictionaryObject::get_property_name_for_index(int p_index) {198switch (p_index) {199case NEW_KEY_INDEX:200return "new_item_key";201case NEW_VALUE_INDEX:202return "new_item_value";203default:204return "indices/" + itos(p_index);205}206}207208String EditorPropertyDictionaryObject::get_key_name_for_index(int p_index) {209switch (p_index) {210case NEW_KEY_INDEX:211return "new_item_key_name";212case NEW_VALUE_INDEX:213return "new_item_value_name";214default:215return "keys/" + itos(p_index);216}217}218219String EditorPropertyDictionaryObject::get_label_for_index(int p_index) {220switch (p_index) {221case NEW_KEY_INDEX:222return TTR("New Key:");223break;224case NEW_VALUE_INDEX:225return TTR("New Value:");226break;227default:228return dict.get_key_at_index(p_index).get_construct_string();229break;230}231}232233///////////////////// ARRAY ///////////////////////////234235void EditorPropertyArray::initialize_array(Variant &p_array) {236if (array_type == Variant::ARRAY && subtype != Variant::NIL) {237Array array;238StringName subtype_class;239Ref<Script> subtype_script;240if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {241if (ClassDB::class_exists(subtype_hint_string)) {242subtype_class = subtype_hint_string;243}244}245array.set_typed(subtype, subtype_class, subtype_script);246p_array = array;247} else {248VariantInternal::initialize(&p_array, array_type);249}250}251252void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {253if (!p_property.begins_with("indices")) {254return;255}256257if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {258p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.259}260261int index = p_property.get_slicec('/', 1).to_int();262263Variant array = object->get_array().duplicate();264array.set(index, p_value);265emit_changed(get_edited_property(), array, p_name, p_changing);266if (p_changing) {267object->set_array(array);268}269}270271void EditorPropertyArray::_change_type(Object *p_button, int p_slot_index) {272Button *button = Object::cast_to<Button>(p_button);273changing_type_index = p_slot_index;274Rect2 rect = button->get_screen_rect();275change_type->reset_size();276change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));277change_type->popup();278}279280void EditorPropertyArray::_change_type_menu(int p_index) {281if (p_index == Variant::VARIANT_MAX) {282_remove_pressed(changing_type_index);283return;284}285286ERR_FAIL_COND_MSG(287changing_type_index == EditorPropertyArrayObject::NOT_CHANGING_TYPE,288"Tried to change type of an array item, but no item was selected.");289290Variant value;291VariantInternal::initialize(&value, Variant::Type(p_index));292293Variant array = object->get_array().duplicate();294array.set(slots[changing_type_index].index, value);295296emit_changed(get_edited_property(), array);297}298299void EditorPropertyArray::_object_id_selected(const StringName &p_property, ObjectID p_id) {300emit_signal(SNAME("object_id_selected"), p_property, p_id);301}302303void EditorPropertyArray::_resource_selected(const String &p_path, Ref<Resource> p_resource) {304emit_signal(SNAME("resource_selected"), get_edited_property(), p_resource);305}306307void EditorPropertyArray::_create_new_property_slot() {308int idx = slots.size();309HBoxContainer *hbox = memnew(HBoxContainer);310311EditorProperty *prop = memnew(EditorPropertyNil);312313Button *reorder_button = memnew(Button);314reorder_button->set_accessibility_name(TTRC("Reorder"));315reorder_button->set_button_icon(get_editor_theme_icon(SNAME("TripleBar")));316reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE);317reorder_button->set_disabled(is_read_only());318reorder_button->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));319reorder_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input));320reorder_button->connect(SNAME("button_up"), callable_mp(this, &EditorPropertyArray::_reorder_button_up));321reorder_button->connect(SNAME("button_down"), callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(idx));322323hbox->add_child(prop);324325bool is_untyped_array = object->get_array().get_type() == Variant::ARRAY && subtype == Variant::NIL;326327Button *edit_btn = nullptr;328Button *remove_btn = nullptr;329if (is_untyped_array) {330edit_btn = memnew(Button);331edit_btn->set_accessibility_name(TTRC("Edit"));332edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));333edit_btn->set_disabled(is_read_only());334edit_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));335edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, idx));336} else {337remove_btn = memnew(Button);338remove_btn->set_accessibility_name(TTRC("Remove"));339remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));340remove_btn->set_disabled(is_read_only());341remove_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));342remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(idx));343}344property_vbox->add_child(hbox);345346Slot slot;347slot.prop = prop;348slot.object = object;349slot.container = hbox;350slot.reorder_button = reorder_button;351slot.edit_button = edit_btn;352slot.remove_button = remove_btn;353slot.set_index(idx + page_index * page_length);354slots.push_back(slot);355}356357void EditorPropertyArray::set_preview_value(bool p_preview_value) {358preview_value = p_preview_value;359}360361void EditorPropertyArray::update_property() {362Variant array = get_edited_property_value();363364String array_type_name = Variant::get_type_name(array_type);365String array_sub_type_name;366if (array_type == Variant::ARRAY && subtype != Variant::NIL) {367String type_name;368if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) {369type_name = subtype_hint_string;370} else {371type_name = Variant::get_type_name(subtype);372}373374if (preview_value) {375array_sub_type_name = vformat("[%s] ", type_name);376} else {377array_type_name = vformat("%s[%s]", array_type_name, type_name);378}379}380381if (!array.is_array()) {382if (preview_value) {383edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);384edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));385edit->set_text(array_type_name);386} else {387edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);388edit->set_button_icon(Ref<Texture2D>());389edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));390}391edit->set_pressed(false);392if (container) {393set_bottom_editor(nullptr);394memdelete(container);395button_add_item = nullptr;396container = nullptr;397slots.clear();398}399return;400}401402object->set_array(array);403404int size = array.call("size");405int max_page = MAX(0, size - 1) / page_length;406if (page_index > max_page) {407_page_changed(max_page);408}409410if (preview_value) {411String ctr_str = array.get_construct_string().trim_prefix(array_type_name + "(").trim_suffix(")").remove_char('\n');412if (array_type == Variant::ARRAY && subtype != Variant::NIL) {413int type_end = ctr_str.find("](");414if (type_end > 0) {415ctr_str = ctr_str.substr(type_end + 2);416}417}418419edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);420edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);421edit->set_button_icon(get_editor_theme_icon(array_type_name));422edit->set_text(vformat("%s%s", array_sub_type_name, ctr_str));423edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), array_type_name, array_sub_type_name, size));424} else {425edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);426edit->set_button_icon(Ref<Texture2D>());427edit->set_text(vformat(TTR("%s (size %d)"), array_type_name, size));428}429430bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());431if (edit->is_pressed() != unfolded) {432edit->set_pressed(unfolded);433}434435if (unfolded) {436updating = true;437438if (!container) {439container = memnew(PanelContainer);440add_child(container);441set_bottom_editor(container);442443VBoxContainer *vbox = memnew(VBoxContainer);444vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));445container->add_child(vbox);446447HBoxContainer *hbox = memnew(HBoxContainer);448vbox->add_child(hbox);449450Label *size_label = memnew(Label(TTR("Size:")));451size_label->set_h_size_flags(SIZE_EXPAND_FILL);452hbox->add_child(size_label);453454size_slider = memnew(EditorSpinSlider);455size_slider->set_step(1);456size_slider->set_max(INT32_MAX);457size_slider->set_editing_integer(true);458size_slider->set_h_size_flags(SIZE_EXPAND_FILL);459size_slider->set_read_only(is_read_only());460size_slider->set_accessibility_name(TTRC("Size"));461size_slider->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyArray::_length_changed));462hbox->add_child(size_slider);463464property_vbox = memnew(VBoxContainer);465property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));466property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);467vbox->add_child(property_vbox);468469button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Element"), SNAME("Add")));470button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_add_element));471button_add_item->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_add_item_draw));472SET_DRAG_FORWARDING_CD(button_add_item, EditorPropertyArray);473button_add_item->set_disabled(is_read_only());474button_add_item->set_accessibility_name(TTRC("Add"));475vbox->add_child(button_add_item);476477paginator = memnew(EditorPaginator);478paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed));479vbox->add_child(paginator);480481for (int i = 0; i < page_length; i++) {482_create_new_property_slot();483}484}485486size_slider->set_value(size);487property_vbox->set_visible(size > 0);488button_add_item->set_visible(page_index == max_page);489paginator->update(page_index, max_page);490paginator->set_visible(max_page > 0);491492for (Slot &slot : slots) {493bool slot_visible = &slot != &reorder_slot && slot.index < size;494slot.container->set_visible(slot_visible);495// If not visible no need to update it496if (!slot_visible) {497continue;498}499500int idx = slot.index;501Variant::Type value_type = subtype;502503if (value_type == Variant::NIL) {504value_type = array.get(idx).get_type();505}506507// Check if the editor property needs to be updated.508bool value_as_id = Object::cast_to<EncodedObjectAsID>(array.get(idx));509if (value_type != slot.type || (value_type == Variant::OBJECT && (value_as_id != slot.as_id))) {510slot.as_id = value_as_id;511slot.type = value_type;512EditorProperty *new_prop = nullptr;513if (value_type == Variant::OBJECT && value_as_id) {514EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);515editor->setup("Object");516new_prop = editor;517} else {518new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);519}520new_prop->set_selectable(false);521new_prop->set_use_folding(is_using_folding());522new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyArray::_property_changed));523new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyArray::_object_id_selected));524if (value_type == Variant::OBJECT) {525new_prop->connect("resource_selected", callable_mp(this, &EditorPropertyArray::_resource_selected), CONNECT_DEFERRED);526}527new_prop->set_h_size_flags(SIZE_EXPAND_FILL);528new_prop->set_read_only(is_read_only());529530if (slot.reorder_button) {531new_prop->add_inline_control(slot.reorder_button, INLINE_CONTROL_LEFT);532slot.reorder_button->get_parent()->move_child(slot.reorder_button, 0);533}534if (slot.edit_button) {535new_prop->add_inline_control(slot.edit_button, INLINE_CONTROL_RIGHT);536} else if (slot.remove_button) {537new_prop->add_inline_control(slot.remove_button, INLINE_CONTROL_RIGHT);538}539540slot.prop->add_sibling(new_prop, false);541slot.prop->queue_free();542slot.prop = new_prop;543slot.set_index(idx);544}545if (slot.index == changing_type_index) {546callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);547changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;548}549slot.prop->update_property();550}551552updating = false;553554} else {555if (container) {556set_bottom_editor(nullptr);557memdelete(container);558button_add_item = nullptr;559container = nullptr;560slots.clear();561}562}563}564565void EditorPropertyArray::_remove_pressed(int p_slot_index) {566Variant array = object->get_array().duplicate();567array.call("remove_at", slots[p_slot_index].index);568569emit_changed(get_edited_property(), array);570}571572void EditorPropertyArray::_button_draw() {573if (dropping) {574Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));575edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false);576}577}578579void EditorPropertyArray::_button_add_item_draw() {580if (dropping) {581Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));582button_add_item->draw_rect(Rect2(Point2(), button_add_item->get_size()), color, false);583}584}585586bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {587if (is_read_only()) {588return false;589}590591String allowed_type = Variant::get_type_name(subtype);592593// When the subtype is of type Object, an additional subtype may be specified in the hint string594// (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object".595if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {596allowed_type = subtype_hint_string;597}598599Dictionary drag_data = p_drag_data;600const String drop_type = drag_data.get("type", "");601602if (drop_type == "files") {603PackedStringArray files = drag_data["files"];604605for (const String &file : files) {606int idx_in_dir;607EditorFileSystemDirectory const *dir = EditorFileSystem::get_singleton()->find_file(file, &idx_in_dir);608if (!dir) {609return false;610}611StringName ftype = dir->get_file_type(idx_in_dir);612String script_class = dir->get_file_resource_script_class(idx_in_dir);613614for (String at : allowed_type.split(",", false)) {615at = at.strip_edges();616// Fail if one of the files is not of allowed type.617if (!ClassDB::is_parent_class(ftype, at) && !EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {618return false;619}620}621}622623// If no files fail, drop is valid.624return true;625}626627if (drop_type == "resource") {628Ref<Resource> res = drag_data["resource"];629if (res.is_null()) {630return false;631}632633String res_type = res->get_class();634StringName script_class;635if (res->get_script()) {636script_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());637}638639for (String at : allowed_type.split(",", false)) {640at = at.strip_edges();641if (ClassDB::is_parent_class(res_type, at) || EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {642return true;643}644}645646return false;647}648649if (drop_type == "nodes") {650Array node_paths = drag_data["nodes"];651652PackedStringArray allowed_subtype_array;653if (allowed_type == "NodePath") {654if (subtype_hint_string == "NodePath") {655return true;656} else {657for (String ast : subtype_hint_string.split(",", false)) {658ast = ast.strip_edges();659allowed_subtype_array.append(ast);660}661}662}663664bool is_drop_allowed = true;665666for (int i = 0; i < node_paths.size(); i++) {667const Node *dropped_node = get_node_or_null(node_paths[i]);668ERR_FAIL_NULL_V_MSG(dropped_node, false, "Could not get the dropped node by its path.");669670if (allowed_type != "NodePath") {671if (!ClassDB::is_parent_class(dropped_node->get_class_name(), allowed_type) &&672!EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, allowed_type)) {673// Fail if one of the nodes is not of allowed type.674return false;675}676}677678// The array of NodePaths is restricted to specific types using @export_node_path().679if (allowed_type == "NodePath" && subtype_hint_string != "NodePath") {680if (!allowed_subtype_array.has(dropped_node->get_class_name())) {681// The dropped node type was not found in the allowed subtype array, we must check if it inherits one of them.682for (const String &ast : allowed_subtype_array) {683if (ClassDB::is_parent_class(dropped_node->get_class_name(), ast) ||684EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, ast)) {685is_drop_allowed = true;686break;687} else {688is_drop_allowed = false;689}690}691if (!is_drop_allowed) {692break;693}694}695}696}697698return is_drop_allowed;699}700701return false;702}703704bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {705return _is_drop_valid(p_data);706}707708void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {709Dictionary drag_data = p_data;710const String drop_type = drag_data.get("type", "");711Variant array = object->get_array();712713// Handle the case where array is not initialized yet.714if (!array.is_array()) {715initialize_array(array);716} else {717array = array.duplicate();718}719720if (drop_type == "files") {721PackedStringArray files = drag_data["files"];722723// Loop the file array and add to existing array.724for (int i = 0; i < files.size(); i++) {725const String &file = files[i];726727Ref<Resource> res = ResourceLoader::load(file);728if (res.is_valid()) {729array.call("push_back", res);730}731}732733emit_changed(get_edited_property(), array);734}735736if (drop_type == "resource") {737Ref<Resource> res = drag_data["resource"];738739if (res.is_valid()) {740array.call("push_back", res);741742emit_changed(get_edited_property(), array);743}744}745746if (drop_type == "nodes") {747Array node_paths = drag_data["nodes"];748Node *base_node = get_base_node();749750for (int i = 0; i < node_paths.size(); i++) {751const NodePath &path = node_paths[i];752753if (subtype == Variant::OBJECT) {754array.call("push_back", get_node(path));755} else if (subtype == Variant::NODE_PATH) {756array.call("push_back", base_node->get_path().rel_path_to(path));757}758}759760emit_changed(get_edited_property(), array);761}762}763764Node *EditorPropertyArray::get_base_node() {765Node *base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());766767if (!base_node) {768base_node = get_tree()->get_edited_scene_root();769}770771return base_node;772}773774void EditorPropertyArray::_notification(int p_what) {775switch (p_what) {776case NOTIFICATION_DRAG_BEGIN: {777if (is_visible_in_tree()) {778if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {779dropping = true;780edit->queue_redraw();781if (button_add_item) {782button_add_item->queue_redraw();783}784}785}786} break;787788case NOTIFICATION_DRAG_END: {789if (dropping) {790dropping = false;791edit->queue_redraw();792if (button_add_item) {793button_add_item->queue_redraw();794}795}796} break;797}798}799800void EditorPropertyArray::_edit_pressed() {801Variant array = get_edited_property_value();802if (!array.is_array() && edit->is_pressed()) {803initialize_array(array);804emit_changed(get_edited_property(), array);805}806807get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());808update_property();809}810811void EditorPropertyArray::_page_changed(int p_page) {812if (updating) {813return;814}815page_index = p_page;816int i = p_page * page_length;817818if (reorder_slot.index < 0) {819for (Slot &slot : slots) {820slot.set_index(i);821i++;822}823} else {824int reorder_from_page = reorder_slot.index / page_length;825if (reorder_from_page < p_page) {826i++;827}828for (Slot &slot : slots) {829if (slot.index != reorder_slot.index) {830slot.set_index(i);831i++;832} else if (i == reorder_slot.index) {833i++;834}835}836}837update_property();838}839840void EditorPropertyArray::_length_changed(double p_page) {841if (updating) {842return;843}844845Variant array = object->get_array().duplicate();846array.call("resize", int(p_page));847848emit_changed(get_edited_property(), array);849}850851void EditorPropertyArray::_add_element() {852_length_changed(double(object->get_array().call("size")) + 1.0);853}854855void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {856array_type = p_array_type;857858// The format of p_hint_string is:859// subType/subTypeHint:nextSubtype ... etc.860if (!p_hint_string.is_empty()) {861int hint_subtype_separator = p_hint_string.find_char(':');862if (hint_subtype_separator >= 0) {863String subtype_string = p_hint_string.substr(0, hint_subtype_separator);864int slash_pos = subtype_string.find_char('/');865if (slash_pos >= 0) {866subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1).to_int());867subtype_string = subtype_string.substr(0, slash_pos);868}869870subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1);871subtype = Variant::Type(subtype_string.to_int());872} else {873subtype = Variant::get_type_by_name(p_hint_string);874875if (subtype == Variant::VARIANT_MAX) {876subtype = Variant::OBJECT;877subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;878subtype_hint_string = p_hint_string;879}880}881}882}883884void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) {885if (reorder_slot.index < 0 || is_read_only()) {886return;887}888889Ref<InputEventMouseMotion> mm = p_event;890if (mm.is_valid()) {891Variant array = object->get_array();892int size = array.call("size");893894// Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point.895reorder_mouse_y_delta += mm->get_relative().y;896897// Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if898// already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta).899if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) {900reorder_mouse_y_delta = 0.0f;901return;902}903904float required_y_distance = 20.0f * EDSCALE;905if (Math::abs(reorder_mouse_y_delta) > required_y_distance) {906int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1;907reorder_mouse_y_delta -= required_y_distance * direction;908909reorder_to_index += direction;910911property_vbox->move_child(reorder_slot.container, reorder_to_index % page_length);912913if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) {914// Automatically move to the next/previous page.915_page_changed(page_index + direction);916}917// Ensure the moving element is visible in the root inspector.918EditorInspector *parent_inspector = get_parent_inspector();919if (parent_inspector) {920// Defer to prevent moving elements from not displaying properly, especially near borders.921callable_mp((ScrollContainer *)parent_inspector->get_root_inspector(), &ScrollContainer::ensure_control_visible).call_deferred(reorder_slot.container);922}923}924}925}926927void EditorPropertyArray::_reorder_button_down(int p_slot_index) {928if (is_read_only()) {929return;930}931reorder_slot = slots[p_slot_index];932reorder_to_index = reorder_slot.index;933// Ideally it'd to be able to show the mouse but I had issues with934// Control's `mouse_exit()`/`mouse_entered()` signals not getting called.935Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED);936}937938void EditorPropertyArray::_reorder_button_up() {939if (is_read_only()) {940return;941}942943if (reorder_slot.index != reorder_to_index) {944// Move the element.945Variant array = object->get_array().duplicate();946947property_vbox->move_child(reorder_slot.container, reorder_slot.index % page_length);948Variant value_to_move = array.get(reorder_slot.index);949array.call("remove_at", reorder_slot.index);950array.call("insert", reorder_to_index, value_to_move);951952slots[reorder_to_index % page_length].reorder_button->grab_focus();953emit_changed(get_edited_property(), array);954}955956Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_VISIBLE);957958ERR_FAIL_NULL(reorder_slot.reorder_button);959reorder_slot.reorder_button->warp_mouse(reorder_slot.reorder_button->get_size() / 2.0f);960reorder_to_index = -1;961reorder_mouse_y_delta = 0.0f;962reorder_slot = Slot();963_page_changed(page_index);964}965966bool EditorPropertyArray::is_colored(ColorationMode p_mode) {967return p_mode == COLORATION_CONTAINER_RESOURCE;968}969970EditorPropertyArray::EditorPropertyArray() {971object.instantiate();972page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));973974edit = memnew(Button);975edit->set_accessibility_name(TTRC("Edit"));976edit->set_h_size_flags(SIZE_EXPAND_FILL);977edit->set_clip_text(true);978edit->set_theme_type_variation(SNAME("EditorInspectorButton"));979edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_edit_pressed));980edit->set_toggle_mode(true);981SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray);982edit->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_draw));983add_child(edit);984add_focusable(edit);985986change_type = memnew(EditorVariantTypePopupMenu(true));987add_child(change_type);988change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyArray::_change_type_menu));989changing_type_index = -1;990991subtype = Variant::NIL;992subtype_hint = PROPERTY_HINT_NONE;993subtype_hint_string = "";994has_borders = true;995}996997///////////////////// DICTIONARY ///////////////////////////998999void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {1000if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {1001Dictionary dict;1002StringName key_subtype_class;1003Ref<Script> key_subtype_script;1004if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {1005key_subtype_class = key_subtype_hint_string;1006}1007StringName value_subtype_class;1008Ref<Script> value_subtype_script;1009if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {1010value_subtype_class = value_subtype_hint_string;1011}1012dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);1013p_dictionary = dict;1014} else {1015VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);1016}1017}10181019void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {1020if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {1021p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.1022}10231024object->set(p_property, p_value);1025bool new_item_or_key = !p_property.begins_with("indices");1026emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing || new_item_or_key);1027if (new_item_or_key) {1028update_property();1029}1030}10311032void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) {1033Button *button = Object::cast_to<Button>(p_button);1034int index = slots[p_slot_index].index;1035Rect2 rect = button->get_screen_rect();1036change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), index < 0);1037change_type->reset_size();1038change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));1039change_type->popup();1040changing_type_index = index;1041}10421043void EditorPropertyDictionary::_add_key_value() {1044// Do not allow nil as valid key. I experienced errors with this1045if (object->get_new_item_key().get_type() == Variant::NIL) {1046return;1047}10481049Dictionary dict = object->get_dict().duplicate();1050Variant new_key = object->get_new_item_key();1051Variant new_value = object->get_new_item_value();1052dict[new_key] = new_value;10531054Variant::Type type = new_key.get_type();1055new_key.zero();1056VariantInternal::initialize(&new_key, type);1057object->set_new_item_key(new_key);10581059type = new_value.get_type();1060new_value.zero();1061VariantInternal::initialize(&new_value, type);1062object->set_new_item_value(new_value);10631064object->set_dict(dict);1065slots[(dict.size() - 1) % page_length].update_prop_or_index();1066emit_changed(get_edited_property(), dict);1067}10681069void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {1070HBoxContainer *hbox = memnew(HBoxContainer);10711072EditorProperty *prop_key = nullptr;1073if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1074prop_key = memnew(EditorPropertyNil);1075}10761077EditorProperty *prop = memnew(EditorPropertyNil);1078prop->set_h_size_flags(SIZE_EXPAND_FILL);1079hbox->add_child(prop);1080if (prop_key) {1081prop->add_inline_control(prop_key, INLINE_CONTROL_LEFT);1082}10831084bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;1085bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;10861087Button *edit_btn = nullptr;1088Button *remove_btn = nullptr;1089if (is_untyped_dict) {1090edit_btn = memnew(Button);1091edit_btn->set_accessibility_name(TTRC("Edit"));1092edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));1093edit_btn->set_disabled(is_read_only());1094edit_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));1095edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));1096} else if (p_idx >= 0) {1097remove_btn = memnew(Button);1098remove_btn->set_accessibility_name(TTRC("Remove"));1099remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));1100remove_btn->set_disabled(is_read_only());1101remove_btn->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));1102remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));1103}11041105if (add_panel) {1106add_panel->get_child(0)->add_child(hbox);1107} else {1108property_vbox->add_child(hbox);1109}11101111Slot slot;1112slot.prop = prop;1113slot.prop_key = prop_key;1114slot.object = object;1115slot.container = hbox;1116slot.edit_button = edit_btn;1117slot.remove_button = remove_btn;1118int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0);1119slot.set_index(index);1120slots.push_back(slot);1121}11221123void EditorPropertyDictionary::_change_type_menu(int p_index) {1124ERR_FAIL_COND_MSG(1125changing_type_index == EditorPropertyDictionaryObject::NOT_CHANGING_TYPE,1126"Tried to change the type of a dict key or value, but nothing was selected.");11271128Variant value;1129switch (changing_type_index) {1130case EditorPropertyDictionaryObject::NEW_KEY_INDEX:1131case EditorPropertyDictionaryObject::NEW_VALUE_INDEX:1132VariantInternal::initialize(&value, Variant::Type(p_index));1133if (changing_type_index == EditorPropertyDictionaryObject::NEW_KEY_INDEX) {1134object->set_new_item_key(value);1135} else {1136object->set_new_item_value(value);1137}1138update_property();1139break;11401141default:1142Dictionary dict = object->get_dict().duplicate();1143Variant key = dict.get_key_at_index(changing_type_index);1144if (p_index < Variant::VARIANT_MAX) {1145VariantInternal::initialize(&value, Variant::Type(p_index));1146dict[key] = value;1147} else {1148dict.erase(key);1149object->set_dict(dict);1150for (Slot &slot : slots) {1151slot.update_prop_or_index();1152}1153}11541155emit_changed(get_edited_property(), dict);1156}1157}11581159void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {1160PackedStringArray types = p_hint_string.split(";");1161if (types.size() > 0 && !types[0].is_empty()) {1162String key = types[0];1163int hint_key_subtype_separator = key.find_char(':');1164if (hint_key_subtype_separator >= 0) {1165String key_subtype_string = key.substr(0, hint_key_subtype_separator);1166int slash_pos = key_subtype_string.find_char('/');1167if (slash_pos >= 0) {1168key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1).to_int());1169key_subtype_string = key_subtype_string.substr(0, slash_pos);1170}11711172key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1);1173key_subtype = Variant::Type(key_subtype_string.to_int());1174} else {1175key_subtype = Variant::get_type_by_name(key);11761177if (key_subtype == Variant::VARIANT_MAX) {1178key_subtype = Variant::OBJECT;1179key_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;1180key_subtype_hint_string = key;1181}1182}11831184Variant new_key = object->get_new_item_key();1185VariantInternal::initialize(&new_key, key_subtype);1186object->set_new_item_key(new_key);1187}11881189if (types.size() > 1 && !types[1].is_empty()) {1190String value = types[1];1191int hint_value_subtype_separator = value.find_char(':');1192if (hint_value_subtype_separator >= 0) {1193String value_subtype_string = value.substr(0, hint_value_subtype_separator);1194int slash_pos = value_subtype_string.find_char('/');1195if (slash_pos >= 0) {1196value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1).to_int());1197value_subtype_string = value_subtype_string.substr(0, slash_pos);1198}11991200value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1);1201value_subtype = Variant::Type(value_subtype_string.to_int());1202} else {1203value_subtype = Variant::get_type_by_name(value);12041205if (value_subtype == Variant::VARIANT_MAX) {1206value_subtype = Variant::OBJECT;1207value_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;1208value_subtype_hint_string = value;1209}1210}12111212Variant new_value = object->get_new_item_value();1213VariantInternal::initialize(&new_value, value_subtype);1214object->set_new_item_value(new_value);1215}1216}12171218void EditorPropertyDictionary::set_preview_value(bool p_preview_value) {1219preview_value = p_preview_value;1220}12211222void EditorPropertyDictionary::update_property() {1223Variant updated_val = get_edited_property_value();12241225String dict_type_name = "Dictionary";1226String dict_sub_type_name;1227if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {1228String key_subtype_name = "Variant";1229if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {1230key_subtype_name = key_subtype_hint_string;1231} else if (key_subtype != Variant::NIL) {1232key_subtype_name = Variant::get_type_name(key_subtype);1233}1234String value_subtype_name = "Variant";1235if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {1236value_subtype_name = value_subtype_hint_string;1237} else if (value_subtype != Variant::NIL) {1238value_subtype_name = Variant::get_type_name(value_subtype);1239}1240if (preview_value) {1241dict_sub_type_name = vformat("[%s, %s] ", key_subtype_name, value_subtype_name);1242} else {1243dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);1244}1245}12461247if (updated_val.get_type() != Variant::DICTIONARY) {1248if (preview_value) {1249edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);1250edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));1251edit->set_text(dict_type_name);1252} else {1253edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);1254edit->set_button_icon(Ref<Texture2D>());1255edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name));1256}1257edit->set_pressed(false);1258if (container) {1259set_bottom_editor(nullptr);1260memdelete(container);1261button_add_item = nullptr;1262container = nullptr;1263add_panel = nullptr;1264slots.clear();1265}1266return;1267}12681269Dictionary dict = updated_val;1270object->set_dict(updated_val);12711272if (preview_value) {1273String ctr_str = updated_val.get_construct_string().remove_char('\n');1274if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {1275int type_end = ctr_str.find("](");1276if (type_end > 0) {1277ctr_str = ctr_str.substr(type_end + 2).trim_suffix(")");1278}1279}12801281edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);1282edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);1283edit->set_button_icon(get_editor_theme_icon(dict_type_name));1284edit->set_text(vformat("%s%s", dict_sub_type_name, ctr_str));1285edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), dict_type_name, dict_sub_type_name, dict.size()));1286} else {1287edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);1288edit->set_button_icon(Ref<Texture2D>());1289edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));1290}12911292bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());1293if (edit->is_pressed() != unfolded) {1294edit->set_pressed(unfolded);1295}12961297if (unfolded) {1298updating = true;12991300if (!container) {1301container = memnew(PanelContainer);1302add_child(container);1303set_bottom_editor(container);13041305VBoxContainer *vbox = memnew(VBoxContainer);1306vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));1307container->add_child(vbox);13081309property_vbox = memnew(VBoxContainer);1310property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));1311property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);1312vbox->add_child(property_vbox);13131314paginator = memnew(EditorPaginator);1315paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));1316vbox->add_child(paginator);13171318for (int i = 0; i < page_length; i++) {1319_create_new_property_slot(slots.size());1320}13211322add_panel = memnew(PanelContainer);1323property_vbox->add_child(add_panel);1324add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));1325VBoxContainer *add_vbox = memnew(VBoxContainer);1326add_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));1327add_panel->add_child(add_vbox);13281329_create_new_property_slot(EditorPropertyDictionaryObject::NEW_KEY_INDEX);1330_create_new_property_slot(EditorPropertyDictionaryObject::NEW_VALUE_INDEX);13311332button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Key/Value Pair"), SNAME("Add")));1333button_add_item->set_disabled(is_read_only());1334button_add_item->set_accessibility_name(TTRC("Add"));1335button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_add_key_value));1336add_vbox->add_child(button_add_item);1337}13381339int size = dict.size();13401341int max_page = MAX(0, size - 1) / page_length;1342if (page_index > max_page) {1343_page_changed(max_page);1344}13451346paginator->update(page_index, max_page);1347paginator->set_visible(max_page > 0);13481349add_panel->set_visible(page_index == max_page);13501351for (Slot &slot : slots) {1352bool slot_visible = slot.index < size;1353slot.container->set_visible(slot_visible);1354// If not visible no need to update it.1355if (!slot_visible) {1356continue;1357}13581359// Check if the editor property key needs to be updated.1360if (slot.prop_key) {1361Variant key;1362object->get_by_property_name(slot.key_name, key);1363Variant::Type key_type = key.get_type();13641365bool key_as_id = Object::cast_to<EncodedObjectAsID>(key);1366if (key_type != slot.key_type || (key_type == Variant::OBJECT && key_as_id != slot.key_as_id)) {1367slot.key_as_id = key_as_id;1368slot.key_type = key_type;1369EditorProperty *new_prop = nullptr;1370if (key_type == Variant::OBJECT && key_as_id) {1371EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);1372editor->setup("Object");1373new_prop = editor;1374} else {1375new_prop = EditorInspector::instantiate_property_editor(this, key_type, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE);1376}1377new_prop->set_read_only(true);1378new_prop->set_selectable(false);1379new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));1380new_prop->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1381new_prop->set_draw_background(false);1382new_prop->set_use_folding(is_using_folding());1383new_prop->set_h_size_flags(SIZE_EXPAND_FILL);1384new_prop->set_draw_label(false);1385EditorPropertyArray *arr_prop = Object::cast_to<EditorPropertyArray>(new_prop);1386if (arr_prop) {1387arr_prop->set_preview_value(true);1388}1389EditorPropertyDictionary *dict_prop = Object::cast_to<EditorPropertyDictionary>(new_prop);1390if (dict_prop) {1391dict_prop->set_preview_value(true);1392}1393slot.set_key_prop(new_prop);1394if (slot.prop) {1395slot.prop->add_inline_control(new_prop, INLINE_CONTROL_LEFT);1396}1397}1398}13991400Variant value;1401object->get_by_property_name(slot.prop_name, value);14021403Variant::Type value_type;14041405if (dict.is_typed_value() && value_subtype != Variant::NIL && slot.prop_key) {1406value_type = value_subtype;1407} else {1408value_type = value.get_type();1409}14101411// Check if the editor property needs to be updated.1412bool value_as_id = Object::cast_to<EncodedObjectAsID>(value);1413if (value_type != slot.type || (value_type == Variant::OBJECT && value_as_id != slot.as_id)) {1414slot.as_id = value_as_id;1415slot.type = value_type;1416EditorProperty *new_prop = nullptr;1417if (value_type == Variant::OBJECT && value_as_id) {1418EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);1419editor->setup("Object");1420new_prop = editor;1421} else {1422bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;1423new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,1424use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);1425}1426new_prop->set_selectable(false);1427new_prop->set_use_folding(is_using_folding());1428new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed));1429new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));1430if (value_type == Variant::OBJECT) {1431new_prop->connect("resource_selected", callable_mp(this, &EditorPropertyDictionary::_resource_selected), CONNECT_DEFERRED);1432}1433new_prop->set_h_size_flags(SIZE_EXPAND_FILL);1434if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1435new_prop->set_label(" ");1436new_prop->set_label_overlayed(true);1437}1438new_prop->set_read_only(is_read_only());1439if (slot.remove_button) {1440new_prop->add_inline_control(slot.remove_button, INLINE_CONTROL_RIGHT);1441}1442if (slot.edit_button) {1443new_prop->add_inline_control(slot.edit_button, INLINE_CONTROL_RIGHT);1444}1445if (slot.prop_key) {1446new_prop->add_inline_control(slot.prop_key, INLINE_CONTROL_LEFT);1447}14481449slot.set_prop(new_prop);14501451} else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1452Variant key = dict.get_key_at_index(slot.index);1453String cs = key.get_construct_string();1454slot.prop->set_tooltip_text(cs);1455}14561457// We need to grab the focus of the property that is being changed, even if the type didn't actually changed.1458// Otherwise, focus will stay on the change type button, which is not very user friendly.1459if (changing_type_index == slot.index) {1460callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);1461changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again.1462}14631464slot.prop->update_property();1465if (slot.prop_key) {1466slot.prop_key->update_property();1467}1468}1469updating = false;14701471} else {1472if (container) {1473set_bottom_editor(nullptr);1474memdelete(container);1475button_add_item = nullptr;1476container = nullptr;1477add_panel = nullptr;1478slots.clear();1479}1480}1481}14821483void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {1484Dictionary dict = object->get_dict().duplicate();1485int index = slots[p_slot_index].index;1486dict.erase(dict.get_key_at_index(index));14871488emit_changed(get_edited_property(), dict);1489}14901491void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {1492emit_signal(SNAME("object_id_selected"), p_property, p_id);1493}14941495void EditorPropertyDictionary::_resource_selected(const String &p_path, Ref<Resource> p_resource) {1496emit_signal(SNAME("resource_selected"), get_edited_property(), p_resource);1497}14981499void EditorPropertyDictionary::_notification(int p_what) {1500switch (p_what) {1501case NOTIFICATION_THEME_CHANGED: {1502if (button_add_item) {1503add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));1504}1505} break;1506}1507}15081509void EditorPropertyDictionary::_edit_pressed() {1510Variant prop_val = get_edited_property_value();1511if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {1512initialize_dictionary(prop_val);1513emit_changed(get_edited_property(), prop_val);1514}15151516get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());1517update_property();1518}15191520void EditorPropertyDictionary::_page_changed(int p_page) {1521page_index = p_page;1522int i = p_page * page_length;1523for (Slot &slot : slots) {1524if (slot.index > -1) {1525slot.set_index(i);1526i++;1527}1528}1529if (updating) {1530return;1531}1532update_property();1533}15341535bool EditorPropertyDictionary::is_colored(ColorationMode p_mode) {1536return p_mode == COLORATION_CONTAINER_RESOURCE;1537}15381539EditorPropertyDictionary::EditorPropertyDictionary() {1540object.instantiate();1541page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));15421543edit = memnew(Button);1544edit->set_accessibility_name(TTRC("Edit"));1545edit->set_h_size_flags(SIZE_EXPAND_FILL);1546edit->set_clip_text(true);1547edit->set_theme_type_variation(SNAME("EditorInspectorButton"));1548edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_edit_pressed));1549edit->set_toggle_mode(true);1550add_child(edit);1551add_focusable(edit);15521553container = nullptr;1554button_add_item = nullptr;1555paginator = nullptr;1556change_type = memnew(EditorVariantTypePopupMenu(true));1557add_child(change_type);1558change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));1559changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE;1560has_borders = true;15611562key_subtype = Variant::NIL;1563key_subtype_hint = PROPERTY_HINT_NONE;1564key_subtype_hint_string = "";15651566value_subtype = Variant::NIL;1567value_subtype_hint = PROPERTY_HINT_NONE;1568value_subtype_hint_string = "";1569}15701571///////////////////// LOCALIZABLE STRING ///////////////////////////15721573void EditorPropertyLocalizableString::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {1574if (p_property.begins_with("indices")) {1575int index = p_property.get_slicec('/', 1).to_int();15761577Dictionary dict = object->get_dict().duplicate();1578Variant key = dict.get_key_at_index(index);1579dict[key] = p_value;15801581object->set_dict(dict);1582emit_changed(get_edited_property(), dict, "", true);1583}1584}15851586void EditorPropertyLocalizableString::_add_locale_popup() {1587locale_select->popup_locale_dialog();1588}15891590void EditorPropertyLocalizableString::_add_locale(const String &p_locale) {1591Dictionary dict = object->get_dict().duplicate();1592object->set_new_item_key(p_locale);1593object->set_new_item_value(String());1594dict[object->get_new_item_key()] = object->get_new_item_value();15951596emit_changed(get_edited_property(), dict, "", false);1597update_property();1598}15991600void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) {1601Dictionary dict = object->get_dict().duplicate();16021603Variant key = dict.get_key_at_index(p_index);1604dict.erase(key);16051606emit_changed(get_edited_property(), dict, "", false);1607update_property();1608}16091610void EditorPropertyLocalizableString::update_property() {1611Variant updated_val = get_edited_property_value();16121613if (updated_val.get_type() == Variant::NIL) {1614edit->set_text(TTR("Localizable String (Nil)")); // This provides symmetry with the array property.1615edit->set_pressed(false);1616if (container) {1617set_bottom_editor(nullptr);1618memdelete(container);1619button_add_item = nullptr;1620container = nullptr;1621}1622return;1623}16241625Dictionary dict = updated_val;1626object->set_dict(dict);16271628edit->set_text(vformat(TTR("Localizable String (size %d)"), dict.size()));16291630bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());1631if (edit->is_pressed() != unfolded) {1632edit->set_pressed(unfolded);1633}16341635if (unfolded) {1636updating = true;16371638if (!container) {1639container = memnew(MarginContainer);1640container->set_theme_type_variation("MarginContainer4px");1641add_child(container);1642set_bottom_editor(container);16431644VBoxContainer *vbox = memnew(VBoxContainer);1645vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));1646container->add_child(vbox);16471648property_vbox = memnew(VBoxContainer);1649property_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));1650property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);1651vbox->add_child(property_vbox);16521653paginator = memnew(EditorPaginator);1654paginator->connect("page_changed", callable_mp(this, &EditorPropertyLocalizableString::_page_changed));1655vbox->add_child(paginator);1656} else {1657// Queue children for deletion, deleting immediately might cause errors.1658for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {1659property_vbox->get_child(i)->queue_free();1660}1661}16621663int size = dict.size();16641665int max_page = MAX(0, size - 1) / page_length;1666page_index = MIN(page_index, max_page);16671668paginator->update(page_index, max_page);1669paginator->set_visible(max_page > 0);16701671int offset = page_index * page_length;16721673int amount = MIN(size - offset, page_length);16741675for (int i = 0; i < amount; i++) {1676String prop_name;1677Variant key;1678Variant value;16791680prop_name = "indices/" + itos(i + offset);1681key = dict.get_key_at_index(i + offset);1682value = dict.get_value_at_index(i + offset);16831684EditorProperty *prop = memnew(EditorPropertyText);16851686prop->set_object_and_property(object.ptr(), prop_name);1687int remove_index = 0;16881689String cs = key.get_construct_string();1690prop->set_label(cs);1691prop->set_tooltip_text(cs);1692remove_index = i + offset;16931694prop->set_selectable(false);1695prop->connect("property_changed", callable_mp(this, &EditorPropertyLocalizableString::_property_changed));1696prop->connect("object_id_selected", callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected));16971698HBoxContainer *hbox = memnew(HBoxContainer);1699property_vbox->add_child(hbox);1700hbox->add_child(prop);1701prop->set_h_size_flags(SIZE_EXPAND_FILL);1702Button *edit_btn = memnew(Button);1703edit_btn->set_accessibility_name(TTRC("Remove Translation"));1704edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));1705hbox->add_child(edit_btn);1706edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index));17071708prop->update_property();1709}17101711if (page_index == max_page) {1712button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Translation"), SNAME("Add")));1713button_add_item->set_accessibility_name(TTRC("Add Translation"));1714button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup));1715property_vbox->add_child(button_add_item);1716}17171718updating = false;17191720} else {1721if (container) {1722set_bottom_editor(nullptr);1723memdelete(container);1724button_add_item = nullptr;1725container = nullptr;1726}1727}1728}17291730void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) {1731emit_signal(SNAME("object_id_selected"), p_property, p_id);1732}17331734void EditorPropertyLocalizableString::_edit_pressed() {1735Variant prop_val = get_edited_property_value();1736if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {1737VariantInternal::initialize(&prop_val, Variant::DICTIONARY);1738get_edited_object()->set(get_edited_property(), prop_val);1739}17401741get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());1742update_property();1743}17441745void EditorPropertyLocalizableString::_page_changed(int p_page) {1746if (updating) {1747return;1748}1749page_index = p_page;1750update_property();1751}17521753EditorPropertyLocalizableString::EditorPropertyLocalizableString() {1754object.instantiate();1755page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));17561757edit = memnew(Button);1758edit->set_accessibility_name(TTRC("Edit"));1759edit->set_h_size_flags(SIZE_EXPAND_FILL);1760edit->set_clip_text(true);1761edit->set_theme_type_variation(SNAME("EditorInspectorButton"));1762edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed));1763edit->set_toggle_mode(true);1764add_child(edit);1765add_focusable(edit);17661767container = nullptr;1768button_add_item = nullptr;1769paginator = nullptr;1770updating = false;17711772locale_select = memnew(EditorLocaleDialog);1773locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyLocalizableString::_add_locale));1774add_child(locale_select);1775}177617771778