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; } } }