diff --git a/src/Core/Reflection/Il2CppReflection.cs b/src/Core/Reflection/Il2CppReflection.cs index cf2ab1d..808ed40 100644 --- a/src/Core/Reflection/Il2CppReflection.cs +++ b/src/Core/Reflection/Il2CppReflection.cs @@ -270,11 +270,8 @@ namespace UnityExplorer try { - var x = new Il2CppSystem.Int32 { m_value = 5 }; - x.BoxIl2CppObject().Unbox(); - if (toType.IsEnum) - return Enum.ToObject(toType, Il2CppSystem.Enum.ToUInt64(cppObj)); + return Enum.Parse(toType, cppObj.ToString()); var name = toType.AssemblyQualifiedName; @@ -316,12 +313,10 @@ namespace UnityExplorer return null; if (type.IsEnum) - return Il2CppSystem.Enum.ToObject(Il2CppType.From(type), (ulong)value); + return Il2CppSystem.Enum.Parse(Il2CppType.From(type), value.ToString()); if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType)) - { return BoxIl2CppObject(MakeIl2CppPrimitive(cppType, value), cppType); - } return BoxIl2CppObject(value, type); } diff --git a/src/UI/IValues/InteractiveColor.cs b/src/UI/IValues/InteractiveColor.cs new file mode 100644 index 0000000..6b4b543 --- /dev/null +++ b/src/UI/IValues/InteractiveColor.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.CacheObject; + +namespace UnityExplorer.UI.IValues +{ + public class InteractiveColor : InteractiveValue + { + public bool IsValueColor32; + + public Color EditedColor; + + private Image m_colorImage; + private readonly InputFieldRef[] m_inputs = new InputFieldRef[4]; + private readonly Slider[] m_sliders = new Slider[4]; + + private ButtonRef m_applyButton; + + private static readonly string[] fieldNames = new[] { "R", "G", "B", "A" }; + + public override void OnBorrowed(CacheObjectBase owner) + { + base.OnBorrowed(owner); + + m_applyButton.Button.gameObject.SetActive(owner.CanWrite); + + foreach (var slider in m_sliders) + slider.interactable = owner.CanWrite; + foreach (var input in m_inputs) + input.InputField.readOnly = !owner.CanWrite; + } + + public override void SetValue(object value) + { + OnOwnerSetValue(value); + } + + public void SetValueToOwner() + { + if (IsValueColor32) + CurrentOwner.SetUserValue((Color32)EditedColor); + else + CurrentOwner.SetUserValue(EditedColor); + } + + private void OnOwnerSetValue(object value) + { + if (value is Color32 c32) + { + IsValueColor32 = true; + EditedColor = c32; + m_inputs[0].Text = c32.r.ToString(); + m_inputs[1].Text = c32.g.ToString(); + m_inputs[2].Text = c32.b.ToString(); + m_inputs[3].Text = c32.a.ToString(); + foreach (var slider in m_sliders) + slider.maxValue = 255; + } + else + { + IsValueColor32 = false; + EditedColor = (Color)value; + m_inputs[0].Text = EditedColor.r.ToString(); + m_inputs[1].Text = EditedColor.g.ToString(); + m_inputs[2].Text = EditedColor.b.ToString(); + m_inputs[3].Text = EditedColor.a.ToString(); + foreach (var slider in m_sliders) + slider.maxValue = 1; + } + + if (m_colorImage) + m_colorImage.color = EditedColor; + } + + private void SetColorField(float val, int fieldIndex) + { + switch (fieldIndex) + { + case 0: EditedColor.r = val; break; + case 1: EditedColor.g = val; break; + case 2: EditedColor.b = val; break; + case 3: EditedColor.a = val; break; + } + + if (m_colorImage) + m_colorImage.color = EditedColor; + } + + private void OnInputChanged(string val, int fieldIndex) + { + try + { + float f; + if (IsValueColor32) + { + byte value = byte.Parse(val); + m_sliders[fieldIndex].value = value; + f = (float)((decimal)value / 255); + } + else + { + f = float.Parse(val); + m_sliders[fieldIndex].value = f; + } + + SetColorField(f, fieldIndex); + } + catch (ArgumentException) { } // ignore bad user input + catch (FormatException) { } + catch (OverflowException) { } + catch (Exception ex) + { + ExplorerCore.LogWarning("InteractiveColor OnInput: " + ex.ToString()); + } + } + + private void OnSliderValueChanged(float val, int fieldIndex) + { + try + { + if (IsValueColor32) + { + m_inputs[fieldIndex].Text = ((byte)val).ToString(); + val /= 255f; + } + else + { + m_inputs[fieldIndex].Text = val.ToString(); + } + + SetColorField(val, fieldIndex); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("InteractiveColor OnSlider: " + ex.ToString()); + } + } + + // UI Construction + + public override GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveColor", false, false, true, true, 3, new Vector4(4, 4, 4, 4), + new Color(0.06f, 0.06f, 0.06f)); + + // hori group + + var horiGroup = UIFactory.CreateHorizontalGroup(UIRoot, "ColorEditor", false, false, true, true, 5, + default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft); + + // apply button + + m_applyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.2f, 0.26f, 0.2f)); + UIFactory.SetLayoutElement(m_applyButton.Button.gameObject, minHeight: 25, minWidth: 90); + m_applyButton.OnClick += SetValueToOwner; + + // sliders / inputs + + var grid = UIFactory.CreateGridGroup(horiGroup, "Grid", new Vector2(140, 25), new Vector2(2, 2), new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(grid, minWidth: 580, minHeight: 25, flexibleWidth: 0); + + for (int i = 0; i < 4; i++) + AddEditorRow(i, grid); + + // image of color + + var imgObj = UIFactory.CreateUIObject("ColorImageHelper", horiGroup); + UIFactory.SetLayoutElement(imgObj, minHeight: 25, minWidth: 50, flexibleWidth: 50); + m_colorImage = imgObj.AddComponent(); + + return UIRoot; + } + + internal void AddEditorRow(int index, GameObject groupObj) + { + var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow_" + fieldNames[index], + false, true, true, true, 5, default, new Color(1, 1, 1, 0)); + + var label = UIFactory.CreateLabel(row, "RowLabel", $"{fieldNames[index]}:", TextAnchor.MiddleRight, Color.cyan); + UIFactory.SetLayoutElement(label.gameObject, minWidth: 17, flexibleWidth: 0, minHeight: 25); + + var input = UIFactory.CreateInputField(row, "Input", "..."); + UIFactory.SetLayoutElement(input.UIRoot, minWidth: 40, minHeight: 25, flexibleHeight: 0); + m_inputs[index] = input; + input.OnValueChanged += (string val) => { OnInputChanged(val, index); }; + + var sliderObj = UIFactory.CreateSlider(row, "Slider", out Slider slider); + m_sliders[index] = slider; + UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 70, flexibleWidth: 999, flexibleHeight: 0); + slider.minValue = 0; + slider.maxValue = 1; + slider.onValueChanged.AddListener((float val) => { OnSliderValueChanged(val, index); }); + } + } +} diff --git a/src/UI/IValues/InteractiveEnum.cs b/src/UI/IValues/InteractiveEnum.cs new file mode 100644 index 0000000..8a348ff --- /dev/null +++ b/src/UI/IValues/InteractiveEnum.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.UI.CacheObject; + +namespace UnityExplorer.UI.IValues +{ + public class InteractiveEnum : InteractiveValue + { + public bool IsFlags; + public Type EnumType; + + private Type lastType; + + public OrderedDictionary CurrentValues; + + public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx]; + public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key]; + + private Dropdown enumDropdown; + private GameObject toggleHolder; + private readonly List flagToggles = new List(); + private readonly List flagTexts = new List(); + + // Setting value from owner + public override void SetValue(object value) + { + EnumType = value.GetType(); + + if (lastType != EnumType) + { + CurrentValues = GetEnumValues(EnumType, out IsFlags); + + if (IsFlags) + SetupTogglesForEnumType(); + else + SetupDropdownForEnumType(); + + lastType = EnumType; + } + + // setup ui for changes + if (IsFlags) + SetTogglesForValue(value); + else + SetDropdownForValue(value); + } + + // Setting value to owner + + private void OnApplyClicked() + { + if (IsFlags) + SetValueFromFlags(); + else + SetValueFromDropdown(); + } + + private void SetValueFromDropdown() + { + try + { + CurrentOwner.SetUserValue(ValueAtIdx(enumDropdown.value).ActualValue); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("Exception setting from dropdown: " + ex); + } + } + + private void SetValueFromFlags() + { + try + { + List values = new List(); + for (int i = 0; i < CurrentValues.Count; i++) + { + if (flagToggles[i].isOn) + values.Add(ValueAtIdx(i).Name); + } + + CurrentOwner.SetUserValue(Enum.Parse(EnumType, string.Join(", ", values))); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("Exception setting from flag toggles: " + ex); + } + } + + // setting UI state for value + + private void SetDropdownForValue(object value) + { + if (CurrentValues.Contains(value)) + { + var cached = ValueAtKey(value); + enumDropdown.value = cached.EnumIndex; + enumDropdown.RefreshShownValue(); + } + else + ExplorerCore.LogWarning("CurrentValues does not contain key '" + value?.ToString() ?? "" + "'"); + } + + private void SetTogglesForValue(object value) + { + try + { + var split = value.ToString().Split(','); + var set = new HashSet(); + foreach (var s in split) + set.Add(s.Trim()); + + for (int i = 0; i < CurrentValues.Count; i++) + flagToggles[i].isOn = set.Contains(ValueAtIdx(i).Name); + } + catch (Exception ex) + { + ExplorerCore.LogWarning("Exception setting flag toggles: " + ex); + } + } + + // Setting up the UI for the enum type when it changes or is first set + + private void SetupDropdownForEnumType() + { + toggleHolder.SetActive(false); + enumDropdown.gameObject.SetActive(true); + + // create dropdown entries + enumDropdown.options.Clear(); + + foreach (CachedEnumValue entry in CurrentValues.Values) + enumDropdown.options.Add(new Dropdown.OptionData(entry.Name)); + + enumDropdown.value = 0; + enumDropdown.RefreshShownValue(); + } + + private void SetupTogglesForEnumType() + { + toggleHolder.SetActive(true); + enumDropdown.gameObject.SetActive(false); + + // create / set / hide toggles + for (int i = 0; i < CurrentValues.Count || i < flagToggles.Count; i++) + { + if (i >= CurrentValues.Count) + { + if (i >= flagToggles.Count) + break; + + flagToggles[i].gameObject.SetActive(false); + continue; + } + + if (i >= flagToggles.Count) + AddToggleRow(); + + flagToggles[i].isOn = false; + flagTexts[i].text = ValueAtIdx(i).Name; + } + } + + private void AddToggleRow() + { + var row = UIFactory.CreateUIObject("ToggleRow", toggleHolder); + UIFactory.SetLayoutGroup(row, false, false, true, true, 2); + UIFactory.SetLayoutElement(row, minHeight: 25, flexibleWidth: 9999); + + var toggleObj = UIFactory.CreateToggle(row, "ToggleObj", out Toggle toggle, out Text toggleText); + UIFactory.SetLayoutElement(toggleObj, minHeight: 25, flexibleWidth: 9999); + + flagToggles.Add(toggle); + flagTexts.Add(toggleText); + } + + // UI Construction + + public override GameObject CreateContent(GameObject parent) + { + UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveEnum", false, false, true, true, 3, new Vector4(4, 4, 4, 4), + new Color(0.06f, 0.06f, 0.06f)); + UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 9999, flexibleWidth: 9999); + + var hori = UIFactory.CreateUIObject("Hori", UIRoot); + UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999); + UIFactory.SetLayoutGroup(hori, false, false, true, true, 2); + + var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f)); + UIFactory.SetLayoutElement(applyButton.Button.gameObject, minHeight: 25, minWidth: 100); + applyButton.OnClick += OnApplyClicked; + + var dropdownObj = UIFactory.CreateDropdown(hori, out enumDropdown, "not set", 14, null); + UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleWidth: 600); + + toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot); + UIFactory.SetLayoutGroup(toggleHolder, false, false, true, true, 4); + UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999); + + return UIRoot; + } + + + #region Enum cache + + public struct CachedEnumValue + { + public CachedEnumValue(object value, int index, string name) + { + EnumIndex = index; + Name = name; + ActualValue = value; + } + + public readonly object ActualValue; + public int EnumIndex; + public readonly string Name; + } + + internal static readonly Dictionary enumCache = new Dictionary(); + + internal static OrderedDictionary GetEnumValues(Type enumType, out bool isFlags) + { + isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any(); + + if (!enumCache.ContainsKey(enumType.AssemblyQualifiedName)) + { + var dict = new OrderedDictionary(); + var addedNames = new HashSet(); + + int i = 0; + foreach (var value in Enum.GetValues(enumType)) + { + var name = value.ToString(); + if (addedNames.Contains(name)) + continue; + addedNames.Add(name); + + dict.Add(value, new CachedEnumValue(value, i, name)); + i++; + } + + enumCache.Add(enumType.AssemblyQualifiedName, dict); + } + + return enumCache[enumType.AssemblyQualifiedName]; + } + + #endregion + } +} diff --git a/src/UI/IValues/InteractiveString.cs b/src/UI/IValues/InteractiveString.cs index 26e7952..063e57a 100644 --- a/src/UI/IValues/InteractiveString.cs +++ b/src/UI/IValues/InteractiveString.cs @@ -60,7 +60,7 @@ namespace UnityExplorer.UI.IValues private void OnApplyClicked() { - CurrentOwner.SetValueFromIValue(EditedValue); + CurrentOwner.SetUserValue(EditedValue); } private void OnInputChanged(string input) diff --git a/src/UI/IValues/InteractiveValue.cs b/src/UI/IValues/InteractiveValue.cs index 730fa85..61bd7c4 100644 --- a/src/UI/IValues/InteractiveValue.cs +++ b/src/UI/IValues/InteractiveValue.cs @@ -17,17 +17,17 @@ namespace UnityExplorer.UI.IValues { case ValueState.String: return typeof(InteractiveString); - //case ValueState.Enum: - // return typeof(InteractiveEnum); + case ValueState.Enum: + return typeof(InteractiveEnum); case ValueState.Collection: return typeof(InteractiveList); case ValueState.Dictionary: return typeof(InteractiveDictionary); //case ValueState.ValueStruct: // return typeof(InteractiveValueStruct); - //case ValueState.Color: - // return typeof(InteractiveColor); - default: return typeof(InteractiveValue); + case ValueState.Color: + return typeof(InteractiveColor); + default: return null; } } @@ -63,21 +63,5 @@ namespace UnityExplorer.UI.IValues public virtual void SetLayout() { } public abstract GameObject CreateContent(GameObject parent); - - // - //public virtual GameObject CreateContent(GameObject parent) - //{ - // UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent); - // UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - // UIFactory.SetLayoutGroup(UIRoot, true, true, true, true, 3, childAlignment: TextAnchor.MiddleLeft); - // - // UIFactory.CreateLabel(UIRoot, "Label", "this is an ivalue", TextAnchor.MiddleLeft); - // UIFactory.CreateInputField(UIRoot, "InputFIeld", "...", out var input); - // UIFactory.SetLayoutElement(input.gameObject, minHeight: 25, flexibleHeight: 500); - // input.lineType = InputField.LineType.MultiLineNewline; - // input.gameObject.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; - // - // return UIRoot; - //} } }