CultPong/Assets/Scripts/PropertiesPanel.cs
2020-09-25 07:12:38 +01:00

459 lines
18 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class PropertiesPanel : MonoBehaviour
{
public Prototype FreeFloatField;
public Prototype RangedFloatField;
public Prototype EnumField;
public Prototype BoolField;
public Prototype ColorField;
public Prototype StringField; // A beautiful town, home of the Stringsons
public Prototype Divider;
public int ResolutionExponentMinumum = 6;
public int ResolutionExponentCount = 5;
private readonly List<Prototype> _brushPropertyFields = new List<Prototype>();
private event EventHandler RefreshPropertyValues;
public void Clear()
{
foreach (var property in _brushPropertyFields)
{
property.ReturnToPool();
}
RefreshPropertyValues = null;
}
// public void Inspect(ProceduralMaterial substance)
// {
// foreach (var property in substance.GetProceduralPropertyDescriptions())
// {
// switch (property.type)
// {
// case ProceduralPropertyType.Boolean:
// Inspect(property.label, () => substance.GetProceduralBoolean(property.name),
// b =>
// {
// substance.SetProceduralBoolean(property.name, b);
// substance.RebuildTextures();
// });
// break;
// case ProceduralPropertyType.Float:
// if (property.hasRange)
// Inspect(property.label,
// () => substance.GetProceduralFloat(property.name),
// f =>
// {
// substance.SetProceduralFloat(property.name, f);
// substance.RebuildTextures();
// },
// property.minimum,
// property.maximum);
// else
// Inspect(property.label,
// () => substance.GetProceduralFloat(property.name),
// f =>
// {
// substance.SetProceduralFloat(property.name, f);
// substance.RebuildTextures();
// });
// break;
// case ProceduralPropertyType.Vector2:
// break;
// case ProceduralPropertyType.Vector3:
// break;
// case ProceduralPropertyType.Vector4:
// break;
// case ProceduralPropertyType.Color3:
// Inspect(property.label,
// () =>
// {
// var c = substance.GetProceduralColor(property.name);
// c.a = 1.0f;
// return c;
// },
// c =>
// {
// substance.SetProceduralColor(property.name, c);
// substance.RebuildTextures();
// });
// break;
// case ProceduralPropertyType.Color4:
// Inspect(property.label,
// () => substance.GetProceduralColor(property.name),
// c =>
// {
// substance.SetProceduralColor(property.name, c);
// substance.RebuildTextures();
// });
// break;
// case ProceduralPropertyType.Enum:
// Inspect(property.label,
// () => substance.GetProceduralEnum(property.name),
// i => substance.SetProceduralEnum(property.name, i),
// property.enumOptions);
// break;
// case ProceduralPropertyType.Texture:
// break;
// default:
// throw new ArgumentOutOfRangeException();
// }
// }
//
// RefreshValues();
// }
public void Inspect(object obj, bool inspectablesOnly = false)
{
foreach (var field in obj.GetType().GetFields())
Inspect(obj, field, inspectablesOnly);
RefreshValues();
}
public void Inspect(object obj, FieldInfo field, bool inspectablesOnly = false)
{
var inspectable = field.GetCustomAttribute<InspectableAttribute>();
if (inspectable == null) return;
if (field.FieldType == typeof(float))
{
if (inspectable is RangedFloatInspectableAttribute && RangedFloatField!=null)
{
var range = inspectable as RangedFloatInspectableAttribute;
Inspect(field.Name, () => (float) field.GetValue(obj), f => field.SetValue(obj, f), range.Min, range.Max);
}
else if(FreeFloatField!=null)
Inspect(field.Name, () => (float) field.GetValue(obj), f => field.SetValue(obj, f));
}
else if (field.FieldType.IsEnum && EnumField!=null) Inspect(field.Name, () => (int) field.GetValue(obj), i => field.SetValue(obj, i), Enum.GetNames(field.FieldType));
else if (field.FieldType == typeof(Color) && ColorField!=null) Inspect(field.Name, () => (Color) field.GetValue(obj), c => field.SetValue(obj, c));
else if (field.FieldType == typeof(bool) && BoolField!=null) Inspect(field.Name, () => (bool) field.GetValue(obj), b => field.SetValue(obj, b));
else Debug.Log($"Field \"{field.Name}\" has unknown type {field.FieldType.Name}. No inspector was generated.");
}
public void Inspect(string name, Func<string> read, Action<string> write)
{
var stringFieldInstance = StringField.Instantiate<Prototype>();
_brushPropertyFields.Add(stringFieldInstance);
stringFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var inputField = stringFieldInstance.GetComponentInChildren<TMP_InputField>();
inputField.onValueChanged.AddListener(val => write(val));
RefreshPropertyValues += (sender, args) => inputField.text = read();
}
public void Inspect(string name, Func<float> read, Action<float> write)
{
var freeFloatFieldInstance = FreeFloatField.Instantiate<Prototype>();
_brushPropertyFields.Add(freeFloatFieldInstance);
freeFloatFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var inputField = freeFloatFieldInstance.GetComponentInChildren<TMP_InputField>();
inputField.onValueChanged.AddListener(val => write(float.Parse(val)));
RefreshPropertyValues += (sender, args) => inputField.text = read().ToString(CultureInfo.InvariantCulture);
}
public void Inspect(string name, Func<float> read, Action<float> write, float min, float max)
{
var rangedFloatFieldInstance = RangedFloatField.Instantiate<Prototype>();
_brushPropertyFields.Add(rangedFloatFieldInstance);
rangedFloatFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var slider = rangedFloatFieldInstance.GetComponentInChildren<Slider>();
slider.minValue = min;
slider.maxValue = max;
slider.onValueChanged.AddListener(val => write(val));
RefreshPropertyValues += (sender, args) => slider.value = read();
}
public void Inspect(string name, Func<Color> read, Action<Color> write)
{
var colorFieldInstance = ColorField.Instantiate<Prototype>();
_brushPropertyFields.Add(colorFieldInstance);
colorFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var colorButton = colorFieldInstance.GetComponentInChildren<ColorButton>();
colorButton.OnColorChanged.AddListener(col => write(col));
RefreshPropertyValues += (sender, args) => colorButton.GetComponent<Image>().color = read();
}
public void Inspect(string name, Func<bool> read, Action<bool> write)
{
var boolFieldInstance = BoolField.Instantiate<Prototype>();
_brushPropertyFields.Add(boolFieldInstance);
boolFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var toggle = boolFieldInstance.GetComponentInChildren<Toggle>();
toggle.onValueChanged.AddListener(val => write(val));
RefreshPropertyValues += (sender, args) => toggle.isOn = read();
}
public void Inspect(string name, Func<int> read, Action<int> write, string[] enumOptions)
{
var enumFieldInstance = EnumField.Instantiate<Prototype>();
_brushPropertyFields.Add(enumFieldInstance);
enumFieldInstance.transform.Find("Label").GetComponent<TextMeshProUGUI>().text = name;
var dropDown = enumFieldInstance.GetComponentInChildren<TMP_Dropdown>();
dropDown.options = enumOptions.Select(s => new TMP_Dropdown.OptionData(s)).ToList();
dropDown.onValueChanged.AddListener(val => write(val));
RefreshPropertyValues += (sender, args) => dropDown.value = read();
}
/*
public void RefreshBrushProperties()
{
var brush = BrushManager.Instance.SelectedLayer.LayerBrush;
foreach (var brushProperty in _brushPropertyFields)
Destroy(brushProperty.gameObject);
_brushPropertyFields.Clear();
var depthInputField = DepthPropertyRect.GetComponentInChildren<TMP_InputField>();
depthInputField.text = brush.Depth.ToString(CultureInfo.InvariantCulture);
depthInputField.onValueChanged.RemoveAllListeners();
depthInputField.onValueChanged.AddListener(val => brush.Depth = float.Parse(val));
var opacitySlider = OpacityPropertyRect.GetComponentInChildren<Slider>();
opacitySlider.minValue = 0;
opacitySlider.maxValue = 0;
opacitySlider.value = brush.Opacity;
opacitySlider.onValueChanged.RemoveAllListeners();
opacitySlider.onValueChanged.AddListener(val => brush.Opacity = val);
var softnessSlider = SoftnessPropertyRect.GetComponentInChildren<Slider>();
softnessSlider.minValue = .05f;
softnessSlider.maxValue = 5;
softnessSlider.value = brush.Softness;
softnessSlider.onValueChanged.RemoveAllListeners();
softnessSlider.onValueChanged.AddListener(val => brush.Softness = val);
var shaderBrush = brush as BrushManager.ShaderBrush;
if (shaderBrush != null)
{
if (shaderBrush.BrushMaterial.HasProperty("_Tiling"))
{
TilingPropertyRect.gameObject.SetActive(true);
Debug.Log("Tiling property found. Enabling inspector.");
var inputField = TilingPropertyRect.GetComponentInChildren<TMP_InputField>();
inputField.text = shaderBrush.Tiling.ToString(CultureInfo.InvariantCulture);
inputField.onValueChanged.RemoveAllListeners();
inputField.onValueChanged.AddListener(val => shaderBrush.Tiling = float.Parse(val));
}
else
{
Debug.Log("Tiling property not found. Disabling inspector.");
TilingPropertyRect.gameObject.SetActive(false);
}
foreach (var inspectedShaderProperty in BrushManager.Instance.InspectedShaderProperties.Where(sp =>
shaderBrush.DisplacementMaterial.HasProperty(sp)))
{
var fieldInstance = Instantiate(FreeFloatFieldPrefab, ContentRect);
_brushPropertyFields.Add(fieldInstance);
fieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = SplitCamelCase(inspectedShaderProperty.Trim('_'));
fieldInstance.GetComponentInChildren<TMP_InputField>().onValueChanged
.AddListener(val => shaderBrush.DisplacementMaterial.SetFloat(inspectedShaderProperty, float.Parse(val)));
}
}
var substanceBrush = brush as BrushManager.SubstanceBrush;
if (substanceBrush != null)
{
var tilingInputField = TilingPropertyRect.GetComponentInChildren<TMP_InputField>();
tilingInputField.text = substanceBrush.Tiling.ToString(CultureInfo.InvariantCulture);
tilingInputField.onValueChanged.RemoveAllListeners();
tilingInputField.onValueChanged.AddListener(val => substanceBrush.Tiling = float.Parse(val));
var resolutionField = Instantiate(EnumFieldPrefab, ContentRect);
_brushPropertyFields.Add(resolutionField);
resolutionField.Find("Label").GetComponent<TextMeshProUGUI>().text = "Resolution";
var resolutionDropdown = resolutionField.GetComponentInChildren<TMP_Dropdown>();
Func<int, int> intPow = i => Mathf.ClosestPowerOfTwo((int) Mathf.Pow(2, i + SubstanceResolutionExponentMinumum));
resolutionDropdown.options =
Enumerable.Range(0, SubstanceResolutionExponentCount).Select(i => intPow(i))
.Select(pow => new TMP_Dropdown.OptionData($"{pow}x{pow}")).ToList();
resolutionDropdown.onValueChanged.AddListener(i => Debug.Log($"Selected res = {intPow(i)}"));
Debug.Log(substanceBrush.Substance.GetProceduralPropertyDescriptions().Aggregate("Substance Properties:",
(s, desc) => $"{s}\n\"{desc.name}\": {Enum.GetName(typeof(ProceduralPropertyType), desc.type)}"));
foreach (var proceduralPropertyDescription in substanceBrush.Substance.GetProceduralPropertyDescriptions()
.Where(ppd => !BrushManager.Instance.IgnoreSubstanceProperties.Any(isp => isp.Equals(ppd.name))))
{
switch (proceduralPropertyDescription.type)
{
case ProceduralPropertyType.Boolean:
var boolFieldInstance = Instantiate(BoolFieldPrefab, ContentRect);
_brushPropertyFields.Add(boolFieldInstance);
boolFieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var toggle = boolFieldInstance.GetComponentInChildren<Toggle>();
toggle.isOn = substanceBrush.Substance.GetProceduralBoolean(proceduralPropertyDescription.name);
toggle.onValueChanged.AddListener(val =>
{
substanceBrush.Substance.SetProceduralBoolean(proceduralPropertyDescription.name, val);
substanceBrush.Substance.RebuildTextures();
});
break;
case ProceduralPropertyType.Float:
if (proceduralPropertyDescription.hasRange)
{
var rangedFloatFieldInstance = Instantiate(RangedFloatFieldPrefab, ContentRect);
_brushPropertyFields.Add(rangedFloatFieldInstance);
rangedFloatFieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var slider = rangedFloatFieldInstance.GetComponentInChildren<Slider>();
slider.minValue = proceduralPropertyDescription.minimum;
slider.maxValue = proceduralPropertyDescription.maximum;
slider.value = substanceBrush.Substance.GetProceduralFloat(proceduralPropertyDescription.name);
slider.onValueChanged.AddListener(val =>
{
substanceBrush.Substance.SetProceduralFloat(proceduralPropertyDescription.name,val);
substanceBrush.Substance.RebuildTextures();
});
}
else
{
var freeFloatFieldInstance = Instantiate(FreeFloatFieldPrefab, ContentRect);
_brushPropertyFields.Add(freeFloatFieldInstance);
freeFloatFieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var inputField = freeFloatFieldInstance.GetComponentInChildren<TMP_InputField>();
inputField.text = substanceBrush.Substance.GetProceduralFloat(proceduralPropertyDescription.name).ToString(CultureInfo.InvariantCulture);
inputField.onValueChanged.AddListener(val =>
{
substanceBrush.Substance.SetProceduralFloat(proceduralPropertyDescription.name,float.Parse(val));
substanceBrush.Substance.RebuildTextures();
});
}
break;
case ProceduralPropertyType.Vector2:
break;
case ProceduralPropertyType.Vector3:
break;
case ProceduralPropertyType.Vector4:
break;
case ProceduralPropertyType.Color3:
var colorFieldInstance = Instantiate(ColorFieldPrefab, ContentRect);
_brushPropertyFields.Add(colorFieldInstance);
colorFieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var colorButton = colorFieldInstance.GetComponentInChildren<ColorButton>();
var color3 = substanceBrush.Substance.GetProceduralColor(proceduralPropertyDescription.name);
color3.a = 1.0f;
colorButton.GetComponent<Image>().color = color3;
colorButton.OnColorChanged.AddListener(col =>
{
substanceBrush.Substance.SetProceduralColor(proceduralPropertyDescription.name, col);
substanceBrush.Substance.RebuildTextures();
});
break;
case ProceduralPropertyType.Color4:
var color4FieldInstance = Instantiate(ColorFieldPrefab, ContentRect);
_brushPropertyFields.Add(color4FieldInstance);
color4FieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var color4Button = color4FieldInstance.GetComponentInChildren<ColorButton>();
color4Button.GetComponent<Image>().color = substanceBrush.Substance.GetProceduralColor(proceduralPropertyDescription.name);
color4Button.OnColorChanged.AddListener(col =>
{
substanceBrush.Substance.SetProceduralColor(proceduralPropertyDescription.name, col);
substanceBrush.Substance.RebuildTextures();
});
break;
case ProceduralPropertyType.Enum:
var enumFieldInstance = Instantiate(EnumFieldPrefab, ContentRect);
_brushPropertyFields.Add(enumFieldInstance);
enumFieldInstance.Find("Label").GetComponent<TextMeshProUGUI>().text = proceduralPropertyDescription.label;
var dropDown = enumFieldInstance.GetComponentInChildren<TMP_Dropdown>();
dropDown.options = proceduralPropertyDescription.enumOptions.Select(s => new TMP_Dropdown.OptionData(s)).ToList();
dropDown.value = substanceBrush.Substance.GetProceduralEnum(proceduralPropertyDescription.name);
dropDown.onValueChanged.AddListener(val =>
{
substanceBrush.Substance.SetProceduralEnum(proceduralPropertyDescription.name,val);
substanceBrush.Substance.RebuildTextures();
});
break;
case ProceduralPropertyType.Texture:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}*/
public static string SplitCamelCase( string str )
{
return Regex.Replace(
Regex.Replace(
str,
@"(\P{Ll})(\P{Ll}\p{Ll})",
"$1 $2"
),
@"(\p{Ll})(\P{Ll})",
"$1 $2"
);
}
// Use this for initialization
void Start ()
{
/*
var brushTypeDropdown = BrushTypePropertyRect.GetComponentInChildren<TMP_Dropdown>();
brushTypeDropdown.options = new List<TMP_Dropdown.OptionData>
{
new TMP_Dropdown.OptionData("Shader"),
new TMP_Dropdown.OptionData("Substance")
};
brushTypeDropdown.onValueChanged.AddListener(brushType =>
{
if (brushType == 0)
BrushManager.Instance.SelectedLayer.LayerBrush = new BrushManager.ShaderBrush(BrushManager.Instance.BrushShaders.First());
else if (brushType == 1)
BrushManager.Instance.SelectedLayer.LayerBrush = new BrushManager.SubstanceBrush(BrushManager.Instance.DefaultSubstance);
RefreshBrushProperties();
});
var sizeInputField = SizePropertyRect.GetComponentInChildren<TMP_InputField>();
sizeInputField.text = BrushManager.Instance.SplatSize.ToString(CultureInfo.InvariantCulture);
sizeInputField.onValueChanged.AddListener(str => BrushManager.Instance.SplatSize = float.Parse(str));
*/
}
// Update is called once per frame
void Update () {
}
public void RefreshValues()
{
RefreshPropertyValues?.Invoke(this, EventArgs.Empty);
}
}
[AttributeUsage(AttributeTargets.Field)]
public class InspectableAttribute : Attribute
{
}
public class RangedFloatInspectableAttribute : InspectableAttribute
{
public readonly float Min, Max;
public RangedFloatInspectableAttribute(float min, float max)
{
Min = min;
Max = max;
}
}