diff --git a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
index 387c2d9ef..e811a4e0f 100644
--- a/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
+++ b/src/Artemis.Core/DefaultTypes/Properties/ColorGradientLayerProperty.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
using SkiaSharp;
namespace Artemis.Core
@@ -23,17 +24,16 @@ namespace Artemis.Core
if (CurrentValue == null)
return;
- for (int index = 0; index < CurrentValue.Stops.Count; index++)
+ for (int index = 0; index < CurrentValue.Count; index++)
{
int stopIndex = index;
void Setter(SKColor value)
{
- CurrentValue.Stops[stopIndex].Color = value;
- CurrentValue.OnColorValuesUpdated();
+ CurrentValue[stopIndex].Color = value;
}
- RegisterDataBindingProperty(() => CurrentValue.Stops[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
+ RegisterDataBindingProperty(() => CurrentValue[stopIndex].Color, Setter, new ColorStopDataBindingConverter(), $"Color #{stopIndex + 1}");
}
}
@@ -61,17 +61,17 @@ namespace Artemis.Core
if (_subscribedGradient != BaseValue)
{
if (_subscribedGradient != null)
- _subscribedGradient.PropertyChanged -= SubscribedGradientOnPropertyChanged;
+ _subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
_subscribedGradient = BaseValue;
- _subscribedGradient.PropertyChanged += SubscribedGradientOnPropertyChanged;
+ _subscribedGradient.CollectionChanged += SubscribedGradientOnPropertyChanged;
}
CreateDataBindingRegistrations();
}
- private void SubscribedGradientOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ private void SubscribedGradientOnPropertyChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
- if (CurrentValue.Stops.Count != GetAllDataBindingRegistrations().Count)
+ if (CurrentValue.Count != GetAllDataBindingRegistrations().Count)
CreateDataBindingRegistrations();
}
diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
index 4f630fcda..c621eb5c7 100644
--- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
+++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
using System.Linq;
using SkiaSharp;
@@ -8,7 +11,7 @@ namespace Artemis.Core
///
/// A gradient containing a list of s
///
- public class ColorGradient : CorePropertyChanged
+ public class ColorGradient : IList, INotifyCollectionChanged
{
private static readonly SKColor[] FastLedRainbow =
{
@@ -23,19 +26,16 @@ namespace Artemis.Core
new(0xFFFF0000) // and back to Red
};
+ private readonly List _stops;
+
///
/// Creates a new instance of the class
///
public ColorGradient()
{
- Stops = new List();
+ _stops = new List();
}
- ///
- /// Gets a list of all the s in the gradient
- ///
- public List Stops { get; }
-
///
/// Gets all the colors in the color gradient
///
@@ -49,13 +49,16 @@ namespace Artemis.Core
{
List result = new();
if (timesToRepeat == 0)
- result = Stops.Select(c => c.Color).ToList();
+ {
+ result = this.Select(c => c.Color).ToList();
+ }
else
{
- List colors = Stops.Select(c => c.Color).ToList();
+ 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]);
@@ -77,11 +80,13 @@ namespace Artemis.Core
{
List result = new();
if (timesToRepeat == 0)
- result = Stops.Select(c => c.Position).ToList();
+ {
+ result = this.Select(c => c.Position).ToList();
+ }
else
{
// Create stops and a list of divided stops
- List stops = Stops.Select(c => c.Position / (timesToRepeat + 1)).ToList();
+ 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++)
@@ -104,25 +109,16 @@ namespace Artemis.Core
return result.ToArray();
}
- ///
- /// Triggers a property changed event of the collection
- ///
- public void OnColorValuesUpdated()
- {
- Stops.Sort((a, b) => a.Position.CompareTo(b.Position));
- OnPropertyChanged(nameof(Stops));
- }
-
///
/// 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 (!Stops.Any())
+ if (!this.Any())
return SKColor.Empty;
- ColorGradientStop[] stops = Stops.ToArray();
+ ColorGradientStop[] stops = this.ToArray();
if (position <= 0) return stops[0].Color;
if (position >= 1) return stops[^1].Color;
ColorGradientStop left = stops[0];
@@ -160,7 +156,7 @@ namespace Artemis.Core
{
SKColor skColor = FastLedRainbow[index];
float position = 1f / (FastLedRainbow.Length - 1f) * index;
- gradient.Stops.Add(new ColorGradientStop(skColor, position));
+ gradient.Add(new ColorGradientStop(skColor, position));
}
return gradient;
@@ -172,7 +168,141 @@ namespace Artemis.Core
/// if the gradient is seamless; otherwise
public bool IsSeamless()
{
- return Stops.Count == 0 || Stops.First().Color.Equals(Stops.Last().Color);
+ return Count == 0 || this.First().Color.Equals(this.Last().Color);
}
+
+ internal void Sort()
+ {
+ _stops.Sort((a, b) => a.Position.CompareTo(b.Position));
+ }
+
+ private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ Sort();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ #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 void Clear()
+ {
+ foreach (ColorGradientStop item in _stops)
+ item.PropertyChanged -= ItemOnPropertyChanged;
+ _stops.Clear();
+ OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+
+ ///
+ 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 int Count => _stops.Count;
+
+ ///
+ public bool IsReadOnly => false;
+
+ #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 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/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs
index 47a08ab8a..5e544e187 100644
--- a/src/Artemis.Core/Models/Profile/Layer.cs
+++ b/src/Artemis.Core/Models/Profile/Layer.cs
@@ -297,6 +297,9 @@ namespace Artemis.Core
private void ApplyTimeline(Timeline timeline)
{
+ if (timeline.Delta == TimeSpan.Zero)
+ return;
+
General.Update(timeline);
Transform.Update(timeline);
if (LayerBrush != null)
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
index c42b1f74c..2b4202cb9 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/ILayerProperty.cs
@@ -33,6 +33,11 @@ namespace Artemis.Core
///
Type PropertyType { get; }
+ ///
+ /// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
+ ///
+ bool IsLoadedFromStorage { get; }
+
///
/// Initializes the layer property
///
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushPreset.cs b/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushPreset.cs
new file mode 100644
index 000000000..72fa2aa40
--- /dev/null
+++ b/src/Artemis.Core/Plugins/LayerBrushes/ILayerBrushPreset.cs
@@ -0,0 +1,28 @@
+namespace Artemis.Core.LayerBrushes
+{
+ ///
+ /// Represents a brush preset for a brush.
+ ///
+ public interface ILayerBrushPreset
+ {
+ ///
+ /// Gets the name of the preset
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the description of the preset
+ ///
+ string Description { get; }
+
+ ///
+ /// Gets the icon of the preset
+ ///
+ string Icon { get; }
+
+ ///
+ /// Applies the preset to the layer brush
+ ///
+ void Apply();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
index c452384e3..2e1712250 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using SkiaSharp;
namespace Artemis.Core.LayerBrushes
@@ -70,6 +72,16 @@ namespace Artemis.Core.LayerBrushes
///
public virtual LayerPropertyGroup? BaseProperties => null;
+ ///
+ /// Gets a list of presets available to this layer brush
+ ///
+ public virtual List? Presets => null;
+
+ ///
+ /// Gets the default preset used for new instances of this layer brush
+ ///
+ public virtual ILayerBrushPreset? DefaultPreset => Presets?.FirstOrDefault();
+
///
/// Gets or sets whether the brush supports transformations
/// Note: RGB.NET brushes can never be transformed and setting this to true will throw an exception
diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
index 5b12849b8..9184e1c6b 100644
--- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
+++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/PropertiesLayerBrush.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
namespace Artemis.Core.LayerBrushes
{
diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml
index e9f083456..be9ac72d7 100644
--- a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml
+++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml
@@ -38,7 +38,7 @@
diff --git a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs
index ad4138311..2a1c9de0a 100644
--- a/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs
+++ b/src/Artemis.UI.Shared/Converters/ColorGradientToGradientStopsConverter.cs
@@ -14,18 +14,18 @@ namespace Artemis.UI.Shared
/// Converts into a
/// .
///
- [ValueConversion(typeof(List), typeof(GradientStopCollection))]
+ [ValueConversion(typeof(ColorGradient), typeof(GradientStopCollection))]
public class ColorGradientToGradientStopsConverter : IValueConverter
{
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- List colorGradients = (List) value;
+ ColorGradient? colorGradient = value as ColorGradient;
GradientStopCollection collection = new();
- if (colorGradients == null)
+ if (colorGradient == null)
return collection;
- foreach (ColorGradientStop c in colorGradients.OrderBy(s => s.Position))
+ foreach (ColorGradientStop c in colorGradient.OrderBy(s => s.Position))
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
return collection;
}
@@ -33,8 +33,8 @@ namespace Artemis.UI.Shared
///
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
- GradientStopCollection collection = (GradientStopCollection) value;
- List colorGradients = new();
+ GradientStopCollection? collection = value as GradientStopCollection;
+ ColorGradient colorGradients = new();
if (collection == null)
return colorGradients;
diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs
index 7d93d868a..7629a9899 100644
--- a/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs
+++ b/src/Artemis.UI.Shared/Screens/GradientEditor/ColorStopViewModel.cs
@@ -20,7 +20,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
{
_gradientEditorViewModel = gradientEditorViewModel;
ColorStop = colorStop;
- ColorStop.PropertyChanged += ColorStopOnPropertyChanged;
}
public ColorGradientStop ColorStop { get; }
@@ -58,12 +57,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
get => _willRemoveColorStop;
set => SetAndNotify(ref _willRemoveColorStop, value);
}
-
- private void ColorStopOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- _gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
- }
-
+
#region Movement
public void StopMouseDown(object sender, MouseButtonEventArgs e)
@@ -102,7 +96,7 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
double minValue = 0.0;
double maxValue = _gradientEditorViewModel.PreviewWidth;
- List stops = _gradientEditorViewModel.ColorGradient.Stops.OrderBy(s => s.Position).ToList();
+ List stops = _gradientEditorViewModel.ColorGradient.ToList();
ColorGradientStop? previous = stops.IndexOf(ColorStop) >= 1 ? stops[stops.IndexOf(ColorStop) - 1] : null;
ColorGradientStop? next = stops.IndexOf(ColorStop) + 1 < stops.Count ? stops[stops.IndexOf(ColorStop) + 1] : null;
if (previous != null)
@@ -111,7 +105,6 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
maxValue = next.Position * _gradientEditorViewModel.PreviewWidth;
Offset = Math.Max(minValue, Math.Min(maxValue, position.X));
- _gradientEditorViewModel.ColorGradient.OnColorValuesUpdated();
}
#endregion
diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml
index 49af71b72..64e084204 100644
--- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml
+++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorView.xaml
@@ -66,7 +66,7 @@
diff --git a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs
index f53a099af..497d81963 100644
--- a/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs
+++ b/src/Artemis.UI.Shared/Screens/GradientEditor/GradientEditorViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
@@ -6,6 +7,7 @@ using System.Windows.Controls;
using System.Windows.Input;
using Artemis.Core;
using Artemis.UI.Shared.Services;
+using MaterialDesignThemes.Wpf;
using Stylet;
namespace Artemis.UI.Shared.Screens.GradientEditor
@@ -21,12 +23,24 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
ColorGradient = colorGradient;
ColorStopViewModels = new BindableCollection();
- _originalStops = ColorGradient.Stops.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
+ _originalStops = ColorGradient.Select(s => new ColorGradientStop(s.Color, s.Position)).ToList();
PropertyChanged += UpdateColorStopViewModels;
+ ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
}
- public BindableCollection ColorStopViewModels { get; set; }
+ #region Overrides of DialogViewModelBase
+
+ ///
+ public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
+ {
+ ColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
+ base.OnDialogClosed(sender, e);
+ }
+
+ #endregion
+
+ public BindableCollection ColorStopViewModels { get; }
public ColorStopViewModel? SelectedColorStopViewModel
{
@@ -48,15 +62,19 @@ namespace Artemis.UI.Shared.Screens.GradientEditor
set => SetAndNotify(ref _previewWidth, value);
}
+ public ColorGradient Stops
+ {
+ get => ColorGradient;
+ }
+
public void AddColorStop(object sender, MouseEventArgs e)
{
Canvas? child = VisualTreeUtilities.FindChild