using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.Annotations; using Artemis.Core.Models.Profile.Conditions; using Artemis.Core.Models.Profile.LayerProperties; using Artemis.Core.Plugins.LayerBrush.Abstract; using Artemis.Core.Plugins.LayerEffect.Abstract; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile.Abstract; using SkiaSharp; namespace Artemis.Core.Models.Profile { public abstract class RenderProfileElement : ProfileElement { protected void ApplyRenderElementDefaults() { MainSegmentLength = TimeSpan.FromSeconds(5); } protected void ApplyRenderElementEntity() { StartSegmentLength = RenderElementEntity.StartSegmentLength; MainSegmentLength = RenderElementEntity.MainSegmentLength; EndSegmentLength = RenderElementEntity.EndSegmentLength; RepeatMainSegment = RenderElementEntity.RepeatMainSegment; AlwaysFinishTimeline = RenderElementEntity.AlwaysFinishTimeline; } protected void ApplyRenderElementToEntity() { RenderElementEntity.StartSegmentLength = StartSegmentLength; RenderElementEntity.MainSegmentLength = MainSegmentLength; RenderElementEntity.EndSegmentLength = EndSegmentLength; RenderElementEntity.RepeatMainSegment = RepeatMainSegment; 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(); } } /// /// Returns a list of all keyframes on all properties and effects of this layer /// public virtual List GetAllKeyframes() { var keyframes = new List(); foreach (var layerEffect in LayerEffects) { foreach (var baseLayerProperty in layerEffect.BaseProperties.GetAllLayerProperties()) keyframes.AddRange(baseLayerProperty.BaseKeyframes); } return keyframes; } #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 _repeatMainSegment; 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 RepeatMainSegment { get => _repeatMainSegment; set => SetAndNotify(ref _repeatMainSegment, 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 (RepeatMainSegment && TimelinePosition >= mainSegmentEnd) TimelinePosition = StartSegmentLength + (mainSegmentEnd - TimelinePosition); else if (TimelinePosition >= TimelineLength) TimelinePosition = TimelineLength; } 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 Effects protected List _layerEffects; /// /// Gets a read-only collection of the layer effects on this entity /// public ReadOnlyCollection LayerEffects => _layerEffects.AsReadOnly(); internal void RemoveLayerEffect([NotNull] BaseLayerEffect effect) { if (effect == null) throw new ArgumentNullException(nameof(effect)); DeactivateLayerEffect(effect); // 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 AddLayerEffect([NotNull] BaseLayerEffect effect) { if (effect == null) throw new ArgumentNullException(nameof(effect)); _layerEffects.Add(effect); OnLayerEffectsUpdated(); } internal void DeactivateLayerEffect([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(); } #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 } }