// ReSharper disable UnusedMember.Global
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using RGB.NET.Core;
namespace RGB.NET.Presets.Textures.Gradients;
///
///
/// Represents a linear interpolated gradient with n stops.
///
public sealed class LinearGradient : AbstractGradient
{
#region Properties & Fields
private bool _isOrderedGradientListDirty = true;
private readonly List _orderedGradientStops = new();
#endregion
#region Constructors
///
///
/// Initializes a new instance of the class.
///
public LinearGradient()
{
Initialize();
}
///
///
/// Initializes a new instance of the class.
///
/// The stops with which the gradient should be initialized.
public LinearGradient(params GradientStop[] gradientStops)
: base(gradientStops)
{
Initialize();
}
///
///
/// Initializes a new instance of the class.
///
/// Specifies whether the gradient should wrapp or not (see for an example of what this means).
/// The stops with which the gradient should be initialized.
public LinearGradient(bool wrapGradient, params GradientStop[] gradientStops)
: base(wrapGradient, gradientStops)
{
Initialize();
}
#endregion
#region Methods
private void Initialize()
{
void OnGradientStopOnPropertyChanged(object? sender, PropertyChangedEventArgs args) => _isOrderedGradientListDirty = true;
foreach (GradientStop gradientStop in GradientStops)
gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged;
GradientStops.CollectionChanged += (_, args) =>
{
if (args.OldItems != null)
foreach (GradientStop gradientStop in args.OldItems)
gradientStop.PropertyChanged -= OnGradientStopOnPropertyChanged;
if (args.NewItems != null)
foreach (GradientStop gradientStop in args.NewItems)
gradientStop.PropertyChanged += OnGradientStopOnPropertyChanged;
};
}
///
///
/// Gets the linear interpolated at the specified offset.
///
/// The percentage offset to take the color from.
/// The at the specific offset.
public override Color GetColor(float offset)
{
if (GradientStops.Count == 0) return Color.Transparent;
if (GradientStops.Count == 1) return GradientStops[0].Color;
if (_isOrderedGradientListDirty)
{
_orderedGradientStops.Clear();
_orderedGradientStops.AddRange(GradientStops.OrderBy(x => x.Offset));
}
(GradientStop gsBefore, GradientStop gsAfter) = GetEnclosingGradientStops(offset, _orderedGradientStops, WrapGradient);
float blendFactor = 0;
if (!gsBefore.Offset.Equals(gsAfter.Offset))
blendFactor = ((offset - gsBefore.Offset) / (gsAfter.Offset - gsBefore.Offset));
float colA = ((gsAfter.Color.A - gsBefore.Color.A) * blendFactor) + gsBefore.Color.A;
float colR = ((gsAfter.Color.R - gsBefore.Color.R) * blendFactor) + gsBefore.Color.R;
float colG = ((gsAfter.Color.G - gsBefore.Color.G) * blendFactor) + gsBefore.Color.G;
float colB = ((gsAfter.Color.B - gsBefore.Color.B) * blendFactor) + gsBefore.Color.B;
return new Color(colA, colR, colG, colB);
}
///
/// Get the two s encapsulating the specified offset.
///
/// The reference offset.
/// The ordered list of to choose from.
/// Bool indicating if the gradient should be wrapped or not.
/// The two s encapsulating the specified offset.
private (GradientStop gsBefore, GradientStop gsAfter) GetEnclosingGradientStops(float offset, IEnumerable orderedStops, bool wrap)
{
LinkedList gradientStops = new(orderedStops);
if (wrap)
{
GradientStop? gsBefore, gsAfter;
do
{
gsBefore = gradientStops.LastOrDefault(n => n.Offset <= offset);
if (gsBefore == null)
{
GradientStop lastStop = gradientStops.Last!.Value;
gradientStops.AddFirst(new GradientStop(lastStop.Offset - 1, lastStop.Color));
gradientStops.RemoveLast();
}
gsAfter = gradientStops.FirstOrDefault(n => n.Offset >= offset);
if (gsAfter == null)
{
GradientStop firstStop = gradientStops.First!.Value;
gradientStops.AddLast(new GradientStop(firstStop.Offset + 1, firstStop.Color));
gradientStops.RemoveFirst();
}
} while ((gsBefore == null) || (gsAfter == null));
return (gsBefore, gsAfter);
}
offset = ClipOffset(offset);
return (gradientStops.Last(n => n.Offset <= offset), gradientStops.First(n => n.Offset >= offset));
}
#endregion
}