using System; using UnityEngine; using UnityEngine.UI; using UnityEngine.UI.MPUIKIT; #if UNITY_EDITOR using UnityEditor; #endif namespace MPUIKIT { [AddComponentMenu("UI/MPUI/MPImage")] public class MPImage : Image { #region Constants public const string MpShaderName = "MPUI/Procedural Image"; #endregion #region SerializedFields [SerializeField] private DrawShape m_DrawShape = DrawShape.None; [SerializeField] private Type m_ImageType = Type.Simple; [SerializeField] private MaterialMode m_MaterialMode; [SerializeField] private float m_StrokeWidth; [SerializeField] private float m_OutlineWidth; [SerializeField] private Color m_OutlineColor = Color.black; [SerializeField] private float m_FalloffDistance = 0.5f; [SerializeField] private bool m_ConstrainRotation = true; [SerializeField] private float m_ShapeRotation; [SerializeField] private bool m_FlipHorizontal; [SerializeField] private bool m_FlipVertical; [SerializeField] private Triangle m_Triangle = new Triangle(); [SerializeField] private Rectangle m_Rectangle = new Rectangle(); [SerializeField] private Circle m_Circle = new Circle(); [SerializeField] private Pentagon m_Pentagon = new Pentagon(); [SerializeField] private Hexagon m_Hexagon = new Hexagon(); [SerializeField] private NStarPolygon m_NStarPolygon = new NStarPolygon(); [SerializeField] private GradientEffect m_GradientEffect = new GradientEffect(); #endregion #region Public Properties #region Draw Settings /// /// Type of the shape to be drawn. ie: Rectangle, Circle /// public DrawShape DrawShape { get => m_DrawShape; set { m_DrawShape = value; if (material == m_Material) { m_Material.SetInt(SpDrawShape, (int) m_DrawShape); } base.SetMaterialDirty(); } } /// /// Width of the stroke for the drawn shape. 0 is no stroke. /// public float StrokeWidth { get => m_StrokeWidth; set { m_StrokeWidth = value; m_StrokeWidth = m_StrokeWidth < 0 ? 0 : m_StrokeWidth; if (material == m_Material) { m_Material.SetFloat(SpStrokeWidth, m_StrokeWidth); } base.SetMaterialDirty(); } } /// /// Width of the outline for the drawn shape. 0 is no outline. /// public float OutlineWidth { get => m_OutlineWidth; set { m_OutlineWidth = value; m_OutlineWidth = m_OutlineWidth < 0 ? 0 : m_OutlineWidth; if (m_Material == material) { m_Material.SetFloat(SpOutlineWidth, m_OutlineWidth); } base.SetMaterialDirty(); } } /// /// Color of the Outline. Has no effect is teh value of the OutlineWidth is 0 /// public Color OutlineColor { get => m_OutlineColor; set { m_OutlineColor = value; if (m_Material == material) { m_Material.SetColor(SpOutlineColor, m_OutlineColor); } base.SetMaterialDirty(); } } /// /// Edge falloff distance of the shape /// public float FalloffDistance { get { return m_FalloffDistance; } set { m_FalloffDistance = Mathf.Max(value, 0f); if (material == m_Material) { m_Material.SetFloat(SpFalloffDistance, m_FalloffDistance); } base.SetMaterialDirty(); } } /// /// Constrains rotation to 0, 90, 270 degrees angle if set to true. But the width and height of the shape /// is replaced as necessary to avoid clipping. /// If set to false, any shapes can be rotated in any arbitrary angle but will often result in /// clipping of the shape. /// public bool ConstrainRotation { get { return m_ConstrainRotation; } set { m_ConstrainRotation = value; if (m_Material == material) { m_Material.SetInt(SpConstrainedRotation, value?1:0); } if (value) { m_ShapeRotation = ConstrainRotationValue(m_ShapeRotation); } base.SetVerticesDirty(); base.SetMaterialDirty(); } } private float ConstrainRotationValue(float val) { float finalRotation = val - val % 90; if (Mathf.Abs(finalRotation) >= 360) finalRotation = 0; return finalRotation; } /// /// Rotation of the shape. /// public float ShapeRotation { get { return m_ShapeRotation; } set { m_ShapeRotation = m_ConstrainRotation ? ConstrainRotationValue(value) : value; if (m_Material == material) { m_Material.SetFloat(SpShapeRotation, m_ShapeRotation); } base.SetMaterialDirty(); } } /// /// Flips the shape horizontally. /// public bool FlipHorizontal { get { return m_FlipHorizontal; } set { m_FlipHorizontal = value; if (m_Material == material) { m_Material.SetInt(SpFlipHorizontal, m_FlipHorizontal ? 1 : 0); } base.SetMaterialDirty(); } } /// /// Flips the shape vertically /// public bool FlipVertical { get { return m_FlipVertical; } set { m_FlipVertical = value; if (m_Material == material) { m_Material.SetInt(SpFlipVertical, m_FlipVertical ? 1 : 0); } base.SetMaterialDirty(); } } /// /// Defines what material type of use to render the shape. Dynamic or Shared. /// Default is Dynamic and will issue one draw call per image object. If set to shared, assigned /// material in the material slot will be used to render the image. It will fallback to dynamic /// if no material in the material slot is assigned /// public MaterialMode MaterialMode { get { return m_MaterialMode; } set { if (m_MaterialMode == value) return; m_MaterialMode = value; InitializeComponents(); if (material == m_Material) { InitValuesFromSharedMaterial(); #if UNITY_EDITOR _parseAgainOnValidate = true; #endif } base.SetMaterialDirty(); } } /// /// Shared material to use to render the shape. the material must use the "MPUI/Procedural Sprite" shader /// public override Material material { get { if (m_Material && m_MaterialMode == MaterialMode.Shared) { return m_Material; } return DynamicMaterial; } set { m_Material = value; if (m_Material && m_MaterialMode == MaterialMode.Shared && m_Material.shader.name == MpShaderName) { InitValuesFromSharedMaterial(); #if UNITY_EDITOR _parseAgainOnValidate = true; #endif } InitializeComponents(); base.SetMaterialDirty(); } } // ReSharper disable once InconsistentNaming /// /// Type of the image. Only two types are supported. Simple and Filled. /// Default and fallback value is Simple. /// public new Type type { get => m_ImageType; set { if (m_ImageType == value) return; switch (value) { case Type.Simple: case Type.Filled: m_ImageType = value; break; case Type.Tiled: case Type.Sliced: break; default: throw new ArgumentOutOfRangeException(value.ToString(), value, null); } base.type = m_ImageType; } } #endregion public Triangle Triangle { get => m_Triangle; set { m_Triangle = value; SetMaterialDirty(); } } public Rectangle Rectangle { get => m_Rectangle; set { m_Rectangle = value; SetMaterialDirty(); } } public Circle Circle{ get => m_Circle; set { m_Circle = value; SetMaterialDirty(); } } public Pentagon Pentagon { get => m_Pentagon; set { m_Pentagon = value; SetMaterialDirty(); } } public Hexagon Hexagon { get => m_Hexagon; set { m_Hexagon = value; SetMaterialDirty(); } } public NStarPolygon NStarPolygon { get => m_NStarPolygon; set { m_NStarPolygon = value; SetMaterialDirty(); } } public GradientEffect GradientEffect { get => m_GradientEffect; set { m_GradientEffect = value; SetMaterialDirty(); } } #endregion #region Private Variables private Material _dynamicMaterial; private Material DynamicMaterial { get { if (_dynamicMaterial == null) { _dynamicMaterial = new Material(Shader.Find(MpShaderName)); _dynamicMaterial.name += " [Dynamic]"; } return _dynamicMaterial; } } #if UNITY_EDITOR private bool _parseAgainOnValidate; #endif private Sprite ActiveSprite { get { Sprite overrideSprite1 = overrideSprite; return overrideSprite1 != null ? overrideSprite1 : sprite; } } #endregion #region Material PropertyIds private static readonly int SpPixelWorldScale = Shader.PropertyToID("_PixelWorldScale"); private static readonly int SpDrawShape = Shader.PropertyToID("_DrawShape"); private static readonly int SpStrokeWidth = Shader.PropertyToID("_StrokeWidth"); private static readonly int SpOutlineWidth = Shader.PropertyToID("_OutlineWidth"); private static readonly int SpOutlineColor = Shader.PropertyToID("_OutlineColor"); private static readonly int SpFalloffDistance = Shader.PropertyToID("_FalloffDistance"); private static readonly int SpShapeRotation = Shader.PropertyToID("_ShapeRotation"); private static readonly int SpConstrainedRotation = Shader.PropertyToID("_ConstrainRotation"); private static readonly int SpFlipHorizontal = Shader.PropertyToID("_FlipHorizontal"); private static readonly int SpFlipVertical = Shader.PropertyToID("_FlipVertical"); #endregion #if UNITY_EDITOR public void UpdateSerializedValuesFromSharedMaterial() { if (m_Material && MaterialMode == MaterialMode.Shared) { InitValuesFromSharedMaterial(); base.SetMaterialDirty(); } } protected override void OnValidate() { InitializeComponents(); if (_parseAgainOnValidate) { InitValuesFromSharedMaterial(); _parseAgainOnValidate = false; } DrawShape = m_DrawShape; StrokeWidth = m_StrokeWidth; OutlineWidth = m_OutlineWidth; OutlineColor = m_OutlineColor; FalloffDistance = m_FalloffDistance; ConstrainRotation = m_ConstrainRotation; ShapeRotation = m_ShapeRotation; FlipHorizontal = m_FlipHorizontal; FlipVertical = m_FlipVertical; m_Triangle.OnValidate(); m_Circle.OnValidate(); m_Rectangle.OnValidate(); m_Pentagon.OnValidate(); m_Hexagon.OnValidate(); m_NStarPolygon.OnValidate(); m_GradientEffect.OnValidate(); base.OnValidate(); base.SetMaterialDirty(); } #endif private void InitializeComponents() { m_Circle.Init(m_Material, material, rectTransform); m_Triangle.Init(m_Material, material, rectTransform); m_Rectangle.Init(m_Material, material, rectTransform); m_Pentagon.Init(m_Material, material, rectTransform); m_Hexagon.Init(m_Material, material, rectTransform); m_NStarPolygon.Init(m_Material, material, rectTransform); m_GradientEffect.Init(m_Material, material, rectTransform); } void FixAdditionalShaderChannelsInCanvas() { Canvas c = canvas; if(canvas == null) return; AdditionalCanvasShaderChannels additionalShaderChannels = c.additionalShaderChannels; additionalShaderChannels |= AdditionalCanvasShaderChannels.TexCoord1; additionalShaderChannels |= AdditionalCanvasShaderChannels.TexCoord2; c.additionalShaderChannels = additionalShaderChannels; } #if UNITY_EDITOR protected override void Reset() { InitializeComponents(); base.Reset(); } #else void Reset() { InitializeComponents(); } #endif protected override void Awake() { base.Awake(); Init(); } public void Init() { InitializeComponents(); FixAdditionalShaderChannelsInCanvas(); if (m_Material && MaterialMode == MaterialMode.Shared) { InitValuesFromSharedMaterial(); } ListenToComponentChanges(true); base.SetAllDirty(); } protected override void OnDestroy() { ListenToComponentChanges(false); base.OnDestroy(); } protected void ListenToComponentChanges(bool toggle) { if (toggle) { m_Circle.OnComponentSettingsChanged += OnComponentSettingsChanged; m_Triangle.OnComponentSettingsChanged += OnComponentSettingsChanged; m_Rectangle.OnComponentSettingsChanged += OnComponentSettingsChanged; m_Pentagon.OnComponentSettingsChanged += OnComponentSettingsChanged; m_Hexagon.OnComponentSettingsChanged += OnComponentSettingsChanged; m_NStarPolygon.OnComponentSettingsChanged += OnComponentSettingsChanged; m_GradientEffect.OnComponentSettingsChanged += OnComponentSettingsChanged; } else { m_Circle.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_Triangle.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_Rectangle.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_Pentagon.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_Hexagon.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_NStarPolygon.OnComponentSettingsChanged -= OnComponentSettingsChanged; m_GradientEffect.OnComponentSettingsChanged += OnComponentSettingsChanged; } } protected override void OnTransformParentChanged() { base.OnTransformParentChanged(); FixAdditionalShaderChannelsInCanvas(); } private void OnComponentSettingsChanged(object sender, EventArgs e) { base.SetMaterialDirty(); } protected override void OnRectTransformDimensionsChange() { base.OnRectTransformDimensionsChange(); m_Circle.UpdateCircleRadius(rectTransform); base.SetMaterialDirty(); } protected override void OnPopulateMesh(VertexHelper vh) { switch (type) { case Type.Simple: case Type.Sliced: case Type.Tiled: MPImageHelper.GenerateSimpleSprite(vh, preserveAspect, canvas, rectTransform, ActiveSprite, color, m_FalloffDistance); break; case Type.Filled: MPImageHelper.GenerateFilledSprite(vh, preserveAspect, canvas, rectTransform, ActiveSprite, color, fillMethod, fillAmount, fillOrigin, fillClockwise, m_FalloffDistance); break; default: throw new ArgumentOutOfRangeException(); } } public override Material GetModifiedMaterial(Material baseMaterial) { Material mat = base.GetModifiedMaterial(baseMaterial); if (m_Material && MaterialMode == MaterialMode.Shared) { InitValuesFromSharedMaterial(); } DisableAllMaterialKeywords(mat); RectTransform rt = rectTransform; if (DrawShape != DrawShape.None) { mat.SetFloat(SpOutlineWidth, m_OutlineWidth); mat.SetFloat(SpStrokeWidth, m_StrokeWidth); mat.SetColor(SpOutlineColor, OutlineColor); mat.SetFloat(SpFalloffDistance, FalloffDistance); float pixelSize = 1/Mathf.Max(0, FalloffDistance); mat.SetFloat(SpPixelWorldScale, Mathf.Clamp(pixelSize, 0f, 999999f)); if (m_StrokeWidth > 0 && m_OutlineWidth > 0) { mat.EnableKeyword("OUTLINED_STROKE"); } else { if (m_StrokeWidth > 0) { mat.EnableKeyword("STROKE"); } else if (m_OutlineWidth > 0) { mat.EnableKeyword("OUTLINED"); } else { mat.DisableKeyword("OUTLINED_STROKE"); mat.DisableKeyword("STROKE"); mat.DisableKeyword("OUTLINED"); } } } m_Triangle.ModifyMaterial(ref mat); m_Circle.ModifyMaterial(ref mat, m_FalloffDistance); m_Rectangle.ModifyMaterial(ref mat); m_Pentagon.ModifyMaterial(ref mat); m_Hexagon.ModifyMaterial(ref mat); m_NStarPolygon.ModifyMaterial(ref mat); m_GradientEffect.ModifyMaterial(ref mat); switch (DrawShape) { case DrawShape.None: mat.DisableKeyword("CIRCLE"); mat.DisableKeyword("TRIANGLE"); mat.DisableKeyword("RECTANGLE"); mat.DisableKeyword("PENTAGON"); mat.DisableKeyword("HEXAGON"); mat.DisableKeyword("NSTAR_POLYGON"); break; case DrawShape.Circle: mat.EnableKeyword("CIRCLE"); break; case DrawShape.Triangle: mat.EnableKeyword("TRIANGLE"); break; case DrawShape.Rectangle: mat.EnableKeyword("RECTANGLE"); break; case DrawShape.Pentagon: mat.EnableKeyword("PENTAGON"); break; case DrawShape.NStarPolygon: mat.EnableKeyword("NSTAR_POLYGON"); break; case DrawShape.Hexagon: mat.EnableKeyword("HEXAGON"); break; default: throw new ArgumentOutOfRangeException(); } mat.SetInt(SpDrawShape, (int) DrawShape); mat.SetInt(SpFlipHorizontal, m_FlipHorizontal ? 1 : 0); mat.SetInt(SpFlipVertical, m_FlipVertical ? 1 : 0); mat.SetFloat(SpShapeRotation, m_ShapeRotation); mat.SetInt(SpConstrainedRotation, m_ConstrainRotation?1:0); return mat; } private void DisableAllMaterialKeywords(Material mat) { mat.DisableKeyword("PROCEDURAL"); mat.DisableKeyword("HYBRID"); mat.DisableKeyword("CIRCLE"); mat.DisableKeyword("TRIANGLE"); mat.DisableKeyword("RECTANGLE"); mat.DisableKeyword("PENTAGON"); mat.DisableKeyword("HEXAGON"); mat.DisableKeyword("NSTAR_POLYGON"); mat.DisableKeyword("STROKE"); mat.DisableKeyword("OUTLINED"); mat.DisableKeyword("OUTLINED_STROKE"); mat.DisableKeyword("ROUNDED_CORNERS"); mat.DisableKeyword("GRADIENT_LINEAR"); mat.DisableKeyword("GRADIENT_CORNER"); mat.DisableKeyword("GRADIENT_RADIAL"); } public void InitValuesFromSharedMaterial() { if (m_Material == null) return; Material mat = m_Material; //Debug.Log("Parsing shared mat"); //Basic Settings m_DrawShape = (DrawShape) mat.GetInt(SpDrawShape); m_StrokeWidth = mat.GetFloat(SpStrokeWidth); m_FalloffDistance = mat.GetFloat(SpFalloffDistance); m_OutlineWidth = mat.GetFloat(SpOutlineWidth); m_OutlineColor = mat.GetColor(SpOutlineColor); m_FlipHorizontal = mat.GetInt(SpFlipHorizontal) == 1; m_FlipVertical = mat.GetInt(SpFlipVertical) == 1; m_ConstrainRotation = mat.GetInt(SpConstrainedRotation) == 1; m_ShapeRotation = mat.GetFloat(SpShapeRotation); //Debug.Log($"Parsed Falloff Distance: {m_FalloffDistance}"); m_Triangle.InitValuesFromMaterial(ref mat); m_Circle.InitValuesFromMaterial(ref mat); m_Rectangle.InitValuesFromMaterial(ref mat); m_Pentagon.InitValuesFromMaterial(ref mat); m_Hexagon.InitValuesFromMaterial(ref mat); m_NStarPolygon.InitValuesFromMaterial(ref mat); //GradientEffect m_GradientEffect.InitValuesFromMaterial(ref mat); } #if UNITY_EDITOR public Material CreateMaterialAssetFromComponentSettings() { Material matAsset = new Material(Shader.Find(MpShaderName)); matAsset = GetModifiedMaterial(matAsset); string path = EditorUtility.SaveFilePanelInProject("Create Material for MPImage", "MPMaterial", "mat", "Choose location"); AssetDatabase.CreateAsset(matAsset, path); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return matAsset; } #endif } }