From e4ff86259bd7bbc2b75bcb9d9178604fb526bdb3 Mon Sep 17 00:00:00 2001 From: Sinai Date: Wed, 5 May 2021 21:27:09 +1000 Subject: [PATCH] More progress --- src/Core/ReflectionUtility.cs | 101 ++-- src/Core/Runtime/Il2Cpp/Il2CppReflection.cs | 87 ++- src/Core/Search/SearchContext.cs | 4 +- src/Core/Search/SearchProvider.cs | 228 ++++---- src/Core/Tests/TestClass.cs | 25 + src/Core/Utility/MiscUtility.cs | 12 + src/ExplorerCore.cs | 8 +- .../CacheObject/CacheField.cs | 4 +- .../CacheObject/CacheKeyValuePair.cs | 10 +- .../CacheObject/CacheListEntry.cs | 10 +- .../CacheObject/CacheMember.cs | 14 +- .../CacheObject/CacheMethod.cs | 4 +- .../CacheObject/CacheObjectBase.cs | 99 ++-- .../CacheObject/CacheProperty.cs | 8 +- .../ICacheObjectController.cs | 4 +- .../Views/CacheKeyValuePairCell.cs | 19 +- .../CacheObject/Views/CacheListEntryCell.cs | 7 +- .../CacheObject/Views/CacheMemberCell.cs | 4 +- .../CacheObject/Views/CacheObjectCell.cs | 7 +- .../CacheObject/Views/EvaluateWidget.cs | 31 +- .../IValues/InteractiveDictionary.cs | 35 +- .../IValues/InteractiveList.cs | 43 +- .../IValues/InteractiveValue.cs | 4 +- src/UI/Inspectors/InspectorBase.cs | 2 + src/UI/Inspectors/InspectorManager.cs | 2 +- src/UI/Inspectors/ListInspector.cs | 4 +- src/UI/Inspectors/ReflectionInspector.cs | 531 ++++++++++++++---- src/UI/ObjectExplorer/ObjectSearch.cs | 90 ++- src/UI/ObjectExplorer/SceneExplorer.cs | 2 +- src/UI/Panels/CSConsolePanel.cs | 21 +- src/UI/Panels/InspectorPanel.cs | 33 +- src/UI/Panels/ObjectExplorer.cs | 27 +- src/UI/Panels/PanelDragger.cs | 22 +- src/UI/Panels/UIPanel.cs | 48 +- src/UI/UIFactory.cs | 2 +- src/UI/Utility/ToStringUtility.cs | 32 +- src/UI/Widgets/AutoComplete/AutoCompleter.cs | 9 +- src/UI/Widgets/AutoComplete/Suggestion.cs | 13 +- src/UI/Widgets/AutoComplete/TypeCompleter.cs | 138 ++--- src/UI/Widgets/ScrollPool/ScrollPool.cs | 30 +- src/UI/Widgets/TransformTree/TransformTree.cs | 83 ++- src/UnityExplorer.csproj | 32 +- 42 files changed, 1159 insertions(+), 730 deletions(-) rename src/UI/{Inspectors => }/CacheObject/CacheField.cs (91%) rename src/UI/{Inspectors => }/CacheObject/CacheKeyValuePair.cs (92%) rename src/UI/{Inspectors => }/CacheObject/CacheListEntry.cs (81%) rename src/UI/{Inspectors => }/CacheObject/CacheMember.cs (97%) rename src/UI/{Inspectors => }/CacheObject/CacheMethod.cs (92%) rename src/UI/{Inspectors => }/CacheObject/CacheObjectBase.cs (90%) rename src/UI/{Inspectors => }/CacheObject/CacheProperty.cs (85%) rename src/UI/{Inspectors => CacheObject}/ICacheObjectController.cs (77%) rename src/UI/{Inspectors => }/CacheObject/Views/CacheKeyValuePairCell.cs (82%) rename src/UI/{Inspectors => }/CacheObject/Views/CacheListEntryCell.cs (86%) rename src/UI/{Inspectors => }/CacheObject/Views/CacheMemberCell.cs (94%) rename src/UI/{Inspectors => }/CacheObject/Views/CacheObjectCell.cs (97%) rename src/UI/{Inspectors => }/CacheObject/Views/EvaluateWidget.cs (89%) rename src/UI/{Inspectors => }/IValues/InteractiveDictionary.cs (90%) rename src/UI/{Inspectors => }/IValues/InteractiveList.cs (86%) rename src/UI/{Inspectors => }/IValues/InteractiveValue.cs (96%) diff --git a/src/Core/ReflectionUtility.cs b/src/Core/ReflectionUtility.cs index 150aaab..54b99d3 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/ReflectionUtility.cs @@ -6,11 +6,48 @@ using System.Linq; using System.Reflection; using BF = System.Reflection.BindingFlags; using UnityExplorer.Core.Runtime; +using System.Text; namespace UnityExplorer { public static class ReflectionUtility { + static ReflectionUtility() + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + CacheTypes(asm); + + AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded; + } + + private static readonly Dictionary allCachedTypes = new Dictionary(); + + private static void CacheTypes(Assembly asm) + { + foreach (var type in asm.TryGetTypes()) + { + if (allCachedTypes.ContainsKey(type.FullName)) + continue; + + if (type.FullName.ContainsIgnoreCase("PrivateImplementationDetails")) + continue; + + allCachedTypes.Add(type.FullName, type); + } + } + + private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args) + { + if (args.LoadedAssembly == null) + return; + + s_cachedTypeInheritance.Clear(); + s_cachedGenericParameterInheritance.Clear(); + + CacheTypes(args.LoadedAssembly); + } + + public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; public static bool ValueEqual(this T objA, T objB) @@ -119,28 +156,8 @@ namespace UnityExplorer /// The Type if found, otherwise null. public static Type GetTypeByName(string fullName) { - s_typesByName.TryGetValue(fullName, out Type ret); - - if (ret != null) - return ret; - - foreach (var type in from asm in AppDomain.CurrentDomain.GetAssemblies() - from type in asm.TryGetTypes() - select type) - { - if (type.FullName == fullName) - { - ret = type; - break; - } - } - - if (s_typesByName.ContainsKey(fullName)) - s_typesByName[fullName] = ret; - else - s_typesByName.Add(fullName, ret); - - return ret; + allCachedTypes.TryGetValue(fullName, out Type type); + return type; } // cache for GetBaseTypes @@ -180,49 +197,47 @@ namespace UnityExplorer } // cache for GetImplementationsOf - internal static readonly Dictionary> s_cachedTypeInheritance = new Dictionary>(); - internal static int s_lastAssemblyCount; + internal static readonly Dictionary> s_cachedTypeInheritance = new Dictionary>(); + internal static readonly Dictionary> s_cachedGenericParameterInheritance = new Dictionary>(); /// /// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain. + /// Also works for generic parameters by analyzing the constraints. /// /// The base type, which can optionally be abstract / interface. /// All implementations of the type in the current AppDomain. public static HashSet GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric) { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var key = baseType.AssemblyQualifiedName; - if (!s_cachedTypeInheritance.ContainsKey(baseType) || assemblies.Length != s_lastAssemblyCount) + if (!s_cachedTypeInheritance.ContainsKey(key)) { - if (assemblies.Length != s_lastAssemblyCount) - { - s_cachedTypeInheritance.Clear(); - s_lastAssemblyCount = assemblies.Length; - } - var set = new HashSet(); if (!baseType.IsAbstract && !baseType.IsInterface) set.Add(baseType); - foreach (var asm in assemblies) + var keys = allCachedTypes.Keys.ToArray(); + for (int i = 0; i < keys.Length; i++) { - foreach (var t in asm.TryGetTypes().Where(t => (allowAbstract || (!t.IsAbstract && !t.IsInterface)) - && (allowGeneric || !t.IsGenericType))) + var type = allCachedTypes[keys[i]]; + try { - try - { - if (baseType.IsAssignableFrom(t) && !set.Contains(t)) - set.Add(t); - } - catch { } + if ((type.IsAbstract && type.IsSealed) // ignore static classes + || (!allowAbstract && type.IsAbstract) + || (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))) + continue; + + if (baseType.IsAssignableFrom(type) && !set.Contains(type)) + set.Add(type); } + catch { } } - s_cachedTypeInheritance.Add(baseType, set); + s_cachedTypeInheritance.Add(key, set); } - return s_cachedTypeInheritance[baseType]; + return s_cachedTypeInheritance[key]; } /// diff --git a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs index c96e58e..1a9d739 100644 --- a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs +++ b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs @@ -25,6 +25,15 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp Instance = this; TryLoadGameModules(); + + BuildDeobfuscationCache(); + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded; + } + + private void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args) + { + foreach (var type in args.LoadedAssembly.TryGetTypes()) + TryCacheDeobfuscatedType(type); } public override object Cast(object obj, Type castTo) @@ -71,10 +80,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp public override Type GetDeobfuscatedType(Type type) { - if (!builtDeobCache) - BuildDeobfuscationCache(); - - Type ret = type; try { var cppType = Il2CppType.From(type); @@ -84,14 +89,11 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp } catch { } - return ret; + return type; } public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName) { - if (!builtDeobCache) - BuildDeobfuscationCache(); - if (!Il2CppTypeNotNull(type)) return theString; @@ -105,27 +107,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return theString; } - //public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName) - //{ - // if (!builtDeobCache) - // BuildDeobfuscationCache(); - - // try - // { - // var cppType = Il2CppType.From(type); - // if (s_deobfuscatedTypeNames.ContainsKey(cppType.FullName)) - // { - // typeName = s_deobfuscatedTypeNames[cppType.FullName]; - // theString = theString.Replace(cppType.FullName, typeName); - // } - // } - // catch - // { - // } - - // return theString; - //} - public override Type GetActualType(object obj) { if (obj == null) @@ -161,9 +142,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return getType; } } - catch // (Exception ex) + catch (Exception ex) { - // ExplorerCore.LogWarning("Exception in GetActualType: " + ex); + ExplorerCore.LogWarning("Exception in GetActualType: " + ex); } return type; @@ -175,37 +156,36 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp // keep deobfuscated type name cache, used to display proper name. internal static Dictionary s_deobfuscatedTypeNames = new Dictionary(); - private static bool builtDeobCache = false; - private static void BuildDeobfuscationCache() { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.TryGetTypes()) - { - try - { - if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute")) - { - var cppType = Il2CppType.From(type); - - if (!Il2CppToMonoType.ContainsKey(cppType.FullName)) - { - Il2CppToMonoType.Add(cppType.AssemblyQualifiedName, type); - s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName); - } - } - } - catch { } - } + TryCacheDeobfuscatedType(type); } - builtDeobCache = true; - if (s_deobfuscatedTypeNames.Count > 0) ExplorerCore.Log($"Built deobfuscation cache, count: {s_deobfuscatedTypeNames.Count}"); } + private static void TryCacheDeobfuscatedType(Type type) + { + try + { + if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute")) + { + var cppType = Il2CppType.From(type); + + if (!Il2CppToMonoType.ContainsKey(cppType.FullName)) + { + Il2CppToMonoType.Add(cppType.AssemblyQualifiedName, type); + s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName); + } + } + } + catch { } + } + /// /// Try to get the Mono (Unhollowed) Type representation of the provided . /// @@ -213,9 +193,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp /// The Mono Type if found, otherwise null. public static Type GetMonoType(CppType cppType) { - if (!builtDeobCache) - BuildDeobfuscationCache(); - string name = cppType.AssemblyQualifiedName; if (Il2CppToMonoType.ContainsKey(name)) @@ -344,6 +321,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return false; } + // Not currently using, not sure if its necessary anymore, was necessary to prevent crashes at one point. public override bool IsReflectionSupported(Type type) { try @@ -467,7 +445,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp var valueList = new List(); var hashtable = value.TryCast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable; - if (hashtable != null) { EnumerateCppHashtable(hashtable, keyList, valueList); diff --git a/src/Core/Search/SearchContext.cs b/src/Core/Search/SearchContext.cs index 6cb463c..d26fb8a 100644 --- a/src/Core/Search/SearchContext.cs +++ b/src/Core/Search/SearchContext.cs @@ -9,8 +9,8 @@ namespace UnityExplorer.Core.Search { UnityObject, GameObject, - Component, - Custom, + //Component, + //Custom, Singleton, StaticClass } diff --git a/src/Core/Search/SearchProvider.cs b/src/Core/Search/SearchProvider.cs index 004303e..a60f393 100644 --- a/src/Core/Search/SearchProvider.cs +++ b/src/Core/Search/SearchProvider.cs @@ -11,6 +11,114 @@ namespace UnityExplorer.Core.Search { public static class SearchProvider { + + private static bool Filter(Scene scene, SceneFilter filter) + { + switch (filter) + { + case SceneFilter.Any: + return true; + case SceneFilter.DontDestroyOnLoad: + return scene == SceneHandler.DontDestroyScene; + case SceneFilter.HideAndDontSave: + return scene == SceneHandler.AssetScene; + case SceneFilter.ActivelyLoaded: + return scene != SceneHandler.DontDestroyScene && scene != SceneHandler.AssetScene; + default: + return false; + } + } + + internal static List UnityObjectSearch(string input, string customTypeInput, SearchContext context, + ChildFilter childFilter, SceneFilter sceneFilter) + { + var results = new List(); + + Type searchType; + switch (context) + { + case SearchContext.GameObject: + searchType = typeof(GameObject); + break; + + case SearchContext.UnityObject: + default: + + if (!string.IsNullOrEmpty(customTypeInput)) + { + if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType) + { + if (typeof(UnityEngine.Object).IsAssignableFrom(customType)) + { + searchType = customType; + break; + } + else + ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!"); + } + else + ExplorerCore.LogWarning($"Could not find any type by name '{customTypeInput}'!"); + } + + searchType = typeof(UnityEngine.Object); + break; + } + + + if (searchType == null) + return results; + + var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType); + + // perform filter comparers + + string nameFilter = null; + if (!string.IsNullOrEmpty(input)) + nameFilter = input; + + bool canGetGameObject = context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType); + + foreach (var obj in allObjects) + { + // name check + if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter)) + continue; + + if (canGetGameObject) + { + var go = context == SearchContext.GameObject + ? obj.TryCast() + : obj.TryCast().gameObject; + + if (go) + { + // scene check + if (sceneFilter != SceneFilter.Any) + { + if (!Filter(go.scene, sceneFilter)) + continue; + } + + if (childFilter != ChildFilter.Any) + { + if (!go) + continue; + + // root object check (no parent) + if (childFilter == ChildFilter.HasParent && !go.transform.parent) + continue; + else if (childFilter == ChildFilter.RootObject && go.transform.parent) + continue; + } + } + } + + results.Add(obj); + } + + return results; + } + internal static List StaticClassSearch(string input) { var list = new List(); @@ -76,125 +184,5 @@ namespace UnityExplorer.Core.Search return instances; } - private static bool Filter(Scene scene, SceneFilter filter) - { - switch (filter) - { - case SceneFilter.Any: - return true; - case SceneFilter.DontDestroyOnLoad: - return scene == SceneHandler.DontDestroyScene; - case SceneFilter.HideAndDontSave: - return scene == SceneHandler.AssetScene; - case SceneFilter.ActivelyLoaded: - return scene != SceneHandler.DontDestroyScene && scene != SceneHandler.AssetScene; - default: - return false; - } - } - - internal static List UnityObjectSearch(string input, string customTypeInput, SearchContext context, - ChildFilter childFilter, SceneFilter sceneFilter) - { - var results = new List(); - - Type searchType = null; - switch (context) - { - case SearchContext.GameObject: - searchType = typeof(GameObject); break; - - case SearchContext.Component: - searchType = typeof(Component); break; - - case SearchContext.Custom: - if (string.IsNullOrEmpty(customTypeInput)) - { - ExplorerCore.LogWarning("Custom Type input must not be empty!"); - return results; - } - if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType) - { - if (typeof(UnityEngine.Object).IsAssignableFrom(customType)) - searchType = customType; - else - ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!"); - } - else - ExplorerCore.LogWarning($"Could not find a type by the name '{customTypeInput}'!"); - break; - - default: - searchType = typeof(UnityEngine.Object); break; - } - - - if (searchType == null) - return results; - - var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType); - - // perform filter comparers - - string nameFilter = null; - if (!string.IsNullOrEmpty(input)) - nameFilter = input; - - bool canGetGameObject = (sceneFilter != SceneFilter.Any || childFilter != ChildFilter.Any) - && (context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType)); - - if (!canGetGameObject) - { - if (context != SearchContext.UnityObject && (sceneFilter != SceneFilter.Any || childFilter != ChildFilter.Any)) - ExplorerCore.LogWarning($"Type '{searchType}' cannot have Scene or Child filters applied to it"); - } - - foreach (var obj in allObjects) - { - // name check - if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter)) - continue; - - if (canGetGameObject) - { - var go = context == SearchContext.GameObject - ? obj.TryCast() - : obj.TryCast().gameObject; - - // scene check - if (sceneFilter != SceneFilter.Any) - { - if (!go) - continue; - - switch (context) - { - case SearchContext.GameObject: - case SearchContext.Custom: - case SearchContext.Component: - if (!Filter(go.scene, sceneFilter)) - continue; - break; - } - } - - if (childFilter != ChildFilter.Any) - { - if (!go) - continue; - - // root object check (no parent) - if (childFilter == ChildFilter.HasParent && !go.transform.parent) - continue; - else if (childFilter == ChildFilter.RootObject && go.transform.parent) - continue; - } - } - - results.Add(obj); - } - - return results; - } } } diff --git a/src/Core/Tests/TestClass.cs b/src/Core/Tests/TestClass.cs index 2c95315..10964cd 100644 --- a/src/Core/Tests/TestClass.cs +++ b/src/Core/Tests/TestClass.cs @@ -76,6 +76,31 @@ namespace UnityExplorer.Tests } } + private static void TestGeneric() + { + ExplorerCore.Log("Test1 " + typeof(T).FullName); + } + + private static void TestGenericClass() where T : class + { + ExplorerCore.Log("Test2 " + typeof(T).FullName); + } + + //private static void TestGenericMultiInterface() where T : IEnumerable, IList, ICollection + //{ + // ExplorerCore.Log("Test3 " + typeof(T).FullName); + //} + + private static void TestComponent() where T : Component + { + ExplorerCore.Log("Test3 " + typeof(T).FullName); + } + + private static void TestStruct() where T : struct + { + ExplorerCore.Log("Test3 " + typeof(T).FullName); + } + private static object GetRandomObject() { object ret = null; diff --git a/src/Core/Utility/MiscUtility.cs b/src/Core/Utility/MiscUtility.cs index 46a0d26..7d021b4 100644 --- a/src/Core/Utility/MiscUtility.cs +++ b/src/Core/Utility/MiscUtility.cs @@ -10,9 +10,21 @@ namespace UnityExplorer { private static CultureInfo _enCulture = new CultureInfo("en-US"); + /// + /// Check if a string contains another string, case-insensitive. + /// public static bool ContainsIgnoreCase(this string _this, string s) { return _enCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0; } + + /// + /// Just to allow Enum to do .HasFlag() in NET 3.5 + /// + public static bool HasFlag(this Enum flags, Enum value) + { + ulong num = Convert.ToUInt64(value); + return (Convert.ToUInt64(flags) & num) == num; + } } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 04567c9..064b6eb 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -81,11 +81,17 @@ namespace UnityExplorer } } + // long name object + new GameObject(new string('#', 500)); + // END - InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent()); + //InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent()); InspectorManager.Inspect(typeof(TestClass)); //InspectorManager.InspectType(typeof(ReflectionUtility)); + + //var tex = Resources.FindObjectsOfTypeAll()[0]; + //InspectorManager.Inspect(tex); } /// diff --git a/src/UI/Inspectors/CacheObject/CacheField.cs b/src/UI/CacheObject/CacheField.cs similarity index 91% rename from src/UI/Inspectors/CacheObject/CacheField.cs rename to src/UI/CacheObject/CacheField.cs index 267345e..d56a541 100644 --- a/src/UI/Inspectors/CacheObject/CacheField.cs +++ b/src/UI/CacheObject/CacheField.cs @@ -3,13 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using UnityExplorer.UI.Inspectors; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public class CacheField : CacheMember { public FieldInfo FieldInfo { get; internal set; } public override Type DeclaringType => FieldInfo.DeclaringType; + public override bool IsStatic => FieldInfo.IsStatic; public override bool CanWrite => m_canWrite ?? (bool)(m_canWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly)); private bool? m_canWrite; diff --git a/src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs b/src/UI/CacheObject/CacheKeyValuePair.cs similarity index 92% rename from src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs rename to src/UI/CacheObject/CacheKeyValuePair.cs index 349cb3a..d8ab727 100644 --- a/src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs +++ b/src/UI/CacheObject/CacheKeyValuePair.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using UnityExplorer.UI.Inspectors.CacheObject.Views; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.IValues; using UnityExplorer.UI.Utility; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public class CacheKeyValuePair : CacheObjectBase { @@ -52,9 +52,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject } } - public override void SetCell(CacheObjectCell cell) + public override void SetDataToCell(CacheObjectCell cell) { - base.SetCell(cell); + base.SetDataToCell(cell); var kvpCell = cell as CacheKeyValuePairCell; diff --git a/src/UI/Inspectors/CacheObject/CacheListEntry.cs b/src/UI/CacheObject/CacheListEntry.cs similarity index 81% rename from src/UI/Inspectors/CacheObject/CacheListEntry.cs rename to src/UI/CacheObject/CacheListEntry.cs index cda99fc..d1b9b97 100644 --- a/src/UI/Inspectors/CacheObject/CacheListEntry.cs +++ b/src/UI/CacheObject/CacheListEntry.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using UnityExplorer.UI.Inspectors.CacheObject.Views; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.IValues; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public class CacheListEntry : CacheObjectBase { @@ -21,9 +21,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject this.ListIndex = listIndex; } - public override void SetCell(CacheObjectCell cell) + public override void SetDataToCell(CacheObjectCell cell) { - base.SetCell(cell); + base.SetDataToCell(cell); var listCell = cell as CacheListEntryCell; diff --git a/src/UI/Inspectors/CacheObject/CacheMember.cs b/src/UI/CacheObject/CacheMember.cs similarity index 97% rename from src/UI/Inspectors/CacheObject/CacheMember.cs rename to src/UI/CacheObject/CacheMember.cs index 4e61c1c..a55b5fd 100644 --- a/src/UI/Inspectors/CacheObject/CacheMember.cs +++ b/src/UI/CacheObject/CacheMember.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Reflection; using System.Text; using UnityEngine; -using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Utility; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public abstract class CacheMember : CacheObjectBase { @@ -18,6 +19,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public abstract Type DeclaringType { get; } public string NameForFiltering { get; protected set; } + public abstract bool IsStatic { get; } public override bool HasArguments => Arguments?.Length > 0 || GenericArguments.Length > 0; public ParameterInfo[] Arguments { get; protected set; } = new ParameterInfo[0]; public Type[] GenericArguments { get; protected set; } = new Type[0]; @@ -43,12 +45,12 @@ namespace UnityExplorer.UI.Inspectors.CacheObject } } - internal override void HidePooledObjects() + public override void UnlinkFromView() { - base.HidePooledObjects(); - if (this.Evaluator != null) this.Evaluator.UIRoot.transform.SetParent(Pool.Instance.InactiveHolder.transform, false); + + base.UnlinkFromView(); } protected abstract object TryEvaluate(); @@ -59,7 +61,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { Evaluate(); if (CellView != null) - SetCell(CellView); + SetDataToCell(CellView); } /// diff --git a/src/UI/Inspectors/CacheObject/CacheMethod.cs b/src/UI/CacheObject/CacheMethod.cs similarity index 92% rename from src/UI/Inspectors/CacheObject/CacheMethod.cs rename to src/UI/CacheObject/CacheMethod.cs index 23d4625..c167b13 100644 --- a/src/UI/Inspectors/CacheObject/CacheMethod.cs +++ b/src/UI/CacheObject/CacheMethod.cs @@ -3,14 +3,16 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using UnityExplorer.UI.Inspectors; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public class CacheMethod : CacheMember { public MethodInfo MethodInfo { get; internal set; } public override Type DeclaringType => MethodInfo.DeclaringType; public override bool CanWrite => false; + public override bool IsStatic => MethodInfo.IsStatic; public override bool ShouldAutoEvaluate => false; diff --git a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs similarity index 90% rename from src/UI/Inspectors/CacheObject/CacheObjectBase.cs rename to src/UI/CacheObject/CacheObjectBase.cs index e9dfefe..c427da5 100644 --- a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs +++ b/src/UI/CacheObject/CacheObjectBase.cs @@ -5,12 +5,12 @@ using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject.Views; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.IValues; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Utility; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public enum ValueState { @@ -64,38 +64,32 @@ namespace UnityExplorer.UI.Inspectors.CacheObject protected const string NOT_YET_EVAL = "Not yet evaluated"; - internal static GameObject InactiveIValueHolder - { - get - { - if (!inactiveIValueHolder) - { - inactiveIValueHolder = new GameObject("InactiveIValueHolder"); - GameObject.DontDestroyOnLoad(inactiveIValueHolder); - inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform; - inactiveIValueHolder.SetActive(false); - } - return inactiveIValueHolder; - } - } - private static GameObject inactiveIValueHolder; - - // On parent destroying this public virtual void ReleasePooledObjects() { if (this.IValue != null) ReleaseIValue(); - // TODO release Evaluate - if (this.CellView != null) - { - this.CellView.Occupant = null; - this.CellView.SubContentHolder.SetActive(false); - this.CellView = null; - } - + UnlinkFromView(); + } + + public virtual void SetView(CacheObjectCell cellView) + { + this.CellView = cellView; + cellView.Occupant = this; + } + + public virtual void UnlinkFromView() + { + if (this.CellView == null) + return; + + this.CellView.Occupant = null; + this.CellView = null; + + if (this.IValue != null) + this.IValue.UIRoot.transform.SetParent(InactiveIValueHolder.transform, false); } // Updating and applying values @@ -112,13 +106,16 @@ namespace UnityExplorer.UI.Inspectors.CacheObject if (State != prevState) { - // TODO handle if subcontent / evaluate shown, check type change, etc + if (this.IValue != null) + { + // State has changed, need to return IValue + ReleaseIValue(); + SubContentShowWanted = false; + } } if (this.IValue != null) - { this.IValue.SetValue(Value); - } } public abstract void SetUserValue(object value); @@ -190,10 +187,12 @@ namespace UnityExplorer.UI.Inspectors.CacheObject this.ValueLabelText = label; } + // Setting cell state from our model + /// Return true if SetCell should abort, false if it should continue. protected abstract bool SetCellEvaluateState(CacheObjectCell cell); - public virtual void SetCell(CacheObjectCell cell) + public virtual void SetDataToCell(CacheObjectCell cell) { cell.NameLabel.text = NameLabelText; cell.ValueLabel.gameObject.SetActive(true); @@ -212,7 +211,6 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { case ValueState.Exception: case ValueState.NullValue: - ReleaseIValue(); SetValueState(cell, ValueStateArgs.Default); break; case ValueState.Boolean: @@ -222,18 +220,15 @@ namespace UnityExplorer.UI.Inspectors.CacheObject SetValueState(cell, new ValueStateArgs(false, typeLabelActive: true, inputActive: true, applyActive: CanWrite)); break; case ValueState.String: - SetIValueState(); SetValueState(cell, new ValueStateArgs(true, false, SignatureHighlighter.StringOrange, subContentButtonActive: true)); break; case ValueState.Enum: - SetIValueState(); SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite)); break; case ValueState.Collection: case ValueState.Dictionary: case ValueState.ValueStruct: case ValueState.Color: - SetIValueState(); SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true)); break; case ValueState.Unsupported: @@ -281,16 +276,22 @@ namespace UnityExplorer.UI.Inspectors.CacheObject // IValues - /// Called from SetCellState if SubContent button is wanted. - public void SetIValueState() + internal static GameObject InactiveIValueHolder { - if (this.IValue == null) - return; - - // TODO ? + get + { + if (!inactiveIValueHolder) + { + inactiveIValueHolder = new GameObject("Temp_IValue_Holder"); + GameObject.DontDestroyOnLoad(inactiveIValueHolder); + inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform; + inactiveIValueHolder.SetActive(false); + } + return inactiveIValueHolder; + } } + private static GameObject inactiveIValueHolder; - // temp for testing public virtual void OnCellSubContentToggle() { if (this.IValue == null) @@ -307,7 +308,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject // update our cell after creating the ivalue (the value may have updated, make sure its consistent) this.ProcessOnEvaluate(); - this.SetCell(this.CellView); + this.SetDataToCell(this.CellView); } else { @@ -329,14 +330,6 @@ namespace UnityExplorer.UI.Inspectors.CacheObject IValue = null; } - internal virtual void HidePooledObjects() - { - if (this.IValue == null) - return; - - this.IValue.UIRoot.transform.SetParent(InactiveIValueHolder.transform, false); - } - // CacheObjectCell Apply public virtual void OnCellApplyClicked() @@ -363,7 +356,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject SetUserValue(val); } - SetCell(this.CellView); + SetDataToCell(this.CellView); } public struct ValueStateArgs diff --git a/src/UI/Inspectors/CacheObject/CacheProperty.cs b/src/UI/CacheObject/CacheProperty.cs similarity index 85% rename from src/UI/Inspectors/CacheObject/CacheProperty.cs rename to src/UI/CacheObject/CacheProperty.cs index 104372a..9bc92f5 100644 --- a/src/UI/Inspectors/CacheObject/CacheProperty.cs +++ b/src/UI/CacheObject/CacheProperty.cs @@ -3,14 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using UnityExplorer.UI.Inspectors; -namespace UnityExplorer.UI.Inspectors.CacheObject +namespace UnityExplorer.UI.CacheObject { public class CacheProperty : CacheMember { public PropertyInfo PropertyInfo { get; internal set; } public override Type DeclaringType => PropertyInfo.DeclaringType; public override bool CanWrite => PropertyInfo.CanWrite; + public override bool IsStatic => m_isStatic ?? (bool)(m_isStatic = PropertyInfo.GetAccessors(true)[0].IsStatic); + private bool? m_isStatic; public override bool ShouldAutoEvaluate => !HasArguments; @@ -25,8 +28,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { try { - bool _static = PropertyInfo.GetAccessors(true)[0].IsStatic; - var target = _static ? null : Owner.Target.TryCast(DeclaringType); + var target = IsStatic ? null : Owner.Target.TryCast(DeclaringType); if (HasArguments) return PropertyInfo.GetValue(target, this.Evaluator.TryParseArguments()); diff --git a/src/UI/Inspectors/ICacheObjectController.cs b/src/UI/CacheObject/ICacheObjectController.cs similarity index 77% rename from src/UI/Inspectors/ICacheObjectController.cs rename to src/UI/CacheObject/ICacheObjectController.cs index fa33141..d240df8 100644 --- a/src/UI/Inspectors/ICacheObjectController.cs +++ b/src/UI/CacheObject/ICacheObjectController.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using UnityExplorer.UI.Inspectors.CacheObject; +using UnityExplorer.UI.CacheObject; -namespace UnityExplorer.UI.Inspectors +namespace UnityExplorer.UI.CacheObject { public interface ICacheObjectController { diff --git a/src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs similarity index 82% rename from src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs rename to src/UI/CacheObject/Views/CacheKeyValuePairCell.cs index 03f6d3e..3d24848 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs +++ b/src/UI/CacheObject/Views/CacheKeyValuePairCell.cs @@ -4,10 +4,11 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.Inspectors; +using UnityExplorer.UI.IValues; using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Inspectors.CacheObject.Views +namespace UnityExplorer.UI.CacheObject.Views { public class CacheKeyValuePairCell : CacheObjectCell { @@ -23,8 +24,8 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views public static Color EvenColor = new Color(0.07f, 0.07f, 0.07f); public static Color OddColor = new Color(0.063f, 0.063f, 0.063f); - public int HalfWidth => (int)(0.5f * Rect.rect.width); - public int AdjustedKeyWidth => HalfWidth - 50; + public int HalfWidth => (int)(0.5f * Rect.rect.width) + 50; + public int AdjustedKeyWidth => HalfWidth - 100; private void KeyInspectClicked() { @@ -40,6 +41,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views this.NameLayout.minWidth = 40; this.NameLayout.flexibleWidth = 50; this.NameLayout.minHeight = 30; + this.NameLayout.flexibleHeight = 0; this.NameLabel.alignment = TextAnchor.MiddleRight; this.RightGroupLayout.minWidth = HalfWidth; @@ -47,8 +49,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views // Key area var keyGroup = UIFactory.CreateUIObject("KeyHolder", root.transform.Find("HoriGroup").gameObject); UIFactory.SetLayoutGroup(keyGroup, false, false, true, true, 2, 0, 0, 4, 4, childAlignment: TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(keyGroup, minHeight: 30, minWidth: AdjustedKeyWidth, flexibleWidth: 0); - KeyGroupLayout = keyGroup.GetComponent(); + KeyGroupLayout = UIFactory.SetLayoutElement(keyGroup, minHeight: 30, minWidth: AdjustedKeyWidth, flexibleWidth: 0); // set to be after the NameLabel (our index label), and before the main horizontal group. keyGroup.transform.SetSiblingIndex(1); @@ -62,17 +63,17 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views // label KeyLabel = UIFactory.CreateLabel(keyGroup, "KeyLabel", "empty", TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(KeyLabel.gameObject, minWidth: 50, flexibleWidth: 999, minHeight: 30); + UIFactory.SetLayoutElement(KeyLabel.gameObject, minWidth: 50, flexibleWidth: 999, minHeight: 25); // Type label for input field KeyInputTypeLabel = UIFactory.CreateLabel(keyGroup, "InputTypeLabel", "null", TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(KeyInputTypeLabel.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 30); + UIFactory.SetLayoutElement(KeyInputTypeLabel.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); // input field var keyInputObj = UIFactory.CreateInputField(keyGroup, "KeyInput", "empty", out KeyInputField); - UIFactory.SetLayoutElement(keyInputObj, minHeight: 30, flexibleHeight: 0, flexibleWidth: 200); + UIFactory.SetLayoutElement(keyInputObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 200); //KeyInputField.lineType = InputField.LineType.MultiLineNewline; KeyInputField.readOnly = true; diff --git a/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs b/src/UI/CacheObject/Views/CacheListEntryCell.cs similarity index 86% rename from src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs rename to src/UI/CacheObject/Views/CacheListEntryCell.cs index fd7e5b3..46d7a9b 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs +++ b/src/UI/CacheObject/Views/CacheListEntryCell.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.IValues; -namespace UnityExplorer.UI.Inspectors.CacheObject.Views +namespace UnityExplorer.UI.CacheObject.Views { public class CacheListEntryCell : CacheObjectCell { @@ -24,7 +24,8 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views this.NameLayout.minWidth = 40; this.NameLayout.flexibleWidth = 50; - this.NameLayout.minHeight = 30; + this.NameLayout.minHeight = 25; + this.NameLayout.flexibleHeight = 0; this.NameLabel.alignment = TextAnchor.MiddleRight; return root; diff --git a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs b/src/UI/CacheObject/Views/CacheMemberCell.cs similarity index 94% rename from src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs rename to src/UI/CacheObject/Views/CacheMemberCell.cs index 5afde1d..0a58aaa 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs +++ b/src/UI/CacheObject/Views/CacheMemberCell.cs @@ -6,11 +6,11 @@ using UnityEngine; using UnityEngine.UI; using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Inspectors.CacheObject.Views +namespace UnityExplorer.UI.CacheObject.Views { public class CacheMemberCell : CacheObjectCell { - public ReflectionInspector Owner { get; set; } + //public ReflectionInspector Owner { get; set; } public CacheMember MemberOccupant => Occupant as CacheMember; diff --git a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs b/src/UI/CacheObject/Views/CacheObjectCell.cs similarity index 97% rename from src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs rename to src/UI/CacheObject/Views/CacheObjectCell.cs index b6c6678..a50f798 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs +++ b/src/UI/CacheObject/Views/CacheObjectCell.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.Inspectors; +using UnityExplorer.UI.IValues; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Inspectors.CacheObject.Views +namespace UnityExplorer.UI.CacheObject.Views { public abstract class CacheObjectCell : ICell { @@ -106,7 +107,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); Rect = UIRoot.GetComponent(); - UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 0, 0); + UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, childAlignment: TextAnchor.UpperLeft); UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; diff --git a/src/UI/Inspectors/CacheObject/Views/EvaluateWidget.cs b/src/UI/CacheObject/Views/EvaluateWidget.cs similarity index 89% rename from src/UI/Inspectors/CacheObject/Views/EvaluateWidget.cs rename to src/UI/CacheObject/Views/EvaluateWidget.cs index e54bc35..913a3a5 100644 --- a/src/UI/Inspectors/CacheObject/Views/EvaluateWidget.cs +++ b/src/UI/CacheObject/Views/EvaluateWidget.cs @@ -7,8 +7,9 @@ using UnityEngine; using UnityEngine.UI; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets.AutoComplete; -namespace UnityExplorer.UI.Inspectors.CacheObject.Views +namespace UnityExplorer.UI.CacheObject.Views { public class EvaluateWidget : IPooledObject { @@ -117,7 +118,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views if (genericArguments.Any()) { genericArgHolder.SetActive(true); - SetupGenericArgs(); + SetGenericRows(); } else genericArgHolder.SetActive(false); @@ -125,13 +126,13 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views if (arguments.Any()) { argHolder.SetActive(true); - SetupArgs(); + SetNormalArgRows(); } else argHolder.SetActive(false); } - private void SetupGenericArgs() + private void SetGenericRows() { for (int i = 0; i < genericArguments.Length || i < genericArgRows.Count; i++) { @@ -152,15 +153,18 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views genericArgRows[i].SetActive(true); + var constraints = arg.GetGenericParameterConstraints(); + + // TODO show "class" constraints as they dont show up, "struct" does effectively. + var sb = new StringBuilder($"{arg.Name}"); - var constraints = arg.GetGenericParameterConstraints(); for (int j = 0; j < constraints.Length; j++) { if (j == 0) sb.Append(' ').Append('('); else sb.Append(',').Append(' '); - sb.Append(SignatureHighlighter.ParseType(constraints[i])); + sb.Append(SignatureHighlighter.ParseType(constraints[j])); if (j + 1 == constraints.Length) sb.Append(')'); @@ -170,7 +174,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views } } - private void SetupArgs() + private void SetNormalArgRows() { for (int i = 0; i < arguments.Length || i < argRows.Count; i++) { @@ -197,16 +201,17 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views private void AddArgRow(int index, bool generic) { if (!generic) - AddArgRow(index, argHolder, argRows, argLabels, argumentInput); + AddArgRow(index, argHolder, argRows, argLabels, argumentInput);//, false); else - AddArgRow(index, genericArgHolder, genericArgRows, genericArgLabels, genericInput); + AddArgRow(index, genericArgHolder, genericArgRows, genericArgLabels, genericInput);//, true); } - private void AddArgRow(int index, GameObject parent, List objectList, List labelList, string[] inputArray) + private void AddArgRow(int index, GameObject parent, List objectList, List labelList, string[] inputArray)//, bool autocomplete) { var horiGroup = UIFactory.CreateUIObject("ArgRow_" + index, parent); UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleHeight: 50, minWidth: 50, flexibleWidth: 9999); UIFactory.SetLayoutGroup(horiGroup, false, false, true, true, 5); + horiGroup.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; objectList.Add(horiGroup); var label = UIFactory.CreateLabel(horiGroup, "ArgLabel", "not set", TextAnchor.MiddleLeft); @@ -215,8 +220,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views label.horizontalOverflow = HorizontalWrapMode.Wrap; var inputObj = UIFactory.CreateInputField(horiGroup, "InputField", "...", out InputField inputField); - UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 0, minWidth: 100, flexibleWidth: 1000); + UIFactory.SetLayoutElement(inputObj, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000); inputField.lineType = InputField.LineType.MultiLineNewline; + inputObj.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; inputField.onValueChanged.AddListener((string val) => { inputArray[index] = val; }); inputFieldCache.Add(inputField); } @@ -226,6 +232,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIRoot = UIFactory.CreateVerticalGroup(parent, "EvaluateWidget", false, false, true, true, 3, new Vector4(2, 2, 2, 2), new Color(0.15f, 0.15f, 0.15f)); UIFactory.SetLayoutElement(UIRoot, minWidth: 50, flexibleWidth: 9999, minHeight: 50, flexibleHeight: 800); + //UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; // generic args this.genericArgHolder = UIFactory.CreateUIObject("GenericHolder", UIRoot); @@ -234,6 +241,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIFactory.SetLayoutElement(genericsTitle.gameObject, minHeight: 25, flexibleWidth: 1000); UIFactory.SetLayoutGroup(genericArgHolder, false, false, true, true, 3); UIFactory.SetLayoutElement(genericArgHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999); + //genericArgHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; // args this.argHolder = UIFactory.CreateUIObject("ArgHolder", UIRoot); @@ -242,6 +250,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIFactory.SetLayoutElement(argsTitle.gameObject, minHeight: 25, flexibleWidth: 1000); UIFactory.SetLayoutGroup(argHolder, false, false, true, true, 3); UIFactory.SetLayoutElement(argHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999); + //argHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; // evaluate button var evalButton = UIFactory.CreateButton(UIRoot, "EvaluateButton", "Evaluate", new Color(0.2f, 0.2f, 0.2f)); diff --git a/src/UI/Inspectors/IValues/InteractiveDictionary.cs b/src/UI/IValues/InteractiveDictionary.cs similarity index 90% rename from src/UI/Inspectors/IValues/InteractiveDictionary.cs rename to src/UI/IValues/InteractiveDictionary.cs index 84ae0e5..106748b 100644 --- a/src/UI/Inspectors/IValues/InteractiveDictionary.cs +++ b/src/UI/IValues/InteractiveDictionary.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject; -using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Inspectors.IValues +namespace UnityExplorer.UI.IValues { public class InteractiveDictionary : InteractiveValue, IPoolDataSource, ICacheObjectController { @@ -137,10 +138,8 @@ namespace UnityExplorer.UI.Inspectors.IValues { var cache = cachedEntries[i]; if (cache.CellView != null) - { - cache.CellView.Occupant = null; - cache.CellView = null; - } + cache.UnlinkFromView(); + cache.ReleasePooledObjects(); cachedEntries.RemoveAt(i); } @@ -159,10 +158,7 @@ namespace UnityExplorer.UI.Inspectors.IValues if (index < 0 || index >= cachedEntries.Count) { if (cell.Occupant != null) - { - cell.Occupant.CellView = null; - cell.Occupant = null; - } + cell.Occupant.UnlinkFromView(); cell.Disable(); return; @@ -170,20 +166,11 @@ namespace UnityExplorer.UI.Inspectors.IValues var entry = cachedEntries[index]; - if (entry != cell.Occupant) - { - if (cell.Occupant != null) - { - cell.Occupant.HidePooledObjects(); - cell.Occupant.CellView = null; - cell.Occupant = null; - } + if (cell.Occupant != null && entry != cell.Occupant) + cell.Occupant.UnlinkFromView(); - cell.Occupant = entry; - entry.CellView = cell; - } - - entry.SetCell(cell); + entry.SetView(cell); + entry.SetDataToCell(cell); SetCellLayout(cell); } diff --git a/src/UI/Inspectors/IValues/InteractiveList.cs b/src/UI/IValues/InteractiveList.cs similarity index 86% rename from src/UI/Inspectors/IValues/InteractiveList.cs rename to src/UI/IValues/InteractiveList.cs index 3354f55..8f4021c 100644 --- a/src/UI/Inspectors/IValues/InteractiveList.cs +++ b/src/UI/IValues/InteractiveList.cs @@ -4,13 +4,14 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject; -using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.CacheObject.Views; +using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Inspectors.IValues +namespace UnityExplorer.UI.IValues { public class InteractiveList : InteractiveValue, IPoolDataSource, ICacheObjectController { @@ -51,7 +52,10 @@ namespace UnityExplorer.UI.Inspectors.IValues values.Clear(); foreach (var entry in cachedEntries) + { + entry.UnlinkFromView(); entry.ReleasePooledObjects(); + } cachedEntries.Clear(); } @@ -121,10 +125,8 @@ namespace UnityExplorer.UI.Inspectors.IValues { var cache = cachedEntries[i]; if (cache.CellView != null) - { - cache.CellView.Occupant = null; - cache.CellView = null; - } + cache.UnlinkFromView(); + cache.ReleasePooledObjects(); cachedEntries.RemoveAt(i); } @@ -146,20 +148,14 @@ namespace UnityExplorer.UI.Inspectors.IValues this.scrollLayout.minHeight = Math.Min(InspectorPanel.CurrentPanelHeight - 400f, minHeight); } - public void OnCellBorrowed(CacheListEntryCell cell) - { - - } + public void OnCellBorrowed(CacheListEntryCell cell) { } // not needed public void SetCell(CacheListEntryCell cell, int index) { if (index < 0 || index >= cachedEntries.Count) { if (cell.Occupant != null) - { - cell.Occupant.CellView = null; - cell.Occupant = null; - } + cell.Occupant.UnlinkFromView(); cell.Disable(); return; @@ -167,20 +163,11 @@ namespace UnityExplorer.UI.Inspectors.IValues var entry = cachedEntries[index]; - if (entry != cell.Occupant) - { - if (cell.Occupant != null) - { - cell.Occupant.HidePooledObjects(); - cell.Occupant.CellView = null; - cell.Occupant = null; - } + if (cell.Occupant != null && entry != cell.Occupant) + cell.Occupant.UnlinkFromView(); - cell.Occupant = entry; - entry.CellView = cell; - } - - entry.SetCell(cell); + entry.SetView(cell); + entry.SetDataToCell(cell); } private LayoutElement scrollLayout; diff --git a/src/UI/Inspectors/IValues/InteractiveValue.cs b/src/UI/IValues/InteractiveValue.cs similarity index 96% rename from src/UI/Inspectors/IValues/InteractiveValue.cs rename to src/UI/IValues/InteractiveValue.cs index 8e3f344..84e61a5 100644 --- a/src/UI/Inspectors/IValues/InteractiveValue.cs +++ b/src/UI/IValues/InteractiveValue.cs @@ -4,10 +4,10 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject; +using UnityExplorer.UI.CacheObject; using UnityExplorer.UI.ObjectPool; -namespace UnityExplorer.UI.Inspectors.IValues +namespace UnityExplorer.UI.IValues { public class InteractiveValue : IPooledObject { diff --git a/src/UI/Inspectors/InspectorBase.cs b/src/UI/Inspectors/InspectorBase.cs index 219497d..6a8d6a1 100644 --- a/src/UI/Inspectors/InspectorBase.cs +++ b/src/UI/Inspectors/InspectorBase.cs @@ -37,6 +37,8 @@ namespace UnityExplorer.UI.Inspectors { Pool.Return(Tab); + this.Target = null; + Tab.TabButton.OnClick -= OnTabButtonClicked; Tab.CloseButton.OnClick -= OnCloseClicked; } diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs index a2d6981..5dbb530 100644 --- a/src/UI/Inspectors/InspectorManager.cs +++ b/src/UI/Inspectors/InspectorManager.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject; +using UnityExplorer.UI.CacheObject; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Panels; diff --git a/src/UI/Inspectors/ListInspector.cs b/src/UI/Inspectors/ListInspector.cs index 31b5c83..daa268c 100644 --- a/src/UI/Inspectors/ListInspector.cs +++ b/src/UI/Inspectors/ListInspector.cs @@ -5,8 +5,8 @@ //using System.Text; //using UnityEngine; //using UnityEngine.UI; -//using UnityExplorer.UI.Inspectors.CacheObject; -//using UnityExplorer.UI.Inspectors.CacheObject.Views; +//using UnityExplorer.UI.CacheObject; +//using UnityExplorer.UI.CacheObject.Views; //using UnityExplorer.UI.Utility; //using UnityExplorer.UI.Widgets; diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index 1e14a24..03e6e1c 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -7,8 +7,10 @@ using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.UI; -using UnityExplorer.UI.Inspectors.CacheObject; -using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.Core.Config; +using UnityExplorer.Core.Runtime; +using UnityExplorer.UI.CacheObject; +using UnityExplorer.UI.CacheObject.Views; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; @@ -20,34 +22,57 @@ namespace UnityExplorer.UI.Inspectors { public CacheObjectBase ParentCacheObject { get; set; } - public bool StaticOnly { get; internal set; } - - //public object Target { get; private set; } public Type TargetType { get; private set; } public bool CanWrite => true; - public ScrollPool MemberScrollPool { get; private set; } + // Instance state + + public bool StaticOnly { get; internal set; } + + public BindingFlags FlagsFilter { get; private set; } + public string NameFilter { get; private set; } + + public bool AutoUpdateWanted { get; set; } private List members = new List(); private readonly List filteredMembers = new List(); - // private readonly HashSet displayedMembers = new HashSet(); + + + // UI + + public ScrollPool MemberScrollPool { get; private set; } public Text NameText; public Text AssemblyText; - private LayoutElement memberTitleLayout; + // Unity object helpers + private UnityEngine.Object ObjectRef; + private Component ComponentRef; + private Texture2D TextureRef; + private bool TextureViewerWanted; + private GameObject unityObjectRow; + private ButtonRef gameObjectButton; + private InputField nameInput; + private InputField instanceIdInput; + private ButtonRef textureButton; + private GameObject textureViewer; + + private readonly Color disabledButtonColor = new Color(0.24f, 0.24f, 0.24f); + private readonly Color enabledButtonColor = new Color(0.2f, 0.27f, 0.2f); + private readonly Dictionary scopeFilterButtons = new Dictionary(); + private InputField filterInputField; + + //private LayoutElement memberTitleLayout; - public bool AutoUpdateWanted { get; set; } private Toggle autoUpdateToggle; public override void OnBorrowedFromPool(object target) { base.OnBorrowedFromPool(target); + CalculateLayouts(); - SetTitleLayouts(); SetTarget(target); - MemberScrollPool.Refresh(true, true); RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); } @@ -58,10 +83,18 @@ namespace UnityExplorer.UI.Inspectors LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect); } + protected override void OnCloseClicked() + { + InspectorManager.ReleaseInspector(this); + } + public override void OnReturnToPool() { foreach (var member in members) + { + member.UnlinkFromView(); member.ReleasePooledObjects(); + } members.Clear(); filteredMembers.Clear(); @@ -69,9 +102,16 @@ namespace UnityExplorer.UI.Inspectors autoUpdateToggle.isOn = false; AutoUpdateWanted = false; + ObjectRef = null; + ComponentRef = null; + TextureRef = null; + CleanupTextureViewer(); + base.OnReturnToPool(); } + // Setting target + private void SetTarget(object target) { string prefix; @@ -87,50 +127,33 @@ namespace UnityExplorer.UI.Inspectors prefix = "[R]"; } + // Setup main labels and tab text Tab.TabText.text = $"{prefix} {SignatureHighlighter.ParseType(TargetType)}"; - NameText.text = SignatureHighlighter.ParseFullSyntax(TargetType, true); string asmText; if (TargetType.Assembly != null && !string.IsNullOrEmpty(TargetType.Assembly.Location)) - asmText = Path.GetFileName(TargetType.Assembly.Location); + asmText = Path.GetFileName(TargetType.Assembly.Location); else asmText = $"{TargetType.Assembly.GetName().Name} (in memory)"; AssemblyText.text = $"Assembly: {asmText}"; + // unity helpers + SetUnityTargets(); + // Get cache members, and set filter to default this.members = CacheMember.GetCacheMembers(Target, TargetType, this); - FilterMembers(); + this.filterInputField.text = ""; + SetFilter("", StaticOnly ? BindingFlags.Static : BindingFlags.Instance); + refreshWanted = true; } - public void FilterMembers() - { - // todo - for (int i = 0; i < members.Count; i++) - { - var member = members[i]; - filteredMembers.Add(member); - } + // Updating - //MemberScrollPool.Refresh - } - - public override void OnSetActive() - { - base.OnSetActive(); - } - - public override void OnSetInactive() - { - base.OnSetInactive(); - } - - protected override void OnCloseClicked() - { - InspectorManager.ReleaseInspector(this); - } - - private float timeOfLastUpdate; + private bool refreshWanted; + private string lastNameFilter; + private BindingFlags lastFlagsFilter; + private float timeOfLastAutoUpdate; public override void Update() { @@ -143,15 +166,68 @@ namespace UnityExplorer.UI.Inspectors return; } - if (timeOfLastUpdate.OccuredEarlierThan(1)) + if (refreshWanted || NameFilter != lastNameFilter || FlagsFilter != lastFlagsFilter) { - timeOfLastUpdate = Time.realtimeSinceStartup; + lastNameFilter = NameFilter; + lastFlagsFilter = FlagsFilter; + + FilterMembers(); + MemberScrollPool.Refresh(true, true); + refreshWanted = false; + } + + if (timeOfLastAutoUpdate.OccuredEarlierThan(1)) + { + timeOfLastAutoUpdate = Time.realtimeSinceStartup; if (AutoUpdateWanted) UpdateDisplayedMembers();// true); } } + // Filtering + + public void SetFilter(string filter) => SetFilter(filter, FlagsFilter); + + public void SetFilter(BindingFlags flagsFilter) => SetFilter(NameFilter, flagsFilter); + + public void SetFilter(string nameFilter, BindingFlags flagsFilter) + { + this.NameFilter = nameFilter; + + if (flagsFilter != FlagsFilter) + { + var btn = scopeFilterButtons[FlagsFilter].Button; + RuntimeProvider.Instance.SetColorBlock(btn, disabledButtonColor, disabledButtonColor * 1.3f); + + this.FlagsFilter = flagsFilter; + btn = scopeFilterButtons[FlagsFilter].Button; + RuntimeProvider.Instance.SetColorBlock(btn, enabledButtonColor, enabledButtonColor * 1.3f); + } + } + + private void FilterMembers() + { + filteredMembers.Clear(); + + for (int i = 0; i < members.Count; i++) + { + var member = members[i]; + + if (!string.IsNullOrEmpty(NameFilter) && !member.NameForFiltering.ContainsIgnoreCase(NameFilter)) + continue; + + if (FlagsFilter != BindingFlags.Default) + { + if (FlagsFilter == BindingFlags.Instance && member.IsStatic + || FlagsFilter == BindingFlags.Static && !member.IsStatic) + continue; + } + + filteredMembers.Add(member); + } + } + private void UpdateDisplayedMembers()// bool onlyAutoUpdate) { bool shouldRefresh = false; @@ -164,7 +240,7 @@ namespace UnityExplorer.UI.Inspectors { shouldRefresh = true; member.Evaluate(); - member.SetCell(member.CellView); + member.SetDataToCell(member.CellView); } } @@ -176,20 +252,14 @@ namespace UnityExplorer.UI.Inspectors public int ItemCount => filteredMembers.Count; - public void OnCellBorrowed(CacheMemberCell cell) - { - cell.Owner = this; - } + public void OnCellBorrowed(CacheMemberCell cell) { } // not needed public void SetCell(CacheMemberCell cell, int index) { if (index < 0 || index >= filteredMembers.Count) { if (cell.Occupant != null) - { - cell.Occupant.CellView = null; - cell.Occupant = null; - } + cell.Occupant.UnlinkFromView(); cell.Disable(); return; @@ -197,20 +267,11 @@ namespace UnityExplorer.UI.Inspectors var member = filteredMembers[index]; - if (member != cell.Occupant) - { - if (cell.Occupant != null) - { - cell.Occupant.HidePooledObjects(); - cell.Occupant.CellView = null; - cell.Occupant = null; - } + if (cell.Occupant != null && member != cell.Occupant) + cell.Occupant.UnlinkFromView(); - cell.Occupant = member; - member.CellView = cell; - } - - member.SetCell(cell); + member.SetView(cell); + member.SetDataToCell(cell); SetCellLayout(cell); } @@ -220,13 +281,21 @@ namespace UnityExplorer.UI.Inspectors private static int LeftGroupWidth { get; set; } private static int RightGroupWidth { get; set; } - private void SetTitleLayouts() + internal void SetLayouts() + { + CalculateLayouts(); + + foreach (var cell in MemberScrollPool.CellPool) + SetCellLayout(cell); + } + + private void CalculateLayouts() { // Calculate sizes - LeftGroupWidth = (int)Math.Max(200, (0.45f * InspectorManager.PanelWidth) - 5);// Math.Min(450f, 0.4f * InspectorManager.PanelWidth - 5)); - RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 55); + LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);// Math.Min(450f, 0.4f * InspectorManager.PanelWidth - 5)); + RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65); - memberTitleLayout.minWidth = LeftGroupWidth; + //memberTitleLayout.minWidth = LeftGroupWidth; } private void SetCellLayout(CacheObjectCell cell) @@ -238,57 +307,40 @@ namespace UnityExplorer.UI.Inspectors cell.Occupant.IValue.SetLayout(); } - internal void SetLayouts() - { - SetTitleLayouts(); + // UI Construction - foreach (var cell in MemberScrollPool.CellPool) - SetCellLayout(cell); - } + private GameObject mainContentHolder; public override GameObject CreateContent(GameObject parent) { - UIRoot = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, true, true, true, 5, - new Vector4(4, 4, 4, 4), new Color(0.12f, 0.12f, 0.12f)); + UIRoot = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, true, true, true, 5, + new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f)); - // Class name, assembly. TODO more details + // Class name, assembly - NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 20); + NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 17); UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 25, flexibleHeight: 0); AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999); - // TODO filter row + ConstructUnityObjectRow(); + mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2,2,2,2), + new Color(0.12f, 0.12f, 0.12f)); + UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999); + ConstructFilterRow(mainContentHolder); - // Member list titles - - var listTitles = UIFactory.CreateUIObject("ListTitles", UIRoot); - UIFactory.SetLayoutElement(listTitles, minHeight: 25); - UIFactory.SetLayoutGroup(listTitles, true, true, true, true, 5, 1, 1, 1, 1); - - var memberTitle = UIFactory.CreateLabel(listTitles, "MemberTitle", "Member Name", TextAnchor.LowerLeft, Color.grey, fontSize: 15); - memberTitleLayout = memberTitle.gameObject.AddComponent(); - - var valueTitle = UIFactory.CreateLabel(listTitles, "ValueTitle", "Value", TextAnchor.LowerLeft, Color.grey, fontSize: 15); - UIFactory.SetLayoutElement(valueTitle.gameObject, minWidth: 50, flexibleWidth: 9999); - - var updateButton = UIFactory.CreateButton(listTitles, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f)); - UIFactory.SetLayoutElement(updateButton.Button.gameObject, minHeight: 25, minWidth: 160, flexibleWidth: 0); - updateButton.OnClick += UpdateDisplayedMembers; - - var toggleObj = UIFactory.CreateToggle(listTitles, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText); - //GameObject.DestroyImmediate(toggleText); - UIFactory.SetLayoutElement(toggleObj, minWidth: 185, minHeight: 25); - autoUpdateToggle.isOn = false; - autoUpdateToggle.onValueChanged.AddListener((bool val) => { AutoUpdateWanted = val; }); - toggleText.text = "Auto-update displayed"; + ConstructUpdateRow(mainContentHolder); // Member scroll pool - MemberScrollPool = UIFactory.CreateScrollPool(UIRoot, "MemberList", out GameObject scrollObj, + var memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2,2,2,2), + bgColor: new Color(0.05f, 0.05f, 0.05f)); + UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999); + + MemberScrollPool = UIFactory.CreateScrollPool(memberBorder, "MemberList", out GameObject scrollObj, out GameObject _, new Color(0.09f, 0.09f, 0.09f)); UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); MemberScrollPool.Initialize(this); @@ -299,5 +351,278 @@ namespace UnityExplorer.UI.Inspectors return UIRoot; } + + // Filter row + + private void ConstructFilterRow(GameObject parent) + { + var filterRow = UIFactory.CreateUIObject("FilterRow", parent); + UIFactory.SetLayoutGroup(filterRow, true, true, true, true, 5, 2, 2, 2, 2); + UIFactory.SetLayoutElement(filterRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + + var nameLabel = UIFactory.CreateLabel(filterRow, "NameFilterLabel", "Filter names:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 90, flexibleWidth: 0); + var nameFilterObj = UIFactory.CreateInputField(filterRow, "NameFilterInput", "...", out filterInputField); + UIFactory.SetLayoutElement(nameFilterObj, minHeight: 25, flexibleWidth: 300); + filterInputField.onValueChanged.AddListener((string val) => { SetFilter(val); }); + + var spacer = UIFactory.CreateUIObject("Spacer", filterRow); + UIFactory.SetLayoutElement(spacer, minWidth: 25); + + var scopeLabel = UIFactory.CreateLabel(filterRow, "ScopeLabel", "Scope:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(scopeLabel.gameObject, minHeight: 25, minWidth: 60, flexibleWidth: 0); + AddFilterButton(filterRow, BindingFlags.Default, true); + AddFilterButton(filterRow, BindingFlags.Instance); + AddFilterButton(filterRow, BindingFlags.Static); + } + + private void AddFilterButton(GameObject parent, BindingFlags flags, bool setAsActive = false) + { + string lbl = flags == BindingFlags.Default ? "All" : flags.ToString(); + var color = setAsActive ? enabledButtonColor : disabledButtonColor; + + var button = UIFactory.CreateButton(parent, "Filter_" + flags, lbl, color); + UIFactory.SetLayoutElement(button.Button.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 100, flexibleWidth: 0); + scopeFilterButtons.Add(flags, button); + + button.OnClick += () => { SetFilter(flags); }; + } + + // Update row + + private void ConstructUpdateRow(GameObject parent) + { + var updateRow = UIFactory.CreateUIObject("UpdateRow", parent); + UIFactory.SetLayoutGroup(updateRow, false, false, true, true, 4); + UIFactory.SetLayoutElement(updateRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + + var updateButton = UIFactory.CreateButton(updateRow, "UpdateButton", "Update displayed values", new Color(0.22f, 0.28f, 0.22f)); + UIFactory.SetLayoutElement(updateButton.Button.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0); + updateButton.OnClick += UpdateDisplayedMembers; + + var toggleObj = UIFactory.CreateToggle(updateRow, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText); + //GameObject.DestroyImmediate(toggleText); + UIFactory.SetLayoutElement(toggleObj, minWidth: 185, minHeight: 25); + autoUpdateToggle.isOn = false; + autoUpdateToggle.onValueChanged.AddListener((bool val) => { AutoUpdateWanted = val; }); + toggleText.text = "Auto-update displayed"; + } + + #region UNITY OBJECT SPECIFIC + + // Unity object helpers + + private void SetUnityTargets() + { + if (!typeof(UnityEngine.Object).IsAssignableFrom(TargetType)) + { + unityObjectRow.SetActive(false); + textureViewer.SetActive(false); + return; + } + + ObjectRef = (UnityEngine.Object)Target.TryCast(typeof(UnityEngine.Object)); + unityObjectRow.SetActive(true); + + nameInput.text = ObjectRef.name; + instanceIdInput.text = ObjectRef.GetInstanceID().ToString(); + + if (typeof(Component).IsAssignableFrom(TargetType)) + { + ComponentRef = (Component)Target.TryCast(typeof(Component)); + gameObjectButton.Button.gameObject.SetActive(true); + } + else + gameObjectButton.Button.gameObject.SetActive(false); + + if (typeof(Texture2D).IsAssignableFrom(TargetType)) + { + TextureRef = (Texture2D)Target.TryCast(typeof(Texture2D)); + textureButton.Button.gameObject.SetActive(true); + } + else + textureButton.Button.gameObject.SetActive(false); + } + + private void OnGameObjectButtonClicked() + { + if (!ComponentRef) + { + ExplorerCore.LogWarning("Component reference is null or destroyed!"); + return; + } + + InspectorManager.Inspect(ComponentRef.gameObject); + } + + private void ToggleTextureViewer() + { + if (TextureViewerWanted) + { + // disable + TextureViewerWanted = false; + textureViewer.gameObject.SetActive(false); + mainContentHolder.SetActive(true); + textureButton.ButtonText.text = "View Texture"; + } + else + { + if (!textureImage.sprite) + { + // First show, need to create sprite for displaying texture + SetTextureViewer(); + } + + // enable + TextureViewerWanted = true; + textureViewer.gameObject.SetActive(true); + mainContentHolder.gameObject.SetActive(false); + textureButton.ButtonText.text = "Hide Texture"; + } + } + + // UI construction + + private void ConstructUnityObjectRow() + { + unityObjectRow = UIFactory.CreateUIObject("UnityObjectRow", UIRoot); + UIFactory.SetLayoutGroup(unityObjectRow, false, false, true, true, 5); + UIFactory.SetLayoutElement(unityObjectRow, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + + textureButton = UIFactory.CreateButton(unityObjectRow, "TextureButton", "View Texture", new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(textureButton.Button.gameObject, minHeight: 25, minWidth: 150); + textureButton.OnClick += ToggleTextureViewer; + + gameObjectButton = UIFactory.CreateButton(unityObjectRow, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); + UIFactory.SetLayoutElement(gameObjectButton.Button.gameObject, minHeight: 25, minWidth: 170); + gameObjectButton.OnClick += OnGameObjectButtonClicked; + + var nameLabel = UIFactory.CreateLabel(unityObjectRow, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); + UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); + + var nameInputObj = UIFactory.CreateInputField(unityObjectRow, "NameInput", "untitled", out nameInput); + UIFactory.SetLayoutElement(nameInputObj, minHeight: 25, minWidth: 100, flexibleWidth: 1000); + nameInput.readOnly = true; + + var instanceLabel = UIFactory.CreateLabel(unityObjectRow, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); + UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + + var instanceInputObj = UIFactory.CreateInputField(unityObjectRow, "InstanceIDInput", "ERROR", out instanceIdInput); + UIFactory.SetLayoutElement(instanceInputObj, minHeight: 25, minWidth: 100, flexibleWidth: 0); + instanceIdInput.readOnly = true; + + unityObjectRow.SetActive(false); + + ConstructTextureHelper(); + } + + // Texture viewer helper + + private InputField textureSavePathInput; + private Image textureImage; + private LayoutElement textureImageLayout; + + private void CleanupTextureViewer() + { + if (textureImage.sprite) + GameObject.Destroy(textureImage.sprite); + + if (TextureViewerWanted) + ToggleTextureViewer(); + } + + private void ConstructTextureHelper() + { + textureViewer = UIFactory.CreateVerticalGroup(UIRoot, "TextureViewer", false, false, true, true, 2, new Vector4(5, 5, 5, 5), + new Color(0.1f, 0.1f, 0.1f)); + UIFactory.SetLayoutElement(textureViewer, flexibleWidth: 9999, flexibleHeight: 9999); + + // Save helper + + var saveRowObj = UIFactory.CreateHorizontalGroup(textureViewer, "SaveRow", false, false, true, true, 2, new Vector4(2, 2, 2, 2), + new Color(0.1f, 0.1f, 0.1f)); + + var saveBtn = UIFactory.CreateButton(saveRowObj, "SaveButton", "Save .PNG", new Color(0.2f, 0.25f, 0.2f)); + UIFactory.SetLayoutElement(saveBtn.Button.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); + saveBtn.OnClick += OnSaveTextureClicked; + + var inputObj = UIFactory.CreateInputField(saveRowObj, "SaveInput", "...", out textureSavePathInput); + UIFactory.SetLayoutElement(inputObj, minHeight: 25, minWidth: 100, flexibleWidth: 9999); + + // Actual texture viewer + + var imageObj = UIFactory.CreateUIObject("TextureViewerImage", textureViewer); + textureImage = imageObj.AddComponent(); + textureImageLayout = textureImage.gameObject.AddComponent(); + + var fitter = imageObj.AddComponent(); + fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + textureViewer.SetActive(false); + } + + private void SetTextureViewer() + { + if (!this.TextureRef) + return; + + var name = TextureRef.name; + if (string.IsNullOrEmpty(name)) + name = "untitled"; + + textureSavePathInput.text = Path.Combine(ConfigManager.Default_Output_Path.Value, $"{name}.png"); + + var sprite = TextureUtilProvider.Instance.CreateSprite(TextureRef); + textureImage.sprite = sprite; + + textureImageLayout.preferredHeight = sprite.rect.height; + textureImageLayout.preferredWidth = sprite.rect.width; + } + + private void OnSaveTextureClicked() + { + if (!TextureRef) + { + ExplorerCore.LogWarning("Ref Texture is null, maybe it was destroyed?"); + return; + } + + if (string.IsNullOrEmpty(textureSavePathInput.text)) + { + ExplorerCore.LogWarning("Save path cannot be empty!"); + return; + } + + var path = textureSavePathInput.text; + if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase)) + { + ExplorerCore.LogWarning("Desired save path must end with '.png'!"); + return; + } + + var dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + if (File.Exists(path)) + File.Delete(path); + + var tex = TextureRef; + + if (!TextureUtilProvider.IsReadable(tex)) + tex = TextureUtilProvider.ForceReadTexture(tex); + + byte[] data = TextureUtilProvider.Instance.EncodeToPNG(tex); + + File.WriteAllBytes(path, data); + + if (tex != TextureRef) + { + // cleanup temp texture if we had to force-read it. + GameObject.Destroy(tex); + } + } + + #endregion } } diff --git a/src/UI/ObjectExplorer/ObjectSearch.cs b/src/UI/ObjectExplorer/ObjectSearch.cs index 69c6f5a..14c64bf 100644 --- a/src/UI/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/ObjectExplorer/ObjectSearch.cs @@ -26,45 +26,29 @@ namespace UnityExplorer.UI.Panels private SearchContext m_context = SearchContext.UnityObject; private SceneFilter m_sceneFilter = SceneFilter.Any; private ChildFilter m_childFilter = ChildFilter.Any; + private string desiredTypeInput; + private string lastCheckedTypeInput; + private bool lastTypeCanHaveGO; public ButtonListSource dataHandler; private ScrollPool resultsScrollPool; private List currentResults = new List(); + public TypeCompleter typeAutocompleter; + public override GameObject UIRoot => uiRoot; private GameObject uiRoot; private GameObject sceneFilterRow; private GameObject childFilterRow; private GameObject unityObjectClassRow; - private InputField nameInputField; - private InputField classInputField; private Text resultsLabel; public List GetEntries() => currentResults; - private void OnContextDropdownChanged(int value) - { - m_context = (SearchContext)value; - - // show/hide other filters depending on what we just selected. - bool shouldShowGoFilters = m_context == SearchContext.GameObject - || m_context == SearchContext.Component - || m_context == SearchContext.Custom; - - sceneFilterRow.SetActive(shouldShowGoFilters); - childFilterRow.SetActive(shouldShowGoFilters); - - unityObjectClassRow.SetActive(m_context == SearchContext.Custom); - } - - private void OnSceneFilterDropChanged(int value) => m_sceneFilter = (SceneFilter)value; - - private void OnChildFilterDropChanged(int value) => m_childFilter = (ChildFilter)value; - public void DoSearch() { cachedCellTexts.Clear(); @@ -76,8 +60,8 @@ namespace UnityExplorer.UI.Panels else { string compType = ""; - if (m_context == SearchContext.Custom) - compType = classInputField.text; + if (m_context == SearchContext.UnityObject) + compType = this.desiredTypeInput; currentResults = SearchProvider.UnityObjectSearch(nameInputField.text, compType, m_context, m_childFilter, m_sceneFilter); } @@ -88,6 +72,59 @@ namespace UnityExplorer.UI.Panels resultsLabel.text = $"{currentResults.Count} results"; } + public void Update() + { + if (lastCheckedTypeInput != desiredTypeInput) + { + lastCheckedTypeInput = desiredTypeInput; + + //var type = ReflectionUtility.GetTypeByName(desiredTypeInput); + if (typeAutocompleter.AllTypes.TryGetValue(desiredTypeInput, out var cachedType)) + { + var type = cachedType.Type; + lastTypeCanHaveGO = typeof(Component).IsAssignableFrom(type) || type == typeof(GameObject); + sceneFilterRow.SetActive(lastTypeCanHaveGO); + childFilterRow.SetActive(lastTypeCanHaveGO); + } + else + { + sceneFilterRow.SetActive(false); + childFilterRow.SetActive(false); + lastTypeCanHaveGO = false; + } + } + } + + // UI Callbacks + + private void OnContextDropdownChanged(int value) + { + m_context = (SearchContext)value; + + bool shouldShowGoFilters = m_context == SearchContext.GameObject || m_context == SearchContext.UnityObject; + + sceneFilterRow.SetActive(shouldShowGoFilters); + childFilterRow.SetActive(shouldShowGoFilters); + + unityObjectClassRow.SetActive(m_context == SearchContext.UnityObject); + } + + private void OnSceneFilterDropChanged(int value) => m_sceneFilter = (SceneFilter)value; + + private void OnChildFilterDropChanged(int value) => m_childFilter = (ChildFilter)value; + + private void OnTypeInputChanged(string val) + { + desiredTypeInput = val; + + if (string.IsNullOrEmpty(val)) + { + sceneFilterRow.SetActive(false); + childFilterRow.SetActive(false); + lastCheckedTypeInput = val; + } + } + // Cache the syntax-highlighted text for each search result to reduce allocs. private static readonly Dictionary cachedCellTexts = new Dictionary(); @@ -143,12 +180,13 @@ namespace UnityExplorer.UI.Panels var unityClassLbl = UIFactory.CreateLabel(unityObjectClassRow, "UnityClassLabel", "Custom Type:", TextAnchor.MiddleLeft); UIFactory.SetLayoutElement(unityClassLbl.gameObject, minWidth: 110, flexibleWidth: 0); - var classInputObj = UIFactory.CreateInputField(unityObjectClassRow, "ClassInput", "...", out this.classInputField); + var classInputObj = UIFactory.CreateInputField(unityObjectClassRow, "ClassInput", "...", out var classInputField); UIFactory.SetLayoutElement(classInputObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); - new TypeCompleter(typeof(UnityEngine.Object), classInputField); + typeAutocompleter = new TypeCompleter(typeof(UnityEngine.Object), classInputField); + classInputField.onValueChanged.AddListener(OnTypeInputChanged); - unityObjectClassRow.SetActive(false); + //unityObjectClassRow.SetActive(false); // Child filter row diff --git a/src/UI/ObjectExplorer/SceneExplorer.cs b/src/UI/ObjectExplorer/SceneExplorer.cs index c72fe33..e5c8505 100644 --- a/src/UI/ObjectExplorer/SceneExplorer.cs +++ b/src/UI/ObjectExplorer/SceneExplorer.cs @@ -197,7 +197,7 @@ namespace UnityExplorer.UI.Panels toggle.isOn = false; toggle.onValueChanged.AddListener((bool val) => AutoUpdate = val); - //refreshRow.SetActive(false); + refreshRow.SetActive(false); // Transform Tree diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs index 9024eb5..9c9dfd6 100644 --- a/src/UI/Panels/CSConsolePanel.cs +++ b/src/UI/Panels/CSConsolePanel.cs @@ -15,6 +15,8 @@ namespace UnityExplorer.UI.Panels { public override string Name => "C# Console"; public override UIManager.Panels PanelType => UIManager.Panels.CSConsole; + public override int MinWidth => 400; + public override int MinHeight => 300; public static CSConsolePanel Instance { get; private set; } @@ -95,25 +97,18 @@ namespace UnityExplorer.UI.Panels ConfigManager.CSConsoleData.Value = this.ToSaveData(); } - public override void LoadSaveData() - { - this.ApplySaveData(ConfigManager.CSConsoleData.Value); - } + public override string GetSaveData() => ConfigManager.CSConsoleData.Value; - public override void SetDefaultPosAndAnchors() + // UI Construction + + protected internal override void DoSetDefaultPosAndAnchors() { mainPanelRect.localPosition = Vector2.zero; mainPanelRect.pivot = new Vector2(0f, 1f); - mainPanelRect.anchorMin = new Vector2(0.5f, 0); - mainPanelRect.anchorMax = new Vector2(0.5f, 1); - mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom - mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top - mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); - mainPanelRect.anchoredPosition = new Vector2(-150, 0); + mainPanelRect.anchorMin = new Vector2(0.4f, 0.1f); + mainPanelRect.anchorMax = new Vector2(0.9f, 0.85f); } - // UI Construction - public override void ConstructPanelContent() { //Content = UIFactory.CreateVerticalGroup(MainMenu.Instance.PageViewport, "CSharpConsole", true, true, true, true); diff --git a/src/UI/Panels/InspectorPanel.cs b/src/UI/Panels/InspectorPanel.cs index fa04146..6ce44b7 100644 --- a/src/UI/Panels/InspectorPanel.cs +++ b/src/UI/Panels/InspectorPanel.cs @@ -6,11 +6,7 @@ using System.Text; using UnityEngine; using UnityEngine.UI; using UnityExplorer.Core.Config; -using UnityExplorer.Core.Runtime; using UnityExplorer.UI.Inspectors; -using UnityExplorer.UI.Models; -using UnityExplorer.UI.Utility; -using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Panels { @@ -23,6 +19,8 @@ namespace UnityExplorer.UI.Panels public override string Name => "Inspector"; public override UIManager.Panels PanelType => UIManager.Panels.Inspector; public override bool ShouldSaveActiveState => false; + public override int MinWidth => 550; + public override int MinHeight => 350; public GameObject NavbarHolder; public GameObject ContentHolder; @@ -40,41 +38,40 @@ namespace UnityExplorer.UI.Panels { base.OnFinishResize(panel); + InspectorManager.PanelWidth = this.mainPanelRect.rect.width; InspectorManager.OnPanelResized(panel.rect.width); } - public override void LoadSaveData() - { - ApplySaveData(ConfigManager.InspectorData.Value); + public override string GetSaveData() => ConfigManager.InspectorData.Value; - InspectorManager.PanelWidth = this.mainPanelRect.rect.width; - } + //public override void LoadSaveData() + //{ + // ApplySaveData(ConfigManager.InspectorData.Value); + // + // InspectorManager.PanelWidth = this.mainPanelRect.rect.width; + //} public override void DoSaveToConfigElement() { ConfigManager.InspectorData.Value = this.ToSaveData(); } - public override void SetDefaultPosAndAnchors() + protected internal override void DoSetDefaultPosAndAnchors() { mainPanelRect.localPosition = Vector2.zero; mainPanelRect.pivot = new Vector2(0f, 1f); - mainPanelRect.anchorMin = new Vector2(0.1f, 0.15f); - mainPanelRect.anchorMax = new Vector2(0.1f, 0.95f); - mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom - mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top - mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); - mainPanelRect.anchoredPosition = new Vector2(-150, 0); + mainPanelRect.anchorMin = new Vector2(0.35f, 0.175f); + mainPanelRect.anchorMax = new Vector2(0.8f, 0.925f); } public override void ConstructPanelContent() { // this.UIRoot.GetComponent().enabled = false; - UIFactory.SetLayoutGroup(this.content, forceHeight: true, spacing: 4, padLeft: 5, padRight: 5); + UIFactory.SetLayoutGroup(this.content, true, true, true, true, 4, padLeft: 5, padRight: 5); this.NavbarHolder = UIFactory.CreateGridGroup(this.content, "Navbar", new Vector2(200, 22), new Vector2(4, 4), - new Color(0.12f, 0.12f, 0.12f)); + new Color(0.05f, 0.05f, 0.05f)); //UIFactory.SetLayoutElement(NavbarHolder, flexibleWidth: 9999, minHeight: 0, preferredHeight: 0, flexibleHeight: 9999); NavbarHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; diff --git a/src/UI/Panels/ObjectExplorer.cs b/src/UI/Panels/ObjectExplorer.cs index 88b6096..0e18eb3 100644 --- a/src/UI/Panels/ObjectExplorer.cs +++ b/src/UI/Panels/ObjectExplorer.cs @@ -20,6 +20,8 @@ namespace UnityExplorer.UI.Panels { public override string Name => "Object Explorer"; public override UIManager.Panels PanelType => UIManager.Panels.ObjectExplorer; + public override int MinWidth => 350; + public override int MinHeight => 200; public SceneExplorer SceneExplorer; public ObjectSearch ObjectSearch; @@ -56,18 +58,17 @@ namespace UnityExplorer.UI.Panels { if (SelectedTab == 0) SceneExplorer.Update(); + else + ObjectSearch.Update(); } + public override string GetSaveData() => ConfigManager.ObjectExplorerData.Value; + public override void DoSaveToConfigElement() { ConfigManager.ObjectExplorerData.Value = this.ToSaveData(); } - public override void LoadSaveData() - { - ApplySaveData(ConfigManager.ObjectExplorerData.Value); - } - public override string ToSaveData() { string ret = base.ToSaveData(); @@ -95,21 +96,13 @@ namespace UnityExplorer.UI.Panels SetTab(SelectedTab); } - public override void SetDefaultPosAndAnchors() + protected internal override void DoSetDefaultPosAndAnchors() { - // todo proper default size mainPanelRect.localPosition = Vector2.zero; mainPanelRect.pivot = new Vector2(0f, 1f); - mainPanelRect.anchorMin = new Vector2(0.1f, 0.25f); - mainPanelRect.anchorMax = new Vector2(0.25f, 0.8f); - - - //mainPanelRect.anchorMin = Vector3.zero; - //mainPanelRect.anchorMax = new Vector2(0, 1); - //mainPanelRect.sizeDelta = new Vector2(320f, mainPanelRect.sizeDelta.y); - //mainPanelRect.anchoredPosition = new Vector2(200, 0); - //mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom - //mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top + mainPanelRect.anchorMin = new Vector2(0.125f, 0.175f); + mainPanelRect.anchorMax = new Vector2(0.325f, 0.925f); + //mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 350); } public override void ConstructPanelContent() diff --git a/src/UI/Panels/PanelDragger.cs b/src/UI/Panels/PanelDragger.cs index be1ed0b..83ab1d0 100644 --- a/src/UI/Panels/PanelDragger.cs +++ b/src/UI/Panels/PanelDragger.cs @@ -77,7 +77,8 @@ namespace UnityExplorer.UI.Panels // Instance - public bool AllowDragAndResize { get; set; } + public UIPanel UIPanel { get; private set; } + public bool AllowDragAndResize => UIPanel.CanDragAndResize; public RectTransform Panel { get; set; } public event Action OnFinishResize; @@ -95,7 +96,7 @@ namespace UnityExplorer.UI.Panels public static GameObject s_resizeCursorObj; - internal readonly Vector2 minResize = new Vector2(200, 50); + //internal readonly Vector2 minResize = new Vector2(200, 50); private bool WasResizing { get; set; } private ResizeTypes m_currentResizeType = ResizeTypes.NONE; @@ -109,8 +110,9 @@ namespace UnityExplorer.UI.Panels private Rect m_totalResizeRect; - public PanelDragger(RectTransform dragArea, RectTransform panelToDrag) + public PanelDragger(RectTransform dragArea, RectTransform panelToDrag, UIPanel panel) { + this.UIPanel = panel; Instances.Add(this); DragableArea = dragArea; Panel = panelToDrag; @@ -417,12 +419,12 @@ namespace UnityExplorer.UI.Panels Panel.anchorMin = new Vector2(anchorMin.x, anchorMin.y); Panel.anchorMax = new Vector2(anchorMax.x, anchorMax.y); - if (Panel.rect.width < minResize.x) + if (Panel.rect.width < UIPanel.MinWidth) { Panel.anchorMin = new Vector2(prevMin.x, Panel.anchorMin.y); Panel.anchorMax = new Vector2(prevMax.x, Panel.anchorMax.y); } - if (Panel.rect.height < minResize.y) + if (Panel.rect.height < UIPanel.MinHeight) { Panel.anchorMin = new Vector2(Panel.anchorMin.x, prevMin.y); Panel.anchorMax = new Vector2(Panel.anchorMax.x, prevMax.y); @@ -459,14 +461,4 @@ namespace UnityExplorer.UI.Panels #endregion } - - // Just to allow Enum to do .HasFlag() in NET 3.5 - public static class Net35FlagsEx - { - public static bool HasFlag(this Enum flags, Enum value) - { - ulong num = Convert.ToUInt64(value); - return (Convert.ToUInt64(flags) & num) == num; - } - } } \ No newline at end of file diff --git a/src/UI/Panels/UIPanel.cs b/src/UI/Panels/UIPanel.cs index 6193323..9265a16 100644 --- a/src/UI/Panels/UIPanel.cs +++ b/src/UI/Panels/UIPanel.cs @@ -75,6 +75,8 @@ namespace UnityExplorer.UI.Panels public abstract UIManager.Panels PanelType { get; } public abstract string Name { get; } + public abstract int MinWidth { get; } + public abstract int MinHeight { get; } public virtual bool ShowByDefault => false; public virtual bool ShouldSaveActiveState => true; @@ -126,6 +128,18 @@ namespace UnityExplorer.UI.Panels base.Destroy(); } + protected internal abstract void DoSetDefaultPosAndAnchors(); + + public void SetTransformDefaults() + { + DoSetDefaultPosAndAnchors(); + + if (mainPanelRect.rect.width < MinWidth) + mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth); + if (mainPanelRect.rect.height < MinHeight) + mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight); + } + public void ConstructUI() { //this.Enabled = true; @@ -146,16 +160,16 @@ namespace UnityExplorer.UI.Panels // create core canvas uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent); mainPanelRect = this.uiRoot.GetComponent(); - UIFactory.SetLayoutGroup(this.uiRoot, true, true, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft); + UIFactory.SetLayoutGroup(this.uiRoot, false, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft); int id = this.uiRoot.transform.GetInstanceID(); transformToPanelDict.Add(id, this); content = panelContent; - UIFactory.SetLayoutGroup(this.content, true, true, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft); + UIFactory.SetLayoutGroup(this.content, false, false, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft); // always apply default pos and anchors (save data may only be partial) - SetDefaultPosAndAnchors(); + SetTransformDefaults(); // Title bar var titleGroup = UIFactory.CreateHorizontalGroup(content, "TitleBar", false, true, true, true, 2, @@ -165,11 +179,14 @@ namespace UnityExplorer.UI.Panels // Title text var titleTxt = UIFactory.CreateLabel(titleGroup, "TitleBar", Name, TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(titleTxt.gameObject, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); + UIFactory.SetLayoutElement(titleTxt.gameObject, minWidth: 250, minHeight: 25, flexibleHeight: 0, flexibleWidth: 0); // close button - var closeBtn = UIFactory.CreateButton(titleGroup, "CloseButton", "—"); + var closeHolder = UIFactory.CreateUIObject("CloseHolder", titleGroup); + UIFactory.SetLayoutElement(closeHolder, minHeight: 25, flexibleHeight: 0, minWidth: 30, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(closeHolder, false, false, true, true, 0, childAlignment: TextAnchor.MiddleRight); + var closeBtn = UIFactory.CreateButton(closeHolder, "CloseButton", "—"); UIFactory.SetLayoutElement(closeBtn.Button.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0); RuntimeProvider.Instance.SetColorBlock(closeBtn.Button, new Color(0.33f, 0.32f, 0.31f)); @@ -184,15 +201,15 @@ namespace UnityExplorer.UI.Panels // Panel dragger - Dragger = new PanelDragger(titleTxt.GetComponent(), mainPanelRect); + Dragger = new PanelDragger(titleGroup.GetComponent(), mainPanelRect, this); Dragger.OnFinishResize += OnFinishResize; Dragger.OnFinishDrag += OnFinishDrag; - Dragger.AllowDragAndResize = this.CanDragAndResize; // content (abstract) ConstructPanelContent(); + UIManager.SetPanelActive(this.PanelType, true); UIManager.SetPanelActive(this.PanelType, false); UIManager.SetPanelActive(this.PanelType, ShowByDefault); @@ -200,15 +217,16 @@ namespace UnityExplorer.UI.Panels // apply panel save data or revert to default try { - LoadSaveData(); - Dragger.OnEndResize(); + ApplySaveData(GetSaveData()); } catch (Exception ex) { ExplorerCore.Log($"Exception loading panel save data: {ex}"); - SetDefaultPosAndAnchors(); + SetTransformDefaults(); } + Dragger.OnEndResize(); + // simple listener for saving enabled state this.OnToggleEnabled += (bool val) => { @@ -221,6 +239,8 @@ namespace UnityExplorer.UI.Panels // SAVE DATA + public abstract void DoSaveToConfigElement(); + public void SaveToConfigManager() { if (UIManager.Initializing) @@ -229,11 +249,7 @@ namespace UnityExplorer.UI.Panels DoSaveToConfigElement(); } - public abstract void DoSaveToConfigElement(); - - public abstract void SetDefaultPosAndAnchors(); - - public abstract void LoadSaveData(); + public abstract string GetSaveData(); public bool ApplyingSaveData { get; set; } @@ -268,7 +284,7 @@ namespace UnityExplorer.UI.Panels catch { ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default."); - SetDefaultPosAndAnchors(); + SetTransformDefaults(); } } } diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index db51a7a..e629c65 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -490,7 +490,7 @@ namespace UnityExplorer.UI Image mainImage = mainObj.AddComponent(); mainImage.type = Image.Type.Sliced; - mainImage.color = new Color(0.12f, 0.12f, 0.12f); + mainImage.color = new Color(0.04f, 0.04f, 0.04f, 0.75f); inputField = mainObj.AddComponent(); Navigation nav = inputField.navigation; diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 4577a11..7466bbe 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -21,6 +21,8 @@ namespace UnityExplorer.UI.Utility private const string destroyedString = "Destroyed"; private const string untitledString = "untitled"; + private const string eventSystemNamespace = "UnityEngine.EventSystem"; + public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true) { if (value == null && fallbackType == null) @@ -49,10 +51,21 @@ namespace UnityExplorer.UI.Utility } if (value is UnityEngine.Object obj) - { - _stringBuilder.Append(string.IsNullOrEmpty(obj.name) ? untitledString : obj.name); + { + var name = obj.name; + if (string.IsNullOrEmpty(name)) + name = untitledString; + else if (name.Length > 50) + name = $"{name.Substring(0, 50)}..."; + + _stringBuilder.Append($"\"{name}\""); AppendRichType(_stringBuilder, richType); } + else if (type.FullName.StartsWith(eventSystemNamespace)) + { + // UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text. + _stringBuilder.Append(richType); + } else { var toString = ToString(value); @@ -84,8 +97,10 @@ namespace UnityExplorer.UI.Utility } else // the ToString contains some actual implementation, use that value. { - if (toString.Length > 200) - _stringBuilder.Append(toString.Substring(0, 200)); + // prune long strings unless they're unity structs + // (Matrix4x4 and Rect can have some longs ones that we want to display fully) + if (toString.Length > 100 && !(type.IsValueType && type.FullName.StartsWith("UnityEngine"))) + _stringBuilder.Append(toString.Substring(0, 100)); else _stringBuilder.Append(toString); @@ -154,6 +169,15 @@ namespace UnityExplorer.UI.Utility string _ = null; toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref _); +#if CPP + if (value is Il2CppSystem.Type cppType) + { + var monoType = Core.Runtime.Il2Cpp.Il2CppReflection.GetMonoType(cppType); + if (monoType != null) + toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(monoType, toString, ref _); + } +#endif + return toString; } } diff --git a/src/UI/Widgets/AutoComplete/AutoCompleter.cs b/src/UI/Widgets/AutoComplete/AutoCompleter.cs index 3badf59..4faaa73 100644 --- a/src/UI/Widgets/AutoComplete/AutoCompleter.cs +++ b/src/UI/Widgets/AutoComplete/AutoCompleter.cs @@ -22,6 +22,8 @@ namespace UnityExplorer.UI.Widgets.AutoComplete public override string Name => "AutoCompleter"; public override UIManager.Panels PanelType => UIManager.Panels.AutoCompleter; + public override int MinWidth => -1; + public override int MinHeight => -1; public override bool CanDragAndResize => false; public override bool ShouldSaveActiveState => false; @@ -164,7 +166,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete this.Dragger.OnEndResize(); } - public override void SetDefaultPosAndAnchors() + protected internal override void DoSetDefaultPosAndAnchors() { var mainRect = uiRoot.GetComponent(); mainRect.pivot = new Vector2(0f, 1f); @@ -190,9 +192,6 @@ namespace UnityExplorer.UI.Widgets.AutoComplete // not savable } - public override void LoadSaveData() - { - // not savable - } + public override string GetSaveData() => null; } } diff --git a/src/UI/Widgets/AutoComplete/Suggestion.cs b/src/UI/Widgets/AutoComplete/Suggestion.cs index 6731868..55e7190 100644 --- a/src/UI/Widgets/AutoComplete/Suggestion.cs +++ b/src/UI/Widgets/AutoComplete/Suggestion.cs @@ -11,16 +11,17 @@ namespace UnityExplorer.UI.Widgets.AutoComplete { public readonly string DisplayText; public readonly string UnderlyingValue; - public readonly string Prefix; - public readonly string Addition; + //public int InsertIndex; + //public readonly string Prefix; + //public readonly string Addition; - public string Full => Prefix + Addition; + //public string Full => Prefix + Addition; - public Suggestion(string displayText, string prefix, string addition, string underlyingValue) + public Suggestion(string displayText, /* string prefix, string addition, */ string underlyingValue) { DisplayText = displayText; - Addition = addition; - Prefix = prefix; + //Addition = addition; + //Prefix = prefix; UnderlyingValue = underlyingValue; } } diff --git a/src/UI/Widgets/AutoComplete/TypeCompleter.cs b/src/UI/Widgets/AutoComplete/TypeCompleter.cs index 349940a..40d2aab 100644 --- a/src/UI/Widgets/AutoComplete/TypeCompleter.cs +++ b/src/UI/Widgets/AutoComplete/TypeCompleter.cs @@ -1,47 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.Core.Runtime; using UnityExplorer.UI.Models; +using UnityExplorer.UI.Panels; namespace UnityExplorer.UI.Widgets.AutoComplete { public class TypeCompleter : ISuggestionProvider { - private class CachedType + public class CachedType { - public string FullNameForFilter; + public Type Type; public string FullNameValue; public string DisplayName; } - public Type BaseType { get; } + public event Action SuggestionClicked; + + public Type BaseType { get; set; } + public Type[] GenericConstraints { get; set; } + public InputField InputField { get; } public bool AnchorToCaretPosition => false; - public event Action SuggestionClicked; - public void OnSuggestionClicked(Suggestion suggestion) - { - SuggestionClicked?.Invoke(suggestion); - suggestions.Clear(); - AutoCompleter.Instance.SetSuggestions(suggestions); - - timeOfLastCheck = Time.realtimeSinceStartup; - InputField.text = suggestion.UnderlyingValue; - } - private readonly List suggestions = new List(); + private float timeOfLastCheck; - private readonly Dictionary typeCache = new Dictionary(); + public Dictionary AllTypes = new Dictionary(); - //// cached list of names for displaying (with proper case) - //private readonly List cachedTypesNames = new List(); - //// cached list of lookup by index (lowercase) - //private readonly List cachedTypesFilter = new List(); - //// cached hashset of names (lower case) - //private readonly HashSet cachedTypesSet = new HashSet(); + // cached type trees from all autocompleters + private static readonly Dictionary> typeCache = new Dictionary>(); public TypeCompleter(Type baseType, InputField inputField) { @@ -50,37 +43,20 @@ namespace UnityExplorer.UI.Widgets.AutoComplete inputField.onValueChanged.AddListener(OnInputFieldChanged); - var types = ReflectionUtility.GetImplementationsOf(this.BaseType, true, false); - - var list = new List(); - - foreach (var type in types) - { - string displayName = Utility.SignatureHighlighter.ParseFullSyntax(type, true); - string fullName = RuntimeProvider.Instance.Reflection.GetDeobfuscatedType(type).FullName; - - string filteredName = fullName; - - list.Add(new CachedType - { - FullNameValue = fullName, - FullNameForFilter = filteredName, - DisplayName = displayName, - }); - } - - list.Sort((CachedType a, CachedType b) => a.FullNameForFilter.CompareTo(b.FullNameForFilter)); - - foreach (var cache in list) - { - if (typeCache.ContainsKey(cache.FullNameForFilter)) - continue; - typeCache.Add(cache.FullNameForFilter, cache); - } - + if (BaseType != null) + CacheTypes(); } - private float timeOfLastCheck; + public void OnSuggestionClicked(Suggestion suggestion) + { + timeOfLastCheck = Time.realtimeSinceStartup; + + InputField.text = suggestion.UnderlyingValue; + SuggestionClicked?.Invoke(suggestion); + + suggestions.Clear(); + AutoCompleter.Instance.SetSuggestions(suggestions); + } private void OnInputFieldChanged(string value) { @@ -110,29 +86,59 @@ namespace UnityExplorer.UI.Widgets.AutoComplete var added = new HashSet(); - if (typeCache.TryGetValue(value, out CachedType cache)) - AddToDict(cache); + // Check for exact match first + if (AllTypes.TryGetValue(value, out CachedType cache)) + AddSuggestion(cache); - foreach (var entry in typeCache.Values) + foreach (var entry in AllTypes.Values) + AddSuggestion(entry); + + void AddSuggestion(CachedType entry) { + if (entry.FullNameValue == null) + entry.FullNameValue = ReflectionProvider.Instance.GetDeobfuscatedType(entry.Type).FullName; + if (added.Contains(entry.FullNameValue)) - continue; - - if (entry.FullNameForFilter.ContainsIgnoreCase(value)) - AddToDict(entry); - + return; added.Add(entry.FullNameValue); - } - void AddToDict(CachedType entry) + if (entry.DisplayName == null) + entry.DisplayName = Utility.SignatureHighlighter.ParseFullSyntax(entry.Type, true); + + suggestions.Add(new Suggestion(entry.DisplayName, entry.FullNameValue)); + } + } + + public void CacheTypes() + { + var key = BaseType.AssemblyQualifiedName; + + if (typeCache.ContainsKey(key)) { - added.Add(entry.FullNameValue); - - suggestions.Add(new Suggestion(entry.DisplayName, - value, - entry.FullNameForFilter.Substring(value.Length, entry.FullNameForFilter.Length - value.Length), - entry.FullNameValue)); + AllTypes = typeCache[key]; + return; } + + AllTypes = new Dictionary(); + + var list = ReflectionUtility.GetImplementationsOf(BaseType, true, false) + .Select(it => new CachedType() + { + Type = it, + FullNameValue = ReflectionProvider.Instance.GetDeobfuscatedType(it).FullName + }) + .ToList(); + + list.Sort((CachedType a, CachedType b) => a.FullNameValue.CompareTo(b.FullNameValue)); + + foreach (var cache in list) + { + if (AllTypes.ContainsKey(cache.FullNameValue)) + continue; + AllTypes.Add(cache.FullNameValue, cache); + } + + typeCache.Add(key, AllTypes); } } } diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index 65af6ef..ed69dc7 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -111,15 +111,12 @@ namespace UnityExplorer.UI.Widgets if (!writingLocked) { - if (prevContentHeight <= 1f && Content.rect.height > 1f) + bool viewChange = CheckRecycleViewBounds(true); + + if (viewChange || Content.rect.height != prevContentHeight) { prevContentHeight = Content.rect.height; - } - else if (Content.rect.height != prevContentHeight) - { - prevContentHeight = Content.rect.height; - if (!writingLocked) - OnValueChangedListener(Vector2.zero); + OnValueChangedListener(Vector2.zero); OnHeightChanged?.Invoke(); } @@ -176,7 +173,7 @@ namespace UnityExplorer.UI.Widgets // set intial bounds prevAnchoredPos = Content.anchoredPosition; - SetRecycleViewBounds(false); + CheckRecycleViewBounds(false); // create initial cell pool and set cells CreateCellPool(); @@ -186,7 +183,7 @@ namespace UnityExplorer.UI.Widgets SetCell(CellPool[enumerator.Current.cellIndex], enumerator.Current.dataIndex); LayoutRebuilder.ForceRebuildLayoutImmediate(Content); - + prevContentHeight = Content.rect.height; // update slider SetScrollBounds(); UpdateSliderHandle(); @@ -203,14 +200,19 @@ namespace UnityExplorer.UI.Widgets NormalizedScrollBounds = new Vector2(Viewport.rect.height * 0.5f, TotalDataHeight - (Viewport.rect.height * 0.5f)); } - private void SetRecycleViewBounds(bool extendPoolIfGrown) + /// + /// return value = viewport changed height + /// + private bool CheckRecycleViewBounds(bool extendPoolIfGrown) { RecycleViewBounds = new Vector2(Viewport.MinY() + HalfThreshold, Viewport.MaxY() - HalfThreshold); if (extendPoolIfGrown && prevViewportHeight < Viewport.rect.height && prevViewportHeight != 0.0f) CheckExtendCellPool(); + bool ret = prevViewportHeight == Viewport.rect.height; prevViewportHeight = Viewport.rect.height; + return ret; } // Cell pool @@ -361,7 +363,7 @@ namespace UnityExplorer.UI.Widgets { if (!CellPool.Any()) return; - SetRecycleViewBounds(true); + CheckRecycleViewBounds(true); CheckDataSourceCountChange(out bool jumpToBottom); @@ -432,7 +434,7 @@ namespace UnityExplorer.UI.Widgets RefreshCellHeightsFast(); - SetRecycleViewBounds(true); + CheckRecycleViewBounds(true); float yChange = ((Vector2)ScrollRect.content.localPosition - prevAnchoredPos).y; float adjust = 0f; @@ -544,7 +546,7 @@ namespace UnityExplorer.UI.Widgets // Prevent spam invokes unless value is 0 or 1 (so we dont skip over the start/end) if (DataSource == null || (WritingLocked && val != 0 && val != 1)) return; - this.WritingLocked = true; + //this.WritingLocked = true; ScrollRect.StopMovement(); RefreshCellHeightsFast(); @@ -626,7 +628,7 @@ namespace UnityExplorer.UI.Widgets } } - SetRecycleViewBounds(true); + CheckRecycleViewBounds(true); SetScrollBounds(); ScrollRect.UpdatePrevData(); diff --git a/src/UI/Widgets/TransformTree/TransformTree.cs b/src/UI/Widgets/TransformTree/TransformTree.cs index 5d11f1e..d9c9730 100644 --- a/src/UI/Widgets/TransformTree/TransformTree.cs +++ b/src/UI/Widgets/TransformTree/TransformTree.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Text; using UnityEngine; @@ -14,6 +15,21 @@ namespace UnityExplorer.UI.Widgets { public Func> GetRootEntriesMethod; + internal ScrollPool ScrollPool; + + // Using an OrderedDictionary because we need constant-time lookup of both key and index. + /// + /// Key: UnityEngine.Transform instance ID
+ /// Value: CachedTransform + ///
+ private readonly OrderedDictionary displayedObjects = new OrderedDictionary(); + + // for keeping track of which actual transforms are expanded or not, outside of the cache data. + private readonly HashSet expandedInstanceIDs = new HashSet(); + private readonly HashSet autoExpandedIDs = new HashSet(); + + public int ItemCount => displayedObjects.Count; + public bool Filtering => !string.IsNullOrEmpty(currentFilter); private bool wasFiltering; @@ -34,17 +50,6 @@ namespace UnityExplorer.UI.Widgets } private string currentFilter; - internal ScrollPool ScrollPool; - - internal readonly List displayedObjects = new List(); - - private readonly Dictionary objectCache = new Dictionary(); - - private readonly HashSet expandedInstanceIDs = new HashSet(); - private readonly HashSet autoExpandedIDs = new HashSet(); - - public int ItemCount => displayedObjects.Count; - public TransformTree(ScrollPool scrollPool) { ScrollPool = scrollPool; @@ -72,18 +77,38 @@ namespace UnityExplorer.UI.Widgets RefreshData(true, true); } + private readonly HashSet visited = new HashSet(); + private bool needRefresh; + private int displayIndex; + public void RefreshData(bool andReload = false, bool jumpToTop = false) { - displayedObjects.Clear(); + visited.Clear(); + displayIndex = 0; + needRefresh = false; var rootObjects = GetRootEntriesMethod.Invoke(); + //int displayIndex = 0; foreach (var obj in rootObjects) + if (obj) Traverse(obj.transform); + + // Prune displayed transforms that we didnt visit in that traverse + for (int i = displayedObjects.Count - 1; i >= 0; i--) { - if (obj) - Traverse(obj.transform); + var obj = (CachedTransform)displayedObjects[i]; + if (!visited.Contains(obj.InstanceID)) + { + displayedObjects.Remove(obj.InstanceID); + needRefresh = true; + } } + if (!needRefresh) + return; + + //displayedObjects.Clear(); + if (andReload) { if (!jumpToTop) @@ -97,32 +122,36 @@ namespace UnityExplorer.UI.Widgets { int instanceID = transform.GetInstanceID(); + if (visited.Contains(instanceID)) + return; + visited.Add(instanceID); + if (Filtering) { - //auto - expand to show results: works, but then we need to collapse after the search ends. - - if (FilterHierarchy(transform)) - { - if (!autoExpandedIDs.Contains(instanceID)) - autoExpandedIDs.Add(instanceID); - } - else + if (!FilterHierarchy(transform)) return; + + if (!autoExpandedIDs.Contains(instanceID)) + autoExpandedIDs.Add(instanceID); } CachedTransform cached; - if (objectCache.ContainsKey(instanceID)) + if (displayedObjects.Contains(instanceID)) { - cached = objectCache[instanceID]; + cached = (CachedTransform)displayedObjects[(object)instanceID]; cached.Update(transform, depth); } else { + needRefresh = true; cached = new CachedTransform(this, transform, depth, parent); - objectCache.Add(instanceID, cached); + if (displayedObjects.Count <= displayIndex) + displayedObjects.Add(instanceID, cached); + else + displayedObjects.Insert(displayIndex, instanceID, cached); } - displayedObjects.Add(cached); + displayIndex++; if (IsCellExpanded(instanceID) && cached.Value.childCount > 0) { @@ -149,7 +178,7 @@ namespace UnityExplorer.UI.Widgets public void SetCell(TransformCell cell, int index) { if (index < displayedObjects.Count) - cell.ConfigureCell(displayedObjects[index], index); + cell.ConfigureCell((CachedTransform)displayedObjects[index], index); else cell.Disable(); } diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index b47ef84..9958415 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -235,26 +235,26 @@ - - - - - - - - - - - - + + + + + + + + + + + + - + - - - + + +