mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
733 lines
26 KiB
C#
733 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Linq;
|
|
using Artemis.Core.LayerBrushes;
|
|
using Artemis.Core.LayerEffects;
|
|
using Artemis.Storage.Entities.Profile;
|
|
using Artemis.Storage.Entities.Profile.Abstract;
|
|
using SkiaSharp;
|
|
|
|
namespace Artemis.Core
|
|
{
|
|
/// <summary>
|
|
/// Represents a layer in a <see cref="Profile" />
|
|
/// </summary>
|
|
public sealed class Layer : RenderProfileElement
|
|
{
|
|
private LayerGeneralProperties _general;
|
|
private BaseLayerBrush? _layerBrush;
|
|
private LayerShape? _layerShape;
|
|
private List<ArtemisLed> _leds;
|
|
private LayerTransformProperties _transform;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided
|
|
/// <paramref name="parent" />
|
|
/// </summary>
|
|
/// <param name="parent">The parent of the layer</param>
|
|
/// <param name="name">The name of the layer</param>
|
|
public Layer(ProfileElement parent, string name) : base(parent.Profile)
|
|
{
|
|
LayerEntity = new LayerEntity();
|
|
EntityId = Guid.NewGuid();
|
|
|
|
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
|
Profile = Parent.Profile;
|
|
Name = name;
|
|
Enabled = true;
|
|
_general = new LayerGeneralProperties();
|
|
_transform = new LayerTransformProperties();
|
|
|
|
_leds = new List<ArtemisLed>();
|
|
|
|
Initialize();
|
|
Parent.AddChild(this, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the <see cref="Layer" /> class based on the provided layer entity
|
|
/// </summary>
|
|
/// <param name="profile">The profile the layer belongs to</param>
|
|
/// <param name="parent">The parent of the layer</param>
|
|
/// <param name="layerEntity">The entity of the layer</param>
|
|
public Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity) : base(parent.Profile)
|
|
{
|
|
LayerEntity = layerEntity;
|
|
EntityId = layerEntity.Id;
|
|
|
|
Profile = profile;
|
|
Parent = parent;
|
|
_general = new LayerGeneralProperties();
|
|
_transform = new LayerTransformProperties();
|
|
|
|
_leds = new List<ArtemisLed>();
|
|
|
|
Load();
|
|
Initialize();
|
|
}
|
|
|
|
/// <summary>
|
|
/// A collection of all the LEDs this layer is assigned to.
|
|
/// </summary>
|
|
public ReadOnlyCollection<ArtemisLed> Leds => _leds.AsReadOnly();
|
|
|
|
/// <summary>
|
|
/// Defines the shape that is rendered by the <see cref="LayerBrush" />.
|
|
/// </summary>
|
|
public LayerShape? LayerShape
|
|
{
|
|
get => _layerShape;
|
|
set
|
|
{
|
|
SetAndNotify(ref _layerShape, value);
|
|
if (Path != null)
|
|
CalculateRenderProperties();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the general properties of the layer
|
|
/// </summary>
|
|
[PropertyGroupDescription(Name = "General", Description = "A collection of general properties")]
|
|
public LayerGeneralProperties General
|
|
{
|
|
get => _general;
|
|
private set => SetAndNotify(ref _general, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the transform properties of the layer
|
|
/// </summary>
|
|
[PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties")]
|
|
public LayerTransformProperties Transform
|
|
{
|
|
get => _transform;
|
|
private set => SetAndNotify(ref _transform, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The brush that will fill the <see cref="LayerShape" />.
|
|
/// </summary>
|
|
public BaseLayerBrush? LayerBrush
|
|
{
|
|
get => _layerBrush;
|
|
internal set => SetAndNotify(ref _layerBrush, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the layer entity this layer uses for persistent storage
|
|
/// </summary>
|
|
public LayerEntity LayerEntity { get; internal set; }
|
|
|
|
internal override RenderElementEntity RenderElementEntity => LayerEntity;
|
|
|
|
/// <inheritdoc />
|
|
public override List<ILayerProperty> GetAllLayerProperties()
|
|
{
|
|
List<ILayerProperty> result = new();
|
|
result.AddRange(General.GetAllLayerProperties());
|
|
result.AddRange(Transform.GetAllLayerProperties());
|
|
if (LayerBrush?.BaseProperties != null)
|
|
result.AddRange(LayerBrush.BaseProperties.GetAllLayerProperties());
|
|
foreach (BaseLayerEffect layerEffect in LayerEffects)
|
|
if (layerEffect.BaseProperties != null)
|
|
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override string ToString()
|
|
{
|
|
return $"[Layer] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
|
|
}
|
|
|
|
#region IDisposable
|
|
|
|
/// <inheritdoc />
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
Disposed = true;
|
|
|
|
// Brush first in case it depends on any of the other disposables during it's own disposal
|
|
_layerBrush?.Dispose();
|
|
_general.Dispose();
|
|
_transform.Dispose();
|
|
Renderer.Dispose();
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void Initialize()
|
|
{
|
|
LayerBrushStore.LayerBrushAdded += LayerBrushStoreOnLayerBrushAdded;
|
|
LayerBrushStore.LayerBrushRemoved += LayerBrushStoreOnLayerBrushRemoved;
|
|
|
|
// Layers have two hardcoded property groups, instantiate them
|
|
Attribute generalAttribute = Attribute.GetCustomAttribute(
|
|
GetType().GetProperty(nameof(General))!,
|
|
typeof(PropertyGroupDescriptionAttribute)
|
|
)!;
|
|
Attribute transformAttribute = Attribute.GetCustomAttribute(
|
|
GetType().GetProperty(nameof(Transform))!,
|
|
typeof(PropertyGroupDescriptionAttribute)
|
|
)!;
|
|
General.GroupDescription = (PropertyGroupDescriptionAttribute) generalAttribute;
|
|
General.Initialize(this, "General.", Constants.CorePluginFeature);
|
|
Transform.GroupDescription = (PropertyGroupDescriptionAttribute) transformAttribute;
|
|
Transform.Initialize(this, "Transform.", Constants.CorePluginFeature);
|
|
|
|
General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet;
|
|
ApplyShapeType();
|
|
ActivateLayerBrush();
|
|
}
|
|
|
|
#region Storage
|
|
|
|
internal override void Load()
|
|
{
|
|
EntityId = LayerEntity.Id;
|
|
Name = LayerEntity.Name;
|
|
Enabled = LayerEntity.Enabled;
|
|
Order = LayerEntity.Order;
|
|
|
|
ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
|
|
LoadRenderElement();
|
|
}
|
|
|
|
internal override void Save()
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
// Properties
|
|
LayerEntity.Id = EntityId;
|
|
LayerEntity.ParentId = Parent?.EntityId ?? new Guid();
|
|
LayerEntity.Order = Order;
|
|
LayerEntity.Enabled = Enabled;
|
|
LayerEntity.Name = Name;
|
|
LayerEntity.ProfileId = Profile.EntityId;
|
|
LayerEntity.ExpandedPropertyGroups.Clear();
|
|
LayerEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
|
|
|
|
General.ApplyToEntity();
|
|
Transform.ApplyToEntity();
|
|
LayerBrush?.BaseProperties?.ApplyToEntity();
|
|
|
|
// LEDs
|
|
LayerEntity.Leds.Clear();
|
|
foreach (ArtemisLed artemisLed in Leds)
|
|
{
|
|
LedEntity ledEntity = new()
|
|
{
|
|
DeviceIdentifier = artemisLed.Device.RgbDevice.GetDeviceIdentifier(),
|
|
LedName = artemisLed.RgbLed.Id.ToString()
|
|
};
|
|
LayerEntity.Leds.Add(ledEntity);
|
|
}
|
|
|
|
SaveRenderElement();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Shape management
|
|
|
|
private void ShapeTypeOnCurrentValueSet(object? sender, EventArgs e)
|
|
{
|
|
ApplyShapeType();
|
|
}
|
|
|
|
private void ApplyShapeType()
|
|
{
|
|
LayerShape = General.ShapeType.CurrentValue switch
|
|
{
|
|
LayerShapeType.Ellipse => new EllipseShape(this),
|
|
LayerShapeType.Rectangle => new RectangleShape(this),
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Rendering
|
|
|
|
/// <inheritdoc />
|
|
public override void Update(double deltaTime)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
if (!Enabled)
|
|
return;
|
|
|
|
UpdateDisplayCondition();
|
|
UpdateTimeline(deltaTime);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Reset()
|
|
{
|
|
DisplayConditionMet = false;
|
|
Timeline.JumpToStart();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Render(SKCanvas canvas)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
// Ensure the layer is ready
|
|
if (!Enabled || Path == null || LayerShape?.Path == null || !General.PropertiesInitialized || !Transform.PropertiesInitialized)
|
|
return;
|
|
// Ensure the brush is ready
|
|
if (LayerBrush?.BaseProperties?.PropertiesInitialized == false || LayerBrush?.BrushType != LayerBrushType.Regular)
|
|
return;
|
|
|
|
lock (Timeline)
|
|
{
|
|
RenderTimeline(Timeline, canvas);
|
|
foreach (Timeline extraTimeline in Timeline.ExtraTimelines)
|
|
RenderTimeline(extraTimeline, canvas);
|
|
Timeline.ClearDelta();
|
|
}
|
|
}
|
|
|
|
private void ApplyTimeline(Timeline timeline)
|
|
{
|
|
General.Update(timeline);
|
|
Transform.Update(timeline);
|
|
if (LayerBrush != null)
|
|
{
|
|
LayerBrush.BaseProperties?.Update(timeline);
|
|
LayerBrush.Update(timeline.Delta.TotalSeconds);
|
|
}
|
|
|
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
{
|
|
baseLayerEffect.BaseProperties?.Update(timeline);
|
|
baseLayerEffect.Update(timeline.Delta.TotalSeconds);
|
|
}
|
|
}
|
|
|
|
private void RenderTimeline(Timeline timeline, SKCanvas canvas)
|
|
{
|
|
if (Path == null || LayerBrush == null)
|
|
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
|
|
|
if (timeline.IsFinished)
|
|
return;
|
|
|
|
ApplyTimeline(timeline);
|
|
|
|
try
|
|
{
|
|
canvas.Save();
|
|
Renderer.Open(Path, Parent as Folder);
|
|
|
|
if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null)
|
|
throw new ArtemisCoreException("Failed to open layer render context");
|
|
|
|
// Apply blend mode and color
|
|
Renderer.Paint.BlendMode = General.BlendMode.CurrentValue;
|
|
Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f));
|
|
|
|
using SKPath renderPath = new();
|
|
if (General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
|
|
renderPath.AddRect(Renderer.Path.Bounds);
|
|
else
|
|
renderPath.AddOval(Renderer.Path.Bounds);
|
|
|
|
if (General.TransformMode.CurrentValue == LayerTransformMode.Normal)
|
|
{
|
|
// Apply transformation except rotation to the render path
|
|
if (LayerBrush.SupportsTransformation)
|
|
{
|
|
SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, false);
|
|
renderPath.Transform(renderPathMatrix);
|
|
}
|
|
|
|
// Apply rotation to the canvas
|
|
if (LayerBrush.SupportsTransformation)
|
|
{
|
|
SKMatrix rotationMatrix = GetTransformMatrix(true, false, false, true);
|
|
Renderer.Canvas.SetMatrix(Renderer.Canvas.TotalMatrix.PreConcat(rotationMatrix));
|
|
}
|
|
|
|
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
|
|
Renderer.Canvas.ClipPath(renderPath);
|
|
DelegateRendering(renderPath.Bounds);
|
|
}
|
|
else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip)
|
|
{
|
|
SKMatrix renderPathMatrix = GetTransformMatrix(true, true, true, true);
|
|
renderPath.Transform(renderPathMatrix);
|
|
|
|
// If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
|
|
Renderer.Canvas.ClipPath(renderPath);
|
|
DelegateRendering(Renderer.Path.Bounds);
|
|
}
|
|
|
|
canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint);
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
canvas.Restore();
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
Renderer.Close();
|
|
}
|
|
}
|
|
|
|
private void DelegateRendering(SKRect bounds)
|
|
{
|
|
if (LayerBrush == null)
|
|
throw new ArtemisCoreException("The layer is not yet ready for rendering");
|
|
if (Renderer.Canvas == null || Renderer.Paint == null)
|
|
throw new ArtemisCoreException("Failed to open layer render context");
|
|
|
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
baseLayerEffect.PreProcess(Renderer.Canvas, bounds, Renderer.Paint);
|
|
|
|
LayerBrush.InternalRender(Renderer.Canvas, bounds, Renderer.Paint);
|
|
|
|
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
|
|
baseLayerEffect.PostProcess(Renderer.Canvas, bounds, Renderer.Paint);
|
|
}
|
|
|
|
internal void CalculateRenderProperties()
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
if (!Leds.Any())
|
|
{
|
|
Path = new SKPath();
|
|
}
|
|
else
|
|
{
|
|
SKPath path = new() {FillType = SKPathFillType.Winding};
|
|
foreach (ArtemisLed artemisLed in Leds)
|
|
path.AddRect(artemisLed.AbsoluteRectangle);
|
|
|
|
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();
|
|
}
|
|
|
|
internal SKPoint GetLayerAnchorPosition(SKPath layerPath, bool applyTranslation, bool zeroBased)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
SKPoint positionProperty = Transform.Position.CurrentValue;
|
|
|
|
// Start at the center of the shape
|
|
SKPoint position = zeroBased
|
|
? new SKPoint(layerPath.Bounds.MidX - layerPath.Bounds.Left, layerPath.Bounds.MidY - layerPath.Bounds.Top)
|
|
: new SKPoint(layerPath.Bounds.MidX, layerPath.Bounds.MidY);
|
|
|
|
// Apply translation
|
|
if (applyTranslation)
|
|
{
|
|
position.X += positionProperty.X * layerPath.Bounds.Width;
|
|
position.Y += positionProperty.Y * layerPath.Bounds.Height;
|
|
}
|
|
|
|
return position;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a transformation matrix that applies the current transformation settings
|
|
/// </summary>
|
|
/// <param name="zeroBased">
|
|
/// If true, treats the layer as if it is located at 0,0 instead of its actual position on the
|
|
/// surface
|
|
/// </param>
|
|
/// <param name="includeTranslation">Whether translation should be included</param>
|
|
/// <param name="includeScale">Whether the scale should be included</param>
|
|
/// <param name="includeRotation">Whether the rotation should be included</param>
|
|
/// <returns>The transformation matrix containing the current transformation settings</returns>
|
|
public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
if (Path == null)
|
|
return SKMatrix.Empty;
|
|
|
|
SKSize sizeProperty = Transform.Scale.CurrentValue;
|
|
float rotationProperty = Transform.Rotation.CurrentValue;
|
|
|
|
SKPoint anchorPosition = GetLayerAnchorPosition(Path, true, zeroBased);
|
|
SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue;
|
|
|
|
// Translation originates from the unscaled center of the shape and is tied to the anchor
|
|
float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width;
|
|
float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height;
|
|
|
|
SKMatrix transform = SKMatrix.Empty;
|
|
|
|
if (includeTranslation)
|
|
// transform is always SKMatrix.Empty here...
|
|
transform = SKMatrix.CreateTranslation(x, y);
|
|
|
|
if (includeScale)
|
|
{
|
|
if (transform == SKMatrix.Empty)
|
|
transform = SKMatrix.CreateScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y);
|
|
else
|
|
transform = transform.PostConcat(SKMatrix.CreateScale(sizeProperty.Width / 100f, sizeProperty.Height / 100f, anchorPosition.X, anchorPosition.Y));
|
|
}
|
|
|
|
if (includeRotation)
|
|
{
|
|
if (transform == SKMatrix.Empty)
|
|
transform = SKMatrix.CreateRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y);
|
|
else
|
|
transform = transform.PostConcat(SKMatrix.CreateRotationDegrees(rotationProperty, anchorPosition.X, anchorPosition.Y));
|
|
}
|
|
|
|
return transform;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region LED management
|
|
|
|
/// <summary>
|
|
/// Adds a new <see cref="ArtemisLed" /> to the layer and updates the render properties.
|
|
/// </summary>
|
|
/// <param name="led">The LED to add</param>
|
|
public void AddLed(ArtemisLed led)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
_leds.Add(led);
|
|
CalculateRenderProperties();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a collection of new <see cref="ArtemisLed" />s to the layer and updates the render properties.
|
|
/// </summary>
|
|
/// <param name="leds">The LEDs to add</param>
|
|
public void AddLeds(IEnumerable<ArtemisLed> leds)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
_leds.AddRange(leds);
|
|
CalculateRenderProperties();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a <see cref="ArtemisLed" /> from the layer and updates the render properties.
|
|
/// </summary>
|
|
/// <param name="led">The LED to remove</param>
|
|
public void RemoveLed(ArtemisLed led)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
_leds.Remove(led);
|
|
CalculateRenderProperties();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all <see cref="ArtemisLed" />s from the layer and updates the render properties.
|
|
/// </summary>
|
|
public void ClearLeds()
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
_leds.Clear();
|
|
CalculateRenderProperties();
|
|
}
|
|
|
|
internal void PopulateLeds(ArtemisSurface surface)
|
|
{
|
|
if (Disposed)
|
|
throw new ObjectDisposedException("Layer");
|
|
|
|
List<ArtemisLed> leds = new();
|
|
|
|
// Get the surface LEDs for this layer
|
|
List<ArtemisLed> availableLeds = surface.Devices.SelectMany(d => d.Leds).ToList();
|
|
foreach (LedEntity ledEntity in LayerEntity.Leds)
|
|
{
|
|
ArtemisLed? match = availableLeds.FirstOrDefault(a => a.Device.RgbDevice.GetDeviceIdentifier() == ledEntity.DeviceIdentifier &&
|
|
a.RgbLed.Id.ToString() == ledEntity.LedName);
|
|
if (match != null)
|
|
leds.Add(match);
|
|
}
|
|
|
|
_leds = leds;
|
|
CalculateRenderProperties();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Brush management
|
|
|
|
/// <summary>
|
|
/// Changes the current layer brush to the brush described in the provided <paramref name="descriptor" />
|
|
/// </summary>
|
|
public void ChangeLayerBrush(LayerBrushDescriptor descriptor)
|
|
{
|
|
if (descriptor == null)
|
|
throw new ArgumentNullException(nameof(descriptor));
|
|
|
|
if (LayerBrush != null)
|
|
{
|
|
BaseLayerBrush brush = LayerBrush;
|
|
LayerBrush = null;
|
|
brush.Dispose();
|
|
}
|
|
|
|
// Ensure the brush reference matches the brush
|
|
LayerBrushReference? current = General.BrushReference.BaseValue;
|
|
if (!descriptor.MatchesLayerBrushReference(current))
|
|
General.BrushReference.BaseValue = new LayerBrushReference(descriptor);
|
|
|
|
ActivateLayerBrush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the current layer brush from the layer
|
|
/// </summary>
|
|
public void RemoveLayerBrush()
|
|
{
|
|
if (LayerBrush == null)
|
|
return;
|
|
|
|
BaseLayerBrush brush = LayerBrush;
|
|
DeactivateLayerBrush();
|
|
LayerEntity.PropertyEntities.RemoveAll(p => p.FeatureId == brush.ProviderId && p.Path.StartsWith("LayerBrush."));
|
|
}
|
|
|
|
internal void ActivateLayerBrush()
|
|
{
|
|
LayerBrushReference? current = General.BrushReference.CurrentValue;
|
|
if (current == null)
|
|
return;
|
|
|
|
LayerBrushDescriptor? descriptor = current.LayerBrushProviderId != null && current.BrushType != null
|
|
? LayerBrushStore.Get(current.LayerBrushProviderId, current.BrushType)?.LayerBrushDescriptor
|
|
: null;
|
|
descriptor?.CreateInstance(this);
|
|
|
|
OnLayerBrushUpdated();
|
|
}
|
|
|
|
internal void DeactivateLayerBrush()
|
|
{
|
|
if (LayerBrush == null)
|
|
return;
|
|
|
|
BaseLayerBrush brush = LayerBrush;
|
|
LayerBrush = null;
|
|
brush.Dispose();
|
|
|
|
OnLayerBrushUpdated();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event handlers
|
|
|
|
private void LayerBrushStoreOnLayerBrushRemoved(object? sender, LayerBrushStoreEvent e)
|
|
{
|
|
if (LayerBrush?.Descriptor == e.Registration.LayerBrushDescriptor)
|
|
DeactivateLayerBrush();
|
|
}
|
|
|
|
private void LayerBrushStoreOnLayerBrushAdded(object? sender, LayerBrushStoreEvent e)
|
|
{
|
|
if (LayerBrush != null || !General.PropertiesInitialized)
|
|
return;
|
|
|
|
LayerBrushReference? current = General.BrushReference.CurrentValue;
|
|
if (e.Registration.PluginFeature.Id == current?.LayerBrushProviderId &&
|
|
e.Registration.LayerBrushDescriptor.LayerBrushType.Name == current.BrushType)
|
|
ActivateLayerBrush();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
/// <summary>
|
|
/// Occurs when a property affecting the rendering properties of this layer has been updated
|
|
/// </summary>
|
|
public event EventHandler? RenderPropertiesUpdated;
|
|
|
|
/// <summary>
|
|
/// Occurs when the layer brush of this layer has been updated
|
|
/// </summary>
|
|
public event EventHandler? LayerBrushUpdated;
|
|
|
|
private void OnRenderPropertiesUpdated()
|
|
{
|
|
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
internal void OnLayerBrushUpdated()
|
|
{
|
|
LayerBrushUpdated?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a type of layer shape
|
|
/// </summary>
|
|
public enum LayerShapeType
|
|
{
|
|
/// <summary>
|
|
/// A circular layer shape
|
|
/// </summary>
|
|
Ellipse,
|
|
|
|
/// <summary>
|
|
/// A rectangular layer shape
|
|
/// </summary>
|
|
Rectangle
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a layer transform mode
|
|
/// </summary>
|
|
public enum LayerTransformMode
|
|
{
|
|
/// <summary>
|
|
/// Normal transformation
|
|
/// </summary>
|
|
Normal,
|
|
|
|
/// <summary>
|
|
/// Transforms only a clip
|
|
/// </summary>
|
|
Clip
|
|
}
|
|
} |