Path: blob/master/editor/inspector/editor_properties_array_dict.cpp
9903 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::_create_new_property_slot() {304int idx = slots.size();305HBoxContainer *hbox = memnew(HBoxContainer);306307Button *reorder_button = memnew(Button);308reorder_button->set_accessibility_name(TTRC("Reorder"));309reorder_button->set_button_icon(get_editor_theme_icon(SNAME("TripleBar")));310reorder_button->set_default_cursor_shape(Control::CURSOR_MOVE);311reorder_button->set_disabled(is_read_only());312reorder_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorPropertyArray::_reorder_button_gui_input));313reorder_button->connect(SNAME("button_up"), callable_mp(this, &EditorPropertyArray::_reorder_button_up));314reorder_button->connect(SNAME("button_down"), callable_mp(this, &EditorPropertyArray::_reorder_button_down).bind(idx));315316hbox->add_child(reorder_button);317EditorProperty *prop = memnew(EditorPropertyNil);318hbox->add_child(prop);319320bool is_untyped_array = object->get_array().get_type() == Variant::ARRAY && subtype == Variant::NIL;321322if (is_untyped_array) {323Button *edit_btn = memnew(Button);324edit_btn->set_accessibility_name(TTRC("Edit"));325edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));326edit_btn->set_disabled(is_read_only());327edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_change_type).bind(edit_btn, idx));328hbox->add_child(edit_btn);329} else {330Button *remove_btn = memnew(Button);331remove_btn->set_accessibility_name(TTRC("Remove"));332remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));333remove_btn->set_disabled(is_read_only());334remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_remove_pressed).bind(idx));335hbox->add_child(remove_btn);336}337property_vbox->add_child(hbox);338339Slot slot;340slot.prop = prop;341slot.object = object;342slot.container = hbox;343slot.reorder_button = reorder_button;344slot.set_index(idx + page_index * page_length);345slots.push_back(slot);346}347348void EditorPropertyArray::set_preview_value(bool p_preview_value) {349preview_value = p_preview_value;350}351352void EditorPropertyArray::update_property() {353Variant array = get_edited_property_value();354355String array_type_name = Variant::get_type_name(array_type);356String array_sub_type_name;357if (array_type == Variant::ARRAY && subtype != Variant::NIL) {358String type_name;359if (subtype == Variant::OBJECT && (subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || subtype_hint == PROPERTY_HINT_NODE_TYPE)) {360type_name = subtype_hint_string;361} else {362type_name = Variant::get_type_name(subtype);363}364365if (preview_value) {366array_sub_type_name = vformat("[%s] ", type_name);367} else {368array_type_name = vformat("%s[%s]", array_type_name, type_name);369}370}371372if (!array.is_array()) {373if (preview_value) {374edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);375edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));376edit->set_text(array_type_name);377} else {378edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);379edit->set_button_icon(Ref<Texture2D>());380edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));381}382edit->set_pressed(false);383if (container) {384set_bottom_editor(nullptr);385memdelete(container);386button_add_item = nullptr;387container = nullptr;388slots.clear();389}390return;391}392393object->set_array(array);394395int size = array.call("size");396int max_page = MAX(0, size - 1) / page_length;397if (page_index > max_page) {398_page_changed(max_page);399}400401if (preview_value) {402String ctr_str = array.get_construct_string().trim_prefix(array_type_name + "(").trim_suffix(")").remove_char('\n');403if (array_type == Variant::ARRAY && subtype != Variant::NIL) {404int type_end = ctr_str.find("](");405if (type_end > 0) {406ctr_str = ctr_str.substr(type_end + 2);407}408}409410edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);411edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);412edit->set_button_icon(get_editor_theme_icon(array_type_name));413edit->set_text(vformat("%s%s", array_sub_type_name, ctr_str));414edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), array_type_name, array_sub_type_name, size));415} else {416edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);417edit->set_button_icon(Ref<Texture2D>());418edit->set_text(vformat(TTR("%s (size %d)"), array_type_name, size));419}420421bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());422if (edit->is_pressed() != unfolded) {423edit->set_pressed(unfolded);424}425426if (unfolded) {427updating = true;428429if (!container) {430container = memnew(PanelContainer);431add_child(container);432set_bottom_editor(container);433434VBoxContainer *vbox = memnew(VBoxContainer);435container->add_child(vbox);436437HBoxContainer *hbox = memnew(HBoxContainer);438vbox->add_child(hbox);439440Label *size_label = memnew(Label(TTR("Size:")));441size_label->set_h_size_flags(SIZE_EXPAND_FILL);442hbox->add_child(size_label);443444size_slider = memnew(EditorSpinSlider);445size_slider->set_step(1);446size_slider->set_max(INT32_MAX);447size_slider->set_editing_integer(true);448size_slider->set_h_size_flags(SIZE_EXPAND_FILL);449size_slider->set_read_only(is_read_only());450size_slider->set_accessibility_name(TTRC("Size"));451size_slider->connect(SceneStringName(value_changed), callable_mp(this, &EditorPropertyArray::_length_changed));452hbox->add_child(size_slider);453454property_vbox = memnew(VBoxContainer);455property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);456vbox->add_child(property_vbox);457458button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Element"), SNAME("Add")));459button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_add_element));460button_add_item->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_add_item_draw));461SET_DRAG_FORWARDING_CD(button_add_item, EditorPropertyArray);462button_add_item->set_disabled(is_read_only());463button_add_item->set_accessibility_name(TTRC("Add"));464vbox->add_child(button_add_item);465466paginator = memnew(EditorPaginator);467paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed));468vbox->add_child(paginator);469470for (int i = 0; i < page_length; i++) {471_create_new_property_slot();472}473}474475size_slider->set_value(size);476property_vbox->set_visible(size > 0);477button_add_item->set_visible(page_index == max_page);478paginator->update(page_index, max_page);479paginator->set_visible(max_page > 0);480481for (Slot &slot : slots) {482bool slot_visible = &slot != &reorder_slot && slot.index < size;483slot.container->set_visible(slot_visible);484// If not visible no need to update it485if (!slot_visible) {486continue;487}488489int idx = slot.index;490Variant::Type value_type = subtype;491492if (value_type == Variant::NIL) {493value_type = array.get(idx).get_type();494}495496// Check if the editor property needs to be updated.497bool value_as_id = Object::cast_to<EncodedObjectAsID>(array.get(idx));498if (value_type != slot.type || (value_type == Variant::OBJECT && (value_as_id != slot.as_id))) {499slot.as_id = value_as_id;500slot.type = value_type;501EditorProperty *new_prop = nullptr;502if (value_type == Variant::OBJECT && value_as_id) {503EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);504editor->setup("Object");505new_prop = editor;506} else {507new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);508}509new_prop->set_selectable(false);510new_prop->set_use_folding(is_using_folding());511new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyArray::_property_changed));512new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyArray::_object_id_selected));513new_prop->set_h_size_flags(SIZE_EXPAND_FILL);514new_prop->set_read_only(is_read_only());515slot.prop->add_sibling(new_prop, false);516slot.prop->queue_free();517slot.prop = new_prop;518slot.set_index(idx);519}520if (slot.index == changing_type_index) {521callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);522changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;523}524slot.prop->update_property();525}526527updating = false;528529} else {530if (container) {531set_bottom_editor(nullptr);532memdelete(container);533button_add_item = nullptr;534container = nullptr;535slots.clear();536}537}538}539540void EditorPropertyArray::_remove_pressed(int p_slot_index) {541Variant array = object->get_array().duplicate();542array.call("remove_at", slots[p_slot_index].index);543544emit_changed(get_edited_property(), array);545}546547void EditorPropertyArray::_button_draw() {548if (dropping) {549Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));550edit->draw_rect(Rect2(Point2(), edit->get_size()), color, false);551}552}553554void EditorPropertyArray::_button_add_item_draw() {555if (dropping) {556Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));557button_add_item->draw_rect(Rect2(Point2(), button_add_item->get_size()), color, false);558}559}560561bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const {562if (is_read_only()) {563return false;564}565566String allowed_type = Variant::get_type_name(subtype);567568// When the subtype is of type Object, an additional subtype may be specified in the hint string569// (e.g. Resource, Texture2D, ShaderMaterial, etc). We want the allowed type to be that, not just "Object".570if (subtype == Variant::OBJECT && !subtype_hint_string.is_empty()) {571allowed_type = subtype_hint_string;572}573574Dictionary drag_data = p_drag_data;575const String drop_type = drag_data.get("type", "");576577if (drop_type == "files") {578PackedStringArray files = drag_data["files"];579580for (const String &file : files) {581int idx_in_dir;582EditorFileSystemDirectory const *dir = EditorFileSystem::get_singleton()->find_file(file, &idx_in_dir);583if (!dir) {584return false;585}586StringName ftype = dir->get_file_type(idx_in_dir);587String script_class = dir->get_file_resource_script_class(idx_in_dir);588589for (String at : allowed_type.split(",", false)) {590at = at.strip_edges();591// Fail if one of the files is not of allowed type.592if (!ClassDB::is_parent_class(ftype, at) && !EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {593return false;594}595}596}597598// If no files fail, drop is valid.599return true;600}601602if (drop_type == "resource") {603Ref<Resource> res = drag_data["resource"];604if (res.is_null()) {605return false;606}607608String res_type = res->get_class();609StringName script_class;610if (res->get_script()) {611script_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());612}613614for (String at : allowed_type.split(",", false)) {615at = at.strip_edges();616if (ClassDB::is_parent_class(res_type, at) || EditorNode::get_editor_data().script_class_is_parent(script_class, at)) {617return true;618}619}620621return false;622}623624if (drop_type == "nodes") {625Array node_paths = drag_data["nodes"];626627PackedStringArray allowed_subtype_array;628if (allowed_type == "NodePath") {629if (subtype_hint_string == "NodePath") {630return true;631} else {632for (String ast : subtype_hint_string.split(",", false)) {633ast = ast.strip_edges();634allowed_subtype_array.append(ast);635}636}637}638639bool is_drop_allowed = true;640641for (int i = 0; i < node_paths.size(); i++) {642const Node *dropped_node = get_node_or_null(node_paths[i]);643ERR_FAIL_NULL_V_MSG(dropped_node, false, "Could not get the dropped node by its path.");644645if (allowed_type != "NodePath") {646if (!ClassDB::is_parent_class(dropped_node->get_class_name(), allowed_type) &&647!EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, allowed_type)) {648// Fail if one of the nodes is not of allowed type.649return false;650}651}652653// The array of NodePaths is restricted to specific types using @export_node_path().654if (allowed_type == "NodePath" && subtype_hint_string != "NodePath") {655if (!allowed_subtype_array.has(dropped_node->get_class_name())) {656// The dropped node type was not found in the allowed subtype array, we must check if it inherits one of them.657for (const String &ast : allowed_subtype_array) {658if (ClassDB::is_parent_class(dropped_node->get_class_name(), ast) ||659EditorNode::get_singleton()->is_object_of_custom_type(dropped_node, ast)) {660is_drop_allowed = true;661break;662} else {663is_drop_allowed = false;664}665}666if (!is_drop_allowed) {667break;668}669}670}671}672673return is_drop_allowed;674}675676return false;677}678679bool EditorPropertyArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {680return _is_drop_valid(p_data);681}682683void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {684Dictionary drag_data = p_data;685const String drop_type = drag_data.get("type", "");686Variant array = object->get_array();687688// Handle the case where array is not initialized yet.689if (!array.is_array()) {690initialize_array(array);691} else {692array = array.duplicate();693}694695if (drop_type == "files") {696PackedStringArray files = drag_data["files"];697698// Loop the file array and add to existing array.699for (int i = 0; i < files.size(); i++) {700const String &file = files[i];701702Ref<Resource> res = ResourceLoader::load(file);703if (res.is_valid()) {704array.call("push_back", res);705}706}707708emit_changed(get_edited_property(), array);709}710711if (drop_type == "resource") {712Ref<Resource> res = drag_data["resource"];713714if (res.is_valid()) {715array.call("push_back", res);716717emit_changed(get_edited_property(), array);718}719}720721if (drop_type == "nodes") {722Array node_paths = drag_data["nodes"];723Node *base_node = get_base_node();724725for (int i = 0; i < node_paths.size(); i++) {726const NodePath &path = node_paths[i];727728if (subtype == Variant::OBJECT) {729array.call("push_back", get_node(path));730} else if (subtype == Variant::NODE_PATH) {731array.call("push_back", base_node->get_path().rel_path_to(path));732}733}734735emit_changed(get_edited_property(), array);736}737}738739Node *EditorPropertyArray::get_base_node() {740Node *base_node = Object::cast_to<Node>(InspectorDock::get_inspector_singleton()->get_edited_object());741742if (!base_node) {743base_node = get_tree()->get_edited_scene_root();744}745746return base_node;747}748749void EditorPropertyArray::_notification(int p_what) {750switch (p_what) {751case NOTIFICATION_DRAG_BEGIN: {752if (is_visible_in_tree()) {753if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {754dropping = true;755edit->queue_redraw();756if (button_add_item) {757button_add_item->queue_redraw();758}759}760}761} break;762763case NOTIFICATION_DRAG_END: {764if (dropping) {765dropping = false;766edit->queue_redraw();767if (button_add_item) {768button_add_item->queue_redraw();769}770}771} break;772}773}774775void EditorPropertyArray::_edit_pressed() {776Variant array = get_edited_property_value();777if (!array.is_array() && edit->is_pressed()) {778initialize_array(array);779emit_changed(get_edited_property(), array);780}781782get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());783update_property();784}785786void EditorPropertyArray::_page_changed(int p_page) {787if (updating) {788return;789}790page_index = p_page;791int i = p_page * page_length;792793if (reorder_slot.index < 0) {794for (Slot &slot : slots) {795slot.set_index(i);796i++;797}798} else {799int reorder_from_page = reorder_slot.index / page_length;800if (reorder_from_page < p_page) {801i++;802}803for (Slot &slot : slots) {804if (slot.index != reorder_slot.index) {805slot.set_index(i);806i++;807} else if (i == reorder_slot.index) {808i++;809}810}811}812update_property();813}814815void EditorPropertyArray::_length_changed(double p_page) {816if (updating) {817return;818}819820Variant array = object->get_array().duplicate();821array.call("resize", int(p_page));822823emit_changed(get_edited_property(), array);824}825826void EditorPropertyArray::_add_element() {827_length_changed(double(object->get_array().call("size")) + 1.0);828}829830void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) {831array_type = p_array_type;832833// The format of p_hint_string is:834// subType/subTypeHint:nextSubtype ... etc.835if (!p_hint_string.is_empty()) {836int hint_subtype_separator = p_hint_string.find_char(':');837if (hint_subtype_separator >= 0) {838String subtype_string = p_hint_string.substr(0, hint_subtype_separator);839int slash_pos = subtype_string.find_char('/');840if (slash_pos >= 0) {841subtype_hint = PropertyHint(subtype_string.substr(slash_pos + 1).to_int());842subtype_string = subtype_string.substr(0, slash_pos);843}844845subtype_hint_string = p_hint_string.substr(hint_subtype_separator + 1);846subtype = Variant::Type(subtype_string.to_int());847} else {848subtype = Variant::get_type_by_name(p_hint_string);849850if (subtype == Variant::VARIANT_MAX) {851subtype = Variant::OBJECT;852subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;853subtype_hint_string = p_hint_string;854}855}856}857}858859void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_event) {860if (reorder_slot.index < 0 || is_read_only()) {861return;862}863864Ref<InputEventMouseMotion> mm = p_event;865if (mm.is_valid()) {866Variant array = object->get_array();867int size = array.call("size");868869// Cumulate the mouse delta, many small changes (dragging slowly) should result in reordering at some point.870reorder_mouse_y_delta += mm->get_relative().y;871872// Reordering is done by moving the dragged element by +1/-1 index at a time based on the cumulated mouse delta so if873// already at the array bounds make sure to ignore the remaining out of bounds drag (by resetting the cumulated delta).874if ((reorder_to_index == 0 && reorder_mouse_y_delta < 0.0f) || (reorder_to_index == size - 1 && reorder_mouse_y_delta > 0.0f)) {875reorder_mouse_y_delta = 0.0f;876return;877}878879float required_y_distance = 20.0f * EDSCALE;880if (Math::abs(reorder_mouse_y_delta) > required_y_distance) {881int direction = reorder_mouse_y_delta > 0.0f ? 1 : -1;882reorder_mouse_y_delta -= required_y_distance * direction;883884reorder_to_index += direction;885886property_vbox->move_child(reorder_slot.container, reorder_to_index % page_length);887888if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) {889// Automatically move to the next/previous page.890_page_changed(page_index + direction);891}892// Ensure the moving element is visible in the root inspector.893EditorInspector *parent_inspector = get_parent_inspector();894if (parent_inspector) {895// Defer to prevent moving elements from not displaying properly, especially near borders.896callable_mp((ScrollContainer *)parent_inspector->get_root_inspector(), &ScrollContainer::ensure_control_visible).call_deferred(reorder_slot.container);897}898}899}900}901902void EditorPropertyArray::_reorder_button_down(int p_slot_index) {903if (is_read_only()) {904return;905}906reorder_slot = slots[p_slot_index];907reorder_to_index = reorder_slot.index;908// Ideally it'd to be able to show the mouse but I had issues with909// Control's `mouse_exit()`/`mouse_entered()` signals not getting called.910Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);911}912913void EditorPropertyArray::_reorder_button_up() {914if (is_read_only()) {915return;916}917918if (reorder_slot.index != reorder_to_index) {919// Move the element.920Variant array = object->get_array().duplicate();921922property_vbox->move_child(reorder_slot.container, reorder_slot.index % page_length);923Variant value_to_move = array.get(reorder_slot.index);924array.call("remove_at", reorder_slot.index);925array.call("insert", reorder_to_index, value_to_move);926927slots[reorder_to_index % page_length].reorder_button->grab_focus();928emit_changed(get_edited_property(), array);929}930931Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);932933ERR_FAIL_NULL(reorder_slot.reorder_button);934reorder_slot.reorder_button->warp_mouse(reorder_slot.reorder_button->get_size() / 2.0f);935reorder_to_index = -1;936reorder_mouse_y_delta = 0.0f;937reorder_slot = Slot();938_page_changed(page_index);939}940941bool EditorPropertyArray::is_colored(ColorationMode p_mode) {942return p_mode == COLORATION_CONTAINER_RESOURCE;943}944945EditorPropertyArray::EditorPropertyArray() {946object.instantiate();947page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));948949edit = memnew(Button);950edit->set_accessibility_name(TTRC("Edit"));951edit->set_h_size_flags(SIZE_EXPAND_FILL);952edit->set_clip_text(true);953edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyArray::_edit_pressed));954edit->set_toggle_mode(true);955SET_DRAG_FORWARDING_CD(edit, EditorPropertyArray);956edit->connect(SceneStringName(draw), callable_mp(this, &EditorPropertyArray::_button_draw));957add_child(edit);958add_focusable(edit);959960change_type = memnew(EditorVariantTypePopupMenu(true));961add_child(change_type);962change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyArray::_change_type_menu));963changing_type_index = -1;964965subtype = Variant::NIL;966subtype_hint = PROPERTY_HINT_NONE;967subtype_hint_string = "";968has_borders = true;969}970971///////////////////// DICTIONARY ///////////////////////////972973void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {974if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {975Dictionary dict;976StringName key_subtype_class;977Ref<Script> key_subtype_script;978if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {979key_subtype_class = key_subtype_hint_string;980}981StringName value_subtype_class;982Ref<Script> value_subtype_script;983if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {984value_subtype_class = value_subtype_hint_string;985}986dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);987p_dictionary = dict;988} else {989VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);990}991}992993void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {994if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {995p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.996}997998object->set(p_property, p_value);999bool new_item_or_key = !p_property.begins_with("indices");1000emit_changed(get_edited_property(), object->get_dict(), p_name, p_changing || new_item_or_key);1001if (new_item_or_key) {1002update_property();1003}1004}10051006void EditorPropertyDictionary::_change_type(Object *p_button, int p_slot_index) {1007Button *button = Object::cast_to<Button>(p_button);1008int index = slots[p_slot_index].index;1009Rect2 rect = button->get_screen_rect();1010change_type->set_item_disabled(change_type->get_item_index(Variant::VARIANT_MAX), index < 0);1011change_type->reset_size();1012change_type->set_position(rect.get_end() - Vector2(change_type->get_contents_minimum_size().x, 0));1013change_type->popup();1014changing_type_index = index;1015}10161017void EditorPropertyDictionary::_add_key_value() {1018// Do not allow nil as valid key. I experienced errors with this1019if (object->get_new_item_key().get_type() == Variant::NIL) {1020return;1021}10221023Dictionary dict = object->get_dict().duplicate();1024Variant new_key = object->get_new_item_key();1025Variant new_value = object->get_new_item_value();1026dict[new_key] = new_value;10271028Variant::Type type = new_key.get_type();1029new_key.zero();1030VariantInternal::initialize(&new_key, type);1031object->set_new_item_key(new_key);10321033type = new_value.get_type();1034new_value.zero();1035VariantInternal::initialize(&new_value, type);1036object->set_new_item_value(new_value);10371038object->set_dict(dict);1039slots[(dict.size() - 1) % page_length].update_prop_or_index();1040emit_changed(get_edited_property(), dict);1041}10421043void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {1044HBoxContainer *hbox = memnew(HBoxContainer);10451046EditorProperty *prop_key = nullptr;1047if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1048prop_key = memnew(EditorPropertyNil);1049hbox->add_child(prop_key);1050}10511052EditorProperty *prop = memnew(EditorPropertyNil);1053prop->set_h_size_flags(SIZE_EXPAND_FILL);1054if (p_idx != EditorPropertyDictionaryObject::NEW_KEY_INDEX && p_idx != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1055prop->set_draw_label(false);1056}1057hbox->add_child(prop);10581059bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;1060bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;10611062if (is_untyped_dict) {1063Button *edit_btn = memnew(Button);1064edit_btn->set_accessibility_name(TTRC("Edit"));1065edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));1066edit_btn->set_disabled(is_read_only());1067edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));1068hbox->add_child(edit_btn);1069} else if (p_idx >= 0) {1070Button *remove_btn = memnew(Button);1071remove_btn->set_accessibility_name(TTRC("Remove"));1072remove_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));1073remove_btn->set_disabled(is_read_only());1074remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));1075hbox->add_child(remove_btn);1076}10771078if (add_panel) {1079add_panel->get_child(0)->add_child(hbox);1080} else {1081property_vbox->add_child(hbox);1082}10831084Slot slot;1085slot.prop = prop;1086slot.prop_key = prop_key;1087slot.object = object;1088slot.container = hbox;1089int index = p_idx + (p_idx >= 0 ? page_index * page_length : 0);1090slot.set_index(index);1091slots.push_back(slot);1092}10931094void EditorPropertyDictionary::_change_type_menu(int p_index) {1095ERR_FAIL_COND_MSG(1096changing_type_index == EditorPropertyDictionaryObject::NOT_CHANGING_TYPE,1097"Tried to change the type of a dict key or value, but nothing was selected.");10981099Variant value;1100switch (changing_type_index) {1101case EditorPropertyDictionaryObject::NEW_KEY_INDEX:1102case EditorPropertyDictionaryObject::NEW_VALUE_INDEX:1103VariantInternal::initialize(&value, Variant::Type(p_index));1104if (changing_type_index == EditorPropertyDictionaryObject::NEW_KEY_INDEX) {1105object->set_new_item_key(value);1106} else {1107object->set_new_item_value(value);1108}1109update_property();1110break;11111112default:1113Dictionary dict = object->get_dict().duplicate();1114Variant key = dict.get_key_at_index(changing_type_index);1115if (p_index < Variant::VARIANT_MAX) {1116VariantInternal::initialize(&value, Variant::Type(p_index));1117dict[key] = value;1118} else {1119dict.erase(key);1120object->set_dict(dict);1121for (Slot &slot : slots) {1122slot.update_prop_or_index();1123}1124}11251126emit_changed(get_edited_property(), dict);1127}1128}11291130void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {1131PackedStringArray types = p_hint_string.split(";");1132if (types.size() > 0 && !types[0].is_empty()) {1133String key = types[0];1134int hint_key_subtype_separator = key.find_char(':');1135if (hint_key_subtype_separator >= 0) {1136String key_subtype_string = key.substr(0, hint_key_subtype_separator);1137int slash_pos = key_subtype_string.find_char('/');1138if (slash_pos >= 0) {1139key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1).to_int());1140key_subtype_string = key_subtype_string.substr(0, slash_pos);1141}11421143key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1);1144key_subtype = Variant::Type(key_subtype_string.to_int());1145} else {1146key_subtype = Variant::get_type_by_name(key);11471148if (key_subtype == Variant::VARIANT_MAX) {1149key_subtype = Variant::OBJECT;1150key_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;1151key_subtype_hint_string = key;1152}1153}11541155Variant new_key = object->get_new_item_key();1156VariantInternal::initialize(&new_key, key_subtype);1157object->set_new_item_key(new_key);1158}11591160if (types.size() > 1 && !types[1].is_empty()) {1161String value = types[1];1162int hint_value_subtype_separator = value.find_char(':');1163if (hint_value_subtype_separator >= 0) {1164String value_subtype_string = value.substr(0, hint_value_subtype_separator);1165int slash_pos = value_subtype_string.find_char('/');1166if (slash_pos >= 0) {1167value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1).to_int());1168value_subtype_string = value_subtype_string.substr(0, slash_pos);1169}11701171value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1);1172value_subtype = Variant::Type(value_subtype_string.to_int());1173} else {1174value_subtype = Variant::get_type_by_name(value);11751176if (value_subtype == Variant::VARIANT_MAX) {1177value_subtype = Variant::OBJECT;1178value_subtype_hint = PROPERTY_HINT_RESOURCE_TYPE;1179value_subtype_hint_string = value;1180}1181}11821183Variant new_value = object->get_new_item_value();1184VariantInternal::initialize(&new_value, value_subtype);1185object->set_new_item_value(new_value);1186}1187}11881189void EditorPropertyDictionary::set_preview_value(bool p_preview_value) {1190preview_value = p_preview_value;1191}11921193void EditorPropertyDictionary::update_property() {1194Variant updated_val = get_edited_property_value();11951196String dict_type_name = "Dictionary";1197String dict_sub_type_name;1198if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {1199String key_subtype_name = "Variant";1200if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {1201key_subtype_name = key_subtype_hint_string;1202} else if (key_subtype != Variant::NIL) {1203key_subtype_name = Variant::get_type_name(key_subtype);1204}1205String value_subtype_name = "Variant";1206if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {1207value_subtype_name = value_subtype_hint_string;1208} else if (value_subtype != Variant::NIL) {1209value_subtype_name = Variant::get_type_name(value_subtype);1210}1211if (preview_value) {1212dict_sub_type_name = vformat("[%s, %s] ", key_subtype_name, value_subtype_name);1213} else {1214dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);1215}1216}12171218if (updated_val.get_type() != Variant::DICTIONARY) {1219if (preview_value) {1220edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);1221edit->set_button_icon(get_editor_theme_icon(SNAME("Nil")));1222edit->set_text(dict_type_name);1223} else {1224edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);1225edit->set_button_icon(Ref<Texture2D>());1226edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name));1227}1228edit->set_pressed(false);1229if (container) {1230set_bottom_editor(nullptr);1231memdelete(container);1232button_add_item = nullptr;1233container = nullptr;1234add_panel = nullptr;1235slots.clear();1236}1237return;1238}12391240Dictionary dict = updated_val;1241object->set_dict(updated_val);12421243if (preview_value) {1244String ctr_str = updated_val.get_construct_string().remove_char('\n');1245if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {1246int type_end = ctr_str.find("](");1247if (type_end > 0) {1248ctr_str = ctr_str.substr(type_end + 2).trim_suffix(")");1249}1250}12511252edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);1253edit->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);1254edit->set_button_icon(get_editor_theme_icon(dict_type_name));1255edit->set_text(vformat("%s%s", dict_sub_type_name, ctr_str));1256edit->set_tooltip_text(vformat(TTR("%s%s (size %d)"), dict_type_name, dict_sub_type_name, dict.size()));1257} else {1258edit->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);1259edit->set_button_icon(Ref<Texture2D>());1260edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));1261}12621263bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());1264if (edit->is_pressed() != unfolded) {1265edit->set_pressed(unfolded);1266}12671268if (unfolded) {1269updating = true;12701271if (!container) {1272container = memnew(PanelContainer);1273add_child(container);1274set_bottom_editor(container);12751276VBoxContainer *vbox = memnew(VBoxContainer);1277container->add_child(vbox);12781279property_vbox = memnew(VBoxContainer);1280property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);1281vbox->add_child(property_vbox);12821283paginator = memnew(EditorPaginator);1284paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed));1285vbox->add_child(paginator);12861287for (int i = 0; i < page_length; i++) {1288_create_new_property_slot(slots.size());1289}12901291add_panel = memnew(PanelContainer);1292property_vbox->add_child(add_panel);1293add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));1294VBoxContainer *add_vbox = memnew(VBoxContainer);1295add_panel->add_child(add_vbox);12961297_create_new_property_slot(EditorPropertyDictionaryObject::NEW_KEY_INDEX);1298_create_new_property_slot(EditorPropertyDictionaryObject::NEW_VALUE_INDEX);12991300button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Key/Value Pair"), SNAME("Add")));1301button_add_item->set_disabled(is_read_only());1302button_add_item->set_accessibility_name(TTRC("Add"));1303button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_add_key_value));1304add_vbox->add_child(button_add_item);1305}13061307int size = dict.size();13081309int max_page = MAX(0, size - 1) / page_length;1310if (page_index > max_page) {1311_page_changed(max_page);1312}13131314paginator->update(page_index, max_page);1315paginator->set_visible(max_page > 0);13161317add_panel->set_visible(page_index == max_page);13181319for (Slot &slot : slots) {1320bool slot_visible = slot.index < size;1321slot.container->set_visible(slot_visible);1322// If not visible no need to update it.1323if (!slot_visible) {1324continue;1325}13261327// Check if the editor property key needs to be updated.1328if (slot.prop_key) {1329Variant key;1330object->get_by_property_name(slot.key_name, key);1331Variant::Type key_type = key.get_type();13321333bool key_as_id = Object::cast_to<EncodedObjectAsID>(key);1334if (key_type != slot.key_type || (key_type == Variant::OBJECT && key_as_id != slot.key_as_id)) {1335slot.key_as_id = key_as_id;1336slot.key_type = key_type;1337EditorProperty *new_prop = nullptr;1338if (key_type == Variant::OBJECT && key_as_id) {1339EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);1340editor->setup("Object");1341new_prop = editor;1342} else {1343new_prop = EditorInspector::instantiate_property_editor(this, key_type, "", key_subtype_hint, key_subtype_hint_string, PROPERTY_USAGE_NONE);1344}1345new_prop->set_read_only(true);1346new_prop->set_selectable(false);1347new_prop->set_focus_mode(Control::FOCUS_ACCESSIBILITY);1348new_prop->set_draw_background(false);1349new_prop->set_use_folding(is_using_folding());1350new_prop->set_h_size_flags(SIZE_EXPAND_FILL);1351new_prop->set_draw_label(false);1352EditorPropertyArray *arr_prop = Object::cast_to<EditorPropertyArray>(new_prop);1353if (arr_prop) {1354arr_prop->set_preview_value(true);1355}1356EditorPropertyDictionary *dict_prop = Object::cast_to<EditorPropertyDictionary>(new_prop);1357if (dict_prop) {1358dict_prop->set_preview_value(true);1359}1360slot.set_key_prop(new_prop);1361}1362}13631364Variant value;1365object->get_by_property_name(slot.prop_name, value);13661367Variant::Type value_type;13681369if (dict.is_typed_value() && value_subtype != Variant::NIL && slot.prop_key) {1370value_type = value_subtype;1371} else {1372value_type = value.get_type();1373}13741375// Check if the editor property needs to be updated.1376bool value_as_id = Object::cast_to<EncodedObjectAsID>(value);1377if (value_type != slot.type || (value_type == Variant::OBJECT && value_as_id != slot.as_id)) {1378slot.as_id = value_as_id;1379slot.type = value_type;1380EditorProperty *new_prop = nullptr;1381if (value_type == Variant::OBJECT && value_as_id) {1382EditorPropertyObjectID *editor = memnew(EditorPropertyObjectID);1383editor->setup("Object");1384new_prop = editor;1385} else {1386bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;1387new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,1388use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);1389}1390new_prop->set_selectable(false);1391new_prop->set_use_folding(is_using_folding());1392new_prop->connect(SNAME("property_changed"), callable_mp(this, &EditorPropertyDictionary::_property_changed));1393new_prop->connect(SNAME("object_id_selected"), callable_mp(this, &EditorPropertyDictionary::_object_id_selected));1394new_prop->set_h_size_flags(SIZE_EXPAND_FILL);1395if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1396new_prop->set_draw_label(false);1397}1398new_prop->set_read_only(is_read_only());1399slot.set_prop(new_prop);1400} else if (slot.index != EditorPropertyDictionaryObject::NEW_KEY_INDEX && slot.index != EditorPropertyDictionaryObject::NEW_VALUE_INDEX) {1401Variant key = dict.get_key_at_index(slot.index);1402String cs = key.get_construct_string();1403slot.prop->set_tooltip_text(cs);1404}14051406// We need to grab the focus of the property that is being changed, even if the type didn't actually changed.1407// Otherwise, focus will stay on the change type button, which is not very user friendly.1408if (changing_type_index == slot.index) {1409callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);1410changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again.1411}14121413slot.prop->update_property();1414if (slot.prop_key) {1415slot.prop_key->update_property();1416}1417}1418updating = false;14191420} else {1421if (container) {1422set_bottom_editor(nullptr);1423memdelete(container);1424button_add_item = nullptr;1425container = nullptr;1426add_panel = nullptr;1427slots.clear();1428}1429}1430}14311432void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {1433Dictionary dict = object->get_dict().duplicate();1434int index = slots[p_slot_index].index;1435dict.erase(dict.get_key_at_index(index));14361437emit_changed(get_edited_property(), dict);1438}14391440void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {1441emit_signal(SNAME("object_id_selected"), p_property, p_id);1442}14431444void EditorPropertyDictionary::_notification(int p_what) {1445switch (p_what) {1446case NOTIFICATION_THEME_CHANGED: {1447if (button_add_item) {1448add_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DictionaryAddItem")));1449}1450} break;1451}1452}14531454void EditorPropertyDictionary::_edit_pressed() {1455Variant prop_val = get_edited_property_value();1456if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {1457initialize_dictionary(prop_val);1458emit_changed(get_edited_property(), prop_val);1459}14601461get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());1462update_property();1463}14641465void EditorPropertyDictionary::_page_changed(int p_page) {1466page_index = p_page;1467int i = p_page * page_length;1468for (Slot &slot : slots) {1469if (slot.index > -1) {1470slot.set_index(i);1471i++;1472}1473}1474if (updating) {1475return;1476}1477update_property();1478}14791480bool EditorPropertyDictionary::is_colored(ColorationMode p_mode) {1481return p_mode == COLORATION_CONTAINER_RESOURCE;1482}14831484EditorPropertyDictionary::EditorPropertyDictionary() {1485object.instantiate();1486page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));14871488edit = memnew(Button);1489edit->set_accessibility_name(TTRC("Edit"));1490edit->set_h_size_flags(SIZE_EXPAND_FILL);1491edit->set_clip_text(true);1492edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_edit_pressed));1493edit->set_toggle_mode(true);1494add_child(edit);1495add_focusable(edit);14961497container = nullptr;1498button_add_item = nullptr;1499paginator = nullptr;1500change_type = memnew(EditorVariantTypePopupMenu(true));1501add_child(change_type);1502change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));1503changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE;1504has_borders = true;15051506key_subtype = Variant::NIL;1507key_subtype_hint = PROPERTY_HINT_NONE;1508key_subtype_hint_string = "";15091510value_subtype = Variant::NIL;1511value_subtype_hint = PROPERTY_HINT_NONE;1512value_subtype_hint_string = "";1513}15141515///////////////////// LOCALIZABLE STRING ///////////////////////////15161517void EditorPropertyLocalizableString::_property_changed(const String &p_property, const Variant &p_value, const String &p_name, bool p_changing) {1518if (p_property.begins_with("indices")) {1519int index = p_property.get_slicec('/', 1).to_int();15201521Dictionary dict = object->get_dict().duplicate();1522Variant key = dict.get_key_at_index(index);1523dict[key] = p_value;15241525object->set_dict(dict);1526emit_changed(get_edited_property(), dict, "", true);1527}1528}15291530void EditorPropertyLocalizableString::_add_locale_popup() {1531locale_select->popup_locale_dialog();1532}15331534void EditorPropertyLocalizableString::_add_locale(const String &p_locale) {1535Dictionary dict = object->get_dict().duplicate();1536object->set_new_item_key(p_locale);1537object->set_new_item_value(String());1538dict[object->get_new_item_key()] = object->get_new_item_value();15391540emit_changed(get_edited_property(), dict, "", false);1541update_property();1542}15431544void EditorPropertyLocalizableString::_remove_item(Object *p_button, int p_index) {1545Dictionary dict = object->get_dict().duplicate();15461547Variant key = dict.get_key_at_index(p_index);1548dict.erase(key);15491550emit_changed(get_edited_property(), dict, "", false);1551update_property();1552}15531554void EditorPropertyLocalizableString::update_property() {1555Variant updated_val = get_edited_property_value();15561557if (updated_val.get_type() == Variant::NIL) {1558edit->set_text(TTR("Localizable String (Nil)")); // This provides symmetry with the array property.1559edit->set_pressed(false);1560if (container) {1561set_bottom_editor(nullptr);1562memdelete(container);1563button_add_item = nullptr;1564container = nullptr;1565}1566return;1567}15681569Dictionary dict = updated_val;1570object->set_dict(dict);15711572edit->set_text(vformat(TTR("Localizable String (size %d)"), dict.size()));15731574bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());1575if (edit->is_pressed() != unfolded) {1576edit->set_pressed(unfolded);1577}15781579if (unfolded) {1580updating = true;15811582if (!container) {1583container = memnew(MarginContainer);1584container->set_theme_type_variation("MarginContainer4px");1585add_child(container);1586set_bottom_editor(container);15871588VBoxContainer *vbox = memnew(VBoxContainer);1589container->add_child(vbox);15901591property_vbox = memnew(VBoxContainer);1592property_vbox->set_h_size_flags(SIZE_EXPAND_FILL);1593vbox->add_child(property_vbox);15941595paginator = memnew(EditorPaginator);1596paginator->connect("page_changed", callable_mp(this, &EditorPropertyLocalizableString::_page_changed));1597vbox->add_child(paginator);1598} else {1599// Queue children for deletion, deleting immediately might cause errors.1600for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) {1601property_vbox->get_child(i)->queue_free();1602}1603}16041605int size = dict.size();16061607int max_page = MAX(0, size - 1) / page_length;1608page_index = MIN(page_index, max_page);16091610paginator->update(page_index, max_page);1611paginator->set_visible(max_page > 0);16121613int offset = page_index * page_length;16141615int amount = MIN(size - offset, page_length);16161617for (int i = 0; i < amount; i++) {1618String prop_name;1619Variant key;1620Variant value;16211622prop_name = "indices/" + itos(i + offset);1623key = dict.get_key_at_index(i + offset);1624value = dict.get_value_at_index(i + offset);16251626EditorProperty *prop = memnew(EditorPropertyText);16271628prop->set_object_and_property(object.ptr(), prop_name);1629int remove_index = 0;16301631String cs = key.get_construct_string();1632prop->set_label(cs);1633prop->set_tooltip_text(cs);1634remove_index = i + offset;16351636prop->set_selectable(false);1637prop->connect("property_changed", callable_mp(this, &EditorPropertyLocalizableString::_property_changed));1638prop->connect("object_id_selected", callable_mp(this, &EditorPropertyLocalizableString::_object_id_selected));16391640HBoxContainer *hbox = memnew(HBoxContainer);1641property_vbox->add_child(hbox);1642hbox->add_child(prop);1643prop->set_h_size_flags(SIZE_EXPAND_FILL);1644Button *edit_btn = memnew(Button);1645edit_btn->set_accessibility_name(TTRC("Remove Translation"));1646edit_btn->set_button_icon(get_editor_theme_icon(SNAME("Remove")));1647hbox->add_child(edit_btn);1648edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_remove_item).bind(edit_btn, remove_index));16491650prop->update_property();1651}16521653if (page_index == max_page) {1654button_add_item = memnew(EditorInspectorActionButton(TTRC("Add Translation"), SNAME("Add")));1655button_add_item->set_accessibility_name(TTRC("Add Translation"));1656button_add_item->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_add_locale_popup));1657property_vbox->add_child(button_add_item);1658}16591660updating = false;16611662} else {1663if (container) {1664set_bottom_editor(nullptr);1665memdelete(container);1666button_add_item = nullptr;1667container = nullptr;1668}1669}1670}16711672void EditorPropertyLocalizableString::_object_id_selected(const StringName &p_property, ObjectID p_id) {1673emit_signal(SNAME("object_id_selected"), p_property, p_id);1674}16751676void EditorPropertyLocalizableString::_edit_pressed() {1677Variant prop_val = get_edited_property_value();1678if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {1679VariantInternal::initialize(&prop_val, Variant::DICTIONARY);1680get_edited_object()->set(get_edited_property(), prop_val);1681}16821683get_edited_object()->editor_set_section_unfold(get_edited_property(), edit->is_pressed());1684update_property();1685}16861687void EditorPropertyLocalizableString::_page_changed(int p_page) {1688if (updating) {1689return;1690}1691page_index = p_page;1692update_property();1693}16941695EditorPropertyLocalizableString::EditorPropertyLocalizableString() {1696object.instantiate();1697page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page"));16981699edit = memnew(Button);1700edit->set_accessibility_name(TTRC("Edit"));1701edit->set_h_size_flags(SIZE_EXPAND_FILL);1702edit->set_clip_text(true);1703edit->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyLocalizableString::_edit_pressed));1704edit->set_toggle_mode(true);1705add_child(edit);1706add_focusable(edit);17071708container = nullptr;1709button_add_item = nullptr;1710paginator = nullptr;1711updating = false;17121713locale_select = memnew(EditorLocaleDialog);1714locale_select->connect("locale_selected", callable_mp(this, &EditorPropertyLocalizableString::_add_locale));1715add_child(locale_select);1716}171717181719