diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj
index 8869fac59..3a632ed07 100644
--- a/src/Artemis.Core/Artemis.Core.csproj
+++ b/src/Artemis.Core/Artemis.Core.csproj
@@ -56,7 +56,4 @@
..\..\..\RGB.NET\bin\netstandard2.0\RGB.NET.Groups.dll
-
-
-
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs
new file mode 100644
index 000000000..201aaefb4
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/Abstract/LayerProperty.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Artemis.Core.Utilities;
+
+namespace Artemis.Core.Models.Profile.LayerProperties.Abstract
+{
+ public abstract class LayerProperty
+ {
+ private List> _keyframes;
+
+ protected LayerProperty()
+ {
+ _keyframes = new List>();
+ }
+
+ public T BaseValue { get; set; }
+ public T CurrentValue { get; set; }
+ public IReadOnlyList> Keyframes => _keyframes.AsReadOnly();
+
+ ///
+ /// The total progress on the timeline
+ ///
+ public TimeSpan TimelineProgress { get; private set; }
+
+ ///
+ /// The current keyframe in the timeline
+ ///
+ public LayerPropertyKeyFrame CurrentKeyframe { get; protected set; }
+
+ ///
+ /// The next keyframe in the timeline
+ ///
+ public LayerPropertyKeyFrame 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs
new file mode 100644
index 000000000..d6eb245d4
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/Attributes/PropertyDescriptionAttribute.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Artemis.Core.Models.Profile.LayerProperties.Attributes
+{
+ public class PropertyDescriptionAttribute : Attribute
+ {
+ }
+}
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
index 184415ff9..78edeed6c 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
@@ -3,6 +3,7 @@ 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;
@@ -28,7 +29,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
CanUseKeyframes = true;
InputStepSize = 1;
- // This can only be null if accessed internally
+ // This can only be null if accessed internally, all public ways of creating enforce a plugin info
if (PluginInfo == null)
PluginInfo = Constants.CorePluginInfo;
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs
new file mode 100644
index 000000000..5fa765240
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/FloatLayerProperty.cs
@@ -0,0 +1,13 @@
+using Artemis.Core.Models.Profile.LayerProperties.Abstract;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class FloatLayerProperty : LayerProperty
+ {
+ protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
+ {
+ var diff = NextKeyframe.Value - CurrentKeyframe.Value;
+ CurrentValue = CurrentKeyframe.Value + diff * keyframeProgressEased;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs
new file mode 100644
index 000000000..7f315a1e3
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/IntLayerProperty.cs
@@ -0,0 +1,14 @@
+using System;
+using Artemis.Core.Models.Profile.LayerProperties.Abstract;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class IntLayerProperty : LayerProperty
+ {
+ protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
+ {
+ var diff = NextKeyframe.Value - CurrentKeyframe.Value;
+ CurrentValue = (int) Math.Round(CurrentKeyframe.Value + diff * keyframeProgressEased, MidpointRounding.AwayFromZero);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
new file mode 100644
index 000000000..cb94d88e6
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerPropertyKeyFrame.cs
@@ -0,0 +1,33 @@
+using System;
+using Artemis.Core.Utilities;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class LayerPropertyKeyFrame
+ {
+ private TimeSpan _position;
+
+ public LayerPropertyKeyFrame(LayerProperty layerProperty, T value, TimeSpan position, Easings.Functions easingFunction)
+ {
+ _position = position;
+ Value = value;
+ LayerProperty = layerProperty;
+ EasingFunction = easingFunction;
+ }
+
+ public LayerProperty LayerProperty { get; set; }
+ public T Value { get; set; }
+
+ public TimeSpan Position
+ {
+ get => _position;
+ set
+ {
+ _position = value;
+ LayerProperty.SortKeyframes();
+ }
+ }
+
+ public Easings.Functions EasingFunction { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs
new file mode 100644
index 000000000..78635385a
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKColorLayerProperty.cs
@@ -0,0 +1,29 @@
+using System;
+using Artemis.Core.Models.Profile.LayerProperties.Abstract;
+using SkiaSharp;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class SKColorLayerProperty : LayerProperty
+ {
+ protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
+ {
+ var redDiff = NextKeyframe.Value.Red - CurrentKeyframe.Value.Red;
+ var greenDiff = NextKeyframe.Value.Green - CurrentKeyframe.Value.Green;
+ var blueDiff = NextKeyframe.Value.Blue - CurrentKeyframe.Value.Blue;
+ var alphaDiff = NextKeyframe.Value.Alpha - CurrentKeyframe.Value.Alpha;
+
+ CurrentValue = new SKColor(
+ ClampToByte(CurrentKeyframe.Value.Red + redDiff * keyframeProgressEased),
+ ClampToByte(CurrentKeyframe.Value.Green + greenDiff * keyframeProgressEased),
+ ClampToByte(CurrentKeyframe.Value.Blue + blueDiff * keyframeProgressEased),
+ ClampToByte(CurrentKeyframe.Value.Alpha + alphaDiff * keyframeProgressEased)
+ );
+ }
+
+ private static byte ClampToByte(float value)
+ {
+ return (byte) Math.Max(0, Math.Min(255, value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs
new file mode 100644
index 000000000..a11719034
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKPointLayerProperty.cs
@@ -0,0 +1,15 @@
+using Artemis.Core.Models.Profile.LayerProperties.Abstract;
+using SkiaSharp;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class SKPointLayerProperty : LayerProperty
+ {
+ protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
+ {
+ var xDiff = NextKeyframe.Value.X - CurrentKeyframe.Value.X;
+ var yDiff = NextKeyframe.Value.Y - CurrentKeyframe.Value.Y;
+ CurrentValue = new SKPoint(CurrentKeyframe.Value.X + xDiff * keyframeProgressEased, CurrentKeyframe.Value.Y + yDiff * keyframeProgressEased);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs
new file mode 100644
index 000000000..46fceec37
--- /dev/null
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/SKSizeLayerProperty.cs
@@ -0,0 +1,15 @@
+using Artemis.Core.Models.Profile.LayerProperties.Abstract;
+using SkiaSharp;
+
+namespace Artemis.Core.Models.Profile.LayerProperties
+{
+ public class SKSizeLayerProperty : LayerProperty
+ {
+ protected override void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased)
+ {
+ var widthDiff = NextKeyframe.Value.Width - CurrentKeyframe.Value.Width;
+ var heightDiff = NextKeyframe.Value.Height - CurrentKeyframe.Value.Height;
+ CurrentValue = new SKSize(CurrentKeyframe.Value.Width + widthDiff * keyframeProgressEased, CurrentKeyframe.Value.Height + heightDiff * keyframeProgressEased);
+ }
+ }
+}
\ No newline at end of file