* Added support for Enums with [Flags] attribute (can set each flag individually)
* Added support for easier bitwise operations on ints (or any primitive assignable to int), and viewing the int as binary. This is intended for things like `Camera.cullingMask`, etc.
* Fixed an issue with Enums that contain duplicate values, for example `CameraClearFlags` (has duplicate values for 2).
This commit is contained in:
sinaioutlander 2020-09-23 19:19:29 +10:00
parent 2006a9ea76
commit f203ae37fc
8 changed files with 324 additions and 32 deletions

View File

@ -121,11 +121,6 @@ namespace Explorer
return null;
}
// This is pretty ugly, could probably make a cleaner implementation.
// However, the only cleaner ways I can think of are slower and probably not worth it.
// Note: the order is somewhat important.
if (mi != null)
{
holder = new CacheMethod();
@ -140,7 +135,14 @@ namespace Explorer
}
else if (valueType.IsEnum)
{
holder = new CacheEnum();
if (valueType.GetCustomAttributes(typeof(FlagsAttribute), false) is object[] attributes && attributes.Length > 0)
{
holder = new CacheEnumFlags();
}
else
{
holder = new CacheEnum();
}
}
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{

View File

@ -23,7 +23,18 @@ namespace Explorer
if (ValueType != null)
{
EnumNames = Enum.GetNames(ValueType);
// using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags)
var values = Enum.GetValues(ValueType);
var list = new List<string>();
foreach (var value in values)
{
var v = value.ToString();
if (list.Contains(v)) continue;
list.Add(v);
}
EnumNames = list.ToArray();
}
else
{
@ -37,12 +48,12 @@ namespace Explorer
{
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
{
SetEnum(ref Value, -1);
SetEnum(-1);
SetValue();
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
{
SetEnum(ref Value, 1);
SetEnum(1);
SetValue();
}
}
@ -50,15 +61,15 @@ namespace Explorer
GUILayout.Label(Value.ToString() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", null);
}
public void SetEnum(ref object value, int change)
public void SetEnum(int change)
{
var names = EnumNames.ToList();
int newindex = names.IndexOf(value.ToString()) + change;
int newindex = names.IndexOf(Value.ToString()) + change;
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
if (newindex >= 0 && newindex < names.Count)
{
value = Enum.Parse(ValueType, names[newindex]);
Value = Enum.Parse(ValueType, EnumNames[newindex]);
}
}
}

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheEnumFlags : CacheObjectBase, IExpandHeight
{
public string[] EnumNames = new string[0];
public bool[] m_enabledFlags = new bool[0];
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void Init()
{
base.Init();
if (ValueType == null && Value != null)
{
ValueType = Value.GetType();
}
if (ValueType != null)
{
EnumNames = Enum.GetNames(ValueType);
m_enabledFlags = new bool[EnumNames.Length];
UpdateValue();
}
else
{
ReflectionException = "Unknown, could not get Enum names.";
}
}
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);
SetValue();
}
public override void UpdateValue()
{
base.UpdateValue();
try
{
var enabledNames = Value.ToString().Split(',').Select(it => it.Trim());
for (int i = 0; i < EnumNames.Length; i++)
{
m_enabledFlags[i] = enabledNames.Contains(EnumNames[i]);
}
}
catch (Exception e)
{
MelonLogger.Log(e.ToString());
}
}
public override void DrawValue(Rect window, float width)
{
if (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() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", null);
if (IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = CalcWhitespace(window);
for (int i = 0; i < EnumNames.Length; i++)
{
GUILayout.BeginHorizontal(null);
GUIUnstrip.Space(whitespace);
m_enabledFlags[i] = GUILayout.Toggle(m_enabledFlags[i], EnumNames[i], null);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(null);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetFlagsFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
}
}

View File

@ -1,7 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using MelonLoader;
using UnhollowerRuntimeLib;
using UnityEngine;
namespace Explorer
@ -16,6 +18,12 @@ namespace Explorer
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_bitwiseToString;
//private BitArray m_bitMask; // not needed I think
public override void Init()
{
if (ValueType == null)
@ -37,17 +45,31 @@ namespace Explorer
{
m_isBool = true;
}
m_canBitwiseOperate = typeof(int).IsAssignableFrom(ValueType);
}
public override void UpdateValue()
{
base.UpdateValue();
RefreshToString();
}
public void RefreshToString()
{
m_valueToString = Value?.ToString();
if (m_inBitwiseMode)
{
var _int = (int)Value;
m_bitwiseToString = Convert.ToString(_int, toBase: 2);
}
}
public override void DrawValue(Rect window, float width)
{
// bool uses Toggle
if (m_isBool)
{
var b = (bool)Value;
@ -65,35 +87,127 @@ namespace Explorer
{
GUILayout.Label(label, null);
}
return;
}
// all other non-bool values use TextField
GUILayout.BeginVertical(null);
GUILayout.BeginHorizontal(null);
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 310f;
if (CanWrite) maxwidth -= 60;
if (dynSize > maxwidth)
{
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
}
else
{
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) });
}
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 310f;
if (CanWrite) maxwidth -= 60;
if (dynSize > maxwidth)
if (CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
}
else
{
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) });
SetValueFromInput(m_valueToString);
RefreshToString();
}
}
if (m_canBitwiseOperate)
{
bool orig = m_inBitwiseMode;
m_inBitwiseMode = GUILayout.Toggle(m_inBitwiseMode, "Bitwise?", null);
if (orig != m_inBitwiseMode)
{
RefreshToString();
}
}
GUIUnstrip.Space(10);
GUILayout.EndHorizontal();
if (m_inBitwiseMode)
{
if (CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
GUILayout.BeginHorizontal(null);
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) }))
{
SetValueFromInput(m_valueToString);
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 = GUILayout.TextField(m_bitwiseOperatorInput, new GUILayoutOption[] { GUILayout.Width(55) });
GUILayout.EndHorizontal();
}
GUIUnstrip.Space(10);
GUILayout.BeginHorizontal(null);
GUILayout.Label($"<color=cyan>Binary:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
GUILayout.TextField(m_bitwiseToString, null);
GUILayout.EndHorizontal();
}
GUILayout.EndVertical();
}
public void SetValueFromInput(string valueString)
@ -113,6 +227,16 @@ namespace Explorer
try
{
Value = ParseMethod.Invoke(null, new object[] { valueString });
//if (m_inBitwiseMode)
//{
// var method = typeof(Convert).GetMethod($"To{ValueType.Name}", new Type[] { typeof(string), typeof(int) });
// Value = method.Invoke(null, new object[] { valueString, 2 });
//}
//else
//{
// Value = ParseMethod.Invoke(null, new object[] { valueString });
//}
}
catch (Exception e)
{

View File

@ -6,7 +6,7 @@ namespace Explorer
public class CppExplorer : MelonMod
{
public const string NAME = "CppExplorer";
public const string VERSION = "1.7.4";
public const string VERSION = "1.7.5";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.cppexplorer";

View File

@ -83,6 +83,7 @@
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
<Compile Include="CachedObjects\Object\CacheGameObject.cs" />
<Compile Include="CachedObjects\Object\CacheList.cs" />
<Compile Include="CachedObjects\Struct\CacheEnumFlags.cs" />
<Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
<Compile Include="CachedObjects\Other\CacheOther.cs" />
<Compile Include="CachedObjects\Other\CacheMethod.cs" />

View File

@ -568,7 +568,7 @@ namespace Explorer
GUILayout.EndHorizontal();
bool b = m_localContext;
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "orange") + ">Use local transform values?</color>", null);
if (b != m_localContext)
{
m_localContext = b;
@ -609,7 +609,7 @@ namespace Explorer
private void BoolToggle(ref bool value, string message)
{
string lbl = "<color=";
lbl += value ? "lime" : "red";
lbl += value ? "lime" : "orange";
lbl += $">{message}</color>";
value = GUILayout.Toggle(value, lbl, null);

View File

@ -2,6 +2,9 @@
using System.Collections.Generic;
using System;
using UnityEngine;
using System.Reflection;
using System.Collections.Specialized;
using MelonLoader;
// used to test multiple generic constraints
public class TestGeneric : IComparable<string>
@ -11,10 +14,33 @@ public class TestGeneric : IComparable<string>
public int CompareTo(string other) => throw new NotImplementedException();
}
[Flags]
public enum TestFlags
{
Red,
Green,
Blue
}
// test non-flags weird enum
public enum WeirdEnum
{
First = 1,
Second,
Third = 2,
Fourth,
Fifth
}
namespace Explorer.Tests
{
public class TestClass
{
public static TestFlags testFlags = TestFlags.Blue | TestFlags.Green;
public static WeirdEnum testWeird = WeirdEnum.First;
public static int testBitmask;
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
private static TestClass m_instance;
@ -24,13 +50,14 @@ namespace Explorer.Tests
ILHashSetTest.Add("1");
ILHashSetTest.Add("2");
ILHashSetTest.Add("3");
testBitmask = 1 | 2;
}
public static int StaticProperty => 5;
public static int StaticField = 5;
public int NonStaticField;
public static string TestGeneric<C, T>(string arg0) where C : Component where T : TestGeneric, IComparable<string>
{
return $"C: '{typeof(C).FullName}', T: '{typeof(T).FullName}', arg0: '{arg0}'";