From 2378925a8b2424d1d65ea682f692062578327262 Mon Sep 17 00:00:00 2001 From: Sinai Date: Fri, 30 Apr 2021 21:34:50 +1000 Subject: [PATCH] More progress --- README.md | 4 + src/Core/Config/ConfigManager.cs | 2 + src/Core/Input/CursorUnlocker.cs | 2 +- src/ExplorerCore.cs | 36 +-- src/UI/CSConsole/CSConsoleManager.cs | 34 ++ src/UI/Inspectors/CacheObject/CacheField.cs | 4 +- src/UI/Inspectors/CacheObject/CacheMember.cs | 238 ++------------ src/UI/Inspectors/CacheObject/CacheMethod.cs | 4 +- .../Inspectors/CacheObject/CacheObjectBase.cs | 295 ++++++++++++++++++ .../Inspectors/CacheObject/CacheProperty.cs | 4 +- .../CacheObject/Views/CacheMemberCell.cs | 184 +---------- .../CacheObject/Views/CacheObjectCell.cs | 181 ++++++++++- src/UI/Inspectors/IValues/IValueTest.cs | 28 -- src/UI/Inspectors/IValues/InteractiveValue.cs | 60 ++++ src/UI/Inspectors/ReflectionInspector.cs | 72 +++-- src/UI/Models/UIModel.cs | 2 +- src/UI/Panels/CSConsolePanel.cs | 220 +++++++++++++ src/UI/Panels/ObjectExplorer.cs | 1 + src/UI/Panels/UIPanel.cs | 16 +- src/UI/UIFactory.cs | 243 ++++++++++----- src/UI/UIManager.cs | 41 ++- src/UI/Widgets/AutoSliderScrollbar.cs | 138 ++++++++ src/UI/Widgets/InputFieldScroller.cs | 109 +++---- src/UI/Widgets/ScrollPool/DataHeightCache.cs | 17 +- src/UI/Widgets/ScrollPool/ScrollPool.cs | 151 +++++---- src/UI/Widgets/SliderScrollbar.cs | 155 --------- src/UnityExplorer.csproj | 6 +- 27 files changed, 1401 insertions(+), 846 deletions(-) create mode 100644 src/UI/CSConsole/CSConsoleManager.cs delete mode 100644 src/UI/Inspectors/IValues/IValueTest.cs create mode 100644 src/UI/Inspectors/IValues/InteractiveValue.cs create mode 100644 src/UI/Panels/CSConsolePanel.cs create mode 100644 src/UI/Widgets/AutoSliderScrollbar.cs delete mode 100644 src/UI/Widgets/SliderScrollbar.cs diff --git a/README.md b/README.md index 7fc463e..41ed3e7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ An in-game explorer and a suite of debugging tools for IL2CPP and Mono Unity games, to aid with modding development.

+

+ Supports most Unity games from versions 5.2 to 2020+. +

+ ## Releases [![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=release%20notes)](../../releases/latest) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../releases/latest) | Mod Loader | IL2CPP | Mono | diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs index 84bde0a..c7a90cd 100644 --- a/src/Core/Config/ConfigManager.cs +++ b/src/Core/Config/ConfigManager.cs @@ -31,6 +31,7 @@ namespace UnityExplorer.Core.Config public static ConfigElement ObjectExplorerData; public static ConfigElement InspectorData; + public static ConfigElement CSConsoleData; internal static readonly Dictionary ConfigElements = new Dictionary(); internal static readonly Dictionary InternalConfigs = new Dictionary(); @@ -107,6 +108,7 @@ namespace UnityExplorer.Core.Config ObjectExplorerData = new ConfigElement("ObjectExplorer", "", "", true); InspectorData = new ConfigElement("Inspector", "", "", true); + CSConsoleData = new ConfigElement("CSConsole", "", "", true); } } } diff --git a/src/Core/Input/CursorUnlocker.cs b/src/Core/Input/CursorUnlocker.cs index 72ee097..01caa29 100644 --- a/src/Core/Input/CursorUnlocker.cs +++ b/src/Core/Input/CursorUnlocker.cs @@ -61,7 +61,7 @@ namespace UnityExplorer.Core.Input } catch (Exception ex) { - ExplorerCore.LogWarning($"Exception setting up Camera.onPostRender callback: {ex}"); + ExplorerCore.LogWarning($"Exception setting up Aggressive Mouse Unlock: {ex}"); } } diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 07da4f2..55d52fd 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -35,22 +35,6 @@ namespace UnityExplorer return; } - // TEMP DEBUG TEST FOR TRANSFORM TREE - - var stressTest = new GameObject("StressTest"); - for (int i = 0; i < 100; i++) - { - var obj = new GameObject($"Parent_{i}"); - obj.transform.parent = stressTest.transform; - for (int j = 0; j < 100; j++) - { - var obj2 = new GameObject($"Child_{j}"); - obj2.transform.parent = obj.transform; - } - } - - // END - Loader = loader; if (!Directory.Exists(Loader.ExplorerFolder)) @@ -81,8 +65,24 @@ namespace UnityExplorer UIManager.InitUI(); - InspectorManager.Inspect(typeof(TestClass)); - //InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent()); + // TEMP DEBUG TEST FOR TRANSFORM TREE + + var stressTest = new GameObject("StressTest"); + for (int i = 0; i < 100; i++) + { + var obj = new GameObject($"Parent_{i}"); + obj.transform.parent = stressTest.transform; + for (int j = 0; j < 100; j++) + { + var obj2 = new GameObject($"Child_{j}"); + obj2.transform.parent = obj.transform; + } + } + + // END + + //InspectorManager.Inspect(typeof(TestClass)); + InspectorManager.Inspect(UIManager.CanvasRoot.gameObject.GetComponent()); //InspectorManager.InspectType(typeof(ReflectionUtility)); } diff --git a/src/UI/CSConsole/CSConsoleManager.cs b/src/UI/CSConsole/CSConsoleManager.cs new file mode 100644 index 0000000..d72d33e --- /dev/null +++ b/src/UI/CSConsole/CSConsoleManager.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.UI.CSConsole +{ + public static class CSConsoleManager + { + internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console. + +The following helper methods are available: + +* Log(""message"") logs a message to the debug console + +* StartCoroutine(IEnumerator routine) start the IEnumerator as a UnityEngine.Coroutine + +* CurrentTarget() returns the currently inspected target on the Home page + +* AllTargets() returns an object[] array containing all inspected instances + +* Inspect(someObject) to inspect an instance, eg. Inspect(Camera.main); + +* Inspect(typeof(SomeClass)) to inspect a Class with static reflection + +* AddUsing(""SomeNamespace"") adds a using directive to the C# console + +* GetUsing() logs the current using directives to the debug console + +* Reset() resets all using directives and variables +"; + + } +} diff --git a/src/UI/Inspectors/CacheObject/CacheField.cs b/src/UI/Inspectors/CacheObject/CacheField.cs index 59ce3a4..c302ce4 100644 --- a/src/UI/Inspectors/CacheObject/CacheField.cs +++ b/src/UI/Inspectors/CacheObject/CacheField.cs @@ -12,9 +12,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public override bool ShouldAutoEvaluate => true; - public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { - base.Initialize(inspector, declaringType, member, returnType); + base.SetInspectorOwner(inspector, member); // not constant CanWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly); diff --git a/src/UI/Inspectors/CacheObject/CacheMember.cs b/src/UI/Inspectors/CacheObject/CacheMember.cs index 4a10e72..7530c2f 100644 --- a/src/UI/Inspectors/CacheObject/CacheMember.cs +++ b/src/UI/Inspectors/CacheObject/CacheMember.cs @@ -15,98 +15,26 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public abstract class CacheMember : CacheObjectBase { public ReflectionInspector ParentInspector { get; internal set; } - public CacheMemberCell CurrentView { get; internal set; } public bool AutoUpdateWanted { get; internal set; } + + public Type DeclaringType { get; protected set; } + public string NameForFiltering { get; protected set; } - public Type DeclaringType { get; private set; } - public string NameForFiltering { get; private set; } - - public object Value { get; protected set; } - public Type FallbackType { get; private set; } - - public abstract bool ShouldAutoEvaluate { get; } - public bool HasArguments => Arguments?.Length > 0; + public override 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; } - public Exception LastException { get; protected set; } - - public string MemberLabelText { get; private set; } - public string TypeLabelText { get; protected set; } - public string ValueLabelText { get; protected set; } - - private static readonly Dictionary numberParseMethods = new Dictionary(); - - public enum ValueState + + public virtual void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { - 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; this.ParentInspector = inspector; - this.FallbackType = returnType; - this.MemberLabelText = SignatureHighlighter.ParseFullSyntax(declaringType, false, member); - this.NameForFiltering = $"{declaringType.Name}.{member.Name}"; - this.TypeLabelText = SignatureHighlighter.ParseFullType(FallbackType, false); - this.ValueLabelText = GetValueLabel(); - } - - public virtual void OnDestroyed() - { - // TODO release IValue / Evaluate back to pool, etc + this.NameLabelText = SignatureHighlighter.ParseFullSyntax(member.DeclaringType, false, member); + this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}"; } protected abstract void TryEvaluate(); - public void SetValue(object value) - { - // TODO unbox string, cast, etc - - TrySetValue(value); - - Evaluate(); - } - protected abstract void TrySetValue(object value); - public void OnCellApplyClicked() - { - if (CurrentView == null) - { - ExplorerCore.LogWarning("Trying to apply CacheMember but current cell reference is null!"); - return; - } - - if (State == ValueState.Boolean) - SetValue(this.CurrentView.Toggle.isOn); - else - { - if (!numberParseMethods.ContainsKey(FallbackType.AssemblyQualifiedName)) - { - var method = FallbackType.GetMethod("Parse", new Type[] { typeof(string) }); - numberParseMethods.Add(FallbackType.AssemblyQualifiedName, method); - } - - var val = numberParseMethods[FallbackType.AssemblyQualifiedName] - .Invoke(null, new object[] { CurrentView.InputField.text }); - SetValue(val); - } - - SetCell(this.CurrentView); - } - /// /// Evaluate when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked. /// @@ -120,79 +48,27 @@ namespace UnityExplorer.UI.Inspectors.CacheObject ProcessOnEvaluate(); } - /// - /// Process the CacheMember state when the value has been evaluated (or re-evaluated) - /// - protected virtual void ProcessOnEvaluate() + public override void SetUserValue(object value) { - var prevState = State; + // TODO unbox string, cast, etc - if (HadException) - State = ValueState.Exception; - else if (Value.IsNullOrDestroyed()) - State = ValueState.NullValue; - else - { - var type = Value.GetActualType(); + TrySetValue(value); - 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; - } - - // Set label text - ValueLabelText = GetValueLabel(); - - if (State != prevState) - { - // TODO handle if subcontent / evaluate shown, check type change, etc - } + Evaluate(); } - private string GetValueLabel() + protected override void SetValueState(CacheObjectCell cell, bool valueActive, bool valueRichText, Color valueColor, + bool typeLabelActive, bool toggleActive, bool inputActive, bool applyActive, bool inspectActive, bool subContentActive) { - switch (State) - { - case ValueState.NotEvaluated: - return $"{NOT_YET_EVAL} ({SignatureHighlighter.ParseFullType(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); - } + base.SetValueState(cell, valueActive, valueRichText, valueColor, typeLabelActive, toggleActive, inputActive, applyActive, + inspectActive, subContentActive); + + (cell as CacheMemberCell).UpdateToggle.gameObject.SetActive(ShouldAutoEvaluate); } - /// - /// Set the cell view for an enabled cell based on this CacheMember model. - /// - public void SetCell(CacheMemberCell cell) + protected override bool SetCellEvaluateState(CacheObjectCell objectcell) { - cell.MemberLabel.text = MemberLabelText; - cell.ValueLabel.gameObject.SetActive(true); + var cell = objectcell as CacheMemberCell; cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate); if (!ShouldAutoEvaluate) @@ -205,7 +81,7 @@ namespace UnityExplorer.UI.Inspectors.CacheObject cell.EvaluateButton.ButtonText.text = "Evaluate"; } else - { + { cell.UpdateToggle.gameObject.SetActive(true); cell.UpdateToggle.isOn = AutoUpdateWanted; } @@ -215,79 +91,15 @@ namespace UnityExplorer.UI.Inspectors.CacheObject // todo evaluate buttons etc SetValueState(cell, true, true, Color.white, false, false, false, false, false, false); - return; + return true; } if (State == ValueState.NotEvaluated) Evaluate(); - switch (State) - { - case ValueState.Exception: - case ValueState.NullValue: - SetValueState(cell, true, true, Color.white, false, false, false, false, false, false); - break; - case ValueState.Boolean: - SetValueState(cell, false, false, default, true, toggleActive: true, false, CanWrite, false, false); - break; - case ValueState.Number: - SetValueState(cell, false, true, Color.white, true, false, inputActive: true, CanWrite, false, false); - break; - case ValueState.String: - SetValueState(cell, true, false, SignatureHighlighter.StringOrange, false, false, false, false, false, true); - break; - case ValueState.Enum: - SetValueState(cell, true, true, Color.white, false, false, false, false, false, true); - break; - case ValueState.Collection: - case ValueState.ValueStruct: - SetValueState(cell, true, true, Color.white, false, false, false, false, true, true); - break; - case ValueState.Unsupported: - SetValueState(cell, true, true, Color.white, false, false, false, false, true, false); - break; - } + return false; } - private void SetValueState(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.InputField.readOnly = !CanWrite; - } - - cell.ApplyButton.Button.gameObject.SetActive(applyActive); - cell.InspectButton.Button.gameObject.SetActive(inspectActive); - cell.SubContentButton.Button.gameObject.SetActive(subContentActive); - - cell.UpdateToggle.gameObject.SetActive(ShouldAutoEvaluate); - } - - #region Cache Member Util public static bool CanProcessArgs(ParameterInfo[] parameters) @@ -430,7 +242,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject cachedSigs.Add(sig); - cached.Initialize(_inspector, declaringType, member, returnType); + //cached.Initialize(_inspector, declaringType, member, returnType); + cached.Initialize(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 7221450..1d727ef 100644 --- a/src/UI/Inspectors/CacheObject/CacheMethod.cs +++ b/src/UI/Inspectors/CacheObject/CacheMethod.cs @@ -12,9 +12,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public override bool ShouldAutoEvaluate => false; - public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { - base.Initialize(inspector, declaringType, member, returnType); + base.SetInspectorOwner(inspector, member); Arguments = MethodInfo.GetParameters(); } diff --git a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs index 1409e81..65d3247 100644 --- a/src/UI/Inspectors/CacheObject/CacheObjectBase.cs +++ b/src/UI/Inspectors/CacheObject/CacheObjectBase.cs @@ -1,12 +1,307 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; +using UnityEngine; +using UnityExplorer.UI.Inspectors.CacheObject.Views; +using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.ObjectPool; +using UnityExplorer.UI.Utility; namespace UnityExplorer.UI.Inspectors.CacheObject { public abstract class CacheObjectBase { + public CacheObjectCell CellView { get; internal set; } + public InteractiveValue IValue { get; private set; } + public Type CurrentIValueType { get; private set; } + public bool SubContentState { get; private set; } + + public object Value { get; protected set; } + public Type FallbackType { get; protected set; } + + public string NameLabelText { get; protected set; } + public string TypeLabelText { get; protected set; } + public string ValueLabelText { get; protected set; } + + public abstract bool ShouldAutoEvaluate { get; } + public abstract bool HasArguments { get; } + public bool CanWrite { get; protected set; } + public bool HadException { get; protected set; } + public Exception LastException { get; protected set; } + + public virtual void Initialize(Type fallbackType) + { + this.FallbackType = fallbackType; + this.TypeLabelText = SignatureHighlighter.ParseFullType(FallbackType, false); + this.ValueLabelText = GetValueLabel(); + } + + // internals + + private static readonly Dictionary numberParseMethods = new Dictionary(); + + public enum ValueState + { + NotEvaluated, Exception, NullValue, + Boolean, Number, String, Enum, + Collection, ValueStruct, Unsupported + } + + public ValueState State = ValueState.NotEvaluated; + + protected const string NOT_YET_EVAL = "Not yet evaluated"; + + internal static GameObject InactiveIValueHolder + { + get + { + if (!inactiveIValueHolder) + { + inactiveIValueHolder = new GameObject("InactiveIValueHolder"); + GameObject.DontDestroyOnLoad(inactiveIValueHolder); + inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform; + inactiveIValueHolder.SetActive(false); + } + return inactiveIValueHolder; + } + } + private static GameObject inactiveIValueHolder; + + // On parent destroying this + + public virtual void OnDestroyed() + { + // TODO release IValue / Evaluate back to pool, etc + ReleaseIValue(); + } + + // Updating and applying values + + public abstract void SetUserValue(object value); + + /// + /// 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 + { + 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; + } + + // Set label text + ValueLabelText = GetValueLabel(); + + if (State != prevState) + { + // TODO handle if subcontent / evaluate shown, check type change, etc + } + } + + protected string GetValueLabel() + { + switch (State) + { + case ValueState.NotEvaluated: + return $"{NOT_YET_EVAL} ({SignatureHighlighter.ParseFullType(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); + } + } + + protected abstract bool SetCellEvaluateState(CacheObjectCell cell); + + public virtual void SetCell(CacheObjectCell cell) + { + cell.NameLabel.text = NameLabelText; + cell.ValueLabel.gameObject.SetActive(true); + + cell.SubContentHolder.gameObject.SetActive(SubContentState); + if (IValue != null) + IValue.UIRoot.transform.SetParent(cell.SubContentHolder.transform, false); + + if (SetCellEvaluateState(cell)) + return; + + switch (State) + { + case ValueState.Exception: + case ValueState.NullValue: + ReleaseIValue(); + SetValueState(cell, true, true, Color.white, false, false, false, false, false, false); + break; + case ValueState.Boolean: + SetValueState(cell, false, false, default, false, toggleActive: true, false, CanWrite, false, false); + break; + case ValueState.Number: + SetValueState(cell, false, true, Color.white, true, false, inputActive: true, CanWrite, false, false); + break; + case ValueState.String: + UpdateIValueOnValueUpdate(); + SetValueState(cell, true, false, SignatureHighlighter.StringOrange, false, false, false, false, false, true); + break; + case ValueState.Enum: + UpdateIValueOnValueUpdate(); + SetValueState(cell, true, true, Color.white, false, false, false, false, false, true); + break; + case ValueState.Collection: + case ValueState.ValueStruct: + UpdateIValueOnValueUpdate(); + SetValueState(cell, true, true, Color.white, false, false, false, false, true, true); + break; + case ValueState.Unsupported: + SetValueState(cell, true, true, Color.white, false, false, false, false, true, false); + break; + } + } + + protected virtual void SetValueState(CacheObjectCell 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.InputField.readOnly = !CanWrite; + } + + cell.ApplyButton.Button.gameObject.SetActive(applyActive); + cell.InspectButton.Button.gameObject.SetActive(inspectActive); + cell.SubContentButton.Button.gameObject.SetActive(subContentActive); + } + + // IValues + + public virtual void OnCellSubContentToggle() + { + if (this.IValue == null) + { + IValue = (InteractiveValue)Pool.Borrow(typeof(InteractiveValue)); + IValue.SetOwner(this); + IValue.UIRoot.transform.SetParent(CellView.SubContentHolder.transform, false); + CellView.SubContentHolder.SetActive(true); + SubContentState = true; + } + else + { + SubContentState = !SubContentState; + CellView.SubContentHolder.SetActive(SubContentState); + } + } + + public virtual void ReleaseIValue() + { + if (IValue == null) + return; + + IValue.OnOwnerReleased(); + Pool.Return(CurrentIValueType, IValue); + + IValue = null; + } + + internal void HideIValue() + { + if (this.IValue == null) + return; + + this.IValue.UIRoot.transform.SetParent(InactiveIValueHolder.transform, false); + } + + public void UpdateIValueOnValueUpdate() + { + if (this.IValue == null) + return; + + IValue.SetValue(Value); + } + + // CacheObjectCell Apply + + public virtual void OnCellApplyClicked() + { + if (CellView == null) + { + ExplorerCore.LogWarning("Trying to apply CacheMember but current cell reference is null!"); + return; + } + + if (State == ValueState.Boolean) + SetUserValue(this.CellView.Toggle.isOn); + else + { + if (!numberParseMethods.ContainsKey(FallbackType.AssemblyQualifiedName)) + { + var method = FallbackType.GetMethod("Parse", new Type[] { typeof(string) }); + numberParseMethods.Add(FallbackType.AssemblyQualifiedName, method); + } + + var val = numberParseMethods[FallbackType.AssemblyQualifiedName] + .Invoke(null, new object[] { CellView.InputField.text }); + SetUserValue(val); + } + + SetCell(this.CellView); + } } } diff --git a/src/UI/Inspectors/CacheObject/CacheProperty.cs b/src/UI/Inspectors/CacheObject/CacheProperty.cs index a775087..9915987 100644 --- a/src/UI/Inspectors/CacheObject/CacheProperty.cs +++ b/src/UI/Inspectors/CacheObject/CacheProperty.cs @@ -12,9 +12,9 @@ namespace UnityExplorer.UI.Inspectors.CacheObject public override bool ShouldAutoEvaluate => !HasArguments; - public override void Initialize(ReflectionInspector inspector, Type declaringType, MemberInfo member, Type returnType) + public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) { - base.Initialize(inspector, declaringType, member, returnType); + base.SetInspectorOwner(inspector, member); this.CanWrite = PropertyInfo.CanWrite; Arguments = PropertyInfo.GetIndexParameters(); diff --git a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs index 0bc8847..79d5d7f 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs +++ b/src/UI/Inspectors/CacheObject/Views/CacheMemberCell.cs @@ -4,207 +4,57 @@ 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 { - public class CacheMemberCell : ICell + public class CacheMemberCell : CacheObjectCell { - #region ICell - - public float DefaultHeight => 30f; - - public GameObject UIRoot => uiRoot; - public GameObject uiRoot; - - public bool Enabled => m_enabled; - private bool m_enabled; - - public RectTransform Rect => m_rect; - private RectTransform m_rect; - - public void Disable() - { - m_enabled = false; - uiRoot.SetActive(false); - } - - public void Enable() - { - m_enabled = true; - uiRoot.SetActive(true); - } - - #endregion - public ReflectionInspector CurrentOwner { get; set; } - public CacheMember CurrentOccupant { get; set; } - public LayoutElement MemberLayout; - public LayoutElement RightGroupLayout; - - public Text MemberLabel; - public Text TypeLabel; - public Text ValueLabel; - public Toggle Toggle; - public Text ToggleText; - public InputField InputField; + public CacheMember MemberOccupant => Occupant as CacheMember; public GameObject EvaluateHolder; public ButtonRef EvaluateButton; - public ButtonRef InspectButton; - public ButtonRef SubContentButton; - public ButtonRef ApplyButton; - public Toggle UpdateToggle; - public GameObject SubContentHolder; - - public void OnReturnToPool() + protected virtual void EvaluateClicked() { - if (CurrentOccupant != null) - { - // TODO + // TODO + } - CurrentOccupant = null; - } + public override void OnReturnToPool() + { + base.OnReturnToPool(); + + // TODO ? CurrentOwner = null; } - private void ApplyClicked() + protected override void ConstructEvaluateHolder(GameObject parent) { - CurrentOccupant.OnCellApplyClicked(); - } - - private void InspectClicked() - { - InspectorManager.Inspect(CurrentOccupant.Value); - } - - private void EvaluateClicked() - { - // TODO - } - - private void SubContentClicked() - { - // TODO - } - - private void ToggleClicked(bool value) - { - ToggleText.text = value.ToString(); - } - - // Todo could create these as needed maybe, just need to make sure the transform order is correct. - - public GameObject CreateContent(GameObject parent) - { - // Main layout - - uiRoot = UIFactory.CreateUIObject("CacheMemberCell", parent, new Vector2(100, 30)); - m_rect = uiRoot.GetComponent(); - 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; - - var separator = UIFactory.CreateUIObject("TopSeperator", uiRoot); - UIFactory.SetLayoutElement(separator, minHeight: 1, flexibleHeight: 0, flexibleWidth: 9999); - separator.AddComponent().color = Color.black; - - var horiRow = UIFactory.CreateUIObject("HoriGroup", uiRoot); - UIFactory.SetLayoutElement(horiRow, minHeight: 29, flexibleHeight: 150, flexibleWidth: 9999); - UIFactory.SetLayoutGroup(horiRow, false, false, true, true, 5, 2, childAlignment: TextAnchor.UpperLeft); - horiRow.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - - // Left member label - - 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(); - - // Right vertical group - - 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(); - // Evaluate vert group - EvaluateHolder = UIFactory.CreateUIObject("EvalGroup", rightGroupHolder); + EvaluateHolder = UIFactory.CreateUIObject("EvalGroup", parent); 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; + } - // Right horizontal group - - 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: 70, flexibleWidth: 0); - - // Bool and number value interaction - - 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); - - var inputObj = UIFactory.CreateInputField(rightHoriGroup, "InputField", "...", out InputField); - UIFactory.SetLayoutElement(inputObj, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); - - // Inspect and apply buttons - - 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; - - 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; - - // Main value label - - ValueLabel = UIFactory.CreateLabel(rightHoriGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); - ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap; - UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999); - + protected override void ConstructUpdateToggle(GameObject parent) + { // Auto-update toggle - var updateToggle = UIFactory.CreateToggle(rightHoriGroup, "AutoUpdate", out UpdateToggle, out Text autoText); + var updateToggle = UIFactory.CreateToggle(parent, "AutoUpdate", out UpdateToggle, out Text autoText); UIFactory.SetLayoutElement(updateToggle, minHeight: 25, minWidth: 30, flexibleWidth: 0, flexibleHeight: 0); GameObject.Destroy(autoText); UpdateToggle.isOn = false; - UpdateToggle.onValueChanged.AddListener((bool val) => { CurrentOccupant.AutoUpdateWanted = val; }); - - //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); - UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 500, minWidth: 100, flexibleWidth: 9999); - UIFactory.SetLayoutGroup(SubContentHolder, true, false, true, true, 2, childAlignment: TextAnchor.UpperLeft); - - SubContentHolder.SetActive(false); - - return uiRoot; + UpdateToggle.onValueChanged.AddListener((bool val) => { MemberOccupant.AutoUpdateWanted = val; }); } } } diff --git a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs index 1625fd1..9eb93b8 100644 --- a/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs +++ b/src/UI/Inspectors/CacheObject/Views/CacheObjectCell.cs @@ -2,10 +2,189 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Inspectors.IValues; +using UnityExplorer.UI.ObjectPool; +using UnityExplorer.UI.Utility; +using UnityExplorer.UI.Widgets; namespace UnityExplorer.UI.Inspectors.CacheObject.Views { - class CacheObjectCell + public abstract class CacheObjectCell : ICell { + #region ICell + + public float DefaultHeight => 30f; + + public GameObject UIRoot => uiRoot; + public GameObject uiRoot; + + public bool Enabled => m_enabled; + private bool m_enabled; + + public RectTransform Rect => m_rect; + private RectTransform m_rect; + + public void Disable() + { + m_enabled = false; + uiRoot.SetActive(false); + } + + public void Enable() + { + m_enabled = true; + uiRoot.SetActive(true); + } + + #endregion + + public CacheObjectBase Occupant { get; set; } + public bool SubContentActive => SubContentHolder.activeSelf; + + public LayoutElement MemberLayout; + public LayoutElement RightGroupLayout; + + public Text NameLabel; + public Text TypeLabel; + public Text ValueLabel; + public Toggle Toggle; + public Text ToggleText; + public InputField InputField; + + public ButtonRef InspectButton; + public ButtonRef SubContentButton; + public ButtonRef ApplyButton; + + public GameObject SubContentHolder; + + public virtual void OnReturnToPool() + { + if (Occupant != null) + { + // TODO ? + + SubContentHolder.SetActive(false); + + Occupant = null; + } + } + + protected virtual void ApplyClicked() + { + Occupant.OnCellApplyClicked(); + } + + protected virtual void InspectClicked() + { + InspectorManager.Inspect(Occupant.Value); + } + + protected virtual void ToggleClicked(bool value) + { + ToggleText.text = value.ToString(); + } + + protected virtual void SubContentClicked() + { + this.Occupant.OnCellSubContentToggle(); + } + + protected abstract void ConstructEvaluateHolder(GameObject parent); + + protected abstract void ConstructUpdateToggle(GameObject parent); + + // Todo could create these as needed maybe, just need to make sure the transform order is correct. + + public GameObject CreateContent(GameObject parent) + { + // Main layout + + uiRoot = UIFactory.CreateUIObject("CacheMemberCell", parent, new Vector2(100, 30)); + m_rect = uiRoot.GetComponent(); + 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; + + var separator = UIFactory.CreateUIObject("TopSeperator", uiRoot); + UIFactory.SetLayoutElement(separator, minHeight: 1, flexibleHeight: 0, flexibleWidth: 9999); + separator.AddComponent().color = Color.black; + + var horiRow = UIFactory.CreateUIObject("HoriGroup", uiRoot); + UIFactory.SetLayoutElement(horiRow, minHeight: 29, flexibleHeight: 150, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(horiRow, false, false, true, true, 5, 2, childAlignment: TextAnchor.UpperLeft); + horiRow.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; + + // Left member label + + NameLabel = UIFactory.CreateLabel(horiRow, "MemberLabel", "", TextAnchor.MiddleLeft); + NameLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0); + MemberLayout = NameLabel.GetComponent(); + + // Right vertical group + + 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(); + + ConstructEvaluateHolder(rightGroupHolder); + + // Right horizontal group + + 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; + + // Type label + + TypeLabel = UIFactory.CreateLabel(rightHoriGroup, "ReturnLabel", "", TextAnchor.MiddleLeft); + TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 60, flexibleWidth: 0); + + // Bool and number value interaction + + 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); + + var inputObj = UIFactory.CreateInputField(rightHoriGroup, "InputField", "...", out InputField); + UIFactory.SetLayoutElement(inputObj, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); + + // Inspect and apply buttons + + 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; + + 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; + + // Main value label + + ValueLabel = UIFactory.CreateLabel(rightHoriGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); + ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap; + UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999); + + ConstructUpdateToggle(rightHoriGroup); + + // Subcontent (todo?) + + SubContentHolder = UIFactory.CreateUIObject("SubContent", uiRoot); + UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 500, minWidth: 100, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(SubContentHolder, true, false, true, true, 2, childAlignment: TextAnchor.UpperLeft); + + SubContentHolder.SetActive(false); + + return uiRoot; + } } } diff --git a/src/UI/Inspectors/IValues/IValueTest.cs b/src/UI/Inspectors/IValues/IValueTest.cs deleted file mode 100644 index fa73fce..0000000 --- a/src/UI/Inspectors/IValues/IValueTest.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; -using UnityEngine.UI; -using UnityExplorer.UI.ObjectPool; - -namespace UnityExplorer.UI.Inspectors.IValues -{ - public class IValueTest : IPooledObject - { - public GameObject UIRoot => uiRoot; - private GameObject uiRoot; - - public float DefaultHeight => -1f; - - public GameObject CreateContent(GameObject parent) - { - uiRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); - UIFactory.SetLayoutGroup(uiRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); - - - - return uiRoot; - } - } -} diff --git a/src/UI/Inspectors/IValues/InteractiveValue.cs b/src/UI/Inspectors/IValues/InteractiveValue.cs new file mode 100644 index 0000000..6c64a94 --- /dev/null +++ b/src/UI/Inspectors/IValues/InteractiveValue.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.Inspectors.CacheObject; +using UnityExplorer.UI.ObjectPool; + +namespace UnityExplorer.UI.Inspectors.IValues +{ + public class InteractiveValue : IPooledObject + { + public GameObject UIRoot => uiRoot; + private GameObject uiRoot; + + public float DefaultHeight => -1f; + + public CacheObjectBase CurrentOwner { get; } + private CacheObjectBase m_owner; + + public object EditedValue { get; private set; } + + public virtual void SetOwner(CacheObjectBase owner) + { + if (this.m_owner != null) + { + ExplorerCore.LogWarning("Setting an IValue's owner but there is already one set. Maybe it wasn't cleaned up?"); + OnOwnerReleased(); + } + + this.m_owner = owner; + // ... + } + + public virtual void SetValue(object value) + { + this.EditedValue = value; + } + + public virtual void OnOwnerReleased() + { + if (this.m_owner == null) + return; + + // ... + this.m_owner = null; + } + + public GameObject CreateContent(GameObject parent) + { + uiRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); + UIFactory.SetLayoutGroup(uiRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); + + UIFactory.CreateLabel(uiRoot, "Label", "this is an ivalue", TextAnchor.MiddleLeft); + + return uiRoot; + } + } +} diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index d26e6c2..27c037b 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -19,7 +19,6 @@ namespace UnityExplorer.UI.Inspectors public class ReflectionInspector : InspectorBase, IPoolDataSource { public bool StaticOnly { get; internal set; } - //public bool AutoUpdate { get; internal set; } public object Target { get; private set; } public Type TargetType { get; private set; } @@ -38,6 +37,8 @@ namespace UnityExplorer.UI.Inspectors private LayoutElement memberTitleLayout; + private Toggle autoUpdateToggle; + public override void OnBorrowedFromPool(object target) { base.OnBorrowedFromPool(target); @@ -45,6 +46,7 @@ namespace UnityExplorer.UI.Inspectors SetTitleLayouts(); SetTarget(target); + MemberScrollPool.Initialize(this); RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); } @@ -53,9 +55,6 @@ namespace UnityExplorer.UI.Inspectors yield return null; LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect); - - MemberScrollPool.RecreateHeightCache(); - MemberScrollPool.Rebuild(); } public override void OnReturnToPool() @@ -71,6 +70,8 @@ namespace UnityExplorer.UI.Inspectors filteredMembers.Clear(); displayedMembers.Clear(); + autoUpdateToggle.isOn = false; + base.OnReturnToPool(); } @@ -113,6 +114,8 @@ namespace UnityExplorer.UI.Inspectors var member = members[i]; filteredMembers.Add(member); } + + //MemberScrollPool.RecreateHeightCache(); } public override void OnSetActive() @@ -161,11 +164,11 @@ namespace UnityExplorer.UI.Inspectors bool shouldRefresh = false; foreach (var member in displayedMembers) { - if (!onlyAutoUpdate || member.AutoUpdateWanted) + if (member.ShouldAutoEvaluate && (!onlyAutoUpdate || member.AutoUpdateWanted)) { shouldRefresh = true; member.Evaluate(); - member.SetCell(member.CurrentView); + member.SetCell(member.CellView); } } @@ -191,12 +194,12 @@ namespace UnityExplorer.UI.Inspectors { if (index < 0 || index >= filteredMembers.Count) { - if (cell.CurrentOccupant != null) + if (cell.Occupant != null) { - if (displayedMembers.Contains(cell.CurrentOccupant)) - displayedMembers.Remove(cell.CurrentOccupant); + if (displayedMembers.Contains(cell.MemberOccupant)) + displayedMembers.Remove(cell.MemberOccupant); - cell.CurrentOccupant.CurrentView = null; + cell.Occupant.CellView = null; } cell.Disable(); @@ -205,19 +208,17 @@ namespace UnityExplorer.UI.Inspectors var member = filteredMembers[index]; - if (member != cell.CurrentOccupant) + if (member != cell.Occupant) { - if (cell.CurrentOccupant != null) + if (cell.Occupant != null) { - // TODO - // changing occupant, put subcontent/evaluator on temp holder, etc - - displayedMembers.Remove(cell.CurrentOccupant); - cell.CurrentOccupant.CurrentView = null; + cell.Occupant.HideIValue(); + displayedMembers.Remove(cell.MemberOccupant); + cell.Occupant.CellView = null; } - cell.CurrentOccupant = member; - member.CurrentView = cell; + cell.Occupant = member; + member.CellView = cell; displayedMembers.Add(member); } @@ -226,6 +227,18 @@ namespace UnityExplorer.UI.Inspectors SetCellLayout(cell); } + private void ToggleAllAutoUpdateStates(bool state) + { + if (members == null || !members.Any()) + return; + + foreach (var member in members) + member.AutoUpdateWanted = state; + + foreach (var cell in MemberScrollPool.CellPool) + cell.UpdateToggle.isOn = state; + } + // Cell layout (fake table alignment) private static float MemLabelWidth { get; set; } @@ -240,7 +253,7 @@ namespace UnityExplorer.UI.Inspectors memberTitleLayout.minWidth = MemLabelWidth; } - private void SetCellLayout(CacheMemberCell cell) + private void SetCellLayout(CacheObjectCell cell) { cell.MemberLayout.minWidth = MemLabelWidth; cell.RightGroupLayout.minWidth = RightGroupWidth; @@ -279,7 +292,7 @@ namespace UnityExplorer.UI.Inspectors memberTitleLayout = memberTitle.gameObject.AddComponent(); var valueTitle = UIFactory.CreateLabel(listTitles, "ValueTitle", "Value", TextAnchor.LowerLeft, Color.grey, fontSize: 15); - UIFactory.SetLayoutElement(valueTitle.gameObject, minWidth: 150, flexibleWidth: 9999); + UIFactory.SetLayoutElement(valueTitle.gameObject, minWidth: 50, flexibleWidth: 9999); var updateButton = UIFactory.CreateButton(listTitles, "UpdateButton", "Update values", new Color(0.22f, 0.28f, 0.22f)); UIFactory.SetLayoutElement(updateButton.Button.gameObject, minHeight: 25, minWidth: 130, flexibleWidth: 0); @@ -288,17 +301,24 @@ namespace UnityExplorer.UI.Inspectors var updateText = UIFactory.CreateLabel(listTitles, "AutoUpdateLabel", "Auto-update", TextAnchor.MiddleRight, Color.grey); UIFactory.SetLayoutElement(updateText.gameObject, minHeight: 25, minWidth: 80, flexibleWidth: 0); + var toggleObj = UIFactory.CreateToggle(listTitles, "AutoUpdateToggle", out autoUpdateToggle, out Text toggleText); + GameObject.DestroyImmediate(toggleText); + UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: 25); + autoUpdateToggle.isOn = false; + autoUpdateToggle.onValueChanged.AddListener((bool val) => { ToggleAllAutoUpdateStates(val); }); + + var spacer = UIFactory.CreateUIObject("spacer", listTitles); + UIFactory.SetLayoutElement(spacer, minWidth: 25, flexibleWidth: 0); + // Member scroll pool MemberScrollPool = UIFactory.CreateScrollPool(uiRoot, "MemberList", out GameObject scrollObj, out GameObject _, new Color(0.09f, 0.09f, 0.09f)); UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); - MemberScrollPool.Initialize(this); - - InspectorPanel.Instance.UIRoot.GetComponent().enabled = false; - MemberScrollPool.Viewport.GetComponent().enabled = false; - MemberScrollPool.Viewport.GetComponent().color = new Color(0.12f, 0.12f, 0.12f); + //InspectorPanel.Instance.UIRoot.GetComponent().enabled = false; + //MemberScrollPool.Viewport.GetComponent().enabled = false; + //MemberScrollPool.Viewport.GetComponent().color = new Color(0.12f, 0.12f, 0.12f); return uiRoot; } diff --git a/src/UI/Models/UIModel.cs b/src/UI/Models/UIModel.cs index 1046d08..bb1c885 100644 --- a/src/UI/Models/UIModel.cs +++ b/src/UI/Models/UIModel.cs @@ -12,7 +12,7 @@ namespace UnityExplorer.UI.Models public bool Enabled { - get => UIRoot && UIRoot.activeSelf; + get => UIRoot && UIRoot.activeInHierarchy; set { if (!UIRoot || Enabled == value) diff --git a/src/UI/Panels/CSConsolePanel.cs b/src/UI/Panels/CSConsolePanel.cs new file mode 100644 index 0000000..7114ee0 --- /dev/null +++ b/src/UI/Panels/CSConsolePanel.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.UI.CSConsole; +using UnityExplorer.UI.Utility; + +namespace UnityExplorer.UI.Panels +{ + public class CSConsolePanel : UIPanel + { + public override string Name => "C# Console"; + public override UIManager.Panels PanelType => UIManager.Panels.CSConsole; + + public static CSConsolePanel Instance { get; private set; } + + public InputField InputField { get; private set; } + public Text InputText { get; private set; } + public Text HighlightText { get; private set; } + + public Action OnInputChanged; + private float m_timeOfLastInputInvoke; + + public Action OnResetClicked; + public Action OnCompileClicked; + public Action OnCtrlRToggled; + public Action OnSuggestionsToggled; + public Action OnAutoIndentToggled; + + private int m_lastCaretPosition; + private int m_desiredCaretFix; + private bool m_fixWaiting; + private float m_defaultInputFieldAlpha; + + public void UseSuggestion(string suggestion) + { + string input = InputField.text; + input = input.Insert(m_lastCaretPosition, suggestion); + InputField.text = input; + + m_desiredCaretFix = m_lastCaretPosition += suggestion.Length; + + var color = InputField.selectionColor; + color.a = 0f; + InputField.selectionColor = color; + } + + private void InvokeOnValueChanged(string value) + { + if (Time.time - m_timeOfLastInputInvoke <= 0f) + return; + + m_timeOfLastInputInvoke = Time.time; + OnInputChanged?.Invoke(value); + } + + public override void Update() + { + base.Update(); + + if (m_desiredCaretFix >= 0) + { + if (!m_fixWaiting) + { + EventSystem.current.SetSelectedGameObject(InputField.gameObject, null); + m_fixWaiting = true; + } + else + { + InputField.caretPosition = m_desiredCaretFix; + InputField.selectionFocusPosition = m_desiredCaretFix; + var color = InputField.selectionColor; + color.a = m_defaultInputFieldAlpha; + InputField.selectionColor = color; + + m_fixWaiting = false; + m_desiredCaretFix = -1; + } + } + else if (InputField.caretPosition > 0) + m_lastCaretPosition = InputField.caretPosition; + } + + // Saving + + public override void DoSaveToConfigElement() + { + ConfigManager.CSConsoleData.Value = this.ToSaveData(); + } + + public override void LoadSaveData() + { + this.ApplySaveData(ConfigManager.CSConsoleData.Value); + } + + public override void SetDefaultPosAndAnchors() + { + mainPanelRect.localPosition = Vector2.zero; + mainPanelRect.pivot = new Vector2(0.5f, 0.5f); + mainPanelRect.anchorMin = new Vector2(0.5f, 0); + mainPanelRect.anchorMax = new Vector2(0.5f, 1); + mainPanelRect.offsetMin = new Vector2(mainPanelRect.offsetMin.x, 100); // bottom + mainPanelRect.offsetMax = new Vector2(mainPanelRect.offsetMax.x, -50); // top + mainPanelRect.sizeDelta = new Vector2(700f, mainPanelRect.sizeDelta.y); + mainPanelRect.anchoredPosition = new Vector2(-150, 0); + } + + // UI Construction + + public override void ConstructPanelContent() + { + //Content = UIFactory.CreateVerticalGroup(MainMenu.Instance.PageViewport, "CSharpConsole", true, true, true, true); + //UIFactory.SetLayoutElement(Content, preferredHeight: 500, flexibleHeight: 9000); + + #region TOP BAR + + // Main group object + + var topBarObj = UIFactory.CreateHorizontalGroup(this.content, "TopBar", true, true, true, true, 10, new Vector4(8, 8, 30, 30), + default, TextAnchor.LowerCenter); + UIFactory.SetLayoutElement(topBarObj, minHeight: 50, flexibleHeight: 0); + + // Top label + + var topBarLabel = UIFactory.CreateLabel(topBarObj, "TopLabel", "C# Console", TextAnchor.MiddleLeft, default, true, 25); + UIFactory.SetLayoutElement(topBarLabel.gameObject, preferredWidth: 150, flexibleWidth: 5000); + + // Enable Ctrl+R toggle + + var ctrlRToggleObj = UIFactory.CreateToggle(topBarObj, "CtrlRToggle", out var CtrlRToggle, out Text ctrlRToggleText); + UIFactory.SetLayoutElement(ctrlRToggleObj, minWidth: 140, flexibleWidth: 0, minHeight: 25); + ctrlRToggleText.alignment = TextAnchor.UpperLeft; + ctrlRToggleText.text = "Run on Ctrl+R"; + CtrlRToggle.onValueChanged.AddListener((bool val) => { OnCtrlRToggled?.Invoke(val); }); + + // Enable Suggestions toggle + + var suggestToggleObj = UIFactory.CreateToggle(topBarObj, "SuggestionToggle", out var SuggestionsToggle, out Text suggestToggleText); + UIFactory.SetLayoutElement(suggestToggleObj, minWidth: 120, flexibleWidth: 0, minHeight: 25); + suggestToggleText.alignment = TextAnchor.UpperLeft; + suggestToggleText.text = "Suggestions"; + SuggestionsToggle.onValueChanged.AddListener((bool val) => { OnSuggestionsToggled?.Invoke(val); }); + + // Enable Auto-indent toggle + + var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, "IndentToggle", out var AutoIndentToggle, out Text autoIndentToggleText); + UIFactory.SetLayoutElement(autoIndentToggleObj, minWidth: 180, flexibleWidth: 0, minHeight: 25); + autoIndentToggleText.alignment = TextAnchor.UpperLeft; + autoIndentToggleText.text = "Auto-indent on Enter"; + AutoIndentToggle.onValueChanged.AddListener((bool val) => { OnAutoIndentToggled?.Invoke(val); }); + + #endregion + + #region CONSOLE INPUT + + int fontSize = 16; + + //var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsoleManager.STARTUP_TEXT, + // out InputFieldScroller consoleScroll, fontSize); + + var inputObj = UIFactory.CreateSrollInputField(this.content, "ConsoleInput", CSConsoleManager.STARTUP_TEXT, out var inputField, fontSize); + InputField = inputField.InputField; + m_defaultInputFieldAlpha = InputField.selectionColor.a; + InputField.onValueChanged.AddListener(InvokeOnValueChanged); + + var placeHolderText = InputField.placeholder.GetComponent(); + placeHolderText.fontSize = fontSize; + + InputText = InputField.textComponent; + InputText.supportRichText = false; + InputText.color = new Color(1, 1, 1, 0.5f); + + var mainTextObj = InputText.gameObject; + var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject); + var highlightTextRect = highlightTextObj.GetComponent(); + highlightTextRect.pivot = new Vector2(0, 1); + highlightTextRect.anchorMin = Vector2.zero; + highlightTextRect.anchorMax = Vector2.one; + highlightTextRect.offsetMin = new Vector2(20, 0); + highlightTextRect.offsetMax = new Vector2(14, 0); + + HighlightText = highlightTextObj.AddComponent(); + HighlightText.supportRichText = true; + HighlightText.fontSize = fontSize; + + #endregion + + #region COMPILE BUTTON BAR + + var horozGroupObj = UIFactory.CreateHorizontalGroup(this.content, "BigButtons", true, true, true, true, 0, new Vector4(2, 2, 2, 2), + new Color(1, 1, 1, 0)); + + var resetButton = UIFactory.CreateButton(horozGroupObj, "ResetButton", "Reset", new Color(0.33f, 0.33f, 0.33f)); + UIFactory.SetLayoutElement(resetButton.Button.gameObject, minHeight: 45, minWidth: 80, flexibleHeight: 0); + resetButton.ButtonText.fontSize = 18; + resetButton.OnClick += OnResetClicked; + + var compileButton = UIFactory.CreateButton(horozGroupObj, "CompileButton", "Compile", new Color(0.33f, 0.5f, 0.33f)); + UIFactory.SetLayoutElement(compileButton.Button.gameObject, minHeight: 45, minWidth: 80, flexibleHeight: 0); + compileButton.ButtonText.fontSize = 18; + compileButton.OnClick += OnCompileClicked; + + #endregion + + InputText.font = UIManager.ConsoleFont; + placeHolderText.font = UIManager.ConsoleFont; + HighlightText.font = UIManager.ConsoleFont; + + // reset this after formatting finalized + highlightTextRect.anchorMin = Vector2.zero; + highlightTextRect.anchorMax = Vector2.one; + highlightTextRect.offsetMin = Vector2.zero; + highlightTextRect.offsetMax = Vector2.zero; + } + } +} diff --git a/src/UI/Panels/ObjectExplorer.cs b/src/UI/Panels/ObjectExplorer.cs index c3bda6d..d55a966 100644 --- a/src/UI/Panels/ObjectExplorer.cs +++ b/src/UI/Panels/ObjectExplorer.cs @@ -24,6 +24,7 @@ namespace UnityExplorer.UI.Panels public SceneExplorer SceneExplorer; public ObjectSearch ObjectSearch; + public override bool ShowByDefault => true; public override bool ShouldSaveActiveState => true; public int SelectedTab = 0; diff --git a/src/UI/Panels/UIPanel.cs b/src/UI/Panels/UIPanel.cs index f19bfbc..f992390 100644 --- a/src/UI/Panels/UIPanel.cs +++ b/src/UI/Panels/UIPanel.cs @@ -76,6 +76,7 @@ namespace UnityExplorer.UI.Panels public abstract UIManager.Panels PanelType { get; } public abstract string Name { get; } + public virtual bool ShowByDefault => false; public virtual bool ShouldSaveActiveState => true; public virtual bool CanDragAndResize => true; public virtual bool NavButtonWanted => true; @@ -127,6 +128,8 @@ namespace UnityExplorer.UI.Panels public void ConstructUI() { + //this.Enabled = true; + if (NavButtonWanted) { // create navbar button @@ -143,12 +146,12 @@ namespace UnityExplorer.UI.Panels // create core canvas uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent); mainPanelRect = this.uiRoot.GetComponent(); - content = panelContent; + UIFactory.SetLayoutGroup(this.uiRoot, true, true, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft); int id = this.uiRoot.transform.GetInstanceID(); transformToPanelDict.Add(id, this); - UIFactory.SetLayoutGroup(this.uiRoot, true, true, true, true, 0, 0, 0, 0, 0, TextAnchor.UpperLeft); + content = panelContent; UIFactory.SetLayoutGroup(this.content, true, true, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft); // always apply default pos and anchors (save data may only be partial) @@ -166,10 +169,9 @@ namespace UnityExplorer.UI.Panels // close button - var closeBtn = UIFactory.CreateButton(titleGroup, "CloseButton", "X"); + var closeBtn = UIFactory.CreateButton(titleGroup, "CloseButton", "—"); UIFactory.SetLayoutElement(closeBtn.Button.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0); - RuntimeProvider.Instance.SetColorBlock(closeBtn.Button, new Color(0.63f, 0.32f, 0.31f), - new Color(0.81f, 0.25f, 0.2f), new Color(0.6f, 0.18f, 0.16f)); + RuntimeProvider.Instance.SetColorBlock(closeBtn.Button, new Color(0.33f, 0.32f, 0.31f)); closeBtn.OnClick += () => { @@ -186,12 +188,14 @@ namespace UnityExplorer.UI.Panels Dragger.OnFinishResize += OnFinishResize; Dragger.OnFinishDrag += OnFinishDrag; Dragger.AllowDragAndResize = this.CanDragAndResize; - //Dragger.CanResize = this.CanResize; // content (abstract) ConstructPanelContent(); + UIManager.SetPanelActive(this.PanelType, false); + UIManager.SetPanelActive(this.PanelType, ShowByDefault); + ApplyingSaveData = true; // apply panel save data or revert to default try diff --git a/src/UI/UIFactory.cs b/src/UI/UIFactory.cs index 401232e..59d916d 100644 --- a/src/UI/UIFactory.cs +++ b/src/UI/UIFactory.cs @@ -148,6 +148,8 @@ namespace UnityExplorer.UI public static GameObject CreatePanel(string name, out GameObject contentHolder, Color? bgColor = null) { var panelObj = CreateUIObject(name, UIManager.PanelHolder); + SetLayoutGroup(panelObj, true, true, true, true); + var rect = panelObj.GetComponent(); rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; @@ -155,17 +157,15 @@ namespace UnityExplorer.UI rect.sizeDelta = Vector2.zero; var maskImg = panelObj.AddComponent(); - maskImg.color = Color.white; - panelObj.AddComponent().showMaskGraphic = false; - - SetLayoutGroup(panelObj, true, true, true, true); + maskImg.color = Color.black; + panelObj.AddComponent().showMaskGraphic = true; contentHolder = CreateUIObject("Content", panelObj); Image bgImage = contentHolder.AddComponent(); bgImage.type = Image.Type.Filled; if (bgColor == null) - bgImage.color = new Color(0.06f, 0.06f, 0.06f); + bgImage.color = new Color(0.07f, 0.07f, 0.07f); else bgImage.color = (Color)bgColor; @@ -454,27 +454,6 @@ namespace UnityExplorer.UI return toggleObj; } - /// - /// Create a Scrollable Input Field control (custom InputFieldScroller). - /// - public static GameObject CreateSrollInputField(GameObject parent, string name, string placeHolderText, - out InputFieldScroller inputScroll, int fontSize = 14, Color color = default) - { - if (color == default) - color = new Color(0.15f, 0.15f, 0.15f); - - var mainObj = CreateScrollView(parent, "InputFieldScrollView", out GameObject scrollContent, out SliderScrollbar scroller, color); - - CreateInputField(scrollContent, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); - - inputField.lineType = InputField.LineType.MultiLineNewline; - inputField.targetGraphic.color = color; - - inputScroll = new InputFieldScroller(scroller, inputField); - - return mainObj; - } - // Little helper class to force rebuild of an input field's layout on value change. // This is limited to once per frame per input field, so its not too expensive. private class InputFieldRefresher @@ -507,6 +486,7 @@ namespace UnityExplorer.UI int fontSize = 14, int alignment = 3, int wrap = 0) { GameObject mainObj = CreateUIObject(name, parent); + //SetLayoutGroup(mainObj, true, true, true, true); Image mainImage = mainObj.AddComponent(); mainImage.type = Image.Type.Sliced; @@ -524,10 +504,9 @@ namespace UnityExplorer.UI RuntimeProvider.Instance.SetColorBlock(inputField, new Color(1, 1, 1, 1), new Color(0.95f, 0.95f, 0.95f, 1.0f), new Color(0.78f, 0.78f, 0.78f, 1.0f)); - SetLayoutGroup(mainObj, true, true, true, true); - GameObject textArea = CreateUIObject("TextArea", mainObj); textArea.AddComponent(); + //textArea.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; RectTransform textAreaRect = textArea.GetComponent(); textAreaRect.anchorMin = Vector2.zero; @@ -552,7 +531,7 @@ namespace UnityExplorer.UI placeHolderRect.offsetMin = Vector2.zero; placeHolderRect.offsetMax = Vector2.zero; - SetLayoutElement(placeHolderObj, minWidth: 500, flexibleWidth: 5000); + SetLayoutElement(placeHolderObj, minWidth: 200, flexibleWidth: 5000); inputField.placeholder = placeholderText; @@ -571,7 +550,7 @@ namespace UnityExplorer.UI inputTextRect.offsetMin = Vector2.zero; inputTextRect.offsetMax = Vector2.zero; - SetLayoutElement(inputTextObj, minWidth: 500, flexibleWidth: 5000); + SetLayoutElement(inputTextObj, minWidth: 200, flexibleWidth: 5000); inputField.textComponent = inputText; @@ -773,73 +752,130 @@ namespace UnityExplorer.UI SetLayoutElement(sliderContainer, minWidth: 25, flexibleWidth:0, flexibleHeight: 9999); sliderContainer.AddComponent(); - var sliderObj = SliderScrollbar.CreateSliderScrollbar(sliderContainer, out Slider slider); - slider.direction = Slider.Direction.TopToBottom; - SetLayoutElement(sliderObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + CreateSliderScrollbar(sliderContainer, out Slider slider); RuntimeProvider.Instance.SetColorBlock(slider, disabled: new Color(0.1f, 0.1f, 0.1f)); - slider.handleRect.offsetMin = new Vector2(slider.handleRect.offsetMin.x, 0); - slider.handleRect.offsetMax = new Vector2(slider.handleRect.offsetMax.x, 0); - slider.handleRect.pivot = new Vector2(0.5f, 0.5f); - - var container = slider.m_HandleContainerRect; - container.anchorMin = Vector3.zero; - container.anchorMax = Vector3.one; - container.pivot = new Vector3(0.5f, 0.5f); - // finalize and create ScrollPool uiRoot = mainObj; var scrollPool = new ScrollPool(scrollRect); - //viewportObj.GetComponent().enabled = false; - return scrollPool; } + public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider) + { + GameObject mainObj = CreateUIObject("SliderScrollbar", parent, UIFactory._smallElementSize); + + GameObject bgImageObj = CreateUIObject("Background", mainObj); + GameObject handleSlideAreaObj = CreateUIObject("Handle Slide Area", mainObj); + GameObject handleObj = CreateUIObject("Handle", handleSlideAreaObj); + + Image bgImage = bgImageObj.AddComponent(); + bgImage.type = Image.Type.Sliced; + bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f); + + RectTransform bgRect = bgImageObj.GetComponent(); + bgRect.pivot = new Vector2(0, 1); + bgRect.anchorMin = Vector2.zero; + bgRect.anchorMax = Vector2.one; + bgRect.sizeDelta = Vector2.zero; + bgRect.offsetMax = new Vector2(0f, 0f); + + RectTransform handleSlideRect = handleSlideAreaObj.GetComponent(); + handleSlideRect.anchorMin = Vector3.zero; + handleSlideRect.anchorMax = Vector3.one; + handleSlideRect.pivot = new Vector3(0.5f, 0.5f); + + Image handleImage = handleObj.AddComponent(); + handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f); + + var handleRect = handleObj.GetComponent(); + handleRect.pivot = new Vector2(0.5f, 0.5f); + UIFactory.SetLayoutElement(handleObj, minWidth: 21, flexibleWidth: 0); + + var sliderBarLayout = mainObj.AddComponent(); + sliderBarLayout.minWidth = 25; + sliderBarLayout.flexibleWidth = 0; + sliderBarLayout.minHeight = 30; + sliderBarLayout.flexibleHeight = 5000; + + slider = mainObj.AddComponent(); + slider.handleRect = handleObj.GetComponent(); + slider.targetGraphic = handleImage; + slider.direction = Slider.Direction.TopToBottom; + + SetLayoutElement(mainObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + + RuntimeProvider.Instance.SetColorBlock(slider, + new Color(0.4f, 0.4f, 0.4f), + new Color(0.5f, 0.5f, 0.5f), + new Color(0.3f, 0.3f, 0.3f), + new Color(0.5f, 0.5f, 0.5f)); + + return mainObj; + } + /// /// Create a ScrollView element. /// - public static GameObject CreateScrollView(GameObject parent, string name, out GameObject content, out SliderScrollbar scroller, + public static GameObject CreateAutoScrollView(GameObject parent, string name, out GameObject content, out AutoSliderScrollbar autoScrollbar, Color color = default) { - GameObject mainObj = CreateUIObject("DynamicScrollView", parent); - + GameObject mainObj = CreateUIObject(name, parent); SetLayoutElement(mainObj, minWidth: 100, minHeight: 30, flexibleWidth: 5000, flexibleHeight: 5000); - + SetLayoutGroup(mainObj, false, true, true, true, 2); Image mainImage = mainObj.AddComponent(); mainImage.type = Image.Type.Filled; mainImage.color = (color == default) ? new Color(0.3f, 0.3f, 0.3f, 1f) : color; GameObject viewportObj = CreateUIObject("Viewport", mainObj); - + UIFactory.SetLayoutElement(viewportObj, minWidth: 1, flexibleWidth: 9999, flexibleHeight: 9999); var viewportRect = viewportObj.GetComponent(); viewportRect.anchorMin = Vector2.zero; viewportRect.anchorMax = Vector2.one; viewportRect.pivot = new Vector2(0.0f, 1.0f); - viewportRect.sizeDelta = new Vector2(-15.0f, 0.0f); - viewportRect.offsetMax = new Vector2(-20.0f, 0.0f); - + //viewportRect.sizeDelta = new Vector2(-15.0f, 0.0f); + //viewportRect.offsetMax = new Vector2(-25.0f, 0.0f); viewportObj.AddComponent().color = Color.white; viewportObj.AddComponent().showMaskGraphic = false; content = CreateUIObject("Content", viewportObj); var contentRect = content.GetComponent(); + SetLayoutGroup(content, true, true, true, true);//, 5, 5, 5, 5, 5); contentRect.anchorMin = new Vector2(0.0f, 1.0f); contentRect.anchorMax = new Vector2(1.0f, 1.0f); contentRect.pivot = new Vector2(0.0f, 1.0f); - contentRect.sizeDelta = new Vector2(5f, 0f); - contentRect.offsetMax = new Vector2(0f, 0f); - var contentFitter = content.AddComponent(); - contentFitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained; - contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; + //contentRect.sizeDelta = new Vector2(5f, 0f); + //contentRect.offsetMax = new Vector2(0f, 0f); - SetLayoutGroup(content, true, true, true, true, 5, 5, 5, 5, 5); + // Slider - CreateSliderScrollbar(mainObj, out scroller, out Scrollbar hiddenScrollbar); + GameObject scrollBarObj = CreateUIObject("AutoSliderScrollbar", mainObj); + SetLayoutGroup(scrollBarObj, true, true, true, true); + SetLayoutElement(scrollBarObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + scrollBarObj.AddComponent().color = Color.white; + scrollBarObj.AddComponent().showMaskGraphic = false; + + GameObject hiddenBar = CreateScrollbar(scrollBarObj, "HiddenScrollviewScroller", out var hiddenScrollbar); + hiddenScrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true); - // Back to the main scrollview ScrollRect, setting it up now that we have all references. + for (int i = 0; i < hiddenBar.transform.childCount; i++) + { + var child = hiddenBar.transform.GetChild(i); + child.gameObject.SetActive(false); + } + + CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider); + + autoScrollbar = new AutoSliderScrollbar(hiddenScrollbar, scrollSlider, contentRect, viewportRect); + + //var sliderContainer = autoScrollbar.Slider.m_HandleContainerRect.gameObject; + //SetLayoutElement(sliderContainer, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + //sliderContainer.AddComponent(); + + // Set up the ScrollRect component var scrollRect = mainObj.AddComponent(); scrollRect.horizontal = false; @@ -856,21 +892,54 @@ namespace UnityExplorer.UI return mainObj; } - public static GameObject CreateSliderScrollbar(GameObject mainObj, out SliderScrollbar scroller, out Scrollbar hiddenScrollbar) + + /// + /// Create a Scrollable Input Field control (custom InputFieldScroller). + /// + public static GameObject CreateSrollInputField(GameObject parent, string name, string placeHolderText, out InputFieldScroller inputScroll, + int fontSize = 14, Color color = default) { - GameObject scrollBarObj = CreateUIObject("DynamicScrollbar", mainObj); + if (color == default) + color = new Color(0.12f, 0.12f, 0.12f); - var scrollbarLayout = scrollBarObj.AddComponent(); - scrollbarLayout.childForceExpandHeight = true; - scrollbarLayout.SetChildControlHeight(true); + GameObject mainObj = CreateUIObject(name, parent); + SetLayoutElement(mainObj, minWidth: 100, minHeight: 30, flexibleWidth: 5000, flexibleHeight: 5000); + SetLayoutGroup(mainObj, false, true, true, true, 2); + Image mainImage = mainObj.AddComponent(); + mainImage.type = Image.Type.Filled; + mainImage.color = (color == default) ? new Color(0.3f, 0.3f, 0.3f, 1f) : color; - RectTransform scrollBarRect = scrollBarObj.GetComponent(); - scrollBarRect.anchorMin = new Vector2(1.0f, 0.0f); - scrollBarRect.anchorMax = new Vector2(1.0f, 1.0f); - scrollBarRect.sizeDelta = new Vector2(15.0f, 0.0f); - scrollBarRect.offsetMin = new Vector2(-15.0f, 0.0f); + GameObject viewportObj = CreateUIObject("Viewport", mainObj); + SetLayoutElement(viewportObj, minWidth: 1, flexibleWidth: 9999, flexibleHeight: 9999); + var viewportRect = viewportObj.GetComponent(); + viewportRect.anchorMin = Vector2.zero; + viewportRect.anchorMax = Vector2.one; + viewportRect.pivot = new Vector2(0.0f, 1.0f); + viewportObj.AddComponent().color = Color.white; + viewportObj.AddComponent().showMaskGraphic = false; - GameObject hiddenBar = CreateScrollbar(scrollBarObj, "HiddenScrollviewScroller", out hiddenScrollbar); + // Input Field + + var content = CreateInputField(viewportObj, name, placeHolderText ?? "...", out InputField inputField, fontSize, 0); + SetLayoutElement(content, flexibleHeight: 9999, flexibleWidth: 9999); + var contentRect = content.GetComponent(); + contentRect.pivot = new Vector2(0, 1); + contentRect.anchorMin = new Vector2(0, 1); + contentRect.anchorMax = new Vector2(1, 1); + contentRect.offsetMin = new Vector2(2, 0); + contentRect.offsetMax = new Vector2(2, 0); + inputField.lineType = InputField.LineType.MultiLineNewline; + inputField.targetGraphic.color = color; + + // Slider + + GameObject scrollBarObj = CreateUIObject("AutoSliderScrollbar", mainObj); + SetLayoutGroup(scrollBarObj, true, true, true, true); + SetLayoutElement(scrollBarObj, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + scrollBarObj.AddComponent().color = Color.white; + scrollBarObj.AddComponent().showMaskGraphic = false; + + GameObject hiddenBar = CreateScrollbar(scrollBarObj, "HiddenScrollviewScroller", out var hiddenScrollbar); hiddenScrollbar.SetDirection(Scrollbar.Direction.BottomToTop, true); for (int i = 0; i < hiddenBar.transform.childCount; i++) @@ -879,11 +948,37 @@ namespace UnityExplorer.UI child.gameObject.SetActive(false); } - SliderScrollbar.CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider); + CreateSliderScrollbar(scrollBarObj, out Slider scrollSlider); - scroller = new SliderScrollbar(hiddenScrollbar, scrollSlider); + // Set up the AutoSliderScrollbar module - return scrollBarObj; + var autoScroller = new AutoSliderScrollbar(hiddenScrollbar, scrollSlider, contentRect, viewportRect); + + var sliderContainer = autoScroller.Slider.m_HandleContainerRect.gameObject; + SetLayoutElement(sliderContainer, minWidth: 25, flexibleWidth: 0, flexibleHeight: 9999); + sliderContainer.AddComponent(); + + // Set up the InputFieldScroller module + + inputScroll = new InputFieldScroller(autoScroller, inputField); + inputScroll.ProcessInputText(); + + // Set up the ScrollRect component + + var scrollRect = mainObj.AddComponent(); + scrollRect.horizontal = false; + scrollRect.vertical = true; + scrollRect.verticalScrollbar = hiddenScrollbar; + scrollRect.movementType = ScrollRect.MovementType.Clamped; + scrollRect.scrollSensitivity = 35; + scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHideAndExpandViewport; + scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.Permanent; + + scrollRect.viewport = viewportRect; + scrollRect.content = contentRect; + + + return mainObj; } } } diff --git a/src/UI/UIManager.cs b/src/UI/UIManager.cs index fee3f7c..aa6eaa6 100644 --- a/src/UI/UIManager.cs +++ b/src/UI/UIManager.cs @@ -41,6 +41,8 @@ namespace UnityExplorer.UI public static ObjectExplorer Explorer { get; private set; } public static InspectorPanel Inspector { get; private set; } + public static CSConsolePanel CSConsole { get; private set; } + public static AutoCompleter AutoCompleter { get; private set; } // other @@ -50,6 +52,23 @@ 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 static UIPanel GetPanel(Panels panel) + { + switch (panel) + { + case Panels.ObjectExplorer: + return Explorer; + case Panels.Inspector: + return Inspector; + case Panels.AutoCompleter: + return AutoCompleter; + case Panels.CSConsole: + return CSConsole; + default: + throw new NotImplementedException($"TODO GetPanel: {panel}"); + } + } + // main menu toggle public static bool ShowMenu { @@ -96,21 +115,6 @@ namespace UnityExplorer.UI AutoCompleter.Update(); } - public static UIPanel GetPanel(Panels panel) - { - switch (panel) - { - case Panels.ObjectExplorer: - return Explorer; - case Panels.Inspector: - return Inspector; - case Panels.AutoCompleter: - return AutoCompleter; - default: - throw new NotImplementedException($"TODO GetPanel: {panel}"); - } - } - public static void TogglePanel(Panels panel) { var uiPanel = GetPanel(panel); @@ -153,17 +157,20 @@ namespace UnityExplorer.UI CreateTopNavBar(); + //InspectUnderMouse.ConstructUI(); + AutoCompleter = new AutoCompleter(); AutoCompleter.ConstructUI(); - //InspectUnderMouse.ConstructUI(); - Explorer = new ObjectExplorer(); Explorer.ConstructUI(); Inspector = new InspectorPanel(); Inspector.ConstructUI(); + CSConsole = new CSConsolePanel(); + CSConsole.ConstructUI(); + ShowMenu = !ConfigManager.Hide_On_Startup.Value; ExplorerCore.Log("UI initialized."); diff --git a/src/UI/Widgets/AutoSliderScrollbar.cs b/src/UI/Widgets/AutoSliderScrollbar.cs new file mode 100644 index 0000000..6117602 --- /dev/null +++ b/src/UI/Widgets/AutoSliderScrollbar.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEngine.Events; +using UnityEngine.UI; +using UnityExplorer; +using UnityExplorer.Core; +using UnityExplorer.UI; +using UnityExplorer.UI.Models; + +namespace UnityExplorer.UI.Utility +{ + // A Slider Scrollbar which automatically resizes for the content size (no pooling). + // Currently just used for the C# Console input field. + + public class AutoSliderScrollbar : UIBehaviourModel + { + public override GameObject UIRoot + { + get + { + if (Slider) + return Slider.gameObject; + return null; + } + } + + //public event Action OnValueChanged; + + internal readonly Scrollbar Scrollbar; + internal readonly Slider Slider; + internal RectTransform ContentRect; + internal RectTransform ViewportRect; + + //internal InputFieldScroller m_parentInputScroller; + + public AutoSliderScrollbar(Scrollbar scrollbar, Slider slider, RectTransform contentRect, RectTransform viewportRect) + { + this.Scrollbar = scrollbar; + this.Slider = slider; + this.ContentRect = contentRect; + this.ViewportRect = viewportRect; + + this.Scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged); + this.Slider.onValueChanged.AddListener(this.OnSliderValueChanged); + + //this.RefreshVisibility(); + this.Slider.Set(0f, false); + } + + private float lastAnchorPosition; + private float lastContentHeight; + private float lastViewportHeight; + private bool _refreshWanted; + + public override void Update() + { + if (!Enabled) + return; + + _refreshWanted = false; + if (ContentRect.localPosition.y != lastAnchorPosition) + { + lastAnchorPosition = ContentRect.localPosition.y; + _refreshWanted = true; + } + if (ContentRect.rect.height != lastContentHeight) + { + lastContentHeight = ContentRect.rect.height; + _refreshWanted = true; + } + if (ViewportRect.rect.height != lastViewportHeight) + { + lastViewportHeight = ViewportRect.rect.height; + _refreshWanted = true; + } + + if (_refreshWanted) + UpdateSliderHandle(); + } + + public void UpdateSliderHandle() + { + // calculate handle size based on viewport / total data height + var totalHeight = ContentRect.rect.height; + var viewportHeight = ViewportRect.rect.height; + + if (totalHeight <= viewportHeight) + { + Slider.value = 0f; + Slider.interactable = false; + return; + } + + var handleHeight = viewportHeight * Math.Min(1, viewportHeight / totalHeight); + handleHeight = Math.Max(15f, handleHeight); + + // resize the handle container area for the size of the handle (bigger handle = smaller container) + var container = Slider.m_HandleContainerRect; + container.offsetMax = new Vector2(container.offsetMax.x, -(handleHeight * 0.5f)); + container.offsetMin = new Vector2(container.offsetMin.x, handleHeight * 0.5f); + + // set handle size + Slider.handleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, handleHeight); + + // if slider is 100% height then make it not interactable + Slider.interactable = !Mathf.Approximately(handleHeight, viewportHeight); + + float val = 0f; + if (totalHeight > 0f) + val = (float)((decimal)ContentRect.localPosition.y / (decimal)(totalHeight - ViewportRect.rect.height)); + + Slider.value = val; + } + + public void OnScrollbarValueChanged(float value) + { + value = 1f - value; + if (this.Slider.value != value) + this.Slider.Set(value, false); + //OnValueChanged?.Invoke(value); + } + + public void OnSliderValueChanged(float value) + { + value = 1f - value; + this.Scrollbar.value = value; + //OnValueChanged?.Invoke(value); + } + + public override void ConstructUI(GameObject parent) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/UI/Widgets/InputFieldScroller.cs b/src/UI/Widgets/InputFieldScroller.cs index 16db052..11a3861 100644 --- a/src/UI/Widgets/InputFieldScroller.cs +++ b/src/UI/Widgets/InputFieldScroller.cs @@ -19,106 +19,93 @@ namespace UnityExplorer.UI.Utility { get { - if (inputField) - return inputField.gameObject; + if (InputField) + return InputField.gameObject; return null; } } - internal SliderScrollbar sliderScroller; - internal InputField inputField; + internal AutoSliderScrollbar Slider; + internal InputField InputField; - internal RectTransform inputRect; - internal LayoutElement layoutElement; - internal VerticalLayoutGroup parentLayoutGroup; + internal RectTransform ContentRect; + internal RectTransform ViewportRect; - internal static CanvasScaler canvasScaler; + internal static CanvasScaler RootScaler; - public InputFieldScroller(SliderScrollbar sliderScroller, InputField inputField) + public InputFieldScroller(AutoSliderScrollbar sliderScroller, InputField inputField) { - //Instances.Add(this); - - this.sliderScroller = sliderScroller; - this.inputField = inputField; - - sliderScroller.m_parentInputScroller = this; + this.Slider = sliderScroller; + this.InputField = inputField; inputField.onValueChanged.AddListener(OnTextChanged); - inputRect = inputField.GetComponent(); - layoutElement = inputField.gameObject.AddComponent(); - parentLayoutGroup = inputField.transform.parent.GetComponent(); + ContentRect = inputField.GetComponent(); + ViewportRect = ContentRect.transform.parent.GetComponent(); - layoutElement.minHeight = 25; - layoutElement.minWidth = 100; - - if (!canvasScaler) - canvasScaler = UIManager.CanvasRoot.GetComponent(); + if (!RootScaler) + RootScaler = UIManager.CanvasRoot.GetComponent(); } internal string m_lastText; internal bool m_updateWanted; - - // only done once, to fix height on creation. - internal bool heightInitAfterLayout; + internal bool m_wantJumpToBottom; + private float m_desiredContentHeight; public override void Update() { - if (!heightInitAfterLayout) - { - heightInitAfterLayout = true; - var height = sliderScroller.m_scrollRect.parent.parent.GetComponent().rect.height; - layoutElement.preferredHeight = height; - } - - if (m_updateWanted && inputField.gameObject.activeInHierarchy) + if (m_updateWanted) { m_updateWanted = false; - RefreshUI(); + ProcessInputText(); + } + + float desiredHeight = Math.Max(m_desiredContentHeight, ViewportRect.rect.height); + + if (ContentRect.rect.height < desiredHeight) + { + ContentRect.sizeDelta = new Vector2(0, desiredHeight); + this.Slider.UpdateSliderHandle(); + } + else if (ContentRect.rect.height > desiredHeight) + { + ContentRect.sizeDelta = new Vector2(0, desiredHeight); + this.Slider.UpdateSliderHandle(); + } + + if (m_wantJumpToBottom) + { + Slider.Slider.value = 1f; + m_wantJumpToBottom = false; } } - //internal bool CheckDestroyed() - //{ - // if (sliderScroller == null || sliderScroller.CheckDestroyed()) - // { - // Instances.Remove(this); - // return true; - // } - - // return false; - //} - internal void OnTextChanged(string text) { m_lastText = text; m_updateWanted = true; } - internal void RefreshUI() + internal void ProcessInputText() { - var curInputRect = inputField.textComponent.rectTransform.rect; - var scaleFactor = canvasScaler.scaleFactor; + var curInputRect = InputField.textComponent.rectTransform.rect; + var scaleFactor = RootScaler.scaleFactor; // Current text settings - var texGenSettings = inputField.textComponent.GetGenerationSettings(curInputRect.size); + var texGenSettings = InputField.textComponent.GetGenerationSettings(curInputRect.size); texGenSettings.generateOutOfBounds = false; texGenSettings.scaleFactor = scaleFactor; // Preferred text rect height - var textGen = inputField.textComponent.cachedTextGeneratorForLayout; - float preferredHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10; + var textGen = InputField.textComponent.cachedTextGeneratorForLayout; + m_desiredContentHeight = textGen.GetPreferredHeight(m_lastText, texGenSettings) + 10; - // Default text rect height (fit to scroll parent or expand to fit text) - float minHeight = Mathf.Max(preferredHeight, sliderScroller.m_scrollRect.rect.height - 25); - - layoutElement.preferredHeight = minHeight; - - if (inputField.caretPosition == inputField.text.Length - && inputField.text.Length > 0 - && inputField.text[inputField.text.Length - 1] == '\n') + // jump to bottom + if (InputField.caretPosition == InputField.text.Length + && InputField.text.Length > 0 + && InputField.text[InputField.text.Length - 1] == '\n') { - sliderScroller.m_slider.value = 0f; + m_wantJumpToBottom = true; } } diff --git a/src/UI/Widgets/ScrollPool/DataHeightCache.cs b/src/UI/Widgets/ScrollPool/DataHeightCache.cs index 8412cd4..7e61c80 100644 --- a/src/UI/Widgets/ScrollPool/DataHeightCache.cs +++ b/src/UI/Widgets/ScrollPool/DataHeightCache.cs @@ -67,7 +67,7 @@ namespace UnityExplorer.UI.Widgets // 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 (!Mathf.Approximately(rem, 0f)) + if (rem != 0.0f) height -= (DefaultHeight - rem); return (int)Math.Ceiling((decimal)height / (decimal)DefaultHeight); @@ -139,8 +139,11 @@ namespace UnityExplorer.UI.Widgets { if (dataIndex >= ScrollPool.DataSource.ItemCount) { - while (heightCache.Count > dataIndex) - RemoveLast(); + if (heightCache.Count > dataIndex) + { + while (heightCache.Count > dataIndex) + RemoveLast(); + } return; } @@ -249,14 +252,6 @@ namespace UnityExplorer.UI.Widgets } } } - - //// if sister cache is set, then update it too. - //if (SisterCache != null) - //{ - // var realIdx = ScrollPool.DataSource.GetRealIndexOfTempIndex(dataIndex); - // if (realIdx >= 0) - // SisterCache.SetIndex(realIdx, height, true); - //} } private void RebuildCache() diff --git a/src/UI/Widgets/ScrollPool/ScrollPool.cs b/src/UI/Widgets/ScrollPool/ScrollPool.cs index 97befb4..5abf911 100644 --- a/src/UI/Widgets/ScrollPool/ScrollPool.cs +++ b/src/UI/Widgets/ScrollPool/ScrollPool.cs @@ -115,7 +115,7 @@ namespace UnityExplorer.UI.Widgets if (writingLocked && timeofLastWriteLock < Time.time) writingLocked = false; - if (prevContentHeight <= 1f && Content?.rect.height > 1f) + if (prevContentHeight <= 1f && Content.rect.height > 1f) { prevContentHeight = Content.rect.height; } @@ -131,10 +131,12 @@ namespace UnityExplorer.UI.Widgets public void Rebuild() { + HeightCache = new DataHeightCache(this); + SetRecycleViewBounds(false); SetScrollBounds(); - ExtendCellPool(); + CheckExtendCellPool(); writingLocked = false; Content.anchoredPosition = Vector2.zero; UpdateSliderHandle(true); @@ -163,6 +165,7 @@ namespace UnityExplorer.UI.Widgets // Initialize + private bool m_doneFirstInit; private bool m_initialized; public void Initialize(IPoolDataSource dataSource) @@ -173,12 +176,16 @@ namespace UnityExplorer.UI.Widgets HeightCache = new DataHeightCache(this); DataSource = dataSource; - this.contentLayout = ScrollRect.content.GetComponent(); - this.slider = ScrollRect.GetComponentInChildren(); - slider.onValueChanged.AddListener(OnSliderValueChanged); + if (!m_doneFirstInit) + { + m_doneFirstInit = true; + this.contentLayout = ScrollRect.content.GetComponent(); + this.slider = ScrollRect.GetComponentInChildren(); + slider.onValueChanged.AddListener(OnSliderValueChanged); - ScrollRect.vertical = true; - ScrollRect.horizontal = false; + ScrollRect.vertical = true; + ScrollRect.horizontal = false; + } ScrollRect.onValueChanged.RemoveListener(OnValueChangedListener); RuntimeProvider.Instance.StartCoroutine(InitCoroutine()); @@ -224,12 +231,18 @@ namespace UnityExplorer.UI.Widgets } CellPool.Clear(); } + + bottomDataIndex = -1; + topPoolIndex = 0; + bottomPoolIndex = 0; } - private void CreateCellPool(bool andResetDataIndex = true) + private void CreateCellPool() { ReturnCells(); + CheckDataSourceCountChange(out _); + float currentPoolCoverage = 0f; float requiredCoverage = ScrollRect.viewport.rect.height + RecycleThreshold; @@ -250,8 +263,7 @@ namespace UnityExplorer.UI.Widgets currentPoolCoverage += PrototypeHeight; } - if (andResetDataIndex) - bottomDataIndex = CellPool.Count - 1; + bottomDataIndex = CellPool.Count - 1; LayoutRebuilder.ForceRebuildLayoutImmediate(Content); @@ -266,13 +278,12 @@ namespace UnityExplorer.UI.Widgets RecycleViewBounds = new Vector2(Viewport.MinY() + HalfThreshold, Viewport.MaxY() - HalfThreshold); if (extendPoolIfGrown && prevViewportHeight < Viewport.rect.height && prevViewportHeight != 0.0f) - ExtendCellPool(); + CheckExtendCellPool(); prevViewportHeight = Viewport.rect.height; - } - private bool ExtendCellPool() + private bool CheckExtendCellPool() { CheckDataSourceCountChange(out _); @@ -285,6 +296,7 @@ namespace UnityExplorer.UI.Widgets bottomDataIndex += cellsRequired; + // TODO sometimes still jumps a litte bit, need to figure out why. float prevAnchor = Content.localPosition.y; float prevHeight = Content.rect.height; @@ -299,21 +311,29 @@ namespace UnityExplorer.UI.Widgets { int index = CellPool.Count - 1 - (topPoolIndex % (CellPool.Count - 1)); cell.Rect.SetSiblingIndex(index); + + if (bottomPoolIndex == index - 1) + bottomPoolIndex++; } } RefreshCells(true); + //ExplorerCore.Log("Anchor: " + Content.localPosition.y + ", prev: " + prevAnchor); + //ExplorerCore.Log("Height: " + Content.rect.height + ", prev:" + prevHeight); + if (Content.localPosition.y != prevAnchor) { var diff = Content.localPosition.y - prevAnchor; Content.localPosition = new Vector3(Content.localPosition.x, Content.localPosition.y - diff); } - ScrollRect.UpdatePrevData(); - - SetScrollBounds(); - UpdateSliderHandle(true); + if (Content.rect.height != prevHeight) + { + var diff = Content.rect.height - prevHeight; + //ExplorerCore.Log("Height diff: " + diff); + //Content.localPosition = new Vector3(Content.localPosition.x, Content.localPosition.y - diff); + } return true; } @@ -413,6 +433,16 @@ namespace UnityExplorer.UI.Widgets ScrollRect.UpdatePrevData(); } + private void RefreshCellHeightsFast() + { + var enumerator = GetPoolEnumerator(); + while (enumerator.MoveNext()) + { + var curr = enumerator.Current; + HeightCache.SetIndex(curr.dataIndex, CellPool[curr.cellIndex].Rect.rect.height); + } + } + private void SetCell(T cachedCell, int dataIndex) { cachedCell.Enable(); @@ -432,6 +462,8 @@ namespace UnityExplorer.UI.Widgets if (InputManager.MouseScrollDelta != Vector2.zero) ScrollRect.StopMovement(); + RefreshCellHeightsFast(); + SetRecycleViewBounds(true); float yChange = ((Vector2)ScrollRect.content.localPosition - prevAnchoredPos).y; @@ -458,7 +490,7 @@ namespace UnityExplorer.UI.Widgets SetScrollBounds(); //WritingLocked = true; - UpdateSliderHandle(); + UpdateSliderHandle(true); } private bool ShouldRecycleTop => GetCellExtent(CellPool[topPoolIndex].Rect) > RecycleViewBounds.x @@ -598,6 +630,8 @@ namespace UnityExplorer.UI.Widgets ScrollRect.StopMovement(); + RefreshCellHeightsFast(); + // normalize the scroll position for the scroll bounds. // this translates the value into saying "point at the center of the height of the viewport" var scrollHeight = NormalizedScrollBounds.y - NormalizedScrollBounds.x; @@ -630,52 +664,49 @@ namespace UnityExplorer.UI.Widgets // check if our pool indices contain the desired index. If so, rotate and set if (bottomDataIndex == desiredBottomIndex) { - // cells will be the same, do nothing? + // cells will be the same, do nothing + } + else if (TopDataIndex > poolStartIndex && TopDataIndex < desiredBottomIndex) + { + // top cell falls within the new range, rotate around that + int rotate = TopDataIndex - poolStartIndex; + for (int i = 0; i < rotate; i++) + { + CellPool[bottomPoolIndex].Rect.SetAsFirstSibling(); + + //set new indices + topPoolIndex = bottomPoolIndex; + bottomPoolIndex = (bottomPoolIndex - 1 + CellPool.Count) % CellPool.Count; + bottomDataIndex--; + + SetCell(CellPool[topPoolIndex], TopDataIndex); + } + } + else if (bottomDataIndex > poolStartIndex && bottomDataIndex < desiredBottomIndex) + { + // bottom cells falls within the new range, rotate around that + int rotate = desiredBottomIndex - bottomDataIndex; + for (int i = 0; i < rotate; i++) + { + CellPool[topPoolIndex].Rect.SetAsLastSibling(); + + //set new indices + bottomPoolIndex = topPoolIndex; + topPoolIndex = (topPoolIndex + 1) % CellPool.Count; + bottomDataIndex++; + + SetCell(CellPool[bottomPoolIndex], bottomDataIndex); + } } else { - if (TopDataIndex > poolStartIndex && TopDataIndex < desiredBottomIndex) + bottomDataIndex = desiredBottomIndex; + var enumerator = GetPoolEnumerator(); + while (enumerator.MoveNext()) { - // top cell falls within the new range, rotate around that - int rotate = TopDataIndex - poolStartIndex; - for (int i = 0; i < rotate; i++) - { - CellPool[bottomPoolIndex].Rect.SetAsFirstSibling(); - - //set new indices - topPoolIndex = bottomPoolIndex; - bottomPoolIndex = (bottomPoolIndex - 1 + CellPool.Count) % CellPool.Count; - bottomDataIndex--; - - SetCell(CellPool[topPoolIndex], TopDataIndex); - } - } - else if (bottomDataIndex > poolStartIndex && bottomDataIndex < desiredBottomIndex) - { - // bottom cells falls within the new range, rotate around that - int rotate = desiredBottomIndex - bottomDataIndex; - for (int i = 0; i < rotate; i++) - { - CellPool[topPoolIndex].Rect.SetAsLastSibling(); - - //set new indices - bottomPoolIndex = topPoolIndex; - topPoolIndex = (topPoolIndex + 1) % CellPool.Count; - bottomDataIndex++; - - SetCell(CellPool[bottomPoolIndex], bottomDataIndex); - } - } - else - { - bottomDataIndex = desiredBottomIndex; - var enumerator = GetPoolEnumerator(); - while (enumerator.MoveNext()) - { - var curr = enumerator.Current; - var cell = CellPool[curr.cellIndex]; - SetCell(cell, curr.dataIndex); - } + var curr = enumerator.Current; + var cell = CellPool[curr.cellIndex]; + SetCell(cell, curr.dataIndex); } } diff --git a/src/UI/Widgets/SliderScrollbar.cs b/src/UI/Widgets/SliderScrollbar.cs deleted file mode 100644 index f2c4e43..0000000 --- a/src/UI/Widgets/SliderScrollbar.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEngine; -using UnityEngine.Events; -using UnityEngine.UI; -using UnityExplorer; -using UnityExplorer.Core; -using UnityExplorer.UI; -using UnityExplorer.UI.Models; - -namespace UnityExplorer.UI.Utility -{ - // Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar. - public class SliderScrollbar : UIBehaviourModel - { - public bool IsActive { get; private set; } - - public override GameObject UIRoot - { - get - { - if (m_slider) - return m_slider.gameObject; - return null; - } - } - - public event Action OnValueChanged; - - internal readonly Scrollbar m_scrollbar; - internal readonly Slider m_slider; - internal readonly RectTransform m_scrollRect; - - internal InputFieldScroller m_parentInputScroller; - - public SliderScrollbar(Scrollbar scrollbar, Slider slider) - { - this.m_scrollbar = scrollbar; - this.m_slider = slider; - this.m_scrollRect = scrollbar.transform.parent.GetComponent(); - - this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged); - this.m_slider.onValueChanged.AddListener(this.OnSliderValueChanged); - - this.RefreshVisibility(); - this.m_slider.Set(1f, false); - } - - public override void Update() - { - this.RefreshVisibility(); - } - - internal void RefreshVisibility() - { - if (!m_slider.gameObject.activeInHierarchy) - { - IsActive = false; - return; - } - - bool shouldShow = !Mathf.Approximately(this.m_scrollbar.size, 1); - //var obj = this.m_slider.handleRect.gameObject; - - if (IsActive != shouldShow) - { - IsActive = shouldShow; - m_slider.interactable = shouldShow; - - if (IsActive) - this.m_slider.Set(this.m_scrollbar.value, false); - else - m_slider.Set(1f, false); - } - } - - public void OnScrollbarValueChanged(float _value) - { - if (this.m_slider.value != _value) - this.m_slider.Set(_value, false); - OnValueChanged?.Invoke(_value); - } - - public void OnSliderValueChanged(float _value) - { - this.m_scrollbar.value = _value; - OnValueChanged?.Invoke(_value); - } - - #region UI CONSTRUCTION - - public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider) - { - GameObject sliderObj = UIFactory.CreateUIObject("SliderScrollbar", parent, UIFactory._smallElementSize); - - GameObject bgObj = UIFactory.CreateUIObject("Background", sliderObj); - GameObject handleSlideAreaObj = UIFactory.CreateUIObject("Handle Slide Area", sliderObj); - GameObject handleObj = UIFactory.CreateUIObject("Handle", handleSlideAreaObj); - - Image bgImage = bgObj.AddComponent(); - bgImage.type = Image.Type.Sliced; - bgImage.color = new Color(0.1f, 0.1f, 0.1f, 1.0f); - - RectTransform bgRect = bgObj.GetComponent(); - bgRect.anchorMin = Vector2.zero; - bgRect.anchorMax = Vector2.one; - bgRect.sizeDelta = Vector2.zero; - bgRect.offsetMax = new Vector2(0f, 0f); - - RectTransform handleSlideRect = handleSlideAreaObj.GetComponent(); - handleSlideRect.anchorMin = new Vector2(0f, 0f); - handleSlideRect.anchorMax = new Vector2(1f, 1f); - handleSlideRect.pivot = new Vector2(0.5f, 0.5f); - handleSlideRect.offsetMin = new Vector2(25f, 30f); - handleSlideRect.offsetMax = new Vector2(-15f, 0f); - handleSlideRect.sizeDelta = new Vector2(-20f, -30f); - - Image handleImage = handleObj.AddComponent(); - handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f); - - var handleRect = handleObj.GetComponent(); - handleRect.sizeDelta = new Vector2(15f, 30f); - handleRect.offsetMin = new Vector2(-13f, -28f); - handleRect.offsetMax = new Vector2(2f, -2f); - - var sliderBarLayout = sliderObj.AddComponent(); - sliderBarLayout.minWidth = 25; - sliderBarLayout.flexibleWidth = 0; - sliderBarLayout.minHeight = 30; - sliderBarLayout.flexibleHeight = 5000; - - slider = sliderObj.AddComponent(); - slider.handleRect = handleObj.GetComponent(); - slider.targetGraphic = handleImage; - slider.direction = Slider.Direction.BottomToTop; - - RuntimeProvider.Instance.SetColorBlock(slider, - new Color(0.4f, 0.4f, 0.4f), - new Color(0.5f, 0.5f, 0.5f), - new Color(0.3f, 0.3f, 0.3f), - new Color(0.2f, 0.2f, 0.2f)); - - return sliderObj; - } - - public override void ConstructUI(GameObject parent) - { - throw new NotImplementedException(); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 4707f44..d46ba31 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -230,6 +230,7 @@ + @@ -241,10 +242,11 @@ - + + @@ -330,7 +332,7 @@ - +