mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Conditions - Refactor layer properties WIP (needed for consistency..)
This commit is contained in:
parent
302ba10caa
commit
d9bba8cb54
@ -22,6 +22,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.1.6" />
|
||||
<PackageReference Include="Castle.Core" Version="4.4.0" />
|
||||
<PackageReference Include="FastMember" Version="1.5.0" />
|
||||
<PackageReference Include="HidSharp" Version="2.1.0" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.7" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.2.0" />
|
||||
|
||||
@ -5,11 +5,11 @@ namespace Artemis.Core.Events
|
||||
{
|
||||
public class LayerPropertyEventArgs : EventArgs
|
||||
{
|
||||
public LayerPropertyEventArgs(BaseLayerProperty layerProperty)
|
||||
public LayerPropertyEventArgs(LayerProperty layerProperty)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
}
|
||||
|
||||
public BaseLayerProperty LayerProperty { get; }
|
||||
public LayerProperty LayerProperty { get; }
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
/// <summary>
|
||||
/// The layer property this keyframe engine applies to.
|
||||
/// </summary>
|
||||
public BaseLayerProperty LayerProperty { get; private set; }
|
||||
public LayerProperty LayerProperty { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total progress
|
||||
@ -55,7 +55,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
/// Associates the keyframe engine with the provided layer property.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty"></param>
|
||||
public void Initialize(BaseLayerProperty layerProperty)
|
||||
public void Initialize(LayerProperty layerProperty)
|
||||
{
|
||||
if (Initialized)
|
||||
throw new ArtemisCoreException("Cannot initialize the same keyframe engine twice");
|
||||
|
||||
@ -3,22 +3,28 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Extensions;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Profile.LayerShapes;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a layer on a profile. To create new layers use the <see cref="LayerService" /> by injecting
|
||||
/// <see cref="ILayerService" /> into your code
|
||||
/// </summary>
|
||||
public sealed class Layer : ProfileElement
|
||||
{
|
||||
private LayerShape _layerShape;
|
||||
private List<ArtemisLed> _leds;
|
||||
private SKPath _path;
|
||||
|
||||
public Layer(Profile profile, ProfileElement parent, string name)
|
||||
internal Layer(Profile profile, ProfileElement parent, string name)
|
||||
{
|
||||
LayerEntity = new LayerEntity();
|
||||
EntityId = Guid.NewGuid();
|
||||
@ -26,12 +32,10 @@ namespace Artemis.Core.Models.Profile
|
||||
Profile = profile;
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
Properties = new LayerPropertyCollection(this);
|
||||
General = new LayerGeneralProperties();
|
||||
Transform = new LayerTransformProperties();
|
||||
|
||||
_leds = new List<ArtemisLed>();
|
||||
|
||||
ApplyShapeType();
|
||||
Properties.ShapeType.ValueChanged += (sender, args) => ApplyShapeType();
|
||||
}
|
||||
|
||||
internal Layer(Profile profile, ProfileElement parent, LayerEntity layerEntity)
|
||||
@ -43,12 +47,10 @@ namespace Artemis.Core.Models.Profile
|
||||
Parent = parent;
|
||||
Name = layerEntity.Name;
|
||||
Order = layerEntity.Order;
|
||||
Properties = new LayerPropertyCollection(this);
|
||||
General = new LayerGeneralProperties();
|
||||
Transform = new LayerTransformProperties();
|
||||
|
||||
_leds = new List<ArtemisLed>();
|
||||
|
||||
ApplyShapeType();
|
||||
Properties.ShapeType.ValueChanged += (sender, args) => ApplyShapeType();
|
||||
}
|
||||
|
||||
internal LayerEntity LayerEntity { get; set; }
|
||||
@ -93,15 +95,16 @@ namespace Artemis.Core.Models.Profile
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The properties of this layer
|
||||
/// </summary>
|
||||
public LayerPropertyCollection Properties { get; set; }
|
||||
[PropertyGroupDescription(Name = "General", Description = "A collection of general properties", ExpandByDefault = true)]
|
||||
public LayerGeneralProperties General { get; set; }
|
||||
|
||||
[PropertyGroupDescription(Name = "Transform", Description = "A collection of transformation properties", ExpandByDefault = true)]
|
||||
public LayerTransformProperties Transform { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The brush that will fill the <see cref="LayerShape" />.
|
||||
/// </summary>
|
||||
public LayerBrush LayerBrush { get; internal set; }
|
||||
public ILayerBrush LayerBrush { get; internal set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@ -158,6 +161,19 @@ namespace Artemis.Core.Models.Profile
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
internal void InitializeProperties(ILayerService layerService)
|
||||
{
|
||||
PropertiesInitialized = true;
|
||||
|
||||
ApplyShapeType();
|
||||
}
|
||||
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rendering
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
35
src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs
Normal file
35
src/Artemis.Core/Models/Profile/LayerGeneralProperties.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Types;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
public class LayerGeneralProperties : LayerPropertyGroup
|
||||
{
|
||||
[PropertyDescription(Name = "Shape type", Description = "The type of shape to draw in this layer")]
|
||||
public EnumLayerProperty<LayerShapeType> ShapeType { get; set; }
|
||||
|
||||
[PropertyDescription(Name = "Fill type", Description = "How to make the shape adjust to scale changes")]
|
||||
public EnumLayerProperty<LayerFillType> FillType { get; set; }
|
||||
|
||||
[PropertyDescription(Name = "Blend mode", Description = "How to blend this layer into the resulting image")]
|
||||
public EnumLayerProperty<SKBlendMode> BlendMode { get; set; }
|
||||
|
||||
[PropertyDescription(Name = "Brush type", Description = "The type of brush to use for this layer")]
|
||||
public LayerBrushReferenceLayerProperty BrushReference { get; set; }
|
||||
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
// Populate defaults
|
||||
if (!ShapeType.IsLoadedFromStorage)
|
||||
ShapeType.BaseValue = LayerShapeType.Rectangle;
|
||||
if (!FillType.IsLoadedFromStorage)
|
||||
FillType.BaseValue = LayerFillType.Stretch;
|
||||
if (!BlendMode.IsLoadedFromStorage)
|
||||
BlendMode.BaseValue = SKBlendMode.SrcOver;
|
||||
|
||||
// TODO: SpoinkyNL 28-4-2020: Select preferred default brush type with a fallback to the first available
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Utilities;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Abstract
|
||||
{
|
||||
public abstract class LayerProperty<T>
|
||||
{
|
||||
private List<LayerPropertyKeyFrame<T>> _keyframes;
|
||||
|
||||
protected LayerProperty()
|
||||
{
|
||||
_keyframes = new List<LayerPropertyKeyFrame<T>>();
|
||||
}
|
||||
|
||||
public T BaseValue { get; set; }
|
||||
public T CurrentValue { get; set; }
|
||||
public IReadOnlyList<LayerPropertyKeyFrame<T>> Keyframes => _keyframes.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// The total progress on the timeline
|
||||
/// </summary>
|
||||
public TimeSpan TimelineProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current keyframe in the timeline
|
||||
/// </summary>
|
||||
public LayerPropertyKeyFrame<T> CurrentKeyframe { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next keyframe in the timeline
|
||||
/// </summary>
|
||||
public LayerPropertyKeyFrame<T> NextKeyframe { get; protected set; }
|
||||
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
float keyframeProgress;
|
||||
float keyframeProgressEased;
|
||||
|
||||
TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime));
|
||||
// The current keyframe is the last keyframe before the current time
|
||||
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
// The next keyframe is the first keyframe that's after the current time
|
||||
NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress);
|
||||
|
||||
if (CurrentKeyframe == null)
|
||||
{
|
||||
keyframeProgress = 0;
|
||||
keyframeProgressEased = 0;
|
||||
}
|
||||
else if (NextKeyframe == null)
|
||||
{
|
||||
keyframeProgress = 1;
|
||||
keyframeProgressEased = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||
keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||
keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
|
||||
}
|
||||
|
||||
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
||||
}
|
||||
|
||||
protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased);
|
||||
|
||||
public void OverrideProgress(TimeSpan progress)
|
||||
{
|
||||
TimelineProgress = TimeSpan.Zero;
|
||||
Update(progress.TotalSeconds);
|
||||
}
|
||||
|
||||
internal void SortKeyframes()
|
||||
{
|
||||
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Attributes
|
||||
{
|
||||
public class PropertyDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property, shown in the UI
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input prefix to show before input elements in the UI
|
||||
/// </summary>
|
||||
public string InputPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Input affix to show behind input elements in the UI
|
||||
/// </summary>
|
||||
public string InputAffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The input drag step size, used in the UI
|
||||
/// </summary>
|
||||
public float InputStepSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object MinInputValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum input value, only enforced in the UI
|
||||
/// </summary>
|
||||
public object MaxInputValue { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Attributes
|
||||
{
|
||||
public class PropertyGroupDescriptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The user-friendly name for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user-friendly description for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to expand this property by default, this is useful for important parent properties.
|
||||
/// </summary>
|
||||
public bool ExpandByDefault { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Artemis.Core.Utilities;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
public class BaseKeyframe
|
||||
{
|
||||
private TimeSpan _position;
|
||||
|
||||
protected BaseKeyframe(Layer layer, BaseLayerProperty property)
|
||||
{
|
||||
Layer = layer;
|
||||
BaseProperty = property;
|
||||
}
|
||||
|
||||
public Layer Layer { get; set; }
|
||||
|
||||
public TimeSpan Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if (value == _position) return;
|
||||
_position = value;
|
||||
BaseProperty.SortKeyframes();
|
||||
}
|
||||
}
|
||||
|
||||
protected BaseLayerProperty BaseProperty { get; }
|
||||
public object BaseValue { get; internal set; }
|
||||
public Easings.Functions EasingFunction { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,375 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.KeyframeEngines;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
public abstract class BaseLayerProperty : PropertyChangedBase
|
||||
{
|
||||
private object _baseValue;
|
||||
private bool _isHidden;
|
||||
|
||||
protected BaseLayerProperty(Layer layer, PluginInfo pluginInfo, BaseLayerProperty parent, string id, string name, string description, Type type)
|
||||
{
|
||||
Layer = layer;
|
||||
PluginInfo = pluginInfo;
|
||||
Parent = parent;
|
||||
Id = id;
|
||||
Name = name;
|
||||
Description = description;
|
||||
Type = type;
|
||||
CanUseKeyframes = true;
|
||||
InputStepSize = 1;
|
||||
|
||||
// This can only be null if accessed internally, all public ways of creating enforce a plugin info
|
||||
if (PluginInfo == null)
|
||||
PluginInfo = Constants.CorePluginInfo;
|
||||
|
||||
Children = new List<BaseLayerProperty>();
|
||||
BaseKeyframes = new List<BaseKeyframe>();
|
||||
|
||||
|
||||
parent?.Children.Add(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer this property applies to
|
||||
/// </summary>
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Info of the plugin associated with this property
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent property of this property.
|
||||
/// </summary>
|
||||
public BaseLayerProperty Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the child properties of this property.
|
||||
/// <remarks>If the layer has children it cannot contain a value or keyframes.</remarks>
|
||||
/// </summary>
|
||||
public List<BaseLayerProperty> Children { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a unique identifier for this property, a layer may not contain two properties with the same ID.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user-friendly name for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user-friendly description for this property, shown in the UI.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to expand this property by default, this is useful for important parent properties.
|
||||
/// </summary>
|
||||
public bool ExpandByDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the an optional input prefix to show before input elements in the UI.
|
||||
/// </summary>
|
||||
public string InputPrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional input affix to show behind input elements in the UI.
|
||||
/// </summary>
|
||||
public string InputAffix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional maximum input value, only enforced in the UI.
|
||||
/// </summary>
|
||||
public object MaxInputValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the input drag step size, used in the UI.
|
||||
/// </summary>
|
||||
public float InputStepSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional minimum input value, only enforced in the UI.
|
||||
/// </summary>
|
||||
public object MinInputValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this property can use keyframes, True by default.
|
||||
/// </summary>
|
||||
public bool CanUseKeyframes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this property is using keyframes.
|
||||
/// </summary>
|
||||
public bool IsUsingKeyframes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of value this layer property contains.
|
||||
/// </summary>
|
||||
public Type Type { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this property is hidden in the UI.
|
||||
/// </summary>
|
||||
public bool IsHidden
|
||||
{
|
||||
get => _isHidden;
|
||||
set
|
||||
{
|
||||
_isHidden = value;
|
||||
OnVisibilityChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of keyframes defining different values of the property in time, this list contains the untyped
|
||||
/// <see cref="BaseKeyframe" />.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<BaseKeyframe> UntypedKeyframes => BaseKeyframes.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keyframe engine instance of this property
|
||||
/// </summary>
|
||||
public KeyframeEngine KeyframeEngine { get; internal set; }
|
||||
|
||||
protected List<BaseKeyframe> BaseKeyframes { get; set; }
|
||||
|
||||
public object BaseValue
|
||||
{
|
||||
get => _baseValue;
|
||||
internal set
|
||||
{
|
||||
if (value != null && value.GetType() != Type)
|
||||
throw new ArtemisCoreException($"Cannot set value of type {value.GetType()} on property {this}, expected type is {Type}.");
|
||||
if (!Equals(_baseValue, value))
|
||||
{
|
||||
_baseValue = value;
|
||||
OnValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new keyframe for this base property without knowing the type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public BaseKeyframe CreateNewKeyframe(TimeSpan position, object value)
|
||||
{
|
||||
// Create a strongly typed keyframe or else it cannot be cast later on
|
||||
var keyframeType = typeof(Keyframe<>);
|
||||
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
|
||||
keyframe.Position = position;
|
||||
keyframe.BaseValue = value;
|
||||
BaseKeyframes.Add(keyframe);
|
||||
SortKeyframes();
|
||||
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keyframes from the property and sets the base value to the current value.
|
||||
/// </summary>
|
||||
public void ClearKeyframes()
|
||||
{
|
||||
if (KeyframeEngine != null)
|
||||
BaseValue = KeyframeEngine.GetCurrentValue();
|
||||
|
||||
BaseKeyframes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value using the regular value or if present, keyframes
|
||||
/// </summary>
|
||||
public object GetCurrentValue()
|
||||
{
|
||||
if (KeyframeEngine == null || !UntypedKeyframes.Any())
|
||||
return BaseValue;
|
||||
|
||||
return KeyframeEngine.GetCurrentValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value using the regular value or keyframes.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <param name="time">
|
||||
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
|
||||
/// or existing keyframe.
|
||||
/// </param>
|
||||
public void SetCurrentValue(object value, TimeSpan? time)
|
||||
{
|
||||
if (value != null && value.GetType() != Type)
|
||||
throw new ArtemisCoreException($"Cannot set value of type {value.GetType()} on property {this}, expected type is {Type}.");
|
||||
|
||||
if (time == null || !CanUseKeyframes || !IsUsingKeyframes)
|
||||
BaseValue = value;
|
||||
else
|
||||
{
|
||||
// If on a keyframe, update the keyframe
|
||||
var currentKeyframe = UntypedKeyframes.FirstOrDefault(k => k.Position == time.Value);
|
||||
// Create a new keyframe if none found
|
||||
if (currentKeyframe == null)
|
||||
currentKeyframe = CreateNewKeyframe(time.Value, value);
|
||||
|
||||
currentKeyframe.BaseValue = value;
|
||||
}
|
||||
|
||||
OnValueChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the property.
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to remove</param>
|
||||
public void AddKeyframe(BaseKeyframe keyframe)
|
||||
{
|
||||
BaseKeyframes.Add(keyframe);
|
||||
SortKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a keyframe from the property.
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to remove</param>
|
||||
public void RemoveKeyframe(BaseKeyframe keyframe)
|
||||
{
|
||||
BaseKeyframes.Remove(keyframe);
|
||||
SortKeyframes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the flattened index of this property on the layer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetFlattenedIndex()
|
||||
{
|
||||
if (Parent == null)
|
||||
return Layer.Properties.ToList().IndexOf(this);
|
||||
|
||||
// Create a flattened list of all properties in their order as defined by the parent/child hierarchy
|
||||
var properties = new List<BaseLayerProperty>();
|
||||
// Iterate root properties (those with children)
|
||||
foreach (var baseLayerProperty in Layer.Properties)
|
||||
{
|
||||
// First add self, then add all children
|
||||
if (baseLayerProperty.Children.Any())
|
||||
{
|
||||
properties.Add(baseLayerProperty);
|
||||
properties.AddRange(baseLayerProperty.GetAllChildren());
|
||||
}
|
||||
}
|
||||
|
||||
return properties.IndexOf(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}";
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
{
|
||||
var propertyEntity = Layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == Id);
|
||||
if (propertyEntity == null)
|
||||
{
|
||||
propertyEntity = new PropertyEntity {Id = Id};
|
||||
Layer.LayerEntity.PropertyEntities.Add(propertyEntity);
|
||||
}
|
||||
|
||||
propertyEntity.ValueType = Type.Name;
|
||||
propertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
|
||||
propertyEntity.IsUsingKeyframes = IsUsingKeyframes;
|
||||
|
||||
propertyEntity.KeyframeEntities.Clear();
|
||||
foreach (var baseKeyframe in BaseKeyframes)
|
||||
{
|
||||
propertyEntity.KeyframeEntities.Add(new KeyframeEntity
|
||||
{
|
||||
Position = baseKeyframe.Position,
|
||||
Value = JsonConvert.SerializeObject(baseKeyframe.BaseValue),
|
||||
EasingFunction = (int) baseKeyframe.EasingFunction
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplyToProperty(PropertyEntity propertyEntity)
|
||||
{
|
||||
BaseValue = DeserializePropertyValue(propertyEntity.Value);
|
||||
IsUsingKeyframes = propertyEntity.IsUsingKeyframes;
|
||||
|
||||
BaseKeyframes.Clear();
|
||||
foreach (var keyframeEntity in propertyEntity.KeyframeEntities.OrderBy(e => e.Position))
|
||||
{
|
||||
// Create a strongly typed keyframe or else it cannot be cast later on
|
||||
var keyframeType = typeof(Keyframe<>);
|
||||
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
|
||||
keyframe.Position = keyframeEntity.Position;
|
||||
keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
|
||||
keyframe.EasingFunction = (Easings.Functions) keyframeEntity.EasingFunction;
|
||||
|
||||
BaseKeyframes.Add(keyframe);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SortKeyframes()
|
||||
{
|
||||
BaseKeyframes = BaseKeyframes.OrderBy(k => k.Position).ToList();
|
||||
}
|
||||
|
||||
internal IEnumerable<BaseLayerProperty> GetAllChildren()
|
||||
{
|
||||
var children = new List<BaseLayerProperty>();
|
||||
children.AddRange(Children);
|
||||
foreach (var layerPropertyViewModel in Children)
|
||||
children.AddRange(layerPropertyViewModel.GetAllChildren());
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
private object DeserializePropertyValue(string value)
|
||||
{
|
||||
if (value == "null")
|
||||
return Type.IsValueType ? Activator.CreateInstance(Type) : null;
|
||||
return JsonConvert.DeserializeObject(value, Type);
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when this property's value was changed outside regular keyframe updates
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> ValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when this property or any of it's ancestors visibility is changed
|
||||
/// </summary>
|
||||
public event EventHandler<EventArgs> VisibilityChanged;
|
||||
|
||||
protected virtual void OnValueChanged()
|
||||
{
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnVisibilityChanged()
|
||||
{
|
||||
VisibilityChanged?.Invoke(this, EventArgs.Empty);
|
||||
foreach (var baseLayerProperty in Children)
|
||||
baseLayerProperty.OnVisibilityChanged();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
|
||||
/// <para>
|
||||
/// Note: You cannot initialize layer properties yourself. If properly placed, the Artemis core will initialize
|
||||
/// these for you.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of property encapsulated in this layer property</typeparam>
|
||||
public abstract class GenericLayerProperty<T> : LayerProperty
|
||||
{
|
||||
private T _currentValue;
|
||||
private List<LayerPropertyKeyframe<T>> _keyframes;
|
||||
private T _baseValue;
|
||||
|
||||
protected GenericLayerProperty()
|
||||
{
|
||||
_keyframes = new List<LayerPropertyKeyframe<T>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base value of this layer property without any keyframes applied
|
||||
/// </summary>
|
||||
public T BaseValue
|
||||
{
|
||||
get => _baseValue;
|
||||
set
|
||||
{
|
||||
if (_baseValue != null && !_baseValue.Equals(value) || _baseValue == null && value != null)
|
||||
{
|
||||
_baseValue = value;
|
||||
OnBaseValueChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
|
||||
/// </summary>
|
||||
public T CurrentValue
|
||||
{
|
||||
get => !KeyframesEnabled || !KeyframesSupported ? BaseValue : _currentValue;
|
||||
internal set => _currentValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether keyframes are supported on this property
|
||||
/// </summary>
|
||||
public bool KeyframesSupported { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether keyframes are enabled on this property, has no effect if <see cref="KeyframesSupported" /> is
|
||||
/// False
|
||||
/// </summary>
|
||||
public bool KeyframesEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the property is hidden in the UI
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the BaseValue was loaded from storage, useful to check whether a default value must be applied
|
||||
/// </summary>
|
||||
public bool IsLoadedFromStorage { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all the keyframes on this layer property
|
||||
/// </summary>
|
||||
public IReadOnlyList<LayerPropertyKeyframe<T>> Keyframes => _keyframes.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total progress on the timeline
|
||||
/// </summary>
|
||||
public TimeSpan TimelineProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current keyframe in the timeline according to the current progress
|
||||
/// </summary>
|
||||
public LayerPropertyKeyframe<T> CurrentKeyframe { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next keyframe in the timeline according to the current progress
|
||||
/// </summary>
|
||||
public LayerPropertyKeyframe<T> NextKeyframe { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the property, moving the timeline forwards by the provided <paramref name="deltaTime" />
|
||||
/// </summary>
|
||||
/// <param name="deltaTime">The amount of time to move the timeline forwards</param>
|
||||
public void Update(double deltaTime)
|
||||
{
|
||||
TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime));
|
||||
if (!KeyframesSupported || !KeyframesEnabled)
|
||||
return;
|
||||
|
||||
// The current keyframe is the last keyframe before the current time
|
||||
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
|
||||
// The next keyframe is the first keyframe that's after the current time
|
||||
NextKeyframe = _keyframes.FirstOrDefault(k => k.Position > TimelineProgress);
|
||||
|
||||
// No need to update the current value if either of the keyframes are null
|
||||
if (CurrentKeyframe == null)
|
||||
CurrentValue = BaseValue;
|
||||
else if (NextKeyframe == null)
|
||||
CurrentValue = CurrentKeyframe.Value;
|
||||
// Only determine progress and current value if both keyframes are present
|
||||
else
|
||||
{
|
||||
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
|
||||
var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
|
||||
var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
|
||||
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
|
||||
}
|
||||
|
||||
OnUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the timeline progress to match the provided <paramref name="progress" />
|
||||
/// </summary>
|
||||
/// <param name="progress">The new progress to set the layer property timeline to.</param>
|
||||
public void OverrideProgress(TimeSpan progress)
|
||||
{
|
||||
TimelineProgress = TimeSpan.Zero;
|
||||
Update(progress.TotalSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the layer property
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to add</param>
|
||||
public void AddKeyframe(LayerPropertyKeyframe<T> keyframe)
|
||||
{
|
||||
keyframe.LayerProperty = this;
|
||||
_keyframes.Add(keyframe);
|
||||
SortKeyframes();
|
||||
OnKeyframeAdded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a keyframe from the layer property
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to remove</param>
|
||||
public void RemoveKeyframe(LayerPropertyKeyframe<T> keyframe)
|
||||
{
|
||||
_keyframes.Remove(keyframe);
|
||||
keyframe.LayerProperty = null;
|
||||
SortKeyframes();
|
||||
OnKeyframeRemoved();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every update (if keyframes are both supported and enabled) to determine the new <see cref="CurrentValue" />
|
||||
/// based on the provided progress
|
||||
/// </summary>
|
||||
/// <param name="keyframeProgress">The linear current keyframe progress</param>
|
||||
/// <param name="keyframeProgressEased">The current keyframe progress, eased with the current easing function</param>
|
||||
protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased);
|
||||
|
||||
internal void SortKeyframes()
|
||||
{
|
||||
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
|
||||
}
|
||||
|
||||
internal override void LoadFromEntity(PropertyEntity entity)
|
||||
{
|
||||
BaseValue = JsonConvert.DeserializeObject<T>(entity.Value);
|
||||
CurrentValue = BaseValue;
|
||||
|
||||
_keyframes.Clear();
|
||||
foreach (var keyframeEntity in entity.KeyframeEntities)
|
||||
{
|
||||
var value = JsonConvert.DeserializeObject<T>(keyframeEntity.Value);
|
||||
var keyframe = new LayerPropertyKeyframe<T>(value, keyframeEntity.Position, (Easings.Functions) keyframeEntity.EasingFunction);
|
||||
_keyframes.Add(keyframe);
|
||||
}
|
||||
SortKeyframes();
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler Updated;
|
||||
public event EventHandler BaseValueChanged;
|
||||
public event EventHandler KeyframeAdded;
|
||||
public event EventHandler KeyframeRemoved;
|
||||
|
||||
protected virtual void OnUpdated()
|
||||
{
|
||||
Updated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnBaseValueChanged()
|
||||
{
|
||||
BaseValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeAdded()
|
||||
{
|
||||
KeyframeAdded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnKeyframeRemoved()
|
||||
{
|
||||
KeyframeRemoved?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public abstract class LayerProperty
|
||||
{
|
||||
internal abstract void LoadFromEntity(PropertyEntity entity);
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class Keyframe<T> : BaseKeyframe
|
||||
{
|
||||
public Keyframe(Layer layer, LayerProperty<T> propertyBase) : base(layer, propertyBase)
|
||||
{
|
||||
}
|
||||
|
||||
public LayerProperty<T> Property => (LayerProperty<T>) BaseProperty;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => BaseValue != null ? (T) BaseValue : default;
|
||||
set => BaseValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a property on the layer. This property is visible in the profile editor and can be key-framed (unless
|
||||
/// opted out).
|
||||
/// <para>To create and register a new LayerProperty use <see cref="LayerBrush.RegisterLayerProperty" /></para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class LayerProperty<T> : BaseLayerProperty
|
||||
{
|
||||
internal LayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description)
|
||||
: base(layer, null, parent, id, name, description, typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
internal LayerProperty(Layer layer, string id, string name, string description)
|
||||
: base(layer, null, null, id, name, description, typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
internal LayerProperty(Layer layer, PluginInfo pluginInfo, BaseLayerProperty parent, string id, string name, string description)
|
||||
: base(layer, pluginInfo, parent, id, name, description, typeof(T))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the property without any keyframes applied
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => BaseValue != null ? (T) BaseValue : default;
|
||||
set => BaseValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the property with keyframes applied
|
||||
/// </summary>
|
||||
public T CurrentValue
|
||||
{
|
||||
get
|
||||
{
|
||||
var currentValue = GetCurrentValue();
|
||||
return currentValue == null ? default : (T) currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of keyframes defining different values of the property in time, this list contains the strongly typed
|
||||
/// <see cref="Keyframe{T}" />
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<Keyframe<T>> Keyframes => BaseKeyframes.Cast<Keyframe<T>>().ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a keyframe to the property.
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to remove</param>
|
||||
public void AddKeyframe(Keyframe<T> keyframe)
|
||||
{
|
||||
base.AddKeyframe(keyframe);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a keyframe from the property.
|
||||
/// </summary>
|
||||
/// <param name="keyframe">The keyframe to remove</param>
|
||||
public void RemoveKeyframe(Keyframe<T> keyframe)
|
||||
{
|
||||
base.RemoveKeyframe(keyframe);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,225 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains all the properties of the layer and provides easy access to the default properties.
|
||||
/// </summary>
|
||||
public class LayerPropertyCollection : IEnumerable<BaseLayerProperty>
|
||||
{
|
||||
private readonly Dictionary<(Guid, string), BaseLayerProperty> _properties;
|
||||
|
||||
internal LayerPropertyCollection(Layer layer)
|
||||
{
|
||||
_properties = new Dictionary<(Guid, string), BaseLayerProperty>();
|
||||
|
||||
Layer = layer;
|
||||
CreateDefaultProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer these properties are applied on
|
||||
/// </summary>
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<BaseLayerProperty> GetEnumerator()
|
||||
{
|
||||
return _properties.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If found, returns the <see cref="LayerProperty{T}" /> matching the provided ID
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the layer property</typeparam>
|
||||
/// <param name="pluginInfo">The plugin this property belongs to</param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public LayerProperty<T> GetLayerPropertyById<T>(PluginInfo pluginInfo, string id)
|
||||
{
|
||||
if (!_properties.ContainsKey((pluginInfo.Guid, id)))
|
||||
return null;
|
||||
|
||||
var property = _properties[(pluginInfo.Guid, id)];
|
||||
if (property.Type != typeof(T))
|
||||
throw new ArtemisCoreException($"Property type mismatch. Expected property {property} to have type {typeof(T)} but it has {property.Type} instead.");
|
||||
return (LayerProperty<T>)_properties[(pluginInfo.Guid, id)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided layer property from the layer.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value of the layer property</typeparam>
|
||||
/// <param name="layerProperty">The property to remove from the layer</param>
|
||||
internal void RemoveLayerProperty<T>(LayerProperty<T> layerProperty)
|
||||
{
|
||||
RemoveLayerProperty((BaseLayerProperty) layerProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided layer property from the layer.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The property to remove from the layer</param>
|
||||
internal void RemoveLayerProperty(BaseLayerProperty layerProperty)
|
||||
{
|
||||
if (!_properties.ContainsKey((layerProperty.PluginInfo.Guid, layerProperty.Id)))
|
||||
throw new ArtemisCoreException($"Could not find a property with ID {layerProperty.Id}.");
|
||||
|
||||
var property = _properties[(layerProperty.PluginInfo.Guid, layerProperty.Id)];
|
||||
property.Parent?.Children.Remove(property);
|
||||
_properties.Remove((layerProperty.PluginInfo.Guid, layerProperty.Id));
|
||||
|
||||
OnLayerPropertyRemoved(new LayerPropertyEventArgs(property));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided layer property and its children to the layer.
|
||||
/// If found, the last stored base value and keyframes will be applied to the provided property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value of the layer property</typeparam>
|
||||
/// <param name="layerProperty">The property to apply to the layer</param>
|
||||
/// <returns>True if an existing value was found and applied, otherwise false.</returns>
|
||||
internal bool RegisterLayerProperty<T>(LayerProperty<T> layerProperty)
|
||||
{
|
||||
return RegisterLayerProperty((BaseLayerProperty) layerProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided layer property to the layer.
|
||||
/// If found, the last stored base value and keyframes will be applied to the provided property.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The property to apply to the layer</param>
|
||||
/// <returns>True if an existing value was found and applied, otherwise false.</returns>
|
||||
internal bool RegisterLayerProperty(BaseLayerProperty layerProperty)
|
||||
{
|
||||
if (_properties.ContainsKey((layerProperty.PluginInfo.Guid, layerProperty.Id)))
|
||||
throw new ArtemisCoreException($"Duplicate property ID detected. Layer already contains a property with ID {layerProperty.Id}.");
|
||||
|
||||
var entity = Layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == layerProperty.Id && p.ValueType == layerProperty.Type.Name);
|
||||
// TODO: Catch serialization exceptions and log them
|
||||
if (entity != null)
|
||||
layerProperty.ApplyToProperty(entity);
|
||||
|
||||
_properties.Add((layerProperty.PluginInfo.Guid, layerProperty.Id), layerProperty);
|
||||
OnLayerPropertyRegistered(new LayerPropertyEventArgs(layerProperty));
|
||||
return entity != null;
|
||||
}
|
||||
|
||||
#region Default properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shape type property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<LayerShapeType> ShapeType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the fill type property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<LayerFillType> FillType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blend mode property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<SKBlendMode> BlendMode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brush reference property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<LayerBrushReference> BrushReference { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the anchor point property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<SKPoint> AnchorPoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<SKPoint> Position { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size property of the layer
|
||||
/// </summary>
|
||||
public LayerProperty<SKSize> Scale { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation property of the layer range 0 - 360
|
||||
/// </summary>
|
||||
public LayerProperty<float> Rotation { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the opacity property of the layer range 0 - 100
|
||||
/// </summary>
|
||||
public LayerProperty<float> Opacity { get; private set; }
|
||||
|
||||
private void CreateDefaultProperties()
|
||||
{
|
||||
// Shape
|
||||
var shape = new LayerProperty<object>(Layer, "Core.Shape", "Shape", "A collection of basic shape properties");
|
||||
ShapeType = new LayerProperty<LayerShapeType>(Layer, shape, "Core.ShapeType", "Shape type", "The type of shape to draw in this layer") {CanUseKeyframes = false};
|
||||
FillType = new LayerProperty<LayerFillType>(Layer, shape, "Core.FillType", "Fill type", "How to make the shape adjust to scale changes") {CanUseKeyframes = false};
|
||||
BlendMode = new LayerProperty<SKBlendMode>(Layer, shape, "Core.BlendMode", "Blend mode", "How to blend this layer into the resulting image") {CanUseKeyframes = false};
|
||||
ShapeType.Value = LayerShapeType.Rectangle;
|
||||
FillType.Value = LayerFillType.Stretch;
|
||||
BlendMode.Value = SKBlendMode.SrcOver;
|
||||
|
||||
RegisterLayerProperty(shape);
|
||||
foreach (var shapeProperty in shape.Children)
|
||||
RegisterLayerProperty(shapeProperty);
|
||||
|
||||
// Brush
|
||||
var brush = new LayerProperty<object>(Layer, "Core.Brush", "Brush", "A collection of properties that configure the selected brush");
|
||||
BrushReference = new LayerProperty<LayerBrushReference>(Layer, brush, "Core.BrushReference", "Brush type", "The type of brush to use for this layer") {CanUseKeyframes = false};
|
||||
|
||||
RegisterLayerProperty(brush);
|
||||
foreach (var brushProperty in brush.Children)
|
||||
RegisterLayerProperty(brushProperty);
|
||||
|
||||
// Transform
|
||||
var transform = new LayerProperty<object>(Layer, "Core.Transform", "Transform", "A collection of transformation properties") {ExpandByDefault = true};
|
||||
AnchorPoint = new LayerProperty<SKPoint>(Layer, transform, "Core.AnchorPoint", "Anchor Point", "The point at which the shape is attached to its position") {InputStepSize = 0.001f};
|
||||
Position = new LayerProperty<SKPoint>(Layer, transform, "Core.Position", "Position", "The position of the shape") {InputStepSize = 0.001f};
|
||||
Scale = new LayerProperty<SKSize>(Layer, transform, "Core.Scale", "Scale", "The scale of the shape") {InputAffix = "%", MinInputValue = 0f};
|
||||
Rotation = new LayerProperty<float>(Layer, transform, "Core.Rotation", "Rotation", "The rotation of the shape in degrees") {InputAffix = "°"};
|
||||
Opacity = new LayerProperty<float>(Layer, transform, "Core.Opacity", "Opacity", "The opacity of the shape") {InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f};
|
||||
Scale.Value = new SKSize(100, 100);
|
||||
Opacity.Value = 100;
|
||||
|
||||
RegisterLayerProperty(transform);
|
||||
foreach (var transformProperty in transform.Children)
|
||||
RegisterLayerProperty(transformProperty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<LayerPropertyEventArgs> LayerPropertyRegistered;
|
||||
public event EventHandler<LayerPropertyEventArgs> LayerPropertyRemoved;
|
||||
|
||||
private void OnLayerPropertyRegistered(LayerPropertyEventArgs e)
|
||||
{
|
||||
LayerPropertyRegistered?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OnLayerPropertyRemoved(LayerPropertyEventArgs e)
|
||||
{
|
||||
LayerPropertyRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -3,19 +3,18 @@ using Artemis.Core.Utilities;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
{
|
||||
public class LayerPropertyKeyFrame<T>
|
||||
public class LayerPropertyKeyframe<T>
|
||||
{
|
||||
private TimeSpan _position;
|
||||
|
||||
public LayerPropertyKeyFrame(LayerProperty<T> layerProperty, T value, TimeSpan position, Easings.Functions easingFunction)
|
||||
public LayerPropertyKeyframe(T value, TimeSpan position, Easings.Functions easingFunction)
|
||||
{
|
||||
_position = position;
|
||||
Value = value;
|
||||
LayerProperty = layerProperty;
|
||||
EasingFunction = easingFunction;
|
||||
}
|
||||
|
||||
public LayerProperty<T> LayerProperty { get; set; }
|
||||
public GenericLayerProperty<T> LayerProperty { get; internal set; }
|
||||
public T Value { get; set; }
|
||||
|
||||
public TimeSpan Position
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
using Artemis.Core.Exceptions;
|
||||
using Artemis.Core.Models.Profile.Colors;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public class ColorGradientLayerProperty : GenericLayerProperty<ColorGradient>
|
||||
{
|
||||
internal ColorGradientLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Color Gradients do not support keyframes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
using Artemis.Core.Exceptions;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public class EnumLayerProperty<T> : GenericLayerProperty<T> where T : System.Enum
|
||||
{
|
||||
public EnumLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Enum properties do not support keyframes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,12 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
public class FloatLayerProperty : LayerProperty<float>
|
||||
/// <inheritdoc/>
|
||||
public class FloatLayerProperty : GenericLayerProperty<float>
|
||||
{
|
||||
internal FloatLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
var diff = NextKeyframe.Value - CurrentKeyframe.Value;
|
||||
@ -1,10 +1,14 @@
|
||||
using System;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
public class IntLayerProperty : LayerProperty<int>
|
||||
/// <inheritdoc/>
|
||||
public class IntLayerProperty : GenericLayerProperty<int>
|
||||
{
|
||||
internal IntLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
var diff = NextKeyframe.Value - CurrentKeyframe.Value;
|
||||
@ -0,0 +1,20 @@
|
||||
using Artemis.Core.Exceptions;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// A special layer property used to configure the selected layer brush
|
||||
/// </summary>
|
||||
public class LayerBrushReferenceLayerProperty : GenericLayerProperty<LayerBrushReference>
|
||||
{
|
||||
internal LayerBrushReferenceLayerProperty()
|
||||
{
|
||||
KeyframesSupported = false;
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
throw new ArtemisCoreException("Layer brush references do not support keyframes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
public class SKColorLayerProperty : LayerProperty<SKColor>
|
||||
/// <inheritdoc/>
|
||||
public class SKColorLayerProperty : GenericLayerProperty<SKColor>
|
||||
{
|
||||
internal SKColorLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red;
|
||||
@ -1,10 +1,14 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
public class SKPointLayerProperty : LayerProperty<SKPoint>
|
||||
/// <inheritdoc/>
|
||||
public class SKPointLayerProperty : GenericLayerProperty<SKPoint>
|
||||
{
|
||||
internal SKPointLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
var xDiff = NextKeyframe.Value.X - CurrentKeyframe.Value.X;
|
||||
@ -1,10 +1,14 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
namespace Artemis.Core.Models.Profile.LayerProperties.Types
|
||||
{
|
||||
public class SKSizeLayerProperty : LayerProperty<SKSize>
|
||||
/// <inheritdoc/>
|
||||
public class SKSizeLayerProperty : GenericLayerProperty<SKSize>
|
||||
{
|
||||
internal SKSizeLayerProperty()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
|
||||
{
|
||||
var widthDiff = NextKeyframe.Value.Width - CurrentKeyframe.Value.Width;
|
||||
62
src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
Normal file
62
src/Artemis.Core/Models/Profile/LayerPropertyGroup.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Plugins.Exceptions;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
|
||||
namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
public class LayerPropertyGroup
|
||||
{
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when all layer properties in this property group have been initialized
|
||||
/// </summary>
|
||||
protected virtual void OnPropertiesInitialized()
|
||||
{
|
||||
}
|
||||
|
||||
internal void InitializeProperties(ILayerService layerService, Layer layer, string path)
|
||||
{
|
||||
// Get all properties with a PropertyDescriptionAttribute
|
||||
foreach (var propertyInfo in GetType().GetProperties())
|
||||
{
|
||||
var propertyDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyDescriptionAttribute));
|
||||
if (propertyDescription != null)
|
||||
{
|
||||
if (!typeof(GenericLayerProperty<>).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException("Layer property with PropertyDescription attribute must be of type LayerProperty");
|
||||
|
||||
var instance = (LayerProperty) Activator.CreateInstance(propertyInfo.PropertyType);
|
||||
InitializeProperty(layer, path, instance);
|
||||
propertyInfo.SetValue(this, instance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyGroupDescription = Attribute.GetCustomAttribute(propertyInfo, typeof(PropertyGroupDescriptionAttribute));
|
||||
if (propertyGroupDescription != null)
|
||||
{
|
||||
if (!typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
throw new ArtemisPluginException("Layer property with PropertyGroupDescription attribute must be of type LayerPropertyGroup");
|
||||
|
||||
var instance = (LayerPropertyGroup) Activator.CreateInstance(propertyInfo.PropertyType);
|
||||
instance.InitializeProperties(layerService, layer, $"{path}{propertyInfo.Name}.");
|
||||
propertyInfo.SetValue(this, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertiesInitialized();
|
||||
PropertiesInitialized = true;
|
||||
}
|
||||
|
||||
private void InitializeProperty(Layer layer, string path, LayerProperty instance)
|
||||
{
|
||||
var entity = layer.LayerEntity.PropertyEntities.FirstOrDefault(p => p.Id == path);
|
||||
if (entity != null)
|
||||
instance.LoadFromEntity(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
src/Artemis.Core/Models/Profile/LayerTransformProperties.cs
Normal file
34
src/Artemis.Core/Models/Profile/LayerTransformProperties.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Types;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Models.Profile
|
||||
{
|
||||
public class LayerTransformProperties : LayerPropertyGroup
|
||||
{
|
||||
[PropertyDescription(Description = "The point at which the shape is attached to its position", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty AnchorPoint { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The position of the shape", InputStepSize = 0.001f)]
|
||||
public SKPointLayerProperty Position { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The scale of the shape", InputAffix = "%", MinInputValue = 0f)]
|
||||
public SKSizeLayerProperty Scale { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The rotation of the shape in degrees", InputAffix = "°")]
|
||||
public FloatLayerProperty Rotation { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The opacity of the shape", InputAffix = "%", MinInputValue = 0f, MaxInputValue = 100f)]
|
||||
public FloatLayerProperty Opacity { get; set; }
|
||||
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
// Populate defaults
|
||||
if (!Scale.IsLoadedFromStorage)
|
||||
Scale.BaseValue = new SKSize(100, 100);
|
||||
if (!Opacity.IsLoadedFromStorage)
|
||||
Opacity.BaseValue = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs
Normal file
39
src/Artemis.Core/Plugins/LayerBrush/ILayerBrush.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Plugins.LayerBrush
|
||||
{
|
||||
public interface ILayerBrush : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the layer this brush is applied to
|
||||
/// </summary>
|
||||
Layer Layer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the descriptor of this brush
|
||||
/// </summary>
|
||||
LayerBrushDescriptor Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called before rendering every frame, write your update logic here
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
void Update(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// The main method of rendering anything to the layer. The provided <see cref="SKCanvas" /> is specific to the layer
|
||||
/// and matches it's width and height.
|
||||
/// <para>Called during rendering or layer preview, in the order configured on the layer</para>
|
||||
/// </summary>
|
||||
/// <param name="canvas">The layer canvas</param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
/// <param name="path">The path to be filled, represents the shape</param>
|
||||
/// <param name="paint">The paint to be used to fill the shape</param>
|
||||
void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint);
|
||||
|
||||
public void InitializeProperties(ILayerService layerService, string path);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,13 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Plugins.Exceptions;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Plugins.LayerBrush
|
||||
{
|
||||
public abstract class LayerBrush : IDisposable
|
||||
public abstract class LayerBrush<T> : ILayerBrush where T : LayerPropertyGroup
|
||||
{
|
||||
private ILayerService _layerService;
|
||||
private T _properties;
|
||||
|
||||
protected LayerBrush(Layer layer, LayerBrushDescriptor descriptor)
|
||||
{
|
||||
@ -18,112 +15,63 @@ namespace Artemis.Core.Plugins.LayerBrush
|
||||
Descriptor = descriptor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Layer Layer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerBrushDescriptor Descriptor { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before rendering every frame, write your update logic here
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
/// <inheritdoc />
|
||||
public virtual void Update(double deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main method of rendering anything to the layer. The provided <see cref="SKCanvas" /> is specific to the layer
|
||||
/// and matches it's width and height.
|
||||
/// <para>Called during rendering or layer preview, in the order configured on the layer</para>
|
||||
/// </summary>
|
||||
/// <param name="canvas">The layer canvas</param>
|
||||
/// <param name="canvasInfo"></param>
|
||||
/// <param name="path">The path to be filled, represents the shape</param>
|
||||
/// <param name="paint">The paint to be used to fill the shape</param>
|
||||
/// <inheritdoc />
|
||||
public virtual void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides an easy way to add your own properties to the layer, for more info see <see cref="LayerProperty{T}" />.
|
||||
/// <para>Note: If found, the last value and keyframes are loaded and set when calling this method.</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="parent">The parent of this property, use this to create a tree-hierarchy in the editor</param>
|
||||
/// <param name="id">A and ID identifying your property, must be unique within your plugin</param>
|
||||
/// <param name="name">A name for your property, this is visible in the editor</param>
|
||||
/// <param name="description">A description for your property, this is visible in the editor</param>
|
||||
/// <param name="defaultValue">The default value of the property, if not configured by the user</param>
|
||||
/// <returns>The layer property</returns>
|
||||
protected LayerProperty<T> RegisterLayerProperty<T>(BaseLayerProperty parent, string id, string name, string description, T defaultValue = default)
|
||||
{
|
||||
var property = new LayerProperty<T>(Layer, Descriptor.LayerBrushProvider.PluginInfo, parent, id, name, description) {Value = defaultValue};
|
||||
Layer.Properties.RegisterLayerProperty(property);
|
||||
// It's fine if this is null, it'll be picked up by SetLayerService later
|
||||
_layerService?.InstantiateKeyframeEngine(property);
|
||||
return property;
|
||||
}
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Provides an easy way to add your own properties to the layer, for more info see <see cref="LayerProperty{T}" />.
|
||||
/// <para>Note: If found, the last value and keyframes are loaded and set when calling this method.</para>
|
||||
/// Gets the properties of this brush.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="id">A and ID identifying your property, must be unique within your plugin</param>
|
||||
/// <param name="name">A name for your property, this is visible in the editor</param>
|
||||
/// <param name="description">A description for your property, this is visible in the editor</param>
|
||||
/// <param name="defaultValue">The default value of the property, if not configured by the user</param>
|
||||
/// <returns>The layer property</returns>
|
||||
protected LayerProperty<T> RegisterLayerProperty<T>(string id, string name, string description, T defaultValue = default)
|
||||
public T Properties
|
||||
{
|
||||
// Check if the property already exists
|
||||
var existing = Layer.Properties.FirstOrDefault(p =>
|
||||
p.PluginInfo.Guid == Descriptor.LayerBrushProvider.PluginInfo.Guid &&
|
||||
p.Id == id &&
|
||||
p.Name == name &&
|
||||
p.Description == description);
|
||||
|
||||
if (existing != null)
|
||||
get
|
||||
{
|
||||
// If it exists and the types match, return the existing property
|
||||
if (existing.Type == typeof(T))
|
||||
return (LayerProperty<T>) existing;
|
||||
// If it exists and the types are different, something is wrong
|
||||
throw new ArtemisPluginException($"Cannot register the property {id} with different types twice.");
|
||||
// I imagine a null reference here can be confusing, so lets throw an exception explaining what to do
|
||||
if (_properties == null)
|
||||
throw new ArtemisPluginException("Cannot access brush properties until OnPropertiesInitialized has been called");
|
||||
return _properties;
|
||||
}
|
||||
|
||||
var property = new LayerProperty<T>(Layer, Descriptor.LayerBrushProvider.PluginInfo, Layer.Properties.BrushReference.Parent, id, name, description)
|
||||
{
|
||||
Value = defaultValue
|
||||
};
|
||||
|
||||
Layer.Properties.RegisterLayerProperty(property);
|
||||
// It's fine if this is null, it'll be picked up by SetLayerService later
|
||||
_layerService?.InstantiateKeyframeEngine(property);
|
||||
return property;
|
||||
internal set => _properties = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to remove layer properties previously added by using <see cref="RegisterLayerProperty{T}(BaseLayerProperty,string,string,string,T)" />.
|
||||
/// Gets whether all properties on this brush are initialized
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="layerProperty"></param>
|
||||
protected void UnRegisterLayerProperty<T>(LayerProperty<T> layerProperty)
|
||||
public bool PropertiesInitialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when all layer properties in this brush have been initialized
|
||||
/// </summary>
|
||||
protected virtual void OnPropertiesInitialized()
|
||||
{
|
||||
if (layerProperty == null)
|
||||
return;
|
||||
|
||||
if (Layer.Properties.Any(p => p == layerProperty))
|
||||
Layer.Properties.RemoveLayerProperty(layerProperty);
|
||||
}
|
||||
|
||||
internal void SetLayerService(ILayerService layerService)
|
||||
public void InitializeProperties(ILayerService layerService, string path)
|
||||
{
|
||||
_layerService = layerService;
|
||||
foreach (var baseLayerProperty in Layer.Properties)
|
||||
_layerService.InstantiateKeyframeEngine(baseLayerProperty);
|
||||
Properties.InitializeProperties(layerService, Descriptor.LayerBrushProvider.PluginInfo, path);
|
||||
OnPropertiesInitialized();
|
||||
PropertiesInitialized = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ namespace Artemis.Core.Plugins.LayerBrush
|
||||
|
||||
public ReadOnlyCollection<LayerBrushDescriptor> LayerBrushDescriptors => _layerBrushDescriptors.AsReadOnly();
|
||||
|
||||
protected void AddLayerBrushDescriptor<T>(string displayName, string description, string icon) where T : LayerBrush
|
||||
protected void AddLayerBrushDescriptor<T>(string displayName, string description, string icon) where T : ILayerBrush
|
||||
{
|
||||
_layerBrushDescriptors.Add(new LayerBrushDescriptor(displayName, description, icon, typeof(T), this));
|
||||
}
|
||||
|
||||
@ -13,22 +13,9 @@ namespace Artemis.Core.Services.Interfaces
|
||||
/// </summary>
|
||||
/// <param name="layer">The layer to instantiate the brush for</param>
|
||||
/// <returns></returns>
|
||||
LayerBrush InstantiateLayerBrush(Layer layer);
|
||||
ILayerBrush InstantiateLayerBrush(Layer layer);
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates and adds a compatible <see cref="KeyframeEngine" /> to the provided <see cref="LayerProperty{T}" />.
|
||||
/// If the property already has a compatible keyframe engine, nothing happens.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The layer property to apply the keyframe engine to.</param>
|
||||
/// <returns>The resulting keyframe engine, if a compatible engine was found.</returns>
|
||||
KeyframeEngine InstantiateKeyframeEngine<T>(LayerProperty<T> layerProperty);
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates and adds a compatible <see cref="KeyframeEngine" /> to the provided <see cref="BaseLayerProperty" />.
|
||||
/// If the property already has a compatible keyframe engine, nothing happens.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The layer property to apply the keyframe engine to.</param>
|
||||
/// <returns>The resulting keyframe engine, if a compatible engine was found.</returns>
|
||||
KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty);
|
||||
void LoadPropertyBaseValue(Layer layer, string path, object layerProperty);
|
||||
void LoadPropertyKeyframes(Layer layer, string path, object layerProperty);
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,9 @@ using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.KeyframeEngines;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using Artemis.Core.Plugins.Models;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Newtonsoft.Json;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using Serilog;
|
||||
@ -24,11 +26,25 @@ namespace Artemis.Core.Services
|
||||
_pluginService = pluginService;
|
||||
}
|
||||
|
||||
public LayerBrush InstantiateLayerBrush(Layer layer)
|
||||
public Layer CreateLayer(Profile profile, ProfileElement parent, string name)
|
||||
{
|
||||
var layer = new Layer(profile, parent, name);
|
||||
|
||||
// Layers have two hardcoded property groups, instantiate them
|
||||
layer.General.InitializeProperties(this, layer, null, null);
|
||||
layer.Transform.InitializeProperties(this, layer, null, null);
|
||||
|
||||
// With the properties loaded, the layer brush can be instantiated
|
||||
InstantiateLayerBrush(layer);
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
public ILayerBrush InstantiateLayerBrush(Layer layer)
|
||||
{
|
||||
RemoveLayerBrush(layer);
|
||||
|
||||
var descriptorReference = layer.Properties.BrushReference.CurrentValue;
|
||||
var descriptorReference = layer.General.BrushReference.CurrentValue;
|
||||
if (descriptorReference == null)
|
||||
return null;
|
||||
|
||||
@ -46,36 +62,13 @@ namespace Artemis.Core.Services
|
||||
new ConstructorArgument("layer", layer),
|
||||
new ConstructorArgument("descriptor", descriptor)
|
||||
};
|
||||
var layerBrush = (LayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments);
|
||||
// Set the layer service after the brush was created to avoid constructor clutter, SetLayerService will play catch-up for us.
|
||||
// If layer brush implementations need the LayerService they can inject it themselves, but don't require it by default
|
||||
layerBrush.SetLayerService(this);
|
||||
var layerBrush = (ILayerBrush) _kernel.Get(descriptor.LayerBrushType, arguments);
|
||||
layerBrush.InitializeProperties(this, null);
|
||||
layer.LayerBrush = layerBrush;
|
||||
|
||||
return layerBrush;
|
||||
}
|
||||
|
||||
public KeyframeEngine InstantiateKeyframeEngine<T>(LayerProperty<T> layerProperty)
|
||||
{
|
||||
return InstantiateKeyframeEngine((BaseLayerProperty) layerProperty);
|
||||
}
|
||||
|
||||
public KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty)
|
||||
{
|
||||
if (layerProperty.KeyframeEngine != null && layerProperty.KeyframeEngine.CompatibleTypes.Contains(layerProperty.Type))
|
||||
return layerProperty.KeyframeEngine;
|
||||
|
||||
// This creates an instance of each keyframe engine, which is pretty cheap since all the expensive stuff is done during
|
||||
// Initialize() call but it's not ideal
|
||||
var keyframeEngines = _kernel.Get<List<KeyframeEngine>>();
|
||||
var keyframeEngine = keyframeEngines.FirstOrDefault(k => k.CompatibleTypes.Contains(layerProperty.Type));
|
||||
if (keyframeEngine == null)
|
||||
return null;
|
||||
|
||||
keyframeEngine.Initialize(layerProperty);
|
||||
return keyframeEngine;
|
||||
}
|
||||
|
||||
public void RemoveLayerBrush(Layer layer)
|
||||
{
|
||||
if (layer.LayerBrush == null)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using Artemis.Core.Models.Surface;
|
||||
using Artemis.Core.Plugins.Abstract;
|
||||
using Artemis.UI.Screens.Module;
|
||||
@ -51,7 +50,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
|
||||
public interface ILayerPropertyVmFactory : IVmFactory
|
||||
{
|
||||
LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent);
|
||||
LayerPropertyViewModel Create(LayerProperty layerProperty, LayerPropertyViewModel parent);
|
||||
}
|
||||
|
||||
public interface IPropertyTreeVmFactory : IVmFactory
|
||||
|
||||
@ -169,7 +169,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
PopulateProperties(e.LayerProperty.Layer);
|
||||
}
|
||||
|
||||
private LayerPropertyViewModel CreatePropertyViewModel(BaseLayerProperty layerProperty)
|
||||
private LayerPropertyViewModel CreatePropertyViewModel(LayerProperty layerProperty)
|
||||
{
|
||||
LayerPropertyViewModel parent = null;
|
||||
// If the property has a parent, find it's VM
|
||||
|
||||
@ -16,7 +16,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
private bool _keyframesEnabled;
|
||||
private bool _isExpanded;
|
||||
|
||||
public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService)
|
||||
public LayerPropertyViewModel(LayerProperty layerProperty, LayerPropertyViewModel parent, IKernel kernel, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_profileEditorService = profileEditorService;
|
||||
@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
|
||||
Parent?.Children.Add(this);
|
||||
}
|
||||
|
||||
public BaseLayerProperty LayerProperty { get; }
|
||||
public LayerProperty LayerProperty { get; }
|
||||
|
||||
public LayerPropertyViewModel Parent { get; }
|
||||
public List<LayerPropertyViewModel> Children { get; }
|
||||
|
||||
@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Abstract;
|
||||
using Artemis.Core.Utilities;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Stylet;
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.Colors;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Plugins.LayerBrushes.Color
|
||||
{
|
||||
public class ColorBrush : LayerBrush
|
||||
public class ColorBrush : LayerBrush<ColorBrushProperties>
|
||||
{
|
||||
private SKColor _color;
|
||||
private SKPaint _paint;
|
||||
@ -18,33 +14,16 @@ namespace Artemis.Plugins.LayerBrushes.Color
|
||||
|
||||
public ColorBrush(Layer layer, LayerBrushDescriptor descriptor) : base(layer, descriptor)
|
||||
{
|
||||
GradientTypeProperty = RegisterLayerProperty("Brush.GradientType", "Gradient type", "The type of color brush to draw", GradientType.Solid);
|
||||
GradientTypeProperty.CanUseKeyframes = false;
|
||||
ColorProperty = RegisterLayerProperty("Brush.Color", "Color", "The color of the brush", new SKColor(255, 0, 0));
|
||||
GradientProperty = RegisterLayerProperty("Brush.Gradient", "Gradient", "The gradient of the brush", new ColorGradient());
|
||||
GradientProperty.CanUseKeyframes = false;
|
||||
if (!GradientProperty.Value.Stops.Any())
|
||||
GradientProperty.Value.MakeFabulous();
|
||||
|
||||
UpdateColorProperties();
|
||||
|
||||
Layer.RenderPropertiesUpdated += (sender, args) => CreateShader();
|
||||
GradientTypeProperty.ValueChanged += (sender, args) => UpdateColorProperties();
|
||||
ColorProperty.ValueChanged += (sender, args) => CreateShader();
|
||||
GradientProperty.Value.PropertyChanged += (sender, args) => CreateShader();
|
||||
}
|
||||
|
||||
public LayerProperty<SKColor> ColorProperty { get; set; }
|
||||
public LayerProperty<ColorGradient> GradientProperty { get; set; }
|
||||
public LayerProperty<GradientType> GradientTypeProperty { get; set; }
|
||||
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
// Only check if a solid is being drawn, because that can be changed by keyframes
|
||||
if (GradientTypeProperty.Value == GradientType.Solid && _color != ColorProperty.CurrentValue)
|
||||
if (Properties.GradientType.BaseValue == GradientType.Solid && _color != Properties.Color.CurrentValue)
|
||||
{
|
||||
// If the color was changed since the last frame, recreate the shader
|
||||
_color = ColorProperty.CurrentValue;
|
||||
_color = Properties.Color.CurrentValue;
|
||||
CreateShader();
|
||||
}
|
||||
|
||||
@ -63,36 +42,35 @@ namespace Artemis.Plugins.LayerBrushes.Color
|
||||
canvas.DrawPath(path, paint);
|
||||
}
|
||||
|
||||
private void UpdateColorProperties()
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
ColorProperty.IsHidden = GradientTypeProperty.Value != GradientType.Solid;
|
||||
GradientProperty.IsHidden = GradientTypeProperty.Value == GradientType.Solid;
|
||||
|
||||
CreateShader();
|
||||
Properties.GradientType.BaseValueChanged += (sender, args) => CreateShader();
|
||||
Properties.Color.BaseValueChanged += (sender, args) => CreateShader();
|
||||
Properties.Gradient.BaseValue.PropertyChanged += (sender, args) => CreateShader();
|
||||
}
|
||||
|
||||
private void CreateShader()
|
||||
{
|
||||
var center = new SKPoint(_shaderBounds.MidX, _shaderBounds.MidY);
|
||||
var shader = GradientTypeProperty.CurrentValue switch
|
||||
var shader = Properties.GradientType.CurrentValue switch
|
||||
{
|
||||
GradientType.Solid => SKShader.CreateColor(_color),
|
||||
GradientType.LinearGradient => SKShader.CreateLinearGradient(
|
||||
new SKPoint(_shaderBounds.Left, _shaderBounds.Top),
|
||||
new SKPoint(_shaderBounds.Right, _shaderBounds.Top),
|
||||
GradientProperty.Value.GetColorsArray(),
|
||||
GradientProperty.Value.GetPositionsArray(),
|
||||
Properties.Gradient.BaseValue.GetColorsArray(),
|
||||
Properties.Gradient.BaseValue.GetPositionsArray(),
|
||||
SKShaderTileMode.Repeat),
|
||||
GradientType.RadialGradient => SKShader.CreateRadialGradient(
|
||||
center,
|
||||
Math.Min(_shaderBounds.Width, _shaderBounds.Height),
|
||||
GradientProperty.Value.GetColorsArray(),
|
||||
GradientProperty.Value.GetPositionsArray(),
|
||||
Properties.Gradient.BaseValue.GetColorsArray(),
|
||||
Properties.Gradient.BaseValue.GetPositionsArray(),
|
||||
SKShaderTileMode.Repeat),
|
||||
GradientType.SweepGradient => SKShader.CreateSweepGradient(
|
||||
center,
|
||||
GradientProperty.Value.GetColorsArray(),
|
||||
GradientProperty.Value.GetPositionsArray(),
|
||||
Properties.Gradient.BaseValue.GetColorsArray(),
|
||||
Properties.Gradient.BaseValue.GetPositionsArray(),
|
||||
SKShaderTileMode.Clamp,
|
||||
0,
|
||||
360),
|
||||
@ -107,19 +85,4 @@ namespace Artemis.Plugins.LayerBrushes.Color
|
||||
oldPaint?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public enum GradientType
|
||||
{
|
||||
[Description("Solid")]
|
||||
Solid,
|
||||
|
||||
[Description("Linear Gradient")]
|
||||
LinearGradient,
|
||||
|
||||
[Description("Radial Gradient")]
|
||||
RadialGradient,
|
||||
|
||||
[Description("Sweep Gradient")]
|
||||
SweepGradient
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.Colors;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Types;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Plugins.LayerBrushes.Color
|
||||
{
|
||||
public class ColorBrushProperties : LayerPropertyGroup
|
||||
{
|
||||
[PropertyDescription(Description = "The type of color brush to draw")]
|
||||
public EnumLayerProperty<GradientType> GradientType { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The color of the brush")]
|
||||
public SKColorLayerProperty Color { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The gradient of the brush")]
|
||||
public ColorGradientLayerProperty Gradient { get; set; }
|
||||
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
// Populate defaults
|
||||
if (!GradientType.IsLoadedFromStorage)
|
||||
GradientType.BaseValue = LayerBrushes.Color.GradientType.Solid;
|
||||
if (!Color.IsLoadedFromStorage)
|
||||
Color.BaseValue = new SKColor(255, 0, 0);
|
||||
if (!Gradient.IsLoadedFromStorage)
|
||||
{
|
||||
Gradient.BaseValue = new ColorGradient();
|
||||
Gradient.BaseValue.MakeFabulous();
|
||||
}
|
||||
|
||||
GradientType.BaseValueChanged += GradientTypeOnBaseValueChanged;
|
||||
}
|
||||
|
||||
private void GradientTypeOnBaseValueChanged(object? sender, EventArgs e)
|
||||
{
|
||||
Color.IsHidden = GradientType.BaseValue != LayerBrushes.Color.GradientType.Solid;
|
||||
Gradient.IsHidden = GradientType.BaseValue == LayerBrushes.Color.GradientType.Solid;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GradientType
|
||||
{
|
||||
[Description("Solid")]
|
||||
Solid,
|
||||
|
||||
[Description("Linear Gradient")]
|
||||
LinearGradient,
|
||||
|
||||
[Description("Radial Gradient")]
|
||||
RadialGradient,
|
||||
|
||||
[Description("Sweep Gradient")]
|
||||
SweepGradient
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.Colors;
|
||||
using Artemis.Core.Models.Profile.LayerProperties;
|
||||
using Artemis.Core.Plugins.LayerBrush;
|
||||
using Artemis.Core.Services.Interfaces;
|
||||
using Artemis.Plugins.LayerBrushes.Noise.Utilities;
|
||||
@ -11,18 +8,18 @@ using SkiaSharp;
|
||||
|
||||
namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
{
|
||||
public class NoiseBrush : LayerBrush
|
||||
public class NoiseBrush : LayerBrush<NoiseBrushProperties>
|
||||
{
|
||||
private static readonly Random Rand = new Random();
|
||||
private readonly OpenSimplexNoise _noise;
|
||||
private readonly IRgbService _rgbService;
|
||||
private SKBitmap _bitmap;
|
||||
private SKColor[] _colorMap;
|
||||
|
||||
private float _renderScale;
|
||||
private float _x;
|
||||
private float _y;
|
||||
private float _z;
|
||||
private SKColor[] _colorMap;
|
||||
|
||||
public NoiseBrush(Layer layer, LayerBrushDescriptor descriptor, IRgbService rgbService) : base(layer, descriptor)
|
||||
{
|
||||
@ -31,58 +28,15 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
_y = Rand.Next(0, 4096);
|
||||
_z = Rand.Next(0, 4096);
|
||||
_noise = new OpenSimplexNoise(Rand.Next(0, 4096));
|
||||
|
||||
DetermineRenderScale();
|
||||
|
||||
ColorTypeProperty = RegisterLayerProperty("Brush.ColorType", "Color mapping type", "The way the noise is converted to colors", ColorMappingType.Simple);
|
||||
ColorTypeProperty.CanUseKeyframes = false;
|
||||
ColorTypeProperty.ValueChanged += (sender, args) => UpdateColorProperties();
|
||||
ScaleProperty = RegisterLayerProperty("Brush.Scale", "Scale", "The scale of the noise.", new SKSize(100, 100));
|
||||
ScaleProperty.MinInputValue = 0f;
|
||||
HardnessProperty = RegisterLayerProperty("Brush.Hardness", "Hardness", "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", 500f);
|
||||
HardnessProperty.MinInputValue = 0f;
|
||||
HardnessProperty.MaxInputValue = 2048f;
|
||||
ScrollSpeedProperty = RegisterLayerProperty<SKPoint>("Brush.ScrollSpeed", "Movement speed", "The speed at which the noise moves vertically and horizontally");
|
||||
ScrollSpeedProperty.MinInputValue = -64f;
|
||||
ScrollSpeedProperty.MaxInputValue = 64f;
|
||||
AnimationSpeedProperty = RegisterLayerProperty("Brush.AnimationSpeed", "Animation speed", "The speed at which the noise moves", 25f);
|
||||
AnimationSpeedProperty.MinInputValue = 0f;
|
||||
AnimationSpeedProperty.MaxInputValue = 64f;
|
||||
ScaleProperty.InputAffix = "%";
|
||||
MainColorProperty = RegisterLayerProperty("Brush.MainColor", "Main color", "The main color of the noise", new SKColor(255, 0, 0));
|
||||
SecondaryColorProperty = RegisterLayerProperty("Brush.SecondaryColor", "Secondary color", "The secondary color of the noise", new SKColor(0, 0, 255));
|
||||
GradientColorProperty = RegisterLayerProperty("Brush.GradientColor", "Noise gradient map", "The gradient the noise will map it's value to", new ColorGradient());
|
||||
GradientColorProperty.CanUseKeyframes = false;
|
||||
if (!GradientColorProperty.Value.Stops.Any())
|
||||
GradientColorProperty.Value.MakeFabulous();
|
||||
GradientColorProperty.Value.PropertyChanged += CreateColorMap;
|
||||
|
||||
UpdateColorProperties();
|
||||
CreateColorMap(null, null);
|
||||
}
|
||||
|
||||
|
||||
public LayerProperty<ColorMappingType> ColorTypeProperty { get; set; }
|
||||
public LayerProperty<SKColor> MainColorProperty { get; set; }
|
||||
public LayerProperty<SKColor> SecondaryColorProperty { get; set; }
|
||||
public LayerProperty<ColorGradient> GradientColorProperty { get; set; }
|
||||
|
||||
public LayerProperty<SKSize> ScaleProperty { get; set; }
|
||||
public LayerProperty<float> HardnessProperty { get; set; }
|
||||
public LayerProperty<SKPoint> ScrollSpeedProperty { get; set; }
|
||||
public LayerProperty<float> AnimationSpeedProperty { get; set; }
|
||||
|
||||
private void UpdateColorProperties()
|
||||
{
|
||||
GradientColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Gradient;
|
||||
MainColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Simple;
|
||||
SecondaryColorProperty.IsHidden = ColorTypeProperty.Value != ColorMappingType.Simple;
|
||||
}
|
||||
|
||||
public override void Update(double deltaTime)
|
||||
{
|
||||
_x += ScrollSpeedProperty.CurrentValue.X / 500f / (float) deltaTime;
|
||||
_y += ScrollSpeedProperty.CurrentValue.Y / 500f / (float) deltaTime;
|
||||
_z += AnimationSpeedProperty.CurrentValue / 500f / 0.04f * (float) deltaTime;
|
||||
_x += Properties.ScrollSpeed.CurrentValue.X / 500f / (float) deltaTime;
|
||||
_y += Properties.ScrollSpeed.CurrentValue.Y / 500f / (float) deltaTime;
|
||||
_z += Properties.AnimationSpeed.CurrentValue / 500f / 0.04f * (float) deltaTime;
|
||||
|
||||
// A telltale sign of someone who can't do math very well
|
||||
if (float.IsPositiveInfinity(_x) || float.IsNegativeInfinity(_x) || float.IsNaN(_x))
|
||||
@ -98,11 +52,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
|
||||
public override void Render(SKCanvas canvas, SKImageInfo canvasInfo, SKPath path, SKPaint paint)
|
||||
{
|
||||
var mainColor = MainColorProperty?.CurrentValue;
|
||||
var gradientColor = GradientColorProperty?.CurrentValue;
|
||||
var scale = ScaleProperty.CurrentValue;
|
||||
var mainColor = Properties.MainColor?.CurrentValue;
|
||||
var gradientColor = Properties.GradientColor?.CurrentValue;
|
||||
var scale = Properties.Scale.CurrentValue;
|
||||
var opacity = mainColor != null ? (float) Math.Round(mainColor.Value.Alpha / 255.0, 2, MidpointRounding.AwayFromZero) : 0;
|
||||
var hardness = 127 + HardnessProperty.CurrentValue;
|
||||
var hardness = 127 + Properties.Hardness.CurrentValue;
|
||||
|
||||
// Scale down the render path to avoid computing a value for every pixel
|
||||
var width = (int) Math.Floor(path.Bounds.Width * _renderScale);
|
||||
@ -122,10 +76,8 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
|
||||
var v = _noise.Evaluate(evalX, evalY, _z);
|
||||
var alpha = (byte) Math.Max(0, Math.Min(255, v * hardness));
|
||||
if (ColorTypeProperty.Value == ColorMappingType.Simple && mainColor != null)
|
||||
{
|
||||
if (Properties.ColorType.BaseValue == ColorMappingType.Simple && mainColor != null)
|
||||
_bitmap.SetPixel(x, y, new SKColor(mainColor.Value.Red, mainColor.Value.Green, mainColor.Value.Blue, (byte) (alpha * opacity)));
|
||||
}
|
||||
else if (gradientColor != null && _colorMap.Length == 101)
|
||||
{
|
||||
var color = _colorMap[(int) Math.Round(alpha / 255f * 100, MidpointRounding.AwayFromZero)];
|
||||
@ -141,9 +93,9 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
);
|
||||
|
||||
canvas.ClipPath(path);
|
||||
if (ColorTypeProperty.Value == ColorMappingType.Simple)
|
||||
if (Properties.ColorType.BaseValue == ColorMappingType.Simple)
|
||||
{
|
||||
using var backgroundShader = SKShader.CreateColor(SecondaryColorProperty.CurrentValue);
|
||||
using var backgroundShader = SKShader.CreateColor(Properties.SecondaryColor.CurrentValue);
|
||||
paint.Shader = backgroundShader;
|
||||
canvas.DrawRect(path.Bounds, paint);
|
||||
}
|
||||
@ -153,6 +105,17 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
canvas.DrawRect(path.Bounds, paint);
|
||||
}
|
||||
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
Properties.GradientColor.BaseValue.PropertyChanged += GradientColorChanged;
|
||||
CreateColorMap();
|
||||
}
|
||||
|
||||
private void GradientColorChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
CreateColorMap();
|
||||
}
|
||||
|
||||
|
||||
private void DetermineRenderScale()
|
||||
{
|
||||
@ -170,11 +133,11 @@ namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateColorMap(object sender, EventArgs e)
|
||||
private void CreateColorMap()
|
||||
{
|
||||
var colorMap = new SKColor[101];
|
||||
for (var i = 0; i < 101; i++)
|
||||
colorMap[i] = GradientColorProperty.Value.GetColor(i / 100f);
|
||||
colorMap[i] = Properties.GradientColor.BaseValue.GetColor(i / 100f);
|
||||
|
||||
_colorMap = colorMap;
|
||||
}
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Artemis.Core.Models.Profile;
|
||||
using Artemis.Core.Models.Profile.Colors;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Attributes;
|
||||
using Artemis.Core.Models.Profile.LayerProperties.Types;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Plugins.LayerBrushes.Noise
|
||||
{
|
||||
public class NoiseBrushProperties : LayerPropertyGroup
|
||||
{
|
||||
[PropertyDescription(Name = "Color mapping type", Description = "The way the noise is converted to colors")]
|
||||
public EnumLayerProperty<ColorMappingType> ColorType { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The main color of the noise")]
|
||||
public SKColorLayerProperty MainColor { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The secondary color of the noise")]
|
||||
public SKColorLayerProperty SecondaryColor { get; set; }
|
||||
|
||||
[PropertyDescription(Name = "Noise gradient map", Description = "The gradient the noise will map it's value to")]
|
||||
public ColorGradientLayerProperty GradientColor { get; set; }
|
||||
|
||||
|
||||
[PropertyDescription(Description = "The scale of the noise", MinInputValue = 0f, InputAffix = "%")]
|
||||
public SKSizeLayerProperty Scale { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The hardness of the noise, lower means there are gradients in the noise, higher means hard lines", MinInputValue = 0f, MaxInputValue = 2048f)]
|
||||
public FloatLayerProperty Hardness { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The speed at which the noise moves vertically and horizontally", MinInputValue = -64f, MaxInputValue = 64f)]
|
||||
public SKPointLayerProperty ScrollSpeed { get; set; }
|
||||
|
||||
[PropertyDescription(Description = "The speed at which the noise moves", MinInputValue = 0f, MaxInputValue = 64f)]
|
||||
public FloatLayerProperty AnimationSpeed { get; set; }
|
||||
|
||||
protected override void OnPropertiesInitialized()
|
||||
{
|
||||
// Populate defaults
|
||||
if (!MainColor.IsLoadedFromStorage)
|
||||
MainColor.BaseValue = new SKColor(255, 0, 0);
|
||||
if (!SecondaryColor.IsLoadedFromStorage)
|
||||
SecondaryColor.BaseValue = new SKColor(0, 0, 255);
|
||||
if (!GradientColor.IsLoadedFromStorage)
|
||||
{
|
||||
GradientColor.BaseValue = new ColorGradient();
|
||||
GradientColor.BaseValue.MakeFabulous();
|
||||
}
|
||||
|
||||
if (!Scale.IsLoadedFromStorage)
|
||||
Scale.BaseValue = new SKSize(100, 100);
|
||||
if (!Hardness.IsLoadedFromStorage)
|
||||
Hardness.BaseValue = 500f;
|
||||
if (!AnimationSpeed.IsLoadedFromStorage)
|
||||
AnimationSpeed.BaseValue = 25f;
|
||||
|
||||
ColorType.BaseValueChanged += ColorTypeOnBaseValueChanged;
|
||||
}
|
||||
|
||||
private void ColorTypeOnBaseValueChanged(object? sender, EventArgs e)
|
||||
{
|
||||
GradientColor.IsHidden = ColorType.BaseValue != ColorMappingType.Gradient;
|
||||
MainColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple;
|
||||
SecondaryColor.IsHidden = ColorType.BaseValue != ColorMappingType.Simple;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user