Path: blob/master/editor/inspector/editor_inspector.cpp
20875 views
/**************************************************************************/1/* editor_inspector.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_inspector.h"31#include "editor_inspector.compat.inc"3233#include "core/input/input.h"34#include "core/os/keyboard.h"35#include "editor/debugger/editor_debugger_inspector.h"36#include "editor/doc/doc_tools.h"37#include "editor/docks/inspector_dock.h"38#include "editor/editor_main_screen.h"39#include "editor/editor_node.h"40#include "editor/editor_string_names.h"41#include "editor/editor_undo_redo_manager.h"42#include "editor/gui/editor_toaster.h"43#include "editor/inspector/add_metadata_dialog.h"44#include "editor/inspector/editor_properties.h"45#include "editor/inspector/editor_property_name_processor.h"46#include "editor/inspector/editor_resource_picker.h"47#include "editor/inspector/multi_node_edit.h"48#include "editor/script/script_editor_plugin.h"49#include "editor/settings/editor_feature_profile.h"50#include "editor/settings/editor_settings.h"51#include "editor/themes/editor_scale.h"52#include "scene/gui/margin_container.h"53#include "scene/gui/separator.h"54#include "scene/gui/spin_box.h"55#include "scene/gui/texture_rect.h"56#include "scene/main/timer.h"57#include "scene/property_utils.h"58#include "scene/resources/packed_scene.h"59#include "scene/resources/style_box_flat.h"60#include "scene/scene_string_names.h"6162void EditorInspectorActionButton::_notification(int p_what) {63switch (p_what) {64case NOTIFICATION_THEME_CHANGED: {65set_button_icon(get_editor_theme_icon(icon_name));66} break;67}68}6970EditorInspectorActionButton::EditorInspectorActionButton(const String &p_text, const StringName &p_icon_name) {71icon_name = p_icon_name;72set_text(p_text);73set_theme_type_variation(SNAME("InspectorActionButton"));74set_h_size_flags(SIZE_SHRINK_CENTER);75}7677bool EditorInspector::_property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) {78if (p_property_path.containsn(p_filter)) {79return true;80}8182const Vector<String> prop_sections = p_property_path.split("/");83for (int i = 0; i < prop_sections.size(); i++) {84if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(prop_sections[i], p_style, p_property_path))) {85return true;86}87}88return false;89}9091bool EditorInspector::_resource_properties_matches(const Ref<Resource> &p_resource, const String &p_filter) {92String group;93String group_base;94String subgroup;95String subgroup_base;9697List<PropertyInfo> plist;98p_resource->get_property_list(&plist, true);99100// Employ a lighter version of the update_tree() property listing to find a match.101for (PropertyInfo &p : plist) {102if (p.usage & PROPERTY_USAGE_SUBGROUP) {103subgroup = p.name;104subgroup_base = p.hint_string.get_slicec(',', 0);105106continue;107108} else if (p.usage & PROPERTY_USAGE_GROUP) {109group = p.name;110group_base = p.hint_string.get_slicec(',', 0);111subgroup = "";112subgroup_base = "";113114continue;115116} else if (p.usage & PROPERTY_USAGE_CATEGORY) {117group = "";118group_base = "";119subgroup = "";120subgroup_base = "";121122continue;123124} else if (p.name.begins_with("metadata/_") || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) ||125(p_filter.is_empty() && restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {126// Ignore properties that are not supposed to be in the inspector.127continue;128}129130if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) {131// Do not show this property in low end gfx.132continue;133}134135if (p.name == "script") {136// The script is always hidden in sub inspectors.137continue;138}139140if (p.name.begins_with("metadata/") && bool(object->call(SNAME("_hide_metadata_from_inspector")))) {141// Hide metadata from inspector if required.142continue;143}144145String path = p.name;146147// Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string.148if (!subgroup.is_empty() && !subgroup_base.is_empty()) {149if (path.begins_with(subgroup_base)) {150path = path.trim_prefix(subgroup_base);151} else if (subgroup_base.begins_with(path)) {152// Keep it, this is used pretty often.153} else {154subgroup = ""; // The prefix changed, we are no longer in the subgroup.155}156}157158// Check if we exit or not a group. If there is a prefix, remove it from the property label string.159if (!group.is_empty() && !group_base.is_empty() && subgroup.is_empty()) {160if (path.begins_with(group_base)) {161path = path.trim_prefix(group_base);162} else if (group_base.begins_with(path)) {163// Keep it, this is used pretty often.164} else {165group = ""; // The prefix changed, we are no longer in the group.166subgroup = "";167}168}169170// Add the group and subgroup to the path.171if (!subgroup.is_empty()) {172path = subgroup + "/" + path;173}174if (!group.is_empty()) {175path = group + "/" + path;176}177178// Get the property label's string.179String name_override = (path.contains_char('/')) ? path.substr(path.rfind_char('/') + 1) : path;180const int dot = name_override.find_char('.');181if (dot != -1) {182name_override = name_override.substr(0, dot);183}184185// Remove the property from the path.186int idx = path.rfind_char('/');187if (idx > -1) {188path = path.left(idx);189} else {190path = "";191}192193// Check if the property matches the filter.194const String property_path = (path.is_empty() ? "" : path + "/") + name_override;195if (_property_path_matches(property_path, p_filter, property_name_style)) {196return true;197}198199// Check if the sub-resource has any properties that match the filter.200if (p.hint && p.hint == PROPERTY_HINT_RESOURCE_TYPE) {201Ref<Resource> res = p_resource->get(p.name);202if (res.is_valid() && _resource_properties_matches(res, p_filter)) {203return true;204}205}206}207208return false;209}210211String EditorProperty::get_tooltip_string(const String &p_string) const {212// Trim to 100 characters to prevent the tooltip from being too long.213constexpr int TOOLTIP_MAX_LENGTH = 100;214return p_string.left(TOOLTIP_MAX_LENGTH).strip_edges() + String((p_string.length() > TOOLTIP_MAX_LENGTH) ? "..." : "");215}216217Size2 EditorProperty::get_minimum_size() const {218if (theme_cache.font.is_null()) {219// Too early.220return Vector2();221}222223Size2 ms = Size2(0, theme_cache.inspector_property_height);224for (int i = 0; i < get_child_count(); i++) {225Control *c = as_sortable_control(get_child(i));226if (!c) {227continue;228}229if (c == bottom_editor) {230continue;231}232if (c == left_container) {233continue;234}235if (c == right_container) {236continue;237}238239Size2 minsize = c->get_combined_minimum_size();240ms = ms.max(minsize);241}242243if (keying) {244ms.width += theme_cache.key_icon->get_width() + theme_cache.padding + theme_cache.horizontal_separation;245}246247if (deletable) {248ms.width += theme_cache.delete_icon->get_width() + theme_cache.padding + theme_cache.horizontal_separation;249}250251if (checkable) {252ms.width += theme_cache.checked_icon->get_width() + theme_cache.padding + theme_cache.horizontal_separation;253}254255Size2 ls = left_container->get_combined_minimum_size();256ms.width += ls.x;257ms.height = MAX(ms.height, ls.y);258259Size2 rs = right_container->get_combined_minimum_size();260ms.width += rs.x;261ms.height = MAX(ms.height, rs.y);262263if (bottom_editor != nullptr && bottom_editor->is_visible()) {264ms.height += label.is_empty() ? 0 : _get_v_separation();265Size2 bems = bottom_editor->get_combined_minimum_size();266ms.height += bems.height;267ms.width = MAX(ms.width, bems.width);268}269270return ms;271}272273void EditorProperty::emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field, bool p_changing) {274Variant args[4] = { p_property, p_value, p_field, p_changing };275const Variant *argptrs[4] = { &args[0], &args[1], &args[2], &args[3] };276277cache[p_property] = p_value;278emit_signalp(SNAME("property_changed"), (const Variant **)argptrs, 4);279}280281void EditorProperty::_notification(int p_what) {282switch (p_what) {283case NOTIFICATION_THEME_CHANGED: {284EditorInspector::initialize_property_theme(theme_cache, this);285} break;286287case NOTIFICATION_ACCESSIBILITY_UPDATE: {288RID ae = get_accessibility_element();289ERR_FAIL_COND(ae.is_null());290291DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);292293DisplayServer::get_singleton()->accessibility_update_set_name(ae, vformat(TTR("Property: %s"), label));294DisplayServer::get_singleton()->accessibility_update_set_value(ae, vformat(TTR("Property: %s"), label));295296DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_MENU);297DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &EditorProperty::_accessibility_action_menu));298DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_CLICK, callable_mp(this, &EditorProperty::_accessibility_action_click));299300DisplayServer::get_singleton()->accessibility_update_set_flag(ae, DisplayServer::AccessibilityFlags::FLAG_READONLY, read_only);301if (checkable) {302DisplayServer::get_singleton()->accessibility_update_set_checked(ae, checked);303}304} break;305306case NOTIFICATION_SORT_CHILDREN: {307Size2 size = get_size();308Rect2 rect;309Rect2 bottom_rect;310311right_child_rect = Rect2();312bottom_child_rect = Rect2();313314{315int child_room = size.width * (1.0 - split_ratio);316int separation = 4 * EDSCALE;317int height = theme_cache.inspector_property_height;318int half_padding = theme_cache.padding / 2;319bool no_children = true;320321//compute room needed322for (int i = 0; i < get_child_count(); i++) {323Control *c = as_sortable_control(get_child(i));324if (!c) {325continue;326}327if (c == bottom_editor) {328continue;329}330331Size2 minsize = c->get_combined_minimum_size();332if (c != left_container && c != right_container) {333child_room = MAX(child_room, minsize.width);334no_children = false;335}336height = MAX(height, minsize.height);337}338339if (no_children) {340text_size = size.width;341rect = Rect2(size.width - 1, 0, 1, height);342} else if (!draw_label) {343text_size = 0;344rect = Rect2(1, 0, size.width - 1, height);345} else {346text_size = MAX(0, size.width - (child_room + separation));347if (is_layout_rtl()) {348rect = Rect2(1, 0, child_room, height);349} else {350rect = Rect2(size.width - child_room, 0, child_room, height);351}352}353354if (rect.size.x > 1) {355rect.size.x -= right_container->get_combined_minimum_size().x;356if (is_layout_rtl()) {357rect.position.x += right_container->get_combined_minimum_size().x;358}359}360361if (bottom_editor) {362int v_offset = label.is_empty() ? 0 : _get_v_separation();363bottom_rect = Rect2(0, rect.size.height + v_offset, size.width, bottom_editor->get_combined_minimum_size().height);364}365366if (keying) {367Ref<Texture2D> key;368369if (use_keying_next()) {370key = theme_cache.key_next_icon;371} else {372key = theme_cache.key_icon;373}374375rect.size.x -= key->get_width() + half_padding + theme_cache.horizontal_separation;376if (is_layout_rtl()) {377rect.position.x += key->get_width() + half_padding + theme_cache.horizontal_separation;378}379380if (no_children) {381// Use full padding to avoid overlapping with the revert button.382text_size -= key->get_width() + half_padding * 2 + separation;383}384}385386if (deletable) {387rect.size.x -= theme_cache.delete_icon->get_width() + half_padding + theme_cache.horizontal_separation;388389if (is_layout_rtl()) {390rect.position.x += theme_cache.delete_icon->get_width() + half_padding + theme_cache.horizontal_separation;391}392393if (no_children) {394text_size -= theme_cache.delete_icon->get_width() + half_padding + separation;395}396}397398// Account for the space needed on the outer side399// when any of the icons are visible.400if (keying || deletable) {401separation = theme_cache.horizontal_separation;402rect.size.x -= separation;403404if (is_layout_rtl()) {405rect.position.x += separation;406}407}408}409410//set children411for (int i = 0; i < get_child_count(); i++) {412Control *c = as_sortable_control(get_child(i));413if (!c) {414continue;415}416if (c == bottom_editor) {417continue;418}419if (c == left_container) {420continue;421}422if (c == right_container) {423continue;424}425426fit_child_in_rect(c, rect);427right_child_rect = rect;428}429430if (bottom_editor) {431fit_child_in_rect(bottom_editor, bottom_rect);432bottom_child_rect = bottom_rect;433}434435Size2 rs = right_container->get_combined_minimum_size();436rs.y = MAX(rs.y, rect.size.y);437if (is_layout_rtl()) {438fit_child_in_rect(right_container, Rect2(0, 0, rs.width, rs.y));439} else {440fit_child_in_rect(right_container, Rect2(size.width - rs.width, 0, rs.width, rs.y));441}442443Size2 ls = left_container->get_combined_minimum_size();444real_t right_size = rect.size.x + rs.x;445ls.y = MAX(ls.y, rect.size.y);446if (is_layout_rtl()) {447fit_child_in_rect(left_container, Rect2(right_size, 0, size.x - right_size, ls.y));448} else {449fit_child_in_rect(left_container, Rect2(0, 0, size.x - right_size, ls.y));450}451452queue_redraw(); //need to redraw text453} break;454455case NOTIFICATION_DRAW: {456Ref<Font> font = theme_cache.font;457int font_size = theme_cache.font_size;458bool rtl = is_layout_rtl();459460Size2 size = get_size();461if (bottom_editor) {462size.height = bottom_editor->get_offset(SIDE_TOP) - _get_v_separation();463} else if (label_reference) {464size.height = label_reference->get_size().height;465}466size.height = MAX(size.height, left_container->get_size().height);467468// Only draw the label if it's not empty.469if (label.is_empty()) {470size.height = 0;471} else if (sub_inspector_color_level >= 0 && theme_cache.sub_inspector_background[sub_inspector_color_level].is_valid()) {472draw_style_box(theme_cache.sub_inspector_background[sub_inspector_color_level], Rect2(Vector2(), size));473} else {474draw_style_box(selected ? theme_cache.background_selected : theme_cache.background, Rect2(Vector2(), size));475}476477if (draw_top_bg && right_child_rect != Rect2() && draw_background) {478draw_style_box(theme_cache.child_background, right_child_rect);479}480if (bottom_child_rect != Rect2() && draw_background) {481draw_style_box(theme_cache.child_background, bottom_child_rect);482}483484Color color;485if (draw_warning || draw_prop_warning) {486color = is_read_only() ? theme_cache.readonly_warning_color : theme_cache.warning_color;487} else if (is_read_only()) {488color = (sub_inspector_color_level >= 0) ? theme_cache.sub_inspector_property_color : theme_cache.readonly_property_color;489} else {490color = (sub_inspector_color_level >= 0) ? theme_cache.sub_inspector_property_color : theme_cache.property_color;491}492if (label.contains_char('.')) {493// FIXME: Move this to the project settings editor, as this is only used494// for project settings feature tag overrides.495color.a = 0.5;496}497498int ofs = theme_cache.font_offset;499int text_limit = text_size - ofs;500int half_padding = EDITOR_GET("interface/theme/base_spacing");501int padding = half_padding * 2;502503int left_ofs = left_container->get_combined_minimum_size().x;504ofs += left_ofs;505text_limit -= left_ofs;506507if (checkable) {508Ref<Texture2D> checkbox;509if (checked) {510checkbox = theme_cache.checked_icon;511} else {512checkbox = theme_cache.unchecked_icon;513}514515check_rect = Rect2(ofs, 0, checkbox->get_width() + padding, size.height);516517Point2 rtl_pos;518if (rtl) {519rtl_pos = Point2(size.width - check_rect.position.x - (checkbox->get_width() + padding + (1 * EDSCALE)), check_rect.position.y);520}521522Color color2(1, 1, 1);523if (check_hover) {524color2.r *= 1.2;525color2.g *= 1.2;526color2.b *= 1.2;527528if (rtl) {529draw_style_box(theme_cache.hover, Rect2(rtl_pos, check_rect.size));530} else {531draw_style_box(theme_cache.hover, check_rect);532}533}534535Point2 icon_ofs = (Point2(padding, size.height - checkbox->get_height()) / 2).round();536if (rtl) {537draw_texture(checkbox, rtl_pos + icon_ofs, color2);538} else {539draw_texture(checkbox, check_rect.position + icon_ofs, color2);540}541542int check_ofs = checkbox->get_width() + padding + theme_cache.horizontal_separation;543ofs += check_ofs;544text_limit -= check_ofs;545} else {546check_rect = Rect2();547}548549if (can_revert && !is_read_only()) {550const Ref<Texture2D> &reload_icon = theme_cache.revert_icon;551text_limit -= reload_icon->get_width() + half_padding + theme_cache.horizontal_separation;552revert_rect = Rect2(ofs + text_limit, 0, reload_icon->get_width() + padding + (1 * EDSCALE), size.height);553554Point2 rtl_pos;555if (rtl) {556rtl_pos = Point2(size.width - revert_rect.position.x - (reload_icon->get_width() + padding + (1 * EDSCALE)), revert_rect.position.y);557}558559Color color2(1, 1, 1);560if (revert_hover) {561color2.r *= 1.2;562color2.g *= 1.2;563color2.b *= 1.2;564565if (rtl) {566draw_style_box(theme_cache.hover, Rect2(rtl_pos, revert_rect.size));567} else {568draw_style_box(theme_cache.hover, revert_rect);569}570}571572Point2 icon_ofs = (Point2(padding, size.height - reload_icon->get_height()) / 2).round();573if (rtl) {574draw_texture(reload_icon, rtl_pos + icon_ofs, color2);575} else {576draw_texture(reload_icon, revert_rect.position + icon_ofs, color2);577}578579text_limit -= half_padding;580} else {581revert_rect = Rect2();582}583584if (!pin_hidden && pinned) {585const Ref<Texture2D> &pinned_icon = theme_cache.pin_icon;586int margin_w = theme_cache.horizontal_separation;587int total_icon_w = margin_w + pinned_icon->get_width();588int text_w = font->get_string_size(label, rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT, text_limit - total_icon_w, font_size).x;589int y = (size.height - pinned_icon->get_height()) / 2;590if (rtl) {591draw_texture(pinned_icon, Vector2(size.width - ofs - text_w - total_icon_w, y), color);592} else {593draw_texture(pinned_icon, Vector2(ofs + text_w + margin_w, y), color);594}595text_limit -= total_icon_w;596}597598int v_ofs = (size.height - font->get_height(font_size)) / 2;599if (rtl) {600draw_string(font, Point2(size.width - ofs - text_limit, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_RIGHT, text_limit, font_size, color);601} else {602draw_string(font, Point2(ofs, v_ofs + font->get_ascent(font_size)), label, HORIZONTAL_ALIGNMENT_LEFT, text_limit, font_size, color);603}604605ofs = size.width;606ofs -= right_container->get_minimum_size().x;607608if (keying) {609Ref<Texture2D> key;610611if (use_keying_next()) {612key = theme_cache.key_next_icon;613} else {614key = theme_cache.key_icon;615}616617ofs -= key->get_width() + half_padding + theme_cache.horizontal_separation;618keying_rect = Rect2(ofs, 0, key->get_width() + padding, size.height);619620Point2 rtl_pos;621if (rtl) {622rtl_pos = Point2(size.width - keying_rect.position.x - (key->get_width() + padding), keying_rect.position.y);623}624625Color color2(1, 1, 1);626if (keying_hover) {627color2.r *= 1.2;628color2.g *= 1.2;629color2.b *= 1.2;630631if (rtl) {632draw_style_box(theme_cache.hover, Rect2(rtl_pos, keying_rect.size));633} else {634draw_style_box(theme_cache.hover, keying_rect);635}636}637638Point2 icon_ofs = (Point2(padding, size.height - key->get_height()) / 2).round();639if (rtl) {640draw_texture(key, rtl_pos + icon_ofs, color2);641} else {642draw_texture(key, keying_rect.position + icon_ofs, color2);643}644645} else {646keying_rect = Rect2();647}648649if (deletable) {650const Ref<Texture2D> &close = theme_cache.delete_icon;651652ofs -= close->get_width() + half_padding + theme_cache.horizontal_separation;653delete_rect = Rect2(ofs, 0, close->get_width() + padding, size.height);654655Point2 rtl_pos;656if (rtl) {657rtl_pos = Point2(size.width - delete_rect.position.x - (close->get_width() + padding), delete_rect.position.y);658}659660Color color2(1, 1, 1);661if (delete_hover) {662color2.r *= 1.2;663color2.g *= 1.2;664color2.b *= 1.2;665666if (rtl) {667draw_style_box(theme_cache.hover, Rect2(rtl_pos, delete_rect.size));668} else {669draw_style_box(theme_cache.hover, delete_rect);670}671}672673Point2 icon_ofs = (Point2(padding, size.height - close->get_height()) / 2).round();674if (rtl) {675draw_texture(close, rtl_pos + icon_ofs, color2);676} else {677draw_texture(close, delete_rect.position + icon_ofs, color2);678}679} else {680delete_rect = Rect2();681}682} break;683684case NOTIFICATION_ENTER_TREE: {685EditorInspector *inspector = get_parent_inspector();686if (inspector) {687inspector = inspector->get_root_inspector();688}689set_shortcut_context(inspector);690691if (has_borders) {692get_parent()->connect(SceneStringName(theme_changed), callable_mp(this, &EditorProperty::_update_property_bg));693_update_property_bg();694}695} break;696697case NOTIFICATION_EXIT_TREE: {698if (has_borders) {699get_parent()->disconnect(SceneStringName(theme_changed), callable_mp(this, &EditorProperty::_update_property_bg));700}701} break;702703case NOTIFICATION_MOUSE_EXIT_SELF:704case NOTIFICATION_MOUSE_EXIT: {705if (keying_hover || revert_hover || check_hover || delete_hover) {706keying_hover = false;707revert_hover = false;708check_hover = false;709delete_hover = false;710queue_redraw();711}712} break;713}714}715716void EditorProperty::set_label(const String &p_label) {717label = p_label;718queue_redraw();719}720721String EditorProperty::get_label() const {722return label;723}724725Object *EditorProperty::get_edited_object() {726return object;727}728729StringName EditorProperty::get_edited_property() const {730return property;731}732733Variant EditorProperty::get_edited_property_display_value() const {734ERR_FAIL_NULL_V(object, Variant());735Control *control = Object::cast_to<Control>(object);736if (checkable && !checked && control && String(property).begins_with("theme_override_")) {737return control->get_used_theme_item(property);738} else {739return get_edited_property_value();740}741}742743EditorInspector *EditorProperty::get_parent_inspector() const {744Node *parent = get_parent();745while (parent) {746EditorInspector *ei = Object::cast_to<EditorInspector>(parent);747if (ei) {748return ei;749}750parent = parent->get_parent();751}752return nullptr;753}754755void EditorProperty::set_doc_path(const String &p_doc_path) {756doc_path = p_doc_path;757}758759void EditorProperty::set_internal(bool p_internal) {760internal = p_internal;761}762763void EditorProperty::update_property() {764GDVIRTUAL_CALL(_update_property);765}766767void EditorProperty::_set_read_only(bool p_read_only) {768}769770void EditorProperty::set_read_only(bool p_read_only) {771read_only = p_read_only;772if (GDVIRTUAL_CALL(_set_read_only, p_read_only)) {773return;774}775_set_read_only(p_read_only);776}777778bool EditorProperty::is_read_only() const {779return read_only;780}781782Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid) {783if (p_object->property_can_revert(p_property)) {784if (r_is_valid) {785*r_is_valid = true;786}787return p_object->property_get_revert(p_property);788}789790return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid);791}792793bool EditorPropertyRevert::can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value) {794bool is_valid_revert = false;795Variant revert_value = EditorPropertyRevert::get_property_revert_value(p_object, p_property, &is_valid_revert);796if (!is_valid_revert) {797return false;798}799Variant current_value = p_custom_current_value ? *p_custom_current_value : p_object->get(p_property);800return PropertyUtils::is_property_value_different(p_object, current_value, revert_value);801}802803StringName EditorProperty::_get_revert_property() const {804return property;805}806807void EditorProperty::_update_property_bg() {808// This function is to be called on EditorPropertyResource, EditorPropertyArray, and EditorPropertyDictionary.809// Behavior is undetermined on any other EditorProperty.810if (!is_inside_tree()) {811return;812}813814if (bottom_editor) {815ColorationMode nested_color_mode = (ColorationMode)(int)EDITOR_GET("interface/inspector/nested_color_mode");816bool delimitate_all_container_and_resources = EDITOR_GET("interface/inspector/delimitate_all_container_and_resources");817818sub_inspector_color_level = 0;819if (is_colored(nested_color_mode)) {820Node *n = this;821while (n) {822EditorProperty *ep = Object::cast_to<EditorProperty>(n);823if (ep && ep->is_colored(nested_color_mode)) {824sub_inspector_color_level++;825if (sub_inspector_color_level == 16) {826break;827}828}829n = n->get_parent();830}831}832add_theme_style_override(SNAME("DictionaryAddItem"), get_theme_stylebox("DictionaryAddItem" + itos(sub_inspector_color_level), EditorStringName(EditorStyles)));833if (delimitate_all_container_and_resources || is_colored(nested_color_mode)) {834bottom_editor->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("sub_inspector_bg" + itos(sub_inspector_color_level), EditorStringName(EditorStyles)));835} else {836bottom_editor->add_theme_style_override(SceneStringName(panel), get_theme_stylebox("sub_inspector_bg_no_border", EditorStringName(EditorStyles)));837sub_inspector_color_level = -1;838}839} else {840sub_inspector_color_level = -1;841}842queue_redraw();843}844845void EditorProperty::update_editor_property_status() {846if (property == StringName()) {847return; //no property, so nothing to do848}849850bool new_pinned = false;851if (can_pin) {852Node *node = Object::cast_to<Node>(object);853CRASH_COND(!node);854new_pinned = node->is_property_pinned(property);855}856857bool new_warning = false;858if (object->has_method("_get_property_warning")) {859new_warning = !String(object->call("_get_property_warning", property)).is_empty();860}861862Variant current = object->get(_get_revert_property());863bool new_can_revert = EditorPropertyRevert::can_property_revert(object, property, ¤t) && !is_read_only();864865bool new_checked = checked;866if (checkable) { // for properties like theme overrides.867bool valid = false;868Variant value = object->get(property, &valid);869if (valid) {870new_checked = value.get_type() != Variant::NIL;871}872}873874if (new_can_revert != can_revert || new_pinned != pinned || new_checked != checked || new_warning != draw_prop_warning) {875if (new_can_revert != can_revert) {876emit_signal(SNAME("property_can_revert_changed"), property, new_can_revert);877}878draw_prop_warning = new_warning;879can_revert = new_can_revert;880pinned = new_pinned;881checked = new_checked;882queue_redraw();883}884}885886bool EditorProperty::use_keying_next() const {887List<PropertyInfo> plist;888object->get_property_list(&plist, true);889890for (const PropertyInfo &p : plist) {891if (p.name == property) {892return (p.usage & PROPERTY_USAGE_KEYING_INCREMENTS);893}894}895896return false;897}898899void EditorProperty::set_draw_label(bool p_draw_label) {900draw_label = p_draw_label;901queue_redraw();902queue_sort();903}904905bool EditorProperty::is_draw_label() const {906return draw_label;907}908909void EditorProperty::set_draw_background(bool p_draw_background) {910draw_background = p_draw_background;911queue_redraw();912}913914bool EditorProperty::is_draw_background() const {915return draw_background;916}917918void EditorProperty::set_checkable(bool p_checkable) {919checkable = p_checkable;920queue_redraw();921queue_sort();922}923924bool EditorProperty::is_checkable() const {925return checkable;926}927928void EditorProperty::set_checked(bool p_checked) {929checked = p_checked;930queue_redraw();931}932933bool EditorProperty::is_checked() const {934return checked;935}936937void EditorProperty::set_draw_warning(bool p_draw_warning) {938draw_warning = p_draw_warning;939queue_redraw();940}941942void EditorProperty::set_keying(bool p_keying) {943keying = p_keying;944queue_redraw();945queue_sort();946}947948void EditorProperty::set_deletable(bool p_deletable) {949deletable = p_deletable;950queue_redraw();951queue_sort();952}953954bool EditorProperty::is_deletable() const {955return deletable;956}957958bool EditorProperty::is_keying() const {959return keying;960}961962bool EditorProperty::is_draw_warning() const {963return draw_warning;964}965966void EditorProperty::_focusable_focused(int p_index) {967if (!selectable) {968return;969}970bool already_selected = selected;971selected = true;972selected_focusable = p_index;973queue_redraw();974if (!already_selected && selected) {975emit_signal(SNAME("selected"), property, selected_focusable);976}977}978979void EditorProperty::add_focusable(Control *p_control) {980p_control->connect(SceneStringName(focus_entered), callable_mp(this, &EditorProperty::_focusable_focused).bind(focusables.size()));981focusables.push_back(p_control);982}983984void EditorProperty::grab_focus(int p_focusable) {985if (focusables.is_empty()) {986return;987}988989if (p_focusable >= 0) {990ERR_FAIL_INDEX(p_focusable, focusables.size());991focusables[p_focusable]->grab_focus(true);992} else {993focusables[0]->grab_focus(true);994}995}996997void EditorProperty::select(int p_focusable) {998bool already_selected = selected;999if (!selectable) {1000return;1001}10021003if (p_focusable >= 0) {1004ERR_FAIL_INDEX(p_focusable, focusables.size());1005focusables[p_focusable]->grab_focus(true);1006} else {1007selected = true;1008queue_redraw();1009}10101011if (!already_selected && selected) {1012emit_signal(SNAME("selected"), property, selected_focusable);1013}1014}10151016void EditorProperty::deselect() {1017selected = false;1018selected_focusable = -1;1019queue_redraw();1020}10211022bool EditorProperty::is_selected() const {1023return selected;1024}10251026void EditorProperty::add_inline_control(Control *p_control, InlineControlSide p_side) {1027Node *parent = p_control->get_parent();1028if (parent != nullptr) {1029parent->remove_child(p_control);1030}1031if (p_side == INLINE_CONTROL_LEFT) {1032left_container->add_child(p_control);1033} else {1034right_container->add_child(p_control);1035}1036}10371038void EditorProperty::set_label_overlayed(bool p_overlay) {1039label_overlayed = p_overlay;1040}10411042void EditorProperty::gui_input(const Ref<InputEvent> &p_event) {1043ERR_FAIL_COND(p_event.is_null());10441045if (property == StringName()) {1046return;1047}10481049Ref<InputEventMouse> me = p_event;10501051if (me.is_valid()) {1052Vector2 mpos = me->get_position();1053if (bottom_child_rect.has_point(mpos)) {1054return; // Makes child EditorProperties behave like sibling nodes when handling mouse events.1055}1056if (is_layout_rtl()) {1057mpos.x = get_size().x - mpos.x;1058}1059bool button_left = me->get_button_mask().has_flag(MouseButtonMask::LEFT);10601061bool new_keying_hover = keying_rect.has_point(mpos) && !button_left;1062if (new_keying_hover != keying_hover) {1063keying_hover = new_keying_hover;1064queue_redraw();1065}10661067bool new_delete_hover = delete_rect.has_point(mpos) && !button_left;1068if (new_delete_hover != delete_hover) {1069delete_hover = new_delete_hover;1070queue_redraw();1071}10721073bool new_revert_hover = revert_rect.has_point(mpos) && !button_left;1074if (new_revert_hover != revert_hover) {1075revert_hover = new_revert_hover;1076queue_redraw();1077}10781079bool new_check_hover = check_rect.has_point(mpos) && !button_left;1080if (new_check_hover != check_hover) {1081check_hover = new_check_hover;1082queue_redraw();1083}1084}10851086Ref<InputEventMouseButton> mb = p_event;10871088if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {1089Vector2 mpos = mb->get_position();1090if (is_layout_rtl()) {1091mpos.x = get_size().x - mpos.x;1092}10931094select();10951096if (keying_rect.has_point(mpos)) {1097accept_event();1098emit_signal(SNAME("property_keyed"), property, use_keying_next());10991100if (use_keying_next()) {1101if (property == "frame_coords" && (object->is_class("Sprite2D") || object->is_class("Sprite3D"))) {1102Vector2i new_coords = object->get(property);1103new_coords.x++;1104if (new_coords.x >= int64_t(object->get("hframes"))) {1105new_coords.x = 0;1106new_coords.y++;1107}1108if (new_coords.x < int64_t(object->get("hframes")) && new_coords.y < int64_t(object->get("vframes"))) {1109callable_mp(this, &EditorProperty::emit_changed).call_deferred(property, new_coords, "", false);1110}1111} else {1112if (int64_t(object->get(property)) + 1 < (int64_t(object->get("hframes")) * int64_t(object->get("vframes")))) {1113callable_mp(this, &EditorProperty::emit_changed).call_deferred(property, object->get(property).operator int64_t() + 1, "", false);1114}1115}1116callable_mp(this, &EditorProperty::update_property).call_deferred();1117}1118}1119if (delete_rect.has_point(mpos)) {1120accept_event();1121emit_signal(SNAME("property_deleted"), property);1122}11231124if (revert_rect.has_point(mpos)) {1125accept_event();1126get_viewport()->gui_release_focus();1127bool is_valid_revert = false;1128Variant revert_value = EditorPropertyRevert::get_property_revert_value(object, property, &is_valid_revert);1129ERR_FAIL_COND(!is_valid_revert);1130emit_changed(_get_revert_property(), revert_value);1131update_property();1132}11331134if (check_rect.has_point(mpos)) {1135accept_event();1136if (!checked && Object::cast_to<Control>(object) && property_path.begins_with("theme_override_")) {1137List<PropertyInfo> pinfo;1138object->get_property_list(&pinfo);1139for (const PropertyInfo &E : pinfo) {1140if (E.type == Variant::OBJECT && E.name == property_path) {1141EditorToaster::get_singleton()->popup_str(TTR("Toggling the checkbox is disabled for Resource properties. Modify the property using the resource picker instead."), EditorToaster::SEVERITY_WARNING);1142return; // Disallow clicking to toggle the checkbox of type Resource to checked.1143}1144}1145}1146checked = !checked;1147queue_redraw();1148emit_signal(SNAME("property_checked"), property, checked);1149}1150} else if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {1151accept_event();1152_update_popup();1153menu->set_position(get_screen_position() + get_local_mouse_position());1154menu->reset_size();1155menu->popup();1156select();1157return;1158}1159}11601161void EditorProperty::_accessibility_action_click(const Variant &p_data) {1162select();1163if (checkable) {1164if (!checked && Object::cast_to<Control>(object) && property_path.begins_with("theme_override_")) {1165List<PropertyInfo> pinfo;1166object->get_property_list(&pinfo);1167for (const PropertyInfo &E : pinfo) {1168if (E.type == Variant::OBJECT && E.name == property_path) {1169EditorToaster::get_singleton()->popup_str(TTR("Toggling the checkbox is disabled for Resource properties. Modify the property using the resource picker instead."), EditorToaster::SEVERITY_WARNING);1170return;1171}1172}1173}11741175checked = !checked;1176queue_redraw();1177emit_signal(SNAME("property_checked"), property, checked);1178}1179}11801181void EditorProperty::_accessibility_action_menu(const Variant &p_data) {1182_update_popup();1183menu->set_position(get_screen_position());1184menu->reset_size();1185menu->popup();1186}11871188void EditorProperty::shortcut_input(const Ref<InputEvent> &p_event) {1189if (!selected || !p_event->is_pressed()) {1190return;1191}11921193const Ref<InputEventKey> k = p_event;11941195if (k.is_valid() && k->is_pressed()) {1196if (ED_IS_SHORTCUT("property_editor/copy_value", p_event)) {1197menu_option(MENU_COPY_VALUE);1198accept_event();1199} else if (!is_read_only() && ED_IS_SHORTCUT("property_editor/paste_value", p_event)) {1200menu_option(MENU_PASTE_VALUE);1201accept_event();1202} else if (!internal && ED_IS_SHORTCUT("property_editor/copy_property_path", p_event)) {1203menu_option(MENU_COPY_PROPERTY_PATH);1204accept_event();1205}1206}1207}12081209const Color *EditorProperty::_get_property_colors() {1210static Color c[4];1211c[0] = theme_cache.property_color_x;1212c[1] = theme_cache.property_color_y;1213c[2] = theme_cache.property_color_z;1214c[3] = theme_cache.property_color_w;1215return c;1216}12171218void EditorProperty::set_label_reference(Control *p_control) {1219label_reference = p_control;1220}12211222HBoxContainer *EditorProperty::get_inline_container(InlineControlSide p_side) {1223if (p_side == INLINE_CONTROL_LEFT) {1224return left_container;1225} else {1226return right_container;1227}1228}12291230void EditorProperty::set_bottom_editor(Control *p_control) {1231bottom_editor = p_control;1232if (has_borders) {1233_update_property_bg();1234}1235}12361237Variant EditorProperty::_get_cache_value(const StringName &p_prop, bool &r_valid) const {1238return object->get(p_prop, &r_valid);1239}12401241bool EditorProperty::is_cache_valid() const {1242if (object) {1243for (const KeyValue<StringName, Variant> &E : cache) {1244bool valid;1245Variant value = _get_cache_value(E.key, valid);1246if (!valid || value != E.value) {1247return false;1248}1249}1250}1251return true;1252}1253void EditorProperty::update_cache() {1254cache.clear();1255if (object && property != StringName()) {1256bool valid;1257Variant value = _get_cache_value(property, valid);1258if (valid) {1259cache[property] = value;1260}1261}1262}1263Variant EditorProperty::get_drag_data(const Point2 &p_point) {1264if (property == StringName()) {1265return Variant();1266}12671268Dictionary dp;1269dp["type"] = "obj_property";1270dp["object"] = object;1271dp["property"] = property;1272dp["value"] = object->get(property);12731274Label *drag_label = memnew(Label);1275drag_label->set_focus_mode(FOCUS_ACCESSIBILITY);1276drag_label->set_text(property);1277drag_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); // Don't translate raw property name.1278set_drag_preview(drag_label);1279return dp;1280}12811282void EditorProperty::set_use_folding(bool p_use_folding) {1283use_folding = p_use_folding;1284}12851286bool EditorProperty::is_using_folding() const {1287return use_folding;1288}12891290void EditorProperty::expand_all_folding() {1291}12921293void EditorProperty::collapse_all_folding() {1294}12951296void EditorProperty::expand_revertable() {1297}12981299void EditorProperty::set_selectable(bool p_selectable) {1300selectable = p_selectable;1301}13021303bool EditorProperty::is_selectable() const {1304return selectable;1305}13061307void EditorProperty::set_name_split_ratio(float p_ratio) {1308split_ratio = p_ratio;1309}13101311float EditorProperty::get_name_split_ratio() const {1312return split_ratio;1313}13141315void EditorProperty::set_favoritable(bool p_favoritable) {1316can_favorite = p_favoritable;1317}13181319bool EditorProperty::is_favoritable() const {1320return can_favorite;1321}13221323void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) {1324object = p_object;1325property = p_property;13261327_update_flags();1328}13291330static bool _is_value_potential_override(Node *p_node, const String &p_property) {1331// Consider a value is potentially overriding another if either of the following is true:1332// a) The node is foreign (inheriting or an instance), so the original value may come from another scene.1333// b) The node belongs to the scene, but the original value comes from somewhere but the builtin class (i.e., a script).1334Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();1335Vector<SceneState::PackState> states_stack = PropertyUtils::get_node_states_stack(p_node, edited_scene);1336if (states_stack.size()) {1337return true;1338} else {1339bool is_valid_default = false;1340bool is_class_default = false;1341PropertyUtils::get_property_default_value(p_node, p_property, &is_valid_default, &states_stack, false, nullptr, &is_class_default);1342return !is_class_default;1343}1344}13451346void EditorProperty::_update_flags() {1347can_pin = false;1348pin_hidden = true;13491350if (read_only) {1351return;1352}13531354if (Node *node = Object::cast_to<Node>(object)) {1355// Avoid errors down the road by ignoring nodes which are not part of a scene1356if (!node->get_owner()) {1357bool is_scene_root = false;1358for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); ++i) {1359if (EditorNode::get_editor_data().get_edited_scene_root(i) == node) {1360is_scene_root = true;1361break;1362}1363}1364if (!is_scene_root) {1365return;1366}1367}1368if (!_is_value_potential_override(node, property)) {1369return;1370}1371pin_hidden = false;1372{1373HashSet<StringName> storable_properties;1374node->get_storable_properties(storable_properties);1375if (storable_properties.has(node->get_property_store_alias(property))) {1376can_pin = true;1377}1378}1379}1380}13811382Control *EditorProperty::make_custom_tooltip(const String &p_text) const {1383String symbol;1384String prologue;13851386if (object->has_method("_get_property_warning")) {1387const String custom_warning = object->call("_get_property_warning", property);1388if (!custom_warning.is_empty()) {1389prologue = "[b][color=" + theme_cache.warning_color.to_html(false) + "]" + custom_warning + "[/color][/b]";1390}1391}13921393if (has_doc_tooltip) {1394symbol = p_text;13951396const EditorInspector *inspector = get_parent_inspector();1397if (inspector) {1398const String custom_description = inspector->get_custom_property_description(p_text);1399if (!custom_description.is_empty()) {1400if (!prologue.is_empty()) {1401prologue += '\n';1402}1403prologue += custom_description;1404}1405}1406}14071408if (!symbol.is_empty() || !prologue.is_empty()) {1409return EditorHelpBitTooltip::make_tooltip(const_cast<EditorProperty *>(this), symbol, prologue);1410}14111412return nullptr;1413}14141415void EditorProperty::menu_option(int p_option) {1416switch (p_option) {1417case MENU_COPY_VALUE: {1418EditorInspector::set_property_clipboard(object->get(property));1419} break;1420case MENU_PASTE_VALUE: {1421EditorPropertyResource *epr = Object::cast_to<EditorPropertyResource>(this);1422if (epr) {1423const Ref<Resource> res = InspectorDock::get_inspector_singleton()->get_property_clipboard();1424if (res.is_valid() && !epr->get_resource_picker()->is_resource_allowed(res)) {1425return;1426}1427}1428emit_changed(property, EditorInspector::get_property_clipboard());1429} break;1430case MENU_COPY_PROPERTY_PATH: {1431DisplayServer::get_singleton()->clipboard_set(property_path);1432} break;1433case MENU_OVERRIDE_FOR_PROJECT: {1434emit_signal(SNAME("property_overridden"));1435} break;1436case MENU_FAVORITE_PROPERTY: {1437emit_signal(SNAME("property_favorited"), property, !favorited);1438queue_redraw();1439} break;1440case MENU_PIN_VALUE: {1441emit_signal(SNAME("property_pinned"), property, !pinned);1442queue_redraw();1443} break;1444case MENU_DELETE: {1445accept_event();1446emit_signal(SNAME("property_deleted"), property);1447} break;1448case MENU_REVERT_VALUE: {1449accept_event();1450get_viewport()->gui_release_focus();1451bool is_valid_revert = false;1452Variant revert_value = EditorPropertyRevert::get_property_revert_value(object, property, &is_valid_revert);1453ERR_FAIL_COND(!is_valid_revert);1454emit_changed(_get_revert_property(), revert_value);1455update_property();1456} break;1457case MENU_OPEN_DOCUMENTATION: {1458ScriptEditor::get_singleton()->goto_help(doc_path);1459EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);1460} break;1461}1462}14631464void EditorProperty::_bind_methods() {1465ClassDB::bind_method(D_METHOD("set_label", "text"), &EditorProperty::set_label);1466ClassDB::bind_method(D_METHOD("get_label"), &EditorProperty::get_label);14671468ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorProperty::set_read_only);1469ClassDB::bind_method(D_METHOD("is_read_only"), &EditorProperty::is_read_only);14701471ClassDB::bind_method(D_METHOD("set_draw_label", "draw_label"), &EditorProperty::set_draw_label);1472ClassDB::bind_method(D_METHOD("is_draw_label"), &EditorProperty::is_draw_label);14731474ClassDB::bind_method(D_METHOD("set_draw_background", "draw_background"), &EditorProperty::set_draw_background);1475ClassDB::bind_method(D_METHOD("is_draw_background"), &EditorProperty::is_draw_background);14761477ClassDB::bind_method(D_METHOD("set_checkable", "checkable"), &EditorProperty::set_checkable);1478ClassDB::bind_method(D_METHOD("is_checkable"), &EditorProperty::is_checkable);14791480ClassDB::bind_method(D_METHOD("set_checked", "checked"), &EditorProperty::set_checked);1481ClassDB::bind_method(D_METHOD("is_checked"), &EditorProperty::is_checked);14821483ClassDB::bind_method(D_METHOD("set_draw_warning", "draw_warning"), &EditorProperty::set_draw_warning);1484ClassDB::bind_method(D_METHOD("is_draw_warning"), &EditorProperty::is_draw_warning);14851486ClassDB::bind_method(D_METHOD("set_keying", "keying"), &EditorProperty::set_keying);1487ClassDB::bind_method(D_METHOD("is_keying"), &EditorProperty::is_keying);14881489ClassDB::bind_method(D_METHOD("set_deletable", "deletable"), &EditorProperty::set_deletable);1490ClassDB::bind_method(D_METHOD("is_deletable"), &EditorProperty::is_deletable);14911492ClassDB::bind_method(D_METHOD("get_edited_property"), &EditorProperty::get_edited_property);1493ClassDB::bind_method(D_METHOD("get_edited_object"), &EditorProperty::get_edited_object);14941495ClassDB::bind_method(D_METHOD("update_property"), &EditorProperty::update_property);14961497ClassDB::bind_method(D_METHOD("add_focusable", "control"), &EditorProperty::add_focusable);1498ClassDB::bind_method(D_METHOD("set_bottom_editor", "editor"), &EditorProperty::set_bottom_editor);14991500ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &EditorProperty::set_selectable);1501ClassDB::bind_method(D_METHOD("is_selectable"), &EditorProperty::is_selectable);15021503ClassDB::bind_method(D_METHOD("set_use_folding", "use_folding"), &EditorProperty::set_use_folding);1504ClassDB::bind_method(D_METHOD("is_using_folding"), &EditorProperty::is_using_folding);15051506ClassDB::bind_method(D_METHOD("set_name_split_ratio", "ratio"), &EditorProperty::set_name_split_ratio);1507ClassDB::bind_method(D_METHOD("get_name_split_ratio"), &EditorProperty::get_name_split_ratio);15081509ClassDB::bind_method(D_METHOD("deselect"), &EditorProperty::deselect);1510ClassDB::bind_method(D_METHOD("is_selected"), &EditorProperty::is_selected);1511ClassDB::bind_method(D_METHOD("select", "focusable"), &EditorProperty::select, DEFVAL(-1));1512ClassDB::bind_method(D_METHOD("set_object_and_property", "object", "property"), &EditorProperty::set_object_and_property);1513ClassDB::bind_method(D_METHOD("set_label_reference", "control"), &EditorProperty::set_label_reference);15141515ClassDB::bind_method(D_METHOD("emit_changed", "property", "value", "field", "changing"), &EditorProperty::emit_changed, DEFVAL(StringName()), DEFVAL(false));15161517ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label");1518ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");1519ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_label"), "set_draw_label", "is_draw_label");1520ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_background"), "set_draw_background", "is_draw_background");1521ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checkable"), "set_checkable", "is_checkable");1522ADD_PROPERTY(PropertyInfo(Variant::BOOL, "checked"), "set_checked", "is_checked");1523ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_warning"), "set_draw_warning", "is_draw_warning");1524ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keying"), "set_keying", "is_keying");1525ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deletable"), "set_deletable", "is_deletable");1526ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable");1527ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_folding"), "set_use_folding", "is_using_folding");1528ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "name_split_ratio"), "set_name_split_ratio", "get_name_split_ratio");15291530ADD_SIGNAL(MethodInfo("property_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::STRING_NAME, "field"), PropertyInfo(Variant::BOOL, "changing")));1531ADD_SIGNAL(MethodInfo("multiple_properties_changed", PropertyInfo(Variant::PACKED_STRING_ARRAY, "properties"), PropertyInfo(Variant::ARRAY, "value")));1532ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING_NAME, "property")));1533ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING_NAME, "property")));1534ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));1535ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "checked")));1536ADD_SIGNAL(MethodInfo("property_overridden"));1537ADD_SIGNAL(MethodInfo("property_favorited", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "favorited")));1538ADD_SIGNAL(MethodInfo("property_pinned", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "pinned")));1539ADD_SIGNAL(MethodInfo("property_can_revert_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "can_revert")));1540ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static())));1541ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::INT, "id")));1542ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "focusable_idx")));15431544GDVIRTUAL_BIND(_update_property)1545GDVIRTUAL_BIND(_set_read_only, "read_only")15461547ClassDB::bind_method(D_METHOD("_update_editor_property_status"), &EditorProperty::update_editor_property_status);1548}15491550EditorProperty::EditorProperty() {1551set_focus_mode(FOCUS_ACCESSIBILITY);15521553object = nullptr;1554split_ratio = 0.5;1555text_size = 0;1556property_usage = 0;1557selected_focusable = -1;1558label_reference = nullptr;1559bottom_editor = nullptr;1560menu = nullptr;15611562left_container = memnew(HBoxContainer);1563left_container->add_theme_constant_override(SNAME("separation"), 0);1564add_child(left_container);15651566right_container = memnew(HBoxContainer);1567right_container->add_theme_constant_override(SNAME("separation"), 0);1568add_child(right_container);15691570set_process_shortcut_input(true);1571}15721573void EditorProperty::_update_popup() {1574if (menu) {1575menu->clear();1576} else {1577menu = memnew(PopupMenu);1578add_child(menu);1579menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorProperty::menu_option));1580}1581menu->add_icon_shortcut(theme_cache.copy_icon, ED_GET_SHORTCUT("property_editor/copy_value"), MENU_COPY_VALUE);1582menu->add_icon_shortcut(theme_cache.paste_icon, ED_GET_SHORTCUT("property_editor/paste_value"), MENU_PASTE_VALUE);1583menu->add_icon_shortcut(theme_cache.copy_node_path_icon, ED_GET_SHORTCUT("property_editor/copy_property_path"), MENU_COPY_PROPERTY_PATH);1584menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());1585menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal);15861587if (can_favorite || !pin_hidden) {1588menu->add_separator();1589}15901591if (can_favorite) {1592if (favorited) {1593menu->add_icon_item(theme_cache.unfavorite_icon, TTR("Unfavorite Property"), MENU_FAVORITE_PROPERTY);1594menu->set_item_tooltip(menu->get_item_index(MENU_FAVORITE_PROPERTY), TTR("Make this property be put back at its original place."));1595} else {1596// TRANSLATORS: This is a menu item to add a property to the favorites.1597menu->add_icon_item(theme_cache.favorite_icon, TTR("Favorite Property"), MENU_FAVORITE_PROPERTY);1598menu->set_item_tooltip(menu->get_item_index(MENU_FAVORITE_PROPERTY), TTR("Make this property be placed at the top for all objects of this class."));1599}1600}16011602if (!pin_hidden) {1603if (can_pin) {1604menu->add_icon_check_item(theme_cache.pin_icon, TTR("Pin Value"), MENU_PIN_VALUE);1605menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned);1606} else {1607menu->add_icon_check_item(theme_cache.pin_icon, vformat(TTR("Pin Value [Disabled because '%s' is editor-only]"), property), MENU_PIN_VALUE);1608menu->set_item_disabled(menu->get_item_index(MENU_PIN_VALUE), true);1609}1610menu->set_item_tooltip(menu->get_item_index(MENU_PIN_VALUE), TTR("Pinning a value forces it to be saved even if it's equal to the default."));1611}1612if (deletable || can_revert || can_override) {1613menu->add_separator();1614if (can_override) {1615menu->add_icon_item(theme_cache.override_icon, TTRC("Override for Project"), MENU_OVERRIDE_FOR_PROJECT);1616}1617if (deletable) {1618menu->add_icon_item(theme_cache.remove_icon, TTR("Delete Property"), MENU_DELETE);1619}1620if (can_revert) {1621menu->add_icon_item(theme_cache.revert_icon, TTR("Revert Value"), MENU_REVERT_VALUE);1622}1623}1624if (!doc_path.is_empty() && ScriptEditor::get_singleton() && EditorNode::get_singleton()) {1625menu->add_separator();1626menu->add_icon_item(theme_cache.help_icon, TTR("Open Documentation"), MENU_OPEN_DOCUMENTATION);1627}1628}16291630////////////////////////////////////////////////1631////////////////////////////////////////////////16321633void EditorInspectorPlugin::add_custom_control(Control *control) {1634AddedEditor ae;1635ae.property_editor = control;1636added_editors.push_back(ae);1637}16381639void EditorInspectorPlugin::add_property_editor(const String &p_for_property, Control *p_prop, bool p_add_to_end, const String &p_label) {1640AddedEditor ae;1641ae.properties.push_back(p_for_property);1642ae.property_editor = p_prop;1643ae.add_to_end = p_add_to_end;1644ae.label = p_label;1645added_editors.push_back(ae);1646}16471648void EditorInspectorPlugin::add_property_editor_for_multiple_properties(const String &p_label, const Vector<String> &p_properties, Control *p_prop) {1649AddedEditor ae;1650ae.properties = p_properties;1651ae.property_editor = p_prop;1652ae.label = p_label;1653added_editors.push_back(ae);1654}16551656bool EditorInspectorPlugin::can_handle(Object *p_object) {1657bool success = false;1658GDVIRTUAL_CALL(_can_handle, p_object, success);1659return success;1660}16611662void EditorInspectorPlugin::parse_begin(Object *p_object) {1663GDVIRTUAL_CALL(_parse_begin, p_object);1664}16651666void EditorInspectorPlugin::parse_category(Object *p_object, const String &p_category) {1667GDVIRTUAL_CALL(_parse_category, p_object, p_category);1668}16691670void EditorInspectorPlugin::parse_group(Object *p_object, const String &p_group) {1671GDVIRTUAL_CALL(_parse_group, p_object, p_group);1672}16731674bool EditorInspectorPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) {1675bool ret = false;1676GDVIRTUAL_CALL(_parse_property, p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide, ret);1677return ret;1678}16791680void EditorInspectorPlugin::parse_end(Object *p_object) {1681GDVIRTUAL_CALL(_parse_end, p_object);1682}16831684void EditorInspectorPlugin::_bind_methods() {1685ClassDB::bind_method(D_METHOD("add_custom_control", "control"), &EditorInspectorPlugin::add_custom_control);1686ClassDB::bind_method(D_METHOD("add_property_editor", "property", "editor", "add_to_end", "label"), &EditorInspectorPlugin::add_property_editor, DEFVAL(false), DEFVAL(String()));1687ClassDB::bind_method(D_METHOD("add_property_editor_for_multiple_properties", "label", "properties", "editor"), &EditorInspectorPlugin::add_property_editor_for_multiple_properties);16881689GDVIRTUAL_BIND(_can_handle, "object")1690GDVIRTUAL_BIND(_parse_begin, "object")1691GDVIRTUAL_BIND(_parse_category, "object", "category")1692GDVIRTUAL_BIND(_parse_group, "object", "group")1693GDVIRTUAL_BIND(_parse_property, "object", "type", "name", "hint_type", "hint_string", "usage_flags", "wide");1694GDVIRTUAL_BIND(_parse_end, "object")1695}16961697////////////////////////////////////////////////1698////////////////////////////////////////////////16991700static Ref<Script> _get_category_script(const PropertyInfo &p_info) {1701if (!p_info.hint_string.is_empty() && !EditorNode::get_editor_data().is_type_recognized(p_info.name) && ResourceLoader::exists(p_info.hint_string, "Script")) {1702return ResourceLoader::load(p_info.hint_string, "Script");1703}1704return Ref<Script>();1705}17061707void EditorInspectorCategory::_bind_methods() {1708ADD_SIGNAL(MethodInfo("unfavorite_all"));1709}17101711void EditorInspectorCategory::_notification(int p_what) {1712switch (p_what) {1713case NOTIFICATION_POSTINITIALIZE: {1714connect(SceneStringName(theme_changed), callable_mp(this, &EditorInspectorCategory::_theme_changed));1715} break;17161717case NOTIFICATION_ACCESSIBILITY_UPDATE: {1718RID ae = get_accessibility_element();1719ERR_FAIL_COND(ae.is_null());17201721DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);17221723DisplayServer::get_singleton()->accessibility_update_set_name(ae, vformat(TTR("Category: %s"), label));1724DisplayServer::get_singleton()->accessibility_update_set_value(ae, vformat(TTR("Category: %s"), label));17251726DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_MENU);1727DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &EditorInspectorCategory::_accessibility_action_menu));1728} break;17291730case NOTIFICATION_TRANSLATION_CHANGED: {1731if (is_favorite) {1732label = TTR("Favorites");1733}1734queue_accessibility_update();1735} break;17361737case NOTIFICATION_DRAW: {1738const Ref<StyleBox> &sb = theme_cache.background;17391740draw_style_box(sb, Rect2(Vector2(), get_size()));17411742const Ref<Font> &font = theme_cache.bold_font;1743int font_size = theme_cache.bold_font_size;17441745int hs = theme_cache.horizontal_separation;1746int icon_size = theme_cache.class_icon_size;17471748int w = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;1749if (icon.is_valid()) {1750w += hs + icon_size;1751}1752w = MIN(w, get_size().width - sb->get_minimum_size().width);17531754int ofs = (get_size().width - w) / 2;17551756float v_margin_offset = sb->get_content_margin(SIDE_TOP) - sb->get_content_margin(SIDE_BOTTOM);17571758if (icon.is_valid()) {1759Size2 rect_size = Size2(icon_size, icon_size);1760Point2 rect_pos = Point2(ofs, (get_size().height - icon_size) / 2 + v_margin_offset).round();1761if (is_layout_rtl()) {1762rect_pos.x = get_size().width - rect_pos.x - icon_size;1763}1764draw_texture_rect(icon, Rect2(rect_pos, rect_size));17651766ofs += hs + icon_size;1767w -= hs + icon_size;1768}17691770if (is_layout_rtl()) {1771ofs = get_size().width - ofs - w;1772}17731774// Use TextLine so we have access to accurate font metrics. This way,1775// we can ensure the line is vertically centered regardless of the font used1776// or its size.1777Ref<TextLine> tl;1778tl.instantiate();1779tl->add_string(label, font, font_size);1780tl->set_width(w);1781tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);1782float text_pos_y = (get_size().height - tl->get_size().height) / 2 + v_margin_offset;1783Point2 text_pos = Point2(ofs, text_pos_y).round();1784tl->draw(get_canvas_item(), text_pos, theme_cache.font_color);1785} break;1786}1787}17881789void EditorInspectorCategory::_accessibility_action_menu(const Variant &p_data) {1790_popup_context_menu(get_screen_position());1791}17921793Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {1794// If it's not a doc tooltip, fallback to the default one.1795if (doc_class_name.is_empty()) {1796return nullptr;1797}17981799return EditorHelpBitTooltip::make_tooltip(const_cast<EditorInspectorCategory *>(this), p_text);1800}18011802void EditorInspectorCategory::set_as_favorite() {1803is_favorite = true;1804_update_icon();1805}18061807void EditorInspectorCategory::set_property_info(const PropertyInfo &p_info) {1808info = p_info;18091810Ref<Script> scr = _get_category_script(info);1811if (scr.is_valid()) {1812StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());1813if (script_name != StringName()) {1814label = script_name;1815}1816}1817if (label.is_empty()) {1818label = info.name;1819}1820_update_icon();1821}18221823void EditorInspectorCategory::set_doc_class_name(const String &p_name) {1824doc_class_name = p_name;1825}18261827Size2 EditorInspectorCategory::get_minimum_size() const {1828Size2 ms;1829if (theme_cache.bold_font.is_valid()) {1830ms.height = theme_cache.bold_font->get_height(theme_cache.bold_font_size);1831}1832if (icon.is_valid()) {1833ms.height = MAX(theme_cache.class_icon_size, ms.height);1834}1835ms.height += theme_cache.vertical_separation;18361837if (theme_cache.background.is_valid()) {1838ms.height += theme_cache.background->get_content_margin(SIDE_TOP) + theme_cache.background->get_content_margin(SIDE_BOTTOM);1839}18401841return ms;1842}18431844void EditorInspectorCategory::_handle_menu_option(int p_option) {1845switch (p_option) {1846case MENU_OPEN_DOCS: {1847ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name);1848EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);1849} break;18501851case MENU_UNFAVORITE_ALL: {1852emit_signal(SNAME("unfavorite_all"));1853} break;1854}1855}18561857void EditorInspectorCategory::_popup_context_menu(const Point2i &p_position) {1858if (!is_favorite && doc_class_name.is_empty()) {1859return;1860}18611862if (menu == nullptr) {1863menu = memnew(PopupMenu);18641865if (is_favorite) {1866menu->add_item(TTRC("Unfavorite All"), MENU_UNFAVORITE_ALL);1867} else {1868menu->add_item(TTRC("Open Documentation"), MENU_OPEN_DOCS);1869menu->set_item_disabled(-1, !EditorHelp::get_doc_data()->class_list.has(doc_class_name));1870}18711872menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));1873add_child(menu);1874}18751876if (menu_icon_dirty) {1877if (is_favorite) {1878menu->set_item_icon(menu->get_item_index(MENU_UNFAVORITE_ALL), theme_cache.icon_unfavorite);1879} else {1880menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), theme_cache.icon_help);1881}1882menu_icon_dirty = false;1883}18841885menu->set_position(p_position);1886menu->reset_size();1887menu->popup();1888}18891890void EditorInspectorCategory::_update_icon() {1891if (is_favorite) {1892icon = theme_cache.icon_favorites;1893return;1894}18951896icon = Ref<Texture2D>();18971898Ref<Script> scr = _get_category_script(info);1899if (scr.is_valid()) {1900StringName script_name = EditorNode::get_editor_data().script_class_get_name(scr->get_path());1901if (script_name == StringName()) {1902icon = EditorNode::get_singleton()->get_object_icon(scr.ptr());1903} else {1904icon = EditorNode::get_singleton()->get_class_icon(script_name);1905}1906}1907if (icon.is_null() && !info.name.is_empty()) {1908icon = EditorNode::get_singleton()->get_class_icon(info.name);1909}1910}19111912void EditorInspectorCategory::_theme_changed() {1913// This needs to be done via the signal, as it's fired before the minimum since is updated.1914EditorInspector::initialize_category_theme(theme_cache, this);1915menu_icon_dirty = true;1916_update_icon();1917}19181919void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {1920const Ref<InputEventMouseButton> &mb = p_event;1921if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {1922_popup_context_menu(get_screen_position() + mb->get_position());1923}1924}19251926EditorInspectorCategory::EditorInspectorCategory() {1927set_focus_mode(FOCUS_ACCESSIBILITY);1928}19291930////////////////////////////////////////////////1931////////////////////////////////////////////////19321933void EditorInspectorSection::_test_unfold() {1934if (!vbox_added) {1935add_child(vbox);1936move_child(vbox, 0);1937vbox_added = true;1938}1939}19401941Ref<Texture2D> EditorInspectorSection::_get_arrow() {1942Ref<Texture2D> arrow;1943if (foldable) {1944if (object->editor_is_section_unfolded(section)) {1945arrow = theme_cache.arrow;1946} else {1947if (is_layout_rtl()) {1948arrow = theme_cache.arrow_collapsed_mirrored;1949} else {1950arrow = theme_cache.arrow_collapsed;1951}1952}1953}1954return arrow;1955}19561957Ref<Texture2D> EditorInspectorSection::_get_checkbox() {1958Ref<Texture2D> checkbox;19591960if (checkable) {1961if (checked) {1962checkbox = theme_cache.icon_gui_checked;1963} else {1964checkbox = theme_cache.icon_gui_unchecked;1965}1966}19671968return checkbox;1969}19701971int EditorInspectorSection::_get_header_height() {1972int header_height = theme_cache.bold_font->get_height(theme_cache.bold_font_size);1973Ref<Texture2D> arrow = _get_arrow();1974if (arrow.is_valid()) {1975header_height = MAX(header_height, arrow->get_height());1976}1977header_height += theme_cache.vertical_separation;19781979return header_height;1980}19811982void EditorInspectorSection::_notification(int p_what) {1983switch (p_what) {1984case NOTIFICATION_ACCESSIBILITY_UPDATE: {1985RID ae = get_accessibility_element();1986ERR_FAIL_COND(ae.is_null());19871988DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);19891990DisplayServer::get_singleton()->accessibility_update_set_name(ae, vformat(TTR("Section: %s"), label));1991DisplayServer::get_singleton()->accessibility_update_set_value(ae, vformat(TTR("Section: %s"), label));1992DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_COLLAPSE, callable_mp(this, &EditorInspectorSection::_accessibility_action_collapse));1993DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_EXPAND, callable_mp(this, &EditorInspectorSection::_accessibility_action_expand));1994} break;19951996case NOTIFICATION_THEME_CHANGED: {1997EditorInspector::initialize_section_theme(theme_cache, this);19981999bg_color = theme_cache.prop_subsection;2000bg_color.a /= level;2001} break;20022003case NOTIFICATION_SORT_CHILDREN: {2004if (!vbox_added) {2005return;2006}20072008int inspector_margin = theme_cache.inspector_margin;2009if (indent_depth > 0 && theme_cache.indent_size > 0) {2010inspector_margin += indent_depth * theme_cache.indent_size;2011}2012if (indent_depth > 0 && theme_cache.indent_box.is_valid()) {2013inspector_margin += theme_cache.indent_box->get_margin(SIDE_LEFT) + theme_cache.indent_box->get_margin(SIDE_RIGHT);2014}20152016Size2 size = get_size() - Vector2(inspector_margin, 0);2017int header_height = _get_header_height();2018Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height);2019for (int i = 0; i < get_child_count(); i++) {2020Control *c = as_sortable_control(get_child(i));2021if (!c) {2022continue;2023}2024fit_child_in_rect(c, Rect2(offset, size));2025}2026} break;20272028case NOTIFICATION_DRAW: {2029int section_indent = 0;2030int section_indent_size = theme_cache.indent_size;2031if (indent_depth > 0 && section_indent_size > 0) {2032section_indent = indent_depth * section_indent_size;2033}2034Ref<StyleBoxFlat> section_indent_style = theme_cache.indent_box;2035if (indent_depth > 0 && section_indent_style.is_valid()) {2036section_indent += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT);2037}20382039int header_width = get_size().width - section_indent;2040int header_offset_x = 0.0;2041bool rtl = is_layout_rtl();2042if (!rtl) {2043header_offset_x += section_indent;2044}20452046bool can_click_unfold = vbox->get_child_count(false) != 0 && !(checkable && !checked && !checkbox_only);20472048// Draw header area.2049int header_height = _get_header_height();2050Rect2 header_rect = Rect2(Vector2(header_offset_x, 0.0), Vector2(header_width, header_height));2051Color c = bg_color;2052c.a *= 0.4;2053if (header_hover) {2054c = c.lightened(Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) ? -0.05 : 0.2);2055}2056draw_rect(header_rect, c);20572058// Draw header title, folding arrow and count of revertable properties.2059{2060int outer_margin = Math::round(2 * EDSCALE);20612062int margin_start = section_indent + outer_margin;2063int margin_end = outer_margin;20642065// - Arrow.2066Ref<Texture2D> arrow = _get_arrow();2067if (arrow.is_valid()) {2068Point2 arrow_position;2069if (rtl) {2070arrow_position.x = get_size().width - (margin_start + arrow->get_width());2071} else {2072arrow_position.x = margin_start;2073}2074arrow_position.y = (header_height - arrow->get_height()) / 2;2075if (can_click_unfold) {2076draw_texture(arrow, arrow_position, Color(1, 1, 1, header_hover ? 1.0 : 0.85));2077}2078margin_start += arrow->get_width() + theme_cache.horizontal_separation;2079}20802081Ref<Font> font = theme_cache.bold_font;2082int font_size = theme_cache.bold_font_size;2083Color font_color = theme_cache.font_color;20842085Ref<Font> light_font = theme_cache.light_font;2086int light_font_size = theme_cache.light_font_size;20872088// - Keying2089Ref<Texture2D> key = theme_cache.icon_gui_animation_key;2090if (keying && key.is_valid()) {2091Point2 key_position;2092key_position.x = (rtl ? margin_end : (get_size().width - key->get_width() - margin_end)) - theme_cache.key_padding_size / 2;2093keying_rect = Rect2(key_position.x - theme_cache.key_padding_size / 2, 0, key->get_width() + theme_cache.key_padding_size, header_height);20942095Color key_color(1, 1, 1);2096if (keying_hover) {2097key_color.r *= 1.2;2098key_color.g *= 1.2;2099key_color.b *= 1.2;21002101Ref<StyleBox> sb_hover = theme_cache.key_hover;2102draw_style_box(sb_hover, keying_rect);2103}2104key_position.y = (header_height - key->get_height()) / 2;21052106draw_texture(key, key_position, key_color);2107margin_end += keying_rect.size.width + theme_cache.horizontal_separation;2108} else {2109keying_rect = Rect2();2110}21112112// - Checkbox.2113Ref<Texture2D> checkbox = _get_checkbox();2114if (checkbox.is_valid()) {2115const String checkbox_text = TTR("On");2116Size2 label_size = light_font->get_string_size(checkbox_text, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size);2117Point2 checkbox_position;2118Point2 label_position;2119if (rtl) {2120label_position.x = margin_end;2121checkbox_position.x = margin_end + label_size.width + 2 * EDSCALE;2122} else {2123label_position.x = get_size().width - (margin_end + label_size.width);2124checkbox_position.x = label_position.x - checkbox->get_width() - 2 * EDSCALE;2125}2126checkbox_position.y = (header_height - checkbox->get_height()) / 2;2127label_position.y = light_font->get_ascent(light_font_size) + (header_height - label_size.height) / 2.0;21282129check_rect = Rect2(checkbox_position.x, 0, checkbox->get_width() + label_size.width + 2 * EDSCALE, header_height);21302131Color check_font_color = font_color;2132Color checkbox_color(1, 1, 1);2133if (check_hover) {2134checkbox_color.r *= 1.2;2135checkbox_color.g *= 1.2;2136checkbox_color.b *= 1.2;2137check_font_color = checked ? theme_cache.font_hover_pressed_color : theme_cache.font_hover_color;2138} else if (checked) {2139check_font_color = theme_cache.font_pressed_color;2140}21412142draw_texture(checkbox, checkbox_position, checkbox_color);2143draw_string(light_font, label_position, checkbox_text, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, check_font_color, TextServer::JUSTIFICATION_NONE);2144margin_end += label_size.width + checkbox->get_width() + 6 * EDSCALE;2145} else {2146check_rect = Rect2();2147}21482149int available = get_size().width - (margin_start + margin_end);21502151// - Count of revertable properties.2152String num_revertable_str;2153int num_revertable_width = 0;21542155bool folded = (foldable || !checkbox_only) && !vbox->is_visible();2156if (folded && revertable_properties.size()) {2157int label_width = theme_cache.bold_font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, available, theme_cache.bold_font_size, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS).x;21582159// Can we fit the long version of the revertable count text?2160num_revertable_str = vformat(TTRN("(%d change)", "(%d changes)", revertable_properties.size()), revertable_properties.size());2161num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x;2162if (label_width + outer_margin + num_revertable_width > available) {2163// We'll have to use the short version.2164num_revertable_str = vformat("(%d)", revertable_properties.size());2165num_revertable_width = light_font->get_string_size(num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, TextServer::JUSTIFICATION_NONE).x;2166}21672168float text_offset_y = light_font->get_ascent(light_font_size) + (header_height - light_font->get_height(light_font_size)) / 2;2169Point2 text_offset = Point2(margin_end, text_offset_y).round();2170if (!rtl) {2171text_offset.x = get_size().width - (text_offset.x + num_revertable_width);2172}2173draw_string(light_font, text_offset, num_revertable_str, HORIZONTAL_ALIGNMENT_LEFT, -1.0f, light_font_size, theme_cache.font_disabled_color, TextServer::JUSTIFICATION_NONE);2174margin_end += num_revertable_width + outer_margin;2175available -= num_revertable_width + outer_margin;2176}21772178// - Label.2179float text_offset_y = font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2;2180Point2 text_offset = Point2(margin_start, text_offset_y).round();2181if (rtl) {2182text_offset.x = margin_end;2183}2184if (object->has_method("_get_property_warning") && !String(object->call("_get_property_warning", related_enable_property)).is_empty()) {2185font_color = theme_cache.warning_color;2186}2187const Color string_color = header_hover ? theme_cache.font_hover_mono_color : font_color;2188HorizontalAlignment text_align = rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT;2189draw_string(font, text_offset, label, text_align, available, font_size, string_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);2190}21912192// Draw section indentation.2193if (section_indent_style.is_valid() && section_indent > 0) {2194Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height));2195if (rtl) {2196indent_rect.position.x = get_size().width - section_indent + section_indent_style->get_margin(SIDE_RIGHT);2197} else {2198indent_rect.position.x = section_indent_style->get_margin(SIDE_LEFT);2199}2200draw_style_box(section_indent_style, indent_rect);2201}2202} break;22032204case NOTIFICATION_DRAG_BEGIN: {2205dropping_for_unfold = true;2206} break;22072208case NOTIFICATION_DRAG_END: {2209dropping_for_unfold = false;2210} break;22112212case NOTIFICATION_MOUSE_ENTER: {2213if (dropping_for_unfold) {2214dropping_unfold_timer->start();2215}2216queue_redraw();2217} break;22182219case NOTIFICATION_MOUSE_EXIT_SELF:2220case NOTIFICATION_MOUSE_EXIT: {2221if (dropping_for_unfold) {2222dropping_unfold_timer->stop();2223}22242225if (header_hover || check_hover || keying_hover) {2226header_hover = false;2227check_hover = false;2228keying_hover = false;2229queue_redraw();2230}2231} break;22322233case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {2234if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) {2235dropping_unfold_timer->set_wait_time(EDITOR_GET("interface/editor/dragging_hover_wait_seconds"));2236}2237} break;2238}2239}22402241Size2 EditorInspectorSection::get_minimum_size() const {2242Size2 ms;2243for (int i = 0; i < get_child_count(); i++) {2244Control *c = as_sortable_control(get_child(i));2245if (!c) {2246continue;2247}2248Size2 minsize = c->get_combined_minimum_size();2249ms = ms.max(minsize);2250}22512252if (theme_cache.font.is_valid()) {2253ms.height += theme_cache.font->get_height(theme_cache.font_size) + theme_cache.vertical_separation;2254ms.width += theme_cache.inspector_margin;2255}22562257if (indent_depth > 0 && theme_cache.indent_size > 0) {2258ms.width += indent_depth * theme_cache.indent_size;2259}2260if (indent_depth > 0 && theme_cache.indent_box.is_valid()) {2261ms.width += theme_cache.indent_box->get_margin(SIDE_LEFT) + theme_cache.indent_box->get_margin(SIDE_RIGHT);2262}22632264return ms;2265}22662267EditorInspector *EditorInspectorSection::_get_parent_inspector() const {2268Node *parent = get_parent();2269while (parent) {2270EditorInspector *ei = Object::cast_to<EditorInspector>(parent);2271if (ei) {2272return ei;2273}2274parent = parent->get_parent();2275}2276return nullptr;2277}22782279Control *EditorInspectorSection::make_custom_tooltip(const String &p_text) const {2280if (!checkable) {2281return Container::make_custom_tooltip(p_text);2282}22832284String symbol;2285String prologue;22862287if (object->has_method("_get_property_warning")) {2288const String custom_warning = object->call("_get_property_warning", related_enable_property);2289if (!custom_warning.is_empty()) {2290prologue = "[b][color=" + theme_cache.warning_color.to_html(false) + "]" + custom_warning + "[/color][/b]";2291}2292}22932294symbol = p_text;22952296const EditorInspector *inspector = _get_parent_inspector();2297if (inspector) {2298const String custom_description = inspector->get_custom_property_description(p_text);2299if (!custom_description.is_empty()) {2300if (!prologue.is_empty()) {2301prologue += '\n';2302}2303prologue += custom_description;2304}2305}23062307if (!symbol.is_empty() || !prologue.is_empty()) {2308return EditorHelpBitTooltip::make_tooltip(const_cast<EditorInspectorSection *>(this), symbol, prologue);2309}23102311return nullptr;2312}23132314void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth, int p_level) {2315section = p_section;2316label = p_label;2317object = p_object;2318bg_color = p_bg_color;2319foldable = p_foldable;2320indent_depth = p_indent_depth;2321level = p_level;23222323_test_unfold();23242325if (foldable) {2326if (object->editor_is_section_unfolded(section)) {2327vbox->show();2328} else {2329vbox->hide();2330}2331}2332}23332334void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) {2335ERR_FAIL_COND(p_event.is_null());2336bool can_click_unfold = vbox->get_child_count(false) != 0 && !(!checkbox_only && checkable && !checked);23372338Ref<InputEventMouseMotion> mm = p_event;2339if (mm.is_valid()) {2340Vector2 mpos = mm->get_position();23412342bool new_check_hover = check_rect.has_point(mpos);2343if (new_check_hover != check_hover) {2344check_hover = new_check_hover;2345queue_redraw();2346}23472348bool new_keying_hover = keying_rect.has_point(mpos);2349if (new_keying_hover != keying_hover) {2350keying_hover = new_keying_hover;2351queue_redraw();2352}23532354bool new_header_hover = foldable && can_click_unfold && (get_local_mouse_position().y < _get_header_height());2355if (new_header_hover != header_hover) {2356header_hover = new_header_hover;2357queue_redraw();2358}2359}23602361Ref<InputEventKey> k = p_event;2362if (k.is_valid() && k->is_pressed()) {2363if (foldable && can_click_unfold && k->is_action("ui_accept", true)) {2364accept_event();23652366bool should_unfold = !object->editor_is_section_unfolded(section);2367if (should_unfold) {2368unfold();2369} else {2370fold();2371}2372}2373}23742375Ref<InputEventMouseButton> mb = p_event;2376if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {2377Vector2 pos = mb->get_position();23782379if (object->editor_is_section_unfolded(section)) {2380int header_height = _get_header_height();23812382if (pos.y >= header_height) {2383return;2384}2385}23862387accept_event();23882389if (checkable && check_rect.has_point(pos)) {2390checked = !checked;2391emit_signal(SNAME("section_toggled_by_user"), related_enable_property, checked);2392if (checked) {2393unfold();2394} else if (!checkbox_only) {2395vbox->hide();2396}2397} else if (keying && keying_rect.has_point(pos)) {2398emit_signal(SNAME("property_keyed"), related_enable_property, false);2399} else if (foldable) {2400bool should_unfold = can_click_unfold && !object->editor_is_section_unfolded(section);2401if (should_unfold) {2402unfold();2403} else {2404fold();2405}2406}2407} else if (mb.is_valid() && !mb->is_pressed()) {2408queue_redraw();2409}2410}24112412String EditorInspectorSection::get_section() const {2413return section;2414}24152416VBoxContainer *EditorInspectorSection::get_vbox() {2417return vbox;2418}24192420void EditorInspectorSection::_accessibility_action_collapse(const Variant &p_data) {2421fold();2422}24232424void EditorInspectorSection::_accessibility_action_expand(const Variant &p_data) {2425unfold();2426}24272428void EditorInspectorSection::unfold() {2429if ((!foldable && !checkable) || (!checkbox_only && checkable && !checked)) {2430return;2431}24322433_test_unfold();24342435if (foldable) {2436object->editor_set_section_unfold(section, true);2437}24382439vbox->show();2440queue_redraw();2441}24422443void EditorInspectorSection::fold() {2444if (!foldable || !vbox_added) {2445return;2446}24472448object->editor_set_section_unfold(section, false);2449vbox->hide();2450queue_redraw();2451}24522453void EditorInspectorSection::set_bg_color(const Color &p_bg_color) {2454bg_color = p_bg_color;2455queue_redraw();2456}24572458void EditorInspectorSection::set_keying(bool p_keying) {2459if (keying == (checkable && p_keying)) {2460return;2461}24622463keying = checkable && p_keying;2464if (checkable) {2465queue_redraw();2466}2467}24682469void EditorInspectorSection::reset_timer() {2470if (dropping_for_unfold && !dropping_unfold_timer->is_stopped()) {2471dropping_unfold_timer->start();2472}2473}24742475void EditorInspectorSection::set_checkable(const String &p_related_check_property, bool p_checkbox_only, bool p_checked) {2476if (checkable == !p_related_check_property.is_empty()) {2477return;2478}24792480checkbox_only = p_checkbox_only;2481checkable = !p_related_check_property.is_empty();2482checked = p_checked;2483related_enable_property = p_related_check_property;24842485if (InspectorDock::get_singleton()) {2486if (checkable) {2487InspectorDock::get_inspector_singleton()->connect("property_edited", callable_mp(this, &EditorInspectorSection::_property_edited));2488} else {2489InspectorDock::get_inspector_singleton()->disconnect("property_edited", callable_mp(this, &EditorInspectorSection::_property_edited));2490}2491}24922493if (!checkbox_only && checkable && !checked) {2494vbox->hide();2495}24962497queue_redraw();2498}24992500void EditorInspectorSection::set_checked(bool p_checked) {2501if (checked == p_checked) {2502return;2503}25042505checked = p_checked;2506if (!checkbox_only && checkable && !checked) {2507vbox->hide();2508} else if (!checkbox_only) {2509unfold();2510}25112512queue_redraw();2513}25142515bool EditorInspectorSection::has_revertable_properties() const {2516return !revertable_properties.is_empty();2517}25182519void EditorInspectorSection::property_can_revert_changed(const String &p_path, bool p_can_revert) {2520bool had_revertable_properties = has_revertable_properties();2521if (p_can_revert) {2522revertable_properties.insert(p_path);2523} else {2524revertable_properties.erase(p_path);2525}2526if (has_revertable_properties() != had_revertable_properties) {2527queue_redraw();2528}2529}25302531void EditorInspectorSection::_property_edited(const String &p_property) {2532if (!related_enable_property.is_empty() && p_property == related_enable_property) {2533update_property();2534}2535}25362537void EditorInspectorSection::update_property() {2538if (!checkable) {2539return;2540}25412542bool valid = false;2543Variant value_checked = object->get(related_enable_property, &valid);25442545if (valid) {2546set_checked(value_checked.operator bool());2547}2548}25492550void EditorInspectorSection::_bind_methods() {2551ClassDB::bind_method(D_METHOD("setup", "section", "label", "object", "bg_color", "foldable", "indent_depth", "level"), &EditorInspectorSection::setup, DEFVAL(0), DEFVAL(1));2552ClassDB::bind_method(D_METHOD("get_vbox"), &EditorInspectorSection::get_vbox);2553ClassDB::bind_method(D_METHOD("unfold"), &EditorInspectorSection::unfold);2554ClassDB::bind_method(D_METHOD("fold"), &EditorInspectorSection::fold);25552556ADD_SIGNAL(MethodInfo("section_toggled_by_user", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "value")));2557ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING_NAME, "property")));2558}25592560EditorInspectorSection::EditorInspectorSection() {2561set_focus_mode(FOCUS_ACCESSIBILITY);25622563vbox = memnew(VBoxContainer);2564vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));25652566dropping_unfold_timer = memnew(Timer);2567dropping_unfold_timer->set_wait_time(EDITOR_GET("interface/editor/dragging_hover_wait_seconds"));2568dropping_unfold_timer->set_one_shot(true);2569add_child(dropping_unfold_timer);2570dropping_unfold_timer->connect("timeout", callable_mp(this, &EditorInspectorSection::unfold));2571}25722573EditorInspectorSection::~EditorInspectorSection() {2574if (!vbox_added) {2575memdelete(vbox);2576}25772578if (checkable && InspectorDock::get_singleton()) {2579InspectorDock::get_inspector_singleton()->disconnect("property_edited", callable_mp(this, &EditorInspectorSection::_property_edited));2580}2581}25822583////////////////////////////////////////////////2584////////////////////////////////////////////////25852586int EditorInspectorArray::_get_array_count() {2587if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {2588List<PropertyInfo> object_property_list;2589object->get_property_list(&object_property_list);2590return _extract_properties_as_array(object_property_list).size();2591} else if (mode == MODE_USE_COUNT_PROPERTY) {2592bool valid;2593int count_val = object->get(count_property, &valid);2594ERR_FAIL_COND_V_MSG(!valid, 0, vformat("%s is not a valid property to be used as array count.", count_property));2595return count_val;2596}2597return 0;2598}25992600void EditorInspectorArray::_add_button_pressed() {2601_move_element(-1, -1);2602}26032604void EditorInspectorArray::_paginator_page_changed(int p_page) {2605emit_signal("page_change_request", p_page);2606}26072608void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) {2609switch (p_id) {2610case OPTION_MOVE_UP:2611if (popup_array_index_pressed > 0) {2612_move_element(popup_array_index_pressed, popup_array_index_pressed - 1);2613}2614break;2615case OPTION_MOVE_DOWN:2616if (popup_array_index_pressed < count - 1) {2617_move_element(popup_array_index_pressed, popup_array_index_pressed + 2);2618}2619break;2620case OPTION_NEW_BEFORE:2621_move_element(-1, popup_array_index_pressed);2622break;2623case OPTION_NEW_AFTER:2624_move_element(-1, popup_array_index_pressed + 1);2625break;2626case OPTION_REMOVE:2627_move_element(popup_array_index_pressed, -1);2628break;2629case OPTION_CLEAR_ARRAY:2630_clear_array();2631break;2632case OPTION_RESIZE_ARRAY:2633new_size_spin_box->set_value(count);2634resize_dialog->get_ok_button()->set_disabled(true);2635resize_dialog->popup_centered(Size2(250, 0) * EDSCALE);2636new_size_spin_box->get_line_edit()->grab_focus();2637new_size_spin_box->get_line_edit()->select_all();2638break;2639default:2640break;2641}2642}26432644void EditorInspectorArray::_control_dropping_draw() {2645int drop_position = _drop_position();26462647if (dropping && drop_position >= 0) {2648Vector2 from;2649Vector2 to;2650if (drop_position < elements_vbox->get_child_count()) {2651Transform2D xform = Object::cast_to<Control>(elements_vbox->get_child(drop_position))->get_transform();2652from = xform.xform(Vector2());2653to = xform.xform(Vector2(elements_vbox->get_size().x, 0));2654} else {2655Control *child = Object::cast_to<Control>(elements_vbox->get_child(drop_position - 1));2656Transform2D xform = child->get_transform();2657from = xform.xform(Vector2(0, child->get_size().y));2658to = xform.xform(Vector2(elements_vbox->get_size().x, child->get_size().y));2659}2660Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));2661control_dropping->draw_line(from, to, color, 2);2662}2663}26642665void EditorInspectorArray::_vbox_visibility_changed() {2666control_dropping->set_visible(vbox->is_visible_in_tree());2667}26682669void EditorInspectorArray::_panel_draw(int p_index) {2670ERR_FAIL_INDEX(p_index, (int)array_elements.size());26712672Ref<StyleBox> style = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles));2673if (style.is_null()) {2674return;2675}2676if (array_elements[p_index].panel->has_focus(true)) {2677array_elements[p_index].panel->draw_style_box(style, Rect2(Vector2(), array_elements[p_index].panel->get_size()));2678}2679}26802681void EditorInspectorArray::_panel_gui_focus(int p_index) {2682array_elements[p_index].panel->queue_redraw();2683selected = p_index;2684}26852686void EditorInspectorArray::_panel_gui_unfocus(int p_index) {2687array_elements[p_index].panel->queue_redraw();2688if (selected == p_index) {2689selected = -1;2690}2691}26922693void EditorInspectorArray::_panel_gui_input(Ref<InputEvent> p_event, int p_index) {2694ERR_FAIL_INDEX(p_index, (int)array_elements.size());26952696if (read_only) {2697return;2698}26992700Ref<InputEventKey> key_ref = p_event;2701if (key_ref.is_valid()) {2702const InputEventKey &key = **key_ref;27032704if (array_elements[p_index].panel->has_focus() && key.is_pressed() && key.get_keycode() == Key::KEY_DELETE) {2705_move_element(begin_array_index + p_index, -1);2706array_elements[p_index].panel->accept_event();2707}2708}27092710Ref<InputEventMouseButton> mb = p_event;2711if (mb.is_valid()) {2712if (movable && mb->get_button_index() == MouseButton::RIGHT) {2713array_elements[p_index].panel->accept_event();2714popup_array_index_pressed = begin_array_index + p_index;2715rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);2716rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);2717rmb_popup->set_position(array_elements[p_index].panel->get_screen_position() + mb->get_position());2718rmb_popup->reset_size();2719rmb_popup->popup();2720}2721}2722}27232724void EditorInspectorArray::show_menu(int p_index, const Vector2 &p_offset) {2725popup_array_index_pressed = begin_array_index + p_index;2726rmb_popup->set_item_disabled(OPTION_MOVE_UP, popup_array_index_pressed == 0);2727rmb_popup->set_item_disabled(OPTION_MOVE_DOWN, popup_array_index_pressed == count - 1);2728rmb_popup->set_position(get_screen_position() + p_offset);2729rmb_popup->reset_size();2730rmb_popup->popup();2731}27322733void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) {2734String action_name;2735if (p_element_index < 0) {2736action_name = vformat(TTR("Add element to property array with prefix %s."), array_element_prefix);2737} else if (p_to_pos < 0) {2738action_name = vformat(TTR("Remove element %d from property array with prefix %s."), p_element_index, array_element_prefix);2739} else {2740action_name = vformat(TTR("Move element %d to position %d in property array with prefix %s."), p_element_index, p_to_pos, array_element_prefix);2741}2742EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2743undo_redo->create_action(action_name);2744if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {2745// Call the function.2746Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());2747if (move_function.is_valid()) {2748move_function.call(undo_redo, object, array_element_prefix, p_element_index, p_to_pos);2749} else {2750WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));2751}2752} else if (mode == MODE_USE_COUNT_PROPERTY) {2753ERR_FAIL_COND(p_to_pos < -1 || p_to_pos > count);27542755if (!swap_method.is_empty()) {2756ERR_FAIL_COND(!object->has_method(swap_method));27572758// Swap method was provided, use it.2759if (p_element_index < 0) {2760// Add an element at position2761undo_redo->add_do_property(object, count_property, count + 1);2762if (p_to_pos >= 0) {2763for (int i = count; i > p_to_pos; i--) {2764undo_redo->add_do_method(object, swap_method, i, i - 1);2765}2766for (int i = p_to_pos; i < count; i++) {2767undo_redo->add_undo_method(object, swap_method, i, i + 1);2768}2769}2770undo_redo->add_undo_property(object, count_property, count);27712772} else if (p_to_pos < 0) {2773if (count > 0) {2774// Remove element at position2775undo_redo->add_undo_property(object, count_property, count);27762777List<PropertyInfo> object_property_list;2778object->get_property_list(&object_property_list);27792780for (int i = p_element_index; i < count - 1; i++) {2781undo_redo->add_do_method(object, swap_method, i, i + 1);2782}27832784for (int i = count; i > p_element_index; i--) {2785undo_redo->add_undo_method(object, swap_method, i, i - 1);2786}27872788String erase_prefix = String(array_element_prefix) + itos(p_element_index);27892790for (const PropertyInfo &E : object_property_list) {2791if (E.name.begins_with(erase_prefix)) {2792undo_redo->add_undo_property(object, E.name, object->get(E.name));2793}2794}27952796undo_redo->add_do_property(object, count_property, count - 1);2797}2798} else {2799if (p_to_pos > p_element_index) {2800p_to_pos--;2801}28022803if (p_to_pos < p_element_index) {2804for (int i = p_element_index; i > p_to_pos; i--) {2805undo_redo->add_do_method(object, swap_method, i, i - 1);2806}2807for (int i = p_to_pos; i < p_element_index; i++) {2808undo_redo->add_undo_method(object, swap_method, i, i + 1);2809}2810} else if (p_to_pos > p_element_index) {2811for (int i = p_element_index; i < p_to_pos; i++) {2812undo_redo->add_do_method(object, swap_method, i, i + 1);2813}28142815for (int i = p_to_pos; i > p_element_index; i--) {2816undo_redo->add_undo_method(object, swap_method, i, i - 1);2817}2818}2819}2820} else {2821// Use standard properties.2822List<PropertyInfo> object_property_list;2823object->get_property_list(&object_property_list);28242825Array properties_as_array = _extract_properties_as_array(object_property_list);2826properties_as_array.resize(count);28272828// For undoing things2829undo_redo->add_undo_property(object, count_property, properties_as_array.size());2830for (int i = 0; i < (int)properties_as_array.size(); i++) {2831Dictionary d = Dictionary(properties_as_array[i]);2832for (const KeyValue<Variant, Variant> &kv : d) {2833undo_redo->add_undo_property(object, vformat(kv.key, i), kv.value);2834}2835}28362837if (p_element_index < 0) {2838// Add an element.2839properties_as_array.insert(p_to_pos < 0 ? properties_as_array.size() : p_to_pos, Dictionary());2840} else if (p_to_pos < 0) {2841// Delete the element.2842properties_as_array.remove_at(p_element_index);2843} else {2844// Move the element.2845properties_as_array.insert(p_to_pos, properties_as_array[p_element_index].duplicate());2846properties_as_array.remove_at(p_to_pos < p_element_index ? p_element_index + 1 : p_element_index);2847}28482849// Change the array size then set the properties.2850undo_redo->add_do_property(object, count_property, properties_as_array.size());2851for (int i = 0; i < (int)properties_as_array.size(); i++) {2852Dictionary d = properties_as_array[i];2853for (const KeyValue<Variant, Variant> &kv : d) {2854undo_redo->add_do_property(object, vformat(kv.key, i), kv.value);2855}2856}2857}2858}2859undo_redo->commit_action();28602861// Handle page change and update counts.2862if (p_element_index < 0) {2863int added_index = p_to_pos < 0 ? count : p_to_pos;2864emit_signal(SNAME("page_change_request"), added_index / page_length);2865count += 1;2866} else if (p_to_pos < 0) {2867count -= 1;2868if (page == max_page && (MAX(0, count - 1) / page_length != max_page)) {2869emit_signal(SNAME("page_change_request"), max_page - 1);2870}2871} else if (p_to_pos == begin_array_index - 1) {2872emit_signal(SNAME("page_change_request"), page - 1);2873} else if (p_to_pos > end_array_index) {2874emit_signal(SNAME("page_change_request"), page + 1);2875}2876begin_array_index = page * page_length;2877end_array_index = MIN(count, (page + 1) * page_length);2878max_page = MAX(0, count - 1) / page_length;2879}28802881void EditorInspectorArray::_clear_array() {2882EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2883undo_redo->create_action(vformat(TTR("Clear Property Array with Prefix %s"), array_element_prefix));2884if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {2885for (int i = count - 1; i >= 0; i--) {2886// Call the function.2887Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());2888if (move_function.is_valid()) {2889move_function.call(undo_redo, object, array_element_prefix, i, -1);2890} else {2891WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));2892}2893}2894} else if (mode == MODE_USE_COUNT_PROPERTY) {2895List<PropertyInfo> object_property_list;2896object->get_property_list(&object_property_list);28972898Array properties_as_array = _extract_properties_as_array(object_property_list);2899properties_as_array.resize(count);29002901// For undoing things2902undo_redo->add_undo_property(object, count_property, count);2903for (int i = 0; i < (int)properties_as_array.size(); i++) {2904Dictionary d = Dictionary(properties_as_array[i]);2905for (const KeyValue<Variant, Variant> &kv : d) {2906undo_redo->add_undo_property(object, vformat(kv.key, i), kv.value);2907}2908}29092910// Change the array size then set the properties.2911undo_redo->add_do_property(object, count_property, 0);2912}2913undo_redo->commit_action();29142915// Handle page change and update counts.2916emit_signal(SNAME("page_change_request"), 0);2917count = 0;2918begin_array_index = 0;2919end_array_index = 0;2920max_page = 0;2921}29222923void EditorInspectorArray::_resize_array(int p_size) {2924ERR_FAIL_COND(p_size < 0);2925if (p_size == count) {2926return;2927}29282929EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();2930undo_redo->create_action(vformat(TTR("Resize Property Array with Prefix %s"), array_element_prefix));2931if (p_size > count) {2932if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {2933for (int i = count; i < p_size; i++) {2934// Call the function.2935Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());2936if (move_function.is_valid()) {2937move_function.call(undo_redo, object, array_element_prefix, -1, -1);2938} else {2939WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));2940}2941}2942} else if (mode == MODE_USE_COUNT_PROPERTY) {2943undo_redo->add_undo_property(object, count_property, count);2944undo_redo->add_do_property(object, count_property, p_size);2945}2946} else {2947if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) {2948for (int i = count - 1; i > p_size - 1; i--) {2949// Call the function.2950Callable move_function = EditorNode::get_editor_data().get_move_array_element_function(object->get_class_name());2951if (move_function.is_valid()) {2952move_function.call(undo_redo, object, array_element_prefix, i, -1);2953} else {2954WARN_PRINT(vformat("Could not find a function to move arrays elements for class %s. Register a move element function using EditorData::add_move_array_element_function", object->get_class_name()));2955}2956}2957} else if (mode == MODE_USE_COUNT_PROPERTY) {2958List<PropertyInfo> object_property_list;2959object->get_property_list(&object_property_list);29602961Array properties_as_array = _extract_properties_as_array(object_property_list);2962properties_as_array.resize(count);29632964// For undoing things2965undo_redo->add_undo_property(object, count_property, count);2966for (int i = count - 1; i > p_size - 1; i--) {2967Dictionary d = Dictionary(properties_as_array[i]);2968for (const KeyValue<Variant, Variant> &kv : d) {2969undo_redo->add_undo_property(object, vformat(kv.key, i), kv.value);2970}2971}29722973// Change the array size then set the properties.2974undo_redo->add_do_property(object, count_property, p_size);2975}2976}2977undo_redo->commit_action();29782979// Handle page change and update counts.2980emit_signal(SNAME("page_change_request"), 0);2981/*2982count = 0;2983begin_array_index = 0;2984end_array_index = 0;2985max_page = 0;2986*/2987}29882989Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo> &p_list) {2990Array output;29912992for (const PropertyInfo &pi : p_list) {2993if (!(pi.usage & PROPERTY_USAGE_EDITOR)) {2994continue;2995}29962997if (pi.name.begins_with(array_element_prefix)) {2998String str = pi.name.trim_prefix(array_element_prefix);29993000int to_char_index = 0;3001while (to_char_index < str.length()) {3002if (!is_digit(str[to_char_index])) {3003break;3004}3005to_char_index++;3006}3007if (to_char_index > 0) {3008int array_index = str.left(to_char_index).to_int();3009Error error = OK;3010if (array_index >= output.size()) {3011error = output.resize(array_index + 1);3012}3013if (error == OK) {3014String format_string = String(array_element_prefix) + "%d" + str.substr(to_char_index);3015Dictionary dict = output[array_index];3016dict[format_string] = object->get(pi.name);3017output[array_index] = dict;3018} else {3019WARN_PRINT(vformat("Array element %s has an index too high. Array allocation failed.", pi.name));3020}3021}3022}3023}3024return output;3025}30263027int EditorInspectorArray::_drop_position() const {3028for (int i = 0; i < (int)array_elements.size(); i++) {3029const ArrayElement &ae = array_elements[i];30303031Size2 size = ae.panel->get_size();3032Vector2 mp = ae.panel->get_local_mouse_position();30333034if (Rect2(Vector2(), size).has_point(mp)) {3035if (mp.y < size.y / 2) {3036return i;3037} else {3038return i + 1;3039}3040}3041}3042return -1;3043}30443045void EditorInspectorArray::_resize_dialog_confirmed() {3046if (int(new_size_spin_box->get_value()) == count) {3047return;3048}30493050resize_dialog->hide();3051_resize_array(int(new_size_spin_box->get_value()));3052}30533054void EditorInspectorArray::_new_size_spin_box_value_changed(float p_value) {3055resize_dialog->get_ok_button()->set_disabled(int(p_value) == count);3056}30573058void EditorInspectorArray::_new_size_spin_box_text_submitted(const String &p_text) {3059_resize_dialog_confirmed();3060}30613062void EditorInspectorArray::_setup() {3063// Setup counts.3064count = _get_array_count();3065begin_array_index = page * page_length;3066end_array_index = MIN(count, (page + 1) * page_length);3067max_page = MAX(0, count - 1) / page_length;3068array_elements.resize(MAX(0, end_array_index - begin_array_index));3069if (page < 0 || page > max_page) {3070WARN_PRINT(vformat("Invalid page number %d", page));3071page = CLAMP(page, 0, max_page);3072}30733074Ref<Font> numbers_font;3075int numbers_min_w = 0;3076bool unresizable = is_const || read_only;30773078if (numbered) {3079numbers_font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3080int digits_found = count;3081String test;3082while (digits_found) {3083test += "8";3084digits_found /= 10;3085}3086numbers_min_w = numbers_font->get_string_size(test).width;3087}30883089for (int i = 0; i < (int)array_elements.size(); i++) {3090ArrayElement &ae = array_elements[i];30913092// Panel and its hbox.3093ae.panel = memnew(ArrayPanelContainer);3094ae.panel->set_focus_mode(FOCUS_ALL);3095ae.panel->set_mouse_filter(MOUSE_FILTER_PASS);3096SET_DRAG_FORWARDING_GCD(ae.panel, EditorInspectorArray);30973098int element_position = begin_array_index + i;3099String ae_name = vformat(TTR("Element %d: %s%d*"), element_position, array_element_prefix, element_position);31003101ae.panel->set_meta("index", element_position);3102ae.panel->set_meta("name", ae_name);3103ae.panel->set_meta("element", this);3104ae.panel->set_tooltip_text(ae_name);3105ae.panel->connect(SceneStringName(focus_entered), callable_mp(this, &EditorInspectorArray::_panel_gui_focus).bind(i));3106ae.panel->connect(SceneStringName(focus_exited), callable_mp(this, &EditorInspectorArray::_panel_gui_unfocus).bind(i));3107ae.panel->connect(SceneStringName(draw), callable_mp(this, &EditorInspectorArray::_panel_draw).bind(i));3108ae.panel->connect(SceneStringName(gui_input), callable_mp(this, &EditorInspectorArray::_panel_gui_input).bind(i));3109ae.panel->add_theme_style_override(SceneStringName(panel), i % 2 ? odd_style : even_style);3110elements_vbox->add_child(ae.panel);31113112ae.margin = memnew(MarginContainer);3113ae.margin->set_mouse_filter(MOUSE_FILTER_PASS);3114if (is_inside_tree()) {3115Size2 min_size = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles))->get_minimum_size();3116ae.margin->begin_bulk_theme_override();3117ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);3118ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);3119ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);3120ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);3121ae.margin->end_bulk_theme_override();3122}3123ae.panel->add_child(ae.margin);31243125ae.hbox = memnew(HBoxContainer);3126ae.hbox->set_h_size_flags(SIZE_EXPAND_FILL);3127ae.hbox->set_v_size_flags(SIZE_EXPAND_FILL);3128ae.margin->add_child(ae.hbox);31293130// Move button.3131if (movable) {3132VBoxContainer *move_vbox = memnew(VBoxContainer);3133move_vbox->set_v_size_flags(SIZE_EXPAND_FILL);3134move_vbox->set_alignment(BoxContainer::ALIGNMENT_CENTER);3135ae.hbox->add_child(move_vbox);31363137if (element_position > 0) {3138ae.move_up = memnew(Button);3139ae.move_up->set_accessibility_name(TTRC("Move Up"));3140ae.move_up->set_button_icon(get_editor_theme_icon(SNAME("MoveUp")));3141ae.move_up->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position - 1));3142move_vbox->add_child(ae.move_up);3143}31443145ae.move_texture_rect = memnew(TextureRect);3146ae.move_texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);3147ae.move_texture_rect->set_default_cursor_shape(Control::CURSOR_MOVE);31483149if (is_inside_tree()) {3150ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar")));3151}3152move_vbox->add_child(ae.move_texture_rect);31533154if (element_position < count - 1) {3155ae.move_down = memnew(Button);3156ae.move_down->set_accessibility_name(TTRC("Move Down"));3157ae.move_down->set_button_icon(get_editor_theme_icon(SNAME("MoveDown")));3158ae.move_down->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_move_element).bind(element_position, element_position + 2));3159move_vbox->add_child(ae.move_down);3160}3161}31623163if (numbered) {3164ae.number = memnew(Label);3165ae.number->add_theme_font_override(SceneStringName(font), numbers_font);3166ae.number->set_custom_minimum_size(Size2(numbers_min_w, 0));3167ae.number->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);3168ae.number->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);3169ae.number->set_text(itos(element_position));3170ae.hbox->add_child(ae.number);3171}31723173// Right vbox.3174ae.vbox = memnew(VBoxContainer);3175ae.vbox->set_h_size_flags(SIZE_EXPAND_FILL);3176ae.vbox->set_v_size_flags(SIZE_EXPAND_FILL);3177ae.hbox->add_child(ae.vbox);31783179if (!unresizable) {3180ae.erase = memnew(Button);3181ae.erase->set_accessibility_name(TTRC("Remove"));3182ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove")));3183ae.erase->set_v_size_flags(SIZE_SHRINK_CENTER);3184ae.erase->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_remove_item).bind(element_position));3185ae.hbox->add_child(ae.erase);3186}3187}31883189// Hide/show the add button.3190add_button->set_visible(page == max_page && !unresizable);31913192// Add paginator if there's more than 1 page.3193if (max_page > 0) {3194EditorPaginator *paginator = memnew(EditorPaginator);3195paginator->update(page, max_page);3196paginator->connect("page_changed", callable_mp(this, &EditorInspectorArray::_paginator_page_changed));3197vbox->add_child(paginator);3198}3199}32003201void EditorInspectorArray::_remove_item(int p_index) {3202_move_element(p_index, -1);3203}32043205Variant EditorInspectorArray::get_drag_data_fw(const Point2 &p_point, Control *p_from) {3206if (!movable) {3207return Variant();3208}3209int index = p_from->get_meta("index");3210Dictionary dict;3211dict["type"] = "property_array_element";3212dict["property_array_prefix"] = array_element_prefix;3213dict["index"] = index;32143215return dict;3216}32173218void EditorInspectorArray::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {3219Dictionary dict = p_data;32203221int to_drop = dict["index"];3222int drop_position = (p_point == Vector2(Math::INF, Math::INF)) ? selected : _drop_position();3223if (drop_position < 0) {3224return;3225}3226_move_element(to_drop, begin_array_index + drop_position);3227}32283229bool EditorInspectorArray::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {3230if (!movable || read_only) {3231return false;3232}3233// First, update drawing.3234control_dropping->queue_redraw();32353236if (p_data.get_type() != Variant::DICTIONARY) {3237return false;3238}3239Dictionary dict = p_data;3240int drop_position = (p_point == Vector2(Math::INF, Math::INF)) ? selected : _drop_position();3241if (!dict.has("type") || dict["type"] != "property_array_element" || String(dict["property_array_prefix"]) != array_element_prefix || drop_position < 0) {3242return false;3243}32443245// Check in dropping at the given index does indeed move the item.3246int moved_array_index = (int)dict["index"];3247int drop_array_index = begin_array_index + drop_position;32483249return drop_array_index != moved_array_index && drop_array_index - 1 != moved_array_index;3250}32513252void ArrayPanelContainer::_accessibility_action_menu(const Variant &p_data) {3253EditorInspectorArray *el = Object::cast_to<EditorInspectorArray>(get_meta("element"));3254if (el) {3255int index = get_meta("index");3256el->show_menu(index, Vector2());3257}3258}32593260void ArrayPanelContainer::_notification(int p_what) {3261switch (p_what) {3262case NOTIFICATION_ACCESSIBILITY_UPDATE: {3263RID ae = get_accessibility_element();3264ERR_FAIL_COND(ae.is_null());32653266DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_BUTTON);32673268DisplayServer::get_singleton()->accessibility_update_set_name(ae, get_meta("text"));3269DisplayServer::get_singleton()->accessibility_update_set_value(ae, get_meta("text"));32703271DisplayServer::get_singleton()->accessibility_update_set_popup_type(ae, DisplayServer::AccessibilityPopupType::POPUP_MENU);3272DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SHOW_CONTEXT_MENU, callable_mp(this, &ArrayPanelContainer::_accessibility_action_menu));3273} break;3274}3275}32763277ArrayPanelContainer::ArrayPanelContainer() {3278set_focus_mode(FOCUS_ACCESSIBILITY);3279}32803281void EditorInspectorArray::_notification(int p_what) {3282switch (p_what) {3283case NOTIFICATION_ACCESSIBILITY_UPDATE: {3284RID ae = get_accessibility_element();3285ERR_FAIL_COND(ae.is_null());32863287DisplayServer::get_singleton()->accessibility_update_set_name(ae, vformat(TTR("Array: %s"), get_label()));3288DisplayServer::get_singleton()->accessibility_update_set_value(ae, vformat(TTR("Array: %s"), get_label()));3289} break;32903291case NOTIFICATION_THEME_CHANGED: {3292Color color = get_theme_color(SNAME("bg"), SNAME("EditorInspectorArray"));3293odd_style->set_bg_color(color.darkened(-0.1));3294even_style->set_bg_color(color.darkened(0.1));32953296for (ArrayElement &ae : array_elements) {3297if (ae.move_texture_rect) {3298ae.move_texture_rect->set_texture(get_editor_theme_icon(SNAME("TripleBar")));3299}3300if (ae.move_up) {3301ae.move_up->set_button_icon(get_editor_theme_icon(SNAME("MoveUp")));3302}3303if (ae.move_down) {3304ae.move_down->set_button_icon(get_editor_theme_icon(SNAME("MoveDown")));3305}3306Size2 min_size = get_theme_stylebox(SNAME("Focus"), EditorStringName(EditorStyles))->get_minimum_size();3307ae.margin->begin_bulk_theme_override();3308ae.margin->add_theme_constant_override("margin_left", min_size.x / 2);3309ae.margin->add_theme_constant_override("margin_top", min_size.y / 2);3310ae.margin->add_theme_constant_override("margin_right", min_size.x / 2);3311ae.margin->add_theme_constant_override("margin_bottom", min_size.y / 2);3312ae.margin->end_bulk_theme_override();33133314if (ae.erase) {3315ae.erase->set_button_icon(get_editor_theme_icon(SNAME("Remove")));3316}3317}3318} break;33193320case NOTIFICATION_DRAG_BEGIN: {3321Dictionary dict = get_viewport()->gui_get_drag_data();3322if (dict.has("type") && dict["type"] == "property_array_element" && String(dict["property_array_prefix"]) == array_element_prefix) {3323dropping = true;3324control_dropping->queue_redraw();3325}3326} break;33273328case NOTIFICATION_DRAG_END: {3329if (dropping) {3330dropping = false;3331control_dropping->queue_redraw();3332}3333} break;3334}3335}33363337void EditorInspectorArray::_bind_methods() {3338ADD_SIGNAL(MethodInfo("page_change_request"));3339}33403341void EditorInspectorArray::setup_with_move_element_function(Object *p_object, const String &p_label, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text) {3342count_property = "";3343mode = MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION;3344array_element_prefix = p_array_element_prefix;3345page = p_page;3346movable = p_movable;3347is_const = p_is_const;3348page_length = p_page_length;3349numbered = p_numbered;33503351EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable, 0);33523353_setup();3354}33553356void EditorInspectorArray::setup_with_count_property(Object *p_object, const String &p_label, const StringName &p_count_property, const StringName &p_array_element_prefix, int p_page, const Color &p_bg_color, bool p_foldable, bool p_movable, bool p_is_const, bool p_numbered, int p_page_length, const String &p_add_item_text, const String &p_swap_method) {3357count_property = p_count_property;3358mode = MODE_USE_COUNT_PROPERTY;3359array_element_prefix = p_array_element_prefix;3360page = p_page;3361movable = p_movable;3362is_const = p_is_const;3363page_length = p_page_length;3364numbered = p_numbered;3365swap_method = p_swap_method;33663367add_button->set_text(p_add_item_text);3368EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable, 0);33693370_setup();3371}33723373VBoxContainer *EditorInspectorArray::get_vbox(int p_index) {3374if (p_index >= begin_array_index && p_index < end_array_index) {3375return array_elements[p_index - begin_array_index].vbox;3376} else if (p_index < 0) {3377return vbox;3378} else {3379return nullptr;3380}3381}33823383EditorInspectorArray::EditorInspectorArray(bool p_read_only) {3384read_only = p_read_only;33853386odd_style.instantiate();3387even_style.instantiate();33883389rmb_popup = memnew(PopupMenu);3390rmb_popup->set_accessibility_name(TTRC("Move"));3391rmb_popup->add_item(TTR("Move Up"), OPTION_MOVE_UP);3392rmb_popup->add_item(TTR("Move Down"), OPTION_MOVE_DOWN);3393rmb_popup->add_separator();3394rmb_popup->add_item(TTR("Insert New Before"), OPTION_NEW_BEFORE);3395rmb_popup->add_item(TTR("Insert New After"), OPTION_NEW_AFTER);3396rmb_popup->add_separator();3397rmb_popup->add_item(TTR("Remove"), OPTION_REMOVE);3398rmb_popup->add_separator();3399rmb_popup->add_item(TTR("Clear Array"), OPTION_CLEAR_ARRAY);3400rmb_popup->add_item(TTR("Resize Array..."), OPTION_RESIZE_ARRAY);3401rmb_popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorArray::_rmb_popup_id_pressed));3402add_child(rmb_popup);34033404elements_vbox = memnew(VBoxContainer);3405elements_vbox->add_theme_constant_override("separation", 0);3406vbox->add_child(elements_vbox);34073408add_button = memnew(EditorInspectorActionButton(TTRC("Add Element"), SNAME("Add")));3409add_button->connect(SceneStringName(pressed), callable_mp(this, &EditorInspectorArray::_add_button_pressed));3410add_button->set_disabled(read_only);3411vbox->add_child(add_button);34123413control_dropping = memnew(Control);3414control_dropping->connect(SceneStringName(draw), callable_mp(this, &EditorInspectorArray::_control_dropping_draw));3415control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);3416add_child(control_dropping);34173418resize_dialog = memnew(AcceptDialog);3419resize_dialog->set_title(TTRC("Resize Array"));3420resize_dialog->add_cancel_button();3421resize_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorInspectorArray::_resize_dialog_confirmed));3422add_child(resize_dialog);34233424VBoxContainer *resize_dialog_vbox = memnew(VBoxContainer);3425resize_dialog->add_child(resize_dialog_vbox);34263427new_size_spin_box = memnew(SpinBox);3428new_size_spin_box->set_accessibility_name(TTRC("New Size:"));3429new_size_spin_box->set_max(16384);3430new_size_spin_box->connect(SceneStringName(value_changed), callable_mp(this, &EditorInspectorArray::_new_size_spin_box_value_changed));3431new_size_spin_box->get_line_edit()->connect(SceneStringName(text_submitted), callable_mp(this, &EditorInspectorArray::_new_size_spin_box_text_submitted));3432new_size_spin_box->set_editable(!read_only);3433resize_dialog_vbox->add_margin_child(TTRC("New Size:"), new_size_spin_box);34343435vbox->connect(SceneStringName(visibility_changed), callable_mp(this, &EditorInspectorArray::_vbox_visibility_changed));3436}34373438////////////////////////////////////////////////3439////////////////////////////////////////////////34403441void EditorPaginator::_first_page_button_pressed() {3442emit_signal("page_changed", 0);3443}34443445void EditorPaginator::_prev_page_button_pressed() {3446emit_signal("page_changed", MAX(0, page - 1));3447}34483449void EditorPaginator::_page_line_edit_text_submitted(const String &p_text) {3450if (p_text.is_valid_int()) {3451int new_page = p_text.to_int() - 1;3452new_page = MIN(MAX(0, new_page), max_page);3453page_line_edit->set_text(Variant(new_page));3454emit_signal("page_changed", new_page);3455} else {3456page_line_edit->set_text(Variant(page));3457}3458}34593460void EditorPaginator::_next_page_button_pressed() {3461emit_signal("page_changed", MIN(max_page, page + 1));3462}34633464void EditorPaginator::_last_page_button_pressed() {3465emit_signal("page_changed", max_page);3466}34673468void EditorPaginator::update(int p_page, int p_max_page) {3469page = p_page;3470max_page = p_max_page;34713472// Update buttons.3473first_page_button->set_disabled(page == 0);3474prev_page_button->set_disabled(page == 0);3475next_page_button->set_disabled(page == max_page);3476last_page_button->set_disabled(page == max_page);34773478// Update page number and page count.3479page_line_edit->set_text(vformat("%d", page + 1));3480page_count_label->set_text(vformat("/ %d", max_page + 1));3481}34823483void EditorPaginator::_notification(int p_what) {3484switch (p_what) {3485case NOTIFICATION_THEME_CHANGED: {3486first_page_button->set_button_icon(get_editor_theme_icon(SNAME("PageFirst")));3487prev_page_button->set_button_icon(get_editor_theme_icon(SNAME("PagePrevious")));3488next_page_button->set_button_icon(get_editor_theme_icon(SNAME("PageNext")));3489last_page_button->set_button_icon(get_editor_theme_icon(SNAME("PageLast")));3490} break;3491}3492}34933494void EditorPaginator::_bind_methods() {3495ADD_SIGNAL(MethodInfo("page_changed", PropertyInfo(Variant::INT, "page")));3496}34973498EditorPaginator::EditorPaginator() {3499set_h_size_flags(SIZE_EXPAND_FILL);3500set_alignment(ALIGNMENT_CENTER);35013502first_page_button = memnew(Button);3503first_page_button->set_accessibility_name(TTRC("First Page"));3504first_page_button->set_flat(true);3505first_page_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPaginator::_first_page_button_pressed));3506add_child(first_page_button);35073508prev_page_button = memnew(Button);3509prev_page_button->set_accessibility_name(TTRC("Previous Page"));3510prev_page_button->set_flat(true);3511prev_page_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPaginator::_prev_page_button_pressed));3512add_child(prev_page_button);35133514page_line_edit = memnew(LineEdit);3515page_line_edit->set_accessibility_name(TTRC("Page Number"));3516page_line_edit->connect(SceneStringName(text_submitted), callable_mp(this, &EditorPaginator::_page_line_edit_text_submitted));3517page_line_edit->add_theme_constant_override("minimum_character_width", 2);3518add_child(page_line_edit);35193520page_count_label = memnew(Label);3521page_count_label->set_focus_mode(FOCUS_ACCESSIBILITY);3522add_child(page_count_label);35233524next_page_button = memnew(Button);3525prev_page_button->set_accessibility_name(TTRC("Next Page"));3526next_page_button->set_flat(true);3527next_page_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPaginator::_next_page_button_pressed));3528add_child(next_page_button);35293530last_page_button = memnew(Button);3531last_page_button->set_accessibility_name(TTRC("Last Page"));3532last_page_button->set_flat(true);3533last_page_button->connect(SceneStringName(pressed), callable_mp(this, &EditorPaginator::_last_page_button_pressed));3534add_child(last_page_button);3535}35363537////////////////////////////////////////////////3538////////////////////////////////////////////////35393540Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS];3541int EditorInspector::inspector_plugin_count = 0;35423543EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {3544for (int i = inspector_plugin_count - 1; i >= 0; i--) {3545if (!inspector_plugins[i]->can_handle(p_object)) {3546continue;3547}35483549inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);3550if (inspector_plugins[i]->added_editors.size()) {3551for (List<EditorInspectorPlugin::AddedEditor>::Element *E = inspector_plugins[i]->added_editors.front()->next(); E; E = E->next()) { //only keep first one3552memdelete(E->get().property_editor);3553}35543555EditorProperty *prop = Object::cast_to<EditorProperty>(inspector_plugins[i]->added_editors.front()->get().property_editor);3556if (prop) {3557inspector_plugins[i]->added_editors.clear();3558return prop;3559} else {3560memdelete(inspector_plugins[i]->added_editors.front()->get().property_editor);3561inspector_plugins[i]->added_editors.clear();3562}3563}3564}3565return nullptr;3566}35673568void EditorInspector::initialize_section_theme(EditorInspectorSection::ThemeCache &p_cache, Control *p_control) {3569EditorInspector *parent_inspector = _get_control_parent_inspector(p_control);3570if (parent_inspector && parent_inspector != p_control) {3571p_cache = parent_inspector->section_theme_cache;3572return;3573}35743575p_cache.horizontal_separation = p_control->get_theme_constant(SNAME("h_separation"), SNAME("EditorInspectorSection"));3576p_cache.vertical_separation = p_control->get_theme_constant(SNAME("separation"), SNAME("EditorPropertyContainer"));3577p_cache.inspector_margin = p_control->get_theme_constant(SNAME("inspector_margin"), EditorStringName(Editor));3578p_cache.indent_size = p_control->get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection"));3579p_cache.key_padding_size = int(EDITOR_GET("interface/theme/base_spacing")) * 2;35803581p_cache.warning_color = p_control->get_theme_color(SNAME("warning_color"), EditorStringName(Editor));3582p_cache.prop_subsection = p_control->get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));3583p_cache.font_color = p_control->get_theme_color(SceneStringName(font_color), EditorStringName(Editor));3584p_cache.font_disabled_color = p_control->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor));3585p_cache.font_hover_color = p_control->get_theme_color(SNAME("font_hover_color"), EditorStringName(Editor));3586p_cache.font_pressed_color = p_control->get_theme_color(SNAME("font_pressed_color"), EditorStringName(Editor));3587p_cache.font_hover_pressed_color = p_control->get_theme_color(SNAME("font_hover_pressed_color"), EditorStringName(Editor));3588p_cache.font_hover_mono_color = p_control->get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.925);35893590p_cache.font = p_control->get_theme_font(SceneStringName(font), SNAME("Tree"));3591p_cache.font_size = p_control->get_theme_font_size(SceneStringName(font_size), SNAME("Tree"));3592p_cache.bold_font = p_control->get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3593p_cache.bold_font_size = p_control->get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));3594p_cache.light_font = p_control->get_theme_font(SNAME("main"), EditorStringName(EditorFonts));3595p_cache.light_font_size = p_control->get_theme_font_size(SNAME("main_size"), EditorStringName(EditorFonts));35963597p_cache.arrow = p_control->get_theme_icon(SNAME("arrow"), SNAME("Tree"));3598p_cache.arrow_collapsed = p_control->get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree"));3599p_cache.arrow_collapsed_mirrored = p_control->get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree"));3600p_cache.icon_gui_checked = p_control->get_editor_theme_icon(SNAME("GuiChecked"));3601p_cache.icon_gui_unchecked = p_control->get_editor_theme_icon(SNAME("GuiUnchecked"));3602p_cache.icon_gui_animation_key = p_control->get_editor_theme_icon(SNAME("Key"));36033604p_cache.indent_box = p_control->get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection"));3605p_cache.key_hover = p_control->get_theme_stylebox(SceneStringName(hover), SceneStringName(FlatButton));3606}36073608void EditorInspector::initialize_category_theme(EditorInspectorCategory::ThemeCache &p_cache, Control *p_control) {3609EditorInspector *parent_inspector = _get_control_parent_inspector(p_control);3610if (parent_inspector && parent_inspector != p_control) {3611p_cache = parent_inspector->category_theme_cache;3612return;3613}36143615p_cache.horizontal_separation = p_control->get_theme_constant(SNAME("h_separation"), SNAME("Tree"));3616p_cache.vertical_separation = p_control->get_theme_constant(SNAME("separation"), SNAME("EditorPropertyContainer"));3617p_cache.class_icon_size = p_control->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));36183619p_cache.font_color = p_control->get_theme_color(SceneStringName(font_color), SNAME("Tree"));36203621p_cache.bold_font = p_control->get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));3622p_cache.bold_font_size = p_control->get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));36233624p_cache.icon_favorites = p_control->get_editor_theme_icon(SNAME("Favorites"));3625p_cache.icon_unfavorite = p_control->get_editor_theme_icon(SNAME("Unfavorite"));3626p_cache.icon_help = p_control->get_editor_theme_icon(SNAME("Help"));36273628p_cache.background = p_control->get_theme_stylebox(SNAME("bg"), SNAME("EditorInspectorCategory"));3629}36303631void EditorInspector::initialize_property_theme(EditorProperty::ThemeCache &p_cache, Control *p_control) {3632EditorInspector *parent_inspector = _get_control_parent_inspector(p_control);3633if (parent_inspector && parent_inspector != p_control) {3634p_cache = parent_inspector->property_theme_cache;3635return;3636}36373638p_cache.font = p_control->get_theme_font(SceneStringName(font), SNAME("Tree"));36393640p_cache.background = p_control->get_theme_stylebox(SNAME("bg"), SNAME("EditorProperty"));3641p_cache.background_selected = p_control->get_theme_stylebox(SNAME("bg_selected"), SNAME("EditorProperty"));3642p_cache.child_background = p_control->get_theme_stylebox(SNAME("child_bg"), SNAME("EditorProperty"));3643p_cache.hover = p_control->get_theme_stylebox(SceneStringName(hover), SceneStringName(FlatButton));36443645p_cache.key_icon = p_control->get_editor_theme_icon(SNAME("Key"));3646p_cache.key_next_icon = p_control->get_editor_theme_icon(SNAME("KeyNext"));3647p_cache.delete_icon = p_control->get_editor_theme_icon(SNAME("Close"));3648p_cache.checked_icon = p_control->get_editor_theme_icon(SNAME("GuiChecked"));3649p_cache.unchecked_icon = p_control->get_editor_theme_icon(SNAME("GuiUnchecked"));3650p_cache.revert_icon = p_control->get_editor_theme_icon(SNAME("ReloadSmall"));3651p_cache.pin_icon = p_control->get_editor_theme_icon(SNAME("Pin"));3652p_cache.copy_icon = p_control->get_editor_theme_icon(SNAME("ActionCopy"));3653p_cache.copy_node_path_icon = p_control->get_editor_theme_icon(SNAME("CopyNodePath"));3654p_cache.paste_icon = p_control->get_editor_theme_icon(SNAME("ActionPaste"));3655p_cache.unfavorite_icon = p_control->get_editor_theme_icon(SNAME("Unfavorite"));3656p_cache.favorite_icon = p_control->get_editor_theme_icon(SNAME("Favorites"));3657p_cache.override_icon = p_control->get_editor_theme_icon(SNAME("Override"));3658p_cache.remove_icon = p_control->get_editor_theme_icon(SNAME("Remove"));3659p_cache.help_icon = p_control->get_editor_theme_icon(SNAME("Help"));36603661p_cache.font_size = p_control->get_theme_font_size(SceneStringName(font_size), SNAME("Tree"));3662p_cache.font_offset = p_control->get_theme_constant(SNAME("font_offset"), SNAME("EditorProperty"));3663p_cache.horizontal_separation = p_control->get_theme_constant(SNAME("h_separation"), SNAME("Tree"));3664p_cache.vertical_separation = p_control->get_theme_constant(SNAME("separation"), SNAME("EditorPropertyContainer"));3665p_cache.padding = int(EDITOR_GET("interface/theme/base_spacing")) * 2;3666p_cache.inspector_property_height = p_control->get_theme_constant(SNAME("inspector_property_height"), EditorStringName(Editor));36673668p_cache.property_color = p_control->get_theme_color(SNAME("property_color"), SNAME("EditorProperty"));3669p_cache.readonly_property_color = p_control->get_theme_color(SNAME("readonly_color"), SNAME("EditorProperty"));3670p_cache.warning_color = p_control->get_theme_color(SNAME("warning_color"), SNAME("EditorProperty"));3671p_cache.readonly_warning_color = p_control->get_theme_color(SNAME("readonly_warning_color"), SNAME("EditorProperty"));3672p_cache.property_color_x = p_control->get_theme_color(SNAME("property_color_x"), EditorStringName(Editor));3673p_cache.property_color_y = p_control->get_theme_color(SNAME("property_color_y"), EditorStringName(Editor));3674p_cache.property_color_z = p_control->get_theme_color(SNAME("property_color_z"), EditorStringName(Editor));3675p_cache.property_color_w = p_control->get_theme_color(SNAME("property_color_w"), EditorStringName(Editor));3676p_cache.sub_inspector_property_color = p_control->get_theme_color(SNAME("sub_inspector_property_color"), EditorStringName(EditorStyles));36773678if (p_control == parent_inspector) {3679// Only initialize for the inspector, as stand-alone properties won't need it.3680for (int i = 0; i <= 16; i++) {3681p_cache.sub_inspector_background[i] = p_control->get_theme_stylebox("sub_inspector_property_bg" + itos(i), EditorStringName(EditorStyles));3682}3683}3684}36853686void EditorInspector::add_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {3687ERR_FAIL_COND(inspector_plugin_count == MAX_PLUGINS);36883689for (int i = 0; i < inspector_plugin_count; i++) {3690if (inspector_plugins[i] == p_plugin) {3691return; //already exists3692}3693}3694inspector_plugins[inspector_plugin_count++] = p_plugin;3695}36963697void EditorInspector::remove_inspector_plugin(const Ref<EditorInspectorPlugin> &p_plugin) {3698int idx = -1;3699for (int i = 0; i < inspector_plugin_count; i++) {3700if (inspector_plugins[i] == p_plugin) {3701idx = i;3702break;3703}3704}37053706ERR_FAIL_COND_MSG(idx == -1, "Trying to remove nonexistent inspector plugin.");3707for (int i = idx; i < inspector_plugin_count - 1; i++) {3708inspector_plugins[i] = inspector_plugins[i + 1];3709}3710inspector_plugins[inspector_plugin_count - 1] = Ref<EditorInspectorPlugin>();37113712inspector_plugin_count--;3713}37143715void EditorInspector::cleanup_plugins() {3716for (int i = 0; i < inspector_plugin_count; i++) {3717inspector_plugins[i].unref();3718}3719inspector_plugin_count = 0;3720}37213722bool EditorInspector::is_main_editor_inspector() const {3723return InspectorDock::get_singleton() && InspectorDock::get_inspector_singleton() == this;3724}37253726String EditorInspector::get_selected_path() const {3727return property_selected;3728}37293730void EditorInspector::_parse_added_editors(VBoxContainer *p_current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> p_plugin) {3731for (const EditorInspectorPlugin::AddedEditor &F : p_plugin->added_editors) {3732EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);37333734if (ep && !F.properties.is_empty() && current_favorites.has(F.properties[0])) {3735ep->favorited = true;3736favorites_vbox->add_child(F.property_editor);3737} else {3738p_current_vbox->add_child(F.property_editor);3739}37403741if (ep) {3742ep->object = object;3743ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed).bind(false));3744ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));3745ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);3746ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));3747ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));3748ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);3749ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));3750ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));3751ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));3752ep->connect("resource_selected", callable_mp(get_root_inspector(), &EditorInspector::_resource_selected), CONNECT_DEFERRED);3753ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);37543755if (F.properties.size()) {3756if (F.properties.size() == 1) {3757//since it's one, associate:3758ep->property = F.properties[0];3759ep->property_path = property_prefix + F.properties[0];3760ep->property_usage = 0;3761}37623763if (!F.label.is_empty()) {3764ep->set_label(F.label);3765}37663767for (int i = 0; i < F.properties.size(); i++) {3768String prop = F.properties[i];37693770if (!editor_property_map.has(prop)) {3771editor_property_map[prop] = List<EditorProperty *>();3772}3773editor_property_map[prop].push_back(ep);3774}3775}37763777Node *section_search = p_section;3778while (section_search) {3779EditorInspectorSection *section = Object::cast_to<EditorInspectorSection>(section_search);3780if (section) {3781ep->connect("property_can_revert_changed", callable_mp(section, &EditorInspectorSection::property_can_revert_changed));3782}3783section_search = section_search->get_parent();3784if (Object::cast_to<EditorInspector>(section_search)) {3785// Skip sub-resource inspectors.3786break;3787}3788}37893790ep->set_read_only(read_only);3791ep->update_property();3792ep->_update_flags();3793ep->update_editor_property_status();3794ep->set_deletable(deletable_properties);3795ep->update_cache();3796}3797}3798p_plugin->added_editors.clear();3799}38003801bool EditorInspector::_is_property_disabled_by_feature_profile(const StringName &p_property) {3802if (!EditorFeatureProfileManager::get_singleton()) {3803return false;3804}38053806Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();3807if (profile.is_null()) {3808return false;3809}38103811StringName class_name = object->get_class();38123813while (class_name != StringName()) {3814if (profile->is_class_property_disabled(class_name, p_property)) {3815return true;3816}3817if (profile->is_class_disabled(class_name)) {3818//won't see properties of a disabled class3819return true;3820}3821class_name = ClassDB::get_parent_class(class_name);3822}38233824return false;3825}38263827void EditorInspector::_add_section_in_tree(EditorInspectorSection *p_section, VBoxContainer *p_current_vbox) {3828// Place adjacent sections in their own vbox with a theme-specific separation.3829VBoxContainer *container = nullptr;3830if (p_current_vbox->get_child_count() > 0) {3831Node *last_child = p_current_vbox->get_child(-1);3832container = Object::cast_to<VBoxContainer>(last_child);3833}3834if (!container) {3835container = memnew(VBoxContainer);3836container->set_theme_type_variation(SNAME("EditorSectionContainer"));3837p_current_vbox->add_child(container);3838}3839container->add_child(p_section);3840}38413842void EditorInspector::update_tree() {3843if (!object) {3844return;3845}38463847bool root_inspector_was_following_focus = get_root_inspector()->is_following_focus();3848if (root_inspector_was_following_focus) {3849// Temporarily disable focus following on the root inspector to avoid jumping while the inspector is updating.3850get_root_inspector()->set_follow_focus(false);3851}38523853// Store currently selected and focused elements to restore after the update.3854// TODO: Can be useful to store more context for the focusable, such as the caret position in LineEdit.3855StringName current_selected = property_selected;3856int current_focusable = -1;38573858if (property_focusable != -1) {3859// Check that focusable is actually focusable.3860bool restore_focus = false;3861Control *focused = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;3862if (focused) {3863Node *parent = focused->get_parent();3864while (parent) {3865EditorInspector *inspector = Object::cast_to<EditorInspector>(parent);3866if (inspector) {3867restore_focus = inspector == this; // May be owned by another inspector.3868break; // Exit after the first inspector is found, since there may be nested ones.3869}3870parent = parent->get_parent();3871}3872}38733874if (restore_focus) {3875current_focusable = property_focusable;3876}3877}38783879// The call here is for the edited object that has not changed, but the tree needs to be updated (for example, the object's property list has been modified).3880// Since the edited object has not changed, there is no need to hide the plugin at this time.3881_clear(false);38823883List<Ref<EditorInspectorPlugin>> valid_plugins;38843885for (int i = inspector_plugin_count - 1; i >= 0; i--) { //start by last, so lastly added can override newly added3886if (!inspector_plugins[i]->can_handle(object)) {3887continue;3888}3889valid_plugins.push_back(inspector_plugins[i]);3890}38913892// Decide if properties should be drawn with the warning color (yellow),3893// or if the whole object should be considered read-only.3894bool draw_warning = false;3895bool all_read_only = false;3896if (is_inside_tree() && EditorNode::get_singleton()) {3897if (object->has_method("_is_read_only")) {3898all_read_only = object->call("_is_read_only");3899}39003901Node *nod = Object::cast_to<Node>(object);3902Node *es = EditorNode::get_singleton()->get_edited_scene();3903if (nod && es != nod && nod->get_owner() != es) {3904// Draw in warning color edited nodes that are not in the currently edited scene,3905// as changes may be lost in the future.3906draw_warning = true;3907} else {3908if (!all_read_only) {3909Resource *res = Object::cast_to<Resource>(object);3910if (res) {3911all_read_only = EditorNode::get_singleton()->is_resource_read_only(res);3912}3913}3914}3915}39163917String filter = search_box ? search_box->get_text() : "";3918String group;3919String group_base;3920EditorInspectorSection *group_togglable_property = nullptr;3921String subgroup;3922String subgroup_base;3923EditorInspectorSection *subgroup_togglable_property = nullptr;3924int section_depth = 0;3925bool disable_favorite = false;3926VBoxContainer *category_vbox = nullptr;39273928List<PropertyInfo> plist;3929object->get_property_list(&plist, true);39303931HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;3932HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix;3933HashMap<String, HashMap<String, LocalVector<EditorProperty *>>> favorites_to_add;3934HashMap<String, EditorInspectorSection *> togglable_editor_inspector_sections;39353936const Color sscolor = theme_cache.prop_subsection;3937bool sub_inspectors_enabled = EDITOR_GET("interface/inspector/open_resources_in_current_inspector");39383939if (!valid_plugins.is_empty()) {3940for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {3941ped->parse_begin(object);3942_parse_added_editors(begin_vbox, nullptr, ped);3943}39443945// Show if any of the editors were added to the beginning.3946if (begin_vbox->get_child_count() > 0) {3947begin_vbox->show();3948}3949}39503951StringName doc_name;39523953// Get the lists of editors for properties.3954for (List<PropertyInfo>::Element *E_property = plist.front(); E_property; E_property = E_property->next()) {3955PropertyInfo &p = E_property->get();39563957if (p.usage & PROPERTY_USAGE_SUBGROUP) {3958// Setup a property sub-group.3959subgroup = p.name;3960subgroup_togglable_property = nullptr;39613962subgroup_base = p.hint_string.get_slicec(',', 0);3963if (p.hint_string.get_slice_count(",") > 1) {3964section_depth = p.hint_string.get_slicec(',', 1).to_int();3965} else {3966section_depth = 0;3967}39683969continue;39703971} else if (p.usage & PROPERTY_USAGE_GROUP) {3972// Setup a property group.3973group = p.name;3974group_togglable_property = nullptr;39753976group_base = p.hint_string.get_slicec(',', 0);3977if (p.hint_string.get_slice_count(",") > 1) {3978section_depth = p.hint_string.get_slicec(',', 1).to_int();3979} else {3980section_depth = 0;3981}39823983subgroup = "";3984subgroup_base = "";3985subgroup_togglable_property = nullptr;39863987continue;39883989} else if (p.usage & PROPERTY_USAGE_CATEGORY) {3990// Setup a property category.3991group = "";3992group_base = "";3993group_togglable_property = nullptr;3994subgroup = "";3995subgroup_base = "";3996subgroup_togglable_property = nullptr;3997section_depth = 0;3998disable_favorite = false;39994000vbox_per_path.clear();4001editor_inspector_array_per_prefix.clear();40024003// `hint_script` should contain a native class name or a script path.4004// Otherwise the category was probably added via `@export_category` or `_get_property_list()`.4005const bool is_custom_category = p.hint_string.is_empty();40064007// Iterate over remaining properties. If no properties in category, skip the category.4008List<PropertyInfo>::Element *N = E_property->next();4009bool valid = true;4010while (N) {4011if (!N->get().name.begins_with("metadata/_") && N->get().usage & PROPERTY_USAGE_EDITOR &&4012(!filter.is_empty() || !restrict_to_basic || (N->get().usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {4013break;4014}4015// Treat custom categories as second-level ones. Do not skip a normal category if it is followed by a custom one.4016// Skip in the other 3 cases (normal -> normal, custom -> custom, custom -> normal).4017if ((N->get().usage & PROPERTY_USAGE_CATEGORY) && (is_custom_category || !N->get().hint_string.is_empty())) {4018valid = false;4019break;4020}4021N = N->next();4022}4023if (!valid) {4024continue; // Empty, ignore it.4025}40264027String category_tooltip;40284029// Do not add an icon, do not change the current class (`doc_name`) for custom categories.4030if (is_custom_category) {4031category_tooltip = p.name;4032} else {4033doc_name = p.name;40344035// Use category's owner script to update some of its information.4036if (!EditorNode::get_editor_data().is_type_recognized(p.name) && ResourceLoader::exists(p.hint_string, "Script")) {4037Ref<Script> scr = ResourceLoader::load(p.hint_string, "Script");4038if (scr.is_valid()) {4039doc_name = scr->get_doc_class_name();40404041// Property favorites aren't compatible with built-in scripts.4042if (scr->is_built_in()) {4043disable_favorite = true;4044}4045}4046}40474048if (use_doc_hints) {4049// `|` separators used in `EditorHelpBit`.4050category_tooltip = "class|" + doc_name + "|";4051}4052}40534054if ((is_custom_category && !show_custom_categories) || (!is_custom_category && !show_standard_categories)) {4055continue;4056}40574058// Hide the "MultiNodeEdit" category for MultiNodeEdit.4059if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") {4060continue;4061}40624063// Create an EditorInspectorCategory and add it to the inspector.4064EditorInspectorCategory *category = memnew(EditorInspectorCategory);4065category->set_property_info(p);4066main_vbox->add_child(category);4067category_vbox = nullptr; // Reset.40684069// Set the category info.4070category->set_tooltip_text(category_tooltip);4071if (!is_custom_category) {4072category->set_doc_class_name(doc_name);4073}40744075// Add editors at the start of a category.4076for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {4077ped->parse_category(object, p.name);4078_parse_added_editors(main_vbox, nullptr, ped);4079}40804081continue;40824083} else if (p.name.begins_with("metadata/_") || !(p.usage & PROPERTY_USAGE_EDITOR) || _is_property_disabled_by_feature_profile(p.name) ||4084(filter.is_empty() && restrict_to_basic && !(p.usage & PROPERTY_USAGE_EDITOR_BASIC_SETTING))) {4085// Ignore properties that are not supposed to be in the inspector.4086continue;4087}40884089if (p.name == "script") {4090// Script should go into its own category.4091category_vbox = nullptr;4092}40934094if (p.usage & PROPERTY_USAGE_HIGH_END_GFX && RS::get_singleton()->is_low_end()) {4095// Do not show this property in low end gfx.4096continue;4097}40984099if (p.name == "script" && (hide_script || bool(object->call("_hide_script_from_inspector")))) {4100// Hide script variables from inspector if required.4101continue;4102}41034104if (p.name.begins_with("metadata/") && bool(object->call(SNAME("_hide_metadata_from_inspector")))) {4105// Hide metadata from inspector if required.4106continue;4107}41084109// Get the path for property.4110String path = p.name;41114112// First check if we have an array that fits the prefix.4113String array_prefix = "";4114int array_index = -1;4115for (KeyValue<String, EditorInspectorArray *> &E : editor_inspector_array_per_prefix) {4116if (p.name.begins_with(E.key) && E.key.length() > array_prefix.length()) {4117array_prefix = E.key;4118}4119}41204121if (!array_prefix.is_empty()) {4122// If we have an array element, find the according index in array.4123String str = p.name.trim_prefix(array_prefix);4124int to_char_index = 0;4125while (to_char_index < str.length()) {4126if (!is_digit(str[to_char_index])) {4127break;4128}4129to_char_index++;4130}4131if (to_char_index > 0) {4132array_index = str.left(to_char_index).to_int();4133} else {4134array_prefix = "";4135}4136}41374138// Don't allow to favorite array items.4139if (!disable_favorite) {4140disable_favorite = !array_prefix.is_empty();4141}41424143if (!array_prefix.is_empty()) {4144path = path.trim_prefix(array_prefix);4145int char_index = path.find_char('/');4146if (char_index >= 0) {4147path = path.right(-char_index - 1);4148} else {4149path = vformat(TTR("Element %s"), array_index);4150}4151} else {4152// Check if we exit or not a subgroup. If there is a prefix, remove it from the property label string.4153if (!subgroup.is_empty() && !subgroup_base.is_empty()) {4154if (path.begins_with(subgroup_base)) {4155path = path.trim_prefix(subgroup_base);4156} else if (subgroup_base.begins_with(path)) {4157// Keep it, this is used pretty often.4158} else {4159subgroup = ""; // The prefix changed, we are no longer in the subgroup.4160subgroup_togglable_property = nullptr;4161}4162}41634164// Check if we exit or not a group. If there is a prefix, remove it from the property label string.4165if (!group.is_empty() && !group_base.is_empty() && subgroup.is_empty()) {4166if (path.begins_with(group_base)) {4167path = path.trim_prefix(group_base);4168} else if (group_base.begins_with(path)) {4169// Keep it, this is used pretty often.4170} else {4171group = ""; // The prefix changed, we are no longer in the group.4172group_togglable_property = nullptr;4173subgroup = "";4174subgroup_togglable_property = nullptr;4175}4176}41774178// Add the group and subgroup to the path.4179if (!subgroup.is_empty()) {4180path = subgroup + "/" + path;4181}4182if (!group.is_empty()) {4183path = group + "/" + path;4184}4185}41864187// Get the property label's string.4188String name_override = (path.contains_char('/')) ? path.substr(path.rfind_char('/') + 1) : path;4189String feature_tag;4190{4191const int dot = name_override.find_char('.');4192if (dot != -1) {4193feature_tag = name_override.substr(dot);4194name_override = name_override.substr(0, dot);4195}4196}4197name_override = name_override.uri_decode();41984199// Don't localize script variables.4200EditorPropertyNameProcessor::Style name_style = property_name_style;4201if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {4202name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED;4203}4204const String property_label_string = EditorPropertyNameProcessor::get_singleton()->process_name(name_override, name_style, p.name, doc_name) + feature_tag;42054206// Remove the property from the path.4207int idx = path.rfind_char('/');4208if (idx > -1) {4209path = path.left(idx);4210} else {4211path = "";4212}42134214// Ignore properties that do not fit the filter.4215bool sub_inspector_use_filter = false;4216if (use_filter && !filter.is_empty()) {4217const String property_path = property_prefix + (path.is_empty() ? "" : path + "/") + name_override;4218if (!_property_path_matches(property_path, filter, property_name_style)) {4219if (!sub_inspectors_enabled || p.hint != PROPERTY_HINT_RESOURCE_TYPE) {4220continue;4221}42224223Ref<Resource> res = object->get(p.name);4224if (res.is_null()) {4225continue;4226}42274228// Check if the sub-resource has any properties that match the filter.4229if (!_resource_properties_matches(res, filter)) {4230continue;4231}42324233sub_inspector_use_filter = true;4234}4235}42364237// Recreate the category vbox if it was reset.4238if (category_vbox == nullptr) {4239category_vbox = memnew(VBoxContainer);4240category_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));4241category_vbox->hide();4242main_vbox->add_child(category_vbox);4243}42444245// Find the correct section/vbox to add the property editor to.4246VBoxContainer *root_vbox = array_prefix.is_empty() ? category_vbox : editor_inspector_array_per_prefix[array_prefix]->get_vbox(array_index);4247if (!root_vbox) {4248continue;4249}4250category_vbox->show();42514252if (!vbox_per_path.has(root_vbox)) {4253vbox_per_path[root_vbox] = HashMap<String, VBoxContainer *>();4254vbox_per_path[root_vbox][""] = root_vbox;4255}42564257VBoxContainer *current_vbox = root_vbox;4258String acc_path = "";4259int level = 1;42604261Vector<String> components = path.split("/");4262for (int i = 0; i < components.size(); i++) {4263const String &component = components[i];4264acc_path += (i > 0) ? "/" + component : component;42654266if (!vbox_per_path[root_vbox].has(acc_path)) {4267// If the section does not exists, create it.4268EditorInspectorSection *section = memnew(EditorInspectorSection);4269get_root_inspector()->get_v_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(section, &EditorInspectorSection::reset_timer).unbind(1));4270_add_section_in_tree(section, current_vbox);4271sections.push_back(section);42724273String label;4274String tooltip;42754276// Don't localize groups for script variables.4277EditorPropertyNameProcessor::Style section_name_style = property_name_style;4278if ((p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE) && section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {4279section_name_style = EditorPropertyNameProcessor::STYLE_CAPITALIZED;4280}42814282// Only process group label if this is not the group or subgroup.4283if ((i == 0 && component == group) || (i == 1 && component == subgroup)) {4284if (section_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {4285label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component);4286tooltip = component;4287} else {4288label = component;4289tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(component);4290}4291} else {4292label = EditorPropertyNameProcessor::get_singleton()->process_name(component, section_name_style, p.name, doc_name);4293tooltip = EditorPropertyNameProcessor::get_singleton()->process_name(component, EditorPropertyNameProcessor::get_tooltip_style(section_name_style), p.name, doc_name);4294}42954296Color c = sscolor;4297c.a /= level;4298section->setup(acc_path, label, object, c, use_folding, section_depth, level);4299section->set_tooltip_text(tooltip);43004301section->connect("section_toggled_by_user", callable_mp(this, &EditorInspector::_section_toggled_by_user));4302section->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));43034304// Add editors at the start of a group.4305for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {4306ped->parse_group(object, path);4307_parse_added_editors(section->get_vbox(), section, ped);4308}43094310vbox_per_path[root_vbox][acc_path] = section->get_vbox();4311}43124313current_vbox = vbox_per_path[root_vbox][acc_path];4314level = (MIN(level + 1, 4));4315}43164317// If we did not find a section to add the property to, add it to the category vbox instead (the category vbox handles margins correctly).4318if (current_vbox == main_vbox) {4319category_vbox->show();4320current_vbox = category_vbox;4321}43224323// Check if the property is an array counter, if so create a dedicated array editor for the array.4324if (p.usage & PROPERTY_USAGE_ARRAY) {4325EditorInspectorArray *editor_inspector_array = nullptr;4326StringName array_element_prefix;4327Color c = sscolor;4328c.a /= level;43294330Vector<String> class_name_components = String(p.class_name).split(",");43314332int page_size = 5;4333bool movable = true;4334bool is_const = false;4335bool numbered = false;4336bool foldable = use_folding;4337String add_button_text = TTRC("Add Element");4338String swap_method;4339for (int i = (p.type == Variant::NIL ? 1 : 2); i < class_name_components.size(); i++) {4340if (class_name_components[i].begins_with("page_size") && class_name_components[i].get_slice_count("=") == 2) {4341page_size = class_name_components[i].get_slicec('=', 1).to_int();4342} else if (class_name_components[i].begins_with("add_button_text") && class_name_components[i].get_slice_count("=") == 2) {4343add_button_text = class_name_components[i].get_slicec('=', 1).strip_edges();4344} else if (class_name_components[i] == "static") {4345movable = false;4346} else if (class_name_components[i] == "const") {4347is_const = true;4348} else if (class_name_components[i] == "numbered") {4349numbered = true;4350} else if (class_name_components[i] == "unfoldable") {4351foldable = false;4352} else if (class_name_components[i].begins_with("swap_method") && class_name_components[i].get_slice_count("=") == 2) {4353swap_method = class_name_components[i].get_slicec('=', 1).strip_edges();4354}4355}43564357if (p.type == Variant::NIL) {4358// Setup the array to use a method to create/move/delete elements.4359array_element_prefix = class_name_components[0];4360editor_inspector_array = memnew(EditorInspectorArray(all_read_only));43614362String array_label = path.contains_char('/') ? path.substr(path.rfind_char('/') + 1) : path;4363array_label = EditorPropertyNameProcessor::get_singleton()->process_name(property_label_string, property_name_style, p.name, doc_name);4364int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;4365editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding);4366editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix));4367} else if (p.type == Variant::INT) {4368// Setup the array to use the count property and built-in functions to create/move/delete elements.4369if (class_name_components.size() >= 2) {4370array_element_prefix = class_name_components[1];4371editor_inspector_array = memnew(EditorInspectorArray(all_read_only));4372int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;43734374editor_inspector_array->setup_with_count_property(object, class_name_components[0], p.name, array_element_prefix, page, c, foldable, movable, is_const, numbered, page_size, add_button_text, swap_method);4375editor_inspector_array->connect("page_change_request", callable_mp(this, &EditorInspector::_page_change_request).bind(array_element_prefix));4376}4377}43784379if (editor_inspector_array) {4380_add_section_in_tree(editor_inspector_array, current_vbox);4381editor_inspector_array_per_prefix[array_element_prefix] = editor_inspector_array;4382}43834384continue;4385}43864387// Checkable and checked properties.4388bool checkable = false;4389bool checked = false;4390if (p.usage & PROPERTY_USAGE_CHECKABLE) {4391checkable = true;4392checked = p.usage & PROPERTY_USAGE_CHECKED;4393}43944395bool property_read_only = (p.usage & PROPERTY_USAGE_READ_ONLY) || read_only;43964397// Mark properties that would require an editor restart (mostly when editing editor settings).4398if (p.usage & PROPERTY_USAGE_RESTART_IF_CHANGED) {4399restart_request_props.insert(p.name);4400}44014402String doc_path;4403String theme_item_name;4404String doc_tooltip_text;44054406// Build the doc hint, to use as tooltip.4407if (use_doc_hints) {4408StringName classname = doc_name;4409if (!object_class.is_empty()) {4410classname = object_class;4411} else if (Object::cast_to<MultiNodeEdit>(object)) {4412classname = Object::cast_to<MultiNodeEdit>(object)->get_edited_class_name();4413} else if (classname == "") {4414classname = object->get_class_name();4415Resource *res = Object::cast_to<Resource>(object);4416if (res && !res->get_script().is_null()) {4417// Grab the script of this resource to get the evaluated script class.4418Ref<Script> scr = res->get_script();4419if (scr.is_valid()) {4420Vector<DocData::ClassDoc> docs = scr->get_documentation();4421if (!docs.is_empty()) {4422// The documentation of a GDScript's main class is at the end of the array.4423// Hacky because this isn't necessarily always guaranteed.4424classname = docs[docs.size() - 1].name;4425}4426}4427}4428}44294430StringName propname = property_prefix + p.name;4431for (const KeyValue<String, String> &E : doc_property_class_remaps) {4432if (property_prefix.begins_with(E.key)) {4433propname = property_prefix.trim_prefix(E.key) + p.name;4434classname = E.value;4435break;4436}4437}44384439bool found = false;44404441// Small hack for theme_overrides. They are listed under Control, but come from another class.4442if (classname == "Control" && p.name.begins_with("theme_override_")) {4443classname = get_edited_object()->get_class();4444}44454446// Search for the doc path in the cache.4447HashMap<StringName, HashMap<StringName, DocCacheInfo>>::Iterator E = doc_cache.find(classname);4448if (E) {4449HashMap<StringName, DocCacheInfo>::Iterator F = E->value.find(propname);4450if (F) {4451found = true;4452doc_path = F->value.doc_path;4453theme_item_name = F->value.theme_item_name;4454}4455}44564457if (!found) {4458DocTools *dd = EditorHelp::get_doc_data();4459// Do not cache the doc path information of scripts.4460bool is_native_class = ClassDB::class_exists(classname);44614462HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);4463while (F) {4464Vector<String> slices = propname.operator String().split("/");4465// Check if it's a theme item first.4466if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {4467for (int i = 0; i < F->value.theme_properties.size(); i++) {4468String doc_path_current = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;4469if (F->value.theme_properties[i].name == slices[1]) {4470doc_path = doc_path_current;4471theme_item_name = F->value.theme_properties[i].name;4472}4473}4474} else {4475for (int i = 0; i < F->value.properties.size(); i++) {4476String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name;4477if (F->value.properties[i].name == propname.operator String()) {4478doc_path = doc_path_current;4479}4480}4481}44824483if (is_native_class) {4484DocCacheInfo cache_info;4485cache_info.doc_path = doc_path;4486cache_info.theme_item_name = theme_item_name;4487doc_cache[classname][propname] = cache_info;4488}44894490if (!doc_path.is_empty() || F->value.inherits.is_empty()) {4491break;4492}4493// Couldn't find the doc path in the class itself, try its super class.4494F = dd->class_list.find(F->value.inherits);4495}4496}44974498// `|` separators used in `EditorHelpBit`.4499if (theme_item_name.is_empty()) {4500if (p.name.contains("shader_parameter/")) {4501doc_tooltip_text = "property|" + p.class_name + "|" + property_prefix + propname;4502} else if (p.usage & PROPERTY_USAGE_INTERNAL) {4503doc_tooltip_text = "internal_property|" + classname + "|" + propname;4504} else {4505doc_tooltip_text = "property|" + classname + "|" + propname;4506}4507} else {4508doc_tooltip_text = "theme_item|" + classname + "|" + theme_item_name;4509}4510}45114512// Only used for boolean types. Makes the section header a checkable group and adds tooltips.4513if (p.hint == PROPERTY_HINT_GROUP_ENABLE) {4514if (p.type == Variant::BOOL && (p.name.begins_with(group_base) || p.name.begins_with(subgroup_base))) {4515EditorInspectorSection *last_created_section = Object::cast_to<EditorInspectorSection>(current_vbox->get_parent());4516if (last_created_section) {4517bool valid = false;4518Variant value_checked = object->get(p.name, &valid);45194520if (valid) {4521last_created_section->set_checkable(p.name, p.hint_string == "checkbox_only", value_checked.operator bool());4522last_created_section->set_keying(keying);45234524if (p.name.begins_with(group_base)) {4525group_togglable_property = last_created_section;4526} else {4527subgroup_togglable_property = last_created_section;4528}45294530if (use_doc_hints) {4531last_created_section->set_tooltip_text(doc_tooltip_text);4532}4533continue;4534}4535}4536} else {4537ERR_PRINT("PROPERTY_HINT_GROUP_ENABLE can only be used on boolean types and must have the same prefix as the group.");4538}4539}45404541Vector<EditorInspectorPlugin::AddedEditor> editors;4542Vector<EditorInspectorPlugin::AddedEditor> late_editors;45434544// Search for the inspector plugin that will handle the properties. Then add the correct property editor to it.4545for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {4546bool exclusive = ped->parse_property(object, p.type, p.name, p.hint, p.hint_string, p.usage, wide_editors);45474548for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {4549if (F.add_to_end) {4550late_editors.push_back(F);4551} else {4552editors.push_back(F);4553}4554}45554556ped->added_editors.clear();45574558if (exclusive) {4559break;4560}4561}45624563editors.append_array(late_editors);45644565const Node *node = Object::cast_to<Node>(object);45664567Vector<SceneState::PackState> sstack;4568if (node != nullptr) {4569const Node *es = EditorNode::get_singleton()->get_edited_scene();4570sstack = PropertyUtils::get_node_states_stack(node, es);4571}45724573for (int i = 0; i < editors.size(); i++) {4574EditorProperty *ep = Object::cast_to<EditorProperty>(editors[i].property_editor);4575const Vector<String> &properties = editors[i].properties;45764577if (ep) {4578// Set all this before the control gets the ENTER_TREE notification.4579ep->object = object;45804581if (properties.size()) {4582if (properties.size() == 1) {4583// Since it's one, associate:4584ep->property = properties[0];4585ep->property_path = property_prefix + properties[0];4586ep->property_usage = p.usage;4587// And set label?4588}4589if (!editors[i].label.is_empty()) {4590ep->set_label(editors[i].label);4591} else {4592// Use the existing one.4593ep->set_label(property_label_string);4594}45954596for (int j = 0; j < properties.size(); j++) {4597String prop = properties[j];45984599if (!editor_property_map.has(prop)) {4600editor_property_map[prop] = List<EditorProperty *>();4601}4602editor_property_map[prop].push_back(ep);4603}4604}46054606if (sub_inspector_use_filter) {4607EditorPropertyResource *epr = Object::cast_to<EditorPropertyResource>(ep);4608if (epr) {4609epr->set_use_filter(true);4610}4611}46124613if (p.name.begins_with("metadata/")) {4614Variant _default = Variant();4615if (node != nullptr) {4616_default = PropertyUtils::get_property_default_value(node, p.name, nullptr, &sstack, false, nullptr, nullptr);4617}4618ep->set_deletable(_default == Variant());4619} else {4620ep->set_deletable(deletable_properties);4621}46224623ep->set_draw_warning(draw_warning);4624ep->set_use_folding(use_folding);4625ep->set_favoritable(can_favorite && !disable_favorite && !ep->is_deletable());4626ep->set_checkable(checkable);4627ep->set_checked(checked);4628ep->set_keying(keying);4629ep->set_read_only(property_read_only || all_read_only);4630}46314632if (ep && ep->is_favoritable() && current_favorites.has(p.name)) {4633ep->favorited = true;4634favorites_to_add[group][subgroup].push_back(ep);46354636if (group_togglable_property) {4637togglable_editor_inspector_sections[group] = group_togglable_property;4638}4639if (subgroup_togglable_property) {4640togglable_editor_inspector_sections[group + "/" + subgroup] = subgroup_togglable_property;4641}4642} else {4643current_vbox->add_child(editors[i].property_editor);46444645if (ep) {4646Node *section_search = current_vbox->get_parent();4647while (section_search) {4648EditorInspectorSection *section = Object::cast_to<EditorInspectorSection>(section_search);4649if (section) {4650ep->connect("property_can_revert_changed", callable_mp(section, &EditorInspectorSection::property_can_revert_changed));4651}4652section_search = section_search->get_parent();4653if (Object::cast_to<EditorInspector>(section_search)) {4654// Skip sub-resource inspectors.4655break;4656}4657}4658}4659}46604661if (ep) {4662// Eventually, set other properties/signals after the property editor got added to the tree.4663bool update_all = (p.usage & PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED);4664ep->connect("property_changed", callable_mp(this, &EditorInspector::_property_changed).bind(update_all));4665ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));4666ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);4667ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));4668ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);4669ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));4670ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));4671ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));4672ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));4673ep->connect("resource_selected", callable_mp(get_root_inspector(), &EditorInspector::_resource_selected), CONNECT_DEFERRED);4674ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);46754676ep->set_tooltip_text(doc_tooltip_text);4677ep->has_doc_tooltip = use_doc_hints;4678ep->set_doc_path(doc_path);4679ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);46804681// If this property is favorited, it won't be in the tree yet. So don't do this setup right now.4682if (ep->is_inside_tree()) {4683ep->update_property();4684ep->_update_flags();4685ep->update_editor_property_status();4686ep->update_cache();46874688if (current_selected && ep->property == current_selected) {4689ep->select(current_focusable);4690}4691}4692}4693}4694}46954696if (!current_favorites.is_empty()) {4697favorites_section->show();46984699// Organize the favorited properties in their sections, to keep context and differentiate from others with the same name.4700bool is_localized = property_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED;4701for (const KeyValue<String, HashMap<String, LocalVector<EditorProperty *>>> &KV : favorites_to_add) {4702String section_name = KV.key;4703String label;4704String tooltip;4705VBoxContainer *parent_vbox = favorites_vbox;4706if (!section_name.is_empty()) {4707favorites_groups_vbox->show();47084709if (is_localized) {4710label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);4711tooltip = section_name;4712} else {4713label = section_name;4714tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);4715}47164717EditorInspectorSection *section = memnew(EditorInspectorSection);4718get_root_inspector()->get_v_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(section, &EditorInspectorSection::reset_timer).unbind(1));4719favorites_groups_vbox->add_child(section);4720parent_vbox = section->get_vbox();4721section->setup("", section_name, object, sscolor, false);4722section->set_tooltip_text(tooltip);47234724if (togglable_editor_inspector_sections.has(section_name)) {4725EditorInspectorSection *corresponding_section = togglable_editor_inspector_sections.get(section_name);47264727bool valid = false;4728Variant value_checked = object->get(corresponding_section->related_enable_property, &valid);4729if (valid) {4730section->section = corresponding_section->section;4731section->set_checkable(corresponding_section->related_enable_property, corresponding_section->checkbox_only, value_checked.operator bool());4732section->set_keying(keying);4733if (use_doc_hints) {4734section->set_tooltip_text(corresponding_section->get_tooltip_text());4735}47364737section->connect("section_toggled_by_user", callable_mp(this, &EditorInspector::_section_toggled_by_user));4738section->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));4739sections.push_back(section);4740}4741}4742}47434744for (const KeyValue<String, LocalVector<EditorProperty *>> &KV2 : KV.value) {4745section_name = KV2.key;4746VBoxContainer *vbox = parent_vbox;4747if (!section_name.is_empty()) {4748if (is_localized) {4749label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);4750tooltip = section_name;4751} else {4752label = section_name;4753tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);4754}47554756EditorInspectorSection *section = memnew(EditorInspectorSection);4757get_root_inspector()->get_v_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(section, &EditorInspectorSection::reset_timer).unbind(1));4758vbox->add_child(section);4759vbox = section->get_vbox();4760section->setup("", section_name, object, sscolor, false);4761section->set_tooltip_text(tooltip);47624763if (togglable_editor_inspector_sections.has(KV.key + "/" + section_name)) {4764EditorInspectorSection *corresponding_section = togglable_editor_inspector_sections.get(KV.key + "/" + section_name);47654766bool valid = false;4767Variant value_checked = object->get(corresponding_section->related_enable_property, &valid);4768if (valid) {4769section->section = corresponding_section->section;4770section->set_checkable(corresponding_section->related_enable_property, corresponding_section->checkbox_only, value_checked.operator bool());4771section->set_keying(keying);4772if (use_doc_hints) {4773section->set_tooltip_text(corresponding_section->get_tooltip_text());4774}47754776section->connect("section_toggled_by_user", callable_mp(this, &EditorInspector::_section_toggled_by_user));4777section->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));4778sections.push_back(section);4779}4780}4781}47824783for (EditorProperty *ep : KV2.value) {4784vbox->add_child(ep);47854786Node *section_search = vbox->get_parent();4787while (section_search) {4788EditorInspectorSection *section = Object::cast_to<EditorInspectorSection>(section_search);4789if (section) {4790ep->connect("property_can_revert_changed", callable_mp(section, &EditorInspectorSection::property_can_revert_changed));4791}4792section_search = section_search->get_parent();4793if (Object::cast_to<EditorInspector>(section_search)) {4794// Skip sub-resource inspectors.4795break;4796}4797}47984799// Now that it's inside the tree, do the setup.4800ep->update_property();4801ep->_update_flags();4802ep->update_editor_property_status();4803ep->update_cache();48044805if (current_selected && ep->property == current_selected) {4806ep->select(current_focusable);4807}4808}4809}48104811if (favorites_vbox->get_child_count() > 0) {4812favorites_vbox->show();4813}4814}48154816// Show a separator if there's no category to clearly divide the properties.4817favorites_separator->hide();4818if (main_vbox->get_child_count() > 0) {4819EditorInspectorCategory *category = Object::cast_to<EditorInspectorCategory>(main_vbox->get_child(0));4820if (!category) {4821favorites_separator->show();4822}4823}48244825// Clean up empty sections.4826for (List<EditorInspectorSection *>::Element *I = sections.back(); I;) {4827EditorInspectorSection *section = I->get();4828I = I->prev(); // Note: Advance before erasing element.4829if (section->get_vbox()->get_child_count() == 0) {4830sections.erase(section);4831vbox_per_path[main_vbox].erase(section->get_section());4832memdelete(section);4833}4834}4835}48364837if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {4838// Add 4px of spacing between the "Add Metadata" button and the content above it.4839Control *spacer = memnew(Control);4840spacer->set_custom_minimum_size(Size2(0, 4) * EDSCALE);4841main_vbox->add_child(spacer);48424843Button *add_md = memnew(EditorInspectorActionButton(TTRC("Add Metadata"), SNAME("Add")));4844add_md->connect(SceneStringName(pressed), callable_mp(this, &EditorInspector::_show_add_meta_dialog));4845main_vbox->add_child(add_md);4846if (all_read_only) {4847add_md->set_disabled(true);4848}4849}48504851// Get the lists of to add at the end.4852for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {4853ped->parse_end(object);4854_parse_added_editors(main_vbox, nullptr, ped);4855}48564857if (is_main_editor_inspector()) {4858// Updating inspector might invalidate some editing owners.4859EditorNode::get_singleton()->hide_unused_editors();4860}48614862if (root_inspector_was_following_focus) {4863get_root_inspector()->set_follow_focus(true);4864}4865}48664867void EditorInspector::update_property(const String &p_prop) {4868if (!editor_property_map.has(p_prop)) {4869return;4870}48714872for (EditorProperty *E : editor_property_map[p_prop]) {4873E->update_property();4874E->update_editor_property_status();4875E->update_cache();4876}48774878for (EditorInspectorSection *S : sections) {4879if (S->is_checkable()) {4880S->_property_edited(p_prop);4881}4882}4883}48844885void EditorInspector::_clear(bool p_hide_plugins) {4886begin_vbox->hide();4887while (begin_vbox->get_child_count()) {4888memdelete(begin_vbox->get_child(0));4889}48904891favorites_section->hide();4892favorites_vbox->hide();4893while (favorites_vbox->get_child_count()) {4894memdelete(favorites_vbox->get_child(0));4895}4896favorites_groups_vbox->hide();4897while (favorites_groups_vbox->get_child_count()) {4898memdelete(favorites_groups_vbox->get_child(0));4899}49004901while (main_vbox->get_child_count()) {4902memdelete(main_vbox->get_child(0));4903}49044905property_selected = StringName();4906property_focusable = -1;4907editor_property_map.clear();4908sections.clear();4909pending.clear();4910restart_request_props.clear();49114912if (p_hide_plugins && is_main_editor_inspector()) {4913EditorNode::get_singleton()->hide_unused_editors(this);4914}4915}49164917Object *EditorInspector::get_edited_object() {4918return object;4919}49204921Object *EditorInspector::get_next_edited_object() {4922return next_object;4923}49244925void EditorInspector::edit(Object *p_object) {4926if (object == p_object) {4927return;4928}49294930next_object = p_object; // Some plugins need to know the next edited object when clearing the inspector.4931if (object) {4932if (likely(Variant(object).get_validated_object())) {4933object->disconnect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback));4934}4935_clear();4936}4937per_array_page.clear();49384939object = p_object;49404941if (object) {4942update_scroll_request = 0; //reset4943if (scroll_cache.has(object->get_instance_id())) { //if exists, set something else4944update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accommodated4945}4946object->connect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback));49474948can_favorite = Object::cast_to<Node>(object) || Object::cast_to<Resource>(object);4949_update_current_favorites();49504951update_tree();4952}49534954// Keep it available until the end so it works with both main and sub inspectors.4955next_object = nullptr;49564957emit_signal(SNAME("edited_object_changed"));4958}49594960void EditorInspector::set_keying(bool p_active) {4961if (keying == p_active) {4962return;4963}4964keying = p_active;4965_keying_changed();4966}49674968void EditorInspector::_keying_changed() {4969for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {4970for (EditorProperty *E : F.value) {4971if (E) {4972E->set_keying(keying);4973}4974}4975}49764977for (EditorInspectorSection *S : sections) {4978S->set_keying(keying);4979}4980}49814982void EditorInspector::set_read_only(bool p_read_only) {4983if (p_read_only == read_only) {4984return;4985}4986read_only = p_read_only;4987update_tree();4988}49894990EditorPropertyNameProcessor::Style EditorInspector::get_property_name_style() const {4991return property_name_style;4992}49934994void EditorInspector::set_property_name_style(EditorPropertyNameProcessor::Style p_style) {4995if (property_name_style == p_style) {4996return;4997}4998property_name_style = p_style;4999update_tree();5000}50015002void EditorInspector::set_use_settings_name_style(bool p_enable) {5003if (use_settings_name_style == p_enable) {5004return;5005}5006use_settings_name_style = p_enable;5007if (use_settings_name_style) {5008set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style());5009}5010}50115012void EditorInspector::set_autoclear(bool p_enable) {5013autoclear = p_enable;5014}50155016void EditorInspector::set_show_categories(bool p_show_standard, bool p_show_custom) {5017show_standard_categories = p_show_standard;5018show_custom_categories = p_show_custom;5019update_tree();5020}50215022void EditorInspector::set_use_doc_hints(bool p_enable) {5023use_doc_hints = p_enable;5024update_tree();5025}50265027void EditorInspector::set_hide_script(bool p_hide) {5028hide_script = p_hide;5029update_tree();5030}50315032void EditorInspector::set_hide_metadata(bool p_hide) {5033hide_metadata = p_hide;5034update_tree();5035}50365037void EditorInspector::set_use_filter(bool p_use) {5038use_filter = p_use;5039update_tree();5040}50415042void EditorInspector::register_text_enter(Node *p_line_edit) {5043search_box = Object::cast_to<LineEdit>(p_line_edit);5044if (search_box) {5045search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorInspector::update_tree).unbind(1));5046}5047}50485049void EditorInspector::set_use_folding(bool p_use_folding, bool p_update_tree) {5050use_folding = p_use_folding;50515052if (p_update_tree) {5053update_tree();5054}5055}50565057bool EditorInspector::is_using_folding() {5058return use_folding;5059}50605061void EditorInspector::collapse_all_folding() {5062for (EditorInspectorSection *E : sections) {5063E->fold();5064}50655066for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {5067for (EditorProperty *E : F.value) {5068E->collapse_all_folding();5069}5070}5071}50725073void EditorInspector::expand_all_folding() {5074for (EditorInspectorSection *E : sections) {5075E->unfold();5076}5077for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {5078for (EditorProperty *E : F.value) {5079E->expand_all_folding();5080}5081}5082}50835084void EditorInspector::expand_revertable() {5085HashSet<EditorInspectorSection *> sections_to_unfold[2];5086for (EditorInspectorSection *E : sections) {5087if (E->has_revertable_properties()) {5088sections_to_unfold[0].insert(E);5089}5090}50915092// Climb up the hierarchy doing double buffering with the sets.5093int a = 0;5094int b = 1;5095while (sections_to_unfold[a].size()) {5096for (EditorInspectorSection *E : sections_to_unfold[a]) {5097E->unfold();50985099Node *n = E->get_parent();5100while (n) {5101if (Object::cast_to<EditorInspector>(n)) {5102break;5103}5104if (Object::cast_to<EditorInspectorSection>(n) && !sections_to_unfold[a].has((EditorInspectorSection *)n)) {5105sections_to_unfold[b].insert((EditorInspectorSection *)n);5106}5107n = n->get_parent();5108}5109}51105111sections_to_unfold[a].clear();5112SWAP(a, b);5113}51145115for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {5116for (EditorProperty *E : F.value) {5117E->expand_revertable();5118}5119}5120}51215122void EditorInspector::set_scroll_offset(int p_offset) {5123// This can be called before the container finishes sorting its children, so defer it.5124callable_mp((ScrollContainer *)this, &ScrollContainer::set_v_scroll).call_deferred(p_offset);5125}51265127int EditorInspector::get_scroll_offset() const {5128return get_v_scroll();5129}51305131void EditorInspector::set_use_wide_editors(bool p_enable) {5132wide_editors = p_enable;5133}51345135void EditorInspector::set_root_inspector(EditorInspector *p_root_inspector) {5136root_inspector = p_root_inspector;5137// Only the root inspector should follow focus.5138set_follow_focus(false);5139}51405141void EditorInspector::_section_toggled_by_user(const String &p_path, bool p_value) {5142_property_changed(p_path, p_value);5143}51445145void EditorInspector::set_use_deletable_properties(bool p_enabled) {5146deletable_properties = p_enabled;5147}51485149void EditorInspector::_page_change_request(int p_new_page, const StringName &p_array_prefix) {5150int prev_page = per_array_page.has(p_array_prefix) ? per_array_page[p_array_prefix] : 0;5151int new_page = MAX(0, p_new_page);5152if (new_page != prev_page) {5153per_array_page[p_array_prefix] = new_page;5154update_tree_pending = true;5155}5156}51575158void EditorInspector::_edit_request_change(Object *p_object, const String &p_property) {5159if (object != p_object) { //may be undoing/redoing for a non edited object, so ignore5160return;5161}51625163if (changing) {5164return;5165}51665167if (p_property.is_empty()) {5168update_tree_pending = true;5169} else {5170pending.insert(p_property);5171}5172}51735174void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bool p_refresh_all, const String &p_changed_field) {5175if (autoclear && editor_property_map.has(p_name)) {5176for (EditorProperty *E : editor_property_map[p_name]) {5177if (E->is_checkable()) {5178E->set_checked(true);5179}5180}5181}51825183EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5184if (!undo_redo || bool(object->call("_dont_undo_redo"))) {5185object->set(p_name, p_value);5186if (p_refresh_all) {5187_edit_request_change(object, "");5188} else {5189_edit_request_change(object, p_name);5190}51915192emit_signal(_prop_edited, p_name);5193} else if (Object::cast_to<MultiNodeEdit>(object)) {5194Object::cast_to<MultiNodeEdit>(object)->set_property_field(p_name, p_value, p_changed_field);5195_edit_request_change(object, p_name);5196emit_signal(_prop_edited, p_name);5197} else if (Object::cast_to<EditorDebuggerRemoteObjects>(object)) {5198Object::cast_to<EditorDebuggerRemoteObjects>(object)->set_property_field(p_name, p_value, p_changed_field);5199_edit_request_change(object, p_name);5200emit_signal(_prop_edited, p_name);5201} else {5202undo_redo->create_action(vformat(TTR("Set %s"), p_name), UndoRedo::MERGE_ENDS, nullptr, false, mark_unsaved);5203undo_redo->add_do_property(object, p_name, p_value);5204bool valid = false;5205Variant value = object->get(p_name, &valid);5206Variant::Type type = p_value.get_type();5207if (valid) {5208if (Object::cast_to<Control>(object) && (p_name == "anchors_preset" || p_name == "layout_mode")) {5209undo_redo->add_undo_method(object, "_edit_set_state", Object::cast_to<Control>(object)->_edit_get_state());5210} else {5211undo_redo->add_undo_property(object, p_name, value);5212}5213Node *N = Object::cast_to<Node>(object);5214bool double_counting = Object::cast_to<Node>(p_value) == N || Object::cast_to<Node>(value) == N;5215if (N && !double_counting && (type == Variant::OBJECT || type == Variant::ARRAY || type == Variant::DICTIONARY) && value != p_value) {5216undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", value, N, true);5217undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, false);5218// Perhaps an inefficient way of updating the resource count.5219// We could go in depth and check which Resource values changed/got removed and which ones stayed the same, but this is more readable at the moment.5220undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, true);5221undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", value, N, false);5222}5223}52245225List<StringName> linked_properties;5226ClassDB::get_linked_properties_info(object->get_class_name(), p_name, &linked_properties);52275228for (const StringName &linked_prop : linked_properties) {5229valid = false;5230Variant undo_value = object->get(linked_prop, &valid);5231if (valid) {5232undo_redo->add_undo_property(object, linked_prop, undo_value);5233}5234}52355236PackedStringArray linked_properties_dynamic = object->call("_get_linked_undo_properties", p_name, p_value);5237for (int i = 0; i < linked_properties_dynamic.size(); i++) {5238valid = false;5239Variant undo_value = object->get(linked_properties_dynamic[i], &valid);5240if (valid) {5241undo_redo->add_undo_property(object, linked_properties_dynamic[i], undo_value);5242}5243}52445245Variant v_undo_redo = undo_redo;5246Variant v_object = object;5247Variant v_name = p_name;5248const Vector<Callable> &callbacks = EditorNode::get_editor_data().get_undo_redo_inspector_hook_callback();5249for (int i = 0; i < callbacks.size(); i++) {5250const Callable &callback = callbacks[i];52515252const Variant *p_arguments[] = { &v_undo_redo, &v_object, &v_name, &p_value };5253Variant return_value;5254Callable::CallError call_error;52555256callback.callp(p_arguments, 4, return_value, call_error);5257if (call_error.error != Callable::CallError::CALL_OK) {5258ERR_PRINT("Invalid UndoRedo callback.");5259}5260}52615262if (p_refresh_all) {5263undo_redo->add_do_method(this, "_edit_request_change", object, "");5264undo_redo->add_undo_method(this, "_edit_request_change", object, "");5265} else {5266undo_redo->add_do_method(this, "_edit_request_change", object, p_name);5267undo_redo->add_undo_method(this, "_edit_request_change", object, p_name);5268}52695270Resource *r = Object::cast_to<Resource>(object);52715272if (r) {5273//Setting a Subresource. Since there's possibly multiple Nodes referencing 'r', we need to link them to the Subresource.5274List<Node *> shared_nodes = EditorNode::get_singleton()->get_resource_node_list(r);5275for (Node *N : shared_nodes) {5276if ((type == Variant::OBJECT || type == Variant::ARRAY || type == Variant::DICTIONARY) && value != p_value) {5277undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", value, N, true);5278undo_redo->add_do_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, false);5279// Perhaps an inefficient way of updating the resource count.5280// We could go in depth and check which Resource values changed/got removed and which ones stayed the same, but this is more readable at the moment.5281undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", p_value, N, true);5282undo_redo->add_undo_method(EditorNode::get_singleton(), "update_node_reference", value, N, false);5283}5284}5285if (String(p_name) == "resource_local_to_scene") {5286bool prev = object->get(p_name);5287bool next = p_value;5288if (next) {5289undo_redo->add_do_method(r, "setup_local_to_scene");5290}5291if (prev) {5292undo_redo->add_undo_method(r, "setup_local_to_scene");5293}5294}5295}5296undo_redo->add_do_method(this, "emit_signal", _prop_edited, p_name);5297undo_redo->add_undo_method(this, "emit_signal", _prop_edited, p_name);5298undo_redo->commit_action();5299}53005301if (editor_property_map.has(p_name)) {5302for (EditorProperty *E : editor_property_map[p_name]) {5303E->update_editor_property_status();5304}5305}5306}53075308void EditorInspector::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing, bool p_update_all) {5309// The "changing" variable must be true for properties that trigger events as typing occurs,5310// like "text_changed" signal. E.g. text property of Label, Button, RichTextLabel, etc.5311if (p_changing) {5312changing++;5313}53145315_edit_set(p_path, p_value, p_update_all, p_name);53165317if (p_changing) {5318changing--;5319}53205321if (restart_request_props.has(p_path)) {5322emit_signal(SNAME("restart_requested"));5323}5324}53255326void EditorInspector::_multiple_properties_changed(const Vector<String> &p_paths, const Array &p_values, bool p_changing) {5327ERR_FAIL_COND(p_paths.is_empty() || p_values.is_empty());5328ERR_FAIL_COND(p_paths.size() != p_values.size());5329String names;5330for (int i = 0; i < p_paths.size(); i++) {5331if (i > 0) {5332names += ",";5333}5334names += p_paths[i];5335}5336EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5337// TRANSLATORS: This is describing a change to multiple properties at once. The parameter is a list of property names.5338undo_redo->create_action(vformat(TTR("Set Multiple: %s"), names), UndoRedo::MERGE_ENDS);5339for (int i = 0; i < p_paths.size(); i++) {5340_edit_set(p_paths[i], p_values[i], false, "");5341if (restart_request_props.has(p_paths[i])) {5342emit_signal(SNAME("restart_requested"));5343}5344}5345if (p_changing) {5346changing++;5347}5348undo_redo->commit_action();5349if (p_changing) {5350changing--;5351}5352}53535354void EditorInspector::_property_keyed(const String &p_path, bool p_advance) {5355if (!object) {5356return;5357}53585359// The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it.5360const Variant args[3] = { p_path, object->get(p_path), p_advance };5361const Variant *argp[3] = { &args[0], &args[1], &args[2] };5362emit_signalp(SNAME("property_keyed"), argp, 3);5363}53645365void EditorInspector::_property_deleted(const String &p_path) {5366if (!object) {5367return;5368}53695370if (p_path.begins_with("metadata/")) {5371String name = p_path.replace_first("metadata/", "");5372EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5373undo_redo->create_action(vformat(TTR("Remove metadata %s"), name));5374undo_redo->add_do_method(object, "remove_meta", name);5375undo_redo->add_undo_method(object, "set_meta", name, object->get_meta(name));5376undo_redo->commit_action();5377}53785379if (restart_request_props.has(p_path)) {5380emit_signal(SNAME("restart_requested"));5381}5382emit_signal(SNAME("property_deleted"), p_path);5383}53845385void EditorInspector::_property_keyed_with_value(const String &p_path, const Variant &p_value, bool p_advance) {5386if (!object) {5387return;5388}53895390// The second parameter could be null, causing the event to fire with less arguments, so use the pointer call which preserves it.5391const Variant args[3] = { p_path, p_value, p_advance };5392const Variant *argp[3] = { &args[0], &args[1], &args[2] };5393emit_signalp(SNAME("property_keyed"), argp, 3);5394}53955396void EditorInspector::_property_checked(const String &p_path, bool p_checked) {5397if (!object) {5398return;5399}54005401//property checked5402if (autoclear) {5403if (!p_checked) {5404_edit_set(p_path, Variant(), false, "");5405} else {5406Variant to_create;5407Control *control = Object::cast_to<Control>(object);5408if (control && p_path.begins_with("theme_override_")) {5409to_create = control->get_used_theme_item(p_path);5410} else {5411List<PropertyInfo> pinfo;5412object->get_property_list(&pinfo);5413for (const PropertyInfo &E : pinfo) {5414if (E.name == p_path) {5415Callable::CallError ce;5416Variant::construct(E.type, to_create, nullptr, 0, ce);5417break;5418}5419}5420}5421_edit_set(p_path, to_create, false, "");5422}54235424if (editor_property_map.has(p_path)) {5425for (EditorProperty *E : editor_property_map[p_path]) {5426E->set_checked(p_checked);5427E->update_property();5428E->update_editor_property_status();5429E->update_cache();5430}5431}5432} else {5433emit_signal(SNAME("property_toggled"), p_path, p_checked);5434}5435}54365437void EditorInspector::_property_pinned(const String &p_path, bool p_pinned) {5438if (!object) {5439return;5440}54415442Node *node = Object::cast_to<Node>(object);5443ERR_FAIL_NULL(node);54445445EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5446undo_redo->create_action(vformat(p_pinned ? TTR("Pinned %s") : TTR("Unpinned %s"), p_path));5447undo_redo->add_do_method(node, "_set_property_pinned", p_path, p_pinned);5448undo_redo->add_undo_method(node, "_set_property_pinned", p_path, !p_pinned);5449if (editor_property_map.has(p_path)) {5450for (List<EditorProperty *>::Element *E = editor_property_map[p_path].front(); E; E = E->next()) {5451undo_redo->add_do_method(E->get(), "_update_editor_property_status");5452undo_redo->add_undo_method(E->get(), "_update_editor_property_status");5453}5454}5455undo_redo->commit_action();5456}54575458void EditorInspector::_property_selected(const String &p_path, int p_focusable) {5459property_selected = p_path;5460property_focusable = p_focusable;5461// Deselect the others.5462for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {5463if (F.key == property_selected) {5464continue;5465}5466for (EditorProperty *E : F.value) {5467if (E->is_selected()) {5468E->deselect();5469}5470}5471}54725473emit_signal(SNAME("property_selected"), p_path);5474}54755476void EditorInspector::_object_id_selected(const String &p_path, ObjectID p_id) {5477emit_signal(SNAME("object_id_selected"), p_id);5478}54795480void EditorInspector::_resource_selected(const String &p_path, Ref<Resource> p_resource) {5481emit_signal(SNAME("resource_selected"), p_resource, p_path);5482}54835484void EditorInspector::_node_removed(Node *p_node) {5485if (p_node == object) {5486edit(nullptr);5487}5488}54895490void EditorInspector::_update_current_favorites() {5491current_favorites.clear();5492if (!can_favorite) {5493return;5494}54955496HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();54975498// Fetch script properties.5499Ref<Script> scr = object->get_script();5500if (scr.is_valid()) {5501List<PropertyInfo> plist;5502// FIXME: Only properties from a saved script will be available, unsaved ones will be ignored.5503// Can cause a little wonkiness, while nothing serious, would be nice to find a way to get5504// unsaved ones without needing to get the entire property list of an object.5505scr->get_script_property_list(&plist);55065507String path;5508HashMap<String, LocalVector<String>> props;55095510for (PropertyInfo &p : plist) {5511if (p.usage & PROPERTY_USAGE_CATEGORY) {5512path = favorites.has(p.hint_string) ? p.hint_string : String();5513} else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && !path.is_empty()) {5514props[path].push_back(p.name);5515}5516}55175518// Add favorited properties while removing invalid ones.5519bool invalid_props = false;5520for (const KeyValue<String, LocalVector<String>> &KV : props) {5521path = KV.key;5522for (int i = 0; i < favorites[path].size(); i++) {5523String prop = favorites[path][i];5524if (KV.value.has(prop)) {5525current_favorites.append(prop);5526} else {5527invalid_props = true;5528favorites[path].erase(prop);5529i--;5530}5531}55325533if (favorites[path].is_empty()) {5534favorites.erase(path);5535}5536}55375538if (invalid_props) {5539EditorSettings::get_singleton()->set_favorite_properties(favorites);5540}5541}55425543// Fetch built-in properties.5544StringName class_name = object->get_class_name();5545for (const KeyValue<String, PackedStringArray> &KV : favorites) {5546if (ClassDB::is_parent_class(class_name, KV.key)) {5547current_favorites.append_array(KV.value);5548}5549}5550}55515552void EditorInspector::_set_property_favorited(const String &p_path, bool p_favorited) {5553if (!object) {5554return;5555}55565557StringName validate_name = object->get_class_name();5558StringName class_name;55595560String theme_property;5561if (p_path.begins_with("theme_override_")) {5562theme_property = p_path.get_slicec('/', 1);5563}55645565while (!validate_name.is_empty()) {5566class_name = validate_name;55675568if (!theme_property.is_empty()) { // Deal with theme properties.5569bool found = false;5570HashMap<String, DocData::ClassDoc>::ConstIterator F = EditorHelp::get_doc_data()->class_list.find(class_name);5571if (F) {5572for (const DocData::ThemeItemDoc &prop : F->value.theme_properties) {5573if (prop.name == theme_property) {5574found = true;5575break;5576}5577}5578}55795580if (found) {5581break;5582}5583} else if (ClassDB::has_property(class_name, p_path, true)) { // Check if the property is built-in.5584break;5585}55865587validate_name = ClassDB::get_parent_class_nocheck(class_name);5588}55895590// "script" isn't a real property, so a hack is necessary.5591if (validate_name.is_empty() && p_path != "script") {5592class_name = "";5593}55945595if (class_name.is_empty()) {5596// Check if it's part of a script.5597Ref<Script> scr = object->get_script();5598if (scr.is_valid()) {5599List<PropertyInfo> plist;5600scr->get_script_property_list(&plist);56015602String path;5603for (PropertyInfo &p : plist) {5604if (p.usage & PROPERTY_USAGE_CATEGORY) {5605path = p.hint_string;5606} else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && p.name == p_path) {5607class_name = path;5608break;5609}5610}5611}56125613ERR_FAIL_COND_MSG(class_name.is_empty(), "Can't favorite invalid property. If said property was from a script and recently renamed, try saving it first.");5614}56155616HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();5617if (p_favorited) {5618current_favorites.append(p_path);5619favorites[class_name].append(p_path);5620} else {5621current_favorites.erase(p_path);56225623if (favorites.has(class_name) && favorites[class_name].has(p_path)) {5624if (favorites[class_name].size() > 1) {5625favorites[class_name].erase(p_path);5626} else {5627favorites.erase(class_name);5628}5629}5630}5631EditorSettings::get_singleton()->set_favorite_properties(favorites);56325633update_tree();5634}56355636void EditorInspector::_clear_current_favorites() {5637current_favorites.clear();56385639HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();56405641Ref<Script> scr = object->get_script();5642if (scr.is_valid()) {5643List<PropertyInfo> plist;5644scr->get_script_property_list(&plist);56455646for (PropertyInfo &p : plist) {5647if (p.usage & PROPERTY_USAGE_CATEGORY && favorites.has(p.hint_string)) {5648favorites.erase(p.hint_string);5649}5650}5651}56525653StringName class_name = object->get_class_name();5654while (class_name) {5655if (favorites.has(class_name)) {5656favorites.erase(class_name);5657}56585659class_name = ClassDB::get_parent_class(class_name);5660}56615662EditorSettings::get_singleton()->set_favorite_properties(favorites);5663update_tree();5664}56655666void EditorInspector::_notification(int p_what) {5667switch (p_what) {5668case NOTIFICATION_TRANSLATION_CHANGED: {5669if (property_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED) {5670update_tree_pending = true;5671}5672} break;56735674case NOTIFICATION_THEME_CHANGED: {5675if (updating_theme) {5676break;5677}56785679theme_cache.prop_subsection = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));56805681initialize_section_theme(section_theme_cache, this);5682initialize_category_theme(category_theme_cache, this);5683initialize_property_theme(property_theme_cache, this);5684} break;56855686case NOTIFICATION_READY: {5687if (EditorFeatureProfileManager::get_singleton()) {5688EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &EditorInspector::_feature_profile_changed));5689}5690set_process(is_visible_in_tree());5691if (!is_sub_inspector()) {5692get_tree()->connect("node_removed", callable_mp(this, &EditorInspector::_node_removed));5693}5694} break;56955696case NOTIFICATION_PREDELETE: {5697if (!is_sub_inspector() && is_inside_tree()) {5698get_tree()->disconnect("node_removed", callable_mp(this, &EditorInspector::_node_removed));5699}5700edit(nullptr);5701} break;57025703case NOTIFICATION_VISIBILITY_CHANGED: {5704set_process(is_visible_in_tree());5705} break;57065707case NOTIFICATION_PROCESS: {5708if (update_scroll_request >= 0) {5709callable_mp((Range *)get_v_scroll_bar(), &Range::set_value).call_deferred(update_scroll_request);5710update_scroll_request = -1;5711}5712if (update_tree_pending) {5713refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));5714} else if (refresh_countdown > 0) {5715refresh_countdown -= get_process_delta_time();5716if (refresh_countdown <= 0) {5717for (const KeyValue<StringName, List<EditorProperty *>> &F : editor_property_map) {5718for (EditorProperty *E : F.value) {5719if (E && !E->is_cache_valid()) {5720E->update_property();5721E->update_editor_property_status();5722E->update_cache();5723}5724}5725}57265727for (EditorInspectorSection *S : sections) {5728S->update_property();5729}57305731refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));5732}5733}57345735changing++;57365737if (update_tree_pending) {5738update_tree();5739update_tree_pending = false;5740} else {5741for (const StringName &prop : pending) {5742if (editor_property_map.has(prop)) {5743for (EditorProperty *E : editor_property_map[prop]) {5744E->update_property();5745E->update_editor_property_status();5746E->update_cache();5747}5748}5749}57505751for (EditorInspectorSection *S : sections) {5752S->update_property();5753}5754}57555756pending.clear();57575758changing--;5759} break;57605761case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {5762if (use_settings_name_style && EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor/localize_settings")) {5763EditorPropertyNameProcessor::Style style = EditorPropertyNameProcessor::get_settings_style();5764if (property_name_style != style) {5765property_name_style = style;5766update_tree_pending = true;5767}5768}57695770if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/inspector")) {5771update_tree_pending = true;5772}5773} break;5774}5775}57765777void EditorInspector::_changed_callback() {5778//this is called when property change is notified via notify_property_list_changed()5779if (object != nullptr) {5780_update_current_favorites();5781_edit_request_change(object, String());5782}5783}57845785void EditorInspector::_vscroll_changed(double p_offset) {5786if (update_scroll_request >= 0) { //waiting, do nothing5787return;5788}57895790if (object) {5791scroll_cache[object->get_instance_id()] = p_offset;5792}5793}57945795void EditorInspector::set_property_prefix(const String &p_prefix) {5796property_prefix = p_prefix;5797}57985799String EditorInspector::get_property_prefix() const {5800return property_prefix;5801}58025803void EditorInspector::add_custom_property_description(const String &p_class, const String &p_property, const String &p_description) {5804const String key = vformat("property|%s|%s", p_class, p_property);5805custom_property_descriptions[key] = p_description;5806}58075808String EditorInspector::get_custom_property_description(const String &p_property) const {5809HashMap<String, String>::ConstIterator E = custom_property_descriptions.find(p_property);5810if (E) {5811return E->value;5812}5813return "";5814}58155816void EditorInspector::remap_doc_property_class(const String &p_property_prefix, const String &p_class) {5817doc_property_class_remaps[p_property_prefix] = p_class;5818}58195820void EditorInspector::set_object_class(const String &p_class) {5821object_class = p_class;5822}58235824String EditorInspector::get_object_class() const {5825return object_class;5826}58275828void EditorInspector::_feature_profile_changed() {5829update_tree();5830}58315832void EditorInspector::set_restrict_to_basic_settings(bool p_restrict) {5833restrict_to_basic = p_restrict;5834update_tree();5835}58365837void EditorInspector::set_property_clipboard(const Variant &p_value) {5838property_clipboard = p_value;5839}58405841Variant EditorInspector::get_property_clipboard() {5842return property_clipboard;5843}58445845void EditorInspector::_show_add_meta_dialog() {5846if (!add_meta_dialog) {5847add_meta_dialog = memnew(AddMetadataDialog());5848add_meta_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorInspector::_add_meta_confirm));5849add_child(add_meta_dialog);5850}58515852StringName dialog_title;5853Node *node = Object::cast_to<Node>(object);5854// If object is derived from Node use node name, if derived from Resource use classname.5855dialog_title = node ? node->get_name() : StringName(object->get_class());58565857List<StringName> existing_meta_keys;5858object->get_meta_list(&existing_meta_keys);5859add_meta_dialog->open(dialog_title, existing_meta_keys);5860}58615862void EditorInspector::_add_meta_confirm() {5863// Ensure metadata is unfolded when adding a new metadata.5864object->editor_set_section_unfold("metadata", true);58655866String name = add_meta_dialog->get_meta_name();5867EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();5868undo_redo->create_action(vformat(TTR("Add metadata %s"), name));5869undo_redo->add_do_method(object, "set_meta", name, add_meta_dialog->get_meta_defval());5870undo_redo->add_undo_method(object, "remove_meta", name);5871undo_redo->commit_action();5872}58735874EditorInspector *EditorInspector::_get_control_parent_inspector(Control *p_control) {5875{5876EditorInspector *inspector = Object::cast_to<EditorInspector>(p_control);5877if (inspector) {5878return inspector;5879}5880}58815882Control *parent = p_control->get_parent_control();5883while (parent) {5884EditorInspector *inspector = Object::cast_to<EditorInspector>(parent);5885if (inspector) {5886return inspector;5887}5888parent = parent->get_parent_control();5889}5890return nullptr;5891}58925893void EditorInspector::_bind_methods() {5894ClassDB::bind_method(D_METHOD("edit", "object"), &EditorInspector::edit);5895ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);5896ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path);5897ClassDB::bind_method("get_edited_object", &EditorInspector::get_edited_object);58985899ClassDB::bind_static_method("EditorInspector", D_METHOD("instantiate_property_editor", "object", "type", "path", "hint", "hint_text", "usage", "wide"), &EditorInspector::instantiate_property_editor, DEFVAL(false));59005901ADD_SIGNAL(MethodInfo("property_selected", PropertyInfo(Variant::STRING, "property")));5902ADD_SIGNAL(MethodInfo("property_keyed", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT), PropertyInfo(Variant::BOOL, "advance")));5903ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING, "property")));5904ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static()), PropertyInfo(Variant::STRING, "path")));5905ADD_SIGNAL(MethodInfo("object_id_selected", PropertyInfo(Variant::INT, "id")));5906ADD_SIGNAL(MethodInfo("property_edited", PropertyInfo(Variant::STRING, "property")));5907ADD_SIGNAL(MethodInfo("property_toggled", PropertyInfo(Variant::STRING, "property"), PropertyInfo(Variant::BOOL, "checked")));5908ADD_SIGNAL(MethodInfo("edited_object_changed"));5909ADD_SIGNAL(MethodInfo("restart_requested"));5910}59115912EditorInspector::EditorInspector() {5913object = nullptr;59145915base_vbox = memnew(VBoxContainer);5916base_vbox->set_theme_type_variation(SNAME("EditorInspectorContainer"));5917base_vbox->set_h_size_flags(SIZE_EXPAND_FILL);5918add_child(base_vbox);59195920begin_vbox = memnew(VBoxContainer);5921begin_vbox->set_theme_type_variation(SNAME("EditorInspectorContainer"));5922base_vbox->add_child(begin_vbox);5923begin_vbox->hide();59245925favorites_section = memnew(VBoxContainer);5926favorites_section->set_theme_type_variation(SNAME("EditorInspectorContainer"));5927base_vbox->add_child(favorites_section);5928favorites_section->hide();59295930EditorInspectorCategory *favorites_category = memnew(EditorInspectorCategory);5931favorites_category->set_as_favorite();5932favorites_category->connect("unfavorite_all", callable_mp(this, &EditorInspector::_clear_current_favorites));5933favorites_section->add_child(favorites_category);59345935favorites_vbox = memnew(VBoxContainer);5936favorites_vbox->set_theme_type_variation(SNAME("EditorPropertyContainer"));5937favorites_section->add_child(favorites_vbox);5938favorites_vbox->hide();5939favorites_groups_vbox = memnew(VBoxContainer);5940favorites_groups_vbox->set_theme_type_variation(SNAME("EditorInspectorContainer"));5941favorites_section->add_child(favorites_groups_vbox);5942favorites_groups_vbox->hide();59435944favorites_separator = memnew(HSeparator);5945favorites_section->add_child(favorites_separator);5946favorites_separator->hide();59475948main_vbox = memnew(VBoxContainer);5949main_vbox->set_theme_type_variation(SNAME("EditorInspectorContainer"));5950base_vbox->add_child(main_vbox);59515952set_horizontal_scroll_mode(SCROLL_MODE_DISABLED);5953set_follow_focus(true);59545955changing = 0;5956search_box = nullptr;5957_prop_edited = "property_edited";5958set_process(false);5959set_focus_mode(FocusMode::FOCUS_ALL);5960property_focusable = -1;59615962get_v_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorInspector::_vscroll_changed));5963update_scroll_request = -1;5964if (EditorSettings::get_singleton()) {5965refresh_countdown = float(EDITOR_GET("docks/property_editor/auto_refresh_interval"));5966} else {5967//used when class is created by the docgen to dump default values of everything bindable, editorsettings may not be created5968refresh_countdown = 0.33;5969}59705971ED_SHORTCUT("property_editor/copy_value", TTRC("Copy Value"), KeyModifierMask::CMD_OR_CTRL | Key::C);5972ED_SHORTCUT("property_editor/paste_value", TTRC("Paste Value"), KeyModifierMask::CMD_OR_CTRL | Key::V);5973ED_SHORTCUT("property_editor/copy_property_path", TTRC("Copy Property Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C);59745975// `use_settings_name_style` is true by default, set the name style accordingly.5976set_property_name_style(EditorPropertyNameProcessor::get_singleton()->get_settings_style());59775978set_draw_focus_border(true);5979set_scroll_on_drag_hover(true);5980}598159825983