#if UNITY_2018_3_OR_NEWER
#define SETTINGS_PROVIDER_ENABLED
#endif
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;
#if SETTINGS_PROVIDER_ENABLED
#if UNITY_2019_1_OR_NEWER
using UnityEngine.UIElements;
#else
using UnityEngine.Experimental.UIElements;
#endif
#endif
namespace UnityEditor.SettingsManagement
{
///
/// A implementation that creates an interface from settings reflected
/// from a collection of assemblies.
///
#if SETTINGS_PROVIDER_ENABLED
public sealed class UserSettingsProvider : SettingsProvider
#else
public sealed class UserSettingsProvider
#endif
{
///
/// Category string constant to check whether Unity is running in developer (internal) mode.
///
public const string developerModeCategory = "Developer Mode";
const string k_SettingsName = "UserSettingsProviderSettings";
#if SETTINGS_PROVIDER_ENABLED
const int k_LabelWidth = 240;
static int labelWidth
{
get
{
if (s_DefaultLabelWidth != null)
return (int)((float)s_DefaultLabelWidth.GetValue(null, null));
return k_LabelWidth;
}
}
static int defaultLayoutMaxWidth
{
get
{
if (s_DefaultLayoutMaxWidth != null)
return (int)((float)s_DefaultLayoutMaxWidth.GetValue(null, null));
return 0;
}
}
#else
const int k_LabelWidth = 180;
int labelWidth
{
get { return k_LabelWidth; }
}
int defaultLayoutMaxWidth
{
get { return 0; }
}
#endif
List m_Categories;
Dictionary> m_Settings;
Dictionary> m_SettingBlocks;
#if !SETTINGS_PROVIDER_ENABLED
HashSet keywords = new HashSet();
#endif
static readonly string[] s_SearchContext = new string[1];
EventType m_SettingsBlockKeywordsInitialized;
Assembly[] m_Assemblies;
static Settings s_Settings;
Settings m_SettingsInstance;
#if SETTINGS_PROVIDER_ENABLED
static PropertyInfo s_DefaultLabelWidth;
static PropertyInfo s_DefaultLayoutMaxWidth;
#endif
static Settings userSettingsProviderSettings
{
get
{
if (s_Settings == null)
s_Settings = new Settings(new[] { new UserSettingsRepository() });
return s_Settings;
}
}
internal static UserSetting showHiddenSettings = new UserSetting(userSettingsProviderSettings, "settings.showHidden", false, SettingsScope.User);
internal static UserSetting showUnregisteredSettings = new UserSetting(userSettingsProviderSettings, "settings.showUnregistered", false, SettingsScope.User);
internal static UserSetting listByKey = new UserSetting(userSettingsProviderSettings, "settings.listByKey", false, SettingsScope.User);
internal static UserSetting showUserSettings = new UserSetting(userSettingsProviderSettings, "settings.showUserSettings", true, SettingsScope.User);
internal static UserSetting showProjectSettings = new UserSetting(userSettingsProviderSettings, "settings.showProjectSettings", true, SettingsScope.User);
#if SETTINGS_PROVIDER_ENABLED
///
/// Initializes and returns a new `UserSettingsProvider` instance.
///
/// The settings menu path.
/// The Settings instance that this provider is inspecting.
/// A collection of assemblies to scan for and attributes.
/// Which scopes this provider is valid for.
/// Thrown if settings or assemblies is null.
public UserSettingsProvider(string path, Settings settings, Assembly[] assemblies, SettingsScope scopes = SettingsScope.User)
: base(path, scopes)
#else
///
/// Initializes and returns a new `UserSettingsProvider` instance.
///
/// The instance that this provider is inspecting.
/// A collection of assemblies to scan for and attributes.
public UserSettingsProvider(Settings settings, Assembly[] assemblies)
#endif
{
if (settings == null)
throw new ArgumentNullException("settings");
if (assemblies == null)
throw new ArgumentNullException("assemblies");
m_SettingsInstance = settings;
m_Assemblies = assemblies;
#if !SETTINGS_PROVIDER_ENABLED
SearchForUserSettingAttributes();
#endif
}
#if SETTINGS_PROVIDER_ENABLED
///
/// Invoked by the when activated in the Editor.
///
///
/// Search context in the search box on the
/// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
///
///
/// Root of the UIElements tree. If you add to this root, the SettingsProvider uses
/// [UIElements](https://docs.unity3d.com/ScriptReference/UnityEngine.UIElementsModule.html)
/// instead of calling to build the UI.
/// See for details.
///
public override void OnActivate(string searchContext, VisualElement rootElement)
{
base.OnActivate(searchContext, rootElement);
SearchForUserSettingAttributes();
var window = GetType().GetProperty("settingsWindow", BindingFlags.Instance | BindingFlags.NonPublic);
if (window != null)
{
s_DefaultLabelWidth = window.PropertyType.GetProperty("s_DefaultLabelWidth", BindingFlags.Public | BindingFlags.Static);
s_DefaultLayoutMaxWidth = window.PropertyType.GetProperty("s_DefaultLayoutMaxWidth", BindingFlags.Public | BindingFlags.Static);
}
}
#endif
struct PrefEntry
{
GUIContent m_Content;
IUserSetting m_Pref;
public GUIContent content
{
get { return m_Content; }
}
public IUserSetting pref
{
get { return m_Pref; }
}
public PrefEntry(GUIContent content, IUserSetting pref)
{
m_Content = content;
m_Pref = pref;
}
}
void SearchForUserSettingAttributes()
{
var isDeveloperMode = EditorPrefs.GetBool("DeveloperMode", false);
var keywordsHash = new HashSet();
if (m_Settings != null)
m_Settings.Clear();
else
m_Settings = new Dictionary>();
if (m_SettingBlocks != null)
m_SettingBlocks.Clear();
else
m_SettingBlocks = new Dictionary>();
var types = m_Assemblies.SelectMany(x => x.GetTypes());
// collect instance fields/methods too, but only so we can throw a warning that they're invalid.
var fields = types.SelectMany(x =>
x.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
.Where(prop => Attribute.IsDefined(prop, typeof(UserSettingAttribute))));
var methods = types.SelectMany(x => x.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.Where(y => Attribute.IsDefined(y, typeof(UserSettingBlockAttribute))));
foreach (var field in fields)
{
if (!field.IsStatic)
{
Debug.LogWarning("Cannot create setting entries for instance fields. Skipping \"" + field.Name + "\".");
continue;
}
var attrib = (UserSettingAttribute)Attribute.GetCustomAttribute(field, typeof(UserSettingAttribute));
if (!attrib.visibleInSettingsProvider)
continue;
var pref = (IUserSetting)field.GetValue(null);
if (pref == null)
{
Debug.LogWarning("[UserSettingAttribute] is only valid for types implementing the IUserSetting interface. Skipping \"" + field.Name + "\"");
continue;
}
var category = string.IsNullOrEmpty(attrib.category) ? "Uncategorized" : attrib.category;
var content = listByKey ? new GUIContent(pref.key) : attrib.title;
if (developerModeCategory.Equals(category) && !isDeveloperMode)
continue;
List settings;
if (m_Settings.TryGetValue(category, out settings))
settings.Add(new PrefEntry(content, pref));
else
m_Settings.Add(category, new List() { new PrefEntry(content, pref) });
}
foreach (var method in methods)
{
var attrib = (UserSettingBlockAttribute)Attribute.GetCustomAttribute(method, typeof(UserSettingBlockAttribute));
var category = string.IsNullOrEmpty(attrib.category) ? "Uncategorized" : attrib.category;
if (developerModeCategory.Equals(category) && !isDeveloperMode)
continue;
List blocks;
var parameters = method.GetParameters();
if (!method.IsStatic || parameters.Length < 1 || parameters[0].ParameterType != typeof(string))
{
Debug.LogWarning("[UserSettingBlockAttribute] is only valid for static functions with a single string parameter. Ex, `static void MySettings(string searchContext)`. Skipping \"" + method.Name + "\"");
continue;
}
if (m_SettingBlocks.TryGetValue(category, out blocks))
blocks.Add(method);
else
m_SettingBlocks.Add(category, new List() { method });
}
if (showHiddenSettings)
{
var unlisted = new List();
m_Settings.Add("Unlisted", unlisted);
foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Unlisted | SettingVisibility.Hidden))
unlisted.Add(new PrefEntry(new GUIContent(pref.key), pref));
}
if (showUnregisteredSettings)
{
var unregistered = new List();
m_Settings.Add("Unregistered", unregistered);
foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Unregistered))
unregistered.Add(new PrefEntry(new GUIContent(pref.key), pref));
}
foreach (var cat in m_Settings)
{
foreach (var entry in cat.Value)
{
var content = entry.content;
if (content != null && !string.IsNullOrEmpty(content.text))
{
foreach (var word in content.text.Split(' '))
keywordsHash.Add(word);
}
}
}
keywords = keywordsHash;
m_Categories = m_Settings.Keys.Union(m_SettingBlocks.Keys).ToList();
m_Categories.Sort();
}
#if SETTINGS_PROVIDER_ENABLED
///
/// Invoked by the SettingsProvider container when drawing the UI header.
///
public override void OnTitleBarGUI()
{
if (GUILayout.Button(GUIContent.none, SettingsGUIStyles.settingsGizmo))
DoContextMenu();
}
#endif
void InitSettingsBlockKeywords()
{
// Have to let the blocks run twice - one for Layout, one for Repaint.
if (m_SettingsBlockKeywordsInitialized == EventType.Repaint)
return;
m_SettingsBlockKeywordsInitialized = Event.current.type;
// Allows SettingsGUILayout.SettingsField to populate keywords
SettingsGUILayout.s_Keywords = new HashSet(keywords);
// Set a dummy value so that GUI blocks with conditional foldouts will behave as though searching.
s_SearchContext[0] = "Search";
foreach (var category in m_SettingBlocks)
{
foreach (var block in category.Value)
block.Invoke(null, s_SearchContext);
}
keywords = SettingsGUILayout.s_Keywords;
SettingsGUILayout.s_Keywords = null;
s_SearchContext[0] = "";
}
void DoContextMenu()
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Reset All"), false, () =>
{
if (!UnityEditor.EditorUtility.DisplayDialog("Reset All Settings", "Reset all settings? This is not undo-able.", "Reset", "Cancel"))
return;
// Do not reset SettingVisibility.Unregistered
foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Visible | SettingVisibility.Hidden | SettingVisibility.Unlisted))
pref.Reset();
m_SettingsInstance.Save();
});
if (EditorPrefs.GetBool("DeveloperMode", false))
{
menu.AddSeparator("");
menu.AddItem(new GUIContent("Developer/List Settings By Key"), listByKey, () =>
{
listByKey.SetValue(!listByKey, true);
SearchForUserSettingAttributes();
});
menu.AddSeparator("Developer/");
menu.AddItem(new GUIContent("Developer/Show User Settings"), showUserSettings, () =>
{
showUserSettings.SetValue(!showUserSettings, true);
SearchForUserSettingAttributes();
});
menu.AddItem(new GUIContent("Developer/Show Project Settings"), showProjectSettings, () =>
{
showProjectSettings.SetValue(!showProjectSettings, true);
SearchForUserSettingAttributes();
});
menu.AddSeparator("Developer/");
menu.AddItem(new GUIContent("Developer/Show Unlisted Settings"), showHiddenSettings, () =>
{
showHiddenSettings.SetValue(!showHiddenSettings, true);
SearchForUserSettingAttributes();
});
menu.AddItem(new GUIContent("Developer/Show Unregistered Settings"), showUnregisteredSettings, () =>
{
showUnregisteredSettings.SetValue(!showUnregisteredSettings, true);
SearchForUserSettingAttributes();
});
menu.AddSeparator("Developer/");
menu.AddItem(new GUIContent("Developer/Open Project Settings File"), false, () =>
{
var project = m_SettingsInstance.GetRepository(SettingsScope.Project);
if (project != null)
{
var path = Path.GetFullPath(project.path);
System.Diagnostics.Process.Start(path);
}
});
menu.AddItem(new GUIContent("Developer/Print All Settings"), false, () =>
{
Debug.Log(UserSettings.GetSettingsString(m_Assemblies));
});
#if UNITY_2019_1_OR_NEWER
menu.AddSeparator("Developer/");
#if UNITY_2019_3_OR_NEWER
menu.AddItem(new GUIContent("Developer/Recompile Scripts"), false, EditorUtility.RequestScriptReload);
#else
menu.AddItem(new GUIContent("Developer/Recompile Scripts"), false, UnityEditorInternal.InternalEditorUtility.RequestScriptReload);
#endif
#endif
}
menu.ShowAsContext();
}
#if SETTINGS_PROVIDER_ENABLED
///
/// Called when the Settings window opens in the Editor.
///
///
/// Search context in the search box on the
/// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
///
///
public override void OnGUI(string searchContext)
#else
///
/// Called when the Settings window opens in the Editor.
///
///
/// Search context in the search box on the
/// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
///
///
public void OnGUI(string searchContext)
#endif
{
#if !SETTINGS_PROVIDER_ENABLED
var evt = Event.current;
if (evt.type == EventType.ContextClick)
DoContextMenu();
#endif
InitSettingsBlockKeywords();
EditorGUIUtility.labelWidth = labelWidth;
EditorGUI.BeginChangeCheck();
var maxWidth = defaultLayoutMaxWidth;
if (maxWidth != 0)
GUILayout.BeginVertical(SettingsGUIStyles.settingsArea, GUILayout.MaxWidth(maxWidth));
else
GUILayout.BeginVertical(SettingsGUIStyles.settingsArea);
var hasSearchContext = !string.IsNullOrEmpty(searchContext);
s_SearchContext[0] = searchContext;
if (hasSearchContext)
{
// todo - Improve search comparison
var searchKeywords = searchContext.Split(' ');
foreach (var settingField in m_Settings)
{
foreach (var setting in settingField.Value)
{
if (searchKeywords.Any(x => !string.IsNullOrEmpty(x) && setting.content.text.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) > -1))
DoPreferenceField(setting.content, setting.pref);
}
}
foreach (var settingsBlock in m_SettingBlocks)
{
foreach (var block in settingsBlock.Value)
{
block.Invoke(null, s_SearchContext);
}
}
}
else
{
foreach (var key in m_Categories)
{
GUILayout.Label(key, EditorStyles.boldLabel);
List settings;
if (m_Settings.TryGetValue(key, out settings))
foreach (var setting in settings)
DoPreferenceField(setting.content, setting.pref);
List blocks;
if (m_SettingBlocks.TryGetValue(key, out blocks))
foreach (var block in blocks)
block.Invoke(null, s_SearchContext);
GUILayout.Space(8);
}
}
EditorGUIUtility.labelWidth = 0;
GUILayout.EndVertical();
if (EditorGUI.EndChangeCheck())
{
m_SettingsInstance.Save();
}
}
void DoPreferenceField(GUIContent title, IUserSetting pref)
{
if (EditorPrefs.GetBool("DeveloperMode", false))
{
if (pref.scope == SettingsScope.Project && !showProjectSettings)
return;
if (pref.scope == SettingsScope.User && !showUserSettings)
return;
}
if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.FloatField(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.IntField(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.Toggle(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.TextField(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.ColorField(title, cast.value);
}
#if UNITY_2018_3_OR_NEWER
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.GradientField(title, cast.value);
}
#endif
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.Vector2Field(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.Vector3Field(title, cast.value);
}
else if (pref is UserSetting)
{
var cast = (UserSetting)pref;
cast.value = EditorGUILayout.Vector4Field(title, cast.value);
}
else if (typeof(Enum).IsAssignableFrom(pref.type))
{
Enum val = (Enum)pref.GetValue();
EditorGUI.BeginChangeCheck();
if (Attribute.IsDefined(pref.type, typeof(FlagsAttribute)))
val = EditorGUILayout.EnumFlagsField(title, val);
else
val = EditorGUILayout.EnumPopup(title, val);
if (EditorGUI.EndChangeCheck())
pref.SetValue(val);
}
else if (typeof(UnityEngine.Object).IsAssignableFrom(pref.type))
{
var obj = (UnityEngine.Object)pref.GetValue();
EditorGUI.BeginChangeCheck();
obj = EditorGUILayout.ObjectField(title, obj, pref.type, false);
if (EditorGUI.EndChangeCheck())
pref.SetValue(obj);
}
else
{
GUILayout.BeginHorizontal();
GUILayout.Label(title, GUILayout.Width(EditorGUIUtility.labelWidth - EditorStyles.label.margin.right * 2));
var obj = pref.GetValue();
GUILayout.Label(obj == null ? "null" : pref.GetValue().ToString());
GUILayout.EndHorizontal();
}
SettingsGUILayout.DoResetContextMenuForLastRect(pref);
}
}
}