More progress

This commit is contained in:
Sinai 2021-05-05 21:27:09 +10:00
parent 961ff80c6d
commit e4ff86259b
42 changed files with 1159 additions and 730 deletions

View File

@ -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<string, Type> allCachedTypes = new Dictionary<string, Type>();
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<T>(this T objA, T objB)
@ -119,28 +156,8 @@ namespace UnityExplorer
/// <returns>The Type if found, otherwise null.</returns>
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<Type, HashSet<Type>> s_cachedTypeInheritance = new Dictionary<Type, HashSet<Type>>();
internal static int s_lastAssemblyCount;
internal static readonly Dictionary<string, HashSet<Type>> s_cachedTypeInheritance = new Dictionary<string, HashSet<Type>>();
internal static readonly Dictionary<string, HashSet<Type>> s_cachedGenericParameterInheritance = new Dictionary<string, HashSet<Type>>();
/// <summary>
/// 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.
/// </summary>
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
/// <returns>All implementations of the type in the current AppDomain.</returns>
public static HashSet<Type> 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<Type>();
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];
}
/// <summary>

View File

@ -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<string, string> s_deobfuscatedTypeNames = new Dictionary<string, string>();
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 { }
}
/// <summary>
/// Try to get the Mono (Unhollowed) Type representation of the provided <see cref="Il2CppSystem.Type"/>.
/// </summary>
@ -213,9 +193,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
/// <returns>The Mono Type if found, otherwise null.</returns>
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<object>();
var hashtable = value.TryCast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable;
if (hashtable != null)
{
EnumerateCppHashtable(hashtable, keyList, valueList);

View File

@ -9,8 +9,8 @@ namespace UnityExplorer.Core.Search
{
UnityObject,
GameObject,
Component,
Custom,
//Component,
//Custom,
Singleton,
StaticClass
}

View File

@ -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<object> UnityObjectSearch(string input, string customTypeInput, SearchContext context,
ChildFilter childFilter, SceneFilter sceneFilter)
{
var results = new List<object>();
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<GameObject>()
: obj.TryCast<Component>().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<object> StaticClassSearch(string input)
{
var list = new List<object>();
@ -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<object> UnityObjectSearch(string input, string customTypeInput, SearchContext context,
ChildFilter childFilter, SceneFilter sceneFilter)
{
var results = new List<object>();
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<GameObject>()
: obj.TryCast<Component>().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;
}
}
}

View File

@ -76,6 +76,31 @@ namespace UnityExplorer.Tests
}
}
private static void TestGeneric<T>()
{
ExplorerCore.Log("Test1 " + typeof(T).FullName);
}
private static void TestGenericClass<T>() where T : class
{
ExplorerCore.Log("Test2 " + typeof(T).FullName);
}
//private static void TestGenericMultiInterface<T>() where T : IEnumerable, IList, ICollection
//{
// ExplorerCore.Log("Test3 " + typeof(T).FullName);
//}
private static void TestComponent<T>() where T : Component
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
private static void TestStruct<T>() where T : struct
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
private static object GetRandomObject()
{
object ret = null;

View File

@ -10,9 +10,21 @@ namespace UnityExplorer
{
private static CultureInfo _enCulture = new CultureInfo("en-US");
/// <summary>
/// Check if a string contains another string, case-insensitive.
/// </summary>
public static bool ContainsIgnoreCase(this string _this, string s)
{
return _enCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
}
/// <summary>
/// Just to allow Enum to do .HasFlag() in NET 3.5
/// </summary>
public static bool HasFlag(this Enum flags, Enum value)
{
ulong num = Convert.ToUInt64(value);
return (Convert.ToUInt64(flags) & num) == num;
}
}
}

View File

@ -81,11 +81,17 @@ namespace UnityExplorer
}
}
// long name object
new GameObject(new string('#', 500));
// END
InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent<GraphicRaycaster>());
//InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent<GraphicRaycaster>());
InspectorManager.Inspect(typeof(TestClass));
//InspectorManager.InspectType(typeof(ReflectionUtility));
//var tex = Resources.FindObjectsOfTypeAll<Texture2D>()[0];
//InspectorManager.Inspect(tex);
}
/// <summary>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<EvaluateWidget>.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);
}
/// <summary>

View File

@ -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;

View File

@ -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 = "<color=grey>Not yet evaluated</color>";
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
/// <summary>Return true if SetCell should abort, false if it should continue.</summary>
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
/// <summary>Called from SetCellState if SubContent button is wanted.</summary>
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

View File

@ -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());

View File

@ -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
{

View File

@ -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<HorizontalLayoutGroup>(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<LayoutElement>();
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", "<i>empty</i>", 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", "<i>null</i>", 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;

View File

@ -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;

View File

@ -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;

View File

@ -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<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, false, false, true, true, 0, 0);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, false, false, true, true, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;

View File

@ -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($"<color={SignatureHighlighter.CONST}>{arg.Name}</color>");
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<GameObject> objectList, List<Text> labelList, string[] inputArray)
private void AddArgRow(int index, GameObject parent, List<GameObject> objectList, List<Text> 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<HorizontalLayoutGroup>(horiGroup, false, false, true, true, 5);
horiGroup.AddComponent<ContentSizeFitter>().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<ContentSizeFitter>().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<ContentSizeFitter>().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<VerticalLayoutGroup>(genericArgHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(genericArgHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//genericArgHolder.AddComponent<ContentSizeFitter>().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<VerticalLayoutGroup>(argHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(argHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//argHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// evaluate button
var evalButton = UIFactory.CreateButton(UIRoot, "EvaluateButton", "Evaluate", new Color(0.2f, 0.2f, 0.2f));

View File

@ -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<CacheKeyValuePairCell>, 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);
}

View File

@ -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<CacheListEntryCell>, 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;

View File

@ -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
{

View File

@ -37,6 +37,8 @@ namespace UnityExplorer.UI.Inspectors
{
Pool<InspectorTab>.Return(Tab);
this.Target = null;
Tab.TabButton.OnClick -= OnTabButtonClicked;
Tab.CloseButton.OnClick -= OnCloseClicked;
}

View File

@ -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;

View File

@ -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;

View File

@ -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<CacheMemberCell> 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<CacheMember> members = new List<CacheMember>();
private readonly List<CacheMember> filteredMembers = new List<CacheMember>();
// private readonly HashSet<CacheMember> displayedMembers = new HashSet<CacheMember>();
// UI
public ScrollPool<CacheMemberCell> 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<BindingFlags, ButtonRef> scopeFilterButtons = new Dictionary<BindingFlags, ButtonRef>();
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} <color=grey><i>(in memory)</i></color>";
AssemblyText.text = $"<color=grey>Assembly:</color> {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<HorizontalLayoutGroup>(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<LayoutElement>();
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<CacheMemberCell>(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<CacheMemberCell>(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<HorizontalLayoutGroup>(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<HorizontalLayoutGroup>(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<HorizontalLayoutGroup>(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<Image>();
textureImageLayout = textureImage.gameObject.AddComponent<LayoutElement>();
var fitter = imageObj.AddComponent<ContentSizeFitter>();
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
}
}

View File

@ -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<object> dataHandler;
private ScrollPool<ButtonCell> resultsScrollPool;
private List<object> currentResults = new List<object>();
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<object> 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<int, string> cachedCellTexts = new Dictionary<int, string>();
@ -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

View File

@ -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

View File

@ -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);

View File

@ -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<Mask>().enabled = false;
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.content, forceHeight: true, spacing: 4, padLeft: 5, padRight: 5);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(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<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;

View File

@ -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()

View File

@ -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<RectTransform> 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;
}
}
}

View File

@ -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<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.uiRoot, true, true, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(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<VerticalLayoutGroup>(this.content, true, true, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(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<HorizontalLayoutGroup>(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<RectTransform>(), mainPanelRect);
Dragger = new PanelDragger(titleGroup.GetComponent<RectTransform>(), 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();
}
}
}

View File

@ -490,7 +490,7 @@ namespace UnityExplorer.UI
Image mainImage = mainObj.AddComponent<Image>();
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<InputField>();
Navigation nav = inputField.navigation;

View File

@ -21,6 +21,8 @@ namespace UnityExplorer.UI.Utility
private const string destroyedString = "<color=red>Destroyed</color>";
private const string untitledString = "<i><color=grey>untitled</color></i>";
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;
}
}

View File

@ -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<RectTransform>();
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;
}
}

View File

@ -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;
}
}

View File

@ -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<Suggestion> SuggestionClicked;
public Type BaseType { get; set; }
public Type[] GenericConstraints { get; set; }
public InputField InputField { get; }
public bool AnchorToCaretPosition => false;
public event Action<Suggestion> 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<Suggestion> suggestions = new List<Suggestion>();
private float timeOfLastCheck;
private readonly Dictionary<string, CachedType> typeCache = new Dictionary<string, CachedType>();
public Dictionary<string, CachedType> AllTypes = new Dictionary<string, CachedType>();
//// cached list of names for displaying (with proper case)
//private readonly List<string> cachedTypesNames = new List<string>();
//// cached list of lookup by index (lowercase)
//private readonly List<string> cachedTypesFilter = new List<string>();
//// cached hashset of names (lower case)
//private readonly HashSet<string> cachedTypesSet = new HashSet<string>();
// cached type trees from all autocompleters
private static readonly Dictionary<string, Dictionary<string, CachedType>> typeCache = new Dictionary<string, Dictionary<string, CachedType>>();
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<CachedType>();
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<string>();
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<string, CachedType>();
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);
}
}
}

View File

@ -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)
/// <summary>
/// return value = viewport changed height
/// </summary>
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();

View File

@ -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<IEnumerable<GameObject>> GetRootEntriesMethod;
internal ScrollPool<TransformCell> ScrollPool;
// Using an OrderedDictionary because we need constant-time lookup of both key and index.
/// <summary>
/// Key: UnityEngine.Transform instance ID<br/>
/// Value: CachedTransform
/// </summary>
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<int> expandedInstanceIDs = new HashSet<int>();
private readonly HashSet<int> autoExpandedIDs = new HashSet<int>();
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<TransformCell> ScrollPool;
internal readonly List<CachedTransform> displayedObjects = new List<CachedTransform>();
private readonly Dictionary<int, CachedTransform> objectCache = new Dictionary<int, CachedTransform>();
private readonly HashSet<int> expandedInstanceIDs = new HashSet<int>();
private readonly HashSet<int> autoExpandedIDs = new HashSet<int>();
public int ItemCount => displayedObjects.Count;
public TransformTree(ScrollPool<TransformCell> scrollPool)
{
ScrollPool = scrollPool;
@ -72,18 +77,38 @@ namespace UnityExplorer.UI.Widgets
RefreshData(true, true);
}
private readonly HashSet<int> visited = new HashSet<int>();
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();
}

View File

@ -235,26 +235,26 @@
<Compile Include="Inspectors_OLD\Reflection\ReflectionInspector.cs" />
<Compile Include="Inspectors_OLD\Reflection\StaticInspector.cs" />
<Compile Include="UI\CSConsole\CSConsoleManager.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheField.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheKeyValuePair.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheListEntry.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheMember.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheMethod.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheObjectBase.cs" />
<Compile Include="UI\Inspectors\CacheObject\CacheProperty.cs" />
<Compile Include="UI\Inspectors\CacheObject\Views\CacheKeyValuePairCell.cs" />
<Compile Include="UI\Inspectors\CacheObject\Views\CacheListEntryCell.cs" />
<Compile Include="UI\Inspectors\CacheObject\Views\CacheMemberCell.cs" />
<Compile Include="UI\Inspectors\CacheObject\Views\CacheObjectCell.cs" />
<Compile Include="UI\Inspectors\CacheObject\Views\EvaluateWidget.cs" />
<Compile Include="UI\CacheObject\CacheField.cs" />
<Compile Include="UI\CacheObject\CacheKeyValuePair.cs" />
<Compile Include="UI\CacheObject\CacheListEntry.cs" />
<Compile Include="UI\CacheObject\CacheMember.cs" />
<Compile Include="UI\CacheObject\CacheMethod.cs" />
<Compile Include="UI\CacheObject\CacheObjectBase.cs" />
<Compile Include="UI\CacheObject\CacheProperty.cs" />
<Compile Include="UI\CacheObject\Views\CacheKeyValuePairCell.cs" />
<Compile Include="UI\CacheObject\Views\CacheListEntryCell.cs" />
<Compile Include="UI\CacheObject\Views\CacheMemberCell.cs" />
<Compile Include="UI\CacheObject\Views\CacheObjectCell.cs" />
<Compile Include="UI\CacheObject\Views\EvaluateWidget.cs" />
<Compile Include="UI\Inspectors\GameObjectInspector.cs" />
<Compile Include="UI\Inspectors\ICacheObjectController.cs" />
<Compile Include="UI\CacheObject\ICacheObjectController.cs" />
<Compile Include="UI\Inspectors\InspectorManager.cs" />
<Compile Include="UI\Inspectors\InspectorTab.cs" />
<Compile Include="UI\Inspectors\InspectorBase.cs" />
<Compile Include="UI\Inspectors\IValues\InteractiveDictionary.cs" />
<Compile Include="UI\Inspectors\IValues\InteractiveList.cs" />
<Compile Include="UI\Inspectors\IValues\InteractiveValue.cs" />
<Compile Include="UI\IValues\InteractiveDictionary.cs" />
<Compile Include="UI\IValues\InteractiveList.cs" />
<Compile Include="UI\IValues\InteractiveValue.cs" />
<Compile Include="UI\Inspectors\ListInspector.cs" />
<Compile Include="UI\Inspectors\ReflectionInspector.cs" />
<Compile Include="UI\ObjectPool\IPooledObject.cs" />