From 75b0ee8151daca1ced10b6b65915c2077a1adeaa Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 17 Jun 2020 19:21:23 +0200 Subject: [PATCH] Profiles - Abstracted property elements and effect elements Folders - Added properties to folders Layer effects - Expanded to folders Layer effects - Added shape clipping --- .../Extensions/SKColorExtensions.cs | 23 +- src/Artemis.Core/Extensions/TypeExtensions.cs | 15 ++ .../Models/Profile/EffectProfileElement.cs | 104 +++++++++ src/Artemis.Core/Models/Profile/Folder.cs | 74 +++++- src/Artemis.Core/Models/Profile/Layer.cs | 221 +++++++----------- .../LayerProperties/BaseLayerProperty.cs | 4 +- .../Types/SKColorLayerProperty.cs | 13 +- .../Models/Profile/LayerPropertyGroup.cs | 20 +- src/Artemis.Core/Models/Profile/Profile.cs | 4 +- .../Models/Profile/ProfileElement.cs | 2 +- .../Profile/PropertiesProfileElement.cs | 54 +++++ .../Plugins/Abstract/ProfileModule.cs | 3 +- .../LayerEffect/Abstract/BaseLayerEffect.cs | 72 ++++-- .../LayerEffect/Abstract/LayerEffect.cs | 3 +- src/Artemis.Core/Services/CoreService.cs | 9 +- .../Services/Interfaces/ILayerService.cs | 8 +- .../Services/Interfaces/IRgbService.cs | 1 + src/Artemis.Core/Services/LayerService.cs | 91 ++++---- src/Artemis.Core/Services/RgbService.cs | 2 + .../Services/Storage/ProfileService.cs | 17 +- .../Entities/Profile/EffectsEntity.cs | 9 + .../Entities/Profile/FolderEntity.cs | 12 +- .../Entities/Profile/LayerEntity.cs | 10 +- .../Entities/Profile/PropertiesEntity.cs | 11 + .../Services/ProfileEditorService.cs | 5 + .../BrushPropertyInputViewModel.cs | 7 +- .../SKPointPropertyInputViewModel.cs | 20 +- .../SKSizePropertyInputViewModel.cs | 20 +- .../LayerEffects/EffectsViewModel.cs | 15 +- .../LayerPropertiesViewModel.cs | 68 +++--- .../LayerPropertyGroupViewModel.cs | 4 +- .../SurfaceEditor/SurfaceEditorViewModel.cs | 4 +- .../LogitechDeviceProvider.cs | 4 +- .../NoiseBrush.cs | 27 +-- .../NoiseBrushProperties.cs | 4 +- .../BlurEffect.cs | 4 +- .../DilateEffect.cs | 4 +- .../DilateEffectProperties.cs | 2 +- .../ErodeEffect.cs | 4 +- .../ErodeEffectProperties.cs | 3 +- .../GlowEffect.cs | 4 +- .../GrayScaleEffect.cs | 4 +- 42 files changed, 638 insertions(+), 347 deletions(-) create mode 100644 src/Artemis.Core/Models/Profile/EffectProfileElement.cs create mode 100644 src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs create mode 100644 src/Artemis.Storage/Entities/Profile/EffectsEntity.cs create mode 100644 src/Artemis.Storage/Entities/Profile/PropertiesEntity.cs diff --git a/src/Artemis.Core/Extensions/SKColorExtensions.cs b/src/Artemis.Core/Extensions/SKColorExtensions.cs index d7dfac717..d638b4967 100644 --- a/src/Artemis.Core/Extensions/SKColorExtensions.cs +++ b/src/Artemis.Core/Extensions/SKColorExtensions.cs @@ -1,4 +1,5 @@ -using RGB.NET.Core; +using System; +using RGB.NET.Core; using SkiaSharp; namespace Artemis.Core.Extensions @@ -10,5 +11,25 @@ namespace Artemis.Core.Extensions { return new Color(color.Alpha, color.Red, color.Green, color.Blue); } + + public static SKColor Interpolate(this SKColor from, SKColor to, float progress) + { + var redDiff = to.Red - from.Red; + var greenDiff = to.Green - from.Green; + var blueDiff = to.Blue - from.Blue; + var alphaDiff = to.Alpha - from.Alpha; + + return new SKColor( + ClampToByte(from.Red + redDiff * progress), + ClampToByte(from.Green + greenDiff * progress), + ClampToByte(from.Blue + blueDiff * progress), + ClampToByte(from.Alpha + alphaDiff * progress) + ); + } + + private static byte ClampToByte(float value) + { + return (byte)Math.Clamp(value, 0, 255); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs index 737a9be6c..78069713e 100644 --- a/src/Artemis.Core/Extensions/TypeExtensions.cs +++ b/src/Artemis.Core/Extensions/TypeExtensions.cs @@ -11,5 +11,20 @@ namespace Artemis.Core.Extensions return type.BaseType?.GetGenericTypeDefinition() == genericType; } + + public static bool IsNumber(this object value) + { + return value is sbyte + || value is byte + || value is short + || value is ushort + || value is int + || value is uint + || value is long + || value is ulong + || value is float + || value is double + || value is decimal; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/EffectProfileElement.cs b/src/Artemis.Core/Models/Profile/EffectProfileElement.cs new file mode 100644 index 000000000..ab1331d3b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/EffectProfileElement.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Artemis.Core.Annotations; +using Artemis.Core.Plugins.LayerEffect.Abstract; +using Artemis.Storage.Entities.Profile; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile +{ + public abstract class EffectProfileElement : PropertiesProfileElement + { + internal abstract EffectsEntity EffectsEntity { get; } + + protected List _layerEffects; + + /// + /// Gets a read-only collection of the layer effects on this entity + /// + public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly(); + + internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect) + { + if (effect == null) throw new ArgumentNullException(nameof(effect)); + + DeactivateLayerEffect(effect); + + // Update the order on the remaining effects + var index = 0; + foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) + { + baseLayerEffect.Order = Order = index + 1; + index++; + } + + OnLayerEffectsUpdated(); + } + + internal void AddLayerEffect([NotNull] BaseLayerEffect effect) + { + if (effect == null) throw new ArgumentNullException(nameof(effect)); + _layerEffects.Add(effect); + OnLayerEffectsUpdated(); + } + + internal void DeactivateLayerEffect([NotNull] BaseLayerEffect effect) + { + if (effect == null) throw new ArgumentNullException(nameof(effect)); + + // Remove the effect from the layer and dispose it + _layerEffects.Remove(effect); + effect.Dispose(); + } + + internal SKPath CreateShapeClip() + { + var shapeClip = new SKPath(); + if (Path == null) + return shapeClip; + + if (Parent is EffectProfileElement effectParent) + shapeClip = shapeClip.Op(effectParent.CreateShapeClip(), SKPathOp.Union); + + foreach (var baseLayerEffect in LayerEffects) + { + var effectClip = baseLayerEffect.InternalCreateShapeClip(Path); + shapeClip = shapeClip.Op(effectClip, SKPathOp.Union); + } + + return shapeClip; + } + + protected void ApplyLayerEffectsToEntity() + { + EffectsEntity.LayerEffects.Clear(); + foreach (var layerEffect in LayerEffects) + { + var layerEffectEntity = new LayerEffectEntity + { + Id = layerEffect.EntityId, + PluginGuid = layerEffect.PluginInfo.Guid, + EffectType = layerEffect.GetType().Name, + Name = layerEffect.Name, + HasBeenRenamed = layerEffect.HasBeenRenamed, + Order = layerEffect.Order + }; + EffectsEntity.LayerEffects.Add(layerEffectEntity); + layerEffect.BaseProperties.ApplyToEntity(); + } + } + + #region Events + + public event EventHandler LayerEffectsUpdated; + + internal void OnLayerEffectsUpdated() + { + LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 732a581d7..744a03600 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Storage.Entities.Profile; @@ -8,10 +7,8 @@ using SkiaSharp; namespace Artemis.Core.Models.Profile { - public sealed class Folder : ProfileElement + public sealed class Folder : EffectProfileElement { - private readonly List _layerEffects; - public Folder(Profile profile, ProfileElement parent, string name) { FolderEntity = new FolderEntity(); @@ -20,19 +17,25 @@ namespace Artemis.Core.Models.Profile Profile = profile; Parent = parent; Name = name; + _layerEffects = new List(); + _expandedPropertyGroups = new List(); } internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) { FolderEntity = folderEntity; + EntityId = folderEntity.Id; Profile = profile; Parent = parent; Name = folderEntity.Name; Order = folderEntity.Order; + _layerEffects = new List(); + _expandedPropertyGroups = new List(); + _expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups); // TODO: Load conditions @@ -50,14 +53,14 @@ namespace Artemis.Core.Models.Profile } internal FolderEntity FolderEntity { get; set; } - - /// - /// Gets a read-only collection of the layer effects on this layer - /// - public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly(); + internal override PropertiesEntity PropertiesEntity => FolderEntity; + internal override EffectsEntity EffectsEntity => FolderEntity; public override void Update(double deltaTime) { + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.Update(deltaTime); + // Iterate the children in reverse because that's how they must be rendered too for (var index = Children.Count - 1; index > -1; index--) { @@ -66,14 +69,32 @@ namespace Artemis.Core.Models.Profile } } - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { + canvas.Save(); + canvas.ClipPath(Path); + + // Clone the paint so that any changes are confined to the current group + var groupPaint = paint.Clone(); + + // Pre-processing only affects other pre-processors and the brushes + canvas.Save(); + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.InternalPreProcess(canvas, canvasInfo, new SKPath(Path), groupPaint); + // Iterate the children in reverse because the first layer must be rendered last to end up on top for (var index = Children.Count - 1; index > -1; index--) { var profileElement = Children[index]; - profileElement.Render(deltaTime, canvas, canvasInfo); + profileElement.Render(deltaTime, canvas, canvasInfo, groupPaint); } + + // Restore the canvas as to not be affected by pre-processors + canvas.Restore(); + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.InternalPostProcess(canvas, canvasInfo, new SKPath(Path), groupPaint); + + canvas.Restore(); } public Folder AddFolder(string name) @@ -88,6 +109,24 @@ namespace Artemis.Core.Models.Profile return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } + public void CalculateRenderProperties() + { + var path = new SKPath {FillType = SKPathFillType.Winding}; + foreach (var child in Children) + { + if (child is EffectProfileElement effectChild && effectChild.Path != null) + path.AddPath(effectChild.Path); + } + + Path = path; + + // Folder render properties are based on child paths and thus require an update + if (Parent is Folder folder) + folder.CalculateRenderProperties(); + + OnRenderPropertiesUpdated(); + } + internal override void ApplyToEntity() { FolderEntity.Id = EntityId; @@ -98,7 +137,20 @@ namespace Artemis.Core.Models.Profile FolderEntity.ProfileId = Profile.EntityId; + ApplyLayerEffectsToEntity(); + // TODO: conditions } + + #region Events + + public event EventHandler RenderPropertiesUpdated; + + private void OnRenderPropertiesUpdated() + { + RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); + } + + #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 6e7df90c0..a15106546 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -21,13 +21,10 @@ namespace Artemis.Core.Models.Profile /// Represents a layer on a profile. To create new layers use the by injecting /// into your code /// - public sealed class Layer : ProfileElement + public sealed class Layer : EffectProfileElement { - private readonly List _expandedPropertyGroups; - private readonly List _layerEffects; private LayerShape _layerShape; private List _leds; - private SKPath _path; internal Layer(Profile profile, ProfileElement parent, string name) { @@ -68,38 +65,14 @@ namespace Artemis.Core.Models.Profile } internal LayerEntity LayerEntity { get; set; } - - /// - /// Gets a read-only collection of the layer effects on this layer - /// - public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly(); + internal override PropertiesEntity PropertiesEntity => LayerEntity; + internal override EffectsEntity EffectsEntity => LayerEntity; /// /// A collection of all the LEDs this layer is assigned to. /// public ReadOnlyCollection Leds => _leds.AsReadOnly(); - /// - /// Gets a copy of the path containing all the LEDs this layer is applied to, any rendering outside the layer Path is - /// clipped. - /// - public SKPath Path - { - get => _path != null ? new SKPath(_path) : null; - private set - { - _path = value; - // I can't really be sure about the performance impact of calling Bounds often but - // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive - Bounds = value?.Bounds ?? SKRect.Empty; - } - } - - /// - /// The bounds of this layer - /// - public SKRect Bounds { get; private set; } - /// /// Defines the shape that is rendered by the . /// @@ -130,19 +103,6 @@ namespace Artemis.Core.Models.Profile return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; } - public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup) - { - return _expandedPropertyGroups.Contains(layerPropertyGroup.Path); - } - - public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded) - { - if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup)) - _expandedPropertyGroups.Remove(layerPropertyGroup.Path); - else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup)) - _expandedPropertyGroups.Add(layerPropertyGroup.Path); - } - #region Storage internal override void ApplyToEntity() @@ -161,21 +121,7 @@ namespace Artemis.Core.Models.Profile LayerBrush?.BaseProperties.ApplyToEntity(); // Effects - LayerEntity.LayerEffects.Clear(); - foreach (var layerEffect in LayerEffects) - { - var layerEffectEntity = new LayerEffectEntity - { - Id = layerEffect.EntityId, - PluginGuid = layerEffect.PluginInfo.Guid, - EffectType = layerEffect.GetType().Name, - Name = layerEffect.Name, - HasBeenRenamed = layerEffect.HasBeenRenamed, - Order = layerEffect.Order - }; - LayerEntity.LayerEffects.Add(layerEffectEntity); - layerEffect.BaseProperties.ApplyToEntity(); - } + ApplyLayerEffectsToEntity(); // LEDs LayerEntity.Leds.Clear(); @@ -190,7 +136,7 @@ namespace Artemis.Core.Models.Profile } // Conditions TODO - LayerEntity.Condition.Clear(); + LayerEntity.Conditions.Clear(); } #endregion @@ -273,7 +219,7 @@ namespace Artemis.Core.Models.Profile } /// - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { // Ensure the layer is ready if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) @@ -284,36 +230,42 @@ namespace Artemis.Core.Models.Profile canvas.Save(); canvas.ClipPath(Path); + + paint.BlendMode = General.BlendMode.CurrentValue; + paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); - using (var paint = new SKPaint()) - { - paint.FilterQuality = SKFilterQuality.Low; - paint.BlendMode = General.BlendMode.CurrentValue; - paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); + // Pre-processing only affects other pre-processors and the brushes + canvas.Save(); + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.InternalPreProcess(canvas, canvasInfo, new SKPath(Path), paint); - foreach (var baseLayerEffect in LayerEffects) - baseLayerEffect.PreProcess(canvas, canvasInfo, Path, paint); + // Shape clip must be determined before commiting to any rendering + var shapeClip = CreateShapeClip(); + if (!shapeClip.IsEmpty) + ExcludePathFromTranslation(shapeClip); - if (!LayerBrush.SupportsTransformation) - SimpleRender(canvas, canvasInfo, paint); - else if (General.FillType.CurrentValue == LayerFillType.Stretch) - StretchRender(canvas, canvasInfo, paint); - else if (General.FillType.CurrentValue == LayerFillType.Clip) - ClipRender(canvas, canvasInfo, paint); - - foreach (var baseLayerEffect in LayerEffects) - baseLayerEffect.PostProcess(canvas, canvasInfo, Path, paint); - } + if (!LayerBrush.SupportsTransformation) + SimpleRender(canvas, canvasInfo, paint, shapeClip); + else if (General.FillType.CurrentValue == LayerFillType.Stretch) + StretchRender(canvas, canvasInfo, paint, shapeClip); + else if (General.FillType.CurrentValue == LayerFillType.Clip) + ClipRender(canvas, canvasInfo, paint, shapeClip); + // Restore the canvas as to not be affected by pre-processors + canvas.Restore(); + foreach (var baseLayerEffect in LayerEffects) + baseLayerEffect.InternalPostProcess(canvas, canvasInfo, new SKPath(Path), paint); + canvas.Restore(); } - private void SimpleRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) + private void SimpleRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath shapeClip) { - LayerBrush.InternalRender(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + var path = LayerShape.Path.Op(shapeClip, SKPathOp.Difference); + LayerBrush.InternalRender(canvas, canvasInfo, path, paint); } - private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) + private void StretchRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath shapeClip) { // Apply transformations var sizeProperty = Transform.Scale.CurrentValue; @@ -331,10 +283,11 @@ namespace Artemis.Core.Models.Profile canvas.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y); canvas.Translate(x, y); - LayerBrush.InternalRender(canvas, canvasInfo, new SKPath(LayerShape.Path), paint); + var path = LayerShape.Path.Op(shapeClip, SKPathOp.Difference); + LayerBrush.InternalRender(canvas, canvasInfo, path, paint); } - private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) + private void ClipRender(SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint, SKPath shapeClip) { // Apply transformation var sizeProperty = Transform.Scale.CurrentValue; @@ -367,29 +320,31 @@ namespace Artemis.Core.Models.Profile var renderPath = new SKPath(); renderPath.AddRect(boundsRect); + renderPath = renderPath.Op(shapeClip, SKPathOp.Difference); LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); } internal void CalculateRenderProperties() { if (!Leds.Any()) - { Path = new SKPath(); + else + { + var path = new SKPath {FillType = SKPathFillType.Winding}; + foreach (var artemisLed in Leds) + path.AddRect(artemisLed.AbsoluteRenderRectangle); - LayerShape?.CalculateRenderProperties(); - OnRenderPropertiesUpdated(); - return; + Path = path; } - var path = new SKPath {FillType = SKPathFillType.Winding}; - foreach (var artemisLed in Leds) - path.AddRect(artemisLed.AbsoluteRenderRectangle); - - Path = path; - // This is called here so that the shape's render properties are up to date when other code // responds to OnRenderPropertiesUpdated LayerShape?.CalculateRenderProperties(); + + // Folder render properties are based on child paths and thus require an update + if (Parent is Folder folder) + folder.CalculateRenderProperties(); + OnRenderPropertiesUpdated(); } @@ -407,9 +362,39 @@ namespace Artemis.Core.Models.Profile return position; } - #endregion - #region Effect management + /// + /// Excludes the provided path from the translations applied to the layer by applying translations that cancel the + /// layer translations out + /// + /// + public void ExcludePathFromTranslation(SKPath path) + { + var sizeProperty = Transform.Scale.CurrentValue; + var rotationProperty = Transform.Rotation.CurrentValue; + + var anchorPosition = GetLayerAnchorPosition(); + var anchorProperty = Transform.AnchorPoint.CurrentValue; + + // Translation originates from the unscaled center of the shape and is tied to the anchor + var x = anchorPosition.X - Bounds.MidX - anchorProperty.X * Bounds.Width; + var y = anchorPosition.Y - Bounds.MidY - anchorProperty.Y * Bounds.Height; + + var reversedXScale = 1f / (sizeProperty.Width / 100f); + var reversedYScale = 1f / (sizeProperty.Height / 100f); + + if (General.FillType == LayerFillType.Stretch) + { + path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeScale(reversedXScale, reversedYScale, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); + } + else + { + path.Transform(SKMatrix.MakeRotationDegrees(rotationProperty * -1, anchorPosition.X, anchorPosition.Y)); + path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); + } + } #endregion @@ -494,15 +479,6 @@ namespace Artemis.Core.Models.Profile brush.Dispose(); } - private void DeactivateLayerEffect([NotNull] BaseLayerEffect effect) - { - if (effect == null) throw new ArgumentNullException(nameof(effect)); - - // Remove the effect from the layer and dispose it - _layerEffects.Remove(effect); - effect.Dispose(); - } - internal void RemoveLayerBrush() { if (LayerBrush == null) @@ -513,62 +489,23 @@ namespace Artemis.Core.Models.Profile LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == brush.PluginInfo.Guid && p.Path.StartsWith("LayerBrush.")); } - internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect) - { - if (effect == null) throw new ArgumentNullException(nameof(effect)); - - DeactivateLayerEffect(effect); - - // Clean up properties - LayerEntity.PropertyEntities.RemoveAll(p => p.PluginGuid == effect.PluginInfo.Guid && p.Path.StartsWith(effect.PropertyRootPath)); - - // Update the order on the remaining effects - var index = 0; - foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order)) - { - baseLayerEffect.Order = Order = index + 1; - index++; - } - - OnLayerEffectsUpdated(); - } - - internal void AddLayerEffect([NotNull] BaseLayerEffect effect) - { - if (effect == null) throw new ArgumentNullException(nameof(effect)); - _layerEffects.Add(effect); - OnLayerEffectsUpdated(); - } - #endregion #region Events public event EventHandler RenderPropertiesUpdated; - public event EventHandler ShapePropertiesUpdated; public event EventHandler LayerBrushUpdated; - public event EventHandler LayerEffectsUpdated; private void OnRenderPropertiesUpdated() { RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); } - private void OnShapePropertiesUpdated() - { - ShapePropertiesUpdated?.Invoke(this, EventArgs.Empty); - } - internal void OnLayerBrushUpdated() { LayerBrushUpdated?.Invoke(this, EventArgs.Empty); } - internal void OnLayerEffectsUpdated() - { - LayerEffectsUpdated?.Invoke(this, EventArgs.Empty); - } - #endregion } diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs index 27ed19c4c..22e232ed0 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs @@ -18,9 +18,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties } /// - /// The layer this property applies to + /// Gets the profile element (such as layer or folder) this effect is applied to /// - public Layer Layer { get; internal set; } + public PropertiesProfileElement ProfileElement { get; internal set; } /// /// The parent group of this layer property, set after construction diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs index 8c8dd2c69..2ad1452b9 100644 --- a/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs +++ b/src/Artemis.Core/Models/Profile/LayerProperties/Types/SKColorLayerProperty.cs @@ -1,4 +1,5 @@ using System; +using Artemis.Core.Extensions; using SkiaSharp; namespace Artemis.Core.Models.Profile.LayerProperties.Types @@ -17,17 +18,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties.Types protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased) { - var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red; - var greenDiff = NextKeyframe.Value.Green - CurrentKeyframe.Value.Green; - var blueDiff = NextKeyframe.Value.Blue - CurrentKeyframe.Value.Blue; - var alphaDiff = NextKeyframe.Value.Alpha - CurrentKeyframe.Value.Alpha; - - CurrentValue = new SKColor( - ClampToByte(CurrentKeyframe.Value.Red + redDiff * keyframeProgressEased), - ClampToByte(CurrentKeyframe.Value.Green + greenDiff * keyframeProgressEased), - ClampToByte(CurrentKeyframe.Value.Blue + blueDiff * keyframeProgressEased), - ClampToByte(CurrentKeyframe.Value.Alpha + alphaDiff * keyframeProgressEased) - ); + CurrentValue = CurrentKeyframe.Value.Interpolate(NextKeyframe.Value, keyframeProgressEased); } private static byte ClampToByte(float value) diff --git a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs index 599f1d704..e88f62315 100644 --- a/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs +++ b/src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs @@ -29,9 +29,9 @@ namespace Artemis.Core.Models.Profile } /// - /// The layer this property group applies to + /// Gets the profile element (such as layer or folder) this effect is applied to /// - public Layer Layer { get; internal set; } + public PropertiesProfileElement ProfileElement { get; internal set; } /// /// The path of this property group @@ -129,7 +129,7 @@ namespace Artemis.Core.Models.Profile PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); } - internal void InitializeProperties(ILayerService layerService, Layer layer, [NotNull] string path) + internal void InitializeProperties(ILayerService layerService, PropertiesProfileElement profileElement, [NotNull] string path) { if (path == null) throw new ArgumentNullException(nameof(path)); @@ -137,7 +137,7 @@ namespace Artemis.Core.Models.Profile if (PropertiesInitialized) throw new ArtemisCoreException("Layer property group already initialized, wut"); - Layer = layer; + ProfileElement = profileElement; Path = path.TrimEnd('.'); // Get all properties with a PropertyDescriptionAttribute @@ -153,10 +153,10 @@ namespace Artemis.Core.Models.Profile if (instance == null) throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); - instance.Layer = layer; + instance.ProfileElement = profileElement; instance.Parent = this; instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription; - InitializeProperty(layer, path + propertyInfo.Name, instance); + InitializeProperty(profileElement, path + propertyInfo.Name, instance); propertyInfo.SetValue(this, instance); _layerProperties.Add(instance); @@ -177,7 +177,7 @@ namespace Artemis.Core.Models.Profile instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription; instance.LayerBrush = LayerBrush; instance.LayerEffect = LayerEffect; - instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); + instance.InitializeProperties(layerService, profileElement, $"{path}{propertyInfo.Name}."); propertyInfo.SetValue(this, instance); _layerPropertyGroups.Add(instance); @@ -236,7 +236,7 @@ namespace Artemis.Core.Models.Profile OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); } - private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) + private void InitializeProperty(PropertiesProfileElement profileElement, string path, BaseLayerProperty instance) { Guid pluginGuid; if (IsCorePropertyGroup || instance.IsCoreProperty) @@ -246,13 +246,13 @@ namespace Artemis.Core.Models.Profile else pluginGuid = instance.Parent.LayerEffect.PluginInfo.Guid; - var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); + var entity = profileElement.PropertiesEntity.PropertyEntities.FirstOrDefault(p => p.PluginGuid == pluginGuid && p.Path == path); var fromStorage = true; if (entity == null) { fromStorage = false; entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path}; - layer.LayerEntity.PropertyEntities.Add(entity); + profileElement.PropertiesEntity.PropertyEntities.Add(entity); } instance.ApplyToLayerProperty(entity, this, fromStorage); diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index e3e84284b..c1c945548 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -57,7 +57,7 @@ namespace Artemis.Core.Models.Profile } } - public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo) + public override void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint) { lock (this) { @@ -65,7 +65,7 @@ namespace Artemis.Core.Models.Profile throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); foreach (var profileElement in Children) - profileElement.Render(deltaTime, canvas, canvasInfo); + profileElement.Render(deltaTime, canvas, canvasInfo, paint); } } diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 92cc472b9..c57f4e774 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -44,7 +44,7 @@ namespace Artemis.Core.Models.Profile /// /// Renders the element /// - public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo); + public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint); public List GetAllFolders() { diff --git a/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs b/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs new file mode 100644 index 000000000..fbbaa5b7b --- /dev/null +++ b/src/Artemis.Core/Models/Profile/PropertiesProfileElement.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using Artemis.Storage.Entities.Profile; +using SkiaSharp; + +namespace Artemis.Core.Models.Profile +{ + public abstract class PropertiesProfileElement : ProfileElement + { + internal abstract PropertiesEntity PropertiesEntity { get; } + + private SKPath _path; + + /// + /// Gets a copy of the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is + /// clipped. + /// + public SKPath Path + { + get => _path != null ? new SKPath(_path) : null; + protected set + { + _path = value; + // I can't really be sure about the performance impact of calling Bounds often but + // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive + Bounds = value?.Bounds ?? SKRect.Empty; + } + } + + /// + /// The bounds of this entity + /// + public SKRect Bounds { get; private set; } + + #region Property group expansion + + protected List _expandedPropertyGroups; + + public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup) + { + return _expandedPropertyGroups.Contains(layerPropertyGroup.Path); + } + + public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded) + { + if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup)) + _expandedPropertyGroups.Remove(layerPropertyGroup.Path); + else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup)) + _expandedPropertyGroups.Add(layerPropertyGroup.Path); + } + + #endregion + + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs index 1d34f53d0..d9445bb66 100644 --- a/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Abstract/ProfileModule.cs @@ -26,7 +26,8 @@ namespace Artemis.Core.Plugins.Abstract lock (this) { // Render the profile - ActiveProfile?.Render(deltaTime, canvas, canvasInfo); + using var paint = new SKPaint {FilterQuality = SKFilterQuality.Low}; + ActiveProfile?.Render(deltaTime, canvas, canvasInfo, paint); } } diff --git a/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs index bd884e2d1..ba19e0347 100644 --- a/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffect/Abstract/BaseLayerEffect.cs @@ -13,19 +13,14 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract public abstract class BaseLayerEffect : PropertyChangedBase, IDisposable { /// - /// Gets the unique ID of this effect + /// Gets the unique ID of this effect /// public Guid EntityId { get; internal set; } /// - /// Gets the layer this effect is applied to + /// Gets the profile element (such as layer or folder) this effect is applied to /// - public Layer Layer { get; internal set; } - - /// - /// Gets the folder this effect is applied to - /// - public Folder Folder { get; internal set; } + public EffectProfileElement ProfileElement { get; internal set; } /// /// The name which appears in the editor @@ -84,31 +79,78 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract /// /// Called before the layer or folder will be rendered /// - public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); + /// The canvas used to render the frame + /// Info on the canvas size and pixel type + /// The bounds this layer/folder will render in + /// The paint this layer/folder will use to render + public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint); /// /// Called after the layer of folder has been rendered /// - public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); - + /// The canvas used to render the frame + /// Info on the canvas size and pixel type + /// The bounds this layer/folder rendered in + /// The paint this layer/folder used to render + public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint); + + /// + /// Allows you to return a clip that is applied to the layer shape (or in case of a folder, all layer shapes in the + /// folder). + /// + /// To avoid breaking other effects, use this instead of applying a clip to the entire canvas if you want to limit + /// where shapes may render + /// + /// + /// The bounds this layer/folder rendered in + /// + public virtual SKPath CreateShapeClip(SKPath renderBounds) + { + return new SKPath(); + } + internal void InternalPreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { + var x = path.Bounds.Left; + var y = path.Bounds.Top; // Move the canvas to the top-left of the render path - canvas.Translate(path.Bounds.Left, path.Bounds.Top); + canvas.Translate(x, y); + // Pass the render path to the layer effect positioned at 0,0 - path.Transform(SKMatrix.MakeTranslation(path.Bounds.Left * -1, path.Bounds.Top * -1)); + path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); PreProcess(canvas, canvasInfo, path, paint); + + // Move the canvas back + canvas.Translate(x * -1, y * -1); } internal void InternalPostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { + var x = path.Bounds.Left; + var y = path.Bounds.Top; // Move the canvas to the top-left of the render path - canvas.Translate(path.Bounds.Left, path.Bounds.Top); + canvas.Translate(x, y); + // Pass the render path to the layer effect positioned at 0,0 - path.Transform(SKMatrix.MakeTranslation(path.Bounds.Left * -1, path.Bounds.Top * -1)); + path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); PostProcess(canvas, canvasInfo, path, paint); + + // Move the canvas back + canvas.Translate(x * -1, y * -1); + } + + internal virtual SKPath InternalCreateShapeClip(SKPath path) + { + var x = path.Bounds.Left; + var y = path.Bounds.Top; + + path.Transform(SKMatrix.MakeTranslation(x * -1, y * -1)); + var shapeClip = CreateShapeClip(path); + if (!shapeClip.IsEmpty) + shapeClip.Transform(SKMatrix.MakeTranslation(x, y)); + return shapeClip; } // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything diff --git a/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs index 662310ff9..6b322fb64 100644 --- a/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffect/Abstract/LayerEffect.cs @@ -2,6 +2,7 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Services.Interfaces; +using SkiaSharp; namespace Artemis.Core.Plugins.LayerEffect.Abstract { @@ -39,7 +40,7 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract { Properties = Activator.CreateInstance(); Properties.LayerEffect = this; - Properties.InitializeProperties(layerService, Layer, PropertyRootPath); + Properties.InitializeProperties(layerService, ProfileElement, PropertyRootPath); PropertiesInitialized = true; EnableLayerEffect(); diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 51e92312c..a14067b41 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -48,8 +48,7 @@ namespace Artemis.Core.Services _modules = _pluginService.GetPluginsOfType(); _pluginService.PluginEnabled += (sender, args) => _modules = _pluginService.GetPluginsOfType(); _pluginService.PluginDisabled += (sender, args) => _modules = _pluginService.GetPluginsOfType(); - - + ConfigureJsonConvert(); } @@ -113,6 +112,9 @@ namespace Artemis.Core.Services private void SurfaceOnUpdating(UpdatingEventArgs args) { + if (_rgbService.IsRenderPaused) + return; + try { if (!ModuleUpdatingDisabled && _modules != null) @@ -159,6 +161,9 @@ namespace Artemis.Core.Services private void SurfaceOnUpdated(UpdatedEventArgs args) { + if (_rgbService.IsRenderPaused) + return; + OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush, _rgbService.Surface)); } diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs index dae95c425..85c6b7e93 100644 --- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs +++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs @@ -36,17 +36,17 @@ namespace Artemis.Core.Services.Interfaces /// Instantiates and adds the described by the provided /// to the . /// - /// The layer to instantiate the effect for - void InstantiateLayerEffects(Layer layer); + /// The layer/folder to instantiate the effect for + void InstantiateLayerEffects(EffectProfileElement effectProfileElement); /// /// Adds the described by the provided to the /// . /// - /// The layer to instantiate the effect for + /// The layer/folder to instantiate the effect for /// /// - BaseLayerEffect AddLayerEffect(Layer layer, LayerEffectDescriptor layerEffectDescriptor); + BaseLayerEffect AddLayerEffect(EffectProfileElement effectProfileElement, LayerEffectDescriptor layerEffectDescriptor); void RemoveLayerEffect(BaseLayerEffect layerEffect); } diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 7f5ece69a..f30d42eed 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -29,6 +29,7 @@ namespace Artemis.Core.Services.Interfaces IReadOnlyCollection LoadedDevices { get; } TimerUpdateTrigger UpdateTrigger { get; } + bool IsRenderPaused { get; set; } /// /// Adds the given device provider to the diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs index eb9967fa7..1323cc32f 100644 --- a/src/Artemis.Core/Services/LayerService.cs +++ b/src/Artemis.Core/Services/LayerService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Artemis.Core.Exceptions; using Artemis.Core.Models.Profile; @@ -7,6 +8,7 @@ using Artemis.Core.Plugins.LayerBrush.Abstract; using Artemis.Core.Plugins.LayerEffect; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Services.Interfaces; +using Artemis.Storage.Entities.Profile; using Ninject; using Serilog; @@ -77,61 +79,68 @@ namespace Artemis.Core.Services return brush; } - public void InstantiateLayerEffects(Layer layer) - { - if (layer.LayerEffects.Any()) - throw new ArtemisCoreException("Layer already has instantiated layer effects"); - - foreach (var layerEntityLayerEffect in layer.LayerEntity.LayerEffects.OrderByDescending(e => e.Order)) - { - // Get a matching descriptor - var layerEffectProviders = _pluginService.GetPluginsOfType(); - var descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); - var descriptor = descriptors.FirstOrDefault(d => d.LayerEffectProvider.PluginInfo.Guid == layerEntityLayerEffect.PluginGuid && - d.LayerEffectType.Name == layerEntityLayerEffect.EffectType); - - if (descriptor == null) - continue; - - var effect = (BaseLayerEffect) _kernel.Get(descriptor.LayerEffectType); - effect.EntityId = layerEntityLayerEffect.Id; - effect.Layer = layer; - effect.Order = layerEntityLayerEffect.Order; - effect.Name = layerEntityLayerEffect.Name; - effect.Descriptor = descriptor; - effect.Initialize(this); - effect.Update(0); - - layer.AddLayerEffect(effect); - _logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath); - } - - layer.OnLayerEffectsUpdated(); - } - - public BaseLayerEffect AddLayerEffect(Layer layer, LayerEffectDescriptor layerEffectDescriptor) + public BaseLayerEffect AddLayerEffect(EffectProfileElement effectElement, LayerEffectDescriptor layerEffectDescriptor) { + // Create the effect with dependency injection var effect = (BaseLayerEffect) _kernel.Get(layerEffectDescriptor.LayerEffectType); + + effect.ProfileElement = effectElement; effect.EntityId = Guid.NewGuid(); - effect.Layer = layer; - effect.Order = layer.LayerEffects.Count + 1; + effect.Order = effectElement.LayerEffects.Count + 1; effect.Descriptor = layerEffectDescriptor; effect.Initialize(this); effect.Update(0); - layer.AddLayerEffect(effect); + effectElement.AddLayerEffect(effect); _logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath); - - layer.OnLayerEffectsUpdated(); return effect; } public void RemoveLayerEffect(BaseLayerEffect layerEffect) { - // // Make sure the group is collapsed or the effect that gets this effect's order gets expanded - // layerEffect.Layer.SetPropertyGroupExpanded(layerEffect.BaseProperties, false); - layerEffect.Layer.RemoveLayerEffect(layerEffect); + layerEffect.ProfileElement.RemoveLayerEffect(layerEffect); + } + + public void InstantiateLayerEffects(EffectProfileElement effectElement) + { + if (effectElement.LayerEffects.Any()) + throw new ArtemisCoreException("Effect element (layer/folder) already has instantiated layer effects"); + + var layerEffectProviders = _pluginService.GetPluginsOfType(); + var descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList(); + + List entities; + if (effectElement is Layer layer) + entities = layer.LayerEntity.LayerEffects.OrderByDescending(e => e.Order).ToList(); + else if (effectElement is Folder folder) + entities = folder.FolderEntity.LayerEffects.OrderByDescending(e => e.Order).ToList(); + else + throw new ArtemisCoreException("Provided effect element is of an unsupported type, must be Layer of Folder"); + + foreach (var layerEffectEntity in entities) + { + // Get a matching descriptor + var descriptor = descriptors.FirstOrDefault(d => d.LayerEffectProvider.PluginInfo.Guid == layerEffectEntity.PluginGuid && + d.LayerEffectType.Name == layerEffectEntity.EffectType); + if (descriptor == null) + continue; + + // Create the effect with dependency injection + var effect = (BaseLayerEffect) _kernel.Get(descriptor.LayerEffectType); + + effect.ProfileElement = effectElement; + effect.EntityId = layerEffectEntity.Id; + effect.Order = layerEffectEntity.Order; + effect.Name = layerEffectEntity.Name; + effect.Descriptor = descriptor; + + effect.Initialize(this); + effect.Update(0); + + effectElement.AddLayerEffect(effect); + _logger.Debug("Instantiated layer effect with root path {rootPath}", effect.PropertyRootPath); + } } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index f6c8c43ad..46fb69dbb 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using Artemis.Core.Events; using Artemis.Core.Plugins.Models; using Artemis.Core.RGB.NET; @@ -47,6 +48,7 @@ namespace Artemis.Core.Services public BitmapBrush BitmapBrush { get; private set; } public IReadOnlyCollection LoadedDevices => _loadedDevices.AsReadOnly(); public double RenderScale => _renderScaleSetting.Value; + public bool IsRenderPaused { get; set; } public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) { diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 459c53b78..cf62ff2f8 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -94,6 +94,7 @@ namespace Artemis.Core.Services.Storage { InitializeLayerProperties(profile); InstantiateLayers(profile); + InstantiateFolders(profile); } } @@ -173,9 +174,16 @@ namespace Artemis.Core.Services.Storage } } + private void InstantiateFolders(Profile profile) + { + foreach (var folder in profile.GetAllFolders()) + { + _layerService.InstantiateLayerEffects(folder); + } + } + private void InstantiateLayers(Profile profile) { - // Only instantiate brushes for layers without an existing brush/effect instance foreach (var layer in profile.GetAllLayers()) { _layerService.InstantiateLayerBrush(layer); @@ -190,11 +198,14 @@ namespace Artemis.Core.Services.Storage profileModule.ActiveProfile.PopulateLeds(surface); } - private void ActiveProfilesInstantiateProfileLayerBrushes() + private void ActiveProfilesInstantiatePlugins() { var profileModules = _pluginService.GetPluginsOfType(); foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) + { InstantiateLayers(profileModule.ActiveProfile); + InstantiateFolders(profileModule.ActiveProfile); + } } #region Event handlers @@ -213,7 +224,7 @@ namespace Artemis.Core.Services.Storage private void OnPluginLoaded(object sender, PluginEventArgs e) { if (e.PluginInfo.Instance is LayerBrushProvider) - ActiveProfilesInstantiateProfileLayerBrushes(); + ActiveProfilesInstantiatePlugins(); else if (e.PluginInfo.Instance is ProfileModule profileModule) { var activeProfile = GetActiveProfile(profileModule); diff --git a/src/Artemis.Storage/Entities/Profile/EffectsEntity.cs b/src/Artemis.Storage/Entities/Profile/EffectsEntity.cs new file mode 100644 index 000000000..d443c3573 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/EffectsEntity.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Artemis.Storage.Entities.Profile +{ + public abstract class EffectsEntity : PropertiesEntity + { + public List LayerEffects { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs index bfa677765..06731f2a5 100644 --- a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs @@ -4,16 +4,22 @@ using LiteDB; namespace Artemis.Storage.Entities.Profile { - public class FolderEntity + public class FolderEntity : EffectsEntity { + public FolderEntity() + { + PropertyEntities = new List(); + Conditions = new List(); + LayerEffects = new List(); + ExpandedPropertyGroups = new List(); + } + public Guid Id { get; set; } public Guid ParentId { get; set; } public int Order { get; set; } public string Name { get; set; } - public List Conditions { get; set; } - [BsonRef("ProfileEntity")] public ProfileEntity Profile { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs index 891e651d6..30317cb22 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs @@ -4,13 +4,13 @@ using LiteDB; namespace Artemis.Storage.Entities.Profile { - public class LayerEntity + public class LayerEntity : EffectsEntity { public LayerEntity() { Leds = new List(); PropertyEntities = new List(); - Condition = new List(); + Conditions = new List(); LayerEffects = new List(); ExpandedPropertyGroups = new List(); } @@ -22,11 +22,7 @@ namespace Artemis.Storage.Entities.Profile public string Name { get; set; } public List Leds { get; set; } - public List PropertyEntities { get; set; } - public List Condition { get; set; } - public List LayerEffects { get; set; } - public List ExpandedPropertyGroups { get; set; } - + [BsonRef("ProfileEntity")] public ProfileEntity Profile { get; set; } diff --git a/src/Artemis.Storage/Entities/Profile/PropertiesEntity.cs b/src/Artemis.Storage/Entities/Profile/PropertiesEntity.cs new file mode 100644 index 000000000..3f2baa9a8 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/PropertiesEntity.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Artemis.Storage.Entities.Profile +{ + public abstract class PropertiesEntity + { + public List Conditions { get; set; } + public List PropertyEntities { get; set; } + public List ExpandedPropertyGroups { get; set; } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 59b90bf56..88231e319 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -98,6 +98,11 @@ namespace Artemis.UI.Shared.Services return; var delta = CurrentTime - _lastUpdateTime; + foreach (var folder in SelectedProfile.GetAllFolders()) + { + foreach (var baseLayerEffect in folder.LayerEffects) + baseLayerEffect.Update(delta.TotalSeconds); + } foreach (var layer in SelectedProfile.GetAllLayers()) { layer.OverrideProgress(CurrentTime); diff --git a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs index b62721c9a..aa8fe56e0 100644 --- a/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/BrushPropertyInputViewModel.cs @@ -52,8 +52,11 @@ namespace Artemis.UI.PropertyInput protected override void OnInputValueApplied() { - _layerService.RemoveLayerBrush(LayerProperty.Layer); - _layerService.InstantiateLayerBrush(LayerProperty.Layer); + if (LayerProperty.ProfileElement is Layer layer) + { + _layerService.RemoveLayerBrush(layer); + _layerService.InstantiateLayerBrush(layer); + } } private void SetBrushByDescriptor(LayerBrushDescriptor value) diff --git a/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs index 82bd7cd00..9465fb7c4 100644 --- a/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/SKPointPropertyInputViewModel.cs @@ -1,4 +1,6 @@ -using Artemis.Core.Models.Profile.LayerProperties; +using System; +using Artemis.Core.Extensions; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.UI.Shared.PropertyInput; using Artemis.UI.Shared.Services.Interfaces; using FluentValidation; @@ -42,18 +44,18 @@ namespace Artemis.UI.PropertyInput public SKPointPropertyInputViewModelValidator() { RuleFor(vm => vm.X) - .LessThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).X) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); + .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber()); RuleFor(vm => vm.X) - .GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).X) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); + .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber()); RuleFor(vm => vm.Y) - .LessThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).Y) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); + .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber()); RuleFor(vm => vm.Y) - .GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).Y) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); + .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber()); } } } \ No newline at end of file diff --git a/src/Artemis.UI/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Artemis.UI/PropertyInput/SKSizePropertyInputViewModel.cs index b0021c4d5..4165dff5e 100644 --- a/src/Artemis.UI/PropertyInput/SKSizePropertyInputViewModel.cs +++ b/src/Artemis.UI/PropertyInput/SKSizePropertyInputViewModel.cs @@ -1,4 +1,6 @@ -using Artemis.Core.Models.Profile.LayerProperties; +using System; +using Artemis.Core.Extensions; +using Artemis.Core.Models.Profile.LayerProperties; using Artemis.UI.Shared.PropertyInput; using Artemis.UI.Shared.Services.Interfaces; using FluentValidation; @@ -42,18 +44,18 @@ namespace Artemis.UI.PropertyInput public SKSizePropertyInputViewModelValidator() { RuleFor(vm => vm.Width) - .LessThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Width) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); + .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber()); RuleFor(vm => vm.Width) - .GreaterThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Width) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); + .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber()); RuleFor(vm => vm.Height) - .LessThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Height) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); + .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber()); RuleFor(vm => vm.Height) - .GreaterThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Height) - .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); + .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue)) + .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber()); } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs index 14903f320..75167a483 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerEffects/EffectsViewModel.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Linq; using System.Threading.Tasks; +using Artemis.Core.Models.Profile; using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.LayerEffect; using Artemis.Core.Services.Interfaces; @@ -42,13 +43,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerEffects private void HandleSelectedLayerEffectChanged(object sender, PropertyChangedEventArgs e) { + EffectProfileElement effectElement; + if (LayerPropertiesViewModel.SelectedLayer != null) + effectElement = LayerPropertiesViewModel.SelectedLayer; + else if (LayerPropertiesViewModel.SelectedFolder != null) + effectElement = LayerPropertiesViewModel.SelectedFolder; + else + return; + if (e.PropertyName == nameof(SelectedLayerEffectDescriptor) && SelectedLayerEffectDescriptor != null) { - // Jump off the UI thread and let the fancy animation run - Task.Run(async () => + // Let the fancy animation run + Execute.PostToUIThread(async () => { await Task.Delay(500); - return _layerService.AddLayerEffect(LayerPropertiesViewModel.SelectedLayer, SelectedLayerEffectDescriptor); + _layerService.AddLayerEffect(effectElement, SelectedLayerEffectDescriptor); }); } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index 4a6b5e7c7..d35ef1d81 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -37,6 +37,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties CoreService = coreService; SettingsService = settingsService; + EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this); LayerPropertyGroups = new BindableCollection(); PropertyChanged += HandlePropertyTreeIndexChanged; } @@ -57,17 +58,20 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public int PropertyTreeIndex { get; set; } public bool PropertyTreeVisible => PropertyTreeIndex == 0; - public Layer SelectedLayer { get; set; } - public Folder SelectedFolder { get; set; } + + public PropertiesProfileElement SelectedPropertiesElement { get; set; } + public Layer SelectedLayer => SelectedPropertiesElement as Layer; + public Folder SelectedFolder => SelectedPropertiesElement as Folder; public BindableCollection LayerPropertyGroups { get; set; } public TreeViewModel TreeViewModel { get; set; } public EffectsViewModel EffectsViewModel { get; set; } public TimelineViewModel TimelineViewModel { get; set; } - + protected override void OnInitialActivate() { - PopulateProperties(ProfileEditorService.SelectedProfileElement); + if (ProfileEditorService.SelectedProfileElement is PropertiesProfileElement propertiesElement) + PopulateProperties(propertiesElement); ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; @@ -99,9 +103,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties private void ProfileEditorServiceOnProfileElementSelected(object sender, ProfileElementEventArgs e) { - PopulateProperties(e.ProfileElement); + PopulateProperties(e.ProfileElement as PropertiesProfileElement); } + private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e) { NotifyOfPropertyChange(() => FormattedCurrentTime); @@ -122,51 +127,45 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties return groups; } - private void PopulateProperties(ProfileElement profileElement) + private void PopulateProperties(PropertiesProfileElement profileElement) { - if (SelectedFolder != null) SelectedFolder = null; - + if (SelectedPropertiesElement != null && SelectedPropertiesElement is EffectProfileElement effectElement) + effectElement.LayerEffectsUpdated -= SelectedElementOnLayerEffectsUpdated; if (SelectedLayer != null) - { SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; - SelectedLayer.LayerEffectsUpdated -= SelectedLayerOnLayerEffectsUpdated; - SelectedLayer = null; - } foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) layerPropertyGroupViewModel.Dispose(); LayerPropertyGroups.Clear(); _brushPropertyGroup = null; - if (profileElement is Folder folder) - SelectedFolder = folder; - else if (profileElement is Layer layer) + SelectedPropertiesElement = profileElement; + if (SelectedPropertiesElement is EffectProfileElement newEffectElement) + newEffectElement.LayerEffectsUpdated += SelectedElementOnLayerEffectsUpdated; + + // Apply layer properties + if (SelectedLayer != null) { - SelectedLayer = layer; SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated; - SelectedLayer.LayerEffectsUpdated += SelectedLayerOnLayerEffectsUpdated; // Add the built-in root groups of the layer var generalAttribute = Attribute.GetCustomAttribute( - layer.GetType().GetProperty(nameof(layer.General)), + SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.General)), typeof(PropertyGroupDescriptionAttribute) ); var transformAttribute = Attribute.GetCustomAttribute( - layer.GetType().GetProperty(nameof(layer.Transform)), + SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.Transform)), typeof(PropertyGroupDescriptionAttribute) ); - LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); - LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); + LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); + LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); } - else - SelectedLayer = null; TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); - EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this); TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); ApplyLayerBrush(); - ApplyLayerEffects(); + ApplyEffects(); } private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e) @@ -174,9 +173,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties ApplyLayerBrush(); } - private void SelectedLayerOnLayerEffectsUpdated(object sender, EventArgs e) + private void SelectedElementOnLayerEffectsUpdated(object sender, EventArgs e) { - ApplyLayerEffects(); + ApplyEffects(); } public void ApplyLayerBrush() @@ -214,18 +213,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties TimelineViewModel.UpdateKeyframes(); } - private void ApplyLayerEffects() + private void ApplyEffects() { - if (SelectedLayer == null) + EffectProfileElement effectElement; + if (SelectedLayer != null) + effectElement = SelectedLayer; + else if (SelectedFolder != null) + effectElement = SelectedFolder; + else return; // Remove VMs of effects no longer applied on the layer - var toRemove = LayerPropertyGroups.Where(l => l.LayerPropertyGroup.LayerEffect != null && !SelectedLayer.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect)).ToList(); + var toRemove = LayerPropertyGroups.Where(l => l.LayerPropertyGroup.LayerEffect != null && !effectElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect)).ToList(); LayerPropertyGroups.RemoveRange(toRemove); foreach (var layerPropertyGroupViewModel in toRemove) layerPropertyGroupViewModel.Dispose(); - foreach (var layerEffect in SelectedLayer.LayerEffects) + foreach (var layerEffect in effectElement.LayerEffects) { if (LayerPropertyGroups.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect)) continue; @@ -250,7 +254,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties var nonEffectProperties = LayerPropertyGroups.Where(l => l.GroupType != LayerEffectRoot).ToList(); // Order the effects var effectProperties = LayerPropertyGroups.Where(l => l.GroupType == LayerEffectRoot).OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order).ToList(); - + // Put the non-effect properties in front for (var index = 0; index < nonEffectProperties.Count; index++) { diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs index d1e45882e..f2b8c18fc 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyGroupViewModel.cs @@ -46,8 +46,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties public override bool IsExpanded { - get => LayerPropertyGroup.Layer.IsPropertyGroupExpanded(LayerPropertyGroup); - set => LayerPropertyGroup.Layer.SetPropertyGroupExpanded(LayerPropertyGroup, value); + get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup); + set => LayerPropertyGroup.ProfileElement.SetPropertyGroupExpanded(LayerPropertyGroup, value); } public override bool IsVisible => !LayerPropertyGroup.IsHidden; diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 75d63ad13..cd6ea0662 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -306,7 +306,7 @@ namespace Artemis.UI.Screens.SurfaceEditor var device = Devices.LastOrDefault(d => PanZoomViewModel.TransformContainingRect(d.DeviceRectangle).Contains(position)); if (device != null) { - _rgbService.UpdateTrigger.Stop(); + _rgbService.IsRenderPaused = true; _mouseDragStatus = MouseDragStatus.Dragging; // If the device is not selected, deselect others and select only this one (if shift not held) if (device.SelectionStatus != SelectionStatus.Selected) @@ -351,7 +351,7 @@ namespace Artemis.UI.Screens.SurfaceEditor _surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true); _mouseDragStatus = MouseDragStatus.None; - _rgbService.UpdateTrigger.Start(); + _rgbService.IsRenderPaused = false; } private void UpdateSelection(Point position) diff --git a/src/Plugins/Artemis.Plugins.Devices.Logitech/LogitechDeviceProvider.cs b/src/Plugins/Artemis.Plugins.Devices.Logitech/LogitechDeviceProvider.cs index 152b4fb3f..5aa6b954d 100644 --- a/src/Plugins/Artemis.Plugins.Devices.Logitech/LogitechDeviceProvider.cs +++ b/src/Plugins/Artemis.Plugins.Devices.Logitech/LogitechDeviceProvider.cs @@ -43,11 +43,11 @@ namespace Artemis.Plugins.Devices.Logitech { try { - _logger.Debug("Found Logitech device {name} with PID {pid}", hidDevice.GetFriendlyName(), hidDevice.ProductID); + _logger.Debug("Found Logitech device {name} with PID 0x{pid}", hidDevice.GetFriendlyName(), hidDevice.ProductID.ToString("X")); } catch (Exception) { - _logger.Debug("Found Logitech device with PID {pid}", hidDevice.ProductID); + _logger.Debug("Found Logitech device with PID 0x{pid}", hidDevice.ProductID.ToString("X")); } } } diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index b5226d304..6828a813d 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Artemis.Core.Extensions; using Artemis.Core.Plugins.LayerBrush.Abstract; using Artemis.Core.Services.Interfaces; using Artemis.Plugins.LayerBrushes.Noise.Utilities; @@ -61,11 +62,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) { - var mainColor = Properties.MainColor?.CurrentValue; - var gradientColor = Properties.GradientColor?.CurrentValue; + var mainColor = Properties.MainColor.CurrentValue; + var secondColor = Properties.SecondaryColor.CurrentValue; + var gradientColor = Properties.GradientColor.CurrentValue; var scale = Properties.Scale.CurrentValue; - var opacity = mainColor != null ? (float) Math.Round(mainColor.Value.Alpha / 255.0, 2, MidpointRounding.AwayFromZero) : 0; - var hardness = 127 + Properties.Hardness.CurrentValue; + var hardness = Properties.Hardness.CurrentValue / 100f; // Scale down the render path to avoid computing a value for every pixel var width = (int) Math.Floor(path.Bounds.Width * _renderScale); @@ -83,35 +84,31 @@ namespace Artemis.Plugins.LayerBrushes.Noise if (double.IsInfinity(evalX) || double.IsNaN(evalX) || double.IsNaN(evalY) || double.IsInfinity(evalY)) continue; - var v = _noise.Evaluate(evalX, evalY, _z); - var alpha = (byte) Math.Max(0, Math.Min(255, v * hardness)); - if (Properties.ColorType.BaseValue == ColorMappingType.Simple && mainColor != null) - _bitmap.SetPixel(x, y, new SKColor(mainColor.Value.Red, mainColor.Value.Green, mainColor.Value.Blue, (byte) (alpha * opacity))); + var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness; + var amount = Math.Max(0f, Math.Min(1f, v)); + if (Properties.ColorType.BaseValue == ColorMappingType.Simple) + _bitmap.SetPixel(x, y, mainColor.Interpolate(secondColor, amount)); else if (gradientColor != null && _colorMap.Length == 101) { - var color = _colorMap[(int) Math.Round(alpha / 255f * 100, MidpointRounding.AwayFromZero)]; + var color = _colorMap[(int) Math.Round(amount * 100, MidpointRounding.AwayFromZero)]; _bitmap.SetPixel(x, y, color); } } } - var bitmapTransform = SKMatrix.Concat( SKMatrix.MakeTranslation(path.Bounds.Left, path.Bounds.Top), SKMatrix.MakeScale(1f / _renderScale, 1f / _renderScale) ); - canvas.ClipPath(path); if (Properties.ColorType.BaseValue == ColorMappingType.Simple) { - using var backgroundShader = SKShader.CreateColor(Properties.SecondaryColor.CurrentValue); - paint.Shader = backgroundShader; - canvas.DrawRect(path.Bounds, paint); + paint.Color = Properties.SecondaryColor.CurrentValue; } using var foregroundShader = SKShader.CreateBitmap(_bitmap, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, bitmapTransform); paint.Shader = foregroundShader; - canvas.DrawRect(path.Bounds, paint); + canvas.DrawPath(path, paint); } private void GradientColorChanged(object sender, PropertyChangedEventArgs e) diff --git a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs index c6318a8ec..17fe7255e 100644 --- a/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerBrushes.Noise/NoiseBrushProperties.cs @@ -24,7 +24,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise [PropertyDescription(Description = "The scale of the noise", MinInputValue = 0f, InputAffix = "%")] public SKSizeLayerProperty Scale { get; set; } - [PropertyDescription(Description = "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", MinInputValue = 0f, MaxInputValue = 2048f)] + [PropertyDescription(Description = "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 400)] public FloatLayerProperty Hardness { get; set; } [PropertyDescription(Description = "The speed at which the noise moves vertically and horizontally", MinInputValue = -64f, MaxInputValue = 64f)] @@ -39,7 +39,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise SecondaryColor.DefaultValue = new SKColor(0, 0, 255); GradientColor.DefaultValue = ColorGradient.GetUnicornBarf(); Scale.DefaultValue = new SKSize(100, 100); - Hardness.DefaultValue = 500f; + Hardness.DefaultValue = 100f; AnimationSpeed.DefaultValue = 25f; } diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/BlurEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/BlurEffect.cs index b5c08913b..978c4ca6e 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/BlurEffect.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/BlurEffect.cs @@ -37,13 +37,13 @@ namespace Artemis.Plugins.LayerEffects.Filter } } - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { if (_imageFilter != null) paint.ImageFilter = SKImageFilter.CreateMerge(paint.ImageFilter, _imageFilter); } - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { } diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffect.cs index 411a63d75..6d232bd92 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffect.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffect.cs @@ -17,7 +17,7 @@ namespace Artemis.Plugins.LayerEffects.Filter { } - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { paint.ImageFilter = SKImageFilter.CreateDilate( (int) Properties.DilateRadius.CurrentValue.Width, @@ -26,7 +26,7 @@ namespace Artemis.Plugins.LayerEffects.Filter ); } - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { } } diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffectProperties.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffectProperties.cs index 3b3cfa502..3049f2867 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffectProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/DilateEffectProperties.cs @@ -6,7 +6,7 @@ namespace Artemis.Plugins.LayerEffects.Filter { public class DilateEffectProperties : LayerPropertyGroup { - [PropertyDescription(Description = "The amount of dilation to apply")] + [PropertyDescription(Description = "The amount of dilation to apply", MinInputValue = 0)] public SKSizeLayerProperty DilateRadius { get; set; } protected override void PopulateDefaults() diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffect.cs index d532aac3a..d671639a2 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffect.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffect.cs @@ -17,7 +17,7 @@ namespace Artemis.Plugins.LayerEffects.Filter { } - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { paint.ImageFilter = SKImageFilter.CreateErode( (int) Properties.ErodeRadius.CurrentValue.Width, @@ -26,7 +26,7 @@ namespace Artemis.Plugins.LayerEffects.Filter ); } - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { } } diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffectProperties.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffectProperties.cs index 4a643c858..84fad6aff 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffectProperties.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/ErodeEffectProperties.cs @@ -1,12 +1,13 @@ using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerProperties.Types; +using SkiaSharp; namespace Artemis.Plugins.LayerEffects.Filter { public class ErodeEffectProperties : LayerPropertyGroup { - [PropertyDescription(Description = "The amount of erode to apply")] + [PropertyDescription(Description = "The amount of erode to apply", MinInputValue = 0)] public SKSizeLayerProperty ErodeRadius { get; set; } protected override void PopulateDefaults() diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GlowEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GlowEffect.cs index 636c21bab..b3db29c1b 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GlowEffect.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GlowEffect.cs @@ -17,7 +17,7 @@ namespace Artemis.Plugins.LayerEffects.Filter { } - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { paint.ImageFilter = SKImageFilter.CreateDropShadow( Properties.GlowOffset.CurrentValue.X, @@ -29,7 +29,7 @@ namespace Artemis.Plugins.LayerEffects.Filter ); } - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { } } diff --git a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GrayScaleEffect.cs b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GrayScaleEffect.cs index 5e7657118..44cd83b26 100644 --- a/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GrayScaleEffect.cs +++ b/src/Plugins/Artemis.Plugins.LayerEffects.Filter/GrayScaleEffect.cs @@ -17,7 +17,7 @@ namespace Artemis.Plugins.LayerEffects.Filter { } - public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { paint.ImageFilter = SKImageFilter.CreateColorFilter(SKColorFilter.CreateColorMatrix(new[] { @@ -28,7 +28,7 @@ namespace Artemis.Plugins.LayerEffects.Filter }), paint.ImageFilter); } - public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint) { } }