From c04a864b74b9c0922a35fb27678227e30e2e4dea Mon Sep 17 00:00:00 2001 From: Sinai Date: Sun, 9 May 2021 01:25:26 +1000 Subject: [PATCH] Made ParseUtility helper to simplify and improve parsing of various input types --- src/Core/Reflection/Extensions.cs | 24 +- src/Core/Reflection/Il2CppReflection.cs | 2 +- src/Core/Reflection/ReflectionUtility.cs | 2 - src/Core/Utility/MiscUtility.cs | 4 +- src/Core/Utility/ParseUtility.cs | 390 ++++++++++++++++++++ src/UI/CacheObject/CacheMember.cs | 8 +- src/UI/CacheObject/CacheObjectBase.cs | 66 ++-- src/UI/CacheObject/Views/CacheObjectCell.cs | 14 +- src/UI/CacheObject/Views/EvaluateWidget.cs | 25 +- src/UI/IValues/InteractiveValueStruct.cs | 64 ++-- src/UI/Panels/UIPanel.cs | 18 +- src/UnityExplorer.csproj | 1 + 12 files changed, 512 insertions(+), 106 deletions(-) create mode 100644 src/Core/Utility/ParseUtility.cs diff --git a/src/Core/Reflection/Extensions.cs b/src/Core/Reflection/Extensions.cs index b451318..7ba0753 100644 --- a/src/Core/Reflection/Extensions.cs +++ b/src/Core/Reflection/Extensions.cs @@ -92,17 +92,23 @@ namespace UnityExplorer public static string ReflectionExToString(this Exception e, bool innerMost = true) { if (innerMost) - { - while (e.InnerException != null) - { - if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException) - break; - - e = e.InnerException; - } - } + e.GetInnerMostException(); return $"{e.GetType()}: {e.Message}"; } + + public static Exception GetInnerMostException(this Exception e) + { + while (e.InnerException != null) + { +#if CPP + if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException) + break; +#endif + e = e.InnerException; + } + + return e; + } } } diff --git a/src/Core/Reflection/Il2CppReflection.cs b/src/Core/Reflection/Il2CppReflection.cs index f5ceaf6..41513e8 100644 --- a/src/Core/Reflection/Il2CppReflection.cs +++ b/src/Core/Reflection/Il2CppReflection.cs @@ -275,7 +275,7 @@ namespace UnityExplorer var name = toType.AssemblyQualifiedName; - if (!unboxMethods.ContainsKey(toType.AssemblyQualifiedName)) + if (!unboxMethods.ContainsKey(name)) { unboxMethods.Add(name, typeof(Il2CppObjectBase) .GetMethod("Unbox") diff --git a/src/Core/Reflection/ReflectionUtility.cs b/src/Core/Reflection/ReflectionUtility.cs index 8fa336c..7dbdf8e 100644 --- a/src/Core/Reflection/ReflectionUtility.cs +++ b/src/Core/Reflection/ReflectionUtility.cs @@ -182,7 +182,6 @@ namespace UnityExplorer #endregion - #region Type and Generic Parameter implementation cache // cache for GetImplementationsOf @@ -302,7 +301,6 @@ namespace UnityExplorer #endregion - #region Internal MemberInfo Cache internal static Dictionary> fieldInfos = new Dictionary>(); diff --git a/src/Core/Utility/MiscUtility.cs b/src/Core/Utility/MiscUtility.cs index 4a939fc..94d20d3 100644 --- a/src/Core/Utility/MiscUtility.cs +++ b/src/Core/Utility/MiscUtility.cs @@ -8,14 +8,12 @@ namespace UnityExplorer { public static class MiscUtility { - private static CultureInfo _enCulture = new CultureInfo("en-US"); - /// /// Check if a string contains another string, case-insensitive. /// public static bool ContainsIgnoreCase(this string _this, string s) { - return _enCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0; + return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0; } /// diff --git a/src/Core/Utility/ParseUtility.cs b/src/Core/Utility/ParseUtility.cs new file mode 100644 index 0000000..88100cc --- /dev/null +++ b/src/Core/Utility/ParseUtility.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace UnityExplorer +{ + public static class ParseUtility + { + public static CultureInfo en_US = new CultureInfo("en-US"); + + private static readonly HashSet nonPrimitiveTypes = new HashSet + { + typeof(string), + typeof(decimal), + typeof(DateTime), + }; + + public static bool CanParse(Type type) + { + if (string.IsNullOrEmpty(type.FullName)) + return false; + return type.IsPrimitive || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName); + } + + public static bool TryParse(string input, Type type, out object obj, out Exception parseException) + { + obj = null; + parseException = null; + + if (type == null) + return false; + + if (type == typeof(string)) + { + obj = input; + return true; + } + + try + { + if (customTypes.ContainsKey(type.FullName)) + { + obj = customTypes[type.FullName].Invoke(input); + } + else + { + obj = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs) + .Invoke(null, new object[] { input }); + } + + return true; + } + catch (Exception ex) + { + ex = ex.GetInnerMostException(); + parseException = ex; + } + + return false; + } + + public static string ToStringForInput(object obj, Type type) + { + if (type == null || obj == null) + return null; + + if (type == typeof(string)) + return obj as string; + + try + { + if (customTypes.ContainsKey(type.FullName)) + { + return customTypesToString[type.FullName].Invoke(obj); + } + else + { + if (obj is IntPtr ptr) + return ptr.ToString(); + else if (obj is UIntPtr uPtr) + return uPtr.ToString(); + else + return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(IFormatProvider) }) + .Invoke(obj, new object[] { en_US }) + as string; + } + } + catch (Exception ex) + { + ExplorerCore.LogWarning($"Exception formatting object for input: {ex}"); + return null; + } + } + + private static readonly Dictionary typeInputExamples = new Dictionary(); + + public static string GetExampleInput(Type type) + { + if (!typeInputExamples.ContainsKey(type.AssemblyQualifiedName)) + { + try + { + var instance = Activator.CreateInstance(type); + typeInputExamples.Add(type.AssemblyQualifiedName, ToStringForInput(instance, type)); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("Exception generating default instance for example input for '" + type.FullName + "'"); + ExplorerCore.Log(ex); + return ""; + } + } + + return typeInputExamples[type.AssemblyQualifiedName]; + } + + #region Custom parse methods + + internal delegate object ParseMethod(string input); + + private static readonly Dictionary customTypes = new Dictionary + { + { typeof(Vector2).FullName, TryParseVector2 }, + { typeof(Vector3).FullName, TryParseVector3 }, + { typeof(Vector4).FullName, TryParseVector4 }, + { typeof(Quaternion).FullName, TryParseQuaternion }, + { typeof(Rect).FullName, TryParseRect }, + { typeof(Color).FullName, TryParseColor }, + { typeof(Color32).FullName, TryParseColor32 }, + { typeof(LayerMask).FullName, TryParseLayerMask }, + }; + + internal delegate string ToStringMethod(object obj); + + private static readonly Dictionary customTypesToString = new Dictionary + { + { typeof(Vector2).FullName, Vector2ToString }, + { typeof(Vector3).FullName, Vector3ToString }, + { typeof(Vector4).FullName, Vector4ToString }, + { typeof(Quaternion).FullName, QuaternionToString }, + { typeof(Rect).FullName, RectToString }, + { typeof(Color).FullName, ColorToString }, + { typeof(Color32).FullName, Color32ToString }, + { typeof(LayerMask).FullName, LayerMaskToString }, + }; + + // Vector2 + + public static object TryParseVector2(string input) + { + Vector2 vector = default; + + var split = input.Split(','); + + vector.x = float.Parse(split[0].Trim(), en_US); + vector.y = float.Parse(split[1].Trim(), en_US); + + return vector; + } + + public static string Vector2ToString(object obj) + { + if (!(obj is Vector2 vector)) + return null; + + return string.Format(en_US, "{0}, {1}", new object[] + { + vector.x, + vector.y + }); + } + + // Vector3 + + public static object TryParseVector3(string input) + { + Vector3 vector = default; + + var split = input.Split(','); + + vector.x = float.Parse(split[0].Trim(), en_US); + vector.y = float.Parse(split[1].Trim(), en_US); + vector.z = float.Parse(split[2].Trim(), en_US); + + return vector; + } + + public static string Vector3ToString(object obj) + { + if (!(obj is Vector3 vector)) + return null; + + return string.Format(en_US, "{0}, {1}, {2}", new object[] + { + vector.x, + vector.y, + vector.z + }); + } + + // Vector4 + + public static object TryParseVector4(string input) + { + Vector4 vector = default; + + var split = input.Split(','); + + vector.x = float.Parse(split[0].Trim(), en_US); + vector.y = float.Parse(split[1].Trim(), en_US); + vector.z = float.Parse(split[2].Trim(), en_US); + vector.w = float.Parse(split[3].Trim(), en_US); + + return vector; + } + + public static string Vector4ToString(object obj) + { + if (!(obj is Vector4 vector)) + return null; + + return string.Format(en_US, "{0}, {1}, {2}, {3}", new object[] + { + vector.x, + vector.y, + vector.z, + vector.w + }); + } + + // Quaternion + + public static object TryParseQuaternion(string input) + { + Vector3 vector = default; + + var split = input.Split(','); + + if (split.Length == 4) + { + Quaternion quat = default; + quat.x = float.Parse(split[0].Trim(), en_US); + quat.y = float.Parse(split[1].Trim(), en_US); + quat.z = float.Parse(split[2].Trim(), en_US); + quat.w = float.Parse(split[3].Trim(), en_US); + return quat; + } + else + { + vector.x = float.Parse(split[0].Trim(), en_US); + vector.y = float.Parse(split[1].Trim(), en_US); + vector.z = float.Parse(split[2].Trim(), en_US); + return Quaternion.Euler(vector); + } + } + + public static string QuaternionToString(object obj) + { + if (!(obj is Quaternion quaternion)) + return null; + + Vector3 vector = Quaternion.ToEulerAngles(quaternion); + + return string.Format(en_US, "{0}, {1}, {2}", new object[] + { + vector.x, + vector.y, + vector.z, + }); + } + + // Rect + + public static object TryParseRect(string input) + { + Rect rect = default; + + var split = input.Split(','); + + rect.x = float.Parse(split[0].Trim(), en_US); + rect.y = float.Parse(split[1].Trim(), en_US); + rect.width = float.Parse(split[2].Trim(), en_US); + rect.height = float.Parse(split[3].Trim(), en_US); + + return rect; + } + + public static string RectToString(object obj) + { + if (!(obj is Rect rect)) + return null; + + return string.Format(en_US, "{0}, {1}, {2}, {3}", new object[] + { + rect.x, + rect.y, + rect.width, + rect.height + }); + } + + // Color + + public static object TryParseColor(string input) + { + Color color = default; + + var split = input.Split(','); + + color.r = float.Parse(split[0].Trim(), en_US); + color.g = float.Parse(split[1].Trim(), en_US); + color.b = float.Parse(split[2].Trim(), en_US); + if (split.Length > 3) + color.a = float.Parse(split[3].Trim(), en_US); + else + color.a = 1; + + return color; + } + + public static string ColorToString(object obj) + { + if (!(obj is Color color)) + return null; + + return string.Format(en_US, "{0}, {1}, {2}, {3}", new object[] + { + color.r, + color.g, + color.b, + color.a + }); + } + + // Color32 + + public static object TryParseColor32(string input) + { + Color32 color = default; + + var split = input.Split(','); + + color.r = byte.Parse(split[0].Trim(), en_US); + color.g = byte.Parse(split[1].Trim(), en_US); + color.b = byte.Parse(split[2].Trim(), en_US); + if (split.Length > 3) + color.a = byte.Parse(split[3].Trim(), en_US); + else + color.a = 255; + + return color; + } + + public static string Color32ToString(object obj) + { + if (!(obj is Color32 color)) + return null; + + return string.Format(en_US, "{0}, {1}, {2}, {3}", new object[] + { + color.r, + color.g, + color.b, + color.a + }); + } + + // Layermask (Int32) + + public static object TryParseLayerMask(string input) + { + return (LayerMask)int.Parse(input); + } + + public static string LayerMaskToString(object obj) + { + if (!(obj is LayerMask mask)) + return null; + + return mask.ToString(); + } + + #endregion + } +} diff --git a/src/UI/CacheObject/CacheMember.cs b/src/UI/CacheObject/CacheMember.cs index aadccf7..d73ef8e 100644 --- a/src/UI/CacheObject/CacheMember.cs +++ b/src/UI/CacheObject/CacheMember.cs @@ -171,7 +171,7 @@ namespace UnityExplorer.UI.CacheObject #region Cache Member Util - public static bool CanProcessArgs(ParameterInfo[] parameters) + public static bool CanParseArgs(ParameterInfo[] parameters) { foreach (var param in parameters) { @@ -180,7 +180,7 @@ namespace UnityExplorer.UI.CacheObject if (pType.IsByRef && pType.HasElementType) pType = pType.GetElementType(); - if (pType != null && (pType.IsPrimitive || pType == typeof(string))) + if (pType != null && ParseUtility.CanParse(pType)) continue; else return false; @@ -260,7 +260,7 @@ namespace UnityExplorer.UI.CacheObject return; var args = mi.GetParameters(); - if (!CanProcessArgs(args)) + if (!CanParseArgs(args)) return; sig += AppendArgsToSig(args); @@ -277,7 +277,7 @@ namespace UnityExplorer.UI.CacheObject var pi = member as PropertyInfo; var args = pi.GetIndexParameters(); - if (!CanProcessArgs(args)) + if (!CanParseArgs(args)) return; if (!pi.CanRead && pi.CanWrite) diff --git a/src/UI/CacheObject/CacheObjectBase.cs b/src/UI/CacheObject/CacheObjectBase.cs index fecfc5a..0428942 100644 --- a/src/UI/CacheObject/CacheObjectBase.cs +++ b/src/UI/CacheObject/CacheObjectBase.cs @@ -39,6 +39,9 @@ namespace UnityExplorer.UI.CacheObject public Type FallbackType { get; protected set; } public bool LastValueWasNull { get; private set; } + public ValueState State = ValueState.NotEvaluated; + public Type LastValueType; + public InteractiveValue IValue { get; private set; } public Type CurrentIValueType { get; private set; } public bool SubContentShowWanted { get; private set; } @@ -58,12 +61,6 @@ namespace UnityExplorer.UI.CacheObject this.ValueLabelText = GetValueLabel(); } - // internals - - // private static readonly Dictionary numberParseMethods = new Dictionary(); - - public ValueState State = ValueState.NotEvaluated; - protected const string NOT_YET_EVAL = "Not yet evaluated"; public virtual void ReleasePooledObjects() @@ -127,6 +124,7 @@ namespace UnityExplorer.UI.CacheObject if (HadException) { LastValueWasNull = true; + LastValueType = FallbackType; State = ValueState.Exception; } else if (Value.IsNullOrDestroyed()) @@ -158,6 +156,10 @@ namespace UnityExplorer.UI.CacheObject public ValueState GetStateForType(Type type) { + if (LastValueType == type) + return State; + + LastValueType = type; if (type == typeof(bool)) return ValueState.Boolean; else if (type.IsPrimitive || type == typeof(decimal)) @@ -184,17 +186,24 @@ namespace UnityExplorer.UI.CacheObject switch (State) { + case ValueState.NotEvaluated: + return $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})"; + + case ValueState.Exception: + return $"{LastException.ReflectionExToString()}"; + // bool and number dont want the label for the value at all case ValueState.Boolean: case ValueState.Number: return null; - case ValueState.NotEvaluated: - return $"{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})"; - - case ValueState.Exception: - return $"{LastException.ReflectionExToString()}"; + // and valuestruct also doesnt want it if we can parse it + case ValueState.ValueStruct: + if (ParseUtility.CanParse(LastValueType)) + return null; + break; + // string wants it trimmed to max 200 chars case ValueState.String: if (!LastValueWasNull) { @@ -204,7 +213,8 @@ namespace UnityExplorer.UI.CacheObject return $"\"{s}\""; } break; - + + // try to prefix the count of the collection for lists and dicts case ValueState.Collection: if (!LastValueWasNull) { @@ -256,7 +266,6 @@ namespace UnityExplorer.UI.CacheObject switch (State) { case ValueState.Exception: - //case ValueState.NullValue: SetValueState(cell, ValueStateArgs.Default); break; case ValueState.Boolean: @@ -274,10 +283,15 @@ namespace UnityExplorer.UI.CacheObject case ValueState.Enum: SetValueState(cell, new ValueStateArgs(true, subContentButtonActive: CanWrite)); break; + case ValueState.Color: + case ValueState.ValueStruct: + if (ParseUtility.CanParse(LastValueType)) + SetValueState(cell, new ValueStateArgs(false, false, null, true, false, true, CanWrite, true, true)); + else + SetValueState(cell, new ValueStateArgs(true, inspectActive: true, subContentButtonActive: true)); + break; case ValueState.Collection: case ValueState.Dictionary: - case ValueState.ValueStruct: - case ValueState.Color: SetValueState(cell, new ValueStateArgs(true, inspectActive: !LastValueWasNull, subContentButtonActive: !LastValueWasNull)); break; case ValueState.Unsupported: @@ -303,7 +317,7 @@ namespace UnityExplorer.UI.CacheObject // Type label (for primitives) cell.TypeLabel.gameObject.SetActive(args.typeLabelActive); if (args.typeLabelActive) - cell.TypeLabel.text = SignatureHighlighter.Parse(Value.GetActualType(), false); + cell.TypeLabel.text = SignatureHighlighter.Parse(LastValueType, false); // toggle for bools cell.Toggle.gameObject.SetActive(args.toggleActive); @@ -318,7 +332,7 @@ namespace UnityExplorer.UI.CacheObject cell.InputField.UIRoot.SetActive(args.inputActive); if (args.inputActive) { - cell.InputField.Text = Value.ToString(); + cell.InputField.Text = ParseUtility.ToStringForInput(Value, LastValueType); cell.InputField.InputField.readOnly = !CanWrite; } @@ -342,21 +356,15 @@ namespace UnityExplorer.UI.CacheObject SetUserValue(this.CellView.Toggle.isOn); else { - try + if (ParseUtility.TryParse(CellView.InputField.Text, LastValueType, out object value, out Exception ex)) { - var type = Value.GetType(); - - var val = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs) - .Invoke(null, new object[] { CellView.InputField.Text }); - - SetUserValue(val); + SetUserValue(value); } - catch (ArgumentException) { } // ignore bad user input - catch (FormatException) { } - catch (OverflowException) { } - catch (Exception ex) + else { - ExplorerCore.LogWarning("CacheObjectBase OnCellApplyClicked (number): " + ex.ToString()); + ExplorerCore.LogWarning("Unable to parse input!"); + if (ex != null) + ExplorerCore.Log(ex.ReflectionExToString()); } } diff --git a/src/UI/CacheObject/Views/CacheObjectCell.cs b/src/UI/CacheObject/Views/CacheObjectCell.cs index dca03c3..23bb203 100644 --- a/src/UI/CacheObject/Views/CacheObjectCell.cs +++ b/src/UI/CacheObject/Views/CacheObjectCell.cs @@ -155,16 +155,18 @@ namespace UnityExplorer.UI.CacheObject.Views InputField = UIFactory.CreateInputField(rightHoriGroup, "InputField", "..."); UIFactory.SetLayoutElement(InputField.UIRoot, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); - // Inspect and apply buttons + // Apply - InspectButton = UIFactory.CreateButton(rightHoriGroup, "InspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f)); - UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25); - InspectButton.OnClick += InspectClicked; - - ApplyButton = UIFactory.CreateButton(rightHoriGroup, "ApplyButton", "Apply", new Color(0.15f, 0.15f, 0.15f)); + ApplyButton = UIFactory.CreateButton(rightHoriGroup, "ApplyButton", "Apply", new Color(0.15f, 0.19f, 0.15f)); UIFactory.SetLayoutElement(ApplyButton.Button.gameObject, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); ApplyButton.OnClick += ApplyClicked; + // Inspect + + InspectButton = UIFactory.CreateButton(rightHoriGroup, "InspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f)); + UIFactory.SetLayoutElement(InspectButton.Button.gameObject, minWidth: 70, flexibleWidth: 0, minHeight: 25); + InspectButton.OnClick += InspectClicked; + // Main value label ValueLabel = UIFactory.CreateLabel(rightHoriGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); diff --git a/src/UI/CacheObject/Views/EvaluateWidget.cs b/src/UI/CacheObject/Views/EvaluateWidget.cs index 3e73b58..f4d3481 100644 --- a/src/UI/CacheObject/Views/EvaluateWidget.cs +++ b/src/UI/CacheObject/Views/EvaluateWidget.cs @@ -33,7 +33,7 @@ namespace UnityExplorer.UI.CacheObject.Views private readonly List genericArgLabels = new List(); private readonly List genericAutocompleters = new List(); - private readonly List inputFieldCache = new List(); + private readonly List inputFields = new List(); public void OnBorrowedFromPool(CacheMember owner) { @@ -52,7 +52,7 @@ namespace UnityExplorer.UI.CacheObject.Views public void OnReturnToPool() { - foreach (var input in inputFieldCache) + foreach (var input in inputFields) input.Text = ""; this.Owner = null; @@ -99,15 +99,11 @@ namespace UnityExplorer.UI.CacheObject.Views continue; } - try + if (!ParseUtility.TryParse(input, type, out outArgs[i], out Exception ex)) { - var parse = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs); - outArgs[i] = parse.Invoke(null, new object[] { input }); - } - catch (Exception ex) - { - ExplorerCore.LogWarning($"Cannot parse argument '{arg.Name}' ({arg.ParameterType.Name}), {ex.GetType().Name}: {ex.Message}"); outArgs[i] = null; + ExplorerCore.LogWarning($"Cannot parse argument '{arg.Name}' ({arg.ParameterType.Name})" + + $"{(ex == null ? "" : $", {ex.GetType().Name}: {ex.Message}")}"); } } @@ -199,6 +195,15 @@ namespace UnityExplorer.UI.CacheObject.Views argRows[i].SetActive(true); argLabels[i].text = $"{SignatureHighlighter.Parse(arg.ParameterType, false)} {arg.Name}"; + if (arg.ParameterType == typeof(string)) + inputFields[i].PlaceholderText.text = ""; + else + { + var elemType = arg.ParameterType; + if (elemType.IsByRef) + elemType = elemType.GetElementType(); + inputFields[i].PlaceholderText.text = $"eg. {ParseUtility.GetExampleInput(elemType)}"; + } } } @@ -228,7 +233,7 @@ namespace UnityExplorer.UI.CacheObject.Views inputField.InputField.lineType = InputField.LineType.MultiLineNewline; inputField.UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; inputField.OnValueChanged += (string val) => { inputArray[index] = val; }; - inputFieldCache.Add(inputField); + inputFields.Add(inputField); if (autocomplete) genericAutocompleters.Add(new TypeCompleter(null, inputField)); diff --git a/src/UI/IValues/InteractiveValueStruct.cs b/src/UI/IValues/InteractiveValueStruct.cs index 555a27e..ec3105b 100644 --- a/src/UI/IValues/InteractiveValueStruct.cs +++ b/src/UI/IValues/InteractiveValueStruct.cs @@ -26,33 +26,31 @@ namespace UnityExplorer.UI.IValues Fields = fields; } - public void SetValue(object instance, string value, int fieldIndex) + public void SetValue(object instance, string input, int fieldIndex) { - try - { - var field = Fields[fieldIndex]; + var field = Fields[fieldIndex]; - object val; - if (field.FieldType == typeof(string)) - val = value; - else - val = ReflectionUtility.GetMethodInfo(field.FieldType, "Parse", ArgumentUtility.ParseArgs) - .Invoke(null, new object[] { value }); - - field.SetValue(instance, val); - } - catch (FormatException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); } - catch (ArgumentException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); } - catch (OverflowException) { ExplorerCore.LogWarning($"Invalid argument '{value}'!"); } - catch (Exception ex) + object val; + if (field.FieldType == typeof(string)) + val = input; + else { - ExplorerCore.Log("Excepting setting value '" + value + "'! " + ex); + if (!ParseUtility.TryParse(input, field.FieldType, out val, out Exception ex)) + { + ExplorerCore.LogWarning("Unable to parse input!"); + if (ex != null) ExplorerCore.Log(ex.ReflectionExToString()); + return; + } } + + field.SetValue(instance, val); } public string GetValue(object instance, int fieldIndex) { - return Fields[fieldIndex].GetValue(instance)?.ToString() ?? ""; + var field = Fields[fieldIndex]; + var value = field.GetValue(instance); + return ParseUtility.ToStringForInput(value, field.FieldType); } } @@ -69,19 +67,21 @@ namespace UnityExplorer.UI.IValues if (typeSupportCache.TryGetValue(type.AssemblyQualifiedName, out var info)) return info.IsSupported; - var supported = true; + var supported = false; var fields = type.GetFields(INSTANCE_FLAGS); - - if (fields.Any(it => !it.FieldType.IsPrimitive && it.FieldType != typeof(string))) + if (fields.Length > 0) { - supported = false; - info = new StructInfo(supported, null); - } - else - { - supported = true; - info = new StructInfo(supported, fields); + if (fields.Any(it => !ParseUtility.CanParse(it.FieldType))) + { + supported = false; + info = new StructInfo(supported, null); + } + else + { + supported = true; + info = new StructInfo(supported, fields); + } } typeSupportCache.Add(type.AssemblyQualifiedName, info); @@ -182,12 +182,12 @@ namespace UnityExplorer.UI.IValues fieldRows.Add(row); var label = UIFactory.CreateLabel(row, "Label", "notset", TextAnchor.MiddleRight); - UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 150, flexibleWidth: 0); + UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 175, flexibleWidth: 0); label.horizontalOverflow = HorizontalWrapMode.Wrap; labels.Add(label); var input = UIFactory.CreateInputField(row, "InputField", "..."); - UIFactory.SetLayoutElement(input.UIRoot, minHeight: 25, minWidth: 100); + UIFactory.SetLayoutElement(input.UIRoot, minHeight: 25, minWidth: 200); var fitter = input.UIRoot.AddComponent(); fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; @@ -204,7 +204,7 @@ namespace UnityExplorer.UI.IValues UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleWidth: 9999); applyButton = UIFactory.CreateButton(UIRoot, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f)); - UIFactory.SetLayoutElement(applyButton.Button.gameObject, minHeight: 25, minWidth: 100); + UIFactory.SetLayoutElement(applyButton.Button.gameObject, minHeight: 25, minWidth: 175); applyButton.OnClick += OnApplyClicked; return UIRoot; diff --git a/src/UI/Panels/UIPanel.cs b/src/UI/Panels/UIPanel.cs index 9265a16..b500bc6 100644 --- a/src/UI/Panels/UIPanel.cs +++ b/src/UI/Panels/UIPanel.cs @@ -295,14 +295,12 @@ namespace UnityExplorer.UI.Panels { // Window Anchors helpers - internal static CultureInfo _enCulture = new CultureInfo("en-US"); - internal static string RectAnchorsToString(this RectTransform rect) { if (!rect) throw new ArgumentNullException("rect"); - return string.Format(_enCulture, "{0},{1},{2},{3}", new object[] + return string.Format(ParseUtility.en_US, "{0},{1},{2},{3}", new object[] { rect.anchorMin.x, rect.anchorMin.y, @@ -322,10 +320,10 @@ namespace UnityExplorer.UI.Panels throw new Exception($"stringAnchors split is unexpected length: {split.Length}"); Vector4 anchors; - anchors.x = float.Parse(split[0], _enCulture); - anchors.y = float.Parse(split[1], _enCulture); - anchors.z = float.Parse(split[2], _enCulture); - anchors.w = float.Parse(split[3], _enCulture); + anchors.x = float.Parse(split[0], ParseUtility.en_US); + anchors.y = float.Parse(split[1], ParseUtility.en_US); + anchors.z = float.Parse(split[2], ParseUtility.en_US); + anchors.w = float.Parse(split[3], ParseUtility.en_US); panel.anchorMin = new Vector2(anchors.x, anchors.y); panel.anchorMax = new Vector2(anchors.z, anchors.w); @@ -336,7 +334,7 @@ namespace UnityExplorer.UI.Panels if (!rect) throw new ArgumentNullException("rect"); - return string.Format(_enCulture, "{0},{1}", new object[] + return string.Format(ParseUtility.en_US, "{0},{1}", new object[] { rect.localPosition.x, rect.localPosition.y }); @@ -350,8 +348,8 @@ namespace UnityExplorer.UI.Panels throw new Exception($"stringPosition split is unexpected length: {split.Length}"); Vector3 vector = rect.localPosition; - vector.x = float.Parse(split[0], _enCulture); - vector.y = float.Parse(split[1], _enCulture); + vector.x = float.Parse(split[0], ParseUtility.en_US); + vector.y = float.Parse(split[1], ParseUtility.en_US); rect.localPosition = vector; } } diff --git a/src/UnityExplorer.csproj b/src/UnityExplorer.csproj index 037856a..b71ac98 100644 --- a/src/UnityExplorer.csproj +++ b/src/UnityExplorer.csproj @@ -226,6 +226,7 @@ +