Background gradients

nope, image backs are cropped correctly with border radius (almost perfect except some white outline on corners (idk what’s that)

8990851--1237948--upload_2023-5-4_9-52-34.png

The only problem is vector graphics package gives me such weird result on gradients when i try to import them as vector images, details under spoiler

svg importer

on both importers same 2x2 gradient vector image from FFFF(up) to 888F(down)

8990851--1237954--upload_2023-5-4_9-55-2.jpg

using directly vector data is not possible yet for gradients, also for radial gradients larger texture need to be generated on svg import
in any case i used vectors in project as sources and assigned them through styles so import type change will not affect any UXML or USS code.

Hi!
Another solution inspired by HrabiaWrzontq’s solution.
This code allow to control gradient from uss properties, usage :

// uxml
    <DuckReaction.Ui.GradientElement name="GradientElement" class="gradient" style="width: 100%; height: 100%;" />

// uss
.gradient {
    --gradient-from: var(--color-moon-1);
    --gradient-to: var(--color-moon-2);
    --gradient-direction: vertical; // or horizontal
}

Source :

using System;
using UnityEngine;
using UnityEngine.UIElements;

namespace DuckReaction.Ui
{
    public enum GradientDirection
    {
        Horizontal,
        Vertical
    }

    public class GradientElement : VisualElement
    {
        static readonly Vertex[] _vertices = new Vertex[4];
        static readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 };
        static readonly CustomStyleProperty<Color> _gradientFromProperty = new("--gradient-from");
        static readonly CustomStyleProperty<Color> _gradientToProperty = new("--gradient-to");
        static readonly CustomStyleProperty<string> _gradientDirectionProperty = new("--gradient-direction");
        GradientDirection _gradientDirection;

        Color _gradientFrom;
        Color _gradientTo;

        public GradientElement()
        {
            generateVisualContent += GenerateVisualContent;
            RegisterCallback<CustomStyleResolvedEvent>(OnStylesResolved);
        }

        void OnStylesResolved(CustomStyleResolvedEvent @event)
        {
            @event.customStyle.TryGetValue(_gradientFromProperty, out _gradientFrom);
            @event.customStyle.TryGetValue(_gradientToProperty, out _gradientTo);
            @event.customStyle.TryGetValue(_gradientDirectionProperty, out var gradientDirectionAsString);
            if (Enum.TryParse(typeof(GradientDirection), gradientDirectionAsString, true, out var gradientDirection))
                _gradientDirection = (GradientDirection)gradientDirection;
            else
                _gradientDirection = GradientDirection.Horizontal;
        }

        void GenerateVisualContent(MeshGenerationContext meshGenerationContext)
        {
            var rect = contentRect;
            if (rect.width < 0.1f || rect.height < 0.1f)
                return;

            UpdateVerticesTint();
            UpdateVerticesPosition(rect);

            var meshWriteData = meshGenerationContext.Allocate(_vertices.Length, _indices.Length);
            meshWriteData.SetAllVertices(_vertices);
            meshWriteData.SetAllIndices(_indices);
        }

        static void UpdateVerticesPosition(Rect rect)
        {
            const float left = 0f;
            var right = rect.width;
            const float top = 0f;
            var bottom = rect.height;

            _vertices[0].position = new Vector3(left, bottom, Vertex.nearZ);
            _vertices[1].position = new Vector3(left, top, Vertex.nearZ);
            _vertices[2].position = new Vector3(right, top, Vertex.nearZ);
            _vertices[3].position = new Vector3(right, bottom, Vertex.nearZ);
        }

        void UpdateVerticesTint()
        {
            if (_gradientDirection is GradientDirection.Horizontal)
            {
                _vertices[0].tint = _gradientFrom;
                _vertices[1].tint = _gradientFrom;
                _vertices[2].tint = _gradientTo;
                _vertices[3].tint = _gradientTo;
            }
            else
            {
                _vertices[0].tint = _gradientTo;
                _vertices[1].tint = _gradientFrom;
                _vertices[2].tint = _gradientFrom;
                _vertices[3].tint = _gradientTo;
            }
        }

        public new class UxmlFactory : UxmlFactory<GradientElement, GradientElementUxmlTraits>
        {
        }

        public class GradientElementUxmlTraits : UxmlTraits
        {
        }
    }
}

Enjoy

I see that Unity finally supports attributes on custom UITK controls in latest Alpha! That’s great!

I also think gradient backgrounds would be a nice feature to have natively.
Alternatively I agree that the SVG vector background on a Visual Elements (VE) is a good alternative solution.

I noticed that the border radius is ignored on the VE that has the background SVG, but if I add it to a container and give that the border radius with overflow:none; then it manages to make rounded corners and hide the background on the child. Is this the right way to do it?


I added a white outline to the VEs above to show where the child should’ve masked the corner and where the parent does manage to mask the corners.

a simple svg with gradient:

<svg height="100" width="100">
  <defs>
    <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
      <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
    </linearGradient>
  </defs>
  <rect width="100" height="100" fill="url(#grad1)" />
</svg>

This functionality needs to be here. Or can we get an update when it might be available? As a front-end dev I want UI Toolkit for Runtime to be my default UI solution… But when it doesn’t have basic functionality like this I just get discouraged.

On road map since 2021.
Better pay per install.

UxmlFactory and UxmlTraits are deprecated. Here is an updated version.
Anyone trying to achiev this can also refer to https://docs.unity3d.com/Manual/UIE-create-custom-style-custom-control.html to add uss and uxml files to make in work in the UI Builder
@duckreaction , thank you for the original code

using System;
using UnityEngine;
using UnityEngine.UIElements;


namespace YourNameSpace.CustomVisualElements
{
    public enum GradientDirection
    {
        Horizontal,
        Vertical
    }

    [UxmlElement]
    public partial class GradientElement : VisualElement
    {
        static readonly Vertex[] _vertices = new Vertex[4];
        static readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 };
        static readonly CustomStyleProperty<Color> _gradientFromProperty = new("--gradient-from");
        static readonly CustomStyleProperty<Color> _gradientToProperty = new("--gradient-to");
        static readonly CustomStyleProperty<string> _gradientDirectionProperty = new("--gradient-direction");

        [UxmlAttribute]
        public GradientDirection _gradientDirection;

        [UxmlAttribute]
        public Color _gradientFrom;
        
        [UxmlAttribute]
        public Color _gradientTo;

        public GradientElement()
        {
            generateVisualContent += GenerateVisualContent;
            RegisterCallback<CustomStyleResolvedEvent>(OnStylesResolved);
        }

        void GenerateVisualContent(MeshGenerationContext meshGenerationContext)
        {
            var rect = contentRect;
            if (rect.width < 0.1f || rect.height < 0.1f)
                return;

            UpdateVerticesTint();
            UpdateVerticesPosition(rect);

            var meshWriteData = meshGenerationContext.Allocate(_vertices.Length, _indices.Length);
            meshWriteData.SetAllVertices(_vertices);
            meshWriteData.SetAllIndices(_indices);
        }

        void OnStylesResolved(CustomStyleResolvedEvent @event)
        {
            @event.customStyle.TryGetValue(_gradientFromProperty, out _gradientFrom);
            @event.customStyle.TryGetValue(_gradientToProperty, out _gradientTo);
            @event.customStyle.TryGetValue(_gradientDirectionProperty, out var gradientDirectionAsString);
            if (Enum.TryParse(typeof(GradientDirection), gradientDirectionAsString, true, out var gradientDirection))
                _gradientDirection = (GradientDirection)gradientDirection;
            else
                _gradientDirection = GradientDirection.Horizontal;
        }

        static void UpdateVerticesPosition(Rect rect)
        {
            const float left = 0f;
            var right = rect.width;
            const float top = 0f;
            var bottom = rect.height;

            _vertices[0].position = new Vector3(left, bottom, Vertex.nearZ);
            _vertices[1].position = new Vector3(left, top, Vertex.nearZ);
            _vertices[2].position = new Vector3(right, top, Vertex.nearZ);
            _vertices[3].position = new Vector3(right, bottom, Vertex.nearZ);
        }

        void UpdateVerticesTint()
        {
            if (_gradientDirection is GradientDirection.Horizontal)
            {
                _vertices[0].tint = _gradientFrom;
                _vertices[1].tint = _gradientFrom;
                _vertices[2].tint = _gradientTo;
                _vertices[3].tint = _gradientTo;
            }
            else
            {
                _vertices[0].tint = _gradientTo;
                _vertices[1].tint = _gradientFrom;
                _vertices[2].tint = _gradientFrom;
                _vertices[3].tint = _gradientTo;
            }
        }
    }
}

Day 4 of using UI Toolkit and finding half a decade old request for basic functionality that requires hacks to make it work.

Day 1: No media queries
Day 2: No z index
Day 3: No aspect ratio
And now Day 4: No gradients

Anyway, thanks @nawash for the code. Here’s an updated version that adds one layer of wrapping so that you can use border on the gradient element.

I had it working with border without wrapping, but it didn’t work with border radius as it’s not trivial to calculate the rounded gradient.

So this one layer of wrapping fixes it and it’s working nicely.

public enum GradientDirection
{
    Horizontal,
    Vertical
}

[UxmlElement]
public partial class GradientElement : VisualElement
{
    static readonly Vertex[] _vertices = new Vertex[4];
    static readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 };
    private VisualElement gradient;

    [UxmlAttribute]
    public GradientDirection GradientDirection { get; set; } = GradientDirection.Horizontal;

    [UxmlAttribute]
    public Color GradientFrom { get; set; }

    [UxmlAttribute]
    public Color GradientTo { get; set; }

    public GradientElement()
    {
        this.style.overflow = Overflow.Hidden;

        gradient = new VisualElement();
        gradient.name = "Gradient";
        gradient.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
        gradient.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
        gradient.generateVisualContent += GenerateVisualContent;

        hierarchy.Add(gradient);
    }

    void GenerateVisualContent(MeshGenerationContext meshGenerationContext)
    {
        var rect = gradient.contentRect;

        if (rect.width < 0.1f || rect.height < 0.1f)
        {
            return;
        }

        UpdateVerticesTint();
        UpdateVerticesPosition(rect);

        var meshWriteData = meshGenerationContext.Allocate(_vertices.Length, _indices.Length);
        meshWriteData.SetAllVertices(_vertices);
        meshWriteData.SetAllIndices(_indices);
    }

    static void UpdateVerticesPosition(Rect rect)
    {
        const float left = 0f;
        var right = rect.width;
        const float top = 0f;
        var bottom = rect.height;

        _vertices[0].position = new Vector3(left, bottom, Vertex.nearZ);
        _vertices[1].position = new Vector3(left, top, Vertex.nearZ);
        _vertices[2].position = new Vector3(right, top, Vertex.nearZ);
        _vertices[3].position = new Vector3(right, bottom, Vertex.nearZ);
    }

    void UpdateVerticesTint()
    {
        if (GradientDirection is GradientDirection.Horizontal)
        {
            _vertices[0].tint = GradientFrom;
            _vertices[1].tint = GradientFrom;
            _vertices[2].tint = GradientTo;
            _vertices[3].tint = GradientTo;
        }
        else
        {
            _vertices[0].tint = GradientTo;
            _vertices[1].tint = GradientFrom;
            _vertices[2].tint = GradientFrom;
            _vertices[3].tint = GradientTo;
        }
    }
}

Edit: I realized I removed the custom USS as I don’t use it, haven’t tested that myself. If you need it I guess should be ok to add it back and propagate it.

All problem have answer, find it yourself

Here is a version with angle gradient support

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using Unity.Properties;

public enum GradientDirection
{
    Horizontal,
    Vertical,
    Angle
}

[UxmlElement]
public partial class GradientElement : VisualElement
{
    static readonly Vertex[] _vertices = new Vertex[4];
    static readonly ushort[] _indices = { 0, 1, 2, 2, 3, 0 };
    private VisualElement gradient;

    private GradientDirection _gradientDirection = GradientDirection.Horizontal;
    private float _gradientAngle = 0f;
    private Color _gradientFrom;
    private Color _gradientTo;

    [UxmlAttribute]
    public GradientDirection GradientDirection
    {
        get => _gradientDirection;
        set
        {
            if (_gradientDirection != value)
            {
                _gradientDirection = value;
                gradient?.MarkDirtyRepaint();
            }
        }
    }

    [UxmlAttribute]
    public float GradientAngle
    {
        get => _gradientAngle;
        set
        {
            if (_gradientAngle != value)
            {
                _gradientAngle = value;
                gradient?.MarkDirtyRepaint();
            }
        }
    }

    [UxmlAttribute]
    public Color GradientFrom
    {
        get => _gradientFrom;
        set
        {
            if (_gradientFrom != value)
            {
                _gradientFrom = value;
                gradient?.MarkDirtyRepaint();
            }
        }
    }

    [UxmlAttribute]
    public Color GradientTo
    {
        get => _gradientTo;
        set
        {
            if (_gradientTo != value)
            {
                _gradientTo = value;
                gradient?.MarkDirtyRepaint();
            }
        }
    }

    public GradientElement()
    {
        this.style.overflow = Overflow.Hidden;

        gradient = new VisualElement();
        gradient.name = "Gradient";
        gradient.style.width = new StyleLength(new Length(100, LengthUnit.Percent));
        gradient.style.height = new StyleLength(new Length(100, LengthUnit.Percent));
        gradient.generateVisualContent += GenerateVisualContent;

        hierarchy.Add(gradient);
    }

    void GenerateVisualContent(MeshGenerationContext meshGenerationContext)
    {
        var rect = gradient.contentRect;

        if (rect.width < 0.1f || rect.height < 0.1f)
        {
            return;
        }

        UpdateVerticesPosition(rect);
        UpdateVerticesTint();

        var meshWriteData = meshGenerationContext.Allocate(_vertices.Length, _indices.Length);
        meshWriteData.SetAllVertices(_vertices);
        meshWriteData.SetAllIndices(_indices);
    }

    static void UpdateVerticesPosition(Rect rect)
    {
        const float left = 0f;
        var right = rect.width;
        const float top = 0f;
        var bottom = rect.height;

        _vertices[0].position = new Vector3(left, bottom, Vertex.nearZ);
        _vertices[1].position = new Vector3(left, top, Vertex.nearZ);
        _vertices[2].position = new Vector3(right, top, Vertex.nearZ);
        _vertices[3].position = new Vector3(right, bottom, Vertex.nearZ);
    }

    void UpdateVerticesTint()
    {
        float angle = 0f;
        if (GradientDirection == GradientDirection.Horizontal)
        {
            angle = 0f;
        }
        else if (GradientDirection == GradientDirection.Vertical)
        {
            angle = 90f;
        }
        else if (GradientDirection == GradientDirection.Angle)
        {
            angle = GradientAngle;
        }

        float rad = angle * Mathf.Deg2Rad;
        float dirX = Mathf.Cos(rad);
        float dirY = Mathf.Sin(rad);

        float p0 = _vertices[0].position.x * dirX + _vertices[0].position.y * dirY;
        float p1 = _vertices[1].position.x * dirX + _vertices[1].position.y * dirY;
        float p2 = _vertices[2].position.x * dirX + _vertices[2].position.y * dirY;
        float p3 = _vertices[3].position.x * dirX + _vertices[3].position.y * dirY;

        float minP = Mathf.Min(Mathf.Min(p0, p1), Mathf.Min(p2, p3));
        float maxP = Mathf.Max(Mathf.Max(p0, p1), Mathf.Max(p2, p3));

        float range = maxP - minP;
        if (range < 0.001f)
        {
            _vertices[0].tint = GradientFrom;
            _vertices[1].tint = GradientFrom;
            _vertices[2].tint = GradientFrom;
            _vertices[3].tint = GradientFrom;
            return;
        }

        _vertices[0].tint = Color.Lerp(GradientFrom, GradientTo, (p0 - minP) / range);
        _vertices[1].tint = Color.Lerp(GradientFrom, GradientTo, (p1 - minP) / range);
        _vertices[2].tint = Color.Lerp(GradientFrom, GradientTo, (p2 - minP) / range);
        _vertices[3].tint = Color.Lerp(GradientFrom, GradientTo, (p3 - minP) / range);
    }
}