Przeglądaj źródła

Allow to override editor settings per project

kobewi 2 lat temu
rodzic
commit
b41d6ecf8c

+ 25 - 1
core/config/project_settings.cpp

@@ -482,6 +482,18 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const {
 			pi.name = base.name;
 			pi.usage = base.flags;
 			p_list->push_back(pi);
+#ifdef TOOLS_ENABLED
+		} else if (base.name.begins_with(EDITOR_SETTING_OVERRIDE_PREFIX)) {
+			PropertyInfo info(base.type, base.name, PROPERTY_HINT_NONE, "", base.flags);
+
+			const PropertyInfo *pi = editor_settings_info.getptr(base.name.trim_prefix(EDITOR_SETTING_OVERRIDE_PREFIX));
+			if (pi) {
+				info.usage = pi->usage;
+				info.hint = pi->hint;
+				info.hint_string = pi->hint_string;
+			}
+			p_list->push_back(info);
+#endif
 		} else {
 			p_list->push_back(PropertyInfo(base.type, base.name, PROPERTY_HINT_NONE, "", base.flags));
 		}
@@ -1263,7 +1275,7 @@ bool ProjectSettings::is_project_loaded() const {
 }
 
 bool ProjectSettings::_property_can_revert(const StringName &p_name) const {
-	return props.has(p_name);
+	return props.has(p_name) && !String(p_name).begins_with(EDITOR_SETTING_OVERRIDE_PREFIX);
 }
 
 bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const {
@@ -1449,6 +1461,18 @@ void ProjectSettings::get_argument_options(const StringName &p_function, int p_i
 }
 #endif
 
+void ProjectSettings::set_editor_setting_override(const String &p_setting, const Variant &p_value) {
+	set_setting(EDITOR_SETTING_OVERRIDE_PREFIX + p_setting, p_value);
+}
+
+bool ProjectSettings::has_editor_setting_override(const String &p_setting) const {
+	return has_setting(EDITOR_SETTING_OVERRIDE_PREFIX + p_setting);
+}
+
+Variant ProjectSettings::get_editor_setting_override(const String &p_setting) const {
+	return get_setting(EDITOR_SETTING_OVERRIDE_PREFIX + p_setting);
+}
+
 void ProjectSettings::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("has_setting", "name"), &ProjectSettings::has_setting);
 	ClassDB::bind_method(D_METHOD("set_setting", "name", "value"), &ProjectSettings::set_setting);

+ 9 - 0
core/config/project_settings.h

@@ -49,6 +49,7 @@ class ProjectSettings : public Object {
 public:
 	typedef HashMap<String, Variant> CustomMap;
 	static inline const String PROJECT_DATA_DIR_NAME_SUFFIX = "godot";
+	static inline const String EDITOR_SETTING_OVERRIDE_PREFIX = "editor_overrides/";
 
 	// Properties that are not for built in values begin from this value, so builtin ones are displayed first.
 	constexpr static const int32_t NO_BUILTIN_ORDER_BASE = 1 << 16;
@@ -153,6 +154,10 @@ protected:
 public:
 	static const int CONFIG_VERSION = 5;
 
+#ifdef TOOLS_ENABLED
+	HashMap<String, PropertyInfo> editor_settings_info;
+#endif
+
 	void set_setting(const String &p_setting, const Variant &p_value);
 	Variant get_setting(const String &p_setting, const Variant &p_default_value = Variant()) const;
 	TypedArray<Dictionary> get_global_class_list();
@@ -229,6 +234,10 @@ public:
 	virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
 #endif
 
+	void set_editor_setting_override(const String &p_setting, const Variant &p_value);
+	bool has_editor_setting_override(const String &p_setting) const;
+	Variant get_editor_setting_override(const String &p_setting) const;
+
 	ProjectSettings();
 	ProjectSettings(const String &p_path);
 	~ProjectSettings();

+ 5 - 0
doc/classes/EditorProperty.xml

@@ -204,6 +204,11 @@
 				Emit it if you want to key a property with a single value.
 			</description>
 		</signal>
+		<signal name="property_overridden">
+			<description>
+				Emitted when a setting override for the current project is requested.
+			</description>
+		</signal>
 		<signal name="property_pinned">
 			<param index="0" name="property" type="StringName" />
 			<param index="1" name="pinned" type="bool" />

+ 12 - 2
editor/editor_inspector.cpp

@@ -1296,6 +1296,9 @@ void EditorProperty::menu_option(int p_option) {
 		case MENU_COPY_PROPERTY_PATH: {
 			DisplayServer::get_singleton()->clipboard_set(property_path);
 		} break;
+		case MENU_OVERRIDE_FOR_PROJECT: {
+			emit_signal(SNAME("property_overridden"));
+		} break;
 		case MENU_FAVORITE_PROPERTY: {
 			emit_signal(SNAME("property_favorited"), property, !favorited);
 			queue_redraw();
@@ -1396,6 +1399,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_overridden"));
 	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")));
@@ -1462,10 +1466,13 @@ void EditorProperty::_update_popup() {
 		}
 		menu->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."));
 	}
-	if (deletable || can_revert) {
+	if (deletable || can_revert || can_override) {
 		menu->add_separator();
+		if (can_override) {
+			menu->add_icon_item(get_editor_theme_icon(SNAME("Override")), TTRC("Override for Project"), MENU_OVERRIDE_FOR_PROJECT);
+		}
 		if (deletable) {
-			menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Property"), MENU_PIN_VALUE);
+			menu->add_icon_item(get_editor_theme_icon(SNAME("Remove")), TTR("Delete Property"), MENU_DELETE);
 		}
 		if (can_revert) {
 			menu->add_icon_item(get_editor_theme_icon(SNAME("Reload")), TTR("Revert Value"), MENU_REVERT_VALUE);
@@ -4924,6 +4931,9 @@ void EditorInspector::_property_deleted(const String &p_path) {
 		undo_redo->commit_action();
 	}
 
+	if (restart_request_props.has(p_path)) {
+		emit_signal(SNAME("restart_requested"));
+	}
 	emit_signal(SNAME("property_deleted"), p_path);
 }
 

+ 2 - 0
editor/editor_inspector.h

@@ -65,6 +65,7 @@ public:
 		MENU_COPY_VALUE,
 		MENU_PASTE_VALUE,
 		MENU_COPY_PROPERTY_PATH,
+		MENU_OVERRIDE_FOR_PROJECT,
 		MENU_FAVORITE_PROPERTY,
 		MENU_PIN_VALUE,
 		MENU_DELETE,
@@ -147,6 +148,7 @@ private:
 
 protected:
 	bool has_borders = false;
+	bool can_override = false;
 
 	void _notification(int p_what);
 	static void _bind_methods();

+ 15 - 0
editor/editor_node.cpp

@@ -770,6 +770,12 @@ void EditorNode::_notification(int p_what) {
 				EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorNode::_execute_upgrades), CONNECT_ONE_SHOT);
 				EditorFileSystem::get_singleton()->scan();
 			}
+
+			if (settings_overrides_changed) {
+				EditorSettings::get_singleton()->notify_changes();
+				EditorSettings::get_singleton()->emit_signal(SNAME("settings_changed"));
+				settings_overrides_changed = false;
+			}
 		} break;
 
 		case NOTIFICATION_ENTER_TREE: {
@@ -7368,6 +7374,15 @@ GameViewPluginBase *get_game_view_plugin() {
 }
 #endif
 
+void EditorNode::open_setting_override(const String &p_property) {
+	editor_settings_dialog->hide();
+	project_settings_editor->popup_for_override(p_property);
+}
+
+void EditorNode::notify_settings_overrides_changed() {
+	settings_overrides_changed = true;
+}
+
 EditorNode::EditorNode() {
 	DEV_ASSERT(!singleton);
 	singleton = this;

+ 4 - 0
editor/editor_node.h

@@ -448,6 +448,7 @@ private:
 	bool convert_old = false;
 	bool immediate_dialog_confirmed = false;
 	bool restoring_scenes = false;
+	bool settings_overrides_changed = false;
 	bool unsaved_cache = true;
 
 	bool requested_first_scan = false;
@@ -977,6 +978,9 @@ public:
 	void restart_editor(bool p_goto_project_manager = false);
 	void unload_editor_addons();
 
+	void open_setting_override(const String &p_property);
+	void notify_settings_overrides_changed();
+
 	void dim_editor(bool p_dimming);
 	bool is_editor_dimmed() const;
 

+ 2 - 0
editor/editor_sectioned_inspector.cpp

@@ -141,6 +141,7 @@ public:
 
 void SectionedInspector::_bind_methods() {
 	ClassDB::bind_method("update_category_list", &SectionedInspector::update_category_list);
+	ADD_SIGNAL(MethodInfo("category_changed", PropertyInfo(Variant::STRING, "new_category")));
 }
 
 void SectionedInspector::_section_selected() {
@@ -152,6 +153,7 @@ void SectionedInspector::_section_selected() {
 	filter->set_section(selected_category, sections->get_selected()->get_first_child() == nullptr);
 	inspector->set_property_prefix(selected_category + "/");
 	inspector->set_v_scroll(0);
+	emit_signal(SNAME("category_changed"), selected_category);
 }
 
 void SectionedInspector::set_current_section(const String &p_section) {

+ 5 - 2
editor/editor_settings.cpp

@@ -1347,7 +1347,7 @@ fail:
 }
 
 void EditorSettings::setup_language() {
-	String lang = get("interface/editor/editor_language");
+	String lang = _EDITOR_GET("interface/editor/editor_language");
 
 	if (lang == "en") {
 		TranslationServer::get_singleton()->set_locale(lang);
@@ -1460,6 +1460,9 @@ void EditorSettings::set_setting(const String &p_setting, const Variant &p_value
 
 Variant EditorSettings::get_setting(const String &p_setting) const {
 	_THREAD_SAFE_METHOD_
+	if (ProjectSettings::get_singleton()->has_editor_setting_override(p_setting)) {
+		return ProjectSettings::get_singleton()->get_editor_setting_override(p_setting);
+	}
 	return get(p_setting);
 }
 
@@ -1534,7 +1537,7 @@ Variant _EDITOR_DEF(const String &p_setting, const Variant &p_default, bool p_re
 
 Variant _EDITOR_GET(const String &p_setting) {
 	ERR_FAIL_COND_V(!EditorSettings::get_singleton() || !EditorSettings::get_singleton()->has_setting(p_setting), Variant());
-	return EditorSettings::get_singleton()->get(p_setting);
+	return EditorSettings::get_singleton()->get_setting(p_setting);
 }
 
 bool EditorSettings::_property_can_revert(const StringName &p_name) const {

+ 121 - 0
editor/editor_settings_dialog.cpp

@@ -30,6 +30,7 @@
 
 #include "editor_settings_dialog.h"
 
+#include "core/config/project_settings.h"
 #include "core/input/input_map.h"
 #include "core/os/keyboard.h"
 #include "editor/debugger/editor_debugger_node.h"
@@ -44,6 +45,7 @@
 #include "editor/event_listener_line_edit.h"
 #include "editor/input_event_configuration_dialog.h"
 #include "editor/plugins/node_3d_editor_plugin.h"
+#include "editor/project_settings_editor.h"
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_theme_manager.h"
 #include "scene/gui/check_button.h"
@@ -239,6 +241,8 @@ void EditorSettingsDialog::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_READY: {
+			EditorSettingsPropertyWrapper::restart_request_callback = callable_mp(this, &EditorSettingsDialog::_editor_restart_request);
+
 			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 			undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_method_notify_callback(EditorDebuggerNode::_methods_changed, nullptr);
 			undo_redo->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY).undo_redo->set_property_notify_callback(EditorDebuggerNode::_properties_changed, nullptr);
@@ -1009,4 +1013,121 @@ EditorSettingsDialog::EditorSettingsDialog() {
 	add_child(timer);
 	EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorSettingsDialog::_settings_changed));
 	set_ok_button_text(TTRC("Close"));
+
+	Ref<EditorSettingsInspectorPlugin> plugin;
+	plugin.instantiate();
+	plugin->inspector = inspector;
+	EditorInspector::add_inspector_plugin(plugin);
+}
+
+void EditorSettingsPropertyWrapper::_update_override() {
+	// Don't allow overriding theme properties, because it causes problems. Overriding Project Manager settings makes no sense.
+	// TODO: Find a better way to define exception prefixes (if the list happens to grow).
+	if (property.begins_with("interface/theme") || property.begins_with("project_manager")) {
+		can_override = false;
+		return;
+	}
+
+	const bool has_override = ProjectSettings::get_singleton()->has_editor_setting_override(property);
+	if (has_override) {
+		const Variant override_value = EDITOR_GET(property);
+		override_label->set_text(vformat(TTR("Overridden in project: %s"), override_value));
+		// In case the text is too long and trimmed.
+		override_label->set_tooltip_text(override_value);
+	}
+	override_info->set_visible(has_override);
+	can_override = !has_override;
+}
+
+void EditorSettingsPropertyWrapper::_create_override() {
+	ProjectSettings::get_singleton()->set_editor_setting_override(property, EDITOR_GET(property));
+	ProjectSettings::get_singleton()->save();
+	_update_override();
+}
+
+void EditorSettingsPropertyWrapper::_remove_override() {
+	ProjectSettings::get_singleton()->set_editor_setting_override(property, Variant());
+	ProjectSettings::get_singleton()->save();
+	EditorSettings::get_singleton()->mark_setting_changed(property);
+	EditorNode::get_singleton()->notify_settings_overrides_changed();
+	_update_override();
+
+	if (requires_restart) {
+		restart_request_callback.call();
+	}
+}
+
+void EditorSettingsPropertyWrapper::_notification(int p_what) {
+	if (p_what == NOTIFICATION_THEME_CHANGED) {
+		goto_button->set_button_icon(get_editor_theme_icon(SNAME("MethodOverride")));
+		remove_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
+	}
+}
+
+void EditorSettingsPropertyWrapper::update_property() {
+	editor_property->update_property();
+}
+
+void EditorSettingsPropertyWrapper::setup(const String &p_property, EditorProperty *p_editor_property, bool p_requires_restart) {
+	requires_restart = p_requires_restart;
+
+	property = p_property;
+	container = memnew(VBoxContainer);
+
+	editor_property = p_editor_property;
+	editor_property->set_h_size_flags(SIZE_EXPAND_FILL);
+	container->add_child(editor_property);
+
+	override_info = memnew(HBoxContainer);
+	override_info->hide();
+	container->add_child(override_info);
+
+	override_label = memnew(Label);
+	override_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
+	override_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+	override_label->set_mouse_filter(MOUSE_FILTER_STOP); // For tooltip.
+	override_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	override_info->add_child(override_label);
+
+	goto_button = memnew(Button);
+	goto_button->set_tooltip_text(TTRC("Go to the override in the Project Settings."));
+	override_info->add_child(goto_button);
+	goto_button->connect(SceneStringName(pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::open_setting_override).bind(property), CONNECT_DEFERRED);
+
+	remove_button = memnew(Button);
+	remove_button->set_tooltip_text(TTRC("Remove this override."));
+	override_info->add_child(remove_button);
+	remove_button->connect(SceneStringName(pressed), callable_mp(this, &EditorSettingsPropertyWrapper::_remove_override));
+
+	add_child(container);
+	_update_override();
+
+	connect(SNAME("property_overridden"), callable_mp(this, &EditorSettingsPropertyWrapper::_create_override));
+	editor_property->connect("property_changed", callable_mp((EditorProperty *)this, &EditorProperty::emit_changed));
+}
+
+bool EditorSettingsInspectorPlugin::can_handle(Object *p_object) {
+	return p_object->is_class("SectionedInspectorFilter") && p_object != current_object;
+}
+
+bool EditorSettingsInspectorPlugin::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) {
+	if (!p_object->is_class("SectionedInspectorFilter")) {
+		return false;
+	}
+
+	const String property = inspector->get_full_item_path(p_path);
+	if (!EditorSettings::get_singleton()->has_setting(property)) {
+		return false;
+	}
+	current_object = p_object;
+
+	EditorSettingsPropertyWrapper *editor = memnew(EditorSettingsPropertyWrapper);
+	EditorProperty *real_property = inspector->get_inspector()->instantiate_property_editor(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);
+	real_property->set_object_and_property(p_object, p_path);
+	real_property->set_name_split_ratio(0.0);
+	editor->setup(property, real_property, bool(p_usage & PROPERTY_USAGE_RESTART_IF_CHANGED));
+
+	add_property_editor(p_path, editor);
+	current_object = nullptr;
+	return true;
 }

+ 42 - 0
editor/editor_settings_dialog.h

@@ -30,6 +30,7 @@
 
 #pragma once
 
+#include "editor/editor_inspector.h"
 #include "scene/gui/dialogs.h"
 
 class CheckButton;
@@ -137,3 +138,44 @@ public:
 
 	EditorSettingsDialog();
 };
+
+class EditorSettingsPropertyWrapper : public EditorProperty {
+	GDCLASS(EditorSettingsPropertyWrapper, EditorProperty);
+
+	String property;
+	EditorProperty *editor_property = nullptr;
+
+	BoxContainer *container = nullptr;
+
+	HBoxContainer *override_info = nullptr;
+	Label *override_label = nullptr;
+	Button *goto_button = nullptr;
+	Button *remove_button = nullptr;
+
+	bool requires_restart = false;
+
+	void _update_override();
+	void _create_override();
+	void _remove_override();
+
+protected:
+	void _notification(int p_what);
+
+public:
+	static inline Callable restart_request_callback;
+
+	virtual void update_property() override;
+	void setup(const String &p_property, EditorProperty *p_editor_property, bool p_requires_restart);
+};
+
+class EditorSettingsInspectorPlugin : public EditorInspectorPlugin {
+	GDCLASS(EditorSettingsInspectorPlugin, EditorInspectorPlugin);
+
+	Object *current_object = nullptr;
+
+public:
+	SectionedInspector *inspector = nullptr;
+
+	virtual bool can_handle(Object *p_object) override;
+	virtual bool 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 = false) override;
+};

+ 3 - 3
editor/export/editor_export_platform.cpp

@@ -2330,7 +2330,7 @@ bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, S
 }
 
 Error EditorExportPlatform::ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out, int p_port_fwd) const {
-	String ssh_path = EditorSettings::get_singleton()->get("export/ssh/ssh");
+	String ssh_path = EDITOR_GET("export/ssh/ssh");
 	if (ssh_path.is_empty()) {
 		ssh_path = "ssh";
 	}
@@ -2387,7 +2387,7 @@ Error EditorExportPlatform::ssh_run_on_remote(const String &p_host, const String
 }
 
 Error EditorExportPlatform::ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid, int p_port_fwd) const {
-	String ssh_path = EditorSettings::get_singleton()->get("export/ssh/ssh");
+	String ssh_path = EDITOR_GET("export/ssh/ssh");
 	if (ssh_path.is_empty()) {
 		ssh_path = "ssh";
 	}
@@ -2424,7 +2424,7 @@ Error EditorExportPlatform::ssh_run_on_remote_no_wait(const String &p_host, cons
 }
 
 Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const {
-	String scp_path = EditorSettings::get_singleton()->get("export/ssh/scp");
+	String scp_path = EDITOR_GET("export/ssh/scp");
 	if (scp_path.is_empty()) {
 		scp_path = "scp";
 	}

+ 1 - 1
editor/gui/editor_spin_slider.cpp

@@ -453,7 +453,7 @@ void EditorSpinSlider::_draw_spin_slider() {
 void EditorSpinSlider::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_TREE: {
-			grabbing_spinner_speed = EditorSettings::get_singleton()->get("interface/inspector/float_drag_speed");
+			grabbing_spinner_speed = EDITOR_GET("interface/inspector/float_drag_speed");
 			_update_value_input_stylebox();
 		} break;
 

+ 1 - 1
editor/plugins/node_3d_editor_plugin.cpp

@@ -236,7 +236,7 @@ void ViewportNavigationControl::_update_navigation() {
 			real_t speed_multiplier = MIN(delta.length() / (get_size().x * 100.0), 3.0);
 			real_t speed = viewport->freelook_speed * speed_multiplier;
 
-			const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_navigation_scheme").operator int();
+			const Node3DEditorViewport::FreelookNavigationScheme navigation_scheme = (Node3DEditorViewport::FreelookNavigationScheme)EDITOR_GET("editors/3d/freelook/freelook_navigation_scheme").operator int();
 
 			Vector3 forward;
 			if (navigation_scheme == Node3DEditorViewport::FreelookNavigationScheme::FREELOOK_FULLY_AXIS_LOCKED) {

+ 45 - 3
editor/project_settings_editor.cpp

@@ -74,6 +74,12 @@ void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) {
 	_focus_current_search_box();
 }
 
+void ProjectSettingsEditor::popup_for_override(const String &p_override) {
+	popup_project_settings();
+	tab_container->set_current_tab(0);
+	general_settings_inspector->set_current_section(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX + p_override.get_slicec('/', 0));
+}
+
 void ProjectSettingsEditor::queue_save() {
 	settings_changed = true;
 	timer->start();
@@ -84,6 +90,10 @@ void ProjectSettingsEditor::_save() {
 	if (ps) {
 		ps->save();
 	}
+	if (pending_override_notify) {
+		pending_override_notify = false;
+		EditorNode::get_singleton()->notify_settings_overrides_changed();
+	}
 }
 
 void ProjectSettingsEditor::set_plugins_page() {
@@ -104,6 +114,11 @@ void ProjectSettingsEditor::init_autoloads() {
 }
 
 void ProjectSettingsEditor::_setting_edited(const String &p_name) {
+	const String full_name = general_settings_inspector->get_full_item_path(p_name);
+	if (full_name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX)) {
+		EditorSettings::get_singleton()->mark_setting_changed(full_name.trim_prefix(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
+		pending_override_notify = true;
+	}
 	queue_save();
 }
 
@@ -111,6 +126,21 @@ void ProjectSettingsEditor::_update_advanced(bool p_is_advanced) {
 	custom_properties->set_visible(p_is_advanced);
 }
 
+void ProjectSettingsEditor::_on_category_changed(const String &p_new_category) {
+	general_settings_inspector->get_inspector()->set_use_deletable_properties(p_new_category.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
+}
+
+void ProjectSettingsEditor::_on_editor_override_deleted(const String &p_setting) {
+	const String full_name = general_settings_inspector->get_full_item_path(p_setting);
+	ERR_FAIL_COND(!full_name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
+
+	ProjectSettings::get_singleton()->set_setting(full_name, Variant());
+	EditorSettings::get_singleton()->mark_setting_changed(full_name.trim_prefix(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
+	pending_override_notify = true;
+	_save();
+	general_settings_inspector->update_category_list();
+}
+
 void ProjectSettingsEditor::_advanced_toggled(bool p_button_pressed) {
 	EditorSettings::get_singleton()->set("_project_settings_advanced_mode", p_button_pressed);
 	EditorSettings::get_singleton()->save();
@@ -224,7 +254,7 @@ void ProjectSettingsEditor::_update_property_box() {
 	}
 
 	if (ps->has_setting(setting)) {
-		del_button->set_disabled(ps->is_builtin_setting(setting));
+		del_button->set_disabled(ps->is_builtin_setting(setting) || setting.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX));
 		_select_type(ps->get_setting(setting).get_type());
 	} else {
 		if (ps->has_setting(name)) {
@@ -233,7 +263,7 @@ void ProjectSettingsEditor::_update_property_box() {
 			type_box->select(0);
 		}
 
-		if (feature_invalid) {
+		if (feature_invalid || name.begins_with(ProjectSettings::EDITOR_SETTING_OVERRIDE_PREFIX)) {
 			return;
 		}
 
@@ -611,7 +641,17 @@ void ProjectSettingsEditor::_update_theme() {
 void ProjectSettingsEditor::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
-			if (!is_visible()) {
+			if (is_visible()) {
+				HashMap<String, PropertyInfo> editor_settings_info;
+
+				List<PropertyInfo> infos;
+				EditorSettings::get_singleton()->get_property_list(&infos);
+
+				for (const PropertyInfo &pi : infos) {
+					editor_settings_info[pi.name] = pi;
+				}
+				ProjectSettings::get_singleton()->editor_settings_info = editor_settings_info;
+			} else {
 				EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", Rect2(get_position(), get_size()));
 				if (settings_changed) {
 					timer->stop();
@@ -712,10 +752,12 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
 	general_settings_inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	general_settings_inspector->register_search_box(search_box);
 	general_settings_inspector->register_advanced_toggle(advanced);
+	general_settings_inspector->connect("category_changed", callable_mp(this, &ProjectSettingsEditor::_on_category_changed));
 	general_settings_inspector->get_inspector()->set_use_filter(true);
 	general_settings_inspector->get_inspector()->set_mark_unsaved(false);
 	general_settings_inspector->get_inspector()->connect("property_selected", callable_mp(this, &ProjectSettingsEditor::_setting_selected));
 	general_settings_inspector->get_inspector()->connect("property_edited", callable_mp(this, &ProjectSettingsEditor::_setting_edited));
+	general_settings_inspector->get_inspector()->connect("property_deleted", callable_mp(this, &ProjectSettingsEditor::_on_editor_override_deleted));
 	general_settings_inspector->get_inspector()->connect("restart_requested", callable_mp(this, &ProjectSettingsEditor::_editor_restart_request));
 	general_editor->add_child(general_settings_inspector);
 

+ 7 - 0
editor/project_settings_editor.h

@@ -90,6 +90,10 @@ class ProjectSettingsEditor : public AcceptDialog {
 	EditorData *data = nullptr;
 
 	bool settings_changed = false;
+	bool pending_override_notify = false;
+
+	void _on_category_changed(const String &p_new_category);
+	void _on_editor_override_deleted(const String &p_setting);
 
 	void _advanced_toggled(bool p_button_pressed);
 	void _update_advanced(bool p_is_advanced);
@@ -131,7 +135,10 @@ protected:
 
 public:
 	static ProjectSettingsEditor *get_singleton() { return singleton; }
+
 	void popup_project_settings(bool p_clear_filter = false);
+	void popup_for_override(const String &p_override);
+
 	void set_plugins_page();
 	void set_general_page(const String &p_category);
 	void update_plugins();

+ 1 - 1
editor/themes/editor_theme_manager.cpp

@@ -2733,7 +2733,7 @@ void EditorThemeManager::_generate_text_editor_defaults(ThemeConfiguration &p_co
 }
 
 void EditorThemeManager::_populate_text_editor_styles(const Ref<EditorTheme> &p_theme, ThemeConfiguration &p_config) {
-	String text_editor_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme");
+	String text_editor_color_theme = EDITOR_GET("text_editor/theme/color_theme");
 	if (text_editor_color_theme == "Default") {
 		_generate_text_editor_defaults(p_config);
 	} else if (text_editor_color_theme == "Godot 2") {

+ 2 - 3
main/main.cpp

@@ -4433,13 +4433,12 @@ int Main::start() {
 
 #ifdef TOOLS_ENABLED
 		if (editor) {
-			bool editor_embed_subwindows = EditorSettings::get_singleton()->get_setting(
-					"interface/editor/single_window_mode");
+			bool editor_embed_subwindows = EDITOR_GET("interface/editor/single_window_mode");
 
 			if (editor_embed_subwindows) {
 				sml->get_root()->set_embedding_subwindows(true);
 			}
-			restore_editor_window_layout = EditorSettings::get_singleton()->get_setting("interface/editor/editor_screen").operator int() == EditorSettings::InitialScreen::INITIAL_SCREEN_AUTO;
+			restore_editor_window_layout = EDITOR_GET("interface/editor/editor_screen").operator int() == EditorSettings::InitialScreen::INITIAL_SCREEN_AUTO;
 		}
 #endif
 

+ 1 - 1
modules/gltf/register_types.cpp

@@ -61,7 +61,7 @@ static void _editor_init() {
 
 	String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
 	if (blender_path.is_empty() && EditorSettings::get_singleton()->has_setting("filesystem/import/blender/blender3_path")) {
-		blender_path = EditorSettings::get_singleton()->get("filesystem/import/blender/blender3_path");
+		blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
 
 		if (!blender_path.is_empty()) {
 #if defined(MACOS_ENABLED)

+ 1 - 1
platform/linuxbsd/export/export_plugin.cpp

@@ -526,7 +526,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset,
 	}
 
 	const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
-	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
+	int dbg_port = EDITOR_GET("network/debug/remote_port");
 
 	print_line("Creating temporary directory...");
 	ep.step(TTR("Creating temporary directory..."), 2);

+ 1 - 1
platform/macos/export/export_plugin.cpp

@@ -2636,7 +2636,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in
 	}
 
 	const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
-	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
+	int dbg_port = EDITOR_GET("network/debug/remote_port");
 
 	print_line("Creating temporary directory...");
 	ep.step(TTR("Creating temporary directory..."), 2);

+ 1 - 1
platform/windows/export/export_plugin.cpp

@@ -1104,7 +1104,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
 	}
 
 	const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT);
-	int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port");
+	int dbg_port = EDITOR_GET("network/debug/remote_port");
 
 	print_line("Creating temporary directory...");
 	ep.step(TTR("Creating temporary directory..."), 2);