diff --git a/Editor.meta b/Editor.meta
new file mode 100644
index 0000000..46f3536
--- /dev/null
+++ b/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 07f120a4700a2d2468e29d1ec6cac28f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/GradientImageEditor.cs b/Editor/GradientImageEditor.cs
new file mode 100644
index 0000000..8f65459
--- /dev/null
+++ b/Editor/GradientImageEditor.cs
@@ -0,0 +1,163 @@
+using System.Linq;
+using UnityEditor;
+using UnityEditor.AnimatedValues;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace Gilzoide.GradientRect.Editor
+{
+ [CustomEditor(typeof(GradientImage))]
+ public class GradientImageEditor : GradientRectEditor
+ {
+ private SerializedProperty _sprite;
+ private SerializedProperty _type;
+ private SerializedProperty _fillCenter;
+ private SerializedProperty _pixelsPerUnitMultiplier;
+
+ private AnimBool m_ShowSlicedOrTiled;
+ private AnimBool m_ShowSliced;
+ private AnimBool m_ShowTiled;
+ private AnimBool m_ShowFilled;
+
+ private AnimBool m_ShowType;
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+
+ _sprite = serializedObject.FindProperty("_sprite");
+ _type = serializedObject.FindProperty("_type");
+ _fillCenter = serializedObject.FindProperty("_fillCenter");
+ _pixelsPerUnitMultiplier = serializedObject.FindProperty("_pixelsPerUnitMultiplier");
+
+ Image.Type enumValueIndex = (Image.Type)_type.enumValueIndex;
+
+ m_ShowSlicedOrTiled = new AnimBool(!_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Sliced);
+ m_ShowSlicedOrTiled.valueChanged.AddListener(Repaint);
+
+ m_ShowSliced = new AnimBool(!_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Sliced);
+ m_ShowSliced.valueChanged.AddListener(Repaint);
+
+ m_ShowTiled = new AnimBool(!_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Tiled);
+ m_ShowTiled.valueChanged.AddListener(Repaint);
+
+ m_ShowFilled = new AnimBool(!_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Filled);
+ m_ShowFilled.valueChanged.AddListener(Repaint);
+
+ m_ShowType = new AnimBool(_sprite.objectReferenceValue != null);
+ m_ShowType.valueChanged.AddListener(Repaint);
+ }
+
+ protected override void OnDisable()
+ {
+ base.OnDisable();
+
+ m_ShowType.valueChanged.RemoveListener(Repaint);
+ m_ShowSlicedOrTiled.valueChanged.RemoveListener(Repaint);
+ m_ShowSliced.valueChanged.RemoveListener(Repaint);
+
+ m_ShowTiled.valueChanged.RemoveListener(Repaint);
+ m_ShowFilled.valueChanged.RemoveListener(Repaint);
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ SpriteGUI();
+
+ AppearanceControlsGUI();
+ DrawGradientPropertys();
+ RaycastControlsGUI();
+ MaskableControlsGUI();
+
+ m_ShowType.target = _sprite.objectReferenceValue != null;
+ if (EditorGUILayout.BeginFadeGroup(m_ShowType.faded))
+ {
+ TypeGUI();
+ }
+ EditorGUILayout.EndFadeGroup();
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ protected void SpriteGUI()
+ {
+ EditorGUI.BeginChangeCheck();
+ EditorGUILayout.PropertyField(_sprite);
+ if (!EditorGUI.EndChangeCheck())
+ {
+ return;
+ }
+
+ Sprite sprite = _sprite.objectReferenceValue as Sprite;
+ if (sprite)
+ {
+ Image.Type enumValueIndex = (Image.Type)_type.enumValueIndex;
+ if (sprite.border.SqrMagnitude() > 0f)
+ {
+ _type.enumValueIndex = 1;
+ }
+ else if (enumValueIndex == Image.Type.Sliced)
+ {
+ _type.enumValueIndex = 0;
+ }
+ }
+ }
+
+ protected void TypeGUI()
+ {
+ EditorGUILayout.PropertyField(_type, EditorGUIUtility.TrTextContent("Image Type"));
+ EditorGUI.indentLevel++;
+ Image.Type enumValueIndex = (Image.Type)_type.enumValueIndex;
+ bool flag = !_type.hasMultipleDifferentValues && (enumValueIndex == Image.Type.Sliced || enumValueIndex == Image.Type.Tiled);
+ if (flag && targets.Length > 1)
+ {
+ flag = targets.Select((Object obj) => obj as Image).All((Image img) => img.hasBorder);
+ }
+
+ m_ShowSlicedOrTiled.target = flag;
+ m_ShowSliced.target = flag && !_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Sliced;
+ m_ShowTiled.target = flag && !_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Tiled;
+ m_ShowFilled.target = !_type.hasMultipleDifferentValues && enumValueIndex == Image.Type.Filled;
+
+ GradientImage image = target as GradientImage;
+
+ if (EditorGUILayout.BeginFadeGroup(m_ShowSliced.faded))
+ //if (EditorGUILayout.BeginFadeGroup(m_ShowSlicedOrTiled.faded))
+ {
+ if (image.HasBorder)
+ {
+ EditorGUILayout.PropertyField(_fillCenter);
+ }
+
+ EditorGUILayout.PropertyField(_pixelsPerUnitMultiplier);
+
+ _pixelsPerUnitMultiplier.floatValue = Mathf.Max(0.01f, _pixelsPerUnitMultiplier.floatValue);
+ }
+ EditorGUILayout.EndFadeGroup();
+
+ if (EditorGUILayout.BeginFadeGroup(m_ShowSliced.faded) && image.Sprite != null && !image.HasBorder)
+ {
+ EditorGUILayout.HelpBox("This Image doesn't have a border.", MessageType.Warning);
+ }
+ EditorGUILayout.EndFadeGroup();
+
+ if (EditorGUILayout.BeginFadeGroup(m_ShowTiled.faded) && image.Sprite != null && !image.HasBorder && ((image.Sprite.texture != null && image.Sprite.texture.wrapMode != TextureWrapMode.Repeat) || image.Sprite.packed))
+ {
+ EditorGUILayout.HelpBox("This type unsoported to paint image.", MessageType.Error);
+ EditorGUILayout.HelpBox("It looks like you want to tile a sprite with no border. It would be more efficient to modify the Sprite properties, clear the Packing tag and set the Wrap mode to Repeat.", MessageType.Warning);
+ }
+ EditorGUILayout.EndFadeGroup();
+
+ if (EditorGUILayout.BeginFadeGroup(m_ShowFilled.faded))
+ {
+ // TODO: code for editor for filled type image
+ EditorGUILayout.HelpBox("This type unsoported to paint image.", MessageType.Error);
+ }
+ EditorGUILayout.EndFadeGroup();
+
+ EditorGUI.indentLevel--;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/GradientImageEditor.cs.meta b/Editor/GradientImageEditor.cs.meta
new file mode 100644
index 0000000..c6dbbcb
--- /dev/null
+++ b/Editor/GradientImageEditor.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ae45e5b4c3948ba45bada172ba0843d8
\ No newline at end of file
diff --git a/Editor/GradientRectEditor.cs b/Editor/GradientRectEditor.cs
new file mode 100644
index 0000000..5c540c2
--- /dev/null
+++ b/Editor/GradientRectEditor.cs
@@ -0,0 +1,38 @@
+using UnityEditor;
+using UnityEditor.UI;
+
+namespace Gilzoide.GradientRect.Editor
+{
+ [CustomEditor(typeof(GradientRect))]
+ public class GradientRectEditor : GraphicEditor
+ {
+ private SerializedProperty _gradient;
+ private SerializedProperty _direction;
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+ _gradient = serializedObject.FindProperty("_gradient");
+ _direction = serializedObject.FindProperty("_direction");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ AppearanceControlsGUI();
+ DrawGradientPropertys();
+ RaycastControlsGUI();
+ MaskableControlsGUI();
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+
+ protected virtual void DrawGradientPropertys()
+ {
+ EditorGUILayout.PropertyField(_gradient);
+ EditorGUILayout.PropertyField(_direction);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/GradientRectEditor.cs.meta b/Editor/GradientRectEditor.cs.meta
new file mode 100644
index 0000000..4483d70
--- /dev/null
+++ b/Editor/GradientRectEditor.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d3867f895b9c4e74aa88c7b3ba321b44
\ No newline at end of file
diff --git a/README.md b/README.md
index 5d0c634..be8679c 100644
--- a/README.md
+++ b/README.md
@@ -33,4 +33,4 @@ Either:
- [GradientTexture](Runtime/GradientTexture.cs): texture with configurable UV, tinted with Gradient colors.
- [GradientImage](Runtime/GradientImage.cs): sprite tinted with Gradient colors.
UVs are automatically fetched from sprite data.
- Only simple filling is supported, no slicing nor tiling.
\ No newline at end of file
+ Only simple filling and slicing is supported, not tiling.
\ No newline at end of file
diff --git a/Runtime/GradientImage.cs b/Runtime/GradientImage.cs
index df6dc8e..ab12524 100644
--- a/Runtime/GradientImage.cs
+++ b/Runtime/GradientImage.cs
@@ -1,24 +1,76 @@
using UnityEngine;
+using UnityEngine.Sprites;
+using UnityEngine.UI;
namespace Gilzoide.GradientRect
{
[RequireComponent(typeof(CanvasRenderer))]
public class GradientImage : GradientRect
{
- [Header("Texture")]
[SerializeField] protected Sprite _sprite;
- /// Sprite used to draw Image.
- /// For now, the only fill supported is simple mode, no slicing nor tiling.
+ [SerializeField] private Image.Type _type = Image.Type.Simple;
+ [SerializeField] private bool _fillCenter = true;
+ [SerializeField] private float _pixelsPerUnitMultiplier = 1f;
+
+ private static readonly Vector2[] s_VertScratch = new Vector2[4];
+ private static readonly Vector2[] s_UVScratch = new Vector2[4];
+
+ public float PixelsPerUnitMultiplier
+ {
+ get => _pixelsPerUnitMultiplier;
+ set
+ {
+ _pixelsPerUnitMultiplier = Mathf.Max(0.01f, value);
+ SetVerticesDirty();
+ }
+ }
+
+ public Image.Type Type
+ {
+ get => _type;
+ set
+ {
+ if (_type == value)
+ return;
+
+ _type = value;
+ SetVerticesDirty();
+ }
+ }
+
+ public bool FillCenter
+ {
+ get => _fillCenter;
+ set
+ {
+ if (_fillCenter == value)
+ return;
+
+ _fillCenter = value;
+ SetVerticesDirty();
+ }
+ }
+
+ public bool HasBorder
+ {
+ get
+ {
+ if (_sprite == null)
+ return false;
+
+ Vector4 v = _sprite.border;
+ return v.sqrMagnitude > 0f;
+ }
+ }
+
public Sprite Sprite
{
get => _sprite;
set
{
if (_sprite == value)
- {
return;
- }
_sprite = value;
SetVerticesDirty();
@@ -30,16 +82,169 @@ public override Texture mainTexture
{
get
{
- if (_sprite == null)
+ if (_sprite != null)
+ return _sprite.texture;
+
+ if (material != null && material.mainTexture != null)
+ return material.mainTexture;
+
+ return s_WhiteTexture;
+ }
+ }
+
+ protected override void OnPopulateMesh(VertexHelper vh)
+ {
+ switch (_type)
+ {
+ case Image.Type.Simple:
+ base.OnPopulateMesh(vh);
+ break;
+ case Image.Type.Sliced:
+ GenerateSlicedSprite(vh);
+ break;
+ default:
+ vh.Clear();
+ Debug.LogWarning($"Unsupported image type: {_type}");
+ break;
+ }
+ }
+
+ private void GenerateSlicedSprite(VertexHelper vh)
+ {
+ if (!HasBorder)
+ {
+ base.OnPopulateMesh(vh);
+ return;
+ }
+
+ Vector4 outer, inner, padding, border;
+
+ if (_sprite != null)
+ {
+ outer = DataUtility.GetOuterUV(_sprite);
+ inner = DataUtility.GetInnerUV(_sprite);
+ padding = DataUtility.GetPadding(_sprite);
+ border = _sprite.border;
+ }
+ else
+ {
+ outer = Vector4.zero;
+ inner = Vector4.zero;
+ padding = Vector4.zero;
+ border = Vector4.zero;
+ }
+
+ Rect rect = GetPixelAdjustedRect();
+
+ Vector4 adjustedBorders = GetAdjustedBorders(border / _pixelsPerUnitMultiplier, rect);
+ padding = padding / _pixelsPerUnitMultiplier;
+
+ s_VertScratch[0] = new Vector2(padding.x, padding.y);
+ s_VertScratch[3] = new Vector2(rect.width - padding.z, rect.height - padding.w);
+
+ s_VertScratch[1].x = adjustedBorders.x;
+ s_VertScratch[1].y = adjustedBorders.y;
+
+ s_VertScratch[2].x = rect.width - adjustedBorders.z;
+ s_VertScratch[2].y = rect.height - adjustedBorders.w;
+
+ for (int i = 0; i < 4; ++i)
+ {
+ s_VertScratch[i].x += rect.x;
+ s_VertScratch[i].y += rect.y;
+ }
+
+ s_UVScratch[0] = new Vector2(outer.x, outer.y);
+ s_UVScratch[1] = new Vector2(inner.x, inner.y);
+ s_UVScratch[2] = new Vector2(inner.z, inner.w);
+ s_UVScratch[3] = new Vector2(outer.z, outer.w);
+
+ vh.Clear();
+
+ for (int x = 0; x < 3; ++x)
+ {
+ int x2 = x + 1;
+
+ for (int y = 0; y < 3; ++y)
{
- if (material != null && material.mainTexture != null)
- {
- return material.mainTexture;
- }
- return s_WhiteTexture;
+ if (!_fillCenter && x == 1 && y == 1)
+ continue;
+
+ int y2 = y + 1;
+
+ if ((s_VertScratch[x2].x - s_VertScratch[x].x <= 0) || (s_VertScratch[y2].y - s_VertScratch[y].y <= 0))
+ continue;
+
+ Vector2 bottomLeft = new Vector2(s_VertScratch[x].x, s_VertScratch[y].y);
+ Vector2 topRight = new Vector2(s_VertScratch[x2].x, s_VertScratch[y2].y);
+
+ Vector2 normalizedBottomLeft = new Vector2(
+ (bottomLeft.x - rect.x) / rect.width,
+ (bottomLeft.y - rect.y) / rect.height
+ );
+ Vector2 normalizedTopLeft = new Vector2(
+ (bottomLeft.x - rect.x) / rect.width,
+ (topRight.y - rect.y) / rect.height
+ );
+ Vector2 normalizedTopRight = new Vector2(
+ (topRight.x - rect.x) / rect.width,
+ (topRight.y - rect.y) / rect.height
+ );
+ Vector2 normalizedBottomRight = new Vector2(
+ (topRight.x - rect.x) / rect.width,
+ (bottomLeft.y - rect.y) / rect.height
+ );
+
+ Color colorBL = _gradient.Evaluate(GetGradientTime(normalizedBottomLeft)) * color;
+ Color colorTL = _gradient.Evaluate(GetGradientTime(normalizedTopLeft)) * color;
+ Color colorTR = _gradient.Evaluate(GetGradientTime(normalizedTopRight)) * color;
+ Color colorBR = _gradient.Evaluate(GetGradientTime(normalizedBottomRight)) * color;
+
+ AddQuad(vh,
+ bottomLeft,
+ topRight,
+ colorBL, colorTL, colorTR, colorBR,
+ new Vector2(s_UVScratch[x].x, s_UVScratch[y].y),
+ new Vector2(s_UVScratch[x2].x, s_UVScratch[y2].y));
+ }
+ }
+ }
+
+ private Vector4 GetAdjustedBorders(Vector4 border, Rect rect)
+ {
+ Rect originalRect = rectTransform.rect;
+
+ for (int axis = 0; axis <= 1; axis++)
+ {
+ float borderScaleRatio;
+
+ if (originalRect.size[axis] != 0)
+ {
+ borderScaleRatio = rect.size[axis] / originalRect.size[axis];
+ border[axis] *= borderScaleRatio;
+ border[axis + 2] *= borderScaleRatio;
}
- return _sprite.texture;
+ float combinedBorders = border[axis] + border[axis + 2];
+ if (rect.size[axis] < combinedBorders && combinedBorders != 0)
+ {
+ borderScaleRatio = rect.size[axis] / combinedBorders;
+ border[axis] *= borderScaleRatio;
+ border[axis + 2] *= borderScaleRatio;
+ }
+ }
+ return border;
+ }
+
+ private float GetGradientTime(Vector2 normalizedPos)
+ {
+ switch (_direction)
+ {
+ case GradientDirection.LeftToRight: return normalizedPos.x;
+ case GradientDirection.RightToLeft: return 1 - normalizedPos.x;
+ case GradientDirection.BottomToTop: return normalizedPos.y;
+ case GradientDirection.TopToBottom: return 1 - normalizedPos.y;
+ default: return normalizedPos.x;
}
}
@@ -47,7 +252,7 @@ protected override Vector2 GetUVForNormalizedPosition(Vector2 position)
{
if (_sprite)
{
- Vector4 outerUV = UnityEngine.Sprites.DataUtility.GetOuterUV(_sprite);
+ Vector4 outerUV = DataUtility.GetOuterUV(_sprite);
return new Vector2(
Mathf.Lerp(outerUV.x, outerUV.z, position.x),
Mathf.Lerp(outerUV.y, outerUV.w, position.y)
@@ -58,5 +263,20 @@ protected override Vector2 GetUVForNormalizedPosition(Vector2 position)
return position;
}
}
+
+ private static void AddQuad(VertexHelper vertexHelper, Vector2 posMin, Vector2 posMax,
+ Color colorBottomLeft, Color colorTopLeft, Color colorTopRight, Color colorBottomRight,
+ Vector2 uvMin, Vector2 uvMax)
+ {
+ int startIndex = vertexHelper.currentVertCount;
+
+ vertexHelper.AddVert(new Vector3(posMin.x, posMin.y, 0), colorBottomLeft, new Vector2(uvMin.x, uvMin.y));
+ vertexHelper.AddVert(new Vector3(posMin.x, posMax.y, 0), colorTopLeft, new Vector2(uvMin.x, uvMax.y));
+ vertexHelper.AddVert(new Vector3(posMax.x, posMax.y, 0), colorTopRight, new Vector2(uvMax.x, uvMax.y));
+ vertexHelper.AddVert(new Vector3(posMax.x, posMin.y, 0), colorBottomRight, new Vector2(uvMax.x, uvMin.y));
+
+ vertexHelper.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
+ vertexHelper.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
+ }
}
}
diff --git a/Runtime/GradientRect.cs b/Runtime/GradientRect.cs
index 52d92a3..96d7fac 100644
--- a/Runtime/GradientRect.cs
+++ b/Runtime/GradientRect.cs
@@ -8,6 +8,7 @@ namespace Gilzoide.GradientRect
[RequireComponent(typeof(CanvasRenderer))]
public class GradientRect : MaskableGraphic
{
+ [Serializable]
public enum GradientDirection
{
LeftToRight,
@@ -16,7 +17,6 @@ public enum GradientDirection
TopToBottom,
}
- [Header("Gradient")]
[SerializeField] protected Gradient _gradient;
[SerializeField] protected GradientDirection _direction;
@@ -79,7 +79,7 @@ protected override void OnPopulateMesh(VertexHelper vh)
c1 = c2 = color1 * tint;
c3 = c4 = color2 * tint;
break;
-
+
case GradientDirection.RightToLeft:
v1 = new Vector2(1 - time2, 0);
v2 = new Vector2(1 - time2, 1);
@@ -88,7 +88,7 @@ protected override void OnPopulateMesh(VertexHelper vh)
c1 = c2 = color2 * tint;
c3 = c4 = color1 * tint;
break;
-
+
case GradientDirection.BottomToTop:
v1 = new Vector2(0, time1);
v2 = new Vector2(0, time2);
@@ -106,7 +106,7 @@ protected override void OnPopulateMesh(VertexHelper vh)
c1 = c4 = color2 * tint;
c2 = c3 = color1 * tint;
break;
-
+
default: throw new ArgumentOutOfRangeException(nameof(_direction));
}