Core - Refactored effects

Profile editor - Added effect creation
This commit is contained in:
Robert committed 2022-04-23 17:07:04 +02:00
1 parent e5ba48c7f4
commit 2bf36fbf20
61 files changed
+1005 -763

No files matched your search

+16 -27
View File
@@ -227,19 +227,29 @@ namespace Artemis.Core
/// <inheritdoc />
public override void Enable()
{
if (Enabled)
return;
// No checks here, effects will do their own checks to ensure they never enable twice
// Also not enabling children, they'll enable themselves during their own Update
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalEnable();
Enabled = true;
}
/// <inheritdoc />
public override void Disable()
{
// No checks here, effects will do their own checks to ensure they never disable twice
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
// Disabling children since their Update won't get called with their parent disabled
foreach (ProfileElement profileElement in Children)
{
if (profileElement is RenderProfileElement renderProfileElement && renderProfileElement.ShouldBeEnabled)
renderProfileElement.Enable();
if (profileElement is RenderProfileElement renderProfileElement)
renderProfileElement.Disable();
}
Enabled = true;
Enabled = false;
}
/// <inheritdoc />
@@ -250,24 +260,6 @@ namespace Artemis.Core
baseLayerEffect.InternalUpdate(Timeline); ;
}
/// <inheritdoc />
public override void Disable()
{
if (!Enabled)
return;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
foreach (ProfileElement profileElement in Children)
{
if (profileElement is RenderProfileElement renderProfileElement)
renderProfileElement.Disable();
}
Enabled = false;
}
/// <summary>
/// Occurs when a property affecting the rendering properties of this folder has been updated
/// </summary>
@@ -308,7 +300,6 @@ namespace Artemis.Core
internal override void Load()
{
ExpandedPropertyGroups.AddRange(FolderEntity.ExpandedPropertyGroups);
Reset();
// Load child folders
@@ -340,8 +331,6 @@ namespace Artemis.Core
FolderEntity.Suspended = Suspended;
FolderEntity.ProfileId = Profile.EntityId;
FolderEntity.ExpandedPropertyGroups.Clear();
FolderEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
SaveRenderElement();
}
+15 -21
View File
@@ -296,7 +296,6 @@ namespace Artemis.Core
Suspended = LayerEntity.Suspended;
Order = LayerEntity.Order;
ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
LoadRenderElement();
Adapter.Load();
}
@@ -313,8 +312,6 @@ namespace Artemis.Core
LayerEntity.Suspended = Suspended;
LayerEntity.Name = Name;
LayerEntity.ProfileId = Profile.EntityId;
LayerEntity.ExpandedPropertyGroups.Clear();
LayerEntity.ExpandedPropertyGroups.AddRange(ExpandedPropertyGroups);
General.ApplyToEntity();
Transform.ApplyToEntity();
@@ -382,7 +379,7 @@ namespace Artemis.Core
UpdateDisplayCondition();
UpdateTimeline(deltaTime);
if (ShouldBeEnabled)
Enable();
else if (Timeline.IsFinished && !_renderCopies.Any())
@@ -505,9 +502,7 @@ namespace Artemis.Core
/// <inheritdoc />
public override void Enable()
{
if (Enabled)
return;
// No checks here, the brush and effects will do their own checks to ensure they never enable twice
bool tryOrBreak = TryOrBreak(() => LayerBrush?.InternalEnable(), "Failed to enable layer brush");
if (!tryOrBreak)
return;
@@ -519,10 +514,21 @@ namespace Artemis.Core
}, "Failed to enable one or more effects");
if (!tryOrBreak)
return;
Enabled = true;
}
/// <inheritdoc />
public override void Disable()
{
// No checks here, the brush and effects will do their own checks to ensure they never disable twice
LayerBrush?.InternalDisable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
Enabled = false;
}
/// <inheritdoc />
public override void OverrideTimelineAndApply(TimeSpan position)
{
@@ -536,19 +542,6 @@ namespace Artemis.Core
baseLayerEffect.InternalUpdate(Timeline);
}
/// <inheritdoc />
public override void Disable()
{
if (!Enabled)
return;
LayerBrush?.InternalDisable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
Enabled = false;
}
/// <inheritdoc />
public override void Reset()
{
@@ -822,6 +815,7 @@ namespace Artemis.Core
General.ShapeType.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
General.BlendMode.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
Transform.IsHidden = LayerBrush != null && !LayerBrush.SupportsTransformation;
LayerBrush?.Update(0);
OnLayerBrushUpdated();
ClearBrokenState("Failed to initialize layer brush");
@@ -0,0 +1,25 @@
namespace Artemis.Core
{
/// <summary>
/// Represents a property group on a layer
/// <para>
/// Note: You cannot initialize property groups yourself. If properly placed and annotated, the Artemis core will
/// initialize these for you.
/// </para>
/// </summary>
public abstract class LayerEffectPropertyGroup : LayerPropertyGroup
{
/// <summary>
/// Whether or not this layer effect is enabled
/// </summary>
[PropertyDescription(Name = "Enabled", Description = "Whether or not this layer effect is enabled")]
public BoolLayerProperty IsEnabled { get; set; } = null!;
internal void InitializeIsEnabled()
{
IsEnabled.DefaultValue = true;
if (!IsEnabled.IsLoadedFromStorage)
IsEnabled.SetCurrentValue(true);
}
}
}
@@ -203,7 +203,7 @@ namespace Artemis.Core
/// or existing keyframe.
/// </param>
/// <returns>The keyframe if one was created or updated.</returns>
public LayerPropertyKeyframe<T>? SetCurrentValue(T value, TimeSpan? time)
public LayerPropertyKeyframe<T>? SetCurrentValue(T value, TimeSpan? time = null)
{
if (_disposed)
throw new ObjectDisposedException("LayerProperty");
@@ -249,6 +249,15 @@ namespace Artemis.Core
layerPropertyGroup.Update(timeline);
}
internal void MoveLayerProperty(ILayerProperty layerProperty, int index)
{
if (!_layerProperties.Contains(layerProperty))
return;
_layerProperties.Remove(layerProperty);
_layerProperties.Insert(index, layerProperty);
}
internal virtual void OnVisibilityChanged()
{
VisibilityChanged?.Invoke(this, EventArgs.Empty);
@@ -292,7 +301,7 @@ namespace Artemis.Core
throw new ArtemisPluginException($"Property with PropertyGroupDescription attribute must be of type LayerPropertyGroup: {propertyGroupDescription.Identifier}");
if (!(Activator.CreateInstance(propertyInfo.PropertyType) is LayerPropertyGroup instance))
throw new ArtemisPluginException($"Failed to create instance of layer property group: {propertyGroupDescription.Identifier}");
PropertyGroupEntity entity = GetPropertyGroupEntity(propertyGroupDescription.Identifier);
instance.Initialize(ProfileElement, this, propertyGroupDescription, entity);
@@ -4,393 +4,393 @@ using System.Collections.ObjectModel;
using System.Linq;
using Artemis.Core.LayerEffects;
using Artemis.Core.LayerEffects.Placeholder;
using Artemis.Core.Properties;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
using SkiaSharp;
namespace Artemis.Core
namespace Artemis.Core;
/// <summary>
/// Represents an element of a <see cref="Profile" /> that has advanced rendering capabilities
/// </summary>
public abstract class RenderProfileElement : ProfileElement
{
/// <summary>
/// Represents an element of a <see cref="Profile" /> that has advanced rendering capabilities
/// </summary>
public abstract class RenderProfileElement : ProfileElement
private SKRectI _bounds;
private SKPath? _path;
internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile)
{
private SKRectI _bounds;
private SKPath? _path;
internal RenderProfileElement(ProfileElement parent, Profile profile) : base(profile)
{
Timeline = new Timeline();
ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>();
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(LayerEffectsList);
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
_displayCondition = new AlwaysOnCondition(this);
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
}
/// <summary>
/// Gets a boolean indicating whether this render element and its layers/brushes are enabled
/// </summary>
public bool Enabled { get; protected set; }
/// <summary>
/// Gets a boolean indicating whether this render element and its layers/brushes should be enabled
/// </summary>
public abstract bool ShouldBeEnabled { get; }
/// <summary>
/// Creates a list of all layer properties present on this render element
/// </summary>
/// <returns>A list of all layer properties present on this render element</returns>
public abstract List<ILayerProperty> GetAllLayerProperties();
/// <summary>
/// Occurs when a layer effect has been added or removed to this render element
/// </summary>
public event EventHandler? LayerEffectsUpdated;
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
if (DisplayCondition is IDisposable disposable)
disposable.Dispose();
base.Dispose(disposing);
}
internal void LoadRenderElement()
{
Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline)
: new Timeline();
DisplayCondition = RenderElementEntity.DisplayCondition switch
{
AlwaysOnConditionEntity entity => new AlwaysOnCondition(entity, this),
PlayOnceConditionEntity entity => new PlayOnceCondition(entity, this),
StaticConditionEntity entity => new StaticCondition(entity, this),
EventConditionEntity entity => new EventCondition(entity, this),
_ => DisplayCondition
};
ActivateEffects();
}
internal void SaveRenderElement()
{
RenderElementEntity.LayerEffects.Clear();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
baseLayerEffect.Save();
RenderElementEntity.LayerEffects.Add(baseLayerEffect.LayerEffectEntity);
}
// Condition
DisplayCondition?.Save();
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
// Timeline
RenderElementEntity.Timeline = Timeline?.Entity;
Timeline?.Save();
}
internal void LoadNodeScript()
{
if (DisplayCondition is INodeScriptCondition scriptCondition)
scriptCondition.LoadNodeScript();
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.BaseDataBinding.LoadNodeScript();
}
internal void OnLayerEffectsUpdated()
{
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
}
#region Timeline
/// <summary>
/// Gets the timeline associated with this render element
/// </summary>
public Timeline Timeline { get; private set; }
/// <summary>
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display condition
/// </summary>
protected void UpdateTimeline(double deltaTime)
{
DisplayCondition.UpdateTimeline(deltaTime);
}
#endregion
#region Properties
internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary>
/// Gets the parent of this element
/// </summary>
public new ProfileElement Parent
{
get => base.Parent!;
internal set
{
base.Parent = value;
OnPropertyChanged(nameof(Parent));
}
}
/// <summary>
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped.
/// </summary>
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 = SKRectI.Round(value?.Bounds ?? SKRect.Empty);
}
}
/// <summary>
/// The bounds of this entity
/// </summary>
public SKRectI Bounds
{
get => _bounds;
private set => SetAndNotify(ref _bounds, value);
}
#region Property group expansion
internal List<string> ExpandedPropertyGroups;
/// <summary>
/// Determines whether the provided property group is expanded
/// </summary>
/// <param name="layerPropertyGroup">The property group to check</param>
/// <returns>A boolean indicating whether the provided property group is expanded</returns>
public bool IsPropertyGroupExpanded(LayerPropertyGroup layerPropertyGroup)
{
return ExpandedPropertyGroups.Contains(layerPropertyGroup.Path);
}
/// <summary>
/// Expands or collapses the provided property group
/// </summary>
/// <param name="layerPropertyGroup">The group to expand or collapse</param>
/// <param name="expanded">Whether to expand or collapse the property group</param>
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 State
/// <summary>
/// Enables the render element and its brushes and effects
/// </summary>
public abstract void Disable();
/// <summary>
/// Disables the render element and its brushes and effects
/// </summary>
public abstract void Enable();
#endregion
#region Effect management
internal readonly List<BaseLayerEffect> LayerEffectsList;
/// <summary>
/// Gets a read-only collection of the layer effects on this entity
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects { get; }
/// <summary>
/// Adds a the layer effect described inthe provided <paramref name="descriptor" />
/// </summary>
public void AddLayerEffect(LayerEffectDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
LayerEffectEntity entity = new()
{
Id = Guid.NewGuid(),
Suspended = false,
Order = LayerEffects.Count + 1
};
descriptor.CreateInstance(this, entity);
OrderEffects();
OnLayerEffectsUpdated();
}
/// <summary>
/// Removes the provided layer
/// </summary>
/// <param name="effect"></param>
public void RemoveLayerEffect([NotNull] BaseLayerEffect effect)
{
if (effect == null) throw new ArgumentNullException(nameof(effect));
// Remove the effect from the layer and dispose it
LayerEffectsList.Remove(effect);
effect.Dispose();
// Update the order on the remaining effects
OrderEffects();
OnLayerEffectsUpdated();
}
private void OrderEffects()
{
int index = 0;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.OrderBy(e => e.Order))
{
baseLayerEffect.Order = Order = index + 1;
index++;
}
LayerEffectsList.Sort((a, b) => a.Order.CompareTo(b.Order));
}
internal void ActivateEffects()
{
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects)
{
// If there is a non-placeholder existing effect, skip this entity
BaseLayerEffect? existing = LayerEffectsList.FirstOrDefault(e => e.LayerEffectEntity.Id == layerEffectEntity.Id);
if (existing != null && existing.Descriptor.PlaceholderFor == null)
continue;
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
if (descriptor != null)
{
// If a descriptor is found but there is an existing placeholder, remove the placeholder
if (existing != null)
{
LayerEffectsList.Remove(existing);
existing.Dispose();
}
// Create an instance with the descriptor
descriptor.CreateInstance(this, layerEffectEntity);
}
else if (existing == null)
{
// If no descriptor was found and there was no existing placeholder, create a placeholder
descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId);
descriptor.CreateInstance(this, layerEffectEntity);
}
}
OrderEffects();
}
internal void ActivateLayerEffect(BaseLayerEffect layerEffect)
{
LayerEffectsList.Add(layerEffect);
OnLayerEffectsUpdated();
}
private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e)
{
// If effects provided by the plugin are on the element, replace them with placeholders
List<BaseLayerEffect> pluginEffects = LayerEffectsList.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList();
foreach (BaseLayerEffect pluginEffect in pluginEffects)
{
LayerEffectEntity entity = RenderElementEntity.LayerEffects.First(en => en.Id == pluginEffect.LayerEffectEntity.Id);
LayerEffectsList.Remove(pluginEffect);
pluginEffect.Dispose();
LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(pluginEffect.ProviderId);
descriptor.CreateInstance(this, entity);
}
}
private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e)
{
if (RenderElementEntity.LayerEffects.Any(ef => ef.ProviderId == e.Registration.PluginFeature.Id))
ActivateEffects();
}
#endregion
#region Conditions
/// <summary>
/// Gets whether the display conditions applied to this layer where met or not during last update
/// <para>Always true if the layer has no display conditions</para>
/// </summary>
public bool DisplayConditionMet
{
get => _displayConditionMet;
protected set => SetAndNotify(ref _displayConditionMet, value);
}
private bool _displayConditionMet;
/// <summary>
/// Gets or sets the display condition used to determine whether this element is active or not
/// </summary>
public ICondition DisplayCondition
{
get => _displayCondition;
set => SetAndNotify(ref _displayCondition, value);
}
private ICondition _displayCondition;
/// <summary>
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline" />
/// </summary>
public void UpdateDisplayCondition()
{
if (Suspended)
{
DisplayConditionMet = false;
return;
}
DisplayCondition.Update();
DisplayConditionMet = DisplayCondition.IsMet;
}
#endregion
/// <summary>
/// Overrides the main timeline to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
public abstract void OverrideTimelineAndApply(TimeSpan position);
_layerEffects = new List<BaseLayerEffect>();
_displayCondition = new AlwaysOnCondition(this);
Timeline = new Timeline();
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(_layerEffects);
Parent = parent ?? throw new ArgumentNullException(nameof(parent));
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
}
/// <summary>
/// Gets a boolean indicating whether this render element and its layers/brushes are enabled
/// </summary>
public bool Enabled { get; protected set; }
/// <summary>
/// Gets a boolean indicating whether this render element and its layers/brushes should be enabled
/// </summary>
public abstract bool ShouldBeEnabled { get; }
/// <summary>
/// Creates a list of all layer properties present on this render element
/// </summary>
/// <returns>A list of all layer properties present on this render element</returns>
public abstract List<ILayerProperty> GetAllLayerProperties();
/// <summary>
/// Occurs when a layer effect has been added or removed to this render element
/// </summary>
public event EventHandler? LayerEffectsUpdated;
/// <summary>
/// Overrides the main timeline to the specified time and clears any extra time lines
/// </summary>
/// <param name="position">The position to set the timeline to</param>
public abstract void OverrideTimelineAndApply(TimeSpan position);
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
LayerEffectStore.LayerEffectAdded -= LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved -= LayerEffectStoreOnLayerEffectRemoved;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
if (DisplayCondition is IDisposable disposable)
disposable.Dispose();
base.Dispose(disposing);
}
internal void LoadRenderElement()
{
Timeline = RenderElementEntity.Timeline != null
? new Timeline(RenderElementEntity.Timeline)
: new Timeline();
DisplayCondition = RenderElementEntity.DisplayCondition switch
{
AlwaysOnConditionEntity entity => new AlwaysOnCondition(entity, this),
PlayOnceConditionEntity entity => new PlayOnceCondition(entity, this),
StaticConditionEntity entity => new StaticCondition(entity, this),
EventConditionEntity entity => new EventCondition(entity, this),
_ => DisplayCondition
};
LoadLayerEffects();
}
internal void SaveRenderElement()
{
RenderElementEntity.LayerEffects.Clear();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
baseLayerEffect.Save();
RenderElementEntity.LayerEffects.Add(baseLayerEffect.LayerEffectEntity);
}
// Condition
DisplayCondition?.Save();
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
// Timeline
RenderElementEntity.Timeline = Timeline?.Entity;
Timeline?.Save();
}
internal void LoadNodeScript()
{
if (DisplayCondition is INodeScriptCondition scriptCondition)
scriptCondition.LoadNodeScript();
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.BaseDataBinding.LoadNodeScript();
}
private void OnLayerEffectsUpdated()
{
LayerEffectsUpdated?.Invoke(this, EventArgs.Empty);
}
#region Timeline
/// <summary>
/// Gets the timeline associated with this render element
/// </summary>
public Timeline Timeline { get; private set; }
/// <summary>
/// Updates the <see cref="Timeline" /> according to the provided <paramref name="deltaTime" /> and current display
/// condition
/// </summary>
protected void UpdateTimeline(double deltaTime)
{
DisplayCondition.UpdateTimeline(deltaTime);
}
#endregion
#region Properties
internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary>
/// Gets the parent of this element
/// </summary>
public new ProfileElement Parent
{
get => base.Parent!;
internal set
{
base.Parent = value;
OnPropertyChanged(nameof(Parent));
}
}
/// <summary>
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped.
/// </summary>
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 = SKRectI.Round(value?.Bounds ?? SKRect.Empty);
}
}
/// <summary>
/// The bounds of this entity
/// </summary>
public SKRectI Bounds
{
get => _bounds;
private set => SetAndNotify(ref _bounds, value);
}
#endregion
#region State
/// <summary>
/// Enables the render element and its brushes and effects
/// </summary>
public abstract void Disable();
/// <summary>
/// Disables the render element and its brushes and effects
/// </summary>
public abstract void Enable();
#endregion
#region Effect management
private readonly List<BaseLayerEffect> _layerEffects;
/// <summary>
/// Gets a read-only collection of the layer effects on this entity
/// </summary>
public ReadOnlyCollection<BaseLayerEffect> LayerEffects { get; }
/// <summary>
/// Adds a the provided layer effect to the render profile element
/// </summary>
/// <param name="layerEffect">The effect to add.</param>
public void AddLayerEffect(BaseLayerEffect layerEffect)
{
if (layerEffect == null)
throw new ArgumentNullException(nameof(layerEffect));
// Ensure something needs to be done
if (_layerEffects.Contains(layerEffect))
return;
// Make sure the layer effect is tied to this element
layerEffect.ProfileElement = this;
_layerEffects.Add(layerEffect);
// Update the order on the effects
OrderEffects();
OnLayerEffectsUpdated();
}
/// <summary>
/// Removes the provided layer effect.
/// </summary>
/// <param name="layerEffect">The effect to remove.</param>
public void RemoveLayerEffect(BaseLayerEffect layerEffect)
{
if (layerEffect == null)
throw new ArgumentNullException(nameof(layerEffect));
// Ensure something needs to be done
if (!_layerEffects.Contains(layerEffect))
return;
// Remove the effect from the layer
_layerEffects.Remove(layerEffect);
// Update the order on the remaining effects
OrderEffects();
OnLayerEffectsUpdated();
}
private void LoadLayerEffects()
{
foreach (BaseLayerEffect baseLayerEffect in _layerEffects)
baseLayerEffect.Dispose();
_layerEffects.Clear();
foreach (LayerEffectEntity layerEffectEntity in RenderElementEntity.LayerEffects.OrderBy(e => e.Order))
LoadLayerEffect(layerEffectEntity);
}
private void LoadLayerEffect(LayerEffectEntity layerEffectEntity)
{
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(layerEffectEntity.ProviderId, layerEffectEntity.EffectType)?.LayerEffectDescriptor;
BaseLayerEffect layerEffect;
// Create an instance with the descriptor
if (descriptor != null)
{
layerEffect = descriptor.CreateInstance(this, layerEffectEntity);
}
// If no descriptor was found and there was no existing placeholder, create a placeholder
else
{
descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffectEntity.ProviderId);
layerEffect = descriptor.CreateInstance(this, layerEffectEntity);
}
_layerEffects.Add(layerEffect);
}
private void ReplaceLayerEffectWithPlaceholder(BaseLayerEffect layerEffect)
{
int index = _layerEffects.IndexOf(layerEffect);
if (index == -1)
return;
LayerEffectDescriptor descriptor = PlaceholderLayerEffectDescriptor.Create(layerEffect.ProviderId);
BaseLayerEffect placeholder = descriptor.CreateInstance(this, layerEffect.LayerEffectEntity);
_layerEffects[index] = placeholder;
layerEffect.Dispose();
OnLayerEffectsUpdated();
}
private void ReplacePlaceholderWithLayerEffect(PlaceholderLayerEffect placeholder)
{
int index = _layerEffects.IndexOf(placeholder);
if (index == -1)
return;
LayerEffectDescriptor? descriptor = LayerEffectStore.Get(placeholder.OriginalEntity.ProviderId, placeholder.PlaceholderFor)?.LayerEffectDescriptor;
if (descriptor == null)
throw new ArtemisCoreException("Can't replace a placeholder effect because the real effect isn't available.");
BaseLayerEffect layerEffect = descriptor.CreateInstance(this, placeholder.OriginalEntity);
_layerEffects[index] = layerEffect;
placeholder.Dispose();
OnLayerEffectsUpdated();
}
private void OrderEffects()
{
int index = 0;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.OrderBy(e => e.Order))
{
baseLayerEffect.Order = Order = index + 1;
index++;
}
_layerEffects.Sort((a, b) => a.Order.CompareTo(b.Order));
}
private void LayerEffectStoreOnLayerEffectRemoved(object? sender, LayerEffectStoreEvent e)
{
// Find effects that just got disabled and replace them with placeholders
List<BaseLayerEffect> affectedLayerEffects = _layerEffects.Where(ef => ef.ProviderId == e.Registration.PluginFeature.Id).ToList();
if (!affectedLayerEffects.Any())
return;
foreach (BaseLayerEffect baseLayerEffect in affectedLayerEffects)
ReplaceLayerEffectWithPlaceholder(baseLayerEffect);
OnLayerEffectsUpdated();
}
private void LayerEffectStoreOnLayerEffectAdded(object? sender, LayerEffectStoreEvent e)
{
// Find placeholders that just got enabled and replace them with real effects
List<PlaceholderLayerEffect> affectedPlaceholders = LayerEffects
.Where(l => l is PlaceholderLayerEffect ph && ph.OriginalEntity.ProviderId == e.Registration.PluginFeature.Id)
.Cast<PlaceholderLayerEffect>()
.ToList();
if (!affectedPlaceholders.Any())
return;
foreach (PlaceholderLayerEffect placeholderLayerEffect in affectedPlaceholders)
ReplacePlaceholderWithLayerEffect(placeholderLayerEffect);
OnLayerEffectsUpdated();
}
#endregion
#region Conditions
/// <summary>
/// Gets whether the display conditions applied to this layer where met or not during last update
/// <para>Always true if the layer has no display conditions</para>
/// </summary>
public bool DisplayConditionMet
{
get => _displayConditionMet;
private set => SetAndNotify(ref _displayConditionMet, value);
}
private bool _displayConditionMet;
/// <summary>
/// Gets or sets the display condition used to determine whether this element is active or not
/// </summary>
public ICondition DisplayCondition
{
get => _displayCondition;
set => SetAndNotify(ref _displayCondition, value);
}
private ICondition _displayCondition;
/// <summary>
/// Evaluates the display conditions on this element and applies any required changes to the <see cref="Timeline" />
/// </summary>
public void UpdateDisplayCondition()
{
if (Suspended)
{
DisplayConditionMet = false;
return;
}
DisplayCondition.Update();
DisplayConditionMet = DisplayCondition.IsMet;
}
#endregion
}