浏览代码

Rewrite ProjectSettings editor advanced options

 - New layout: advanced options hidden by default, error labels added.
 - Disallow adding invalid new settings, or overwriting built-in settings.
Stijn Hinlopen 5 年之前
父节点
当前提交
5563722628
共有 5 个文件被更改,包括 291 次插入326 次删除
  1. 6 0
      core/project_settings.cpp
  2. 1 0
      core/project_settings.h
  3. 1 2
      editor/doc_data.cpp
  4. 250 287
      editor/project_settings_editor.cpp
  5. 33 37
      editor/project_settings_editor.h

+ 6 - 0
core/project_settings.cpp

@@ -659,6 +659,12 @@ void ProjectSettings::set_builtin_order(const String &p_name) {
 	}
 }
 
+bool ProjectSettings::is_builtin_setting(const String &p_name) const {
+	// Return true because a false negative is worse than a false positive.
+	ERR_FAIL_COND_V_MSG(!props.has(p_name), true, "Request for nonexistent project setting: " + p_name + ".");
+	return props[p_name].order < NO_BUILTIN_ORDER_BASE;
+}
+
 void ProjectSettings::clear(const String &p_name) {
 	ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
 	props.erase(p_name);

+ 1 - 0
core/project_settings.h

@@ -142,6 +142,7 @@ public:
 	int get_order(const String &p_name) const;
 	void set_order(const String &p_name, int p_order);
 	void set_builtin_order(const String &p_name);
+	bool is_builtin_setting(const String &p_name) const;
 
 	Error setup(const String &p_path, const String &p_main_pack, bool p_upwards = false);
 

+ 1 - 2
editor/doc_data.cpp

@@ -316,8 +316,7 @@ void DocData::generate(bool p_basic_types) {
 
 			if (name == "ProjectSettings") {
 				// Special case for project settings, so that settings are not taken from the current project's settings
-				if (E->get().name == "script" ||
-						ProjectSettings::get_singleton()->get_order(E->get().name) >= ProjectSettings::NO_BUILTIN_ORDER_BASE) {
+				if (E->get().name == "script" || !ProjectSettings::get_singleton()->is_builtin_setting(E->get().name)) {
 					continue;
 				}
 				if (E->get().usage & PROPERTY_USAGE_EDITOR) {

+ 250 - 287
editor/project_settings_editor.cpp

@@ -30,8 +30,6 @@
 
 #include "project_settings_editor.h"
 
-#include "core/global_constants.h"
-#include "core/os/keyboard.h"
 #include "core/project_settings.h"
 #include "editor/editor_export.h"
 #include "editor/editor_node.h"
@@ -48,175 +46,157 @@ void ProjectSettingsEditor::popup_project_settings() {
 		popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
 	}
 
-	globals_editor->update_category_list();
+	_add_feature_overrides();
+	inspector->update_category_list();
+
 	localization_editor->update_translations();
 	autoload_settings->update_autoload();
 	plugin_settings->update_plugins();
-	set_process_unhandled_input(true);
 }
 
-void ProjectSettingsEditor::_unhandled_input(const Ref<InputEvent> &p_event) {
-	const Ref<InputEventKey> k = p_event;
-
-	if (k.is_valid() && k->is_pressed()) {
-		if (k->get_keycode_with_modifiers() == (KEY_MASK_CMD | KEY_F)) {
-			if (search_button->is_pressed()) {
-				search_box->grab_focus();
-				search_box->select_all();
-			} else {
-				// This toggles the search bar display while giving the button its "pressed" appearance
-				search_button->set_pressed(true);
-			}
-
-			set_input_as_handled();
-		}
-	}
+void ProjectSettingsEditor::queue_save() {
+	timer->start();
 }
 
-void ProjectSettingsEditor::_notification(int p_what) {
-	switch (p_what) {
-		case NOTIFICATION_VISIBILITY_CHANGED: {
-			if (!is_visible()) {
-				EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", Rect2(get_position(), get_size()));
-				set_process_unhandled_input(false);
-			}
-		} break;
-		case NOTIFICATION_ENTER_TREE: {
-			globals_editor->edit(ProjectSettings::get_singleton());
-
-			search_button->set_icon(get_theme_icon("Search", "EditorIcons"));
-			search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
-			search_box->set_clear_button_enabled(true);
-
-			restart_close_button->set_icon(get_theme_icon("Close", "EditorIcons"));
-			restart_container->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree"));
-			restart_icon->set_texture(get_theme_icon("StatusWarning", "EditorIcons"));
-			restart_label->add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"));
-
-		} break;
-		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
-			search_button->set_icon(get_theme_icon("Search", "EditorIcons"));
-			search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
-			search_box->set_clear_button_enabled(true);
-		} break;
-	}
+void ProjectSettingsEditor::set_plugins_page() {
+	tab_container->set_current_tab(plugin_settings->get_index());
 }
 
 void ProjectSettingsEditor::update_plugins() {
 	plugin_settings->update_plugins();
 }
 
-void ProjectSettingsEditor::_item_selected(const String &p_path) {
-	const String &selected_path = p_path;
-	if (selected_path == String()) {
-		return;
+void ProjectSettingsEditor::_setting_edited(const String &p_name) {
+	queue_save();
+}
+
+void ProjectSettingsEditor::_advanced_pressed() {
+	if (advanced->is_pressed()) {
+		_update_advanced_bar();
+		advanced_bar->show();
+	} else {
+		advanced_bar->hide();
 	}
-	category->set_text(globals_editor->get_current_section());
-	property->set_text(selected_path);
-	popup_copy_to_feature->set_disabled(false);
 }
 
-void ProjectSettingsEditor::_item_adds(String) {
-	_item_add();
+void ProjectSettingsEditor::_setting_selected(const String &p_path) {
+	if (p_path == String()) {
+		return;
+	}
+
+	category_box->set_text(inspector->get_current_section());
+	property_box->set_text(p_path);
+
+	if (advanced_bar->is_visible()) {
+		_update_advanced_bar(); // set_text doesn't trigger text_changed
+	}
 }
 
-void ProjectSettingsEditor::_item_add() {
+void ProjectSettingsEditor::_add_setting() {
+	String setting = _get_setting_name();
+
 	// Initialize the property with the default value for the given type.
 	// The type list starts at 1 (as we exclude Nil), so add 1 to the selected value.
 	Callable::CallError ce;
 	const Variant value = Variant::construct(Variant::Type(type->get_selected() + 1), nullptr, 0, ce);
 
-	String catname = category->get_text().strip_edges();
-	String propname = property->get_text().strip_edges();
+	undo_redo->create_action(TTR("Add Project Setting"));
+	undo_redo->add_do_property(ps, setting, value);
+	undo_redo->add_undo_property(ps, setting, ps->has_setting(setting) ? ps->get(setting) : Variant());
 
-	if (propname.empty()) {
-		return;
-	}
+	undo_redo->add_do_method(inspector, "update_category_list");
+	undo_redo->add_undo_method(inspector, "update_category_list");
+	undo_redo->add_do_method(this, "queue_save");
+	undo_redo->add_undo_method(this, "queue_save");
+	undo_redo->commit_action();
 
-	if (catname.empty()) {
-		catname = "global";
-	}
+	inspector->set_current_section(setting.get_slice("/", 1));
+}
 
-	String name = catname + "/" + propname;
+void ProjectSettingsEditor::_delete_setting() {
+	String setting = _get_setting_name();
+	Variant value = ps->get(setting);
+	int order = ps->get_order(setting);
 
-	undo_redo->create_action(TTR("Add Global Property"));
+	undo_redo->create_action(TTR("Delete Item"));
 
-	undo_redo->add_do_property(ProjectSettings::get_singleton(), name, value);
+	undo_redo->add_do_method(ps, "clear", setting);
+	undo_redo->add_undo_method(ps, "set", setting, value);
+	undo_redo->add_undo_method(ps, "set_order", setting, order);
 
-	if (ProjectSettings::get_singleton()->has_setting(name)) {
-		undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, ProjectSettings::get_singleton()->get(name));
-	} else {
-		undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, Variant());
-	}
+	undo_redo->add_do_method(inspector, "update_category_list");
+	undo_redo->add_undo_method(inspector, "update_category_list");
+	undo_redo->add_do_method(this, "queue_save");
+	undo_redo->add_undo_method(this, "queue_save");
 
-	undo_redo->add_do_method(globals_editor, "update_category_list");
-	undo_redo->add_undo_method(globals_editor, "update_category_list");
-	undo_redo->add_do_method(this, "_settings_changed");
-	undo_redo->add_undo_method(this, "_settings_changed");
 	undo_redo->commit_action();
 
-	globals_editor->set_current_section(catname);
-
-	_settings_changed();
+	property_box->clear();
 }
 
-void ProjectSettingsEditor::_item_del() {
-	String path = globals_editor->get_inspector()->get_selected_path();
-	if (path == String()) {
-		EditorNode::get_singleton()->show_warning(TTR("Select a setting item first!"));
-		return;
-	}
+void ProjectSettingsEditor::_text_field_changed(const String &p_text) {
+	_update_advanced_bar();
+}
 
-	String property = globals_editor->get_current_section().plus_file(path);
+void ProjectSettingsEditor::_feature_selected(int p_index) {
+	_update_advanced_bar();
+}
 
-	if (!ProjectSettings::get_singleton()->has_setting(property)) {
-		EditorNode::get_singleton()->show_warning(vformat(TTR("No property '%s' exists."), property));
-		return;
-	}
+void ProjectSettingsEditor::_update_advanced_bar() {
+	const String property_text = property_box->get_text().strip_edges();
 
-	if (ProjectSettings::get_singleton()->get_order(property) < ProjectSettings::NO_BUILTIN_ORDER_BASE) {
-		EditorNode::get_singleton()->show_warning(vformat(TTR("Setting '%s' is internal, and it can't be deleted."), property));
-		return;
-	}
-
-	undo_redo->create_action(TTR("Delete Item"));
+	String error_msg = "";
+	bool disable_add = true;
+	bool disable_del = true;
 
-	Variant value = ProjectSettings::get_singleton()->get(property);
-	int order = ProjectSettings::get_singleton()->get_order(property);
+	if (!property_box->get_text().empty()) {
+		const String setting = _get_setting_name();
+		bool setting_exists = ps->has_setting(setting);
+		if (setting_exists) {
+			error_msg = TTR(" - Cannot add already existing setting.");
 
-	undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", property);
-	undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property, value);
-	undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", property, order);
+			disable_del = ps->is_builtin_setting(setting);
+			if (disable_del) {
+				String msg = TTR(" - Cannot delete built-in setting.");
+				error_msg += (error_msg == "") ? msg : "\n" + msg;
+			}
+		} else {
+			bool bad_category = false; // Allow empty string.
+			Vector<String> cats = category_box->get_text().strip_edges().split("/");
+			for (int i = 0; i < cats.size(); i++) {
+				if (!cats[i].is_valid_identifier()) {
+					bad_category = true;
+					error_msg = TTR(" - Invalid category name.");
+					break;
+				}
+			}
 
-	undo_redo->add_do_method(globals_editor, "update_category_list");
-	undo_redo->add_undo_method(globals_editor, "update_category_list");
+			disable_add = !bad_category;
 
-	undo_redo->add_do_method(this, "_settings_changed");
-	undo_redo->add_undo_method(this, "_settings_changed");
+			if (!property_text.is_valid_identifier()) {
+				disable_add = true;
+				String msg = TTR(" - Invalid property name.");
+				error_msg += (error_msg == "") ? msg : "\n" + msg;
+			}
+		}
+	}
 
-	undo_redo->commit_action();
-}
+	add_button->set_disabled(disable_add);
+	del_button->set_disabled(disable_del);
 
-void ProjectSettingsEditor::_save() {
-	Error err = ProjectSettings::get_singleton()->save();
-	message->set_text(err != OK ? TTR("Error saving settings.") : TTR("Settings saved OK."));
-	message->popup_centered(Size2(300, 100) * EDSCALE);
+	error_label->set_text(error_msg);
+	error_label->set_visible(error_msg != "");
 }
 
-void ProjectSettingsEditor::_settings_prop_edited(const String &p_name) {
-	// Method needed to discard the mandatory argument of the property_edited signal
-	_settings_changed();
-}
+String ProjectSettingsEditor::_get_setting_name() const {
+	const String cat = category_box->get_text();
+	const String name = (cat.empty() ? "global" : cat.strip_edges()).plus_file(property_box->get_text().strip_edges());
+	const String feature = feature_override->get_item_text(feature_override->get_selected());
 
-void ProjectSettingsEditor::_settings_changed() {
-	timer->start();
+	return (feature == "") ? name : (name + "." + feature);
 }
 
-void ProjectSettingsEditor::queue_save() {
-	_settings_changed();
-}
-
-void ProjectSettingsEditor::_copy_to_platform_about_to_show() {
+void ProjectSettingsEditor::_add_feature_overrides() {
 	Set<String> presets;
 
 	presets.insert("bptc");
@@ -230,25 +210,26 @@ void ProjectSettingsEditor::_copy_to_platform_about_to_show() {
 	presets.insert("standalone");
 	presets.insert("32");
 	presets.insert("64");
-	// Not available as an export platform yet, so it needs to be added manually
-	presets.insert("Server");
+	presets.insert("Server"); // Not available as an export platform yet, so it needs to be added manually
 
-	for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
+	EditorExport *ee = EditorExport::get_singleton();
+
+	for (int i = 0; i < ee->get_export_platform_count(); i++) {
 		List<String> p;
-		EditorExport::get_singleton()->get_export_platform(i)->get_platform_features(&p);
+		ee->get_export_platform(i)->get_platform_features(&p);
 		for (List<String>::Element *E = p.front(); E; E = E->next()) {
 			presets.insert(E->get());
 		}
 	}
 
-	for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
+	for (int i = 0; i < ee->get_export_preset_count(); i++) {
 		List<String> p;
-		EditorExport::get_singleton()->get_export_preset(i)->get_platform()->get_preset_features(EditorExport::get_singleton()->get_export_preset(i), &p);
+		ee->get_export_preset(i)->get_platform()->get_preset_features(ee->get_export_preset(i), &p);
 		for (List<String>::Element *E = p.front(); E; E = E->next()) {
 			presets.insert(E->get());
 		}
 
-		String custom = EditorExport::get_singleton()->get_export_preset(i)->get_custom_features();
+		String custom = ee->get_export_preset(i)->get_custom_features();
 		Vector<String> custom_list = custom.split(",");
 		for (int j = 0; j < custom_list.size(); j++) {
 			String f = custom_list[j].strip_edges();
@@ -258,70 +239,14 @@ void ProjectSettingsEditor::_copy_to_platform_about_to_show() {
 		}
 	}
 
-	popup_copy_to_feature->get_popup()->clear();
-	int id = 0;
+	feature_override->clear();
+	feature_override->add_item("", 0); // So it is always on top.
+	int id = 1;
 	for (Set<String>::Element *E = presets.front(); E; E = E->next()) {
-		popup_copy_to_feature->get_popup()->add_item(E->get(), id++);
+		feature_override->add_item(E->get(), id++);
 	}
 }
 
-void ProjectSettingsEditor::_copy_to_platform(int p_which) {
-	String path = globals_editor->get_inspector()->get_selected_path();
-	if (path == String()) {
-		EditorNode::get_singleton()->show_warning(TTR("Select a setting item first!"));
-		return;
-	}
-
-	String property = globals_editor->get_current_section().plus_file(path);
-
-	undo_redo->create_action(TTR("Override for Feature"));
-
-	Variant value = ProjectSettings::get_singleton()->get(property);
-	if (property.find(".") != -1) { //overwriting overwrite, keep overwrite
-		undo_redo->add_do_method(ProjectSettings::get_singleton(), "clear", property);
-		undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", property, value);
-	}
-
-	String feature = popup_copy_to_feature->get_popup()->get_item_text(p_which);
-	String new_path = property + "." + feature;
-
-	undo_redo->add_do_method(ProjectSettings::get_singleton(), "set", new_path, value);
-	if (ProjectSettings::get_singleton()->has_setting(new_path)) {
-		undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set", new_path, ProjectSettings::get_singleton()->get(new_path));
-	}
-
-	undo_redo->add_do_method(globals_editor, "update_category_list");
-	undo_redo->add_undo_method(globals_editor, "update_category_list");
-
-	undo_redo->add_do_method(this, "_settings_changed");
-	undo_redo->add_undo_method(this, "_settings_changed");
-
-	undo_redo->commit_action();
-}
-
-void ProjectSettingsEditor::_toggle_search_bar(bool p_pressed) {
-	globals_editor->get_inspector()->set_use_filter(p_pressed);
-
-	if (p_pressed) {
-		search_bar->show();
-		add_prop_bar->hide();
-		search_box->grab_focus();
-		search_box->select_all();
-	} else {
-		search_box->clear();
-		search_bar->hide();
-		add_prop_bar->show();
-	}
-}
-
-void ProjectSettingsEditor::set_plugins_page() {
-	tab_container->set_current_tab(plugin_settings->get_index());
-}
-
-TabContainer *ProjectSettingsEditor::get_tabs() {
-	return tab_container;
-}
-
 void ProjectSettingsEditor::_editor_restart() {
 	EditorNode::get_singleton()->save_all_scenes();
 	EditorNode::get_singleton()->restart_editor();
@@ -335,17 +260,48 @@ void ProjectSettingsEditor::_editor_restart_close() {
 	restart_container->hide();
 }
 
-void ProjectSettingsEditor::_bind_methods() {
-	ClassDB::bind_method(D_METHOD("_unhandled_input"), &ProjectSettingsEditor::_unhandled_input);
-	ClassDB::bind_method(D_METHOD("_save"), &ProjectSettingsEditor::_save);
+void ProjectSettingsEditor::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_VISIBILITY_CHANGED: {
+			if (!is_visible()) {
+				EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", Rect2(get_position(), get_size()));
+				if (advanced->is_pressed()) {
+					advanced->set_pressed(false);
+					advanced_bar->hide();
+				}
+			}
+		} break;
+		case NOTIFICATION_ENTER_TREE: {
+			inspector->edit(ps);
+
+			error_label->add_theme_color_override("font_color", error_label->get_theme_color("error_color", "Editor"));
+			add_button->set_icon(get_theme_icon("Add", "EditorIcons"));
+			del_button->set_icon(get_theme_icon("Remove", "EditorIcons"));
+
+			search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
+			search_box->set_clear_button_enabled(true);
+
+			restart_close_button->set_icon(get_theme_icon("Close", "EditorIcons"));
+			restart_container->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree"));
+			restart_icon->set_texture(get_theme_icon("StatusWarning", "EditorIcons"));
+			restart_label->add_theme_color_override("font_color", get_theme_color("warning_color", "Editor"));
+		} break;
+		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
+			search_box->set_right_icon(get_theme_icon("Search", "EditorIcons"));
+			search_box->set_clear_button_enabled(true);
+		} break;
+	}
+}
 
-	ClassDB::bind_method(D_METHOD("get_tabs"), &ProjectSettingsEditor::get_tabs);
+void ProjectSettingsEditor::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("queue_save"), &ProjectSettingsEditor::queue_save);
 }
 
 ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
 	singleton = this;
 	set_title(TTR("Project Settings (project.godot)"));
 
+	ps = ProjectSettings::get_singleton();
 	undo_redo = &p_data->get_undo_redo();
 	data = p_data;
 
@@ -354,103 +310,110 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
 	tab_container->set_use_hidden_tabs_for_min_size(true);
 	add_child(tab_container);
 
-	VBoxContainer *props_base = memnew(VBoxContainer);
-	props_base->set_name(TTR("General"));
-	props_base->set_alignment(BoxContainer::ALIGN_BEGIN);
-	props_base->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	tab_container->add_child(props_base);
-
-	HBoxContainer *hbc = memnew(HBoxContainer);
-	hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	props_base->add_child(hbc);
-
-	search_button = memnew(Button);
-	search_button->set_text(TTR("Search"));
-	search_button->set_toggle_mode(true);
-	search_button->set_pressed(false);
-	search_button->connect("toggled", callable_mp(this, &ProjectSettingsEditor::_toggle_search_bar));
-	hbc->add_child(search_button);
-
-	hbc->add_child(memnew(VSeparator));
-
-	add_prop_bar = memnew(HBoxContainer);
-	add_prop_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	hbc->add_child(add_prop_bar);
-
-	Label *l = memnew(Label);
-	l->set_text(TTR("Category:"));
-	add_prop_bar->add_child(l);
-
-	category = memnew(LineEdit);
-	category->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	category->connect("text_entered", callable_mp(this, &ProjectSettingsEditor::_item_adds));
-	add_prop_bar->add_child(category);
-
-	l = memnew(Label);
-	l->set_text(TTR("Property:"));
-	add_prop_bar->add_child(l);
-
-	property = memnew(LineEdit);
-	property->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	property->connect("text_entered", callable_mp(this, &ProjectSettingsEditor::_item_adds));
-	add_prop_bar->add_child(property);
-
-	l = memnew(Label);
-	l->set_text(TTR("Type:"));
-	add_prop_bar->add_child(l);
-
-	type = memnew(OptionButton);
-	type->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	add_prop_bar->add_child(type);
-
-	// Start at 1 to avoid adding "Nil" as an option
-	for (int i = 1; i < Variant::VARIANT_MAX; i++) {
-		type->add_item(Variant::get_type_name(Variant::Type(i)));
+	VBoxContainer *general_editor = memnew(VBoxContainer);
+	general_editor->set_name(TTR("General"));
+	general_editor->set_alignment(BoxContainer::ALIGN_BEGIN);
+	general_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+	tab_container->add_child(general_editor);
+
+	VBoxContainer *header = memnew(VBoxContainer);
+	header->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+	general_editor->add_child(header);
+
+	{
+		// Search bar.
+		search_bar = memnew(HBoxContainer);
+		search_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+		header->add_child(search_bar);
+
+		search_box = memnew(LineEdit);
+		search_box->set_custom_minimum_size(Size2(300, 0));
+		search_bar->add_child(search_box);
+
+		search_bar->add_spacer();
+
+		advanced = memnew(CheckButton);
+		advanced->set_text(TTR("Advanced"));
+		advanced->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_advanced_pressed));
+		search_bar->add_child(advanced);
 	}
 
-	Button *add = memnew(Button);
-	add->set_text(TTR("Add"));
-	add->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_item_add));
-	add_prop_bar->add_child(add);
+	{
+		// Advanced bar.
+		advanced_bar = memnew(VBoxContainer);
+		advanced_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+		advanced_bar->hide();
+		header->add_child(advanced_bar);
+
+		HBoxContainer *hbc = memnew(HBoxContainer);
+		hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+		advanced_bar->add_margin_child(TTR("Add or remove custom project settings."), hbc, true);
+
+		category_box = memnew(LineEdit);
+		category_box->set_custom_minimum_size(Size2(140, 0) * EDSCALE);
+		category_box->connect("text_changed", callable_mp(this, &ProjectSettingsEditor::_text_field_changed));
+		category_box->set_placeholder(TTR("Category"));
+		hbc->add_child(category_box);
+
+		Label *l = memnew(Label);
+		l->set_text("/");
+		hbc->add_child(l);
+
+		property_box = memnew(LineEdit);
+		property_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+		property_box->set_placeholder(TTR("Property"));
+		property_box->connect("text_changed", callable_mp(this, &ProjectSettingsEditor::_text_field_changed));
+		hbc->add_child(property_box);
+
+		l = memnew(Label);
+		l->set_text(TTR("Type:"));
+		hbc->add_child(l);
+
+		type = memnew(OptionButton);
+		type->set_custom_minimum_size(Size2(70, 0) * EDSCALE);
+		hbc->add_child(type);
+
+		// Start at 1 to avoid adding "Nil" as an option
+		for (int i = 1; i < Variant::VARIANT_MAX; i++) {
+			type->add_item(Variant::get_type_name(Variant::Type(i)));
+		}
 
-	search_bar = memnew(HBoxContainer);
-	search_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	search_bar->hide();
-	hbc->add_child(search_bar);
+		l = memnew(Label);
+		l->set_text(TTR("Feature Override:"));
+		hbc->add_child(l);
 
-	search_box = memnew(LineEdit);
-	search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	search_bar->add_child(search_box);
+		feature_override = memnew(OptionButton);
+		feature_override->set_custom_minimum_size(Size2(70, 0) * EDSCALE);
+		feature_override->connect("item_selected", callable_mp(this, &ProjectSettingsEditor::_feature_selected));
+		hbc->add_child(feature_override);
 
-	globals_editor = memnew(SectionedInspector);
-	globals_editor->get_inspector()->set_undo_redo(EditorNode::get_singleton()->get_undo_redo());
-	globals_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
-	globals_editor->register_search_box(search_box);
-	globals_editor->get_inspector()->connect("property_selected", callable_mp(this, &ProjectSettingsEditor::_item_selected));
-	globals_editor->get_inspector()->connect("property_edited", callable_mp(this, &ProjectSettingsEditor::_settings_prop_edited));
-	globals_editor->get_inspector()->connect("restart_requested", callable_mp(this, &ProjectSettingsEditor::_editor_restart_request));
-	props_base->add_child(globals_editor);
+		hbc->add_spacer();
 
-	Button *del = memnew(Button);
-	del->set_text(TTR("Delete"));
-	del->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_item_del));
-	hbc->add_child(del);
+		add_button = memnew(Button);
+		add_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_add_setting));
+		hbc->add_child(add_button);
 
-	add_prop_bar->add_child(memnew(VSeparator));
+		del_button = memnew(Button);
+		del_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_delete_setting));
+		hbc->add_child(del_button);
 
-	popup_copy_to_feature = memnew(MenuButton);
-	popup_copy_to_feature->set_text(TTR("Override For..."));
-	popup_copy_to_feature->set_disabled(true);
-	add_prop_bar->add_child(popup_copy_to_feature);
+		error_label = memnew(Label);
+		advanced_bar->add_child(error_label);
+	}
 
-	popup_copy_to_feature->get_popup()->connect("id_pressed", callable_mp(this, &ProjectSettingsEditor::_copy_to_platform));
-	popup_copy_to_feature->get_popup()->connect("about_to_popup", callable_mp(this, &ProjectSettingsEditor::_copy_to_platform_about_to_show));
+	header->add_child(memnew(HSeparator));
 
-	get_ok()->set_text(TTR("Close"));
-	set_hide_on_ok(true);
+	inspector = memnew(SectionedInspector);
+	inspector->get_inspector()->set_undo_redo(EditorNode::get_singleton()->get_undo_redo());
+	inspector->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+	inspector->register_search_box(search_box);
+	inspector->get_inspector()->connect("property_selected", callable_mp(this, &ProjectSettingsEditor::_setting_selected));
+	inspector->get_inspector()->connect("property_edited", callable_mp(this, &ProjectSettingsEditor::_setting_edited));
+	inspector->get_inspector()->connect("restart_requested", callable_mp(this, &ProjectSettingsEditor::_editor_restart_request));
+	general_editor->add_child(inspector);
 
 	restart_container = memnew(PanelContainer);
-	props_base->add_child(restart_container);
+	general_editor->add_child(restart_container);
 
 	HBoxContainer *restart_hb = memnew(HBoxContainer);
 	restart_container->hide();
@@ -475,27 +438,24 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
 	restart_close_button->connect("pressed", callable_mp(this, &ProjectSettingsEditor::_editor_restart_close));
 	restart_hb->add_child(restart_close_button);
 
-	message = memnew(AcceptDialog);
-	add_child(message);
-
 	inputmap_editor = memnew(InputMapEditor);
 	inputmap_editor->set_name(TTR("Input Map"));
-	inputmap_editor->connect("inputmap_changed", callable_mp(this, &ProjectSettingsEditor::_settings_changed));
+	inputmap_editor->connect("inputmap_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
 	tab_container->add_child(inputmap_editor);
 
 	localization_editor = memnew(LocalizationEditor);
 	localization_editor->set_name(TTR("Localization"));
-	localization_editor->connect("localization_changed", callable_mp(this, &ProjectSettingsEditor::_settings_changed));
+	localization_editor->connect("localization_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
 	tab_container->add_child(localization_editor);
 
 	autoload_settings = memnew(EditorAutoloadSettings);
 	autoload_settings->set_name(TTR("AutoLoad"));
-	autoload_settings->connect("autoload_changed", callable_mp(this, &ProjectSettingsEditor::_settings_changed));
+	autoload_settings->connect("autoload_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
 	tab_container->add_child(autoload_settings);
 
 	shaders_global_variables_editor = memnew(ShaderGlobalsEditor);
 	shaders_global_variables_editor->set_name(TTR("Shader Globals"));
-	shaders_global_variables_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::_settings_changed));
+	shaders_global_variables_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
 	tab_container->add_child(shaders_global_variables_editor);
 
 	plugin_settings = memnew(EditorPluginSettings);
@@ -504,7 +464,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
 
 	timer = memnew(Timer);
 	timer->set_wait_time(1.5);
-	timer->connect("timeout", callable_mp(ProjectSettings::get_singleton(), &ProjectSettings::save));
+	timer->connect("timeout", callable_mp(ps, &ProjectSettings::save));
 	timer->set_one_shot(true);
 	add_child(timer);
+
+	get_ok()->set_text(TTR("Close"));
+	set_hide_on_ok(true);
 }

+ 33 - 37
editor/project_settings_editor.h

@@ -35,12 +35,11 @@
 #include "editor/editor_data.h"
 #include "editor/editor_plugin_settings.h"
 #include "editor/editor_sectioned_inspector.h"
+#include "editor/input_map_editor.h"
+#include "editor/localization_editor.h"
+#include "editor/shader_globals_editor.h"
 #include "editor_autoload_settings.h"
-#include "input_map_editor.h"
-#include "localization_editor.h"
-#include "scene/gui/dialogs.h"
 #include "scene/gui/tab_container.h"
-#include "shader_globals_editor.h"
 
 class ProjectSettingsEditor : public AcceptDialog {
 	GDCLASS(ProjectSettingsEditor, AcceptDialog);
@@ -53,28 +52,31 @@ class ProjectSettingsEditor : public AcceptDialog {
 		INPUT_MOUSE_BUTTON
 	};
 
-	TabContainer *tab_container;
-	AcceptDialog *message;
+	static ProjectSettingsEditor *singleton;
+	ProjectSettings *ps;
 	Timer *timer;
 
-	HBoxContainer *search_bar;
-	Button *search_button;
-	LineEdit *search_box;
-	HBoxContainer *add_prop_bar;
-	LineEdit *category;
-	LineEdit *property;
-	OptionButton *type;
-
-	SectionedInspector *globals_editor;
-
-	MenuButton *popup_copy_to_feature;
-
+	TabContainer *tab_container;
+	SectionedInspector *inspector;
 	InputMapEditor *inputmap_editor;
 	LocalizationEditor *localization_editor;
 	EditorAutoloadSettings *autoload_settings;
 	ShaderGlobalsEditor *shaders_global_variables_editor;
 	EditorPluginSettings *plugin_settings;
 
+	HBoxContainer *search_bar;
+	LineEdit *search_box;
+	CheckButton *advanced;
+
+	VBoxContainer *advanced_bar;
+	LineEdit *category_box;
+	LineEdit *property_box;
+	Button *add_button;
+	Button *del_button;
+	OptionButton *type;
+	OptionButton *feature_override;
+	Label *error_label;
+
 	Label *restart_label;
 	TextureRect *restart_icon;
 	PanelContainer *restart_container;
@@ -83,30 +85,25 @@ class ProjectSettingsEditor : public AcceptDialog {
 	EditorData *data;
 	UndoRedo *undo_redo;
 
-	void _item_selected(const String &);
-	void _item_adds(String);
-	void _item_add();
-	void _item_del();
-	void _save();
-
-	void _settings_prop_edited(const String &p_name);
-	void _settings_changed();
+	void _advanced_pressed();
+	void _update_advanced_bar();
+	void _text_field_changed(const String &p_text);
+	void _feature_selected(int p_index);
 
-	void _copy_to_platform(int p_which);
-	void _copy_to_platform_about_to_show();
-
-	void _toggle_search_bar(bool p_pressed);
-
-	ProjectSettingsEditor();
-
-	static ProjectSettingsEditor *singleton;
+	String _get_setting_name() const;
+	void _setting_edited(const String &p_name);
+	void _setting_selected(const String &p_path);
+	void _add_setting();
+	void _delete_setting();
 
 	void _editor_restart_request();
 	void _editor_restart();
 	void _editor_restart_close();
 
+	void _add_feature_overrides();
+	ProjectSettingsEditor();
+
 protected:
-	void _unhandled_input(const Ref<InputEvent> &p_event);
 	void _notification(int p_what);
 	static void _bind_methods();
 
@@ -117,8 +114,7 @@ public:
 	void update_plugins();
 
 	EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
-
-	TabContainer *get_tabs();
+	TabContainer *get_tabs() { return tab_container; }
 
 	void queue_save();