-
-
Save tattyd/6807d6be9e550a1f24ad17f21d69177e to your computer and use it in GitHub Desktop.
/* MIT License | |
Copyright (c) 2022 David Tattersall | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. */ | |
using System.Collections.Generic; | |
using UnityEngine; | |
using UnityEngine.UIElements; | |
class Shadow : VisualElement | |
{ | |
private Vertex[] k_Vertices; | |
public int shadowCornerRadius { get; set; } | |
public float shadowScale { get; set; } | |
public int shadowOffsetX { get; set; } | |
public int shadowOffsetY { get; set; } | |
public int shadowCornerSubdivisions => 3; | |
public new class UxmlFactory : UxmlFactory<Shadow, UxmlTraits> { } | |
public new class UxmlTraits : VisualElement.UxmlTraits | |
{ | |
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription | |
{ | |
get { yield break; } | |
} | |
// Rounded corner radius. Increase to make the shadow "fluffier" | |
UxmlIntAttributeDescription radiusAttr = | |
new UxmlIntAttributeDescription { name = "shadow-corner-radius", defaultValue = 10 }; | |
// Scale. Increase to make the shadow extend farther away from the element. | |
UxmlFloatAttributeDescription scaleAttr = | |
new UxmlFloatAttributeDescription { name = "shadow-scale", defaultValue = 1.1f }; | |
// Offsets. Tweak to have e.g. a shadow below and to the right of an element. | |
UxmlIntAttributeDescription offsetXAttr = | |
new UxmlIntAttributeDescription { name = "shadow-offset-x", defaultValue = 0 }; | |
UxmlIntAttributeDescription offsetYAttr = | |
new UxmlIntAttributeDescription { name = "shadow-offset-y", defaultValue = 0 }; | |
// Buggy right now - always set to 3. | |
/*UxmlIntAttributeDescription subdivisionsAttr = | |
new UxmlIntAttributeDescription { name="shadow-corner-subdivisions", defaultValue = 3};*/ | |
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) | |
{ | |
base.Init(ve, bag, cc); | |
var shadow = ve as Shadow; | |
shadow.shadowCornerRadius = radiusAttr.GetValueFromBag(bag, cc); | |
shadow.shadowScale = scaleAttr.GetValueFromBag(bag, cc); | |
shadow.shadowOffsetX = offsetXAttr.GetValueFromBag(bag, cc); | |
shadow.shadowOffsetY = offsetYAttr.GetValueFromBag(bag, cc); | |
//shadow.shadowCornerSubdivisions = subdivisionsAttr.GetValueFromBag(bag, cc); | |
} | |
} | |
public Shadow() | |
{ | |
generateVisualContent += OnGenerateVisualContent; | |
} | |
private void OnGenerateVisualContent(MeshGenerationContext ctx) | |
{ | |
Rect r = contentRect; | |
float left = 0; | |
float right = r.width; | |
float top = 0; | |
float bottom = r.height; | |
float halfSpread = (shadowCornerRadius / 2f); | |
int curveSubdivisions = this.shadowCornerSubdivisions; | |
int totalVertices = 12 + ((curveSubdivisions - 1) * 4); | |
/* | |
4/5/6/7 = inset rectangle (rect-shadowInsetAmount) | |
0/1/2/3/8/9/10/11 = outset rectangle (rect+shadowSpread) | |
1 2 12 => 12+(subdivisions-1) | |
\| / | |
10 - 5========6 - 11 | |
| | | |
| | | |
| | | |
| | | |
9 - 4========7 - 8 | |
/ \ | |
0 3 (12+subdivisions-1)+1 => 12 + 2*(subdivisions-1) + 1 | |
*/ | |
// Outside edge | |
k_Vertices = new Vertex[totalVertices]; | |
k_Vertices[0].position = new Vector3(left + halfSpread, bottom + halfSpread, Vertex.nearZ); | |
k_Vertices[1].position = new Vector3(left + halfSpread, top - halfSpread, Vertex.nearZ); | |
k_Vertices[2].position = new Vector3(right - halfSpread, top - halfSpread, Vertex.nearZ); | |
k_Vertices[3].position = new Vector3(right - halfSpread, bottom + halfSpread, Vertex.nearZ); | |
k_Vertices[0].tint = Color.clear; | |
k_Vertices[1].tint = Color.clear; | |
k_Vertices[2].tint = Color.clear; | |
k_Vertices[3].tint = Color.clear; | |
k_Vertices[8].position = new Vector3(right + halfSpread, bottom - halfSpread, Vertex.nearZ); | |
k_Vertices[9].position = new Vector3(left - halfSpread, bottom - halfSpread, Vertex.nearZ); | |
k_Vertices[10].position = new Vector3(left - halfSpread, top + halfSpread, Vertex.nearZ); | |
k_Vertices[11].position = new Vector3(right + halfSpread, top + halfSpread, Vertex.nearZ); | |
k_Vertices[8].tint = Color.clear; | |
k_Vertices[9].tint = Color.clear; | |
k_Vertices[10].tint = Color.clear; | |
k_Vertices[11].tint = Color.clear; | |
// Inside rectangle | |
k_Vertices[4].position = new Vector3(0 + halfSpread, r.height - halfSpread, Vertex.nearZ); | |
k_Vertices[5].position = new Vector3(0 + halfSpread, 0 + halfSpread, Vertex.nearZ); | |
k_Vertices[6].position = new Vector3(r.width - halfSpread, 0 + halfSpread, Vertex.nearZ); | |
k_Vertices[7].position = new Vector3(r.width - halfSpread, r.height - halfSpread, Vertex.nearZ); | |
k_Vertices[4].tint = resolvedStyle.color; | |
k_Vertices[5].tint = resolvedStyle.color; | |
k_Vertices[6].tint = resolvedStyle.color; | |
k_Vertices[7].tint = resolvedStyle.color; | |
// Top right corner | |
for (int i = 0; i < curveSubdivisions - 1; i++) | |
{ | |
int vertexId = 12 + i; | |
float angle = (Mathf.PI * 0.5f / curveSubdivisions) + (Mathf.PI * 0.5f / curveSubdivisions) * i; | |
k_Vertices[vertexId].position = new Vector3(r.width - halfSpread + Mathf.Sin(angle) * shadowCornerRadius, 0 + halfSpread + (-Mathf.Cos(angle) * shadowCornerRadius), Vertex.nearZ); | |
k_Vertices[vertexId].tint = Color.clear; | |
} | |
// Bottom right corner | |
for (int i = 0; i < curveSubdivisions - 1; i++) | |
{ | |
int vertexId = 12 + i + (curveSubdivisions - 1); | |
float angle = (Mathf.PI * 0.5f) + (Mathf.PI * 0.5f / curveSubdivisions) + (Mathf.PI * 0.5f / curveSubdivisions) * i; | |
k_Vertices[vertexId].position = new Vector3(r.width - halfSpread + Mathf.Sin(angle) * shadowCornerRadius, r.height - halfSpread + (-Mathf.Cos(angle) * shadowCornerRadius), Vertex.nearZ); | |
k_Vertices[vertexId].tint = Color.clear; | |
} | |
// Bottom left corner | |
for (int i = 0; i < curveSubdivisions - 1; i++) | |
{ | |
int vertexId = 12 + i + (curveSubdivisions - 1) * 2; | |
float angle = (Mathf.PI) + (Mathf.PI * 0.5f / curveSubdivisions) + (Mathf.PI * 0.5f / curveSubdivisions) * i; | |
k_Vertices[vertexId].position = new Vector3(0 + halfSpread + Mathf.Sin(angle) * shadowCornerRadius, r.height - halfSpread + (-Mathf.Cos(angle) * shadowCornerRadius), Vertex.nearZ); | |
k_Vertices[vertexId].tint = Color.clear; | |
} | |
// Top left corner | |
for (int i = 0; i < curveSubdivisions - 1; i++) | |
{ | |
int vertexId = 12 + i + (curveSubdivisions - 1) * 3; | |
float angle = (Mathf.PI * 1.5f) + (Mathf.PI * 0.5f / curveSubdivisions) + (Mathf.PI * 0.5f / curveSubdivisions) * i; | |
k_Vertices[vertexId].position = new Vector3(0 + halfSpread + Mathf.Sin(angle) * shadowCornerRadius, 0 + halfSpread + (-Mathf.Cos(angle) * shadowCornerRadius), Vertex.nearZ); | |
k_Vertices[vertexId].tint = Color.clear; | |
} | |
Vector3 dimensions = new Vector3(r.width, r.height, Vertex.nearZ); | |
for (int i = 0; i < k_Vertices.Length; i++) | |
{ | |
// Do not scale the inner rectangle | |
Vector3 newPos = k_Vertices[i].position; | |
newPos = newPos + new Vector3(shadowOffsetX, shadowOffsetY, 0); | |
if (i >= 4 && i <= 7) | |
{ | |
// Do nothing | |
} | |
else | |
{ | |
newPos = ((newPos - (dimensions * 0.5f)) * shadowScale) + (dimensions * 0.5f); | |
} | |
// Scale verticles using scale factor | |
k_Vertices[i].position = newPos; | |
} | |
List<ushort> tris = new List<ushort>(); | |
tris.AddRange(new ushort[]{ | |
1,6,5, | |
2,6,1, | |
6,11,8, | |
6,8,7, | |
4,7,3, | |
4,3,0, | |
10,5,4, | |
10,4,9, | |
5,6,4, | |
6,7,4, | |
}); | |
for (ushort i = 0; i < curveSubdivisions; i++) | |
{ | |
if (i == 0) | |
{ | |
tris.AddRange(new ushort[] { 2, 12, 6 }); | |
} | |
else if (i == curveSubdivisions - 1) | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1), 11, 6 }); | |
} | |
else | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1), (ushort)(12 + i), 6 }); | |
} | |
} | |
for (ushort i = 0; i < curveSubdivisions; i++) | |
{ | |
if (i == 0) | |
{ | |
tris.AddRange(new ushort[] { 7, 8, 14 }); | |
} | |
else if (i == curveSubdivisions - 1) | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + (curveSubdivisions - 1)), 3, 7 }); | |
} | |
else | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + (curveSubdivisions - 1)), (ushort)(12 + i + (curveSubdivisions - 1)), 7 }); | |
} | |
} | |
for (ushort i = 0; i < curveSubdivisions; i++) | |
{ | |
if (i == 0) | |
{ | |
tris.AddRange(new ushort[] { 4, 0, 16 }); | |
} | |
else if (i == curveSubdivisions - 1) | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + 2 * (curveSubdivisions - 1)), 9, 4 }); | |
} | |
else | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + 2 * (curveSubdivisions - 1)), (ushort)(12 + i + (2 * (curveSubdivisions - 1))), 4 }); | |
} | |
} | |
for (ushort i = 0; i < curveSubdivisions; i++) | |
{ | |
if (i == 0) | |
{ | |
tris.AddRange(new ushort[] { 5, 10, 18 }); | |
} | |
else if (i == curveSubdivisions - 1) | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + 3 * (curveSubdivisions - 1)), 1, 5 }); | |
} | |
else | |
{ | |
tris.AddRange(new ushort[] { (ushort)(12 + i - 1 + 3 * (curveSubdivisions - 1)), (ushort)(12 + i + 3 * (curveSubdivisions - 1)), 5 }); | |
} | |
} | |
MeshWriteData mwd = ctx.Allocate(k_Vertices.Length, tris.Count); | |
mwd.SetAllVertices(k_Vertices); | |
mwd.SetAllIndices(tris.ToArray()); | |
} | |
} |
@alexandret-unity Good suggestion. You can pass a the NativeArray directly to SetAllVertices
method, there is no need to get a NativeSlice. I have tested it here:
https://gist.github.com/edap/304781e090392068f767c4901aef1449
For anyone who uses Unity 2023.2 and later versions, UxmlFactory
and UxmlTrait
is deprecated and Unity suggests using UxmlElementAttribute
for custom control creation. I have made a minor change based on @edap version:
https://gist.github.com/PhamQuangHung-UIT/f06e9c0076ee8df1715256ffa7c8f11f
Hi, I made some modifications for script and want to share it.
For me, transition wasn't working properly, and I needed inner shadows.
Shadows don't change in transition, only at the end of transition, I get new shape instantly instead of gradual change.
It was because resolvedStyle.color returns value before transition started and returns new value only when transition ends.
Unity handles transition with "experimental.animation.Start();". I have found this interface.
ITransitionAnimations.cs -
ValueAnimation<float> Start(float from, float to, int durationMs, Action<VisualElement, float> onValueChanged);
ValueAnimation<Rect> Start(Rect from, Rect to, int durationMs, Action<VisualElement, Rect> onValueChanged);
ValueAnimation<Color> Start(Color from, Color to, int durationMs, Action<VisualElement, Color> onValueChanged);
ValueAnimation<Vector3> Start(Vector3 from, Vector3 to, int durationMs, Action<VisualElement, Vector3> onValueChanged);
ValueAnimation<Vector2> Start(Vector2 from, Vector2 to, int durationMs, Action<VisualElement, Vector2> onValueChanged);
ValueAnimation<Quaternion> Start(Quaternion from, Quaternion to, int durationMs, Action<VisualElement, Quaternion> onValueChanged);
For inner shadows, I just have swapped opacity value of "Inside rectangle" and "clearColor".
I changed clearColor to outerColor and made local variable innerColor for Inside rectangle.
I also added some utility methods and second constructor to provide color without USS.
I hope to eventual making buttons like this - https://getcssscan.com/css-buttons-examples
Hope it will be helpful for someone. Feel free to improve it.
I assume we need to add a way to change color for each vertex to make rainbow shadows and use button's border radius for shadowCornerRadius.
Sorry if my code a little bit clumsy.
And I don't sure if register callback on mouse event is a way to go.
P.S. - I used script from there -
https://forum.unity.com/threads/soft-shadows-my-custom-component.1300653/
My changes -
https://github.com/Laplasov/ShadowsForUnityUIToolkit/blob/main/Shadow.cs
I was inspired by this Gist to create an "outer glow effect" custom control, which is also useful for creating drop shadows, so I want to thank you for the inspiration and head start 😀