From 8949e3dc7db48cca1564b3a91482febddebdba60 Mon Sep 17 00:00:00 2001 From: sinaioutlander <49360850+sinaioutlander@users.noreply.github.com> Date: Fri, 23 Oct 2020 01:48:18 +1100 Subject: [PATCH] Revert "some early steps remaking the GUI with UnityEngine.UI, working in all tested game so far" This reverts commit 4280a071f61ff0bdff713f7fb9970ee972af08a9. --- resources/cursor.png | Bin 1948 -> 0 bytes src/CacheObject/CacheEnumerated.cs | 44 +- src/CacheObject/CacheFactory.cs | 132 +- src/CacheObject/CacheField.cs | 92 +- src/CacheObject/CacheMember.cs | 380 ++-- src/CacheObject/CacheMethod.cs | 334 ++-- src/CacheObject/CacheObjectBase.cs | 206 +-- src/CacheObject/CacheProperty.cs | 140 +- src/Config/ModConfig.cs | 2 +- src/Explorer.csproj | 77 +- src/ExplorerCore.cs | 90 +- src/ExplorerMelonMod.cs | 10 +- src/Extensions/ReflectionExtensions.cs | 4 +- src/Extensions/UnityExtensions.cs | 2 +- src/Helpers/ICallHelper.cs | 2 +- src/Helpers/ReflectionHelpers.cs | 2 +- src/Helpers/Texture2DHelpers.cs | 4 +- src/Helpers/UnityHelpers.cs | 2 +- src/Input/IAbstractInput.cs | 2 +- src/Input/InputManager.cs | 6 +- src/Input/InputSystem.cs | 4 +- src/Input/LegacyInput.cs | 4 +- src/Input/NoInput.cs | 2 +- src/Properties/AssemblyInfo.cs | 4 +- src/Tests/TestClass.cs | 9 +- src/UI/ForceUnlockCursor.cs | 112 +- src/UI/Inspectors/GameObjectInspector.cs | 751 ++++++++ src/UI/Inspectors/InspectUnderMouse.cs | 86 + .../Reflection/InstanceInspector.cs | 107 ++ .../Inspectors/Reflection/StaticInspector.cs | 28 + src/UI/Inspectors/ReflectionInspector.cs | 431 +++++ src/UI/InteractiveValue/InteractiveValue.cs | 324 +++- .../Object/InteractiveDictionary.cs | 312 ++++ .../Object/InteractiveEnumerable.cs | 362 ++++ .../Object/InteractiveGameObject.cs | 25 + .../Object/InteractiveSprite.cs | 51 + .../Object/InteractiveTexture.cs | 30 + .../Object/InteractiveTexture2D.cs | 149 ++ .../Struct/InteractiveColor.cs | 115 ++ .../Struct/InteractiveEnum.cs | 91 + .../Struct/InteractiveFlags.cs | 113 ++ .../Struct/InteractivePrimitive.cs | 262 +++ .../Struct/InteractiveQuaternion.cs | 103 ++ .../Struct/InteractiveRect.cs | 112 ++ .../Struct/InteractiveVector.cs | 171 ++ src/UI/Main/BaseMainMenuPage.cs | 17 + src/UI/Main/Console/AutoComplete.cs | 56 + src/UI/Main/Console/ScriptEvaluator.cs | 70 + src/UI/Main/Console/ScriptInteraction.cs | 79 + src/UI/Main/ConsolePage.cs | 371 ++++ src/UI/Main/MainMenu.cs | 180 -- src/UI/Main/OptionsPage.cs | 145 ++ src/UI/Main/PanelDragger.cs | 390 ----- src/UI/Main/ScenePage.cs | 374 ++++ src/UI/Main/SearchPage.cs | 544 ++++++ src/UI/MainMenu.cs | 125 ++ src/UI/Shared/Buttons.cs | 100 ++ src/UI/Shared/IExpandHeight.cs | 8 + src/UI/Shared/PageHelper.cs | 105 ++ src/UI/Shared/ResizeDrag.cs | 127 ++ src/UI/Shared/Syntax.cs | 2 +- src/UI/Shared/UIStyles.cs | 118 ++ src/UI/TabViewWindow.cs | 124 ++ src/UI/UIFactory.cs | 570 ------ src/UI/UIManager.cs | 126 -- src/UI/WindowBase.cs | 88 + src/UI/WindowManager.cs | 234 +++ src/Unstrip/IMGUI/GUIHelper.cs | 183 ++ src/Unstrip/IMGUI/GUIUnstrip.cs | 812 +++++++++ src/Unstrip/IMGUI/GUIUtilityUnstrip.cs | 67 + src/Unstrip/IMGUI/LayoutUtilityUnstrip.cs | 91 + src/Unstrip/IMGUI/ScrollViewStateUnstrip.cs | 89 + src/Unstrip/IMGUI/SliderHandlerUnstrip.cs | 411 +++++ src/Unstrip/IMGUI/SliderStateUnstrip.cs | 14 + src/Unstrip/IMGUI/TextEditorUnstrip.cs | 1557 +++++++++++++++++ .../ImageConversion/ImageConversionUnstrip.cs | 4 +- src/Unstrip/LayerMask/LayerMaskUnstrip.cs | 4 +- src/Unstrip/Resources/ResourcesUnstrip.cs | 4 +- src/Unstrip/Scene/SceneUnstrip.cs | 4 +- 79 files changed, 10276 insertions(+), 2206 deletions(-) delete mode 100644 resources/cursor.png create mode 100644 src/UI/Inspectors/GameObjectInspector.cs create mode 100644 src/UI/Inspectors/InspectUnderMouse.cs create mode 100644 src/UI/Inspectors/Reflection/InstanceInspector.cs create mode 100644 src/UI/Inspectors/Reflection/StaticInspector.cs create mode 100644 src/UI/Inspectors/ReflectionInspector.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveDictionary.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveEnumerable.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveGameObject.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveSprite.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveTexture.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveTexture2D.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveColor.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveEnum.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveFlags.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractivePrimitive.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveQuaternion.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveRect.cs create mode 100644 src/UI/InteractiveValue/Struct/InteractiveVector.cs create mode 100644 src/UI/Main/BaseMainMenuPage.cs create mode 100644 src/UI/Main/Console/AutoComplete.cs create mode 100644 src/UI/Main/Console/ScriptEvaluator.cs create mode 100644 src/UI/Main/Console/ScriptInteraction.cs create mode 100644 src/UI/Main/ConsolePage.cs delete mode 100644 src/UI/Main/MainMenu.cs create mode 100644 src/UI/Main/OptionsPage.cs delete mode 100644 src/UI/Main/PanelDragger.cs create mode 100644 src/UI/Main/ScenePage.cs create mode 100644 src/UI/Main/SearchPage.cs create mode 100644 src/UI/MainMenu.cs create mode 100644 src/UI/Shared/Buttons.cs create mode 100644 src/UI/Shared/IExpandHeight.cs create mode 100644 src/UI/Shared/PageHelper.cs create mode 100644 src/UI/Shared/ResizeDrag.cs create mode 100644 src/UI/Shared/UIStyles.cs create mode 100644 src/UI/TabViewWindow.cs delete mode 100644 src/UI/UIFactory.cs delete mode 100644 src/UI/UIManager.cs create mode 100644 src/UI/WindowBase.cs create mode 100644 src/UI/WindowManager.cs create mode 100644 src/Unstrip/IMGUI/GUIHelper.cs create mode 100644 src/Unstrip/IMGUI/GUIUnstrip.cs create mode 100644 src/Unstrip/IMGUI/GUIUtilityUnstrip.cs create mode 100644 src/Unstrip/IMGUI/LayoutUtilityUnstrip.cs create mode 100644 src/Unstrip/IMGUI/ScrollViewStateUnstrip.cs create mode 100644 src/Unstrip/IMGUI/SliderHandlerUnstrip.cs create mode 100644 src/Unstrip/IMGUI/SliderStateUnstrip.cs create mode 100644 src/Unstrip/IMGUI/TextEditorUnstrip.cs diff --git a/resources/cursor.png b/resources/cursor.png deleted file mode 100644 index 9cf09c9b0001266eeba90e5aa779d47667f4d15f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmcIlZA{!`94~vzfJ}@qgJ`srkU(ayFW17g+d1J5;e@*}xVSmp!gK9&ca^)gw1qoR zGZms?ZX-gbZkf|8dy!>MB-snTjiHbs)7u3lG7FqPmISzc@?aitHvf@qXH` zdtg+l>oQ<-S0E^LwMv3atbYn$mlP2}99jTR#$&1}Cf&q5uZZUMYl^_> zoBfTrM>8PKJ1R(t;ds1Ka4;24P7t2NS(LQReLk^2g-e*#_ z(8^6jElU?EDv?My5>AI^L?}iO1d3)UmL(B_G&@xbBuUjQNi%q%DH)1xDVmDgj3A`7 zTW$g=U2q|;XJ}P36($rgDhYIoanQC)X`tVKe`q|OL7P@h2eOjVdskvJ*r`LR37T5F zA;Fpss9GgyW3rSvtG64mc~51Df-x9JFcS%5GN$yXW@%yu(=52hV<$$y%Qh*htR>8HTU6={^a2aI36^GQlA%e~8Kh~EuMk;&4a0~uy#V#2 zQjmcK76;a3CEU3fsNXO8RMP^g1btpNfkZhJMHYFNi{mNk)S$486o+W8c;1LJsB3+!|Bn1c|%Y{M} zkd@MG8w>?~JRo)_tF1~#j3J&8ctLWJlB*JuE+rrWt-sMlomXf=2Afi#o+b+3QSwBF(TZHH#_ueBFsavdi5~ zNOqo~Or%~bcZuHObS}^)q7aeZC;1C7Qwv)OV8E&fiu7XYi@KM76Lj2P{o-=}|Ef;~ zSBe5P0#OI02-_dZ?llXkq5hk-`Mo>(QW}n$ojuKTS+vOXX{aJ<19f4w<-i#X%WCs^ ztAcgCzwN0n>j4FCez<*doPV!>7(~XZ%X0@}5zpTKZv*9ruMG{I{%q*!z2%TN#JwZe zo<00Q_{9)7U)^gAUi|Xm*+<#gzjI4`vj;BSy4*SO*VfB_{_zOmJi{KJ$eHUIof<26 zYlfK~9WNa(uUgU6tTxEk5Bf*Wwe{!Etz1$tRQp27?7FMxbGgH-%YGTH#|A)X=%eCq zo-G*eD{6O4KX+^F+~=jf;*p-hz|67!RX@0U2A4h_y>ao>>Aqmr8uVO;`44so4b2&zWeEqTLNDlDZh5Qkl8ooxp35?wjE%y*FJW;a9cx% l&~{^v-8u8!%_o-qgv~rS*D(RVzhM7AeKi5^iRzX&{s9O9ltKUi diff --git a/src/CacheObject/CacheEnumerated.cs b/src/CacheObject/CacheEnumerated.cs index c3072a7..3f7d172 100644 --- a/src/CacheObject/CacheEnumerated.cs +++ b/src/CacheObject/CacheEnumerated.cs @@ -1,26 +1,26 @@ -//using System; -//using System.Collections; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using Explorer.UI; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Explorer.UI; -//namespace Explorer.CacheObject -//{ -// public class CacheEnumerated : CacheObjectBase -// { -// public int Index { get; set; } -// public IList RefIList { get; set; } -// public InteractiveEnumerable ParentEnumeration { get; set; } +namespace Explorer.CacheObject +{ + public class CacheEnumerated : CacheObjectBase + { + public int Index { get; set; } + public IList RefIList { get; set; } + public InteractiveEnumerable ParentEnumeration { get; set; } -// public override bool CanWrite => RefIList != null && ParentEnumeration.OwnerCacheObject.CanWrite; + public override bool CanWrite => RefIList != null && ParentEnumeration.OwnerCacheObject.CanWrite; -// public override void SetValue() -// { -// RefIList[Index] = IValue.Value; -// ParentEnumeration.Value = RefIList; + public override void SetValue() + { + RefIList[Index] = IValue.Value; + ParentEnumeration.Value = RefIList; -// ParentEnumeration.OwnerCacheObject.SetValue(); -// } -// } -//} + ParentEnumeration.OwnerCacheObject.SetValue(); + } + } +} diff --git a/src/CacheObject/CacheFactory.cs b/src/CacheObject/CacheFactory.cs index ff01146..3a881b5 100644 --- a/src/CacheObject/CacheFactory.cs +++ b/src/CacheObject/CacheFactory.cs @@ -1,76 +1,76 @@ -//using System; -//using System.Reflection; -//using Explorer.CacheObject; -//using UnityEngine; -//using Explorer.Helpers; +using System; +using System.Reflection; +using Explorer.CacheObject; +using UnityEngine; +using Explorer.Helpers; -//namespace Explorer -//{ -// public static class CacheFactory -// { -// public static CacheObjectBase GetCacheObject(object obj) -// { -// if (obj == null) return null; +namespace Explorer +{ + public static class CacheFactory + { + public static CacheObjectBase GetCacheObject(object obj) + { + if (obj == null) return null; -// return GetCacheObject(obj, ReflectionHelpers.GetActualType(obj)); -// } + return GetCacheObject(obj, ReflectionHelpers.GetActualType(obj)); + } -// public static CacheObjectBase GetCacheObject(object obj, Type type) -// { -// var ret = new CacheObjectBase(); -// ret.Init(obj, type); -// return ret; -// } + public static CacheObjectBase GetCacheObject(object obj, Type type) + { + var ret = new CacheObjectBase(); + ret.Init(obj, type); + return ret; + } -// public static CacheMember GetCacheObject(MemberInfo member, object declaringInstance) -// { -// CacheMember ret; + public static CacheMember GetCacheObject(MemberInfo member, object declaringInstance) + { + CacheMember ret; -// if (member is MethodInfo mi && CanProcessArgs(mi.GetParameters())) -// { -// ret = new CacheMethod(); -// ret.InitMember(mi, declaringInstance); -// } -// else if (member is PropertyInfo pi && CanProcessArgs(pi.GetIndexParameters())) -// { -// ret = new CacheProperty(); -// ret.InitMember(pi, declaringInstance); -// } -// else if (member is FieldInfo fi) -// { -// ret = new CacheField(); -// ret.InitMember(fi, declaringInstance); -// } -// else -// { -// return null; -// } + if (member is MethodInfo mi && CanProcessArgs(mi.GetParameters())) + { + ret = new CacheMethod(); + ret.InitMember(mi, declaringInstance); + } + else if (member is PropertyInfo pi && CanProcessArgs(pi.GetIndexParameters())) + { + ret = new CacheProperty(); + ret.InitMember(pi, declaringInstance); + } + else if (member is FieldInfo fi) + { + ret = new CacheField(); + ret.InitMember(fi, declaringInstance); + } + else + { + return null; + } -// return ret; -// } + return ret; + } -// public static bool CanProcessArgs(ParameterInfo[] parameters) -// { -// foreach (var param in parameters) -// { -// var pType = param.ParameterType; + public static bool CanProcessArgs(ParameterInfo[] parameters) + { + foreach (var param in parameters) + { + var pType = param.ParameterType; -// if (pType.IsByRef && pType.HasElementType) -// { -// pType = pType.GetElementType(); -// } + if (pType.IsByRef && pType.HasElementType) + { + pType = pType.GetElementType(); + } -// if (pType.IsPrimitive || pType == typeof(string)) -// { -// continue; -// } -// else -// { -// return false; -// } -// } + if (pType.IsPrimitive || pType == typeof(string)) + { + continue; + } + else + { + return false; + } + } -// return true; -// } -// } -//} + return true; + } + } +} diff --git a/src/CacheObject/CacheField.cs b/src/CacheObject/CacheField.cs index 493e61a..2aaa748 100644 --- a/src/CacheObject/CacheField.cs +++ b/src/CacheObject/CacheField.cs @@ -1,54 +1,54 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Reflection; -//using Explorer.UI; -//using Explorer.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Explorer.UI; +using Explorer.Helpers; -//namespace Explorer.CacheObject -//{ -// public class CacheField : CacheMember -// { -// public override bool IsStatic => (MemInfo as FieldInfo).IsStatic; +namespace Explorer.CacheObject +{ + public class CacheField : CacheMember + { + public override bool IsStatic => (MemInfo as FieldInfo).IsStatic; -// public override void InitMember(MemberInfo member, object declaringInstance) -// { -// base.InitMember(member, declaringInstance); + public override void InitMember(MemberInfo member, object declaringInstance) + { + base.InitMember(member, declaringInstance); -// base.Init(null, (member as FieldInfo).FieldType); + base.Init(null, (member as FieldInfo).FieldType); -// UpdateValue(); -// } + UpdateValue(); + } -// public override void UpdateValue() -// { -// if (IValue is InteractiveDictionary iDict) -// { -// if (!iDict.EnsureDictionaryIsSupported()) -// { -// ReflectionException = "Not supported due to TypeInitializationException"; -// return; -// } -// } + public override void UpdateValue() + { + if (IValue is InteractiveDictionary iDict) + { + if (!iDict.EnsureDictionaryIsSupported()) + { + ReflectionException = "Not supported due to TypeInitializationException"; + return; + } + } -// try -// { -// var fi = MemInfo as FieldInfo; -// IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance); + try + { + var fi = MemInfo as FieldInfo; + IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance); -// base.UpdateValue(); -// } -// catch (Exception e) -// { -// ReflectionException = ReflectionHelpers.ExceptionToString(e); -// } -// } + base.UpdateValue(); + } + catch (Exception e) + { + ReflectionException = ReflectionHelpers.ExceptionToString(e); + } + } -// public override void SetValue() -// { -// var fi = MemInfo as FieldInfo; -// fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value); -// } -// } -//} + public override void SetValue() + { + var fi = MemInfo as FieldInfo; + fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value); + } + } +} diff --git a/src/CacheObject/CacheMember.cs b/src/CacheObject/CacheMember.cs index bd5ddd3..e896ac7 100644 --- a/src/CacheObject/CacheMember.cs +++ b/src/CacheObject/CacheMember.cs @@ -1,226 +1,226 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Reflection; -//using UnityEngine; -//using Explorer.UI; -//using Explorer.UI.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI; +using Explorer.UI.Shared; -//namespace Explorer.CacheObject -//{ -// public class CacheMember : CacheObjectBase -// { -// public MemberInfo MemInfo { get; set; } -// public Type DeclaringType { get; set; } -// public object DeclaringInstance { get; set; } +namespace Explorer.CacheObject +{ + public class CacheMember : CacheObjectBase + { + public MemberInfo MemInfo { get; set; } + public Type DeclaringType { get; set; } + public object DeclaringInstance { get; set; } -// public virtual bool IsStatic { get; private set; } + public virtual bool IsStatic { get; private set; } -// public override bool HasParameters => m_arguments != null && m_arguments.Length > 0; -// public override bool IsMember => true; + public override bool HasParameters => m_arguments != null && m_arguments.Length > 0; + public override bool IsMember => true; -// public string RichTextName => m_richTextName ?? GetRichTextName(); -// private string m_richTextName; + public string RichTextName => m_richTextName ?? GetRichTextName(); + private string m_richTextName; -// public override bool CanWrite => m_canWrite ?? GetCanWrite(); -// private bool? m_canWrite; + public override bool CanWrite => m_canWrite ?? GetCanWrite(); + private bool? m_canWrite; -// public string ReflectionException { get; set; } + public string ReflectionException { get; set; } -// public bool m_evaluated = false; -// public bool m_isEvaluating; -// public ParameterInfo[] m_arguments = new ParameterInfo[0]; -// public string[] m_argumentInput = new string[0]; + public bool m_evaluated = false; + public bool m_isEvaluating; + public ParameterInfo[] m_arguments = new ParameterInfo[0]; + public string[] m_argumentInput = new string[0]; -// public virtual void InitMember(MemberInfo member, object declaringInstance) -// { -// MemInfo = member; -// DeclaringInstance = declaringInstance; -// DeclaringType = member.DeclaringType; -// } + public virtual void InitMember(MemberInfo member, object declaringInstance) + { + MemInfo = member; + DeclaringInstance = declaringInstance; + DeclaringType = member.DeclaringType; + } -// public override void UpdateValue() -// { -// base.UpdateValue(); -// } + public override void UpdateValue() + { + base.UpdateValue(); + } -// public override void SetValue() -// { -// // ... -// } + public override void SetValue() + { + // ... + } -// public object[] ParseArguments() -// { -// if (m_arguments.Length < 1) -// { -// return new object[0]; -// } + public object[] ParseArguments() + { + if (m_arguments.Length < 1) + { + return new object[0]; + } -// var parsedArgs = new List(); -// for (int i = 0; i < m_arguments.Length; i++) -// { -// var input = m_argumentInput[i]; -// var type = m_arguments[i].ParameterType; + var parsedArgs = new List(); + for (int i = 0; i < m_arguments.Length; i++) + { + var input = m_argumentInput[i]; + var type = m_arguments[i].ParameterType; -// if (type.IsByRef) -// { -// type = type.GetElementType(); -// } + if (type.IsByRef) + { + type = type.GetElementType(); + } -// if (!string.IsNullOrEmpty(input)) -// { -// if (type == typeof(string)) -// { -// parsedArgs.Add(input); -// continue; -// } -// else -// { -// try -// { -// var arg = type.GetMethod("Parse", new Type[] { typeof(string) }) -// .Invoke(null, new object[] { input }); + if (!string.IsNullOrEmpty(input)) + { + if (type == typeof(string)) + { + parsedArgs.Add(input); + continue; + } + else + { + try + { + var arg = type.GetMethod("Parse", new Type[] { typeof(string) }) + .Invoke(null, new object[] { input }); -// parsedArgs.Add(arg); -// continue; -// } -// catch -// { -// ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'."); -// } -// } -// } + parsedArgs.Add(arg); + continue; + } + catch + { + ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'."); + } + } + } -// // No input, see if there is a default value. -// if (HasDefaultValue(m_arguments[i])) -// { -// parsedArgs.Add(m_arguments[i].DefaultValue); -// continue; -// } + // No input, see if there is a default value. + if (HasDefaultValue(m_arguments[i])) + { + parsedArgs.Add(m_arguments[i].DefaultValue); + continue; + } -// // Try add a null arg I guess -// parsedArgs.Add(null); -// } + // Try add a null arg I guess + parsedArgs.Add(null); + } -// return parsedArgs.ToArray(); -// } + return parsedArgs.ToArray(); + } -// public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value; + public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value; -// public void DrawArgsInput() -// { -// GUILayout.Label($"Arguments:", new GUILayoutOption[0]); -// for (int i = 0; i < this.m_arguments.Length; i++) -// { -// var name = this.m_arguments[i].Name; -// var input = this.m_argumentInput[i]; -// var type = this.m_arguments[i].ParameterType.Name; + public void DrawArgsInput() + { + GUILayout.Label($"Arguments:", new GUILayoutOption[0]); + for (int i = 0; i < this.m_arguments.Length; i++) + { + var name = this.m_arguments[i].Name; + var input = this.m_argumentInput[i]; + var type = this.m_arguments[i].ParameterType.Name; -// var label = $"{type} "; -// label += $"{name}"; -// if (HasDefaultValue(this.m_arguments[i])) -// { -// label = $"[{label} = {this.m_arguments[i].DefaultValue ?? "null"}]"; -// } + var label = $"{type} "; + label += $"{name}"; + if (HasDefaultValue(this.m_arguments[i])) + { + label = $"[{label} = {this.m_arguments[i].DefaultValue ?? "null"}]"; + } -// GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); -// GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUI.skin.label.alignment = TextAnchor.MiddleCenter; -// GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) }); -// GUILayout.Label(label, new GUILayoutOption[] { GUILayout.ExpandWidth(false) }); -// this.m_argumentInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.ExpandWidth(true) }); + GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) }); + GUILayout.Label(label, new GUILayoutOption[] { GUIHelper.ExpandWidth(false) }); + this.m_argumentInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUIHelper.ExpandWidth(true) }); -// GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUI.skin.label.alignment = TextAnchor.MiddleLeft; -// GUILayout.EndHorizontal(); -// } -// } + GUILayout.EndHorizontal(); + } + } -// private bool GetCanWrite() -// { -// if (MemInfo is FieldInfo fi) -// m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly); -// else if (MemInfo is PropertyInfo pi) -// m_canWrite = pi.CanWrite; -// else -// m_canWrite = false; + private bool GetCanWrite() + { + if (MemInfo is FieldInfo fi) + m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly); + else if (MemInfo is PropertyInfo pi) + m_canWrite = pi.CanWrite; + else + m_canWrite = false; -// return (bool)m_canWrite; -// } + return (bool)m_canWrite; + } -// private string GetRichTextName() -// { -// string memberColor = ""; -// bool isStatic = false; + private string GetRichTextName() + { + string memberColor = ""; + bool isStatic = false; -// if (MemInfo is FieldInfo fi) -// { -// if (fi.IsStatic) -// { -// isStatic = true; -// memberColor = Syntax.Field_Static; -// } -// else -// memberColor = Syntax.Field_Instance; -// } -// else if (MemInfo is MethodInfo mi) -// { -// if (mi.IsStatic) -// { -// isStatic = true; -// memberColor = Syntax.Method_Static; -// } -// else -// memberColor = Syntax.Method_Instance; -// } -// else if (MemInfo is PropertyInfo pi) -// { -// if (pi.GetAccessors()[0].IsStatic) -// { -// isStatic = true; -// memberColor = Syntax.Prop_Static; -// } -// else -// memberColor = Syntax.Prop_Instance; -// } + if (MemInfo is FieldInfo fi) + { + if (fi.IsStatic) + { + isStatic = true; + memberColor = Syntax.Field_Static; + } + else + memberColor = Syntax.Field_Instance; + } + else if (MemInfo is MethodInfo mi) + { + if (mi.IsStatic) + { + isStatic = true; + memberColor = Syntax.Method_Static; + } + else + memberColor = Syntax.Method_Instance; + } + else if (MemInfo is PropertyInfo pi) + { + if (pi.GetAccessors()[0].IsStatic) + { + isStatic = true; + memberColor = Syntax.Prop_Static; + } + else + memberColor = Syntax.Prop_Instance; + } -// string classColor; -// if (MemInfo.DeclaringType.IsValueType) -// { -// classColor = Syntax.StructGreen; -// } -// else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed) -// { -// classColor = Syntax.Class_Static; -// } -// else -// { -// classColor = Syntax.Class_Instance; -// } + string classColor; + if (MemInfo.DeclaringType.IsValueType) + { + classColor = Syntax.StructGreen; + } + else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed) + { + classColor = Syntax.Class_Static; + } + else + { + classColor = Syntax.Class_Instance; + } -// m_richTextName = $"{MemInfo.DeclaringType.Name}."; -// if (isStatic) m_richTextName += ""; -// m_richTextName += $"{MemInfo.Name}"; -// if (isStatic) m_richTextName += ""; + m_richTextName = $"{MemInfo.DeclaringType.Name}."; + if (isStatic) m_richTextName += ""; + m_richTextName += $"{MemInfo.Name}"; + if (isStatic) m_richTextName += ""; -// // generic method args -// if (this is CacheMethod cm && cm.GenericArgs.Length > 0) -// { -// m_richTextName += "<"; + // generic method args + if (this is CacheMethod cm && cm.GenericArgs.Length > 0) + { + m_richTextName += "<"; -// var args = ""; -// for (int i = 0; i < cm.GenericArgs.Length; i++) -// { -// if (args != "") args += ", "; -// args += $"{cm.GenericArgs[i].Name}"; -// } -// m_richTextName += args; + var args = ""; + for (int i = 0; i < cm.GenericArgs.Length; i++) + { + if (args != "") args += ", "; + args += $"{cm.GenericArgs[i].Name}"; + } + m_richTextName += args; -// m_richTextName += ">"; -// } + m_richTextName += ">"; + } -// return m_richTextName; -// } -// } -//} + return m_richTextName; + } + } +} diff --git a/src/CacheObject/CacheMethod.cs b/src/CacheObject/CacheMethod.cs index 7006d23..36ecc17 100644 --- a/src/CacheObject/CacheMethod.cs +++ b/src/CacheObject/CacheMethod.cs @@ -1,200 +1,200 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Reflection; -//using UnityEngine; -//using Explorer.UI.Shared; -//using Explorer.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.Helpers; -//namespace Explorer.CacheObject -//{ -// public class CacheMethod : CacheMember -// { -// private CacheObjectBase m_cachedReturnValue; +namespace Explorer.CacheObject +{ + public class CacheMethod : CacheMember + { + private CacheObjectBase m_cachedReturnValue; -// public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0; + public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0; -// public override bool IsStatic => (MemInfo as MethodInfo).IsStatic; + public override bool IsStatic => (MemInfo as MethodInfo).IsStatic; -// public Type[] GenericArgs { get; private set; } -// public Type[][] GenericConstraints { get; private set; } + public Type[] GenericArgs { get; private set; } + public Type[][] GenericConstraints { get; private set; } -// public string[] GenericArgInput = new string[0]; + public string[] GenericArgInput = new string[0]; -// public override void InitMember(MemberInfo member, object declaringInstance) -// { -// base.InitMember(member, declaringInstance); + public override void InitMember(MemberInfo member, object declaringInstance) + { + base.InitMember(member, declaringInstance); -// var mi = MemInfo as MethodInfo; -// GenericArgs = mi.GetGenericArguments(); + var mi = MemInfo as MethodInfo; + GenericArgs = mi.GetGenericArguments(); -// GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints()) -// .ToArray(); + GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints()) + .ToArray(); -// GenericArgInput = new string[GenericArgs.Length]; + GenericArgInput = new string[GenericArgs.Length]; -// m_arguments = mi.GetParameters(); -// m_argumentInput = new string[m_arguments.Length]; + m_arguments = mi.GetParameters(); + m_argumentInput = new string[m_arguments.Length]; -// base.Init(null, mi.ReturnType); -// } + base.Init(null, mi.ReturnType); + } -// public override void UpdateValue() -// { -// // CacheMethod cannot UpdateValue directly. Need to Evaluate. -// } + public override void UpdateValue() + { + // CacheMethod cannot UpdateValue directly. Need to Evaluate. + } -// public void Evaluate() -// { -// MethodInfo mi; -// if (GenericArgs.Length > 0) -// { -// mi = MakeGenericMethodFromInput(); -// if (mi == null) return; -// } -// else -// { -// mi = MemInfo as MethodInfo; -// } + public void Evaluate() + { + MethodInfo mi; + if (GenericArgs.Length > 0) + { + mi = MakeGenericMethodFromInput(); + if (mi == null) return; + } + else + { + mi = MemInfo as MethodInfo; + } -// object ret = null; + object ret = null; -// try -// { -// ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments()); -// m_evaluated = true; -// m_isEvaluating = false; -// } -// catch (Exception e) -// { -// ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}"); -// ReflectionException = ReflectionHelpers.ExceptionToString(e); -// } + try + { + ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments()); + m_evaluated = true; + m_isEvaluating = false; + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}"); + ReflectionException = ReflectionHelpers.ExceptionToString(e); + } -// if (ret != null) -// { -// //m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret); -// m_cachedReturnValue = CacheFactory.GetCacheObject(ret); -// m_cachedReturnValue.UpdateValue(); -// } -// else -// { -// m_cachedReturnValue = null; -// } -// } + if (ret != null) + { + //m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret); + m_cachedReturnValue = CacheFactory.GetCacheObject(ret); + m_cachedReturnValue.UpdateValue(); + } + else + { + m_cachedReturnValue = null; + } + } -// private MethodInfo MakeGenericMethodFromInput() -// { -// var mi = MemInfo as MethodInfo; + private MethodInfo MakeGenericMethodFromInput() + { + var mi = MemInfo as MethodInfo; -// var list = new List(); -// for (int i = 0; i < GenericArgs.Length; i++) -// { -// var input = GenericArgInput[i]; -// if (ReflectionHelpers.GetTypeByName(input) is Type t) -// { -// if (GenericConstraints[i].Length == 0) -// { -// list.Add(t); -// } -// else -// { -// foreach (var constraint in GenericConstraints[i].Where(x => x != null)) -// { -// if (!constraint.IsAssignableFrom(t)) -// { -// ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!"); -// return null; -// } -// } + var list = new List(); + for (int i = 0; i < GenericArgs.Length; i++) + { + var input = GenericArgInput[i]; + if (ReflectionHelpers.GetTypeByName(input) is Type t) + { + if (GenericConstraints[i].Length == 0) + { + list.Add(t); + } + else + { + foreach (var constraint in GenericConstraints[i].Where(x => x != null)) + { + if (!constraint.IsAssignableFrom(t)) + { + ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!"); + return null; + } + } -// list.Add(t); -// } -// } -// else -// { -// ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" + -// $" Make sure you use the full name, including the NameSpace."); -// return null; -// } -// } + list.Add(t); + } + } + else + { + ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" + + $" Make sure you use the full name, including the NameSpace."); + return null; + } + } -// // make into a generic with type list -// mi = mi.MakeGenericMethod(list.ToArray()); + // make into a generic with type list + mi = mi.MakeGenericMethod(list.ToArray()); -// return mi; -// } + return mi; + } -// // ==== GUI DRAW ==== + // ==== GUI DRAW ==== -// //public override void Draw(Rect window, float width) -// //{ -// // base.Draw(window, width); -// //} + //public override void Draw(Rect window, float width) + //{ + // base.Draw(window, width); + //} -// public void DrawValue(Rect window, float width) -// { -// string typeLabel = $"{IValue.ValueType.FullName}"; + public void DrawValue(Rect window, float width) + { + string typeLabel = $"{IValue.ValueType.FullName}"; -// if (m_evaluated) -// { -// if (m_cachedReturnValue != null) -// { -// m_cachedReturnValue.IValue.DrawValue(window, width); -// } -// else -// { -// GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]); -// } -// } -// else -// { -// GUILayout.Label($"Not yet evaluated ({typeLabel})", new GUILayoutOption[0]); -// } -// } + if (m_evaluated) + { + if (m_cachedReturnValue != null) + { + m_cachedReturnValue.IValue.DrawValue(window, width); + } + else + { + GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]); + } + } + else + { + GUILayout.Label($"Not yet evaluated ({typeLabel})", new GUILayoutOption[0]); + } + } -// public void DrawGenericArgsInput() -// { -// GUILayout.Label($"Generic Arguments:", new GUILayoutOption[0]); + public void DrawGenericArgsInput() + { + GUILayout.Label($"Generic Arguments:", new GUILayoutOption[0]); -// for (int i = 0; i < this.GenericArgs.Length; i++) -// { -// string types = ""; -// if (this.GenericConstraints[i].Length > 0) -// { -// foreach (var constraint in this.GenericConstraints[i]) -// { -// if (types != "") types += ", "; + for (int i = 0; i < this.GenericArgs.Length; i++) + { + string types = ""; + if (this.GenericConstraints[i].Length > 0) + { + foreach (var constraint in this.GenericConstraints[i]) + { + if (types != "") types += ", "; -// string type; + string type; -// if (constraint == null) -// type = "Any"; -// else -// type = constraint.ToString(); + if (constraint == null) + type = "Any"; + else + type = constraint.ToString(); -// types += $"{type}"; -// } -// } -// else -// { -// types = $"Any"; -// } -// var input = this.GenericArgInput[i]; + types += $"{type}"; + } + } + else + { + types = $"Any"; + } + var input = this.GenericArgInput[i]; -// GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); -// GUI.skin.label.alignment = TextAnchor.MiddleCenter; -// GUILayout.Label( -// $"{this.GenericArgs[i].Name}", -// new GUILayoutOption[] { GUILayout.Width(15) } -// ); -// this.GenericArgInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) }); -// GUI.skin.label.alignment = TextAnchor.MiddleLeft; -// GUILayout.Label(types, new GUILayoutOption[0]); + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label( + $"{this.GenericArgs[i].Name}", + new GUILayoutOption[] { GUILayout.Width(15) } + ); + this.GenericArgInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) }); + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUILayout.Label(types, new GUILayoutOption[0]); -// GUILayout.EndHorizontal(); -// } -// } -// } -//} + GUILayout.EndHorizontal(); + } + } + } +} diff --git a/src/CacheObject/CacheObjectBase.cs b/src/CacheObject/CacheObjectBase.cs index d82f8b8..efba856 100644 --- a/src/CacheObject/CacheObjectBase.cs +++ b/src/CacheObject/CacheObjectBase.cs @@ -1,117 +1,117 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Reflection; -//using UnityEngine; -//using Explorer.UI; -//using Explorer.UI.Shared; -//using Explorer.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI; +using Explorer.UI.Shared; +using Explorer.Helpers; -//namespace Explorer.CacheObject -//{ -// public class CacheObjectBase -// { -// public InteractiveValue IValue; +namespace Explorer.CacheObject +{ + public class CacheObjectBase + { + public InteractiveValue IValue; -// public virtual bool CanWrite => false; -// public virtual bool HasParameters => false; -// public virtual bool IsMember => false; + public virtual bool CanWrite => false; + public virtual bool HasParameters => false; + public virtual bool IsMember => false; -// public bool IsStaticClassSearchResult { get; set; } + public bool IsStaticClassSearchResult { get; set; } -// public virtual void Init(object obj, Type valueType) -// { -// if (valueType == null && obj == null) -// { -// return; -// } + public virtual void Init(object obj, Type valueType) + { + if (valueType == null && obj == null) + { + return; + } -// //ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName); + //ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName); -// InteractiveValue interactive; + InteractiveValue interactive; -// if (valueType == typeof(GameObject) || valueType == typeof(Transform)) -// { -// interactive = new InteractiveGameObject(); -// } -// else if (valueType == typeof(Texture2D)) -// { -// interactive = new InteractiveTexture2D(); -// } -// else if (valueType == typeof(Texture)) -// { -// interactive = new InteractiveTexture(); -// } -// else if (valueType == typeof(Sprite)) -// { -// interactive = new InteractiveSprite(); -// } -// else if (valueType.IsPrimitive || valueType == typeof(string)) -// { -// interactive = new InteractivePrimitive(); -// } -// else if (valueType.IsEnum) -// { -// if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0) -// { -// interactive = new InteractiveFlags(); -// } -// else -// { -// interactive = new InteractiveEnum(); -// } -// } -// else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4)) -// { -// interactive = new InteractiveVector(); -// } -// else if (valueType == typeof(Quaternion)) -// { -// interactive = new InteractiveQuaternion(); -// } -// else if (valueType == typeof(Color)) -// { -// interactive = new InteractiveColor(); -// } -// else if (valueType == typeof(Rect)) -// { -// interactive = new InteractiveRect(); -// } -// // must check this before IsEnumerable -// else if (ReflectionHelpers.IsDictionary(valueType)) -// { -// interactive = new InteractiveDictionary(); -// } -// else if (ReflectionHelpers.IsEnumerable(valueType)) -// { -// interactive = new InteractiveEnumerable(); -// } -// else -// { -// interactive = new InteractiveValue(); -// } + if (valueType == typeof(GameObject) || valueType == typeof(Transform)) + { + interactive = new InteractiveGameObject(); + } + else if (valueType == typeof(Texture2D)) + { + interactive = new InteractiveTexture2D(); + } + else if (valueType == typeof(Texture)) + { + interactive = new InteractiveTexture(); + } + else if (valueType == typeof(Sprite)) + { + interactive = new InteractiveSprite(); + } + else if (valueType.IsPrimitive || valueType == typeof(string)) + { + interactive = new InteractivePrimitive(); + } + else if (valueType.IsEnum) + { + if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0) + { + interactive = new InteractiveFlags(); + } + else + { + interactive = new InteractiveEnum(); + } + } + else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4)) + { + interactive = new InteractiveVector(); + } + else if (valueType == typeof(Quaternion)) + { + interactive = new InteractiveQuaternion(); + } + else if (valueType == typeof(Color)) + { + interactive = new InteractiveColor(); + } + else if (valueType == typeof(Rect)) + { + interactive = new InteractiveRect(); + } + // must check this before IsEnumerable + else if (ReflectionHelpers.IsDictionary(valueType)) + { + interactive = new InteractiveDictionary(); + } + else if (ReflectionHelpers.IsEnumerable(valueType)) + { + interactive = new InteractiveEnumerable(); + } + else + { + interactive = new InteractiveValue(); + } -// interactive.Value = obj; -// interactive.ValueType = valueType; + interactive.Value = obj; + interactive.ValueType = valueType; -// this.IValue = interactive; -// this.IValue.OwnerCacheObject = this; + this.IValue = interactive; + this.IValue.OwnerCacheObject = this; -// UpdateValue(); + UpdateValue(); -// this.IValue.Init(); -// } + this.IValue.Init(); + } -// public virtual void Draw(Rect window, float width) -// { -// IValue.Draw(window, width); -// } + public virtual void Draw(Rect window, float width) + { + IValue.Draw(window, width); + } -// public virtual void UpdateValue() -// { -// IValue.UpdateValue(); -// } + public virtual void UpdateValue() + { + IValue.UpdateValue(); + } -// public virtual void SetValue() => throw new NotImplementedException(); -// } -//} + public virtual void SetValue() => throw new NotImplementedException(); + } +} diff --git a/src/CacheObject/CacheProperty.cs b/src/CacheObject/CacheProperty.cs index b3322c3..2380d59 100644 --- a/src/CacheObject/CacheProperty.cs +++ b/src/CacheObject/CacheProperty.cs @@ -1,84 +1,84 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Reflection; -//using Explorer.UI; -//using Explorer.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using Explorer.UI; +using Explorer.Helpers; -//namespace Explorer.CacheObject -//{ -// public class CacheProperty : CacheMember -// { -// public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors()[0].IsStatic; +namespace Explorer.CacheObject +{ + public class CacheProperty : CacheMember + { + public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors()[0].IsStatic; -// public override void InitMember(MemberInfo member, object declaringInstance) -// { -// base.InitMember(member, declaringInstance); + public override void InitMember(MemberInfo member, object declaringInstance) + { + base.InitMember(member, declaringInstance); -// var pi = member as PropertyInfo; + var pi = member as PropertyInfo; -// this.m_arguments = pi.GetIndexParameters(); -// this.m_argumentInput = new string[m_arguments.Length]; + this.m_arguments = pi.GetIndexParameters(); + this.m_argumentInput = new string[m_arguments.Length]; -// base.Init(null, pi.PropertyType); + base.Init(null, pi.PropertyType); -// UpdateValue(); -// } + UpdateValue(); + } -// public override void UpdateValue() -// { -// if (HasParameters && !m_isEvaluating) -// { -// // Need to enter parameters first. -// return; -// } + public override void UpdateValue() + { + if (HasParameters && !m_isEvaluating) + { + // Need to enter parameters first. + return; + } -// if (IValue is InteractiveDictionary iDict) -// { -// if (!iDict.EnsureDictionaryIsSupported()) -// { -// ReflectionException = "Not supported due to TypeInitializationException"; -// return; -// } -// } + if (IValue is InteractiveDictionary iDict) + { + if (!iDict.EnsureDictionaryIsSupported()) + { + ReflectionException = "Not supported due to TypeInitializationException"; + return; + } + } -// try -// { -// var pi = MemInfo as PropertyInfo; + try + { + var pi = MemInfo as PropertyInfo; -// if (pi.CanRead) -// { -// var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance; + if (pi.CanRead) + { + var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance; -// IValue.Value = pi.GetValue(target, ParseArguments()); + IValue.Value = pi.GetValue(target, ParseArguments()); -// base.UpdateValue(); -// } -// else // create a dummy value for Write-Only properties. -// { -// if (IValue.ValueType == typeof(string)) -// { -// IValue.Value = ""; -// } -// else -// { -// IValue.Value = Activator.CreateInstance(IValue.ValueType); -// } -// } -// } -// catch (Exception e) -// { -// ReflectionException = ReflectionHelpers.ExceptionToString(e); -// } -// } + base.UpdateValue(); + } + else // create a dummy value for Write-Only properties. + { + if (IValue.ValueType == typeof(string)) + { + IValue.Value = ""; + } + else + { + IValue.Value = Activator.CreateInstance(IValue.ValueType); + } + } + } + catch (Exception e) + { + ReflectionException = ReflectionHelpers.ExceptionToString(e); + } + } -// public override void SetValue() -// { -// var pi = MemInfo as PropertyInfo; -// var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance; + public override void SetValue() + { + var pi = MemInfo as PropertyInfo; + var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance; -// pi.SetValue(target, IValue.Value, ParseArguments()); -// } -// } -//} + pi.SetValue(target, IValue.Value, ParseArguments()); + } + } +} diff --git a/src/Config/ModConfig.cs b/src/Config/ModConfig.cs index da7eb5a..8c016f4 100644 --- a/src/Config/ModConfig.cs +++ b/src/Config/ModConfig.cs @@ -2,7 +2,7 @@ using System.Xml.Serialization; using UnityEngine; -namespace ExplorerBeta.Config +namespace Explorer.Config { public class ModConfig { diff --git a/src/Explorer.csproj b/src/Explorer.csproj index 8bcc7c8..42d0b4e 100644 --- a/src/Explorer.csproj +++ b/src/Explorer.csproj @@ -23,7 +23,7 @@ x64 false Explorer - ExplorerBeta + Explorer D:\Steam\steamapps\common\Hellpoint @@ -132,6 +132,10 @@ $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.CoreModule.dll False + + $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.IMGUIModule.dll + False + $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.PhysicsModule.dll False @@ -144,14 +148,6 @@ $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.UI.dll False - - $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.UIModule.dll - False - - - $(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.IMGUIModule.dll - False - @@ -187,6 +183,10 @@ $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.CoreModule.dll False + + $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll + False + $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.PhysicsModule.dll False @@ -199,14 +199,6 @@ $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.UI.dll False - - $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.UIModule.dll - False - - - $(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll - False - @@ -217,6 +209,20 @@ + + + + + + + + + + + + + + @@ -225,25 +231,50 @@ - + - - - + + + + + + + + + + + + + + + + + - + + + + + + + + + + + - + + diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index 3824aa6..7e80d10 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -1,17 +1,18 @@ using System.Collections; using System.Linq; -using ExplorerBeta.Config; -using ExplorerBeta.Input; -using ExplorerBeta.UI; +using Explorer.Config; +using Explorer.UI; +using Explorer.UI.Inspectors; +using Explorer.UI.Main; +using Explorer.UI.Shared; using UnityEngine; -using UnityEngine.EventSystems; -namespace ExplorerBeta +namespace Explorer { public class ExplorerCore { public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")"; - public const string VERSION = "3.0.0b"; + public const string VERSION = "2.0.7"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.explorer"; @@ -30,16 +31,6 @@ namespace ExplorerBeta public static ExplorerCore Instance { get; private set; } - public static bool ShowMenu - { - get => m_showMenu; - set => SetShowMenu(value); - } - public static bool m_showMenu; - - private static bool m_doneUIInit; - private static float m_startupTime; - public ExplorerCore() { if (Instance != null) @@ -52,9 +43,8 @@ namespace ExplorerBeta ModConfig.OnLoad(); - // Temporary? Need a small delay after OnApplicationStart before we can safely make our GameObject. - // Can't use Threads (crash), can't use Coroutine (no BepInEx support yet). - m_startupTime = Time.realtimeSinceStartup; + new MainMenu(); + new WindowManager(); InputManager.Init(); ForceUnlockCursor.Init(); @@ -62,40 +52,23 @@ namespace ExplorerBeta ShowMenu = true; Log($"{NAME} initialized."); - } + } + + public static bool ShowMenu + { + get => m_showMenu; + set => SetShowMenu(value); + } + public static bool m_showMenu; private static void SetShowMenu(bool show) { m_showMenu = show; - - if (UIManager.CanvasRoot) - { - UIManager.CanvasRoot.SetActive(show); - - if (show) - { - ForceUnlockCursor.SetEventSystem(); - } - else - { - ForceUnlockCursor.ReleaseEventSystem(); - } - } - ForceUnlockCursor.UpdateCursorControl(); } public static void Update() { - // Temporary delay before UIManager.Init - if (!m_doneUIInit && Time.realtimeSinceStartup - m_startupTime > 1f) - { - UIManager.Init(); - - Log("Initialized Explorer UI."); - m_doneUIInit = true; - } - if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle)) { ShowMenu = !ShowMenu; @@ -104,17 +77,36 @@ namespace ExplorerBeta if (ShowMenu) { ForceUnlockCursor.Update(); + InspectUnderMouse.Update(); - UIManager.Update(); - - //// TODO: - //InspectUnderMouse.Update(); + MainMenu.Instance.Update(); + WindowManager.Instance.Update(); } } + public static void OnGUI() + { + if (!ShowMenu) return; + + var origSkin = GUI.skin; + GUI.skin = UIStyles.WindowSkin; + + MainMenu.Instance.OnGUI(); + WindowManager.Instance.OnGUI(); + InspectUnderMouse.OnGUI(); + + if (!ResizeDrag.IsMouseInResizeArea && WindowManager.IsMouseInWindow) + { + InputManager.ResetInputAxes(); + } + + GUI.skin = origSkin; + } + public static void OnSceneChange() { - UIManager.OnSceneChange(); + ScenePage.Instance?.OnSceneChange(); + SearchPage.Instance?.OnSceneChange(); } public static void Log(object message) diff --git a/src/ExplorerMelonMod.cs b/src/ExplorerMelonMod.cs index 6c92d96..3af2a2d 100644 --- a/src/ExplorerMelonMod.cs +++ b/src/ExplorerMelonMod.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Text; using MelonLoader; -namespace ExplorerBeta +namespace Explorer { public class ExplorerMelonMod : MelonMod { @@ -28,10 +28,10 @@ namespace ExplorerBeta ExplorerCore.Update(); } - //public override void OnGUI() - //{ - // ExplorerCore.OnGUI(); - //} + public override void OnGUI() + { + ExplorerCore.OnGUI(); + } } } #endif \ No newline at end of file diff --git a/src/Extensions/ReflectionExtensions.cs b/src/Extensions/ReflectionExtensions.cs index 44e7590..d8422f5 100644 --- a/src/Extensions/ReflectionExtensions.cs +++ b/src/Extensions/ReflectionExtensions.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using ExplorerBeta.Helpers; +using Explorer.Helpers; -namespace ExplorerBeta +namespace Explorer { public static class ReflectionExtensions { diff --git a/src/Extensions/UnityExtensions.cs b/src/Extensions/UnityExtensions.cs index 70cd779..c1cc29c 100644 --- a/src/Extensions/UnityExtensions.cs +++ b/src/Extensions/UnityExtensions.cs @@ -1,6 +1,6 @@ using UnityEngine; -namespace ExplorerBeta +namespace Explorer { public static class UnityExtensions { diff --git a/src/Helpers/ICallHelper.cs b/src/Helpers/ICallHelper.cs index 7e60783..bc89879 100644 --- a/src/Helpers/ICallHelper.cs +++ b/src/Helpers/ICallHelper.cs @@ -7,7 +7,7 @@ using System.Text; using System.Reflection; using System.Diagnostics.CodeAnalysis; -namespace ExplorerBeta.Helpers +namespace Explorer.Helpers { [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")] public static class ICallHelper diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs index 4282fc4..d1aca8f 100644 --- a/src/Helpers/ReflectionHelpers.cs +++ b/src/Helpers/ReflectionHelpers.cs @@ -13,7 +13,7 @@ using UnhollowerRuntimeLib; using System.Runtime.InteropServices; #endif -namespace ExplorerBeta.Helpers +namespace Explorer.Helpers { [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")] public class ReflectionHelpers diff --git a/src/Helpers/Texture2DHelpers.cs b/src/Helpers/Texture2DHelpers.cs index 3f0d9de..2d5f4b0 100644 --- a/src/Helpers/Texture2DHelpers.cs +++ b/src/Helpers/Texture2DHelpers.cs @@ -6,10 +6,10 @@ using UnityEngine; using System.IO; using System.Reflection; #if CPP -using ExplorerBeta.Unstrip.ImageConversion; +using Explorer.Unstrip.ImageConversion; #endif -namespace ExplorerBeta.Helpers +namespace Explorer.Helpers { public static class Texture2DHelpers { diff --git a/src/Helpers/UnityHelpers.cs b/src/Helpers/UnityHelpers.cs index bd01f65..5bad857 100644 --- a/src/Helpers/UnityHelpers.cs +++ b/src/Helpers/UnityHelpers.cs @@ -1,6 +1,6 @@ using UnityEngine; -namespace ExplorerBeta.Helpers +namespace Explorer.Helpers { public class UnityHelpers { diff --git a/src/Input/IAbstractInput.cs b/src/Input/IAbstractInput.cs index cae8ba5..ad33315 100644 --- a/src/Input/IAbstractInput.cs +++ b/src/Input/IAbstractInput.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using UnityEngine; -namespace ExplorerBeta.Input +namespace Explorer.Input { public interface IAbstractInput { diff --git a/src/Input/InputManager.cs b/src/Input/InputManager.cs index 1d28cd2..291a281 100644 --- a/src/Input/InputManager.cs +++ b/src/Input/InputManager.cs @@ -1,14 +1,14 @@ using System; using System.Reflection; using UnityEngine; -using ExplorerBeta.Input; -using ExplorerBeta.Helpers; +using Explorer.Input; +using Explorer.Helpers; using System.Diagnostics.CodeAnalysis; #if CPP using UnhollowerBaseLib; #endif -namespace ExplorerBeta.Input +namespace Explorer { [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Unity style")] public static class InputManager diff --git a/src/Input/InputSystem.cs b/src/Input/InputSystem.cs index d09e88f..d653004 100644 --- a/src/Input/InputSystem.cs +++ b/src/Input/InputSystem.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; -using ExplorerBeta.Helpers; +using Explorer.Helpers; -namespace ExplorerBeta.Input +namespace Explorer.Input { public class InputSystem : IAbstractInput { diff --git a/src/Input/LegacyInput.cs b/src/Input/LegacyInput.cs index c645dc8..4188316 100644 --- a/src/Input/LegacyInput.cs +++ b/src/Input/LegacyInput.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using UnityEngine; -using ExplorerBeta.Helpers; +using Explorer.Helpers; -namespace ExplorerBeta.Input +namespace Explorer.Input { public class LegacyInput : IAbstractInput { diff --git a/src/Input/NoInput.cs b/src/Input/NoInput.cs index adccd6b..a5b2297 100644 --- a/src/Input/NoInput.cs +++ b/src/Input/NoInput.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using UnityEngine; -namespace ExplorerBeta.Input +namespace Explorer.Input { // Just a stub for games where no Input module was able to load at all. diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs index 4687cd6..d607c6f 100644 --- a/src/Properties/AssemblyInfo.cs +++ b/src/Properties/AssemblyInfo.cs @@ -1,12 +1,12 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using ExplorerBeta; +using Explorer; #if ML using MelonLoader; -[assembly: MelonInfo(typeof(ExplorerMelonMod), "ExplorerBeta", ExplorerCore.VERSION, ExplorerCore.AUTHOR)] +[assembly: MelonInfo(typeof(ExplorerMelonMod), "Explorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)] [assembly: MelonGame(null, null)] #endif diff --git a/src/Tests/TestClass.cs b/src/Tests/TestClass.cs index 2f4b283..94f9212 100644 --- a/src/Tests/TestClass.cs +++ b/src/Tests/TestClass.cs @@ -4,15 +4,14 @@ using System; using UnityEngine; using System.Reflection; using System.Runtime.InteropServices; -using ExplorerBeta.UI.Shared; -using ExplorerBeta.UI; +using Explorer.UI.Shared; #if CPP using UnhollowerBaseLib; using UnityEngine.SceneManagement; -using ExplorerBeta.Unstrip.ImageConversion; +using Explorer.Unstrip.ImageConversion; #endif -namespace ExplorerBeta.Tests +namespace Explorer.Tests { public static class StaticTestClass { @@ -41,7 +40,7 @@ namespace ExplorerBeta.Tests } private static bool m_setOnlyProperty; - public Texture2D TestTexture = UIManager.MakeSolidTexture(Color.white, 200, 200); + public Texture2D TestTexture = UIStyles.MakeTex(200, 200, Color.white); public static Sprite TestSprite; public static int StaticProperty => 5; diff --git a/src/UI/ForceUnlockCursor.cs b/src/UI/ForceUnlockCursor.cs index 9a619c8..ff06f02 100644 --- a/src/UI/ForceUnlockCursor.cs +++ b/src/UI/ForceUnlockCursor.cs @@ -1,16 +1,13 @@ using System; using UnityEngine; -using ExplorerBeta.Helpers; -using UnityEngine.EventSystems; -using ExplorerBeta.UI; -using ExplorerBeta.Input; +using Explorer.Helpers; #if ML using Harmony; #else using HarmonyLib; #endif -namespace ExplorerBeta.UI +namespace Explorer.UI { public class ForceUnlockCursor { @@ -21,12 +18,6 @@ namespace ExplorerBeta.UI } private static bool m_forceUnlock; - private static void SetForceUnlock(bool unlock) - { - m_forceUnlock = unlock; - UpdateCursorControl(); - } - public static bool ShouldForceMouse => ExplorerCore.ShowMenu && Unlock; private static CursorLockMode m_lastLockMode; @@ -34,8 +25,8 @@ namespace ExplorerBeta.UI private static bool m_currentlySettingCursor = false; - private static Type CursorType - => m_cursorType + private static Type CursorType + => m_cursorType ?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor")); private static Type m_cursorType; @@ -53,13 +44,11 @@ namespace ExplorerBeta.UI m_lastVisibleState = Cursor.visible; // Setup Harmony Patches - TryPatch(typeof(EventSystem), "current", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_EventSystem_set_current))), true); + TryPatch("lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_lockState))), true); + TryPatch("lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_lockState))), false); - TryPatch(typeof(Cursor), "lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_lockState))), true); - //TryPatch(typeof(Cursor), "lockState", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_lockState))), false); - - TryPatch(typeof(Cursor), "visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))), true); - //TryPatch(typeof(Cursor), "visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_visible))), false); + TryPatch("visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))), true); + TryPatch("visible", new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Postfix_get_visible))), false); } catch (Exception e) { @@ -69,7 +58,7 @@ namespace ExplorerBeta.UI Unlock = true; } - private static void TryPatch(Type type, string property, HarmonyMethod patch, bool setter) + private static void TryPatch(string property, HarmonyMethod patch, bool setter) { try { @@ -81,7 +70,7 @@ namespace ExplorerBeta.UI #endif ; - var prop = type.GetProperty(property); + var prop = typeof(Cursor).GetProperty(property); if (setter) { @@ -96,11 +85,17 @@ namespace ExplorerBeta.UI } catch (Exception e) { - string suf = setter ? "set_" : "get_"; - ExplorerCore.Log($"Unable to patch {type.Name}.{suf}{property}: {e.Message}"); + string s = setter ? "set_" : "get_" ; + ExplorerCore.Log($"Unable to patch Cursor.{s}{property}: {e.Message}"); } } + private static void SetForceUnlock(bool unlock) + { + m_forceUnlock = unlock; + UpdateCursorControl(); + } + public static void Update() { // Check Force-Unlock input @@ -133,45 +128,6 @@ namespace ExplorerBeta.UI } } - // Event system - - private static bool m_settingEventSystem; - private static EventSystem m_lastEventSystem; - private static BaseInputModule m_lastInputModule; - - public static void SetEventSystem() - { - m_settingEventSystem = true; - UIManager.SetEventSystem(); - m_settingEventSystem = false; - } - - public static void ReleaseEventSystem() - { - if (m_lastEventSystem) - { - m_settingEventSystem = true; - EventSystem.current = m_lastEventSystem; - m_lastInputModule?.ActivateModule(); - m_settingEventSystem = false; - } - } - - [HarmonyPrefix] - public static void Prefix_EventSystem_set_current(ref EventSystem value) - { - if (!m_settingEventSystem) - { - m_lastEventSystem = value; - m_lastInputModule = value?.currentInputModule; - - if (ExplorerCore.ShowMenu) - { - value = UIManager.EventSys; - } - } - } - // Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true. // Also keep track of when anything else tries to set Cursor state, this will be the // value that we set back to when we close the menu or disable force-unlock. @@ -204,22 +160,22 @@ namespace ExplorerBeta.UI } } - //[HarmonyPrefix] - //public static void Postfix_get_lockState(ref CursorLockMode __result) - //{ - // if (ShouldForceMouse) - // { - // __result = m_lastLockMode; - // } - //} + [HarmonyPrefix] + public static void Postfix_get_lockState(ref CursorLockMode __result) + { + if (ShouldForceMouse) + { + __result = m_lastLockMode; + } + } - //[HarmonyPrefix] - //public static void Postfix_get_visible(ref bool __result) - //{ - // if (ShouldForceMouse) - // { - // __result = m_lastVisibleState; - // } - //} + [HarmonyPrefix] + public static void Postfix_get_visible(ref bool __result) + { + if (ShouldForceMouse) + { + __result = m_lastVisibleState; + } + } } } diff --git a/src/UI/Inspectors/GameObjectInspector.cs b/src/UI/Inspectors/GameObjectInspector.cs new file mode 100644 index 0000000..26ddb39 --- /dev/null +++ b/src/UI/Inspectors/GameObjectInspector.cs @@ -0,0 +1,751 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.UI.Main; +using Explorer.Unstrip.LayerMasks; +using Explorer.Helpers; +#if CPP +using UnhollowerRuntimeLib; +#endif + +namespace Explorer.UI.Inspectors +{ + public class GameObjectInspector : WindowBase + { + public override string Title => WindowManager.TabView + ? $"[G] {TargetGO.name}" + : $"GameObject Inspector ({TargetGO.name})"; + + public GameObject TargetGO; + + public bool pendingDestroy; + + private static bool m_hideControls; + + // gui element holders + private string m_name; + private string m_scene; + + private Transform[] m_children; + private Vector2 m_transformScroll = Vector2.zero; + private readonly PageHelper ChildPages = new PageHelper(); + + private Component[] m_components; + private Vector2 m_compScroll = Vector2.zero; + private readonly PageHelper CompPages = new PageHelper(); + + private readonly Vector3[] m_cachedInput = new Vector3[3]; + private float m_translateAmount = 0.3f; + private float m_rotateAmount = 50f; + private float m_scaleAmount = 0.1f; + private bool m_freeze; + private Vector3 m_frozenPosition; + private Quaternion m_frozenRotation; + private Vector3 m_frozenScale; + private bool m_autoApplyTransform; + private bool m_autoUpdateTransform; + private bool m_localContext; + + private int m_layer; + + private readonly List m_cachedDestroyList = new List(); + private string m_addComponentInput = ""; + + private string m_setParentInput = "Enter a GameObject name or path"; + + public bool GetObjectAsGameObject() + { + var targetType = Target?.GetType(); + + if (targetType == typeof(GameObject)) + { + TargetGO = Target as GameObject; + return true; + } + else if (targetType == typeof(Transform)) + { + TargetGO = (Target as Transform).gameObject; + return true; + } + + ExplorerCore.Log("Error: Target is null or not a GameObject/Transform!"); + DestroyWindow(); + return false; + } + + public override void Init() + { + if (!GetObjectAsGameObject()) + { + return; + } + + m_name = TargetGO.name; + m_scene = string.IsNullOrEmpty(TargetGO.scene.name) + ? "None (Asset/Resource)" + : TargetGO.scene.name; + + CacheTransformValues(); + + Update(); + } + + private void CacheTransformValues() + { + if (m_localContext) + { + m_cachedInput[0] = TargetGO.transform.localPosition; + m_cachedInput[1] = TargetGO.transform.localEulerAngles; + } + else + { + m_cachedInput[0] = TargetGO.transform.position; + m_cachedInput[1] = TargetGO.transform.eulerAngles; + } + m_cachedInput[2] = TargetGO.transform.localScale; + } + + public override void Update() + { + try + { + if (pendingDestroy) return; + + if (Target == null) + { + DestroyOnException(new Exception("Target was destroyed.")); + return; + } + if (!TargetGO && !GetObjectAsGameObject()) + { + DestroyOnException(new Exception("Target was destroyed.")); + return; + } + + if (m_freeze) + { + if (m_localContext) + { + TargetGO.transform.localPosition = m_frozenPosition; + TargetGO.transform.localRotation = m_frozenRotation; + } + else + { + TargetGO.transform.position = m_frozenPosition; + TargetGO.transform.rotation = m_frozenRotation; + } + TargetGO.transform.localScale = m_frozenScale; + } + + m_layer = TargetGO.layer; + + // update child objects + var childList = new List(); + for (int i = 0; i < TargetGO.transform.childCount; i++) + { + childList.Add(TargetGO.transform.GetChild(i)); + } + childList.Sort((a, b) => b.childCount.CompareTo(a.childCount)); + m_children = childList.ToArray(); + + ChildPages.ItemCount = m_children.Length; + + // update components +#if CPP + var compList = new Il2CppSystem.Collections.Generic.List(); + TargetGO.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList); + m_components = compList.ToArray(); +#else + m_components = TargetGO.GetComponents(); +#endif + + CompPages.ItemCount = m_components.Length; + } + catch (Exception e) + { + DestroyOnException(e); + } + } + + private void DestroyOnException(Exception e) + { + if (pendingDestroy) return; + + ExplorerCore.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}"); + pendingDestroy = true; + DestroyWindow(); + } + + private void InspectGameObject(Transform obj) + { + var window = WindowManager.InspectObject(obj, out bool created); + + if (created) + { + window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height); + DestroyWindow(); + } + } + +#if CPP + private void ReflectObject(Il2CppSystem.Object obj) +#else + private void ReflectObject(object obj) +#endif + { + var window = WindowManager.InspectObject(obj, out bool created, true); + + if (created) + { + if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100)) + { + window.m_rect = new Rect( + this.m_rect.x + this.m_rect.width + 20, + this.m_rect.y, + 550, + 700); + } + else + { + window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700); + } + } + } + + public override void WindowFunction(int windowID) + { + if (pendingDestroy) return; + + try + { + var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect; + + if (!WindowManager.TabView) + { + Header(); + GUIHelper.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box); + } + + scroll = GUIHelper.BeginScrollView(scroll); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Scene: " + (m_scene == "" ? "n/a" : m_scene) + "", new GUILayoutOption[0]); + if (m_scene == UnityHelpers.ActiveSceneName) + { + if (GUILayout.Button("Send to Scene View", new GUILayoutOption[] { GUILayout.Width(150) })) + { + ScenePage.Instance.SetTransformTarget(TargetGO.transform); + MainMenu.SetCurrentPage(0); + } + } + if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) })) + { + WindowManager.InspectObject(Target, out _, true); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) }); + string pathlabel = TargetGO.transform.GetGameObjectPath(); + if (TargetGO.transform.parent != null) + { + if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) })) + { + InspectGameObject(TargetGO.transform.parent); + } + } + GUIHelper.TextArea(pathlabel, new GUILayoutOption[0]); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) }); + GUIHelper.TextArea(m_name, new GUILayoutOption[0]); + GUILayout.EndHorizontal(); + + LayerControls(); + + // --- Horizontal Columns section --- + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) }); + TransformList(rect); + GUILayout.EndVertical(); + + GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) }); + ComponentList(rect); + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); // end horiz columns + + GameObjectControls(); + + GUIHelper.EndScrollView(); + + if (!WindowManager.TabView) + { + m_rect = ResizeDrag.ResizeWindow(rect, windowID); + + GUIHelper.EndArea(); + } + } + catch (Exception e) + { + DestroyOnException(e); + } + } + + private void LayerControls() + { + GUIHelper.BeginHorizontal(); + GUILayout.Label("Layer:", new GUILayoutOption[] { GUILayout.Width(50) }); + + if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) })) + { + if (m_layer > 0) + { + m_layer--; + if (TargetGO) TargetGO.layer = m_layer; + } + } + if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) })) + { + if (m_layer < 32) + { + m_layer++; + if (TargetGO) TargetGO.layer = m_layer; + } + } + + GUILayout.Label($"{m_layer} ({LayerMaskUnstrip.LayerToName(m_layer)})", + new GUILayoutOption[] { GUILayout.Width(200) }); + + GUILayout.EndHorizontal(); + } + + private void TransformList(Rect m_rect) + { + GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null); + m_transformScroll = GUIHelper.BeginScrollView(m_transformScroll); + + GUILayout.Label("Children", new GUILayoutOption[0]); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + ChildPages.DrawLimitInputArea(); + + if (ChildPages.ItemCount > ChildPages.ItemsPerPage) + { + ChildPages.CurrentPageLabel(); + + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) })) + { + ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll); + } + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) })) + { + ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll); + } + } + GUILayout.EndHorizontal(); + + if (m_children != null && m_children.Length > 0) + { + int start = ChildPages.CalculateOffsetIndex(); + + for (int j = start; (j < start + ChildPages.ItemsPerPage && j < ChildPages.ItemCount); j++) + { + var obj = m_children[j]; + + if (!obj) + { + GUILayout.Label("null", new GUILayoutOption[0]); + continue; + } + + Buttons.GameObjectButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80); + } + } + else + { + GUILayout.Label("None", new GUILayoutOption[0]); + } + + GUIHelper.EndScrollView(); + GUILayout.EndVertical(); + } + + private void ComponentList(Rect m_rect) + { + GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null); + m_compScroll = GUIHelper.BeginScrollView(m_compScroll); + GUILayout.Label("Components", new GUILayoutOption[0]); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + CompPages.DrawLimitInputArea(); + + if (CompPages.ItemCount > CompPages.ItemsPerPage) + { + CompPages.CurrentPageLabel(); + + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) })) + { + CompPages.TurnPage(Turn.Left, ref this.m_compScroll); + } + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) })) + { + CompPages.TurnPage(Turn.Right, ref this.m_compScroll); + } + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + var width = m_rect.width / 2 - 135f; + m_addComponentInput = GUIHelper.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(width) }); + if (GUILayout.Button("Add Comp", new GUILayoutOption[0])) + { + if (ReflectionHelpers.GetTypeByName(m_addComponentInput) is Type compType) + { + if (typeof(Component).IsAssignableFrom(compType)) + { +#if CPP + TargetGO.AddComponent(Il2CppType.From(compType)); +#else + TargetGO.AddComponent(compType); +#endif + } + else + { + ExplorerCore.LogWarning($"Type '{compType.Name}' is not assignable from Component!"); + } + } + else + { + ExplorerCore.LogWarning($"Could not find a type by the name of '{m_addComponentInput}'!"); + } + } + GUILayout.EndHorizontal(); + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + if (m_cachedDestroyList.Count > 0) + { + m_cachedDestroyList.Clear(); + } + + if (m_components != null) + { + int start = CompPages.CalculateOffsetIndex(); + + for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++) + { + var component = m_components[j]; + + if (!component) continue; + + var type = +#if CPP + component.GetIl2CppType(); +#else + component.GetType(); +#endif + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + if (ReflectionHelpers.BehaviourType.IsAssignableFrom(type)) + { +#if CPP + BehaviourEnabledBtn(component.TryCast()); +#else + BehaviourEnabledBtn(component as Behaviour); +#endif + } + else + { + GUIHelper.Space(26); + } + if (GUILayout.Button("" + type.Name + "", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) })) + { + ReflectObject(component); + } + if (GUILayout.Button("-", new GUILayoutOption[] { GUILayout.Width(20) })) + { + m_cachedDestroyList.Add(component); + } + GUILayout.EndHorizontal(); + } + } + + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + if (m_cachedDestroyList.Count > 0) + { + for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--) + { + var comp = m_cachedDestroyList[i]; + GameObject.Destroy(comp); + } + } + + GUIHelper.EndScrollView(); + + GUILayout.EndVertical(); + } + + private void BehaviourEnabledBtn(Behaviour obj) + { + var _col = GUI.color; + bool _enabled = obj.enabled; + if (_enabled) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.red; + } + + // ------ toggle active button ------ + + _enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) }); + if (obj.enabled != _enabled) + { + obj.enabled = _enabled; + } + GUI.color = _col; + } + + private void GameObjectControls() + { + if (m_hideControls) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("GameObject Controls", new GUILayoutOption[] { GUILayout.Width(200) }); + if (GUILayout.Button("^ Show ^", new GUILayoutOption[] { GUILayout.Width(75) })) + { + m_hideControls = false; + } + GUILayout.EndHorizontal(); + + return; + } + + GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) }); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("GameObject Controls", new GUILayoutOption[] { GUILayout.Width(200) }); + if (GUILayout.Button("v Hide v", new GUILayoutOption[] { GUILayout.Width(75) })) + { + m_hideControls = true; + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + bool m_active = TargetGO.activeSelf; + m_active = GUILayout.Toggle(m_active, (m_active ? "Enabled " : "Disabled") + "", + new GUILayoutOption[] { GUILayout.Width(80) }); + if (TargetGO.activeSelf != m_active) { TargetGO.SetActive(m_active); } + + Buttons.InstantiateButton(TargetGO, 100); + + if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) })) + { + GameObject.DontDestroyOnLoad(TargetGO); + TargetGO.hideFlags |= HideFlags.DontUnloadUnusedAsset; + } + + var lbl = m_freeze ? "Unfreeze" : "Freeze Pos/Rot"; + if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) })) + { + m_freeze = !m_freeze; + if (m_freeze) + { + UpdateFreeze(); + } + } + + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + m_setParentInput = GUIHelper.TextField(m_setParentInput, new GUILayoutOption[0]); + if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) })) + { + if (GameObject.Find(m_setParentInput) is GameObject newparent) + { + TargetGO.transform.parent = newparent.transform; + } + else + { + ExplorerCore.LogWarning($"Could not find gameobject '{m_setParentInput}'"); + } + } + + if (GUILayout.Button("Detach from parent", new GUILayoutOption[] { GUILayout.Width(160) })) + { + TargetGO.transform.parent = null; + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null); + + m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false); + m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true); + m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + if (GUILayout.Button("Apply to Transform", new GUILayoutOption[0]) || m_autoApplyTransform) + { + if (m_localContext) + { + TargetGO.transform.localPosition = m_cachedInput[0]; + TargetGO.transform.localEulerAngles = m_cachedInput[1]; + } + else + { + TargetGO.transform.position = m_cachedInput[0]; + TargetGO.transform.eulerAngles = m_cachedInput[1]; + } + TargetGO.transform.localScale = m_cachedInput[2]; + + if (m_freeze) + { + UpdateFreeze(); + } + } + if (GUILayout.Button("Update from Transform", new GUILayoutOption[0]) || m_autoUpdateTransform) + { + CacheTransformValues(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?"); + BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?"); + GUILayout.EndHorizontal(); + + bool b = m_localContext; + b = GUILayout.Toggle(b, "Use local transform values?", new GUILayoutOption[0]); + if (b != m_localContext) + { + m_localContext = b; + CacheTransformValues(); + if (m_freeze) + { + UpdateFreeze(); + } + } + + GUILayout.EndVertical(); + + if (GUILayout.Button("Destroy", new GUILayoutOption[] { GUILayout.Width(120) })) + { + GameObject.Destroy(TargetGO); + DestroyWindow(); + return; + } + + GUILayout.EndVertical(); + } + + private void UpdateFreeze() + { + if (m_localContext) + { + m_frozenPosition = TargetGO.transform.localPosition; + m_frozenRotation = TargetGO.transform.localRotation; + } + else + { + m_frozenPosition = TargetGO.transform.position; + m_frozenRotation = TargetGO.transform.rotation; + } + m_frozenScale = TargetGO.transform.localScale; + } + + private void BoolToggle(ref bool value, string message) + { + string lbl = "{message}"; + + value = GUILayout.Toggle(value, lbl, new GUILayoutOption[0]); + } + + public enum TranslateType + { + Position, + Rotation, + Scale + } + + private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label($"{(m_localContext ? "Local " : "")}{mode}:", + new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) }); + + var transform = TargetGO.transform; + switch (mode) + { + case TranslateType.Position: + var pos = m_localContext ? transform.localPosition : transform.position; + GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) }); + break; + case TranslateType.Rotation: + var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles; + GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) }); + break; + case TranslateType.Scale: + GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) }); + break; + } + GUILayout.EndHorizontal(); + + Vector3 input = m_cachedInput[(int)mode]; + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUI.skin.label.alignment = TextAnchor.MiddleRight; + + GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref input.x, amount, multByTime); + + GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref input.y, amount, multByTime); + + GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(20) }); + PlusMinusFloat(ref input.z, amount, multByTime); + + GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) }); + var amountInput = amount.ToString("F3"); + amountInput = GUIHelper.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) }); + if (float.TryParse(amountInput, out float f)) + { + amount = f; + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + GUILayout.EndHorizontal(); + + return input; + } + + private void PlusMinusFloat(ref float f, float amount, bool multByTime) + { + string s = f.ToString("F3"); + s = GUIHelper.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) }); + if (float.TryParse(s, out float f2)) + { + f = f2; + } + if (GUIHelper.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) })) + { + f -= multByTime ? amount * Time.deltaTime : amount; + } + if (GUIHelper.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) })) + { + f += multByTime ? amount * Time.deltaTime : amount; + } + } + } +} diff --git a/src/UI/Inspectors/InspectUnderMouse.cs b/src/UI/Inspectors/InspectUnderMouse.cs new file mode 100644 index 0000000..6144c42 --- /dev/null +++ b/src/UI/Inspectors/InspectUnderMouse.cs @@ -0,0 +1,86 @@ +using UnityEngine; +using Explorer.Helpers; + +namespace Explorer.UI.Inspectors +{ + public class InspectUnderMouse + { + public static bool EnableInspect { get; set; } = false; + + private static string m_objUnderMouseName = ""; + + public static void Update() + { + if (ExplorerCore.ShowMenu) + { + if (InputManager.GetKey(KeyCode.LeftShift) && InputManager.GetMouseButtonDown(1)) + { + EnableInspect = !EnableInspect; + } + + if (EnableInspect) + { + InspectRaycast(); + } + } + else if (EnableInspect) + { + EnableInspect = false; + } + } + + public static void InspectRaycast() + { + if (!UnityHelpers.MainCamera) + return; + + var ray = UnityHelpers.MainCamera.ScreenPointToRay(InputManager.MousePosition); + + if (Physics.Raycast(ray, out RaycastHit hit, 1000f)) + { + var obj = hit.transform.gameObject; + + m_objUnderMouseName = obj.transform.GetGameObjectPath(); + + if (InputManager.GetMouseButtonDown(0)) + { + EnableInspect = false; + m_objUnderMouseName = ""; + + WindowManager.InspectObject(obj, out _); + } + } + else + { + m_objUnderMouseName = ""; + } + } + + public static void OnGUI() + { + if (EnableInspect) + { + if (m_objUnderMouseName != "") + { + var pos = InputManager.MousePosition; + var rect = new Rect( + pos.x - (Screen.width / 2), // x + Screen.height - pos.y - 50, // y + Screen.width, // w + 50 // h + ); + + var origAlign = GUI.skin.label.alignment; + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + + //shadow text + GUI.Label(rect, $"{m_objUnderMouseName}"); + //white text + GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName); + + GUI.skin.label.alignment = origAlign; + } + } + } + } +} diff --git a/src/UI/Inspectors/Reflection/InstanceInspector.cs b/src/UI/Inspectors/Reflection/InstanceInspector.cs new file mode 100644 index 0000000..281974e --- /dev/null +++ b/src/UI/Inspectors/Reflection/InstanceInspector.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI; +using Explorer.UI.Shared; +using Explorer.CacheObject; +#if CPP +using UnhollowerBaseLib; +#endif + +namespace Explorer.UI.Inspectors +{ + public class InstanceInspector : ReflectionInspector + { + public override bool IsStaticInspector => false; + + // some extra cast-caching + public UnityEngine.Object m_uObj; + private Component m_component; + + public override void Init() + { + // cache the extra cast-caching +#if CPP + if (!IsStaticInspector && Target is Il2CppSystem.Object ilObject) + { + var unityObj = ilObject.TryCast(); + if (unityObj) + { + m_uObj = unityObj; + + var component = ilObject.TryCast(); + if (component) + { + m_component = component; + } + } + } +#else + if (!IsStaticInspector) + { + m_uObj = Target as UnityEngine.Object; + m_component = Target as Component; + } +#endif + + base.Init(); + } + + public override void Update() + { + if (Target == null) + { + ExplorerCore.Log("Target is null!"); + DestroyWindow(); + return; + } + if (Target is UnityEngine.Object uObj) + { + if (!uObj) + { + ExplorerCore.Log("Target was destroyed!"); + DestroyWindow(); + return; + } + } + + base.Update(); + } + + public void DrawInstanceControls(Rect rect) + { + //if (m_uObj) + //{ + // GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]); + //} + //GUILayout.EndHorizontal(); + + if (m_uObj) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Tools:", new GUILayoutOption[] { GUILayout.Width(80) }); + Buttons.InstantiateButton(m_uObj); + if (m_component && m_component.gameObject is GameObject obj) + { + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) }); + var charWidth = obj.name.Length * 15; + var maxWidth = rect.width - 350; + var btnWidth = charWidth < maxWidth ? charWidth : maxWidth; + if (GUILayout.Button("" + obj.name + "", new GUILayoutOption[] { GUILayout.Width(btnWidth) })) + { + WindowManager.InspectObject(obj, out bool _); + } + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } + else + { + GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]); + } + GUILayout.EndHorizontal(); + } + } + } +} diff --git a/src/UI/Inspectors/Reflection/StaticInspector.cs b/src/UI/Inspectors/Reflection/StaticInspector.cs new file mode 100644 index 0000000..c3cc84e --- /dev/null +++ b/src/UI/Inspectors/Reflection/StaticInspector.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Explorer.CacheObject; + +namespace Explorer.UI.Inspectors +{ + public class StaticInspector : ReflectionInspector + { + public override bool IsStaticInspector => true; + + public override void Init() + { + base.Init(); + } + + public override bool ShouldProcessMember(CacheMember holder) + { + return base.ShouldProcessMember(holder); + } + + public override void WindowFunction(int windowID) + { + base.WindowFunction(windowID); + } + } +} diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs new file mode 100644 index 0000000..ff094ea --- /dev/null +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI; +using Explorer.UI.Shared; +using Explorer.CacheObject; +using Explorer.UI.Inspectors; +using Explorer.Helpers; +#if CPP +using UnhollowerBaseLib; +#endif + +namespace Explorer.UI.Inspectors +{ + public abstract class ReflectionInspector : WindowBase + { + public override string Title => WindowManager.TabView + ? $"[R] {TargetType.Name}" + : $"Reflection Inspector ({TargetType.Name})"; + + public virtual bool IsStaticInspector { get; } + + public Type TargetType; + + private CacheMember[] m_allCachedMembers; + private CacheMember[] m_cachedMembersFiltered; + + public PageHelper Pages = new PageHelper(); + + private bool m_autoUpdate = false; + private string m_search = ""; + public MemberTypes m_typeFilter = MemberTypes.Property; + private bool m_hideFailedReflection = false; + + private MemberScopes m_scopeFilter; + private enum MemberScopes + { + Both, + Instance, + Static + } + + private static readonly HashSet _typeAndMemberBlacklist = new HashSet + { + // Causes a crash + "Type.DeclaringMethod", + // Causes a crash + "Rigidbody2D.Cast", + }; + + private static readonly HashSet _methodStartsWithBlacklist = new HashSet + { + // Pointless (handled by Properties) + "get_", + "set_", + }; + + public override void Init() + { + if (!IsStaticInspector) + { + TargetType = ReflectionHelpers.GetActualType(Target); + CacheMembers(ReflectionHelpers.GetAllBaseTypes(Target)); + } + else + { + CacheMembers(new Type[] { TargetType }); + } + } + + public override void Update() + { + if (m_allCachedMembers == null) + { + return; + } + + m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray(); + + if (m_autoUpdate) + { + UpdateValues(); + } + } + + private void UpdateValues() + { + foreach (var member in m_cachedMembersFiltered) + { + member.UpdateValue(); + } + } + + public virtual bool ShouldProcessMember(CacheMember holder) + { + // check MemberTypes filter + if (m_typeFilter != MemberTypes.All && m_typeFilter != holder.MemInfo?.MemberType) + return false; + + // check scope filter + if (m_scopeFilter == MemberScopes.Instance) + { + return !holder.IsStatic; + } + else if (m_scopeFilter == MemberScopes.Static) + { + return holder.IsStatic; + } + + // hide failed reflection + if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) + return false; + + // see if we should do name search + if (m_search == "" || holder.MemInfo == null) + return true; + + // ok do name search + return (holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name) + .ToLower() + .Contains(m_search.ToLower()); + } + + private void CacheMembers(Type[] types) + { + var list = new List(); + var cachedSigs = new HashSet(); + + foreach (var declaringType in types) + { + MemberInfo[] infos; + try + { + infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags); + } + catch + { + ExplorerCore.Log($"Exception getting members for type: {declaringType.FullName}"); + continue; + } + + object target = Target; + string exception = null; + +#if CPP + if (!IsStaticInspector && target is Il2CppSystem.Object ilObject) + { + try + { + target = ilObject.Il2CppCast(declaringType); + } + catch (Exception e) + { + exception = ReflectionHelpers.ExceptionToString(e); + } + } +#endif + foreach (var member in infos) + { + try + { + // make sure member type is Field, Method or Property (4 / 8 / 16) + int m = (int)member.MemberType; + if (m < 4 || m > 16) + continue; + + var fi = member as FieldInfo; + var pi = member as PropertyInfo; + var mi = member as MethodInfo; + + if (IsStaticInspector) + { + if (fi != null && !fi.IsStatic) continue; + else if (pi != null && !pi.GetAccessors()[0].IsStatic) continue; + else if (mi != null && !mi.IsStatic) continue; + } + + // check blacklisted members + var sig = $"{member.DeclaringType.Name}.{member.Name}"; + if (_typeAndMemberBlacklist.Any(it => it == sig)) + continue; + + if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it))) + continue; + + if (mi != null) + { + AppendParams(mi.GetParameters()); + } + else if (pi != null) + { + AppendParams(pi.GetIndexParameters()); + } + + void AppendParams(ParameterInfo[] _args) + { + sig += " ("; + foreach (var param in _args) + { + sig += $"{param.ParameterType.Name} {param.Name}, "; + } + sig += ")"; + } + + if (cachedSigs.Contains(sig)) + { + continue; + } + + try + { + var cached = CacheFactory.GetCacheObject(member, target); + + if (cached != null) + { + cachedSigs.Add(sig); + list.Add(cached); + + if (string.IsNullOrEmpty(cached.ReflectionException)) + { + cached.ReflectionException = exception; + } + } + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Exception caching member {sig}!"); + ExplorerCore.Log(e.ToString()); + } + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Exception caching member {member.DeclaringType.FullName}.{member.Name}!"); + ExplorerCore.Log(e.ToString()); + } + } + } + + m_allCachedMembers = list.ToArray(); + } + + // =========== GUI DRAW =========== // + + public override void WindowFunction(int windowID) + { + try + { + // ====== HEADER ====== + + var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect; + + if (!WindowManager.TabView) + { + Header(); + GUIHelper.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box); + } + + var asInstance = this as InstanceInspector; + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + var labelWidth = (asInstance != null && asInstance.m_uObj) + ? new GUILayoutOption[] { GUILayout.Width(245f) } + : new GUILayoutOption[0]; + GUILayout.Label("Type: " + TargetType.FullName + "", labelWidth); + GUILayout.EndHorizontal(); + + if (asInstance != null) + { + asInstance.DrawInstanceControls(rect); + } + + UIStyles.HorizontalLine(Color.grey); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Search:", new GUILayoutOption[] { GUILayout.Width(75) }); + m_search = GUIHelper.TextField(m_search, new GUILayoutOption[0]); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Filter:", new GUILayoutOption[] { GUILayout.Width(75) }); + FilterTypeToggle(MemberTypes.All, "All"); + FilterTypeToggle(MemberTypes.Property, "Properties"); + FilterTypeToggle(MemberTypes.Field, "Fields"); + FilterTypeToggle(MemberTypes.Method, "Methods"); + GUILayout.EndHorizontal(); + + if (this is InstanceInspector) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Scope:", new GUILayoutOption[] { GUILayout.Width(75) }); + FilterScopeToggle(MemberScopes.Both, "Both"); + FilterScopeToggle(MemberScopes.Instance, "Instance"); + FilterScopeToggle(MemberScopes.Static, "Static"); + GUILayout.EndHorizontal(); + } + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Values:", new GUILayoutOption[] { GUILayout.Width(75) }); + if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) })) + { + UpdateValues(); + } + GUI.color = m_autoUpdate ? Color.green : Color.red; + m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) }); + GUI.color = m_hideFailedReflection ? Color.green : Color.red; + m_hideFailedReflection = GUILayout.Toggle(m_hideFailedReflection, "Hide failed Reflection?", new GUILayoutOption[] { GUILayout.Width(150) }); + GUI.color = Color.white; + GUILayout.EndHorizontal(); + + GUIHelper.Space(10); + + Pages.ItemCount = m_cachedMembersFiltered.Length; + + // prev/next page buttons + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + Pages.DrawLimitInputArea(); + + if (Pages.ItemCount > Pages.ItemsPerPage) + { + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) })) + { + Pages.TurnPage(Turn.Left, ref this.scroll); + } + + Pages.CurrentPageLabel(); + + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) })) + { + Pages.TurnPage(Turn.Right, ref this.scroll); + } + } + GUILayout.EndHorizontal(); + + // ====== BODY ====== + + scroll = GUIHelper.BeginScrollView(scroll); + + GUIHelper.Space(10); + + UIStyles.HorizontalLine(Color.grey); + + GUIHelper.BeginVertical(GUIContent.none, GUI.skin.box, null); + + var members = this.m_cachedMembersFiltered; + int start = Pages.CalculateOffsetIndex(); + + for (int j = start; (j < start + Pages.ItemsPerPage && j < members.Length); j++) + { + var holder = members[j]; + + GUIHelper.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); + try + { + holder.Draw(rect, 180f); + } + catch + { + GUILayout.EndHorizontal(); + continue; + } + GUILayout.EndHorizontal(); + + // if not last element + if (!(j == (start + Pages.ItemsPerPage - 1) || j == (members.Length - 1))) + UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true); + } + + GUILayout.EndVertical(); + GUIHelper.EndScrollView(); + + if (!WindowManager.TabView) + { + m_rect = ResizeDrag.ResizeWindow(rect, windowID); + + GUIHelper.EndArea(); + } + } + catch (Exception e) when (e.Message.Contains("in a group with only")) + { + // suppress + } + catch (Exception e) + { + ExplorerCore.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message); + DestroyWindow(); + return; + } + } + + private void FilterTypeToggle(MemberTypes mode, string label) + { + if (m_typeFilter == mode) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.white; + } + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) + { + m_typeFilter = mode; + Pages.PageOffset = 0; + scroll = Vector2.zero; + } + GUI.color = Color.white; + } + + private void FilterScopeToggle(MemberScopes mode, string label) + { + if (m_scopeFilter == mode) + { + GUI.color = Color.green; + } + else + { + GUI.color = Color.white; + } + if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) + { + m_scopeFilter = mode; + Pages.PageOffset = 0; + scroll = Vector2.zero; + } + GUI.color = Color.white; + } + } +} diff --git a/src/UI/InteractiveValue/InteractiveValue.cs b/src/UI/InteractiveValue/InteractiveValue.cs index 42927eb..a921baf 100644 --- a/src/UI/InteractiveValue/InteractiveValue.cs +++ b/src/UI/InteractiveValue/InteractiveValue.cs @@ -1,108 +1,258 @@ -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Reflection; -//using UnityEngine; -//using Explorer.CacheObject; -//using Explorer.Helpers; -//using Explorer.UI.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; +using Explorer.Helpers; -//namespace Explorer.UI -//{ -// public class InteractiveValue -// { -// //public const float MAX_LABEL_WIDTH = 400f; -// //public const string EVALUATE_LABEL = "Evaluate"; +namespace Explorer.UI +{ + public class InteractiveValue + { + public const float MAX_LABEL_WIDTH = 400f; + public const string EVALUATE_LABEL = "Evaluate"; -// public CacheObjectBase OwnerCacheObject; + public CacheObjectBase OwnerCacheObject; -// public object Value { get; set; } -// public Type ValueType; + public object Value { get; set; } + public Type ValueType; -// public string ButtonLabel => m_btnLabel ?? GetButtonLabel(); -// private string m_btnLabel; + public string ButtonLabel => m_btnLabel ?? GetButtonLabel(); + private string m_btnLabel; -// public MethodInfo ToStringMethod => m_toStringMethod ?? GetToStringMethod(); -// private MethodInfo m_toStringMethod; + public MethodInfo ToStringMethod => m_toStringMethod ?? GetToStringMethod(); + private MethodInfo m_toStringMethod; -// public virtual void Init() -// { -// UpdateValue(); -// } + public virtual void Init() + { + UpdateValue(); + } -// public virtual void UpdateValue() -// { -// GetButtonLabel(); -// } + public virtual void UpdateValue() + { + GetButtonLabel(); + } - + public float CalcWhitespace(Rect window) + { + if (!(this is IExpandHeight)) return 0f; -// private MethodInfo GetToStringMethod() -// { -// try -// { -// m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0]) -// ?? typeof(object).GetMethod("ToString", new Type[0]); + float whitespace = (this as IExpandHeight).WhiteSpace; + if (whitespace > 0) + { + ClampLabelWidth(window, ref whitespace); + } -// // test invoke -// m_toStringMethod.Invoke(Value, null); -// } -// catch -// { -// m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]); -// } -// return m_toStringMethod; -// } + return whitespace; + } -// public string GetButtonLabel() -// { -// if (Value == null) return null; + public static void ClampLabelWidth(Rect window, ref float labelWidth) + { + float min = window.width * 0.37f; + if (min > MAX_LABEL_WIDTH) min = MAX_LABEL_WIDTH; -// var valueType = ReflectionHelpers.GetActualType(Value); + labelWidth = Mathf.Clamp(labelWidth, min, MAX_LABEL_WIDTH); + } -// string label; + public void Draw(Rect window, float labelWidth = 215f) + { + if (labelWidth > 0) + { + ClampLabelWidth(window, ref labelWidth); + } -// if (valueType == typeof(TextAsset)) -// { -// var textAsset = Value as TextAsset; + var cacheMember = OwnerCacheObject as CacheMember; -// label = textAsset.text; + if (cacheMember != null && cacheMember.MemInfo != null) + { + GUILayout.Label(cacheMember.RichTextName, new GUILayoutOption[] { GUILayout.Width(labelWidth) }); + } + else + { + GUIHelper.Space(labelWidth); + } -// if (label.Length > 10) -// { -// label = $"{label.Substring(0, 10)}..."; -// } + var cacheMethod = OwnerCacheObject as CacheMethod; -// label = $"\"{label}\" {textAsset.name} (UnityEngine.TextAsset)"; -// } -// else -// { -// label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString(); + if (cacheMember != null && cacheMember.HasParameters) + { + GUIHelper.BeginVertical(new GUILayoutOption[] { GUILayout.ExpandHeight(true) } ); -// var classColor = valueType.IsAbstract && valueType.IsSealed -// ? Syntax.Class_Static -// : Syntax.Class_Instance; + if (cacheMember.m_isEvaluating) + { + if (cacheMethod != null && cacheMethod.GenericArgs.Length > 0) + { + cacheMethod.DrawGenericArgsInput(); + } -// string typeLabel = $"{valueType.FullName}"; + if (cacheMember.m_arguments.Length > 0) + { + cacheMember.DrawArgsInput(); + } -// if (Value is UnityEngine.Object) -// { -// label = label.Replace($"({valueType.FullName})", $"({typeLabel})"); -// } -// else -// { -// if (!label.Contains(valueType.FullName)) -// { -// label += $" ({typeLabel})"; -// } -// else -// { -// label = label.Replace(valueType.FullName, typeLabel); -// } -// } -// } + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) })) + { + if (cacheMethod != null) + cacheMethod.Evaluate(); + else + cacheMember.UpdateValue(); + } + if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) })) + { + cacheMember.m_isEvaluating = false; + } + GUILayout.EndHorizontal(); + } + else + { + var lbl = $"Evaluate ("; + int len = cacheMember.m_arguments.Length; + if (cacheMethod != null) len += cacheMethod.GenericArgs.Length; + lbl += len + " params)"; -// return m_btnLabel = label; -// } -// } -//} + if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(150) })) + { + cacheMember.m_isEvaluating = true; + } + } + + GUILayout.EndVertical(); + + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(labelWidth); + } + else if (cacheMethod != null) + { + if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) })) + { + cacheMethod.Evaluate(); + } + + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(labelWidth); + } + + string typeName = $"{ValueType.FullName}"; + + if (cacheMember != null && !string.IsNullOrEmpty(cacheMember.ReflectionException)) + { + GUILayout.Label("Reflection failed! (" + cacheMember.ReflectionException + ")", new GUILayoutOption[0]); + } + else if (cacheMember != null && (cacheMember.HasParameters || cacheMember is CacheMethod) && !cacheMember.m_evaluated) + { + GUILayout.Label($"Not yet evaluated ({typeName})", new GUILayoutOption[0]); + } + else if ((Value == null || Value is UnityEngine.Object uObj && !uObj) && !(cacheMember is CacheMethod)) + { + GUILayout.Label($"null ({typeName})", new GUILayoutOption[0]); + } + else + { + float _width = window.width - labelWidth - 90; + if (OwnerCacheObject is CacheMethod cm) + { + cm.DrawValue(window, _width); + } + else + { + DrawValue(window, _width); + } + } + } + + public virtual void DrawValue(Rect window, float width) + { + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + if (GUILayout.Button(ButtonLabel, new GUILayoutOption[] { GUILayout.Width(width - 15) })) + { + if (OwnerCacheObject.IsStaticClassSearchResult) + { + WindowManager.InspectStaticReflection(Value as Type); + } + else + { + WindowManager.InspectObject(Value, out bool _); + } + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + } + + private MethodInfo GetToStringMethod() + { + try + { + m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0]) + ?? typeof(object).GetMethod("ToString", new Type[0]); + + // test invoke + m_toStringMethod.Invoke(Value, null); + } + catch + { + m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]); + } + return m_toStringMethod; + } + + public string GetButtonLabel() + { + if (Value == null) return null; + + var valueType = ReflectionHelpers.GetActualType(Value); + + string label; + + if (valueType == typeof(TextAsset)) + { + var textAsset = Value as TextAsset; + + label = textAsset.text; + + if (label.Length > 10) + { + label = $"{label.Substring(0, 10)}..."; + } + + label = $"\"{label}\" {textAsset.name} (UnityEngine.TextAsset)"; + } + else + { + label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString(); + + if (label.Length > 100) + { + label = label.Substring(0, 99); + } + + var classColor = valueType.IsAbstract && valueType.IsSealed + ? Syntax.Class_Static + : Syntax.Class_Instance; + + string typeLabel = $"{valueType.FullName}"; + + if (Value is UnityEngine.Object) + { + label = label.Replace($"({valueType.FullName})", $"({typeLabel})"); + } + else + { + if (!label.Contains(valueType.FullName)) + { + label += $" ({typeLabel})"; + } + else + { + label = label.Replace(valueType.FullName, typeLabel); + } + } + } + + return m_btnLabel = label; + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveDictionary.cs b/src/UI/InteractiveValue/Object/InteractiveDictionary.cs new file mode 100644 index 0000000..5a9b68b --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveDictionary.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; +using Explorer.Helpers; +#if CPP +using UnhollowerBaseLib; +#endif + +namespace Explorer.UI +{ + // TODO: Re-work class using InteractiveEnumerable or maybe InteractiveCollection for the Keys/Value lists. + // Make the keys and values editable. + + public class InteractiveDictionary : InteractiveValue, IExpandHeight + { + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public PageHelper Pages = new PageHelper(); + + private CacheObjectBase[] m_cachedKeys = new CacheObjectBase[0]; + private CacheObjectBase[] m_cachedValues = new CacheObjectBase[0]; + + public Type TypeOfKeys + { + get + { + if (m_keysType == null) GetGenericArguments(); + return m_keysType; + } + } + private Type m_keysType; + + public Type TypeOfValues + { + get + { + if (m_valuesType == null) GetGenericArguments(); + return m_valuesType; + } + } + private Type m_valuesType; + + public IDictionary IDict + { + get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono(); + set => m_iDictionary = value; + } + private IDictionary m_iDictionary; + + // ========== Methods ========== + + // This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary. + private IDictionary Il2CppDictionaryToMono() + { + // note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type. + + // get keys and values + var keys = ValueType.GetProperty("Keys").GetValue(Value, null); + var values = ValueType.GetProperty("Values").GetValue(Value, null); + + // create lists to hold them + var keyList = new List(); + var valueList = new List(); + + // store entries with reflection + EnumerateWithReflection(keys, keyList); + EnumerateWithReflection(values, valueList); + + // make actual mono dictionary + var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>) + .MakeGenericType(TypeOfKeys, TypeOfValues)); + + // finally iterate into dictionary + for (int i = 0; i < keyList.Count; i++) + { + dict.Add(keyList[i], valueList[i]); + } + + return dict; + } + + private void EnumerateWithReflection(object collection, List list) + { + // invoke GetEnumerator + var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null); + // get the type of it + var enumeratorType = enumerator.GetType(); + // reflect MoveNext and Current + var moveNext = enumeratorType.GetMethod("MoveNext"); + var current = enumeratorType.GetProperty("Current"); + // iterate + while ((bool)moveNext.Invoke(enumerator, null)) + { + list.Add(current.GetValue(enumerator, null)); + } + } + + private void GetGenericArguments() + { + if (ValueType.IsGenericType) + { + var generics = ValueType.GetGenericArguments(); + m_keysType = generics[0]; + m_valuesType = generics[1]; + } + else + { + // It's non-generic, just use System.Object to allow for anything. + m_keysType = typeof(object); + m_valuesType = typeof(object); + } + } + + public override void UpdateValue() + { + // first make sure we won't run into a TypeInitializationException. + if (!EnsureDictionaryIsSupported()) + { + if (OwnerCacheObject is CacheMember cacheMember) + { + cacheMember.ReflectionException = "Dictionary Type not supported with Reflection!"; + } + return; + } + + base.UpdateValue(); + + CacheEntries(); + } + + public void CacheEntries() + { + // reset + IDict = null; + + if (Value == null || IDict == null) + { + return; + } + + var keys = new List(); + foreach (var key in IDict.Keys) + { + Type t = ReflectionHelpers.GetActualType(key) ?? TypeOfKeys; + var cache = CacheFactory.GetCacheObject(key, t); + keys.Add(cache); + } + + var values = new List(); + foreach (var val in IDict.Values) + { + Type t = ReflectionHelpers.GetActualType(val) ?? TypeOfValues; + var cache = CacheFactory.GetCacheObject(val, t); + values.Add(cache); + } + + m_cachedKeys = keys.ToArray(); + m_cachedValues = values.ToArray(); + } + + public bool EnsureDictionaryIsSupported() + { + if (typeof(IDictionary).IsAssignableFrom(ValueType)) + { + return true; + } + +#if CPP + try + { + return Check(TypeOfKeys) && Check(TypeOfValues); + + bool Check(Type type) + { + var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>) + .MakeGenericType(type) + .GetField("NativeClassPtr") + .GetValue(null); + + if (ptr == IntPtr.Zero) + { + return false; + } + + return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type; + } + } + catch + { + return false; + } +#else + return false; +#endif + } + + // ============= GUI Draw ============= + + public override void DrawValue(Rect window, float width) + { + if (m_cachedKeys == null || m_cachedValues == null) + { + GUILayout.Label("Cached keys or values is null!", new GUILayoutOption[0]); + return; + } + + var whitespace = CalcWhitespace(window); + + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + + var negativeWhitespace = window.width - (whitespace + 100f); + + int count = m_cachedKeys.Length; + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + string btnLabel = $"[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}>"; + if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) })) + { + WindowManager.InspectObject(Value, out bool _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + GUIHelper.Space(5); + + if (IsExpanded) + { + Pages.ItemCount = count; + + if (count > Pages.ItemsPerPage) + { + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUIHelper.Space(whitespace); + + Pages.CurrentPageLabel(); + + // prev/next page buttons + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Left); + } + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Right); + } + + Pages.DrawLimitInputArea(); + + GUIHelper.Space(5); + } + + int offset = Pages.CalculateOffsetIndex(); + + for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) + { + var key = m_cachedKeys[i]; + var val = m_cachedValues[i]; + + //collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + //GUIUnstrip.Space(whitespace); + + if (key == null && val == null) + { + GUILayout.Label($"[{i}] (null)", new GUILayoutOption[0]); + } + else + { + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(40) }); + + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) }); + if (key != null) + key.IValue.DrawValue(window, (window.width / 2) - 80f); + else + GUILayout.Label("null", new GUILayoutOption[0]); + + GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) }); + if (val != null) + val.IValue.DrawValue(window, (window.width / 2) - 80f); + else + GUILayout.Label("null", new GUILayoutOption[0]); + } + + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs b/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs new file mode 100644 index 0000000..ef51379 --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveEnumerable.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Reflection; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; +using System.Linq; +using Explorer.Helpers; +#if CPP +using UnhollowerBaseLib; +#endif + +namespace Explorer.UI +{ + public class InteractiveEnumerable : InteractiveValue, IExpandHeight + { + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public PageHelper Pages = new PageHelper(); + + private CacheEnumerated[] m_cachedEntries = new CacheEnumerated[0]; + + // Type of Entries in the Array + public Type EntryType + { + get => GetEntryType(); + set => m_entryType = value; + } + private Type m_entryType; + + // Cached IEnumerable object + public IEnumerable Enumerable + { + get => GetEnumerable(); + } + private IEnumerable m_enumerable; + + // Generic Type Definition for Lists + public Type GenericTypeDef + { + get => GetGenericTypeDef(); + } + private Type m_genericTypeDef; + + // Cached ToArray method for Lists + public MethodInfo CppListToArrayMethod + { + get => GetGenericToArrayMethod(); + } + private MethodInfo m_genericToArray; + + // Cached Item Property for ILists + public PropertyInfo ItemProperty + { + get => GetItemProperty(); + } + + private PropertyInfo m_itemProperty; + + // ========== Methods ========== + + private IEnumerable GetEnumerable() + { + if (m_enumerable == null && Value != null) + { + m_enumerable = Value as IEnumerable ?? EnumerateWithReflection(); + } + return m_enumerable; + } + + private Type GetGenericTypeDef() + { + if (m_genericTypeDef == null && Value != null) + { + var type = Value.GetType(); + if (type.IsGenericType) + { + m_genericTypeDef = type.GetGenericTypeDefinition(); + } + } + return m_genericTypeDef; + } + + private MethodInfo GetGenericToArrayMethod() + { + if (GenericTypeDef == null) return null; + + if (m_genericToArray == null) + { + m_genericToArray = GenericTypeDef + .MakeGenericType(new Type[] { this.EntryType }) + .GetMethod("ToArray"); + } + return m_genericToArray; + } + + private PropertyInfo GetItemProperty() + { + if (m_itemProperty == null) + { + m_itemProperty = Value?.GetType().GetProperty("Item"); + } + return m_itemProperty; + } + + private IEnumerable EnumerateWithReflection() + { + if (Value == null) return null; + +#if CPP + if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>)) + { + return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]); + } + else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>)) + { + return CppHashSetToMono(); + } + else + { + return CppIListToMono(); + } +#else + return Value as IEnumerable; +#endif + } + +#if CPP + private IEnumerable CppHashSetToMono() + { + var set = new HashSet(); + + // invoke GetEnumerator + var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null); + // get the type of it + var enumeratorType = enumerator.GetType(); + // reflect MoveNext and Current + var moveNext = enumeratorType.GetMethod("MoveNext"); + var current = enumeratorType.GetProperty("Current"); + // iterate + while ((bool)moveNext.Invoke(enumerator, null)) + { + set.Add(current.GetValue(enumerator)); + } + + return set; + } + + private IList CppIListToMono() + { + try + { + var genericType = typeof(List<>).MakeGenericType(new Type[] { this.EntryType }); + var list = (IList)Activator.CreateInstance(genericType); + + for (int i = 0; ; i++) + { + try + { + var itm = ItemProperty.GetValue(Value, new object[] { i }); + list.Add(itm); + } + catch { break; } + } + + return list; + } + catch (Exception e) + { + ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message); + return null; + } + } +#endif + + private Type GetEntryType() + { + if (ValueType.IsGenericType) + { + var gArgs = ValueType.GetGenericArguments(); + + if (ValueType.FullName.Contains("ValueCollection")) + { + m_entryType = gArgs[gArgs.Length - 1]; + } + else + { + m_entryType = gArgs[0]; + } + } + else + { + m_entryType = typeof(object); + } + + return m_entryType; + } + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value == null || Enumerable == null) + { + return; + } + + CacheEntries(); + } + + public void CacheEntries() + { + var enumerator = Enumerable.GetEnumerator(); + if (enumerator == null) + { + return; + } + + var list = new List(); + int index = 0; + while (enumerator.MoveNext()) + { + var obj = enumerator.Current; + + if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t) + { +#if CPP + if (obj is Il2CppSystem.Object iObj) + { + try + { + var cast = iObj.Il2CppCast(t); + if (cast != null) + { + obj = cast; + } + } + catch { } + } +#endif + + //ExplorerCore.Log("Caching enumeration entry " + obj.ToString() + " as " + EntryType.FullName); + + var cached = new CacheEnumerated() { Index = index, RefIList = Value as IList, ParentEnumeration = this }; + cached.Init(obj, EntryType); + list.Add(cached); + } + else + { + list.Add(null); + } + + index++; + } + + m_cachedEntries = list.ToArray(); + } + + // ============= GUI Draw ============= + + public override void DrawValue(Rect window, float width) + { + if (m_cachedEntries == null) + { + GUILayout.Label("m_cachedEntries is null!", new GUILayoutOption[0]); + return; + } + + var whitespace = CalcWhitespace(window); + + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + + var negativeWhitespace = window.width - (whitespace + 100f); + + int count = m_cachedEntries.Length; + + GUI.skin.button.alignment = TextAnchor.MiddleLeft; + string btnLabel = $"[{count}] {EntryType.FullName}"; + if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) })) + { + WindowManager.InspectObject(Value, out bool _); + } + GUI.skin.button.alignment = TextAnchor.MiddleCenter; + + GUIHelper.Space(5); + + if (IsExpanded) + { + Pages.ItemCount = count; + + if (count > Pages.ItemsPerPage) + { + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUIHelper.Space(whitespace); + + Pages.CurrentPageLabel(); + + // prev/next page buttons + if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Left); + } + if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) })) + { + Pages.TurnPage(Turn.Right); + } + + Pages.DrawLimitInputArea(); + + GUIHelper.Space(5); + } + + int offset = Pages.CalculateOffsetIndex(); + + for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) + { + var entry = m_cachedEntries[i]; + + //collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry + GUILayout.EndHorizontal(); + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUIHelper.Space(whitespace); + + if (entry == null || entry.IValue == null) + { + GUILayout.Label($"[{i}] (null)", new GUILayoutOption[0]); + } + else + { + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) }); + + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + entry.IValue.DrawValue(window, window.width - (whitespace + 85)); + } + + } + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + } + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveGameObject.cs b/src/UI/InteractiveValue/Object/InteractiveGameObject.cs new file mode 100644 index 0000000..851468d --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveGameObject.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveGameObject : InteractiveValue + { + + + public override void DrawValue(Rect window, float width) + { + Buttons.GameObjectButton(Value, null, false, width); + } + + public override void UpdateValue() + { + base.UpdateValue(); + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveSprite.cs b/src/UI/InteractiveValue/Object/InteractiveSprite.cs new file mode 100644 index 0000000..e6cc7c8 --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveSprite.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Explorer.Helpers; +using UnityEngine; + +namespace Explorer.UI +{ + public class InteractiveSprite : InteractiveTexture2D + { + private Sprite refSprite; + + public override void UpdateValue() + { +#if CPP + if (Value != null && Value.Il2CppCast(typeof(Sprite)) is Sprite sprite) + { + refSprite = sprite; + } +#else + if (Value is Sprite sprite) + { + refSprite = sprite; + } +#endif + + base.UpdateValue(); + } + + public override void GetTexture2D() + { + if (refSprite) + { + currentTex = refSprite.texture; + } + } + + public override void GetGUIContent() + { + // Check if the Sprite.textureRect is just the entire texture + if (refSprite.textureRect != new Rect(0, 0, currentTex.width, currentTex.height)) + { + // It's not, do a sub-copy. + currentTex = Texture2DHelpers.Copy(refSprite.texture, refSprite.textureRect); + } + + base.GetGUIContent(); + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveTexture.cs b/src/UI/InteractiveValue/Object/InteractiveTexture.cs new file mode 100644 index 0000000..9c73d56 --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveTexture.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace Explorer.UI +{ + // This class is possibly unnecessary. + // It's just for CacheMembers that have 'Texture' as the value type, but is actually a Texture2D. + + public class InteractiveTexture : InteractiveTexture2D + { + public override void GetTexture2D() + { +#if CPP + if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex) +#else + if (Value is Texture2D tex) +#endif + { + currentTex = tex; + texContent = new GUIContent + { + image = currentTex + }; + } + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs b/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs new file mode 100644 index 0000000..b6e0eb3 --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Explorer.CacheObject; +using Explorer.Config; +using UnityEngine; +using System.IO; +using Explorer.Helpers; +#if CPP +using Explorer.Unstrip.ImageConversion; +#endif + +namespace Explorer.UI +{ + public class InteractiveTexture2D : InteractiveValue, IExpandHeight + { + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public Texture2D currentTex; + public GUIContent texContent; + + private string saveFolder = ModConfig.Instance.Default_Output_Path; + + public override void Init() + { + base.Init(); + } + + public override void UpdateValue() + { + base.UpdateValue(); + + GetTexture2D(); + } + + public virtual void GetTexture2D() + { +#if CPP + if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex) +#else + if (Value is Texture2D tex) +#endif + { + currentTex = tex; + } + } + + public virtual void GetGUIContent() + { + texContent = new GUIContent + { + image = currentTex + }; + } + + public override void DrawValue(Rect window, float width) + { + GUIHelper.BeginVertical(); + + GUIHelper.BeginHorizontal(); + + if (currentTex && !IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + GetGUIContent(); + } + } + else if (currentTex) + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + + base.DrawValue(window, width); + + GUILayout.EndHorizontal(); + + if (currentTex && IsExpanded) + { + DrawTextureControls(); + DrawTexture(); + } + + GUILayout.EndVertical(); + } + + // Temporarily disabled in BepInEx IL2CPP. + private void DrawTexture() + { +#if CPP +#if BIE +#else + GUILayout.Label(texContent, new GUILayoutOption[0]); +#endif +#else + GUILayout.Label(texContent, new GUILayoutOption[0]); +#endif + } + + private void DrawTextureControls() + { + GUIHelper.BeginHorizontal(); + + GUILayout.Label("Save folder:", new GUILayoutOption[] { GUILayout.Width(80f) }); + saveFolder = GUIHelper.TextField(saveFolder, new GUILayoutOption[0]); + GUIHelper.Space(10f); + + GUILayout.EndHorizontal(); + + if (GUILayout.Button("Save to PNG", new GUILayoutOption[] { GUILayout.Width(100f) })) + { + var name = RemoveInvalidFilenameChars(currentTex.name ?? ""); + if (string.IsNullOrEmpty(name)) + { + if (OwnerCacheObject is CacheMember cacheMember) + { + name = cacheMember.MemInfo.Name; + } + else + { + name = "UNTITLED"; + } + } + + Texture2DHelpers.SaveTextureAsPNG(currentTex, saveFolder, name, false); + + ExplorerCore.Log($@"Saved to {saveFolder}\{name}.png!"); + } + } + + private string RemoveInvalidFilenameChars(string s) + { + var invalid = System.IO.Path.GetInvalidFileNameChars(); + foreach (var c in invalid) + { + s = s.Replace(c.ToString(), ""); + } + return s; + } + + + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveColor.cs b/src/UI/InteractiveValue/Struct/InteractiveColor.cs new file mode 100644 index 0000000..1c21a5c --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveColor.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveColor : InteractiveValue, IExpandHeight + { + private string r = "0"; + private string g = "0"; + private string b = "0"; + private string a = "0"; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value == null) return; + + var color = (Color)Value; + + r = color.r.ToString(); + g = color.g.ToString(); + b = color.b.ToString(); + a = color.a.ToString(); + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + //var c = (Color)Value; + //GUI.color = c; + GUILayout.Label($"Color: {((Color)Value).ToString()}", new GUILayoutOption[0]); + //GUI.color = Color.white; + + if (OwnerCacheObject.CanWrite && IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) }); + r = GUIHelper.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) }); + g = GUIHelper.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) }); + b = GUIHelper.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) }); + a = GUIHelper.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + // draw set value button + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetValueFromInput(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + } + } + + private void SetValueFromInput() + { + if (float.TryParse(r, out float fR) + && float.TryParse(g, out float fG) + && float.TryParse(b, out float fB) + && float.TryParse(a, out float fA)) + { + Value = new Color(fR, fG, fB, fA); + OwnerCacheObject.SetValue(); + } + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveEnum.cs b/src/UI/InteractiveValue/Struct/InteractiveEnum.cs new file mode 100644 index 0000000..9899bdf --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveEnum.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveEnum : InteractiveValue + { + internal static Dictionary EnumNamesInternalCache = new Dictionary(); + + // public Type EnumType; + public string[] EnumNames = new string[0]; + + public override void Init() + { + if (ValueType == null && Value != null) + { + ValueType = Value.GetType(); + } + + if (ValueType != null) + { + GetNames(); + } + else + { + if (OwnerCacheObject is CacheMember cacheMember) + { + cacheMember.ReflectionException = "Unknown, could not get Enum names."; + } + } + } + + internal void GetNames() + { + if (!EnumNamesInternalCache.ContainsKey(ValueType)) + { + // using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags) + var values = Enum.GetValues(ValueType); + + var set = new HashSet(); + foreach (var value in values) + { + var v = value.ToString(); + if (set.Contains(v)) continue; + set.Add(v); + } + + EnumNamesInternalCache.Add(ValueType, set.ToArray()); + } + + EnumNames = EnumNamesInternalCache[ValueType]; + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) })) + { + SetEnum(-1); + OwnerCacheObject.SetValue(); + } + if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) })) + { + SetEnum(1); + OwnerCacheObject.SetValue(); + } + } + + GUILayout.Label(Value.ToString() + $" ({ValueType})", new GUILayoutOption[0]); + } + + public void SetEnum(int change) + { + var names = EnumNames.ToList(); + + int newindex = names.IndexOf(Value.ToString()) + change; + + if (newindex >= 0 && newindex < names.Count) + { + Value = Enum.Parse(ValueType, EnumNames[newindex]); + } + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveFlags.cs b/src/UI/InteractiveValue/Struct/InteractiveFlags.cs new file mode 100644 index 0000000..00d76b8 --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveFlags.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveFlags : InteractiveEnum, IExpandHeight + { + public bool[] m_enabledFlags = new bool[0]; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void Init() + { + base.Init(); + + UpdateValue(); + } + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value == null) return; + + try + { + var enabledNames = Value.ToString().Split(',').Select(it => it.Trim()); + + m_enabledFlags = new bool[EnumNames.Length]; + + for (int i = 0; i < EnumNames.Length; i++) + { + m_enabledFlags[i] = enabledNames.Contains(EnumNames[i]); + } + } + catch (Exception e) + { + ExplorerCore.Log(e.ToString()); + } + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + GUILayout.Label(Value.ToString() + " (" + ValueType + ")", new GUILayoutOption[0]); + + if (IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + for (int i = 0; i < EnumNames.Length; i++) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + + m_enabledFlags[i] = GUILayout.Toggle(m_enabledFlags[i], EnumNames[i], new GUILayoutOption[0]); + + GUILayout.EndHorizontal(); + } + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetFlagsFromInput(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + } + } + + public void SetFlagsFromInput() + { + string val = ""; + for (int i = 0; i < EnumNames.Length; i++) + { + if (m_enabledFlags[i]) + { + if (val != "") val += ", "; + val += EnumNames[i]; + } + } + Value = Enum.Parse(ValueType, val); + OwnerCacheObject.SetValue(); + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractivePrimitive.cs b/src/UI/InteractiveValue/Struct/InteractivePrimitive.cs new file mode 100644 index 0000000..78250ae --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractivePrimitive.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; +#if CPP +using UnhollowerRuntimeLib; +#endif +using Explorer.UI.Shared; +using Explorer.CacheObject; +using Explorer.Config; + +namespace Explorer.UI +{ + public class InteractivePrimitive : InteractiveValue + { + private string m_valueToString; + private bool m_isBool; + private bool m_isString; + + public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) })); + private MethodInfo m_parseMethod; + + private bool m_canBitwiseOperate; + private bool m_inBitwiseMode; + private string m_bitwiseOperatorInput = "0"; + private string m_binaryInput; + + public override void Init() + { + if (ValueType == null) + { + ValueType = Value?.GetType(); + + // has to be a string at this point + if (ValueType == null) + { + ValueType = typeof(string); + } + } + + if (ValueType == typeof(string)) + { + m_isString = true; + } + else if (ValueType == typeof(bool)) + { + m_isBool = true; + } + + m_canBitwiseOperate = typeof(int).IsAssignableFrom(ValueType); + + UpdateValue(); + } + + public override void UpdateValue() + { + base.UpdateValue(); + + RefreshToString(); + } + + private void RefreshToString() + { + m_valueToString = Value?.ToString(); + + if (m_canBitwiseOperate && Value != null) + { + var _int = (int)Value; + m_binaryInput = Convert.ToString(_int, toBase: 2); + } + } + + public override void DrawValue(Rect window, float width) + { + if (m_isBool) + { + var b = (bool)Value; + var label = $"{b}"; + + if (OwnerCacheObject.CanWrite) + { + Value = GUILayout.Toggle(b, label, new GUILayoutOption[] { GUILayout.Width(60) }); + DrawApplyButton(); + //if (b != (bool)Value) + //{ + // Value = b; + // OwnerCacheObject.SetValue(); + //} + } + else + { + GUILayout.Label(label, new GUILayoutOption[0]); + } + + return; + } + + // all other non-bool values use TextField + + GUIHelper.BeginVertical(new GUILayoutOption[0]); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUILayout.Label("" + ValueType.Name + "", new GUILayoutOption[] { GUILayout.Width(50) }); + + m_valueToString = GUIHelper.TextArea(m_valueToString, new GUILayoutOption[] { GUIHelper.ExpandWidth(true) }); + + DrawApplyButton(); + + if (ModConfig.Instance.Bitwise_Support && m_canBitwiseOperate) + { + m_inBitwiseMode = GUILayout.Toggle(m_inBitwiseMode, "Bitwise?", new GUILayoutOption[0]); + } + + GUIHelper.Space(10); + + GUILayout.EndHorizontal(); + + if (ModConfig.Instance.Bitwise_Support && m_inBitwiseMode) + { + DrawBitwise(); + } + + GUILayout.EndVertical(); + } + + private void DrawApplyButton() + { + if (OwnerCacheObject.CanWrite) + { + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(60) })) + { + if (m_isBool) + { + OwnerCacheObject.SetValue(); + } + else + { + SetValueFromInput(); + } + } + } + } + + private void DrawBitwise() + { + if (OwnerCacheObject.CanWrite) + { + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("RHS:", new GUILayoutOption[] { GUILayout.Width(35) }); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + + if (GUILayout.Button("~", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = ~bit; + RefreshToString(); + } + } + + if (GUILayout.Button("<<", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value << bit; + RefreshToString(); + } + } + if (GUILayout.Button(">>", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value >> bit; + RefreshToString(); + } + } + if (GUILayout.Button("|", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value | bit; + RefreshToString(); + } + } + if (GUILayout.Button("&", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value & bit; + RefreshToString(); + } + } + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value ^ bit; + RefreshToString(); + } + } + + m_bitwiseOperatorInput = GUIHelper.TextField(m_bitwiseOperatorInput, new GUILayoutOption[] { GUILayout.Width(55) }); + + GUILayout.EndHorizontal(); + } + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label($"Binary:", new GUILayoutOption[] { GUILayout.Width(60) }); + m_binaryInput = GUIHelper.TextField(m_binaryInput, new GUILayoutOption[0]); + if (OwnerCacheObject.CanWrite) + { + if (GUILayout.Button("Apply", new GUILayoutOption[0])) + { + SetValueFromBinaryInput(); + } + } + GUILayout.EndHorizontal(); + } + + public void SetValueFromInput() + { + if (m_isString) + { + Value = m_valueToString; + } + else + { + try + { + Value = ParseMethod.Invoke(null, new object[] { m_valueToString }); + } + catch (Exception e) + { + ExplorerCore.Log("Exception parsing value: " + e.GetType() + ", " + e.Message); + } + } + + OwnerCacheObject.SetValue(); + RefreshToString(); + } + + private void SetValueFromBinaryInput() + { + try + { + var method = typeof(Convert).GetMethod($"To{ValueType.Name}", new Type[] { typeof(string), typeof(int) }); + Value = method.Invoke(null, new object[] { m_binaryInput, 2 }); + + OwnerCacheObject.SetValue(); + RefreshToString(); + } + catch (Exception e) + { + ExplorerCore.Log("Exception setting value: " + e.GetType() + ", " + e.Message); + } + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveQuaternion.cs b/src/UI/InteractiveValue/Struct/InteractiveQuaternion.cs new file mode 100644 index 0000000..10a5a96 --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveQuaternion.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveQuaternion : InteractiveValue, IExpandHeight + { + private string x = "0"; + private string y = "0"; + private string z = "0"; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value == null) return; + + var euler = ((Quaternion)Value).eulerAngles; + + x = euler.x.ToString(); + y = euler.y.ToString(); + z = euler.z.ToString(); + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + GUILayout.Label($"Quaternion: {((Quaternion)Value).eulerAngles.ToString()}", new GUILayoutOption[0]); + + if (OwnerCacheObject.CanWrite && IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) }); + x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) }); + y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) }); + z = GUIHelper.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + // draw set value button + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetValueFromInput(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + } + } + + private void SetValueFromInput() + { + if (float.TryParse(x, out float fX) + && float.TryParse(y, out float fY) + && float.TryParse(z, out float fZ)) + { + Value = Quaternion.Euler(new Vector3(fX, fY, fZ)); + OwnerCacheObject.SetValue(); + } + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveRect.cs b/src/UI/InteractiveValue/Struct/InteractiveRect.cs new file mode 100644 index 0000000..ba5e65f --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveRect.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Explorer.UI.Shared; +using Explorer.CacheObject; + +namespace Explorer.UI +{ + public class InteractiveRect : InteractiveValue, IExpandHeight + { + private string x = "0"; + private string y = "0"; + private string w = "0"; + private string h = "0"; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value == null) return; + + var rect = (Rect)Value; + + x = rect.x.ToString(); + y = rect.y.ToString(); + w = rect.width.ToString(); + h = rect.height.ToString(); + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + GUILayout.Label($"Rect: {((Rect)Value).ToString()}", new GUILayoutOption[0]); + + if (OwnerCacheObject.CanWrite && IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) }); + x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) }); + y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) }); + w = GUIHelper.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) }); + h = GUIHelper.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + // draw set value button + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetValueFromInput(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + } + } + + private void SetValueFromInput() + { + if (float.TryParse(x, out float fX) + && float.TryParse(y, out float fY) + && float.TryParse(w, out float fW) + && float.TryParse(h, out float fH)) + { + Value = new Rect(fX, fY, fW, fH); + OwnerCacheObject.SetValue(); + } + } + } +} diff --git a/src/UI/InteractiveValue/Struct/InteractiveVector.cs b/src/UI/InteractiveValue/Struct/InteractiveVector.cs new file mode 100644 index 0000000..872f12e --- /dev/null +++ b/src/UI/InteractiveValue/Struct/InteractiveVector.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Reflection; +using UnityEngine; + +namespace Explorer.UI +{ + public class InteractiveVector : InteractiveValue, IExpandHeight + { + public int VectorSize = 2; + + private string x = "0"; + private string y = "0"; + private string z = "0"; + private string w = "0"; + + //private MethodInfo m_toStringMethod; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void Init() + { + if (ValueType == null && Value != null) + { + ValueType = Value.GetType(); + } + + if (ValueType == typeof(Vector2)) + { + VectorSize = 2; + //m_toStringMethod = typeof(Vector2).GetMethod("ToString", new Type[0]); + } + else if (ValueType == typeof(Vector3)) + { + VectorSize = 3; + //m_toStringMethod = typeof(Vector3).GetMethod("ToString", new Type[0]); + } + else + { + VectorSize = 4; + //m_toStringMethod = typeof(Vector4).GetMethod("ToString", new Type[0]); + } + + base.Init(); + } + + public override void UpdateValue() + { + base.UpdateValue(); + + if (Value is Vector2 vec2) + { + x = vec2.x.ToString(); + y = vec2.y.ToString(); + } + else if (Value is Vector3 vec3) + { + x = vec3.x.ToString(); + y = vec3.y.ToString(); + z = vec3.z.ToString(); + } + else if (Value is Vector4 vec4) + { + x = vec4.x.ToString(); + y = vec4.y.ToString(); + z = vec4.z.ToString(); + w = vec4.w.ToString(); + } + } + + public override void DrawValue(Rect window, float width) + { + if (OwnerCacheObject.CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + GUILayout.Label($"Vector{VectorSize}: {(string)ToStringMethod.Invoke(Value, new object[0])}", new GUILayoutOption[0]); + + if (OwnerCacheObject.CanWrite && IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + // always draw x and y + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) }); + x = GUIHelper.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) }); + y = GUIHelper.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + + if (VectorSize > 2) + { + // draw z + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) }); + z = GUIHelper.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + } + if (VectorSize > 3) + { + // draw w + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) }); + w = GUIHelper.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) }); + GUILayout.EndHorizontal(); + } + + // draw set value button + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUIHelper.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetValueFromInput(); + } + GUILayout.EndHorizontal(); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + } + } + + private void SetValueFromInput() + { + if (float.TryParse(x, out float fX) + && float.TryParse(y, out float fY) + && float.TryParse(z, out float fZ) + && float.TryParse(w, out float fW)) + { + object vector = null; + + switch (VectorSize) + { + case 2: vector = new Vector2(fX, fY); break; + case 3: vector = new Vector3(fX, fY, fZ); break; + case 4: vector = new Vector4(fX, fY, fZ, fW); break; + } + + if (vector != null) + { + Value = vector; + OwnerCacheObject.SetValue(); + } + } + } + } +} diff --git a/src/UI/Main/BaseMainMenuPage.cs b/src/UI/Main/BaseMainMenuPage.cs new file mode 100644 index 0000000..00bf59d --- /dev/null +++ b/src/UI/Main/BaseMainMenuPage.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Explorer.UI.Main +{ + public abstract class BaseMainMenuPage + { + public virtual string Name { get; } + + public Vector2 scroll = Vector2.zero; + + public abstract void Init(); + + public abstract void DrawWindow(); + + public abstract void Update(); + } +} diff --git a/src/UI/Main/Console/AutoComplete.cs b/src/UI/Main/Console/AutoComplete.cs new file mode 100644 index 0000000..9d2e3b3 --- /dev/null +++ b/src/UI/Main/Console/AutoComplete.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEngine; + +// Thanks to ManlyMarco for this + +namespace Explorer.UI.Main +{ + public struct AutoComplete + { + public string Full => Prefix + Addition; + + public readonly string Prefix; + public readonly string Addition; + public readonly Contexts Context; + + public Color TextColor => Context == Contexts.Namespace + ? Color.gray + : Color.white; + + public AutoComplete(string addition, string prefix, Contexts type) + { + Addition = addition; + Prefix = prefix; + Context = type; + } + + public enum Contexts + { + Namespace, + Other + } + } + + public static class AutoCompleteHelpers + { + public static HashSet Namespaces => _namespaces ?? GetNamespaces(); + private static HashSet _namespaces; + + private static HashSet GetNamespaces() + { + var set = new HashSet( + AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(GetTypes) + .Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace)) + .Select(x => x.Namespace)); + + return _namespaces = set; + + IEnumerable GetTypes(Assembly asm) => asm.TryGetTypes(); + } + } +} diff --git a/src/UI/Main/Console/ScriptEvaluator.cs b/src/UI/Main/Console/ScriptEvaluator.cs new file mode 100644 index 0000000..eb827a6 --- /dev/null +++ b/src/UI/Main/Console/ScriptEvaluator.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Mono.CSharp; + +// Thanks to ManlyMarco for this + +namespace Explorer.UI.Main +{ + internal class ScriptEvaluator : Evaluator, IDisposable + { + private static readonly HashSet StdLib = new HashSet(StringComparer.InvariantCultureIgnoreCase) + { + "mscorlib", "System.Core", "System", "System.Xml" + }; + + private readonly TextWriter _logger; + + public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger)) + { + _logger = logger; + + ImportAppdomainAssemblies(ReferenceAssembly); + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; + } + + public void Dispose() + { + AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; + _logger.Dispose(); + } + + private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) + { + string name = args.LoadedAssembly.GetName().Name; + if (StdLib.Contains(name)) + return; + ReferenceAssembly(args.LoadedAssembly); + } + + private static CompilerContext BuildContext(TextWriter tw) + { + var reporter = new StreamReportPrinter(tw); + + var settings = new CompilerSettings + { + Version = LanguageVersion.Experimental, + GenerateDebugInfo = false, + StdLib = true, + Target = Target.Library, + WarningLevel = 0, + EnhancedWarnings = false + }; + + return new CompilerContext(settings, reporter); + } + + private static void ImportAppdomainAssemblies(Action import) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + string name = assembly.GetName().Name; + if (StdLib.Contains(name)) + continue; + import(assembly); + } + } + } +} diff --git a/src/UI/Main/Console/ScriptInteraction.cs b/src/UI/Main/Console/ScriptInteraction.cs new file mode 100644 index 0000000..5515669 --- /dev/null +++ b/src/UI/Main/Console/ScriptInteraction.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Explorer.UI.Inspectors; +using Mono.CSharp; +using UnityEngine; + +namespace Explorer.UI.Main +{ + public class ScriptInteraction : InteractiveBase + { + public static void Log(object message) + { + ExplorerCore.Log(message); + } + + public static object CurrentTarget() + { + if (!WindowManager.TabView) + { + ExplorerCore.Log("CurrentTarget() is only a valid method when in Tab View mode!"); + return null; + } + + return WindowManager.Windows.ElementAt(TabViewWindow.Instance.TargetTabID).Target; + } + + public static object[] AllTargets() + { + var list = new List(); + foreach (var window in WindowManager.Windows) + { + if (window.Target != null) + { + list.Add(window.Target); + } + } + return list.ToArray(); + } + + public static void Inspect(object obj) + { + WindowManager.InspectObject(obj, out bool _); + } + + public static void Inspect(Type type) + { + WindowManager.InspectStaticReflection(type); + } + + public static void Help() + { + ExplorerCore.Log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + ExplorerCore.Log(" C# Console Help "); + ExplorerCore.Log(""); + ExplorerCore.Log("The following helper methods are available:"); + ExplorerCore.Log(""); + ExplorerCore.Log("void Log(object message)"); + ExplorerCore.Log(" prints a message to the console window and debug log"); + ExplorerCore.Log(" usage: Log(\"hello world\");"); + ExplorerCore.Log(""); + ExplorerCore.Log("object CurrentTarget()"); + ExplorerCore.Log(" returns the target object of the current tab (in tab view mode only)"); + ExplorerCore.Log(" usage: var target = CurrentTarget();"); + ExplorerCore.Log(""); + ExplorerCore.Log("object[] AllTargets()"); + ExplorerCore.Log(" returns an object[] array containing all currently inspected objects"); + ExplorerCore.Log(" usage: var targets = AllTargets();"); + ExplorerCore.Log(""); + ExplorerCore.Log("void Inspect(object obj)"); + ExplorerCore.Log(" inspects the provided object in a new window."); + ExplorerCore.Log(" usage: Inspect(Camera.main);"); + ExplorerCore.Log(""); + ExplorerCore.Log("void Inspect(Type type)"); + ExplorerCore.Log(" attempts to inspect the provided type with static-only reflection."); + ExplorerCore.Log(" usage: Inspect(typeof(Camera));"); + } + } +} \ No newline at end of file diff --git a/src/UI/Main/ConsolePage.cs b/src/UI/Main/ConsolePage.cs new file mode 100644 index 0000000..445b566 --- /dev/null +++ b/src/UI/Main/ConsolePage.cs @@ -0,0 +1,371 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Mono.CSharp; +using System.Reflection; +using System.IO; +#if CPP +using UnhollowerRuntimeLib; +using TextEditor = Explorer.Unstrip.IMGUI.TextEditorUnstrip; +using Explorer.Unstrip.IMGUI; +#endif + +namespace Explorer.UI.Main +{ + public class ConsolePage : BaseMainMenuPage + { + public static ConsolePage Instance { get; private set; } + + public override string Name { get => "C# Console"; } + + private ScriptEvaluator m_evaluator; + + public const string INPUT_CONTROL_NAME = "consoleInput"; + private string m_input = ""; + private string m_prevInput = ""; + private string m_usingInput = ""; + + public static List AutoCompletes = new List(); + public static List UsingDirectives; + + private Vector2 inputAreaScroll; + private Vector2 autocompleteScroll; + public static TextEditor textEditor; + private bool shouldRefocus; + + public static GUIStyle AutocompleteStyle => autocompleteStyle ?? GetAutocompleteStyle(); + private static GUIStyle autocompleteStyle; + + public static readonly string[] DefaultUsing = new string[] + { + "System", + "UnityEngine", + "System.Linq", + "System.Collections", + "System.Collections.Generic", + "System.Reflection" + }; + + public override void Init() + { + Instance = this; + + try + { + m_input = @"// For a list of helper methods, execute the 'Help();' method. +// Enable the Console Window with your Mod Loader to see log output. + +Help();"; + ResetConsole(); + + foreach (var use in DefaultUsing) + { + AddUsing(use); + } + } + catch (Exception e) + { + ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}"); + MainMenu.SetCurrentPage(0); + MainMenu.Pages.Remove(this); + } + } + + public override void Update() { } + + public string AsmToUsing(string asm, bool richtext = false) + { + if (richtext) + { + return $"using {asm};"; + } + return $"using {asm};"; + } + + public void AddUsing(string asm) + { + if (!UsingDirectives.Contains(asm)) + { + UsingDirectives.Add(asm); + Evaluate(AsmToUsing(asm), true); + } + } + + public object Evaluate(string str, bool suppressWarning = false) + { + object ret = VoidType.Value; + + m_evaluator.Compile(str, out var compiled); + + try + { + if (compiled == null) + { + throw new Exception("Mono.Csharp Service was unable to compile the code provided."); + } + + compiled.Invoke(ref ret); + } + catch (Exception e) + { + if (!suppressWarning) + { + ExplorerCore.LogWarning(e.GetType() + ", " + e.Message); + } + } + + return ret; + } + + public void ResetConsole() + { + if (m_evaluator != null) + { + m_evaluator.Dispose(); + } + + m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) }; + + UsingDirectives = new List(); + } + + + public override void DrawWindow() + { + GUILayout.Label("C# Console", new GUILayoutOption[0]); + + GUI.skin.label.alignment = TextAnchor.UpperLeft; + + // SCRIPT INPUT + + GUILayout.Label("Enter code here as though it is a method body:", new GUILayoutOption[0]); + + inputAreaScroll = GUIHelper.BeginScrollView( + inputAreaScroll, + new GUILayoutOption[] { GUILayout.Height(250), GUIHelper.ExpandHeight(true) } + ); + + GUI.SetNextControlName(INPUT_CONTROL_NAME); + m_input = GUIHelper.TextArea(m_input, new GUILayoutOption[] { GUIHelper.ExpandHeight(true) }); + + GUIHelper.EndScrollView(); + + // EXECUTE BUTTON + + if (GUILayout.Button("Execute", new GUILayoutOption[0])) + { + try + { + m_input = m_input.Trim(); + + if (!string.IsNullOrEmpty(m_input)) + { + Evaluate(m_input); + + //var result = Evaluate(m_input); + + //if (result != null && !Equals(result, VoidType.Value)) + //{ + // ExplorerCore.Log("[Console Output]\r\n" + result.ToString()); + //} + } + } + catch (Exception e) + { + ExplorerCore.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace); + } + } + + // SUGGESTIONS + if (AutoCompletes.Count > 0) + { + autocompleteScroll = GUIHelper.BeginScrollView(autocompleteScroll, new GUILayoutOption[] { GUILayout.Height(150) }); + + var origSkin = GUI.skin.button; + GUI.skin.button = AutocompleteStyle; + + foreach (var autocomplete in AutoCompletes) + { + AutocompleteStyle.normal.textColor = autocomplete.TextColor; + if (GUILayout.Button(autocomplete.Full, new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 50) })) + { + UseAutocomplete(autocomplete.Addition); + break; + } + } + + GUI.skin.button = origSkin; + + GUIHelper.EndScrollView(); + } + + if (shouldRefocus) + { + GUI.FocusControl(INPUT_CONTROL_NAME); + shouldRefocus = false; + } + + // USING DIRECTIVES + + GUILayout.Label("Using directives:", new GUILayoutOption[0]); + + GUIHelper.BeginHorizontal(new GUILayoutOption[0]); + GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) }); + m_usingInput = GUIHelper.TextField(m_usingInput, new GUILayoutOption[] { GUILayout.Width(150) }); + if (GUILayout.Button("Add", new GUILayoutOption[] { GUILayout.Width(120) })) + { + AddUsing(m_usingInput); + } + if (GUILayout.Button("Clear All", new GUILayoutOption[] { GUILayout.Width(120) })) + { + ResetConsole(); + } + GUILayout.EndHorizontal(); + + foreach (var asm in UsingDirectives) + { + GUILayout.Label(AsmToUsing(asm, true), new GUILayoutOption[0]); + } + + CheckAutocomplete(); + } + + private void CheckAutocomplete() + { + // Temporary disabling this check in BepInEx Il2Cpp. +#if BIE +#if CPP +#else + if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME) + return; +#endif +#else + if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME) + return; +#endif + +#if CPP + //textEditor = GUIUtility.GetStateObject(Il2CppType.Of(), GUIUtility.keyboardControl).TryCast(); + textEditor = (TextEditor)GUIUtilityUnstrip.GetMonoStateObject(typeof(TextEditor), GUIUtility.keyboardControl); +#else + textEditor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); +#endif + + var input = m_input; + + if (!string.IsNullOrEmpty(input)) + { + try + { + var splitChars = new[] { ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&' }; + + // Credit ManlyMarco + // Separate input into parts, grab only the part with cursor in it + var cursorIndex = textEditor.cursorIndex; + var start = cursorIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, cursorIndex - 1) + 1; + var end = cursorIndex <= 0 ? input.Length : input.IndexOfAny(splitChars, cursorIndex - 1); + if (end < 0 || end < start) end = input.Length; + input = input.Substring(start, end - start); + } + catch (ArgumentException) { } + + if (!string.IsNullOrEmpty(input) && input != m_prevInput) + { + GetAutocompletes(input); + } + } + else + { + ClearAutocompletes(); + } + + m_prevInput = input; + } + + private void ClearAutocompletes() + { + if (AutoCompletes.Any()) + { + AutoCompletes.Clear(); + shouldRefocus = true; + } + } + + private void UseAutocomplete(string suggestion) + { + int cursorIndex = textEditor.cursorIndex; + m_input = m_input.Insert(cursorIndex, suggestion); + + ClearAutocompletes(); + shouldRefocus = true; + } + + private void GetAutocompletes(string input) + { + try + { + //ExplorerCore.Log("Fetching suggestions for input " + input); + + // Credit ManylMarco + AutoCompletes.Clear(); + var completions = m_evaluator.GetCompletions(input, out string prefix); + if (completions != null) + { + if (prefix == null) + prefix = input; + + AutoCompletes.AddRange(completions + .Where(x => !string.IsNullOrEmpty(x)) + .Select(x => new AutoComplete(x, prefix, AutoComplete.Contexts.Other)) + ); + } + + var trimmed = input.Trim(); + if (trimmed.StartsWith("using")) + trimmed = trimmed.Remove(0, 5).Trim(); + + var namespaces = AutoCompleteHelpers.Namespaces + .Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length) + .Select(x => new AutoComplete( + x.Substring(trimmed.Length), + x.Substring(0, trimmed.Length), + AutoComplete.Contexts.Namespace)); + + AutoCompletes.AddRange(namespaces); + + shouldRefocus = true; + } + catch (Exception ex) + { + ExplorerCore.Log("C# Console error:\r\n" + ex); + ClearAutocompletes(); + } + } + + // Credit ManlyMarco + private static GUIStyle GetAutocompleteStyle() + { + var style = new GUIStyle + { + border = new RectOffset(), + margin = new RectOffset(), + padding = new RectOffset(), + hover = { background = Texture2D.whiteTexture, textColor = Color.black }, + normal = { background = null }, + focused = { background = Texture2D.whiteTexture, textColor = Color.black }, + active = { background = Texture2D.whiteTexture, textColor = Color.black }, + alignment = TextAnchor.MiddleLeft + }; + + return autocompleteStyle = style; + } + + private class VoidType + { + public static readonly VoidType Value = new VoidType(); + private VoidType() { } + } + } +} diff --git a/src/UI/Main/MainMenu.cs b/src/UI/Main/MainMenu.cs deleted file mode 100644 index 55bcbaf..0000000 --- a/src/UI/Main/MainMenu.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using ExplorerBeta.UI; -using UnityEngine; -using UnityEngine.UI; -using UnityEngine.EventSystems; -using ExplorerBeta.UI.Shared; - -namespace ExplorerBeta.UI.Main -{ - // TODO REMAKE THIS - - public class MainMenu - { - public static MainMenu Instance { get; set; } - - public PanelDragger Dragger { get; private set; } - public GameObject MainPanel { get; private set; } - - public MainMenu() - { - if (Instance != null) - { - ExplorerCore.LogWarning("An instance of MainMenu already exists, cannot create another!"); - return; - } - - Instance = this; - - MainPanel = CreateBasePanel("MainMenu"); - CreateTitleBar(); - CreateNavbar(); - CreateViewArea(); - } - - public void Update() - { - - } - - private void TestButtonCallback() - { - //if (EventSystem.current != EventSys) - // return; - - var go = EventSystem.current.currentSelectedGameObject; - if (!go) - return; - - var name = go.name; - if (go.GetComponentInChildren() is Text text) - { - name = text.text; - } - ExplorerCore.Log($"{Time.time} | Pressed {name ?? "null"}"); - - if (name == "X") - { - ExplorerCore.ShowMenu = false; - } - } - - #region UI Generator - - public virtual GameObject CreateBasePanel(string name) - { - var basePanel = UIFactory.CreatePanel(UIManager.CanvasRoot.gameObject, name); - var panelRect = basePanel.GetComponent(); - panelRect.anchorMin = new Vector2(0.327f, 0.0967f); - panelRect.anchorMax = new Vector2(0.672f, 0.904f); - panelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 620f); - panelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 800f); - - return basePanel; - } - - private void CreateTitleBar() - { - // Make the horizontal group for window title area - var titleGroup = UIFactory.CreateHorizontalGroup(MainPanel); - var titleRect = titleGroup.GetComponent(); - titleRect.anchorMin = new Vector2(0.005f, 0.96f); - titleRect.anchorMax = new Vector2(0.995f, 0.994f); - titleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 613); - titleRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 30); - - var group = titleGroup.GetComponent(); - group.childControlWidth = true; - //group.childScaleWidth = true; - group.childForceExpandHeight = true; - group.childForceExpandWidth = true; - - // Create window title - var titleLabel = UIFactory.CreateLabel(titleGroup, TextAnchor.MiddleCenter); - var labelText = titleLabel.GetComponent(); - labelText.text = ExplorerCore.NAME; - labelText.fontSize = 15; - var labelRect = labelText.GetComponent(); - labelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 575); - labelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 25); - - // Add drag handler (on Title label) - Dragger = new PanelDragger(titleLabel.transform.TryCast(), - MainPanel.GetComponent()); - - // Create X Button - var exitBtnObj = UIFactory.CreateButton(titleGroup); - var exitBtn = exitBtnObj.GetComponentInChildren