Newer
Older
Simple-Multiplayer-Unity3D / Multiplayer Project / Library / PackageCache / com.unity.settings-manager@2.0.1 / Editor / UserSettingsProvider.cs
  1. #if UNITY_2018_3_OR_NEWER
  2. #define SETTINGS_PROVIDER_ENABLED
  3. #endif
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Reflection;
  9. using UnityEngine;
  10. #if SETTINGS_PROVIDER_ENABLED
  11. #if UNITY_2019_1_OR_NEWER
  12. using UnityEngine.UIElements;
  13. #else
  14. using UnityEngine.Experimental.UIElements;
  15. #endif
  16. #endif
  17. namespace UnityEditor.SettingsManagement
  18. {
  19. /// <summary>
  20. /// A <see cref="UnityEditor.SettingsProvider"/> implementation that creates an interface from settings reflected
  21. /// from a collection of assemblies.
  22. /// </summary>
  23. #if SETTINGS_PROVIDER_ENABLED
  24. public sealed class UserSettingsProvider : SettingsProvider
  25. #else
  26. public sealed class UserSettingsProvider
  27. #endif
  28. {
  29. /// <summary>
  30. /// Category string constant to check whether Unity is running in developer (internal) mode.
  31. /// </summary>
  32. public const string developerModeCategory = "Developer Mode";
  33. const string k_SettingsName = "UserSettingsProviderSettings";
  34. #if SETTINGS_PROVIDER_ENABLED
  35. const int k_LabelWidth = 240;
  36. static int labelWidth
  37. {
  38. get
  39. {
  40. if (s_DefaultLabelWidth != null)
  41. return (int)((float)s_DefaultLabelWidth.GetValue(null, null));
  42. return k_LabelWidth;
  43. }
  44. }
  45. static int defaultLayoutMaxWidth
  46. {
  47. get
  48. {
  49. if (s_DefaultLayoutMaxWidth != null)
  50. return (int)((float)s_DefaultLayoutMaxWidth.GetValue(null, null));
  51. return 0;
  52. }
  53. }
  54. #else
  55. const int k_LabelWidth = 180;
  56. int labelWidth
  57. {
  58. get { return k_LabelWidth; }
  59. }
  60. int defaultLayoutMaxWidth
  61. {
  62. get { return 0; }
  63. }
  64. #endif
  65. List<string> m_Categories;
  66. Dictionary<string, List<PrefEntry>> m_Settings;
  67. Dictionary<string, List<MethodInfo>> m_SettingBlocks;
  68. #if !SETTINGS_PROVIDER_ENABLED
  69. HashSet<string> keywords = new HashSet<string>();
  70. #endif
  71. static readonly string[] s_SearchContext = new string[1];
  72. EventType m_SettingsBlockKeywordsInitialized;
  73. Assembly[] m_Assemblies;
  74. static Settings s_Settings;
  75. Settings m_SettingsInstance;
  76. #if SETTINGS_PROVIDER_ENABLED
  77. static PropertyInfo s_DefaultLabelWidth;
  78. static PropertyInfo s_DefaultLayoutMaxWidth;
  79. #endif
  80. static Settings userSettingsProviderSettings
  81. {
  82. get
  83. {
  84. if (s_Settings == null)
  85. s_Settings = new Settings(new[] { new UserSettingsRepository() });
  86. return s_Settings;
  87. }
  88. }
  89. internal static UserSetting<bool> showHiddenSettings = new UserSetting<bool>(userSettingsProviderSettings, "settings.showHidden", false, SettingsScope.User);
  90. internal static UserSetting<bool> showUnregisteredSettings = new UserSetting<bool>(userSettingsProviderSettings, "settings.showUnregistered", false, SettingsScope.User);
  91. internal static UserSetting<bool> listByKey = new UserSetting<bool>(userSettingsProviderSettings, "settings.listByKey", false, SettingsScope.User);
  92. internal static UserSetting<bool> showUserSettings = new UserSetting<bool>(userSettingsProviderSettings, "settings.showUserSettings", true, SettingsScope.User);
  93. internal static UserSetting<bool> showProjectSettings = new UserSetting<bool>(userSettingsProviderSettings, "settings.showProjectSettings", true, SettingsScope.User);
  94. #if SETTINGS_PROVIDER_ENABLED
  95. /// <summary>
  96. /// Initializes and returns a new `UserSettingsProvider` instance.
  97. /// </summary>
  98. /// <param name="path">The settings menu path.</param>
  99. /// <param name="settings">The Settings instance that this provider is inspecting.</param>
  100. /// <param name="assemblies">A collection of assemblies to scan for <see cref="UserSettingAttribute"/> and <see cref="UserSettingBlockAttribute"/> attributes.</param>
  101. /// <param name="scopes">Which scopes this provider is valid for.</param>
  102. /// <exception cref="ArgumentNullException">Thrown if settings or assemblies is null.</exception>
  103. public UserSettingsProvider(string path, Settings settings, Assembly[] assemblies, SettingsScope scopes = SettingsScope.User)
  104. : base(path, scopes)
  105. #else
  106. /// <summary>
  107. /// Initializes and returns a new `UserSettingsProvider` instance.
  108. /// </summary>
  109. /// <param name="settings">The <see cref="Settings"/> instance that this provider is inspecting.</param>
  110. /// <param name="assemblies">A collection of assemblies to scan for <see cref="UserSettingAttribute"/> and <see cref="UserSettingBlockAttribute"/> attributes.</param>
  111. public UserSettingsProvider(Settings settings, Assembly[] assemblies)
  112. #endif
  113. {
  114. if (settings == null)
  115. throw new ArgumentNullException("settings");
  116. if (assemblies == null)
  117. throw new ArgumentNullException("assemblies");
  118. m_SettingsInstance = settings;
  119. m_Assemblies = assemblies;
  120. #if !SETTINGS_PROVIDER_ENABLED
  121. SearchForUserSettingAttributes();
  122. #endif
  123. }
  124. #if SETTINGS_PROVIDER_ENABLED
  125. /// <summary>
  126. /// Invoked by the <see cref="UnityEditor.SettingsProvider"/> when activated in the Editor.
  127. /// </summary>
  128. /// <param name="searchContext">
  129. /// Search context in the search box on the
  130. /// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
  131. /// </param>
  132. /// <param name="rootElement">
  133. /// Root of the UIElements tree. If you add to this root, the SettingsProvider uses
  134. /// [UIElements](https://docs.unity3d.com/ScriptReference/UnityEngine.UIElementsModule.html)
  135. /// instead of calling <see cref="OnGUI"/> to build the UI.
  136. /// See <see cref="UnityEditor.SettingsProvider.OnActivate"/> for details.
  137. /// </param>
  138. public override void OnActivate(string searchContext, VisualElement rootElement)
  139. {
  140. base.OnActivate(searchContext, rootElement);
  141. SearchForUserSettingAttributes();
  142. var window = GetType().GetProperty("settingsWindow", BindingFlags.Instance | BindingFlags.NonPublic);
  143. if (window != null)
  144. {
  145. s_DefaultLabelWidth = window.PropertyType.GetProperty("s_DefaultLabelWidth", BindingFlags.Public | BindingFlags.Static);
  146. s_DefaultLayoutMaxWidth = window.PropertyType.GetProperty("s_DefaultLayoutMaxWidth", BindingFlags.Public | BindingFlags.Static);
  147. }
  148. }
  149. #endif
  150. struct PrefEntry
  151. {
  152. GUIContent m_Content;
  153. IUserSetting m_Pref;
  154. public GUIContent content
  155. {
  156. get { return m_Content; }
  157. }
  158. public IUserSetting pref
  159. {
  160. get { return m_Pref; }
  161. }
  162. public PrefEntry(GUIContent content, IUserSetting pref)
  163. {
  164. m_Content = content;
  165. m_Pref = pref;
  166. }
  167. }
  168. void SearchForUserSettingAttributes()
  169. {
  170. var isDeveloperMode = EditorPrefs.GetBool("DeveloperMode", false);
  171. var keywordsHash = new HashSet<string>();
  172. if (m_Settings != null)
  173. m_Settings.Clear();
  174. else
  175. m_Settings = new Dictionary<string, List<PrefEntry>>();
  176. if (m_SettingBlocks != null)
  177. m_SettingBlocks.Clear();
  178. else
  179. m_SettingBlocks = new Dictionary<string, List<MethodInfo>>();
  180. var types = m_Assemblies.SelectMany(x => x.GetTypes());
  181. // collect instance fields/methods too, but only so we can throw a warning that they're invalid.
  182. var fields = types.SelectMany(x =>
  183. x.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
  184. .Where(prop => Attribute.IsDefined(prop, typeof(UserSettingAttribute))));
  185. var methods = types.SelectMany(x => x.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
  186. .Where(y => Attribute.IsDefined(y, typeof(UserSettingBlockAttribute))));
  187. foreach (var field in fields)
  188. {
  189. if (!field.IsStatic)
  190. {
  191. Debug.LogWarning("Cannot create setting entries for instance fields. Skipping \"" + field.Name + "\".");
  192. continue;
  193. }
  194. var attrib = (UserSettingAttribute)Attribute.GetCustomAttribute(field, typeof(UserSettingAttribute));
  195. if (!attrib.visibleInSettingsProvider)
  196. continue;
  197. var pref = (IUserSetting)field.GetValue(null);
  198. if (pref == null)
  199. {
  200. Debug.LogWarning("[UserSettingAttribute] is only valid for types implementing the IUserSetting interface. Skipping \"" + field.Name + "\"");
  201. continue;
  202. }
  203. var category = string.IsNullOrEmpty(attrib.category) ? "Uncategorized" : attrib.category;
  204. var content = listByKey ? new GUIContent(pref.key) : attrib.title;
  205. if (developerModeCategory.Equals(category) && !isDeveloperMode)
  206. continue;
  207. List<PrefEntry> settings;
  208. if (m_Settings.TryGetValue(category, out settings))
  209. settings.Add(new PrefEntry(content, pref));
  210. else
  211. m_Settings.Add(category, new List<PrefEntry>() { new PrefEntry(content, pref) });
  212. }
  213. foreach (var method in methods)
  214. {
  215. var attrib = (UserSettingBlockAttribute)Attribute.GetCustomAttribute(method, typeof(UserSettingBlockAttribute));
  216. var category = string.IsNullOrEmpty(attrib.category) ? "Uncategorized" : attrib.category;
  217. if (developerModeCategory.Equals(category) && !isDeveloperMode)
  218. continue;
  219. List<MethodInfo> blocks;
  220. var parameters = method.GetParameters();
  221. if (!method.IsStatic || parameters.Length < 1 || parameters[0].ParameterType != typeof(string))
  222. {
  223. Debug.LogWarning("[UserSettingBlockAttribute] is only valid for static functions with a single string parameter. Ex, `static void MySettings(string searchContext)`. Skipping \"" + method.Name + "\"");
  224. continue;
  225. }
  226. if (m_SettingBlocks.TryGetValue(category, out blocks))
  227. blocks.Add(method);
  228. else
  229. m_SettingBlocks.Add(category, new List<MethodInfo>() { method });
  230. }
  231. if (showHiddenSettings)
  232. {
  233. var unlisted = new List<PrefEntry>();
  234. m_Settings.Add("Unlisted", unlisted);
  235. foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Unlisted | SettingVisibility.Hidden))
  236. unlisted.Add(new PrefEntry(new GUIContent(pref.key), pref));
  237. }
  238. if (showUnregisteredSettings)
  239. {
  240. var unregistered = new List<PrefEntry>();
  241. m_Settings.Add("Unregistered", unregistered);
  242. foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Unregistered))
  243. unregistered.Add(new PrefEntry(new GUIContent(pref.key), pref));
  244. }
  245. foreach (var cat in m_Settings)
  246. {
  247. foreach (var entry in cat.Value)
  248. {
  249. var content = entry.content;
  250. if (content != null && !string.IsNullOrEmpty(content.text))
  251. {
  252. foreach (var word in content.text.Split(' '))
  253. keywordsHash.Add(word);
  254. }
  255. }
  256. }
  257. keywords = keywordsHash;
  258. m_Categories = m_Settings.Keys.Union(m_SettingBlocks.Keys).ToList();
  259. m_Categories.Sort();
  260. }
  261. #if SETTINGS_PROVIDER_ENABLED
  262. /// <summary>
  263. /// Invoked by the SettingsProvider container when drawing the UI header.
  264. /// </summary>
  265. public override void OnTitleBarGUI()
  266. {
  267. if (GUILayout.Button(GUIContent.none, SettingsGUIStyles.settingsGizmo))
  268. DoContextMenu();
  269. }
  270. #endif
  271. void InitSettingsBlockKeywords()
  272. {
  273. // Have to let the blocks run twice - one for Layout, one for Repaint.
  274. if (m_SettingsBlockKeywordsInitialized == EventType.Repaint)
  275. return;
  276. m_SettingsBlockKeywordsInitialized = Event.current.type;
  277. // Allows SettingsGUILayout.SettingsField to populate keywords
  278. SettingsGUILayout.s_Keywords = new HashSet<string>(keywords);
  279. // Set a dummy value so that GUI blocks with conditional foldouts will behave as though searching.
  280. s_SearchContext[0] = "Search";
  281. foreach (var category in m_SettingBlocks)
  282. {
  283. foreach (var block in category.Value)
  284. block.Invoke(null, s_SearchContext);
  285. }
  286. keywords = SettingsGUILayout.s_Keywords;
  287. SettingsGUILayout.s_Keywords = null;
  288. s_SearchContext[0] = "";
  289. }
  290. void DoContextMenu()
  291. {
  292. var menu = new GenericMenu();
  293. menu.AddItem(new GUIContent("Reset All"), false, () =>
  294. {
  295. if (!UnityEditor.EditorUtility.DisplayDialog("Reset All Settings", "Reset all settings? This is not undo-able.", "Reset", "Cancel"))
  296. return;
  297. // Do not reset SettingVisibility.Unregistered
  298. foreach (var pref in UserSettings.FindUserSettings(m_Assemblies, SettingVisibility.Visible | SettingVisibility.Hidden | SettingVisibility.Unlisted))
  299. pref.Reset();
  300. m_SettingsInstance.Save();
  301. });
  302. if (EditorPrefs.GetBool("DeveloperMode", false))
  303. {
  304. menu.AddSeparator("");
  305. menu.AddItem(new GUIContent("Developer/List Settings By Key"), listByKey, () =>
  306. {
  307. listByKey.SetValue(!listByKey, true);
  308. SearchForUserSettingAttributes();
  309. });
  310. menu.AddSeparator("Developer/");
  311. menu.AddItem(new GUIContent("Developer/Show User Settings"), showUserSettings, () =>
  312. {
  313. showUserSettings.SetValue(!showUserSettings, true);
  314. SearchForUserSettingAttributes();
  315. });
  316. menu.AddItem(new GUIContent("Developer/Show Project Settings"), showProjectSettings, () =>
  317. {
  318. showProjectSettings.SetValue(!showProjectSettings, true);
  319. SearchForUserSettingAttributes();
  320. });
  321. menu.AddSeparator("Developer/");
  322. menu.AddItem(new GUIContent("Developer/Show Unlisted Settings"), showHiddenSettings, () =>
  323. {
  324. showHiddenSettings.SetValue(!showHiddenSettings, true);
  325. SearchForUserSettingAttributes();
  326. });
  327. menu.AddItem(new GUIContent("Developer/Show Unregistered Settings"), showUnregisteredSettings, () =>
  328. {
  329. showUnregisteredSettings.SetValue(!showUnregisteredSettings, true);
  330. SearchForUserSettingAttributes();
  331. });
  332. menu.AddSeparator("Developer/");
  333. menu.AddItem(new GUIContent("Developer/Open Project Settings File"), false, () =>
  334. {
  335. var project = m_SettingsInstance.GetRepository(SettingsScope.Project);
  336. if (project != null)
  337. {
  338. var path = Path.GetFullPath(project.path);
  339. System.Diagnostics.Process.Start(path);
  340. }
  341. });
  342. menu.AddItem(new GUIContent("Developer/Print All Settings"), false, () =>
  343. {
  344. Debug.Log(UserSettings.GetSettingsString(m_Assemblies));
  345. });
  346. #if UNITY_2019_1_OR_NEWER
  347. menu.AddSeparator("Developer/");
  348. #if UNITY_2019_3_OR_NEWER
  349. menu.AddItem(new GUIContent("Developer/Recompile Scripts"), false, EditorUtility.RequestScriptReload);
  350. #else
  351. menu.AddItem(new GUIContent("Developer/Recompile Scripts"), false, UnityEditorInternal.InternalEditorUtility.RequestScriptReload);
  352. #endif
  353. #endif
  354. }
  355. menu.ShowAsContext();
  356. }
  357. #if SETTINGS_PROVIDER_ENABLED
  358. /// <summary>
  359. /// Called when the Settings window opens in the Editor.
  360. /// </summary>
  361. /// <param name="searchContext">
  362. /// Search context in the search box on the
  363. /// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
  364. /// </param>
  365. /// <seealso cref="UnityEditor.SettingsProvider.OnGUI"/>
  366. public override void OnGUI(string searchContext)
  367. #else
  368. /// <summary>
  369. /// Called when the Settings window opens in the Editor.
  370. /// </summary>
  371. /// <param name="searchContext">
  372. /// Search context in the search box on the
  373. /// [Settings](https://docs.unity3d.com/Manual/comp-ManagerGroup.html) window.
  374. /// </param>
  375. /// <seealso cref="UnityEditor.SettingsProvider.OnGUI"/>
  376. public void OnGUI(string searchContext)
  377. #endif
  378. {
  379. #if !SETTINGS_PROVIDER_ENABLED
  380. var evt = Event.current;
  381. if (evt.type == EventType.ContextClick)
  382. DoContextMenu();
  383. #endif
  384. InitSettingsBlockKeywords();
  385. EditorGUIUtility.labelWidth = labelWidth;
  386. EditorGUI.BeginChangeCheck();
  387. var maxWidth = defaultLayoutMaxWidth;
  388. if (maxWidth != 0)
  389. GUILayout.BeginVertical(SettingsGUIStyles.settingsArea, GUILayout.MaxWidth(maxWidth));
  390. else
  391. GUILayout.BeginVertical(SettingsGUIStyles.settingsArea);
  392. var hasSearchContext = !string.IsNullOrEmpty(searchContext);
  393. s_SearchContext[0] = searchContext;
  394. if (hasSearchContext)
  395. {
  396. // todo - Improve search comparison
  397. var searchKeywords = searchContext.Split(' ');
  398. foreach (var settingField in m_Settings)
  399. {
  400. foreach (var setting in settingField.Value)
  401. {
  402. if (searchKeywords.Any(x => !string.IsNullOrEmpty(x) && setting.content.text.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) > -1))
  403. DoPreferenceField(setting.content, setting.pref);
  404. }
  405. }
  406. foreach (var settingsBlock in m_SettingBlocks)
  407. {
  408. foreach (var block in settingsBlock.Value)
  409. {
  410. block.Invoke(null, s_SearchContext);
  411. }
  412. }
  413. }
  414. else
  415. {
  416. foreach (var key in m_Categories)
  417. {
  418. GUILayout.Label(key, EditorStyles.boldLabel);
  419. List<PrefEntry> settings;
  420. if (m_Settings.TryGetValue(key, out settings))
  421. foreach (var setting in settings)
  422. DoPreferenceField(setting.content, setting.pref);
  423. List<MethodInfo> blocks;
  424. if (m_SettingBlocks.TryGetValue(key, out blocks))
  425. foreach (var block in blocks)
  426. block.Invoke(null, s_SearchContext);
  427. GUILayout.Space(8);
  428. }
  429. }
  430. EditorGUIUtility.labelWidth = 0;
  431. GUILayout.EndVertical();
  432. if (EditorGUI.EndChangeCheck())
  433. {
  434. m_SettingsInstance.Save();
  435. }
  436. }
  437. void DoPreferenceField(GUIContent title, IUserSetting pref)
  438. {
  439. if (EditorPrefs.GetBool("DeveloperMode", false))
  440. {
  441. if (pref.scope == SettingsScope.Project && !showProjectSettings)
  442. return;
  443. if (pref.scope == SettingsScope.User && !showUserSettings)
  444. return;
  445. }
  446. if (pref is UserSetting<float>)
  447. {
  448. var cast = (UserSetting<float>)pref;
  449. cast.value = EditorGUILayout.FloatField(title, cast.value);
  450. }
  451. else if (pref is UserSetting<int>)
  452. {
  453. var cast = (UserSetting<int>)pref;
  454. cast.value = EditorGUILayout.IntField(title, cast.value);
  455. }
  456. else if (pref is UserSetting<bool>)
  457. {
  458. var cast = (UserSetting<bool>)pref;
  459. cast.value = EditorGUILayout.Toggle(title, cast.value);
  460. }
  461. else if (pref is UserSetting<string>)
  462. {
  463. var cast = (UserSetting<string>)pref;
  464. cast.value = EditorGUILayout.TextField(title, cast.value);
  465. }
  466. else if (pref is UserSetting<Color>)
  467. {
  468. var cast = (UserSetting<Color>)pref;
  469. cast.value = EditorGUILayout.ColorField(title, cast.value);
  470. }
  471. #if UNITY_2018_3_OR_NEWER
  472. else if (pref is UserSetting<Gradient>)
  473. {
  474. var cast = (UserSetting<Gradient>)pref;
  475. cast.value = EditorGUILayout.GradientField(title, cast.value);
  476. }
  477. #endif
  478. else if (pref is UserSetting<Vector2>)
  479. {
  480. var cast = (UserSetting<Vector2>)pref;
  481. cast.value = EditorGUILayout.Vector2Field(title, cast.value);
  482. }
  483. else if (pref is UserSetting<Vector3>)
  484. {
  485. var cast = (UserSetting<Vector3>)pref;
  486. cast.value = EditorGUILayout.Vector3Field(title, cast.value);
  487. }
  488. else if (pref is UserSetting<Vector4>)
  489. {
  490. var cast = (UserSetting<Vector4>)pref;
  491. cast.value = EditorGUILayout.Vector4Field(title, cast.value);
  492. }
  493. else if (typeof(Enum).IsAssignableFrom(pref.type))
  494. {
  495. Enum val = (Enum)pref.GetValue();
  496. EditorGUI.BeginChangeCheck();
  497. if (Attribute.IsDefined(pref.type, typeof(FlagsAttribute)))
  498. val = EditorGUILayout.EnumFlagsField(title, val);
  499. else
  500. val = EditorGUILayout.EnumPopup(title, val);
  501. if (EditorGUI.EndChangeCheck())
  502. pref.SetValue(val);
  503. }
  504. else if (typeof(UnityEngine.Object).IsAssignableFrom(pref.type))
  505. {
  506. var obj = (UnityEngine.Object)pref.GetValue();
  507. EditorGUI.BeginChangeCheck();
  508. obj = EditorGUILayout.ObjectField(title, obj, pref.type, false);
  509. if (EditorGUI.EndChangeCheck())
  510. pref.SetValue(obj);
  511. }
  512. else
  513. {
  514. GUILayout.BeginHorizontal();
  515. GUILayout.Label(title, GUILayout.Width(EditorGUIUtility.labelWidth - EditorStyles.label.margin.right * 2));
  516. var obj = pref.GetValue();
  517. GUILayout.Label(obj == null ? "null" : pref.GetValue().ToString());
  518. GUILayout.EndHorizontal();
  519. }
  520. SettingsGUILayout.DoResetContextMenuForLastRect(pref);
  521. }
  522. }
  523. }