diff --git a/doc/classes/EditorProperty.xml b/doc/classes/EditorProperty.xml
index 2b1083393f6..4ff541f72df 100644
--- a/doc/classes/EditorProperty.xml
+++ b/doc/classes/EditorProperty.xml
@@ -132,6 +132,13 @@
Emitted when a property was deleted. Used internally.
+
+
+
+
+ Emit it if you want to mark a property as favorited, making it appear at the top of the inspector.
+
+
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index a215662f167..ae34eea03ca 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -37,6 +37,7 @@
#include "editor/editor_feature_profile.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
#include "editor/editor_property_name_processor.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -931,10 +932,19 @@ float EditorProperty::get_name_split_ratio() const {
return split_ratio;
}
+void EditorProperty::set_favoritable(bool p_favoritable) {
+ can_favorite = p_favoritable;
+}
+
+bool EditorProperty::is_favoritable() const {
+ return can_favorite;
+}
+
void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) {
object = p_object;
property = p_property;
- _update_pin_flags();
+
+ _update_flags();
}
static bool _is_value_potential_override(Node *p_node, const String &p_property) {
@@ -953,12 +963,14 @@ static bool _is_value_potential_override(Node *p_node, const String &p_property)
}
}
-void EditorProperty::_update_pin_flags() {
+void EditorProperty::_update_flags() {
can_pin = false;
pin_hidden = true;
+
if (read_only) {
return;
}
+
if (Node *node = Object::cast_to(object)) {
// Avoid errors down the road by ignoring nodes which are not part of a scene
if (!node->get_owner()) {
@@ -1034,6 +1046,10 @@ void EditorProperty::menu_option(int p_option) {
case MENU_COPY_PROPERTY_PATH: {
DisplayServer::get_singleton()->clipboard_set(property_path);
} break;
+ case MENU_FAVORITE_PROPERTY: {
+ emit_signal(SNAME("property_favorited"), property, !favorited);
+ queue_redraw();
+ } break;
case MENU_PIN_VALUE: {
emit_signal(SNAME("property_pinned"), property, !pinned);
queue_redraw();
@@ -1091,6 +1107,7 @@ void EditorProperty::_bind_methods() {
ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING_NAME, "property")));
ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "checked")));
+ ADD_SIGNAL(MethodInfo("property_favorited", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "favorited")));
ADD_SIGNAL(MethodInfo("property_pinned", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "pinned")));
ADD_SIGNAL(MethodInfo("property_can_revert_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "can_revert")));
ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
@@ -1129,6 +1146,17 @@ void EditorProperty::_update_popup() {
menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal);
+ if (can_favorite) {
+ menu->add_separator();
+ if (favorited) {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Unfavorite")), TTR("Unfavorite Property"), MENU_FAVORITE_PROPERTY);
+ menu->set_item_tooltip(menu->get_item_index(MENU_FAVORITE_PROPERTY), TTR("Make this property be put back at its original place."));
+ } else {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Favorites")), TTR("Favorite Property"), MENU_FAVORITE_PROPERTY);
+ menu->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."));
+ }
+ }
+
if (!pin_hidden) {
menu->add_separator();
if (can_pin) {
@@ -1267,6 +1295,15 @@ void EditorInspectorCategory::_notification(int p_what) {
}
}
+void EditorInspectorCategory::override_menu(PopupMenu *p_menu) {
+ if (!p_menu) {
+ menu_override = nullptr;
+ return;
+ }
+
+ menu_override = p_menu;
+}
+
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
// If it's not a doc tooltip, fallback to the default one.
if (doc_class_name.is_empty()) {
@@ -1306,7 +1343,7 @@ void EditorInspectorCategory::_handle_menu_option(int p_option) {
}
void EditorInspectorCategory::gui_input(const Ref &p_event) {
- if (doc_class_name.is_empty()) {
+ if (!menu_override && doc_class_name.is_empty()) {
return;
}
@@ -1315,11 +1352,16 @@ void EditorInspectorCategory::gui_input(const Ref &p_event) {
return;
}
- menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+ PopupMenu *current_menu = menu_override ? menu_override : menu;
+ if (current_menu == menu) {
+ menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+ } else {
+ ERR_FAIL_COND(!current_menu->is_inside_tree());
+ }
- menu->set_position(get_screen_position() + mb_event->get_position());
- menu->reset_size();
- menu->popup();
+ current_menu->set_position(get_screen_position() + mb_event->get_position());
+ current_menu->reset_size();
+ current_menu->popup();
}
EditorInspectorCategory::EditorInspectorCategory() {
@@ -1622,6 +1664,10 @@ void EditorInspectorSection::gui_input(const Ref &p_event) {
}
}
+String EditorInspectorSection::get_section() const {
+ return section;
+}
+
VBoxContainer *EditorInspectorSection::get_vbox() {
return vbox;
}
@@ -2675,7 +2721,13 @@ String EditorInspector::get_selected_path() const {
void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref ped) {
for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
EditorProperty *ep = Object::cast_to(F.property_editor);
- current_vbox->add_child(F.property_editor);
+
+ if (ep && current_favorites.has(F.properties[0])) {
+ ep->favorited = true;
+ favorites_vbox->add_child(F.property_editor);
+ } else {
+ current_vbox->add_child(F.property_editor);
+ }
if (ep) {
ep->object = object;
@@ -2684,6 +2736,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn
ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
+ ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);
ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
@@ -2727,7 +2780,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn
ep->set_read_only(read_only);
ep->update_property();
- ep->_update_pin_flags();
+ ep->_update_flags();
ep->update_editor_property_status();
ep->set_deletable(deletable_properties);
ep->update_cache();
@@ -2835,6 +2888,7 @@ void EditorInspector::update_tree() {
String subgroup;
String subgroup_base;
int section_depth = 0;
+ bool disable_favorite = false;
VBoxContainer *category_vbox = nullptr;
List plist;
@@ -2842,13 +2896,17 @@ void EditorInspector::update_tree() {
HashMap> vbox_per_path;
HashMap editor_inspector_array_per_prefix;
+ HashMap>> favorites_to_add;
Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
// Get the lists of editors to add the beginning.
for (Ref &ped : valid_plugins) {
ped->parse_begin(object);
- _parse_added_editors(main_vbox, nullptr, ped);
+ _parse_added_editors(begin_vbox, nullptr, ped);
+ }
+ if (begin_vbox->get_child_count()) {
+ begin_vbox->show();
}
StringName doc_name;
@@ -2895,6 +2953,7 @@ void EditorInspector::update_tree() {
subgroup = "";
subgroup_base = "";
section_depth = 0;
+ disable_favorite = false;
vbox_per_path.clear();
editor_inspector_array_per_prefix.clear();
@@ -2958,6 +3017,11 @@ void EditorInspector::update_tree() {
} else {
category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
}
+
+ // Property favorites aren't compatible with built-in scripts.
+ if (scr->is_built_in()) {
+ disable_favorite = true;
+ }
}
}
@@ -3056,6 +3120,11 @@ void EditorInspector::update_tree() {
}
}
+ // Don't allow to favorite array items.
+ if (!disable_favorite) {
+ disable_favorite = !array_prefix.is_empty();
+ }
+
if (!array_prefix.is_empty()) {
path = path.trim_prefix(array_prefix);
int char_index = path.find("/");
@@ -3447,6 +3516,7 @@ void EditorInspector::update_tree() {
ep->set_draw_warning(draw_warning);
ep->set_use_folding(use_folding);
+ ep->set_favoritable(can_favorite && !disable_favorite);
ep->set_checkable(checkable);
ep->set_checked(checked);
ep->set_keying(keying);
@@ -3454,7 +3524,12 @@ void EditorInspector::update_tree() {
ep->set_deletable(deletable_properties || p.name.begins_with("metadata/"));
}
- current_vbox->add_child(editors[i].property_editor);
+ if (ep && ep->is_favoritable() && current_favorites.has(p.name)) {
+ ep->favorited = true;
+ favorites_to_add[group][subgroup].push_back(ep);
+ } else {
+ current_vbox->add_child(editors[i].property_editor);
+ }
if (ep) {
// Eventually, set other properties/signals after the property editor got added to the tree.
@@ -3463,6 +3538,7 @@ void EditorInspector::update_tree() {
ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
+ ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);
ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
@@ -3493,7 +3569,7 @@ void EditorInspector::update_tree() {
ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);
ep->update_property();
- ep->_update_pin_flags();
+ ep->_update_flags();
ep->update_editor_property_status();
ep->update_cache();
@@ -3504,6 +3580,88 @@ void EditorInspector::update_tree() {
}
}
+ if (!current_favorites.is_empty()) {
+ favorites_section->show();
+
+ // Organize the favorited properties in their sections, to keep context and differentiate from others with the same name.
+ bool is_localized = property_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED;
+ for (HashMap>>::Iterator I = favorites_to_add.begin(); I;) {
+ HashMap>>::Iterator grp = I;
+ ++grp;
+
+ String section_name = I->key;
+ String label;
+ String tooltip;
+ VBoxContainer *parent_vbox = favorites_vbox;
+ if (!section_name.is_empty()) {
+ if (is_localized) {
+ label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ tooltip = section_name;
+ } else {
+ label = section_name;
+ tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ }
+
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ favorites_groups_vbox->add_child(section);
+ parent_vbox = section->get_vbox();
+ section->setup("", section_name, object, sscolor, false);
+ section->set_tooltip_text(tooltip);
+ }
+
+ for (HashMap>::Iterator J = I->value.begin(); J;) {
+ HashMap>::Iterator sgrp = J;
+ ++sgrp;
+
+ section_name = J->key;
+ VBoxContainer *vbox = parent_vbox;
+ if (!section_name.is_empty()) {
+ if (is_localized) {
+ label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ tooltip = section_name;
+ } else {
+ label = section_name;
+ tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ }
+
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ vbox->add_child(section);
+ vbox = section->get_vbox();
+ section->setup("", section_name, object, sscolor, false);
+ section->set_tooltip_text(tooltip);
+ }
+
+ for (EditorProperty *ep : J->value) {
+ vbox->add_child(ep);
+ }
+
+ J = sgrp;
+ }
+
+ I = grp;
+ }
+
+ // Add a separator if there's no category to clearly divide the properties.
+ if (main_vbox->get_child_count() > 0) {
+ EditorInspectorCategory *category = Object::cast_to(main_vbox->get_child(0));
+ if (!category) {
+ favorites_section->add_child(memnew(HSeparator));
+ }
+ }
+
+ // Clean up empty sections.
+ for (List::Element *I = sections.back(); I; I = I->prev()) {
+ EditorInspectorSection *section = I->get();
+ if (section->get_vbox()->get_child_count() == 0) {
+ I = I->prev();
+
+ sections.erase(section);
+ vbox_per_path[main_vbox].erase(section->get_section());
+ memdelete(section);
+ }
+ }
+ }
+
if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {
// Add 4px of spacing between the "Add Metadata" button and the content above it.
Control *spacer = memnew(Control);
@@ -3545,6 +3703,19 @@ void EditorInspector::update_property(const String &p_prop) {
}
void EditorInspector::_clear(bool p_hide_plugins) {
+ begin_vbox->hide();
+ while (begin_vbox->get_child_count()) {
+ memdelete(begin_vbox->get_child(0));
+ }
+
+ favorites_section->hide();
+ while (favorites_vbox->get_child_count()) {
+ memdelete(favorites_vbox->get_child(0));
+ }
+ while (favorites_groups_vbox->get_child_count()) {
+ memdelete(favorites_groups_vbox->get_child(0));
+ }
+
while (main_vbox->get_child_count()) {
memdelete(main_vbox->get_child(0));
}
@@ -3591,6 +3762,10 @@ void EditorInspector::edit(Object *p_object) {
update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accommodated
}
object->connect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback));
+
+ can_favorite = Object::cast_to(object) || Object::cast_to(object);
+ _update_current_favorites();
+
update_tree();
}
@@ -4086,10 +4261,182 @@ void EditorInspector::_node_removed(Node *p_node) {
}
}
+void EditorInspector::_update_current_favorites() {
+ current_favorites.clear();
+ if (!can_favorite) {
+ return;
+ }
+
+ HashMap favorites = EditorSettings::get_singleton()->get_favorite_properties();
+
+ // Fetch script properties.
+ Ref