Search Unity

  1. We are migrating the Unity Forums to Unity Discussions by the end of July. Read our announcement for more information and let us know if you have any questions.
    Dismiss Notice
  2. Dismiss Notice

Discussion still no search field in the inspector...

Discussion in 'Unity 6 Beta' started by laurentlavigne, Mar 10, 2024.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,457
    when coming back from unreal to unity, you realize that your work goes to a crawl and see that there is no search in the inspector

    upload_2024-3-9_20-23-10.png

    vital because in production, a gamobject easily contains 10 components totaling 50 variables
    when making a decision of changing a variable during tuning, one has to switch context from decision to MANUAL search mode, this context switching kicks you out of flow, which is extremely costly

    because unreal has search inspector, it doesn't require context switch to manual search so it is far better at keeping you in the state of flow, which is simply astonishing because unity is smoother

    as a result, instead of much preferring working in unity, which should be the case when you look at the list of pros (for example editor state = game state), preference goes towards unreal.

    example of typical gameobject

    upload_2024-3-9_20-28-39.png

    ECS team, acknowledging this problem already implemented inspector search
     
  2. joan_stark

    joan_stark

    Joined:
    Jul 24, 2018
    Posts:
    57
    So true, I don't understand how hard this is, when plugins like Odin have done it. Its key and vital.
     
  3. Wrymnn

    Wrymnn

    Joined:
    Sep 24, 2014
    Posts:
    388
    odin can search only individual components, not entire hierarchy of them for entire gameobject
     
  4. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,488
    Hi. Interesting suggestion. We have had limited success doing these type of search fields in parts for example we have something in the property window. It gets complicated when we have to deal with IMGUI and custom property drawers. I'll bring it up with the team, it would certainly be useful.
     
    Last edited: Mar 12, 2024
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    GPT-4 was able to do it. Here's the initial version it came up with.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Reflection;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6.  
    7. [CustomEditor(typeof(GameObject))]
    8. public class CustomGameObjectInspector : Editor
    9. {
    10.     private string searchString = "";
    11.     private List<string> matchingFieldNames = new List<string>();
    12.  
    13.     public override void OnInspectorGUI()
    14.     {
    15.         base.OnInspectorGUI(); // Draws the default inspector
    16.  
    17.         GUILayout.Space(10);
    18.         GUILayout.Label("Field Search in Inspector", EditorStyles.boldLabel);
    19.  
    20.         EditorGUI.BeginChangeCheck();
    21.         searchString = EditorGUILayout.TextField("Search String", searchString);
    22.         if (EditorGUI.EndChangeCheck() && !string.IsNullOrWhiteSpace(searchString))
    23.         {
    24.             SearchFieldsByDisplayName(target);
    25.         }
    26.  
    27.         if (matchingFieldNames.Any())
    28.         {
    29.             GUILayout.Label("Results:", EditorStyles.boldLabel);
    30.             foreach (var fieldName in matchingFieldNames)
    31.             {
    32.                 EditorGUILayout.TextField(fieldName);
    33.             }
    34.         }
    35.     }
    36.  
    37.     private void SearchFieldsByDisplayName(Object searchTarget)
    38.     {
    39.         matchingFieldNames.Clear();
    40.         if (searchTarget == null) return;
    41.  
    42.         Component[] components = ((GameObject)searchTarget).GetComponents<Component>();
    43.         foreach (Component component in components)
    44.         {
    45.             SerializedObject serializedObject = new SerializedObject(component);
    46.             SerializedProperty serializedProperty = serializedObject.GetIterator();
    47.  
    48.             while (serializedProperty.NextVisible(true))
    49.             {
    50.                 if (serializedProperty.displayName.ToLowerInvariant().Contains(searchString.ToLowerInvariant()))
    51.                 {
    52.                     matchingFieldNames.Add($"{component.GetType().Name}: {serializedProperty.displayName}");
    53.                 }
    54.             }
    55.         }
    56.  
    57.         matchingFieldNames = matchingFieldNames.Distinct().ToList(); // Remove duplicates
    58.     }
    59. }

    upload_2024-3-12_14-11-35.png

    And a version that allows you to directly modify the property fields.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. [CustomEditor(typeof(GameObject))]
    7. public class CustomGameObjectInspectorWithPropertyModification : Editor
    8. {
    9.     private string searchString = "";
    10.     private List<SerializedProperty> matchingProperties = new List<SerializedProperty>();
    11.     private Dictionary<string, SerializedObject> serializedObjectsCache = new Dictionary<string, SerializedObject>();
    12.  
    13.     public override void OnInspectorGUI()
    14.     {
    15.         base.OnInspectorGUI(); // Draws the default inspector
    16.  
    17.         GUILayout.Space(10);
    18.         GUILayout.Label("Field Search in Inspector", EditorStyles.boldLabel);
    19.  
    20.         EditorGUI.BeginChangeCheck();
    21.         searchString = EditorGUILayout.TextField("Search String", searchString);
    22.         if (EditorGUI.EndChangeCheck())
    23.         {
    24.             if (!string.IsNullOrWhiteSpace(searchString))
    25.             {
    26.                 SearchFieldsByDisplayName(target);
    27.             }
    28.             else
    29.             {
    30.                 matchingProperties.Clear(); // Clear results if search string is cleared
    31.             }
    32.         }
    33.  
    34.         if (matchingProperties.Any())
    35.         {
    36.             GUILayout.Label("Results:", EditorStyles.boldLabel);
    37.             foreach (var property in matchingProperties)
    38.             {
    39.                 SerializedObject serializedObject = serializedObjectsCache[property.propertyPath];
    40.                 serializedObject.Update();
    41.                 EditorGUILayout.PropertyField(property, true);
    42.                 serializedObject.ApplyModifiedProperties();
    43.             }
    44.         }
    45.     }
    46.  
    47.     private void SearchFieldsByDisplayName(Object searchTarget)
    48.     {
    49.         matchingProperties.Clear();
    50.         serializedObjectsCache.Clear();
    51.         if (searchTarget == null) return;
    52.  
    53.         Component[] components = ((GameObject)searchTarget).GetComponents<Component>();
    54.         foreach (Component component in components)
    55.         {
    56.             SerializedObject serializedObject = new SerializedObject(component);
    57.             SerializedProperty serializedProperty = serializedObject.GetIterator();
    58.             bool enterChildren = true;
    59.  
    60.             while (serializedProperty.NextVisible(enterChildren))
    61.             {
    62.                 if (serializedProperty.displayName.ToLowerInvariant().Contains(searchString.ToLowerInvariant()))
    63.                 {
    64.                     matchingProperties.Add(serializedProperty.Copy());
    65.                     serializedObjectsCache[serializedProperty.propertyPath] = serializedObject;
    66.                 }
    67.                 enterChildren = false;
    68.             }
    69.         }
    70.     }
    71. }

    upload_2024-3-12_14-11-11.png

    There are limitations with both of these scripts but it's a good enough proof of concept.
     
    Last edited: Mar 12, 2024
  6. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 5, 2024
    Posts:
    574
    They can't make a sad context menu work in a year, why do we think they can implement a search?
     
    Ruslank100 and IOU_RAY like this.
  7. nehvaleem

    nehvaleem

    Joined:
    Dec 13, 2012
    Posts:
    445
    Yeah, I don't like to rant too much, but it is quite baffling that the company making game engine (which is really complex and demanding task) fails to perform in such not-groundbreaking features.

    "We have had limited success doing these type of search fields" that's one way to put it :)
     
    Ruslank100, joan_stark and IOU_RAY like this.
  8. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    Yeah, I'm confused by this too, I've seen them make similar comments on other things too. Like the title bar that they wanted to change the behavior of and seemingly not knowing basic features of Win32. It seems like obvious things that I'm curious if it's just management making dumb choices which is admittedly normal for this company.
     
    Ruslank100 likes this.
  9. marcoantap

    marcoantap

    Joined:
    Sep 23, 2012
    Posts:
    256
    Ryiah likes this.
  10. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,457
    not useful, vital!

    did you see that inspector image I showed you? this was in a 2 people project.

    now imagine a project with 10x more people, plus contractors that need to get a hang of it quickly, and realize that time is money.

    everything that cuts down onboarding time and trends search down to 0 second is money saved
     
    Last edited: Mar 13, 2024
    Ruslank100 and joan_stark like this.
  11. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,457
    then
    *sigh*

    that's all we need.

    it's not like you're releasing an API so there is no need to over engineer things. no need for indexing either, if users complain about perfs on large arrays then yeah you can add that

    so, just release what you can but do it next month, then work your way up to handle customization
     
    Last edited: Mar 13, 2024
  12. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    I've had so many cases where I wanted to build an editor tool and didn't know how to start but GPT-4 was able to generate an example with just a few prompts. The first example code took two prompts and the second example came from the third prompt after I had already started posting and was curious.
     
    laurentlavigne likes this.
  13. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,488
    That's how we get accused of releasing unfinished features. We already have our roadmap and plans, we don't have space to add things on a whim. They have to be planned for.

    There's a lot more to it than chat gpt would have you believe.
     
    MousePods, PaulMDev, halley and 2 others like this.
  14. IOU_RAY

    IOU_RAY

    Joined:
    Jul 25, 2017
    Posts:
    127
    Not to push Unity Devs even further from communicating with us with rants/complaints.....but it is abundantly clear there's way too much bureaucracy involved in the workflow.

    Yet still....major issues go released unnoticed, it takes eons for small adjustments and important fixes, QoL features constantly either work poorly or are tossed under the rug. Priorities are always leaving massive question marks for the majority of us.

    Constantly having to work around editor/service issues, given the usual shpeal of how complicated it is or that it's in the works only to see the thread updated by a random pass-byer years later, "Any updates??" followed by apologies and excuses.

    On a personal level I'm finding all joy of game development sucked out by working within the Unity editor, and my only hope is improvements on these future versions. Something as simple as a parameter filter as suggested above would be a tiny but solid improvement, but it'll have to be yet another custom tool working around the editor.
     
    Ruslank100 and laurentlavigne like this.
  15. IAndrewNovak

    IAndrewNovak

    Joined:
    Nov 29, 2013
    Posts:
    132
    Ruslank100 and marcoantap like this.
  16. Saniell

    Saniell

    Joined:
    Oct 24, 2015
    Posts:
    210
    Don't you guys already have search in project settings which does support custom menus? Or *at least* highlighting
     
  17. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    Yes and no. What I generated with ChatGPT is a very minimal proof of concept, but what the team is developing is far more than needed because it has to handle all of the common use cases. If I had proper access to the editor's interface source code I could make the editor behave in the exact fashion that I wanted it to.

    I'm tempted to try modifying the UnityEngine assembly because the source code (albeit in a reverse engineered format rather than the original) is right there. I wouldn't even need to make extensive changes. In some cases it's literally just switching access permissions from internal or private to public to enable a feature in a custom editor.
     
    Last edited: Mar 13, 2024
  18. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 5, 2024
    Posts:
    574
    Yeah, but that's broken too... as always.
     
    IOU_RAY likes this.
  19. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    880
    great script, made some tweaks to it, mainly due to how it is presented(didnt care for the text only representation of the original inspector) - https://gist.github.com/thelebaron/d2b58da74b8fd186ebb41618824565e8
    Unity_8xY6Z9tpcB.png
    honestly its so simple, kinda wondering what pitfalls there are for it. One thing I did notice, it wont find fields inside of a serialized struct within a monobehaviour
     
    laurentlavigne likes this.
  20. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    Approximately one dozen prompts later... :p

    upload_2024-3-13_19-50-31.png

    Code (csharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. namespace Junk.Utilities
    7. {
    8.     public static class SearchInspector
    9.     {
    10.         private static string searchString = "Search...";
    11.         private static Dictionary<string, List<SerializedProperty>> matchingProperties = new Dictionary<string, List<SerializedProperty>>();
    12.         private static Dictionary<string, SerializedObject> serializedObjectsCache = new Dictionary<string, SerializedObject>();
    13.  
    14.         [InitializeOnLoadMethod]
    15.         static void Init()
    16.         {
    17.             UnityEditor.Editor.finishedDefaultHeaderGUI += OnDisplaySearch;
    18.         }
    19.  
    20.         static void OnDisplaySearch(UnityEditor.Editor editor)
    21.         {
    22.             if (!(editor.target is GameObject))
    23.                 return;
    24.  
    25.             EditorGUI.BeginChangeCheck();
    26.             searchString = EditorGUILayout.TextField(searchString);
    27.             if (EditorGUI.EndChangeCheck())
    28.             {
    29.                 if (!string.IsNullOrWhiteSpace(searchString))
    30.                 {
    31.                     SearchFieldsAndProperties(editor.target);
    32.                 }
    33.                 else
    34.                 {
    35.                     matchingProperties.Clear();
    36.                     serializedObjectsCache.Clear();
    37.                 }
    38.             }
    39.  
    40.             if (matchingProperties.Any())
    41.             {
    42.                 GUILayout.Label("Results:", EditorStyles.boldLabel);
    43.                 foreach (var kvp in matchingProperties)
    44.                 {
    45.                     // Get display name for the parent path.
    46.                     string parentDisplayName = GetDisplayNameFromPath(kvp.Key);
    47.                     SerializedObject serializedObject = serializedObjectsCache[kvp.Key];
    48.                     serializedObject.Update();
    49.                     EditorGUI.indentLevel++;
    50.  
    51.                     // Track if we have shown the struct/class label.
    52.                     bool shownParentLabel = false;
    53.  
    54.                     foreach (SerializedProperty property in kvp.Value)
    55.                     {
    56.                         // Check if we should skip the parent label.
    57.                         if (!shownParentLabel && parentDisplayName != property.displayName)
    58.                         {
    59.                             GUILayout.Label(parentDisplayName, EditorStyles.boldLabel);
    60.                             shownParentLabel = true;
    61.                         }
    62.  
    63.                         EditorGUILayout.PropertyField(property, true);
    64.                     }
    65.                     EditorGUI.indentLevel--;
    66.                     serializedObject.ApplyModifiedProperties();
    67.                 }
    68.             }
    69.         }
    70.  
    71.         private static void SearchFieldsAndProperties(Object searchTarget)
    72.         {
    73.             matchingProperties.Clear();
    74.             serializedObjectsCache.Clear();
    75.             if (searchTarget == null) return;
    76.  
    77.             Component[] components = ((GameObject)searchTarget).GetComponents<Component>();
    78.             foreach (Component component in components)
    79.             {
    80.                 SerializedObject serializedObject = new SerializedObject(component);
    81.                 SerializedProperty serializedProperty = serializedObject.GetIterator();
    82.                 bool enterChildren = true;
    83.  
    84.                 while (serializedProperty.NextVisible(enterChildren))
    85.                 {
    86.                     if (PropertyMatchesSearch(serializedProperty))
    87.                     {
    88.                         AddPropertyIfNotAdded(serializedProperty, serializedObject);
    89.                     }
    90.                     else if (serializedProperty.hasChildren)
    91.                     {
    92.                         SearchWithinSerializedProperty(serializedProperty, serializedObject);
    93.                     }
    94.                     enterChildren = false;
    95.                 }
    96.             }
    97.         }
    98.  
    99.         private static bool PropertyMatchesSearch(SerializedProperty property)
    100.         {
    101.             return property.displayName.ToLowerInvariant().Contains(searchString.ToLowerInvariant());
    102.         }
    103.  
    104.         private static void AddPropertyIfNotAdded(SerializedProperty property, SerializedObject serializedObject)
    105.         {
    106.             string parentPath = GetPropertyParentPath(property);
    107.             if (!matchingProperties.ContainsKey(parentPath))
    108.             {
    109.                 matchingProperties[parentPath] = new List<SerializedProperty>();
    110.             }
    111.             if (!matchingProperties[parentPath].Any(p => p.propertyPath == property.propertyPath))
    112.             {
    113.                 matchingProperties[parentPath].Add(property.Copy());
    114.                 if (!serializedObjectsCache.ContainsKey(parentPath))
    115.                 {
    116.                     serializedObjectsCache[parentPath] = serializedObject;
    117.                 }
    118.             }
    119.         }
    120.  
    121.         private static void SearchWithinSerializedProperty(SerializedProperty parentProperty, SerializedObject parentSerializedObject)
    122.         {
    123.             var childSerializedProperty = parentProperty.Copy();
    124.             bool enterChildren = true;
    125.             while (childSerializedProperty.NextVisible(enterChildren))
    126.             {
    127.                 if (PropertyMatchesSearch(childSerializedProperty))
    128.                 {
    129.                     AddPropertyIfNotAdded(childSerializedProperty, parentSerializedObject);
    130.                 }
    131.                 enterChildren = false;
    132.             }
    133.         }
    134.  
    135.         private static string GetPropertyParentPath(SerializedProperty property)
    136.         {
    137.             var path = property.propertyPath;
    138.             int dotIndex = path.LastIndexOf('.');
    139.             if (dotIndex != -1)
    140.             {
    141.                 return path.Substring(0, dotIndex);
    142.             }
    143.             return property.propertyPath; // Return the full path if no parent is identified
    144.         }
    145.  
    146.         private static string GetDisplayNameFromPath(string path)
    147.         {
    148.             // Convert a path into a user-friendly display name.
    149.             var parts = path.Split('.');
    150.             if (parts.Length > 0)
    151.             {
    152.                 // Attempt to infer a user-friendly name from the last segment of the path
    153.                 return ObjectNames.NicifyVariableName(parts.Last());
    154.             }
    155.             return path; // Fallback to the path if unable to process
    156.         }
    157.     }
    158. }

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class Foo : MonoBehaviour
    5. {
    6.     [Serializable]
    7.     public struct MyStruct
    8.     {
    9.         public int x;
    10.         public float someFloat;
    11.         public Color color;
    12.     }
    13.  
    14.     public MyStruct myStruct;
    15. }
     
    Last edited: Mar 13, 2024
  21. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,457
    no, you get accused of releasing unfinished features because of wrong focus on what *you* want us to use vs what we asked for
    and in this case, we've asked for it over 5 years ago
    also, anything that reduces our cost is higher priority
    and finally, UI tools such as inspector search isn't the same as long term API such as network and rendering, which half baked system have long term costs on production teams
    UI is only front end, we don't care how you do it. if it's half baked it's better than nothing at all, then you fix it.
     
    AcidArrow, IOU_RAY, Ryiah and 3 others like this.
  22. Slashbot64

    Slashbot64

    Joined:
    Jun 15, 2020
    Posts:
    358
    lets keep going with it... I'd like a [x] to reset the search field at the end like all good search boxes have. fuzzy search?
     
    laurentlavigne likes this.
  23. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,740
    I ended up with a button rather than the fancier approach because it needed an icon and I don't know off hand what is needed to access the built-in icon.

    upload_2024-3-14_20-34-0.png

    Code (csharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. namespace Junk.Utilities
    7. {
    8.     public static class SearchInspector
    9.     {
    10.         private static string searchString = "Search...";
    11.         private static Dictionary<string, List<SerializedProperty>> matchingProperties = new Dictionary<string, List<SerializedProperty>>();
    12.         private static Dictionary<string, SerializedObject> serializedObjectsCache = new Dictionary<string, SerializedObject>();
    13.  
    14.         [InitializeOnLoadMethod]
    15.         static void Init()
    16.         {
    17.             UnityEditor.Editor.finishedDefaultHeaderGUI += OnDisplaySearch;
    18.         }
    19.  
    20.         static void OnDisplaySearch(UnityEditor.Editor editor)
    21.         {
    22.             if (!(editor.target is GameObject))
    23.                 return;
    24.  
    25.             EditorGUILayout.BeginHorizontal(); // Start of the horizontal layout for the search field and reset button
    26.  
    27.             EditorGUI.BeginChangeCheck();
    28.             searchString = EditorGUILayout.TextField(searchString);
    29.             if (EditorGUI.EndChangeCheck())
    30.             {
    31.                 // If search string is changed, update the search results
    32.                 if (!string.IsNullOrWhiteSpace(searchString) && searchString != "Search...")
    33.                 {
    34.                     SearchFieldsAndProperties(editor.target);
    35.                 }
    36.                 else
    37.                 {
    38.                     matchingProperties.Clear();
    39.                     serializedObjectsCache.Clear();
    40.                 }
    41.             }
    42.  
    43.             // "X" button to clear the search field
    44.             if (GUILayout.Button("X", GUILayout.Width(20), GUILayout.Height(20)))
    45.             {
    46.                 searchString = "Search..."; // Clear the search string
    47.                 GUI.FocusControl(null); // Remove focus from the search field to reflect the clear immediately
    48.                 matchingProperties.Clear(); // Clear search results
    49.                 serializedObjectsCache.Clear(); // Clear cache
    50.             }
    51.  
    52.             EditorGUILayout.EndHorizontal(); // End of the horizontal layout
    53.  
    54.             // If there are any matching properties, display them below the search field
    55.             if (matchingProperties.Any())
    56.             {
    57.                 GUILayout.Label("Results:", EditorStyles.boldLabel);
    58.                 foreach (var kvp in matchingProperties)
    59.                 {
    60.                     string parentDisplayName = GetDisplayNameFromPath(kvp.Key);
    61.                     SerializedObject serializedObject = serializedObjectsCache[kvp.Key];
    62.                     serializedObject.Update();
    63.                     EditorGUI.indentLevel++;
    64.  
    65.                     bool shownParentLabel = false;
    66.  
    67.                     foreach (SerializedProperty property in kvp.Value)
    68.                     {
    69.                         if (!shownParentLabel && parentDisplayName != property.displayName)
    70.                         {
    71.                             GUILayout.Label(parentDisplayName, EditorStyles.boldLabel);
    72.                             shownParentLabel = true;
    73.                         }
    74.  
    75.                         EditorGUILayout.PropertyField(property, true);
    76.                     }
    77.                     EditorGUI.indentLevel--;
    78.                     serializedObject.ApplyModifiedProperties();
    79.                 }
    80.             }
    81.         }
    82.  
    83.         private static void SearchFieldsAndProperties(Object searchTarget)
    84.         {
    85.             matchingProperties.Clear();
    86.             serializedObjectsCache.Clear();
    87.             if (searchTarget == null) return;
    88.  
    89.             Component[] components = ((GameObject)searchTarget).GetComponents<Component>();
    90.             foreach (Component component in components)
    91.             {
    92.                 SerializedObject serializedObject = new SerializedObject(component);
    93.                 SerializedProperty serializedProperty = serializedObject.GetIterator();
    94.                 bool enterChildren = true;
    95.  
    96.                 while (serializedProperty.NextVisible(enterChildren))
    97.                 {
    98.                     if (PropertyMatchesSearch(serializedProperty))
    99.                     {
    100.                         AddPropertyIfNotAdded(serializedProperty, serializedObject);
    101.                     }
    102.                     else if (serializedProperty.hasChildren)
    103.                     {
    104.                         SearchWithinSerializedProperty(serializedProperty, serializedObject);
    105.                     }
    106.                     enterChildren = false;
    107.                 }
    108.             }
    109.         }
    110.  
    111.         private static bool PropertyMatchesSearch(SerializedProperty property)
    112.         {
    113.             return property.displayName.ToLowerInvariant().Contains(searchString.ToLowerInvariant());
    114.         }
    115.  
    116.         private static void AddPropertyIfNotAdded(SerializedProperty property, SerializedObject serializedObject)
    117.         {
    118.             string parentPath = GetPropertyParentPath(property);
    119.             if (!matchingProperties.ContainsKey(parentPath))
    120.             {
    121.                 matchingProperties[parentPath] = new List<SerializedProperty>();
    122.             }
    123.             if (!matchingProperties[parentPath].Any(p => p.propertyPath == property.propertyPath))
    124.             {
    125.                 matchingProperties[parentPath].Add(property.Copy());
    126.                 if (!serializedObjectsCache.ContainsKey(parentPath))
    127.                 {
    128.                     serializedObjectsCache[parentPath] = serializedObject;
    129.                 }
    130.             }
    131.         }
    132.  
    133.         private static void SearchWithinSerializedProperty(SerializedProperty parentProperty, SerializedObject parentSerializedObject)
    134.         {
    135.             var childSerializedProperty = parentProperty.Copy();
    136.             bool enterChildren = true;
    137.             while (childSerializedProperty.NextVisible(enterChildren))
    138.             {
    139.                 if (PropertyMatchesSearch(childSerializedProperty))
    140.                 {
    141.                     AddPropertyIfNotAdded(childSerializedProperty, parentSerializedObject);
    142.                 }
    143.                 enterChildren = false;
    144.             }
    145.         }
    146.  
    147.         private static string GetPropertyParentPath(SerializedProperty property)
    148.         {
    149.             var path = property.propertyPath;
    150.             int dotIndex = path.LastIndexOf('.');
    151.             if (dotIndex != -1)
    152.             {
    153.                 return path.Substring(0, dotIndex);
    154.             }
    155.             return property.propertyPath;
    156.         }
    157.  
    158.         private static string GetDisplayNameFromPath(string path)
    159.         {
    160.             var parts = path.Split('.');
    161.             if (parts.Length > 0)
    162.             {
    163.                 return ObjectNames.NicifyVariableName(parts.Last());
    164.             }
    165.             return path;
    166.         }
    167.     }
    168. }
     
    Last edited: Mar 15, 2024
    laurentlavigne and Slashbot64 like this.
  24. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,988
    This is somewhat offtopic, but [Header] does not generate collapsible sections, or am I being wrong?
     
  25. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,457
    Thanks @Ryiah

    I added:
    1. Keeps search and refresh result on selection change
    2. searchfield style
    3. ESC clears field, supports both input
    4. thick line at the bottom to delineate, ideally the bg should be of different color
    5. -No Match-
    would be nice to have option to dig through the entire hierarchy... woozgonnadozat?

    Code (CSharp):
    1. //v.4
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using UnityEditor;
    5. using UnityEngine;
    6. using UnityEngine.InputSystem;
    7. namespace Junk.Utilities
    8. {
    9.     public static class SearchInspector
    10.     {
    11.         const  string                                       DEFAULT                = "Search...";
    12.         static string                                       searchString           = DEFAULT;
    13.         static Dictionary<string, List<SerializedProperty>> matchingProperties     = new Dictionary<string, List<SerializedProperty>>();
    14.         static Dictionary<string, SerializedObject>         serializedObjectsCache = new Dictionary<string, SerializedObject>();
    15.         static Object                                       _oldEditorTarget;
    16.         [InitializeOnLoadMethod]
    17.         static void Init()
    18.         {
    19.             Editor.finishedDefaultHeaderGUI += OnDisplaySearch;
    20.         }
    21.         static void OnDisplaySearch(Editor editor)
    22.         {
    23.             if (!(editor.target is GameObject))
    24.                 return;
    25.             EditorGUILayout.BeginHorizontal(); // Start of the horizontal layout for the search field and reset button
    26.             // object selection has changed
    27.             SearchFieldsAndProperties(editor.target);
    28.             EditorGUI.BeginChangeCheck();
    29.             searchString = EditorGUILayout.TextField(searchString, EditorStyles.toolbarSearchField);
    30.             if (EditorGUI.EndChangeCheck() || _oldEditorTarget != editor.target)
    31.             {
    32.                 _oldEditorTarget = editor.target;
    33.                 // If search string is changed , update the search results
    34.                 if (FieldIsNotEmpty())
    35.                     SearchFieldsAndProperties(editor.target);
    36.                 else
    37.                 {
    38.                     matchingProperties.Clear();
    39.                     serializedObjectsCache.Clear();
    40.                 }
    41.             }
    42.             // ESC or "X" button to clear the search field (handles both input system)
    43.             var ESCPressed = false;
    44.         #if ENABLE_INPUT_SYSTEM
    45.             ESCPressed = Keyboard.current.escapeKey.isPressed;
    46.         #else
    47.             ESCPressed = Input.GetKey(KeyCode.Escape)
    48.         #endif
    49.             //todo: ESC should effect only when focus on field
    50.             // ESCPressed &= GUI.GetNameOfFocusedControl() ==
    51.             if (ESCPressed || GUILayout.Button("X", GUILayout.Width(20), GUILayout.Height(20)))
    52.             {
    53.                 searchString = DEFAULT;         // Clear the search string
    54.                 GUI.FocusControl(null);         // Remove focus from the search field to reflect the clear immediately
    55.                 matchingProperties.Clear();     // Clear search results
    56.                 serializedObjectsCache.Clear(); // Clear cache
    57.             }
    58.             EditorGUILayout.EndHorizontal(); // End of the horizontal layout
    59.             // If there are any matching properties, display them below the search field
    60.             if (matchingProperties.Any())
    61.             {
    62.                 GUILayout.Label("Results:", EditorStyles.boldLabel);
    63.                 foreach (var kvp in matchingProperties)
    64.                 {
    65.                     var parentDisplayName = GetDisplayNameFromPath(kvp.Key);
    66.                     var serializedObject  = serializedObjectsCache[kvp.Key];
    67.                     serializedObject.Update();
    68.                     EditorGUI.indentLevel++;
    69.                     var shownParentLabel = false;
    70.                     foreach (var property in kvp.Value)
    71.                     {
    72.                         if (!shownParentLabel && parentDisplayName != property.displayName)
    73.                         {
    74.                             GUILayout.Label(parentDisplayName, EditorStyles.boldLabel);
    75.                             shownParentLabel = true;
    76.                         }
    77.                         EditorGUILayout.PropertyField(property, true);
    78.                     }
    79.                     EditorGUI.indentLevel--;
    80.                     serializedObject.ApplyModifiedProperties();
    81.                 }
    82.                 GUILayout.Space(5);
    83.                 EditorGUI.DrawRect(GUILayoutUtility.GetRect(10, 5), Color.black);
    84.             }
    85.             else
    86.             {
    87.                 if (FieldIsNotEmpty())
    88.                 {
    89.                     var tmpColor = GUI.color;
    90.                     GUI.color = Color.red;
    91.                     GUILayout.Label("-No Match-", EditorStyles.boldLabel);
    92.                     GUI.color = tmpColor;
    93.                     GUILayout.Space(5);
    94.                     EditorGUI.DrawRect(GUILayoutUtility.GetRect(10, 5), Color.black);
    95.                 }
    96.             }
    97.         }
    98.         static bool FieldIsNotEmpty() => !string.IsNullOrWhiteSpace(searchString) && searchString != DEFAULT;
    99.         static void SearchFieldsAndProperties(Object searchTarget)
    100.         {
    101.             matchingProperties.Clear();
    102.             serializedObjectsCache.Clear();
    103.             if (searchTarget == null) return;
    104.             var components = ((GameObject) searchTarget).GetComponents<Component>();
    105.             foreach (var component in components)
    106.             {
    107.                 var serializedObject   = new SerializedObject(component);
    108.                 var serializedProperty = serializedObject.GetIterator();
    109.                 var enterChildren      = true;
    110.                 while (serializedProperty.NextVisible(enterChildren))
    111.                 {
    112.                     if (PropertyMatchesSearch(serializedProperty))
    113.                         AddPropertyIfNotAdded(serializedProperty, serializedObject);
    114.                     else
    115.                         if (serializedProperty.hasChildren) SearchWithinSerializedProperty(serializedProperty, serializedObject);
    116.                     enterChildren = false;
    117.                 }
    118.             }
    119.         }
    120.         static bool PropertyMatchesSearch(SerializedProperty property) =>
    121.             property.displayName.ToLowerInvariant().Contains(searchString.ToLowerInvariant());
    122.         static void AddPropertyIfNotAdded(SerializedProperty property, SerializedObject serializedObject)
    123.         {
    124.             var parentPath                                                                  = GetPropertyParentPath(property);
    125.             if (!matchingProperties.ContainsKey(parentPath)) matchingProperties[parentPath] = new List<SerializedProperty>();
    126.             if (!matchingProperties[parentPath].Any(p => p.propertyPath == property.propertyPath))
    127.             {
    128.                 matchingProperties[parentPath].Add(property.Copy());
    129.                 if (!serializedObjectsCache.ContainsKey(parentPath)) serializedObjectsCache[parentPath] = serializedObject;
    130.             }
    131.         }
    132.         static void SearchWithinSerializedProperty(SerializedProperty parentProperty, SerializedObject parentSerializedObject)
    133.         {
    134.             var childSerializedProperty = parentProperty.Copy();
    135.             var enterChildren           = true;
    136.             while (childSerializedProperty.NextVisible(enterChildren))
    137.             {
    138.                 if (PropertyMatchesSearch(childSerializedProperty)) AddPropertyIfNotAdded(childSerializedProperty, parentSerializedObject);
    139.                 enterChildren = false;
    140.             }
    141.         }
    142.         static string GetPropertyParentPath(SerializedProperty property)
    143.         {
    144.             var path     = property.propertyPath;
    145.             var dotIndex = path.LastIndexOf('.');
    146.             if (dotIndex != -1) return path.Substring(0, dotIndex);
    147.             return property.propertyPath;
    148.         }
    149.         static string GetDisplayNameFromPath(string path)
    150.         {
    151.             var parts = path.Split('.');
    152.             if (parts.Length > 0) return ObjectNames.NicifyVariableName(parts.Last());
    153.             return path;
    154.         }
    155.     }
    156. }
     
    PanthenEye likes this.