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