From ad61ff243a329c298b19d9901d1d3e4ac57501ef Mon Sep 17 00:00:00 2001 From: Sinai Date: Mon, 3 May 2021 01:29:02 +1000 Subject: [PATCH] progress on lists and dictionaries, fixes for scrollpool --- src/UI/Inspectors/CacheObject/CacheField.cs | 9 +- .../CacheObject/CacheKeyValuePair.cs | 97 +++++++ .../Inspectors/CacheObject/CacheListEntry.cs | 8 +- src/UI/Inspectors/CacheObject/CacheMember.cs | 7 +- src/UI/Inspectors/CacheObject/CacheMethod.cs | 1 + .../Inspectors/CacheObject/CacheObjectBase.cs | 75 ++++-- .../Inspectors/CacheObject/CacheProperty.cs | 6 +- .../Views/CacheKeyValuePairCell.cs | 92 +++++++ .../CacheObject/Views/CacheListEntryCell.cs | 15 +- .../CacheObject/Views/CacheObjectCell.cs | 41 ++- src/UI/Inspectors/GameObjectInspector.cs | 15 +- src/UI/Inspectors/ICacheObjectController.cs | 18 ++ .../IValues/InteractiveDictionary.cs | 250 ++++++++++++++++++ src/UI/Inspectors/IValues/InteractiveList.cs | 76 +++--- src/UI/Inspectors/IValues/InteractiveValue.cs | 8 +- src/UI/Inspectors/InspectorBase.cs | 5 +- src/UI/Inspectors/InspectorManager.cs | 18 +- src/UI/Inspectors/ListInspector.cs | 197 ++++++++++++++ src/UI/Inspectors/ReflectionInspector.cs | 27 +- src/UI/Panels/CSConsolePanel.cs | 3 + src/UI/UIFactory.cs | 5 +- src/UI/UIManager.cs | 2 + src/UI/Widgets/ScrollPool/DataHeightCache.cs | 150 +++++------ src/UI/Widgets/ScrollPool/ScrollPool.cs | 24 +- src/UnityExplorer.csproj | 7 + 25 files changed, 942 insertions(+), 214 deletions(-) create mode 100644 src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs create mode 100644 src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs create mode 100644 src/UI/Inspectors/ICacheObjectController.cs create mode 100644 src/UI/Inspectors/IValues/InteractiveDictionary.cs create mode 100644 src/UI/Inspectors/ListInspector.cs diff --git a/src/UI/Inspectors/CacheObject/CacheField.cs b/src/UI/Inspectors/CacheObject/CacheField.cs index 285a679..9de77a3 100644 --- a/src/UI/Inspectors/CacheObject/CacheField.cs +++ b/src/UI/Inspectors/CacheObject/CacheField.cs @@ -10,22 +10,21 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public FieldInfo FieldInfo { get; internal set; } public override Type DeclaringType => FieldInfo.DeclaringType; + public override bool CanWrite => m_canWrite ?? (bool)(m_canWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly)); + private bool? m_canWrite; public override bool ShouldAutoEvaluate => true; public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { base.SetInspectorOwner(inspector, member); - - // not constant - CanWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly); } protected override void TryEvaluate() { try { - Value = FieldInfo.GetValue(this.ParentInspector.Target.TryCast(this.DeclaringType)); + Value = FieldInfo.GetValue(this.Owner.Target.TryCast(this.DeclaringType)); } catch (Exception ex) { @@ -38,7 +37,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { try { - FieldInfo.SetValue(FieldInfo.IsStatic ? null : ParentInspector.Target, value); + FieldInfo.SetValue(FieldInfo.IsStatic ? null : Owner.Target, value); } catch (Exception ex) { diff --git a/src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs b/src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs new file mode 100644 index 0000000..e7641b8 --- /dev/null +++ b/src/UI/Inspectors/CacheObject/CacheKeyValuePair.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.Utility; + +namespace UnityExplorer.UI.Inspectors.CacheObject +{ + public class CacheKeyValuePair : CacheObjectBase + { + //public InteractiveList CurrentList { get; set; } + + public int DictIndex; + public object DictKey; + + public bool KeyInputWanted; + public bool InspectWanted; + public string KeyLabelText; + public string KeyInputText; + public string KeyInputTypeText; + + public float DesiredKeyWidth; + public float DesiredValueWidth; + + public override bool ShouldAutoEvaluate => true; + public override bool HasArguments => false; + public override bool CanWrite => false; // TODO Parent.CanWrite; + + public void SetDictOwner(InteractiveDictionary dict, int index) + { + this.Owner = dict; + this.DictIndex = index; + } + + public void SetKey(object key) + { + this.DictKey = key; + var type = key.GetActualType(); + if (type == typeof(string) || (type.IsPrimitive && !(type == typeof(bool))) || type == typeof(decimal)) + { + KeyInputWanted = true; + KeyInputText = key.ToString(); + KeyInputTypeText = SignatureHighlighter.ParseFullType(type, false); + } + else + { + KeyInputWanted = false; + InspectWanted = type != typeof(bool) && !type.IsEnum; + KeyLabelText = ToStringUtility.ToStringWithType(key, type, true); + } + } + + public override void SetCell(CacheObjectCell cell) + { + base.SetCell(cell); + + var kvpCell = cell as CacheKeyValuePairCell; + + kvpCell.NameLabel.text = $"{DictIndex}:"; + kvpCell.Image.color = DictIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor; + + if (KeyInputWanted) + { + kvpCell.KeyInputField.gameObject.SetActive(true); + kvpCell.KeyInputTypeLabel.gameObject.SetActive(true); + kvpCell.KeyLabel.gameObject.SetActive(false); + kvpCell.KeyInspectButton.Button.gameObject.SetActive(false); + + kvpCell.KeyInputField.text = KeyInputText; + kvpCell.KeyInputTypeLabel.text = KeyInputTypeText; + } + else + { + kvpCell.KeyInputField.gameObject.SetActive(false); + kvpCell.KeyInputTypeLabel.gameObject.SetActive(false); + kvpCell.KeyLabel.gameObject.SetActive(true); + kvpCell.KeyInspectButton.Button.gameObject.SetActive(InspectWanted); + + kvpCell.KeyLabel.text = KeyLabelText; + } + } + + public override void SetUserValue(object value) + { + throw new NotImplementedException("TODO"); + } + + + protected override bool SetCellEvaluateState(CacheObjectCell cell) + { + // not needed + return false; + } + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheListEntry.cs b/src/UI/Inspectors/CacheObject/CacheListEntry.cs index 3467db9..cda99fc 100644 --- a/src/UI/Inspectors/CacheObject/CacheListEntry.cs +++ b/src/UI/Inspectors/CacheObject/CacheListEntry.cs @@ -9,16 +9,15 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public class CacheListEntry : CacheObjectBase { - public InteractiveList CurrentList { get; set; } - public int ListIndex; public override bool ShouldAutoEvaluate => true; public override bool HasArguments => false; + public override bool CanWrite => Owner.CanWrite; - public void SetListOwner(InteractiveList iList, int listIndex) + public void SetListOwner(InteractiveList list, int listIndex) { - this.CurrentList = iList; + this.Owner = list; this.ListIndex = listIndex; } @@ -29,6 +28,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject var listCell = cell as CacheListEntryCell; listCell.NameLabel.text = $"{ListIndex}:"; + listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor; } public override void SetUserValue(object value) diff --git a/src/UI/Inspectors/CacheObject/CacheMember.cs b/src/UI/Inspectors/CacheObject/CacheMember.cs index 83220f6..b5a83a8 100644 --- a/src/UI/Inspectors/CacheObject/CacheMember.cs +++ b/src/UI/Inspectors/CacheObject/CacheMember.cs @@ -11,7 +11,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public abstract class CacheMember : CacheObjectBase { - public ReflectionInspector ParentInspector { get; internal set; } + //public ReflectionInspector ParentInspector { get; internal set; } //public bool AutoUpdateWanted { get; internal set; } public abstract Type DeclaringType { get; } @@ -23,7 +23,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public virtual void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { - this.ParentInspector = inspector; + this.Owner = inspector; this.NameLabelText = SignatureHighlighter.ParseFullSyntax(member.DeclaringType, false, member); this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}"; } @@ -82,6 +82,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { // todo evaluate buttons etc SetValueState(cell, ValueStateArgs.Default); + cell.RefreshSubcontentButton(); return true; } @@ -235,7 +236,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject cachedSigs.Add(sig); //cached.Initialize(_inspector, declaringType, member, returnType); - cached.Initialize(returnType); + cached.SetFallbackType(returnType); cached.SetInspectorOwner(_inspector, member); list.Add(cached); diff --git a/src/UI/Inspectors/CacheObject/CacheMethod.cs b/src/UI/Inspectors/CacheObject/CacheMethod.cs index c5f204c..3985466 100644 --- a/src/UI/Inspectors/CacheObject/CacheMethod.cs +++ b/src/UI/Inspectors/CacheObject/CacheMethod.cs @@ -10,6 +10,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public MethodInfo MethodInfo { get; internal set; } public override Type DeclaringType => MethodInfo.DeclaringType; + public override bool CanWrite => false; public override bool ShouldAutoEvaluate => false; diff --git a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs index dd9ca95..1e979ed 100644 --- a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs +++ b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs @@ -4,6 +4,7 @@ using System.Linq; 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.ObjectPool; @@ -29,30 +30,30 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public abstract class CacheObjectBase { - public CacheObjectCell CellView { get; internal set; } + public ICacheObjectController Owner { get; set; } - public InteractiveValue IValue { get; private set; } - public Type CurrentIValueType { get; private set; } - public bool SubContentState { get; private set; } + public CacheObjectCell CellView { get; internal set; } public object Value { get; protected set; } public Type FallbackType { get; protected set; } + public InteractiveValue IValue { get; private set; } + public Type CurrentIValueType { get; private set; } + public bool SubContentShowWanted { get; private set; } + public string NameLabelText { get; protected set; } - //public string TypeLabelText { get; set; } public string ValueLabelText { get; protected set; } public abstract bool ShouldAutoEvaluate { get; } public abstract bool HasArguments { get; } - public bool CanWrite { get; protected set; } + public abstract bool CanWrite { get; } public bool HadException { get; protected set; } public Exception LastException { get; protected set; } - public virtual void Initialize(Type fallbackType) + public virtual void SetFallbackType(Type fallbackType) { this.FallbackType = fallbackType; - //this.TypeLabelText = SignatureHighlighter.ParseFullType(FallbackType, false); - this.ValueLabelText = GetValueLabel(); + GetValueLabel(); } // internals @@ -144,44 +145,50 @@ namespace UnityExplorer.UI.Inspectors.CacheObject State = ValueState.String; else if (type.IsEnum) State = ValueState.Enum; - else if (type.IsEnumerable()) - State = ValueState.Collection; + + // todo Color and ValueStruct + else if (type.IsDictionary()) State = ValueState.Dictionary; - // todo Color and ValueStruct + else if (type.IsEnumerable()) + State = ValueState.Collection; else State = ValueState.Unsupported; } // Set label text - ValueLabelText = GetValueLabel(); + GetValueLabel(); } - protected string GetValueLabel() + protected void GetValueLabel() { + string label; switch (State) { case ValueState.NotEvaluated: - return $"{NOT_YET_EVAL} ({SignatureHighlighter.ParseFullType(FallbackType, true)})"; + label = $"{NOT_YET_EVAL} ({SignatureHighlighter.ParseFullType(FallbackType, true)})"; break; case ValueState.Exception: - return $"{ReflectionUtility.ReflectionExToString(LastException)}"; + label = $"{ReflectionUtility.ReflectionExToString(LastException)}"; break; case ValueState.Boolean: case ValueState.Number: - return null; + label = null; break; case ValueState.String: string s = Value as string; if (s.Length > 200) s = $"{s.Substring(0, 200)}..."; - return $"\"{s}\""; + label = $"\"{s}\""; break; case ValueState.NullValue: - return $"{ToStringUtility.ToStringWithType(Value, FallbackType, true)}"; + label = $"{ToStringUtility.ToStringWithType(Value, FallbackType, true)}"; break; case ValueState.Enum: case ValueState.Collection: + case ValueState.Dictionary: case ValueState.ValueStruct: + case ValueState.Color: case ValueState.Unsupported: default: - return ToStringUtility.ToStringWithType(Value, FallbackType, true); + label = ToStringUtility.ToStringWithType(Value, FallbackType, true); break; } + this.ValueLabelText = label; } /// Return true if SetCell should abort, false if it should continue. @@ -192,9 +199,12 @@ namespace UnityExplorer.UI.Inspectors.CacheObject cell.NameLabel.text = NameLabelText; cell.ValueLabel.gameObject.SetActive(true); - cell.SubContentHolder.gameObject.SetActive(SubContentState); + cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted); if (IValue != null) + { IValue.UIRoot.transform.SetParent(cell.SubContentHolder.transform, false); + IValue.SetLayout(); + } if (SetCellEvaluateState(cell)) return; @@ -221,7 +231,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: true)); break; case ValueState.Collection: + case ValueState.Dictionary: case ValueState.ValueStruct: + case ValueState.Color: SetIValueState(); SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true)); break; @@ -229,6 +241,8 @@ namespace UnityExplorer.UI.Inspectors.CacheObject SetValueState(cell, new ValueStateArgs(true, inspectActive: true)); break; } + + cell.RefreshSubcontentButton(); } protected virtual void SetValueState(CacheObjectCell cell, ValueStateArgs args) @@ -285,11 +299,11 @@ namespace UnityExplorer.UI.Inspectors.CacheObject IValue = (InteractiveValue)Pool.Borrow(ivalueType); CurrentIValueType = ivalueType; - IValue.SetOwner(this); + IValue.OnBorrowed(this); IValue.SetValue(this.Value); IValue.UIRoot.transform.SetParent(CellView.SubContentHolder.transform, false); CellView.SubContentHolder.SetActive(true); - SubContentState = true; + SubContentShowWanted = true; // update our cell after creating the ivalue (the value may have updated, make sure its consistent) this.ProcessOnEvaluate(); @@ -297,9 +311,11 @@ namespace UnityExplorer.UI.Inspectors.CacheObject } else { - SubContentState = !SubContentState; - CellView.SubContentHolder.SetActive(SubContentState); + SubContentShowWanted = !SubContentShowWanted; + CellView.SubContentHolder.SetActive(SubContentShowWanted); } + + CellView.RefreshSubcontentButton(); } public virtual void ReleaseIValue() @@ -335,13 +351,14 @@ namespace UnityExplorer.UI.Inspectors.CacheObject SetUserValue(this.CellView.Toggle.isOn); else { - if (!numberParseMethods.ContainsKey(FallbackType.AssemblyQualifiedName)) + var type = Value.GetActualType(); + if (!numberParseMethods.ContainsKey(type.AssemblyQualifiedName)) { - var method = FallbackType.GetMethod("Parse", new Type[] { typeof(string) }); - numberParseMethods.Add(FallbackType.AssemblyQualifiedName, method); + var method = type.GetMethod("Parse", new Type[] { typeof(string) }); + numberParseMethods.Add(type.AssemblyQualifiedName, method); } - var val = numberParseMethods[FallbackType.AssemblyQualifiedName] + var val = numberParseMethods[type.AssemblyQualifiedName] .Invoke(null, new object[] { CellView.InputField.text }); SetUserValue(val); } diff --git a/src/UI/Inspectors/CacheObject/CacheProperty.cs b/src/UI/Inspectors/CacheObject/CacheProperty.cs index 679b02c..1981a09 100644 --- a/src/UI/Inspectors/CacheObject/CacheProperty.cs +++ b/src/UI/Inspectors/CacheObject/CacheProperty.cs @@ -10,6 +10,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public PropertyInfo PropertyInfo { get; internal set; } public override Type DeclaringType => PropertyInfo.DeclaringType; + public override bool CanWrite => PropertyInfo.CanWrite; public override bool ShouldAutoEvaluate => !HasArguments; @@ -17,7 +18,6 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { base.SetInspectorOwner(inspector, member); - this.CanWrite = PropertyInfo.CanWrite; Arguments = PropertyInfo.GetIndexParameters(); } @@ -25,7 +25,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { try { - Value = PropertyInfo.GetValue(ParentInspector.Target.TryCast(DeclaringType), null); + Value = PropertyInfo.GetValue(Owner.Target.TryCast(DeclaringType), null); } catch (Exception ex) { @@ -43,7 +43,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { // TODO property indexers - PropertyInfo.SetValue(PropertyInfo.GetSetMethod().IsStatic ? null : ParentInspector.Target, value, null); + PropertyInfo.SetValue(PropertyInfo.GetSetMethod().IsStatic ? null : Owner.Target, value, null); } catch (Exception ex) { diff --git a/src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs new file mode 100644 index 0000000..72e728c --- /dev/null +++ b/src/UI/Inspectors/CacheObject/Views/CacheKeyValuePairCell.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors.CacheObject.Views +{ + public class CacheKeyValuePairCell : CacheObjectCell + { + public Image Image { get; private set; } + public InteractiveDictionary DictOwner => Occupant.Owner as InteractiveDictionary; + + public LayoutElement KeyGroupLayout; + public Text KeyLabel; + public ButtonRef KeyInspectButton; + public InputField KeyInputField; + public Text KeyInputTypeLabel; + + 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; + + private void KeyInspectClicked() + { + InspectorManager.Inspect((Occupant as CacheKeyValuePair).DictKey, this.Occupant); + } + + public override GameObject CreateContent(GameObject parent) + { + var root = base.CreateContent(parent); + + Image = root.AddComponent(); + + this.NameLayout.minWidth = 40; + this.NameLayout.flexibleWidth = 50; + this.NameLayout.minHeight = 30; + this.NameLabel.alignment = TextAnchor.MiddleRight; + + this.RightGroupLayout.minWidth = HalfWidth; + + // Key area + var keyGroup = UIFactory.CreateUIObject("KeyHolder", root.transform.Find("HoriGroup").gameObject); + UIFactory.SetLayoutGroup(keyGroup, false, false, true, true, 2, 0, 0, 4, 4, childAlignment: TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(keyGroup, minHeight: 30, minWidth: AdjustedKeyWidth, flexibleWidth: 0); + KeyGroupLayout = keyGroup.GetComponent(); + + // set to be after the NameLabel (our index label), and before the main horizontal group. + keyGroup.transform.SetSiblingIndex(1); + + // key Inspect + + KeyInspectButton = UIFactory.CreateButton(keyGroup, "KeyInspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(KeyInspectButton.Button.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + InspectButton.OnClick += KeyInspectClicked; + + // label + + KeyLabel = UIFactory.CreateLabel(keyGroup, "KeyLabel", "not set (key)", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(KeyLabel.gameObject, minWidth: 50, flexibleWidth: 999, minHeight: 30); + + // Type label for input field + + KeyInputTypeLabel = UIFactory.CreateLabel(keyGroup, "InputTypeLabel", "not set", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(KeyInputTypeLabel.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 30); + + // input field + + var keyInputObj = UIFactory.CreateInputField(keyGroup, "KeyInput", "not set", out KeyInputField); + UIFactory.SetLayoutElement(keyInputObj, minHeight: 30, flexibleHeight: 0, flexibleWidth: 200); + //KeyInputField.lineType = InputField.LineType.MultiLineNewline; + KeyInputField.readOnly = true; + + return root; + } + + protected override void ConstructEvaluateHolder(GameObject parent) + { + // not used + } + + //protected override void ConstructUpdateToggle(GameObject parent) + //{ + // // not used + //} + } +} diff --git a/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs index 422eab9..fd7e5b3 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs +++ b/src/UI/Inspectors/CacheObject/Views/CacheListEntryCell.cs @@ -3,20 +3,29 @@ using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; +using UnityEngine.UI; using UnityExplorer.UI.Inspectors.IValues; namespace UnityExplorer.UI.Inspectors.CacheObject.Views { public class CacheListEntryCell : CacheObjectCell { - public InteractiveList ListOwner { get; set; } + public Image Image { get; private set; } + public InteractiveList ListOwner => Occupant.Owner as InteractiveList; + + 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 override GameObject CreateContent(GameObject parent) { var root = base.CreateContent(parent); - this.NameLayout.minWidth = 50; - this.NameLayout.flexibleWidth = 50f; + Image = root.AddComponent(); + + this.NameLayout.minWidth = 40; + this.NameLayout.flexibleWidth = 50; + this.NameLayout.minHeight = 30; + this.NameLabel.alignment = TextAnchor.MiddleRight; return root; } diff --git a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs index d17ed3d..b6c6678 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs +++ b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs @@ -64,7 +64,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views protected virtual void InspectClicked() { - InspectorManager.Inspect(Occupant.Value); + InspectorManager.Inspect(Occupant.Value, this.Occupant); } protected virtual void ToggleClicked(bool value) @@ -77,6 +77,23 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views this.Occupant.OnCellSubContentToggle(); } + private readonly Color subInactiveColor = new Color(0.23f, 0.23f, 0.23f); + private readonly Color subActiveColor = new Color(0.23f, 0.33f, 0.23f); + + public void RefreshSubcontentButton() + { + if (!this.SubContentHolder.activeSelf) + { + this.SubContentButton.ButtonText.text = "▲"; + RuntimeProvider.Instance.SetColorBlock(SubContentButton.Button, subInactiveColor, subInactiveColor * 1.3f); + } + else + { + this.SubContentButton.ButtonText.text = "▼"; + RuntimeProvider.Instance.SetColorBlock(SubContentButton.Button, subActiveColor, subActiveColor * 1.3f); + } + } + protected abstract void ConstructEvaluateHolder(GameObject parent); // protected abstract void ConstructUpdateToggle(GameObject parent); @@ -89,16 +106,16 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); Rect = UIRoot.GetComponent(); - UIFactory.SetLayoutGroup(UIRoot, true, false, true, true, 0, 0); + UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 0, 0); UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - var content = UIFactory.CreateUIObject("Content", UIRoot); - UIFactory.SetLayoutGroup(content, true, false, true, true, 2, 0); - UIFactory.SetLayoutElement(content, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); - content.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + //var content = UIFactory.CreateUIObject("Content", UIRoot); + //UIFactory.SetLayoutGroup(content, true, false, true, true, 2, 0); + //UIFactory.SetLayoutElement(content, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); + //content.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - var horiRow = UIFactory.CreateUIObject("HoriGroup", content); + var horiRow = UIFactory.CreateUIObject("HoriGroup", UIRoot); UIFactory.SetLayoutElement(horiRow, minHeight: 29, flexibleHeight: 150, flexibleWidth: 9999); UIFactory.SetLayoutGroup(horiRow, false, false, true, true, 5, 2, childAlignment: TextAnchor.UpperLeft); horiRow.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; @@ -125,7 +142,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views UIFactory.SetLayoutGroup(rightHoriGroup, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft); UIFactory.SetLayoutElement(rightHoriGroup, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800); - SubContentButton = UIFactory.CreateButton(rightHoriGroup, "SubContentButton", "▲"); + SubContentButton = UIFactory.CreateButton(rightHoriGroup, "SubContentButton", "▲", subInactiveColor); UIFactory.SetLayoutElement(SubContentButton.Button.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); SubContentButton.OnClick += SubContentClicked; @@ -163,10 +180,10 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views // Subcontent - SubContentHolder = UIFactory.CreateUIObject("SubContent", content); - UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 500, minWidth: 100, flexibleWidth: 9999); - UIFactory.SetLayoutGroup(SubContentHolder, true, false, true, true, 2, childAlignment: TextAnchor.UpperLeft); - + SubContentHolder = UIFactory.CreateUIObject("SubContent", UIRoot); + UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 600, minWidth: 100, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(SubContentHolder, true, true, true, true, 2, childAlignment: TextAnchor.UpperLeft); + //SubContentHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.MinSize; SubContentHolder.SetActive(false); // Bottom separator diff --git a/src/UI/Inspectors/GameObjectInspector.cs b/src/UI/Inspectors/GameObjectInspector.cs index 4cf2974..1883293 100644 --- a/src/UI/Inspectors/GameObjectInspector.cs +++ b/src/UI/Inspectors/GameObjectInspector.cs @@ -14,7 +14,8 @@ namespace UnityExplorer.UI.Inspectors { public class GameObjectInspector : InspectorBase { - public GameObject Target; + //public GameObject Target; + public GameObject GOTarget => Target as GameObject; private Text NameText; @@ -32,8 +33,8 @@ namespace UnityExplorer.UI.Inspectors Target = target as GameObject; - NameText.text = Target.name; - Tab.TabText.text = $"[G] {Target.name}"; + NameText.text = GOTarget.name; + Tab.TabText.text = $"[G] {GOTarget.name}"; RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); } @@ -84,15 +85,15 @@ namespace UnityExplorer.UI.Inspectors UpdateComponents(); - Tab.TabText.text = $"[G] {Target.name}"; + Tab.TabText.text = $"[G] {GOTarget.name}"; } } private IEnumerable GetTransformEntries() { _rootEntries.Clear(); - for (int i = 0; i < Target.transform.childCount; i++) - _rootEntries.Add(Target.transform.GetChild(i).gameObject); + for (int i = 0; i < GOTarget.transform.childCount; i++) + _rootEntries.Add(GOTarget.transform.GetChild(i).gameObject); return _rootEntries; } @@ -141,7 +142,7 @@ namespace UnityExplorer.UI.Inspectors private void UpdateComponents() { _componentEntries.Clear(); - var comps = Target.GetComponents(); + var comps = GOTarget.GetComponents(); foreach (var comp in comps) _componentEntries.Add(comp); diff --git a/src/UI/Inspectors/ICacheObjectController.cs b/src/UI/Inspectors/ICacheObjectController.cs new file mode 100644 index 0000000..383e075 --- /dev/null +++ b/src/UI/Inspectors/ICacheObjectController.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.UI.Inspectors.CacheObject; + +namespace UnityExplorer.UI.Inspectors +{ + public interface ICacheObjectController + { + CacheObjectBase ParentCacheObject { get; } // TODO + + object Target { get; } + Type TargetType { get; } + + bool CanWrite { get; } + } +} diff --git a/src/UI/Inspectors/IValues/InteractiveDictionary.cs b/src/UI/Inspectors/IValues/InteractiveDictionary.cs new file mode 100644 index 0000000..c18f025 --- /dev/null +++ b/src/UI/Inspectors/IValues/InteractiveDictionary.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections; +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.Utility; +using UnityExplorer.UI.Widgets; + +namespace UnityExplorer.UI.Inspectors.IValues +{ + public class InteractiveDictionary : InteractiveValue, IPoolDataSource, ICacheObjectController + { + CacheObjectBase ICacheObjectController.ParentCacheObject => this.CurrentOwner; + object ICacheObjectController.Target => this.CurrentOwner.Value; + public Type TargetType { get; private set; } + + public override bool CanWrite => false;// TODO RefIDictionary != null && !RefIDictionary.IsReadOnly; + + public Type KeyType; + public Type ValueType; + public IDictionary RefIDictionary; + + public int ItemCount => values.Count; + private readonly List keys = new List(); + private readonly List values = new List(); + private readonly List cachedEntries = new List(); + + public ScrollPool DictScrollPool { get; private set; } + + public Text TopLabel; + + public LayoutElement KeyTitleLayout; + public LayoutElement ValueTitleLayout; + + public override void OnBorrowed(CacheObjectBase owner) + { + base.OnBorrowed(owner); + } + + public override void ReleaseFromOwner() + { + base.ReleaseFromOwner(); + + ClearAndRelease(); + } + + private void ClearAndRelease() + { + keys.Clear(); + values.Clear(); + + foreach (var entry in cachedEntries) + entry.ReleasePooledObjects(); + + cachedEntries.Clear(); + } + + public override void SetValue(object value) + { + if (value == null) + { + // should never be null + if (keys.Any()) + ClearAndRelease(); + } + else + { + var type = value.GetActualType(); + if (type.IsGenericType && type.GetGenericArguments().Length == 2) + { + KeyType = type.GetGenericArguments()[0]; + ValueType = type.GetGenericArguments()[1]; + } + else + { + KeyType = typeof(object); + ValueType = typeof(object); + } + + CacheEntries(value); + + TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.ParseFullType(type, true)}"; + } + + + this.DictScrollPool.Refresh(true, false); + } + + private void CacheEntries(object value) + { + RefIDictionary = value as IDictionary; + + if (RefIDictionary == null) + { + // todo il2cpp + return; + } + + keys.Clear(); + foreach (var k in RefIDictionary.Keys) + keys.Add(k); + + values.Clear(); + foreach (var v in RefIDictionary.Values) + values.Add(v); + + int idx = 0; + for (int i = 0; i < keys.Count; i++) + { + CacheKeyValuePair cache; + if (idx >= cachedEntries.Count) + { + cache = new CacheKeyValuePair(); + cache.SetDictOwner(this, i); + cachedEntries.Add(cache); + } + else + cache = cachedEntries[i]; + + cache.SetFallbackType(ValueType); + cache.SetKey(keys[i]); + cache.SetValueFromSource(values[i]); + + idx++; + } + + // Remove excess cached entries if dict count decreased + if (cachedEntries.Count > values.Count) + { + for (int i = cachedEntries.Count - 1; i >= values.Count; i--) + { + var cache = cachedEntries[i]; + if (cache.CellView != null) + { + cache.CellView.Occupant = null; + cache.CellView = null; + } + cache.ReleasePooledObjects(); + cachedEntries.RemoveAt(i); + } + } + } + + // KVP entry scroll pool + + public void OnCellBorrowed(CacheKeyValuePairCell cell) + { + + } + + public void SetCell(CacheKeyValuePairCell cell, int index) + { + if (index < 0 || index >= cachedEntries.Count) + { + if (cell.Occupant != null) + { + cell.Occupant.CellView = null; + cell.Occupant = null; + } + + cell.Disable(); + return; + } + + var entry = cachedEntries[index]; + + if (entry != cell.Occupant) + { + if (cell.Occupant != null) + { + cell.Occupant.HideIValue(); + cell.Occupant.CellView = null; + cell.Occupant = null; + } + + cell.Occupant = entry; + entry.CellView = cell; + } + + entry.SetCell(cell); + + SetCellLayout(cell); + } + + public override void SetLayout() + { + var minHeight = 5f; + + foreach (var cell in DictScrollPool.CellPool) + { + SetCellLayout(cell); + if (cell.Enabled) + minHeight += cell.Rect.rect.height; + } + + this.scrollLayout.minHeight = Math.Min(400f, minHeight); + } + + private void SetCellLayout(CacheKeyValuePairCell cell) + { + cell.KeyGroupLayout.minWidth = cell.AdjustedKeyWidth; + cell.RightGroupLayout.minWidth = cell.HalfWidth; + + if (cell.Occupant?.IValue != null) + cell.Occupant.IValue.SetLayout(); + } + + private LayoutElement scrollLayout; + + public override GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveDict", true, true, true, true, 6, new Vector4(10, 3, 15, 4), + new Color(0.05f, 0.05f, 0.05f)); + UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 475); + UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + // Entries label + + TopLabel = UIFactory.CreateLabel(UIRoot, "EntryLabel", "not set", TextAnchor.MiddleLeft, fontSize: 16); + TopLabel.horizontalOverflow = HorizontalWrapMode.Overflow; + + // key / value titles + + var titleGroup = UIFactory.CreateUIObject("TitleGroup", UIRoot); + UIFactory.SetLayoutElement(titleGroup, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 0); + UIFactory.SetLayoutGroup(titleGroup, true, true, true, true, padLeft: 50, padRight: 65); + + var keyTitle = UIFactory.CreateLabel(titleGroup, "KeyTitle", "Keys", TextAnchor.MiddleLeft); + //UIFactory.SetLayoutElement(keyTitle.gameObject, minWidth: ReflectionInspector.LeftGroupWidth); + KeyTitleLayout = keyTitle.GetComponent(); + + var valueTitle = UIFactory.CreateLabel(titleGroup, "ValueTitle", "Values", TextAnchor.MiddleLeft); + //UIFactory.SetLayoutElement(valueTitle.gameObject, minWidth: ReflectionInspector.RightGroupWidth); + ValueTitleLayout = valueTitle.GetComponent(); + + // entry scroll pool + + DictScrollPool = UIFactory.CreateScrollPool(UIRoot, "EntryList", out GameObject scrollObj, + out GameObject _, new Color(0.09f, 0.09f, 0.09f)); + UIFactory.SetLayoutElement(scrollObj, minHeight: 150, flexibleHeight: 0); + DictScrollPool.Initialize(this, SetLayout); + scrollLayout = scrollObj.GetComponent(); + + return UIRoot; + } + } +} \ No newline at end of file diff --git a/src/UI/Inspectors/IValues/InteractiveList.cs b/src/UI/Inspectors/IValues/InteractiveList.cs index 9a0f40e..1b3304f 100644 --- a/src/UI/Inspectors/IValues/InteractiveList.cs +++ b/src/UI/Inspectors/IValues/InteractiveList.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; using UnityEngine; using UnityEngine.UI; using UnityExplorer.UI.Inspectors.CacheObject; @@ -12,16 +11,15 @@ using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Inspectors.IValues { - // TODO - // - set fallback type from generic arguments - // - handle setting through IList - // - handle il2cpp lists - - public class InteractiveList : InteractiveValue, IPoolDataSource + public class InteractiveList : InteractiveValue, IPoolDataSource, ICacheObjectController { - public override bool CanWrite => base.CanWrite && RefIList != null; + CacheObjectBase ICacheObjectController.ParentCacheObject => this.CurrentOwner; + object ICacheObjectController.Target => this.CurrentOwner.Value; + public Type TargetType { get; private set; } - public Type FallbackEntryType; + public override bool CanWrite => RefIList != null && !RefIList.IsReadOnly; + + public Type EntryType; public IEnumerable RefIEnumerable; public IList RefIList; @@ -31,14 +29,27 @@ namespace UnityExplorer.UI.Inspectors.IValues public ScrollPool ListScrollPool { get; private set; } - public LayoutElement ScrollPoolLayout; public Text TopLabel; - public override void SetOwner(CacheObjectBase owner) + public override void OnBorrowed(CacheObjectBase owner) { - base.SetOwner(owner); + base.OnBorrowed(owner); } + public override void SetLayout() + { + var minHeight = 5f; + + foreach (var cell in ListScrollPool.CellPool) + { + if (cell.Enabled) + minHeight += cell.Rect.rect.height; + } + + this.scrollLayout.minHeight = Math.Min(400f, minHeight); + } + + public override void ReleaseFromOwner() { base.ReleaseFromOwner(); @@ -56,33 +67,28 @@ namespace UnityExplorer.UI.Inspectors.IValues cachedEntries.Clear(); } - // TODO temp for testing, needs improvement public override void SetValue(object value) { - //// TEMP - //if (values.Any()) - // ClearAndRelease(); - if (value == null) { + // should never be null if (values.Any()) ClearAndRelease(); } else { - // todo can improve this var type = value.GetActualType(); if (type.IsGenericType) - FallbackEntryType = type.GetGenericArguments()[0]; + EntryType = type.GetGenericArguments()[0]; else - FallbackEntryType = typeof(object); + EntryType = typeof(object); CacheEntries(value); + + TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.ParseFullType(type, true)}"; } - TopLabel.text = $"{cachedEntries.Count} entries"; - - this.ScrollPoolLayout.minHeight = Math.Min(400f, 35f * values.Count); + //this.ScrollPoolLayout.minHeight = Math.Min(400f, 35f * values.Count); this.ListScrollPool.Refresh(true, false); } @@ -93,7 +99,8 @@ namespace UnityExplorer.UI.Inspectors.IValues if (RefIEnumerable == null) { - // todo il2cpp ...? + // todo il2cpp + return; } values.Clear(); @@ -113,7 +120,7 @@ namespace UnityExplorer.UI.Inspectors.IValues else cache = cachedEntries[idx]; - cache.Initialize(this.FallbackEntryType); + cache.SetFallbackType(this.EntryType); cache.SetValueFromSource(entry); idx++; } @@ -139,7 +146,7 @@ namespace UnityExplorer.UI.Inspectors.IValues public void OnCellBorrowed(CacheListEntryCell cell) { - cell.ListOwner = this; + } public void SetCell(CacheListEntryCell cell, int index) @@ -174,26 +181,29 @@ namespace UnityExplorer.UI.Inspectors.IValues entry.SetCell(cell); } + private LayoutElement scrollLayout; + public override GameObject CreateContent(GameObject parent) { - UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveList", true, true, true, true, 2, new Vector4(4, 4, 4, 4), + UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveList", true, true, true, true, 6, new Vector4(10, 3, 15, 4), new Color(0.05f, 0.05f, 0.05f)); - UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 600); + UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; // Entries label - TopLabel = UIFactory.CreateLabel(UIRoot, "EntryLabel", "not set", TextAnchor.MiddleLeft); + TopLabel = UIFactory.CreateLabel(UIRoot, "EntryLabel", "not set", TextAnchor.MiddleLeft, fontSize: 16); + TopLabel.horizontalOverflow = HorizontalWrapMode.Overflow; // entry scroll pool ListScrollPool = UIFactory.CreateScrollPool(UIRoot, "EntryList", out GameObject scrollObj, out GameObject _, new Color(0.09f, 0.09f, 0.09f)); - UIFactory.SetLayoutElement(scrollObj, minHeight: 25, flexibleHeight: 0); - ListScrollPool.Initialize(this); - ScrollPoolLayout = scrollObj.GetComponent(); + UIFactory.SetLayoutElement(scrollObj, minHeight: 400, flexibleHeight: 0); + ListScrollPool.Initialize(this, SetLayout); + scrollLayout = scrollObj.GetComponent(); return UIRoot; } } -} +} \ No newline at end of file diff --git a/src/UI/Inspectors/IValues/InteractiveValue.cs b/src/UI/Inspectors/IValues/InteractiveValue.cs index b680371..7d669de 100644 --- a/src/UI/Inspectors/IValues/InteractiveValue.cs +++ b/src/UI/Inspectors/IValues/InteractiveValue.cs @@ -22,6 +22,8 @@ namespace UnityExplorer.UI.Inspectors.IValues public object EditedValue { get; private set; } + public virtual void SetLayout() { } + public static Type GetIValueTypeForState(ValueState state) { switch (state) @@ -32,8 +34,8 @@ namespace UnityExplorer.UI.Inspectors.IValues // return null; case ValueState.Collection: return typeof(InteractiveList); - //case ValueState.Dictionary: - // return null; + case ValueState.Dictionary: + return typeof(InteractiveDictionary); //case ValueState.ValueStruct: // return null; //case ValueState.Color: @@ -42,7 +44,7 @@ namespace UnityExplorer.UI.Inspectors.IValues } } - public virtual void SetOwner(CacheObjectBase owner) + public virtual void OnBorrowed(CacheObjectBase owner) { if (this.m_owner != null) { diff --git a/src/UI/Inspectors/InspectorBase.cs b/src/UI/Inspectors/InspectorBase.cs index 14ff84f..219497d 100644 --- a/src/UI/Inspectors/InspectorBase.cs +++ b/src/UI/Inspectors/InspectorBase.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; @@ -11,7 +12,7 @@ namespace UnityExplorer.UI.Inspectors public abstract class InspectorBase : IPooledObject { public bool IsActive { get; internal set; } - public object InspectorTarget { get; internal set; } + public object Target { get; set; } public InspectorTab Tab { get; internal set; } @@ -24,6 +25,7 @@ namespace UnityExplorer.UI.Inspectors public virtual void OnBorrowedFromPool(object target) { + this.Target = target; Tab = Pool.Borrow(); Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false); @@ -44,6 +46,7 @@ namespace UnityExplorer.UI.Inspectors Tab.SetTabColor(true); UIRoot.SetActive(true); IsActive = true; + LayoutRebuilder.ForceRebuildLayoutImmediate(UIRoot.GetComponent()); } public virtual void OnSetInactive() diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs index e8c4758..a2d6981 100644 --- a/src/UI/Inspectors/InspectorManager.cs +++ b/src/UI/Inspectors/InspectorManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.UI.Inspectors.CacheObject; using UnityExplorer.UI.ObjectPool; using UnityExplorer.UI.Panels; @@ -17,7 +18,7 @@ namespace UnityExplorer.UI.Inspectors public static float PanelWidth; - public static void Inspect(object obj) + public static void Inspect(object obj, CacheObjectBase sourceCache = null) { if (obj.IsNullOrDestroyed()) return; @@ -27,17 +28,21 @@ namespace UnityExplorer.UI.Inspectors if (TryFocusActiveInspector(obj)) return; + // var type = obj.GetActualType(); + //if (type.IsEnumerable()) + // CreateInspector(obj, false, sourceCache); + //// todo dict if (obj is GameObject) CreateInspector(obj); else - CreateInspector(obj); + CreateInspector(obj, false, sourceCache); } private static bool TryFocusActiveInspector(object target) { foreach (var inspector in Inspectors) { - if (inspector.InspectorTarget.ReferenceEqual(target)) + if (inspector.Target.ReferenceEqual(target)) { UIManager.SetPanelActive(UIManager.Panels.Inspector, true); SetInspectorActive(inspector); @@ -66,11 +71,14 @@ namespace UnityExplorer.UI.Inspectors ActiveInspector.OnSetInactive(); } - private static void CreateInspector(object target, bool staticReflection = false) where T : InspectorBase + private static void CreateInspector(object target, bool staticReflection = false, CacheObjectBase sourceCache = null) where T : InspectorBase { var inspector = Pool.Borrow(); Inspectors.Add(inspector); - inspector.InspectorTarget = target; + inspector.Target = target; + + if (sourceCache != null && inspector is ReflectionInspector ri) + ri.ParentCacheObject = sourceCache; UIManager.SetPanelActive(UIManager.Panels.Inspector, true); inspector.UIRoot.transform.SetParent(InspectorPanel.Instance.ContentHolder.transform, false); diff --git a/src/UI/Inspectors/ListInspector.cs b/src/UI/Inspectors/ListInspector.cs new file mode 100644 index 0000000..31b5c83 --- /dev/null +++ b/src/UI/Inspectors/ListInspector.cs @@ -0,0 +1,197 @@ +//using System; +//using System.Collections; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using UnityEngine.UI; +//using UnityExplorer.UI.Inspectors.CacheObject; +//using UnityExplorer.UI.Inspectors.CacheObject.Views; +//using UnityExplorer.UI.Utility; +//using UnityExplorer.UI.Widgets; + +//namespace UnityExplorer.UI.Inspectors +//{ +// // TODO +// // - set fallback type from generic arguments +// // - handle setting through IList +// // - handle il2cpp lists + +// public class ListInspector : InspectorBase, IPoolDataSource, ICacheObjectController +// { +// // TODO +// public CacheObjectBase ParentCacheObject { get; set; } + +// public Type TargetType { get; private set; } + +// public bool CanWrite => RefIList != null && !RefIList.IsReadOnly; + +// public Type EntryType; +// public IEnumerable RefIEnumerable; +// public IList RefIList; + +// public int ItemCount => values.Count; +// private readonly List values = new List(); +// private readonly List cachedEntries = new List(); + +// public ScrollPool ListScrollPool { get; private set; } + +// public LayoutElement ScrollPoolLayout; +// public Text TopLabel; + +// public override void OnBorrowedFromPool(object target) +// { +// base.OnBorrowedFromPool(target); + +// var type = target.GetActualType(); +// if (type.IsGenericType) +// EntryType = type.GetGenericArguments()[0]; +// else +// EntryType = typeof(object); + +// // Set tab + +// Tab.TabText.text = $"[L] {SignatureHighlighter.ParseFullType(type, false)}"; + +// // Get cache entries + +// CacheEntries(target); + +// TopLabel.text = $"{cachedEntries.Count} entries"; + +// this.ListScrollPool.Refresh(true, false); +// } + +// public override void OnReturnToPool() +// { +// base.OnReturnToPool(); + +// values.Clear(); + +// foreach (var entry in cachedEntries) +// entry.ReleasePooledObjects(); + +// cachedEntries.Clear(); +// } + +// public override void Update() +// { +// // ... +// } + +// protected override void OnCloseClicked() +// { +// InspectorManager.ReleaseInspector(this); +// } + +// private void CacheEntries(object value) +// { +// RefIEnumerable = value as IEnumerable; +// RefIList = value as IList; + +// if (RefIEnumerable == null) +// { +// // todo il2cpp ...? +// } + +// values.Clear(); +// int idx = 0; +// foreach (var entry in RefIEnumerable) +// { +// values.Add(entry); + +// // If list count increased, create new cache entries +// CacheListEntry cache; +// if (idx >= cachedEntries.Count) +// { +// cache = new CacheListEntry(); +// cache.SetListOwner(this, idx); +// cachedEntries.Add(cache); +// } +// else +// cache = cachedEntries[idx]; + +// cache.Initialize(this.EntryType); +// cache.SetValueFromSource(entry); +// idx++; +// } + +// // Remove excess cached entries if list count decreased +// if (cachedEntries.Count > values.Count) +// { +// for (int i = cachedEntries.Count - 1; i >= values.Count; i--) +// { +// var cache = cachedEntries[i]; +// if (cache.CellView != null) +// { +// cache.CellView.Occupant = null; +// cache.CellView = null; +// } +// cache.ReleasePooledObjects(); +// cachedEntries.RemoveAt(i); +// } +// } +// } + +// // List entry scroll pool + +// public void OnCellBorrowed(CacheListEntryCell cell) +// { +// cell.ListOwner = this; +// } + +// 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.Disable(); +// return; +// } + +// var entry = cachedEntries[index]; + +// if (entry != cell.Occupant) +// { +// if (cell.Occupant != null) +// { +// cell.Occupant.HideIValue(); +// cell.Occupant.CellView = null; +// cell.Occupant = null; +// } + +// cell.Occupant = entry; +// entry.CellView = cell; +// } + +// entry.SetCell(cell); +// } + +// public override GameObject CreateContent(GameObject parent) +// { +// UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveList", true, true, true, true, 2, new Vector4(4, 4, 4, 4), +// new Color(0.05f, 0.05f, 0.05f)); + +// UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 600); + +// // Entries label + +// TopLabel = UIFactory.CreateLabel(UIRoot, "EntryLabel", "not set", TextAnchor.MiddleLeft); + +// // entry scroll pool + +// ListScrollPool = UIFactory.CreateScrollPool(UIRoot, "EntryList", out GameObject scrollObj, +// out GameObject _, new Color(0.09f, 0.09f, 0.09f)); +// UIFactory.SetLayoutElement(scrollObj, minHeight: 25, flexibleHeight: 9999); +// ListScrollPool.Initialize(this); +// ScrollPoolLayout = scrollObj.GetComponent(); + +// return UIRoot; +// } +// } +//} diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index 80dfdfd..ef9525b 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -16,12 +16,16 @@ using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Inspectors { - public class ReflectionInspector : InspectorBase, IPoolDataSource + public class ReflectionInspector : InspectorBase, IPoolDataSource, ICacheObjectController { + // TODO + public CacheObjectBase ParentCacheObject { get; set; } + public bool StaticOnly { get; internal set; } - public object Target { get; private set; } + //public object Target { get; private set; } public Type TargetType { get; private set; } + public bool CanWrite => true; public ScrollPool MemberScrollPool { get; private set; } @@ -81,11 +85,12 @@ namespace UnityExplorer.UI.Inspectors } else { - Target = target; TargetType = target.GetActualType(); prefix = "[R]"; } + Tab.TabText.text = $"{prefix} {SignatureHighlighter.ParseFullType(TargetType)}"; + NameText.text = SignatureHighlighter.ParseFullSyntax(TargetType, true); string asmText; @@ -95,7 +100,6 @@ namespace UnityExplorer.UI.Inspectors asmText = $"{TargetType.Assembly.GetName().Name} (in memory)"; AssemblyText.text = $"Assembly: {asmText}"; - Tab.TabText.text = $"{prefix} {SignatureHighlighter.ParseFullType(TargetType)}"; this.members = CacheMember.GetCacheMembers(Target, TargetType, this); FilterMembers(); @@ -217,22 +221,25 @@ namespace UnityExplorer.UI.Inspectors // Cell layout (fake table alignment) - private static float MemLabelWidth { get; set; } - private static float RightGroupWidth { get; set; } + private static int LeftGroupWidth { get; set; } + private static int RightGroupWidth { get; set; } private void SetTitleLayouts() { // Calculate sizes - MemLabelWidth = Math.Max(200, Math.Min(450f, 0.4f * InspectorManager.PanelWidth - 5)); - RightGroupWidth = Math.Max(200, InspectorManager.PanelWidth - MemLabelWidth - 55); + 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); - memberTitleLayout.minWidth = MemLabelWidth; + memberTitleLayout.minWidth = LeftGroupWidth; } private void SetCellLayout(CacheObjectCell cell) { - cell.NameLayout.minWidth = MemLabelWidth; + cell.NameLayout.minWidth = LeftGroupWidth; cell.RightGroupLayout.minWidth = RightGroupWidth; + + if (cell.Occupant?.IValue != null) + cell.Occupant.IValue.SetLayout(); } internal void SetLayouts() diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs index 08e33ba..fe5b23e 100644 --- a/src/UI/Panels/CSConsolePanel.cs +++ b/src/UI/Panels/CSConsolePanel.cs @@ -51,6 +51,9 @@ namespace UnityExplorer.UI.Panels private void InvokeOnValueChanged(string value) { + if (value.Length == UIManager.MAX_INPUTFIELD_CHARS) + ExplorerCore.LogWarning($"Reached maximum InputField character length! ({UIManager.MAX_INPUTFIELD_CHARS})"); + if (Time.time <= m_timeOfLastInputInvoke) return; diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index 59d916d..3631ba2 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -531,7 +531,7 @@ namespace UnityExplorer.UI placeHolderRect.offsetMin = Vector2.zero; placeHolderRect.offsetMax = Vector2.zero; - SetLayoutElement(placeHolderObj, minWidth: 200, flexibleWidth: 5000); + //SetLayoutElement(placeHolderObj, minWidth: 20, flexibleWidth: 5000); inputField.placeholder = placeholderText; @@ -550,9 +550,10 @@ namespace UnityExplorer.UI inputTextRect.offsetMin = Vector2.zero; inputTextRect.offsetMax = Vector2.zero; - SetLayoutElement(inputTextObj, minWidth: 200, flexibleWidth: 5000); + //SetLayoutElement(inputTextObj, minWidth: 200, flexibleWidth: 5000); inputField.textComponent = inputText; + inputField.characterLimit = UIManager.MAX_INPUTFIELD_CHARS; new InputFieldRefresher(inputField); diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index aa6eaa6..9d7a843 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -52,6 +52,8 @@ namespace UnityExplorer.UI internal static readonly Color navButtonEnabledColor = new Color(0.2f, 0.4f, 0.28f); internal static readonly Color navButtonDisabledColor = new Color(0.25f, 0.25f, 0.25f); + public const int MAX_INPUTFIELD_CHARS = 16000; + public static UIPanel GetPanel(Panels panel) { switch (panel) diff --git a/src/UI/Widgets/ScrollPool/DataHeightCache.cs b/src/UI/Widgets/ScrollPool/DataHeightCache.cs index 7e61c80..bf370b2 100644 --- a/src/UI/Widgets/ScrollPool/DataHeightCache.cs +++ b/src/UI/Widgets/ScrollPool/DataHeightCache.cs @@ -63,10 +63,9 @@ namespace UnityExplorer.UI.Widgets // get the remainder of the start position divided by min height float rem = startPosition % DefaultHeight; - // if there is a remainder, this means the previous cell started in - // our first cell and they take priority, so reduce our height by - // (minHeight - remainder) to account for that. We need to fill that - // gap and reach the next cell before we take priority. + // if there is a remainder, this means the previous cell started in our first cell and + // they take priority, so reduce our height by (minHeight - remainder) to account for that. + // We need to fill that gap and reach the next cell before we take priority. if (rem != 0.0f) height -= (DefaultHeight - rem); @@ -107,10 +106,10 @@ namespace UnityExplorer.UI.Widgets rangeCache.RemoveAt(rangeCache.Count - 1); } - /// Get the data index at the specific position of the total height cache. + /// Get the data index at the specified position of the total height cache. public int GetDataIndexAtPosition(float desiredHeight) => GetDataIndexAtPosition(desiredHeight, out _); - /// Get the data index at the specific position of the total height cache. + /// Get the data index and DataViewInfo at the specified position of the total height cache. public int GetDataIndexAtPosition(float desiredHeight, out DataViewInfo cache) { cache = default; @@ -135,18 +134,18 @@ namespace UnityExplorer.UI.Widgets } /// Set a given data index with the specified value. - public void SetIndex(int dataIndex, float height, bool inRebuild = false) + public void SetIndex(int dataIndex, float height) { + // If the index being set is beyond the DataSource item count, prune and return. if (dataIndex >= ScrollPool.DataSource.ItemCount) { - if (heightCache.Count > dataIndex) - { - while (heightCache.Count > dataIndex) - RemoveLast(); - } + while (heightCache.Count > dataIndex) + RemoveLast(); return; } + // If the data index exceeds our cache count, fill the gap. + // This is done by the ScrollPool when the DataSource sets its initial count, or the count increases. if (dataIndex >= heightCache.Count) { while (dataIndex > heightCache.Count) @@ -155,12 +154,11 @@ namespace UnityExplorer.UI.Widgets return; } + // We are actually updating an index. First, update the height and the totalHeight. var cache = heightCache[dataIndex]; - var prevHeight = cache.height; - - var diff = height - prevHeight; - if (diff != 0.0f) + if (cache.height != height) { + var diff = height - cache.height; totalHeight += diff; cache.height = height; } @@ -172,93 +170,71 @@ namespace UnityExplorer.UI.Widgets cache.startPosition = prev.startPosition + prev.height; } + // Get the normalized range index (actually ceiling) and spread based on our start position and height int rangeIndex = GetRangeCeilingOfPosition(cache.startPosition); int spread = GetRangeSpread(cache.startPosition, height); - if (rangeCache.Count <= rangeIndex) + // If the previous item in the range cache is not the previous data index, there is a gap. + if (dataIndex > 0 && rangeCache.Count > rangeIndex && rangeCache[rangeIndex - 1] != (dataIndex - 1)) { - if (rangeCache[rangeCache.Count - 1] != dataIndex - 1) - { - // The previous index in the range cache is not the previous data index for the data we were given. - // Need to rebuild. - if (!inRebuild) - RebuildCache(); - else - throw new Exception($"DataHeightCache rebuild failed. Trying to set {rangeIndex} but current count is {rangeCache.Count}!"); - return; - } + // Recalculate start positions up to this index. The gap could be anywhere. + RecalculateStartPositions(dataIndex); + // Get the range index and spread again after rebuilding + rangeIndex = GetRangeCeilingOfPosition(cache.startPosition); + spread = GetRangeSpread(cache.startPosition, height); } + // Should never happen + if (rangeCache.Count <= rangeIndex || rangeCache[rangeIndex] != dataIndex) + throw new Exception($"Trying to set range index but cache is corrupt after rebuild!\r\n" + + $"dataIndex: {dataIndex}, rangeIndex: {rangeIndex}, rangeCache.Count: {rangeCache.Count}, " + + $"startPos: {cache.startPosition}/{TotalHeight}"); + if (spread != cache.normalizedSpread) { - // The cell's spread has changed, need to update. + ExplorerCore.Log("Updating spread for " + dataIndex + " from " + cache.normalizedSpread + " to " + spread); int spreadDiff = spread - cache.normalizedSpread; cache.normalizedSpread = spread; - if (rangeCache[rangeIndex] != dataIndex) - { - // In some rare cases we may not find our data index at the expected range index. - // We can make some educated guesses and find the real index pretty quickly. - int minStart = GetRangeIndexOfPosition(dataIndex * DefaultHeight); - if (minStart < 0) minStart = 0; - for (int i = minStart; i < rangeCache.Count; i++) - { - if (rangeCache[i] == dataIndex) - { - rangeIndex = i; - break; - } - - // If we somehow reached the end and didn't find the data index... - if (i == rangeCache.Count - 1) - { - if (!inRebuild) - RebuildCache(); - else - ExplorerCore.LogWarning($"DataHeightCache: Looking for range index of data {dataIndex} but " + - $"reached the end and didn't find it. Count: {rangeCache.Count}, last index: {rangeCache[rangeCache.Count - 1]}"); - return; - } - - // our data index is further down. add the min difference and try again. - // the iterator will add 1 on the next loop so account for that. - - int jmp = dataIndex - rangeCache[i] - 1; - i = (jmp < 1 ? i : i + jmp); - } - } - - if (spreadDiff > 0) - { - // need to insert - for (int i = 0; i < spreadDiff; i++) - { - if (rangeCache[rangeIndex] == dataIndex) - rangeCache.Insert(rangeIndex, dataIndex); - else - break; - } - } - else - { - // need to remove - for (int i = 0; i < -spreadDiff; i++) - { - if (rangeCache[rangeIndex] == dataIndex) - rangeCache.RemoveAt(rangeIndex); - else - break; - } - } + SetSpread(dataIndex, rangeIndex, spreadDiff); } } - private void RebuildCache() + private void SetSpread(int dataIndex, int rangeIndex, int spreadDiff) { - //start at 1 because 0's start pos is always 0 - for (int i = 1; i < heightCache.Count; i++) - SetIndex(i, heightCache[i].height, true); + if (spreadDiff > 0) + { + for (int i = 0; i < spreadDiff; i++) + rangeCache.Insert(rangeIndex, dataIndex); + } + else + { + for (int i = 0; i < -spreadDiff; i++) + rangeCache.RemoveAt(rangeIndex); + } + } + + private void RecalculateStartPositions(int toIndex) + { + if (heightCache.Count < 2) + return; + + DataViewInfo cache; + DataViewInfo prev = heightCache[0]; + for (int i = 1; i <= toIndex && i < heightCache.Count; i++) + { + cache = heightCache[i]; + + cache.startPosition = prev.startPosition + prev.height; + + var prevSpread = cache.normalizedSpread; + cache.normalizedSpread = GetRangeSpread(cache.startPosition, cache.height); + if (cache.normalizedSpread != prevSpread) + SetSpread(i, GetRangeCeilingOfPosition(cache.startPosition), cache.normalizedSpread - prevSpread); + + prev = cache; + } } } } diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index 4597f8f..6dd5417 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -98,6 +98,7 @@ namespace UnityExplorer.UI.Widgets private float timeofLastWriteLock; private float prevContentHeight = 1.0f; + private event Action onHeightChanged; public override void Update() { @@ -116,6 +117,8 @@ namespace UnityExplorer.UI.Widgets prevContentHeight = Content.rect.height; if (!writingLocked) OnValueChangedListener(Vector2.zero); + + onHeightChanged?.Invoke(); } } #endregion @@ -138,7 +141,7 @@ namespace UnityExplorer.UI.Widgets //private bool Initialized; /// Should be called only once, when the scroll pool is created. - public void Initialize(IPoolDataSource dataSource) + public void Initialize(IPoolDataSource dataSource, Action onHeightChangedListener = null) { this.DataSource = dataSource; HeightCache = new DataHeightCache(this); @@ -153,14 +156,16 @@ namespace UnityExplorer.UI.Widgets ScrollRect.vertical = true; ScrollRect.horizontal = false; - LayoutRebuilder.ForceRebuildLayoutImmediate(Content); - RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); + RuntimeProvider.Instance.StartCoroutine(InitCoroutine(onHeightChangedListener)); } - private IEnumerator InitCoroutine() + private readonly WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame(); + + private IEnumerator InitCoroutine(Action onHeightChangedListener) { ScrollRect.content.anchoredPosition = Vector2.zero; - yield return null; + //yield return null; + yield return waitForEndOfFrame; LayoutRebuilder.ForceRebuildLayoutImmediate(Content); @@ -184,7 +189,8 @@ namespace UnityExplorer.UI.Widgets // add onValueChanged listener after setup ScrollRect.onValueChanged.AddListener(OnValueChangedListener); - ExplorerCore.Log("ScrollPool Init finished"); + onHeightChanged += onHeightChangedListener; + onHeightChangedListener?.Invoke(); } private void SetScrollBounds() @@ -564,7 +570,11 @@ namespace UnityExplorer.UI.Widgets var scrollPos = topPos + Content.anchoredPosition.y; - val = (float)((decimal)scrollPos / (decimal)(TotalDataHeight - Viewport.rect.height)); + var viewHeight = TotalDataHeight - Viewport.rect.height; + if (viewHeight != 0.0f) + val = (float)((decimal)scrollPos / (decimal)(viewHeight)); + else + val = 0f; } bool prev = writingLocked; diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 7c7c055..82e5f8d 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -75,6 +75,8 @@ UnityExplorer.BIE5.Mono false true + none + false @@ -234,20 +236,25 @@ + + + + +