From a2ff37e36d388371f2f8c92814793ba49556d204 Mon Sep 17 00:00:00 2001 From: Sinai Date: Tue, 27 Apr 2021 21:22:48 +1000 Subject: [PATCH] Some progress on inspector rewrites, most of the framework figured out now. --- .../Reflection/ReflectionInspector.cs | 40 +-- src/UI/Inspectors/CacheObject/CacheField.cs | 33 ++ src/UI/Inspectors/CacheObject/CacheMember.cs | 315 ++++++++++++++++++ src/UI/Inspectors/CacheObject/CacheMethod.cs | 33 ++ .../Inspectors/CacheObject/CacheObjectBase.cs | 12 + .../Inspectors/CacheObject/CacheProperty.cs | 33 ++ .../CacheObject/Views/CacheMemberCell.cs | 106 ++++++ .../CacheObject/Views/CacheObjectCell.cs | 11 + src/UI/Inspectors/GameObjectInspector.cs | 248 +++++++------- src/UI/Inspectors/IValues/IValueTest.cs | 28 ++ src/UI/Inspectors/InspectorBase.cs | 5 +- src/UI/Inspectors/InspectorManager.cs | 40 ++- src/UI/Inspectors/ReflectionInspector.cs | 253 +++++++++++++- src/UI/ObjectPool/Pool.cs | 61 ++-- src/UI/Panels/InspectorPanel.cs | 6 +- src/UI/Panels/ObjectExplorer.cs | 2 + src/UI/{Models => Panels}/UIPanel.cs | 7 +- src/UI/UIFactory.cs | 2 +- src/UI/Utility/SignatureHighlighter.cs | 106 +++--- src/UI/Utility/ToStringUtility.cs | 106 +++--- src/UI/Widgets/AutoComplete/AutoCompleter.cs | 2 +- src/UI/Widgets/ButtonList/ButtonCell.cs | 8 +- src/UI/Widgets/ObjectExplorer/ObjectSearch.cs | 6 +- src/UI/Widgets/ScrollPool/ScrollPool.cs | 47 ++- src/UnityExplorer.csproj | 10 +- 25 files changed, 1197 insertions(+), 323 deletions(-) create mode 100644 src/UI/Inspectors/CacheObject/CacheField.cs create mode 100644 src/UI/Inspectors/CacheObject/CacheMember.cs create mode 100644 src/UI/Inspectors/CacheObject/CacheMethod.cs create mode 100644 src/UI/Inspectors/CacheObject/CacheObjectBase.cs create mode 100644 src/UI/Inspectors/CacheObject/CacheProperty.cs create mode 100644 src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs create mode 100644 src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs create mode 100644 src/UI/Inspectors/IValues/IValueTest.cs rename src/UI/{Models => Panels}/UIPanel.cs (98%) diff --git a/src/Inspectors_OLD/Reflection/ReflectionInspector.cs b/src/Inspectors_OLD/Reflection/ReflectionInspector.cs index 5b121c0..9b563f7 100644 --- a/src/Inspectors_OLD/Reflection/ReflectionInspector.cs +++ b/src/Inspectors_OLD/Reflection/ReflectionInspector.cs @@ -35,26 +35,6 @@ // // ActiveInstance.m_widthUpdateWanted = true; // //} -// // Blacklists -// private static readonly HashSet bl_typeAndMember = new HashSet -// { -//#if CPP -// // these cause a crash in IL2CPP -// "Type.DeclaringMethod", -// "Rigidbody2D.Cast", -// "Collider2D.Cast", -// "Collider2D.Raycast", -// "Texture2D.SetPixelDataImpl", -// "Camera.CalculateProjectionMatrixFromPhysicalProperties", -//#endif -// }; -// private static readonly HashSet bl_methodNameStartsWith = new HashSet -// { -// // these are redundant, just adds noise, properties are supported directly -// "get_", -// "set_", -// }; - // #endregion // #region INSTANCE @@ -131,6 +111,26 @@ // } // } +// // Blacklists +// private static readonly HashSet bl_typeAndMember = new HashSet +// { +//#if CPP +// // these cause a crash in IL2CPP +// "Type.DeclaringMethod", +// "Rigidbody2D.Cast", +// "Collider2D.Cast", +// "Collider2D.Raycast", +// "Texture2D.SetPixelDataImpl", +// "Camera.CalculateProjectionMatrixFromPhysicalProperties", +//#endif +// }; +// private static readonly HashSet bl_methodNameStartsWith = new HashSet +// { +// // these are redundant, just adds noise, properties are supported directly +// "get_", +// "set_", +// }; + // internal bool IsBlacklisted(string sig) => bl_typeAndMember.Any(it => sig.Contains(it)); // internal bool IsBlacklisted(MethodInfo method) => bl_methodNameStartsWith.Any(it => method.Name.StartsWith(it)); diff --git a/src/UI/Inspectors/CacheObject/CacheField.cs b/src/UI/Inspectors/CacheObject/CacheField.cs new file mode 100644 index 0000000..a2ec1f5 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheField.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public class CacheField : CacheMember + { + public FieldInfo FieldInfo { get; internal set; } + + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + { + base.Initialize(inspector, declaringType, member, returnType); + + CanWrite = true; + } + + protected override void TryEvaluate() + { + try + { + Value = FieldInfo.GetValue(this.ParentInspector.Target.TryCast(this.DeclaringType)); + } + catch (Exception ex) + { + HadException = true; + LastException = ex; + } + } + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheMember.cs b/src/UI/Inspectors/CacheObject/CacheMember.cs new file mode 100644 index 0000000..0f5c740 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheMember.cs @@ -0,0 +1,315 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.Utility; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public abstract class CacheMember : CacheObjectBase + { + public ReflectionInspector ParentInspector { get; internal set; } + + public Type DeclaringType { get; private set; } + public string NameForFiltering { get; private set; } + + public object Value { get; protected set; } + public Type FallbackType { get; private set; } + + public bool HasEvaluated { get; protected set; } + public bool HasArguments { get; protected set; } + public bool Evaluating { get; protected set; } + public bool CanWrite { get; protected set; } + public bool HadException { get; protected set; } + public Exception LastException { get; protected set; } + + public string MemberLabelText { get; private set; } + public string TypeLabelText { get; protected set; } + public string ValueLabelText { get; protected set; } + + public virtual void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + { + this.DeclaringType = declaringType; + this.ParentInspector = inspector; + this.FallbackType = returnType; + this.MemberLabelText = SignatureHighlighter.ParseFullSyntax(declaringType, false, member); + this.NameForFiltering = $"{declaringType.Name}.{member.Name}"; + this.TypeLabelText = SignatureHighlighter.HighlightTypeName(returnType); + } + + public void SetCell(CacheMemberCell cell) + { + cell.MemberLabel.text = MemberLabelText; + cell.TypeLabel.text = TypeLabelText; + + if (HasArguments && !HasEvaluated) + { + // todo + cell.ValueLabel.text = "Not yet evalulated"; + } + else if (!HasEvaluated) + Evaluate(); + + if (HadException) + { + cell.InspectButton.Button.gameObject.SetActive(false); + cell.ValueLabel.gameObject.SetActive(true); + cell.ValueLabel.supportRichText = true; + cell.ValueLabel.text = $"{ReflectionUtility.ReflectionExToString(LastException)}"; + } + else if (Value.IsNullOrDestroyed()) + { + cell.InspectButton.Button.gameObject.SetActive(false); + cell.ValueLabel.gameObject.SetActive(true); + cell.ValueLabel.supportRichText = true; + cell.ValueLabel.text = ValueLabelText; + } + else + { + cell.ValueLabel.supportRichText = false; + cell.ValueLabel.text = ValueLabelText; + + var valueType = Value.GetActualType(); + if (valueType.IsPrimitive || valueType == typeof(decimal)) + { + cell.InspectButton.Button.gameObject.SetActive(false); + cell.ValueLabel.gameObject.SetActive(true); + } + else if (valueType == typeof(string)) + { + cell.InspectButton.Button.gameObject.SetActive(false); + cell.ValueLabel.gameObject.SetActive(true); + } + else + { + cell.InspectButton.Button.gameObject.SetActive(true); + cell.ValueLabel.gameObject.SetActive(true); + } + } + } + + protected abstract void TryEvaluate(); + + public void Evaluate() + { + TryEvaluate(); + + if (!HadException) + { + ValueLabelText = ToStringUtility.ToString(Value, FallbackType); + } + + HasEvaluated = true; + } + + + #region Cache Member Util + + public static bool CanProcessArgs(ParameterInfo[] parameters) + { + foreach (var param in parameters) + { + var pType = param.ParameterType; + + if (pType.IsByRef && pType.HasElementType) + pType = pType.GetElementType(); + + if (pType != null && (pType.IsPrimitive || pType == typeof(string))) + continue; + else + return false; + } + return true; + } + + public static List GetCacheMembers(object inspectorTarget, Type _type, ReflectionInspector _inspector) + { + var list = new List(); + var cachedSigs = new HashSet(); + + var types = ReflectionUtility.GetAllBaseTypes(_type); + + var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static; + if (!_inspector.StaticOnly) + flags |= BindingFlags.Instance; + + var infos = new List(); + + foreach (var declaringType in types) + { + var target = inspectorTarget; + if (!_inspector.StaticOnly) + target = target.TryCast(declaringType); + + infos.Clear(); + infos.AddRange(declaringType.GetMethods(flags)); + infos.AddRange(declaringType.GetProperties(flags)); + infos.AddRange(declaringType.GetFields(flags)); + + foreach (var member in infos) + { + if (member.DeclaringType != declaringType) + continue; + TryCacheMember(member, list, cachedSigs, declaringType, _inspector); + } + } + + var typeList = types.ToList(); + + var sorted = new List(); + sorted.AddRange(list.Where(it => it is CacheProperty) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + sorted.AddRange(list.Where(it => it is CacheField) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + sorted.AddRange(list.Where(it => it is CacheMethod) + .OrderBy(it => typeList.IndexOf(it.DeclaringType)) + .ThenBy(it => it.NameForFiltering)); + + return sorted; + } + + private static void TryCacheMember(MemberInfo member, List list, HashSet cachedSigs, + Type declaringType, ReflectionInspector _inspector, bool ignoreMethodBlacklist = false) + { + try + { + var sig = GetSig(member); + + if (IsBlacklisted(sig)) + return; + + //ExplorerCore.Log($"Trying to cache member {sig}..."); + //ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name); + + CacheMember cached; + Type returnType; + switch (member.MemberType) + { + case MemberTypes.Method: + { + var mi = member as MethodInfo; + if (!ignoreMethodBlacklist && IsBlacklisted(mi)) + return; + + var args = mi.GetParameters(); + if (!CanProcessArgs(args)) + return; + + sig += AppendArgsToSig(args); + if (cachedSigs.Contains(sig)) + return; + + cached = new CacheMethod() { MethodInfo = mi }; + returnType = mi.ReturnType; + break; + } + + case MemberTypes.Property: + { + var pi = member as PropertyInfo; + + var args = pi.GetIndexParameters(); + if (!CanProcessArgs(args)) + return; + + if (!pi.CanRead && pi.CanWrite) + { + // write-only property, cache the set method instead. + var setMethod = pi.GetSetMethod(true); + if (setMethod != null) + TryCacheMember(setMethod, list, cachedSigs, declaringType, _inspector, true); + return; + } + + sig += AppendArgsToSig(args); + if (cachedSigs.Contains(sig)) + return; + + cached = new CacheProperty() { PropertyInfo = pi }; + returnType = pi.PropertyType; + break; + } + + case MemberTypes.Field: + { + var fi = member as FieldInfo; + cached = new CacheField() { FieldInfo = fi }; + returnType = fi.FieldType; + break; + } + + default: return; + } + + cachedSigs.Add(sig); + + cached.Initialize(_inspector, declaringType, member, returnType); + + list.Add(cached); + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!"); + ExplorerCore.Log(e.ToString()); + } + } + + internal static string GetSig(MemberInfo member) => $"{member.DeclaringType.Name}.{member.Name}"; + + internal static string AppendArgsToSig(ParameterInfo[] args) + { + string ret = " ("; + foreach (var param in args) + ret += $"{param.ParameterType.Name} {param.Name}, "; + ret += ")"; + return ret; + } + + // Blacklists + private static readonly HashSet bl_typeAndMember = new HashSet + { + // these cause a crash in IL2CPP +#if CPP + "Type.DeclaringMethod", + "Rigidbody2D.Cast", + "Collider2D.Cast", + "Collider2D.Raycast", + "Texture2D.SetPixelDataImpl", + "Camera.CalculateProjectionMatrixFromPhysicalProperties", +#endif + // These were deprecated a long time ago, still show up in some games for some reason + "MonoBehaviour.allowPrefabModeInPlayMode", + "MonoBehaviour.runInEditMode", + "Component.animation", + "Component.audio", + "Component.camera", + "Component.collider", + "Component.collider2D", + "Component.constantForce", + "Component.hingeJoint", + "Component.light", + "Component.networkView", + "Component.particleSystem", + "Component.renderer", + "Component.rigidbody", + "Component.rigidbody2D", + }; + private static readonly HashSet bl_methodNameStartsWith = new HashSet + { + // these are redundant + "get_", + "set_", + }; + + internal static bool IsBlacklisted(string sig) => bl_typeAndMember.Any(it => sig.Contains(it)); + internal static bool IsBlacklisted(MethodInfo method) => bl_methodNameStartsWith.Any(it => method.Name.StartsWith(it)); + + #endregion + + + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheMethod.cs b/src/UI/Inspectors/CacheObject/CacheMethod.cs new file mode 100644 index 0000000..28713ff --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheMethod.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public class CacheMethod : CacheMember + { + public MethodInfo MethodInfo { get; internal set; } + + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + { + base.Initialize(inspector, declaringType, member, returnType); + + + } + + protected override void TryEvaluate() + { + try + { + throw new NotImplementedException("TODO"); + } + catch (Exception ex) + { + HadException = true; + LastException = ex; + } + } + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs new file mode 100644 index 0000000..1409e81 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public abstract class CacheObjectBase + { + + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheProperty.cs b/src/UI/Inspectors/CacheObject/CacheProperty.cs new file mode 100644 index 0000000..f1a0f08 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheProperty.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public class CacheProperty : CacheMember + { + public PropertyInfo PropertyInfo { get; internal set; } + + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + { + base.Initialize(inspector, declaringType, member, returnType); + + + } + + protected override void TryEvaluate() + { + try + { + Value = PropertyInfo.GetValue(ParentInspector.Target.TryCast(DeclaringType), null); + } + catch (Exception ex) + { + HadException = true; + LastException = ex; + } + } + } +} diff --git a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs new file mode 100644 index 0000000..9e9aa3d --- /dev/null +++ b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors.CacheObject.Views +{ + public class CacheMemberCell : ICell + { + #region ICell + + public float DefaultHeight => 30f; + + public GameObject UIRoot => uiRoot; + public GameObject uiRoot; + + public bool Enabled => m_enabled; + private bool m_enabled; + + public RectTransform Rect => m_rect; + private RectTransform m_rect; + + public void Disable() + { + m_enabled = false; + uiRoot.SetActive(false); + } + + public void Enable() + { + m_enabled = true; + uiRoot.SetActive(true); + } + + #endregion + + public ReflectionInspector CurrentOwner { get; set; } + public int CurrentDataIndex { get; set; } + + public LayoutElement MemberLayout; + public LayoutElement ReturnTypeLayout; + public LayoutElement RightGroupLayout; + + public Text MemberLabel; + public Text TypeLabel; + + public GameObject RightGroupHolder; + public ButtonRef InspectButton; + public Text ValueLabel; + + public GameObject SubContentHolder; + + public GameObject CreateContent(GameObject parent) + { + uiRoot = UIFactory.CreateUIObject("CacheMemberCell", parent, new Vector2(100, 30)); + m_rect = uiRoot.GetComponent(); + UIFactory.SetLayoutGroup(uiRoot, true, true, true, true, 2, 0); + UIFactory.SetLayoutElement(uiRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); + UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + var separator = UIFactory.CreateUIObject("TopSeperator", uiRoot); + UIFactory.SetLayoutElement(separator, minHeight: 1, flexibleHeight: 0, flexibleWidth: 9999); + separator.AddComponent().color = Color.black; + + var horiRow = UIFactory.CreateUIObject("HoriGroup", uiRoot); + UIFactory.SetLayoutElement(horiRow, minHeight: 29, flexibleHeight: 150, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(horiRow, false, true, true, true, 5, 2, childAlignment: TextAnchor.UpperLeft); + horiRow.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + MemberLabel = UIFactory.CreateLabel(horiRow, "MemberLabel", "", TextAnchor.UpperLeft); + MemberLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(MemberLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0); + MemberLayout = MemberLabel.GetComponent(); + + TypeLabel = UIFactory.CreateLabel(horiRow, "ReturnLabel", "", TextAnchor.UpperLeft); + TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 20, flexibleWidth: 0); + ReturnTypeLayout = TypeLabel.GetComponent(); + + RightGroupHolder = UIFactory.CreateUIObject("RightGroup", horiRow); + UIFactory.SetLayoutGroup(RightGroupHolder, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(RightGroupHolder, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 150); + RightGroupLayout = RightGroupHolder.GetComponent(); + + InspectButton = UIFactory.CreateButton(RightGroupHolder, "InspectButton", "Inspect", new Color(0.23f, 0.23f, 0.23f)); + UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25); + + ValueLabel = UIFactory.CreateLabel(RightGroupHolder, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); + ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999); + + // Subcontent (todo?) + + SubContentHolder = UIFactory.CreateUIObject("SubContent", uiRoot); + UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 500, minWidth: 100, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(SubContentHolder, true, false, true, true, 2, childAlignment: TextAnchor.UpperLeft); + + SubContentHolder.SetActive(false); + + return uiRoot; + } + } +} diff --git a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs new file mode 100644 index 0000000..1625fd1 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.UI.Inspectors.CacheObject.Views +{ + class CacheObjectCell + { + } +} diff --git a/src/UI/Inspectors/GameObjectInspector.cs b/src/UI/Inspectors/GameObjectInspector.cs index 416d8e5..ab10ec0 100644 --- a/src/UI/Inspectors/GameObjectInspector.cs +++ b/src/UI/Inspectors/GameObjectInspector.cs @@ -1,10 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; using UnityExplorer.UI.ObjectPool; +using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; @@ -25,6 +27,137 @@ namespace UnityExplorer.UI.Inspectors public ButtonListSource ComponentList; private ScrollPool componentScroll; + private readonly List _rootEntries = new List(); + + public override void OnBorrowedFromPool(object target) + { + base.OnBorrowedFromPool(target); + + Target = target as GameObject; + + NameText.text = Target.name; + Tab.TabText.text = $"[G] {Target.name}"; + + RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); + } + + private IEnumerator InitCoroutine() + { + yield return null; + + LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect); + + TransformTree.Rebuild(); + + ComponentList.ScrollPool.Rebuild(); + UpdateComponents(); + } + + public override void OnReturnToPool() + { + base.OnReturnToPool(); + + // release component and transform lists + this.TransformTree.ScrollPool.ReturnCells(); + this.TransformTree.ScrollPool.SetUninitialized(); + + this.ComponentList.ScrollPool.ReturnCells(); + this.ComponentList.ScrollPool.SetUninitialized(); + } + + private float timeOfLastUpdate; + + public override void Update() + { + if (!this.IsActive) + return; + + if (Target.IsNullOrDestroyed(false)) + { + InspectorManager.ReleaseInspector(this); + return; + } + + if (Time.time - timeOfLastUpdate > 1f) + { + timeOfLastUpdate = Time.time; + + // Refresh children and components + TransformTree.RefreshData(true, false); + + UpdateComponents(); + + Tab.TabText.text = $"[G] {Target.name}"; + } + } + + private IEnumerable GetTransformEntries() + { + _rootEntries.Clear(); + for (int i = 0; i < Target.transform.childCount; i++) + _rootEntries.Add(Target.transform.GetChild(i).gameObject); + return _rootEntries; + } + + private readonly List _componentEntries = new List(); + + private List GetComponentEntries() + { + return _componentEntries; + } + + private static readonly Dictionary compToStringCache = new Dictionary(); + + private void SetComponentCell(ButtonCell cell, int index) + { + if (index < 0 || index >= _componentEntries.Count) + { + cell.Disable(); + return; + } + + cell.Enable(); + + var comp = _componentEntries[index]; + var type = comp.GetActualType(); + + if (!compToStringCache.ContainsKey(type.AssemblyQualifiedName)) + { + compToStringCache.Add(type.AssemblyQualifiedName, + $"{type.Namespace}.{SignatureHighlighter.HighlightTypeName(type)}"); + } + + cell.Button.ButtonText.text = compToStringCache[type.AssemblyQualifiedName]; + } + + private bool ShouldDisplay(Component comp, string filter) => true; + + private void OnComponentClicked(int index) + { + if (index < 0 || index >= _componentEntries.Count) + return; + + var comp = _componentEntries[index]; + if (comp) + InspectorManager.Inspect(comp); + } + + private void UpdateComponents() + { + _componentEntries.Clear(); + var comps = Target.GetComponents(); + foreach (var comp in comps) + _componentEntries.Add(comp); + + ComponentList.RefreshData(); + ComponentList.ScrollPool.RefreshCells(true); + } + + protected override void OnCloseClicked() + { + InspectorManager.ReleaseInspector(this); + } + public override GameObject CreateContent(GameObject parent) { uiRoot = UIFactory.CreateVerticalGroup(Pool.Instance.InactiveHolder, @@ -54,120 +187,5 @@ namespace UnityExplorer.UI.Inspectors return uiRoot; } - - private readonly List _rootEntries = new List(); - - private IEnumerable GetTransformEntries() - { - _rootEntries.Clear(); - for (int i = 0; i < Target.transform.childCount; i++) - _rootEntries.Add(Target.transform.GetChild(i).gameObject); - return _rootEntries; - } - - private readonly List _componentEntries = new List(); - - private List GetComponentEntries() - { - return _componentEntries; - } - - private static readonly Dictionary compToStringCache = new Dictionary(); - - private void SetComponentCell(ButtonCell cell, int index) - { - if (index < 0 || index >= _componentEntries.Count) - { - cell.Disable(); - return; - } - - cell.Enable(); - - var comp = _componentEntries[index]; - var type = comp.GetActualType(); - - if (!compToStringCache.ContainsKey(type)) - { - compToStringCache.Add(type, - $"{type.Namespace}.{SignatureHighlighter.HighlightTypeName(type)}"); - } - - cell.Button.ButtonText.text = compToStringCache[type]; - } - - private bool ShouldDisplay(Component comp, string filter) => true; - - private void OnComponentClicked(int index) - { - if (index < 0 || index >= _componentEntries.Count) - return; - - var comp = _componentEntries[index]; - if (comp) - InspectorManager.Inspect(comp); - } - - public override void OnBorrowedFromPool(object target) - { - base.OnBorrowedFromPool(target); - - Target = target as GameObject; - - NameText.text = Target.name; - this.Tab.TabText.text = $"[G] {Target.name}"; - - TransformTree.Rebuild(); - - ComponentList.ScrollPool.Rebuild(); - UpdateComponents(); - } - - public override void OnReturnToPool() - { - base.OnReturnToPool(); - - // release component and transform lists - this.TransformTree.ScrollPool.ReturnCells(); - this.TransformTree.ScrollPool.SetUninitialized(); - - this.ComponentList.ScrollPool.ReturnCells(); - this.ComponentList.ScrollPool.SetUninitialized(); - } - - private float timeOfLastUpdate; - - public override void Update() - { - // todo update tab title? or put that in InspectorBase update? - - if (!this.IsActive) - return; - - if (Time.time - timeOfLastUpdate > 1f) - { - timeOfLastUpdate = Time.time; - - // Refresh children and components - TransformTree.RefreshData(true, false); - - UpdateComponents(); - } - } - - private void UpdateComponents() - { - _componentEntries.Clear(); - foreach (var comp in Target.GetComponents()) - _componentEntries.Add(comp); - - ComponentList.RefreshData(); - ComponentList.ScrollPool.RefreshCells(true); - } - - protected override void OnCloseClicked() - { - InspectorManager.ReleaseInspector(this); - } } } diff --git a/src/UI/Inspectors/IValues/IValueTest.cs b/src/UI/Inspectors/IValues/IValueTest.cs new file mode 100644 index 0000000..fa73fce --- /dev/null +++ b/src/UI/Inspectors/IValues/IValueTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.ObjectPool; + +namespace UnityExplorer.UI.Inspectors.IValues +{ + public class IValueTest : IPooledObject + { + public GameObject UIRoot => uiRoot; + private GameObject uiRoot; + + public float DefaultHeight => -1f; + + public GameObject CreateContent(GameObject parent) + { + uiRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); + UIFactory.SetLayoutGroup(uiRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); + + + + return uiRoot; + } + } +} diff --git a/src/UI/Inspectors/InspectorBase.cs b/src/UI/Inspectors/InspectorBase.cs index 389bd22..d34352d 100644 --- a/src/UI/Inspectors/InspectorBase.cs +++ b/src/UI/Inspectors/InspectorBase.cs @@ -10,16 +10,17 @@ namespace UnityExplorer.UI.Inspectors { public abstract class InspectorBase : IPooledObject { - public InspectorTab Tab { get; internal set; } public bool IsActive { get; internal set; } + public InspectorTab Tab { get; internal set; } + public abstract GameObject UIRoot { get; } private static readonly Color _enabledTabColor = new Color(0.2f, 0.4f, 0.2f); private static readonly Color _disabledTabColor = new Color(0.25f, 0.25f, 0.25f); public float DefaultHeight => -1f; - public abstract GameObject CreateContent(GameObject content); + public abstract GameObject CreateContent(GameObject parent); public abstract void Update(); diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs index 0a94c38..4eee03b 100644 --- a/src/UI/Inspectors/InspectorManager.cs +++ b/src/UI/Inspectors/InspectorManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; +using UnityEngine.UI; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Panels; @@ -13,19 +14,24 @@ namespace UnityExplorer.UI.Inspectors public static readonly List Inspectors = new List(); public static InspectorBase ActiveInspector { get; private set; } - + + public static float PanelWidth; + public static void Inspect(object obj) { + if (obj.IsNullOrDestroyed()) + return; + obj = obj.TryCast(); if (obj is GameObject) CreateInspector(obj); else - CreateInspector(obj); + CreateInspector(obj); } - public static void Inspect(Type type) + public static void InspectStatic(Type type) { - CreateInspector(type); + CreateInspector(type, true); } public static void SetInspectorActive(InspectorBase inspector) @@ -42,17 +48,19 @@ namespace UnityExplorer.UI.Inspectors ActiveInspector.OnSetInactive(); } - private static void CreateInspector(object target) where T : InspectorBase + private static void CreateInspector(object target, bool staticReflection = false) where T : InspectorBase { var inspector = Pool.Borrow(); Inspectors.Add(inspector); + UIManager.SetPanelActive(UIManager.Panels.Inspector, true); inspector.UIRoot.transform.SetParent(InspectorPanel.Instance.ContentHolder.transform, false); + if (inspector is ReflectionInspector reflectInspector) + reflectInspector.StaticOnly = staticReflection; + inspector.OnBorrowedFromPool(target); SetInspectorActive(inspector); - - UIManager.SetPanelActive(UIManager.Panels.Inspector, true); } internal static void ReleaseInspector(T inspector) where T : InspectorBase @@ -65,15 +73,21 @@ namespace UnityExplorer.UI.Inspectors internal static void Update() { - foreach (var inspector in Inspectors) - { - inspector.Update(); - } + for (int i = Inspectors.Count - 1; i >= 0; i--) + Inspectors[i].Update(); } - internal static void OnPanelResized() + internal static void OnPanelResized(float width) { - + PanelWidth = width; + + foreach (var obj in Inspectors) + { + if (obj is ReflectionInspector inspector) + { + inspector.SetLayouts(); + } + } } } } diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index 363d7e1..4bba928 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -1,32 +1,265 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; using System.Text; using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Inspectors.CacheObject; +using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.ObjectPool; +using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Inspectors { - public class InstanceInspector : ReflectionInspector { } - - public class StaticInspector : ReflectionInspector { } - - public class ReflectionInspector : InspectorBase + public class ReflectionInspector : InspectorBase, IPoolDataSource { - public override GameObject UIRoot => throw new NotImplementedException(); + public bool StaticOnly { get; internal set; } + public bool AutoUpdate { get; internal set; } - public override GameObject CreateContent(GameObject content) + public object Target { get; private set; } + public Type TargetType { get; private set; } + + public ScrollPool MemberScrollPool { get; private set; } + + private List members = new List(); + private readonly List filteredMembers = new List(); + private readonly List filteredIndices = new List(); + + public override GameObject UIRoot => uiRoot; + private GameObject uiRoot; + + public Text NameText; + public Text AssemblyText; + + private LayoutElement memberTitleLayout; + private LayoutElement typeTitleLayout; + + public override void OnBorrowedFromPool(object target) { - throw new NotImplementedException(); + base.OnBorrowedFromPool(target); + + SetTitleLayouts(); + SetTarget(target); + + RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); } + private IEnumerator InitCoroutine() + { + yield return null; + + LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect); + + MemberScrollPool.RecreateHeightCache(); + MemberScrollPool.Rebuild(); + } + + private void SetTarget(object target) + { + string prefix; + if (StaticOnly) + { + Target = null; + TargetType = target as Type; + prefix = "[S]"; + } + else + { + Target = target; + TargetType = target.GetActualType(); + prefix = "[R]"; + } + + NameText.text = SignatureHighlighter.ParseFullSyntax(TargetType, true); + + string asmText; + if (TargetType.Assembly != null && !string.IsNullOrEmpty(TargetType.Assembly.Location)) + asmText = Path.GetFileName(TargetType.Assembly.Location); + else + asmText = $"{TargetType.Assembly.GetName().Name} (in memory)"; + AssemblyText.text = $"Assembly: {asmText}"; + + Tab.TabText.text = $"{prefix} {SignatureHighlighter.HighlightTypeName(TargetType)}"; + + this.members = CacheMember.GetCacheMembers(Target, TargetType, this); + FilterMembers(); + } + + public void FilterMembers() + { + // todo + for (int i = 0; i < members.Count; i++) + { + var member = members[i]; + filteredMembers.Add(member); + filteredIndices.Add(i); + } + } + + public override void OnReturnToPool() + { + base.OnReturnToPool(); + + members.Clear(); + filteredMembers.Clear(); + filteredIndices.Clear(); + + // release all cachememberviews + MemberScrollPool.ReturnCells(); + MemberScrollPool.SetUninitialized(); + } + + public override void OnSetActive() + { + base.OnSetActive(); + } + + public override void OnSetInactive() + { + base.OnSetInactive(); + } + + private float timeOfLastUpdate; + public override void Update() { - throw new NotImplementedException(); + if (!this.IsActive) + return; + + if (!StaticOnly && Target.IsNullOrDestroyed(false)) + { + InspectorManager.ReleaseInspector(this); + return; + } + + if (AutoUpdate && Time.time - timeOfLastUpdate > 1f) + { + timeOfLastUpdate = Time.time; + + } } protected override void OnCloseClicked() { - throw new NotImplementedException(); + InspectorManager.ReleaseInspector(this); + } + + + #region IPoolDataSource + + public int ItemCount => filteredMembers.Count; + + public int GetRealIndexOfTempIndex(int tempIndex) + { + if (filteredIndices.Count <= tempIndex) + return -1; + + return filteredIndices[tempIndex]; + } + + public void OnCellBorrowed(CacheMemberCell cell) + { + cell.CurrentOwner = this; + + // todo add listeners + } + + public void OnCellReturned(CacheMemberCell cell) + { + // todo remove listeners + + // return ivalue + + cell.CurrentOwner = null; + } + + public void SetCell(CacheMemberCell cell, int index) + { + index = GetRealIndexOfTempIndex(index); + + if (index < 0 || index >= filteredMembers.Count) + { + cell.Disable(); + return; + } + + members[index].SetCell(cell); + + SetCellLayout(cell); + } + + public void DisableCell(CacheMemberCell cell, int index) + { + // need to do anything? + } + + private static float MemLabelWidth => Math.Min(400f, 0.35f * InspectorManager.PanelWidth - 5); + private static float ReturnLabelWidth => Math.Min(225f, 0.25f * InspectorManager.PanelWidth - 5); + private static float RightGroupWidth => InspectorManager.PanelWidth - MemLabelWidth - ReturnLabelWidth - 50; + + private void SetTitleLayouts() + { + memberTitleLayout.minWidth = MemLabelWidth; + typeTitleLayout.minWidth = ReturnLabelWidth; + } + + private void SetCellLayout(CacheMemberCell cell) + { + cell.MemberLayout.minWidth = MemLabelWidth; + cell.ReturnTypeLayout.minWidth = ReturnLabelWidth; + cell.RightGroupLayout.minWidth = RightGroupWidth; + } + + internal void SetLayouts() + { + SetTitleLayouts(); + + foreach (var cell in MemberScrollPool.CellPool) + SetCellLayout(cell); + } + + #endregion + + 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)); + + NameText = UIFactory.CreateLabel(uiRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 20); + 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); + + var listTitles = UIFactory.CreateUIObject("ListTitles", uiRoot); + UIFactory.SetLayoutElement(listTitles, minHeight: 25); + UIFactory.SetLayoutGroup(listTitles, true, true, true, true, 5, 1, 1, 1, 1); + + var memberTitle = UIFactory.CreateLabel(listTitles, "MemberTitle", "Member Name", TextAnchor.LowerLeft, Color.grey, fontSize: 15); + memberTitleLayout = memberTitle.gameObject.AddComponent(); + + var typeTitle = UIFactory.CreateLabel(listTitles, "TypeTitle", "Type", TextAnchor.LowerLeft, Color.grey, fontSize: 15); + typeTitleLayout = typeTitle.gameObject.AddComponent(); + + var valueTitle = UIFactory.CreateLabel(listTitles, "ValueTitle", "Value", TextAnchor.LowerLeft, Color.grey, fontSize: 15); + UIFactory.SetLayoutElement(valueTitle.gameObject, flexibleWidth: 9999); + + MemberScrollPool = UIFactory.CreateScrollPool(uiRoot, "MemberList", out GameObject scrollObj, + out GameObject scrollContent, new Color(0.09f, 0.09f, 0.09f)); + UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); + UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); + + MemberScrollPool.Initialize(this); + + //InspectorPanel.Instance.UIRoot.GetComponent().enabled = false; + //MemberScrollPool.Viewport.GetComponent().enabled = false; + + return uiRoot; } } } diff --git a/src/UI/ObjectPool/Pool.cs b/src/UI/ObjectPool/Pool.cs index d8c7a8e..2831781 100644 --- a/src/UI/ObjectPool/Pool.cs +++ b/src/UI/ObjectPool/Pool.cs @@ -6,29 +6,44 @@ using UnityEngine; namespace UnityExplorer.UI.ObjectPool { - public interface IObjectPool { } - - public class Pool : IObjectPool where T : IPooledObject + // Abstract non-generic class, handles the pool dictionary and interfacing with the generic pools. + public abstract class Pool { - // internal pool management + protected static readonly Dictionary pools = new Dictionary(); - private static readonly Dictionary pools = new Dictionary(); - - public static Pool GetPool() + public static Pool GetPool(Type type) { - var type = typeof(T); - if (!pools.ContainsKey(type)) - CreatePool(); - return (Pool)pools[type]; - } - - private static Pool CreatePool() - { - var pool = new Pool(); - pools.Add(typeof(T), pool); + if (!pools.TryGetValue(type, out Pool pool)) + pool = CreatePool(type); return pool; } + protected static Pool CreatePool(Type type) + { + Pool pool = (Pool)Activator.CreateInstance(typeof(Pool<>).MakeGenericType(new[] { type })); + pools.Add(type, pool); + return pool; + } + + public static IPooledObject Borrow(Type type) + { + return GetPool(type).TryBorrow(); + } + + public static void Return(Type type, IPooledObject obj) + { + GetPool(type).TryReturn(obj); + } + + protected abstract IPooledObject TryBorrow(); + protected abstract void TryReturn(IPooledObject obj); + } + + // Each generic implementation has its own pool, business logic is here + public class Pool : Pool where T : IPooledObject + { + public static Pool GetPool() => (Pool)GetPool(typeof(T)); + public static T Borrow() { return GetPool().BorrowObject(); @@ -43,7 +58,7 @@ namespace UnityExplorer.UI.ObjectPool public static Pool Instance { - get => s_instance ?? CreatePool(); + get => s_instance ?? (Pool)CreatePool(typeof(T)); } private static Pool s_instance; @@ -58,9 +73,7 @@ namespace UnityExplorer.UI.ObjectPool InactiveHolder.hideFlags |= HideFlags.HideAndDontSave; InactiveHolder.SetActive(false); - // Create an instance (not content) to grab the default height. - // Tiny bit wasteful, but not a big deal, only happens once per type - // and its just the C# wrapper class being created. + // Create an instance (not content) to grab the default height var obj = (T)Activator.CreateInstance(typeof(T)); DefaultHeight = obj.DefaultHeight; } @@ -71,7 +84,7 @@ namespace UnityExplorer.UI.ObjectPool private readonly HashSet available = new HashSet(); private readonly HashSet borrowed = new HashSet(); - public int AvailableObjects => available.Count; + public int AvailableCount => available.Count; private void IncrementPool() { @@ -102,5 +115,9 @@ namespace UnityExplorer.UI.ObjectPool available.Add(obj); obj.UIRoot.transform.SetParent(InactiveHolder.transform, false); } + + protected override IPooledObject TryBorrow() => Borrow(); + + protected override void TryReturn(IPooledObject obj) => Return((T)obj); } } diff --git a/src/UI/Panels/InspectorPanel.cs b/src/UI/Panels/InspectorPanel.cs index bf32045..5203af5 100644 --- a/src/UI/Panels/InspectorPanel.cs +++ b/src/UI/Panels/InspectorPanel.cs @@ -26,6 +26,7 @@ namespace UnityExplorer.UI.Panels public GameObject NavbarHolder; public GameObject ContentHolder; + public RectTransform ContentRect; public static float CurrentPanelWidth => Instance.mainPanelRect.rect.width; @@ -38,12 +39,14 @@ namespace UnityExplorer.UI.Panels { base.OnFinishResize(panel); - InspectorManager.OnPanelResized(); + InspectorManager.OnPanelResized(panel.rect.width); } public override void LoadSaveData() { ApplySaveData(ConfigManager.GameObjectInspectorData.Value); + + InspectorManager.PanelWidth = this.mainPanelRect.rect.width; } public override void SaveToConfigManager() @@ -77,6 +80,7 @@ namespace UnityExplorer.UI.Panels this.ContentHolder = UIFactory.CreateVerticalGroup(this.content, "ContentHolder", true, true, true, true, 0, default, new Color(0.1f, 0.1f, 0.1f)); UIFactory.SetLayoutElement(ContentHolder, flexibleHeight: 9999); + ContentRect = ContentHolder.GetComponent(); UIManager.SetPanelActive(PanelType, false); } diff --git a/src/UI/Panels/ObjectExplorer.cs b/src/UI/Panels/ObjectExplorer.cs index 5740e5c..aae97d8 100644 --- a/src/UI/Panels/ObjectExplorer.cs +++ b/src/UI/Panels/ObjectExplorer.cs @@ -24,6 +24,8 @@ namespace UnityExplorer.UI.Panels public SceneExplorer SceneExplorer; public ObjectSearch ObjectSearch; + public override bool ShouldSaveActiveState => true; + public int SelectedTab = -1; private readonly List tabPages = new List(); private readonly List tabButtons = new List(); diff --git a/src/UI/Models/UIPanel.cs b/src/UI/Panels/UIPanel.cs similarity index 98% rename from src/UI/Models/UIPanel.cs rename to src/UI/Panels/UIPanel.cs index fc66e9c..1307b62 100644 --- a/src/UI/Models/UIPanel.cs +++ b/src/UI/Panels/UIPanel.cs @@ -7,10 +7,10 @@ using UnityEngine; using UnityEngine.UI; using UnityExplorer.Core.Config; using UnityExplorer.Core.Input; -using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Models; using UnityExplorer.UI.Utility; -namespace UnityExplorer.UI.Models +namespace UnityExplorer.UI.Panels { public abstract class UIPanel : UIBehaviourModel { @@ -135,6 +135,7 @@ namespace UnityExplorer.UI.Models closeBtn.OnClick += () => { UIManager.SetPanelActive(this.PanelType, false); + SaveToConfigManager(); }; if (!CanDrag) @@ -185,7 +186,7 @@ namespace UnityExplorer.UI.Models { try { - return $"{(ShouldSaveActiveState ? Enabled : false)}" + + return $"{ShouldSaveActiveState && Enabled}" + $"|{mainPanelRect.RectAnchorsToString()}" + $"|{mainPanelRect.RectPositionToString()}"; } diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index e1b8899..d707223 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -732,7 +732,7 @@ namespace UnityExplorer.UI SetLayoutGroup(mainObj, false, true, true, true); GameObject viewportObj = CreateUIObject("Viewport", mainObj); - SetLayoutElement(viewportObj, flexibleWidth: 9999); + SetLayoutElement(viewportObj, flexibleWidth: 9999, flexibleHeight: 9999); var viewportRect = viewportObj.GetComponent(); viewportRect.anchorMin = Vector2.zero; viewportRect.anchorMax = Vector2.one; diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index 7b7860b..88ee1da 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text; @@ -54,25 +55,18 @@ namespace UnityExplorer.UI.Utility syntaxBuilder.Clear(); - if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter)) - { - syntaxBuilder.Append($"{type.Name}"); - } - else - { - if (includeNamespace && !string.IsNullOrEmpty(type.Namespace)) - syntaxBuilder.Append($"{type.Namespace}."); + if (includeNamespace && !string.IsNullOrEmpty(type.Namespace)) + syntaxBuilder.Append($"{type.Namespace}."); - var declaring = type.DeclaringType; - while (declaring != null) - { - syntaxBuilder.Append(HighlightTypeName(declaring) + "."); - declaring = declaring.DeclaringType; - } - - syntaxBuilder.Append(HighlightTypeName(type)); + var declaring = type.DeclaringType; + while (declaring != null) + { + syntaxBuilder.Append(HighlightTypeName(declaring) + "."); + declaring = declaring.DeclaringType; } + syntaxBuilder.Append(HighlightTypeName(type)); + if (memberInfo != null) { syntaxBuilder.Append('.'); @@ -96,37 +90,73 @@ namespace UnityExplorer.UI.Utility private static readonly Dictionary typeToRichType = new Dictionary(); - public static string HighlightTypeName(Type type) + public static string HighlightTypeName(Type type, bool includeNamespace = false, bool includeDllName = false) { - if (typeToRichType.ContainsKey(type.AssemblyQualifiedName)) - return typeToRichType[type.AssemblyQualifiedName]; + string ret = HighlightType(type); + + if (includeNamespace && !string.IsNullOrEmpty(type.Namespace)) + ret = $"{type.Namespace}.{ret}"; + + if (includeDllName) + { + if (!string.IsNullOrEmpty(type.Assembly.Location)) + ret = $"{ret} ({Path.GetFileName(type.Assembly.Location)})"; + else + ret = $"{ret} ({type.Assembly.GetName().Name})"; + } + + return ret; + } + + private static string HighlightType(Type type) + { + string key = type.ToString(); + if (typeToRichType.ContainsKey(key)) + return typeToRichType[key]; var typeName = type.Name; - var args = type.GetGenericArguments(); - - if (args.Length > 0) + bool isArray = false; + if (typeName.EndsWith("[]")) { - // remove the `N from the end of the type name - // this could actually be >9 in some cases, so get the length of the length string and use that. - // eg, if it was "List`15", we would remove the ending 3 chars - - int suffixLen = 1 + args.Length.ToString().Length; - - // make sure the typename actually has expected "`N" format. - if (typeName[typeName.Length - suffixLen] == '`') - typeName = typeName.Substring(0, typeName.Length - suffixLen); + isArray = true; + typeName = typeName.Substring(0, typeName.Length - 2); } - // highlight the base name itself - // do this after removing the `N suffix, so only the name itself is in the color tags. - typeName = $"{typeName}"; + if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter)) + { + typeName = $"{typeName}"; + } + else + { + var args = type.GetGenericArguments(); - // parse the generic args, if any - if (args.Length > 0) - typeName += ParseGenericArgs(args); + if (args.Length > 0) + { + // remove the `N from the end of the type name + // this could actually be >9 in some cases, so get the length of the length string and use that. + // eg, if it was "List`15", we would remove the ending 3 chars - typeToRichType.Add(type.AssemblyQualifiedName, typeName); + int suffixLen = 1 + args.Length.ToString().Length; + + // make sure the typename actually has expected "`N" format. + if (typeName[typeName.Length - suffixLen] == '`') + typeName = typeName.Substring(0, typeName.Length - suffixLen); + } + + // highlight the base name itself + // do this after removing the `N suffix, so only the name itself is in the color tags. + typeName = $"{typeName}"; + + // parse the generic args, if any + if (args.Length > 0) + typeName += ParseGenericArgs(args); + } + + if (isArray) + typeName += "[]"; + + typeToRichType.Add(key, typeName); return typeName; } diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index 4bcd8c9..ca778b1 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -11,8 +11,8 @@ namespace UnityExplorer.UI.Utility { public static class ToStringUtility { - internal static Dictionary toStringMethods = new Dictionary(); - internal static Dictionary toStringFormattedMethods = new Dictionary(); + internal static Dictionary toStringMethods = new Dictionary(); + internal static Dictionary toStringFormattedMethods = new Dictionary(); // string allocs private static readonly StringBuilder _stringBuilder = new StringBuilder(16384); @@ -20,18 +20,54 @@ namespace UnityExplorer.UI.Utility private const string nullString = "[null]"; private const string destroyedString = "[Destroyed]"; - public static string ToString(object value, Type fallbackType, bool includeNamespace = true, bool includeName = true) + public static string ToString(object value, Type type) + { + if (value.IsNullOrDestroyed()) + { + if (value == null) + return nullString; + else // destroyed unity object + return destroyedString; + } + + if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName)) + { + try + { + var formatMethod = type.GetMethod("ToString", new Type[] { typeof(string) }); + formatMethod.Invoke(value, new object[] { "F3" }); + toStringFormattedMethods.Add(type.AssemblyQualifiedName, formatMethod); + toStringMethods.Add(type.AssemblyQualifiedName, null); + } + catch + { + var toStringMethod = type.GetMethod("ToString", new Type[0]); + toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod); + } + } + + value = value.TryCast(type); + + string toString; + if (toStringFormattedMethods.TryGetValue(type.AssemblyQualifiedName, out MethodInfo f3method)) + toString = (string)f3method.Invoke(value, new object[] { "F3" }); + else + toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, new object[0]); + + return toString; + } + + public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true) { if (value == null && fallbackType == null) return unknownString; Type type = value?.GetActualType() ?? fallbackType; - // todo SB this too string richType = SignatureHighlighter.ParseFullSyntax(type, includeNamespace); - if (!includeName) - return richType; + //if (!includeName) + // return richType; _stringBuilder.Clear(); @@ -58,33 +94,9 @@ namespace UnityExplorer.UI.Utility } else { - if (!toStringMethods.ContainsKey(type)) - { - var toStringMethod = type.GetMethod("ToString", new Type[0]); - var formatMethod = type.GetMethod("ToString", new Type[] { typeof(string) }); + var toString = ToString(value, type); - if (formatMethod != null) - { - try { formatMethod.Invoke(value, new object[] { "F3" }); } - catch { formatMethod = null; } - } - - toStringMethods.Add(type, toStringMethod); - toStringFormattedMethods.Add(type, formatMethod); - } - - var f3Method = toStringFormattedMethods[type]; - var stdMethod = toStringMethods[type]; - - value = value.TryCast(type); - - string toString; - if (f3Method != null) - toString = (string)f3Method.Invoke(value, new object[] { "F3" }); - else - toString = (string)stdMethod.Invoke(value, new object[0]); - - if (toString == type.FullName || toString == $"Il2Cpp{type.FullName}") + if (toString == type.FullName || toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}") { // the ToString was just the default object.ToString(), use our // syntax highlighted type name instead. @@ -99,36 +111,6 @@ namespace UnityExplorer.UI.Utility AppendRichType(_stringBuilder, richType); } - - ////string toString; - - // - //toString = toString ?? ""; - // - //string typeName = type.FullName; - //if (typeName.StartsWith("Il2CppSystem.")) - // typeName = typeName.Substring(6, typeName.Length - 6); - // - //toString = ReflectionProvider.Instance.ProcessTypeFullNameInString(type, toString, ref typeName); - // - //// If the ToString is just the type name, use our syntax highlighted type name instead. - //if (toString == typeName) - //{ - // label = richType; - //} - //else // Otherwise, parse the result and put our highlighted name in. - //{ - // if (toString.Length > 200) - // toString = toString.Substring(0, 200) + "..."; - // - // label = toString; - // - // var unityType = $"({type.FullName})"; - // if (value is UnityEngine.Object && label.Contains(unityType)) - // label = label.Replace(unityType, $"({richType})"); - // else - // label += $" ({richType})"; - //} } return _stringBuilder.ToString(); diff --git a/src/UI/Widgets/AutoComplete/AutoCompleter.cs b/src/UI/Widgets/AutoComplete/AutoCompleter.cs index 165ac29..1b2d04e 100644 --- a/src/UI/Widgets/AutoComplete/AutoCompleter.cs +++ b/src/UI/Widgets/AutoComplete/AutoCompleter.cs @@ -7,8 +7,8 @@ using UnityEngine.UI; using UnityExplorer.Core.Input; using UnityExplorer.Core.Runtime; using UnityExplorer.UI; -using UnityExplorer.UI.Models; using UnityExplorer.UI.ObjectPool; +using UnityExplorer.UI.Panels; namespace UnityExplorer.UI.Widgets.AutoComplete { diff --git a/src/UI/Widgets/ButtonList/ButtonCell.cs b/src/UI/Widgets/ButtonList/ButtonCell.cs index 2427c44..cd7ec23 100644 --- a/src/UI/Widgets/ButtonList/ButtonCell.cs +++ b/src/UI/Widgets/ButtonList/ButtonCell.cs @@ -15,13 +15,13 @@ namespace UnityExplorer.UI.Widgets public Action OnClick; public int CurrentDataIndex; - public GameObject UIRoot => uiRoot; - public GameObject uiRoot; - public ButtonRef Button; #region ICell + public GameObject UIRoot => uiRoot; + public GameObject uiRoot; + public bool Enabled => m_enabled; private bool m_enabled; @@ -69,7 +69,7 @@ namespace UnityExplorer.UI.Widgets Button.OnClick += () => { OnClick?.Invoke(CurrentDataIndex); }; - return m_rect.gameObject; + return uiRoot; } } } diff --git a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs index 946969a..6e4b651 100644 --- a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs @@ -97,9 +97,9 @@ namespace UnityExplorer.UI.Widgets { string text; if (m_context == SearchContext.StaticClass) - text = SignatureHighlighter.HighlightTypeName(currentResults[index].GetActualType()); + text = SignatureHighlighter.HighlightTypeName(currentResults[index] as Type, true, true); else - text = ToStringUtility.ToString(currentResults[index], currentResults[index].GetActualType()); + text = ToStringUtility.ToStringWithType(currentResults[index], currentResults[index]?.GetActualType()); cachedCellTexts.Add(index, text); } @@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets private void OnCellClicked(int dataIndex) { if (m_context == SearchContext.StaticClass) - InspectorManager.Inspect(currentResults[dataIndex] as Type); + InspectorManager.InspectStatic(currentResults[dataIndex] as Type); else InspectorManager.Inspect(currentResults[dataIndex]); } diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index ffd84ca..a4e9bcc 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -16,15 +16,6 @@ namespace UnityExplorer.UI.Widgets public int cellIndex, dataIndex; } - //public abstract class ScrollPool : UIBehaviourModel - //{ - // public abstract IPoolDataSource DataSource { get; set; } - // public abstract RectTransform PrototypeCell { get; } - // - // public abstract void Initialize(IPoolDataSource dataSource); - // - //} - /// /// An object-pooled ScrollRect, attempts to support content of any size and provide a scrollbar for it. /// @@ -37,12 +28,16 @@ namespace UnityExplorer.UI.Widgets public IPoolDataSource DataSource { get; set; } + public readonly List CellPool = new List(); + + internal DataHeightCache HeightCache; + public float PrototypeHeight => _protoHeight ?? (float)(_protoHeight = Pool.Instance.DefaultHeight); private float? _protoHeight; //private float PrototypeHeight => DefaultHeight.rect.height; - public int ExtraPoolCells => 6; + public int ExtraPoolCells => 10; public float RecycleThreshold => PrototypeHeight * ExtraPoolCells; public float HalfThreshold => RecycleThreshold * 0.5f; @@ -76,10 +71,6 @@ namespace UnityExplorer.UI.Widgets private int bottomDataIndex; private int TopDataIndex => Math.Max(0, bottomDataIndex - CellPool.Count + 1); - private readonly List CellPool = new List(); - - internal DataHeightCache HeightCache; - private float TotalDataHeight => HeightCache.TotalHeight + contentLayout.padding.top + contentLayout.padding.bottom; /// @@ -269,12 +260,12 @@ namespace UnityExplorer.UI.Widgets if (andResetDataIndex) bottomDataIndex = CellPool.Count - 1; + LayoutRebuilder.ForceRebuildLayoutImmediate(Content); + // after creating pool, set displayed cells. var enumerator = GetPoolEnumerator(); while (enumerator.MoveNext()) SetCell(CellPool[enumerator.Current.cellIndex], enumerator.Current.dataIndex); - - LayoutRebuilder.ForceRebuildLayoutImmediate(Content); } /// ret = cell pool was extended @@ -303,13 +294,13 @@ namespace UnityExplorer.UI.Widgets { WritingLocked = true; - // Disable cells so DataSource can handle its content if need be - var enumerator = GetPoolEnumerator(); - while (enumerator.MoveNext()) - { - var curr = enumerator.Current; - DataSource.DisableCell(CellPool[curr.cellIndex], curr.dataIndex); - } + //// Disable cells so DataSource can handle its content if need be + //var enumerator = GetPoolEnumerator(); + //while (enumerator.MoveNext()) + //{ + // var curr = enumerator.Current; + // DataSource.DisableCell(CellPool[curr.cellIndex], curr.dataIndex); + //} bottomDataIndex += cellsRequired; int maxDataIndex = Math.Max(CellPool.Count + cellsRequired - 1, DataSource.ItemCount - 1); @@ -438,14 +429,16 @@ namespace UnityExplorer.UI.Widgets private void OnValueChangedListener(Vector2 val) { - if (WritingLocked) + if (WritingLocked || !m_initialized) return; if (InputManager.MouseScrollDelta != Vector2.zero) ScrollRect.StopMovement(); - if (!SetRecycleViewBounds(true)) - RefreshCells(false); + SetRecycleViewBounds(true); + + //if (!SetRecycleViewBounds(true)) + // RefreshCells(false); float yChange = (ScrollRect.content.anchoredPosition - prevAnchoredPos).y; float adjust = 0f; @@ -605,7 +598,7 @@ namespace UnityExplorer.UI.Widgets private void OnSliderValueChanged(float val) { - if (this.WritingLocked) + if (this.WritingLocked || !m_initialized) return; this.WritingLocked = true; diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 24039d0..15981c7 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -230,10 +230,18 @@ + + + + + + + + @@ -282,7 +290,7 @@ - +