using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Artemis.Core.Exceptions;
using Artemis.Core.Utilities;
using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json;
namespace Artemis.Core.Models.Profile.LayerProperties
{
///
/// Represents a property on a layer. Properties are saved in storage and can optionally be modified from the UI.
///
/// Note: You cannot initialize layer properties yourself. If properly placed and annotated, the Artemis core will
/// initialize
/// these for you.
///
///
/// The type of property encapsulated in this layer property
public abstract class LayerProperty : BaseLayerProperty
{
private T _baseValue;
private T _currentValue;
private bool _isInitialized;
private List> _keyframes;
protected LayerProperty()
{
_keyframes = new List>();
}
///
/// Gets or sets the base value of this layer property without any keyframes applied
///
public T BaseValue
{
get => _baseValue;
set
{
if (_baseValue != null && !_baseValue.Equals(value) || _baseValue == null && value != null)
{
_baseValue = value;
OnBaseValueChanged();
}
}
}
///
/// Gets the current value of this property as it is affected by it's keyframes, updated once every frame
///
public T CurrentValue
{
get => !KeyframesEnabled || !KeyframesSupported ? BaseValue : _currentValue;
internal set => _currentValue = value;
}
///
/// Gets or sets the default value of this layer property. If set, this value is automatically applied if the property has no
/// value in storage
///
public T DefaultValue { get; set; }
///
/// Gets a read-only list of all the keyframes on this layer property
///
public IReadOnlyList> Keyframes => _keyframes.AsReadOnly();
///
/// Gets the current keyframe in the timeline according to the current progress
///
public LayerPropertyKeyframe CurrentKeyframe { get; protected set; }
///
/// Gets the next keyframe in the timeline according to the current progress
///
public LayerPropertyKeyframe NextKeyframe { get; protected set; }
public override IReadOnlyList BaseKeyframes => _keyframes.Cast().ToList().AsReadOnly();
///
/// Sets the current value, using either keyframes if enabled or the base value.
///
/// The value to set.
///
/// An optional time to set the value add, if provided and property is using keyframes the value will be set to an new
/// or existing keyframe.
///
public void SetCurrentValue(T value, TimeSpan? time)
{
if (time == null || !KeyframesEnabled || !KeyframesSupported)
BaseValue = value;
else
{
// If on a keyframe, update the keyframe
var currentKeyframe = Keyframes.FirstOrDefault(k => k.Position == time.Value);
// Create a new keyframe if none found
if (currentKeyframe == null)
AddKeyframe(new LayerPropertyKeyframe(value, time.Value, Easings.Functions.Linear, this));
else
currentKeyframe.Value = value;
// Update the property so that the new keyframe is reflected on the current value
Update(0);
}
}
///
/// Adds a keyframe to the layer property
///
/// The keyframe to add
public void AddKeyframe(LayerPropertyKeyframe keyframe)
{
if (_keyframes.Contains(keyframe))
return;
keyframe.LayerProperty?.RemoveKeyframe(keyframe);
keyframe.LayerProperty = this;
keyframe.BaseLayerProperty = this;
_keyframes.Add(keyframe);
SortKeyframes();
OnKeyframeAdded();
}
///
/// Removes a keyframe from the layer property
///
/// The keyframe to remove
public LayerPropertyKeyframe CopyKeyframe(LayerPropertyKeyframe keyframe)
{
var newKeyframe = new LayerPropertyKeyframe(
keyframe.Value,
keyframe.Position,
keyframe.EasingFunction,
keyframe.LayerProperty
);
AddKeyframe(newKeyframe);
return newKeyframe;
}
///
/// Removes a keyframe from the layer property
///
/// The keyframe to remove
public void RemoveKeyframe(LayerPropertyKeyframe keyframe)
{
if (!_keyframes.Contains(keyframe))
return;
_keyframes.Remove(keyframe);
keyframe.LayerProperty = null;
keyframe.BaseLayerProperty = null;
SortKeyframes();
OnKeyframeRemoved();
}
///
/// Removes all keyframes from the layer property
///
public void ClearKeyframes()
{
var keyframes = new List>(_keyframes);
foreach (var layerPropertyKeyframe in keyframes)
RemoveKeyframe(layerPropertyKeyframe);
}
///
/// Called every update (if keyframes are both supported and enabled) to determine the new
/// based on the provided progress
///
/// The linear current keyframe progress
/// The current keyframe progress, eased with the current easing function
protected abstract void UpdateCurrentValue(float keyframeProgress, float keyframeProgressEased);
///
/// Updates the property, moving the timeline forwards by the provided
///
/// The amount of time to move the timeline forwards
internal void Update(double deltaTime)
{
TimelineProgress = TimelineProgress.Add(TimeSpan.FromSeconds(deltaTime));
if (!KeyframesSupported || !KeyframesEnabled)
return;
// The current keyframe is the last keyframe before the current time
CurrentKeyframe = _keyframes.LastOrDefault(k => k.Position <= TimelineProgress);
// Keyframes are sorted by position so we can safely assume the next keyframe's position is after the current
var nextIndex = _keyframes.IndexOf(CurrentKeyframe) + 1;
NextKeyframe = _keyframes.Count > nextIndex ? _keyframes[nextIndex] : null;
// No need to update the current value if either of the keyframes are null
if (CurrentKeyframe == null)
CurrentValue = _keyframes.Any() ? _keyframes[0].Value : BaseValue;
else if (NextKeyframe == null)
CurrentValue = CurrentKeyframe.Value;
// Only determine progress and current value if both keyframes are present
else
{
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
var keyframeProgress = (float) ((TimelineProgress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
var keyframeProgressEased = (float) Easings.Interpolate(keyframeProgress, CurrentKeyframe.EasingFunction);
UpdateCurrentValue(keyframeProgress, keyframeProgressEased);
}
OnUpdated();
}
///
/// Overrides the timeline progress to match the provided
///
/// The new progress to set the layer property timeline to.
internal void OverrideProgress(TimeSpan overrideTime)
{
TimelineProgress = TimeSpan.Zero;
Update(overrideTime.TotalSeconds);
}
///
/// Sorts the keyframes in ascending order by position
///
internal void SortKeyframes()
{
_keyframes = _keyframes.OrderBy(k => k.Position).ToList();
}
internal override void ApplyToLayerProperty(PropertyEntity entity, LayerPropertyGroup layerPropertyGroup, bool fromStorage)
{
// Doubt this will happen but let's make sure
if (_isInitialized)
throw new ArtemisCoreException("Layer property already initialized, wut");
PropertyEntity = entity;
LayerPropertyGroup = layerPropertyGroup;
LayerPropertyGroup.PropertyGroupUpdating += (sender, args) => Update(args.DeltaTime);
LayerPropertyGroup.PropertyGroupOverriding += (sender, args) => OverrideProgress(args.OverrideTime);
try
{
if (entity.Value != null)
BaseValue = JsonConvert.DeserializeObject(entity.Value);
IsLoadedFromStorage = fromStorage;
CurrentValue = BaseValue;
KeyframesEnabled = entity.KeyframesEnabled;
_keyframes.Clear();
_keyframes.AddRange(entity.KeyframeEntities.Select(k => new LayerPropertyKeyframe(
JsonConvert.DeserializeObject(k.Value),
k.Position,
(Easings.Functions) k.EasingFunction,
this
)));
}
catch (JsonException e)
{
// TODO: Properly log the JSON exception
Debug.WriteLine($"JSON exception while deserializing: {e}");
IsLoadedFromStorage = false;
}
finally
{
SortKeyframes();
_isInitialized = true;
}
}
internal override void ApplyToEntity()
{
if (!_isInitialized)
throw new ArtemisCoreException("Layer property is not yet initialized");
PropertyEntity.Value = JsonConvert.SerializeObject(BaseValue);
PropertyEntity.KeyframesEnabled = KeyframesEnabled;
PropertyEntity.KeyframeEntities.Clear();
PropertyEntity.KeyframeEntities.AddRange(Keyframes.Select(k => new KeyframeEntity
{
Value = JsonConvert.SerializeObject(k.Value),
Position = k.Position,
EasingFunction = (int) k.EasingFunction
}));
}
public override void ApplyDefaultValue()
{
BaseValue = DefaultValue;
CurrentValue = DefaultValue;
}
}
}