1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Core - Split up layer conditions into static- and event-conditions

This commit is contained in:
Robert 2021-09-12 22:08:34 +02:00
parent 2a6c004aa6
commit 8ac1431a2f
19 changed files with 491 additions and 156 deletions

View File

@ -44,6 +44,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatabindings_005Cmodifiers_005Cabstract/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdatamodel_005Cvaluechangedevent/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Cdisplay/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Cattributes/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cprofile_005Clayerproperties_005Ctypes/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,98 @@
using System;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
public class EventCondition : CorePropertyChanged, IDisposable, IStorageModel
{
private readonly string _displayName;
private readonly object? _context;
private DateTime _lastProcessedTrigger;
private DataModelPath _eventPath;
internal EventCondition(string displayName, object? context)
{
_displayName = displayName;
_context = context;
Entity = new EventConditionEntity();
Script = new NodeScript<bool>($"Activate {displayName}", $"Whether or not the event should activate the {displayName}", context);
}
internal EventCondition(EventConditionEntity entity, string displayName, object? context)
{
_displayName = displayName;
_context = context;
Entity = entity;
Script = null!;
Load();
}
/// <summary>
/// Gets the script that drives the event condition
/// </summary>
public NodeScript<bool> Script { get; private set; }
/// <summary>
/// Gets or sets the path to the event that drives this event condition
/// </summary>
public DataModelPath EventPath
{
set => SetAndNotify(ref _eventPath, value);
get => _eventPath;
}
internal EventConditionEntity Entity { get; }
internal bool Evaluate()
{
if (EventPath.GetValue() is not DataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
return false;
// TODO: Place dataModelEvent.LastEventArgumentsUntyped; in the start node
Script.Run();
_lastProcessedTrigger = dataModelEvent.LastTrigger;
return Script.Result;
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
Script.Dispose();
EventPath.Dispose();
}
#endregion
internal void LoadNodeScript()
{
Script.Load();
}
#region Implementation of IStorageModel
/// <inheritdoc />
public void Load()
{
EventPath = new DataModelPath(null, Entity.EventPath);
Script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", Entity.Script, _context);
}
/// <inheritdoc />
public void Save()
{
EventPath.Save();
Entity.EventPath = EventPath.Entity;
Script.Save();
Entity.Script = Script.Entity;
}
#endregion
}
}

View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
public class EventsCondition : CorePropertyChanged, INodeScriptCondition
{
private readonly EventsConditionEntity _entity;
private readonly List<EventCondition> _eventsList;
private TimeLineEventOverlapMode _eventOverlapMode;
public EventsCondition(ProfileElement profileElement)
{
_entity = new EventsConditionEntity();
_eventsList = new List<EventCondition>();
ProfileElement = profileElement;
Events = new List<EventCondition>(_eventsList);
}
internal EventsCondition(EventsConditionEntity entity, ProfileElement profileElement)
{
_entity = entity;
_eventsList = new List<EventCondition>();
ProfileElement = profileElement;
Events = new List<EventCondition>(_eventsList);
Load();
}
/// <summary>
/// Gets a list of events this condition reacts to
/// </summary>
public IReadOnlyCollection<EventCondition> Events { get; }
/// <summary>
/// Gets or sets how the condition behaves when events trigger before the timeline finishes
/// </summary>
public TimeLineEventOverlapMode EventOverlapMode
{
get => _eventOverlapMode;
set => SetAndNotify(ref _eventOverlapMode, value);
}
/// <inheritdoc />
public IConditionEntity Entity => _entity;
/// <inheritdoc />
public ProfileElement ProfileElement { get; }
/// <inheritdoc />
public bool IsMet { get; private set; }
/// <summary>
/// Adds a new event condition
/// </summary>
/// <returns>The newly created event condition</returns>
public EventCondition AddEventCondition()
{
EventCondition eventCondition = new(ProfileElement.GetType().Name.ToLower(), ProfileElement.Profile);
lock (_eventsList)
{
_eventsList.Add(eventCondition);
}
return eventCondition;
}
/// <summary>
/// Removes the provided event condition
/// </summary>
/// <param name="eventCondition">The event condition to remove</param>
public void RemoveEventCondition(EventCondition eventCondition)
{
lock (_eventsList)
{
_eventsList.Remove(eventCondition);
}
}
/// <inheritdoc />
public void Update()
{
lock (_eventsList)
{
if (EventOverlapMode == TimeLineEventOverlapMode.Toggle)
{
if (_eventsList.Any(c => c.Evaluate()))
IsMet = !IsMet;
}
else
{
IsMet = _eventsList.Any(c => c.Evaluate());
}
}
}
/// <inheritdoc />
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
{
if (!isMet)
return;
// Event overlap mode doesn't apply in this case
if (timeline.IsFinished)
{
timeline.JumpToStart();
return;
}
// If the timeline was already running, look at the event overlap mode
if (EventOverlapMode == TimeLineEventOverlapMode.Restart)
timeline.JumpToStart();
else if (EventOverlapMode == TimeLineEventOverlapMode.Copy)
timeline.AddExtraTimeline();
else if (EventOverlapMode == TimeLineEventOverlapMode.Toggle && !wasMet)
timeline.JumpToStart();
// The remaining overlap mode is 'ignore' which requires no further action
}
/// <inheritdoc />
public void Dispose()
{
foreach (EventCondition eventCondition in Events)
eventCondition.Dispose();
}
#region Storage
/// <inheritdoc />
public void Load()
{
EventOverlapMode = (TimeLineEventOverlapMode) _entity.EventOverlapMode;
lock (_eventsList)
{
_eventsList.Clear();
foreach (EventConditionEntity eventCondition in _entity.Events)
_eventsList.Add(new EventCondition(eventCondition, ProfileElement.GetType().Name.ToLower(), ProfileElement.Profile));
}
}
/// <inheritdoc />
public void Save()
{
_entity.EventOverlapMode = (int) EventOverlapMode;
_entity.Events.Clear();
lock (_eventsList)
{
foreach (EventCondition eventCondition in _eventsList)
{
eventCondition.Save();
_entity.Events.Add(eventCondition.Entity);
}
}
}
/// <inheritdoc />
public void LoadNodeScript()
{
lock (_eventsList)
{
foreach (EventCondition eventCondition in _eventsList)
eventCondition.LoadNodeScript();
}
}
#endregion
}
}

View File

@ -0,0 +1,51 @@
using System;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Core
{
/// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" />
/// </summary>
public interface ICondition : IDisposable, IStorageModel
{
/// <summary>
/// Gets the entity used to store this condition
/// </summary>
public IConditionEntity Entity { get; }
/// <summary>
/// Gets the profile element this condition applies to
/// </summary>
public ProfileElement ProfileElement { get; }
/// <summary>
/// Gets a boolean indicating whether the condition is currently met
/// </summary>
bool IsMet { get; }
/// <summary>
/// Updates the condition
/// </summary>
void Update();
/// <summary>
/// Applies the display condition to the provided timeline
/// </summary>
/// <param name="isMet"></param>
/// <param name="wasMet"></param>
/// <param name="timeline">The timeline to apply the display condition to</param>
void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline);
}
/// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" /> using a <see cref="INodeScript" />
/// </summary>
public interface INodeScriptCondition : ICondition
{
/// <summary>
/// Loads the node script this node script condition uses
/// </summary>
void LoadNodeScript();
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Linq;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core
{
public class StaticCondition : CorePropertyChanged, INodeScriptCondition
{
private readonly StaticConditionEntity _entity;
public StaticCondition(ProfileElement profileElement)
{
_entity = new StaticConditionEntity();
ProfileElement = profileElement;
string typeDisplayName = profileElement.GetType().Name.ToLower();
Script = new NodeScript<bool>($"Activate {typeDisplayName}", $"Whether or not this {typeDisplayName} should be active", profileElement.Profile);
}
internal StaticCondition(StaticConditionEntity entity, ProfileElement profileElement)
{
_entity = entity;
ProfileElement = profileElement;
Script = null!;
Load();
}
/// <summary>
/// Gets the script that drives the static condition
/// </summary>
public NodeScript<bool> Script { get; private set; }
/// <inheritdoc />
public IConditionEntity Entity => _entity;
/// <inheritdoc />
public ProfileElement ProfileElement { get; }
/// <inheritdoc />
public bool IsMet { get; private set; }
/// <inheritdoc />
public void Update()
{
if (!Script.HasNodes)
{
IsMet = true;
return;
}
Script.Run();
IsMet = Script.Result;
}
/// <inheritdoc />
public void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline)
{
if (isMet && !wasMet && timeline.IsFinished)
timeline.JumpToStart();
else if (!isMet && wasMet && timeline.StopMode == TimelineStopMode.SkipToEnd)
timeline.JumpToEndSegment();
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
Script.Dispose();
}
#endregion
#region Storage
/// <inheritdoc />
public void Load()
{
string typeDisplayName = ProfileElement.GetType().Name.ToLower();
Script = new NodeScript<bool>($"Activate {typeDisplayName}", $"Whether or not this {typeDisplayName} should be active", _entity.Script, ProfileElement.Profile);
}
/// <inheritdoc />
public void Save()
{
Script.Save();
_entity.Script = Script.Entity;
}
/// <inheritdoc />
public void LoadNodeScript()
{
Script.Load();
}
#endregion
}
}

View File

@ -18,17 +18,13 @@ namespace Artemis.Core
{
private SKRectI _bounds;
private SKPath? _path;
private readonly string _typeDisplayName;
internal RenderProfileElement(Profile profile) : base(profile)
{
_typeDisplayName = this is Layer ? "layer" : "folder";
_displayCondition = new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
Timeline = new Timeline();
ExpandedPropertyGroups = new List<string>();
LayerEffectsList = new List<BaseLayerEffect>();
LayerEffects = new(LayerEffectsList);
LayerEffects = new ReadOnlyCollection<BaseLayerEffect>(LayerEffectsList);
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -64,7 +60,8 @@ namespace Artemis.Core
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.Dispose();
DisplayCondition.Dispose();
if (DisplayCondition is IDisposable disposable)
disposable.Dispose();
base.Dispose(disposing);
}
@ -97,11 +94,9 @@ namespace Artemis.Core
layerEffect.BaseProperties?.ApplyToEntity();
}
// Conditions
// Condition
DisplayCondition?.Save();
RenderElementEntity.NodeScript = DisplayCondition?.Entity;
// RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
// DisplayCondition?.Save();
RenderElementEntity.DisplayCondition = DisplayCondition?.Entity;
// Timeline
RenderElementEntity.Timeline = Timeline?.Entity;
@ -110,11 +105,10 @@ namespace Artemis.Core
internal void LoadNodeScript()
{
DisplayCondition = RenderElementEntity.NodeScript != null
? new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", RenderElementEntity.NodeScript, Profile)
: new NodeScript<bool>($"Activate {_typeDisplayName}", $"Whether or not this {_typeDisplayName} should be active", Profile);
if (DisplayCondition is INodeScriptCondition scriptCondition)
scriptCondition.LoadNodeScript();
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
foreach (ILayerProperty layerProperty in GetAllLayerProperties())
layerProperty.BaseDataBinding.LoadNodeScript();
}
@ -367,20 +361,19 @@ namespace Artemis.Core
protected set => SetAndNotify(ref _displayConditionMet, value);
}
private NodeScript<bool> _displayCondition;
private bool _displayConditionMet;
private bool _toggledOnByEvent = false;
/// <summary>
/// Gets or sets the root display condition group
/// Gets the display condition used to determine whether this element is active or not
/// </summary>
public NodeScript<bool> DisplayCondition
public ICondition? DisplayCondition
{
get => _displayCondition;
set => SetAndNotify(ref _displayCondition, value);
private 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>
@ -392,63 +385,30 @@ namespace Artemis.Core
return;
}
if (!DisplayCondition.HasNodes)
if (DisplayCondition == null)
{
DisplayConditionMet = true;
return;
}
if (Timeline.EventOverlapMode != TimeLineEventOverlapMode.Toggle)
_toggledOnByEvent = false;
DisplayCondition.Update();
DisplayCondition.ApplyToTimeline(DisplayCondition.IsMet, DisplayConditionMet, Timeline);
DisplayConditionMet = DisplayCondition.IsMet;
}
DisplayCondition.Run();
/// <summary>
/// Replaces the current <see cref="DisplayCondition" /> with the provided <paramref name="condition" /> or
/// <see langword="null" />
/// </summary>
/// <param name="condition">The condition to change the <see cref="DisplayCondition" /> to</param>
public void ChangeDisplayCondition(ICondition? condition)
{
if (condition == DisplayCondition)
return;
// TODO: Handle this nicely, right now when there's only an exit node we assume true
bool conditionMet = DisplayCondition.Nodes.Count() == 1 || DisplayCondition.Result;
if (Parent is RenderProfileElement parent && !parent.DisplayConditionMet)
conditionMet = false;
// if (!DisplayCondition.ContainsEvents)
{
// Regular conditions reset the timeline whenever their condition is met and was not met before that
if (conditionMet && !DisplayConditionMet && Timeline.IsFinished)
Timeline.JumpToStart();
// If regular conditions are no longer met, jump to the end segment if stop mode requires it
if (!conditionMet && Timeline.StopMode == TimelineStopMode.SkipToEnd)
Timeline.JumpToEndSegment();
}
// else if (conditionMet)
// {
// if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle)
// {
// _toggledOnByEvent = !_toggledOnByEvent;
// if (_toggledOnByEvent)
// Timeline.JumpToStart();
// }
// else
// {
// // Event conditions reset if the timeline finished
// if (Timeline.IsFinished)
// {
// Timeline.JumpToStart();
// }
// // and otherwise apply their overlap mode
// else
// {
// if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Restart)
// Timeline.JumpToStart();
// else if (Timeline.EventOverlapMode == TimeLineEventOverlapMode.Copy)
// Timeline.AddExtraTimeline();
// // The third option is ignore which is handled below:
//
// // done
// }
// }
// }
DisplayConditionMet = Timeline.EventOverlapMode == TimeLineEventOverlapMode.Toggle
? _toggledOnByEvent
: conditionMet;
ICondition? old = DisplayCondition;
DisplayCondition = condition;
old?.Dispose();
}
#endregion

View File

@ -14,7 +14,7 @@ namespace Artemis.Storage.Entities.Profile.Abstract
public List<PropertyEntity> PropertyEntities { get; set; }
public List<string> ExpandedPropertyGroups { get; set; }
public DataModelConditionGroupEntity DisplayCondition { get; set; }
public IConditionEntity DisplayCondition { get; set; }
public TimelineEntity Timeline { get; set; }
public NodeScriptEntity NodeScript { get; set; }

View File

@ -8,6 +8,4 @@
public int Skip { get; set; }
public int Amount { get; set; }
}
}

View File

@ -1,15 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionEventEntity : DataModelConditionPartEntity
{
public DataModelConditionEventEntity()
{
Children = new List<DataModelConditionPartEntity>();
}
public DataModelPathEntity EventPath { get; set; }
}
}

View File

@ -1,6 +0,0 @@
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionEventPredicateEntity : DataModelConditionPredicateEntity
{
}
}

View File

@ -1,6 +0,0 @@
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionGeneralPredicateEntity : DataModelConditionPredicateEntity
{
}
}

View File

@ -1,15 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionGroupEntity : DataModelConditionPartEntity
{
public DataModelConditionGroupEntity()
{
Children = new List<DataModelConditionPartEntity>();
}
public int BooleanOperator { get; set; }
}
}

View File

@ -1,16 +0,0 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionListEntity : DataModelConditionPartEntity
{
public DataModelConditionListEntity()
{
Children = new List<DataModelConditionPartEntity>();
}
public DataModelPathEntity ListPath { get; set; }
public int ListOperator { get; set; }
}
}

View File

@ -1,6 +0,0 @@
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class DataModelConditionListPredicateEntity : DataModelConditionPredicateEntity
{
}
}

View File

@ -1,18 +0,0 @@
using System;
using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public abstract class DataModelConditionPredicateEntity : DataModelConditionPartEntity
{
public int PredicateType { get; set; }
public DataModelPathEntity LeftPath { get; set; }
public DataModelPathEntity RightPath { get; set; }
public string OperatorType { get; set; }
public Guid? OperatorPluginGuid { get; set; }
// Stored as a string to be able to control serialization and deserialization ourselves
public string RightStaticValue { get; set; }
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class EventsConditionEntity : IConditionEntity
{
public int EventOverlapMode { get; set; }
public List<EventConditionEntity> Events { get; set; } = new();
}
public class EventConditionEntity
{
public DataModelPathEntity EventPath { get; set; }
public NodeScriptEntity Script { get; set; }
}
}

View File

@ -0,0 +1,6 @@
namespace Artemis.Storage.Entities.Profile.Abstract
{
public interface IConditionEntity
{
}
}

View File

@ -0,0 +1,10 @@
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Storage.Entities.Profile.Conditions
{
public class StaticConditionEntity : IConditionEntity
{
public NodeScriptEntity Script { get; set; }
}
}

View File

@ -121,7 +121,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
if (RenderProfileElement == null)
return;
_windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition));
// _windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(RenderProfileElement.DisplayCondition));
_profileEditorService.SaveSelectedProfileElement();
}