diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs index 1091a25..277cf0c 100644 --- a/src/Core/Config/ConfigManager.cs +++ b/src/Core/Config/ConfigManager.cs @@ -20,7 +20,6 @@ namespace UnityExplorer.Core.Config public static ConfigElement Force_Unlock_Mouse; public static ConfigElement Force_Unlock_Keybind; public static ConfigElement Aggressive_Force_Unlock; - //public static ConfigElement Default_Tab; public static ConfigElement Default_Page_Limit; public static ConfigElement Default_Output_Path; public static ConfigElement Log_Unity_Debug; @@ -84,7 +83,7 @@ namespace UnityExplorer.Core.Config Force_Unlock_Keybind = new ConfigElement("Force Unlock Keybind", "The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.", - KeyCode.F6); + KeyCode.None); Aggressive_Force_Unlock = new ConfigElement("Aggressive Mouse Unlock", "Use Camera.onPostRender callback to aggressively force the Mouse to be unlocked (requires game restart).", diff --git a/src/Core/Input/InputManager.cs b/src/Core/Input/InputManager.cs index 6d0f11e..624def9 100644 --- a/src/Core/Input/InputManager.cs +++ b/src/Core/Input/InputManager.cs @@ -20,8 +20,19 @@ namespace UnityExplorer.Core.Input public static Vector3 MousePosition => m_inputModule.MousePosition; - public static bool GetKeyDown(KeyCode key) => m_inputModule.GetKeyDown(key); - public static bool GetKey(KeyCode key) => m_inputModule.GetKey(key); + public static bool GetKeyDown(KeyCode key) + { + if (key == KeyCode.None) + return false; + return m_inputModule.GetKeyDown(key); + } + + public static bool GetKey(KeyCode key) + { + if (key == KeyCode.None) + return false; + return m_inputModule.GetKey(key); + } public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn); public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn); diff --git a/src/Core/ReflectionUtility.cs b/src/Core/ReflectionUtility.cs index 343f335..27a875b 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/ReflectionUtility.cs @@ -69,7 +69,8 @@ namespace UnityExplorer /// The Type to check /// True if the Type is assignable to IEnumerable, otherwise false. public static bool IsEnumerable(this Type t) - => ReflectionProvider.Instance.IsAssignableFrom(typeof(IEnumerable), t); + => !typeof(UnityEngine.Transform).IsAssignableFrom(t) + && ReflectionProvider.Instance.IsAssignableFrom(typeof(IEnumerable), t); /// /// Check if the provided Type is assignable to IDictionary. diff --git a/src/Core/Tests/TestClass.cs b/src/Core/Tests/TestClass.cs index c463fe4..f24be0b 100644 --- a/src/Core/Tests/TestClass.cs +++ b/src/Core/Tests/TestClass.cs @@ -7,15 +7,16 @@ namespace UnityExplorer.Tests { public static class TestClass { - static TestClass() - { - List = new List(); - for (int i = 0; i < 10000; i++) - List.Add(i.ToString()); - } - public static List List; + public const int ConstantInt = 5; + + public static string LongString = @"####################################################################################################### +############################################################################################################################### +##################################################################################################################################### +######################################################################################################################### +######################################################################################################"; + #if CPP public static string testStringOne = "Test"; public static Il2CppSystem.Object testStringTwo = "string boxed as cpp object"; @@ -24,9 +25,15 @@ namespace UnityExplorer.Tests public static Il2CppSystem.Collections.Hashtable testHashset; public static Il2CppSystem.Collections.Generic.List testList; +#endif static TestClass() { + List = new List(); + for (int i = 0; i < 10000; i++) + List.Add(i.ToString()); + +#if CPP testHashset = new Il2CppSystem.Collections.Hashtable(); testHashset.Add("key1", "itemOne"); testHashset.Add("key2", "itemTwo"); @@ -36,8 +43,7 @@ namespace UnityExplorer.Tests testList.Add("One"); testList.Add("Two"); testList.Add("Three"); - //testIList = list.TryCast(); - } #endif + } } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 0ca8ffc..328c678 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -2,44 +2,40 @@ using System.Collections; using System.IO; using UnityEngine; +using UnityEngine.UI; using UnityExplorer.Core; using UnityExplorer.Core.Config; using UnityExplorer.Core.Input; using UnityExplorer.Core.Runtime; using UnityExplorer.Tests; using UnityExplorer.UI; +using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.Panels; namespace UnityExplorer { - public class ExplorerCore + public static class ExplorerCore { public const string NAME = "UnityExplorer"; public const string VERSION = "3.4.0"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.unityexplorer"; - public static ExplorerCore Instance { get; private set; } - public static IExplorerLoader Loader { get; private set; } public static RuntimeContext Context { get; internal set; } - // Prevent using ctor, must use Init method. - private ExplorerCore() { } - /// /// Initialize UnityExplorer with the provided Loader implementation. /// public static void Init(IExplorerLoader loader) { - if (Instance != null) + if (Loader != null) { - Log("An instance of UnityExplorer is already active!"); + LogWarning("UnityExplorer is already loaded!"); return; } Loader = loader; - Instance = new ExplorerCore(); if (!Directory.Exists(Loader.ExplorerFolder)) Directory.CreateDirectory(Loader.ExplorerFolder); @@ -69,7 +65,9 @@ namespace UnityExplorer UIManager.InitUI(); - //InspectorManager.Inspect(typeof(TestClass)); + InspectorManager.Inspect(typeof(TestClass)); + //InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent()); + //InspectorManager.InspectType(typeof(ReflectionUtility)); } /// diff --git a/src/UI/Inspectors/CacheObject/CacheField.cs b/src/UI/Inspectors/CacheObject/CacheField.cs index a2ec1f5..1d3edef 100644 --- a/src/UI/Inspectors/CacheObject/CacheField.cs +++ b/src/UI/Inspectors/CacheObject/CacheField.cs @@ -10,11 +10,14 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public FieldInfo FieldInfo { get; internal set; } + public override bool ShouldAutoEvaluate => true; + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) { base.Initialize(inspector, declaringType, member, returnType); - CanWrite = true; + // not constant + CanWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly); } protected override void TryEvaluate() diff --git a/src/UI/Inspectors/CacheObject/CacheMember.cs b/src/UI/Inspectors/CacheObject/CacheMember.cs index 0f5c740..180e330 100644 --- a/src/UI/Inspectors/CacheObject/CacheMember.cs +++ b/src/UI/Inspectors/CacheObject/CacheMember.cs @@ -3,11 +3,15 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using UnityEngine; using UnityExplorer.UI.Inspectors.CacheObject.Views; using UnityExplorer.UI.Utility; namespace UnityExplorer.UI.Inspectors.CacheObject { + // TODO some of this can be reused for CacheEnumerated / CacheKVP as well, just doing members for now. + // Will put shared stuff in CacheObjectBase. + public abstract class CacheMember : CacheObjectBase { public ReflectionInspector ParentInspector { get; internal set; } @@ -18,8 +22,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject 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 abstract bool ShouldAutoEvaluate { get; } + public bool HasArguments => Arguments?.Length > 0; + public ParameterInfo[] Arguments { get; protected set; } public bool Evaluating { get; protected set; } public bool CanWrite { get; protected set; } public bool HadException { get; protected set; } @@ -29,6 +34,20 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public string TypeLabelText { get; protected set; } public string ValueLabelText { get; protected set; } + public enum ValueState + { + NotEvaluated, Exception, NullValue, + Boolean, Number, String, Enum, + Collection, ValueStruct, Unsupported + } + + protected ValueState State = ValueState.NotEvaluated; + + private const string NOT_YET_EVAL = "Not yet evaluated"; + + /// + /// Initialize the CacheMember when an Inspector is opened and caches the member + /// public virtual void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) { this.DeclaringType = declaringType; @@ -36,72 +55,183 @@ namespace UnityExplorer.UI.Inspectors.CacheObject this.FallbackType = returnType; this.MemberLabelText = SignatureHighlighter.ParseFullSyntax(declaringType, false, member); this.NameForFiltering = $"{declaringType.Name}.{member.Name}"; - this.TypeLabelText = SignatureHighlighter.HighlightTypeName(returnType); + this.TypeLabelText = SignatureHighlighter.HighlightTypeName(FallbackType, false); + this.ValueLabelText = GetValueLabel(); } - public void SetCell(CacheMemberCell cell) + public virtual void OnDestroyed() { - 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); - } - } + // TODO release IValue / Evaluate back to pool, etc } protected abstract void TryEvaluate(); + /// + /// Evaluate when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked. + /// public void Evaluate() { TryEvaluate(); - if (!HadException) + ProcessOnEvaluate(); + } + + /// + /// Process the CacheMember state when the value has been evaluated (or re-evaluated) + /// + protected virtual void ProcessOnEvaluate() + { + var prevState = State; + + if (HadException) + State = ValueState.Exception; + else if (Value.IsNullOrDestroyed()) + State = ValueState.NullValue; + else { - ValueLabelText = ToStringUtility.ToString(Value, FallbackType); + var type = Value.GetActualType(); + + if (type == typeof(bool)) + State = ValueState.Boolean; + else if (type.IsPrimitive || type == typeof(decimal)) + State = ValueState.Number; + else if (type == typeof(string)) + State = ValueState.String; + else if (type.IsEnum) + State = ValueState.Enum; + else if (type.IsEnumerable() || type.IsDictionary()) + State = ValueState.Collection; + // todo Color and ValueStruct + else + State = ValueState.Unsupported; } - HasEvaluated = true; + // Set label text + ValueLabelText = GetValueLabel(); + + if (State != prevState) + { + // TODO handle if subcontent / evaluate shown, check type change, etc + } + } + + private string GetValueLabel() + { + switch (State) + { + case ValueState.NotEvaluated: + return $"{NOT_YET_EVAL} ({SignatureHighlighter.HighlightTypeName(FallbackType, true)})"; + case ValueState.Exception: + return $"{ReflectionUtility.ReflectionExToString(LastException)}"; + case ValueState.Boolean: + case ValueState.Number: + return null; + case ValueState.String: + string s = Value as string; + if (s.Length > 200) + s = $"{s.Substring(0, 200)}..."; + return $"\"{s}\""; + case ValueState.NullValue: + return $"{ToStringUtility.ToStringWithType(Value, FallbackType, true)}"; + case ValueState.Enum: + case ValueState.Collection: + case ValueState.ValueStruct: + case ValueState.Unsupported: + default: + return ToStringUtility.ToStringWithType(Value, FallbackType, true); + } + } + + /// + /// Set the cell view for an enabled cell based on this CacheMember model. + /// + public void SetCell(CacheMemberCell cell) + { + cell.MemberLabel.text = MemberLabelText; + cell.ValueLabel.gameObject.SetActive(true); + + cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate); + if (!ShouldAutoEvaluate) + { + cell.EvaluateButton.Button.gameObject.SetActive(true); + if (HasArguments) + cell.EvaluateButton.ButtonText.text = $"Evaluate ({Arguments.Length})"; + else + cell.EvaluateButton.ButtonText.text = "Evaluate"; + } + + if (State == ValueState.NotEvaluated && !ShouldAutoEvaluate) + { + // todo evaluate buttons etc + SetCellState(cell, true, true, Color.white, false, false, false, false, false, false); + + return; + } + + if (State == ValueState.NotEvaluated) + Evaluate(); + + switch (State) + { + case ValueState.Exception: + case ValueState.NullValue: + SetCellState(cell, true, true, Color.white, false, false, false, false, false, false); + break; + case ValueState.Boolean: + SetCellState(cell, false, false, default, true, toggleActive: true, false, CanWrite, false, false); + break; + case ValueState.Number: + SetCellState(cell, false, true, Color.white, true, false, inputActive: true, CanWrite, false, false); + break; + case ValueState.String: + SetCellState(cell, true, false, SignatureHighlighter.StringOrange, false, false, false, false, false, true); + break; + case ValueState.Enum: + SetCellState(cell, true, true, Color.white, false, false, false, false, false, true); + break; + case ValueState.Collection: + case ValueState.ValueStruct: + SetCellState(cell, true, true, Color.white, false, false, false, false, true, true); + break; + case ValueState.Unsupported: + SetCellState(cell, true, true, Color.white, false, false, false, false, true, false); + break; + } + } + + private void SetCellState(CacheMemberCell cell, bool valueActive, bool valueRichText, Color valueColor, + bool typeLabelActive, bool toggleActive, bool inputActive, bool applyActive, bool inspectActive, bool subContentActive) + { + //cell.ValueLabel.gameObject.SetActive(valueActive); + if (valueActive) + { + cell.ValueLabel.text = ValueLabelText; + cell.ValueLabel.supportRichText = valueRichText; + cell.ValueLabel.color = valueColor; + } + else + cell.ValueLabel.text = ""; + + cell.TypeLabel.gameObject.SetActive(typeLabelActive); + if (typeLabelActive) + cell.TypeLabel.text = TypeLabelText; + + cell.Toggle.gameObject.SetActive(toggleActive); + if (toggleActive) + { + cell.Toggle.isOn = (bool)Value; + cell.ToggleText.text = Value.ToString(); + } + + cell.InputField.gameObject.SetActive(inputActive); + if (inputActive) + cell.InputField.text = Value.ToString(); + + cell.ApplyButton.Button.gameObject.SetActive(applyActive); + cell.InspectButton.Button.gameObject.SetActive(inspectActive); + cell.SubContentButton.Button.gameObject.SetActive(subContentActive); + + cell.UpdateButton.Button.gameObject.SetActive(ShouldAutoEvaluate); } @@ -144,9 +274,10 @@ namespace UnityExplorer.UI.Inspectors.CacheObject target = target.TryCast(declaringType); infos.Clear(); - infos.AddRange(declaringType.GetMethods(flags)); infos.AddRange(declaringType.GetProperties(flags)); infos.AddRange(declaringType.GetFields(flags)); + infos.AddRange(declaringType.GetEvents(flags)); + infos.AddRange(declaringType.GetMethods(flags)); foreach (var member in infos) { diff --git a/src/UI/Inspectors/CacheObject/CacheMethod.cs b/src/UI/Inspectors/CacheObject/CacheMethod.cs index 28713ff..b0b4f13 100644 --- a/src/UI/Inspectors/CacheObject/CacheMethod.cs +++ b/src/UI/Inspectors/CacheObject/CacheMethod.cs @@ -10,11 +10,13 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public MethodInfo MethodInfo { get; internal set; } + public override bool ShouldAutoEvaluate => false; + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) { base.Initialize(inspector, declaringType, member, returnType); - + Arguments = MethodInfo.GetParameters(); } protected override void TryEvaluate() diff --git a/src/UI/Inspectors/CacheObject/CacheProperty.cs b/src/UI/Inspectors/CacheObject/CacheProperty.cs index f1a0f08..61f9377 100644 --- a/src/UI/Inspectors/CacheObject/CacheProperty.cs +++ b/src/UI/Inspectors/CacheObject/CacheProperty.cs @@ -10,11 +10,14 @@ namespace UnityExplorer.UI.Inspectors.CacheObject { public PropertyInfo PropertyInfo { get; internal set; } + public override bool ShouldAutoEvaluate => !HasArguments; + public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) { base.Initialize(inspector, declaringType, member, returnType); - + this.CanWrite = PropertyInfo.CanWrite; + Arguments = PropertyInfo.GetIndexParameters(); } protected override void TryEvaluate() diff --git a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs index 9e9aa3d..bd872da 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs +++ b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs @@ -4,10 +4,13 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.UI.Utility; using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Inspectors.CacheObject.Views { + // Todo add C# events for the unity UI listeners + public class CacheMemberCell : ICell { #region ICell @@ -38,26 +41,83 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views #endregion public ReflectionInspector CurrentOwner { get; set; } - public int CurrentDataIndex { get; set; } + public CacheMember CurrentOccupant { get; set; } + + public Action OnApplyClicked; + public Action OnInspectClicked; + public Action OnSubContentClicked; + public Action OnUpdateClicked; + public Action OnEvaluateClicked; public LayoutElement MemberLayout; - public LayoutElement ReturnTypeLayout; public LayoutElement RightGroupLayout; public Text MemberLabel; - public Text TypeLabel; - public GameObject RightGroupHolder; - public ButtonRef InspectButton; + //public GameObject RightGroupHolder; + public Text TypeLabel; public Text ValueLabel; + public Toggle Toggle; + public Text ToggleText; + public InputField InputField; + + public GameObject EvaluateHolder; + public ButtonRef EvaluateButton; + + public ButtonRef InspectButton; + public ButtonRef SubContentButton; + public ButtonRef ApplyButton; + public ButtonRef UpdateButton; public GameObject SubContentHolder; + public void OnReturnToPool() + { + // remove listeners + OnApplyClicked = null; + OnInspectClicked = null; + OnSubContentClicked = null; + OnUpdateClicked = null; + OnEvaluateClicked = null; + + CurrentOwner = null; + } + + private void ApplyClicked() + { + OnApplyClicked?.Invoke(CurrentOccupant); + } + + private void InspectClicked() + { + OnInspectClicked?.Invoke(CurrentOccupant); + } + + private void SubContentClicked() + { + OnSubContentClicked?.Invoke(CurrentOccupant); + } + + private void UpdateClicked() + { + OnUpdateClicked?.Invoke(CurrentOccupant); + } + + private void EvaluateClicked() + { + OnEvaluateClicked?.Invoke(CurrentOccupant); + } + + private void ToggleClicked(bool value) + { + ToggleText.text = value.ToString(); + } + 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.SetLayoutGroup(uiRoot, true, false, true, true, 2, 0); UIFactory.SetLayoutElement(uiRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; @@ -67,31 +127,64 @@ namespace UnityExplorer.UI.Inspectors.CacheObject.Views 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); + UIFactory.SetLayoutGroup(horiRow, false, false, true, true, 5, 2, childAlignment: TextAnchor.UpperLeft); horiRow.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - MemberLabel = UIFactory.CreateLabel(horiRow, "MemberLabel", "", TextAnchor.UpperLeft); + MemberLabel = UIFactory.CreateLabel(horiRow, "MemberLabel", "", TextAnchor.MiddleLeft); 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); + var 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: 800); + RightGroupLayout = rightGroupHolder.GetComponent(); + + EvaluateHolder = UIFactory.CreateUIObject("EvalGroup", rightGroupHolder); + UIFactory.SetLayoutGroup(EvaluateHolder, false, false, true, true, 3); + UIFactory.SetLayoutElement(EvaluateHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 775); + + EvaluateButton = UIFactory.CreateButton(EvaluateHolder, "EvaluateButton", "Evaluate", new Color(0.15f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(EvaluateButton.Button.gameObject, minWidth: 100, minHeight: 25); + EvaluateButton.OnClick += EvaluateClicked; + + var rightHoriGroup = UIFactory.CreateUIObject("RightHoriGroup", rightGroupHolder); + 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", "▲"); + UIFactory.SetLayoutElement(SubContentButton.Button.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + SubContentButton.OnClick += SubContentClicked; + + TypeLabel = UIFactory.CreateLabel(rightHoriGroup, "ReturnLabel", "", TextAnchor.MiddleLeft); TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap; - UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 20, flexibleWidth: 0); - ReturnTypeLayout = TypeLabel.GetComponent(); + UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 70, 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(); + var toggleObj = UIFactory.CreateToggle(rightHoriGroup, "Toggle", out Toggle, out ToggleText); + UIFactory.SetLayoutElement(toggleObj, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + ToggleText.color = SignatureHighlighter.KeywordBlue; + Toggle.onValueChanged.AddListener(ToggleClicked); - InspectButton = UIFactory.CreateButton(RightGroupHolder, "InspectButton", "Inspect", new Color(0.23f, 0.23f, 0.23f)); + var inputObj = UIFactory.CreateInputField(rightHoriGroup, "InputField", "...", out InputField); + UIFactory.SetLayoutElement(inputObj, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + + InspectButton = UIFactory.CreateButton(rightHoriGroup, "InspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f)); UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25); + InspectButton.OnClick += InspectClicked; - ValueLabel = UIFactory.CreateLabel(RightGroupHolder, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); + ApplyButton = UIFactory.CreateButton(rightHoriGroup, "ApplyButton", "Apply", new Color(0.15f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(ApplyButton.Button.gameObject, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + ApplyButton.OnClick += ApplyClicked; + + ValueLabel = UIFactory.CreateLabel(rightHoriGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap; UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999); + UpdateButton = UIFactory.CreateButton(rightHoriGroup, "UpdateButton", "Update", new Color(0.15f, 0.2f, 0.15f)); + UIFactory.SetLayoutElement(UpdateButton.Button.gameObject, minWidth: 65, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + UpdateButton.OnClick += UpdateClicked; + // Subcontent (todo?) SubContentHolder = UIFactory.CreateUIObject("SubContent", uiRoot); diff --git a/src/UI/Inspectors/InspectorManager.cs b/src/UI/Inspectors/InspectorManager.cs index 4eee03b..bcc67b1 100644 --- a/src/UI/Inspectors/InspectorManager.cs +++ b/src/UI/Inspectors/InspectorManager.cs @@ -29,7 +29,7 @@ namespace UnityExplorer.UI.Inspectors CreateInspector(obj); } - public static void InspectStatic(Type type) + public static void Inspect(Type type) { CreateInspector(type, true); } diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index 4bba928..cfec842 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -28,7 +28,6 @@ namespace UnityExplorer.UI.Inspectors 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; @@ -37,7 +36,6 @@ namespace UnityExplorer.UI.Inspectors public Text AssemblyText; private LayoutElement memberTitleLayout; - private LayoutElement typeTitleLayout; public override void OnBorrowedFromPool(object target) { @@ -97,21 +95,22 @@ namespace UnityExplorer.UI.Inspectors { var member = members[i]; filteredMembers.Add(member); - filteredIndices.Add(i); } } public override void OnReturnToPool() { - base.OnReturnToPool(); + foreach (var member in members) + member.OnDestroyed(); members.Clear(); filteredMembers.Clear(); - filteredIndices.Clear(); // release all cachememberviews MemberScrollPool.ReturnCells(); MemberScrollPool.SetUninitialized(); + + base.OnReturnToPool(); } public override void OnSetActive() @@ -141,6 +140,7 @@ namespace UnityExplorer.UI.Inspectors { timeOfLastUpdate = Time.time; + // Update displayed values (TODO) } } @@ -154,33 +154,54 @@ namespace UnityExplorer.UI.Inspectors 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 + cell.OnInspectClicked += OnCellInspect; + cell.OnApplyClicked += OnCellApply; + cell.OnSubContentClicked += OnCellSubContentToggle; + cell.OnUpdateClicked += OnCellUpdateClicked; + cell.OnEvaluateClicked += OnCellEvaluateClicked; + } + + private void OnCellInspect(CacheMember occupant) + { + InspectorManager.Inspect(occupant.Value); + } + + private void OnCellApply(CacheMember occupant) + { + ExplorerCore.Log($"TODO OnApply: {occupant.NameForFiltering}"); + } + + private void OnCellSubContentToggle(CacheMember occupant) + { + ExplorerCore.Log($"TODO SubContentToggle: {occupant.NameForFiltering}"); + } + + private void OnCellUpdateClicked(CacheMember occupant) + { + ExplorerCore.Log("TODO Update: " + occupant.NameForFiltering); + } + + private void OnCellEvaluateClicked(CacheMember occupant) + { + ExplorerCore.Log("TODO Evaluate or toggle: " + occupant); } public void OnCellReturned(CacheMemberCell cell) { - // todo remove listeners - - // return ivalue - - cell.CurrentOwner = null; + cell.OnReturnToPool(); } public void SetCell(CacheMemberCell cell, int index) { - index = GetRealIndexOfTempIndex(index); + if (cell.CurrentOccupant != null) + { + // TODO + } if (index < 0 || index >= filteredMembers.Count) { @@ -188,7 +209,9 @@ namespace UnityExplorer.UI.Inspectors return; } - members[index].SetCell(cell); + var member = filteredMembers[index]; + cell.CurrentOccupant = member; + member.SetCell(cell); SetCellLayout(cell); } @@ -198,20 +221,25 @@ namespace UnityExplorer.UI.Inspectors // 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; + #endregion + + // Cell layout (fake table alignment) + + private static float MemLabelWidth { get; set; } + private static float 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); + memberTitleLayout.minWidth = MemLabelWidth; - typeTitleLayout.minWidth = ReturnLabelWidth; } private void SetCellLayout(CacheMemberCell cell) { cell.MemberLayout.minWidth = MemLabelWidth; - cell.ReturnTypeLayout.minWidth = ReturnLabelWidth; cell.RightGroupLayout.minWidth = RightGroupWidth; } @@ -223,8 +251,6 @@ namespace UnityExplorer.UI.Inspectors SetCellLayout(cell); } - #endregion - public override GameObject CreateContent(GameObject parent) { uiRoot = UIFactory.CreateVerticalGroup(parent, "ReflectionInspector", true, true, true, true, 5, @@ -243,16 +269,16 @@ namespace UnityExplorer.UI.Inspectors 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 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); + UIFactory.SetLayoutElement(valueTitle.gameObject, minWidth: 150, 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); + //UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); MemberScrollPool.Initialize(this); diff --git a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs b/src/UI/Panels/ObjectExplorer/ObjectSearch.cs similarity index 98% rename from src/UI/Widgets/ObjectExplorer/ObjectSearch.cs rename to src/UI/Panels/ObjectExplorer/ObjectSearch.cs index 6e4b651..dd46136 100644 --- a/src/UI/Widgets/ObjectExplorer/ObjectSearch.cs +++ b/src/UI/Panels/ObjectExplorer/ObjectSearch.cs @@ -8,11 +8,11 @@ using UnityExplorer.Core.Search; using UnityExplorer.UI.Inspectors; using UnityExplorer.UI.Models; using UnityExplorer.UI.ObjectPool; -using UnityExplorer.UI.Panels; using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets; using UnityExplorer.UI.Widgets.AutoComplete; -namespace UnityExplorer.UI.Widgets +namespace UnityExplorer.UI.Panels { public class ObjectSearch : UIModel { @@ -110,7 +110,7 @@ namespace UnityExplorer.UI.Widgets private void OnCellClicked(int dataIndex) { if (m_context == SearchContext.StaticClass) - InspectorManager.InspectStatic(currentResults[dataIndex] as Type); + InspectorManager.Inspect(currentResults[dataIndex] as Type); else InspectorManager.Inspect(currentResults[dataIndex]); } diff --git a/src/UI/Widgets/ObjectExplorer/SceneExplorer.cs b/src/UI/Panels/ObjectExplorer/SceneExplorer.cs similarity index 99% rename from src/UI/Widgets/ObjectExplorer/SceneExplorer.cs rename to src/UI/Panels/ObjectExplorer/SceneExplorer.cs index 562c353..13b9a31 100644 --- a/src/UI/Widgets/ObjectExplorer/SceneExplorer.cs +++ b/src/UI/Panels/ObjectExplorer/SceneExplorer.cs @@ -9,9 +9,9 @@ using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityExplorer.Core; using UnityExplorer.UI.Models; -using UnityExplorer.UI.Panels; +using UnityExplorer.UI.Widgets; -namespace UnityExplorer.UI.Widgets +namespace UnityExplorer.UI.Panels { public class SceneExplorer : UIModel { diff --git a/src/UI/Utility/SignatureHighlighter.cs b/src/UI/Utility/SignatureHighlighter.cs index 88ee1da..0bd27a1 100644 --- a/src/UI/Utility/SignatureHighlighter.cs +++ b/src/UI/Utility/SignatureHighlighter.cs @@ -14,6 +14,16 @@ namespace UnityExplorer.UI.Utility /// public class SignatureHighlighter { + public const string NAMESPACE = "#a8a8a8"; + + public const string CONST = "#92c470"; + + public const string CLASS_STATIC = "#3a8d71"; + public const string CLASS_INSTANCE = "#2df7b2"; + + public const string STRUCT = "#0fba3a"; + public const string INTERFACE = "#9b9b82"; + public const string FIELD_STATIC = "#8d8dc6"; public const string FIELD_INSTANCE = "#c266ff"; @@ -23,30 +33,28 @@ namespace UnityExplorer.UI.Utility public const string PROP_STATIC = "#588075"; public const string PROP_INSTANCE = "#55a38e"; - public const string CLASS_STATIC = "#3a8d71"; - public const string CLASS_INSTANCE = "#2df7b2"; - - public const string CLASS_STRUCT = "#0fba3a"; - public const string LOCAL_ARG = "#a6e9e9"; - public static string CONST_VAR = "#92c470"; - - public static string NAMESPACE = "#a8a8a8"; + public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f); + public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f); + public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f); + public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f); internal static string GetClassColor(Type type) { if (type.IsAbstract && type.IsSealed) return CLASS_STATIC; else if (type.IsEnum || type.IsGenericParameter) - return CONST_VAR; + return CONST; else if (type.IsValueType) - return CLASS_STRUCT; + return STRUCT; + else if (type.IsInterface) + return INTERFACE; else return CLASS_INSTANCE; } - private static readonly StringBuilder syntaxBuilder = new StringBuilder(8192); + private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156); public static string ParseFullSyntax(Type type, bool includeNamespace, MemberInfo memberInfo = null) { @@ -82,7 +90,11 @@ namespace UnityExplorer.UI.Utility syntaxBuilder.Append(""); if (memberInfo is MethodInfo method) - syntaxBuilder.Append(ParseGenericArgs(method.GetGenericArguments(), true)); + { + var args = method.GetGenericArguments(); + if (args.Length > 0) + syntaxBuilder.Append($"<{ParseGenericArgs(args, true)}>"); + } } return syntaxBuilder.ToString(); @@ -125,7 +137,7 @@ namespace UnityExplorer.UI.Utility if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter)) { - typeName = $"{typeName}"; + typeName = $"{typeName}"; } else { @@ -150,7 +162,9 @@ namespace UnityExplorer.UI.Utility // parse the generic args, if any if (args.Length > 0) - typeName += ParseGenericArgs(args); + { + typeName += $"<{ParseGenericArgs(args)}>"; + } } if (isArray) @@ -161,33 +175,29 @@ namespace UnityExplorer.UI.Utility return typeName; } - private static readonly StringBuilder genericBuilder = new StringBuilder(4096); - public static string ParseGenericArgs(Type[] args, bool isGenericParams = false) { if (args.Length < 1) return string.Empty; - genericBuilder.Clear(); - genericBuilder.Append('<'); + string ret = ""; for (int i = 0; i < args.Length; i++) { if (i > 0) - genericBuilder.Append(','); + ret += ","; if (isGenericParams) { - genericBuilder.Append($"{args[i].Name}"); + ret += $"{args[i].Name}"; continue; } // using HighlightTypeName makes it recursive, so we can parse nested generic args. - genericBuilder.Append(HighlightTypeName(args[i])); + ret += HighlightTypeName(args[i]); } - genericBuilder.Append('>'); - return genericBuilder.ToString(); + return ret; } public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic) @@ -200,8 +210,8 @@ namespace UnityExplorer.UI.Utility isStatic = true; return FIELD_STATIC; } - else - return FIELD_INSTANCE; + + return FIELD_INSTANCE; } else if (memberInfo is MethodInfo mi) { @@ -210,8 +220,8 @@ namespace UnityExplorer.UI.Utility isStatic = true; return METHOD_STATIC; } - else - return METHOD_INSTANCE; + + return METHOD_INSTANCE; } else if (memberInfo is PropertyInfo pi) { @@ -220,9 +230,19 @@ namespace UnityExplorer.UI.Utility isStatic = true; return PROP_STATIC; } - else - return PROP_INSTANCE; + + return PROP_INSTANCE; } + //else if (memberInfo is EventInfo ei) + //{ + // if (ei.GetAddMethod().IsStatic) + // { + // isStatic = true; + // return EVENT_STATIC; + // } + + // return EVENT_INSTANCE; + //} throw new NotImplementedException(memberInfo.GetType().Name + " is not supported"); } diff --git a/src/UI/Utility/ToStringUtility.cs b/src/UI/Utility/ToStringUtility.cs index ca778b1..26b6057 100644 --- a/src/UI/Utility/ToStringUtility.cs +++ b/src/UI/Utility/ToStringUtility.cs @@ -16,11 +16,11 @@ namespace UnityExplorer.UI.Utility // string allocs private static readonly StringBuilder _stringBuilder = new StringBuilder(16384); - private const string unknownString = ""; - private const string nullString = "[null]"; - private const string destroyedString = "[Destroyed]"; + private const string nullString = "null"; + private const string destroyedString = "Destroyed"; + private const string untitledString = "untitled"; - public static string ToString(object value, Type type) + public static string ToString(object value) { if (value.IsNullOrDestroyed()) { @@ -30,6 +30,10 @@ namespace UnityExplorer.UI.Utility return destroyedString; } + var type = value.GetActualType(); + + // Find and cache the relevant ToString method for this Type, if haven't already. + if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName)) { try @@ -46,6 +50,8 @@ namespace UnityExplorer.UI.Utility } } + // Invoke the ToString method on the object + value = value.TryCast(type); string toString; @@ -60,7 +66,7 @@ namespace UnityExplorer.UI.Utility public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true) { if (value == null && fallbackType == null) - return unknownString; + return nullString; Type type = value?.GetActualType() ?? fallbackType; @@ -89,17 +95,18 @@ namespace UnityExplorer.UI.Utility if (value is UnityEngine.Object obj) { - _stringBuilder.Append(obj.name); + _stringBuilder.Append(string.IsNullOrEmpty(obj.name) ? untitledString : obj.name); AppendRichType(_stringBuilder, richType); } else { - var toString = ToString(value, type); + var toString = ToString(value); - if (toString == type.FullName || toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}") + if (type.IsGenericType + || toString == type.FullName + || toString == $"{type.FullName} {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. _stringBuilder.Append(richType); } else // the ToString contains some actual implementation, use that value. @@ -116,8 +123,6 @@ namespace UnityExplorer.UI.Utility return _stringBuilder.ToString(); } - // Just a little optimization, append chars directly instead of allocating every time - // we want to do this. private static void AppendRichType(StringBuilder sb, string richType) { sb.Append(' '); diff --git a/src/UI/Widgets/ButtonList/ButtonListSource.cs b/src/UI/Widgets/ButtonList/ButtonListSource.cs index 1578b0f..0965a28 100644 --- a/src/UI/Widgets/ButtonList/ButtonListSource.cs +++ b/src/UI/Widgets/ButtonList/ButtonListSource.cs @@ -12,8 +12,6 @@ namespace UnityExplorer.UI.Widgets { public class ButtonListSource : IPoolDataSource { - public int GetRealIndexOfTempIndex(int index) => throw new NotImplementedException("TODO"); - internal ScrollPool ScrollPool; public int ItemCount => currentEntries.Count; diff --git a/src/UI/Widgets/ScrollPool/DataHeightCache.cs b/src/UI/Widgets/ScrollPool/DataHeightCache.cs index aceb492..2801e2d 100644 --- a/src/UI/Widgets/ScrollPool/DataHeightCache.cs +++ b/src/UI/Widgets/ScrollPool/DataHeightCache.cs @@ -24,8 +24,7 @@ namespace UnityExplorer.UI.Widgets ScrollPool = scrollPool; } - // initialize with a reasonably sized pool, most caches will allocate a fair bit. - private readonly List heightCache = new List(16384); + private readonly List heightCache = new List(); public DataViewInfo this[int index] { @@ -104,12 +103,8 @@ namespace UnityExplorer.UI.Widgets heightCache.RemoveAt(heightCache.Count - 1); int idx = heightCache.Count; - if (idx > 0) - { - while (rangeCache[rangeCache.Count - 1] == idx) - rangeCache.RemoveAt(rangeCache.Count - 1); - } - + while (rangeCache.Count > 0 && rangeCache[rangeCache.Count - 1] == idx) + rangeCache.RemoveAt(rangeCache.Count - 1); } /// Get the data index at the specific position of the total height cache. @@ -119,31 +114,29 @@ namespace UnityExplorer.UI.Widgets public int GetDataIndexAtPosition(float desiredHeight, out DataViewInfo cache) { cache = default; + + if (!heightCache.Any()) + return 0; + int rangeIndex = GetRangeIndexOfPosition(desiredHeight); if (rangeIndex < 0) + return 0; + if (rangeIndex >= rangeCache.Count) { - ExplorerCore.LogWarning("RangeIndex < 0? " + rangeIndex); - return -1; - } - - if (rangeCache.Count <= rangeIndex) - { - ExplorerCore.LogWarning("Want range index " + rangeIndex + " but count is " + rangeCache.Count); - RebuildCache(); - rangeIndex = GetRangeIndexOfPosition(desiredHeight); - if (rangeCache.Count <= rangeIndex) - throw new Exception("Range index (" + rangeIndex + ") exceeded rangeCache count (" + rangeCache.Count + ")"); + ExplorerCore.Log("desiredHeight is " + desiredHeight + ", but our total height is " + totalHeight + ", clamping to data count"); + ExplorerCore.Log("highest data index: " + (ScrollPool.DataSource.ItemCount - 1) + ", rangeIndex was " + rangeIndex + ", actual range limit is " + (rangeCache.Count - 1)); + cache = heightCache[heightCache.Count - 1]; + return ScrollPool.DataSource.ItemCount - 1; } int dataIndex = rangeCache[rangeIndex]; cache = heightCache[dataIndex]; - return dataIndex; } /// Set a given data index with the specified value. - public void SetIndex(int dataIndex, float height) + public void SetIndex(int dataIndex, float height, bool inRebuild = false) { if (dataIndex >= ScrollPool.DataSource.ItemCount) { @@ -166,7 +159,6 @@ namespace UnityExplorer.UI.Widgets var diff = height - prevHeight; if (diff != 0.0f) { - // LogWarning("Height for data index " + dataIndex + " changed by " + diff); totalHeight += diff; cache.height = height; } @@ -183,8 +175,16 @@ namespace UnityExplorer.UI.Widgets if (rangeCache.Count <= rangeIndex) { - RebuildCache(); - return; + 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; + } } if (spread != cache.normalizedSpread) @@ -199,6 +199,7 @@ namespace UnityExplorer.UI.Widgets // 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) @@ -218,10 +219,8 @@ namespace UnityExplorer.UI.Widgets // 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. - // also, add the (spread - 1) of the cell we found at this index to skip it. - var spreadCurr = heightCache[rangeCache[i]].normalizedSpread; + int jmp = dataIndex - rangeCache[i] - 1; - jmp += spreadCurr - 2; i = (jmp < 1 ? i : i + jmp); } } @@ -263,7 +262,7 @@ namespace UnityExplorer.UI.Widgets { //start at 1 because 0's start pos is always 0 for (int i = 1; i < heightCache.Count; i++) - SetIndex(i, heightCache[i].height); + SetIndex(i, heightCache[i].height, true); } } } diff --git a/src/UI/Widgets/ScrollPool/IPoolDataSource.cs b/src/UI/Widgets/ScrollPool/IPoolDataSource.cs index d7555a4..8fe6396 100644 --- a/src/UI/Widgets/ScrollPool/IPoolDataSource.cs +++ b/src/UI/Widgets/ScrollPool/IPoolDataSource.cs @@ -9,7 +9,6 @@ namespace UnityExplorer.UI.Widgets public interface IPoolDataSource where T : ICell { int ItemCount { get; } - int GetRealIndexOfTempIndex(int tempIndex); void OnCellBorrowed(T cell); void OnCellReturned(T cell); diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index a4e9bcc..74adc10 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -100,7 +100,7 @@ namespace UnityExplorer.UI.Widgets private bool writingLocked; private float timeofLastWriteLock; - private float prevContentHeight; + private float prevContentHeight = 1.0f; public void SetUninitialized() { @@ -115,8 +115,10 @@ namespace UnityExplorer.UI.Widgets if (writingLocked && timeofLastWriteLock < Time.time) writingLocked = false; - if (prevContentHeight == 0.0f && Content?.rect.height != 0.0f) + if (prevContentHeight <= 1f && Content?.rect.height > 1f) + { prevContentHeight = Content.rect.height; + } else if (Content.rect.height != prevContentHeight) { prevContentHeight = Content.rect.height; @@ -240,21 +242,13 @@ namespace UnityExplorer.UI.Widgets { bottomPoolIndex++; - ////Instantiate and add to Pool - //RectTransform rect = GameObject.Instantiate(PrototypeCell.gameObject).GetComponent(); - //rect.gameObject.SetActive(true); - //rect.name = $"Cell_{CellPool.Count}"; - //var cell = DataSource.CreateCell(rect); - //CellPool.Add(cell); - //rect.SetParent(ScrollRect.content, false); - var cell = Pool.Borrow(); DataSource.OnCellBorrowed(cell); - var rect = cell.Rect; + //var rect = cell.Rect; CellPool.Add(cell); - rect.SetParent(ScrollRect.content, false); + cell.Rect.SetParent(ScrollRect.content, false); - currentPoolCoverage += rect.rect.height; + currentPoolCoverage += PrototypeHeight; } if (andResetDataIndex) @@ -289,30 +283,29 @@ namespace UnityExplorer.UI.Widgets var requiredCoverage = Math.Abs(RecycleViewBounds.y - RecycleViewBounds.x); var currentCoverage = CellPool.Count * PrototypeHeight; - int cellsRequired = (int)Math.Ceiling((decimal)(requiredCoverage - currentCoverage) / (decimal)PrototypeHeight); + int cellsRequired = (int)Math.Floor((decimal)(requiredCoverage - currentCoverage) / (decimal)PrototypeHeight); if (cellsRequired > 0 || forceRecreate) { 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); - //} - bottomDataIndex += cellsRequired; int maxDataIndex = Math.Max(CellPool.Count + cellsRequired - 1, DataSource.ItemCount - 1); if (bottomDataIndex > maxDataIndex) bottomDataIndex = maxDataIndex; - // CreateCellPool will destroy existing cells and recreate list. + float curAnchor = Content.localPosition.y; + float curHeight = Content.rect.height; + CreateCellPool(resetDataIndex); - LayoutRebuilder.ForceRebuildLayoutImmediate(Content); + // fix slight jumping when resizing panel and size increases + + if (Content.rect.height != curHeight) + { + var diff = Content.rect.height - curHeight; + Content.localPosition = new Vector3(Content.localPosition.x, Content.localPosition.y + (diff * 0.5f)); + } - //Content.anchoredPosition = new Vector2(0, pos); ScrollRect.UpdatePrevData(); SetScrollBounds(); @@ -437,10 +430,7 @@ namespace UnityExplorer.UI.Widgets SetRecycleViewBounds(true); - //if (!SetRecycleViewBounds(true)) - // RefreshCells(false); - - float yChange = (ScrollRect.content.anchoredPosition - prevAnchoredPos).y; + float yChange = ((Vector2)ScrollRect.content.localPosition - prevAnchoredPos).y; float adjust = 0f; if (yChange > 0) // Scrolling down @@ -642,7 +632,6 @@ namespace UnityExplorer.UI.Widgets { if (TopDataIndex > poolStartIndex && TopDataIndex < desiredBottomIndex) { - //ExplorerCore.Log("Scroll bottom to top"); // top cell falls within the new range, rotate around that int rotate = TopDataIndex - poolStartIndex; for (int i = 0; i < rotate; i++) @@ -659,7 +648,6 @@ namespace UnityExplorer.UI.Widgets } else if (bottomDataIndex > poolStartIndex && bottomDataIndex < desiredBottomIndex) { - //ExplorerCore.Log("Scroll top to bottom"); // bottom cells falls within the new range, rotate around that int rotate = desiredBottomIndex - bottomDataIndex; for (int i = 0; i < rotate; i++) @@ -676,9 +664,6 @@ namespace UnityExplorer.UI.Widgets } else { - // new cells are completely different, set all cells - //ExplorerCore.Log("Scroll jump"); - bottomDataIndex = desiredBottomIndex; var enumerator = GetPoolEnumerator(); while (enumerator.MoveNext()) @@ -692,17 +677,6 @@ namespace UnityExplorer.UI.Widgets SetRecycleViewBounds(true); - //CheckDataSourceCountChange(out bool jumpToBottom); - - //// force check recycles - //if (andReloadFromDataSource) - //{ - // RecycleBottomToTop(); - // RecycleTopToBottom(); - //} - - //LayoutRebuilder.ForceRebuildLayoutImmediate(Content); - SetScrollBounds(); ScrollRect.UpdatePrevData(); diff --git a/src/UI/Widgets/TransformTree/TransformTree.cs b/src/UI/Widgets/TransformTree/TransformTree.cs index bf3d1f0..6e7dc38 100644 --- a/src/UI/Widgets/TransformTree/TransformTree.cs +++ b/src/UI/Widgets/TransformTree/TransformTree.cs @@ -55,8 +55,6 @@ namespace UnityExplorer.UI.Widgets ScrollPool.Initialize(this); } - public int GetRealIndexOfTempIndex(int index) => -1;// not needed - public void DisableCell(TransformCell cell, int index) => cell.Disable(); diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 15981c7..4707f44 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -319,8 +319,8 @@ - - + +