diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj
index d25e4455b..d9240f12b 100644
--- a/src/Artemis.Core/Artemis.Core.csproj
+++ b/src/Artemis.Core/Artemis.Core.csproj
@@ -212,6 +212,7 @@
+
diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs
index 75272488b..4fd1313b6 100644
--- a/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs
+++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/FloatKeyframeEngine.cs
@@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List CompatibleTypes { get; } = new List {typeof(float)};
- public override object GetCurrentValue()
+ protected override object GetInterpolatedValue()
{
- // Nothing fancy for now, just return the base value
- return ((LayerProperty) LayerProperty).Value;
+ var currentKeyframe = (Keyframe) CurrentKeyframe;
+ var nextKeyframe = (Keyframe) NextKeyframe;
+
+ var diff = nextKeyframe.Value - currentKeyframe.Value;
+ return currentKeyframe.Value + diff * KeyframeProgress;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs
index e387e071e..31b202643 100644
--- a/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs
+++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/IntKeyframeEngine.cs
@@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List CompatibleTypes { get; } = new List {typeof(int)};
- public override object GetCurrentValue()
+ protected override object GetInterpolatedValue()
{
- // Nothing fancy for now, just return the base value
- return ((LayerProperty) LayerProperty).Value;
+ var currentKeyframe = (Keyframe) CurrentKeyframe;
+ var nextKeyframe = (Keyframe) NextKeyframe;
+
+ var diff = nextKeyframe.Value - currentKeyframe.Value;
+ return currentKeyframe.Value + diff * KeyframeProgress;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs
index a6a6d00d1..c9793812f 100644
--- a/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs
+++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/KeyframeEngine.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile.LayerProperties;
@@ -15,12 +16,27 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
///
/// The layer property this keyframe engine applies to.
///
- public BaseLayerProperty LayerProperty { get; set; }
+ public BaseLayerProperty LayerProperty { get; private set; }
///
- /// The keyframe progress in milliseconds.
+ /// The total progress
///
- public double Progress { get; set; }
+ public TimeSpan Progress { get; private set; }
+
+ ///
+ /// The progress from the current keyframe to the next 0 to 1
+ ///
+ public float KeyframeProgress { get; private set; }
+
+ ///
+ /// The current keyframe
+ ///
+ public BaseKeyframe CurrentKeyframe { get; private set; }
+
+ ///
+ /// The next keyframe
+ ///
+ public BaseKeyframe NextKeyframe { get; private set; }
///
/// The types this keyframe engine supports.
@@ -39,6 +55,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
throw new ArtemisCoreException($"This property engine does not support the provided type {layerProperty.Type.Name}");
LayerProperty = layerProperty;
+ LayerProperty.KeyframeEngine = this;
Initialized = true;
}
@@ -51,15 +68,52 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
if (!Initialized)
return;
- Progress += deltaTime;
+ Progress = Progress.Add(TimeSpan.FromMilliseconds(deltaTime));
+
+ // TODO Keep them sorted somewhere else, iterating all keyframes multiple times sucks
+ var sortedKeyframes = LayerProperty.UntypedKeyframes.ToList().OrderBy(k => k.Position).ToList();
+
+ CurrentKeyframe = sortedKeyframes.LastOrDefault(k => k.Position <= Progress);
+ NextKeyframe = sortedKeyframes.FirstOrDefault(k => k.Position > Progress);
+ if (CurrentKeyframe == null)
+ KeyframeProgress = 0;
+ else if (NextKeyframe == null)
+ KeyframeProgress = 1;
+ else
+ {
+ var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
+ KeyframeProgress = (float) ((Progress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
+ }
+
+ // TODO Apply easing and store it separately
// LayerProperty determines what's next: reset, stop, continue
}
+ ///
+ /// Overrides the engine's progress to the provided value
+ ///
+ ///
+ public void OverrideProgress(TimeSpan progress)
+ {
+ Progress = TimeSpan.Zero;
+ Update(progress.TotalMilliseconds);
+ }
+
///
/// Gets the current value, if the progress is in between two keyframes the value will be interpolated
///
///
- public abstract object GetCurrentValue();
+ public object GetCurrentValue()
+ {
+ if (CurrentKeyframe == null)
+ return LayerProperty.BaseValue;
+ if (NextKeyframe == null)
+ return CurrentKeyframe.BaseValue;
+
+ return GetInterpolatedValue();
+ }
+
+ protected abstract object GetInterpolatedValue();
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs
index 15801bf3c..b46e34bea 100644
--- a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs
+++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKPointKeyframeEngine.cs
@@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List CompatibleTypes { get; } = new List {typeof(SKPoint)};
- public override object GetCurrentValue()
+ protected override object GetInterpolatedValue()
{
- // Nothing fancy for now, just return the base value
- return ((LayerProperty) LayerProperty).Value;
+ var currentKeyframe = (Keyframe) CurrentKeyframe;
+ var nextKeyframe = (Keyframe) NextKeyframe;
+
+ var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X;
+ var yDiff = nextKeyframe.Value.Y - currentKeyframe.Value.Y;
+ return new SKPoint(currentKeyframe.Value.X + xDiff * KeyframeProgress, currentKeyframe.Value.Y + yDiff * KeyframeProgress);
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs
index 7bc7a1f7a..59c4c5b97 100644
--- a/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs
+++ b/src/Artemis.Core/Models/Profile/KeyframeEngines/SKSizeKeyframeEngine.cs
@@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List CompatibleTypes { get; } = new List {typeof(SKSize)};
- public override object GetCurrentValue()
+ protected override object GetInterpolatedValue()
{
- // Nothing fancy for now, just return the base value
- return ((LayerProperty) LayerProperty).Value;
+ var currentKeyframe = (Keyframe)CurrentKeyframe;
+ var nextKeyframe = (Keyframe)NextKeyframe;
+
+ var widthDiff = nextKeyframe.Value.Width - currentKeyframe.Value.Width;
+ var heightDiff = nextKeyframe.Value.Height - currentKeyframe.Value.Height;
+ return new SKSize(currentKeyframe.Value.Width + widthDiff * KeyframeProgress, currentKeyframe.Value.Height + heightDiff * KeyframeProgress);
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs
index ddb571051..a7a36f52b 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseKeyframe.cs
@@ -4,7 +4,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
{
public class BaseKeyframe
{
- protected internal BaseKeyframe(Layer layer, BaseLayerProperty property)
+ protected BaseKeyframe(Layer layer, BaseLayerProperty property)
{
Layer = layer;
BaseProperty = property;
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
index d5a3fc074..fef95bb8d 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/BaseLayerProperty.cs
@@ -12,7 +12,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
{
private object _baseValue;
- protected internal BaseLayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description, Type type)
+ protected BaseLayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description, Type type)
{
Layer = layer;
Parent = parent;
@@ -125,7 +125,37 @@ namespace Artemis.Core.Models.Profile.LayerProperties
BaseKeyframes.Clear();
foreach (var keyframeEntity in propertyEntity.KeyframeEntities)
- BaseKeyframes.Add(new BaseKeyframe(Layer, this) {BaseValue = DeserializePropertyValue(keyframeEntity.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.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
+ BaseKeyframes.Add(keyframe);
+ }
+ }
+
+ ///
+ /// Creates a new keyframe for this base property without knowing the type
+ ///
+ ///
+ public BaseKeyframe CreateNewKeyframe(TimeSpan 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 = position;
+ keyframe.BaseValue = BaseValue;
+ BaseKeyframes.Add(keyframe);
+
+ return keyframe;
+ }
+
+ ///
+ /// Removes all keyframes from the property.
+ ///
+ public void ClearKeyframes()
+ {
+ BaseKeyframes.Clear();
}
public override string ToString()
diff --git a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
index 70a3530d2..59fe67272 100644
--- a/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
+++ b/src/Artemis.Core/Models/Profile/LayerProperties/LayerProperty.cs
@@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
set => BaseValue = value;
}
+ ///
+ /// The value of the property with keyframes applied
+ ///
+ public T CurrentValue => (T) KeyframeEngine.GetCurrentValue();
+
///
/// A list of keyframes defining different values of the property in time, this list contains the strongly typed
///
@@ -43,20 +48,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
}
///
- /// Removes all keyframes from the property.
- ///
- public void ClearKeyframes()
- {
- BaseKeyframes.Clear();
- }
-
- ///
- /// Gets the current value using the keyframes
+ /// Gets the current value using the keyframes
///
///
public T GetCurrentValue()
{
- if (KeyframeEngine == null)
+ if (KeyframeEngine == null || !Keyframes.Any())
return Value;
return (T) KeyframeEngine.GetCurrentValue();
diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs
index 074908089..98b7654fc 100644
--- a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs
+++ b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs
@@ -60,7 +60,7 @@ namespace Artemis.Core.Models.Profile.LayerShapes
Layer.SizeProperty.Value = new SKSize((float) (100f / width * rect.Width) / 100f, (float) (100f / height * rect.Height) / 100f);
// TODO: Update keyframes
- CalculateRenderProperties(Layer.PositionProperty.Value, Layer.SizeProperty.Value);
+ CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue);
}
public SKRect GetUnscaledRectangle()
@@ -74,10 +74,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes
var height = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.Y + l.RgbLed.AbsoluteLedRectangle.Size.Height) - y;
return SKRect.Create(
- (float) (x + width * Layer.PositionProperty.Value.X),
- (float) (y + height * Layer.PositionProperty.Value.Y),
- (float) (width * Layer.SizeProperty.Value.Width),
- (float) (height * Layer.SizeProperty.Value.Height)
+ (float) (x + width * Layer.PositionProperty.CurrentValue.X),
+ (float) (y + height * Layer.PositionProperty.CurrentValue.Y),
+ (float) (width * Layer.SizeProperty.CurrentValue.Width),
+ (float) (height * Layer.SizeProperty.CurrentValue.Height)
);
}
}
diff --git a/src/Artemis.Core/Ninject/CoreModule.cs b/src/Artemis.Core/Ninject/CoreModule.cs
index 521866a3c..66ea2d9d0 100644
--- a/src/Artemis.Core/Ninject/CoreModule.cs
+++ b/src/Artemis.Core/Ninject/CoreModule.cs
@@ -1,5 +1,6 @@
using System.IO;
using Artemis.Core.Exceptions;
+using Artemis.Core.Models.Profile.KeyframeEngines;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Repositories.Interfaces;
@@ -50,6 +51,15 @@ namespace Artemis.Core.Ninject
.Configure(c => c.InSingletonScope());
});
+ // Bind all keyframe engines
+ Kernel.Bind(x =>
+ {
+ x.FromAssemblyContaining()
+ .SelectAllClasses()
+ .InheritedFrom()
+ .BindAllBaseClasses();
+ });
+
Kernel.Bind().ToProvider();
Kernel.Bind().ToProvider();
}
diff --git a/src/Artemis.Core/Services/Interfaces/ILayerService.cs b/src/Artemis.Core/Services/Interfaces/ILayerService.cs
index 73c4a6ef4..77e0cba6b 100644
--- a/src/Artemis.Core/Services/Interfaces/ILayerService.cs
+++ b/src/Artemis.Core/Services/Interfaces/ILayerService.cs
@@ -1,4 +1,6 @@
using Artemis.Core.Models.Profile;
+using Artemis.Core.Models.Profile.KeyframeEngines;
+using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerBrush;
namespace Artemis.Core.Services.Interfaces
@@ -14,5 +16,19 @@ namespace Artemis.Core.Services.Interfaces
/// JSON settings to be deserialized and injected into the layer brush
///
LayerBrush InstantiateLayerBrush(Layer layer, LayerBrushDescriptor brushDescriptor, string settings = null);
+
+ ///
+ /// Instantiates and adds a compatible to the provided
+ ///
+ /// The layer property to apply the keyframe engine to.
+ /// The resulting keyframe engine, if a compatible engine was found.
+ KeyframeEngine InstantiateKeyframeEngine(LayerProperty layerProperty);
+
+ ///
+ /// Instantiates and adds a compatible to the provided .
+ ///
+ /// The layer property to apply the keyframe engine to.
+ /// The resulting keyframe engine, if a compatible engine was found.
+ KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty);
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/LayerService.cs b/src/Artemis.Core/Services/LayerService.cs
index 17f52dc5a..1bbc10eba 100644
--- a/src/Artemis.Core/Services/LayerService.cs
+++ b/src/Artemis.Core/Services/LayerService.cs
@@ -1,7 +1,10 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Artemis.Core.Models.Profile;
+using Artemis.Core.Models.Profile.KeyframeEngines;
+using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Plugins.LayerBrush;
using Artemis.Core.Services.Interfaces;
@@ -74,6 +77,24 @@ namespace Artemis.Core.Services
return layerElement;
}
+ public KeyframeEngine InstantiateKeyframeEngine(LayerProperty layerProperty)
+ {
+ return InstantiateKeyframeEngine((BaseLayerProperty) layerProperty);
+ }
+
+ public KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty)
+ {
+ // 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>();
+ 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, LayerBrush layerElement)
{
var brush = layer.LayerBrush;
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index bc661c5c7..1f84a08a5 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -70,6 +70,7 @@ namespace Artemis.Core.Services.Storage
if (_surfaceService.ActiveSurface != null)
profile.PopulateLeds(_surfaceService.ActiveSurface);
+
return profile;
}
@@ -78,7 +79,10 @@ namespace Artemis.Core.Services.Storage
{
module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
if (profile != null)
+ {
InstantiateProfileLayerBrushes(profile);
+ InstantiateProfileKeyframeEngines(profile);
+ }
}
public void DeleteProfile(Profile profile)
@@ -118,6 +122,15 @@ namespace Artemis.Core.Services.Storage
}
}
+ private void InstantiateProfileKeyframeEngines(Profile profile)
+ {
+ // Only instantiate engines for properties without an existing engine instance
+ foreach (var layerProperty in profile.GetAllLayers().SelectMany(l => l.Properties).Where(p => p.KeyframeEngine == null))
+ {
+ _layerService.InstantiateKeyframeEngine(layerProperty);
+ }
+ }
+
private void ActiveProfilesPopulateLeds(ArtemisSurface surface)
{
var profileModules = _pluginService.GetPluginsOfType();
@@ -132,6 +145,13 @@ namespace Artemis.Core.Services.Storage
InstantiateProfileLayerBrushes(profileModule.ActiveProfile);
}
+ private void ActiveProfilesInstantiateKeyframeEngines()
+ {
+ var profileModules = _pluginService.GetPluginsOfType();
+ foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList())
+ InstantiateProfileKeyframeEngines(profileModule.ActiveProfile);
+ }
+
#region Event handlers
private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e)
@@ -148,7 +168,10 @@ namespace Artemis.Core.Services.Storage
private void OnPluginLoaded(object sender, PluginEventArgs e)
{
if (e.PluginInfo.Instance is LayerBrushProvider)
+ {
ActiveProfilesInstantiateProfileLayerBrushes();
+ ActiveProfilesInstantiateKeyframeEngines();
+ }
}
#endregion
diff --git a/src/Artemis.Core/Utilities/Easings.cs b/src/Artemis.Core/Utilities/Easings.cs
new file mode 100644
index 000000000..86e9448a9
--- /dev/null
+++ b/src/Artemis.Core/Utilities/Easings.cs
@@ -0,0 +1,398 @@
+using System;
+
+namespace Artemis.Core.Utilities
+{
+ public static class Easings
+ {
+ ///
+ /// Easing Functions enumeration
+ ///
+ public enum Functions
+ {
+ Linear,
+ QuadraticEaseIn,
+ QuadraticEaseOut,
+ QuadraticEaseInOut,
+ CubicEaseIn,
+ CubicEaseOut,
+ CubicEaseInOut,
+ QuarticEaseIn,
+ QuarticEaseOut,
+ QuarticEaseInOut,
+ QuinticEaseIn,
+ QuinticEaseOut,
+ QuinticEaseInOut,
+ SineEaseIn,
+ SineEaseOut,
+ SineEaseInOut,
+ CircularEaseIn,
+ CircularEaseOut,
+ CircularEaseInOut,
+ ExponentialEaseIn,
+ ExponentialEaseOut,
+ ExponentialEaseInOut,
+ ElasticEaseIn,
+ ElasticEaseOut,
+ ElasticEaseInOut,
+ BackEaseIn,
+ BackEaseOut,
+ BackEaseInOut,
+ BounceEaseIn,
+ BounceEaseOut,
+ BounceEaseInOut
+ }
+
+ ///
+ /// Constant Pi.
+ ///
+ private const double PI = Math.PI;
+
+ ///
+ /// Constant Pi / 2.
+ ///
+ private const double HALFPI = Math.PI / 2.0;
+
+ ///
+ /// Interpolate using the specified function.
+ ///
+ public static double Interpolate(double p, Functions function)
+ {
+ switch (function)
+ {
+ default:
+ case Functions.Linear: return Linear(p);
+ case Functions.QuadraticEaseOut: return QuadraticEaseOut(p);
+ case Functions.QuadraticEaseIn: return QuadraticEaseIn(p);
+ case Functions.QuadraticEaseInOut: return QuadraticEaseInOut(p);
+ case Functions.CubicEaseIn: return CubicEaseIn(p);
+ case Functions.CubicEaseOut: return CubicEaseOut(p);
+ case Functions.CubicEaseInOut: return CubicEaseInOut(p);
+ case Functions.QuarticEaseIn: return QuarticEaseIn(p);
+ case Functions.QuarticEaseOut: return QuarticEaseOut(p);
+ case Functions.QuarticEaseInOut: return QuarticEaseInOut(p);
+ case Functions.QuinticEaseIn: return QuinticEaseIn(p);
+ case Functions.QuinticEaseOut: return QuinticEaseOut(p);
+ case Functions.QuinticEaseInOut: return QuinticEaseInOut(p);
+ case Functions.SineEaseIn: return SineEaseIn(p);
+ case Functions.SineEaseOut: return SineEaseOut(p);
+ case Functions.SineEaseInOut: return SineEaseInOut(p);
+ case Functions.CircularEaseIn: return CircularEaseIn(p);
+ case Functions.CircularEaseOut: return CircularEaseOut(p);
+ case Functions.CircularEaseInOut: return CircularEaseInOut(p);
+ case Functions.ExponentialEaseIn: return ExponentialEaseIn(p);
+ case Functions.ExponentialEaseOut: return ExponentialEaseOut(p);
+ case Functions.ExponentialEaseInOut: return ExponentialEaseInOut(p);
+ case Functions.ElasticEaseIn: return ElasticEaseIn(p);
+ case Functions.ElasticEaseOut: return ElasticEaseOut(p);
+ case Functions.ElasticEaseInOut: return ElasticEaseInOut(p);
+ case Functions.BackEaseIn: return BackEaseIn(p);
+ case Functions.BackEaseOut: return BackEaseOut(p);
+ case Functions.BackEaseInOut: return BackEaseInOut(p);
+ case Functions.BounceEaseIn: return BounceEaseIn(p);
+ case Functions.BounceEaseOut: return BounceEaseOut(p);
+ case Functions.BounceEaseInOut: return BounceEaseInOut(p);
+ }
+ }
+
+ ///
+ /// Modeled after the line y = x
+ ///
+ public static double Linear(double p)
+ {
+ return p;
+ }
+
+ ///
+ /// Modeled after the parabola y = x^2
+ ///
+ public static double QuadraticEaseIn(double p)
+ {
+ return p * p;
+ }
+
+ ///
+ /// Modeled after the parabola y = -x^2 + 2x
+ ///
+ public static double QuadraticEaseOut(double p)
+ {
+ return -(p * (p - 2));
+ }
+
+ ///
+ /// Modeled after the piecewise quadratic
+ /// y = (1/2)((2x)^2) ; [0, 0.5)
+ /// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
+ ///
+ public static double QuadraticEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 2 * p * p;
+ return -2 * p * p + 4 * p - 1;
+ }
+
+ ///
+ /// Modeled after the cubic y = x^3
+ ///
+ public static double CubicEaseIn(double p)
+ {
+ return p * p * p;
+ }
+
+ ///
+ /// Modeled after the cubic y = (x - 1)^3 + 1
+ ///
+ public static double CubicEaseOut(double p)
+ {
+ var f = p - 1;
+ return f * f * f + 1;
+ }
+
+ ///
+ /// Modeled after the piecewise cubic
+ /// y = (1/2)((2x)^3) ; [0, 0.5)
+ /// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
+ ///
+ public static double CubicEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 4 * p * p * p;
+ var f = 2 * p - 2;
+ return 0.5 * f * f * f + 1;
+ }
+
+ ///
+ /// Modeled after the quartic x^4
+ ///
+ public static double QuarticEaseIn(double p)
+ {
+ return p * p * p * p;
+ }
+
+ ///
+ /// Modeled after the quartic y = 1 - (x - 1)^4
+ ///
+ public static double QuarticEaseOut(double p)
+ {
+ var f = p - 1;
+ return f * f * f * (1 - p) + 1;
+ }
+
+ ///
+ // Modeled after the piecewise quartic
+ // y = (1/2)((2x)^4) ; [0, 0.5)
+ // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
+ ///
+ public static double QuarticEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 8 * p * p * p * p;
+ var f = p - 1;
+ return -8 * f * f * f * f + 1;
+ }
+
+ ///
+ /// Modeled after the quintic y = x^5
+ ///
+ public static double QuinticEaseIn(double p)
+ {
+ return p * p * p * p * p;
+ }
+
+ ///
+ /// Modeled after the quintic y = (x - 1)^5 + 1
+ ///
+ public static double QuinticEaseOut(double p)
+ {
+ var f = p - 1;
+ return f * f * f * f * f + 1;
+ }
+
+ ///
+ /// Modeled after the piecewise quintic
+ /// y = (1/2)((2x)^5) ; [0, 0.5)
+ /// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
+ ///
+ public static double QuinticEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 16 * p * p * p * p * p;
+ var f = 2 * p - 2;
+ return 0.5 * f * f * f * f * f + 1;
+ }
+
+ ///
+ /// Modeled after quarter-cycle of sine wave
+ ///
+ public static double SineEaseIn(double p)
+ {
+ return Math.Sin((p - 1) * HALFPI) + 1;
+ }
+
+ ///
+ /// Modeled after quarter-cycle of sine wave (different phase)
+ ///
+ public static double SineEaseOut(double p)
+ {
+ return Math.Sin(p * HALFPI);
+ }
+
+ ///
+ /// Modeled after half sine wave
+ ///
+ public static double SineEaseInOut(double p)
+ {
+ return 0.5 * (1 - Math.Cos(p * PI));
+ }
+
+ ///
+ /// Modeled after shifted quadrant IV of unit circle
+ ///
+ public static double CircularEaseIn(double p)
+ {
+ return 1 - Math.Sqrt(1 - p * p);
+ }
+
+ ///
+ /// Modeled after shifted quadrant II of unit circle
+ ///
+ public static double CircularEaseOut(double p)
+ {
+ return Math.Sqrt((2 - p) * p);
+ }
+
+ ///
+ /// Modeled after the piecewise circular function
+ /// y = (1/2)(1 - Math.Sqrt(1 - 4x^2)) ; [0, 0.5)
+ /// y = (1/2)(Math.Sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
+ ///
+ public static double CircularEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 0.5 * (1 - Math.Sqrt(1 - 4 * (p * p)));
+ return 0.5 * (Math.Sqrt(-(2 * p - 3) * (2 * p - 1)) + 1);
+ }
+
+ ///
+ /// Modeled after the exponential function y = 2^(10(x - 1))
+ ///
+ public static double ExponentialEaseIn(double p)
+ {
+ return p == 0.0 ? p : Math.Pow(2, 10 * (p - 1));
+ }
+
+ ///
+ /// Modeled after the exponential function y = -2^(-10x) + 1
+ ///
+ public static double ExponentialEaseOut(double p)
+ {
+ return p == 1.0 ? p : 1 - Math.Pow(2, -10 * p);
+ }
+
+ ///
+ /// Modeled after the piecewise exponential
+ /// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
+ /// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
+ ///
+ public static double ExponentialEaseInOut(double p)
+ {
+ if (p == 0.0 || p == 1.0) return p;
+
+ if (p < 0.5)
+ return 0.5 * Math.Pow(2, 20 * p - 10);
+ return -0.5 * Math.Pow(2, -20 * p + 10) + 1;
+ }
+
+ ///
+ /// Modeled after the damped sine wave y = sin(13pi/2*x)*Math.Pow(2, 10 * (x - 1))
+ ///
+ public static double ElasticEaseIn(double p)
+ {
+ return Math.Sin(13 * HALFPI * p) * Math.Pow(2, 10 * (p - 1));
+ }
+
+ ///
+ /// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*Math.Pow(2, -10x) + 1
+ ///
+ public static double ElasticEaseOut(double p)
+ {
+ return Math.Sin(-13 * HALFPI * (p + 1)) * Math.Pow(2, -10 * p) + 1;
+ }
+
+ ///
+ /// Modeled after the piecewise exponentially-damped sine wave:
+ /// y = (1/2)*sin(13pi/2*(2*x))*Math.Pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
+ /// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*Math.Pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
+ ///
+ public static double ElasticEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 0.5 * Math.Sin(13 * HALFPI * (2 * p)) * Math.Pow(2, 10 * (2 * p - 1));
+ return 0.5 * (Math.Sin(-13 * HALFPI * (2 * p - 1 + 1)) * Math.Pow(2, -10 * (2 * p - 1)) + 2);
+ }
+
+ ///
+ /// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
+ ///
+ public static double BackEaseIn(double p)
+ {
+ return p * p * p - p * Math.Sin(p * PI);
+ }
+
+ ///
+ /// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
+ ///
+ public static double BackEaseOut(double p)
+ {
+ var f = 1 - p;
+ return 1 - (f * f * f - f * Math.Sin(f * PI));
+ }
+
+ ///
+ /// Modeled after the piecewise overshooting cubic function:
+ /// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
+ /// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
+ ///
+ public static double BackEaseInOut(double p)
+ {
+ if (p < 0.5)
+ {
+ var f = 2 * p;
+ return 0.5 * (f * f * f - f * Math.Sin(f * PI));
+ }
+ else
+ {
+ var f = 1 - (2 * p - 1);
+ return 0.5 * (1 - (f * f * f - f * Math.Sin(f * PI))) + 0.5;
+ }
+ }
+
+ ///
+ ///
+ public static double BounceEaseIn(double p)
+ {
+ return 1 - BounceEaseOut(1 - p);
+ }
+
+ ///
+ ///
+ public static double BounceEaseOut(double p)
+ {
+ if (p < 4 / 11.0)
+ return 121 * p * p / 16.0;
+ if (p < 8 / 11.0)
+ return 363 / 40.0 * p * p - 99 / 10.0 * p + 17 / 5.0;
+ if (p < 9 / 10.0)
+ return 4356 / 361.0 * p * p - 35442 / 1805.0 * p + 16061 / 1805.0;
+ return 54 / 5.0 * p * p - 513 / 25.0 * p + 268 / 25.0;
+ }
+
+ ///
+ ///
+ public static double BounceEaseInOut(double p)
+ {
+ if (p < 0.5)
+ return 0.5 * BounceEaseIn(p * 2);
+ return 0.5 * BounceEaseOut(p * 2 - 1) + 0.5;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Ninject/Factories/IViewModelFactory.cs b/src/Artemis.UI/Ninject/Factories/IViewModelFactory.cs
index 7ef5d016b..74de4bf92 100644
--- a/src/Artemis.UI/Ninject/Factories/IViewModelFactory.cs
+++ b/src/Artemis.UI/Ninject/Factories/IViewModelFactory.cs
@@ -5,6 +5,8 @@ using Artemis.Core.Plugins.Abstract;
using Artemis.UI.Screens.Module;
using Artemis.UI.Screens.Module.ProfileEditor;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties;
+using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree;
+using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline;
using Artemis.UI.Screens.Module.ProfileEditor.ProfileTree.TreeItem;
using Artemis.UI.Screens.Module.ProfileEditor.Visualization;
using Artemis.UI.Screens.Settings.Tabs.Devices;
@@ -50,4 +52,14 @@ namespace Artemis.UI.Ninject.Factories
{
LayerPropertyViewModel Create(BaseLayerProperty layerProperty, LayerPropertyViewModel parent);
}
+
+ public interface IPropertyTreeViewModelFactory : IViewModelFactory
+ {
+ PropertyTreeViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
+ }
+
+ public interface IPropertyTimelineViewModelFactory : IViewModelFactory
+ {
+ PropertyTimelineViewModel Create(LayerPropertiesViewModel layerPropertiesViewModel);
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ModuleRootView.xaml b/src/Artemis.UI/Screens/Module/ModuleRootView.xaml
index 2f4034e6c..12b89d48e 100644
--- a/src/Artemis.UI/Screens/Module/ModuleRootView.xaml
+++ b/src/Artemis.UI/Screens/Module/ModuleRootView.xaml
@@ -18,7 +18,7 @@
-
+
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
index 7ce4bd527..3f9535ed2 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs
@@ -15,29 +15,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
private readonly ILayerPropertyViewModelFactory _layerPropertyViewModelFactory;
private readonly IProfileEditorService _profileEditorService;
- public LayerPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyViewModelFactory layerPropertyViewModelFactory)
+ public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
+ ILayerPropertyViewModelFactory layerPropertyViewModelFactory,
+ IPropertyTreeViewModelFactory propertyTreeViewModelFactory,
+ IPropertyTimelineViewModelFactory propertyTimelineViewModelFactory)
{
_profileEditorService = profileEditorService;
_layerPropertyViewModelFactory = layerPropertyViewModelFactory;
- CurrentTime = TimeSpan.Zero;
PixelsPerSecond = 1;
- PropertyTree = new PropertyTreeViewModel(this);
- PropertyTimeline = new PropertyTimelineViewModel(this);
+ PropertyTree = propertyTreeViewModelFactory.Create(this);
+ PropertyTimeline = propertyTimelineViewModelFactory.Create(this);
PopulateProperties();
_profileEditorService.SelectedProfileElementChanged += (sender, args) => PopulateProperties();
- }
-
- public TimeSpan CurrentTime
- {
- get => _currentTime;
- set
- {
- _currentTime = value;
- OnCurrentTimeChanged();
- }
+ _profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
}
public string FormattedCurrentTime
@@ -45,10 +38,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
get
{
if (PixelsPerSecond > 200)
- return $"{Math.Floor(CurrentTime.TotalSeconds):00}.{CurrentTime.Milliseconds:000}";
+ return $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
if (PixelsPerSecond > 60)
- return $"{Math.Floor(CurrentTime.TotalSeconds):00}.{CurrentTime.Milliseconds:000}";
- return $"{Math.Floor(CurrentTime.TotalMinutes):0}:{CurrentTime.Seconds:00}";
+ return $"{Math.Floor(_profileEditorService.CurrentTime.TotalSeconds):00}.{_profileEditorService.CurrentTime.Milliseconds:000}";
+ return $"{Math.Floor(_profileEditorService.CurrentTime.TotalMinutes):0}:{_profileEditorService.CurrentTime.Seconds:00}";
}
}
@@ -64,8 +57,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
public Thickness TimeCaretPosition
{
- get => new Thickness(CurrentTime.TotalSeconds * PixelsPerSecond, 0, 0, 0);
- set => CurrentTime = TimeSpan.FromSeconds(value.Left / PixelsPerSecond);
+ get => new Thickness(_profileEditorService.CurrentTime.TotalSeconds * PixelsPerSecond, 0, 0, 0);
+ set => _profileEditorService.CurrentTime = TimeSpan.FromSeconds(value.Left / PixelsPerSecond);
}
public PropertyTreeViewModel PropertyTree { get; set; }
@@ -91,15 +84,21 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
}
}
+ private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e)
+ {
+ NotifyOfPropertyChange(() => FormattedCurrentTime);
+ NotifyOfPropertyChange(() => TimeCaretPosition);
+ }
+
#region Caret movement
private double _caretStartMouseStartOffset;
private bool _mouseOverCaret;
private int _pixelsPerSecond;
- private TimeSpan _currentTime;
public void RightGridMouseDown(object sender, MouseButtonEventArgs e)
{
+ // TODO Preserve mouse offset
_caretStartMouseStartOffset = e.GetPosition((IInputElement) sender).X - TimeCaretPosition.Left;
}
@@ -124,14 +123,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
#region Events
- public event EventHandler CurrentTimeChanged;
public event EventHandler PixelsPerSecondChanged;
- protected virtual void OnCurrentTimeChanged()
- {
- CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
- }
-
protected virtual void OnPixelsPerSecondChanged()
{
PixelsPerSecondChanged?.Invoke(this, EventArgs.Empty);
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs
index 37eb42814..af5eab74f 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/LayerPropertyViewModel.cs
@@ -3,6 +3,7 @@ using System.Linq;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput;
+using Artemis.UI.Services.Interfaces;
using Ninject;
using Stylet;
@@ -11,11 +12,14 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
public class LayerPropertyViewModel : PropertyChangedBase
{
private readonly IKernel _kernel;
+ private readonly IProfileEditorService _profileEditorService;
private bool _keyframesEnabled;
- public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, ILayerPropertyViewModelFactory layerPropertyViewModelFactory, IKernel kernel)
+ public LayerPropertyViewModel(BaseLayerProperty layerProperty, LayerPropertyViewModel parent, ILayerPropertyViewModelFactory layerPropertyViewModelFactory, IKernel kernel, IProfileEditorService profileEditorService)
{
_kernel = kernel;
+ _profileEditorService = profileEditorService;
+ _keyframesEnabled = layerProperty.UntypedKeyframes.Any();
LayerProperty = layerProperty;
Parent = parent;
@@ -44,6 +48,16 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties
private void UpdateKeyframes()
{
+ // Either create a new first keyframe or clear all the keyframes
+ if (_keyframesEnabled)
+ LayerProperty.CreateNewKeyframe(_profileEditorService.CurrentTime);
+ else
+ LayerProperty.ClearKeyframes();
+
+ // Force the keyframe engine to update, the new keyframe is the current keyframe
+ LayerProperty.KeyframeEngine.Update(0);
+
+ _profileEditorService.UpdateSelectedProfileElement();
}
public PropertyInputViewModel GetPropertyInputViewModel()
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml
index 8183ab3ec..0b0918fbe 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputView.xaml
@@ -15,6 +15,7 @@
Padding="0 -1"
materialDesign:ValidationAssist.UsePopup="True"
HorizontalAlignment="Left"
+ Text="{Binding FloatInputValue}"
Cursor="/Resources/aero_drag_ew.cur" />
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs
index f25212a72..452b31514 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/FloatPropertyInputViewModel.cs
@@ -1,26 +1,39 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Models.Profile.LayerProperties;
+using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public class FloatPropertyInputViewModel : PropertyInputViewModel
{
+ public FloatPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
+ {
+ }
+
public sealed override List CompatibleTypes { get; } = new List {typeof(float)};
+ public float FloatInputValue
+ {
+ get => (float) InputValue;
+ set => InputValue = value;
+ }
+
+ public override void Update()
+ {
+ NotifyOfPropertyChange(() => FloatInputValue);
+ }
+
protected override void UpdateBaseValue(object value)
{
- throw new NotImplementedException();
+ var layerProperty = (LayerProperty) LayerPropertyViewModel.LayerProperty;
+ layerProperty.Value = (float) value;
}
- protected override void UpdateKeyframeValue(BaseKeyframe keyframe, object value)
+ protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
- throw new NotImplementedException();
- }
-
- protected override void CreateKeyframeForValue(object value)
- {
- throw new NotImplementedException();
+ var keyframe = (Keyframe) baseKeyframe;
+ keyframe.Value = (float) value;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml
index 63a2b3ea9..b7d6bc8f1 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputView.xaml
@@ -15,6 +15,7 @@
Padding="0 -1"
materialDesign:ValidationAssist.UsePopup="True"
HorizontalAlignment="Left"
+ Text="{Binding IntInputValue}"
Cursor="/Resources/aero_drag_ew.cur" />
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs
index 4effe5539..185703805 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/IntPropertyInputViewModel.cs
@@ -1,26 +1,39 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Models.Profile.LayerProperties;
+using Artemis.UI.Services.Interfaces;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public class IntPropertyInputViewModel : PropertyInputViewModel
{
+ public IntPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
+ {
+ }
+
public sealed override List CompatibleTypes { get; } = new List {typeof(int)};
+ public int IntInputValue
+ {
+ get => (int) InputValue;
+ set => InputValue = value;
+ }
+
+ public override void Update()
+ {
+ NotifyOfPropertyChange(() => IntInputValue);
+ }
+
protected override void UpdateBaseValue(object value)
{
- throw new NotImplementedException();
+ var layerProperty = (LayerProperty) LayerPropertyViewModel.LayerProperty;
+ layerProperty.Value = (int) value;
}
- protected override void UpdateKeyframeValue(BaseKeyframe keyframe, object value)
+ protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
- throw new NotImplementedException();
- }
-
- protected override void CreateKeyframeForValue(object value)
- {
- throw new NotImplementedException();
+ var keyframe = (Keyframe) baseKeyframe;
+ keyframe.Value = (int) value;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs
index 528c05458..16c1179ef 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/PropertyInputViewModel.cs
@@ -3,18 +3,26 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.UI.Exceptions;
+using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public abstract class PropertyInputViewModel : PropertyChangedBase
{
+ protected PropertyInputViewModel(IProfileEditorService profileEditorService)
+ {
+ ProfileEditorService = profileEditorService;
+ }
+
+ protected IProfileEditorService ProfileEditorService { get; set; }
+
public bool Initialized { get; private set; }
public abstract List CompatibleTypes { get; }
public LayerPropertyViewModel LayerPropertyViewModel { get; private set; }
- public object InputValue
+ protected object InputValue
{
get => LayerPropertyViewModel.LayerProperty.KeyframeEngine.GetCurrentValue();
set => UpdateInputValue(value);
@@ -33,11 +41,6 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
Initialized = true;
}
- public void Update()
- {
- NotifyOfPropertyChange(() => InputValue);
- }
-
private void UpdateInputValue(object value)
{
// If keyframes are disabled, update the base value
@@ -47,13 +50,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.P
return;
}
- // If on a keyframe, update the keyframe TODO: Make decisions..
- // var currentKeyframe = LayerPropertyViewModel.LayerProperty.UntypedKeyframes.FirstOrDefault(k => k.Position == LayerPropertyViewModel.)
- // Otherwise, add a new keyframe at the current position
+ // If on a keyframe, update the keyframe
+ var currentKeyframe = LayerPropertyViewModel.LayerProperty.UntypedKeyframes.FirstOrDefault(k => k.Position == ProfileEditorService.CurrentTime);
+ // Create a new keyframe if none found
+ if (currentKeyframe == null)
+ currentKeyframe = LayerPropertyViewModel.LayerProperty.CreateNewKeyframe(ProfileEditorService.CurrentTime);
+
+ UpdateKeyframeValue(currentKeyframe, value);
+ // Force the keyframe engine to update, the edited keyframe might affect the current keyframe progress
+ LayerPropertyViewModel.LayerProperty.KeyframeEngine.Update(0);
+
+ ProfileEditorService.UpdateSelectedProfileElement();
+
}
+ public abstract void Update();
protected abstract void UpdateBaseValue(object value);
- protected abstract void UpdateKeyframeValue(BaseKeyframe keyframe, object value);
- protected abstract void CreateKeyframeForValue(object value);
+ protected abstract void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml
index 24ec22465..815b7c16e 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputView.xaml
@@ -8,7 +8,7 @@
mc:Ignorable="d"
d:DesignHeight="25" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SKPointPropertyInputViewModel}">
-
+
+ Text="{Binding X}"
+ Cursor="/Resources/aero_drag_ew.cur" KeyboardNavigation.IsTabStop="True" TabIndex="1" />
,
+ Text="{Binding Y}"
+ Cursor="/Resources/aero_drag_ew.cur" KeyboardNavigation.IsTabStop="True" TabIndex="2" />
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs
index 0977a9bf7..7a0616b60 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKPointPropertyInputViewModel.cs
@@ -1,27 +1,51 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Models.Profile.LayerProperties;
+using Artemis.UI.Services.Interfaces;
+using PropertyChanged;
using SkiaSharp;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public class SKPointPropertyInputViewModel : PropertyInputViewModel
{
+ public SKPointPropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
+ {
+ }
+
public sealed override List CompatibleTypes { get; } = new List {typeof(SKPoint)};
+ // Since SKPoint is immutable we need to create properties that replace the SKPoint entirely
+ [DependsOn(nameof(InputValue))]
+ public float X
+ {
+ get => ((SKPoint) InputValue).X;
+ set => InputValue = new SKPoint(value, Y);
+ }
+
+ [DependsOn(nameof(InputValue))]
+ public float Y
+ {
+ get => ((SKPoint) InputValue).Y;
+ set => InputValue = new SKPoint(X, value);
+ }
+
+ public override void Update()
+ {
+ NotifyOfPropertyChange(() => X);
+ NotifyOfPropertyChange(() => Y);
+ }
+
protected override void UpdateBaseValue(object value)
{
- throw new NotImplementedException();
+ var layerProperty = (LayerProperty) LayerPropertyViewModel.LayerProperty;
+ layerProperty.Value = (SKPoint) value;
}
- protected override void UpdateKeyframeValue(BaseKeyframe keyframe, object value)
+ protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
- throw new NotImplementedException();
- }
-
- protected override void CreateKeyframeForValue(object value)
- {
- throw new NotImplementedException();
+ var keyframe = (Keyframe) baseKeyframe;
+ keyframe.Value = (SKPoint) value;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml
index db8eae794..94fc68a93 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputView.xaml
@@ -16,6 +16,7 @@
materialDesign:ValidationAssist.UsePopup="True"
HorizontalAlignment="Left"
ToolTip="Height"
+ Text="{Binding Height}"
Cursor="/Resources/aero_drag_ew.cur" />
,
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs
index 3b35a67b3..593214f35 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyInput/SKSizePropertyInputViewModel.cs
@@ -1,27 +1,51 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Models.Profile.LayerProperties;
+using Artemis.UI.Services.Interfaces;
+using PropertyChanged;
using SkiaSharp;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree.PropertyInput
{
public class SKSizePropertyInputViewModel : PropertyInputViewModel
{
+ public SKSizePropertyInputViewModel(IProfileEditorService profileEditorService) : base(profileEditorService)
+ {
+ }
+
public sealed override List CompatibleTypes { get; } = new List {typeof(SKSize)};
+ // Since SKSize is immutable we need to create properties that replace the SKPoint entirely
+ [DependsOn(nameof(InputValue))]
+ public float Width
+ {
+ get => ((SKSize) InputValue).Width;
+ set => InputValue = new SKSize(value, Height);
+ }
+
+ [DependsOn(nameof(InputValue))]
+ public float Height
+ {
+ get => ((SKSize) InputValue).Height;
+ set => InputValue = new SKSize(Width, value);
+ }
+
+ public override void Update()
+ {
+ NotifyOfPropertyChange(() => Width);
+ NotifyOfPropertyChange(() => Height);
+ }
+
protected override void UpdateBaseValue(object value)
{
- throw new NotImplementedException();
+ var layerProperty = (LayerProperty) LayerPropertyViewModel.LayerProperty;
+ layerProperty.Value = (SKSize) value;
}
- protected override void UpdateKeyframeValue(BaseKeyframe keyframe, object value)
+ protected override void UpdateKeyframeValue(BaseKeyframe baseKeyframe, object value)
{
- throw new NotImplementedException();
- }
-
- protected override void CreateKeyframeForValue(object value)
- {
- throw new NotImplementedException();
+ var keyframe = (Keyframe) baseKeyframe;
+ keyframe.Value = (SKSize) value;
}
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml
index 69fba2633..ae91ca1d9 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildView.xaml
@@ -22,7 +22,7 @@
ToolTip="Toggle key-framing"
Width="18"
Height="18"
- IsChecked="{Binding KeyframesEnabled}"
+ IsChecked="{Binding LayerPropertyViewModel.KeyframesEnabled}"
VerticalAlignment="Center" Padding="-25">
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs
index 2751bda99..c0a8cf009 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeChildViewModel.cs
@@ -12,5 +12,17 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
public LayerPropertyViewModel LayerPropertyViewModel { get; }
public PropertyInputViewModel PropertyInputViewModel { get; set; }
+
+ public override void Update(bool forceUpdate)
+ {
+ if (forceUpdate)
+ PropertyInputViewModel.Update();
+ else
+ {
+ // Only update if visible and if keyframes are enabled
+ if (LayerPropertyViewModel.Parent.IsExpanded && LayerPropertyViewModel.KeyframesEnabled)
+ PropertyInputViewModel.Update();
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs
index 1837a36d1..0cafdd660 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeItemViewModel.cs
@@ -2,7 +2,12 @@
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
- public class PropertyTreeItemViewModel : PropertyChangedBase
+ public abstract class PropertyTreeItemViewModel : PropertyChangedBase
{
+ ///
+ /// Updates the tree item's input if it is visible and has keyframes enabled
+ ///
+ /// Force update regardless of visibility and keyframes
+ public abstract void Update(bool forceUpdate);
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs
index 3ef565ab9..91e357e9c 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeParentViewModel.cs
@@ -21,5 +21,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
public LayerPropertyViewModel LayerPropertyViewModel { get; }
public BindableCollection Children { get; set; }
+
+ public override void Update(bool forceUpdate)
+ {
+ foreach (var child in Children)
+ child.Update(forceUpdate);
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs
index 1d8ed7f48..12a200271 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/PropertyTree/PropertyTreeViewModel.cs
@@ -1,15 +1,24 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
+using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
public class PropertyTreeViewModel : PropertyChangedBase
{
- public PropertyTreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel)
+ private readonly IProfileEditorService _profileEditorService;
+
+ public PropertyTreeViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
{
+ _profileEditorService = profileEditorService;
+
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTreeItemViewModels = new BindableCollection();
+
+ _profileEditorService.CurrentTimeChanged += (sender, args) => Update(false);
+ _profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update(true);
}
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
@@ -31,5 +40,15 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
{
PropertyTreeItemViewModels.Clear();
}
+
+ ///
+ /// Updates the tree item's input if it is visible and has keyframes enabled
+ ///
+ /// Force update regardless of visibility and keyframes
+ public void Update(bool forceUpdate)
+ {
+ foreach (var viewModel in PropertyTreeItemViewModels)
+ viewModel.Update(forceUpdate);
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml
index e3e57b324..7c92513b7 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineView.xaml
@@ -16,7 +16,7 @@
HorizontalAlignment="Left">
-
+
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs
index 63b4cdc1f..08a208eab 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTimelineViewModel.cs
@@ -1,16 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Artemis.UI.Services.Interfaces;
using Stylet;
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
public class PropertyTimelineViewModel : PropertyChangedBase
{
- public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel)
+ private readonly IProfileEditorService _profileEditorService;
+
+ public PropertyTimelineViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IProfileEditorService profileEditorService)
{
+ _profileEditorService = profileEditorService;
+
LayerPropertiesViewModel = layerPropertiesViewModel;
PropertyTrackViewModels = new BindableCollection();
+
+ _profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update();
+ LayerPropertiesViewModel.PixelsPerSecondChanged += (sender, args) => UpdateKeyframePositions();
}
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
@@ -27,8 +35,8 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
Width = endTime.TotalSeconds * LayerPropertiesViewModel.PixelsPerSecond;
// Ensure the caret isn't outside the end time
- if (LayerPropertiesViewModel.CurrentTime > endTime)
- LayerPropertiesViewModel.CurrentTime = endTime;
+ if (_profileEditorService.CurrentTime > endTime)
+ _profileEditorService.CurrentTime = endTime;
}
public void PopulateProperties(List properties)
@@ -51,5 +59,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
{
PropertyTrackViewModels.Clear();
}
+
+ public void UpdateKeyframePositions()
+ {
+ foreach (var viewModel in PropertyTrackViewModels)
+ viewModel.UpdateKeyframes(LayerPropertiesViewModel.PixelsPerSecond);
+ }
+
+ ///
+ /// Updates the time line's keyframes
+ ///
+ public void Update()
+ {
+ foreach (var viewModel in PropertyTrackViewModels)
+ viewModel.PopulateKeyframes();
+
+ UpdateEndTime();
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs
index b998f1512..2d1662077 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/LayerProperties/Timeline/PropertyTrackViewModel.cs
@@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
KeyframeViewModels = new BindableCollection();
PopulateKeyframes();
- UpdateKeyframes(propertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
+ UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
}
public PropertyTimelineViewModel PropertyTimelineViewModel { get; }
@@ -21,12 +21,22 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
public void PopulateKeyframes()
{
+ // Remove old keyframes
+ foreach (var viewModel in KeyframeViewModels.ToList())
+ {
+ if (!LayerPropertyViewModel.LayerProperty.UntypedKeyframes.Contains(viewModel.Keyframe))
+ KeyframeViewModels.Remove(viewModel);
+ }
+
+ // Add new keyframes
foreach (var keyframe in LayerPropertyViewModel.LayerProperty.UntypedKeyframes)
{
if (KeyframeViewModels.Any(k => k.Keyframe == keyframe))
continue;
KeyframeViewModels.Add(new PropertyTrackKeyframeViewModel(keyframe));
}
+
+ UpdateKeyframes(PropertyTimelineViewModel.LayerPropertiesViewModel.PixelsPerSecond);
}
public void UpdateKeyframes(int pixelsPerSecond)
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml
index 961392118..eda1c8394 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerView.xaml
@@ -42,7 +42,7 @@
-
+
diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs
index 00bc0dfa4..72ecde229 100644
--- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs
+++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs
@@ -24,8 +24,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
Update();
Layer.RenderPropertiesUpdated += LayerOnRenderPropertiesUpdated;
_profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged;
+ _profileEditorService.SelectedProfileElementUpdated += OnSelectedProfileElementUpdated;
+ _profileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
}
-
+
public Layer Layer { get; }
public Geometry LayerGeometry { get; set; }
@@ -119,8 +121,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
shapeGeometry.Freeze();
ShapeGeometry = shapeGeometry;
}
-
-
+
private void CreateViewportRectangle()
{
if (!Layer.Leds.Any() || Layer.LayerShape == null)
@@ -190,6 +191,20 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization
IsSelected = _profileEditorService.SelectedProfileElement == Layer;
}
+ private void OnSelectedProfileElementUpdated(object sender, EventArgs e)
+ {
+ if (IsSelected)
+ Update();
+ }
+
+ private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e)
+ {
+ if (!IsSelected)
+ return;
+ CreateShapeGeometry();
+ CreateViewportRectangle();
+ }
+
public void Dispose()
{
Layer.RenderPropertiesUpdated -= LayerOnRenderPropertiesUpdated;
diff --git a/src/Artemis.UI/Screens/RootView.xaml b/src/Artemis.UI/Screens/RootView.xaml
index 4675f580b..47f1e15d8 100644
--- a/src/Artemis.UI/Screens/RootView.xaml
+++ b/src/Artemis.UI/Screens/RootView.xaml
@@ -57,7 +57,7 @@
-
+
diff --git a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs
index 88b344969..90837eddd 100644
--- a/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs
+++ b/src/Artemis.UI/Services/Interfaces/IProfileEditorService.cs
@@ -7,15 +7,36 @@ namespace Artemis.UI.Services.Interfaces
{
Profile SelectedProfile { get; }
ProfileElement SelectedProfileElement { get; }
+ TimeSpan CurrentTime { get; set; }
void ChangeSelectedProfile(Profile profile);
void UpdateSelectedProfile();
void ChangeSelectedProfileElement(ProfileElement profileElement);
void UpdateSelectedProfileElement();
+ ///
+ /// Occurs when a new profile is selected
+ ///
event EventHandler SelectedProfileChanged;
+
+ ///
+ /// Occurs then the currently selected profile is updated
+ ///
event EventHandler SelectedProfileUpdated;
+
+ ///
+ /// Occurs when a new profile element is selected
+ ///
event EventHandler SelectedProfileElementChanged;
+
+ ///
+ /// Occurs when the currently selected profile element is updated
+ ///
event EventHandler SelectedProfileElementUpdated;
+
+ ///
+ /// Occurs when the current editor time is changed
+ ///
+ event EventHandler CurrentTimeChanged;
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Services/ProfileEditorService.cs b/src/Artemis.UI/Services/ProfileEditorService.cs
index dbf5faf76..f9c60dc85 100644
--- a/src/Artemis.UI/Services/ProfileEditorService.cs
+++ b/src/Artemis.UI/Services/ProfileEditorService.cs
@@ -8,6 +8,8 @@ namespace Artemis.UI.Services
public class ProfileEditorService : IProfileEditorService
{
private readonly IProfileService _profileService;
+ private TimeSpan _currentTime;
+ private TimeSpan _lastUpdateTime;
public ProfileEditorService(IProfileService profileService)
{
@@ -17,6 +19,19 @@ namespace Artemis.UI.Services
public Profile SelectedProfile { get; private set; }
public ProfileElement SelectedProfileElement { get; private set; }
+ public TimeSpan CurrentTime
+ {
+ get => _currentTime;
+ set
+ {
+ if (_currentTime.Equals(value))
+ return;
+ _currentTime = value;
+ UpdateProfilePreview();
+ OnCurrentTimeChanged();
+ }
+ }
+
public void ChangeSelectedProfile(Profile profile)
{
SelectedProfile = profile;
@@ -41,10 +56,30 @@ namespace Artemis.UI.Services
OnSelectedProfileElementUpdated();
}
+
+ private void UpdateProfilePreview()
+ {
+ var delta = CurrentTime - _lastUpdateTime;
+ foreach (var layer in SelectedProfile.GetAllLayers())
+ {
+ // Override keyframe progress
+ foreach (var baseLayerProperty in layer.Properties)
+ baseLayerProperty.KeyframeEngine?.OverrideProgress(CurrentTime);
+
+ // Force layer shape to redraw
+ layer.LayerShape?.CalculateRenderProperties(layer.PositionProperty.GetCurrentValue(), layer.SizeProperty.GetCurrentValue());
+ // Update the brush with the delta (which can now be negative ^^)
+ layer.Update(delta.TotalSeconds);
+ }
+
+ _lastUpdateTime = CurrentTime;
+ }
+
public event EventHandler SelectedProfileChanged;
public event EventHandler SelectedProfileUpdated;
public event EventHandler SelectedProfileElementChanged;
public event EventHandler SelectedProfileElementUpdated;
+ public event EventHandler CurrentTimeChanged;
protected virtual void OnSelectedProfileElementUpdated()
{
@@ -65,5 +100,10 @@ namespace Artemis.UI.Services
{
SelectedProfileChanged?.Invoke(this, EventArgs.Empty);
}
+
+ protected virtual void OnCurrentTimeChanged()
+ {
+ CurrentTimeChanged?.Invoke(this, EventArgs.Empty);
+ }
}
}
\ No newline at end of file