Rewrite EvaluateWidget, add BaseArgumentHandler, use autocomplete for InteractiveEnum

This commit is contained in:
Sinai 2022-01-02 19:43:55 +11:00
parent e585fc6da0
commit 7b477a8b0e
13 changed files with 768 additions and 490 deletions

View File

@ -11,6 +11,7 @@ using UniverseLib.UI.Models;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.CacheObject
{

View File

@ -7,6 +7,8 @@ using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.CacheObject.IValues
@ -20,10 +22,10 @@ namespace UnityExplorer.CacheObject.IValues
public OrderedDictionary CurrentValues;
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
private InputFieldRef inputField;
private ButtonRef enumHelperButton;
private EnumCompleter enumCompleter;
private Dropdown enumDropdown;
private GameObject toggleHolder;
private readonly List<Toggle> flagToggles = new List<Toggle>();
private readonly List<Text> flagTexts = new List<Text>();
@ -35,38 +37,68 @@ namespace UnityExplorer.CacheObject.IValues
if (lastType != EnumType)
{
CurrentValues = GetEnumValues(EnumType, out IsFlags);
CurrentValues = GetEnumValues(EnumType);
IsFlags = EnumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
if (IsFlags)
SetupTogglesForEnumType();
else
SetupDropdownForEnumType();
{
inputField.Component.gameObject.SetActive(true);
enumHelperButton.Component.gameObject.SetActive(true);
toggleHolder.SetActive(false);
}
enumCompleter.EnumType = EnumType;
enumCompleter.CacheEnumValues();
lastType = EnumType;
}
// setup ui for changes
if (IsFlags)
SetTogglesForValue(value);
if (!IsFlags)
inputField.Text = value.ToString();
else
SetDropdownForValue(value);
SetTogglesForValue(value);
this.enumCompleter.chosenSuggestion = value.ToString();
AutoCompleteModal.Instance.ReleaseOwnership(this.enumCompleter);
}
private void SetTogglesForValue(object value)
{
try
{
var split = value.ToString().Split(',');
var set = new HashSet<string>();
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 value to owner
private void OnApplyClicked()
{
if (IsFlags)
SetValueFromFlags();
else
SetValueFromDropdown();
}
private void SetValueFromDropdown()
{
try
{
CurrentOwner.SetUserValue(ValueAtIdx(enumDropdown.value).ActualValue);
if (!IsFlags)
{
if (ParseUtility.TryParse(this.inputField.Text, EnumType, out object value, out Exception ex))
CurrentOwner.SetUserValue(value);
else
throw ex;
}
else
{
SetValueFromFlags();
}
}
catch (Exception ex)
{
@ -93,59 +125,53 @@ namespace UnityExplorer.CacheObject.IValues
}
}
// setting UI state for value
// UI Construction
private void SetDropdownForValue(object value)
private void EnumHelper_OnClick()
{
if (CurrentValues.Contains(value))
{
var cached = ValueAtKey(value);
enumDropdown.value = cached.EnumIndex;
enumDropdown.RefreshShownValue();
}
else
ExplorerCore.LogWarning("CurrentValues does not contain key '" + value?.ToString() ?? "<null>" + "'");
enumCompleter.HelperButtonClicked();
}
private void SetTogglesForValue(object value)
public override GameObject CreateContent(GameObject parent)
{
try
{
var split = value.ToString().Split(',');
var set = new HashSet<string>();
foreach (var s in split)
set.Add(s.Trim());
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);
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);
}
var hori = UIFactory.CreateUIObject("Hori", UIRoot);
UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 100);
applyButton.OnClick += OnApplyClicked;
inputField = UIFactory.CreateInputField(hori, "InputField", "Enter name or underlying value...");
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
enumHelperButton = UIFactory.CreateButton(hori, "EnumHelper", "▼");
UIFactory.SetLayoutElement(enumHelperButton.Component.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
enumHelperButton.OnClick += EnumHelper_OnClick;
enumCompleter = new EnumCompleter(this.EnumType, this.inputField);
toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(toggleHolder, false, false, true, true, 4);
UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999);
return UIRoot;
}
// 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();
}
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
private void SetupTogglesForEnumType()
{
toggleHolder.SetActive(true);
enumDropdown.gameObject.SetActive(false);
inputField.Component.gameObject.SetActive(false);
enumHelperButton.Component.gameObject.SetActive(false);
// create / set / hide toggles
for (int i = 0; i < CurrentValues.Count || i < flagToggles.Count; i++)
@ -180,54 +206,13 @@ namespace UnityExplorer.CacheObject.IValues
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<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Component.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<VerticalLayoutGroup>(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<string, OrderedDictionary> enumCache = new Dictionary<string, OrderedDictionary>();
internal static OrderedDictionary GetEnumValues(Type enumType, out bool isFlags)
internal static OrderedDictionary GetEnumValues(Type enumType)
{
isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
//isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
if (!enumCache.ContainsKey(enumType.AssemblyQualifiedName))
{
@ -254,4 +239,18 @@ namespace UnityExplorer.CacheObject.IValues
#endregion
}
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;
}
}

View File

@ -1,374 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib;
namespace UnityExplorer.CacheObject.Views
{
public class EvaluateWidget : IPooledObject
{
public CacheMember Owner { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => -1f;
private ParameterInfo[] arguments;
private string[] argumentInput;
private GameObject argHolder;
private readonly List<GameObject> argRows = new List<GameObject>();
private readonly List<Text> argLabels = new List<Text>();
private readonly List<InputFieldRef> argInputFields = new List<InputFieldRef>();
private readonly List<Dropdown> argDropdowns = new List<Dropdown>();
private Type[] genericArguments;
private string[] genericInput;
private GameObject genericArgHolder;
private readonly List<GameObject> genericArgRows = new List<GameObject>();
private readonly List<Text> genericArgLabels = new List<Text>();
private readonly List<TypeCompleter> genericAutocompleters = new List<TypeCompleter>();
private readonly List<InputFieldRef> genericInputFields = new List<InputFieldRef>();
public void OnBorrowedFromPool(CacheMember owner)
{
this.Owner = owner;
arguments = owner.Arguments;
argumentInput = new string[arguments.Length];
genericArguments = owner.GenericArguments;
genericInput = new string[genericArguments.Length];
SetArgRows();
this.UIRoot.SetActive(true);
InspectorManager.OnInspectedTabsChanged += InspectorManager_OnInspectedTabsChanged;
}
public void OnReturnToPool()
{
foreach (var input in argInputFields)
input.Text = "";
foreach (var input in genericInputFields)
input.Text = "";
this.Owner = null;
InspectorManager.OnInspectedTabsChanged -= InspectorManager_OnInspectedTabsChanged;
}
private void InspectorManager_OnInspectedTabsChanged()
{
for (int i = 0; i < argDropdowns.Count; i++)
{
var drop = argDropdowns[i];
PopulateNonPrimitiveArgumentDropdown(drop, i);
}
}
public Type[] TryParseGenericArguments()
{
Type[] outArgs = new Type[genericArguments.Length];
for (int i = 0; i < genericArguments.Length; i++)
{
outArgs[i] = ReflectionUtility.GetTypeByName(genericInput[i])
?? throw new Exception($"Could not find any type by name '{genericInput[i]}'!");
}
return outArgs;
}
public object[] TryParseArguments()
{
object[] outArgs = new object[arguments.Length];
for (int i = 0; i < arguments.Length; i++)
{
var paramInfo = arguments[i];
var argType = paramInfo.ParameterType;
if (argType.IsByRef)
argType = argType.GetElementType();
if (ParseUtility.CanParse(argType))
{
var input = argumentInput[i];
if (argType == typeof(string))
{
outArgs[i] = input;
continue;
}
if (string.IsNullOrEmpty(input))
{
if (paramInfo.IsOptional)
outArgs[i] = paramInfo.DefaultValue;
else
outArgs[i] = null;
continue;
}
if (!ParseUtility.TryParse(input, argType, out outArgs[i], out Exception ex))
{
outArgs[i] = null;
ExplorerCore.LogWarning($"Cannot parse argument '{paramInfo.Name}' ({paramInfo.ParameterType.Name})" +
$"{(ex == null ? "" : $", {ex.GetType().Name}: {ex.Message}")}");
}
}
else
{
var dropdown = argDropdowns[i];
if (dropdown.value == 0)
{
outArgs[i] = null;
}
else
{
// Probably should do this in a better way...
// Parse the text of the dropdown option to get the inspector tab index.
string tabIndexString = "";
for (int j = 4; ; j++)
{
char c = dropdown.options[dropdown.value].text[j];
if (c == ':')
break;
tabIndexString += c;
}
int tabIndex = int.Parse(tabIndexString);
var tab = InspectorManager.Inspectors[tabIndex - 1];
outArgs[i] = tab.Target;
}
}
}
return outArgs;
}
private void SetArgRows()
{
if (genericArguments.Any())
{
genericArgHolder.SetActive(true);
SetGenericRows();
}
else
genericArgHolder.SetActive(false);
if (arguments.Any())
{
argHolder.SetActive(true);
SetNormalArgRows();
}
else
argHolder.SetActive(false);
}
private void SetGenericRows()
{
for (int i = 0; i < genericArguments.Length || i < genericArgRows.Count; i++)
{
if (i >= genericArguments.Length)
{
if (i >= genericArgRows.Count)
break;
else
// exceeded actual args, but still iterating so there must be views left, disable them
genericArgRows[i].SetActive(false);
continue;
}
var arg = genericArguments[i];
if (i >= genericArgRows.Count)
AddArgRow(i, true);
genericArgRows[i].SetActive(true);
var autoCompleter = genericAutocompleters[i];
autoCompleter.BaseType = arg;
autoCompleter.CacheTypes();
var constraints = arg.GetGenericParameterConstraints();
autoCompleter.GenericConstraints = constraints;
var sb = new StringBuilder($"<color={SignatureHighlighter.CONST}>{arg.Name}</color>");
for (int j = 0; j < constraints.Length; j++)
{
if (j == 0) sb.Append(' ').Append('(');
else sb.Append(',').Append(' ');
sb.Append(SignatureHighlighter.Parse(constraints[j], false));
if (j + 1 == constraints.Length)
sb.Append(')');
}
genericArgLabels[i].text = sb.ToString();
}
}
private void SetNormalArgRows()
{
for (int i = 0; i < arguments.Length || i < argRows.Count; i++)
{
if (i >= arguments.Length)
{
if (i >= argRows.Count)
break;
else
// exceeded actual args, but still iterating so there must be views left, disable them
argRows[i].SetActive(false);
continue;
}
var arg = arguments[i];
var argType = arg.ParameterType;
if (argType.IsByRef)
argType = argType.GetElementType();
if (i >= argRows.Count)
AddArgRow(i, false);
argRows[i].SetActive(true);
argLabels[i].text =
$"{SignatureHighlighter.Parse(argType, false)} <color={SignatureHighlighter.LOCAL_ARG}>{arg.Name}</color>";
if (ParseUtility.CanParse(argType))
{
argInputFields[i].Component.gameObject.SetActive(true);
argDropdowns[i].gameObject.SetActive(false);
if (arg.ParameterType == typeof(string))
argInputFields[i].PlaceholderText.text = "";
else
argInputFields[i].PlaceholderText.text = $"eg. {ParseUtility.GetExampleInput(argType)}";
}
else
{
argInputFields[i].Component.gameObject.SetActive(false);
argDropdowns[i].gameObject.SetActive(true);
PopulateNonPrimitiveArgumentDropdown(argDropdowns[i], i);
}
}
}
private void PopulateNonPrimitiveArgumentDropdown(Dropdown dropdown, int argIndex)
{
dropdown.options.Clear();
dropdown.options.Add(new Dropdown.OptionData("null"));
var argType = arguments[argIndex].ParameterType;
int tabIndex = 0;
foreach (var tab in InspectorManager.Inspectors)
{
tabIndex++;
if (argType.IsAssignableFrom(tab.Target.GetActualType()))
dropdown.options.Add(new Dropdown.OptionData($"Tab {tabIndex}: {tab.Tab.TabText.text}"));
}
}
private void AddArgRow(int index, bool generic)
{
if (!generic)
AddArgRow(index, argHolder, argRows, argLabels, argumentInput, false);
else
AddArgRow(index, genericArgHolder, genericArgRows, genericArgLabels, genericInput, true);
}
private void AddArgRow(int index, GameObject parent, List<GameObject> objectList, List<Text> labelList, string[] inputArray, bool generic)
{
var horiGroup = UIFactory.CreateUIObject("ArgRow_" + index, parent);
UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleHeight: 50, minWidth: 50, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(horiGroup, false, false, true, true, 5);
horiGroup.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
objectList.Add(horiGroup);
var label = UIFactory.CreateLabel(horiGroup, "ArgLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 40, flexibleWidth: 90, minHeight: 25, flexibleHeight: 50);
labelList.Add(label);
label.horizontalOverflow = HorizontalWrapMode.Wrap;
var inputField = UIFactory.CreateInputField(horiGroup, "InputField", "...");
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
inputField.OnValueChanged += (string val) => { inputArray[index] = val; };
if (!generic)
{
var dropdownObj = UIFactory.CreateDropdown(horiGroup, out Dropdown dropdown, "Select argument...", 14, (int val) =>
{
ArgDropdownChanged(index, val);
});
UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
dropdownObj.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
argInputFields.Add(inputField);
argDropdowns.Add(dropdown);
}
else
genericInputFields.Add(inputField);
if (generic)
genericAutocompleters.Add(new TypeCompleter(null, inputField));
}
private void ArgDropdownChanged(int argIndex, int value)
{
// not sure if necessary
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "EvaluateWidget", false, false, true, true, 3, new Vector4(2, 2, 2, 2),
new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(UIRoot, minWidth: 50, flexibleWidth: 9999, minHeight: 50, flexibleHeight: 800);
//UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// generic args
this.genericArgHolder = UIFactory.CreateUIObject("GenericHolder", UIRoot);
UIFactory.SetLayoutElement(genericArgHolder, flexibleWidth: 1000);
var genericsTitle = UIFactory.CreateLabel(genericArgHolder, "GenericsTitle", "Generic Arguments", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(genericsTitle.gameObject, minHeight: 25, flexibleWidth: 1000);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(genericArgHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(genericArgHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//genericArgHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// args
this.argHolder = UIFactory.CreateUIObject("ArgHolder", UIRoot);
UIFactory.SetLayoutElement(argHolder, flexibleWidth: 1000);
var argsTitle = UIFactory.CreateLabel(argHolder, "ArgsTitle", "Arguments", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(argsTitle.gameObject, minHeight: 25, flexibleWidth: 1000);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(argHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(argHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//argHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// evaluate button
var evalButton = UIFactory.CreateButton(UIRoot, "EvaluateButton", "Evaluate", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(evalButton.Component.gameObject, minHeight: 25, minWidth: 150, flexibleWidth: 0);
evalButton.OnClick += () =>
{
Owner.EvaluateAndSetCell();
};
return UIRoot;
}
}
}

View File

@ -36,6 +36,8 @@ namespace UnityExplorer.Tests
public const int ConstantInt5 = 5;
// Testing other InteractiveValues
public static BindingFlags EnumTest;
public static CameraClearFlags EnumTest2;
public static Color Color = Color.magenta;
public static Color32 Color32 = Color.red;
public static string ALongString = new string('#', 10000);
@ -78,9 +80,9 @@ namespace UnityExplorer.Tests
ExplorerCore.Log($"Test3 {typeof(T).FullName}");
}
public static void TestArgumentParse(string s, int i, Color color, CameraClearFlags flags, Vector3 vector, Quaternion quaternion)
public static void TestArgumentParse(string s, int i, Color color, CameraClearFlags flags, Vector3 vector, Quaternion quaternion, object obj)
{
ExplorerCore.Log($"{s}, {i}, {color.ToString()}, {flags}, {vector.ToString()}, {quaternion.ToString()}");
ExplorerCore.Log($"{s}, {i}, {color.ToString()}, {flags}, {vector.ToString()}, {quaternion.ToString()}, {obj?.ToString() ?? "null"}");
}
private static void Init_Mono()

View File

@ -164,9 +164,7 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
if (!CurrentHandler.InputField.UIRoot.activeInHierarchy)
ReleaseOwnership(CurrentHandler);
else
{
UpdatePosition();
}
}
}
@ -228,9 +226,9 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
private int lastCaretPosition;
private Vector3 lastInputPosition;
private void UpdatePosition()
internal void UpdatePosition()
{
if (CurrentHandler == null || !CurrentHandler.InputField.Component.isFocused)
if (CurrentHandler == null)
return;
var input = CurrentHandler.InputField;
@ -255,9 +253,10 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
}
else
{
var textGen = input.Component.textComponent.cachedTextGenerator;
var pos = input.UIRoot.transform.TransformPoint(textGen.characters[0].cursorPos);
uiRoot.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
uiRoot.transform.position = input.Rect.position + new Vector3(-(input.Rect.rect.width / 2) + 10, -20, 0);
//var textGen = input.Component.textComponent.cachedTextGenerator;
//var pos = input.UIRoot.transform.TransformPoint(textGen.characters[0].cursorPos);
//uiRoot.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
}
this.Dragger.OnEndResize();

View File

@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityExplorer.CacheObject.IValues;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.UI.Widgets.AutoComplete
{
public class EnumCompleter : ISuggestionProvider
{
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
if (!_enabled)
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
}
private bool _enabled = true;
public event Action<Suggestion> SuggestionClicked;
public Type EnumType { get; set; }
public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false;
private readonly List<Suggestion> suggestions = new List<Suggestion>();
private readonly HashSet<string> suggestedValues = new HashSet<string>();
private OrderedDictionary enumValues;
internal string chosenSuggestion;
bool ISuggestionProvider.AllowNavigation => false;
public EnumCompleter(Type enumType, InputFieldRef inputField)
{
EnumType = enumType;
InputField = inputField;
inputField.OnValueChanged += OnInputFieldChanged;
if (EnumType != null)
CacheEnumValues();
}
public void CacheEnumValues()
{
enumValues = InteractiveEnum.GetEnumValues(EnumType);
}
private string GetLastSplitInput(string fullInput)
{
string ret = fullInput;
int lastSplit = fullInput.LastIndexOf(',');
if (lastSplit >= 0)
{
lastSplit++;
if (lastSplit == fullInput.Length)
ret = "";
else
ret = fullInput.Substring(lastSplit);
}
return ret;
}
public void OnSuggestionClicked(Suggestion suggestion)
{
chosenSuggestion = suggestion.UnderlyingValue;
string lastInput = GetLastSplitInput(InputField.Text);
if (lastInput != suggestion.UnderlyingValue)
{
string valueToSet = InputField.Text;
if (valueToSet.Length > 0)
valueToSet = valueToSet.Substring(0, InputField.Text.Length - lastInput.Length);
valueToSet += suggestion.UnderlyingValue;
InputField.Text = valueToSet;
//InputField.Text += suggestion.UnderlyingValue.Substring(lastInput.Length);
}
SuggestionClicked?.Invoke(suggestion);
suggestions.Clear();
AutoCompleteModal.Instance.SetSuggestions(suggestions);
}
public void HelperButtonClicked()
{
GetSuggestions("");
AutoCompleteModal.Instance.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
}
private void OnInputFieldChanged(string value)
{
if (!Enabled)
return;
if (string.IsNullOrEmpty(value) || GetLastSplitInput(value) == chosenSuggestion)
{
chosenSuggestion = null;
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
else
{
GetSuggestions(value);
AutoCompleteModal.Instance.TakeOwnership(this);
AutoCompleteModal.Instance.SetSuggestions(suggestions);
}
}
private void GetSuggestions(string value)
{
suggestions.Clear();
suggestedValues.Clear();
if (EnumType == null)
{
ExplorerCore.LogWarning("Autocompleter Base enum type is null!");
return;
}
value = GetLastSplitInput(value);
for (int i = 0; i < this.enumValues.Count; i++)
{
var enumValue = (CachedEnumValue)enumValues[i];
if (enumValue.Name.ContainsIgnoreCase(value))
AddSuggestion(enumValue.Name);
}
}
internal static readonly Dictionary<string, string> sharedValueToLabel = new Dictionary<string, string>(4096);
void AddSuggestion(string value)
{
if (suggestedValues.Contains(value))
return;
suggestedValues.Add(value);
if (!sharedValueToLabel.ContainsKey(value))
sharedValueToLabel.Add(value, $"<color={SignatureHighlighter.CONST}>{value}</color>");
suggestions.Add(new Suggestion(sharedValueToLabel[value], value));
}
}
}

View File

@ -12,8 +12,6 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
public readonly string DisplayText;
public readonly string UnderlyingValue;
public string Combined => DisplayText + UnderlyingValue;
public Suggestion(string displayText, string underlyingValue)
{
DisplayText = displayText;

View File

@ -9,12 +9,24 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
{
internal static readonly Dictionary<string, string> sharedTypeToLabel = new Dictionary<string, string>(4096);
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
if (!_enabled)
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
}
private bool _enabled = true;
public event Action<Suggestion> SuggestionClicked;
public Type BaseType { get; set; }
public Type[] GenericConstraints { get; set; }
private bool allowAbstract;
private bool allowEnum;
private readonly bool allowAbstract;
private readonly bool allowEnum;
public InputFieldRef InputField { get; }
public bool AnchorToCaretPosition => false;
@ -61,6 +73,9 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
private void OnInputFieldChanged(string value)
{
if (!Enabled)
return;
if (string.IsNullOrEmpty(value) || value == chosenSuggestion)
{
chosenSuggestion = null;

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib.UI.Models;
namespace UnityExplorer.UI.Widgets
{
public abstract class BaseArgumentHandler : IPooledObject
{
protected EvaluateWidget evaluator;
internal Text argNameLabel;
internal InputFieldRef inputField;
internal TypeCompleter typeCompleter;
// IPooledObject
public float DefaultHeight => 25f;
public GameObject UIRoot { get; set; }
public abstract void CreateSpecialContent();
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateUIObject("ArgRow", parent);
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 50, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
argNameLabel = UIFactory.CreateLabel(UIRoot, "ArgLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(argNameLabel.gameObject, minWidth: 40, flexibleWidth: 90, minHeight: 25, flexibleHeight: 50);
argNameLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
inputField = UIFactory.CreateInputField(UIRoot, "InputField", "...");
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
typeCompleter = new TypeCompleter(typeof(object), this.inputField);
typeCompleter.Enabled = false;
CreateSpecialContent();
return UIRoot;
}
}
}

View File

@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib;
using UnityExplorer.CacheObject;
namespace UnityExplorer.UI.Widgets
{
public class EvaluateWidget : IPooledObject
{
public CacheMember Owner { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => -1f;
private ParameterInfo[] parameters;
internal GameObject parametersHolder;
private ParameterHandler[] paramHandlers;
private Type[] genericArguments;
internal GameObject genericArgumentsHolder;
private GenericArgumentHandler[] genericHandlers;
public void OnBorrowedFromPool(CacheMember owner)
{
this.Owner = owner;
parameters = owner.Arguments;
paramHandlers = new ParameterHandler[parameters.Length];
genericArguments = owner.GenericArguments;
genericHandlers = new GenericArgumentHandler[genericArguments.Length];
SetArgRows();
this.UIRoot.SetActive(true);
InspectorManager.OnInspectedTabsChanged += InspectorManager_OnInspectedTabsChanged;
}
public void OnReturnToPool()
{
foreach (var widget in paramHandlers)
{
widget.OnReturned();
Pool<ParameterHandler>.Return(widget);
}
paramHandlers = null;
foreach (var widget in genericHandlers)
{
widget.OnReturned();
Pool<GenericArgumentHandler>.Return(widget);
}
genericHandlers = null;
this.Owner = null;
InspectorManager.OnInspectedTabsChanged -= InspectorManager_OnInspectedTabsChanged;
}
private void InspectorManager_OnInspectedTabsChanged()
{
foreach (var handler in this.paramHandlers)
handler.PopulateDropdown();
}
public Type[] TryParseGenericArguments()
{
Type[] outArgs = new Type[genericArguments.Length];
for (int i = 0; i < genericArguments.Length; i++)
outArgs[i] = genericHandlers[i].Evaluate();
return outArgs;
}
public object[] TryParseArguments()
{
object[] outArgs = new object[parameters.Length];
for (int i = 0; i < parameters.Length; i++)
outArgs[i] = paramHandlers[i].Evaluate();
return outArgs;
}
private void SetArgRows()
{
if (genericArguments.Any())
{
genericArgumentsHolder.SetActive(true);
SetGenericRows();
}
else
genericArgumentsHolder.SetActive(false);
if (parameters.Any())
{
parametersHolder.SetActive(true);
SetNormalArgRows();
}
else
parametersHolder.SetActive(false);
}
private void SetGenericRows()
{
for (int i = 0; i < genericArguments.Length; i++)
{
var type = genericArguments[i];
var holder = genericHandlers[i] = Pool<GenericArgumentHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.genericArgumentsHolder.transform, false);
holder.OnBorrowed(this, type);
}
}
private void SetNormalArgRows()
{
for (int i = 0; i < parameters.Length; i++)
{
var param = parameters[i];
var holder = paramHandlers[i] = Pool<ParameterHandler>.Borrow();
holder.UIRoot.transform.SetParent(this.parametersHolder.transform, false);
holder.OnBorrowed(this, param);
}
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "EvaluateWidget", false, false, true, true, 3, new Vector4(2, 2, 2, 2),
new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(UIRoot, minWidth: 50, flexibleWidth: 9999, minHeight: 50, flexibleHeight: 800);
//UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// generic args
this.genericArgumentsHolder = UIFactory.CreateUIObject("GenericHolder", UIRoot);
UIFactory.SetLayoutElement(genericArgumentsHolder, flexibleWidth: 1000);
var genericsTitle = UIFactory.CreateLabel(genericArgumentsHolder, "GenericsTitle", "Generic Arguments", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(genericsTitle.gameObject, minHeight: 25, flexibleWidth: 1000);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(genericArgumentsHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(genericArgumentsHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//genericArgHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// args
this.parametersHolder = UIFactory.CreateUIObject("ArgHolder", UIRoot);
UIFactory.SetLayoutElement(parametersHolder, flexibleWidth: 1000);
var argsTitle = UIFactory.CreateLabel(parametersHolder, "ArgsTitle", "Arguments", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(argsTitle.gameObject, minHeight: 25, flexibleWidth: 1000);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(parametersHolder, false, false, true, true, 3);
UIFactory.SetLayoutElement(parametersHolder, minHeight: 25, flexibleHeight: 750, minWidth: 50, flexibleWidth: 9999);
//argHolder.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// evaluate button
var evalButton = UIFactory.CreateButton(UIRoot, "EvaluateButton", "Evaluate", new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(evalButton.Component.gameObject, minHeight: 25, minWidth: 150, flexibleWidth: 0);
evalButton.OnClick += () =>
{
Owner.EvaluateAndSetCell();
};
return UIRoot;
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UniverseLib;
namespace UnityExplorer.UI.Widgets
{
public class GenericArgumentHandler : BaseArgumentHandler
{
private Type genericType;
public void OnBorrowed(EvaluateWidget evaluator, Type genericConstraint)
{
this.evaluator = evaluator;
this.genericType = genericConstraint;
typeCompleter.Enabled = true;
typeCompleter.BaseType = genericType;
typeCompleter.CacheTypes();
var constraints = genericType.GetGenericParameterConstraints();
typeCompleter.GenericConstraints = constraints;
var sb = new StringBuilder($"<color={SignatureHighlighter.CONST}>{genericType.Name}</color>");
for (int j = 0; j < constraints.Length; j++)
{
if (j == 0) sb.Append(' ').Append('(');
else sb.Append(',').Append(' ');
sb.Append(SignatureHighlighter.Parse(constraints[j], false));
if (j + 1 == constraints.Length)
sb.Append(')');
}
argNameLabel.text = sb.ToString();
}
public void OnReturned()
{
this.evaluator = null;
this.genericType = null;
this.typeCompleter.Enabled = false;
this.inputField.Text = "";
}
public Type Evaluate()
{
return ReflectionUtility.GetTypeByName(this.inputField.Text)
?? throw new Exception($"Could not find any type by name '{this.inputField.Text}'!");
}
public override void CreateSpecialContent()
{
}
}
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using UnityEngine.UI;
using UnityExplorer.CacheObject.IValues;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
namespace UnityExplorer.UI.Widgets
{
public class ParameterHandler : BaseArgumentHandler
{
private ParameterInfo paramInfo;
private Type paramType;
internal Dropdown dropdown;
private bool usingDropdown;
internal EnumCompleter enumCompleter;
private ButtonRef enumHelperButton;
public void OnBorrowed(EvaluateWidget evaluator, ParameterInfo paramInfo)
{
this.evaluator = evaluator;
this.paramInfo = paramInfo;
this.paramType = paramInfo.ParameterType;
if (paramType.IsByRef)
paramType = paramType.GetElementType();
this.argNameLabel.text =
$"{SignatureHighlighter.Parse(paramType, false)} <color={SignatureHighlighter.LOCAL_ARG}>{paramInfo.Name}</color>";
if (ParseUtility.CanParse(paramType) || typeof(Type).IsAssignableFrom(paramType))
{
this.inputField.Component.gameObject.SetActive(true);
this.dropdown.gameObject.SetActive(false);
this.typeCompleter.Enabled = typeof(Type).IsAssignableFrom(paramType);
this.enumCompleter.Enabled = paramType.IsEnum;
this.enumHelperButton.Component.gameObject.SetActive(paramType.IsEnum);
if (!typeCompleter.Enabled)
{
if (paramType == typeof(string))
inputField.PlaceholderText.text = "...";
else
inputField.PlaceholderText.text = $"eg. {ParseUtility.GetExampleInput(paramType)}";
}
else
{
inputField.PlaceholderText.text = "Enter a Type name...";
this.typeCompleter.BaseType = typeof(object);
this.typeCompleter.CacheTypes();
}
if (enumCompleter.Enabled)
{
enumCompleter.EnumType = paramType;
enumCompleter.CacheEnumValues();
}
}
else
{
// non-parsable, and not a Type
this.inputField.Component.gameObject.SetActive(false);
this.dropdown.gameObject.SetActive(true);
this.typeCompleter.Enabled = false;
this.enumCompleter.Enabled = false;
this.enumHelperButton.Component.gameObject.SetActive(false);
usingDropdown = true;
PopulateDropdown();
InspectorManager.OnInspectedTabsChanged += PopulateDropdown;
}
}
public void OnReturned()
{
this.evaluator = null;
this.paramInfo = null;
usingDropdown = false;
this.enumCompleter.Enabled = false;
this.typeCompleter.Enabled = false;
this.inputField.Text = "";
InspectorManager.OnInspectedTabsChanged -= PopulateDropdown;
}
public object Evaluate()
{
if (!usingDropdown)
{
var input = this.inputField.Text;
if (paramType == typeof(string))
return input;
if (string.IsNullOrEmpty(input))
{
if (paramInfo.IsOptional)
return paramInfo.DefaultValue;
else
return null;
}
if (!ParseUtility.TryParse(input, paramType, out object parsed, out Exception ex))
{
ExplorerCore.LogWarning($"Cannot parse argument '{paramInfo.Name}' ({paramInfo.ParameterType.Name})" +
$"{(ex == null ? "" : $", {ex.GetType().Name}: {ex.Message}")}");
return null;
}
else
return parsed;
}
else
{
if (dropdown.value == 0)
return null;
else
return dropdownUnderlyingValues[dropdown.value];
}
}
private object[] dropdownUnderlyingValues;
internal void PopulateDropdown()
{
if (!usingDropdown)
return;
dropdown.options.Clear();
var underlyingValues = new List<object>();
dropdown.options.Add(new Dropdown.OptionData("null"));
underlyingValues.Add(null);
var argType = paramType;
int tabIndex = 0;
foreach (var tab in InspectorManager.Inspectors)
{
tabIndex++;
if (argType.IsAssignableFrom(tab.Target.GetActualType()))
{
dropdown.options.Add(new Dropdown.OptionData($"Tab {tabIndex}: {tab.Tab.TabText.text}"));
underlyingValues.Add(tab.Target);
}
}
dropdownUnderlyingValues = underlyingValues.ToArray();
}
private void EnumHelper_OnClick()
{
enumCompleter.HelperButtonClicked();
}
public override void CreateSpecialContent()
{
enumHelperButton = UIFactory.CreateButton(UIRoot, "EnumHelper", "▼");
UIFactory.SetLayoutElement(enumHelperButton.Component.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
enumHelperButton.OnClick += EnumHelper_OnClick;
var dropdownObj = UIFactory.CreateDropdown(UIRoot, out dropdown, "Select argument...", 14, (int val) =>
{
//ArgDropdownChanged(val);
});
UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
dropdownObj.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
enumCompleter = new EnumCompleter(paramType, this.inputField);
enumCompleter.Enabled = false;
}
//private void ArgDropdownChanged(int value)
//{
// // not needed
//}
}
}

View File

@ -255,7 +255,8 @@
<Compile Include="CacheObject\Views\CacheListEntryCell.cs" />
<Compile Include="CacheObject\Views\CacheMemberCell.cs" />
<Compile Include="CacheObject\Views\CacheObjectCell.cs" />
<Compile Include="CacheObject\Views\EvaluateWidget.cs" />
<Compile Include="UI\Widgets\AutoComplete\EnumCompleter.cs" />
<Compile Include="UI\Widgets\EvaluateWidget\EvaluateWidget.cs" />
<Compile Include="Inspectors\GameObjectInspector.cs" />
<Compile Include="CacheObject\ICacheObjectController.cs" />
<Compile Include="Inspectors\InspectorManager.cs" />
@ -307,6 +308,9 @@
<Compile Include="UI\Widgets\AutoComplete\TypeCompleter.cs" />
<Compile Include="ObjectExplorer\ObjectSearch.cs" />
<Compile Include="ObjectExplorer\SceneExplorer.cs" />
<Compile Include="UI\Widgets\EvaluateWidget\BaseArgumentHandler.cs" />
<Compile Include="UI\Widgets\EvaluateWidget\GenericArgumentHandler.cs" />
<Compile Include="UI\Widgets\EvaluateWidget\ParameterHandler.cs" />
<Compile Include="UI\Widgets\TransformTree\CachedTransform.cs" />
<Compile Include="UI\Widgets\TransformTree\TransformCell.cs" />
<Compile Include="UI\Widgets\TransformTree\TransformTree.cs" />