/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Unity.Query; using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Leap.Unity { /// /// Add this attribute to a settings check. This method will be called often while the /// Leap Motion Unity Window is open, so it should be as light-weight as possible! /// If you need to do a heavy check that involves scanning the current scene for /// example, you should gate the check behind a button. /// /// This project check is called during OnGUI in a Vertical layout context, so you /// should draw a box containing any messages, buttons, results, warnings, auto-fixes, /// and ignores suitable for the check. /// /// For "ignore" functionality, use LeapProjectChecks.CheckIgnoreKey(string) and /// LeapProjectsChecks.SetIgnoreKey(string) so that ignore actions can be remembered /// and cleared by the project checks GUI. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class LeapProjectCheckAttribute : Attribute { public string header; public int order; public LeapProjectCheckAttribute(string header, int order) { this.header = header; this.order = order; } } /// /// Utility class for working with project checks. Note, most features are only /// available in the Editor. /// public static class LeapProjectChecks { private struct ProjectCheck { public Func checkFunc; public LeapProjectCheckAttribute attribute; } private static List _projectChecks = null; private static void ensureChecksLoaded() { if (_projectChecks != null) { return; } _projectChecks = new List(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var type in assemblies.Query().SelectMany(a => a.GetTypes())) { foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { var attributes = method.GetCustomAttributes(typeof(LeapProjectCheckAttribute), inherit: true); if (attributes.Length == 0) { continue; } var attribute = attributes[0] as LeapProjectCheckAttribute; _projectChecks.Add(new ProjectCheck() { checkFunc = () => { if (!method.IsStatic) { Debug.LogError("Invalid project check definition; project checks must " + "be static methods."); return true; } else if (method.ReturnType == typeof(bool)) { return (bool)method.Invoke(null, null); } else { return true; } }, attribute = attribute }); } } _projectChecks.Sort((a, b) => a.attribute.order.CompareTo(b.attribute.order)); } /// /// Draws the GUI for project checks. All detected project checks will be run and /// their results shown. /// public static void DrawProjectChecksGUI() { #if UNITY_EDITOR ensureChecksLoaded(); bool allChecksPassed = true; foreach (var projectCheck in _projectChecks) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { allChecksPassed &= projectCheck.checkFunc(); } } if (_ignoredKeys != null && _ignoredKeys.Count > 0) { EditorGUILayout.Space(); using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); using (new EditorGUILayout.VerticalScope()) { GUILayout.Space(4f); GUILayout.Label("Some project checks have been ignored."); } if (GUILayout.Button(new GUIContent("Reset Ignore Flags", "Un-ignore any project checks that have been ignored."))) { ClearAllIgnoredKeys(); } EditorGUILayout.Space(); } } #endif } #region Ignored Keys via Editor Prefs private const string IGNORED_KEYS_PREF = "LeapUnityWindow_IgnoredKeys"; #if UNITY_EDITOR private static HashSet _backingIgnoredKeys = null; #endif /// Lazily filled via EditorPrefs. private static HashSet _ignoredKeys { get { #if UNITY_EDITOR if (_backingIgnoredKeys == null) { _backingIgnoredKeys = splitBySemicolonToSet(EditorPrefs.GetString(IGNORED_KEYS_PREF)); } return _backingIgnoredKeys; #else return null; #endif } } public static bool CheckIgnoredKey(string editorPrefKey) { #if UNITY_EDITOR return _ignoredKeys.Contains(editorPrefKey); #else return false; #endif } public static void SetIgnoredKey(string editorPrefKey, bool ignore) { #if UNITY_EDITOR if (ignore) { _ignoredKeys.Add(editorPrefKey); } else { _ignoredKeys.Remove(editorPrefKey); } uploadignoredKeyChangesToEditorPrefs(); #endif } public static void ClearAllIgnoredKeys() { #if UNITY_EDITOR _ignoredKeys.Clear(); uploadignoredKeyChangesToEditorPrefs(); #endif } /// /// Breaks out the semicolon-delimited string into a HashSet of strings. /// Whitespace is preserved. Empty entries are removed. /// private static HashSet splitBySemicolonToSet(string ignoredKeys_semicolonDelimited) { var keys = ignoredKeys_semicolonDelimited; var set = new HashSet(); foreach (var key in keys.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) { set.Add(key); } return set; } private static string joinBySemicolon(HashSet keys) { return string.Join(";", keys.Query().ToArray()); } private static void uploadignoredKeyChangesToEditorPrefs() { #if UNITY_EDITOR EditorPrefs.SetString(IGNORED_KEYS_PREF, joinBySemicolon(_ignoredKeys)); #endif } #endregion } }