diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
index 75d7c8a61..fa786e7c3 100644
--- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
+++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
@@ -20,7 +20,7 @@ namespace Artemis.Core
private void CreateDataBindingRegistrations()
{
DataBinding.ClearDataBindingProperties();
- if (CurrentValue == null)
+ if (CurrentValue == null!)
return;
for (int index = 0; index < CurrentValue.Count; index++)
@@ -54,10 +54,10 @@ namespace Artemis.Core
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
{
// Don't allow color gradients to be null
- if (BaseValue == null)
- BaseValue = DefaultValue ?? new ColorGradient();
+ if (BaseValue == null!)
+ BaseValue = new ColorGradient(DefaultValue);
- if (_subscribedGradient != BaseValue)
+ if (!ReferenceEquals(_subscribedGradient, BaseValue))
{
if (_subscribedGradient != null)
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
@@ -80,8 +80,8 @@ namespace Artemis.Core
protected override void OnInitialize()
{
// Don't allow color gradients to be null
- if (BaseValue == null)
- BaseValue = DefaultValue ?? new ColorGradient();
+ if (BaseValue == null!)
+ BaseValue = new ColorGradient(DefaultValue);
base.OnInitialize();
}
diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
index 589b7f5ba..173c1481e 100644
--- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
+++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
@@ -6,379 +6,571 @@ using System.ComponentModel;
using System.Linq;
using SkiaSharp;
-namespace Artemis.Core
+namespace Artemis.Core;
+
+///
+/// A gradient containing a list of s
+///
+public class ColorGradient : IList, IList, INotifyCollectionChanged
{
+ #region Equality members
+
///
- /// A gradient containing a list of s
+ /// Determines whether all the stops in this gradient are equal to the stops in the given gradient.
///
- public class ColorGradient : IList, IList, INotifyCollectionChanged
+ /// The other gradient to compare to
+ protected bool Equals(ColorGradient other)
{
- private static readonly SKColor[] FastLedRainbow =
- {
- new(0xFFFF0000), // Red
- new(0xFFFF9900), // Orange
- new(0xFFFFFF00), // Yellow
- new(0xFF00FF00), // Green
- new(0xFF00FF7E), // Aqua
- new(0xFF0078FF), // Blue
- new(0xFF9E22FF), // Purple
- new(0xFFFF34AE), // Pink
- new(0xFFFF0000) // and back to Red
- };
+ if (Count != other.Count)
+ return false;
- private readonly List _stops;
-
- ///
- /// Creates a new instance of the class
- ///
- public ColorGradient()
+ for (int i = 0; i < Count; i++)
{
- _stops = new List();
+ if (!Equals(this[i], other[i]))
+ return false;
}
- ///
- /// Gets all the colors in the color gradient
- ///
- /// The amount of times to repeat the colors
- ///
- /// A boolean indicating whether to make the gradient seamless by adding the first color behind the
- /// last color
- ///
- /// An array containing each color in the gradient
- public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
+ return true;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ return false;
+ if (ReferenceEquals(this, obj))
+ return true;
+ if (obj.GetType() != this.GetType())
+ return false;
+ return Equals((ColorGradient) obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
{
- List result = new();
- if (timesToRepeat == 0)
- {
- result = this.Select(c => c.Color).ToList();
- }
- else
- {
- List colors = this.Select(c => c.Color).ToList();
- for (int i = 0; i <= timesToRepeat; i++)
- result.AddRange(colors);
- }
-
- if (seamless && !IsSeamless())
- result.Add(result[0]);
-
- return result.ToArray();
- }
-
- ///
- /// Gets all the positions in the color gradient
- ///
- ///
- /// The amount of times to repeat the positions
- ///
- ///
- /// A boolean indicating whether to make the gradient seamless by adding the first color behind the
- /// last color
- ///
- /// An array containing a position for each color between 0.0 and 1.0
- public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
- {
- List result = new();
- if (timesToRepeat == 0)
- {
- result = this.Select(c => c.Position).ToList();
- }
- else
- {
- // Create stops and a list of divided stops
- List stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
-
- // For each repeat cycle, add the base stops to the end result
- for (int i = 0; i <= timesToRepeat; i++)
- {
- float lastStop = result.LastOrDefault();
- result.AddRange(stops.Select(s => s + lastStop));
- }
- }
-
- if (seamless && !IsSeamless())
- {
- // Compress current points evenly
- float compression = 1f - 1f / result.Count;
- for (int index = 0; index < result.Count; index++)
- result[index] = result[index] * compression;
- // Add one extra point at the end
- result.Add(1f);
- }
-
- return result.ToArray();
- }
-
- ///
- /// Gets a color at any position between 0.0 and 1.0 using interpolation
- ///
- /// A position between 0.0 and 1.0
- public SKColor GetColor(float position)
- {
- if (!this.Any())
- return SKColor.Empty;
-
- ColorGradientStop[] stops = this.ToArray();
- if (position <= 0) return stops[0].Color;
- if (position >= 1) return stops[^1].Color;
- ColorGradientStop left = stops[0];
- ColorGradientStop? right = null;
- foreach (ColorGradientStop stop in stops)
- {
- if (stop.Position >= position)
- {
- right = stop;
- break;
- }
-
- left = stop;
- }
-
- if (right == null || left == right)
- return left.Color;
-
- position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2);
- byte a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha);
- byte r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red);
- byte g = (byte) ((right.Color.Green - left.Color.Green) * position + left.Color.Green);
- byte b = (byte) ((right.Color.Blue - left.Color.Blue) * position + left.Color.Blue);
- return new SKColor(r, g, b, a);
- }
-
- ///
- /// Gets a new ColorGradient with colors looping through the HSV-spectrum
- ///
- ///
- public static ColorGradient GetUnicornBarf()
- {
- ColorGradient gradient = new();
- for (int index = 0; index < FastLedRainbow.Length; index++)
- {
- SKColor skColor = FastLedRainbow[index];
- float position = 1f / (FastLedRainbow.Length - 1f) * index;
- gradient.Add(new ColorGradientStop(skColor, position));
- }
-
- return gradient;
- }
-
- ///
- /// Determines whether the gradient is seamless
- ///
- /// if the gradient is seamless; otherwise
- public bool IsSeamless()
- {
- return Count == 0 || this.First().Color.Equals(this.Last().Color);
- }
-
- internal void Sort()
- {
- int requiredIndex = 0;
- foreach (ColorGradientStop colorGradientStop in _stops.OrderBy(s => s.Position).ToList())
- {
- int actualIndex = _stops.IndexOf(colorGradientStop);
- if (requiredIndex != actualIndex)
- {
- _stops.RemoveAt(actualIndex);
- _stops.Insert(requiredIndex, colorGradientStop);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, colorGradientStop, requiredIndex, actualIndex));
- }
-
- requiredIndex++;
- }
- }
-
- private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- Sort();
- OnStopChanged();
- }
-
- #region Implementation of IEnumerable
-
- ///
- public IEnumerator GetEnumerator()
- {
- return _stops.GetEnumerator();
- }
-
- ///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
-
- #endregion
-
- #region Implementation of ICollection
-
- ///
- public void Add(ColorGradientStop item)
- {
- _stops.Add(item);
- item.PropertyChanged += ItemOnPropertyChanged;
- Sort();
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _stops.IndexOf(item)));
- }
-
- ///
- public int Add(object? value)
- {
- if (value is ColorGradientStop stop)
- _stops.Add(stop);
-
- return IndexOf(value);
- }
-
- ///
- public void Clear()
- {
- foreach (ColorGradientStop item in _stops)
- item.PropertyChanged -= ItemOnPropertyChanged;
- _stops.Clear();
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
-
- ///
- public bool Contains(object? value)
- {
- return _stops.Contains(value);
- }
-
- ///
- public int IndexOf(object? value)
- {
- return _stops.IndexOf(value);
- }
-
- ///
- public void Insert(int index, object? value)
- {
- if (value is ColorGradientStop stop)
- _stops.Insert(index, stop);
- }
-
- ///
- public void Remove(object? value)
- {
- if (value is ColorGradientStop stop)
- _stops.Remove(stop);
- }
-
- ///
- public bool Contains(ColorGradientStop item)
- {
- return _stops.Contains(item);
- }
-
- ///
- public void CopyTo(ColorGradientStop[] array, int arrayIndex)
- {
- _stops.CopyTo(array, arrayIndex);
- }
-
- ///
- public bool Remove(ColorGradientStop item)
- {
- item.PropertyChanged -= ItemOnPropertyChanged;
- int index = _stops.IndexOf(item);
- bool removed = _stops.Remove(item);
- if (removed)
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
-
- return removed;
- }
-
- ///
- public void CopyTo(Array array, int index)
- {
- _stops.CopyTo((ColorGradientStop[]) array, index);
- }
-
- ///
- public int Count => _stops.Count;
-
- ///
- public bool IsSynchronized => false;
-
- ///
- public object SyncRoot => this;
-
- ///
- public bool IsReadOnly => false;
-
- object? IList.this[int index]
- {
- get => this[index];
- set => this[index] = (ColorGradientStop) value!;
- }
-
- #endregion
-
- #region Implementation of IList
-
- ///
- public int IndexOf(ColorGradientStop item)
- {
- return _stops.IndexOf(item);
- }
-
- ///
- public void Insert(int index, ColorGradientStop item)
- {
- _stops.Insert(index, item);
- item.PropertyChanged += ItemOnPropertyChanged;
- Sort();
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
- }
-
- ///
- public void RemoveAt(int index)
- {
- _stops[index].PropertyChanged -= ItemOnPropertyChanged;
- _stops.RemoveAt(index);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
- }
-
- public bool IsFixedSize { get; }
-
- ///
- public ColorGradientStop this[int index]
- {
- get => _stops[index];
- set
- {
- ColorGradientStop? oldValue = _stops[index];
- oldValue.PropertyChanged -= ItemOnPropertyChanged;
- _stops[index] = value;
- _stops[index].PropertyChanged += ItemOnPropertyChanged;
- Sort();
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
- }
- }
-
- #endregion
-
- #region Implementation of INotifyCollectionChanged
-
- ///
- public event NotifyCollectionChangedEventHandler? CollectionChanged;
-
- private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
- {
- CollectionChanged?.Invoke(this, e);
- }
-
- #endregion
-
- ///
- /// Occurs when any of the stops has changed in some way
- ///
- public event EventHandler? StopChanged;
-
- private void OnStopChanged()
- {
- StopChanged?.Invoke(this, EventArgs.Empty);
+ int hash = 19;
+ foreach (ColorGradientStop stops in this)
+ hash = hash * 31 + stops.GetHashCode();
+ return hash;
}
}
+
+ #endregion
+
+ private static readonly SKColor[] FastLedRainbow =
+ {
+ new(0xFFFF0000), // Red
+ new(0xFFFF9900), // Orange
+ new(0xFFFFFF00), // Yellow
+ new(0xFF00FF00), // Green
+ new(0xFF00FF7E), // Aqua
+ new(0xFF0078FF), // Blue
+ new(0xFF9E22FF), // Purple
+ new(0xFFFF34AE), // Pink
+ new(0xFFFF0000) // and back to Red
+ };
+
+ private readonly List _stops;
+ private bool _updating;
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ public ColorGradient()
+ {
+ _stops = new List();
+ }
+
+ ///
+ /// Creates a new instance of the class
+ ///
+ /// The color gradient to copy
+ public ColorGradient(ColorGradient? colorGradient)
+ {
+ _stops = new List();
+ if (colorGradient == null)
+ return;
+
+ foreach (ColorGradientStop colorGradientStop in colorGradient)
+ {
+ ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position);
+ stop.PropertyChanged += ItemOnPropertyChanged;
+ _stops.Add(stop);
+ }
+ }
+
+ ///
+ /// Gets all the colors in the color gradient
+ ///
+ /// The amount of times to repeat the colors
+ ///
+ /// A boolean indicating whether to make the gradient seamless by adding the first color behind the
+ /// last color
+ ///
+ /// An array containing each color in the gradient
+ public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
+ {
+ List result = new();
+ if (timesToRepeat == 0)
+ {
+ result = this.Select(c => c.Color).ToList();
+ }
+ else
+ {
+ List colors = this.Select(c => c.Color).ToList();
+ for (int i = 0; i <= timesToRepeat; i++)
+ result.AddRange(colors);
+ }
+
+ if (seamless && !IsSeamless())
+ result.Add(result[0]);
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Gets all the positions in the color gradient
+ ///
+ ///
+ /// The amount of times to repeat the positions
+ ///
+ ///
+ /// A boolean indicating whether to make the gradient seamless by adding the first color behind the
+ /// last color
+ ///
+ /// An array containing a position for each color between 0.0 and 1.0
+ public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
+ {
+ List result = new();
+ if (timesToRepeat == 0)
+ {
+ result = this.Select(c => c.Position).ToList();
+ }
+ else
+ {
+ // Create stops and a list of divided stops
+ List stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
+
+ // For each repeat cycle, add the base stops to the end result
+ for (int i = 0; i <= timesToRepeat; i++)
+ {
+ float lastStop = result.LastOrDefault();
+ result.AddRange(stops.Select(s => s + lastStop));
+ }
+ }
+
+ if (seamless && !IsSeamless())
+ {
+ // Compress current points evenly
+ float compression = 1f - 1f / result.Count;
+ for (int index = 0; index < result.Count; index++)
+ result[index] = result[index] * compression;
+ // Add one extra point at the end
+ result.Add(1f);
+ }
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Gets a color at any position between 0.0 and 1.0 using interpolation
+ ///
+ /// A position between 0.0 and 1.0
+ public SKColor GetColor(float position)
+ {
+ if (!this.Any())
+ return new SKColor(255, 255, 255);
+
+ ColorGradientStop[] stops = this.ToArray();
+ if (position <= 0) return stops[0].Color;
+ if (position >= 1) return stops[^1].Color;
+ ColorGradientStop left = stops[0];
+ ColorGradientStop? right = null;
+ foreach (ColorGradientStop stop in stops)
+ {
+ if (stop.Position >= position)
+ {
+ right = stop;
+ break;
+ }
+
+ left = stop;
+ }
+
+ if (right == null || left == right)
+ return left.Color;
+
+ position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2);
+ byte a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha);
+ byte r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red);
+ byte g = (byte) ((right.Color.Green - left.Color.Green) * position + left.Color.Green);
+ byte b = (byte) ((right.Color.Blue - left.Color.Blue) * position + left.Color.Blue);
+ return new SKColor(r, g, b, a);
+ }
+
+ ///
+ /// Gets a new ColorGradient with colors looping through the HSV-spectrum
+ ///
+ public static ColorGradient GetUnicornBarf()
+ {
+ ColorGradient gradient = new();
+ for (int index = 0; index < FastLedRainbow.Length; index++)
+ {
+ SKColor skColor = FastLedRainbow[index];
+ float position = 1f / (FastLedRainbow.Length - 1f) * index;
+ gradient.Add(new ColorGradientStop(skColor, position));
+ }
+
+ return gradient;
+ }
+
+ ///
+ /// Gets a new ColorGradient with random colors from the HSV-spectrum
+ ///
+ /// The amount of stops to add
+ public ColorGradient GetRandom(int stops)
+ {
+ ColorGradient gradient = new();
+ Random random = new();
+ for (int index = 0; index < stops; index++)
+ {
+ SKColor skColor = SKColor.FromHsv(random.NextSingle() * 360, 100, 100);
+ float position = 1f / (stops - 1f) * index;
+ gradient.Add(new ColorGradientStop(skColor, position));
+ }
+
+ return gradient;
+ }
+
+ ///
+ /// Determines whether the gradient is seamless
+ ///
+ /// if the gradient is seamless; otherwise
+ public bool IsSeamless()
+ {
+ return Count == 0 || this.First().Color.Equals(this.Last().Color);
+ }
+
+ ///
+ /// Spreads the color stops equally across the gradient.
+ ///
+ public void SpreadStops()
+ {
+ try
+ {
+ _updating = true;
+ for (int i = 0; i < Count; i++)
+ this[i].Position = MathF.Round(i / ((float) Count - 1), 3, MidpointRounding.AwayFromZero);
+ }
+ finally
+ {
+ _updating = false;
+ Sort();
+ }
+ }
+
+ ///
+ /// If not already the case, makes the gradient seamless by adding the first color to the end of the gradient and
+ /// compressing the other stops.
+ ///
+ /// If the gradient is already seamless, removes the last color and spreads the remaining stops to fill the freed
+ /// space.
+ ///
+ ///
+ public void ToggleSeamless()
+ {
+ try
+ {
+ _updating = true;
+
+ if (IsSeamless())
+ {
+ ColorGradientStop stopToRemove = this.Last();
+ Remove(stopToRemove);
+
+ // Uncompress the stops if there is still more than one
+ if (Count >= 2)
+ {
+ float multiplier = Count / (Count - 1f);
+ foreach (ColorGradientStop stop in this)
+ stop.Position = MathF.Round(Math.Min(stop.Position * multiplier, 100f), 3, MidpointRounding.AwayFromZero);
+ }
+ }
+ else
+ {
+ // Compress existing stops to the left
+ float multiplier = (Count - 1f) / Count;
+ foreach (ColorGradientStop stop in this)
+ stop.Position = MathF.Round(stop.Position * multiplier, 3, MidpointRounding.AwayFromZero);
+
+ // Add a stop to the end that is the same color as the first stop
+ ColorGradientStop newStop = new(this.First().Color, 1f);
+ Add(newStop);
+ }
+ }
+ finally
+ {
+ _updating = false;
+ Sort();
+ }
+ }
+
+ ///
+ /// Flips the stops of the gradient.
+ ///
+ public void FlipStops()
+ {
+ try
+ {
+ _updating = true;
+ foreach (ColorGradientStop stop in this)
+ stop.Position = 1 - stop.Position;
+ }
+ finally
+ {
+ _updating = false;
+ Sort();
+ }
+ }
+
+ ///
+ /// Rotates the stops of the gradient shifting every stop over to the position of it's neighbor and wrapping around at
+ /// the end of the gradient.
+ ///
+ /// A boolean indicating whether or not the invert the rotation.
+ public void RotateStops(bool inverse)
+ {
+ try
+ {
+ _updating = true;
+ List stops = inverse
+ ? this.OrderBy(s => s.Position).ToList()
+ : this.OrderByDescending(s => s.Position).ToList();
+
+ float lastStopPosition = stops.Last().Position;
+ foreach (ColorGradientStop stop in stops)
+ (stop.Position, lastStopPosition) = (lastStopPosition, stop.Position);
+ }
+ finally
+ {
+ _updating = false;
+ Sort();
+ }
+ }
+
+ ///
+ /// Occurs when any of the stops has changed in some way
+ ///
+ public event EventHandler? StopChanged;
+
+ internal void Sort()
+ {
+ if (_updating)
+ return;
+
+ int requiredIndex = 0;
+ foreach (ColorGradientStop colorGradientStop in _stops.OrderBy(s => s.Position).ToList())
+ {
+ int actualIndex = _stops.IndexOf(colorGradientStop);
+ if (requiredIndex != actualIndex)
+ {
+ _stops.RemoveAt(actualIndex);
+ _stops.Insert(requiredIndex, colorGradientStop);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, colorGradientStop, requiredIndex, actualIndex));
+ }
+
+ requiredIndex++;
+ }
+ }
+
+ private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ Sort();
+ OnStopChanged();
+ }
+
+ private void OnStopChanged()
+ {
+ StopChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ #region Implementation of IEnumerable
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return _stops.GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+
+ #region Implementation of ICollection
+
+ ///
+ public void Add(ColorGradientStop item)
+ {
+ _stops.Add(item);
+ item.PropertyChanged += ItemOnPropertyChanged;
+
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
+ Sort();
+ }
+
+ ///
+ public int Add(object? value)
+ {
+ if (value is ColorGradientStop stop)
+ _stops.Add(stop);
+
+ return IndexOf(value);
+ }
+
+ ///
+ public void Clear()
+ {
+ foreach (ColorGradientStop item in _stops)
+ item.PropertyChanged -= ItemOnPropertyChanged;
+ _stops.Clear();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ ///
+ public bool Contains(object? value)
+ {
+ return _stops.Contains(value);
+ }
+
+ ///
+ public int IndexOf(object? value)
+ {
+ return _stops.IndexOf(value);
+ }
+
+ ///
+ public void Insert(int index, object? value)
+ {
+ if (value is ColorGradientStop stop)
+ _stops.Insert(index, stop);
+ }
+
+ ///
+ public void Remove(object? value)
+ {
+ if (value is ColorGradientStop stop)
+ _stops.Remove(stop);
+ }
+
+ ///
+ public bool Contains(ColorGradientStop item)
+ {
+ return _stops.Contains(item);
+ }
+
+ ///
+ public void CopyTo(ColorGradientStop[] array, int arrayIndex)
+ {
+ _stops.CopyTo(array, arrayIndex);
+ }
+
+ ///
+ public bool Remove(ColorGradientStop item)
+ {
+ item.PropertyChanged -= ItemOnPropertyChanged;
+ int index = _stops.IndexOf(item);
+ bool removed = _stops.Remove(item);
+ if (removed)
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
+
+ return removed;
+ }
+
+ ///
+ public void CopyTo(Array array, int index)
+ {
+ _stops.CopyTo((ColorGradientStop[]) array, index);
+ }
+
+ ///
+ public int Count => _stops.Count;
+
+ ///
+ public bool IsSynchronized => false;
+
+ ///
+ public object SyncRoot => this;
+
+ ///
+ public bool IsReadOnly => false;
+
+ object? IList.this[int index]
+ {
+ get => this[index];
+ set => this[index] = (ColorGradientStop) value!;
+ }
+
+ #endregion
+
+ #region Implementation of IList
+
+ ///
+ public int IndexOf(ColorGradientStop item)
+ {
+ return _stops.IndexOf(item);
+ }
+
+ ///
+ public void Insert(int index, ColorGradientStop item)
+ {
+ _stops.Insert(index, item);
+ item.PropertyChanged += ItemOnPropertyChanged;
+ Sort();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
+ }
+
+ ///
+ public void RemoveAt(int index)
+ {
+ _stops[index].PropertyChanged -= ItemOnPropertyChanged;
+ _stops.RemoveAt(index);
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
+ }
+
+ public bool IsFixedSize { get; }
+
+ ///
+ public ColorGradientStop this[int index]
+ {
+ get => _stops[index];
+ set
+ {
+ ColorGradientStop? oldValue = _stops[index];
+ oldValue.PropertyChanged -= ItemOnPropertyChanged;
+ _stops[index] = value;
+ _stops[index].PropertyChanged += ItemOnPropertyChanged;
+ Sort();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
+ }
+ }
+
+ #endregion
+
+ #region Implementation of INotifyCollectionChanged
+
+ ///
+ public event NotifyCollectionChangedEventHandler? CollectionChanged;
+
+ private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
+ {
+ CollectionChanged?.Invoke(this, e);
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs
index 242a883d3..8f19b2b95 100644
--- a/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs
+++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs
@@ -1,4 +1,5 @@
-using SkiaSharp;
+using System;
+using SkiaSharp;
namespace Artemis.Core
{
@@ -7,6 +8,34 @@ namespace Artemis.Core
///
public class ColorGradientStop : CorePropertyChanged
{
+ #region Equality members
+
+ ///
+ protected bool Equals(ColorGradientStop other)
+ {
+ return _color.Equals(other._color) && _position.Equals(other._position);
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ return false;
+ if (ReferenceEquals(this, obj))
+ return true;
+ if (obj.GetType() != this.GetType())
+ return false;
+ return Equals((ColorGradientStop) obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_color, _position);
+ }
+
+ #endregion
+
private SKColor _color;
private float _position;
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs b/src/Avalonia/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs
new file mode 100644
index 000000000..ba4be29af
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs
@@ -0,0 +1,25 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+
+namespace Artemis.UI.Shared.Controls.Flyouts;
+
+///
+/// Defines a flyout that hosts a gradient picker.
+///
+public sealed class GradientPickerFlyout : Flyout
+{
+ private GradientPicker.GradientPicker? _picker;
+
+ ///
+ /// Gets the gradient picker that this flyout hosts
+ ///
+ public GradientPicker.GradientPicker GradientPicker => _picker ??= new GradientPicker.GradientPicker();
+
+ ///
+ protected override Control CreatePresenter()
+ {
+ _picker ??= new GradientPicker.GradientPicker();
+ FlyoutPresenter presenter = new() {Content = GradientPicker};
+ return presenter;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs b/src/Avalonia/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs
index 8359568f5..78dd6cf72 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs
@@ -1,24 +1,33 @@
using System;
using System.Collections.Specialized;
using System.Linq;
+using System.Windows.Input;
using Artemis.Core;
+using Artemis.UI.Shared.Providers;
using Avalonia;
+using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
+using Avalonia.Interactivity;
using Avalonia.Media;
+using FluentAvalonia.UI.Controls;
+using FluentAvalonia.UI.Media;
+using ReactiveUI;
+using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker;
+///
+/// Represents a gradient picker that can be used to edit a gradient.
+///
public class GradientPicker : TemplatedControl
{
- private LinearGradientBrush _linearGradientBrush = new();
-
///
/// Gets or sets the color gradient.
///
public static readonly StyledProperty ColorGradientProperty =
- AvaloniaProperty.Register(nameof(Core.ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
+ AvaloniaProperty.Register(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
///
/// Gets or sets the currently selected color stop.
@@ -27,11 +36,55 @@ public class GradientPicker : TemplatedControl
AvaloniaProperty.Register(nameof(SelectedColorStop), defaultBindingMode: BindingMode.TwoWay);
///
- /// Gets the linear gradient brush representing the color gradient.
+ /// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
+ ///
+ public static readonly StyledProperty IsCompactProperty =
+ AvaloniaProperty.Register(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
+
+ ///
+ /// Gets or sets a storage provider to use for storing and loading gradients.
+ ///
+ public static readonly StyledProperty StorageProviderProperty =
+ AvaloniaProperty.Register(nameof(StorageProvider), notifying: StorageProviderChanged);
+
+ ///
+ /// Gets the linear gradient brush representing the color gradient.
///
public static readonly DirectProperty LinearGradientBrushProperty =
AvaloniaProperty.RegisterDirect(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
+ ///
+ /// Gets the command to execute when deleting stops.
+ ///
+ public static readonly DirectProperty DeleteStopProperty =
+ AvaloniaProperty.RegisterDirect(nameof(DeleteStop), g => g.DeleteStop);
+
+ private readonly ICommand _deleteStop;
+ private Button? _flipStops;
+ private Border? _gradient;
+ private Button? _rotateStops;
+ private bool _shiftDown;
+ private Button? _spreadStops;
+ private Button? _toggleSeamless;
+ private ColorGradient? _lastColorGradient;
+ private ColorPicker? _colorPicker;
+
+ public GradientPicker()
+ {
+ _deleteStop = ReactiveCommand.Create(s =>
+ {
+ if (ColorGradient.Count <= 2)
+ return;
+
+ int index = ColorGradient.IndexOf(s);
+ ColorGradient.Remove(s);
+ if (index > ColorGradient.Count - 1)
+ index--;
+
+ SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
+ });
+ }
+
///
/// Gets or sets the color gradient.
///
@@ -47,39 +100,130 @@ public class GradientPicker : TemplatedControl
public ColorGradientStop? SelectedColorStop
{
get => GetValue(SelectedColorStopProperty);
- set => SetValue(SelectedColorStopProperty, value);
+ set
+ {
+ if (_colorPicker != null && SelectedColorStop != null)
+ _colorPicker.PreviousColor = new Color2(SelectedColorStop.Color.Red, SelectedColorStop.Color.Green, SelectedColorStop.Color.Blue, SelectedColorStop.Color.Alpha);
+ SetValue(SelectedColorStopProperty, value);
+ }
}
///
- /// Gets the linear gradient brush representing the color gradient.
+ /// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
///
- public LinearGradientBrush LinearGradientBrush
+ public bool IsCompact
{
- get => _linearGradientBrush;
- private set => SetAndRaise(LinearGradientBrushProperty, ref _linearGradientBrush, value);
+ get => GetValue(IsCompactProperty);
+ set => SetValue(IsCompactProperty, value);
+ }
+
+ ///
+ /// Gets or sets a storage provider to use for storing and loading gradients.
+ ///
+ public IColorGradientStorageProvider? StorageProvider
+ {
+ get => GetValue(StorageProviderProperty);
+ set => SetValue(StorageProviderProperty, value);
+ }
+
+ ///
+ /// Gets the linear gradient brush representing the color gradient.
+ ///
+ public LinearGradientBrush LinearGradientBrush { get; } = new();
+
+ ///
+ /// Gets the command to execute when deleting stops.
+ ///
+ public ICommand DeleteStop
+ {
+ get => _deleteStop;
+ private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
+ }
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ if (_gradient != null)
+ _gradient.PointerPressed -= GradientOnPointerPressed;
+ if (_spreadStops != null)
+ _spreadStops.Click -= SpreadStopsOnClick;
+ if (_toggleSeamless != null)
+ _toggleSeamless.Click -= ToggleSeamlessOnClick;
+ if (_flipStops != null)
+ _flipStops.Click -= FlipStopsOnClick;
+ if (_rotateStops != null)
+ _rotateStops.Click -= RotateStopsOnClick;
+
+ _colorPicker = e.NameScope.Find("ColorPicker");
+ _gradient = e.NameScope.Find("Gradient");
+ _spreadStops = e.NameScope.Find