/// <summary> /// Draws a search field and (if results have been returned) search results. /// </summary> /// <returns>True if search results are being displayed.</returns> public static bool DrawSearchInterface(UnityEngine.Object target) { if (target == null) { return(false); } bool drewSearchGUI = false; if (target != activeTarget) { activeTarget = target; config = new SearchConfig(); prevConfig = new SearchConfig(); searchResults.Clear(); } using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("Search For: ", GUILayout.MaxWidth(70)); string searchString = SessionState.GetString(searchDisplaySearchFieldKey, string.Empty); config.SearchFieldString = EditorGUILayout.TextField(SessionState.GetString(searchDisplaySearchFieldKey, string.Empty), GUILayout.ExpandWidth(true)); if (GUILayout.Button("Clear", EditorStyles.miniButton, GUILayout.MaxWidth(50))) { config.SearchFieldString = string.Empty; } } config.RequireAllKeywords = SessionState.GetBool(searchDisplayRequireAllKeywordsKey, true); config.SearchTooltips = SessionState.GetBool(searchDisplaySearchTooltipsKey, true); config.SearchFieldContent = SessionState.GetBool(searchDisplaySearchFieldContentKey, false); if (!string.IsNullOrEmpty(config.SearchFieldString)) { // If we're searching for something, draw the search GUI DrawSearchResultInterface(target); drewSearchGUI = true; } // Store search settings SessionState.SetString(searchDisplaySearchFieldKey, config.SearchFieldString); SessionState.SetBool(searchDisplayRequireAllKeywordsKey, config.RequireAllKeywords); SessionState.SetBool(searchDisplaySearchTooltipsKey, config.SearchTooltips); SessionState.SetBool(searchDisplaySearchFieldContentKey, config.SearchFieldContent); return(drewSearchGUI); }
private static async Task SearchProfileField(UnityEngine.Object profile, SearchConfig config, List <ProfileSearchResult> searchResults) { await Task.Yield(); // The result that we will return, if not empty ProfileSearchResult result = new ProfileSearchResult(); result.Profile = profile; BaseMixedRealityProfile baseProfile = (profile as BaseMixedRealityProfile); result.IsCustomProfile = (baseProfile != null) ? baseProfile.IsCustomProfile : false; searchResults.Add(result); // Go through the profile's serialized fields foreach (SerializedProperty property in GatherProperties(profile)) { if (CheckFieldForProfile(property)) { await SearchProfileField(property.objectReferenceValue, config, searchResults); } else { CheckFieldForKeywords(property, config, result); } } if (result.Fields.Count > 0) { result.Fields.Sort(delegate(FieldSearchResult r1, FieldSearchResult r2) { if (r1.MatchStrength != r2.MatchStrength) { return(r2.MatchStrength.CompareTo(r1.MatchStrength)); } return(r2.Property.name.CompareTo(r1.Property.name)); }); } }
private static void DrawSearchResultInterface(UnityEngine.Object target) { bool optionsFoldout = SessionState.GetBool(searchDisplayOptionsFoldoutKey, false); optionsFoldout = EditorGUILayout.Foldout(optionsFoldout, "Search Preferences", true); SessionState.SetBool(searchDisplayOptionsFoldoutKey, optionsFoldout); if (optionsFoldout) { config.RequireAllKeywords = EditorGUILayout.Toggle("Require All Keywords", config.RequireAllKeywords); config.SearchTooltips = EditorGUILayout.Toggle("Search Tooltips", config.SearchTooltips); config.SearchFieldContent = EditorGUILayout.Toggle("Search Field Content", config.SearchFieldContent); } if (!config.Equals(prevConfig) && !MixedRealitySearchUtility.Searching) { MixedRealitySearchUtility.StartProfileSearch(target, config, OnSearchComplete); searchResults.Clear(); prevConfig = config; } #region display results using (new EditorGUILayout.VerticalScope()) { if (searchResults.Count == 0) { if (MixedRealitySearchUtility.Searching) { EditorGUILayout.HelpBox("Searching...", MessageType.Info); } else { EditorGUILayout.HelpBox("No search results. Try selecting a subject or entering a keyword.", MessageType.Warning); } } int numDisplayedSearchResults = 0; if (searchResults.Count > 0) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Results:"); foreach (ProfileSearchResult search in searchResults) { if (search.Fields.Count == 0) { // Don't display results with no fields continue; } using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("Fields found in: ", EditorStyles.boldLabel, GUILayout.MaxWidth(105)); EditorGUILayout.ObjectField(search.Profile, typeof(UnityEngine.Object), false, GUILayout.ExpandWidth(true)); if (GUILayout.Button("View Asset", GUILayout.MaxWidth(75))) { Selection.activeObject = search.Profile; EditorGUIUtility.PingObject(search.Profile); } } if (MixedRealityProjectPreferences.LockProfiles && !search.IsCustomProfile) { EditorGUILayout.HelpBox("Clone this profile to edit default properties.", MessageType.Warning); } using (new EditorGUI.DisabledGroupScope(MixedRealityProjectPreferences.LockProfiles && !search.IsCustomProfile)) { using (new EditorGUI.IndentLevelScope(1)) { EditorGUILayout.Space(); foreach (FieldSearchResult r in search.Fields) { numDisplayedSearchResults++; GUI.color = Color.white; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(r.Property, true); if (EditorGUI.EndChangeCheck()) { r.Property.serializedObject.ApplyModifiedProperties(); } EditorGUILayout.Space(); } } } } EditorGUILayout.Space(); } } } #endregion }
/// <summary> /// Starts a profile search. 'Searching' must be false or an exception will be thrown. /// </summary> /// <param name="profile">Profile object to search.</param> /// <param name="config">Configuration for the search.</param> /// <param name="onSearchComplete">Action to invoke once search is complete - delivers final results.</param> public static async void StartProfileSearch(UnityEngine.Object profile, SearchConfig config, Action <bool, UnityEngine.Object, IReadOnlyCollection <ProfileSearchResult> > onSearchComplete) { if (activeTask != null && !activeTask.IsCompleted) { throw new Exception("Can't start a new search until the old one has completed."); } List <ProfileSearchResult> searchResults = new List <ProfileSearchResult>(); // Validate search configuration if (string.IsNullOrEmpty(config.SearchFieldString)) { // If the config is empty, bail early onSearchComplete?.Invoke(true, profile, searchResults); return; } // Generate keywords if we haven't yet if (config.Keywords == null) { config.Keywords = new HashSet <string>(config.SearchFieldString.Split(new string[] { " ", "," }, StringSplitOptions.RemoveEmptyEntries)); config.Keywords.RemoveWhere(s => s.Length < minSearchStringLength); } if (config.Keywords.Count == 0) { // If there are no useful keywords, bail early onSearchComplete?.Invoke(true, profile, searchResults); return; } // Launch the search task bool cancelled = false; try { activeTask = SearchProfileField(profile, config, searchResults); await activeTask; } catch (Exception e) { // Profile was probably deleted in the middle of searching. Debug.LogException(e); cancelled = true; } finally { searchResults.Sort(delegate(ProfileSearchResult r1, ProfileSearchResult r2) { if (r1.ProfileMatchStrength != r2.ProfileMatchStrength) { return(r2.ProfileMatchStrength.CompareTo(r1.ProfileMatchStrength)); } else { return(r2.Profile.name.CompareTo(r1.Profile.name)); } }); searchResults.RemoveAll(r => r.Fields.Count <= 0); onSearchComplete?.Invoke(cancelled, profile, searchResults); } }
private static void CheckFieldForKeywords(SerializedProperty property, SearchConfig config, ProfileSearchResult result) { int numMatchedKeywords = 0; int numExactMatches = 0; int numFieldMatches = 0; int numTooltipMatches = 0; int numContentMatches = 0; string propertyName = property.name.ToLower(); string toolTip = property.tooltip.ToLower(); foreach (string keyword in config.Keywords) { bool keywordMatch = false; if (propertyName.Contains(keyword)) { keywordMatch = true; numFieldMatches++; if (propertyName == keyword) { numExactMatches++; } } if (config.SearchTooltips) { if (toolTip.Contains(keyword)) { keywordMatch = true; numTooltipMatches++; } } if (config.SearchFieldContent) { switch (property.propertyType) { case SerializedPropertyType.ObjectReference: if (property.objectReferenceValue != null && property.objectReferenceValue.name.ToLower().Contains(keyword)) { keywordMatch = true; numContentMatches++; } break; case SerializedPropertyType.String: if (!string.IsNullOrEmpty(property.stringValue) && property.stringValue.ToLower().Contains(keyword)) { keywordMatch = true; numContentMatches++; } break; } } if (keywordMatch) { numMatchedKeywords++; } } bool requirementsMet = numMatchedKeywords > 0; if (config.RequireAllKeywords && config.Keywords.Count > 1) { requirementsMet &= numMatchedKeywords >= config.Keywords.Count; } if (requirementsMet) { int matchStrength = numMatchedKeywords + numExactMatches; if (numMatchedKeywords >= config.Keywords.Count) { // If we match all keywords in a multi-keyword search, double the score matchStrength *= 2; } // Weight the score based on match type matchStrength += numFieldMatches * 3; matchStrength += numTooltipMatches * 2; matchStrength += numContentMatches * 1; result.ProfileMatchStrength += matchStrength; result.Fields.Add(new FieldSearchResult() { Property = property.Copy(), MatchStrength = numMatchedKeywords, }); } }