diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index 4e0274d5b..fc7108775 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Windows.Documents; using Artemis.Core.Annotations; using SkiaSharp; using Stylet; @@ -30,14 +32,37 @@ namespace Artemis.Core.Models.Profile.Colors } } - public SKColor[] GetColorsArray() + public SKColor[] GetColorsArray(int timesToRepeat = 0) { - return Stops.OrderBy(c => c.Position).Select(c => c.Color).ToArray(); + if (timesToRepeat == 0) + return Stops.OrderBy(c => c.Position).Select(c => c.Color).ToArray(); + + var colors = Stops.OrderBy(c => c.Position).Select(c => c.Color).ToList(); + var result = new List(); + + for (var i = 0; i <= timesToRepeat; i++) + result.AddRange(colors); + + return result.ToArray(); } - public float[] GetPositionsArray() + public float[] GetPositionsArray(int timesToRepeat = 0) { - return Stops.OrderBy(c => c.Position).Select(c => c.Position).ToArray(); + if (timesToRepeat == 0) + return Stops.OrderBy(c => c.Position).Select(c => c.Position).ToArray(); + + // Create stops and a list of divided stops + var stops = Stops.OrderBy(c => c.Position).Select(c => c.Position / (timesToRepeat + 1)).ToList(); + var result = new List(); + + // For each repeat cycle, add the base stops to the end result + for (var i = 0; i <= timesToRepeat; i++) + { + var localStops = stops.Select(s => s + result.LastOrDefault()).ToList(); + result.AddRange(localStops); + } + + return result.ToArray(); } public void OnColorValuesUpdated() diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 99b21c3b3..224a08896 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -334,6 +334,7 @@ namespace Artemis.Core.Models.Profile if (Parent is Folder parentFolder) targetLocation = Path.Bounds.Location - parentFolder.Path.Bounds.Location; + canvas.DrawBitmap(_layerBitmap, targetLocation, layerPaint); } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs index ad00c49a9..d069cae16 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs @@ -38,5 +38,10 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Attributes /// Maximum input value, only enforced in the UI /// public object MaxInputValue { get; set; } + + /// + /// Whether or not keyframes are supported, true by default and cannot be changed at runtime + /// + public bool KeyframesSupported { get; set; } = true; } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 111b774ef..54692aff2 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -28,9 +28,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties public LayerPropertyGroup Parent { get; internal set; } /// - /// Gets whether keyframes are supported on this property + /// Gets whether keyframes are supported on this type of property /// - public bool KeyframesSupported { get; protected set; } = true; + public bool KeyframesSupported { get; protected internal set; } = true; /// /// Gets or sets whether keyframes are enabled on this property, has no effect if is diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 2640a9c60..e20d958ae 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -156,6 +156,7 @@ namespace Artemis.Core.Models.Profile instance.ProfileElement = profileElement; instance.Parent = this; instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription; + instance.KeyframesSupported = instance.PropertyDescription.KeyframesSupported; InitializeProperty(profileElement, path + propertyInfo.Name, instance); propertyInfo.SetValue(this, instance); diff --git a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs index 65c4614f4..150a08c3d 100644 --- a/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrush/Abstract/PerLedLayerBrush.cs @@ -27,9 +27,10 @@ namespace Artemis.Core.Plugins.LayerBrush.Abstract internal override void InternalRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { - // This lil' snippet renders per LED, it's neater but doesn't support translations + // We don't want translations on this canvas because that'll displace the LEDs, translations are applied to the points of each LED instead Layer.ExcludeCanvasFromTranslation(canvas, true); + // Apply a translated version of the shape as the clipping mask var shapePath = new SKPath(Layer.LayerShape.Path); Layer.IncludePathInTranslation(shapePath, true); canvas.ClipPath(shapePath); diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index 43338f7cd..c4c3bb30c 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.LayerBrush.Abstract; using SkiaSharp; @@ -17,6 +18,8 @@ namespace Artemis.Plugins.LayerBrushes.Color Layer.RenderPropertiesUpdated += HandleShaderChange; Properties.GradientType.BaseValueChanged += HandleShaderChange; Properties.Color.BaseValueChanged += HandleShaderChange; + Properties.GradientTileMode.BaseValueChanged += HandleShaderChange; + Properties.GradientRepeat.BaseValueChanged += HandleShaderChange; Properties.Gradient.BaseValue.PropertyChanged += BaseValueOnPropertyChanged; } @@ -25,6 +28,8 @@ namespace Artemis.Plugins.LayerBrushes.Color Layer.RenderPropertiesUpdated -= HandleShaderChange; Properties.GradientType.BaseValueChanged -= HandleShaderChange; Properties.Color.BaseValueChanged -= HandleShaderChange; + Properties.GradientTileMode.BaseValueChanged -= HandleShaderChange; + Properties.GradientRepeat.BaseValueChanged -= HandleShaderChange; Properties.Gradient.BaseValue.PropertyChanged -= BaseValueOnPropertyChanged; _paint?.Dispose(); @@ -46,10 +51,22 @@ namespace Artemis.Plugins.LayerBrushes.Color public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { - if (path.Bounds != _shaderBounds) + if (Layer.General.FillType.CurrentValue == LayerFillType.Clip) { - _shaderBounds = path.Bounds; - CreateShader(); + var layerBounds = new SKRect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height); + if (layerBounds != _shaderBounds) + { + _shaderBounds = layerBounds; + CreateShader(); + } + } + else + { + if (path.Bounds != _shaderBounds) + { + _shaderBounds = path.Bounds; + CreateShader(); + } } paint.Shader = _shader; @@ -69,26 +86,27 @@ namespace Artemis.Plugins.LayerBrushes.Color private void CreateShader() { var center = new SKPoint(_shaderBounds.MidX, _shaderBounds.MidY); + var repeat = Properties.GradientRepeat.CurrentValue; var shader = Properties.GradientType.CurrentValue switch { GradientType.Solid => SKShader.CreateColor(_color), GradientType.LinearGradient => SKShader.CreateLinearGradient( new SKPoint(_shaderBounds.Left, _shaderBounds.Top), new SKPoint(_shaderBounds.Right, _shaderBounds.Top), - Properties.Gradient.BaseValue.GetColorsArray(), - Properties.Gradient.BaseValue.GetPositionsArray(), - SKShaderTileMode.Clamp), + Properties.Gradient.BaseValue.GetColorsArray(repeat), + Properties.Gradient.BaseValue.GetPositionsArray(repeat), + Properties.GradientTileMode.CurrentValue), GradientType.RadialGradient => SKShader.CreateRadialGradient( center, Math.Max(_shaderBounds.Width, _shaderBounds.Height) / 2f, - Properties.Gradient.BaseValue.GetColorsArray(), - Properties.Gradient.BaseValue.GetPositionsArray(), - SKShaderTileMode.Clamp), + Properties.Gradient.BaseValue.GetColorsArray(repeat), + Properties.Gradient.BaseValue.GetPositionsArray(repeat), + Properties.GradientTileMode.CurrentValue), GradientType.SweepGradient => SKShader.CreateSweepGradient( center, - Properties.Gradient.BaseValue.GetColorsArray(), - Properties.Gradient.BaseValue.GetPositionsArray(), - SKShaderTileMode.Clamp, + Properties.Gradient.BaseValue.GetColorsArray(repeat), + Properties.Gradient.BaseValue.GetPositionsArray(repeat), + Properties.GradientTileMode.CurrentValue, 0, 360), _ => throw new ArgumentOutOfRangeException() diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs index 45f2b812b..041e9d035 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Color/ColorBrushProperties.cs @@ -12,22 +12,32 @@ namespace Artemis.Plugins.LayerBrushes.Color [PropertyDescription(Description = "The type of color brush to draw")] public EnumLayerProperty GradientType { get; set; } + [PropertyDescription(Description = "How to handle the layer having to stretch beyond it's regular size")] + public EnumLayerProperty GradientTileMode { get; set; } + [PropertyDescription(Description = "The color of the brush")] public SKColorLayerProperty Color { get; set; } [PropertyDescription(Description = "The gradient of the brush")] public ColorGradientLayerProperty Gradient { get; set; } + [PropertyDescription(KeyframesSupported = false, Description = "How many times to repeat the colors in the selected gradient", MinInputValue = 0, MaxInputValue = 10)] + public IntLayerProperty GradientRepeat { get; set; } + protected override void PopulateDefaults() { GradientType.DefaultValue = LayerBrushes.Color.GradientType.Solid; Color.DefaultValue = new SKColor(255, 0, 0); Gradient.DefaultValue = ColorGradient.GetUnicornBarf(); + GradientRepeat.DefaultValue = 0; } protected override void OnPropertiesInitialized() { GradientType.BaseValueChanged += (sender, args) => UpdateVisibility(); + if (ProfileElement is Layer layer) + layer.General.FillType.BaseValueChanged += (sender, args) => UpdateVisibility(); + UpdateVisibility(); } @@ -35,6 +45,12 @@ namespace Artemis.Plugins.LayerBrushes.Color { Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid; Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid; + GradientRepeat.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid; + + if (ProfileElement is Layer layer) + GradientTileMode.IsHidden = layer.General.FillType.CurrentValue != LayerFillType.Clip; + else + GradientTileMode.IsHidden = true; } }