285 lines
7.8 KiB
C#
285 lines
7.8 KiB
C#
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace Dashboard.Drawing
|
|
{
|
|
/// <summary>
|
|
/// Enumeration of the kinds of gradients available.
|
|
/// </summary>
|
|
public enum GradientType
|
|
{
|
|
/// <summary>
|
|
/// A gradient which transitions over a set axis.
|
|
/// </summary>
|
|
Axial,
|
|
/// <summary>
|
|
/// A gradient which transitions along elliptical curves.
|
|
/// </summary>
|
|
Radial,
|
|
}
|
|
|
|
/// <summary>
|
|
/// A single gradient stop.
|
|
/// </summary>
|
|
/// <param name="Position">The position of the gradient stop. Must be [0,1].</param>
|
|
/// <param name="Color">The color value for the stop.</param>
|
|
public readonly record struct GradientStop(float Position, Color Color);
|
|
|
|
/// <summary>
|
|
/// Represents a linear gradient.
|
|
/// </summary>
|
|
public class Gradient : ICollection<GradientStop>, ICloneable, IEquatable<Gradient>, INotifyPropertyChanged, INotifyCollectionChanged
|
|
{
|
|
private readonly List<GradientStop> _stops = new List<GradientStop>();
|
|
|
|
/// <summary>
|
|
/// Gradient type.
|
|
/// </summary>
|
|
public GradientType Type
|
|
{
|
|
get;
|
|
set => SetField(ref field, value);
|
|
} = GradientType.Axial;
|
|
|
|
/// <summary>
|
|
/// First gradient control point.
|
|
/// </summary>
|
|
public Vector2 C0
|
|
{
|
|
get;
|
|
set => SetField(ref field, value);
|
|
} = Vector2.Zero;
|
|
|
|
/// <summary>
|
|
/// Second gradient control point.
|
|
/// </summary>
|
|
public Vector2 C1
|
|
{
|
|
get;
|
|
set => SetField(ref field, value);
|
|
} = Vector2.One;
|
|
|
|
/// <summary>
|
|
/// Number of stops in a gradient.
|
|
/// </summary>
|
|
public int Count => _stops.Count;
|
|
public bool IsReadOnly => false;
|
|
|
|
/// <summary>
|
|
/// Get a gradient control point.
|
|
/// </summary>
|
|
/// <param name="index">The index to get the control point for.</param>
|
|
public GradientStop this[int index]
|
|
{
|
|
get => _stops[index];
|
|
set
|
|
{
|
|
RemoveAt(index);
|
|
Add(value);
|
|
}
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
|
|
|
|
|
public Gradient()
|
|
{
|
|
}
|
|
|
|
public Gradient(Color a, Color b)
|
|
{
|
|
Add(new GradientStop(0, a));
|
|
Add(new GradientStop(1, b));
|
|
}
|
|
|
|
public Gradient(IEnumerable<GradientStop> stops)
|
|
{
|
|
_stops.AddRange(stops);
|
|
|
|
if (_stops.Any(x => x.Position < 0 || x.Position > 1))
|
|
throw new Exception("Gradient stop positions must be in the range [0, 1].");
|
|
|
|
_stops.Sort((a, b) => a.Position.CompareTo(b.Position));
|
|
}
|
|
|
|
public Color GetColor(float position)
|
|
{
|
|
if (Count == 0)
|
|
return Color.Black;
|
|
else if (Count == 1)
|
|
return _stops[0].Color;
|
|
|
|
int pivot = _stops.FindIndex(x => x.Position < position);
|
|
|
|
GradientStop left, right;
|
|
if (pivot == -1)
|
|
{
|
|
left = right = _stops[^1];
|
|
}
|
|
else if (pivot == 0)
|
|
{
|
|
left = right = _stops[0];
|
|
}
|
|
else
|
|
{
|
|
left = _stops[pivot-1];
|
|
right = _stops[pivot];
|
|
}
|
|
|
|
float weight = (position - left.Position) / (right.Position - left.Position);
|
|
|
|
Vector4 lcolor = new Vector4(left.Color.R, left.Color.G, left.Color.B, left.Color.A) * (1-weight);
|
|
Vector4 rcolor = new Vector4(right.Color.R, right.Color.G, right.Color.B, right.Color.A) * weight;
|
|
Vector4 color = lcolor + rcolor;
|
|
|
|
return Color.FromArgb((byte)color.W, (byte)color.X, (byte)color.Y, (byte)color.Z);
|
|
}
|
|
|
|
public Gradient Clone()
|
|
{
|
|
Gradient gradient = new Gradient()
|
|
{
|
|
Type = Type,
|
|
C0 = C0,
|
|
C1 = C1,
|
|
};
|
|
|
|
foreach (GradientStop stop in _stops)
|
|
{
|
|
gradient.Add(stop);
|
|
}
|
|
|
|
return gradient;
|
|
}
|
|
|
|
object ICloneable.Clone()
|
|
{
|
|
return Clone();
|
|
}
|
|
|
|
public IEnumerator<GradientStop> GetEnumerator()
|
|
{
|
|
return _stops.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return ((IEnumerable)_stops).GetEnumerator();
|
|
}
|
|
|
|
public void Add(GradientStop item)
|
|
{
|
|
if (item.Position < 0 || item.Position > 1)
|
|
throw new Exception("Gradient stop positions must be in the range [0, 1].");
|
|
|
|
int index = _stops.FindIndex(x => x.Position > item.Position);
|
|
if (index == -1)
|
|
index = _stops.Count;
|
|
|
|
_stops.Insert(index, item);
|
|
OnCollectionChanged(NotifyCollectionChangedAction.Add);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_stops.Clear();
|
|
OnCollectionChanged(NotifyCollectionChangedAction.Reset);
|
|
}
|
|
|
|
public bool Contains(GradientStop item)
|
|
{
|
|
return _stops.Contains(item);
|
|
}
|
|
|
|
public void CopyTo(GradientStop[] array, int arrayIndex)
|
|
{
|
|
_stops.CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
public bool Remove(GradientStop item)
|
|
{
|
|
if (!_stops.Remove(item))
|
|
return false;
|
|
|
|
OnCollectionChanged(NotifyCollectionChangedAction.Remove);
|
|
return true;
|
|
}
|
|
|
|
public void RemoveAt(int index)
|
|
{
|
|
_stops.RemoveAt(index);
|
|
OnCollectionChanged(NotifyCollectionChangedAction.Remove);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
HashCode code = new HashCode();
|
|
|
|
code.Add(Count);
|
|
code.Add(Type);
|
|
code.Add(C0);
|
|
code.Add(C1);
|
|
|
|
foreach (GradientStop item in this)
|
|
code.Add(item.GetHashCode());
|
|
return code.ToHashCode();
|
|
}
|
|
|
|
public bool Equals(Gradient? other)
|
|
{
|
|
return
|
|
other != null &&
|
|
Type == other.Type &&
|
|
C0 == other.C0 &&
|
|
C1 == other.C1 &&
|
|
_stops.SequenceEqual(other._stops);
|
|
}
|
|
|
|
private bool NotEquals(Gradient? other)
|
|
{
|
|
return
|
|
other == null ||
|
|
Type != other.Type ||
|
|
C0 != other.C0 ||
|
|
C1 != other.C1 ||
|
|
!_stops.SequenceEqual(other._stops);
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return obj is Gradient other && Equals(other);
|
|
}
|
|
|
|
public static bool operator ==(Gradient? left, Gradient? right)
|
|
{
|
|
return left?.Equals(right) ?? right == null;
|
|
}
|
|
|
|
public static bool operator !=(Gradient? left, Gradient? right)
|
|
{
|
|
return left?.NotEquals(right) ?? right != null;
|
|
}
|
|
|
|
protected void SetField<T>(ref T field, T value, [CallerMemberName] string name = "")
|
|
{
|
|
field = value;
|
|
OnPropertyChanged(name);
|
|
}
|
|
|
|
protected virtual void OnPropertyChanged(string property)
|
|
{
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
|
|
}
|
|
|
|
protected virtual void OnCollectionChanged(NotifyCollectionChangedAction action)
|
|
{
|
|
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
|
|
}
|
|
}
|
|
}
|