mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Implemented basic keyframes
This commit is contained in:
parent
e8570a6dd9
commit
0958c3af9f
@ -212,6 +212,7 @@
|
||||
<Compile Include="Services\Storage\ProfileService.cs" />
|
||||
<Compile Include="Services\Storage\Interfaces\ISurfaceService.cs" />
|
||||
<Compile Include="Services\Storage\SurfaceService.cs" />
|
||||
<Compile Include="Utilities\Easings.cs" />
|
||||
<Compile Include="Utilities\EnumUtilities.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
{
|
||||
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(float)};
|
||||
|
||||
public override object GetCurrentValue()
|
||||
protected override object GetInterpolatedValue()
|
||||
{
|
||||
// Nothing fancy for now, just return the base value
|
||||
return ((LayerProperty<float>) LayerProperty).Value;
|
||||
var currentKeyframe = (Keyframe<float>) CurrentKeyframe;
|
||||
var nextKeyframe = (Keyframe<float>) NextKeyframe;
|
||||
|
||||
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
||||
return currentKeyframe.Value + diff * KeyframeProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
{
|
||||
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(int)};
|
||||
|
||||
public override object GetCurrentValue()
|
||||
protected override object GetInterpolatedValue()
|
||||
{
|
||||
// Nothing fancy for now, just return the base value
|
||||
return ((LayerProperty<int>) LayerProperty).Value;
|
||||
var currentKeyframe = (Keyframe<int>) CurrentKeyframe;
|
||||
var nextKeyframe = (Keyframe<int>) NextKeyframe;
|
||||
|
||||
var diff = nextKeyframe.Value - currentKeyframe.Value;
|
||||
return currentKeyframe.Value + diff * KeyframeProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// <summary>
|
||||
/// The layer property this keyframe engine applies to.
|
||||
/// </summary>
|
||||
public BaseLayerProperty LayerProperty { get; set; }
|
||||
public BaseLayerProperty LayerProperty { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The keyframe progress in milliseconds.
|
||||
/// The total progress
|
||||
/// </summary>
|
||||
public double Progress { get; set; }
|
||||
public TimeSpan Progress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The progress from the current keyframe to the next 0 to 1
|
||||
/// </summary>
|
||||
public float KeyframeProgress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current keyframe
|
||||
/// </summary>
|
||||
public BaseKeyframe CurrentKeyframe { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next keyframe
|
||||
/// </summary>
|
||||
public BaseKeyframe NextKeyframe { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the engine's progress to the provided value
|
||||
/// </summary>
|
||||
/// <param name="progress"></param>
|
||||
public void OverrideProgress(TimeSpan progress)
|
||||
{
|
||||
Progress = TimeSpan.Zero;
|
||||
Update(progress.TotalMilliseconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value, if the progress is in between two keyframes the value will be interpolated
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
{
|
||||
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(SKPoint)};
|
||||
|
||||
public override object GetCurrentValue()
|
||||
protected override object GetInterpolatedValue()
|
||||
{
|
||||
// Nothing fancy for now, just return the base value
|
||||
return ((LayerProperty<SKPoint>) LayerProperty).Value;
|
||||
var currentKeyframe = (Keyframe<SKPoint>) CurrentKeyframe;
|
||||
var nextKeyframe = (Keyframe<SKPoint>) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
|
||||
{
|
||||
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(SKSize)};
|
||||
|
||||
public override object GetCurrentValue()
|
||||
protected override object GetInterpolatedValue()
|
||||
{
|
||||
// Nothing fancy for now, just return the base value
|
||||
return ((LayerProperty<SKSize>) LayerProperty).Value;
|
||||
var currentKeyframe = (Keyframe<SKSize>)CurrentKeyframe;
|
||||
var nextKeyframe = (Keyframe<SKSize>)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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new keyframe for this base property without knowing the type
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keyframes from the property.
|
||||
/// </summary>
|
||||
public void ClearKeyframes()
|
||||
{
|
||||
BaseKeyframes.Clear();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
set => BaseValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the property with keyframes applied
|
||||
/// </summary>
|
||||
public T CurrentValue => (T) KeyframeEngine.GetCurrentValue();
|
||||
|
||||
/// <summary>
|
||||
/// A list of keyframes defining different values of the property in time, this list contains the strongly typed
|
||||
/// <see cref="Keyframe{T}" />
|
||||
@ -43,20 +48,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all keyframes from the property.
|
||||
/// </summary>
|
||||
public void ClearKeyframes()
|
||||
{
|
||||
BaseKeyframes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current value using the keyframes
|
||||
/// Gets the current value using the keyframes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T GetCurrentValue()
|
||||
{
|
||||
if (KeyframeEngine == null)
|
||||
if (KeyframeEngine == null || !Keyframes.Any())
|
||||
return Value;
|
||||
|
||||
return (T) KeyframeEngine.GetCurrentValue();
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<KeyframeEngine>()
|
||||
.SelectAllClasses()
|
||||
.InheritedFrom<KeyframeEngine>()
|
||||
.BindAllBaseClasses();
|
||||
});
|
||||
|
||||
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
|
||||
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
|
||||
}
|
||||
|
||||
@ -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
|
||||
/// <param name="settings">JSON settings to be deserialized and injected into the layer brush</param>
|
||||
/// <returns></returns>
|
||||
LayerBrush InstantiateLayerBrush(Layer layer, LayerBrushDescriptor brushDescriptor, string settings = null);
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates and adds a compatible <see cref="KeyframeEngine" /> to the provided <see cref="LayerProperty{T}" />
|
||||
/// </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" />.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
@ -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<T>(LayerProperty<T> 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<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, LayerBrush layerElement)
|
||||
{
|
||||
var brush = layer.LayerBrush;
|
||||
|
||||
@ -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<ProfileModule>();
|
||||
@ -132,6 +145,13 @@ namespace Artemis.Core.Services.Storage
|
||||
InstantiateProfileLayerBrushes(profileModule.ActiveProfile);
|
||||
}
|
||||
|
||||
private void ActiveProfilesInstantiateKeyframeEngines()
|
||||
{
|
||||
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
|
||||
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
|
||||
|
||||
398
src/Artemis.Core/Utilities/Easings.cs
Normal file
398
src/Artemis.Core/Utilities/Easings.cs
Normal file
@ -0,0 +1,398 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.Utilities
|
||||
{
|
||||
public static class Easings
|
||||
{
|
||||
/// <summary>
|
||||
/// Easing Functions enumeration
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constant Pi.
|
||||
/// </summary>
|
||||
private const double PI = Math.PI;
|
||||
|
||||
/// <summary>
|
||||
/// Constant Pi / 2.
|
||||
/// </summary>
|
||||
private const double HALFPI = Math.PI / 2.0;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolate using the specified function.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the line y = x
|
||||
/// </summary>
|
||||
public static double Linear(double p)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the parabola y = x^2
|
||||
/// </summary>
|
||||
public static double QuadraticEaseIn(double p)
|
||||
{
|
||||
return p * p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the parabola y = -x^2 + 2x
|
||||
/// </summary>
|
||||
public static double QuadraticEaseOut(double p)
|
||||
{
|
||||
return -(p * (p - 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the piecewise quadratic
|
||||
/// y = (1/2)((2x)^2) ; [0, 0.5)
|
||||
/// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
|
||||
/// </summary>
|
||||
public static double QuadraticEaseInOut(double p)
|
||||
{
|
||||
if (p < 0.5)
|
||||
return 2 * p * p;
|
||||
return -2 * p * p + 4 * p - 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the cubic y = x^3
|
||||
/// </summary>
|
||||
public static double CubicEaseIn(double p)
|
||||
{
|
||||
return p * p * p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the cubic y = (x - 1)^3 + 1
|
||||
/// </summary>
|
||||
public static double CubicEaseOut(double p)
|
||||
{
|
||||
var f = p - 1;
|
||||
return f * f * f + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the piecewise cubic
|
||||
/// y = (1/2)((2x)^3) ; [0, 0.5)
|
||||
/// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the quartic x^4
|
||||
/// </summary>
|
||||
public static double QuarticEaseIn(double p)
|
||||
{
|
||||
return p * p * p * p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the quartic y = 1 - (x - 1)^4
|
||||
/// </summary>
|
||||
public static double QuarticEaseOut(double p)
|
||||
{
|
||||
var f = p - 1;
|
||||
return f * f * f * (1 - p) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// Modeled after the piecewise quartic
|
||||
// y = (1/2)((2x)^4) ; [0, 0.5)
|
||||
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the quintic y = x^5
|
||||
/// </summary>
|
||||
public static double QuinticEaseIn(double p)
|
||||
{
|
||||
return p * p * p * p * p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the quintic y = (x - 1)^5 + 1
|
||||
/// </summary>
|
||||
public static double QuinticEaseOut(double p)
|
||||
{
|
||||
var f = p - 1;
|
||||
return f * f * f * f * f + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the piecewise quintic
|
||||
/// y = (1/2)((2x)^5) ; [0, 0.5)
|
||||
/// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after quarter-cycle of sine wave
|
||||
/// </summary>
|
||||
public static double SineEaseIn(double p)
|
||||
{
|
||||
return Math.Sin((p - 1) * HALFPI) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after quarter-cycle of sine wave (different phase)
|
||||
/// </summary>
|
||||
public static double SineEaseOut(double p)
|
||||
{
|
||||
return Math.Sin(p * HALFPI);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after half sine wave
|
||||
/// </summary>
|
||||
public static double SineEaseInOut(double p)
|
||||
{
|
||||
return 0.5 * (1 - Math.Cos(p * PI));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after shifted quadrant IV of unit circle
|
||||
/// </summary>
|
||||
public static double CircularEaseIn(double p)
|
||||
{
|
||||
return 1 - Math.Sqrt(1 - p * p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after shifted quadrant II of unit circle
|
||||
/// </summary>
|
||||
public static double CircularEaseOut(double p)
|
||||
{
|
||||
return Math.Sqrt((2 - p) * p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the exponential function y = 2^(10(x - 1))
|
||||
/// </summary>
|
||||
public static double ExponentialEaseIn(double p)
|
||||
{
|
||||
return p == 0.0 ? p : Math.Pow(2, 10 * (p - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the exponential function y = -2^(-10x) + 1
|
||||
/// </summary>
|
||||
public static double ExponentialEaseOut(double p)
|
||||
{
|
||||
return p == 1.0 ? p : 1 - Math.Pow(2, -10 * p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the damped sine wave y = sin(13pi/2*x)*Math.Pow(2, 10 * (x - 1))
|
||||
/// </summary>
|
||||
public static double ElasticEaseIn(double p)
|
||||
{
|
||||
return Math.Sin(13 * HALFPI * p) * Math.Pow(2, 10 * (p - 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*Math.Pow(2, -10x) + 1
|
||||
/// </summary>
|
||||
public static double ElasticEaseOut(double p)
|
||||
{
|
||||
return Math.Sin(-13 * HALFPI * (p + 1)) * Math.Pow(2, -10 * p) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
|
||||
/// </summary>
|
||||
public static double BackEaseIn(double p)
|
||||
{
|
||||
return p * p * p - p * Math.Sin(p * PI);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
|
||||
/// </summary>
|
||||
public static double BackEaseOut(double p)
|
||||
{
|
||||
var f = 1 - p;
|
||||
return 1 - (f * f * f - f * Math.Sin(f * PI));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public static double BounceEaseIn(double p)
|
||||
{
|
||||
return 1 - BounceEaseOut(1 - p);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate >
|
||||
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect FadeIn}">
|
||||
<ContentControl s:View.Model="{Binding}" IsTabStop="False" TextElement.Foreground="{DynamicResource MaterialDesignBody}"/>
|
||||
<ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}"/>
|
||||
</materialDesign:TransitioningContent>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
Padding="0 -1"
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Binding FloatInputValue}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
<TextBlock Margin="5 0 0 0" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
|
||||
</StackPanel>
|
||||
|
||||
@ -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<Type> CompatibleTypes { get; } = new List<Type> {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<float>) 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<float>) baseKeyframe;
|
||||
keyframe.Value = (float) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@
|
||||
Padding="0 -1"
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{Binding IntInputValue}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
<TextBlock Margin="5 0 0 0" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
|
||||
</StackPanel>
|
||||
|
||||
@ -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<Type> CompatibleTypes { get; } = new List<Type> {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<int>) 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<int>) baseKeyframe;
|
||||
keyframe.Value = (int) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Type> 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);
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="25" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:SKPointPropertyInputViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel Orientation="Horizontal" KeyboardNavigation.IsTabStop="True">
|
||||
<TextBlock Margin="0 0 5 0" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputPrefix}" />
|
||||
<TextBox Width="60"
|
||||
Margin="0 2"
|
||||
@ -16,7 +16,8 @@
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip="X-coordinate (horizontal)"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
Text="{Binding X}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" KeyboardNavigation.IsTabStop="True" TabIndex="1" />
|
||||
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
|
||||
<TextBox Width="60"
|
||||
Margin="0 2"
|
||||
@ -24,7 +25,8 @@
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip="Y-coordinate (vertical)"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
Text="{Binding Y}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" KeyboardNavigation.IsTabStop="True" TabIndex="2" />
|
||||
<TextBlock Margin="5 0 0 0" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -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<Type> CompatibleTypes { get; } = new List<Type> {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<SKPoint>) 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<SKPoint>) baseKeyframe;
|
||||
keyframe.Value = (SKPoint) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip="Height"
|
||||
Text="{Binding Height}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
<TextBlock Margin="5 0" VerticalAlignment="Bottom">,</TextBlock>
|
||||
<TextBox Width="60"
|
||||
@ -24,6 +25,7 @@
|
||||
materialDesign:ValidationAssist.UsePopup="True"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip="Width"
|
||||
Text="{Binding Width}"
|
||||
Cursor="/Resources/aero_drag_ew.cur" />
|
||||
<TextBlock Margin="5 0 0 0" Width="10" VerticalAlignment="Bottom" Text="{Binding LayerPropertyViewModel.LayerProperty.InputAffix}" />
|
||||
</StackPanel>
|
||||
|
||||
@ -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<Type> CompatibleTypes { get; } = new List<Type> {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<SKSize>) 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<SKSize>) baseKeyframe;
|
||||
keyframe.Value = (SKSize) value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,7 +22,7 @@
|
||||
ToolTip="Toggle key-framing"
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsChecked="{Binding KeyframesEnabled}"
|
||||
IsChecked="{Binding LayerPropertyViewModel.KeyframesEnabled}"
|
||||
VerticalAlignment="Center" Padding="-25">
|
||||
<materialDesign:PackIcon Kind="Stopwatch" Height="13" Width="13" />
|
||||
</ToggleButton>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,12 @@
|
||||
|
||||
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
{
|
||||
public class PropertyTreeItemViewModel : PropertyChangedBase
|
||||
public abstract class PropertyTreeItemViewModel : PropertyChangedBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates the tree item's input if it is visible and has keyframes enabled
|
||||
/// </summary>
|
||||
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
|
||||
public abstract void Update(bool forceUpdate);
|
||||
}
|
||||
}
|
||||
@ -21,5 +21,11 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.PropertyTree
|
||||
|
||||
public LayerPropertyViewModel LayerPropertyViewModel { get; }
|
||||
public BindableCollection<PropertyTreeItemViewModel> Children { get; set; }
|
||||
|
||||
public override void Update(bool forceUpdate)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
child.Update(forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<PropertyTreeItemViewModel>();
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the tree item's input if it is visible and has keyframes enabled
|
||||
/// </summary>
|
||||
/// <param name="forceUpdate">Force update regardless of visibility and keyframes</param>
|
||||
public void Update(bool forceUpdate)
|
||||
{
|
||||
foreach (var viewModel in PropertyTreeItemViewModels)
|
||||
viewModel.Update(forceUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@
|
||||
HorizontalAlignment="Left">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
|
||||
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
@ -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<PropertyTrackViewModel>();
|
||||
|
||||
_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<LayerPropertyViewModel> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the time line's keyframes
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
foreach (var viewModel in PropertyTrackViewModels)
|
||||
viewModel.PopulateKeyframes();
|
||||
|
||||
UpdateEndTime();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
||||
KeyframeViewModels = new BindableCollection<PropertyTrackKeyframeViewModel>();
|
||||
|
||||
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)
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<!-- The part of the layer's shape that is inside the layer -->
|
||||
<Path Data="{Binding ShapeGeometry, Mode=OneWay}">
|
||||
<Path.Fill>
|
||||
<SolidColorBrush Color="{StaticResource Accent700}" Opacity="0.35" />
|
||||
<SolidColorBrush Color="{StaticResource Accent700}" Opacity="0.25" />
|
||||
</Path.Fill>
|
||||
<Path.Stroke>
|
||||
<SolidColorBrush Color="{StaticResource Accent700}" />
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
<DockPanel>
|
||||
<mde:AppBar Type="Dense" IsNavigationDrawerOpen="{Binding IsSidebarVisible, Mode=TwoWay}" Title="{Binding ActiveItem.DisplayName}" ShowNavigationDrawerButton="True"
|
||||
DockPanel.Dock="Top" />
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}" IsTabStop="False" Style="{StaticResource InitializingFade}" />
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}" Style="{StaticResource InitializingFade}" />
|
||||
</DockPanel>
|
||||
</materialDesign:DrawerHost>
|
||||
</materialDesign:DialogHost>
|
||||
|
||||
@ -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();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile is selected
|
||||
/// </summary>
|
||||
event EventHandler SelectedProfileChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs then the currently selected profile is updated
|
||||
/// </summary>
|
||||
event EventHandler SelectedProfileUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a new profile element is selected
|
||||
/// </summary>
|
||||
event EventHandler SelectedProfileElementChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the currently selected profile element is updated
|
||||
/// </summary>
|
||||
event EventHandler SelectedProfileElementUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the current editor time is changed
|
||||
/// </summary>
|
||||
event EventHandler CurrentTimeChanged;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user