Browse Source

Merge pull request #97415 from YeldhamDev/i_love_all_my_properties_equally

Make possible to favorite properties in the inspector
Thaddeus Crews 9 months ago
parent
commit
ed3de25a9a

+ 7 - 0
doc/classes/EditorProperty.xml

@@ -132,6 +132,13 @@
 				Emitted when a property was deleted. Used internally.
 				Emitted when a property was deleted. Used internally.
 			</description>
 			</description>
 		</signal>
 		</signal>
+		<signal name="property_favorited">
+			<param index="0" name="property" type="StringName" />
+			<param index="1" name="favorited" type="bool" />
+			<description>
+				Emit it if you want to mark a property as favorited, making it appear at the top of the inspector.
+			</description>
+		</signal>
 		<signal name="property_keyed">
 		<signal name="property_keyed">
 			<param index="0" name="property" type="StringName" />
 			<param index="0" name="property" type="StringName" />
 			<description>
 			<description>

+ 380 - 22
editor/editor_inspector.cpp

@@ -931,10 +931,19 @@ float EditorProperty::get_name_split_ratio() const {
 	return split_ratio;
 	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) {
 void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) {
 	object = p_object;
 	object = p_object;
 	property = p_property;
 	property = p_property;
-	_update_pin_flags();
+
+	_update_flags();
 }
 }
 
 
 static bool _is_value_potential_override(Node *p_node, const String &p_property) {
 static bool _is_value_potential_override(Node *p_node, const String &p_property) {
@@ -953,12 +962,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;
 	can_pin = false;
 	pin_hidden = true;
 	pin_hidden = true;
+
 	if (read_only) {
 	if (read_only) {
 		return;
 		return;
 	}
 	}
+
 	if (Node *node = Object::cast_to<Node>(object)) {
 	if (Node *node = Object::cast_to<Node>(object)) {
 		// Avoid errors down the road by ignoring nodes which are not part of a scene
 		// Avoid errors down the road by ignoring nodes which are not part of a scene
 		if (!node->get_owner()) {
 		if (!node->get_owner()) {
@@ -1034,6 +1045,10 @@ void EditorProperty::menu_option(int p_option) {
 		case MENU_COPY_PROPERTY_PATH: {
 		case MENU_COPY_PROPERTY_PATH: {
 			DisplayServer::get_singleton()->clipboard_set(property_path);
 			DisplayServer::get_singleton()->clipboard_set(property_path);
 		} break;
 		} break;
+		case MENU_FAVORITE_PROPERTY: {
+			emit_signal(SNAME("property_favorited"), property, !favorited);
+			queue_redraw();
+		} break;
 		case MENU_PIN_VALUE: {
 		case MENU_PIN_VALUE: {
 			emit_signal(SNAME("property_pinned"), property, !pinned);
 			emit_signal(SNAME("property_pinned"), property, !pinned);
 			queue_redraw();
 			queue_redraw();
@@ -1091,6 +1106,7 @@ void EditorProperty::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING_NAME, "property")));
 	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_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_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_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("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")));
 	ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
@@ -1129,8 +1145,21 @@ void EditorProperty::_update_popup() {
 	menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
 	menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
 	menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal);
 	menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal);
 
 
-	if (!pin_hidden) {
+	if (can_favorite || !pin_hidden) {
 		menu->add_separator();
 		menu->add_separator();
+	}
+
+	if (can_favorite) {
+		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) {
 		if (can_pin) {
 		if (can_pin) {
 			menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin")), TTR("Pin Value"), MENU_PIN_VALUE);
 			menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin")), TTR("Pin Value"), MENU_PIN_VALUE);
 			menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned);
 			menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned);
@@ -1219,9 +1248,14 @@ void EditorInspectorPlugin::_bind_methods() {
 
 
 void EditorInspectorCategory::_notification(int p_what) {
 void EditorInspectorCategory::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
-		case NOTIFICATION_ENTER_TREE:
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
-			menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));
+			if (menu) {
+				if (is_favorite) {
+					menu->set_item_icon(menu->get_item_index(EditorInspector::MENU_UNFAVORITE_ALL), get_editor_theme_icon(SNAME("Unfavorite")));
+				} else {
+					menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));
+				}
+			}
 		} break;
 		} break;
 		case NOTIFICATION_DRAW: {
 		case NOTIFICATION_DRAW: {
 			Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
 			Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
@@ -1278,6 +1312,15 @@ Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) cons
 	return memnew(Control); // Make the standard tooltip invisible.
 	return memnew(Control); // Make the standard tooltip invisible.
 }
 }
 
 
+void EditorInspectorCategory::set_as_favorite(EditorInspector *p_for_inspector) {
+	is_favorite = true;
+
+	menu = memnew(PopupMenu);
+	menu->add_item(TTR("Unfavorite All"), EditorInspector::MENU_UNFAVORITE_ALL);
+	add_child(menu);
+	menu->connect(SceneStringName(id_pressed), callable_mp(p_for_inspector, &EditorInspector::_handle_menu_option));
+}
+
 Size2 EditorInspectorCategory::get_minimum_size() const {
 Size2 EditorInspectorCategory::get_minimum_size() const {
 	Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
 	Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
 	int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
 	int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
@@ -1306,7 +1349,7 @@ void EditorInspectorCategory::_handle_menu_option(int p_option) {
 }
 }
 
 
 void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
 void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
-	if (doc_class_name.is_empty()) {
+	if (!is_favorite && doc_class_name.is_empty()) {
 		return;
 		return;
 	}
 	}
 
 
@@ -1315,20 +1358,21 @@ void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
 		return;
 		return;
 	}
 	}
 
 
-	menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+	if (!is_favorite) {
+		if (!menu) {
+			menu = memnew(PopupMenu);
+			menu->add_icon_item(get_editor_theme_icon(SNAME("Help")), TTR("Open Documentation"), MENU_OPEN_DOCS);
+			add_child(menu);
+			menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
+		}
+		menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+	}
 
 
 	menu->set_position(get_screen_position() + mb_event->get_position());
 	menu->set_position(get_screen_position() + mb_event->get_position());
 	menu->reset_size();
 	menu->reset_size();
 	menu->popup();
 	menu->popup();
 }
 }
 
 
-EditorInspectorCategory::EditorInspectorCategory() {
-	menu = memnew(PopupMenu);
-	menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
-	menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS);
-	add_child(menu);
-}
-
 ////////////////////////////////////////////////
 ////////////////////////////////////////////////
 ////////////////////////////////////////////////
 ////////////////////////////////////////////////
 
 
@@ -1622,6 +1666,10 @@ void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) {
 	}
 	}
 }
 }
 
 
+String EditorInspectorSection::get_section() const {
+	return section;
+}
+
 VBoxContainer *EditorInspectorSection::get_vbox() {
 VBoxContainer *EditorInspectorSection::get_vbox() {
 	return vbox;
 	return vbox;
 }
 }
@@ -2675,7 +2723,13 @@ String EditorInspector::get_selected_path() const {
 void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped) {
 void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped) {
 	for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
 	for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
 		EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);
 		EditorProperty *ep = Object::cast_to<EditorProperty>(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) {
 		if (ep) {
 			ep->object = object;
 			ep->object = object;
@@ -2684,6 +2738,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_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_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
 			ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
 			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("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
 			ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
 			ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
 			ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
 			ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
@@ -2727,7 +2782,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn
 
 
 			ep->set_read_only(read_only);
 			ep->set_read_only(read_only);
 			ep->update_property();
 			ep->update_property();
-			ep->_update_pin_flags();
+			ep->_update_flags();
 			ep->update_editor_property_status();
 			ep->update_editor_property_status();
 			ep->set_deletable(deletable_properties);
 			ep->set_deletable(deletable_properties);
 			ep->update_cache();
 			ep->update_cache();
@@ -2837,6 +2892,7 @@ void EditorInspector::update_tree() {
 	String subgroup;
 	String subgroup;
 	String subgroup_base;
 	String subgroup_base;
 	int section_depth = 0;
 	int section_depth = 0;
+	bool disable_favorite = false;
 	VBoxContainer *category_vbox = nullptr;
 	VBoxContainer *category_vbox = nullptr;
 
 
 	List<PropertyInfo> plist;
 	List<PropertyInfo> plist;
@@ -2844,13 +2900,17 @@ void EditorInspector::update_tree() {
 
 
 	HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
 	HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
 	HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix;
 	HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix;
+	HashMap<String, HashMap<String, LocalVector<EditorProperty *>>> favorites_to_add;
 
 
 	Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
 	Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
 
 
 	// Get the lists of editors to add the beginning.
 	// Get the lists of editors to add the beginning.
 	for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
 	for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
 		ped->parse_begin(object);
 		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;
 	StringName doc_name;
@@ -2897,6 +2957,7 @@ void EditorInspector::update_tree() {
 			subgroup = "";
 			subgroup = "";
 			subgroup_base = "";
 			subgroup_base = "";
 			section_depth = 0;
 			section_depth = 0;
+			disable_favorite = false;
 
 
 			vbox_per_path.clear();
 			vbox_per_path.clear();
 			editor_inspector_array_per_prefix.clear();
 			editor_inspector_array_per_prefix.clear();
@@ -2960,6 +3021,11 @@ void EditorInspector::update_tree() {
 						} else {
 						} else {
 							category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
 							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;
+						}
 					}
 					}
 				}
 				}
 
 
@@ -3058,6 +3124,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()) {
 		if (!array_prefix.is_empty()) {
 			path = path.trim_prefix(array_prefix);
 			path = path.trim_prefix(array_prefix);
 			int char_index = path.find("/");
 			int char_index = path.find("/");
@@ -3449,6 +3520,7 @@ void EditorInspector::update_tree() {
 
 
 				ep->set_draw_warning(draw_warning);
 				ep->set_draw_warning(draw_warning);
 				ep->set_use_folding(use_folding);
 				ep->set_use_folding(use_folding);
+				ep->set_favoritable(can_favorite && !disable_favorite);
 				ep->set_checkable(checkable);
 				ep->set_checkable(checkable);
 				ep->set_checked(checked);
 				ep->set_checked(checked);
 				ep->set_keying(keying);
 				ep->set_keying(keying);
@@ -3456,7 +3528,12 @@ void EditorInspector::update_tree() {
 				ep->set_deletable(deletable_properties || p.name.begins_with("metadata/"));
 				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) {
 			if (ep) {
 				// Eventually, set other properties/signals after the property editor got added to the tree.
 				// Eventually, set other properties/signals after the property editor got added to the tree.
@@ -3465,6 +3542,7 @@ void EditorInspector::update_tree() {
 				ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
 				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_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_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_checked", callable_mp(this, &EditorInspector::_property_checked));
 				ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
 				ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
 				ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
 				ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
@@ -3495,7 +3573,7 @@ void EditorInspector::update_tree() {
 				ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);
 				ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);
 
 
 				ep->update_property();
 				ep->update_property();
-				ep->_update_pin_flags();
+				ep->_update_flags();
 				ep->update_editor_property_status();
 				ep->update_editor_property_status();
 				ep->update_cache();
 				ep->update_cache();
 
 
@@ -3506,6 +3584,79 @@ 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 (const KeyValue<String, HashMap<String, LocalVector<EditorProperty *>>> &KV : favorites_to_add) {
+			String section_name = KV.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 (const KeyValue<String, LocalVector<EditorProperty *>> &KV2 : KV.value) {
+				section_name = KV2.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 : KV2.value) {
+					vbox->add_child(ep);
+				}
+			}
+		}
+
+		// Show a separator if there's no category to clearly divide the properties.
+		favorites_separator->hide();
+		if (main_vbox->get_child_count() > 0) {
+			EditorInspectorCategory *category = Object::cast_to<EditorInspectorCategory>(main_vbox->get_child(0));
+			if (!category) {
+				favorites_separator->show();
+			}
+		}
+
+		// Clean up empty sections.
+		for (List<EditorInspectorSection *>::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")) {
 	if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {
 		// Add 4px of spacing between the "Add Metadata" button and the content above it.
 		// Add 4px of spacing between the "Add Metadata" button and the content above it.
 		Control *spacer = memnew(Control);
 		Control *spacer = memnew(Control);
@@ -3548,6 +3699,19 @@ void EditorInspector::update_property(const String &p_prop) {
 }
 }
 
 
 void EditorInspector::_clear(bool p_hide_plugins) {
 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()) {
 	while (main_vbox->get_child_count()) {
 		memdelete(main_vbox->get_child(0));
 		memdelete(main_vbox->get_child(0));
 	}
 	}
@@ -3594,6 +3758,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
 			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));
 		object->connect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback));
+
+		can_favorite = Object::cast_to<Node>(object) || Object::cast_to<Resource>(object);
+		_update_current_favorites();
+
 		update_tree();
 		update_tree();
 	}
 	}
 
 
@@ -4088,10 +4256,164 @@ void EditorInspector::_node_removed(Node *p_node) {
 	}
 	}
 }
 }
 
 
+void EditorInspector::_update_current_favorites() {
+	current_favorites.clear();
+	if (!can_favorite) {
+		return;
+	}
+
+	HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+
+	// Fetch script properties.
+	Ref<Script> scr = object->get_script();
+	if (scr.is_valid()) {
+		List<PropertyInfo> plist;
+		// FIXME: Only properties from a saved script will be available, unsaved ones will be ignored.
+		// Can cause a little wonkiness, while nothing serious, would be nice to find a way to get
+		// unsaved ones without needing to get the entire property list of an object.
+		scr->get_script_property_list(&plist);
+
+		String path;
+		HashMap<String, LocalVector<String>> props;
+
+		for (PropertyInfo &p : plist) {
+			if (p.usage & PROPERTY_USAGE_CATEGORY) {
+				path = favorites.has(p.hint_string) ? p.hint_string : String();
+			} else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && !path.is_empty()) {
+				props[path].push_back(p.name);
+			}
+		}
+
+		// Add favorited properties while removing invalid ones.
+		bool invalid_props = false;
+		for (const KeyValue<String, LocalVector<String>> &KV : props) {
+			path = KV.key;
+			for (int i = 0; i < favorites[path].size(); i++) {
+				String prop = favorites[path][i];
+				if (KV.value.has(prop)) {
+					current_favorites.append(prop);
+				} else {
+					invalid_props = true;
+					favorites[path].erase(prop);
+					i--;
+				}
+			}
+
+			if (favorites[path].is_empty()) {
+				favorites.erase(path);
+			}
+		}
+
+		if (invalid_props) {
+			EditorSettings::get_singleton()->set_favorite_properties(favorites);
+		}
+	}
+
+	// Fetch built-in properties.
+	StringName class_name = object->get_class_name();
+	for (const KeyValue<String, PackedStringArray> &KV : favorites) {
+		if (ClassDB::is_parent_class(class_name, KV.key)) {
+			current_favorites.append_array(KV.value);
+		}
+	}
+}
+
+void EditorInspector::_set_property_favorited(const String &p_path, bool p_favorited) {
+	if (!object) {
+		return;
+	}
+
+	StringName class_name = object->get_class_name();
+	while (!class_name.is_empty()) {
+		bool has_prop = ClassDB::has_property(class_name, p_path, true);
+		if (has_prop) {
+			break;
+		}
+
+		class_name = ClassDB::get_parent_class_nocheck(class_name);
+	}
+
+	if (class_name.is_empty()) {
+		Ref<Script> scr = object->get_script();
+		if (scr.is_valid()) {
+			List<PropertyInfo> plist;
+			scr->get_script_property_list(&plist);
+
+			String path;
+			for (PropertyInfo &p : plist) {
+				if (p.usage & PROPERTY_USAGE_CATEGORY) {
+					path = p.hint_string;
+				} else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && p.name == p_path) {
+					class_name = path;
+					break;
+				}
+			}
+		}
+
+		ERR_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.");
+	}
+
+	HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+	if (p_favorited) {
+		current_favorites.append(p_path);
+		favorites[class_name].append(p_path);
+	} else {
+		current_favorites.erase(p_path);
+
+		if (favorites.has(class_name) && favorites[class_name].has(p_path)) {
+			if (favorites[class_name].size() > 1) {
+				favorites[class_name].erase(p_path);
+			} else {
+				favorites.erase(class_name);
+			}
+		}
+	}
+	EditorSettings::get_singleton()->set_favorite_properties(favorites);
+
+	update_tree();
+}
+
+void EditorInspector::_clear_current_favorites() {
+	current_favorites.clear();
+
+	HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+
+	Ref<Script> scr = object->get_script();
+	if (scr.is_valid()) {
+		List<PropertyInfo> plist;
+		scr->get_script_property_list(&plist);
+
+		for (PropertyInfo &p : plist) {
+			if (p.usage & PROPERTY_USAGE_CATEGORY && favorites.has(p.hint_string)) {
+				favorites.erase(p.hint_string);
+			}
+		}
+	}
+
+	StringName class_name = object->get_class_name();
+	while (class_name) {
+		if (favorites.has(class_name)) {
+			favorites.erase(class_name);
+		}
+
+		class_name = ClassDB::get_parent_class(class_name);
+	}
+
+	EditorSettings::get_singleton()->set_favorite_properties(favorites);
+	update_tree();
+}
+
 void EditorInspector::_notification(int p_what) {
 void EditorInspector::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
 		case NOTIFICATION_THEME_CHANGED: {
-			main_vbox->add_theme_constant_override("separation", get_theme_constant(SNAME("v_separation"), SNAME("EditorInspector")));
+			favorites_category->icon = get_editor_theme_icon(SNAME("Favorites"));
+
+			int separation = get_theme_constant(SNAME("v_separation"), SNAME("EditorInspector"));
+			base_vbox->add_theme_constant_override("separation", separation);
+			begin_vbox->add_theme_constant_override("separation", separation);
+			favorites_section->add_theme_constant_override("separation", separation);
+			favorites_groups_vbox->add_theme_constant_override("separation", separation);
+			main_vbox->add_theme_constant_override("separation", separation);
 		} break;
 		} break;
 
 
 		case NOTIFICATION_READY: {
 		case NOTIFICATION_READY: {
@@ -4189,6 +4511,7 @@ void EditorInspector::_notification(int p_what) {
 void EditorInspector::_changed_callback() {
 void EditorInspector::_changed_callback() {
 	//this is called when property change is notified via notify_property_list_changed()
 	//this is called when property change is notified via notify_property_list_changed()
 	if (object != nullptr) {
 	if (object != nullptr) {
+		_update_current_favorites();
 		_edit_request_change(object, String());
 		_edit_request_change(object, String());
 	}
 	}
 }
 }
@@ -4278,6 +4601,14 @@ void EditorInspector::_add_meta_confirm() {
 	undo_redo->commit_action();
 	undo_redo->commit_action();
 }
 }
 
 
+void EditorInspector::_handle_menu_option(int p_option) {
+	switch (p_option) {
+		case MENU_UNFAVORITE_ALL:
+			_clear_current_favorites();
+			break;
+	}
+}
+
 void EditorInspector::_bind_methods() {
 void EditorInspector::_bind_methods() {
 	ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
 	ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
 	ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path);
 	ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path);
@@ -4296,9 +4627,36 @@ void EditorInspector::_bind_methods() {
 
 
 EditorInspector::EditorInspector() {
 EditorInspector::EditorInspector() {
 	object = nullptr;
 	object = nullptr;
+
+	base_vbox = memnew(VBoxContainer);
+	base_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+	add_child(base_vbox);
+
+	begin_vbox = memnew(VBoxContainer);
+	base_vbox->add_child(begin_vbox);
+	begin_vbox->hide();
+
+	favorites_section = memnew(VBoxContainer);
+	base_vbox->add_child(favorites_section);
+	favorites_section->hide();
+
+	favorites_category = memnew(EditorInspectorCategory);
+	favorites_category->set_as_favorite(this);
+	favorites_section->add_child(favorites_category);
+	favorites_category->label = TTR("Favorites");
+
+	favorites_vbox = memnew(VBoxContainer);
+	favorites_section->add_child(favorites_vbox);
+	favorites_groups_vbox = memnew(VBoxContainer);
+	favorites_section->add_child(favorites_groups_vbox);
+
+	favorites_separator = memnew(HSeparator);
+	favorites_section->add_child(favorites_separator);
+	favorites_separator->hide();
+
 	main_vbox = memnew(VBoxContainer);
 	main_vbox = memnew(VBoxContainer);
-	main_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
-	add_child(main_vbox);
+	base_vbox->add_child(main_vbox);
+
 	set_horizontal_scroll_mode(SCROLL_MODE_DISABLED);
 	set_horizontal_scroll_mode(SCROLL_MODE_DISABLED);
 	set_follow_focus(true);
 	set_follow_focus(true);
 
 

+ 37 - 3
editor/editor_inspector.h

@@ -41,6 +41,7 @@ class Button;
 class ConfirmationDialog;
 class ConfirmationDialog;
 class EditorInspector;
 class EditorInspector;
 class EditorValidationPanel;
 class EditorValidationPanel;
+class HSeparator;
 class LineEdit;
 class LineEdit;
 class MarginContainer;
 class MarginContainer;
 class OptionButton;
 class OptionButton;
@@ -64,6 +65,7 @@ public:
 		MENU_COPY_VALUE,
 		MENU_COPY_VALUE,
 		MENU_PASTE_VALUE,
 		MENU_PASTE_VALUE,
 		MENU_COPY_PROPERTY_PATH,
 		MENU_COPY_PROPERTY_PATH,
+		MENU_FAVORITE_PROPERTY,
 		MENU_PIN_VALUE,
 		MENU_PIN_VALUE,
 		MENU_OPEN_DOCUMENTATION,
 		MENU_OPEN_DOCUMENTATION,
 	};
 	};
@@ -112,6 +114,9 @@ private:
 	bool pin_hidden = false;
 	bool pin_hidden = false;
 	bool pinned = false;
 	bool pinned = false;
 
 
+	bool can_favorite = false;
+	bool favorited = false;
+
 	bool use_folding = false;
 	bool use_folding = false;
 	bool draw_top_bg = true;
 	bool draw_top_bg = true;
 
 
@@ -134,7 +139,7 @@ private:
 	GDVIRTUAL0(_update_property)
 	GDVIRTUAL0(_update_property)
 	GDVIRTUAL1(_set_read_only, bool)
 	GDVIRTUAL1(_set_read_only, bool)
 
 
-	void _update_pin_flags();
+	void _update_flags();
 
 
 protected:
 protected:
 	bool has_borders = false;
 	bool has_borders = false;
@@ -218,6 +223,9 @@ public:
 	void set_name_split_ratio(float p_ratio);
 	void set_name_split_ratio(float p_ratio);
 	float get_name_split_ratio() const;
 	float get_name_split_ratio() const;
 
 
+	void set_favoritable(bool p_favoritable);
+	bool is_favoritable() const;
+
 	void set_object_and_property(Object *p_object, const StringName &p_property);
 	void set_object_and_property(Object *p_object, const StringName &p_property);
 	virtual Control *make_custom_tooltip(const String &p_text) const override;
 	virtual Control *make_custom_tooltip(const String &p_text) const override;
 
 
@@ -285,6 +293,7 @@ class EditorInspectorCategory : public Control {
 	String label;
 	String label;
 	String doc_class_name;
 	String doc_class_name;
 	PopupMenu *menu = nullptr;
 	PopupMenu *menu = nullptr;
+	bool is_favorite = false;
 
 
 	void _handle_menu_option(int p_option);
 	void _handle_menu_option(int p_option);
 
 
@@ -293,10 +302,10 @@ protected:
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 	virtual void gui_input(const Ref<InputEvent> &p_event) override;
 
 
 public:
 public:
+	void set_as_favorite(EditorInspector *p_for_inspector);
+
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
 	virtual Control *make_custom_tooltip(const String &p_text) const override;
 	virtual Control *make_custom_tooltip(const String &p_text) const override;
-
-	EditorInspectorCategory();
 };
 };
 
 
 class EditorInspectorSection : public Container {
 class EditorInspectorSection : public Container {
@@ -331,6 +340,7 @@ public:
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
 
 
 	void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0, int p_level = 1);
 	void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0, int p_level = 1);
+	String get_section() const;
 	VBoxContainer *get_vbox();
 	VBoxContainer *get_vbox();
 	void unfold();
 	void unfold();
 	void fold();
 	void fold();
@@ -480,13 +490,31 @@ public:
 class EditorInspector : public ScrollContainer {
 class EditorInspector : public ScrollContainer {
 	GDCLASS(EditorInspector, ScrollContainer);
 	GDCLASS(EditorInspector, ScrollContainer);
 
 
+	friend class EditorInspectorCategory;
+
 	enum {
 	enum {
 		MAX_PLUGINS = 1024
 		MAX_PLUGINS = 1024
 	};
 	};
 	static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
 	static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
 	static int inspector_plugin_count;
 	static int inspector_plugin_count;
 
 
+	// Right-click context menu options.
+	enum ClassMenuOption {
+		MENU_UNFAVORITE_ALL,
+	};
+
+	bool can_favorite = false;
+	PackedStringArray current_favorites;
+	VBoxContainer *favorites_section = nullptr;
+	EditorInspectorCategory *favorites_category = nullptr;
+	VBoxContainer *favorites_vbox = nullptr;
+	VBoxContainer *favorites_groups_vbox = nullptr;
+	HSeparator *favorites_separator = nullptr;
+
 	EditorInspector *root_inspector = nullptr;
 	EditorInspector *root_inspector = nullptr;
+
+	VBoxContainer *base_vbox = nullptr;
+	VBoxContainer *begin_vbox = nullptr;
 	VBoxContainer *main_vbox = nullptr;
 	VBoxContainer *main_vbox = nullptr;
 
 
 	// Map used to cache the instantiated editors.
 	// Map used to cache the instantiated editors.
@@ -557,6 +585,10 @@ class EditorInspector : public ScrollContainer {
 	void _property_selected(const String &p_path, int p_focusable);
 	void _property_selected(const String &p_path, int p_focusable);
 	void _object_id_selected(const String &p_path, ObjectID p_id);
 	void _object_id_selected(const String &p_path, ObjectID p_id);
 
 
+	void _update_current_favorites();
+	void _set_property_favorited(const String &p_path, bool p_favorited);
+	void _clear_current_favorites();
+
 	void _node_removed(Node *p_node);
 	void _node_removed(Node *p_node);
 
 
 	HashMap<StringName, int> per_array_page;
 	HashMap<StringName, int> per_array_page;
@@ -584,6 +616,8 @@ class EditorInspector : public ScrollContainer {
 	void _add_meta_confirm();
 	void _add_meta_confirm();
 	void _show_add_meta_dialog();
 	void _show_add_meta_dialog();
 
 
+	void _handle_menu_option(int p_option);
+
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
 	void _notification(int p_what);
 	void _notification(int p_what);

+ 45 - 1
editor/editor_settings.cpp

@@ -1496,10 +1496,26 @@ void EditorSettings::set_favorites(const Vector<String> &p_favorites) {
 	}
 	}
 }
 }
 
 
+void EditorSettings::set_favorite_properties(const HashMap<String, PackedStringArray> &p_favorite_properties) {
+	favorite_properties = p_favorite_properties;
+	String favorite_properties_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorite_properties");
+
+	Ref<ConfigFile> cf;
+	cf.instantiate();
+	for (const KeyValue<String, PackedStringArray> &kv : p_favorite_properties) {
+		cf->set_value(kv.key, "properties", kv.value);
+	}
+	cf->save(favorite_properties_file);
+}
+
 Vector<String> EditorSettings::get_favorites() const {
 Vector<String> EditorSettings::get_favorites() const {
 	return favorites;
 	return favorites;
 }
 }
 
 
+HashMap<String, PackedStringArray> EditorSettings::get_favorite_properties() const {
+	return favorite_properties;
+}
+
 void EditorSettings::set_recent_dirs(const Vector<String> &p_recent_dirs) {
 void EditorSettings::set_recent_dirs(const Vector<String> &p_recent_dirs) {
 	recent_dirs = p_recent_dirs;
 	recent_dirs = p_recent_dirs;
 	String recent_dirs_file;
 	String recent_dirs_file;
@@ -1522,23 +1538,51 @@ Vector<String> EditorSettings::get_recent_dirs() const {
 
 
 void EditorSettings::load_favorites_and_recent_dirs() {
 void EditorSettings::load_favorites_and_recent_dirs() {
 	String favorites_file;
 	String favorites_file;
+	String favorite_properties_file;
 	String recent_dirs_file;
 	String recent_dirs_file;
 	if (Engine::get_singleton()->is_project_manager_hint()) {
 	if (Engine::get_singleton()->is_project_manager_hint()) {
 		favorites_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_dirs");
 		favorites_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_dirs");
+		favorite_properties_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_properties");
 		recent_dirs_file = EditorPaths::get_singleton()->get_config_dir().path_join("recent_dirs");
 		recent_dirs_file = EditorPaths::get_singleton()->get_config_dir().path_join("recent_dirs");
 	} else {
 	} else {
 		favorites_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites");
 		favorites_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites");
+		favorite_properties_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorite_properties");
 		recent_dirs_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("recent_dirs");
 		recent_dirs_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("recent_dirs");
 	}
 	}
+
+	/// File Favorites
+
 	Ref<FileAccess> f = FileAccess::open(favorites_file, FileAccess::READ);
 	Ref<FileAccess> f = FileAccess::open(favorites_file, FileAccess::READ);
 	if (f.is_valid()) {
 	if (f.is_valid()) {
 		String line = f->get_line().strip_edges();
 		String line = f->get_line().strip_edges();
 		while (!line.is_empty()) {
 		while (!line.is_empty()) {
-			favorites.push_back(line);
+			favorites.append(line);
 			line = f->get_line().strip_edges();
 			line = f->get_line().strip_edges();
 		}
 		}
 	}
 	}
 
 
+	/// Inspector Favorites
+
+	Ref<ConfigFile> cf;
+	cf.instantiate();
+	if (cf->load(favorite_properties_file) == OK) {
+		List<String> secs;
+		cf->get_sections(&secs);
+
+		for (String &E : secs) {
+			PackedStringArray properties = PackedStringArray(cf->get_value(E, "properties"));
+			if (EditorNode::get_editor_data().is_type_recognized(E) || ResourceLoader::exists(E, "Script")) {
+				for (const String &property : properties) {
+					if (!favorite_properties[E].has(property)) {
+						favorite_properties[E].push_back(property);
+					}
+				}
+			}
+		}
+	}
+
+	/// Recent Directories
+
 	f = FileAccess::open(recent_dirs_file, FileAccess::READ);
 	f = FileAccess::open(recent_dirs_file, FileAccess::READ);
 	if (f.is_valid()) {
 	if (f.is_valid()) {
 		String line = f->get_line().strip_edges();
 		String line = f->get_line().strip_edges();

+ 3 - 0
editor/editor_settings.h

@@ -102,6 +102,7 @@ private:
 	HashMap<String, List<Ref<InputEvent>>> builtin_action_overrides;
 	HashMap<String, List<Ref<InputEvent>>> builtin_action_overrides;
 
 
 	Vector<String> favorites;
 	Vector<String> favorites;
+	HashMap<String, PackedStringArray> favorite_properties;
 	Vector<String> recent_dirs;
 	Vector<String> recent_dirs;
 
 
 	bool save_changed_setting = true;
 	bool save_changed_setting = true;
@@ -176,6 +177,8 @@ public:
 
 
 	void set_favorites(const Vector<String> &p_favorites);
 	void set_favorites(const Vector<String> &p_favorites);
 	Vector<String> get_favorites() const;
 	Vector<String> get_favorites() const;
+	void set_favorite_properties(const HashMap<String, PackedStringArray> &p_favorite_properties);
+	HashMap<String, PackedStringArray> get_favorite_properties() const;
 	void set_recent_dirs(const Vector<String> &p_recent_dirs);
 	void set_recent_dirs(const Vector<String> &p_recent_dirs);
 	Vector<String> get_recent_dirs() const;
 	Vector<String> get_recent_dirs() const;
 	void load_favorites_and_recent_dirs();
 	void load_favorites_and_recent_dirs();

+ 17 - 11
editor/filesystem_dock.cpp

@@ -1641,21 +1641,27 @@ String FileSystemDock::_get_unique_name(const FileOrFolder &p_entry, const Strin
 	return new_path;
 	return new_path;
 }
 }
 
 
-void FileSystemDock::_update_favorites_list_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const {
-	Vector<String> favorites_list = EditorSettings::get_singleton()->get_favorites();
-	Vector<String> new_favorites;
-
-	for (const String &old_path : favorites_list) {
+void FileSystemDock::_update_favorites_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const {
+	Vector<String> favorite_files = EditorSettings::get_singleton()->get_favorites();
+	Vector<String> new_favorite_files;
+	for (const String &old_path : favorite_files) {
 		if (p_folders_renames.has(old_path)) {
 		if (p_folders_renames.has(old_path)) {
-			new_favorites.push_back(p_folders_renames[old_path]);
+			new_favorite_files.push_back(p_folders_renames[old_path]);
 		} else if (p_files_renames.has(old_path)) {
 		} else if (p_files_renames.has(old_path)) {
-			new_favorites.push_back(p_files_renames[old_path]);
+			new_favorite_files.push_back(p_files_renames[old_path]);
 		} else {
 		} else {
-			new_favorites.push_back(old_path);
+			new_favorite_files.push_back(old_path);
 		}
 		}
 	}
 	}
+	EditorSettings::get_singleton()->set_favorites(new_favorite_files);
 
 
-	EditorSettings::get_singleton()->set_favorites(new_favorites);
+	HashMap<String, PackedStringArray> favorite_properties = EditorSettings::get_singleton()->get_favorite_properties();
+	for (const KeyValue<String, String> &KV : p_files_renames) {
+		if (favorite_properties.has(KV.key)) {
+			favorite_properties.replace_key(KV.key, KV.value);
+		}
+	}
+	EditorSettings::get_singleton()->set_favorite_properties(favorite_properties);
 }
 }
 
 
 void FileSystemDock::_make_scene_confirm() {
 void FileSystemDock::_make_scene_confirm() {
@@ -1798,7 +1804,7 @@ void FileSystemDock::_rename_operation_confirm() {
 	_update_resource_paths_after_move(file_renames, uids);
 	_update_resource_paths_after_move(file_renames, uids);
 	_update_dependencies_after_move(file_renames, file_owners);
 	_update_dependencies_after_move(file_renames, file_owners);
 	_update_project_settings_after_move(file_renames, folder_renames);
 	_update_project_settings_after_move(file_renames, folder_renames);
-	_update_favorites_list_after_move(file_renames, folder_renames);
+	_update_favorites_after_move(file_renames, folder_renames);
 
 
 	EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
 	EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
 
 
@@ -1959,7 +1965,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_cop
 			_update_resource_paths_after_move(file_renames, uids);
 			_update_resource_paths_after_move(file_renames, uids);
 			_update_dependencies_after_move(file_renames, file_owners);
 			_update_dependencies_after_move(file_renames, file_owners);
 			_update_project_settings_after_move(file_renames, folder_renames);
 			_update_project_settings_after_move(file_renames, folder_renames);
-			_update_favorites_list_after_move(file_renames, folder_renames);
+			_update_favorites_after_move(file_renames, folder_renames);
 
 
 			EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
 			EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
 
 

+ 1 - 1
editor/filesystem_dock.h

@@ -275,7 +275,7 @@ private:
 	void _before_move(HashMap<String, ResourceUID::ID> &r_uids, HashSet<String> &r_file_owners) const;
 	void _before_move(HashMap<String, ResourceUID::ID> &r_uids, HashSet<String> &r_file_owners) const;
 	void _update_dependencies_after_move(const HashMap<String, String> &p_renames, const HashSet<String> &p_file_owners) const;
 	void _update_dependencies_after_move(const HashMap<String, String> &p_renames, const HashSet<String> &p_file_owners) const;
 	void _update_resource_paths_after_move(const HashMap<String, String> &p_renames, const HashMap<String, ResourceUID::ID> &p_uids) const;
 	void _update_resource_paths_after_move(const HashMap<String, String> &p_renames, const HashMap<String, ResourceUID::ID> &p_uids) const;
-	void _update_favorites_list_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const;
+	void _update_favorites_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const;
 	void _update_project_settings_after_move(const HashMap<String, String> &p_renames, const HashMap<String, String> &p_folders_renames);
 	void _update_project_settings_after_move(const HashMap<String, String> &p_renames, const HashMap<String, String> &p_folders_renames);
 	String _get_unique_name(const FileOrFolder &p_entry, const String &p_at_path);
 	String _get_unique_name(const FileOrFolder &p_entry, const String &p_at_path);
 
 

+ 1 - 0
editor/icons/Unfavorite.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M 8 1.6992188 L 5.6269531 5.796875 L 1 6.8945312 L 4.2363281 10.302734 L 3.8769531 14.976562 L 8.0175781 12.998047 L 12.173828 14.941406 L 11.777344 10.287109 L 15 6.8945312 L 10.373047 5.796875 L 8 1.6992188 z M 8 4.2773438 L 9.4882812 6.8457031 L 12.388672 7.5332031 L 10.369141 9.6601562 L 10.617188 12.576172 L 8.0097656 11.359375 L 5.4160156 12.599609 L 5.640625 9.6699219 L 3.6113281 7.5332031 L 6.5117188 6.8457031 L 8 4.2773438 z"/></svg>