using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.LayerEffects;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using SkiaSharp;
namespace Artemis.Core
{
public abstract class RenderProfileElement : ProfileElement
{
protected RenderProfileElement()
{
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
}
internal void ApplyRenderElementDefaults()
{
MainSegmentLength = TimeSpan.FromSeconds(5);
}
internal void LoadRenderElement()
{
StartSegmentLength = RenderElementEntity.StartSegmentLength;
MainSegmentLength = RenderElementEntity.MainSegmentLength;
EndSegmentLength = RenderElementEntity.EndSegmentLength;
DisplayContinuously = RenderElementEntity.DisplayContinuously;
AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline;
ActivateEffects();
}
internal void SaveRenderElement()
{
RenderElementEntity.StartSegmentLength = StartSegmentLength;
RenderElementEntity.MainSegmentLength = MainSegmentLength;
RenderElementEntity.EndSegmentLength = EndSegmentLength;
RenderElementEntity.DisplayContinuously = DisplayContinuously;
RenderElementEntity.AlwaysFinishTimeline = AlwaysFinishTimeline;
RenderElementEntity.LayerEffects.Clear();
foreach (var layerEffect in LayerEffects)
{
var layerEffectEntity = new LayerEffectEntity
{
Id = layerEffect.EntityId,
PluginGuid = layerEffect.PluginInfo.Guid,
EffectType = layerEffect.GetType().Name,
Name = layerEffect.Name,
Enabled = layerEffect.Enabled,
HasBeenRenamed = layerEffect.HasBeenRenamed,
Order = layerEffect.Order
};
RenderElementEntity.LayerEffects.Add(layerEffectEntity);
layerEffect.BaseProperties.ApplyToEntity();
}
}
#region Properties
private SKPath _path;
internal abstract RenderElementEntity RenderElementEntity { get; }
///
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped.
///
public SKPath Path
{
get => _path;
protected set
{
SetAndNotify(ref _path, value);
// I can't really be sure about the performance impact of calling Bounds often but
// SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive
Bounds = value?.Bounds ?? SKRect.Empty;
}
}
///
/// The bounds of this entity
///
public SKRect Bounds
{
get => _bounds;
private set => SetAndNotify(ref _bounds, value);
}
#region Property group expansion
protected List _expandedPropertyGroups;
private SKRect _bounds;
public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup)
{
return _expandedPropertyGroups.Contains(layerPropertyGroup.Path);
}
public void SetPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup, bool expanded)
{
if (!expanded && IsPropertyGroupExpanded(layerPropertyGroup))
_expandedPropertyGroups.Remove(layerPropertyGroup.Path);
else if (expanded && !IsPropertyGroupExpanded(layerPropertyGroup))
_expandedPropertyGroups.Add(layerPropertyGroup.Path);
}
#endregion
#endregion
#region Timeline
private TimeSpan _startSegmentLength;
private TimeSpan _mainSegmentLength;
private TimeSpan _endSegmentLength;
private bool _displayContinuously;
private bool _alwaysFinishTimeline;
///
/// Gets or sets the length of the start segment
///
public TimeSpan StartSegmentLength
{
get => _startSegmentLength;
set => SetAndNotify(ref _startSegmentLength, value);
}
///
/// Gets or sets the length of the main segment
///
public TimeSpan MainSegmentLength
{
get => _mainSegmentLength;
set => SetAndNotify(ref _mainSegmentLength, value);
}
///
/// Gets or sets the length of the end segment
///
public TimeSpan EndSegmentLength
{
get => _endSegmentLength;
set => SetAndNotify(ref _endSegmentLength, value);
}
///
/// Gets the current timeline position
///
public TimeSpan TimelinePosition
{
get => _timelinePosition;
protected set => SetAndNotify(ref _timelinePosition, value);
}
///
/// Gets the total combined length of all three segments
///
public TimeSpan TimelineLength => StartSegmentLength + MainSegmentLength + EndSegmentLength;
///
/// Gets or sets whether main timeline should repeat itself as long as display conditions are met
///
public bool DisplayContinuously
{
get => _displayContinuously;
set => SetAndNotify(ref _displayContinuously, value);
}
///
/// Gets or sets whether the timeline should finish when conditions are no longer met
///
public bool AlwaysFinishTimeline
{
get => _alwaysFinishTimeline;
set => SetAndNotify(ref _alwaysFinishTimeline, value);
}
protected double UpdateTimeline(double deltaTime)
{
var oldPosition = _timelinePosition;
var deltaTimeSpan = TimeSpan.FromSeconds(deltaTime);
var mainSegmentEnd = StartSegmentLength + MainSegmentLength;
TimelinePosition += deltaTimeSpan;
// Manage segments while the condition is met
if (DisplayConditionMet)
{
// If we are at the end of the main timeline, wrap around back to the beginning
if (DisplayContinuously && TimelinePosition >= mainSegmentEnd)
TimelinePosition = StartSegmentLength + (mainSegmentEnd - TimelinePosition);
}
else
{
// Skip to the last segment if conditions are no longer met
if (!AlwaysFinishTimeline && TimelinePosition < mainSegmentEnd)
TimelinePosition = mainSegmentEnd;
}
return (TimelinePosition - oldPosition).TotalSeconds;
}
///
/// Overrides the progress of the element
///
///
///
public abstract void OverrideProgress(TimeSpan timeOverride, bool stickToMainSegment);
#endregion
#region Effect management
protected List _layerEffects;
///
/// Gets a read-only collection of the layer effects on this entity
///
public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly();
///
/// Adds a the layer effect described inthe provided
///
public void AddLayerEffect(LayerEffectDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
var entity = new LayerEffectEntity
{
Id = Guid.NewGuid(),
Enabled = true,
Order = LayerEffects.Count + 1
};
descriptor.CreateInstance(this, entity);
OnLayerEffectsUpdated();
}
///
/// Removes the provided layer
///
///
public void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
// Remove the effect from the layer and dispose it
_layerEffects.Remove(effect);
effect.Dispose();
// Update the order on the remaining effects
var index = 0;
foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order))
{
baseLayerEffect.Order = Order = index + 1;
index++;
}
OnLayerEffectsUpdated();
}
internal void ActivateEffects()
{
foreach (var layerEffectEntity in RenderElementEntity.LayerEffects)
{
if (_layerEffects.Any(e => e.EntityId == layerEffectEntity.Id))
continue;
var descriptor = LayerEffectStore.Get(layerEffectEntity.PluginGuid, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
descriptor?.CreateInstance(this, layerEffectEntity);
}
}
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
{
_layerEffects.Add(layerEffect);
// Update the order on the effects
var index = 0;
foreach (var baseLayerEffect in LayerEffects.OrderBy(e => e.Order))
{
baseLayerEffect.Order = Order = index + 1;
index++;
}
OnLayerEffectsUpdated();
}
private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e)
{
throw new NotImplementedException();
}
private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e)
{
ActivateEffects();
}
#endregion
#region Conditions
///
/// Gets whether the display conditions applied to this layer where met or not during last update
/// Always true if the layer has no display conditions
///
public bool DisplayConditionMet
{
get => _displayConditionMet;
private set => SetAndNotify(ref _displayConditionMet, value);
}
private DisplayConditionGroup _displayConditionGroup;
private TimeSpan _timelinePosition;
private bool _displayConditionMet;
///
/// Gets or sets the root display condition group
///
public DisplayConditionGroup DisplayConditionGroup
{
get => _displayConditionGroup;
set => SetAndNotify(ref _displayConditionGroup, value);
}
public void UpdateDisplayCondition()
{
var conditionMet = DisplayConditionGroup == null || DisplayConditionGroup.Evaluate();
if (conditionMet && !DisplayConditionMet)
TimelinePosition = TimeSpan.Zero;
DisplayConditionMet = conditionMet;
}
#endregion
#region Events
public event EventHandler LayerEffectsUpdated;
internal void OnLayerEffectsUpdated()
{
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}