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 _brushPropertyFields = new List(); 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(); 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 read, Action write) { var stringFieldInstance = StringField.Instantiate(); _brushPropertyFields.Add(stringFieldInstance); stringFieldInstance.transform.Find("Label").GetComponent().text = name; var inputField = stringFieldInstance.GetComponentInChildren(); inputField.onValueChanged.AddListener(val => write(val)); RefreshPropertyValues += (sender, args) => inputField.text = read(); } public void Inspect(string name, Func read, Action write) { var freeFloatFieldInstance = FreeFloatField.Instantiate(); _brushPropertyFields.Add(freeFloatFieldInstance); freeFloatFieldInstance.transform.Find("Label").GetComponent().text = name; var inputField = freeFloatFieldInstance.GetComponentInChildren(); inputField.onValueChanged.AddListener(val => write(float.Parse(val))); RefreshPropertyValues += (sender, args) => inputField.text = read().ToString(CultureInfo.InvariantCulture); } public void Inspect(string name, Func read, Action write, float min, float max) { var rangedFloatFieldInstance = RangedFloatField.Instantiate(); _brushPropertyFields.Add(rangedFloatFieldInstance); rangedFloatFieldInstance.transform.Find("Label").GetComponent().text = name; var slider = rangedFloatFieldInstance.GetComponentInChildren(); slider.minValue = min; slider.maxValue = max; slider.onValueChanged.AddListener(val => write(val)); RefreshPropertyValues += (sender, args) => slider.value = read(); } public void Inspect(string name, Func read, Action write) { var colorFieldInstance = ColorField.Instantiate(); _brushPropertyFields.Add(colorFieldInstance); colorFieldInstance.transform.Find("Label").GetComponent().text = name; var colorButton = colorFieldInstance.GetComponentInChildren(); colorButton.OnColorChanged.AddListener(col => write(col)); RefreshPropertyValues += (sender, args) => colorButton.GetComponent().color = read(); } public void Inspect(string name, Func read, Action write) { var boolFieldInstance = BoolField.Instantiate(); _brushPropertyFields.Add(boolFieldInstance); boolFieldInstance.transform.Find("Label").GetComponent().text = name; var toggle = boolFieldInstance.GetComponentInChildren(); toggle.onValueChanged.AddListener(val => write(val)); RefreshPropertyValues += (sender, args) => toggle.isOn = read(); } public void Inspect(string name, Func read, Action write, string[] enumOptions) { var enumFieldInstance = EnumField.Instantiate(); _brushPropertyFields.Add(enumFieldInstance); enumFieldInstance.transform.Find("Label").GetComponent().text = name; var dropDown = enumFieldInstance.GetComponentInChildren(); 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(); depthInputField.text = brush.Depth.ToString(CultureInfo.InvariantCulture); depthInputField.onValueChanged.RemoveAllListeners(); depthInputField.onValueChanged.AddListener(val => brush.Depth = float.Parse(val)); var opacitySlider = OpacityPropertyRect.GetComponentInChildren(); opacitySlider.minValue = 0; opacitySlider.maxValue = 0; opacitySlider.value = brush.Opacity; opacitySlider.onValueChanged.RemoveAllListeners(); opacitySlider.onValueChanged.AddListener(val => brush.Opacity = val); var softnessSlider = SoftnessPropertyRect.GetComponentInChildren(); 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(); 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().text = SplitCamelCase(inspectedShaderProperty.Trim('_')); fieldInstance.GetComponentInChildren().onValueChanged .AddListener(val => shaderBrush.DisplacementMaterial.SetFloat(inspectedShaderProperty, float.Parse(val))); } } var substanceBrush = brush as BrushManager.SubstanceBrush; if (substanceBrush != null) { var tilingInputField = TilingPropertyRect.GetComponentInChildren(); 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().text = "Resolution"; var resolutionDropdown = resolutionField.GetComponentInChildren(); Func 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().text = proceduralPropertyDescription.label; var toggle = boolFieldInstance.GetComponentInChildren(); 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().text = proceduralPropertyDescription.label; var slider = rangedFloatFieldInstance.GetComponentInChildren(); 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().text = proceduralPropertyDescription.label; var inputField = freeFloatFieldInstance.GetComponentInChildren(); 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().text = proceduralPropertyDescription.label; var colorButton = colorFieldInstance.GetComponentInChildren(); var color3 = substanceBrush.Substance.GetProceduralColor(proceduralPropertyDescription.name); color3.a = 1.0f; colorButton.GetComponent().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().text = proceduralPropertyDescription.label; var color4Button = color4FieldInstance.GetComponentInChildren(); color4Button.GetComponent().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().text = proceduralPropertyDescription.label; var dropDown = enumFieldInstance.GetComponentInChildren(); 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(); brushTypeDropdown.options = new List { 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(); 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; } }