Rewrite InteractiveUnityStruct, now called InteractiveFloatStruct

InteractiveFloatStruct supports any struct where all the fields are floats.
This commit is contained in:
Sinai 2021-04-05 20:32:47 +10:00
parent 09dae6f1d3
commit c748be7bcc
5 changed files with 203 additions and 301 deletions

View File

@ -48,6 +48,8 @@ namespace UnityExplorer.UI.InteractiveValues
GetDefaultLabel();
m_baseLabel.text = DefaultLabel;
base.RefreshUIForValue();
if (m_subContentConstructed)
{
for (int i = 0; i < m_values.Length; i++)

View File

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
namespace UnityExplorer.UI.InteractiveValues
{
// Class for supporting any "float struct" (ie Vector, Rect, etc).
// Supports any struct where all the public instance fields are floats (or types assignable to float)
public class StructInfo
{
public string[] FieldNames { get; }
private readonly FieldInfo[] m_fields;
public StructInfo(Type type)
{
m_fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
.Where(it => !it.IsLiteral)
.ToArray();
FieldNames = m_fields.Select(it => it.Name)
.ToArray();
}
public object SetValue(ref object instance, int fieldIndex, float val)
{
m_fields[fieldIndex].SetValue(instance, val);
return instance;
}
public float GetValue(object instance, int fieldIndex)
=> (float)m_fields[fieldIndex].GetValue(instance);
public void RefreshUI(InputField[] inputs, object instance)
{
try
{
for (int i = 0; i < m_fields.Length; i++)
{
var field = m_fields[i];
float val = (float)field.GetValue(instance);
inputs[i].text = val.ToString();
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
}
public class InteractiveFloatStruct : InteractiveValue
{
private static readonly Dictionary<string, bool> _typeSupportCache = new Dictionary<string, bool>();
public static bool IsTypeSupported(Type type)
{
if (!type.IsValueType)
return false;
if (_typeSupportCache.TryGetValue(type.AssemblyQualifiedName, out bool ret))
return ret;
ret = true;
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (var field in fields)
{
if (field.IsLiteral)
continue;
if (!typeof(float).IsAssignableFrom(field.FieldType))
{
ret = false;
break;
}
}
_typeSupportCache.Add(type.AssemblyQualifiedName, ret);
return ret;
}
//~~~~~~~~~ Instance ~~~~~~~~~~
public InteractiveFloatStruct(object value, Type valueType) : base(value, valueType) { }
public override bool HasSubContent => true;
public override bool SubContentWanted => true;
public StructInfo StructInfo;
public override void RefreshUIForValue()
{
InitializeStructInfo();
base.RefreshUIForValue();
if (m_subContentConstructed)
StructInfo.RefreshUI(m_inputs, this.Value);
}
internal override void OnToggleSubcontent(bool toggle)
{
InitializeStructInfo();
base.OnToggleSubcontent(toggle);
StructInfo.RefreshUI(m_inputs, this.Value);
}
internal Type m_lastStructType;
internal void InitializeStructInfo()
{
var type = Value?.GetType() ?? FallbackType;
if (StructInfo != null && type == m_lastStructType)
return;
if (StructInfo != null && m_subContentConstructed)
DestroySubContent();
m_lastStructType = type;
StructInfo = new StructInfo(type);
if (m_subContentParent.activeSelf)
ConstructSubcontent();
}
#region UI CONSTRUCTION
internal InputField[] m_inputs;
public override void ConstructUI(GameObject parent, GameObject subGroup)
{
base.ConstructUI(parent, subGroup);
}
public override void ConstructSubcontent()
{
base.ConstructSubcontent();
if (StructInfo == null)
{
ExplorerCore.LogWarning("Setting up subcontent but structinfo is null");
return;
}
var editorContainer = UIFactory.CreateVerticalGroup(m_subContentParent, "EditorContent", false, true, true, true, 2, new Vector4(4, 4, 4, 4),
new Color(0.08f, 0.08f, 0.08f));
m_inputs = new InputField[StructInfo.FieldNames.Length];
for (int i = 0; i < StructInfo.FieldNames.Length; i++)
AddEditorRow(i, editorContainer);
RefreshUIForValue();
}
internal void AddEditorRow(int index, GameObject groupObj)
{
try
{
var row = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow", false, true, true, true, 5, default, new Color(1, 1, 1, 0));
string name = StructInfo.FieldNames[index];
var label = UIFactory.CreateLabel(row, "RowLabel", $"{name}:", TextAnchor.MiddleRight, Color.cyan);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25);
var inputFieldObj = UIFactory.CreateInputField(row, "InputField", "...", 14, 3, 1);
UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0);
var inputField = inputFieldObj.GetComponent<InputField>();
m_inputs[index] = inputField;
inputField.onValueChanged.AddListener((string val) =>
{
try
{
float f = float.Parse(val);
Value = StructInfo.SetValue(ref this.Value, index, f);
Owner.SetValue();
}
catch { }
});
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
#endregion
}
}

View File

@ -1,298 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Unity;
using UnityExplorer.UI;
namespace UnityExplorer.UI.InteractiveValues
{
#region IStructInfo helper
public interface IStructInfo
{
string[] FieldNames { get; }
object SetValue(ref object value, int fieldIndex, float val);
void RefreshUI(InputField[] inputs, object value);
}
public class StructInfo<T> : IStructInfo where T : struct
{
public string[] FieldNames { get; set; }
public delegate void SetMethod(ref T value, int fieldIndex, float val);
public SetMethod SetValueMethod;
public delegate void UpdateMethod(InputField[] inputs, object value);
public UpdateMethod UpdateUIMethod;
public object SetValue(ref object value, int fieldIndex, float val)
{
var box = (T)value;
SetValueMethod.Invoke(ref box, fieldIndex, val);
return box;
}
public void RefreshUI(InputField[] inputs, object value)
{
UpdateUIMethod.Invoke(inputs, value);
}
}
// This part is a bit ugly, but everything else is generalized above.
// I could generalize it more with reflection, but it would be different for
// mono/il2cpp and also slower.
public static class StructInfoFactory
{
public static IStructInfo Create(Type type)
{
if (type == typeof(Vector2))
{
return new StructInfo<Vector2>()
{
FieldNames = new[] { "x", "y", },
SetValueMethod = (ref Vector2 vec, int fieldIndex, float val) =>
{
switch (fieldIndex)
{
case 0: vec.x = val; break;
case 1: vec.y = val; break;
}
},
UpdateUIMethod = (InputField[] inputs, object value) =>
{
Vector2 vec = (Vector2)value;
inputs[0].text = vec.x.ToString();
inputs[1].text = vec.y.ToString();
}
};
}
else if (type == typeof(Vector3))
{
return new StructInfo<Vector3>()
{
FieldNames = new[] { "x", "y", "z" },
SetValueMethod = (ref Vector3 vec, int fieldIndex, float val) =>
{
switch (fieldIndex)
{
case 0: vec.x = val; break;
case 1: vec.y = val; break;
case 2: vec.z = val; break;
}
},
UpdateUIMethod = (InputField[] inputs, object value) =>
{
Vector3 vec = (Vector3)value;
inputs[0].text = vec.x.ToString();
inputs[1].text = vec.y.ToString();
inputs[2].text = vec.z.ToString();
}
};
}
else if (type == typeof(Vector4))
{
return new StructInfo<Vector4>()
{
FieldNames = new[] { "x", "y", "z", "w" },
SetValueMethod = (ref Vector4 vec, int fieldIndex, float val) =>
{
switch (fieldIndex)
{
case 0: vec.x = val; break;
case 1: vec.y = val; break;
case 2: vec.z = val; break;
case 3: vec.w = val; break;
}
},
UpdateUIMethod = (InputField[] inputs, object value) =>
{
Vector4 vec = (Vector4)value;
inputs[0].text = vec.x.ToString();
inputs[1].text = vec.y.ToString();
inputs[2].text = vec.z.ToString();
inputs[3].text = vec.w.ToString();
}
};
}
else if (type == typeof(Rect))
{
return new StructInfo<Rect>()
{
FieldNames = new[] { "x", "y", "width", "height" },
SetValueMethod = (ref Rect vec, int fieldIndex, float val) =>
{
switch (fieldIndex)
{
case 0: vec.x = val; break;
case 1: vec.y = val; break;
case 2: vec.width = val; break;
case 3: vec.height = val; break;
}
},
UpdateUIMethod = (InputField[] inputs, object value) =>
{
Rect vec = (Rect)value;
inputs[0].text = vec.x.ToString();
inputs[1].text = vec.y.ToString();
inputs[2].text = vec.width.ToString();
inputs[3].text = vec.height.ToString();
}
};
}
else if (type == typeof(Color))
{
return new StructInfo<Color>()
{
FieldNames = new[] { "r", "g", "b", "a" },
SetValueMethod = (ref Color vec, int fieldIndex, float val) =>
{
switch (fieldIndex)
{
case 0: vec.r = val; break;
case 1: vec.g = val; break;
case 2: vec.b = val; break;
case 3: vec.a = val; break;
}
},
UpdateUIMethod = (InputField[] inputs, object value) =>
{
Color vec = (Color)value;
inputs[0].text = vec.r.ToString();
inputs[1].text = vec.g.ToString();
inputs[2].text = vec.b.ToString();
inputs[3].text = vec.a.ToString();
}
};
}
else
throw new NotImplementedException();
}
}
#endregion
public class InteractiveUnityStruct : InteractiveValue
{
public static bool SupportsType(Type type) => s_supportedTypes.Contains(type);
private static readonly HashSet<Type> s_supportedTypes = new HashSet<Type>
{
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Rect),
//typeof(Color) // todo might make a special editor for colors
};
//~~~~~~~~~ Instance ~~~~~~~~~~
public InteractiveUnityStruct(object value, Type valueType) : base(value, valueType) { }
public override bool HasSubContent => true;
public override bool SubContentWanted => true;
public override bool WantInspectBtn => true;
public IStructInfo StructInfo;
public override void RefreshUIForValue()
{
InitializeStructInfo();
base.RefreshUIForValue();
if (m_subContentConstructed)
StructInfo.RefreshUI(m_inputs, this.Value);
}
internal override void OnToggleSubcontent(bool toggle)
{
InitializeStructInfo();
base.OnToggleSubcontent(toggle);
StructInfo.RefreshUI(m_inputs, this.Value);
}
internal Type m_lastStructType;
internal void InitializeStructInfo()
{
var type = Value?.GetType() ?? FallbackType;
if (StructInfo != null && type == m_lastStructType)
return;
if (StructInfo != null)
DestroySubContent();
m_lastStructType = type;
StructInfo = StructInfoFactory.Create(type);
if (m_subContentParent.activeSelf)
{
ConstructSubcontent();
}
}
#region UI CONSTRUCTION
internal InputField[] m_inputs;
public override void ConstructUI(GameObject parent, GameObject subGroup)
{
base.ConstructUI(parent, subGroup);
}
public override void ConstructSubcontent()
{
base.ConstructSubcontent();
if (StructInfo == null)
{
ExplorerCore.LogWarning("Setting up subcontent but structinfo is null");
return;
}
var editorContainer = UIFactory.CreateVerticalGroup(m_subContentParent, "EditorContent", false, true, true, true, 2, new Vector4(4,4,4,4),
new Color(0.08f, 0.08f, 0.08f));
m_inputs = new InputField[StructInfo.FieldNames.Length];
for (int i = 0; i < StructInfo.FieldNames.Length; i++)
AddEditorRow(i, editorContainer);
if (Owner.CanWrite)
{
var applyBtn = UIFactory.CreateButton(editorContainer, "ApplyButton", "Apply", OnSetValue, new Color(0.2f, 0.2f, 0.2f));
UIFactory.SetLayoutElement(applyBtn.gameObject, minWidth: 175, minHeight: 25, flexibleWidth: 0);
void OnSetValue()
{
Owner.SetValue();
RefreshUIForValue();
}
}
}
internal void AddEditorRow(int index, GameObject groupObj)
{
var rowObj = UIFactory.CreateHorizontalGroup(groupObj, "EditorRow", false, true, true, true, 5, default, new Color(1, 1, 1, 0));
var label = UIFactory.CreateLabel(rowObj, "RowLabel", $"{StructInfo.FieldNames[index]}:", TextAnchor.MiddleRight, Color.cyan);
UIFactory.SetLayoutElement(label.gameObject, minWidth: 50, flexibleWidth: 0, minHeight: 25);
var inputFieldObj = UIFactory.CreateInputField(rowObj, "InputField", "...", 14, 3, 1);
UIFactory.SetLayoutElement(inputFieldObj, minWidth: 120, minHeight: 25, flexibleWidth: 0);
var inputField = inputFieldObj.GetComponent<InputField>();
m_inputs[index] = inputField;
inputField.onValueChanged.AddListener((string val) => { Value = StructInfo.SetValue(ref this.Value, index, float.Parse(val)); });
}
#endregion
}
}

View File

@ -49,8 +49,8 @@ namespace UnityExplorer.UI.InteractiveValues
// check for unity struct types
else if (typeof(Color).IsAssignableFrom(type))
return typeof(InteractiveColor);
else if (InteractiveUnityStruct.SupportsType(type))
return typeof(InteractiveUnityStruct);
else if (InteractiveFloatStruct.IsTypeSupported(type))
return typeof(InteractiveFloatStruct);
// check Transform, force InteractiveValue so they dont become InteractiveEnumerables.
else if (typeof(Transform).IsAssignableFrom(type))
return typeof(InteractiveValue);

View File

@ -273,7 +273,7 @@
<Compile Include="UI\InteractiveValues\InteractiveFlags.cs" />
<Compile Include="UI\InteractiveValues\InteractiveNumber.cs" />
<Compile Include="UI\InteractiveValues\InteractiveString.cs" />
<Compile Include="UI\InteractiveValues\InteractiveUnityStruct.cs" />
<Compile Include="UI\InteractiveValues\InteractiveFloatStruct.cs" />
<Compile Include="UI\InteractiveValues\InteractiveValue.cs" />
<Compile Include="UI\Main\BaseMenuPage.cs" />
<Compile Include="UI\Main\CSConsole\AutoCompleter.cs" />