1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profiles - Abstracted property elements and effect elements

Folders - Added properties to folders
Layer effects - Expanded to folders
Layer effects - Added shape clipping
This commit is contained in:
SpoinkyNL 2020-06-17 19:21:23 +02:00
parent 34bcd22f8c
commit 75b0ee8151
42 changed files with 638 additions and 347 deletions

View File

@ -1,4 +1,5 @@
using RGB.NET.Core; using System;
using RGB.NET.Core;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core.Extensions namespace Artemis.Core.Extensions
@ -10,5 +11,25 @@ namespace Artemis.Core.Extensions
{ {
return new Color(color.Alpha, color.Red, color.Green, color.Blue); 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);
}
} }
} }

View File

@ -11,5 +11,20 @@ namespace Artemis.Core.Extensions
return type.BaseType?.GetGenericTypeDefinition() == genericType; 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;
}
} }
} }

View File

@ -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<BaseLayerEffect> _layerEffects;
/// <summary>
/// Gets a read-only collection of the layer effects on this entity
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> 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
}
}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Plugins.LayerEffect.Abstract;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
@ -8,10 +7,8 @@ using SkiaSharp;
namespace Artemis.Core.Models.Profile namespace Artemis.Core.Models.Profile
{ {
public sealed class Folder : ProfileElement public sealed class Folder : EffectProfileElement
{ {
private readonly List<BaseLayerEffect> _layerEffects;
public Folder(Profile profile, ProfileElement parent, string name) public Folder(Profile profile, ProfileElement parent, string name)
{ {
FolderEntity = new FolderEntity(); FolderEntity = new FolderEntity();
@ -20,19 +17,25 @@ namespace Artemis.Core.Models.Profile
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
Name = name; Name = name;
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
} }
internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) internal Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity)
{ {
FolderEntity = folderEntity; FolderEntity = folderEntity;
EntityId = folderEntity.Id; EntityId = folderEntity.Id;
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
Name = folderEntity.Name; Name = folderEntity.Name;
Order = folderEntity.Order; Order = folderEntity.Order;
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>();
_expandedPropertyGroups.AddRange(folderEntity.ExpandedPropertyGroups);
// TODO: Load conditions // TODO: Load conditions
@ -50,14 +53,14 @@ namespace Artemis.Core.Models.Profile
} }
internal FolderEntity FolderEntity { get; set; } internal FolderEntity FolderEntity { get; set; }
internal override PropertiesEntity PropertiesEntity => FolderEntity;
/// <summary> internal override EffectsEntity EffectsEntity => FolderEntity;
/// Gets a read-only collection of the layer effects on this layer
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly();
public override void Update(double deltaTime) 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 // Iterate the children in reverse because that's how they must be rendered too
for (var index = Children.Count - 1; index > -1; index--) 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 // 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--) for (var index = Children.Count - 1; index > -1; index--)
{ {
var profileElement = Children[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) public Folder AddFolder(string name)
@ -88,6 +109,24 @@ namespace Artemis.Core.Models.Profile
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; 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() internal override void ApplyToEntity()
{ {
FolderEntity.Id = EntityId; FolderEntity.Id = EntityId;
@ -98,7 +137,20 @@ namespace Artemis.Core.Models.Profile
FolderEntity.ProfileId = Profile.EntityId; FolderEntity.ProfileId = Profile.EntityId;
ApplyLayerEffectsToEntity();
// TODO: conditions // TODO: conditions
} }
#region Events
public event EventHandler RenderPropertiesUpdated;
private void OnRenderPropertiesUpdated()
{
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
#endregion
} }
} }

View File

@ -21,13 +21,10 @@ namespace Artemis.Core.Models.Profile
/// Represents a layer on a profile. To create new layers use the <see cref="LayerService" /> by injecting /// Represents a layer on a profile. To create new layers use the <see cref="LayerService" /> by injecting
/// <see cref="ILayerService" /> into your code /// <see cref="ILayerService" /> into your code
/// </summary> /// </summary>
public sealed class Layer : ProfileElement public sealed class Layer : EffectProfileElement
{ {
private readonly List<string> _expandedPropertyGroups;
private readonly List<BaseLayerEffect> _layerEffects;
private LayerShape _layerShape; private LayerShape _layerShape;
private List<ArtemisLed> _leds; private List<ArtemisLed> _leds;
private SKPath _path;
internal Layer(Profile profile, ProfileElement parent, string name) internal Layer(Profile profile, ProfileElement parent, string name)
{ {
@ -68,38 +65,14 @@ namespace Artemis.Core.Models.Profile
} }
internal LayerEntity LayerEntity { get; set; } internal LayerEntity LayerEntity { get; set; }
internal override PropertiesEntity PropertiesEntity => LayerEntity;
/// <summary> internal override EffectsEntity EffectsEntity => LayerEntity;
/// Gets a read-only collection of the layer effects on this layer
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects => _layerEffects.AsReadOnly();
/// <summary> /// <summary>
/// A collection of all the LEDs this layer is assigned to. /// A collection of all the LEDs this layer is assigned to.
/// </summary> /// </summary>
public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly(); public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly();
/// <summary>
/// Gets a copy of the path containing all the LEDs this layer is applied to, any rendering outside the layer Path is
/// clipped.
/// </summary>
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;
}
}
/// <summary>
/// The bounds of this layer
/// </summary>
public SKRect Bounds { get; private set; }
/// <summary> /// <summary>
/// Defines the shape that is rendered by the <see cref="LayerBrush" />. /// Defines the shape that is rendered by the <see cref="LayerBrush" />.
/// </summary> /// </summary>
@ -130,19 +103,6 @@ namespace Artemis.Core.Models.Profile
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}"; 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 #region Storage
internal override void ApplyToEntity() internal override void ApplyToEntity()
@ -161,21 +121,7 @@ namespace Artemis.Core.Models.Profile
LayerBrush?.BaseProperties.ApplyToEntity(); LayerBrush?.BaseProperties.ApplyToEntity();
// Effects // Effects
LayerEntity.LayerEffects.Clear(); ApplyLayerEffectsToEntity();
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();
}
// LEDs // LEDs
LayerEntity.Leds.Clear(); LayerEntity.Leds.Clear();
@ -190,7 +136,7 @@ namespace Artemis.Core.Models.Profile
} }
// Conditions TODO // Conditions TODO
LayerEntity.Condition.Clear(); LayerEntity.Conditions.Clear();
} }
#endregion #endregion
@ -273,7 +219,7 @@ namespace Artemis.Core.Models.Profile
} }
/// <inheritdoc /> /// <inheritdoc />
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 // Ensure the layer is ready
if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized) if (Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized)
@ -285,35 +231,41 @@ namespace Artemis.Core.Models.Profile
canvas.Save(); canvas.Save();
canvas.ClipPath(Path); canvas.ClipPath(Path);
using (var paint = new SKPaint()) paint.BlendMode = General.BlendMode.CurrentValue;
{ paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f));
paint.FilterQuality = SKFilterQuality.Low;
paint.BlendMode = General.BlendMode.CurrentValue;
paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f));
foreach (var baseLayerEffect in LayerEffects) // Pre-processing only affects other pre-processors and the brushes
baseLayerEffect.PreProcess(canvas, canvasInfo, Path, paint); canvas.Save();
foreach (var baseLayerEffect in LayerEffects)
baseLayerEffect.InternalPreProcess(canvas, canvasInfo, new SKPath(Path), paint);
if (!LayerBrush.SupportsTransformation) // Shape clip must be determined before commiting to any rendering
SimpleRender(canvas, canvasInfo, paint); var shapeClip = CreateShapeClip();
else if (General.FillType.CurrentValue == LayerFillType.Stretch) if (!shapeClip.IsEmpty)
StretchRender(canvas, canvasInfo, paint); ExcludePathFromTranslation(shapeClip);
else if (General.FillType.CurrentValue == LayerFillType.Clip)
ClipRender(canvas, canvasInfo, paint);
foreach (var baseLayerEffect in LayerEffects) if (!LayerBrush.SupportsTransformation)
baseLayerEffect.PostProcess(canvas, canvasInfo, Path, paint); 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(); 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 // Apply transformations
var sizeProperty = Transform.Scale.CurrentValue; 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.Scale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y);
canvas.Translate(x, 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 // Apply transformation
var sizeProperty = Transform.Scale.CurrentValue; var sizeProperty = Transform.Scale.CurrentValue;
@ -367,29 +320,31 @@ namespace Artemis.Core.Models.Profile
var renderPath = new SKPath(); var renderPath = new SKPath();
renderPath.AddRect(boundsRect); renderPath.AddRect(boundsRect);
renderPath = renderPath.Op(shapeClip, SKPathOp.Difference);
LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint); LayerBrush.InternalRender(canvas, canvasInfo, renderPath, paint);
} }
internal void CalculateRenderProperties() internal void CalculateRenderProperties()
{ {
if (!Leds.Any()) if (!Leds.Any())
{
Path = new SKPath(); Path = new SKPath();
else
{
var path = new SKPath {FillType = SKPathFillType.Winding};
foreach (var artemisLed in Leds)
path.AddRect(artemisLed.AbsoluteRenderRectangle);
LayerShape?.CalculateRenderProperties(); Path = path;
OnRenderPropertiesUpdated();
return;
} }
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 // This is called here so that the shape's render properties are up to date when other code
// responds to OnRenderPropertiesUpdated // responds to OnRenderPropertiesUpdated
LayerShape?.CalculateRenderProperties(); LayerShape?.CalculateRenderProperties();
// Folder render properties are based on child paths and thus require an update
if (Parent is Folder folder)
folder.CalculateRenderProperties();
OnRenderPropertiesUpdated(); OnRenderPropertiesUpdated();
} }
@ -407,9 +362,39 @@ namespace Artemis.Core.Models.Profile
return position; return position;
} }
#endregion
#region Effect management /// <summary>
/// Excludes the provided path from the translations applied to the layer by applying translations that cancel the
/// layer translations out
/// </summary>
/// <param name="path"></param>
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 #endregion
@ -494,15 +479,6 @@ namespace Artemis.Core.Models.Profile
brush.Dispose(); 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() internal void RemoveLayerBrush()
{ {
if (LayerBrush == null) 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.")); 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 #endregion
#region Events #region Events
public event EventHandler RenderPropertiesUpdated; public event EventHandler RenderPropertiesUpdated;
public event EventHandler ShapePropertiesUpdated;
public event EventHandler LayerBrushUpdated; public event EventHandler LayerBrushUpdated;
public event EventHandler LayerEffectsUpdated;
private void OnRenderPropertiesUpdated() private void OnRenderPropertiesUpdated()
{ {
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty); RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
} }
private void OnShapePropertiesUpdated()
{
ShapePropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
internal void OnLayerBrushUpdated() internal void OnLayerBrushUpdated()
{ {
LayerBrushUpdated?.Invoke(this, EventArgs.Empty); LayerBrushUpdated?.Invoke(this, EventArgs.Empty);
} }
internal void OnLayerEffectsUpdated()
{
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
}
#endregion #endregion
} }

View File

@ -18,9 +18,9 @@ namespace Artemis.Core.Models.Profile.LayerProperties
} }
/// <summary> /// <summary>
/// The layer this property applies to /// Gets the profile element (such as layer or folder) this effect is applied to
/// </summary> /// </summary>
public Layer Layer { get; internal set; } public PropertiesProfileElement ProfileElement { get; internal set; }
/// <summary> /// <summary>
/// The parent group of this layer property, set after construction /// The parent group of this layer property, set after construction

View File

@ -1,4 +1,5 @@
using System; using System;
using Artemis.Core.Extensions;
using SkiaSharp; using SkiaSharp;
namespace Artemis.Core.Models.Profile.LayerProperties.Types 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) protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
{ {
var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red; CurrentValue = CurrentKeyframe.Value.Interpolate(NextKeyframe.Value, keyframeProgressEased);
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)
);
} }
private static byte ClampToByte(float value) private static byte ClampToByte(float value)

View File

@ -29,9 +29,9 @@ namespace Artemis.Core.Models.Profile
} }
/// <summary> /// <summary>
/// The layer this property group applies to /// Gets the profile element (such as layer or folder) this effect is applied to
/// </summary> /// </summary>
public Layer Layer { get; internal set; } public PropertiesProfileElement ProfileElement { get; internal set; }
/// <summary> /// <summary>
/// The path of this property group /// The path of this property group
@ -129,7 +129,7 @@ namespace Artemis.Core.Models.Profile
PropertyGroupInitialized?.Invoke(this, EventArgs.Empty); 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) if (path == null)
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
@ -137,7 +137,7 @@ namespace Artemis.Core.Models.Profile
if (PropertiesInitialized) if (PropertiesInitialized)
throw new ArtemisCoreException("Layer property group already initialized, wut"); throw new ArtemisCoreException("Layer property group already initialized, wut");
Layer = layer; ProfileElement = profileElement;
Path = path.TrimEnd('.'); Path = path.TrimEnd('.');
// Get all properties with a PropertyDescriptionAttribute // Get all properties with a PropertyDescriptionAttribute
@ -153,10 +153,10 @@ namespace Artemis.Core.Models.Profile
if (instance == null) if (instance == null)
throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}"); throw new ArtemisPluginException($"Failed to create instance of layer property at {path + propertyInfo.Name}");
instance.Layer = layer; instance.ProfileElement = profileElement;
instance.Parent = this; instance.Parent = this;
instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription; instance.PropertyDescription = (PropertyDescriptionAttribute) propertyDescription;
InitializeProperty(layer, path + propertyInfo.Name, instance); InitializeProperty(profileElement, path + propertyInfo.Name, instance);
propertyInfo.SetValue(this, instance); propertyInfo.SetValue(this, instance);
_layerProperties.Add(instance); _layerProperties.Add(instance);
@ -177,7 +177,7 @@ namespace Artemis.Core.Models.Profile
instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription; instance.GroupDescription = (PropertyGroupDescriptionAttribute) propertyGroupDescription;
instance.LayerBrush = LayerBrush; instance.LayerBrush = LayerBrush;
instance.LayerEffect = LayerEffect; instance.LayerEffect = LayerEffect;
instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}."); instance.InitializeProperties(layerService, profileElement, $"{path}{propertyInfo.Name}.");
propertyInfo.SetValue(this, instance); propertyInfo.SetValue(this, instance);
_layerPropertyGroups.Add(instance); _layerPropertyGroups.Add(instance);
@ -236,7 +236,7 @@ namespace Artemis.Core.Models.Profile
OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime)); OnPropertyGroupOverriding(new PropertyGroupUpdatingEventArgs(overrideTime));
} }
private void InitializeProperty(Layer layer, string path, BaseLayerProperty instance) private void InitializeProperty(PropertiesProfileElement profileElement, string path, BaseLayerProperty instance)
{ {
Guid pluginGuid; Guid pluginGuid;
if (IsCorePropertyGroup || instance.IsCoreProperty) if (IsCorePropertyGroup || instance.IsCoreProperty)
@ -246,13 +246,13 @@ namespace Artemis.Core.Models.Profile
else else
pluginGuid = instance.Parent.LayerEffect.PluginInfo.Guid; 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; var fromStorage = true;
if (entity == null) if (entity == null)
{ {
fromStorage = false; fromStorage = false;
entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path}; entity = new PropertyEntity {PluginGuid = pluginGuid, Path = path};
layer.LayerEntity.PropertyEntities.Add(entity); profileElement.PropertiesEntity.PropertyEntities.Add(entity);
} }
instance.ApplyToLayerProperty(entity, this, fromStorage); instance.ApplyToLayerProperty(entity, this, fromStorage);

View File

@ -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) lock (this)
{ {
@ -65,7 +65,7 @@ namespace Artemis.Core.Models.Profile
throw new ArtemisCoreException($"Cannot render inactive profile: {this}"); throw new ArtemisCoreException($"Cannot render inactive profile: {this}");
foreach (var profileElement in Children) foreach (var profileElement in Children)
profileElement.Render(deltaTime, canvas, canvasInfo); profileElement.Render(deltaTime, canvas, canvasInfo, paint);
} }
} }

View File

@ -44,7 +44,7 @@ namespace Artemis.Core.Models.Profile
/// <summary> /// <summary>
/// Renders the element /// Renders the element
/// </summary> /// </summary>
public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo); public abstract void Render(double deltaTime, SKCanvas canvas, SKImageInfo canvasInfo, SKPaint paint);
public List<Folder> GetAllFolders() public List<Folder> GetAllFolders()
{ {

View File

@ -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;
/// <summary>
/// Gets a copy of the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped.
/// </summary>
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;
}
}
/// <summary>
/// The bounds of this entity
/// </summary>
public SKRect Bounds { get; private set; }
#region Property group expansion
protected List<string> _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
}
}

View File

@ -26,7 +26,8 @@ namespace Artemis.Core.Plugins.Abstract
lock (this) lock (this)
{ {
// Render the profile // Render the profile
ActiveProfile?.Render(deltaTime, canvas, canvasInfo); using var paint = new SKPaint {FilterQuality = SKFilterQuality.Low};
ActiveProfile?.Render(deltaTime, canvas, canvasInfo, paint);
} }
} }

View File

@ -13,19 +13,14 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract
public abstract class BaseLayerEffect : PropertyChangedBase, IDisposable public abstract class BaseLayerEffect : PropertyChangedBase, IDisposable
{ {
/// <summary> /// <summary>
/// Gets the unique ID of this effect /// Gets the unique ID of this effect
/// </summary> /// </summary>
public Guid EntityId { get; internal set; } public Guid EntityId { get; internal set; }
/// <summary> /// <summary>
/// Gets the layer this effect is applied to /// Gets the profile element (such as layer or folder) this effect is applied to
/// </summary> /// </summary>
public Layer Layer { get; internal set; } public EffectProfileElement ProfileElement { get; internal set; }
/// <summary>
/// Gets the folder this effect is applied to
/// </summary>
public Folder Folder { get; internal set; }
/// <summary> /// <summary>
/// The name which appears in the editor /// The name which appears in the editor
@ -84,31 +79,78 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract
/// <summary> /// <summary>
/// Called before the layer or folder will be rendered /// Called before the layer or folder will be rendered
/// </summary> /// </summary>
public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); /// <param name="canvas">The canvas used to render the frame</param>
/// <param name="canvasInfo">Info on the canvas size and pixel type</param>
/// <param name="renderBounds">The bounds this layer/folder will render in</param>
/// <param name="paint">The paint this layer/folder will use to render</param>
public abstract void PreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint);
/// <summary> /// <summary>
/// Called after the layer of folder has been rendered /// Called after the layer of folder has been rendered
/// </summary> /// </summary>
public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint); /// <param name="canvas">The canvas used to render the frame</param>
/// <param name="canvasInfo">Info on the canvas size and pixel type</param>
/// <param name="renderBounds">The bounds this layer/folder rendered in</param>
/// <param name="paint">The paint this layer/folder used to render</param>
public abstract void PostProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath renderBounds, SKPaint paint);
/// <summary>
/// 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).
/// <para>
/// 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
/// </para>
/// </summary>
/// <param name="renderBounds">The bounds this layer/folder rendered in</param>
/// <returns></returns>
public virtual SKPath CreateShapeClip(SKPath renderBounds)
{
return new SKPath();
}
internal void InternalPreProcess(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint) 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 // 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 // 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); 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) 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 // 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 // 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); 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 // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything

View File

@ -2,6 +2,7 @@
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Plugins.Exceptions; using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
using SkiaSharp;
namespace Artemis.Core.Plugins.LayerEffect.Abstract namespace Artemis.Core.Plugins.LayerEffect.Abstract
{ {
@ -39,7 +40,7 @@ namespace Artemis.Core.Plugins.LayerEffect.Abstract
{ {
Properties = Activator.CreateInstance<T>(); Properties = Activator.CreateInstance<T>();
Properties.LayerEffect = this; Properties.LayerEffect = this;
Properties.InitializeProperties(layerService, Layer, PropertyRootPath); Properties.InitializeProperties(layerService, ProfileElement, PropertyRootPath);
PropertiesInitialized = true; PropertiesInitialized = true;
EnableLayerEffect(); EnableLayerEffect();

View File

@ -49,7 +49,6 @@ namespace Artemis.Core.Services
_pluginService.PluginEnabled += (sender, args) => _modules = _pluginService.GetPluginsOfType<Module>(); _pluginService.PluginEnabled += (sender, args) => _modules = _pluginService.GetPluginsOfType<Module>();
_pluginService.PluginDisabled += (sender, args) => _modules = _pluginService.GetPluginsOfType<Module>(); _pluginService.PluginDisabled += (sender, args) => _modules = _pluginService.GetPluginsOfType<Module>();
ConfigureJsonConvert(); ConfigureJsonConvert();
} }
@ -113,6 +112,9 @@ namespace Artemis.Core.Services
private void SurfaceOnUpdating(UpdatingEventArgs args) private void SurfaceOnUpdating(UpdatingEventArgs args)
{ {
if (_rgbService.IsRenderPaused)
return;
try try
{ {
if (!ModuleUpdatingDisabled && _modules != null) if (!ModuleUpdatingDisabled && _modules != null)
@ -159,6 +161,9 @@ namespace Artemis.Core.Services
private void SurfaceOnUpdated(UpdatedEventArgs args) private void SurfaceOnUpdated(UpdatedEventArgs args)
{ {
if (_rgbService.IsRenderPaused)
return;
OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush, _rgbService.Surface)); OnFrameRendered(new FrameRenderedEventArgs(_rgbService.BitmapBrush, _rgbService.Surface));
} }

View File

@ -36,17 +36,17 @@ namespace Artemis.Core.Services.Interfaces
/// Instantiates and adds the <see cref="BaseLayerEffect" /> described by the provided /// Instantiates and adds the <see cref="BaseLayerEffect" /> described by the provided
/// <see cref="LayerEffectDescriptor" /> to the <see cref="Layer" />. /// <see cref="LayerEffectDescriptor" /> to the <see cref="Layer" />.
/// </summary> /// </summary>
/// <param name="layer">The layer to instantiate the effect for</param> /// <param name="effectProfileElement">The layer/folder to instantiate the effect for</param>
void InstantiateLayerEffects(Layer layer); void InstantiateLayerEffects(EffectProfileElement effectProfileElement);
/// <summary> /// <summary>
/// Adds the <see cref="BaseLayerEffect" /> described by the provided <see cref="LayerEffectDescriptor" /> to the /// Adds the <see cref="BaseLayerEffect" /> described by the provided <see cref="LayerEffectDescriptor" /> to the
/// <see cref="Layer" />. /// <see cref="Layer" />.
/// </summary> /// </summary>
/// <param name="layer">The layer to instantiate the effect for</param> /// <param name="effectProfileElement">The layer/folder to instantiate the effect for</param>
/// <param name="layerEffectDescriptor"></param> /// <param name="layerEffectDescriptor"></param>
/// <returns></returns> /// <returns></returns>
BaseLayerEffect AddLayerEffect(Layer layer, LayerEffectDescriptor layerEffectDescriptor); BaseLayerEffect AddLayerEffect(EffectProfileElement effectProfileElement, LayerEffectDescriptor layerEffectDescriptor);
void RemoveLayerEffect(BaseLayerEffect layerEffect); void RemoveLayerEffect(BaseLayerEffect layerEffect);
} }

View File

@ -29,6 +29,7 @@ namespace Artemis.Core.Services.Interfaces
IReadOnlyCollection<IRGBDevice> LoadedDevices { get; } IReadOnlyCollection<IRGBDevice> LoadedDevices { get; }
TimerUpdateTrigger UpdateTrigger { get; } TimerUpdateTrigger UpdateTrigger { get; }
bool IsRenderPaused { get; set; }
/// <summary> /// <summary>
/// Adds the given device provider to the <see cref="Surface" /> /// Adds the given device provider to the <see cref="Surface" />

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.Exceptions; using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile; 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;
using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Core.Plugins.LayerEffect.Abstract;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Entities.Profile;
using Ninject; using Ninject;
using Serilog; using Serilog;
@ -77,61 +79,68 @@ namespace Artemis.Core.Services
return brush; return brush;
} }
public void InstantiateLayerEffects(Layer layer) public BaseLayerEffect AddLayerEffect(EffectProfileElement effectElement, LayerEffectDescriptor layerEffectDescriptor)
{
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<LayerEffectProvider>();
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)
{ {
// Create the effect with dependency injection
var effect = (BaseLayerEffect) _kernel.Get(layerEffectDescriptor.LayerEffectType); var effect = (BaseLayerEffect) _kernel.Get(layerEffectDescriptor.LayerEffectType);
effect.ProfileElement = effectElement;
effect.EntityId = Guid.NewGuid(); effect.EntityId = Guid.NewGuid();
effect.Layer = layer; effect.Order = effectElement.LayerEffects.Count + 1;
effect.Order = layer.LayerEffects.Count + 1;
effect.Descriptor = layerEffectDescriptor; effect.Descriptor = layerEffectDescriptor;
effect.Initialize(this); effect.Initialize(this);
effect.Update(0); effect.Update(0);
layer.AddLayerEffect(effect); effectElement.AddLayerEffect(effect);
_logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath); _logger.Debug("Added layer effect with root path {rootPath}", effect.PropertyRootPath);
layer.OnLayerEffectsUpdated();
return effect; return effect;
} }
public void RemoveLayerEffect(BaseLayerEffect layerEffect) public void RemoveLayerEffect(BaseLayerEffect layerEffect)
{ {
// // Make sure the group is collapsed or the effect that gets this effect's order gets expanded layerEffect.ProfileElement.RemoveLayerEffect(layerEffect);
// layerEffect.Layer.SetPropertyGroupExpanded(layerEffect.BaseProperties, false); }
layerEffect.Layer.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<LayerEffectProvider>();
var descriptors = layerEffectProviders.SelectMany(l => l.LayerEffectDescriptors).ToList();
List<LayerEffectEntity> 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);
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Artemis.Core.Events; using Artemis.Core.Events;
using Artemis.Core.Plugins.Models; using Artemis.Core.Plugins.Models;
using Artemis.Core.RGB.NET; using Artemis.Core.RGB.NET;
@ -47,6 +48,7 @@ namespace Artemis.Core.Services
public BitmapBrush BitmapBrush { get; private set; } public BitmapBrush BitmapBrush { get; private set; }
public IReadOnlyCollection<IRGBDevice> LoadedDevices => _loadedDevices.AsReadOnly(); public IReadOnlyCollection<IRGBDevice> LoadedDevices => _loadedDevices.AsReadOnly();
public double RenderScale => _renderScaleSetting.Value; public double RenderScale => _renderScaleSetting.Value;
public bool IsRenderPaused { get; set; }
public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
{ {

View File

@ -94,6 +94,7 @@ namespace Artemis.Core.Services.Storage
{ {
InitializeLayerProperties(profile); InitializeLayerProperties(profile);
InstantiateLayers(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) private void InstantiateLayers(Profile profile)
{ {
// Only instantiate brushes for layers without an existing brush/effect instance
foreach (var layer in profile.GetAllLayers()) foreach (var layer in profile.GetAllLayers())
{ {
_layerService.InstantiateLayerBrush(layer); _layerService.InstantiateLayerBrush(layer);
@ -190,11 +198,14 @@ namespace Artemis.Core.Services.Storage
profileModule.ActiveProfile.PopulateLeds(surface); profileModule.ActiveProfile.PopulateLeds(surface);
} }
private void ActiveProfilesInstantiateProfileLayerBrushes() private void ActiveProfilesInstantiatePlugins()
{ {
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>(); var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList())
{
InstantiateLayers(profileModule.ActiveProfile); InstantiateLayers(profileModule.ActiveProfile);
InstantiateFolders(profileModule.ActiveProfile);
}
} }
#region Event handlers #region Event handlers
@ -213,7 +224,7 @@ namespace Artemis.Core.Services.Storage
private void OnPluginLoaded(object sender, PluginEventArgs e) private void OnPluginLoaded(object sender, PluginEventArgs e)
{ {
if (e.PluginInfo.Instance is LayerBrushProvider) if (e.PluginInfo.Instance is LayerBrushProvider)
ActiveProfilesInstantiateProfileLayerBrushes(); ActiveProfilesInstantiatePlugins();
else if (e.PluginInfo.Instance is ProfileModule profileModule) else if (e.PluginInfo.Instance is ProfileModule profileModule)
{ {
var activeProfile = GetActiveProfile(profileModule); var activeProfile = GetActiveProfile(profileModule);

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile
{
public abstract class EffectsEntity : PropertiesEntity
{
public List<LayerEffectEntity> LayerEffects { get; set; }
}
}

View File

@ -4,16 +4,22 @@ using LiteDB;
namespace Artemis.Storage.Entities.Profile namespace Artemis.Storage.Entities.Profile
{ {
public class FolderEntity public class FolderEntity : EffectsEntity
{ {
public FolderEntity()
{
PropertyEntities = new List<PropertyEntity>();
Conditions = new List<ProfileConditionEntity>();
LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>();
}
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid ParentId { get; set; } public Guid ParentId { get; set; }
public int Order { get; set; } public int Order { get; set; }
public string Name { get; set; } public string Name { get; set; }
public List<ProfileConditionEntity> Conditions { get; set; }
[BsonRef("ProfileEntity")] [BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } public ProfileEntity Profile { get; set; }

View File

@ -4,13 +4,13 @@ using LiteDB;
namespace Artemis.Storage.Entities.Profile namespace Artemis.Storage.Entities.Profile
{ {
public class LayerEntity public class LayerEntity : EffectsEntity
{ {
public LayerEntity() public LayerEntity()
{ {
Leds = new List<LedEntity>(); Leds = new List<LedEntity>();
PropertyEntities = new List<PropertyEntity>(); PropertyEntities = new List<PropertyEntity>();
Condition = new List<ProfileConditionEntity>(); Conditions = new List<ProfileConditionEntity>();
LayerEffects = new List<LayerEffectEntity>(); LayerEffects = new List<LayerEffectEntity>();
ExpandedPropertyGroups = new List<string>(); ExpandedPropertyGroups = new List<string>();
} }
@ -22,10 +22,6 @@ namespace Artemis.Storage.Entities.Profile
public string Name { get; set; } public string Name { get; set; }
public List<LedEntity> Leds { get; set; } public List<LedEntity> Leds { get; set; }
public List<PropertyEntity> PropertyEntities { get; set; }
public List<ProfileConditionEntity> Condition { get; set; }
public List<LayerEffectEntity> LayerEffects { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }
[BsonRef("ProfileEntity")] [BsonRef("ProfileEntity")]
public ProfileEntity Profile { get; set; } public ProfileEntity Profile { get; set; }

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile
{
public abstract class PropertiesEntity
{
public List<ProfileConditionEntity> Conditions { get; set; }
public List<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }
}
}

View File

@ -98,6 +98,11 @@ namespace Artemis.UI.Shared.Services
return; return;
var delta = CurrentTime - _lastUpdateTime; 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()) foreach (var layer in SelectedProfile.GetAllLayers())
{ {
layer.OverrideProgress(CurrentTime); layer.OverrideProgress(CurrentTime);

View File

@ -52,8 +52,11 @@ namespace Artemis.UI.PropertyInput
protected override void OnInputValueApplied() protected override void OnInputValueApplied()
{ {
_layerService.RemoveLayerBrush(LayerProperty.Layer); if (LayerProperty.ProfileElement is Layer layer)
_layerService.InstantiateLayerBrush(LayerProperty.Layer); {
_layerService.RemoveLayerBrush(layer);
_layerService.InstantiateLayerBrush(layer);
}
} }
private void SetBrushByDescriptor(LayerBrushDescriptor value) private void SetBrushByDescriptor(LayerBrushDescriptor value)

View File

@ -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.PropertyInput;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
using FluentValidation; using FluentValidation;
@ -42,18 +44,18 @@ namespace Artemis.UI.PropertyInput
public SKPointPropertyInputViewModelValidator() public SKPointPropertyInputViewModelValidator()
{ {
RuleFor(vm => vm.X) RuleFor(vm => vm.X)
.LessThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).X) .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber());
RuleFor(vm => vm.X) RuleFor(vm => vm.X)
.GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).X) .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber());
RuleFor(vm => vm.Y) RuleFor(vm => vm.Y)
.LessThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).Y) .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber());
RuleFor(vm => vm.Y) RuleFor(vm => vm.Y)
.GreaterThanOrEqualTo(vm => ((SKPoint) vm.LayerProperty.PropertyDescription.MaxInputValue).Y) .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKPoint); .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber());
} }
} }
} }

View File

@ -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.PropertyInput;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
using FluentValidation; using FluentValidation;
@ -42,18 +44,18 @@ namespace Artemis.UI.PropertyInput
public SKSizePropertyInputViewModelValidator() public SKSizePropertyInputViewModelValidator()
{ {
RuleFor(vm => vm.Width) RuleFor(vm => vm.Width)
.LessThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Width) .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber());
RuleFor(vm => vm.Width) RuleFor(vm => vm.Width)
.GreaterThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Width) .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber());
RuleFor(vm => vm.Height) RuleFor(vm => vm.Height)
.LessThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Height) .LessThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MaxInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); .When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue.IsNumber());
RuleFor(vm => vm.Height) RuleFor(vm => vm.Height)
.GreaterThanOrEqualTo(vm => ((SKSize) vm.LayerProperty.PropertyDescription.MaxInputValue).Height) .GreaterThanOrEqualTo(vm => Convert.ToSingle(vm.LayerProperty.PropertyDescription.MinInputValue))
.When(vm => vm.LayerProperty.PropertyDescription.MaxInputValue is SKSize); .When(vm => vm.LayerProperty.PropertyDescription.MinInputValue.IsNumber());
} }
} }
} }

View File

@ -1,6 +1,7 @@
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.Models.Profile;
using Artemis.Core.Plugins.Abstract; using Artemis.Core.Plugins.Abstract;
using Artemis.Core.Plugins.LayerEffect; using Artemis.Core.Plugins.LayerEffect;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
@ -42,13 +43,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.LayerEffects
private void HandleSelectedLayerEffectChanged(object sender, PropertyChangedEventArgs e) 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) if (e.PropertyName == nameof(SelectedLayerEffectDescriptor) && SelectedLayerEffectDescriptor != null)
{ {
// Jump off the UI thread and let the fancy animation run // Let the fancy animation run
Task.Run(async () => Execute.PostToUIThread(async () =>
{ {
await Task.Delay(500); await Task.Delay(500);
return _layerService.AddLayerEffect(LayerPropertiesViewModel.SelectedLayer, SelectedLayerEffectDescriptor); _layerService.AddLayerEffect(effectElement, SelectedLayerEffectDescriptor);
}); });
} }
} }

View File

@ -37,6 +37,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
CoreService = coreService; CoreService = coreService;
SettingsService = settingsService; SettingsService = settingsService;
EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this);
LayerPropertyGroups = new BindableCollection<LayerPropertyGroupViewModel>(); LayerPropertyGroups = new BindableCollection<LayerPropertyGroupViewModel>();
PropertyChanged += HandlePropertyTreeIndexChanged; PropertyChanged += HandlePropertyTreeIndexChanged;
} }
@ -57,8 +58,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
public int PropertyTreeIndex { get; set; } public int PropertyTreeIndex { get; set; }
public bool PropertyTreeVisible => PropertyTreeIndex == 0; 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<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; } public BindableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; set; }
public TreeViewModel TreeViewModel { get; set; } public TreeViewModel TreeViewModel { get; set; }
@ -67,7 +70,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
PopulateProperties(ProfileEditorService.SelectedProfileElement); if (ProfileEditorService.SelectedProfileElement is PropertiesProfileElement propertiesElement)
PopulateProperties(propertiesElement);
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected; ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged; ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
@ -99,9 +103,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
private void ProfileEditorServiceOnProfileElementSelected(object sender, ProfileElementEventArgs e) private void ProfileEditorServiceOnProfileElementSelected(object sender, ProfileElementEventArgs e)
{ {
PopulateProperties(e.ProfileElement); PopulateProperties(e.ProfileElement as PropertiesProfileElement);
} }
private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e) private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e)
{ {
NotifyOfPropertyChange(() => FormattedCurrentTime); NotifyOfPropertyChange(() => FormattedCurrentTime);
@ -122,51 +127,45 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
return groups; 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) if (SelectedLayer != null)
{
SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
SelectedLayer.LayerEffectsUpdated -= SelectedLayerOnLayerEffectsUpdated;
SelectedLayer = null;
}
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups) foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
layerPropertyGroupViewModel.Dispose(); layerPropertyGroupViewModel.Dispose();
LayerPropertyGroups.Clear(); LayerPropertyGroups.Clear();
_brushPropertyGroup = null; _brushPropertyGroup = null;
if (profileElement is Folder folder) SelectedPropertiesElement = profileElement;
SelectedFolder = folder; if (SelectedPropertiesElement is EffectProfileElement newEffectElement)
else if (profileElement is Layer layer) newEffectElement.LayerEffectsUpdated += SelectedElementOnLayerEffectsUpdated;
// Apply layer properties
if (SelectedLayer != null)
{ {
SelectedLayer = layer;
SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated; SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated;
SelectedLayer.LayerEffectsUpdated += SelectedLayerOnLayerEffectsUpdated;
// Add the built-in root groups of the layer // Add the built-in root groups of the layer
var generalAttribute = Attribute.GetCustomAttribute( var generalAttribute = Attribute.GetCustomAttribute(
layer.GetType().GetProperty(nameof(layer.General)), SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.General)),
typeof(PropertyGroupDescriptionAttribute) typeof(PropertyGroupDescriptionAttribute)
); );
var transformAttribute = Attribute.GetCustomAttribute( var transformAttribute = Attribute.GetCustomAttribute(
layer.GetType().GetProperty(nameof(layer.Transform)), SelectedLayer.GetType().GetProperty(nameof(SelectedLayer.Transform)),
typeof(PropertyGroupDescriptionAttribute) typeof(PropertyGroupDescriptionAttribute)
); );
LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layer.General, (PropertyGroupDescriptionAttribute) generalAttribute)); LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.General, (PropertyGroupDescriptionAttribute) generalAttribute));
LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute)); LayerPropertyGroups.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform, (PropertyGroupDescriptionAttribute) transformAttribute));
} }
else
SelectedLayer = null;
TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups); TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, LayerPropertyGroups);
EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this);
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups); TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, LayerPropertyGroups);
ApplyLayerBrush(); ApplyLayerBrush();
ApplyLayerEffects(); ApplyEffects();
} }
private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e) private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e)
@ -174,9 +173,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
ApplyLayerBrush(); ApplyLayerBrush();
} }
private void SelectedLayerOnLayerEffectsUpdated(object sender, EventArgs e) private void SelectedElementOnLayerEffectsUpdated(object sender, EventArgs e)
{ {
ApplyLayerEffects(); ApplyEffects();
} }
public void ApplyLayerBrush() public void ApplyLayerBrush()
@ -214,18 +213,23 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
TimelineViewModel.UpdateKeyframes(); 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; return;
// Remove VMs of effects no longer applied on the layer // 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); LayerPropertyGroups.RemoveRange(toRemove);
foreach (var layerPropertyGroupViewModel in toRemove) foreach (var layerPropertyGroupViewModel in toRemove)
layerPropertyGroupViewModel.Dispose(); layerPropertyGroupViewModel.Dispose();
foreach (var layerEffect in SelectedLayer.LayerEffects) foreach (var layerEffect in effectElement.LayerEffects)
{ {
if (LayerPropertyGroups.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect)) if (LayerPropertyGroups.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect))
continue; continue;

View File

@ -46,8 +46,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
public override bool IsExpanded public override bool IsExpanded
{ {
get => LayerPropertyGroup.Layer.IsPropertyGroupExpanded(LayerPropertyGroup); get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup);
set => LayerPropertyGroup.Layer.SetPropertyGroupExpanded(LayerPropertyGroup, value); set => LayerPropertyGroup.ProfileElement.SetPropertyGroupExpanded(LayerPropertyGroup, value);
} }
public override bool IsVisible => !LayerPropertyGroup.IsHidden; public override bool IsVisible => !LayerPropertyGroup.IsHidden;

View File

@ -306,7 +306,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
var device = Devices.LastOrDefault(d => PanZoomViewModel.TransformContainingRect(d.DeviceRectangle).Contains(position)); var device = Devices.LastOrDefault(d => PanZoomViewModel.TransformContainingRect(d.DeviceRectangle).Contains(position));
if (device != null) if (device != null)
{ {
_rgbService.UpdateTrigger.Stop(); _rgbService.IsRenderPaused = true;
_mouseDragStatus = MouseDragStatus.Dragging; _mouseDragStatus = MouseDragStatus.Dragging;
// If the device is not selected, deselect others and select only this one (if shift not held) // If the device is not selected, deselect others and select only this one (if shift not held)
if (device.SelectionStatus != SelectionStatus.Selected) if (device.SelectionStatus != SelectionStatus.Selected)
@ -351,7 +351,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
_surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true); _surfaceService.UpdateSurfaceConfiguration(SelectedSurface, true);
_mouseDragStatus = MouseDragStatus.None; _mouseDragStatus = MouseDragStatus.None;
_rgbService.UpdateTrigger.Start(); _rgbService.IsRenderPaused = false;
} }
private void UpdateSelection(Point position) private void UpdateSelection(Point position)

View File

@ -43,11 +43,11 @@ namespace Artemis.Plugins.Devices.Logitech
{ {
try 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) 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"));
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using Artemis.Core.Extensions;
using Artemis.Core.Plugins.LayerBrush.Abstract; using Artemis.Core.Plugins.LayerBrush.Abstract;
using Artemis.Core.Services.Interfaces; using Artemis.Core.Services.Interfaces;
using Artemis.Plugins.LayerBrushes.Noise.Utilities; 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) public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
{ {
var mainColor = Properties.MainColor?.CurrentValue; var mainColor = Properties.MainColor.CurrentValue;
var gradientColor = Properties.GradientColor?.CurrentValue; var secondColor = Properties.SecondaryColor.CurrentValue;
var gradientColor = Properties.GradientColor.CurrentValue;
var scale = Properties.Scale.CurrentValue; var scale = Properties.Scale.CurrentValue;
var opacity = mainColor != null ? (float) Math.Round(mainColor.Value.Alpha / 255.0, 2, MidpointRounding.AwayFromZero) : 0; var hardness = Properties.Hardness.CurrentValue / 100f;
var hardness = 127 + Properties.Hardness.CurrentValue;
// Scale down the render path to avoid computing a value for every pixel // Scale down the render path to avoid computing a value for every pixel
var width = (int) Math.Floor(path.Bounds.Width * _renderScale); 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)) if (double.IsInfinity(evalX) || double.IsNaN(evalX) || double.IsNaN(evalY) || double.IsInfinity(evalY))
continue; continue;
var v = _noise.Evaluate(evalX, evalY, _z); var v = (float) _noise.Evaluate(evalX, evalY, _z) * hardness;
var alpha = (byte) Math.Max(0, Math.Min(255, v * hardness)); var amount = Math.Max(0f, Math.Min(1f, v));
if (Properties.ColorType.BaseValue == ColorMappingType.Simple && mainColor != null) if (Properties.ColorType.BaseValue == ColorMappingType.Simple)
_bitmap.SetPixel(x, y, new SKColor(mainColor.Value.Red, mainColor.Value.Green, mainColor.Value.Blue, (byte) (alpha * opacity))); _bitmap.SetPixel(x, y, mainColor.Interpolate(secondColor, amount));
else if (gradientColor != null && _colorMap.Length == 101) 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); _bitmap.SetPixel(x, y, color);
} }
} }
} }
var bitmapTransform = SKMatrix.Concat( var bitmapTransform = SKMatrix.Concat(
SKMatrix.MakeTranslation(path.Bounds.Left, path.Bounds.Top), SKMatrix.MakeTranslation(path.Bounds.Left, path.Bounds.Top),
SKMatrix.MakeScale(1f / _renderScale, 1f / _renderScale) SKMatrix.MakeScale(1f / _renderScale, 1f / _renderScale)
); );
canvas.ClipPath(path);
if (Properties.ColorType.BaseValue == ColorMappingType.Simple) if (Properties.ColorType.BaseValue == ColorMappingType.Simple)
{ {
using var backgroundShader = SKShader.CreateColor(Properties.SecondaryColor.CurrentValue); paint.Color = Properties.SecondaryColor.CurrentValue;
paint.Shader = backgroundShader;
canvas.DrawRect(path.Bounds, paint);
} }
using var foregroundShader = SKShader.CreateBitmap(_bitmap, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, bitmapTransform); using var foregroundShader = SKShader.CreateBitmap(_bitmap, SKShaderTileMode.Clamp, SKShaderTileMode.Clamp, bitmapTransform);
paint.Shader = foregroundShader; paint.Shader = foregroundShader;
canvas.DrawRect(path.Bounds, paint); canvas.DrawPath(path, paint);
} }
private void GradientColorChanged(object sender, PropertyChangedEventArgs e) private void GradientColorChanged(object sender, PropertyChangedEventArgs e)

View File

@ -24,7 +24,7 @@ namespace Artemis.Plugins.LayerBrushes.Noise
[PropertyDescription(Description = "The scale of the noise", MinInputValue = 0f, InputAffix = "%")] [PropertyDescription(Description = "The scale of the noise", MinInputValue = 0f, InputAffix = "%")]
public SKSizeLayerProperty Scale { get; set; } 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; } public FloatLayerProperty Hardness { get; set; }
[PropertyDescription(Description = "The speed at which the noise moves vertically and horizontally", MinInputValue = -64f, MaxInputValue = 64f)] [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); SecondaryColor.DefaultValue = new SKColor(0, 0, 255);
GradientColor.DefaultValue = ColorGradient.GetUnicornBarf(); GradientColor.DefaultValue = ColorGradient.GetUnicornBarf();
Scale.DefaultValue = new SKSize(100, 100); Scale.DefaultValue = new SKSize(100, 100);
Hardness.DefaultValue = 500f; Hardness.DefaultValue = 100f;
AnimationSpeed.DefaultValue = 25f; AnimationSpeed.DefaultValue = 25f;
} }

View File

@ -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) if (_imageFilter != null)
paint.ImageFilter = SKImageFilter.CreateMerge(paint.ImageFilter, _imageFilter); 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)
{ {
} }

View File

@ -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( paint.ImageFilter = SKImageFilter.CreateDilate(
(int) Properties.DilateRadius.CurrentValue.Width, (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)
{ {
} }
} }

View File

@ -6,7 +6,7 @@ namespace Artemis.Plugins.LayerEffects.Filter
{ {
public class DilateEffectProperties : LayerPropertyGroup 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; } public SKSizeLayerProperty DilateRadius { get; set; }
protected override void PopulateDefaults() protected override void PopulateDefaults()

View File

@ -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( paint.ImageFilter = SKImageFilter.CreateErode(
(int) Properties.ErodeRadius.CurrentValue.Width, (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)
{ {
} }
} }

View File

@ -1,12 +1,13 @@
using Artemis.Core.Models.Profile; using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.LayerProperties.Attributes; using Artemis.Core.Models.Profile.LayerProperties.Attributes;
using Artemis.Core.Models.Profile.LayerProperties.Types; using Artemis.Core.Models.Profile.LayerProperties.Types;
using SkiaSharp;
namespace Artemis.Plugins.LayerEffects.Filter namespace Artemis.Plugins.LayerEffects.Filter
{ {
public class ErodeEffectProperties : LayerPropertyGroup 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; } public SKSizeLayerProperty ErodeRadius { get; set; }
protected override void PopulateDefaults() protected override void PopulateDefaults()

View File

@ -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( paint.ImageFilter = SKImageFilter.CreateDropShadow(
Properties.GlowOffset.CurrentValue.X, 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)
{ {
} }
} }

View File

@ -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[] paint.ImageFilter = SKImageFilter.CreateColorFilter(SKColorFilter.CreateColorMatrix(new[]
{ {
@ -28,7 +28,7 @@ namespace Artemis.Plugins.LayerEffects.Filter
}), paint.ImageFilter); }), 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)
{ {
} }
} }